diff --git a/.github/workflows/convert.yml b/.github/workflows/convert.yml index 0223ea5..3cb4be9 100644 --- a/.github/workflows/convert.yml +++ b/.github/workflows/convert.yml @@ -1,85 +1,18 @@ +# このリポジトリの sample.qd で action.yml をそのまま試す name: qd to pdf on: push: - branches: - - master + branches: [master] -env: - QUARKDOWN_MAJOR: 2 +permissions: + contents: write + +concurrency: pdf jobs: - convert: + pdf: runs-on: ubuntu-latest steps: - uses: actions/checkout@v7 - - name: git config and fetch branch - run: | - git config --local user.email "41898282+github-actions[bot]@users.noreply.github.com" - git config --local user.name "GitHub Action" - git config --global core.quotepath false - git fetch --no-tags --prune --depth=1 - - name: check branch exists and init and remove file and check modify - id: check - continue-on-error: true - run: | - (git checkout pdf && git fetch --unshallow origin pdf) || \ - (git checkout --orphan pdf && \ - git rm -rf . && echo 4b825dc642cb6eb9a060e54bf8d69288fbee4904 > convertdone.txt && \ - git add . && git commit -m "first commit") - set -o pipefail - git diff --name-only --diff-filter=D `git show pdf:convertdone.txt` master | grep -E '\.qd$' | grep -v '\(^\|/\)_' | sed 's/\.[^.]*$//' | { while read -r f; do rm -f "$f.pdf"; done; } && \ - git add . && git commit -m "remove" - git diff --name-only --diff-filter=MAR `git show pdf:convertdone.txt` master | grep -E '\.qd$' | grep -v '\(^\|/\)_' >> modified.txt && \ - echo "status=success" >> $GITHUB_OUTPUT - - name: resolve quarkdown version - id: quarkdown - if: steps.check.outputs.status == 'success' - env: - GH_TOKEN: ${{ github.token }} - run: | - tag=$(gh api "repos/iamgio/quarkdown/releases?per_page=100" \ - --jq "map(select(.draft == false and .prerelease == false and (.tag_name | startswith(\"v$QUARKDOWN_MAJOR.\")))) | .[0].tag_name") - if [ -z "$tag" ] || [ "$tag" = "null" ]; then - echo "quarkdown v$QUARKDOWN_MAJOR.x のリリースが見つかりません" >&2 - exit 1 - fi - echo "version=${tag#v}" >> $GITHUB_OUTPUT - - name: cache quarkdown - id: quarkdown-cache - if: steps.check.outputs.status == 'success' - uses: actions/cache@v6 - with: - # 本体(JRE同梱)・puppeteer・Chromeが全てこの下に入る - path: ${{ runner.tool_cache }}/quarkdown - key: quarkdown-${{ runner.os }}-${{ steps.quarkdown.outputs.version }} - - name: install quarkdown - if: steps.check.outputs.status == 'success' && steps.quarkdown-cache.outputs.cache-hit != 'true' - uses: quarkdown-labs/setup-quarkdown@v1 - with: - quarkdown-version: ${{ steps.quarkdown.outputs.version }} - - name: use cached quarkdown - if: steps.check.outputs.status == 'success' && steps.quarkdown-cache.outputs.cache-hit == 'true' - run: | - # setup-quarkdownが作る/usr/local/binのラッパーはキャッシュ対象外なので同じ環境変数を張り直す - echo "$RUNNER_TOOL_CACHE/quarkdown/bin" >> $GITHUB_PATH - echo "QD_NPM_PREFIX=$RUNNER_TOOL_CACHE/quarkdown/lib" >> $GITHUB_ENV - echo "PUPPETEER_CACHE_DIR=$RUNNER_TOOL_CACHE/quarkdown/lib/puppeteer_cache" >> $GITHUB_ENV - - name: convert - if: steps.check.outputs.status == 'success' - run: | - git checkout master - git checkout -b tmp - # 元のフォルダ構造のままpdfを作る。--pdf-no-sandboxはubuntuランナーでは - # AppArmorによりChromeのsandboxが使えないため - while read -r f; do - quarkdown c "$f" --pdf --strict --allow global-read --pdf-no-sandbox \ - --out "$(dirname "$f")" --out-name "$(basename "${f%.*}")" - done < modified.txt - git add . && git commit -m "tmp" - git checkout pdf - git show tmp:modified.txt | sed 's/\.[^.]*$//' | xargs -i git checkout tmp {}.pdf - echo $GITHUB_SHA > convertdone.txt - git add . && git commit -m "convert" - - name: push - run: git push origin HEAD + - uses: ./ diff --git a/README.md b/README.md index 1502d1a..ce7fe8b 100644 --- a/README.md +++ b/README.md @@ -1,82 +1,48 @@ -# paper +# seisho / 清書 -[Quarkdown](https://github.com/iamgio/quarkdown)でqdファイルをpdfに変換します。 +清書。リポジトリの `.qd` を [Quarkdown](https://github.com/iamgio/quarkdown) で PDF にし、`pdf` ブランチへ置く GitHub Action。 -## 仕様 +## 使い方 -- 初回実行時はpdfブランチが自動で作成されます。(qdファイルが一つもなかった場合エラーで終了しブランチは作成されません。) -- theme等を変更し全てビルドしなおしたい時はpdfブランチ削除してmasterを変更すれば全てビルドしなおします。 -- 初回実行時qdファイルは再帰的に検索され全て変換されます。 -- 元々のファイルと同一のフォルダ構造で配置します。 -- pdfブランチにはpdfファイルのみを生成します。 -- masterからqdファイルを削除するとpdfからも自動で削除されます。 -- 変更があったファイルのみをビルドするようにしています。 -- リネームおよび移動した場合は旧ファイル名のpdfは残ります。 -- `_`で始まるファイル(`_setup.qd`など)は単体では変換されません。`.include`で読み込む共通設定や分割用のファイルはこの名前にしてください。 -- テーマ・用紙・フォントなどの共通設定は[_setup.qd](_setup.qd)にまとめてあります。文書の先頭で`.include {_setup.qd}`と書いて読み込んでください。([including other files](https://quarkdown.com/wiki/including-other-quarkdown-files/)) - - `_setup.qd`からの相対パスではなく、その文書から見た相対パスです。サブフォルダに置いた文書なら`.include {../_setup.qd}`になります。 - - `.include`の後に同じ関数を呼べばその文書だけ設定を上書きできます。 -- 変換対象は`.qd`のみです。mdから移行する場合は[md-to-qd.sh](md-to-qd.sh)を参照してください。 -- `--strict`を付けているので、未定義の関数呼び出しなど変換時のエラーがあればビルドを停止します。 -- ビルドは公式Action[`quarkdown-labs/setup-quarkdown`](https://github.com/quarkdown-labs/setup-quarkdown)でQuarkdown本体(JRE・PuppeteerとChromeを含む)を入れて実行します。バージョンは2系の最新リリースを自動で選びます。 - - インストール先(`$RUNNER_TOOL_CACHE/quarkdown`)を`actions/cache`でキャッシュしているので、バージョンが変わらない限り2回目以降のセットアップはダウンロード無しで済みます。 - - GitHub Actionsのubuntuランナーではpuppeteerが用意するChromeのsandboxがAppArmorで弾かれるため、CIでは`--pdf-no-sandbox`を付けています。手元で変換する場合は不要です。 -- PDFはHTMLをヘッドレスChromeで描画したものなのでLaTeXは不要です。 -- 文書の設定はファイル先頭の関数呼び出しで書きます。主な記法は[sample.qd](sample.qd)に一通り並べてあるので、そちらと[wiki](https://quarkdown.com/wiki/)を参照。 - - 種別: `.doctype {paged}` ([document types](https://quarkdown.com/wiki/document-types/)) - - 用紙: `.pageformat size:{A4} margin:{2cm}` ([page format](https://quarkdown.com/wiki/page-format/)) - - テーマ: `.theme {paperwhite} layout:{latex}` ([themes](https://quarkdown.com/wiki/themes/)) - - フォント: `.font {GoogleFonts:BIZ UDPMincho}` ([font configuration](https://quarkdown.com/wiki/font-configuration/)) -- 日本語フォントはQuarkdownに同梱されていないため、Google Fontsかリポジトリ内のフォントファイルを指定します。既定では`_setup.qd`で本文にBIZ UDPMincho、コードにM PLUS 1 Codeを指定しています。 - - コード用のフォントを別に指定しないと、`インラインコード`やコードブロック内の日本語が豆腐(□)になります。テーマ既定の等幅フォントに日本語が無いためです。 - - Mermaidの図は本文と別のフォントで描画されるため、図の中の日本語は表示されません。ラベルは英数字で書いてください。(図のキャプションは本文扱いなので日本語で書けます) -- その他機能追加,質問はissueでお願いします。 +```yaml +# .github/workflows/pdf.yml +on: + push: + branches: [master] -## プレビュー +permissions: + contents: write -Quarkdown本体のライブプレビューを使います。JRE同梱なのでJavaは不要です。(Node.jsが無ければ自動で入ります) +# 続けて push したときに pdf ブランチへの push がぶつからないように +concurrency: pdf -```sh -curl -fsSL https://raw.githubusercontent.com/quarkdown-labs/get-quarkdown/refs/heads/main/install.sh | sudo env "PATH=$PATH" bash +jobs: + pdf: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v7 + - uses: 5ym/seisho@v1 ``` -WSLで使う場合はWindows側ではなくWSL内にインストールします。他のインストール方法(Homebrew, Scoop等)は[本家のREADME](https://github.com/iamgio/quarkdown#getting-started)を参照。 - -```sh -quarkdown c sample.qd -w -p --allow global-read -``` +| 入力 | 既定 | 内容 | +| --- | --- | --- | +| `source-branch` | push されたブランチ | `.qd` を読むブランチ | +| `pdf-branch` | `pdf` | PDF を置くブランチ。無ければ作る | +| `quarkdown-major` | `2` | この系列の最新リリースを使う | -`-w`が保存のたびに再コンパイル、`-p`がwebserver(既定で`localhost:8089`)を立ててブラウザを開き自動リロードします。`.doctype {paged}`はpaged.jsを使うのでwebserver越しでないと正しく表示されません。([CLI options](https://quarkdown.com/wiki/cli-options/)) +動きの細かいところ (変換する範囲・消したときの扱い・作り直し方など) は [docs/spec.md](docs/spec.md) にまとめています。 -- `--allow global-read`はリポジトリ外・親ディレクトリのファイル(サブフォルダ文書からの`../_setup.qd`など)を読むために付けています。 -- ブラウザを開かずポートだけ使いたい場合は`-b none`、ポート変更は`--server-port`です。 -- PDFを手元で出したい場合は`quarkdown c sample.qd --pdf --strict --allow global-read`です。出力先は`quarkdown-output/`(`.gitignore`済み)なので、リポジトリを汚さずに確認できます。 +## 書き方 -VS Codeの公式拡張[Quarkdown](https://marketplace.visualstudio.com/items?itemName=quarkdown.quarkdown-vscode)(`Ctrl+Shift+V`でプレビュー、`Ctrl+Alt+P`でPDF出力)も使えます。設定に以下を入れると`.include`や画像の読み込みで権限エラーになりません。 +共通設定を `_setup.qd` に置き、各文書の先頭で読み込みます (パスはその文書から見た相対パス)。例は [_setup.qd](_setup.qd)、記法は [sample.qd](sample.qd) と [wiki](https://quarkdown.com/wiki/) を参照。 -```json -{ - "quarkdown.additionalCompilerOptions": "--allow global-read" -} +```text +.include {_setup.qd} ``` -`Quarkdown not found. Please install Quarkdown first.`と出る場合はインストールが済んでいないかPATHが通っていません。設定`quarkdown.path`に実行ファイルのパス(既定は`quarkdown`、install.shなら`/usr/local/bin/quarkdown`)を指定してください。VS CodeはWSLに接続した状態で使います。 - -## 使い方 - -`Use this template`をクリックして新規リポジトリを作成してそこに`.qd`ファイルを追加していきます。 - -## mdからの移行 - -既にmdで書いている場合は[md-to-qd.sh](md-to-qd.sh)を一度実行すると全てqdに移行できます。(引数にファイルを渡すとそれだけ変換します) - -```sh -./md-to-qd.sh -``` +- 日本語フォントは同梱されていないので `.font` で指定します。コード用も指定しないと、コード中の日本語が豆腐になります。 +- Mermaid の図の中は日本語が出ません。ラベルは英数字で書いてください。 -- 先頭のYAMLフロントマターを取り除きます。(そのままだと`---`が区切り線、`title:`行が見出しとして本文に出てしまいます) -- その文書から見た相対パスで`.include {_setup.qd}`を先頭に足します。既に書いてある場合は足しません。 -- `_`で始まるファイルは分割用とみなして`.include`を足しません。 -- 元のmdは削除するので、結果を確認してからコミットしてください。`README.md`は対象外です。 +## 手元で確認 -Quarkdown自体はMarkdownの上位互換なので、本文の書き方はほとんどそのままで通ります。ただし数式はQuarkdownの記法(`$ x $`のように前後に空白)のみ対応で、`\begin{equation}`やスペース無しの`$x$`はそのまま文字として出ます。 +[インストール](https://github.com/iamgio/quarkdown#getting-started)して `quarkdown c 文書.qd -w -p --allow global-read` でライブプレビュー、`--pdf` で PDF です。`--allow global-read` は `../_setup.qd` のような親ディレクトリのファイルを読むために要ります。VS Code なら公式拡張 [Quarkdown](https://marketplace.visualstudio.com/items?itemName=quarkdown.quarkdown-vscode) に `"quarkdown.additionalCompilerOptions": "--allow global-read"` を設定します。 diff --git a/action.yml b/action.yml new file mode 100644 index 0000000..ff22cd4 --- /dev/null +++ b/action.yml @@ -0,0 +1,153 @@ +name: seisho (Quarkdown to PDF branch) +description: 清書。リポジトリ内の .qd を Quarkdown で PDF にし、元のフォルダ構造のまま pdf ブランチへ置く (変更があったものだけ) +author: 5ym +branding: + icon: file-text + color: gray-dark + +inputs: + source-branch: + description: .qd を読むブランチ。省略時は push されたブランチ + required: false + default: "" + pdf-branch: + description: PDF を置くブランチ。無ければ作る + required: false + default: pdf + quarkdown-major: + description: 使う Quarkdown のメジャーバージョン。その系列の最新リリースを使う + required: false + default: "2" + token: + description: Quarkdown のリリースを調べるための GitHub のトークン + required: false + default: ${{ github.token }} + +runs: + using: composite + steps: + - name: git config and fetch branch + shell: bash + run: | + git config --local user.email "41898282+github-actions[bot]@users.noreply.github.com" + git config --local user.name "GitHub Action" + git config --global core.quotepath false + git fetch --no-tags --prune --depth=1 + - name: init pdf branch, remove deleted, list modified + id: check + shell: bash + env: + SRC: ${{ inputs.source-branch || github.ref_name }} + PDF: ${{ inputs.pdf-branch }} + run: | + # source-branch にチェックアウトしていないブランチを指定されたときは origin から作る + if ! git rev-parse --verify -q "refs/heads/$SRC" > /dev/null; then + git branch "$SRC" "origin/$SRC" 2> /dev/null || { + echo "ブランチ $SRC が見つかりません。push か workflow_dispatch で呼ぶか、source-branch を指定してください" >&2 + exit 1 + } + fi + if git ls-remote --exit-code --heads origin "$PDF" > /dev/null; then + git checkout "$PDF" + git fetch --unshallow origin "$PDF" || true + else + # 空のツリーの SHA を起点にすると、初回は全ての .qd が変換対象になる + git checkout --orphan "$PDF" + git rm -rf --quiet . + echo 4b825dc642cb6eb9a060e54bf8d69288fbee4904 > convertdone.txt + git add . && git commit --quiet -m "first commit" + fi + base=$(git show "$PDF:convertdone.txt") + # _ で始まるファイル(_setup.qd など)は単体では変換しない + list() { git diff --name-only --diff-filter="$1" "$base" "$SRC" | grep -E '\.qd$' | grep -v '\(^\|/\)_' || true; } + deleted=$(list D) + if [ -n "$deleted" ]; then + printf '%s\n' "$deleted" | while read -r f; do rm -f "${f%.*}.pdf"; done + git add -A && git commit --quiet -m "remove" || true + fi + list MAR > modified.txt + if [ -s modified.txt ]; then + echo "status=success" >> "$GITHUB_OUTPUT" + else + rm -f modified.txt + fi + echo "src=$SRC" >> "$GITHUB_OUTPUT" + - name: resolve quarkdown version + id: quarkdown + if: steps.check.outputs.status == 'success' + shell: bash + env: + GH_TOKEN: ${{ inputs.token }} + MAJOR: ${{ inputs.quarkdown-major }} + run: | + tag=$(gh api "repos/iamgio/quarkdown/releases?per_page=100" \ + --jq "map(select(.draft == false and .prerelease == false and (.tag_name | startswith(\"v$MAJOR.\")))) | .[0].tag_name") + if [ -z "$tag" ] || [ "$tag" = "null" ]; then + echo "quarkdown v$MAJOR.x のリリースが見つかりません" >&2 + exit 1 + fi + echo "version=${tag#v}" >> "$GITHUB_OUTPUT" + - name: cache quarkdown + id: quarkdown-cache + if: steps.check.outputs.status == 'success' + uses: actions/cache@v6 + with: + # 本体(JRE同梱)・puppeteer・Chromeが全てこの下に入る + path: ${{ runner.tool_cache }}/quarkdown + key: quarkdown-${{ runner.os }}-${{ steps.quarkdown.outputs.version }} + - name: install quarkdown + if: steps.check.outputs.status == 'success' && steps.quarkdown-cache.outputs.cache-hit != 'true' + uses: quarkdown-labs/setup-quarkdown@v1 + with: + quarkdown-version: ${{ steps.quarkdown.outputs.version }} + - name: use cached quarkdown + if: steps.check.outputs.status == 'success' && steps.quarkdown-cache.outputs.cache-hit == 'true' + shell: bash + run: | + # setup-quarkdownが作る/usr/local/binのラッパーはキャッシュ対象外なので、 + # ラッパーが設定する環境変数を張り直す。2.6以降はchrome-headless-shellを + # インストール先のlibに置き、その場所をQD_CHROME_PATHで渡す + dir="$RUNNER_TOOL_CACHE/quarkdown" + echo "$dir/bin" >> "$GITHUB_PATH" + chrome=$(find "$dir/lib" -type f -name chrome-headless-shell -perm -u+x 2>/dev/null | head -n 1) + if [ -n "$chrome" ]; then + echo "QD_CHROME_PATH=$chrome" >> "$GITHUB_ENV" + fi + # 2.5以前(puppeteerでChromeを入れていた版)向け + echo "QD_NPM_PREFIX=$dir/lib" >> "$GITHUB_ENV" + echo "PUPPETEER_CACHE_DIR=$dir/lib/puppeteer_cache" >> "$GITHUB_ENV" + - name: convert + if: steps.check.outputs.status == 'success' + shell: bash + env: + SRC: ${{ steps.check.outputs.src }} + PDF: ${{ inputs.pdf-branch }} + run: | + git checkout "$SRC" + git checkout -b quarkdown-pdf-tmp + # 元のフォルダ構造のまま、.qd と同じ名前の pdf を作る。 + # Quarkdown は出力名の記号(全角のコロンや空白など)を - に置き換えるので、 + # 一時ディレクトリに固定の名前で出してから元の名前へ移す。 + # --pdf-no-sandbox は ubuntu ランナーでは AppArmor により Chrome の sandbox が使えないため + while read -r f; do + out=$(mktemp -d) + quarkdown c "$f" --pdf --strict --allow global-read --pdf-no-sandbox \ + --out "$out" --out-name document + mv "$out/document.pdf" "${f%.*}.pdf" + rm -rf "$out" + done < modified.txt + git add . && git commit --quiet -m "tmp" + git checkout "$PDF" + git show quarkdown-pdf-tmp:modified.txt | sed 's/\.[^.]*$//' | while read -r f; do + git checkout quarkdown-pdf-tmp -- "$f.pdf" + done + git rev-parse "$SRC" > convertdone.txt + git add . && git commit --quiet -m "convert" + - name: push + shell: bash + env: + PDF: ${{ inputs.pdf-branch }} + run: | + if [ "$(git branch --show-current)" = "$PDF" ]; then + git push origin "HEAD:$PDF" + fi diff --git a/docs/spec.md b/docs/spec.md new file mode 100644 index 0000000..92c6884 --- /dev/null +++ b/docs/spec.md @@ -0,0 +1,8 @@ +# 仕様 + +- `push` と `workflow_dispatch` で使えます (`pull_request` には対応していません)。 +- `.qd` と同じフォルダ構造・同じ名前で `pdf` ブランチに PDF を置きます。変更があったものだけ変換し、消した `.qd` の PDF は消します。リネームした場合は旧名の PDF が残ります。 +- `_` で始まるファイル (`_setup.qd` など) は単体では変換しません。共通設定や分割用に使います。 +- 全部作り直したいときは `pdf` ブランチを消してから push します。 +- `--strict` で変換するので、未定義の関数などがあれば失敗します。 +- Quarkdown は `quarkdown-major` の系列の最新リリースを [setup-quarkdown](https://github.com/quarkdown-labs/setup-quarkdown) で入れ、版ごとに `actions/cache` でキャッシュします。PDF は HTML をヘッドレス Chrome で描くので LaTeX は要りません。 diff --git a/md-to-qd.sh b/md-to-qd.sh deleted file mode 100755 index 108d4cd..0000000 --- a/md-to-qd.sh +++ /dev/null @@ -1,70 +0,0 @@ -#!/bin/sh -# 既存のmdをqdに変換する移行用スクリプト(一度だけ実行する想定) -# - 先頭のYAMLフロントマターを取り除く -# - 共通設定の読み込み(.include)を先頭に足す -# - 元のmdは削除する -# 使い方: ./md-to-qd.sh [ファイル...] (省略時はリポジトリ内の全md) -set -e -cd "$(dirname "$0")" - -sources() { - if [ $# -gt 0 ]; then - printf '%s\n' "$@" - else - find . -path ./.git -prune -o -type f -name '*.md' -print | - sed 's|^\./||' | grep -v '\(^\|/\)README\.md$' - fi -} - -# その文書から見た_setup.qdへの相対パス -setup_path() { - d=$(dirname "$1") - p="" - while [ "$d" != "." ]; do - p="../$p" - d=$(dirname "$d") - done - echo "${p}_setup.qd" -} - -list=$(mktemp) -body=$(mktemp) -trap 'rm -f "$list" "$body"' EXIT -sources "$@" > "$list" - -count=0 -while read -r src; do - case $src in - *.md) ;; - *) echo "スキップ(mdではありません): $src" >&2; continue ;; - esac - if [ ! -f "$src" ]; then - echo "スキップ(見つかりません): $src" >&2 - continue - fi - dest=${src%.md}.qd - if [ -e "$dest" ]; then - echo "スキップ(既にあります): $dest" >&2 - continue - fi - - awk 'NR == 1 && $0 == "---" { fm = 1; next } fm && ($0 == "---" || $0 == "...") { fm = 0; next } !fm' "$src" | - sed '/./,$!d' > "$body" - { - # _で始まるファイルは分割用なので共通設定は読み込ませない - case $(basename "$src") in - _*) ;; - *) grep -q '^\.include *{[^}]*_setup\.qd}' "$body" || printf '.include {%s}\n\n' "$(setup_path "$src")" ;; - esac - cat "$body" - } > "$dest" - rm -f "$src" - echo "$src -> $dest" - count=$((count + 1)) -done < "$list" - -if [ "$count" -eq 0 ]; then - echo "変換するmdはありませんでした。" -else - echo "$count件変換しました。内容を確認してコミットしてください。" -fi