{"id":47185551,"url":"https://github.com/davorg/perlschool-util","last_synced_at":"2026-03-13T09:08:20.642Z","repository":{"id":328626960,"uuid":"1115265844","full_name":"davorg/perlschool-util","owner":"davorg","description":null,"archived":false,"fork":false,"pushed_at":"2026-02-23T19:18:34.000Z","size":98,"stargazers_count":3,"open_issues_count":5,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2026-02-23T22:47:13.577Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Perl","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/davorg.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2025-12-12T15:33:39.000Z","updated_at":"2025-12-17T23:13:57.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/davorg/perlschool-util","commit_stats":null,"previous_names":["davorg/perlschool-util"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/davorg/perlschool-util","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/davorg%2Fperlschool-util","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/davorg%2Fperlschool-util/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/davorg%2Fperlschool-util/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/davorg%2Fperlschool-util/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/davorg","download_url":"https://codeload.github.com/davorg/perlschool-util/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/davorg%2Fperlschool-util/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":30463663,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-03-13T06:34:02.089Z","status":"ssl_error","status_checked_at":"2026-03-13T06:33:49.182Z","response_time":60,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.5:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2026-03-13T09:08:20.030Z","updated_at":"2026-03-13T09:08:20.633Z","avatar_url":"https://github.com/davorg.png","language":"Perl","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Perl School utilities\n\nThis repo contains\n\n* a number of useful utility programs for producing Perl School books\n* a Dockerfile that produces a Docker image with all of these utilities installed\n* a GitHub Actions workflow that uses these utilities to regenerate the book\n\nThere are more details on these components below.\n\n## About Perl School\n\nThe Perl School brand has its roots in a series of low-cost Perl training\ncourses that [Dave Cross](https://davecross.co.uk/) ran in 2012. By running\nlow-cost training at the weekend, he hoped to encourage more programmers to\nkeep their Perl knowledge up to date. These courses were run regularly for\nabout a year before the idea was put on hold for a while.\n\nDave always knew that he would want to return to the Perl School brand at some\npoint and late in 2017 he realised what the obvious next step was - low-cost\nPerl books. He had already developed a pipeline for creating e-books from\nMarkdown files so it was a short step to republishing some of his training\nmaterials as books.\n\nThe first Perl School book,\n[*Perl Taster*](https://perlschool.com/books/perl-taster/) was published at\nthe end of 2017 (just in time for the London Perl Workshop). The second was\n[*Selenium and Perl*](https://perlschool.com/books/selenium-perl/) and\n[several more](https://perlschool.com/books/) have followed since then. This\nbook introduces a new author to the Perl School stable.\n\nYou can read more about Perl School at\n[perlschool.com](https://perlschool.com/).\n\n## Utility programs\n\nThis repo currently provides two primary utilities, both written in modern\nPerl and intended to be run either locally (with the right toolchain\ninstalled) or via the Docker image described below.\n\n### `bin/check_ms_html`\n\n[`check_ms_html`](bin/check_ms_html) is a pre-flight linter for manuscripts\nwritten in Markdown/Markua with embedded HTML. Its job is to catch HTML that\nwill break when converted to EPUB (which uses XHTML under the hood).\n\nIn broad terms it:\n\n* Reads one or more manuscript files (typically the main book manuscript).\n* Ignores fenced code blocks (` ``` `) so example code containing `\u003c` / `\u003e`\n  is not treated as HTML.\n* For the remaining text, extracts any raw HTML fragments and wraps them in a\n  temporary XML root element.\n* Uses `XML::LibXML` to check that these fragments are **well-formed XML**\n  (e.g. `\u003cbr /\u003e` instead of `\u003cbr\u003e`, balanced tags, valid attribute syntax).\n* Reports any problems with file name and line number, and exits non-zero if\n  it finds invalid fragments.\n\nThe intent is that you run `check_ms_html` before `make_book` in order to\ncatch issues like unclosed `\u003cbr\u003e` tags before they reach `pandoc` /\n`epubcheck`.\n\n### `bin/make_book`\n\n[`make_book`](bin/make_book) is the main build orchestrator for a Perl School\nbook. Given a book repo containing a `book-metadata.yml` file, it will:\n\n* Read `book-metadata.yml` (using `YAML::XS`) and extract at least:\n\n  * `title`\n  * `manuscript` (path to the main Markdown file)\n  * `cover_image` (path to the cover image, usually under `images/`)\n* Generate HTML front matter using Template Toolkit:\n\n  * Cover page (full-page cover image)\n  * Half-title page\n  * Title page (title, subtitle, author)\n  * Copyright page (using standard Perl School wording and metadata such as\n    year, holder, publisher and ISBN).\n* Convert the manuscript to HTML using `pandoc`, asking it to generate a\n  Table of Contents but **not** to add its own title page/front matter.\n* Stitch the front matter HTML and the `pandoc`-generated body HTML together\n  into a single `book.html`, with the appropriate CSS (`book-shared.css` and\n  `book-pdf.css`) wired in.\n* Call `wkhtmltopdf` on `book.html` to generate a print-ready A4 PDF,\n  writing it to the `built/` directory.\n* Build an EPUB version via `pandoc`, combining a Markdown copyright page\n  with the manuscript, and using `book-metadata.yml` for metadata and the\n  cover image.\n\n`make_book` creates a temporary `build/` directory for intermediate files and\nwrites final artefacts (`.pdf` and `.epub`) under `built/`. By default it\nremoves `build/` at the end of a successful run; you can pass `--keep-build`\nwhen debugging.\n\nBoth utilities assume that external tools (`pandoc`, `wkhtmltopdf`, Java,\n`epubcheck`) are available on `PATH`. When run inside the Docker image, these\nare all preinstalled.\n\n## Docker image\n\nThe repository includes a `Dockerfile` and helper script for building a\nDocker image that contains all required tools (Perl, CPAN modules, pandoc,\nwkhtmltopdf, Java, epubcheck) and this repo itself. The image is intended to\nbe used from individual book repos, mounting a book directory at `/work` and\nrunning `make_book`, `check_ms_html` and `epubcheck` inside the container.\n\nFull details on building, tagging and using the image are in\n[`DOCKER.md`](DOCKER.md).\n\n## GitHub Actions workflow\n\nThe expected deployment model is that **each book repo** (e.g. the repo for a\nparticular Perl School title) includes a GitHub Actions workflow that uses the\n`davorg/perlschool-util` image to lint the manuscript, build the book and run\n`epubcheck` on the generated EPUB.\n\nA typical workflow:\n\n1. Runs on a standard GitHub-hosted runner (`ubuntu-latest`).\n2. Uses the `container:` option to run all steps inside a specific\n   `davorg/perlschool-util` image tag (e.g. `1.0.0`).\n3. Checks out the book repo.\n4. Runs a **pre-flight** Perl script that:\n\n   * Verifies that `book-metadata.yml` exists.\n   * Parses it with `YAML::XS`.\n   * Checks that `title`, `manuscript` and `cover_image` are present and\n     non-empty.\n   * Verifies that the manuscript file and cover image actually exist.\n5. Runs `check_ms_html` against the manuscript to catch invalid HTML/XHTML\n   before conversion.\n6. Runs `make_book` to generate the PDF and EPUB into the repo’s `built/`\n   directory.\n7. Runs `epubcheck` against the generated EPUB(s) to ensure the file\n   validates cleanly.\n8. Uploads the contents of `built/` as workflow artefacts so they can be\n   downloaded from the Actions UI.\n\nA minimal example workflow file for a book repo lives in\n[`github-actions-example.yml`](github-actions-example.yml).\n\nIndividual book repos are free to adapt this example (for example, to use\nspecific image tags, only run on certain branches, or to trigger on tags\ninstead of every push), but the general pattern should remain the same.\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdavorg%2Fperlschool-util","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fdavorg%2Fperlschool-util","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdavorg%2Fperlschool-util/lists"}