aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authornsfisis <nsfisis@gmail.com>2025-11-27 05:05:04 +0900
committernsfisis <nsfisis@gmail.com>2025-11-27 06:07:46 +0900
commitc754d24b162ecd504f3c4bdd8632045dd0398768 (patch)
tree362323710bb329ad609d379df4e4a429e4229fd2
parentd1014de68415df8f0a5dc3389332e086119c6198 (diff)
downloadnsfisis.dev-c754d24b162ecd504f3c4bdd8632045dd0398768.tar.gz
nsfisis.dev-c754d24b162ecd504f3c4bdd8632045dd0398768.tar.zst
nsfisis.dev-c754d24b162ecd504f3c4bdd8632045dd0398768.zip
feat(nuldoc): Djot to Markdown
-rw-r--r--services/nuldoc/content/posts/2021-03-05/my-first-post.md (renamed from services/nuldoc/content/posts/2021-03-05/my-first-post.dj)41
-rw-r--r--services/nuldoc/content/posts/2021-03-30/phperkaigi-2021.md (renamed from services/nuldoc/content/posts/2021-03-30/phperkaigi-2021.dj)48
-rw-r--r--services/nuldoc/content/posts/2021-10-02/cpp-you-can-use-keywords-in-attributes.md (renamed from services/nuldoc/content/posts/2021-10-02/cpp-you-can-use-keywords-in-attributes.dj)2
-rw-r--r--services/nuldoc/content/posts/2021-10-02/python-unbound-local-error.md (renamed from services/nuldoc/content/posts/2021-10-02/python-unbound-local-error.dj)2
-rw-r--r--services/nuldoc/content/posts/2021-10-02/ruby-detect-running-implementation.md (renamed from services/nuldoc/content/posts/2021-10-02/ruby-detect-running-implementation.dj)5
-rw-r--r--services/nuldoc/content/posts/2021-10-02/ruby-then-keyword-and-case-in.md (renamed from services/nuldoc/content/posts/2021-10-02/ruby-then-keyword-and-case-in.dj)25
-rw-r--r--services/nuldoc/content/posts/2021-10-02/rust-where-are-primitive-types-from.md (renamed from services/nuldoc/content/posts/2021-10-02/rust-where-are-primitive-types-from.dj)18
-rw-r--r--services/nuldoc/content/posts/2021-10-02/vim-difference-between-autocmd-bufwrite-and-bufwritepre.md (renamed from services/nuldoc/content/posts/2021-10-02/vim-difference-between-autocmd-bufwrite-and-bufwritepre.dj)39
-rw-r--r--services/nuldoc/content/posts/2021-10-02/vim-swap-order-of-selected-lines.md (renamed from services/nuldoc/content/posts/2021-10-02/vim-swap-order-of-selected-lines.dj)50
-rw-r--r--services/nuldoc/content/posts/2022-04-09/phperkaigi-2022-tokens.md (renamed from services/nuldoc/content/posts/2022-04-09/phperkaigi-2022-tokens.dj)75
-rw-r--r--services/nuldoc/content/posts/2022-04-24/term-banner-write-tool-showing-banner-in-terminal.md (renamed from services/nuldoc/content/posts/2022-04-24/term-banner-write-tool-showing-banner-in-terminal.dj)18
-rw-r--r--services/nuldoc/content/posts/2022-05-01/phperkaigi-2022.md (renamed from services/nuldoc/content/posts/2022-05-01/phperkaigi-2022.dj)21
-rw-r--r--services/nuldoc/content/posts/2022-08-27/php-conference-okinawa-code-golf.md (renamed from services/nuldoc/content/posts/2022-08-27/php-conference-okinawa-code-golf.dj)24
-rw-r--r--services/nuldoc/content/posts/2022-08-31/support-for-communty-is-employee-benefits.md (renamed from services/nuldoc/content/posts/2022-08-31/support-for-communty-is-employee-benefits.dj)11
-rw-r--r--services/nuldoc/content/posts/2022-09-29/write-fizzbuzz-in-php-2-letters-per-line.md (renamed from services/nuldoc/content/posts/2022-09-29/write-fizzbuzz-in-php-2-letters-per-line.dj)40
-rw-r--r--services/nuldoc/content/posts/2022-10-23/phperkaigi-2023-unused-token-quiz-1.md (renamed from services/nuldoc/content/posts/2022-10-23/phperkaigi-2023-unused-token-quiz-1.dj)15
-rw-r--r--services/nuldoc/content/posts/2022-10-28/setup-server-for-this-site.md (renamed from services/nuldoc/content/posts/2022-10-28/setup-server-for-this-site.dj)75
-rw-r--r--services/nuldoc/content/posts/2022-11-19/phperkaigi-2023-unused-token-quiz-2.md (renamed from services/nuldoc/content/posts/2022-11-19/phperkaigi-2023-unused-token-quiz-2.dj)17
-rw-r--r--services/nuldoc/content/posts/2023-01-10/phperkaigi-2023-unused-token-quiz-3.md (renamed from services/nuldoc/content/posts/2023-01-10/phperkaigi-2023-unused-token-quiz-3.dj)24
-rw-r--r--services/nuldoc/content/posts/2023-03-10/rewrite-this-blog-generator.md (renamed from services/nuldoc/content/posts/2023-03-10/rewrite-this-blog-generator.dj)12
-rw-r--r--services/nuldoc/content/posts/2023-04-01/implementation-of-minimal-png-image-encoder.md (renamed from services/nuldoc/content/posts/2023-04-01/implementation-of-minimal-png-image-encoder.dj)65
-rw-r--r--services/nuldoc/content/posts/2023-04-04/phperkaigi-2023-report.md (renamed from services/nuldoc/content/posts/2023-04-04/phperkaigi-2023-report.dj)27
-rw-r--r--services/nuldoc/content/posts/2023-06-25/phpconfuk-2023-report.md (renamed from services/nuldoc/content/posts/2023-06-25/phpconfuk-2023-report.dj)24
-rw-r--r--services/nuldoc/content/posts/2023-10-02/compile-php-runtime-to-wasm.md (renamed from services/nuldoc/content/posts/2023-10-02/compile-php-runtime-to-wasm.dj)33
-rw-r--r--services/nuldoc/content/posts/2023-10-13/i-entered-the-open-university-of-japan.md (renamed from services/nuldoc/content/posts/2023-10-13/i-entered-the-open-university-of-japan.dj)3
-rw-r--r--services/nuldoc/content/posts/2023-12-03/isucon-13.md (renamed from services/nuldoc/content/posts/2023-12-03/isucon-13.dj)32
-rw-r--r--services/nuldoc/content/posts/2023-12-31/2023-reflections.md (renamed from services/nuldoc/content/posts/2023-12-31/2023-reflections.dj)22
-rw-r--r--services/nuldoc/content/posts/2024-01-10/neovim-insert-namespace-declaration-to-empty-php-file.md (renamed from services/nuldoc/content/posts/2024-01-10/neovim-insert-namespace-declaration-to-empty-php-file.dj)20
-rw-r--r--services/nuldoc/content/posts/2024-02-03/install-wireguard-on-personal-server.md (renamed from services/nuldoc/content/posts/2024-02-03/install-wireguard-on-personal-server.dj)21
-rw-r--r--services/nuldoc/content/posts/2024-02-10/yapcjapan-2024-report.md (renamed from services/nuldoc/content/posts/2024-02-10/yapcjapan-2024-report.dj)16
-rw-r--r--services/nuldoc/content/posts/2024-02-22/phpkansai-2024-report.md (renamed from services/nuldoc/content/posts/2024-02-22/phpkansai-2024-report.dj)14
-rw-r--r--services/nuldoc/content/posts/2024-03-17/phperkaigi-2024-report.md (renamed from services/nuldoc/content/posts/2024-03-17/phperkaigi-2024-report.dj)21
-rw-r--r--services/nuldoc/content/posts/2024-03-20/my-bucket-list.md (renamed from services/nuldoc/content/posts/2024-03-20/my-bucket-list.dj)0
-rw-r--r--services/nuldoc/content/posts/2024-04-14/phpcon-odawara-2024-report.md (renamed from services/nuldoc/content/posts/2024-04-14/phpcon-odawara-2024-report.dj)21
-rw-r--r--services/nuldoc/content/posts/2024-04-21/pipefail-option-in-gitlab-ci-cd.md (renamed from services/nuldoc/content/posts/2024-04-21/pipefail-option-in-gitlab-ci-cd.dj)23
-rw-r--r--services/nuldoc/content/posts/2024-04-29/zsh-file-completion-for-composer-custom-commands.md (renamed from services/nuldoc/content/posts/2024-04-29/zsh-file-completion-for-composer-custom-commands.dj)18
-rw-r--r--services/nuldoc/content/posts/2024-05-11/phpconkagawa-2024-report.md (renamed from services/nuldoc/content/posts/2024-05-11/phpconkagawa-2024-report.dj)21
-rw-r--r--services/nuldoc/content/posts/2024-06-19/scalamatsuri-2024-report.md (renamed from services/nuldoc/content/posts/2024-06-19/scalamatsuri-2024-report.dj)15
-rw-r--r--services/nuldoc/content/posts/2024-07-19/reparojson-fix-only-json-formatter.md (renamed from services/nuldoc/content/posts/2024-07-19/reparojson-fix-only-json-formatter.dj)14
-rw-r--r--services/nuldoc/content/posts/2024-08-19/go-template-access-outer-scope-pipeline-within-with-or-range.md (renamed from services/nuldoc/content/posts/2024-08-19/go-template-access-outer-scope-pipeline-within-with-or-range.dj)15
-rw-r--r--services/nuldoc/content/posts/2024-09-28/mncore-challenge-1.md (renamed from services/nuldoc/content/posts/2024-09-28/mncore-challenge-1.dj)11
-rw-r--r--services/nuldoc/content/posts/2024-12-04/cohackpp-report.md (renamed from services/nuldoc/content/posts/2024-12-04/cohackpp-report.dj)18
-rw-r--r--services/nuldoc/content/posts/2024-12-33/2024-reflections.md (renamed from services/nuldoc/content/posts/2024-12-33/2024-reflections.dj)33
-rw-r--r--services/nuldoc/content/posts/2025-01-08/phperkaigi-2023-tokens-q1.md (renamed from services/nuldoc/content/posts/2025-01-08/phperkaigi-2023-tokens-q1.dj)46
-rw-r--r--services/nuldoc/content/posts/2025-01-26/yaml-breaking-changes-between-v1-1-and-v1-2.md (renamed from services/nuldoc/content/posts/2025-01-26/yaml-breaking-changes-between-v1-1-and-v1-2.dj)25
-rw-r--r--services/nuldoc/content/posts/2025-02-24/phpcon-nagoya-2025-report.md (renamed from services/nuldoc/content/posts/2025-02-24/phpcon-nagoya-2025-report.dj)15
-rw-r--r--services/nuldoc/content/posts/2025-03-27/zip-function-like-command-paste-command.md (renamed from services/nuldoc/content/posts/2025-03-27/zip-function-like-command-paste-command.dj)8
-rw-r--r--services/nuldoc/content/posts/2025-03-28/http-1-1-send-multiple-same-headers.md (renamed from services/nuldoc/content/posts/2025-03-28/http-1-1-send-multiple-same-headers.dj)23
-rw-r--r--services/nuldoc/content/posts/2025-04-20/trick-2025-most-ruby-on-ruby-award.md (renamed from services/nuldoc/content/posts/2025-04-20/trick-2025-most-ruby-on-ruby-award.dj)26
-rw-r--r--services/nuldoc/content/posts/2025-04-24/composer-patches-v2-does-not-require-gnu-patch-even-on-macos.md (renamed from services/nuldoc/content/posts/2025-04-24/composer-patches-v2-does-not-require-gnu-patch-even-on-macos.dj)13
-rw-r--r--services/nuldoc/content/posts/2025-05-05/make-tiny-self-hosted-c-compiler.md (renamed from services/nuldoc/content/posts/2025-05-05/make-tiny-self-hosted-c-compiler.dj)57
-rw-r--r--services/nuldoc/content/posts/2025-06-14/baba-is-you.md (renamed from services/nuldoc/content/posts/2025-06-14/baba-is-you.dj)140
-rw-r--r--services/nuldoc/content/posts/2025-07-15/partial-surrender-to-ebooks.md (renamed from services/nuldoc/content/posts/2025-07-15/partial-surrender-to-ebooks.dj)2
-rw-r--r--services/nuldoc/content/posts/2025-10-31/representing-single-value-with-half-open-float-interval.md (renamed from services/nuldoc/content/posts/2025-10-31/representing-single-value-with-half-open-float-interval.dj)22
-rw-r--r--services/nuldoc/content/posts/2025-11-09/rubiks-cube-blindfolded-first-success.md (renamed from services/nuldoc/content/posts/2025-11-09/rubiks-cube-blindfolded-first-success.dj)24
-rw-r--r--services/nuldoc/content/posts/2025-11-27/anybatross-writeup.md (renamed from services/nuldoc/content/posts/2025-11-27/anybatross-writeup.dj)39
-rw-r--r--services/nuldoc/deno.jsonc8
-rw-r--r--services/nuldoc/deno.lock611
-rw-r--r--services/nuldoc/nuldoc-src/commands/build.ts14
-rw-r--r--services/nuldoc/nuldoc-src/components/TableOfContents.ts2
-rw-r--r--services/nuldoc/nuldoc-src/djot/djot2ndoc.ts604
-rw-r--r--services/nuldoc/nuldoc-src/generators/post.ts4
-rw-r--r--services/nuldoc/nuldoc-src/markdown/document.ts (renamed from services/nuldoc/nuldoc-src/djot/document.ts)12
-rw-r--r--services/nuldoc/nuldoc-src/markdown/mdast2ndoc.ts575
-rw-r--r--services/nuldoc/nuldoc-src/markdown/parse.ts (renamed from services/nuldoc/nuldoc-src/djot/parse.ts)24
-rw-r--r--services/nuldoc/nuldoc-src/markdown/to_html.ts (renamed from services/nuldoc/nuldoc-src/djot/to_html.ts)0
-rw-r--r--services/nuldoc/nuldoc-src/pages/PostPage.ts2
-rw-r--r--services/nuldoc/public/blog/posts/2021-03-05/my-first-post/index.html8
-rw-r--r--services/nuldoc/public/blog/posts/2023-12-31/2023-reflections/index.html20
-rw-r--r--services/nuldoc/public/blog/posts/2024-04-14/phpcon-odawara-2024-report/index.html2
-rw-r--r--services/nuldoc/public/blog/posts/2024-05-11/phpconkagawa-2024-report/index.html2
-rw-r--r--services/nuldoc/public/blog/posts/2024-08-19/go-template-access-outer-scope-pipeline-within-with-or-range/index.html2
-rw-r--r--services/nuldoc/public/blog/posts/2024-12-33/2024-reflections/index.html2
-rw-r--r--services/nuldoc/public/blog/posts/2025-05-05/make-tiny-self-hosted-c-compiler/index.html2
-rw-r--r--services/nuldoc/public/blog/posts/2025-06-14/baba-is-you/index.html24
-rw-r--r--services/nuldoc/public/blog/posts/2025-07-15/partial-surrender-to-ebooks/index.html11
-rw-r--r--services/nuldoc/public/blog/posts/2025-11-09/rubiks-cube-blindfolded-first-success/index.html2
77 files changed, 1729 insertions, 1691 deletions
diff --git a/services/nuldoc/content/posts/2021-03-05/my-first-post.dj b/services/nuldoc/content/posts/2021-03-05/my-first-post.md
index 4e56219..2732b20 100644
--- a/services/nuldoc/content/posts/2021-03-05/my-first-post.dj
+++ b/services/nuldoc/content/posts/2021-03-05/my-first-post.md
@@ -13,8 +13,7 @@ remark = "公開"
date = "2025-05-12"
remark = "ジェネレータやスタイルをテストするためのコンテンツを追加"
---
-{#test}
-# Test
+# Test {#test}
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod
tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim
@@ -24,45 +23,34 @@ velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint
occaecat cupidatat non proident, sunt in culpa qui officia deserunt
mollit anim id est laborum.
-{#sec-1}
-# Section level 1
+# Section level 1 {#sec-1}
-{#sec-2}
-## Section level 2
+## Section level 2 {#sec-2}
-{#sec-3}
-### Section level 3
+### Section level 3 {#sec-3}
-{#sec-4}
-#### Section level 4
+#### Section level 4 {#sec-4}
-{#sec-5}
-##### Section level 5
+##### Section level 5 {#sec-5}
* list item 1
* list item 2
-
* list item 2.a
* list item 2.b
-
* list item 3
* list item 3
1. list item 1
1. list item 2
-
- 1. list item 2.a
- 1. list item 2.b
-
+ 1. list item 2.a
+ 1. list item 2.b
1. list item 3
1. list item 3
* [ ] list item 1
* [ ] list item 2
-
* [ ] list item 2.a
* [ ] list item 2.b
-
* [ ] list item 3
* [ ] list item 3
@@ -74,29 +62,30 @@ mollit anim id est laborum.
puts "Hello, World!"
```
-*emph* _strong_
+**emph** *strong*
-{=highlighted=}
+<mark>highlighted</mark>
https://example.com
[example link](https://example.com)
-H~2~O
+H<sub>2</sub>O
-2^64^
+2<sup>64</sup>
'foo' "bar"
`code`
-{+inserted+} {-deleted-}
+<ins>inserted</ins>
+~~deleted~~
footnote. [^foo]
[^foo]: foo bar
-::: note
+:::note
hoge piyo
:::
diff --git a/services/nuldoc/content/posts/2021-03-30/phperkaigi-2021.dj b/services/nuldoc/content/posts/2021-03-30/phperkaigi-2021.md
index fac9f1e..40df706 100644
--- a/services/nuldoc/content/posts/2021-03-30/phperkaigi-2021.dj
+++ b/services/nuldoc/content/posts/2021-03-30/phperkaigi-2021.md
@@ -17,8 +17,7 @@ remark = "公開"
date = "2025-04-09"
remark = "それぞれの発表に関するメモ部分を削除し、感想のみに"
---
-{#report}
-# PHPerKaigi 2021 参加レポ
+# PHPerKaigi 2021 参加レポ {#report}
2021-03-26 から 2021-03-28
にかけて開催された、[PHPerKaigi 2021](https://phperkaigi.jp/2021/)
@@ -32,11 +31,9 @@ remark = "それぞれの発表に関するメモ部分を削除し、感想の
発表はトラック A、B に分かれていたのだが、今回はすべて A
トラックを視聴している (切り替えるのが面倒だっただけ)。
-{#day-0}
-## Day 0 前夜祭 (2021/03/27)
+## Day 0 前夜祭 (2021/03/27) {#day-0}
-{#1730-a}
-### 17:30 [A] LAMPをこじらせてサーバーレスに乗り遅れたPHPerがLambdaに入門してみる
+### 17:30 [A] LAMPをこじらせてサーバーレスに乗り遅れたPHPerがLambdaに入門してみる {#1730-a}
AWS Lambda のような Function as a Service
はマイクロサービス化における一つの到達点に思えるのだが、これを使って実際に
@@ -49,21 +46,18 @@ PHP on AWS Lambda があれだけ簡単に動かせるのには驚いた。
Laravel などでは動かなさそう)
だという先入観を持っていたのだが、この発表のデモによればそうでもないらしい。
-{#1810-a}
-### 18:10 [A] 大規模サイトにおけるSEO観点でのURL設計
+### 18:10 [A] 大規模サイトにおけるSEO観点でのURL設計 {#1810-a}
SEO (Search Engine Optimization)
は大して知らないので新鮮な話が多かった。その分語れることも少ない……。
-{#1850-a}
-### 18:50 [A] PHPerでもわかる!実践Webアクセシビリティ
+### 18:50 [A] PHPerでもわかる!実践Webアクセシビリティ {#1850-a}
つい最近 WAI-ARIA
についての記事を読んだばかりだったので個人的にタイムリーな話題だった。(あまりこの言葉を使いたくないのだが)
いわゆる「健常者」にとって、こうした問題を普段の生活の中で意識するのは難しい。だからこそ情報へのアンテナは張っておくようにしたい。
-{#1930-a}
-### 19:30 [A] PHP でファイルシステムを作ろう
+### 19:30 [A] PHP でファイルシステムを作ろう {#1930-a}
PHP で FUSE
@@ -74,18 +68,15 @@ PHP で FUSE
この発表を聞きながらファイルシステムにマウントできそうなものを考えていたのだが、およそ木構造をしているものすべてと言えそうだ
(ハンマーしか持っていないと云々)。何かできそうだがなかなか思いつかない。
-{#day-1}
-## Day 1 (2021/03/27)
+## Day 1 (2021/03/27) {#day-1}
-{#1050-a}
-### 10:50 [A] 実践ATDD 〜TDDから更に歩みを進めたソフトウェア開発へ〜
+### 10:50 [A] 実践ATDD 〜TDDから更に歩みを進めたソフトウェア開発へ〜 {#1050-a}
User Acceptance Test (UAT)
くらいの規模になると個人開発・趣味開発では触れない領域なので、大いに勉強になった。スライドに添付されている資料が相当に充実していたので、これを読むのが本番といった様相すら感じる。
高レベルテストの自動化は現在のプロジェクトでも感じており、自動化のチャンスは伺っている。とはいえセッションでも指摘されているように自動化することにコストがかかりすぎる領域があるのも事実で、そのバランスが難しい。
-{#1150-a}
-### 11:50 [A] 静的型解析を用いた大規模レガシーコードのリファクタリング計画
+### 11:50 [A] 静的型解析を用いた大規模レガシーコードのリファクタリング計画 {#1150-a}
型のある世界で生きてきた身として大いに楽しみにしていた発表。
@@ -93,8 +84,7 @@ User Acceptance Test (UAT)
今のプロジェクトでも新しく追加するコードには型をつけるよう努めているが、どうしても古いコードには型がついていない。個人的には型のないコードに対してどう型を自動的に付けるかという点に興味があり、その点で
Ruby の typeprof には注目している。
-{#1310-a}
-### 13:10 [A] 目的に沿ったDocumentation as Codeをいかにして実現していくか
+### 13:10 [A] 目的に沿ったDocumentation as Codeをいかにして実現していくか {#1310-a}
この発表も以前から非常に楽しみにしていた。
@@ -102,14 +92,12 @@ Ruby の typeprof には注目している。
(乖離しない)
情報を起点にするのは理にかなっている。問題はトレースをいつ、どう取るかだろうか。それを自動化しなければ、実態との乖離が避けられないだろう。
-{#1410-a}
-### 14:10 [A] PHPで学ぶ、セッションの基本と応用
+### 14:10 [A] PHPで学ぶ、セッションの基本と応用 {#1410-a}
全体的に基本的な話だったので特に触れない。Cookie
やセッションの話としては非常に分かりやすくまとめられていたので、知らない人が学ぶにはいい教材だろう。
-{#1450-a}
-### 14:50 [A] PHP8になった今の時代に、PHPの「エラー」「例外」そして「Error」をおさらいしておこう
+### 14:50 [A] PHP8になった今の時代に、PHPの「エラー」「例外」そして「Error」をおさらいしておこう {#1450-a}
PHP を学んでいる途中の私としては、今まさに聞きたい発表だった (現時点で
PHP を書き始めてから 4ヶ月ほどになる)。
@@ -123,29 +111,25 @@ C言語時代への回帰ともいえるが、その頃と異なるのはエラ
PHP
のように、すでに例外が言語システムに根ざしている言語ではどうすればよいか。この場合も同じく静的検証の力を借りることになるだろう。
-{#1530-a}
-### 15:30 [A] Laravel のメール認証の内部実装を掘り下げる
+### 15:30 [A] Laravel のメール認証の内部実装を掘り下げる {#1530-a}
Laravel
の知識がない私にはまったくついていけなかった。また、個人的にタイトルがややミスリーディングに感じた。
-{#1610-a}
-### 16:10 [A] ブラウザから始めるgRPC 〜 gRPC-WebにPHPを添えて
+### 16:10 [A] ブラウザから始めるgRPC 〜 gRPC-WebにPHPを添えて {#1610-a}
(発表の中でもまさに同じことをおっしゃっていたが) PHP
以外の方が向いているだろう、というのが第一の感想である。gRPC
はそれ自体というよりも Protobuf
というエコシステムに乗れることのメリットが大きいと感じる。そのエコシステムにうまく乗れない時点で、うーんという感じ。
-{#day-2}
-## Day 2 (2021/03/28)
+## Day 2 (2021/03/28) {#day-2}
冒頭に書いた通り、2日目から体調が悪くまともに聴けていない。途中までは頭痛を我慢しつつ見ていたのだが、まともに入ってこなかった。
残念ではあるが、いずれにせよ見られていない発表は他にもあるので、今週末にでもまとめて見ようと思う。
-{#comments}
-## 全体の感想
+## 全体の感想 {#comments}
Day 2
にほとんど参加できなかったのは残念だが、イベント自体は大変楽しく、また興味深いものであった。自分がまったく知らない領域の話を聞けるのはこうしたイベントならではだと感じる。オンライン開催ゆえ現地に行く必要がなく、気軽に参加できたのも
diff --git a/services/nuldoc/content/posts/2021-10-02/cpp-you-can-use-keywords-in-attributes.dj b/services/nuldoc/content/posts/2021-10-02/cpp-you-can-use-keywords-in-attributes.md
index 953b936..5766ce7 100644
--- a/services/nuldoc/content/posts/2021-10-02/cpp-you-can-use-keywords-in-attributes.dj
+++ b/services/nuldoc/content/posts/2021-10-02/cpp-you-can-use-keywords-in-attributes.md
@@ -12,7 +12,7 @@ tags = [
date = "2021-10-02"
remark = "Qiita から移植"
---
-::: note
+:::note
この記事は Qiita から移植してきたものです。
元 URL: https://qiita.com/nsfisis/items/94090937bcf860cfa93b
:::
diff --git a/services/nuldoc/content/posts/2021-10-02/python-unbound-local-error.dj b/services/nuldoc/content/posts/2021-10-02/python-unbound-local-error.md
index 88d0315..1699b45 100644
--- a/services/nuldoc/content/posts/2021-10-02/python-unbound-local-error.dj
+++ b/services/nuldoc/content/posts/2021-10-02/python-unbound-local-error.md
@@ -12,7 +12,7 @@ tags = [
date = "2021-10-02"
remark = "Qiita から移植"
---
-::: note
+:::note
この記事は Qiita から移植してきたものです。
元 URL: https://qiita.com/nsfisis/items/5d733703afcb35bbf399
:::
diff --git a/services/nuldoc/content/posts/2021-10-02/ruby-detect-running-implementation.dj b/services/nuldoc/content/posts/2021-10-02/ruby-detect-running-implementation.md
index 653b7dc..fd72102 100644
--- a/services/nuldoc/content/posts/2021-10-02/ruby-detect-running-implementation.dj
+++ b/services/nuldoc/content/posts/2021-10-02/ruby-detect-running-implementation.md
@@ -11,7 +11,7 @@ tags = [
date = "2021-10-02"
remark = "Qiita から移植"
---
-::: note
+:::note
この記事は Qiita から移植してきたものです。
元 URL: https://qiita.com/nsfisis/items/74d7ffeeebc51b20d791
:::
@@ -65,8 +65,7 @@ CRuby) については執筆時現在 (2020/12/8) も `'ruby'`
[mruby 該当部分のソース](https://github.com/mruby/mruby/blob/ed29d74bfd95362eaeb946fcf7e865d80346b62b/include/mruby/version.h#L32-L35) より引用:
-{filename="version.h"}
-```c
+```c filename="version.h"
/*
* Ruby engine.
*/
diff --git a/services/nuldoc/content/posts/2021-10-02/ruby-then-keyword-and-case-in.dj b/services/nuldoc/content/posts/2021-10-02/ruby-then-keyword-and-case-in.md
index 82d6d9c..ea9526b 100644
--- a/services/nuldoc/content/posts/2021-10-02/ruby-then-keyword-and-case-in.dj
+++ b/services/nuldoc/content/posts/2021-10-02/ruby-then-keyword-and-case-in.md
@@ -12,19 +12,17 @@ tags = [
date = "2021-10-02"
remark = "Qiita から移植"
---
-::: note
+:::note
この記事は Qiita から移植してきたものです。
元 URL: https://qiita.com/nsfisis/items/787a8cf888a304497223
:::
-{#tl-dr}
-# TL; DR
+# TL; DR {#tl-dr}
`case` - `in` によるパターンマッチング構文でも、`case` - `when`
と同じように `then` が使える (場合によっては使う必要がある)。
-{#what-is-then-keyword}
-# `then` とは
+# `then` とは {#what-is-then-keyword}
使われることは稀だが、Ruby では `then`
がキーワードになっている。次のように使う:
@@ -64,8 +62,7 @@ when p then
end
```
-{#why-then-is-usually-unnecessary}
-# なぜ普段は書かなくてもよいのか
+# なぜ普段は書かなくてもよいのか {#why-then-is-usually-unnecessary}
普通 Ruby のコードで `then`
を書くことはない。なぜか。次のコードを実行してみるとわかる。
@@ -97,8 +94,7 @@ puts 'Hello, World!' end
無事 Hello, World! と出力されるようになった。
-{#why-then-or-linebreak-is-needed}
-# なぜ `then` や `;` や改行が必要か
+# なぜ `then` や `;` や改行が必要か {#why-then-or-linebreak-is-needed}
なぜ `then` や `;` や改行 (以下 「`then` 等」)
が必要なのだろうか。次の例を見てほしい:
@@ -132,8 +128,7 @@ end
Ruby の場合、プログラマーが書きやすいよう改行でもって `then`
が代用できるので、ほとんどの場合 `then` は必要ない。
-{#then-in-case-in}
-# `case` - `in` における `then`
+# `case` - `in` における `then` {#then-in-case-in}
ようやく本題にたどり着いた。来る Ruby 3.0 では `case` と `in`
キーワードを使ったパターンマッチングの構文が入る予定である。この構文でもパターン部との区切りとして
@@ -143,8 +138,7 @@ Ruby の場合、プログラマーが書きやすいよう改行でもって `t
https://github.com/ruby/ruby/blob/221ca0f8281d39f0dfdfe13b2448875384bbf735/parse.y#L3961-L3986
-{filename="parse.y"}
-```yacc
+```yacc filename="parse.y"
p_case_body : keyword_in
{
SET_LEX_STATE(EXPR_BEG|EXPR_LABEL);
@@ -222,12 +216,9 @@ in n then c
end
```
-{#outro}
-# まとめ
+# まとめ {#outro}
* `if` や `case` の条件の後ろには `then`、`;`、改行のいずれかが必要
-
* 通常は改行しておけばよい
-
* 3.0 で入る予定の `case` - `in` でも `then` 等が必要になる
* Ruby の構文を正確に知るには (現状) `parse.y` を直接読めばよい
diff --git a/services/nuldoc/content/posts/2021-10-02/rust-where-are-primitive-types-from.dj b/services/nuldoc/content/posts/2021-10-02/rust-where-are-primitive-types-from.md
index 9fa61d5..ab7345e 100644
--- a/services/nuldoc/content/posts/2021-10-02/rust-where-are-primitive-types-from.dj
+++ b/services/nuldoc/content/posts/2021-10-02/rust-where-are-primitive-types-from.md
@@ -11,13 +11,12 @@ tags = [
date = "2021-10-02"
remark = "Qiita から移植"
---
-::: note
+:::note
この記事は Qiita から移植してきたものです。
元 URL: https://qiita.com/nsfisis/items/9a429432258bbcd6c565
:::
-{#intro}
-# 前置き
+# 前置き {#intro}
Rust
において、プリミティブ型の名前は予約語でない。したがって、次のコードは合法である。
@@ -48,11 +47,10 @@ struct str;
では、普段単に `bool` と書いたとき、この `bool`
は一体どこから来ているのか。rustc のソースを追ってみた。
- 前提知識: 一般的なコンパイラの構造、用語。`rustc` そのものの知識は不要
- (というよりも筆者自身がよく知らない)
+前提知識: 一般的なコンパイラの構造、用語。`rustc` そのものの知識は不要
+(というよりも筆者自身がよく知らない)
-{#code-reading}
-# 調査
+# 調査 {#code-reading}
調査に使用したソース (調査時点での最新 master)
@@ -96,8 +94,7 @@ rustc_resolve/src/lib.rs: table.insert(sym::i128, Int(IntTy::I128));
`rustc_resolve`
というのはいかにも名前解決を担いそうなクレート名である。該当箇所を見てみる。
-{filename="rustc_resolve/src/lib.rs"}
-```rust
+```rust filename="rustc_resolve/src/lib.rs"
/// Interns the names of the primitive types.
///
/// All other types are defined somewhere and possibly imported, but the primitive ones need
@@ -199,8 +196,7 @@ fn main() {
として解決される。なぜなら、プリミティブ型の判定をする前に `bool`
という名前の別の型が見つかるからだ。
-{#outro}
-# まとめ
+# まとめ {#outro}
Rust
のプリミティブ型は予約語ではない。名前解決の最終段階で特別扱いされ、他に同名の型が見つかっていなければ対応するプリミティブ型に解決される。
diff --git a/services/nuldoc/content/posts/2021-10-02/vim-difference-between-autocmd-bufwrite-and-bufwritepre.dj b/services/nuldoc/content/posts/2021-10-02/vim-difference-between-autocmd-bufwrite-and-bufwritepre.md
index a97337d..945e270 100644
--- a/services/nuldoc/content/posts/2021-10-02/vim-difference-between-autocmd-bufwrite-and-bufwritepre.dj
+++ b/services/nuldoc/content/posts/2021-10-02/vim-difference-between-autocmd-bufwrite-and-bufwritepre.md
@@ -11,18 +11,16 @@ tags = [
date = "2021-10-02"
remark = "Qiita から移植"
---
-::: note
+:::note
この記事は Qiita から移植してきたものです。
元 URL: https://qiita.com/nsfisis/items/79ab4db8564032de0b25
:::
-{#tl-dr}
-# TL; DR
+# TL; DR {#tl-dr}
違いはない。ただのエイリアス。
-{#code-reading}
-# 調査記録
+# 調査記録 {#code-reading}
Vim の autocmd events には似通った名前のものがいくつかある。大抵は
`:help`
@@ -45,24 +43,21 @@ vim と neovim のソースコードを調査した。
* [vim (調査時点での master branch)](https://github.com/vim/vim/tree/8e6be34338f13a6a625f19bcef82019c9adc65f2)
* [neovim (上に同じ)](https://github.com/neovim/neovim/tree/71d4f5851f068eeb432af34850dddda8cc1c71e3)
-{#vim}
-## vim のソースコード
+## vim のソースコード {#vim}
以下は、autocmd events
の名前と内部で使われている整数値とのマッピングを定義している箇所である。見ての通り、上でエイリアスではないかと述べた3組には、それぞれ同じ内部値が使われている。
https://github.com/vim/vim/blob/8e6be34338f13a6a625f19bcef82019c9adc65f2/src/autocmd.c#L85-L86
-{filename="src/autocmd.c"}
-```c
+```c filename="src/autocmd.c"
{"BufAdd", EVENT_BUFADD},
{"BufCreate", EVENT_BUFADD},
```
https://github.com/vim/vim/blob/8e6be34338f13a6a625f19bcef82019c9adc65f2/src/autocmd.c#L95-L97
-{filename="src/autocmd.c"}
-```c
+```c filename="src/autocmd.c"
{"BufRead", EVENT_BUFREADPOST},
{"BufReadCmd", EVENT_BUFREADCMD},
{"BufReadPost", EVENT_BUFREADPOST},
@@ -70,23 +65,20 @@ https://github.com/vim/vim/blob/8e6be34338f13a6a625f19bcef82019c9adc65f2/src/aut
https://github.com/vim/vim/blob/8e6be34338f13a6a625f19bcef82019c9adc65f2/src/autocmd.c#L103-L105
-{filename="src/autocmd.c"}
-```c
+```c filename="src/autocmd.c"
{"BufWrite", EVENT_BUFWRITEPRE},
{"BufWritePost", EVENT_BUFWRITEPOST},
{"BufWritePre", EVENT_BUFWRITEPRE},
```
-{#neovim}
-## neovim のソースコード
+## neovim のソースコード {#neovim}
neovim の場合でも同様のマッピングが定義されているが、こちらの場合は Lua
で書かれている。以下にある通り、はっきり `aliases` と書かれている。
https://github.com/neovim/neovim/blob/71d4f5851f068eeb432af34850dddda8cc1c71e3/src/nvim/auevents.lua#L119-L124
-{filename="src/nvim/auevents.lua"}
-```lua
+```lua filename="src/nvim/auevents.lua"
aliases = {
BufCreate = 'BufAdd',
BufRead = 'BufReadPost',
@@ -98,32 +90,23 @@ aliases = {
ところで、上では取り上げなかった `FileEncoding` だが、これは
`:help FileEncoding` にしっかりと書いてある。
-{filename=":help FileEncoding"}
-```
+```text filename=":help FileEncoding"
*FileEncoding*
FileEncoding Obsolete. It still works and is equivalent
to |EncodingChanged|.
```
-{#outro}
-# まとめ
+# まとめ {#outro}
記事タイトルについて言えば、どちらも変わらないので好きな方を使えばよい。あえて言えば、次のようになるだろう。
* `BufAdd`/`BufCreate`
-
* → `BufCreate` は歴史的な理由により ("for historic reasons") 存在しているため、新しい方 (`BufAdd`) を使う
-
* `BufRead`/`BufReadPost`
-
* → `BufReadPre` との対称性のため、あるいは `BufWritePost` との対称性のため `BufReadPost` を使う
-
* `BufWrite`/`BufWritePre`
-
* → `BufWritePost` との対称性のため、あるいは `BufReadPre` との対称性のため `BufWritePre` を使う
-
* `FileEncoding`/`EncodingChanged`
-
* → `FileEncoding` は "Obsolete" と明言されているので、`EncodingChanged` を使う
ところでこの調査で知ったのだが、`BufRead` と `BufWrite`
diff --git a/services/nuldoc/content/posts/2021-10-02/vim-swap-order-of-selected-lines.dj b/services/nuldoc/content/posts/2021-10-02/vim-swap-order-of-selected-lines.md
index 1cd070e..6f4ca34 100644
--- a/services/nuldoc/content/posts/2021-10-02/vim-swap-order-of-selected-lines.dj
+++ b/services/nuldoc/content/posts/2021-10-02/vim-swap-order-of-selected-lines.md
@@ -11,13 +11,12 @@ tags = [
date = "2021-10-02"
remark = "Qiita から移植"
---
-::: note
+:::note
この記事は Qiita から移植してきたものです。
元 URL: https://qiita.com/nsfisis/items/4fefb361d9a693803520
:::
-{#tl-dr}
-# TL; DR
+# TL; DR {#tl-dr}
```vim
" License: Public Domain
@@ -27,30 +26,26 @@ command! -bar -range=%
\ keeppatterns <line1>,<line2>g/^/m<line1>-1
```
-{#version}
-# バージョン情報
+# バージョン情報 {#version}
`:version` の一部
> VIM - Vi IMproved 8.2 (2019 Dec 12, compiled Jan 26 2020 11\:30\:30) macOS
> version Included patches: 1-148 Huge version without GUI.
-{#existing-solution}
-# よく紹介されている手法
+# よく紹介されている手法 {#existing-solution}
-{#external-commands}
-## `tac` / `tail`
+## `tac` / `tail` {#external-commands}
`tac` や `tail -r` などの外部コマンドを `!`
を使って呼び出し、置き換える。
-> :h v_!
+> \:h v_!
`tac` コマンドや `tail` の `-r`
オプションは環境によって利用できないことがあり、複数の環境を行き来する場合に採用しづらい
-{#global-command}
-## `:g/^/m0`
+## `:g/^/m0` {#global-command}
こちらは外部コマンドに頼らず、Vim の機能のみを使う。`g` は `:global`
コマンドの、`m` は `:move` コマンドの略
@@ -60,12 +55,12 @@ command! -bar -range=%
で指定された検索パターンにマッチする行に対して、順番に `[command]`
で指定された Ex コマンドを呼び出す。
-> :h :global
+> \:h \:global
`:move` コマンドは `[range]:move {address}` のように使い、`[range]`
で指定された範囲の行を `{address}` で指定された位置に移動させる。
-> :h :move
+> \:h \:move
`:g/^/m0` のように組み合わせると、「すべての行を1行ずつ
0行目(1行目の上)に動かす」という動きをする。これは確かに行の入れ替えになっている。
@@ -82,8 +77,7 @@ command! -bar -range=%
これは望みの動作をするが、実際に実行してみると全行がハイライトされてしまう。次節で詳細を述べる。
-{#problem-of-global-command}
-# `:g/^/m0` の問題点
+# `:g/^/m0` の問題点 {#problem-of-global-command}
`:global`
コマンドは各行に対してマッチングを行う際、現在の検索パターンを上書きしてしまう。`^`
@@ -91,13 +85,11 @@ command! -bar -range=%
オプションを無効にしている場合その限りではないが、その場合でも直前の検索パターンが失われてしまうと
`n` コマンドなどの際に不便である。
-> :h @/
+> \:h @/
-{#solution}
-# 解決策
+# 解決策 {#solution}
-{editat="2020-09-28" operation="追記"}
-::: edit
+:::edit{editat="2020-09-28" operation="追記"}
より簡潔な方法を見つけたので次節に追記した。
:::
@@ -120,9 +112,9 @@ command! -bar -range=%
Vim のヘルプから該当箇所を引用する (強調は筆者による)。
-> :h autocmd-searchpat
+> \:h autocmd-searchpat
>
-> *Autocommands do not change the current search patterns.* Vim saves the
+> **Autocommands do not change the current search patterns.** Vim saves the
> current search patterns before executing autocommands then restores them
> after the autocommands finish. This means that autocommands do not
> affect the strings highlighted with the 'hlsearch' option.
@@ -132,20 +124,18 @@ Vim のヘルプから該当箇所を引用する (強調は筆者による)。
`:nohlsearch` のヘルプにある。同じく該当箇所を引用する
(強調は筆者による)。
-> :h :nohlsearch
+> \:h \:nohlsearch
>
> (略) This command doesn’t work in an autocommand, because the
> highlighting state is saved and restored when executing autocommands
-> |autocmd-searchpat|. *Same thing for when invoking a user function.*
+> |autocmd-searchpat|. **Same thing for when invoking a user function.**
この仕様により、`:g/^/m0`
の呼び出しをユーザー定義関数に切り出すことで上述の問題を解決できる。
-{#solution-revised}
-# 解決策 (改訂版)
+# 解決策 (改訂版) {#solution-revised}
-{editat="2020-09-28" operation="追記"}
-::: edit
+:::edit{editat="2020-09-28" operation="追記"}
より簡潔な方法を見つけたため追記する。
:::
@@ -160,4 +150,4 @@ command! -bar -range=%
のように使い、読んで字の如く、後ろに続く
Exコマンドを「現在の検索パターンを保ったまま」実行する。はるかに分かりやすく意図を表現できる。
-> :h :keeppatterns
+> \:h \:keeppatterns
diff --git a/services/nuldoc/content/posts/2022-04-09/phperkaigi-2022-tokens.dj b/services/nuldoc/content/posts/2022-04-09/phperkaigi-2022-tokens.md
index 3956583..e5f5541 100644
--- a/services/nuldoc/content/posts/2022-04-09/phperkaigi-2022-tokens.dj
+++ b/services/nuldoc/content/posts/2022-04-09/phperkaigi-2022-tokens.md
@@ -17,8 +17,7 @@ remark = "公開"
date = "2022-04-16"
remark = "2問目、3問目の解説を追加、1問目に加筆"
---
-{#intro}
-# はじめに
+# はじめに {#intro}
本日開始された [PHPerKaigi 2022](https://phperkaigi.jp/2022/) の PHPer
チャレンジにおいて、弊社
@@ -27,13 +26,11 @@ remark = "2問目、3問目の解説を追加、1問目に加筆"
リポジトリはこちら: https://github.com/nsfisis/PHPerKaigi2022-tokens
-{#q1-brainfuck}
-# 第1問 brainf_ck.php
+# 第1問 brainf_ck.php {#q1-brainfuck}
ソースコードはこちら。実行には PHP 8.1 以上が必要なので注意。
-{filename="brainf_ck.php"}
-```php
+```php filename="brainf_ck.php"
<?php
declare(strict_types=0O1);
@@ -107,17 +104,14 @@ $🐘([
この問題は、単に適切なバージョンの PHP で動かせばトークンが得られる。
-{#commentary}
-## 解説
+## 解説 {#commentary}
-{#emoji}
-### 絵文字
+### 絵文字 {#emoji}
まず目につくのは大量の絵文字だろう。 PHP
は識別子に使用できる文字の範囲が広く、絵文字も使うことができる。
-{#brainfuck}
-### プログラム全体
+### プログラム全体 {#brainfuck}
Brainf\*ck のインタプリタとプログラムになっている。 Brainf\*ck
とは、難解プログラミング言語のひとつであり、ここで説明するよりも
@@ -169,22 +163,19 @@ https://ja.wikipedia.org/wiki/Brainfuck
なお、`$🐘` はいわゆる main 関数であり、プログラムの実行部分である。
-{#emoji-selection}
-### 絵文字の選択
+### 絵文字の選択 {#emoji-selection}
おおよそ意味に合致するよう選んでいるが、`$🤡` と `$🎪`
は弊社デジタルサーカスにちなんでいる。 また、`$🐘` は PHP
のマスコットの象に由来する。
-{#strict-types}
-### strict_types
+### strict_types {#strict-types}
`declare` 文の `strict_types` に指定できるのは、`0` か `1`
の数値リテラルだが、 `0x0` や `0b1` のような値も受け付ける。 今回は、PHP
8.1 から追加された、`0O` または `0o` から始まる八進数リテラルを使った。
-{#url}
-### URL
+### URL {#url}
ソースコードのライセンスを示したこの部分だが、
@@ -195,8 +186,7 @@ https://creativecommons.org/publicdomain/zero/1.0/
完全に合法な PHP のコードである。 `https:` 部分はラベル、`//`
以降は行コメントになっている。
-{#numbers}
-### リテラルなしで数値を生成する
+### リテラルなしで数値を生成する {#numbers}
ソースコード中に、ほとんど数値リテラルが書かれていないことにお気づきだろうか。
PHP では、型変換を利用することで任意の整数を作り出すことができる。
@@ -220,16 +210,14 @@ assert(10 === +(![].+!![]));
によって文字列を `false` にし、`+` によって `false` を `0`
にし、さらにビット反転して `-1` にしている。
-{#conditionals}
-### `if` 文なしで条件分岐
+### `if` 文なしで条件分岐 {#conditionals}
三項演算子ないし `match` 式を使うことで、`if`
を一切書かずに条件分岐ができる。 また、`&amp;&amp;` / `||` も使えることがある。
遅延評価が不要なケースでは、`[$t, $f][$cond]`
のような形で分岐することもできる。
-{#loops}
-### `while`、`for` 文なしでループ
+### `while`、`for` 文なしでループ {#loops}
不動点コンビネータを使って無名再帰する
(詳しい説明は省略する。これらの単語で検索してほしい)。 ここでは、一般に
@@ -242,13 +230,11 @@ Z コンビネータとして知られるものを使った (`$z`)。
ので、 あまりに長い brainf*ck
プログラムを書くとスタックオーバーフローする。
-{#q2-riddle}
-# 第2問 riddle.php
+# 第2問 riddle.php {#q2-riddle}
ソースコードはこちら。実行には PHP 8.0 以上が必要なので注意。
-{filename="riddle.php"}
-```php
+```php filename="riddle.php"
<?php
/*********************************************************
@@ -291,8 +277,7 @@ foreach ($token as $x) {
ここでは、私の想定解を解説する。
-{#code-reading}
-## 読解
+## 読解 {#code-reading}
まずはソースコードを読んでいく。
@@ -328,8 +313,7 @@ $x = implode("\n", str_split($x, length: 5));
5文字ごとに区切ったあと、改行で結合している。
-{#hint}
-## ヒント
+## ヒント {#hint}
次に、ソースコードに書いてあるヒントを読んでいく。
@@ -343,8 +327,7 @@ $x = implode("\n", str_split($x, length: 5));
になるのではないかと予想される (なお、このことは、リポジトリの README
ファイルに追加ヒントとして書かれている)。
-{#solve}
-## 解く
+## 解く {#solve}
ここまでわかれば、あと一歩で解ける。すなわち、`0x14B499C` が `#`
に変換されるような `N` を見つければよい。
@@ -399,13 +382,11 @@ echo "N = $n\n";
これを実行すると、`N` が得られる。
-{#q3-toquine}
-# 第3問 toquine.php
+# 第3問 toquine.php {#q3-toquine}
ソースコードはこちら。
-{filename="toquine.php"}
-```php
+```php filename="toquine.php"
<?php
// License: https://creativecommons.org/publicdomain/zero/1.0/
@@ -445,11 +426,9 @@ $ php toquine.php | php | php | php | ...
実際にはもう少しパイプで繋げなければならない。
-{#commentary}
-## 解説
+## 解説 {#commentary}
-{#quine}
-### プログラム全体
+### プログラム全体 {#quine}
コメントにもあるとおり、これは quine (風) のプログラムになっている。
Quine
@@ -458,22 +437,19 @@ Quine
このプログラムは、実行すると自身とほとんど同じプログラムを出力する。
異なるのはトークンになっている部分のみである。
-{#tokens}
-### トークン
+### トークン {#tokens}
`$xs` がトークンに対応している。変換のロジックは `riddle.php`
とほぼ同じなので省略する。
-{#states}
-### 状態保持
+### 状態保持 {#states}
トークンの何文字目まで出力したかを、ソースコードを変えずに (quine
なので) 覚えておく必要がある。
このプログラムでは、トークンが出力されるとソースコードがだんだんと長くなっていくのを利用して、`__LINE__`
から情報を取得している。
-{#rot-13}
-### ROT 13
+### ROT 13 {#rot-13}
Quine は、素朴に書くとプログラムの一部が 2回記述されてしまう。
これがあまり美しくないので、`toquine.php` では、ROT 13
@@ -481,8 +457,7 @@ Quine は、素朴に書くとプログラムの一部が 2回記述されてし
それにしてもなぜこんなものが標準ライブラリに……。
-{#outro}
-# おわりに
+# おわりに {#outro}
解いていただいたみなさん、また、難易度調整につきあっていただいた社内のみなさん、ありがとうございました。
diff --git a/services/nuldoc/content/posts/2022-04-24/term-banner-write-tool-showing-banner-in-terminal.dj b/services/nuldoc/content/posts/2022-04-24/term-banner-write-tool-showing-banner-in-terminal.md
index 59c78e3..5293115 100644
--- a/services/nuldoc/content/posts/2022-04-24/term-banner-write-tool-showing-banner-in-terminal.dj
+++ b/services/nuldoc/content/posts/2022-04-24/term-banner-write-tool-showing-banner-in-terminal.md
@@ -13,8 +13,7 @@ remark = "公開"
date = "2022-04-27"
remark = "-f オプションについて追記"
---
-{#intro}
-# はじめに
+# はじめに {#intro}
こんなものを作った。
@@ -28,8 +27,7 @@ $ term-banner 'Hello, World!' 'こんにちは、' '世界!'
リポジトリはこちら: https://github.com/nsfisis/term-banner
-{#motivation}
-# Motivation
+# Motivation {#motivation}
以前、[`big-clock-mode`](https://github.com/nsfisis/big-clock-mode)
という似たようなプログラムを書いた。 これは tmux の `:clock-mode`
@@ -50,8 +48,7 @@ $ term-banner 'Hello, World!' 'こんにちは、' '世界!'
そんなわけで、「任意の文字列をターミナルに表示する」プログラムを書く運びとなった。
まあ、作らなくても探せばあると思うが、作りたいものは作りたいので知ったことではない。
-{#program}
-# プログラム
+# プログラム {#program}
全体の流れは次のようになっている。
@@ -67,8 +64,7 @@ $ term-banner 'Hello, World!' 'こんにちは、' '世界!'
で実行ファイルに埋め込んでいるので、ビルド後はワンバイナリで動く。
仕事ではスクリプト言語ばかり書いているが、やはりコンパイル言語はいい。
-{#font}
-# フォント
+# フォント {#font}
フリーの 8x8
ビットマップフォントである、 [美咲フォント 2021-05-05a 版](https://littlelimit.net/misaki.htm) を使わせていただいた。
@@ -89,12 +85,10 @@ $ term-banner 'Hello, World!' 'こんにちは、' '世界!'
ゴシック体と明朝体があったが、私の好みで明朝体の方にした。
ただ、ゴシック体の方が見やすい気がするので、フォントを選べるように後ほど拡張するかもしれない。
-{editat="2022-04-27" operation="追記"}
-::: edit
+:::edit{editat="2022-04-27" operation="追記"}
`-f` オプションで選べるようにした。
:::
-{#outro}
-# おわりに
+# おわりに {#outro}
あなたもターミナルに住んでみませんか?
diff --git a/services/nuldoc/content/posts/2022-05-01/phperkaigi-2022.dj b/services/nuldoc/content/posts/2022-05-01/phperkaigi-2022.md
index 6758f26..915cc11 100644
--- a/services/nuldoc/content/posts/2022-05-01/phperkaigi-2022.dj
+++ b/services/nuldoc/content/posts/2022-05-01/phperkaigi-2022.md
@@ -13,8 +13,7 @@ tags = [
date = "2022-05-01"
remark = "公開"
---
-{#intro}
-# はじめに
+# はじめに {#intro}
2022-04-09 から 2022-04-11 にかけて開催された、 [PHPerKaigi 2022](https://phperkaigi.jp/2022/) に、
一般参加者として参加した。
@@ -23,11 +22,9 @@ remark = "公開"
昨年のレポートは [こちら](/posts/2021-03-30/phperkaigi-2021) 。
-{#comments}
-# 感想
+# 感想 {#comments}
-{#great-sessions}
-## 厳選おすすめトーク
+## 厳選おすすめトーク {#great-sessions}
多くの素晴らしいトークの中から、特におすすめのものを 5つ選んだ。
是非聞いてほしい。引用部分は、リンク先プロポーザルから引用している。
@@ -84,20 +81,17 @@ remark = "公開"
>
> このセッションでは、そういう状況で私がチーム外からジョインし、聴き役に徹しながら見える化することで状況を改善していった取り組みを紹介します。同じように大きなチームやリモートワークで難しさを感じている人に、難しさの原因への気づきや取り組みへのヒントがあれば幸いです。
-{#token-quizzes}
-## トークン問題の作成
+## トークン問題の作成 {#token-quizzes}
今回は、PHPer チャレンジ用に弊社のトークン問題を 3題作成した。
こちらについては [別途記事にしている](/posts/2022-04-09/phperkaigi-2022-tokens) ので、そちらを参照されたい。
-{#phper-challenge}
-## PHPer チャレンジ
+## PHPer チャレンジ {#phper-challenge}
[1位](https://fortee.jp/phperkaigi-2022/challenge) になった。
また、賞品として [Echo Show 15](https://www.amazon.co.jp/dp/B08MQNJC9Z) をいただいた。
-{#conference}
-## カンファレンス全体への感想
+## カンファレンス全体への感想 {#conference}
[去年の参加レポ](/posts/2021-03-30/phperkaigi-2021) では、こんなことを書いた。
@@ -115,8 +109,7 @@ remark = "公開"
また、今年はオフラインとオンラインのハイブリッド開催であったが、去年の全オンラインと比べて、オンライン参加の体験が落ちていなかったのは、特筆すべきであろう。
今年は 3回目のワクチン接種が間に合わなかったこともあり現地参加は見送ったのだが、来年は是非オフラインで参加したい。
-{#next-year}
-# そして来年へ……?
+# そして来年へ……? {#next-year}
PHPerKaigi 2023 があるかどうか存じ上げないが、あるとすれば、次の 4つを目標としたい。
diff --git a/services/nuldoc/content/posts/2022-08-27/php-conference-okinawa-code-golf.dj b/services/nuldoc/content/posts/2022-08-27/php-conference-okinawa-code-golf.md
index 32acd01..36d9a05 100644
--- a/services/nuldoc/content/posts/2022-08-27/php-conference-okinawa-code-golf.dj
+++ b/services/nuldoc/content/posts/2022-08-27/php-conference-okinawa-code-golf.md
@@ -14,8 +14,7 @@ tags = [
date = "2022-08-27"
remark = "公開"
---
-{#intro}
-# はじめに
+# はじめに {#intro}
本日 [PHP カンファレンス沖縄 2022](https://phpcon.okinawa.jp/) が開催された (らしい)。
@@ -24,8 +23,7 @@ remark = "公開"
* ツイート: https://twitter.com/m3m0r7/status/1563397620231712772
* スライド: https://speakerdeck.com/memory1994/php-conference-okinawa-2022-extra?slide=3
-{#solution}
-# 解
+# 解 {#solution}
細かいレギュレーションは不明だったので、勝手に定めた。
@@ -58,25 +56,21 @@ echo implode(', ', $r ?? []);
?>]
```
-{#techniques}
-# 使用したテクニック
+# 使用したテクニック {#techniques}
-{#exponential-notation}
-## 指数表記
+## 指数表記 {#exponential-notation}
割と多くの言語のゴルフで使えるテクニック。
`e` を用いた指数表記で、大きな数を短く表す。
このコードでは `10000`、`5000`、`2000`、`1000` を指数表記している。
-{#shorten-loop}
-## foreach や for の中身を1つの文に
+## foreach や for の中身を1つの文に {#shorten-loop}
`foreach`、`for`、`if` などの後ろには、
通常 `{` を続けて複数の文を連ねるが、中身の文を1つにしてしまえば、`{` と `}` を省略できる。
C言語などでも使える。
-{#omit-initialization}
-## $r に初期値を入れない
+## $r に初期値を入れない {#omit-initialization}
PHP では、`$r[] = ......` のような配列の末尾に追加する式を実行したとき、
`$r` が未定義だった場合は `$r` を勝手に定義して空の配列で初期化してくれる。
@@ -88,13 +82,11 @@ PHP では、`$r[] = ......` のような配列の末尾に追加する式を実
もし 0 が渡されたケースを無視するなら、これが不要になるので 4 バイト縮む。
-{#put-text-outside-php-tag}
-## PHP タグの外に文字列を置く
+## PHP タグの外に文字列を置く {#put-text-outside-php-tag}
PHP では、`&lt;?php` `?&gt;` で囲われた部分の外側にある文字列は、そのまま出力される。
今回のケースでは、先頭と末尾に必ず `[` と `]` を出力するので、そのまま書いてやればよい。
-{#outro}
-# おわりに
+# おわりに {#outro}
最後になりましたが、 [めもりー](https://twitter.com/m3m0r7) さん、楽しい問題をありがとうございました。
diff --git a/services/nuldoc/content/posts/2022-08-31/support-for-communty-is-employee-benefits.dj b/services/nuldoc/content/posts/2022-08-31/support-for-communty-is-employee-benefits.md
index aae93d3..68c92de 100644
--- a/services/nuldoc/content/posts/2022-08-31/support-for-communty-is-employee-benefits.dj
+++ b/services/nuldoc/content/posts/2022-08-31/support-for-communty-is-employee-benefits.md
@@ -10,10 +10,9 @@ toc = false
date = "2022-08-31"
remark = "公開"
---
-{#intro}
-# はじめに
+# はじめに {#intro}
-*注: これは私個人の意見であり、所属する組織を代表するものではありません。*
+**注: これは私個人の意見であり、所属する組織を代表するものではありません。**
先日、私の勤める [デジタルサーカス株式会社](https://www.dgcircus.com/) が
[PHP Foundation](https://opencollective.com/phpfoundation) へ $2,000 の寄付をおこないました。
@@ -22,8 +21,7 @@ remark = "公開"
本件を社内でしつこく推進した1人として、推進の理由等を書き残しておきます。
-{#why}
-# なぜ?
+# なぜ? {#why}
組織としての寄付理由は前掲した記事に譲るとして、ここでは、私が社内でこの件を推進した理由について書くことにします。
@@ -46,8 +44,7 @@ OSS を金銭的に支援したり、技術カンファレンスへ協賛した
以上が、私が社内で寄付の件を進めた (かなり私的な) 理由です。
-{#outro}
-# おわりに
+# おわりに {#outro}
最終的に社としての寄付まで漕ぎ着けられたのは、もちろん私の力ではなく役員の方々の決定によるものです。
この場を借りて感謝申し上げます。
diff --git a/services/nuldoc/content/posts/2022-09-29/write-fizzbuzz-in-php-2-letters-per-line.dj b/services/nuldoc/content/posts/2022-09-29/write-fizzbuzz-in-php-2-letters-per-line.md
index c23341d..3665b7f 100644
--- a/services/nuldoc/content/posts/2022-09-29/write-fizzbuzz-in-php-2-letters-per-line.dj
+++ b/services/nuldoc/content/posts/2022-09-29/write-fizzbuzz-in-php-2-letters-per-line.md
@@ -15,28 +15,22 @@ remark = "公開"
date = "2022-09-29"
remark = "小さな文言の修正・変更"
---
-{#intro}
-# 記事の構成について
+# 記事の構成について {#intro}
この記事は、普通の fizzbuzz を徐々に変形して最終形にしていく、という構成で書かれている。
最終形を見てどのような仕組みで動いているのか解読してから解説を読みたい、というかたがいれば、
[このページ](https://gist.github.com/nsfisis/04c227d5a419867472a0b23a83ad2919#file-fizzbuzz-php-2-letters-per-line-and-supports-php-8-x-without-warnings)
にソースコードがあるので、そちらを先に見てほしい。
-{#regulations}
-# レギュレーション
+# レギュレーション {#regulations}
PHP で、次のような制約の下に fizzbuzz を書いた。
* 1行あたりの文字数は2文字までに収めること (ただし `&lt;?php` タグは除く)
-
* 厳密な定義: `&lt;?php` タグ以降のソースコードが、2 byte ごとにラインフィード (LF) で区切られること
-
* スペースやタブを使用しないこと
* ループのアンロールをしないこと
-
* 100 回ループの代わりに 100 回コードをコピペ、というのは禁止
-
* PHP 7.4〜8.1 で動作すること
* 実行時に Notice や Warning が出ないこと
* 標準的なインストール構成の PHP で実現できること (デフォルトで有効になっていない拡張等を使わないこと)
@@ -46,8 +40,7 @@ PHP で、次のような制約の下に fizzbuzz を書いた。
を使うことができ、文字どおり1行2文字で書ける。
ただ、このオプションはデフォルト off になっている環境が多いようなので、今回は使わないことにした。
-{#problems}
-# 主な障害
+# 主な障害 {#problems}
1行あたりの文字数など、適当に改行を挟めばいいだけではないのか?
@@ -166,11 +159,9 @@ a'
これらの障害をどのように乗り越えるのか、次節から見ていく。
-{#commentary}
-# 解説
+# 解説 {#commentary}
-{#normal-fizzbuzz}
-## 普通の (?) fizzbuzz
+## 普通の (?) fizzbuzz {#normal-fizzbuzz}
まずは普通に書くとしよう。
@@ -184,8 +175,7 @@ for ($i = 1; $i < 100; $i++) {
素直に書いた fizzbuzz とは言い難いが、このくらいは普通だということにしておかないと、この先がやっていられないので許してほしい。
-{#remove-keywords}
-## `for` の排除
+## `for` の排除 {#remove-keywords}
`for` は、3文字もある長いキーワードである。
こんなものは使えない。`array_` 系の関数を使って、適当に置き換えるとしよう。
@@ -205,8 +195,7 @@ printf((($i % 3 ? '' : 'Fizz') . ($i % 5 ? '' : 'Buzz') ?: $i) . "\n"),
`for` よりも長いトークンが現れてしまったが、これは次節で直すことにする。
なお、`echo` は文 (statement) であり式 (expression) ではないので、式である `printf` に置き換えた。
-{#shorten-function-invocation}
-## 関数呼び出しの短縮
+## 関数呼び出しの短縮 {#shorten-function-invocation}
`range`、`array_walk`、`printf` は長すぎるのでどうにかせねばならない。
ここで、PHP の可変関数を使う。可変関数とは、関数名が文字列として入った変数を経由して、関数を呼び出す機能である。
@@ -231,8 +220,7 @@ $p((($i % 3 ? '' : 'Fizz') . ($i % 5 ? '' : 'Buzz') ?: $i) . "\n"),
また `'Fizz'` や `'Buzz'` はどうやって 1 行 2 文字に収めるのか。
次のテクニックへ移ろう。
-{#incompatible-solution}
-## 余談: PHP 8.x で動作しなくてもいいなら
+## 余談: PHP 8.x で動作しなくてもいいなら {#incompatible-solution}
今回使ったテクニックを説明する前に、余談として、文字列リテラルの短縮法として今回採用しなかったものを紹介する。
@@ -267,8 +255,7 @@ F.
むしろ、このことがわかっていたからこそ PHP 8.x での動作を要件に課したところがある。
-{#shorten-string-literals}
-## 文字列リテラルの短縮
+## 文字列リテラルの短縮 {#shorten-string-literals}
実際に使った手法の説明に移る。
@@ -347,8 +334,7 @@ echo "$r\n";
備考: `Buzz` 中にある小文字の `u` は、このロジックだと non-printable な文字になってしまう。
ここまでのテクニックを駆使すれば回避するのはそう難しくないので、考えてみてほしい。
-{#stretched-fizzbuzz}
-# 完成系
+# 完成系 {#stretched-fizzbuzz}
完成したものがこちら。
@@ -504,16 +490,14 @@ $i
);
```
-{#outro}
-# 感想など
+# 感想など {#outro}
PHP は、スクリプト言語の中だとシンタックスシュガーが少ない (体感)。
この挑戦は不可能に思われたが、PHP マニュアルとにらめっこしていたらなんとかなった。
みんなもプログラムを細長くしよう。
-{#alternative-solution}
-# 余談2: 別解
+# 余談2: 別解 {#alternative-solution}
PHP では、バッククォートを使ってシェルを呼び出せる。
これは `shell_exec` 関数と等価である。
diff --git a/services/nuldoc/content/posts/2022-10-23/phperkaigi-2023-unused-token-quiz-1.dj b/services/nuldoc/content/posts/2022-10-23/phperkaigi-2023-unused-token-quiz-1.md
index 8567c71..c28df51 100644
--- a/services/nuldoc/content/posts/2022-10-23/phperkaigi-2023-unused-token-quiz-1.dj
+++ b/services/nuldoc/content/posts/2022-10-23/phperkaigi-2023-unused-token-quiz-1.md
@@ -12,8 +12,7 @@ tags = [
date = "2022-10-23"
remark = "公開"
---
-{#intro}
-# はじめに
+# はじめに {#intro}
2023 年 3 月 23 日から 25 日にかけて開催予定 (記事執筆時点) の、
[PHPerKaigi 2023](https://phperkaigi.jp/2023/) において、
@@ -27,8 +26,7 @@ remark = "公開"
10 月から 2 月まで、毎月 1 記事ずつ公開していく予定 (忘れていなければ)。
-{#quiz}
-# 問題
+# 問題 {#quiz}
注意: これはボツ問なので、得られたトークンを PHPerKaigi で入力してもポイントにはならない。
@@ -56,8 +54,7 @@ if (md5($t) === '056e831a4146bf123e8ea16613303d2e') {
}
```
-{#how-to-obtain-token}
-# トークン入手方法
+# トークン入手方法 {#how-to-obtain-token}
ソースを見るとわかるとおり、`$argv[1]` を参照している。
それを `$π` なる変数に代入しているので、円周率を渡してみる。
@@ -85,8 +82,7 @@ Token: #YO
めでたくトークン「`#YO`」が手に入った。
-{#commentary}
-# 解説
+# 解説 {#commentary}
短いので頭から追っていく。
@@ -143,8 +139,7 @@ if (md5($t) === '056e831a4146bf123e8ea16613303d2e') {
最後にトークンのハッシュ値を見て、想定解かどうかを確認する。
-{#outro}
-# おわりに
+# おわりに {#outro}
円周率を何桁も計算して ASCII コード経由で文字列化すれば、トークンっぽいものがどこかで出てくるのではないか、と考えて生まれた作品。
diff --git a/services/nuldoc/content/posts/2022-10-28/setup-server-for-this-site.dj b/services/nuldoc/content/posts/2022-10-28/setup-server-for-this-site.md
index 6fed329..4af8854 100644
--- a/services/nuldoc/content/posts/2022-10-28/setup-server-for-this-site.dj
+++ b/services/nuldoc/content/posts/2022-10-28/setup-server-for-this-site.md
@@ -15,8 +15,7 @@ remark = "公開"
date = "2023-08-30"
remark = "ssh_config に IdentitiesOnly yes を追加"
---
-{#intro}
-# はじめに
+# はじめに {#intro}
これまでこの blog は GitHub Pages でホストしていたのだが、先日 VPS に移行した。
そのときにおこなったサーバのセットアップ作業を書き残しておく。
@@ -24,23 +23,19 @@ remark = "ssh_config に IdentitiesOnly yes を追加"
未来の自分へ: 特に自動化してないので、せいぜい苦しんでくれ。
-{#vps}
-# VPS
+# VPS {#vps}
[さくらの VPS](https://vps.sakura.ad.jp/) の 2 GB プラン。
そこまで真面目に選定していないので、困ったら移動するかも。
-{#preparation}
-# 事前準備
+# 事前準備 {#preparation}
-{#hostname}
-## サーバのホスト名を決める
+## サーバのホスト名を決める {#hostname}
モチベーションが上がるという効能がある。今回は藤原定家から取って `teika` にした。
たいていいつも源氏物語の帖か小倉百人一首の歌人から選んでいる。
-{#ssh-key}
-## SSH の鍵生成
+## SSH の鍵生成 {#ssh-key}
ローカルマシンで鍵を生成する。
@@ -52,8 +47,7 @@ $ ssh-keygen -t ed25519 -b 521 -f ~/.ssh/github2teika.key
`teika.key` はローカルからサーバへの接続用、`github2teika.key` は、
GitHub Actions からサーバへのデプロイ用。
-{#ssh-config}
-## SSH の設定
+## SSH の設定 {#ssh-config}
`.ssh/config` に設定しておく。
@@ -66,16 +60,13 @@ Host teika
IdentitiesOnly yes
```
-{#basic-setup}
-# 基本のセットアップ
+# 基本のセットアップ {#basic-setup}
-{#login}
-## SSH 接続
+## SSH 接続 {#login}
VPS 契約時に設定した管理者ユーザとパスワードを使ってログインする。
-{#user}
-## ユーザを作成する
+## ユーザを作成する {#user}
管理者ユーザで作業すると危ないので、メインで使うユーザを作成する。
`sudo` グループに追加して `sudo` できるようにし、`su` で切り替え。
@@ -87,15 +78,13 @@ $ su **********
$ cd
```
-{#hostname}
-## ホスト名を変える
+## ホスト名を変える {#hostname}
```shell-session
$ sudo hostname teika
```
-{#public-key}
-## 公開鍵を置く
+## 公開鍵を置く {#public-key}
```shell-session
$ mkdir ~/.ssh
@@ -106,8 +95,7 @@ $ vi ~/.ssh/authorized_keys
`authorized_keys` には、ローカルで生成した `~/.ssh/teika.key.pub` と
`~/.ssh/github2teika.key.pub` の内容をコピーする。
-{#ssh-config}
-## SSH の設定
+## SSH の設定 {#ssh-config}
SSH の設定を変更し、少しでも安全にしておく。
@@ -127,8 +115,7 @@ $ sudo systemctl restart sshd
$ sudo systemctl status sshd
```
-{#ssh-connect}
-## SSH で接続確認
+## SSH で接続確認 {#ssh-connect}
今の SSH セッションは閉じずに、ターミナルを別途開いて疎通確認する。
セッションを閉じてしまうと、SSH の設定に不備があった場合に締め出しをくらう。
@@ -137,8 +124,7 @@ $ sudo systemctl status sshd
$ ssh teika
```
-{#close-ports}
-## ポートの遮断
+## ポートの遮断 {#close-ports}
デフォルトの 22 番を閉じ、設定したポートだけ空ける。
@@ -152,8 +138,7 @@ $ sudo ufw status
ここでもう一度 SSH の接続確認を挟む。
-{#ssh-key-for-github}
-## GitHub 用の SSH 鍵
+## GitHub 用の SSH 鍵 {#ssh-key-for-github}
GitHub に置いてある private リポジトリをサーバから clone したいので、SSH 鍵を生成して置いておく。
@@ -185,8 +170,7 @@ Host github.com
$ ssh -T github.com
```
-{#upgrade-packages}
-## パッケージの更新
+## パッケージの更新 {#upgrade-packages}
```shell-session
$ sudo apt update
@@ -196,30 +180,25 @@ $ sudo apt upgrade
$ sudo apt autoremove
```
-{#site-hosting-setup}
-# サイトホスティング用のセットアップ
+# サイトホスティング用のセットアップ {#site-hosting-setup}
-{#dns}
-## DNS に IP アドレスを登録する
+## DNS に IP アドレスを登録する {#dns}
このサーバは固定の IP アドレスがあるので、`A` レコードに直接入れるだけで済んだ。
-{#install-softwares}
-## 使うソフトウェアのインストール
+## 使うソフトウェアのインストール {#install-softwares}
```shell-session
$ sudo apt install docker docker-compose git make
```
-{#docker}
-## メインユーザが Docker を使えるように
+## メインユーザが Docker を使えるように {#docker}
```shell-session
$ sudo adduser ********** docker
```
-{#open-http-ports}
-## HTTP/HTTPS を通す
+## HTTP/HTTPS を通す {#open-http-ports}
80 番と 443 番を空ける。
@@ -230,8 +209,7 @@ $ sudo ufw reload
$ sudo ufw status
```
-{#clone-repositories}
-## リポジトリのクローン
+## リポジトリのクローン {#clone-repositories}
```shell-session
$ cd
@@ -240,23 +218,20 @@ $ cd nsfisis.dev
$ git submodule update --init
```
-{#certbot}
-## certbot で証明書取得
+## certbot で証明書取得 {#certbot}
```shell-session
$ docker-compose up -d acme-challenge
$ make setup
```
-{#run-server}
-## サーバを稼動させる
+## サーバを稼動させる {#run-server}
```shell-session
$ make serve
```
-{#outro}
-# 感想
+# 感想 {#outro}
(業務でなく) 個人だと数年ぶりのサーバセットアップで、これだけでも割と時間を食ってしまった。
とはいえ式年遷宮は楽しいので、これからも定期的にやっていきたい。
diff --git a/services/nuldoc/content/posts/2022-11-19/phperkaigi-2023-unused-token-quiz-2.dj b/services/nuldoc/content/posts/2022-11-19/phperkaigi-2023-unused-token-quiz-2.md
index bd752c2..9502489 100644
--- a/services/nuldoc/content/posts/2022-11-19/phperkaigi-2023-unused-token-quiz-2.dj
+++ b/services/nuldoc/content/posts/2022-11-19/phperkaigi-2023-unused-token-quiz-2.md
@@ -12,8 +12,7 @@ tags = [
date = "2022-11-19"
remark = "公開"
---
-{#intro}
-# はじめに
+# はじめに {#intro}
2023 年 3 月 23 日から 25 日にかけて開催予定 (記事執筆時点) の [PHPerKaigi 2023](https://phperkaigi.jp/2023/) において、
昨年と同様に、弊社 [デジタルサーカス株式会社](https://www.dgcircus.com/) からトークン問題を出題予定である。
@@ -26,8 +25,7 @@ remark = "公開"
その 1 はこちら: [PHPerKaigi 2023: ボツになったトークン問題 その 1](/posts/2022-10-23/phperkaigi-2023-unused-token-quiz-1/)
-{#quiz}
-# 問題
+# 問題 {#quiz}
注意: これはボツ問なので、得られたトークンを PHPerKaigi で入力してもポイントにはならない。
@@ -46,8 +44,7 @@ remark = "公開"
"And Then There Were None" (そして誰もいなくなった) と名付けた作品。変則 quine (自分自身と同じソースコードを出力するプログラム) になっている。
-{#how-to-obtain-token}
-# トークン入手方法
+# トークン入手方法 {#how-to-obtain-token}
実行してみると、次のような出力が得られる。
@@ -96,8 +93,7 @@ P
トークン「#WELOVEPHP」が手に入った。
-{#commentary}
-# 解説
+# 解説 {#commentary}
一見すると同じ行が 10 行並んでいるだけなのにも関わらず、なぜそれぞれの行で出力が変わるのか。ソースコードをコピーして、適当なエディタに貼り付けるとわかりやすい。
@@ -109,7 +105,7 @@ Vim で開くと次のようになる (1 行目を抜粋)。
`&lt;200b&gt;` と表示されているのは、Unicode の U+200b で、ゼロ幅スペースである。
-::: note
+:::note
エディタによっては、ゼロ幅スペースが見えないことがある。VSCode ではブラウザと同様に不可視だった。
:::
@@ -131,8 +127,7 @@ $s='<200b><?php printf((isset($s)?fn($s)=>trim($s,"<200b>"):fn($s)=>chr(strlen($
デコード部以外の部分は、quine のための記述である。
-{#outro}
-# おわりに
+# おわりに {#outro}
[CVE-2021-42574](https://blog.rust-lang.org/2021/11/01/cve-2021-42574.html) に着想を得た作品。この脆弱性は、Unicode の制御文字である left-to-right mark と right-to-left mark を利用し、ソースコードの実際の内容を欺く、というもの。簡単のためゼロ幅スペースを用いることとし、ついでに quine にもするとこうなった。
diff --git a/services/nuldoc/content/posts/2023-01-10/phperkaigi-2023-unused-token-quiz-3.dj b/services/nuldoc/content/posts/2023-01-10/phperkaigi-2023-unused-token-quiz-3.md
index 9cbb15b..c55cf5b 100644
--- a/services/nuldoc/content/posts/2023-01-10/phperkaigi-2023-unused-token-quiz-3.dj
+++ b/services/nuldoc/content/posts/2023-01-10/phperkaigi-2023-unused-token-quiz-3.md
@@ -12,8 +12,7 @@ tags = [
date = "2023-01-10"
remark = "公開"
---
-{#intro}
-# はじめに
+# はじめに {#intro}
2023 年 3 月 23 日から 25 日にかけて開催予定 (記事執筆時点) の [PHPerKaigi 2023](https://phperkaigi.jp/2023/) において、
昨年と同様に、弊社 [デジタルサーカス株式会社](https://www.dgcircus.com/) からトークン問題を出題予定である。
@@ -28,8 +27,7 @@ remark = "公開"
* その 1 はこちら: [PHPerKaigi 2023: ボツになったトークン問題 その 1](/posts/2022-10-23/phperkaigi-2023-unused-token-quiz-1/)
* その 2 はこちら: [PHPerKaigi 2023: ボツになったトークン問題 その 2](/posts/2022-11-19/phperkaigi-2023-unused-token-quiz-2/)
-{#quiz}
-# 問題
+# 問題 {#quiz}
注意: これはボツ問なので、得られたトークンを PHPerKaigi で入力してもポイントにはならない。
@@ -152,16 +150,13 @@ function g() {
トークンは PHP の式になっていて、評価すると `Hello, World!` という文字列になる。PHPer チャレンジのトークンには空白を含められないという制約があるが、こういった形でトークンにすれば回避できる。
-{#commentary}
-# 解説
+# 解説 {#commentary}
-{#summary}
-## 概要
+## 概要 {#summary}
例外が発生した行数にデータをエンコードし、それを `catch` で捕まえて表示している。
-{#chain-of-exceptions}
-## 例外オブジェクトの連鎖
+## 例外オブジェクトの連鎖 {#chain-of-exceptions}
[`Exception`](https://www.php.net/class.Exception) や [`Error`](https://www.php.net/class.Error) には `$previous` というプロパティがあり、コンストラクタの第3引数から渡すことができる。主に 2つの用法がある:
@@ -189,8 +184,7 @@ try {
この知識を元に、トークンの出力部を解析してみる。
-{#output}
-## 出力部の解析
+## 出力部の解析 {#output}
出力部をコメントや改行を追加して再掲する:
@@ -220,8 +214,7 @@ try {
[^ras-syndrome]: RAS syndrome
-{#data-construction}
-## データ構成部の解析
+## データ構成部の解析 {#data-construction}
`f()` の定義を再掲する (エラーオブジェクトの行数を利用しているので、一部分だけ抜き出すと値が変わることに注意):
@@ -276,8 +269,7 @@ function g() {
`f()` に `0` を渡したときは 12 行目にある `match` の `0` でゼロ除算が起こるので、行数が 12 となったエラーが投げられる。出力部ではこれに 23 を足した数を ASCII コードとして表示しているのだった。 `12 + 23` は `35`、ASCII コードでは `#` である。これがトークンの 1文字目にあたる。
-{#outro}
-# おわりに
+# おわりに {#outro}
「行数」というのはトークン文字列をデコードする対象として優れている。
diff --git a/services/nuldoc/content/posts/2023-03-10/rewrite-this-blog-generator.dj b/services/nuldoc/content/posts/2023-03-10/rewrite-this-blog-generator.md
index a4ccf87..ef1c034 100644
--- a/services/nuldoc/content/posts/2023-03-10/rewrite-this-blog-generator.dj
+++ b/services/nuldoc/content/posts/2023-03-10/rewrite-this-blog-generator.md
@@ -9,8 +9,7 @@ tags = []
date = "2023-03-10"
remark = "公開"
---
-{#intro}
-# はじめに
+# はじめに {#intro}
このブログを構築するシステムを書き直したのは 2度目である。
元々立ち上げた当初は、静的サイトジェネレータである [Hugo](https://gohugo.io/) を使っていた。
@@ -19,8 +18,7 @@ remark = "公開"
この記事では、移行の理由などを (主に将来の私へ向けて) 書き記しておく。
-{#from-hugo-to-asciidoctor}
-# Hugo から Asciidoctor へ
+# Hugo から Asciidoctor へ {#from-hugo-to-asciidoctor}
最初に断っておくと、Hugo は大変に優れた静的サイトジェネレータである。移行の理由の大半は、自分でジェネレータを書きたかったからに他ならない。
実のところ、この記事を執筆している現在、自作ジェネレータは Hugo よりも機能が劣っている。
@@ -40,8 +38,7 @@ AsciiDoc は Markdown に比べると普及していないが、上記の欠点
なお、Hugo は AsciiDoc もサポートしているのだが、AsciiDoc を使う場合 Asciidoctor を別途インストールする必要があり、それならば最初から Asciidoctor でよかろうと移行を決めた。
-{#from-asciidoctor-to-my-own-generator}
-# Asciidoctor から自前のジェネレータへ
+# Asciidoctor から自前のジェネレータへ {#from-asciidoctor-to-my-own-generator}
AsciiDoc は良いフォーマットだが、私には 1点不満があった。それは、高い表現力を担保するために記号が使い倒されており、エスケープが難しいという点だ (具体例を挙げたいのだが、何だったか覚えていない)。これは、多種多様な記号類を入力する必要のある技術ブログにとっては辛い問題である。この問題を解決するため、
@@ -62,8 +59,7 @@ XML という機械処理しやすいフォーマットを選ぶことには、
欠点は軽量マークアップ言語と比べて冗長であることだが、書く際は補完などを用いるのでそれほど気にならない。
結局のところ、技術ブログの執筆を律速するのは調査と文章の記述であり、マークアップの手段は執筆時間に大した影響を与えない。
-{#outro}
-# おわりに
+# おわりに {#outro}
2度のリライトを経て、記事のフォーマットとサイトジェネレータを上から下まで掌握した。
今後も改善のアイデアは多数あるので、じわじわと進めていきたいところだ。
diff --git a/services/nuldoc/content/posts/2023-04-01/implementation-of-minimal-png-image-encoder.dj b/services/nuldoc/content/posts/2023-04-01/implementation-of-minimal-png-image-encoder.md
index 55d1519..6f4fb3c 100644
--- a/services/nuldoc/content/posts/2023-04-01/implementation-of-minimal-png-image-encoder.dj
+++ b/services/nuldoc/content/posts/2023-04-01/implementation-of-minimal-png-image-encoder.md
@@ -9,15 +9,13 @@ tags = []
date = "2023-04-01"
remark = "公開"
---
-{#intro}
-# はじめに
+# はじめに {#intro}
この記事では、PNG 画像として valid な範囲で最大限手抜きしたエンコーダを書く。
PNG 画像に対応したビューアであれば読み込めるが、圧縮効率については一切考えない。
また、実装には Go 言語を使うが、Go の標準ライブラリにあるさまざまなアルゴリズム (PNG 画像に関係する範囲だと、zlib や CRC32、Adler-32 など) は使わない。
-{#basic-structure-of-png}
-# PNG ファイルの基本構造
+# PNG ファイルの基本構造 {#basic-structure-of-png}
PNG ファイルの基本構造は次のようになっている。
@@ -31,8 +29,7 @@ Chunk には画像データを入れる IDAT chunk、パレットデータを入
次節で、それぞれの具体的な構造を確認しつつ実装していく。
-{#implement-png-encoder}
-# PNG のエンコーダを実装する
+# PNG のエンコーダを実装する {#implement-png-encoder}
以下のソースコードをベースにする。
今回 PNG のデコーダは扱わないので、読み込みには Go の標準ライブラリ `image/png` を用いる。
@@ -80,8 +77,7 @@ func writePng(w io.Writer, img image.Image) {
以降は、`writeSignature` や `writeChunkIhdr` などを実装していく。
-{#png-signature}
-## PNG signature
+## PNG signature {#png-signature}
PNG signature は、PNG 画像の先頭に固定で付与されるバイト列で、8 バイトからなる。
@@ -118,8 +114,7 @@ func writeSignature(w io.Writer) {
`encoding/binary` パッケージの `binary.Write` を使い、固定の 8 バイトを書き込む。
-{#structure-of-chunk}
-## Chunk の構造
+## Chunk の構造 {#structure-of-chunk}
IHDR chunk に進む前に、chunk 一般の構造を確認する。
@@ -187,36 +182,26 @@ PNG では基本的に big endian を使うことに注意する。
準備ができたところで、具体的な chunk をエンコードしていく。
-{#ihdr-chunk}
-## IHDR chunk
+## IHDR chunk {#ihdr-chunk}
IHDR chunk は最初に配置される chunk である。次のようなデータからなる。
1. 画像の幅 (符号なし 4 バイト整数)
1. 画像の高さ (符号なし 4 バイト整数)
1. ビット深度 (符号なし 1 バイト整数)
-
- * 1 色に使うビット数。1 ピクセルに 24 bit 使う truecolor 画像では 8 になる
-
+ * 1 色に使うビット数。1 ピクセルに 24 bit 使う truecolor 画像では 8 になる
1. 色タイプ (符号なし 1 バイト整数)
-
- * 0: グレースケール
- * 2: Truecolor (今回はこれに決め打ち)
- * 3: パレットのインデックス
- * 4: グレースケール + アルファ
- * 6: Truecolor + アルファ
-
+ * 0: グレースケール
+ * 2: Truecolor (今回はこれに決め打ち)
+ * 3: パレットのインデックス
+ * 4: グレースケール + アルファ
+ * 6: Truecolor + アルファ
1. 圧縮方式 (符号なし 1 バイト整数)
-
- * PNG の仕様書に 0 しか定義されていないので 0 で固定
-
+ * PNG の仕様書に 0 しか定義されていないので 0 で固定
1. フィルタ方式 (符号なし 1 バイト整数)
-
- * PNG の仕様書に 0 しか定義されていないので 0 で固定
-
+ * PNG の仕様書に 0 しか定義されていないので 0 で固定
1. インターレース方式 (符号なし 1 バイト整数)
-
- * 今回はインターレースしないので 0
+ * 今回はインターレースしないので 0
今回ほとんどのデータは決め打ちするので、データに応じて変わるのは width と height だけになる。コードは次のようになる。
@@ -237,13 +222,11 @@ func writeChunkIhdr(w io.Writer, width, height uint32) {
}
```
-{#idat-chunk}
-## IDAT chunk
+## IDAT chunk {#idat-chunk}
IDAT chunk は、実際の画像データが格納された chunk である。IDAT chunk は deflate アルゴリズムにより圧縮され、zlib 形式で格納される。
-{#zlib}
-### Zlib
+### Zlib {#zlib}
まずは zlib について確認する。おおよそ次のような構造になっている。
@@ -277,7 +260,7 @@ func adler32(buf []byte) uint32 {
「データ」の部分には圧縮したデータが入るのだが、真面目に deflate アルゴリズムを実装する必要はない。Zlib には無圧縮のデータブロックを格納することができるので、これを使う。本来は、データの圧縮効率の悪いランダムなデータをそのまま格納するためのものだが、今回は deflate の実装をサボるために使う。
-1 つの無圧縮ブロックには 65535 (2^16^ - 1) バイトまで格納できる。それぞれのブロックは次のような構成になっている。
+1 つの無圧縮ブロックには 65535 (2<sup>16</sup> - 1) バイトまで格納できる。それぞれのブロックは次のような構成になっている。
1. 最終ブロックなら 1、そうでなければ 0 (符号なし 1 バイト整数)
1. ブロックのバイト長 (符号なし 2 バイト整数)
@@ -313,8 +296,7 @@ func encodeZlib(data []byte) []byte {
}
```
-{#image-data}
-### 画像データ
+### 画像データ {#image-data}
では次に、zlib 形式で格納するデータを用意する。PNG 画像は次のような順にスキャンする。
画像の左上のピクセルから同じ行を横にスキャンしていき、一番右まで到達したら次の行の左に向かう。
@@ -342,8 +324,7 @@ func writeChunkIdat(w io.Writer, width, height uint32, img image.Image) {
}
```
-{#iend-chunk}
-## IEND chunk
+## IEND chunk {#iend-chunk}
最後に IEND chunk を書き込む。これは PNG 画像の最後に配置される chunk で、PNG のデコーダはこの chunk に出会うとそこでデコードを停止する。
@@ -355,8 +336,7 @@ func writeChunkIend(w io.Writer) {
}
```
-{#outro}
-# おわりに
+# おわりに {#outro}
最後に全ソースコードを再掲しておく。
@@ -537,8 +517,7 @@ func adler32(buf []byte) uint32 {
}
```
-{#references}
-# 参考
+# 参考 {#references}
* [Portable Network Graphics (PNG) Specification (Third Edition)](https://www.w3.org/TR/png)
* [ZLIB Compressed Data Format Specification version 3.3](https://www.rfc-editor.org/rfc/rfc1950)
diff --git a/services/nuldoc/content/posts/2023-04-04/phperkaigi-2023-report.dj b/services/nuldoc/content/posts/2023-04-04/phperkaigi-2023-report.md
index e4047c7..64ca896 100644
--- a/services/nuldoc/content/posts/2023-04-04/phperkaigi-2023-report.dj
+++ b/services/nuldoc/content/posts/2023-04-04/phperkaigi-2023-report.md
@@ -17,8 +17,7 @@ remark = "公開"
date = "2023-06-28"
remark = "トークセッションの記事版の執筆を中止"
---
-{#intro}
-# はじめに
+# はじめに {#intro}
2023-03-23 から 2023-03-25 にかけて開催された、 [PHPerKaigi 2023](https://phperkaigi.jp/2023/) に参加した。
今年は 2つのセッションのスピーカーとして、また、当日スタッフとして参加した。
@@ -28,19 +27,15 @@ remark = "トークセッションの記事版の執筆を中止"
* [PHPerKaigi 2022](/posts/2022-05-01/phperkaigi-2022/)
* [PHPerKaigi 2021](/posts/2021-03-30/phperkaigi-2021/)
-{#as-speaker}
-# スピーカーとして
+# スピーカーとして {#as-speaker}
これまでとの最大の違いとして、今回はスピーカーとして登壇した。まずはそれについて書く。2つのセッションで登壇した。
* 詳説「参照」:PHP 処理系の実装から参照を理解する
-
* [プロポーザル](https://fortee.jp/phperkaigi-2023/proposal/95e4dd94-5fc7-40fe-9e1a-230e36404cbe)
* [スライド](/slides/2023-03-24/phperkaigi-2023/)
* 解説記事 (執筆中) → 追記: 記事版の執筆は諦めた
-
* PHPerチャレンジ解説セッション - デジタルサーカス株式会社
-
* [プロポーザル](https://fortee.jp/phperkaigi-2023/proposal/524c9dca-1d70-4b32-a939-9c73ffe5cb48)
* [スライド](/slides/2023-03-25/phperkaigi-2023-tokens/)
* 解説記事 (執筆中) → 追記: 記事版の執筆は諦めた
@@ -48,8 +43,7 @@ remark = "トークセッションの記事版の執筆を中止"
PHPer チャレンジの話については後述する。
参照については、PHP を書き始めた頃からずっと疑問に思っていたので、仕組みを理解する良い機会となった。
-{#as-staff}
-# 当日スタッフとして
+# 当日スタッフとして {#as-staff}
今回はスピーカーのみならず当日スタッフとしても参加した。
カンファレンスのスタッフとしての参加は初めてだったが、初参加のスタッフでもスムーズに作業ができるような仕組みが整えられていた。
@@ -59,11 +53,9 @@ PHPerKaigi は一般参加者の目線でもよくできたカンファレンス
反省点は私自身の最大 HP がまったく足りていなかったことで、次の機会には最後まで動けるようにしたいところである。
-{#as-attendee}
-# 参加者として
+# 参加者として {#as-attendee}
-{#recommended-sessions}
-## おすすめセッション
+## おすすめセッション {#recommended-sessions}
5つのセッションを厳選した。
@@ -90,8 +82,7 @@ PHP の静的解析ツールは配列にも (無理矢理) 型が付けられる
個人的に最も楽しみにしていたセッションであり、今回のモリアガリトーク賞 (盛り上がったセッションに運営側から贈られる賞) でもある。
ネタバレになるが、最終的に (Go で実装された) 本戦優勝スコアを超えている。
-{#phper-challenge}
-## PHPer チャレンジ
+## PHPer チャレンジ {#phper-challenge}
昨年に引き続き、弊社デジタルサーカス株式会社からのトークン問題の作題を担当した。
また、今年はさらに作成した問題を解説するセッションにも登壇した。
@@ -102,8 +93,7 @@ PHP の静的解析ツールは配列にも (無理矢理) 型が付けられる
(WIP: 解説ブログ記事執筆中。終わったらここにリンク)
-{#random-thoughts}
-## 雑多な感想
+## 雑多な感想 {#random-thoughts}
なんかいろいろ。
@@ -117,8 +107,7 @@ PHP の静的解析ツールは配列にも (無理矢理) 型が付けられる
(あとから見返して自分でもわけがわからなくなりそうなので書いておくと、会場に入場する際に名札をタッチすると小桜エツコさんの声で「ペチパー」という音声が流れるギミックがあった)
-{#outro}
-# おわりに
+# おわりに {#outro}
[去年の参加レポ](/posts/2022-05-01/phperkaigi-2022/#section--next-year) では、来年の目標として次を挙げた。
diff --git a/services/nuldoc/content/posts/2023-06-25/phpconfuk-2023-report.dj b/services/nuldoc/content/posts/2023-06-25/phpconfuk-2023-report.md
index ba1b7d6..e830162 100644
--- a/services/nuldoc/content/posts/2023-06-25/phpconfuk-2023-report.dj
+++ b/services/nuldoc/content/posts/2023-06-25/phpconfuk-2023-report.md
@@ -13,52 +13,38 @@ tags = [
date = "2023-06-25"
remark = "公開"
---
-{#intro}
-# はじめに
+# はじめに {#intro}
2023-06-24 に開催された、 [PHP カンファレンス福岡 2023](https://phpcon.fukuoka.jp/2023/) に参加した。
また、その前日に催された、 [非公式の前夜祭](https://connpass.com/event/282285/) にも参加した。
前夜祭では、15分の登壇もおこなった。 [登壇の方の資料はこちら。](/slides/2023-06-23/phpconfuk-2023-eve/)
-{#sessions-thoughts}
-# セッションの感想
+# セッションの感想 {#sessions-thoughts}
-{#eve}
-## 前夜祭
+## 前夜祭 {#eve}
※セッションの題名と発表者名は、 [前夜祭イベントの connpass ページ](https://connpass.com/event/282285/) から引用。
* スクラム(の一部)を導入してよくなったこと (asumikam さん)
-
* スクラムの「一部」を導入されたということでしたが、理想的な形で改善が進んでいるように見受けられました。特に、ブランチ運用やデプロイ頻度、フィードバックサイクルに大きく変化が起きているのは驚くべき成果だと感じました。
-
* 地方の小さな勉強会を一番の活動舞台にする (tomio さん)
-
* すさまじいほどの「熱」を感じました。私自身、最近になってカンファレンスや勉強会への参加・登壇を活発におこなうようになったことで、頷く点が多かったです。
-{#conference}
-## カンファレンス
+## カンファレンス {#conference}
※セッションの題名と発表者名は、 [カンファレンスの fortee ページ](https://fortee.jp/phpconfukuoka-2023/proposal/accepted) から引用。
* [育成力 - エンジニアの才能を引き出す環境とチューターの立ち回り - (岡嵜 雄平 さん)](https://fortee.jp/phpconfukuoka-2023/proposal/df5f06e8-900e-4e71-94d7-d0c3cc57a0ac)
-
* ちょうど弊チームに新規メンバがジョインしたばかりで、オンボーディングプロセスについて考えていたところの発表でした。すぐにすべてを取り入れるというわけにはいきませんが、弊社での新人育成プロセスの改善につながるヒントをいくつか得られたと思います。
-
* オブジェクト指向は本当に必要か? (たなかひさてる さん、こいほげ さん)
-
* ※当日 D ホールでおこなわれたアンカンファレンスセッションのため、正式タイトル・リンクなし
* 私自身、「オブジェクト指向」については色々と言いたいことがあるのですが、だいたいツイートしたこれとこれです。
-
* 「オブジェクト指向の話は、パラダイムの異なる複数の言語に触れているかどうかで見え方がまったく異なる印象がある。OOPはどうでもいいです (※個人の感想です)」 ( [Twitter のツイートへのリンク](https://twitter.com/nsfisis/status/1672502935983656960) )
* 「OOPは現代の言語で考える意味はほぼない古いパラダイムだよという立場ですが、OOPについてあまり大っぴらに話してると色んなところから刺されそうなんですよね (Twitterは大っぴらじゃないんですか?)」 ( [Twitter のツイートへのリンク](https://twitter.com/nsfisis/status/1672504892244787201) )
-
* [その説明、コードコメントに書く?コミットメッセージに書く?プルリクエストに書く? (おかしょい/岡田正平 さん)](https://fortee.jp/phpconfukuoka-2023/proposal/ae71f3a7-4c3c-4c87-8816-8426bcc8d325)
-
* Twitter にもツイートしましたが、完全に自分の意見と一致していたので、とても共感できました。今後は社内のコードレビュー時に、こちらの資料を貼りつけることにします。
-{#outro}
-# おわりに
+# おわりに {#outro}
居住地域から離れた場所への遠征参加は初めてだったが、大変楽しい (しかも勉強にもなる!) 体験だった。
受け取った「熱」が冷める前に、自らの手を動かしていきたい。
diff --git a/services/nuldoc/content/posts/2023-10-02/compile-php-runtime-to-wasm.dj b/services/nuldoc/content/posts/2023-10-02/compile-php-runtime-to-wasm.md
index 2664b7a..3751f20 100644
--- a/services/nuldoc/content/posts/2023-10-02/compile-php-runtime-to-wasm.dj
+++ b/services/nuldoc/content/posts/2023-10-02/compile-php-runtime-to-wasm.md
@@ -16,15 +16,13 @@ remark = "公開"
date = "2025-04-23"
remark = "fflush() の前に改行の出力が必要だった理由と正しい実装について追記"
---
-{#intro}
-# はじめに
+# はじめに {#intro}
[Emscripten](https://emscripten.org/) を用いて [PHP の処理系](https://github.com/php/php-src) を [WebAssembly](https://developer.mozilla.org/docs/WebAssembly) にコンパイルした。機能をある程度絞ることで、思ったよりも簡単に実現できたので、備忘録として記しておく。
なお、この記事では Emscripten や WebAssembly とは何か知っていることを前提とする。
-{#version}
-# バージョン情報
+# バージョン情報 {#version}
この記事中で使用するソフトウェア等のバージョンを記載する。
@@ -36,14 +34,12 @@ remark = "fflush() の前に改行の出力が必要だった理由と正しい
なお、Docker から下は Docker 上で導入するので、ホストマシンにはインストールしなくてよい。
-{#goal}
-# 本記事のゴール
+# 本記事のゴール {#goal}
先にこの記事のゴールを示しておく。これから示す手順のとおりに進めると、次のようなコードが動くようになる。
このコードはこのあと使うので、`index.mjs` の名前で保存しておくこと。
-{filename="index.mjs"}
-```javascript
+```javascript filename="index.mjs"
import { readFile } from 'node:fs/promises';
import PHPWasm from './php-wasm.mjs'
@@ -60,11 +56,9 @@ console.log(`exit code: ${result}`);
標準入力から与えたコードを WebAssembly にコンパイルされた PHP 処理系の上で実行している。このような `php-wasm.mjs` (とそこから呼び出される `php-wasm.wasm`) を作成する。
-{#build}
-# ビルド
+# ビルド {#build}
-{#write-c-entrypoint}
-## C のエントリポイントを書く
+## C のエントリポイントを書く {#write-c-entrypoint}
先ほどのコードでも使っていたエントリポイントである `php_wasm_run` を用意する。
@@ -107,8 +101,7 @@ int EMSCRIPTEN_KEEPALIVE php_wasm_run(const char* code) {
これにより、PHP コードの出力の後ろに余分な改行が追加されてしまう。
改行を出力せずともバッファを消費させる手段をご存知のかたはご教示願いたい。
-{editat="2025-04-23" operation="追記"}
-::: edit
+:::edit{editat="2025-04-23" operation="追記"}
`fflush()` の前に改行の出力が必要だった理由が判明したので追記する。
これは、`index.mjs` で標準出力・標準エラー出力へ出力する方法を指定せず、デフォルトの実装に任せているため。
Emscripten のデフォルト実装では、改行コードを出力するまで出力内容がバッファリングされ、`fflush()` が機能しない。
@@ -132,8 +125,7 @@ const { ccall } = await PHPWasm({
記事末尾のリポジトリはすでにこの変更を適用済み。`stdout` や `stderr` の完全なサンプルはそちらを参照のこと。
:::
-{#compile-to-wasm}
-## WebAssembly にコンパイルする
+## WebAssembly にコンパイルする {#compile-to-wasm}
それでは WebAssembly にコンパイルしていこう。ここからは `Dockerfile` 上のコマンドとして操作を示す。
@@ -286,8 +278,7 @@ COPY index.mjs /app/
ENTRYPOINT ["node", "index.mjs"]
```
-{#run}
-# 実行
+# 実行 {#run}
`Dockerfile`、`php-wasm.c`、`index.mjs` を用意したら、Docker コンテナをビルドして実行する。
@@ -300,14 +291,12 @@ Hello, World!
exit code: 0
```
-{#outro}
-# まとめ
+# まとめ {#outro}
[ここまでをまとめた Git リポジトリ](https://github.com/nsfisis/tiny-php.wasm) を用意した。
簡単にコンパイルできるので、興味があれば試してみてほしい。
-{#references}
-# 参考リンク
+# 参考リンク {#references}
* [php/php-src: ビルドの方法について](https://github.com/php/php-src)
* [Emscripten: チュートリアル](https://emscripten.org/docs/getting_started/Tutorial.html)
diff --git a/services/nuldoc/content/posts/2023-10-13/i-entered-the-open-university-of-japan.dj b/services/nuldoc/content/posts/2023-10-13/i-entered-the-open-university-of-japan.md
index 1347d90..18dd11a 100644
--- a/services/nuldoc/content/posts/2023-10-13/i-entered-the-open-university-of-japan.dj
+++ b/services/nuldoc/content/posts/2023-10-13/i-entered-the-open-university-of-japan.md
@@ -11,8 +11,7 @@ tags = [
date = "2023-10-13"
remark = "公開"
---
-{#i-entered-ouj}
-# 放送大学に入学しました
+# 放送大学に入学しました {#i-entered-ouj}
とあるきっかけがあり、もう一度大学生をすることにしました。
仕事のほうも、これまでどおりフルタイムで続けていきます。
diff --git a/services/nuldoc/content/posts/2023-12-03/isucon-13.dj b/services/nuldoc/content/posts/2023-12-03/isucon-13.md
index 991ef43..e945e9a 100644
--- a/services/nuldoc/content/posts/2023-12-03/isucon-13.dj
+++ b/services/nuldoc/content/posts/2023-12-03/isucon-13.md
@@ -11,65 +11,53 @@ tags = [
date = "2023-12-03"
remark = "公開"
---
-{#intro}
-# はじめに
+# はじめに {#intro}
先日 11月25日、 [ISUCON 13](https://isucon.net/archives/57801192.html) に参加した。
ISUCON への参加は今回が初めてとなる。
私 nsfisis の1人チーム「うつしもゆ」として参加し、最終スコアは 13,580 点だった。使用言語は Go。
-::: note
+:::note
「ISUCON」は、LINEヤフー株式会社の商標または登録商標です。 [ISUCON 公式サイトはこちら。](https://isucon.net/)
:::
-{#goals}
-# 目標
+# 目標 {#goals}
今回は初参加ということもあり、目標を以下のように定めた。
* 正のスコアを取る
-
* ISUCON ではサーバ動作の整合性がチェックされ、失敗するとスコア 0 となる
-
* 速度改善以外に時間を浪費しない (= ハマらない)
-
* プロビジョニング、デバッグ、ミドルウェアの設定方法の調査など、性能改善に寄与しない時間を最小限にする
-{#strategy}
-# 戦略
+# 戦略 {#strategy}
ISUCON で高スコアを出す戦略については、戦闘力の高い方々が良質な記事を書いてくださっている。
ここでは、上述したような低い目標を達成するための戦略について書こうと思う。
-{#do-not-destroy-environment}
-## 環境を破壊しない
+## 環境を破壊しない {#do-not-destroy-environment}
ミドルウェアの設定やアプリケーションコードなど、変更を加えるあらゆるものは、必ずバックアップを取るか Git で管理する。
復旧不能になって環境ごと作り直すことだけは必ず避ける。
-{#revert-changes-immediately}
-## すぐに変更を取り消す
+## すぐに変更を取り消す {#revert-changes-immediately}
それでも壊してしまったときは、即座に変更を取り消す。壊れた理由を調べることに固執しない。
-{#do-small-deployment}
-## 小さくデプロイする
+## 小さくデプロイする {#do-small-deployment}
一度に複数の変更を加えず、可能な限り小さな単位でデプロイする。そしてその都度ベンチマークを走らせ、整合性チェックが通るかどうかを (当然速くなっているかどうかも) 確かめる。
-{#use-familiar-tools}
-## 使い慣れた道具を使う
+## 使い慣れた道具を使う {#use-familiar-tools}
使用する言語、ミドルウェア、ツール類を、使い慣れたものに限定する。
「このツールのオプションはほとんどそらで指定できる」と言えるようなものだけを使う。
「自分では使ったことがないが ISUCON 強者がお勧めしていた」といった理由でツールを選定しない (もちろん、本番までに練習して習熟するという選択肢は存在する)。
-{#performance-optimization}
-# パフォーマンスの最適化
+# パフォーマンスの最適化 {#performance-optimization}
もっと強い人の記事を参考にしてほしい。
-{#outro}
-# おわりに
+# おわりに {#outro}
事前の準備も含めて、大変楽しいイベントだった。次回があるなら是非また参加したい。その際は、順位やスコアを目標として立てられるようになりたいものである。
diff --git a/services/nuldoc/content/posts/2023-12-31/2023-reflections.dj b/services/nuldoc/content/posts/2023-12-31/2023-reflections.md
index 61c09ab..5e89bfe 100644
--- a/services/nuldoc/content/posts/2023-12-31/2023-reflections.dj
+++ b/services/nuldoc/content/posts/2023-12-31/2023-reflections.md
@@ -10,19 +10,16 @@ tags = [
date = "2023-12-31"
remark = "公開"
---
-{#intro}
-# はじめに
+# はじめに {#intro}
男もすなる年末の振り返りといふものを女もしてみむとてするなり。
-{#conferences}
-# 登壇・カンファレンススタッフ
+# 登壇・カンファレンススタッフ {#conferences}
勉強会やカンファレンスで登壇したりスタッフをしたりし始めたのは今年かららしい。
LT 等も含めて計 11 回の登壇をおこなった。
* PHP 勉強会@東京での登壇 (計 8 回)
-
* [第 148 回](/slides/2023-01-18/phpstudy-tokyo-148/)
* [第 149 回](/slides/2023-02-15/phpstudy-tokyo-149/)
* [第 150 回](/slides/2023-03-15/phpstudy-tokyo-150/)
@@ -31,9 +28,7 @@ LT 等も含めて計 11 回の登壇をおこなった。
* [第 154 回](/slides/2023-07-26/phpstudy-tokyo-154/)
* [第 155 回](/slides/2023-08-24/phpstudy-tokyo-155/)
* [第 157 回](/slides/2023-10-25/phpstudy-tokyo-157/)
-
* PHPerKaigi 2023 での登壇
-
* [レギュラートーク](/slides/2023-03-24/phperkaigi-2023/)
* [トークン解説セッション](/slides/2023-03-25/phperkaigi-2023-tokens/)
@@ -41,8 +36,7 @@ LT 等も含めて計 11 回の登壇をおこなった。
* [非公式でおこなわれた PHP カンファレンス福岡 2023 の 前夜祭イベントでの登壇](/slides/2023-06-23/phpconfuk-2023-eve/)
* PHPerKaigi 2024 でのコアスタッフ業
-{#articles}
-# 書いた記事
+# 書いた記事 {#articles}
登壇が増えたためか記事を書く機会が減ってしまった。
特に社内記事の本数が大きく減少しており、一昨年は約 100 本、昨年は約 60 本の社内記事を書いていたが、今年は 30 本強に留まった。
@@ -50,11 +44,9 @@ LT 等も含めて計 11 回の登壇をおこなった。
* 社外記事 (このブログ): 8本
* 社内記事: 34本
-
* 年間で最も記事を書いた人として社内表彰された
-{#coding}
-# 作ったもの
+# 作ったもの {#coding}
ガラクタをいくつか作った。役には立たないが、作るのが楽しいという効用がある。
@@ -62,15 +54,13 @@ LT 等も含めて計 11 回の登壇をおこなった。
* [twitter2x-quine](https://github.com/nsfisis/twitter2x-quine) : Twitter のロゴを 𝕏 にする変則 quine
* [9-puzzle-quine.php](https://github.com/nsfisis/9-puzzle-quine.php) : 9パズルが遊べる変則 quine
-{#misc}
-# その他
+# その他 {#misc}
* [放送大学に入学した](/posts/2023-10-13/i-entered-the-open-university-of-japan/)
* [ISUCON に初参加した](/posts/2023-12-03/isucon-13/)
* データベーススペシャリストを取得した
* 漢検2級を取得した
-{#outro}
-# おわりに
+# おわりに {#outro}
今年も大変お世話になりました。よいお年を!
diff --git a/services/nuldoc/content/posts/2024-01-10/neovim-insert-namespace-declaration-to-empty-php-file.dj b/services/nuldoc/content/posts/2024-01-10/neovim-insert-namespace-declaration-to-empty-php-file.md
index 483b0b9..5abef80 100644
--- a/services/nuldoc/content/posts/2024-01-10/neovim-insert-namespace-declaration-to-empty-php-file.dj
+++ b/services/nuldoc/content/posts/2024-01-10/neovim-insert-namespace-declaration-to-empty-php-file.md
@@ -12,12 +12,11 @@ tags = [
date = "2024-01-10"
remark = "公開"
---
-::: note
+:::note
この記事は [Vim 駅伝](https://vim-jp.org/ekiden/) #136 の記事です。
:::
-{#intro}
-# やりたいこと
+# やりたいこと {#intro}
Neovim で空の PHP ファイルを開いたとき、そのファイルが置かれているディレクトリの構造に基づいて、自動的に `namespace` 宣言を挿入したい。具体的には、トップレベルの名前空間が `MyNamespace` であり、ファイル `src/Foo/Bar/Baz.php` を開いたときに、そのファイルが空であるなら、次のようなテンプレートが自動的に挿入されてほしい。
@@ -27,8 +26,7 @@ Neovim で空の PHP ファイルを開いたとき、そのファイルが置
namespace MyNamespace\Foo\Bar;
```
-{#version}
-# バージョン情報
+# バージョン情報 {#version}
```
$ nvim --version
@@ -40,8 +38,7 @@ LuaJIT 2.1.1693350652
今回は Lua で処理を記述したため、Vim では動作しない。以下の説明でも Neovim に絞って述べる。
また、パス区切りがスラッシュである前提で記述したため、Windows には対応していない。
-{#ftplugin}
-# ftplugin を用意する
+# ftplugin を用意する {#ftplugin}
Neovim には特定のファイルタイプに対して特別な処理をおこなうための ftplugin と呼ばれる仕組みがある。
Neovim の設定を置くディレクトリ (例えば `~/.config/nvim`) の配下に `ftplugin/&lt;FILE_TYPE&gt;.vim` または `ftplugin/&lt;FILE_TYPE&gt;.lua` というファイルを配置すると、その `&lt;FILE_TYPE&gt;` が読み込まれたときにそのファイルが自動的に実行される。
@@ -51,8 +48,7 @@ Neovim の設定を置くディレクトリ (例えば `~/.config/nvim`) の配
この記事では Lua で処理を記述するため、拡張子には `.lua` を用いる。
これ以降載せるコードは、すべて `after/ftplugin/php.lua` の中に記述している。
-{#did-ftplugin}
-# 二重読み込みを防ぐ
+# 二重読み込みを防ぐ {#did-ftplugin}
ファイルタイプは読み込んだあとに変更されることもあるので、ftplugin は複数回実行されうる。
二重読み込みを防ぐために、`did_ftplugin_&lt;FILE_TYPE&gt;_after` というバッファローカル変数を定義しておくのが慣習となっている。
@@ -67,8 +63,7 @@ end
vim.b.did_ftplugin_php_after = true
```
-{#implement}
-# 実装する
+# 実装する {#implement}
では実装していこう。今回私は次のようなロジックとした。以降、「今 Neovim で開いた PHP ファイル」のことを「対象ファイル」と呼ぶことにする。
@@ -197,8 +192,7 @@ end
vim.b.did_ftplugin_php_after = true
```
-{#outro}
-# おわりに
+# おわりに {#outro}
簡易的な実装だが、多くのケースではうまく動いているようだ。
最大の問題は PSR 4 に準拠しないフレームワークを用いているとまったく役に立たないことで、今まさに職場で困っている。
diff --git a/services/nuldoc/content/posts/2024-02-03/install-wireguard-on-personal-server.dj b/services/nuldoc/content/posts/2024-02-03/install-wireguard-on-personal-server.md
index 89ecd7b..8b8741b 100644
--- a/services/nuldoc/content/posts/2024-02-03/install-wireguard-on-personal-server.dj
+++ b/services/nuldoc/content/posts/2024-02-03/install-wireguard-on-personal-server.md
@@ -16,8 +16,7 @@ remark = "公開"
date = "2024-02-17"
remark = "80 番ポートについて追記"
---
-{#intro}
-# はじめに
+# はじめに {#intro}
個人用サービスのセルフホストに使っているサーバに [WireGuard](https://www.wireguard.com/) を導入する作業をしたのでメモ。
@@ -29,8 +28,7 @@ remark = "80 番ポートについて追記"
後ろの IP アドレスは VPN 内で使用するプライベート IP アドレス。
-{#install-wireguard-server}
-# WireGuard のインストール: サーバ
+# WireGuard のインストール: サーバ {#install-wireguard-server}
まずは個人用サービスをホストしている Ubuntu のサーバに WireGuard をインストールする。
@@ -45,8 +43,7 @@ $ wg genkey | sudo tee /etc/wireguard/server.key | wg pubkey | sudo tee /etc/wir
$ sudo chmod 600 /etc/wireguard/server.{key,pub}
```
-{#install-wireguard-client}
-# WireGuard のインストール: クライアント
+# WireGuard のインストール: クライアント {#install-wireguard-client}
公式サイトから各 OS 向けのクライアントソフトウェアを入手し、インストールする。次に、設定をおこなう。
@@ -76,8 +73,7 @@ Endpoint = <サーバの外部 IP アドレス>:51820
`PrivateKey` や `PublicKey` は鍵ファイルのパスではなく中身を書くことに注意。
-{#configure-wireguard}
-# WireGuard の設定
+# WireGuard の設定 {#configure-wireguard}
一度サーバへ戻り、WireGuard の設定ファイルを書く。
@@ -108,8 +104,7 @@ $ sudo systemctl enable wg-quick@wg0
$ sudo systemctl start wg-quick@wg0
```
-{#configure-firewall}
-# ファイアウォールの設定
+# ファイアウォールの設定 {#configure-firewall}
続けてファイアウォールを設定する。まずは WireGuard が使用する UDP のポートを開き、`wg0` を通る通信を許可する。
@@ -133,12 +128,10 @@ $ sudo ufw status
$ sudo ufw enable
```
-{#connect-each-other}
-# 接続する
+# 接続する {#connect-each-other}
これで、各クライアントで VPN を有効にすると、当該サーバの 80 ポートや 443 ポートにアクセスできるようになったはずだ。念のため VPN を切った状態でアクセスできないことも確認しておくとよいだろう。
-{#edit-80-port}
-# 追記: 80 番ポートについて
+# 追記: 80 番ポートについて {#edit-80-port}
Let's Encrypt でサーバの証明書を取得している場合、80 番ポートを空けておく必要がある。気づかないうちに証明書が切れないよう注意。
diff --git a/services/nuldoc/content/posts/2024-02-10/yapcjapan-2024-report.dj b/services/nuldoc/content/posts/2024-02-10/yapcjapan-2024-report.md
index 3153f96..dbe2df9 100644
--- a/services/nuldoc/content/posts/2024-02-10/yapcjapan-2024-report.dj
+++ b/services/nuldoc/content/posts/2024-02-10/yapcjapan-2024-report.md
@@ -13,31 +13,23 @@ tags = [
date = "2024-02-10"
remark = "公開"
---
-{#intro}
-# はじめに
+# はじめに {#intro}
2024-02-10 に開催された、 [YAPC::Hiroshima 2024](https://yapcjapan.org/2024hiroshima/) に参加した。
-{#sessions-thoughts}
-# セッションの感想
+# セッションの感想 {#sessions-thoughts}
※セッションの題名と発表者名は、 [カンファレンスの fortee ページ](https://fortee.jp/yapc-hiroshima-2024) から引用。
-* [VISAカードの裏側と “手が掛かる” 決済システムの育て方 (三谷 さん)](https://fortee.jp/yapc-hiroshima-2024/proposal/c0e77f91-f856-48a0-9741-b9afb662cd30)
-
+* [VISAカードの裏側と "手が掛かる" 決済システムの育て方 (三谷 さん)](https://fortee.jp/yapc-hiroshima-2024/proposal/c0e77f91-f856-48a0-9741-b9afb662cd30)
* ベストスピーカー賞にも選ばれていましたが、大変面白い発表でした。私自身はカード決済の知識がまったくなかったのですが、巧みな説明により、「わかったような気がする」状態になれました。
-
* [awkでつくってわかる、Webアプリケーション (やんまー さん)](https://fortee.jp/yapc-hiroshima-2024/proposal/0e545260-61e1-465e-951c-91d6afb7782c)
-
* ゲームでもプログラミングでも縛りプレイほど楽しいものはないと思います。発表中ではさらっと流されていましたが、データベースとの通信や TLS、GitHub の SSO など、およそ awk で書かれたとは思えぬ機能が多数実装されており、カンファレンスなどの場でしかなかなか味わうことのない狂気に触れることができました。
-
* キーノート (杜甫々 さん)
-
* ※ 招待講演のため fortee のプロポーザルページなし
* 私が小学6年生のとき、プログラミングを始めようと最初に開いたのが「 [とほほの Java 入門](https://www.tohoho-web.com/java/) 」でした。私の人生の道を決定したその第一歩目のサイトの運営者が今まさに目の前で話しているというのは、感動などという言葉ではとても言い尽くせません。これだけで、広島まで来る価値があったと断言できます。
-{#outro}
-# おわりに
+# おわりに {#outro}
最高だった。特に、杜甫々氏の講演を生で拝聴できたのは、感慨とともに大いに刺激となった。次回の YAPC にも是非参加したい。
diff --git a/services/nuldoc/content/posts/2024-02-22/phpkansai-2024-report.dj b/services/nuldoc/content/posts/2024-02-22/phpkansai-2024-report.md
index 83205e1..38a9160 100644
--- a/services/nuldoc/content/posts/2024-02-22/phpkansai-2024-report.dj
+++ b/services/nuldoc/content/posts/2024-02-22/phpkansai-2024-report.md
@@ -13,30 +13,22 @@ tags = [
date = "2024-02-21"
remark = "公開"
---
-{#intro}
-# はじめに
+# はじめに {#intro}
2024-02-11 に開催された、 [PHPカンファレンス関西 2024](https://2024.kphpug.jp/) に参加した。
-{#sessions-thoughts}
-# セッションの感想
+# セッションの感想 {#sessions-thoughts}
※セッションの題名と発表者名は、 [カンファレンスの fortee ページ](https://fortee.jp/phpcon-kansai2024) から引用。
* [RDBアンチパターンと戦う - 削除フラグ 完全攻略ガイド (曽根 壮大 さん)](https://fortee.jp/phpcon-kansai2024/proposal/4e03491c-2a97-40aa-8ff9-a68593b0e847)
-
* アンチパターンとして紙の上での知識だけあるものの、実際にどう設計すべきなのか、あるいは今すでに使われている場合にどう直していくべきなのかについては、知識がまったく足りていなかったため、よい機会となりました。データベース分野については、今後も知識のインプットと経験が必要だと感じています。
-
* [PHPコミュニティ、その魅力と熱狂をあなたにも!!! (ことみん さん)](https://fortee.jp/phpcon-kansai2024/proposal/c903c4be-77bb-47b9-85a1-5bfdfd61c1aa)
-
* もしこの記事を読んでいるあなたがまだ一度もカンファレンスや勉強会に参加したことがないなら、この記事はどうでもいいのでスライドを見てください。伝えるべきことは以上です。
-
* [ほげ言語にあってPHPにない機能 (田中ひさてる さん)](https://fortee.jp/phpcon-kansai2024/proposal/0e0befdb-2028-42c8-98e2-b19e434f5a82)
-
* 私はプログラミング言語の比較が大好きなので、非常に楽しかったです。UFCS (Uniform Function Call Syntax) の知名度の低さには驚きましたが、D言語er で会場が埋め尽くされていたらそれはそれで驚きなのでやむなしかもしれません。個人的に「ほげ言語にあってPHPにない機能」の中で一番ほしいのは代数的データ型です。
-{#outro}
-# おわりに
+# おわりに {#outro}
[本カンファレンスの前日 2024-02-10 は YAPC::Hiroshima に参加しており](/posts/2024-02-10/yapcjapan-2024-report/) 、2日連続のカンファレンスとなった。かなり疲れはしたが、その分充実した週末となったように思う。
diff --git a/services/nuldoc/content/posts/2024-03-17/phperkaigi-2024-report.dj b/services/nuldoc/content/posts/2024-03-17/phperkaigi-2024-report.md
index 65c7f70..2c789ba 100644
--- a/services/nuldoc/content/posts/2024-03-17/phperkaigi-2024-report.dj
+++ b/services/nuldoc/content/posts/2024-03-17/phperkaigi-2024-report.md
@@ -17,8 +17,7 @@ remark = "公開"
date = "2024-07-07"
remark = "Wasm ランタイムの進捗について追記"
---
-{#intro}
-# はじめに
+# はじめに {#intro}
2024-03-07 から 2024-03-09 にかけて開催された、 [PHPerKaigi 2024](https://phperkaigi.jp/2024/) に参加した。
今年はスピーカーとして、また、コアスタッフとして参加した。
@@ -29,8 +28,7 @@ remark = "Wasm ランタイムの進捗について追記"
* [PHPerKaigi 2022](/posts/2022-05-01/phperkaigi-2022/)
* [PHPerKaigi 2021](/posts/2021-03-30/phperkaigi-2021/)
-{#as-speaker}
-# スピーカーとして
+# スピーカーとして {#as-speaker}
昨年に続き、スピーカーとして登壇をおこなった。
@@ -42,8 +40,7 @@ remark = "Wasm ランタイムの進捗について追記"
WebAssembly の VM を PHP で実装し、実装に至るまでの道程や WebAssembly の特徴、言語処理系を作る楽しさについて語った。
タイトルにある「WebAssembly を理解する」という目的が達成できるようなトークだったかと言われると疑問は残るものの、実際に作った人にしかできない話をすることはできたと思う。
-{#as-staff}
-# コアスタッフとして
+# コアスタッフとして {#as-staff}
昨年は当日スタッフとして参加したが、今年はコアスタッフとして運営に参加した。
今年はコードゴルフ企画を提案し、その準備とシステムの開発、当日の運用をおこなった。
@@ -53,11 +50,9 @@ WebAssembly の VM を PHP で実装し、実装に至るまでの道程や WebA
システムの開発完了や問題の作成完了はスケジュールギリギリとなったのだが、当日はそこそこ安定して稼動していたのではないかと思う。
-{#as-attendee}
-# 参加者として
+# 参加者として {#as-attendee}
-{#my-best-session}
-## マイベストセッション
+## マイベストセッション {#my-best-session}
[RubyVM を PHP で実装する〜Hello World を出力するまで〜](https://fortee.jp/phperkaigi-2024/proposal/ac59d0dd-795a-47cb-ba59-c0b1772d00cc) (めもりー さん)
@@ -66,13 +61,11 @@ WebAssembly の VM を PHP で実装し、実装に至るまでの道程や WebA
P.S. Ask the Speaker で話した、Ruby VM (written in PHP) on PHP VM (compiled to Wasm) on Wasm VM (written in PHP) on PHP というアイデアは「マジ」なので、続報をお待ちください (自作 Wasm runtime に不足している機能を鋭意実装中です)。
-{editat="2024-07-07" operation="追記"}
-::: edit
+:::edit{editat="2024-07-07" operation="追記"}
[コミット a312e95](https://github.com/nsfisis/php-waddiwasi/commit/a312e95a95d243943535f94653822d6796d4637f) で、ついに Ruby VM on PHP VM on Wasm VM on PHP を実現した。現時点での動かしかたは README に記載している。
:::
-{#outro}
-# おわりに
+# おわりに {#outro}
今年はスピーカーとスタッフともに開発を伴うものだったので (Wasm 処理系とコードゴルフシステム)、両者がぶつかった結果として準備段階は去年よりも大変になった。
diff --git a/services/nuldoc/content/posts/2024-03-20/my-bucket-list.dj b/services/nuldoc/content/posts/2024-03-20/my-bucket-list.md
index d998cc2..d998cc2 100644
--- a/services/nuldoc/content/posts/2024-03-20/my-bucket-list.dj
+++ b/services/nuldoc/content/posts/2024-03-20/my-bucket-list.md
diff --git a/services/nuldoc/content/posts/2024-04-14/phpcon-odawara-2024-report.dj b/services/nuldoc/content/posts/2024-04-14/phpcon-odawara-2024-report.md
index 3207d3d..3f4db9e 100644
--- a/services/nuldoc/content/posts/2024-04-14/phpcon-odawara-2024-report.dj
+++ b/services/nuldoc/content/posts/2024-04-14/phpcon-odawara-2024-report.md
@@ -17,18 +17,15 @@ remark = "公開"
date = "2024-06-01"
remark = "セッションの感想を追加"
---
-{#intro}
-# はじめに
+# はじめに {#intro}
2024-04-13 に開催された [PHP カンファレンス小田原](https://phpcon-odawara.jp/) に、スピーカーとして、また当日スタッフとして参加した。
-{#as-speaker}
-# スピーカーとして
+# スピーカーとして {#as-speaker}
PHP 処理系の JIT コンパイルにおける PHP 8.4 での変更について、登壇をおこなった。
* 来る新 JIT エンジンについて知った気になる
-
* [プロポーザル](https://fortee.jp/phpconodawara-2024/proposal/bc9669f6-6583-489c-aa6a-1b68abf7c291)
* [スライド](/slides/2024-04-13/phpcon-odawara-2024/)
@@ -39,8 +36,7 @@ PHP の処理系がスクリプトを opcode へ変換する過程について
Tracing JIT の発火条件や、IR を使って実現される最適化方法など、調べたものの発表に入らなかった話がごまんとあるので、これもどこかに持っていければと考えている。
-{#as-staff}
-# スタッフとして
+# スタッフとして {#as-staff}
当日スタッフとして前日の準備と当日の運営をおこなった。今回はモノの移動が比較的 (比較対象: [PHPerKaigi](/posts/2024-03-17/phperkaigi-2024-report/) ) 少なく、体力にはかなり余裕があった。
@@ -48,28 +44,21 @@ Tracing JIT の発火条件や、IR を使って実現される最適化方法
また、これはコアスタッフの方々のおかげだろうが、初開催としては大きなトラブルなく終わったと言えるのではないだろうか。
-{#as-attendee}
-# 参加者として
+# 参加者として {#as-attendee}
発表タイトルと発表者名は fortee より引用
* FigmaとPHPで作る、1ミリたりとも表示崩れしない最強の帳票印刷ソリューション (たつきち さん)
-
* プロポーザルリンク: https://fortee.jp/phpconodawara-2024/proposal/7c57d5ca-213a-4d7a-aaf0-26ddc44897f0
* 感想: 最初のアイデアから途中の泥臭いワークアラウンドまで非常におもしろかったです。帳票には何度か苦しめられているので、機会があれば試してみたいです。
-
* PHPの次期バージョンはこの時期どうなっているのか、Internalsの開発体制について (てきめん さん)
-
* プロポーザルリンク: https://fortee.jp/phpconodawara-2024/proposal/740b034a-81f0-4b7a-90e9-cd3fa01c651f
* 感想: 前々から出そうとしている RFC があるので、RFC についての日本語情報が増えるのは大変ありがたいです。あとは作業を進めなければ......。
-
* Architecture Decision Record を一年運用してみた (富所 亮 さん)
-
* プロポーザルリンク: https://fortee.jp/phpconodawara-2024/proposal/56218b4f-b724-4199-82f1-67497501a9ef
* 感想: 今回最も楽しみにしていた発表の一つです。設計指針の調査・共有等には課題を感じていたので、弊チームでも導入のために動いていこうと思います。
-{#outro}
-# おわりに
+# おわりに {#outro}
怒涛の月刊 PHP カンファレンスも折り返しとなったが、まだまだ新鮮に楽しい。
diff --git a/services/nuldoc/content/posts/2024-04-21/pipefail-option-in-gitlab-ci-cd.dj b/services/nuldoc/content/posts/2024-04-21/pipefail-option-in-gitlab-ci-cd.md
index 9872d28..41b9513 100644
--- a/services/nuldoc/content/posts/2024-04-21/pipefail-option-in-gitlab-ci-cd.dj
+++ b/services/nuldoc/content/posts/2024-04-21/pipefail-option-in-gitlab-ci-cd.md
@@ -17,17 +17,15 @@ isInternal = true
date = "2024-04-21"
remark = "ブログ記事として一般公開"
---
-::: note
+:::note
この記事は、2022-11-17 に [デジタルサーカス株式会社](https://www.dgcircus.com/) の社内 Qiita Team に公開された記事をベースに、加筆修正して一般公開したものです。
:::
ハマったのでメモ。
-{#background}
-# 前提
+# 前提 {#background}
-{#gitlab-ci-cd}
-## GitLab CI/CD について
+## GitLab CI/CD について {#gitlab-ci-cd}
GitLab CI/CD では、Docker executor を用いて任意の Docker image 上でスクリプトを走らせることができる。
@@ -61,8 +59,7 @@ hello-world:
失敗するコマンドをパイプに接続した。通常 Bash では、パイプの最後のコマンドの exit code が全体の exit code になる。
-{#pipefail-option}
-## `pipefail` オプションについて
+## `pipefail` オプションについて {#pipefail-option}
前述したようなケースにおいて、途中で失敗したときに全体を失敗させるには、`pipefail` オプションを有効にする。
@@ -76,8 +73,7 @@ set +o pipefail
こうすると、パイプ全体が失敗するようになる。
この設定は、デフォルトだと off になっている。
-{#problem}
-# 発生した問題
+# 発生した問題 {#problem}
次のような GitLab CI/CD ジョブが失敗してしまった。
@@ -119,8 +115,7 @@ set +o pipefail
なぜスクリプト内で `set -o pipefail` しているわけでもないのに `pipefail` が on になっているのか。
-{#where-pipefail-is-enabled}
-# どこで `pipefail` が on になるか
+# どこで `pipefail` が on になるか {#where-pipefail-is-enabled}
`.gitlab-ci.yml` で明示的には書いていないので、GitLab Runner (GitLab CI/CD のスクリプトを実行するプログラム) が勝手に追加しているに違いない。
そう仮説を立てて [GitLab Runner のリポジトリ](https://gitlab.com/gitlab-org/gitlab-runner) を調査したところ、 [ソースコード中の以下の箇所](https://gitlab.com/gitlab-org/gitlab-runner/-/blob/c75da0796a0e3048991dccfdf2784e3d931beda4/shells/bash.go#L276) で `set -o pipefail` していることが判明した (コメントは筆者による)。
@@ -131,8 +126,7 @@ set +o pipefail
buf.WriteString("if set -o | grep pipefail > /dev/null; then set -o pipefail; fi; set -o errexit\n")
```
-{#how-to-solve}
-# どのように解決するか
+# どのように解決するか {#how-to-solve}
通常の Bash スクリプトを書く場合と同様に、`pipefail` が on になっていては困る場所だけ off にしてやればよい。
@@ -149,7 +143,6 @@ buf.WriteString("if set -o | grep pipefail > /dev/null; then set -o pipefail; fi
when: always
```
-{#remarks}
-# 備考
+# 備考 {#remarks}
なお、上述した実装ファイルは `shells/bash.go` だが、`alpine:latest` の例でもそうであったように、シェルが `sh` である場合にも適用される。
diff --git a/services/nuldoc/content/posts/2024-04-29/zsh-file-completion-for-composer-custom-commands.dj b/services/nuldoc/content/posts/2024-04-29/zsh-file-completion-for-composer-custom-commands.md
index 5738de8..bae8416 100644
--- a/services/nuldoc/content/posts/2024-04-29/zsh-file-completion-for-composer-custom-commands.dj
+++ b/services/nuldoc/content/posts/2024-04-29/zsh-file-completion-for-composer-custom-commands.md
@@ -13,23 +13,20 @@ tags = [
date = "2024-04-29"
remark = "公開"
---
-{#version-info}
-# バージョン情報
+# バージョン情報 {#version-info}
* Composer: 2.7.4
* PHP: 8.3.6
* Zsh: 5.9
-{#intro}
-# はじめに
+# はじめに {#intro}
[Composer](https://getcomposer.org/) は PHP のデファクトスタンダードなパッケージマネージャである。
Zsh では、`composer` コマンドに対する補完が提供されており、`composer` と入力してタブキーを押すと、利用可能なコマンドやオプションが補完される。
Zsh の補完はシェル関数の形で実装されており、`composer` コマンドに対応した補完をおこなうのは `_composer` である。
[記事執筆時点での補完関数の定義は、GitHub のミラーリポジトリから参照できる。](https://github.com/zsh-users/zsh/blob/a66e92918568881af110a3e2e3018b317c054e4a/Completion/Unix/Command/_composer)
-{#problem}
-# 発生していた問題
+# 発生していた問題 {#problem}
`composer` コマンドはカスタムコマンド (`composer.json` の `scripts` で定義されたコマンド) に対して補完をおこなわない。
つまり、途中まで入力されたカスタムコマンドを補完しないし、カスタムコマンドの引数も補完しない。
@@ -46,8 +43,7 @@ Zsh の補完はシェル関数の形で実装されており、`composer` コ
# commands requires making slow calls to Composer
```
-{#what-i-want-to-achive}
-# やりたいこと
+# やりたいこと {#what-i-want-to-achive}
確かに、カスタムコマンドに対して完全な補完を提供するのは不可能か、あるいは実現できても遅くなりすぎるだろう。
しかし、不完全なフォールバックを提供するくらいなら可能なはずだ。
@@ -55,8 +51,7 @@ Zsh の補完はシェル関数の形で実装されており、`composer` コ
この記事では、これらのカスタムコマンドについて、Zsh が提供するデフォルトのファイル・ディレクトリ補完を適用する。
つまり、`composer phpunit -- tests/` まで打ってタブキーを押すと、`tests` ディレクトリの下にあるテストファイルまたはディレクトリが補完される。
-{#solution}
-# 解決策
+# 解決策 {#solution}
まずは、Zsh で補完関数を提供する場合のボイラープレートコードを書く。
以下は `~/.zshrc` にすべて書く前提だが、`autoload` を設定するなどすれば別ファイルに分離できる (詳細な手順は割愛)。
@@ -79,8 +74,7 @@ function _my_composer() {
`_composer` コマンドは何も補完候補がなかったとき非ゼロな exit status で終了するので、そうであったなら `_files` を呼び出す。
`_files` は、Zsh がデフォルトで用意しているファイル・ディレクトリの補完をおこなう関数である。
-{#conclusion}
-# まとめ
+# まとめ {#conclusion}
これらの設定をおこなうことで、部分的ながら Composer のカスタムコマンドに対して補完をおこなうことができる。
特に、PHPUnit や PHPStan などの対象ファイル・ディレクトリを引数に取るようなコマンドを使う場合に有用であろう。
diff --git a/services/nuldoc/content/posts/2024-05-11/phpconkagawa-2024-report.dj b/services/nuldoc/content/posts/2024-05-11/phpconkagawa-2024-report.md
index a1ec682..0a59717 100644
--- a/services/nuldoc/content/posts/2024-05-11/phpconkagawa-2024-report.dj
+++ b/services/nuldoc/content/posts/2024-05-11/phpconkagawa-2024-report.md
@@ -13,42 +13,30 @@ tags = [
date = "2024-05-11"
remark = "公開"
---
-{#intro}
-# はじめに
+# はじめに {#intro}
2024-05-11 に開催された [PHP カンファレンス香川 2024](https://phpcon.kagawa.jp/2024/) に参加した。
-{#session-thoughts}
-# セッション感想
+# セッション感想 {#session-thoughts}
* 泥まみれの技術革新: あなたの[ PHPバージョンアップ | 新フレームワーク採用 | アーキテクチャ刷新 | … ]を後押しするために by nrslib
-
* fortee URL: https://fortee.jp/phpconkagawa-2024/proposal/7f4622af-03b6-4b83-a0ef-e1cfc7b7c930
* 感想: ちょうどとあるマイグレーション作業をしているので、頷きながら拝聴しました。結局は誰しも移行作業は根気と腕力なのだということに勇気をもらえました。
-
* PHP 9 に備えよ - 動的プロパティ、どうすればいぃ? by 荒瀬 泰輔
-
* fortee URL: https://fortee.jp/phpconkagawa-2024/proposal/039ebb21-d104-4df2-86bb-be2680979b7b
* 感想: これも上と同じく移行作業の話ではあり、結局のところは「頑張って地道にやっていく」しかないところもあります (とはいえこちらは静的解析である程度潰せますが)。PHP 言語のコミュニティ全体で頑張っていきましょう。
-
* 1人プロ・ペアプロ・モブプロの効果的な使い分け by まきまき
-
* fortee URL: https://fortee.jp/phpconkagawa-2024/proposal/db3e9634-4a79-46c1-84fd-8ffa4d495a13
* 感想: 今会社でペアプロを部分的に取り入れているものの、迷うところが多く、楽しみにしていた発表です。まずは何か一つ変えないことには始まらないので、発表から得たヒントを自分たちのチームに反映すべく、何かやりかたを変えてみる予定です。
-
* mb_trim関数を作りました - PHPに新しい関数を追加しました - by てきめん
-
* fortee URL: https://fortee.jp/phpconkagawa-2024/proposal/0ec36f50-c4b7-4aa4-abef-006f8bab3931
* 感想: RFC を必要とするような機能追加のプロセスを日本語で解説する資料がどんどんと増えていくのは、ハードルを下げるという意味で非常にありがたいです。私も以前から出そう出そうと考えている書きかけの RFC があるのですが、具体的なプロセスが明示されるとやはりやる気になりますね。
-
* (「PHPカンファレンス小田原2024」を実行委員長がふりかえる by asumikam)
-
* fortee URL: https://fortee.jp/phpconkagawa-2024/proposal/c1efd828-72c9-4719-93f7-2ca3f8f20ac1
* 備考: ちょっとしたトラブルにより午前中の発表が見られなかったので、生で拝聴したわけではなく、スライドを拝見して感想を書いています。
* 感想: Thanks のスライド非常に嬉しかったです。こちらこそ素晴らしいカンファレンスの場をありがとうございました!スタッフ募集あれば来年も是非参加させてください。
-{#lightning-talk}
-# 懇親会 LT
+# 懇親会 LT {#lightning-talk}
今回登壇者ではなかったのだが、プロポーザル募集時に用意していたスライド (LT 用に作っていたのだが、そもそも LT 枠がなかったのでお蔵入りになっていた) があったので懇親会の LT で発表した。
@@ -56,8 +44,7 @@ remark = "公開"
なお、この発表には [ブログ記事バージョン](/posts/2022-09-29/write-fizzbuzz-in-php-2-letters-per-line/) もある。
-{#outro}
-# おわりに
+# おわりに {#outro}
午前中の発表に間に合わなかったことがとにかく心残りなのだが、それ以外は PHP カンファレンス小田原のスタッフの方々をはじめ多くの方と交流でき、非常に楽しいカンファレンスだった。来年もあるそうなので (この分だと来年も月刊 PHP カンファレンスにならないか?)、是非参加したい。
diff --git a/services/nuldoc/content/posts/2024-06-19/scalamatsuri-2024-report.dj b/services/nuldoc/content/posts/2024-06-19/scalamatsuri-2024-report.md
index 85d713d..a551aa1 100644
--- a/services/nuldoc/content/posts/2024-06-19/scalamatsuri-2024-report.dj
+++ b/services/nuldoc/content/posts/2024-06-19/scalamatsuri-2024-report.md
@@ -13,34 +13,27 @@ tags = [
date = "2024-06-19"
remark = "公開"
---
-{#intro}
-# はじめに
+# はじめに {#intro}
2024-06-08 から 2024-06-09 にかけて開催された [ScalaMatsuri 2024](https://2024.scalamatsuri.org/ja) に参加した。
Day 2 には当日参加できなかったため、day 2 のセッションの感想は YouTube にアップロードされたアーカイブ動画を観て書いている。
-{#sessions}
-# セッション感想
+# セッション感想 {#sessions}
特に印象に残ったセッションを、day 1 と day 2 で一つずつ選んだ (タイトルと登壇者名は [公式ホームページの「プログラム」](https://2024.scalamatsuri.org/ja/programs) から引用)。
* [Scala to WebAssembly: 動機と方法](https://2024.scalamatsuri.org/ja/programs/SESSION_DAY_1_02) (Rikito Taniguchi さん)
-
* [最近 WebAssembly の処理系を作った](/posts/2024-03-17/phperkaigi-2024-report/#section--as-speaker) こともあって、気になっていたセッションです。私の処理系は WasmGC proposal を実装していないので動かせないのですが、いつかサポートして動かしてみたいですね。
-
* [作って学ぶ Extensible Effects](https://2024.scalamatsuri.org/ja/programs/SESSION_DAY_2_04) (Kory さん・hsjoihs さん)
-
* 今回一番楽しみにしていたセッションです。Day 2 当日は参加できず、後日アーカイブ動画を視聴したのですが、期待を裏切らない濃厚なセッションでした。後日開かれた [NB-Scala レトロスペクティブ (非公式後夜祭)](https://nextbeat.connpass.com/event/315988/) の発表も拝聴したのですが、どちらも非常に面白かったです。
-{#others}
-# その他感想
+# その他感想 {#others}
* 良い会場だった。よく取り沙汰されるスライドの文字サイズの問題は、巨大なスクリーンを用意することで解決するという発見があった
* ランチにお弁当が用意されており、おいしかった ( [参考画像](https://x.com/nsfisis/status/1799276217583260092) )
-{#outro}
-# おわりに
+# おわりに {#outro}
私が Scala を書いたり追ったりしていたのは Scala 2 の頃で、Scala 3 はほとんど浦島太郎状態だったのだが、非常に楽しく面白いイベントだった。
イベントに触発されて、長らく塩漬けになっていた Scala 製の趣味プロジェクトを久しぶりに触っているのだが、これもまた楽しい。
diff --git a/services/nuldoc/content/posts/2024-07-19/reparojson-fix-only-json-formatter.dj b/services/nuldoc/content/posts/2024-07-19/reparojson-fix-only-json-formatter.md
index eb63da0..b9590c5 100644
--- a/services/nuldoc/content/posts/2024-07-19/reparojson-fix-only-json-formatter.dj
+++ b/services/nuldoc/content/posts/2024-07-19/reparojson-fix-only-json-formatter.md
@@ -12,12 +12,11 @@ tags = [
date = "2024-07-19"
remark = "公開"
---
-::: note
+:::note
この記事は [Vim 駅伝](https://vim-jp.org/ekiden/) #218 の記事です。
:::
-{#intro}
-# 欲しかったもの
+# 欲しかったもの {#intro}
Vim で JSON を編集しているときに、文法エラー (末尾カンマやカンマの不足) のみを修正して一切の整形をおこなわないプラグインが欲しかった。
整形も同時におこなうプラグインは見つかっただけでも多数あったのだが、整形しないものは見つけられなかったので自作することにした。
@@ -25,8 +24,7 @@ Vim で JSON を編集しているときに、文法エラー (末尾カンマ
なお、作成したツール自体は単体の CLI として動作し、Vim とは無関係に使うことができる。
この記事では Neovim と組み合わせる場合の設定を紹介するが、およそ任意のエディタで使えるだろう。
-{#reparojson}
-# 作ったもの
+# 作ったもの {#reparojson}
作成したものがこちら: [ReparoJSON](https://github.com/nsfisis/reparojson)
@@ -55,8 +53,7 @@ $ echo '{ "foo": 1, "bar": 2, }' | reparojson
他にも自動で直せそうなエラーはいくつか思いつくが (オブジェクトのキーがクォートされていない等)、私自身があまり困っていないので優先度は低い。
-{#itegration-with-neovim}
-# Neovim との連携
+# Neovim との連携 {#itegration-with-neovim}
Neovim で JSON ファイルを保存したときに、上記のツールを自動で走らせるように設定する。
@@ -97,8 +94,7 @@ Neovim で JSON ファイルを保存したときに、上記のツールを自
これは、入力が最初から正しかった場合と修正して正しくなった場合を区別するためだが、異常終了してしまうと置き換えが発生しない。
そのため、`-q` フラグを指定して、修正されたときも exit code 0 で終了するようにしている。
-{#outro}
-# おわりに
+# おわりに {#outro}
このツールが威力を発揮するのは、行の入れ換え時である。次のような JSON があり、
diff --git a/services/nuldoc/content/posts/2024-08-19/go-template-access-outer-scope-pipeline-within-with-or-range.dj b/services/nuldoc/content/posts/2024-08-19/go-template-access-outer-scope-pipeline-within-with-or-range.md
index 6a6f9c3..c982145 100644
--- a/services/nuldoc/content/posts/2024-08-19/go-template-access-outer-scope-pipeline-within-with-or-range.dj
+++ b/services/nuldoc/content/posts/2024-08-19/go-template-access-outer-scope-pipeline-within-with-or-range.md
@@ -11,13 +11,11 @@ tags = [
date = "2024-08-19"
remark = "公開"
---
-{#tldr}
-# TL;DR
+# TL;DR {#tldr}
常にトップレベルを指す特殊変数 `$` を使えばよい。
-{#intro}
-# はじめに
+# はじめに {#intro}
Go には、標準ライブラリにテンプレートライブラリ `text/template` がある。
この `text/template` における制御構造、`with` と `range` は次のように使われる。
@@ -61,8 +59,7 @@ tmpl.Execute(out, Params{
})
```
-{#what-i-want-to-do}
-# やりたいこと
+# やりたいこと {#what-i-want-to-do}
今回おこないたいのは、`with` や `range` の中で、その外側で使われていたトップレベルのオブジェクトを参照することだ。
@@ -89,8 +86,7 @@ tmpl.Execute(out, Params{
しかしながら、頻発するシチュエーションにしてはあまりに不恰好である。よりスマートな方法が用意されているはずだ。
-{#solution}
-# 解決方法
+# 解決方法 {#solution}
常にトップレベルを指す特殊変数 `$` を使えばよい。
@@ -111,8 +107,7 @@ tmpl.Execute(out, Params{
> When execution begins, $ is set to the data argument passed to Execute, that is, to the starting value of dot.
-{#reference}
-# 参考
+# 参考 {#reference}
* [直接の出典である Stack Overflow の回答: "In a template how do you access an outer scope while inside of a "with" or "range" scope?"](https://stackoverflow.com/questions/14800204/in-a-template-how-do-you-access-an-outer-scope-while-inside-of-a-with-or-rang)
* [大元の出典である `text/template` の公式ドキュメント](https://pkg.go.dev/text/template#hdr-Variables)
diff --git a/services/nuldoc/content/posts/2024-09-28/mncore-challenge-1.dj b/services/nuldoc/content/posts/2024-09-28/mncore-challenge-1.md
index 569098a..4c5bbbd 100644
--- a/services/nuldoc/content/posts/2024-09-28/mncore-challenge-1.dj
+++ b/services/nuldoc/content/posts/2024-09-28/mncore-challenge-1.md
@@ -12,19 +12,17 @@ tags = [
date = "2024-09-28"
remark = "公開"
---
-::: note
+:::note
ただの参加記で解説はない。
:::
-{#intro}
-# はじめに
+# はじめに {#intro}
2024-08-28 から 2024-09-24 の約1ヶ月に渡り開催された [MN-Core Challenge #1](https://mncore-challenge.preferred.jp/) に参加した。私 nsfisis ([あるいは `0b0100000111111000`](https://x.com/nsfisis/status/1838276770560364977)) はスコア 1181 で、最終順位 29 位だった。
この記事で解説はしないが、提出した回答はこちらのリポジトリ ([GitHub: nsfisis/mncore-challenge](https://github.com/nsfisis/mncore-challenge)) にアップロードしている。
-{#thought}
-# 感想
+# 感想 {#thought}
MN-Core には初めて触れたが、それでも問題なく全問 (除 FizzBuzz) 解けるよう線路が敷かれており、前半の問題を解くことで自然と後半を解くだけの知識が身に付くように設計されていた。
@@ -32,7 +30,6 @@ MN-Core には初めて触れたが、それでも問題なく全問 (除 FizzBu
悔しいポイントも多数あるのだが、書いているとキリがないので自分で反省するだけにしておく。
-{#outro}
-# おわりに
+# おわりに {#outro}
最後になりましたが、運営のみなさま、素晴しいコンテストをありがとうございました!非常に楽しい時間でした!第2回を首を長くして待っています!
diff --git a/services/nuldoc/content/posts/2024-12-04/cohackpp-report.dj b/services/nuldoc/content/posts/2024-12-04/cohackpp-report.md
index 80da994..60e33e4 100644
--- a/services/nuldoc/content/posts/2024-12-04/cohackpp-report.dj
+++ b/services/nuldoc/content/posts/2024-12-04/cohackpp-report.md
@@ -16,8 +16,7 @@ remark = "公開"
date = "2024-12-05"
remark = "「育てた」枠・「育てられた」枠を勘違いして逆に表記していたので修正"
---
-{#intro}
-# はじめに
+# はじめに {#intro}
2024-11-30 に開催された [紅白ぺぱ合戦](https://connpass.com/event/329428/) なる催しに参加しました。私は「ぺ」陣営のメンバとして LT をおこないました。
@@ -31,8 +30,7 @@ remark = "「育てた」枠・「育てられた」枠を勘違いして逆に
ざっくりと言えば、テックカンファレンスの形式をとった結婚披露宴です。タイトルの「ぺ」は PHPer、「ぱ」は Perl Monger の略です。
-{#thoughts}
-# 感想
+# 感想 {#thoughts}
私は「ぺ」陣営のスピーカーとして LT をしていたのですが、その前にまずは登壇以外の感想を。
@@ -42,11 +40,9 @@ remark = "「育てた」枠・「育てられた」枠を勘違いして逆に
改めて、asumikam さん、stefafafan さん、ご結婚おめでとうございます!
-{#lt}
-# LT
+# LT {#lt}
-{#prepare}
-## 合戦準備
+## 合戦準備 {#prepare}
さて、時を合戦の前に戻しまして、両陣営の登壇者が発表され徐々に謎のイベントの輪郭が見えてきた頃、asumikam さんから次のような連絡を受けました。
@@ -54,8 +50,7 @@ remark = "「育てた」枠・「育てられた」枠を勘違いして逆に
最初は直近のカンファレンスに出して落選したプロポーザルテーマを LT に編集して話そうとしていたのですが、この機会でなければ話せない・この機会で話すことに意味があるテーマにしようとネタ出しをおこない、最終的に次のテーマでの登壇となりました。
-{#battle}
-## いざ尋常に勝負
+## いざ尋常に勝負 {#battle}
当日は、「プログラミングマナー講座」と題して発表をおこないました。
結婚式のマナー、特に「忌み言葉」へフォーカスし、これを無理やりプログラミングに適用するというものです。
@@ -65,8 +60,7 @@ remark = "「育てた」枠・「育てられた」枠を勘違いして逆に
そもそも結婚式・披露宴でのスピーチ自体が初めてだったのでそれなりに緊張していたのですが、登壇時やその後の反応を伺う限り概ね好評だったようで良かったです。
-{#congrats}
-# ご結婚おめでとうございます
+# ご結婚おめでとうございます {#congrats}
https://github.com/nsfisis/cohackpp/blob/main/congrats.php
diff --git a/services/nuldoc/content/posts/2024-12-33/2024-reflections.dj b/services/nuldoc/content/posts/2024-12-33/2024-reflections.md
index 88b6c9b..807856d 100644
--- a/services/nuldoc/content/posts/2024-12-33/2024-reflections.dj
+++ b/services/nuldoc/content/posts/2024-12-33/2024-reflections.md
@@ -10,16 +10,14 @@ tags = [
date = "2025-01-02"
remark = "公開"
---
-{#intro}
-# はじめに
+# はじめに {#intro}
ご存じのとおり、4 と 11 と 23 で割り切れる年は閏年というやつで 12 月が 33 日まである。
1年の振り返りを書く猶予が平年よりも長くなるので大変に都合がよい。
去年のやつ: [/posts/2023-12-31/2023-reflections/](/posts/2023-12-31/2023-reflections/)
-{#conference}
-# 登壇・カンファレンス参加
+# 登壇・カンファレンス参加 {#conference}
参加または登壇した勉強会やカンファレンス。
LT 等も含めて計 8 回の登壇をおこなった。
@@ -30,16 +28,12 @@ LT 等も含めて計 8 回の登壇をおこなった。
* [YAPC::Hiroshima 2024 参加](/posts/2024-02-10/yapcjapan-2024-report/)
* [PHPカンファレンス関西 2024 参加](/posts/2024-02-22/phpkansai-2024-report/)
* PHPerKaigi 2024
-
* [登壇](/slides/2024-03-08/phperkaigi-2024/)
* コアスタッフとして参加
-
* [Ya8 2024 登壇](/slides/2024-03-15/ya8-2024/)
* PHP カンファレンス小田原 2024
-
* [登壇](/slides/2024-04-13/phpcon-odawara-2024/)
* 当日スタッフとして参加
-
* [PHP 勉強会@東京 第 163 回 LT で登壇](/slides/2024-04-25/phpstudy-tokyo-163/)
* [PHP カンファレンス香川 2024 参加](/posts/2024-05-11/phpconkagawa-2024-report/)
* [ScalaMatsuri 2024 参加](/posts/2024-06-19/scalamatsuri-2024-report/)
@@ -50,35 +44,30 @@ LT 等も含めて計 8 回の登壇をおこなった。
* [紅白ぺぱ合戦 LT で登壇](/slides/2024-11-30/cohackpp/)
* PHP カンファレンス 2024 当日スタッフとして参加
-{#articles}
-# 書いた記事
+# 書いた記事 {#articles}
今年はこのブログに月1記事以上の記事を書くという目標を立てていた。本数としては 12 本以上あるが、10月と11月はゼロになってしまった。
社内記事を社外向けにリライトする作業を中々進められていないので、2025年は定期的に消化していきたい。
* 社外記事 (このブログ): 15本
* 社内記事: 22本
-
* 年間で最も記事を書いた人として社内表彰された
-{#coding}
-# 作ったもの
+# 作ったもの {#coding}
今年は主に WebAssembly ランタイムと、カンファレンスの企画で使うシステムを作っていた。
後者のシステムでもサンドボックス化のための技術として WebAssembly を用いているので、今年は WebAssembly と戯れた一年だったと言える。
-* [Waddiwasi: pure PHP で書かれた WebAssembly ランタイム](https://github.com/nsfisis/php-waddiwasi)
-* [Albatross.PHP: PHPerKaigi 2024 のコードゴルフ企画で使われたシステム](https://github.com/nsfisis/phperkaigi-2024-albatross)
-* [Albatross.swift: iOSDC Japan 2024 のコードバトル企画で使われたシステム](https://github.com/nsfisis/iosdc-japan-2024-albatross)
-* [ReparoJSON: 文法エラーを直すだけの JSON フォーマッタ](/posts/2024-07-19/reparojson-fix-only-json-formatter/)
+* [Waddiwasi: pure PHP で書かれた WebAssembly ランタイム](https://github.com/nsfisis/php-waddiwasi)
+* [Albatross.PHP: PHPerKaigi 2024 のコードゴルフ企画で使われたシステム](https://github.com/nsfisis/phperkaigi-2024-albatross)
+* [Albatross.swift: iOSDC Japan 2024 のコードバトル企画で使われたシステム](https://github.com/nsfisis/iosdc-japan-2024-albatross)
+* [ReparoJSON: 文法エラーを直すだけの JSON フォーマッタ](/posts/2024-07-19/reparojson-fix-only-json-formatter/)
-{#misc}
-# その他
+# その他 {#misc}
-* [MN-Core Challenge #1 に参加](/posts/2024-09-28/mncore-challenge-1/)
+* [MN-Core Challenge #1 に参加](/posts/2024-09-28/mncore-challenge-1/)
* ISUCON 14 に参加
-{#outro}
-# おわりに
+# おわりに {#outro}
今年も大変お世話になりました。よいお年を!
diff --git a/services/nuldoc/content/posts/2025-01-08/phperkaigi-2023-tokens-q1.dj b/services/nuldoc/content/posts/2025-01-08/phperkaigi-2023-tokens-q1.md
index c3a5eb4..407e558 100644
--- a/services/nuldoc/content/posts/2025-01-08/phperkaigi-2023-tokens-q1.dj
+++ b/services/nuldoc/content/posts/2025-01-08/phperkaigi-2023-tokens-q1.md
@@ -18,10 +18,9 @@ remark = "公開"
date = "2025-01-11"
remark = "読みやすさのため一部の文言を調整"
---
-{#intro}
-# はじめに
+# はじめに {#intro}
-::: note
+:::note
これは PHPerKaigi 2023 の記事です。今は 2025 年ですが、PHPerKaigi 2023 の記事です。
:::
@@ -42,15 +41,13 @@ PHPerKaigi 当日も [PHPer チャレンジ解説セッション](/slides/2023-0
それぞれの問題はこちらの GitHub リポジトリ ( [nsfisis/PHPerKaigi2023-tokens](https://github.com/nsfisis/PHPerKaigi2023-tokens) ) からも閲覧できる。
-{#quiz}
-# Q1: An Art of Computer Programming
+# Q1: An Art of Computer Programming {#quiz}
第1問『An Art of Computer Programming』はこちら。
![全体がQRコードになっており、中央には小さな文字で「Password is one of the PHPer tokens.」と書かれている](/posts/2025-01-08/phperkaigi-2023-tokens-q1/Q1.png)
-{#how-to-solve}
-# 解き方
+# 解き方 {#how-to-solve}
まずはトークンを得る方法を解説抜きで説明する。次のように実行する。
@@ -60,11 +57,9 @@ $ echo "#iwillblog" | php Q1.png >/dev/null
無事に実行できていれば「#ModernPHPisStaticallyTypedLanguage」というトークンが得られる。
-{#commentary}
-# 解説
+# 解説 {#commentary}
-{#read-as-image}
-## 画像として解釈する
+## 画像として解釈する {#read-as-image}
まずは素直に画像として見てみよう。
全体は QR コードになっている。適当な QR コードリーダで読み込むと、次のようなテキストが表示されるはずだ。
@@ -78,8 +73,7 @@ Guess password. $ echo "password" | php Q1.png >/dev/null
次に QR コードの中央部に目を向けると、小さな文字で「Password is one of the PHPer tokens.」と書かれているのがわかる。
他の PHPer トークンの中から適切な1つを見つけだし、「パスワード」として渡すことで答えとなる PHPer トークンが得られるというわけだ。
-{#password}
-## パスワード
+## パスワード {#password}
不正なパスワードを使って実行してみると、次のようなエラーメッセージが表示される。
@@ -99,8 +93,7 @@ $ echo "foo" | php Q1.png >/dev/null
問題を置いていたリポジトリにヒントとしてパスワードのトークンが「i」で始まると書いていたのだが、これが意図せずミスリードになってしまった。
これは私のミスである。
-{#png-steganography}
-## PNG ステガノグラフィ
+## PNG ステガノグラフィ {#png-steganography}
QR コードも言っているように、このファイルは PNG 画像であるにもかかわらず PHP で実行することができる。なぜこのようなことが可能なのか。
@@ -122,8 +115,8 @@ CLI で実行する場合、PHP タグよりも前にあるデータは標準出
1. PNG ヘッダ (`IHDR` チャンク)
1. 実際の画像データ (`IDAT` チャンク)
1. PNG フッタ (`IEND` チャンク)
-1. *PHP タグ (`&lt;?php`)*
-1. *通常の PHP ソースコード*
+1. **PHP タグ (`&lt;?php`)**
+1. **通常の PHP ソースコード**
PNG ファイルとして読むときは PNG フッタ以降は無視され、PHP スクリプトとして読むときは PHP タグ以前が無視されるという仕掛けである。
@@ -155,8 +148,7 @@ Guess password. $ echo "password" | php Q1.png >/dev/null
なお、このように PNG 画像などに本来のデータとは異なる別のデータを隠すことを「ステガノグラフィ」( [Wikipedia「ステガノグラフィー」](https://ja.wikipedia.org/wiki/%E3%82%B9%E3%83%86%E3%82%AC%E3%83%8E%E3%82%B0%E3%83%A9%E3%83%95%E3%82%A3%E3%83%BC) ) と呼ぶ。
-{#php-program}
-## 実行される PHP プログラム
+## 実行される PHP プログラム {#php-program}
画像の正体がわかったところで、画像に隠されていた PHP プログラムについて見ていこう。
先ほどは一部しか記載しなかったので、全体を載せる。
@@ -282,8 +274,7 @@ $b = unpack('C*', file_get_contents(__FILE__));
そう、今回の問題の画像ファイル `Q1.png` は、PHP 製 Piet インタプリタであると同時に、Piet のソースコード画像でもあるのだ。
QR コード中央のカラフルな部分が Piet の命令になっている。
-{#piet-source-code}
-## Piet のソースコード
+## Piet のソースコード {#piet-source-code}
さて、Piet でどのようなコードが書かれて (いや、描かれて) いるのかを解説したいところだが、今の私にはできそうにない。
というのも、すでに述べたように Piet は「難解プログラミング言語」である。
@@ -293,19 +284,12 @@ QR コード中央のカラフルな部分が Piet の命令になっている
それぞれの部分はおおよそ次のようなことをやっている (再検証・再読解はしていないので大嘘かもしれない)。
* 左上: 入力受け付け
-
* 標準入力から1文字ずつ読み込み、入力がなくなるまでスタックに積む。多分。
-
* 上辺、右辺: パスワードの検証
-
* 入力がパスワードと一致するか (= `#iwillblog` かどうか) を調べる。多分。
-
* 下辺、左辺、上辺の3列目、右辺の3列目、下辺の2列目: トークンの出力
-
* パスワードと一致していればここに飛んでくる。正解のトークンを出力する。多分。
-
* 右辺の2列目、上辺の2列目: 不正解のメッセージ出力
-
* パスワードと一致していなければここに飛んでくる。不正解のときのメッセージを出力する。多分。
ところで、先ほど掲載した Piet のインタプリタのソースコード末尾には次のような箇所がある。
@@ -329,8 +313,7 @@ fwrite(STDERR, str_replace('403 Forbidden', '401 Unauthorized', $o));
これを解決するために私が選んだのは、インタプリタを改造し、本来のメッセージとは異なるメッセージを無理やり出力させて帳尻を合わせることだった。
そういうわけでこの Piet インタプリタは完全な Piet インタプリタではなく、「403 Forbidden」というテキストを絶対に出力できない。
-{#misc}
-## その他小ネタ
+## その他小ネタ {#misc}
ここまでで問題の核心部分は説明し終えたので、ここからは残った小ネタを紹介しておく。
@@ -338,8 +321,7 @@ fwrite(STDERR, str_replace('403 Forbidden', '401 Unauthorized', $o));
この問題で得られるトークン「#ModernPHPisStaticallyTypedLanguage」は特に元ネタがあるわけではない。当然のような顔で嘘を主張したかったのでこうなった。
-{#outro}
-# おわりに
+# おわりに {#outro}
この問題の自己評価はこちら。
問題の出題順はおおよそ作成した順になっているのだが、そのせいで難易度高めの問題が1問目に配置されてしまった。
diff --git a/services/nuldoc/content/posts/2025-01-26/yaml-breaking-changes-between-v1-1-and-v1-2.dj b/services/nuldoc/content/posts/2025-01-26/yaml-breaking-changes-between-v1-1-and-v1-2.md
index 44e8a4f..531a99e 100644
--- a/services/nuldoc/content/posts/2025-01-26/yaml-breaking-changes-between-v1-1-and-v1-2.dj
+++ b/services/nuldoc/content/posts/2025-01-26/yaml-breaking-changes-between-v1-1-and-v1-2.md
@@ -16,12 +16,11 @@ isInternal = true
date = "2025-01-26"
remark = "ブログ記事として一般公開"
---
-::: note
+:::note
この記事は、2021-06-30 に [デジタルサーカス株式会社](https://www.dgcircus.com/) の社内 Qiita Team に公開された記事をベースに、加筆修正して一般公開したものです。
:::
-{#intro}
-# はじめに
+# はじめに {#intro}
データ記述言語の一つ YAML には 1.0、1.1、1.2 のバージョンがある。
これらのうち、1.1 と 1.2 の間には無視できない非互換の変更が多く、1.2 に対応していないライブラリもある (Ruby 同梱の `yaml` など)。
@@ -29,25 +28,21 @@ remark = "ブログ記事として一般公開"
参照した仕様書はこちら: https://yaml.org/spec/1.2.2/ext/changes/
-{#breaking-changes}
-# 主な破壊的変更
+# 主な破壊的変更 {#breaking-changes}
-{#boolean-literals}
-### Boolean としてパースされるトークンが `true` / `false` とその亜種のみに
+### Boolean としてパースされるトークンが `true` / `false` とその亜種のみに {#boolean-literals}
この変更の影響が最も大きいと思われる。
YAML 1.1 では、boolean 値のリテラルとして `true`、`false` のほか `yes`、`no`、`y`、`n`、`on`、`off`、それらの大文字バージョンなどが認められていた。
YAML 1.2 では、`true` と `false`、それらの大文字バージョン (`True`、`TRUE`、`False`、`FALSE`) のみが boolean としてパースされるようになった。
-{#octal-literals}
-### 八進数リテラルには `0o` が必須に
+### 八進数リテラルには `0o` が必須に {#octal-literals}
C 言語などでは、`0` から始まる数字の列を八進数としてパースする。
YAML 1.1 もこれに準じていたが、1.2 からは `0o` のプレフィクスが必須となった ("o" は "octal" の "o")。
プログラミング言語では、Python や Haskell、Swift、Rust などがこの記法を採用している。
-{#merging}
-### `&lt;&lt;` によるマージが不可能に
+### `&lt;&lt;` によるマージが不可能に {#merging}
YAML 1.1 では、`&lt;&lt;` という文字列をキーに指定することで、マップをマージすることができた。
@@ -64,13 +59,11 @@ y:
1.2 からはこれができなくなる。
-{#number-separator}
-### 数字を `_` で区切るのが禁止に
+### 数字を `_` で区切るのが禁止に {#number-separator}
`1234567` を `1_234_567` と書けなくなった。
-{#outro}
-# おわりに
+# おわりに {#outro}
-全体的に、_There's more than one way to do it._ から _There should be one - and preferably only one - obvious way to do it._ へ移行しているように思われる。
+全体的に、*There's more than one way to do it.* から *There should be one - and preferably only one - obvious way to do it.* へ移行しているように思われる。
データ記述言語としては望ましい方向性ではないかと感じる。
diff --git a/services/nuldoc/content/posts/2025-02-24/phpcon-nagoya-2025-report.dj b/services/nuldoc/content/posts/2025-02-24/phpcon-nagoya-2025-report.md
index 35a9e27..ff2399b 100644
--- a/services/nuldoc/content/posts/2025-02-24/phpcon-nagoya-2025-report.dj
+++ b/services/nuldoc/content/posts/2025-02-24/phpcon-nagoya-2025-report.md
@@ -13,26 +13,20 @@ tags = [
date = "2025-02-24"
remark = "公開"
---
-{#intro}
-# はじめに
+# はじめに {#intro}
2025-02-22 に開催された [PHP カンファレンス名古屋](https://phpcon.nagoya/2025/) に参加した。
-{#sessions}
-# セッション感想
+# セッション感想 {#sessions}
特に印象に残ったセッションを二つピックアップした (タイトルと発表者名は fortee のプロポーザルページによる)。
* [PHPで印刷所に入稿できる名札データを作る by 長谷川智希 さん](https://fortee.jp/phpcon-nagoya-2025/proposal/26795bcc-78dd-431e-9538-7450779fa2cf)
-
* PHPerKaigi や iOSDC の名札は品質が高いので、他の勉強会やカンファレンスでもついつい使ってしまうのですが、その裏側を覗くことができ面白かったです。カンファレンスの1セッションという形でなければ触れることのないような話が聴けるのはカンファレンスに参加する醍醐味の一つだと思います。
-
* [PHP 製 OSS のメモリ問題を辻斬りしていく by sji さん](https://fortee.jp/phpcon-nagoya-2025/proposal/d3ecbb68-318d-4b03-abfe-9ecccc6beb81)
-
* 今回一番楽しみにしていた発表です。 [Reli](https://github.com/reliforp/reli-prof) は以前 [自作の WebAssembly 処理系を高速化するのに使ったのもあり](/slides/2024-03-15/ya8-2024/) その強力さについてはある程度知っていたつもりでしたが、実際に広く使われているライブラリでの調査過程を見ると唸るばかりです。これをすべて (FFI こそ使っているものの) pure PHP で実装しているとは俄に信じられません。
-{#my-session}
-# 登壇したセッション
+# 登壇したセッション {#my-session}
[「PHP 処理系の garbage collection を理解する 〜メモリはいつ解放されるのか〜」](https://fortee.jp/phpcon-nagoya-2025/proposal/24a2ec04-ca57-46f1-905c-52143a449eea) というタイトルで登壇もおこなった。タイトルどおり、PHP の garbage collection (GC) について扱った発表である。
@@ -40,8 +34,7 @@ remark = "公開"
ところで今回スライドのフォントサイズを大きくするために各スライドの見出し部分を消してみたのだが、結局ほとんどのスライドで見出しらしき文言が必要になったので、あまり効果はなかったかもしれない。
-{#outro}
-# おわりに
+# おわりに {#outro}
今回もカンファレンスくらいでしか聴けないようなセッションがいくつも聴けてよかった。
また、ちょうど連休だったのもあり名古屋も楽しむことができた。
diff --git a/services/nuldoc/content/posts/2025-03-27/zip-function-like-command-paste-command.dj b/services/nuldoc/content/posts/2025-03-27/zip-function-like-command-paste-command.md
index 4497799..8a0807b 100644
--- a/services/nuldoc/content/posts/2025-03-27/zip-function-like-command-paste-command.dj
+++ b/services/nuldoc/content/posts/2025-03-27/zip-function-like-command-paste-command.md
@@ -17,12 +17,11 @@ isInternal = true
date = "2025-03-27"
remark = "ブログ記事として一般公開"
---
-::: note
+:::note
この記事は、2021-03-22 に [デジタルサーカス株式会社](https://www.dgcircus.com/) の社内 Qiita Team に公開された記事をベースに、加筆修正して一般公開したものです。
:::
-{#intro}
-# 実現したい内容
+# 実現したい内容 {#intro}
次の2ファイル `a.txt` / `b.txt` から出力 `ab.txt` を得たい。
@@ -55,8 +54,7 @@ b3
ちょうど Python や Haskell などにある `zip` 関数のような動きをさせたい。
-{#paste-command}
-# 実現方法
+# 実現方法 {#paste-command}
記事タイトルに書いたように、`paste` コマンドを使うと実現できる。
diff --git a/services/nuldoc/content/posts/2025-03-28/http-1-1-send-multiple-same-headers.dj b/services/nuldoc/content/posts/2025-03-28/http-1-1-send-multiple-same-headers.md
index 687ddef..d4aae45 100644
--- a/services/nuldoc/content/posts/2025-03-28/http-1-1-send-multiple-same-headers.dj
+++ b/services/nuldoc/content/posts/2025-03-28/http-1-1-send-multiple-same-headers.md
@@ -16,12 +16,11 @@ isInternal = true
date = "2025-03-28"
remark = "ブログ記事として一般公開"
---
-::: note
+:::note
この記事は、2022-08-18 に [デジタルサーカス株式会社](https://www.dgcircus.com/) の社内 Qiita Team に公開された記事をベースに、加筆修正して一般公開したものです。
:::
-{#intro}
-# はじめに
+# はじめに {#intro}
HTTP version 1.1 で同じ名前のヘッダを2回送ると、どのように解釈されるのか。仕様を確認した。
@@ -31,11 +30,9 @@ HTTP version 1.1 で同じ名前のヘッダを2回送ると、どのように
ところで、HTTP 周りの仕様を探すときはここから飛ぶと便利: <https://developer.mozilla.org/en-US/docs/Web/HTTP/Resources_and_specifications>
-{#specification}
-# 仕様
+# 仕様 {#specification}
-{#sender}
-### 送信側
+### 送信側 {#sender}
> A sender MUST NOT generate multiple header fields with the same field
> name in a message unless either the entire field value for that
@@ -46,8 +43,7 @@ HTTP version 1.1 で同じ名前のヘッダを2回送ると、どのように
送信者は、同じ field name の header field を複数生成してはならない (MUST NOT)。
ただし、header field の値がコンマ区切りのリストとして定義されているか、header field がよく知られた例外 (後述) である場合はその限りでない。
-{#recipient}
-### 受信側
+### 受信側 {#recipient}
> A recipient MAY combine multiple header fields with the same field
> name into one "field-name: field-value" pair, without changing the
@@ -63,8 +59,7 @@ HTTP version 1.1 で同じ名前のヘッダを2回送ると、どのように
したがって、同じ field name を持つ header field がどのような順序で受信されたかは、結合された値の解釈に影響する。
よって、プロキシは、メッセージを転送する際、header field の順序を変えてはならない (MUST NOT)。
-{#exception}
-### 例外ケース: Set-Cookie
+### 例外ケース: Set-Cookie {#exception}
> Note: In practice, the "Set-Cookie" header field ([[RFC6265](https://datatracker.ietf.org/doc/html/rfc6265)]) often
> appears multiple times in a response message and does not use the
@@ -81,8 +76,7 @@ HTTP version 1.1 で同じ名前のヘッダを2回送ると、どのように
おそらく、「送信側」のところで書かれている「よく知られた例外」の一つがこれだと思われる。
-{#comma-separated-list}
-### どの header field がコンマ区切りのリストなのか
+### どの header field がコンマ区切りのリストなのか {#comma-separated-list}
上記のように、同じ field name を持つ header field を複数回送れるかどうかは、その header field がコンマ区切りのリストとして定義されているかどうかで決まる。では、特定の header field がその条件を満たしているかどうか知りたいときは、何を見ればよいのか。
@@ -93,8 +87,7 @@ HTTP の仕様として定義されているような header field であれば
そうでない場合 (たとえば `X-` から始まるもの等) は、MDN や各ベンダのドキュメントを探すことになるだろう。
-{#outro}
-# まとめ
+# まとめ {#outro}
* 送信側: 基本的には複数回送れない。コンマ区切りのヘッダは例外
* 受信側: 基本的には未規定。コンマ区切りのヘッダは複数回来たらその順に結合する
diff --git a/services/nuldoc/content/posts/2025-04-20/trick-2025-most-ruby-on-ruby-award.dj b/services/nuldoc/content/posts/2025-04-20/trick-2025-most-ruby-on-ruby-award.md
index f51396f..f7f7679 100644
--- a/services/nuldoc/content/posts/2025-04-20/trick-2025-most-ruby-on-ruby-award.dj
+++ b/services/nuldoc/content/posts/2025-04-20/trick-2025-most-ruby-on-ruby-award.md
@@ -14,8 +14,7 @@ tags = [
date = "2025-04-20"
remark = "公開"
---
-{#intro}
-# はじめに
+# はじめに {#intro}
2025-04-16 から 2025-04-18 にかけて開催された [RubyKaigi 2025](https://rubykaigi.org/2025/) に参加した (私が参加できたのは 1日目の 2025-04-16 のみ)。
@@ -24,16 +23,14 @@ remark = "公開"
この記事では、提出した作品の紹介と解説をおこなおうと思う。
-{#trick}
-# TRICK とは
+# TRICK とは {#trick}
TRICK とは RubyKaigi で不定期に開催されているコンテストで、Ruby で書かれた「変わった」コードを表彰する。早い話が [IOCCC](https://www.ioccc.org/) の Ruby 版である。
存在を知ってから次こそは出したいと思っていたところ、ちょうど RubyKaigi の地元開催と被ったのでこれ幸いとエントリーした。
-{#my-work}
-# 作品紹介
+# 作品紹介 {#my-work}
今回頂いたのは審査員賞の一つ eto award (公式の賞の名前に合わせて敬称略) で、"Most Ruby-on-Ruby" Award (『最もRuby on Ruby賞』) として受賞した (IOCCC と同じく、それぞれの賞に個別の名前が付く)。
@@ -41,7 +38,7 @@ TRICK とは RubyKaigi で不定期に開催されているコンテストで、
今回の TRICK では `ruby.wasm` の使用が認められている。
-> * *(NEW)* You can use [ruby.wasm](https://github.com/ruby/ruby.wasm).
+> * **(NEW)** You can use [ruby.wasm](https://github.com/ruby/ruby.wasm).
適当に HTTP サーバを立てて [`index.html`](https://github.com/tric/trick2025/blob/main/10-nsfisis/index.html) を開くと、次のように [`entry.rb`](https://github.com/tric/trick2025/blob/main/10-nsfisis/entry.rb) の内容が表示される。
@@ -63,13 +60,11 @@ TRICK とは RubyKaigi で不定期に開催されているコンテストで、
順に使ったテクニックを解説していく。
-{#quine}
-## Quine
+## Quine {#quine}
改めて quine について説明する。Quine とは、自身のソースコードを出力するようなプログラムである。Ruby では様々な方法で quine を書くことができるが、この作品で使っている基本形は以下のようなものである。
-{numbered="true"}
-```ruby
+```ruby numbered
eval $s=<<'EOS'
print "eval $s=<<'EOS'\n"
print $s
@@ -79,8 +74,7 @@ EOS
変数 `$s` に 2 行目、3 行目、4 行目が入っており、それに加えて 1 行目と 5 行目を出力すれば元のソースコードが得られる。実際には `$s` を加工してシンタックスハイライトや振り仮名を振ることになる。
-{#syntax-highlight}
-## シンタックスハイライト
+## シンタックスハイライト {#syntax-highlight}
シンタックスハイライトは、トークナイズとトークン種別に応じた色付けの2段階からなる。
@@ -130,8 +124,7 @@ Prism.lex($s).value[..-2].each {|t, *|
トークン種別の列挙にはそれなりに文字数を使ってしまうのだが、今回の TRICK のレギュレーションでは `index.html` にサイズ制限がなかったので好きに色を付けることができた。
-{#ruby-text}
-## 振り仮名
+## 振り仮名 {#ruby-text}
それぞれの英単語や記号に対応した振り仮名のデータは、プログラム中に埋め込まれている。
@@ -190,8 +183,7 @@ end
`kana()` 関数が多少長くはなるが、振り仮名データの数 x 2 バイト分サイズが減るのでこちらの方が短くなる。
サイズ制限の都合で振り仮名を振るのを諦めた記号もあったのでもったいない。
-{#outro}
-# おわりに
+# おわりに {#outro}
本っ当に取りたかったので心から嬉しいです。
全部で 3作提出したのですが、他の 2つも選外佳作として選出していただけた上、そのうちの "Least Truthful" については最後に Matz 氏から言及があり、審査員賞と合わせて望外の栄誉となりました。
diff --git a/services/nuldoc/content/posts/2025-04-24/composer-patches-v2-does-not-require-gnu-patch-even-on-macos.dj b/services/nuldoc/content/posts/2025-04-24/composer-patches-v2-does-not-require-gnu-patch-even-on-macos.md
index b64b798..09e9fe8 100644
--- a/services/nuldoc/content/posts/2025-04-24/composer-patches-v2-does-not-require-gnu-patch-even-on-macos.dj
+++ b/services/nuldoc/content/posts/2025-04-24/composer-patches-v2-does-not-require-gnu-patch-even-on-macos.md
@@ -18,12 +18,11 @@ isInternal = true
date = "2025-04-24"
remark = "公開"
---
-::: note
+:::note
この記事は、2025-04-10 に [デジタルサーカス株式会社](https://www.dgcircus.com/) の社内 Qiita Team に公開された記事をベースに、加筆修正して一般公開したものです。
:::
-{#intro}
-# はじめに
+# はじめに {#intro}
[Composer](https://getcomposer.org/) は PHP におけるデファクトスタンダードなパッケージ管理システムである。
@@ -34,8 +33,7 @@ Composer を拡張するプラグインの一つに、[composer-patches](https:/
弊社でも多くのプロジェクトで活用されており、のべ数では数百ものパッチが当てられている。
-{#on-macos}
-# macOS での問題点
+# macOS での問題点 {#on-macos}
`composer-patches` は、macOS で一部のパッチの適用に失敗することが知られている。
関連 issues:
@@ -56,8 +54,7 @@ $ echo 'PATH="/opt/homebrew/opt/gpatch/libexec/gnubin:$PATH"' >> ~/.zshrc
GNU patch を Homebrew などの手段でインストールし、BSD patch よりも優先されるパスに配置すれば問題が解消する。
-{#in-version-2}
-# v2 では
+# v2 では {#in-version-2}
現在ベータ版である `composer-patches` v2 では、このワークアラウンドが不要になる (見込み)。
@@ -68,6 +65,6 @@ GNU patch を Homebrew などの手段でインストールし、BSD patch よ
[2.0.0-beta1](https://github.com/cweagans/composer-patches/releases/tag/2.0.0-beta1) のリリースノートより:
-> * Only have git patchers and freeform patcher? by [*@cweagans*](https://github.com/cweagans) in [#472](https://github.com/cweagans/composer-patches/pull/476)
+> * Only have git patchers and freeform patcher? by [**@cweagans**](https://github.com/cweagans) in [#472](https://github.com/cweagans/composer-patches/pull/476)
この変更で `patch` コマンドへの依存が排除された。
diff --git a/services/nuldoc/content/posts/2025-05-05/make-tiny-self-hosted-c-compiler.dj b/services/nuldoc/content/posts/2025-05-05/make-tiny-self-hosted-c-compiler.md
index 51046f2..cb50ecd 100644
--- a/services/nuldoc/content/posts/2025-05-05/make-tiny-self-hosted-c-compiler.dj
+++ b/services/nuldoc/content/posts/2025-05-05/make-tiny-self-hosted-c-compiler.md
@@ -11,8 +11,7 @@ tags = [
date = "2025-05-05"
remark = "公開"
---
-{#intro}
-# はじめに
+# はじめに {#intro}
C コンパイラと言えば、世界三大自作したいソフトウェアの一角である。
というわけで [『低レイヤを知りたい人のためのCコンパイラ作成入門』](https://www.sigbus.info/compilerbook) (以下 compilerbook) 片手に作ることにした。
@@ -21,8 +20,7 @@ C コンパイラと言えば、世界三大自作したいソフトウェアの
[P4Dcc のリポジトリはこちら](https://github.com/nsfisis/P4Dcc)
-{#regulation}
-# レギュレーション
+# レギュレーション {#regulation}
* 実装するのは C 言語からアセンブリ言語への変換部分のみ。アセンブラやリンカは GCC をそのまま用いる
* compilerbook を読みながら実装してよい
@@ -30,114 +28,80 @@ C コンパイラと言えば、世界三大自作したいソフトウェアの
* GCC の出力は見てもよい。それ以外のコンパイラの出力 (特に 9cc などの compilerbook 準拠のコンパイラ) は見ない
* ソースコードの生成やデバッグに AI を使わない。ツールの使用方法を調べる目的 (GCC に渡すフラグなど) には使ってよい
-{#design}
-# 設計
+# 設計 {#design}
ゴールデンウィークの4日間で終わらせたいので、実装する言語機能は最低限に絞ることが必要になる。
今回は次のような設計とした (compilerbook の設計を踏襲しているものは除く)。
* 宣言の文法を単純にパースできるものに絞る
-
* `typedef` をサポートしない
-
* 構造体には必ず `struct` キーワードを書く
-
* 配列型をサポートしない
-
* 常にヒープに確保してポインタ経由で扱う
-
* 以上の制限により、型に関する情報が必ず変数名の前に来る
-
* 無くてもなんとかなる構文糖を実装しない。ソースを書くときに頑張る
-
* インクリメント・デクリメント演算子 (1足したり引いたりする)
* 複合代入演算子 (左辺と右辺で 2回書く)
-
* なお、`+=` と `-=` はセルフホスト達成後に実装された
-
* `while` (`for` で置き換える)
-
* なお、`while` はセルフホスト達成後に実装された
-
* `switch` (`if` で置き換える)
* ほか多数
-
* プリプロセッサのほとんどを実装しない
-
* 数値または識別子へ置換する単純な `#define` のみサポートする
* 特に、`#include` をサポートしないのは重要な設計判断。すべて 1ファイルでおこなう
-
* グローバル変数を用いない
-
* `stdin`、`stdout`、`stderr` を含む
* これは compilerbook とは大きく設計が変わった部分
* これにより、トップレベルに来るのは関数か構造体の定義/宣言のみとなった
-
* 変数のシャドウイングを実装しない
-
* 変数は常に関数スコープ
* グローバル変数もないので、スコープチェーンの実装が不要になる
-{#language-features}
-## 言語機能
+## 言語機能 {#language-features}
最終的にサポートされた機能は以下のとおり。
* 文
-
* `if` / `else`
* `for`
* `break`
* `continue`
* `return`
* `while` (実装はセルフホスト達成後)
-
* 式
-
* 二項演算
-
* `+` / `-` / `*` / `/` / `%`
* `==` / `!=`
* `<` / `<=` / `>` / `>=`
* `&&` / `||`
-
* 代入
-
* `=`
* `+=` / `-=` (実装はセルフホスト達成後)
-
* 単項演算: `-` / `!` / `*` / `&` / `sizeof`
* 関数呼び出し: `f(a, b)`
* 配列アクセス: `a[b]`
* メンバ呼び出し: `a.b` / `a->b`
* 整数リテラル
* 文字列リテラル
-
* 型
-
* `char`
* `int`
* `long`
* `void`
* `struct`
* それらのポインタ
-
* 宣言・定義
-
* 関数
* 構造体
-
* プリプロセッサ
-
* 引数なし `#define`
-{#development}
-# 開発
+# 開発 {#development}
時系列順に開発の様子を辿っていく。
-{#day1}
-## 1日目 (2025-05-03)
+## 1日目 (2025-05-03) {#day1}
compilerbook では整数一つのパース・コード生成から始めるが、今回は以下のようなソースをパースしてコード生成するところからスタートすることにした。
@@ -189,8 +153,7 @@ int main() {
}
```
-{#day2}
-## 2日目 (2025-05-03)
+## 2日目 (2025-05-03) {#day2}
この時点で、不足している機能はおおよそ2つ。ポインタと構造体である。
@@ -226,8 +189,7 @@ int main() {
これらの作業をおこなうことで、処理系自身のソースコード `main.c` をパースしてバイナリを出力することができるようになった。
いわゆる第2世代のコンパイラである。この現時点ではまだ第2世代コンパイラは何もできない (何を与えてもクラッシュする)。
-{#day3}
-## 3日目 (2025-05-03)
+## 3日目 (2025-05-03) {#day3}
さて、第2世代コンパイラが手に入ったので、ここからは地獄のデバッグ作業が始まる。多段になっているために問題が起きている箇所の特定が難しい。
@@ -278,8 +240,7 @@ $ diff -u <(hexdump -C p4dcc2) <(hexdump -C p4dcc3)
これにてセルフホスト達成である。
-{#outro}
-# おわりに
+# おわりに {#outro}
最終的な実装は1900行ほど、所要時間は20時間弱となった。
diff --git a/services/nuldoc/content/posts/2025-06-14/baba-is-you.dj b/services/nuldoc/content/posts/2025-06-14/baba-is-you.md
index 0484b66..f687721 100644
--- a/services/nuldoc/content/posts/2025-06-14/baba-is-you.dj
+++ b/services/nuldoc/content/posts/2025-06-14/baba-is-you.md
@@ -15,8 +15,7 @@ remark = "公開"
date = "2025-06-15"
remark = "後半 (ネタバレあり) のとある面について追記"
---
-{#intro}
-# Baba Is You とは
+# Baba Is You とは {#intro}
[Baba Is You](https://www.hempuli.com/baba/) という倉庫番系パズルゲームがある。
私がこれまでプレイしたことのあるパズルゲームの中で、間違いなく最高のパズルゲームだと断言できる。
@@ -26,11 +25,9 @@ remark = "後半 (ネタバレあり) のとある面について追記"
前半はネタバレなし、後半はネタバレありで書くので、プレイしていない人は前半まで読んだら閉じてほしい。
-{#no-spoiler}
-# 前半 (ネタバレなし)
+# 前半 (ネタバレなし) {#no-spoiler}
-{#what-is-baba-is-you}
-## どういうゲームか?
+## どういうゲームか? {#what-is-baba-is-you}
Baba Is You はいわゆる倉庫番パズルの一種である。
2D のグリッドで操作キャラを動かし、アイテムを押して動かすことでパズルを解く。
@@ -43,19 +40,12 @@ Baba Is You の特異な点は、倉庫番のルールが盤面上で動かせ
ここには次のようなルールがある。
* `BABA` `IS` `YOU`
-
* Baba はあなた (操作キャラ)
-
* `ROCK` `IS` `PUSH`
-
* 岩は押せる
-
* `WALL` `IS` `STOP`
-
* 壁は止まる (押せない)
-
* `FLAG` `IS` `WIN`
-
* 旗は勝ち
最初の状態では、`YOU` である baba (うさぎや猫のような白い生き物) が `WIN` である旗に触れることで勝利条件を満たしクリアとなる。
@@ -64,24 +54,17 @@ Baba Is You の特異な点は、倉庫番のルールが盤面上で動かせ
この面なら一例として次のようなルールが作れるだろう。
* `FLAG` `IS` `YOU`
-
* 旗が操作キャラになり、キー入力で動かせる
-
* `ROCK` `IS` `STOP`
-
* 岩が押せなくなる
-
* `BABA` `IS` `WALL`
-
* Baba が壁へと変化し、操作不能になる
-
* `WALL` `IS` `YOU` を同時に作っていればその限りでない!
この「ルール自体を変えられる」という性質により、パズルの難易度・複雑さが大きく上がっている。
プレイヤーは、どのオブジェクトを `YOU` にするのか、`WIN` にすべきは何か、どれに `PUSH` を付けるべきか、いつどの順番でルールを変えるのか、今の手札で作れるルールは何か等と悩みながら、次第に難しくなるパズルと格闘しなければならない。
-{#play-time-and-difficulty}
-## ゲームのボリューム・難易度
+## ゲームのボリューム・難易度 {#play-time-and-difficulty}
遊べる面の数は「200 以上」ある (Steam ストアページの表記より引用。正確な個数はここでは控える)。
これには DLC の分が含まれないので、実際には更に大量にある。
@@ -94,13 +77,11 @@ Baba Is You の特異な点は、倉庫番のルールが盤面上で動かせ
完全クリア以外にもいくつかマイルストーンはあるので、それを目指すのもありだろう。
-{#appeal}
-## 魅力
+## 魅力 {#appeal}
何がこのゲームを傑作たらしめているのか。
-{#very-difficult}
-### 高い難易度
+### 高い難易度 {#very-difficult}
すでに書いたが、このゲームは非常に難しい。
ゲームのルールを変えられると聞くと、何でもありの大味なプレイ体験かのように思えるかもしれない。
@@ -118,8 +99,7 @@ Baba Is You の特異な点は、倉庫番のルールが盤面上で動かせ
この特徴により、その難易度に比して理不尽さが大きく低減されていると感じる。
-{#very-flexible}
-### 新しい単語との出会い
+### 新しい単語との出会い {#very-flexible}
このゲームには `PUSH`、`STOP`、`WIN`、`YOU` 以外にもさまざまな単語がある。
新しい単語が導入されるときは大抵チュートリアル用の簡単な面が用意されており、プレイヤーはそこで新単語を使っていろいろと実験をすることになる。
@@ -129,8 +109,7 @@ Baba Is You の特異な点は、倉庫番のルールが盤面上で動かせ
危険な匂いのする単語と出会ったときの「こんな単語を許したらとんでもないことになるぞ」という感覚は実際にプレイしなければ味わえない。
-{#very-beautiful}
-### 美しいパズル
+### 美しいパズル {#very-beautiful}
私が大好きなとある面の話をしよう。
この面は最終盤に出現する。
@@ -144,8 +123,7 @@ Baba Is You の特異な点は、倉庫番のルールが盤面上で動かせ
他にも似たような例はいくつもある。
素晴しい出来の美しいパズルに何度も何度も出会うことができる。
-{#play-now}
-## Baba Is You をやれ
+## Baba Is You をやれ {#play-now}
Baba Is You は最高のパズルゲームである。
@@ -154,8 +132,7 @@ Baba Is You は最高のパズルゲームである。
この次のセクションからはネタバレありの感想を書くが、プレイしていない人はもちろん、プレイ中で完全クリアしていない人も読まないことを勧める。
その価値があるゲームだと保証する。
-{#spoiler}
-# 後半 (ネタバレあり)
+# 後半 (ネタバレあり) {#spoiler}
ではここからは完全クリアしたプレイヤーに向けて話そう。
@@ -163,15 +140,13 @@ Baba Is You は最高のパズルゲームである。
Steam の場合、全実績解除と読み替えてもよい。
すなわち、「Museum」や「New Adventures」を含まない。
-{#notation}
-## 表記
+## 表記 {#notation}
ゲーム上のオブジェクトについて次のように表記することにする。
* `BABA`: テキストとしての `BABA`
* Baba: オブジェクトとしての baba
* `A`、`B` など: 任意のテキスト
-
* そういうテキストが出てくる面もあるがその面の話はしない
* A、B など: 任意のオブジェクト
@@ -180,16 +155,13 @@ Steam の場合、全実績解除と読み替えてもよい。
また、個々のパズルのことはここまでと同様に「面」と呼ぶことにする。
「`LEVEL` というテキストが指すゲーム上のオブジェクト」は「level」と書く。
-{#impressive-levels}
-## 印象的な面
+## 印象的な面 {#impressive-levels}
ここからは印象的な面を語っていく。
-{#map toc="false"}
-### MAP
+### MAP {#map toc="false"}
-{#submerged-ruins-and-sunken-temple}
-#### SUBMERGED RUINS、SUNKEN TEMPLE
+#### SUBMERGED RUINS、SUNKEN TEMPLE {#submerged-ruins-and-sunken-temple}
![「SUBMERGED RUINS」のスクリーンショット](/posts/2025-06-14/baba-is-you/LEVEL_SUBMERGED_RUINS.jpeg)
@@ -199,8 +171,7 @@ Steam の場合、全実績解除と読み替えてもよい。
また、苦戦して解いた次の面がその面の派生で絶望するという経験をした最初の面。
この瞬間が苦しくもあり楽しくもある。
-{#prison-and-dungeon}
-#### PRISON、DUNGEON
+#### PRISON、DUNGEON {#prison-and-dungeon}
![「PRISON」のスクリーンショット](/posts/2025-06-14/baba-is-you/LEVEL_PRISON.jpeg)
@@ -209,23 +180,20 @@ Steam の場合、全実績解除と読み替えてもよい。
高難易度面で当然のように要求されるテクニックの初出。
可能な行動が大きく制限されているのでマシだが、それでも初見時には困惑した。
-{#further-fields}
-#### FURTHER FIELDS
+#### FURTHER FIELDS {#further-fields}
![「FURTHER FIELDS」のスクリーンショット](/posts/2025-06-14/baba-is-you/LEVEL_FURTHER_FIELDS.jpeg)
お気に入りの面。
`MOVE` を活用するのも `YOU` を一時的に消すのも好きなので、両方出てくるこの面は大好き。
-{#scenic-pond}
-#### SCENIC POND
+#### SCENIC POND {#scenic-pond}
![「SCENIC POND」のスクリーンショット](/posts/2025-06-14/baba-is-you/LEVEL_SCENIC_POND.jpeg)
はい。まあこいつは後で触れることにしよう。
-{#concrete-goals}
-#### CONCRETE GOALS
+#### CONCRETE GOALS {#concrete-goals}
![「CONCRETE GOALS」のスクリーンショット](/posts/2025-06-14/baba-is-you/LEVEL_CONCRETE_GOALS.jpeg)
@@ -233,8 +201,7 @@ Steam の場合、全実績解除と読み替えてもよい。
PRISON などと同様に一度理解すれば何ということのない面だが、最初に解けたときは偶然だった。
`FLAG` `IS` `WIN` がギリギリ取り出せそうに「見える」のが嫌らしい。
-{#lock-the-door}
-#### LOCK THE DOOR
+#### LOCK THE DOOR {#lock-the-door}
![「LOCK THE DOOR」のスクリーンショット](/posts/2025-06-14/baba-is-you/LEVEL_LOCK_THE_DOOR.jpeg)
@@ -243,8 +210,7 @@ PRISON などと同様に一度理解すれば何ということのない面だ
戯れにスロット2を使って2周目をやっていてこの面まで到達し、そこでようやく `SHIFT` 重ねが `MOVE` もどきになることを思い出した。
その意味でも印象深い面。
-{#insulation}
-#### INSULATION
+#### INSULATION {#insulation}
![「INSULATION」のスクリーンショット](/posts/2025-06-14/baba-is-you/LEVEL_INSULATION.jpeg)
@@ -252,8 +218,7 @@ MAP の前半 (~DEEP FOREST) では最も苦戦した面。
`SWAP` の理解が固まっておらず、「こういう状況が作れたら解けそうだ」という勘が働かなかった。
正直なところ `SWAP` は今も苦手意識がある (終盤で強制的に学ばされる `SHIFT` と違って、それほど高難度面での出番がないのも大きいと思う)。
-{#bottleneck}
-#### BOTTLENECK
+#### BOTTLENECK {#bottleneck}
![「BOTTLENECK」のスクリーンショット](/posts/2025-06-14/baba-is-you/LEVEL_BOTTLENECK.jpeg)
@@ -261,8 +226,7 @@ MAP の中で一番苦しんだ面。
実は一度ここで投げて諦めたのだが、`EMPTY` を理解した今となっては脳内でも瞬殺できるくらい簡単になってしまった。
最初に面を見てから解き終わるまでの時間は間違いなく最長で、半年以上かかっている (他は長くとも数日間)。
-{#heavy-cloud}
-#### HEAVY CLOUD
+#### HEAVY CLOUD {#heavy-cloud}
![「HEAVY CLOUD」のスクリーンショット](/posts/2025-06-14/baba-is-you/LEVEL_HEAVY_CLOUD.jpeg)
@@ -270,15 +234,13 @@ MAP の中で一番苦しんだ面。
解き終わった後に思わず「美しい......」と呟いてしまったのはこの面だけだった。
Baba Is You の好きな面はと聞かれれば真っ先にこれを挙げる。
-{#adventurers}
-#### ADVENTURERS
+#### ADVENTURERS {#adventurers}
![「ADVENTURERS」のスクリーンショット](/posts/2025-06-14/baba-is-you/LEVEL_ADVENTURERS.jpeg)
難所の多い FLOWER GARDEN の癒し。Hand が `MOVE` と `SHIFT` でガチャガチャ動くのを見るのが楽しい。
-{#out-at-sea}
-#### OUT AT SEA
+#### OUT AT SEA {#out-at-sea}
![「OUT AT SEA」のスクリーンショット](/posts/2025-06-14/baba-is-you/LEVEL_OUT_AT_SEA.jpeg)
@@ -287,8 +249,7 @@ MAP の問題児。正攻法がテキスト重ねである最初の面。
「テキスト同士を重ねて `A` `IS` `PUSH` と `B` `IS` `PUSH` を両立させるというぶっ飛んだアイデアを実現してもなお解けないのか?」「この方針がまさか間違っているなどということがあるのか、いやそんなはずはない......」
実際のところそこからのリカバリーはすぐできたが、そのときの絶望はこれまででも最大であった。
-{#seeking-acceptance}
-#### SEEKING ACCEPTANCE
+#### SEEKING ACCEPTANCE {#seeking-acceptance}
![「SEEKING ACCEPTANCE」のスクリーンショット](/posts/2025-06-14/baba-is-you/LEVEL_SEEKING_ACCEPTANCE.jpeg)
@@ -296,8 +257,7 @@ MAP の問題児。正攻法がテキスト重ねである最初の面。
FURTHER FIELDS の精神的後継のようなものなので当然かもしれない。
せっせと働く bird がかわいい。
-{#fragile-existence}
-#### FRAGILE EXISTENCE
+#### FRAGILE EXISTENCE {#fragile-existence}
![「FRAGILE EXISTENCE」のスクリーンショット](/posts/2025-06-14/baba-is-you/LEVEL_FRAGILE_EXISTENCE.jpeg)
@@ -306,8 +266,7 @@ MAP の印象的な面と言えば、これを取り上げないわけにはい
ここまで Baba Is You を進めたプレイヤーであれば、初出のテキストが現れたらまずはその場の色々なテキストと組み合わせてみて相互作用を確認する。
それを見事に利用されたというか、気付かずにはいられないように仕向けられているというか、本当によくできたゲームである。
-{#map}
-#### MAP
+#### MAP {#map}
MAP 自身。FRAGILE EXISTENCE でそれに気付いたなら当然 HOSTILE ENVIRONMENT でも気付くし、MAP で baba が操作できることに気付いたならもちろん右下のルールに目を向ける。
次に考えるのは無論こうだ。ここで flag を取ったらどうなるんだ?
@@ -316,11 +275,9 @@ MAP 自身。FRAGILE EXISTENCE でそれに気付いたなら当然 HOSTILE ENVI
??? でまたしても待ち構える `BABA` `IS` `YOU` と不穏な `LEVEL` のテキスト、そして GLITCH の `W` `E` `L` `C` `O` `M` `E`。
間違いなく最高のパズルゲームだと確信した。
-{#triple-question toc="false"}
-### ???
+### ??? {#triple-question toc="false"}
-{#vip-area}
-#### VIP AREA
+#### VIP AREA {#vip-area}
![「VIP AREA」のスクリーンショット](/posts/2025-06-14/baba-is-you/LEVEL_VIP_AREA.jpeg)
@@ -328,8 +285,7 @@ MAP 自身。FRAGILE EXISTENCE でそれに気付いたなら当然 HOSTILE ENVI
PRISON と DUNGEON で既出のテクニックが肝だが、ちと離れすぎじゃないのか。
この面のリメイクもあるが、ここで苦しんだからかそちらはあまり苦戦しなかった。
-{#ultimate-maze}
-#### ULTIMATE MAZE
+#### ULTIMATE MAZE {#ultimate-maze}
![「ULTIMATE MAZE」のスクリーンショット](/posts/2025-06-14/baba-is-you/LEVEL_ULTIMATE_MAZE.jpeg)
@@ -339,14 +295,12 @@ PRISON と DUNGEON で既出のテクニックが肝だが、ちと離れすぎ
個人的にこのゲームで一番苦しかったのがここの `TEXT` 変換解である。
単純な難しさに加え、実績が取れていない原因がこの面だという確信も持てなかったので、解けるかどうかわからない状態で挑み続けることとなり疲弊した。
-{editat="2025-06-15" operation="追記"}
-::: edit
+:::edit{editat="2025-06-15" operation="追記"}
??? の DO IT YOURSELF 以降を開くにはこの面の `LEVEL` `IS` `TEXT` が必須だと思っていたのだが、どうもそうではないらしい。
いずれにせよそれを思いつけなかったので同じことか。
:::
-{#stardrop-and-meteor-strike}
-#### STARDROP、METEOR STRIKE
+#### STARDROP、METEOR STRIKE {#stardrop-and-meteor-strike}
![「STARDROP」のスクリーンショット](/posts/2025-06-14/baba-is-you/LEVEL_STARDROP.jpeg)
@@ -355,42 +309,36 @@ PRISON と DUNGEON で既出のテクニックが肝だが、ちと離れすぎ
両方とも難しい面ではあったが、単文字のテキストが綺麗に活用された美しい面として印象に残っている。
`G` `R` `A` `S` `S` `IS` `H` `O` `T` をこれほど無駄なく使えるとは!
-{#getting-together}
-#### GETTING TOGETHER
+#### GETTING TOGETHER {#getting-together}
![「GETTING TOGETHER」のスクリーンショット](/posts/2025-06-14/baba-is-you/LEVEL_GETTING_TOGETHER.jpeg)
初見のインパクト大にして難易度も相応に高い良作。
この頃はまだ `SHIFT` を「理解」していなかったので大変だったが、ここを越えたことでむしろこの後の難所が楽になったと言える。
-{#depths toc="false"}
-### DEPTHS
+### DEPTHS {#depths toc="false"}
??? といういかにもクリア後のオマケっぽい名前のマップを攻略したらまだまだ深淵が待ち構えていた。
-{#crushers}
-#### CRUSHERS
+#### CRUSHERS {#crushers}
![「CRUSHERS」のスクリーンショット](/posts/2025-06-14/baba-is-you/LEVEL_CRUSHERS.jpeg)
DEPTHS の序盤で道を塞いでいる必須面であるにもかかわらず圧倒的難易度で立ちはだかる凶悪な面。
大苦戦した挙句 `LEVEL` `IS` `BELT` を作ってアレ?となったのは私だけではないはず。
-{#parade}
-#### PARADE
+#### PARADE {#parade}
![「PARADE」のスクリーンショット](/posts/2025-06-14/baba-is-you/LEVEL_PARADE.jpeg)
取れる行動が多いこと、もう少しで解けそうなルートが多いこと、そのどれもが一筋縄ではいかないこと、すべて揃った高難度面。
昔のバージョンでは ??? に置いてあったらしい。そんなバカな。
-{#meta toc="false"}
-### META
+### META {#meta toc="false"}
ここではもう覚悟していたので続きがあることには驚かなかったが、明らかに不穏な `CURSOR` に震えつつ先へ進むことになる。
-{#booby-trap}
-#### BOOBY TRAP
+#### BOOBY TRAP {#booby-trap}
![「BOOBY TRAP」のスクリーンショット](/posts/2025-06-14/baba-is-you/LEVEL_BOOBY_TRAP.jpeg)
@@ -400,8 +348,7 @@ META での試行錯誤のためにはこいつの形を毎回変えなければ
しかもどの変換もそれなりにステップ数を要するのが厄介である。
印象に残った面であるのは確か。
-{#the-box}
-#### THE BOX
+#### THE BOX {#the-box}
![「THE BOX」のスクリーンショット](/posts/2025-06-14/baba-is-you/LEVEL_THE_BOX.jpeg)
@@ -409,8 +356,7 @@ META での試行錯誤のためにはこいつの形を毎回変えなければ
外の level を参照させるギミックは、`LEVEL` `IS` `A` の変換をやりだした頃からいつかあるはずと思っていたので、そのとおりのパズルが出てきてくれて嬉しい。
これぞ Baba Is You。
-{#the-return-of-scenic-pond}
-#### THE RETURN OF SCENIC POND
+#### THE RETURN OF SCENIC POND {#the-return-of-scenic-pond}
![「THE RETURN OF SCENIC POND」のスクリーンショット](/posts/2025-06-14/baba-is-you/LEVEL_THE_RETURN_OF_SCENIC_POND.jpeg)
@@ -420,8 +366,7 @@ META での試行錯誤のためにはこいつの形を毎回変えなければ
最終盤に配置されていたことで難易度の割には苦戦しなかったが、難易度以上にリメイクの美しさに感動した面。
-{#difficult-levels}
-## 初見時難易度ランキング
+## 初見時難易度ランキング {#difficult-levels}
最後に、初見時の難易度を 10 位までランキングにしてみた。
あくまで初見のときの難易度なので、面自体の難易度ではない。
@@ -439,7 +384,6 @@ META での試行錯誤のためにはこいつの形を毎回変えなければ
1. VIP AREA
1. STARDROP
-{#outro}
-# おわりに
+# おわりに {#outro}
神ゲー。プレイ済みの人は会ったとき一番好きな面の話でもしましょう。
diff --git a/services/nuldoc/content/posts/2025-07-15/partial-surrender-to-ebooks.dj b/services/nuldoc/content/posts/2025-07-15/partial-surrender-to-ebooks.md
index f009061..dea3b22 100644
--- a/services/nuldoc/content/posts/2025-07-15/partial-surrender-to-ebooks.dj
+++ b/services/nuldoc/content/posts/2025-07-15/partial-surrender-to-ebooks.md
@@ -23,4 +23,4 @@ remark = "公開"
この基準だと『ヘイル・メアリー』の件は避けられないわけだが、幸いにして直近で読んだ本は本の山の山頂付近に居るので見失うことはないだろう。しばらくは。
-[^thin-red-line] 『プロジェクト・ヘイル・メアリー』最序盤の一節
+[^thin-red-line]: 『プロジェクト・ヘイル・メアリー』最序盤の一節
diff --git a/services/nuldoc/content/posts/2025-10-31/representing-single-value-with-half-open-float-interval.dj b/services/nuldoc/content/posts/2025-10-31/representing-single-value-with-half-open-float-interval.md
index 961cb89..3d0ea07 100644
--- a/services/nuldoc/content/posts/2025-10-31/representing-single-value-with-half-open-float-interval.dj
+++ b/services/nuldoc/content/posts/2025-10-31/representing-single-value-with-half-open-float-interval.md
@@ -22,16 +22,15 @@ isInternal = true
date = "2025-10-31"
remark = "公開"
---
-::: note
+:::note
この記事は、2025-01-23 に [デジタルサーカス株式会社](https://www.dgcircus.com/) の社内 Qiita Team に公開された記事をベースに、加筆修正して一般公開したものです。
:::
-::: note
+:::note
この記事の内容を、[PHP 勉強会@東京 第 180 回](/slides/2025-10-29/phpstudy-tokyo-180/) で発表しました。
:::
-{#intro}
-# はじめに
+# はじめに {#intro}
数値の範囲を指定して検索をおこなう API の中に、半開区間を指定させるものがある。半開区間とは、一方の端を含み一方の端を含まないような区間である。ここでは特に左端が閉じ右端が開いているような区間を扱う。例えば、次の区間 `[3, 7)` は `3 <= x < 7` であるような `x` の集合である。
@@ -40,8 +39,7 @@ remark = "公開"
しかし、検索の対象が実数であればどうだろうか?
-{#half-open-real-interval}
-# 実数の半開区間
+# 実数の半開区間 {#half-open-real-interval}
ちょうど `1` だけを含むような半開区間が作れないか考えよう。つまり、左端に `1` を、右端に `1` より少しだけ大きい値を指定して、「ちょうど `1`」を表すような範囲を作れないだろうか。
@@ -50,16 +48,14 @@ remark = "公開"
数学の世界ではこのような区間を作ることはできない。では、コンピュータ上ならばどうだろうか?
-{#float-numbers}
-# コンピュータにおける実数表現
+# コンピュータにおける実数表現 {#float-numbers}
コンピュータにおける実数の表現にはさまざまなものがあるが、ここでは最もよく使われる IEEE 754 という標準規格に従う形式、その中でも `binary64` と呼ばれる形式を考えることにする。これは多くの言語で `float` や `double` と呼ばれるものと同じである。
`binary64` は 64 bit で構成されており、無限個ある実数をすべて覆い尽くすことはできない。数学の上では存在しなかった `p` も、`binary64` の範囲に実数を限定すれば都合のよい `p` を見つけることができる。
-{#single-value-float-interval}
-# 浮動小数点数で単一値を指す半開区間を作る
+# 浮動小数点数で単一値を指す半開区間を作る {#single-value-float-interval}
結論から言うと、`p` は `1.0000000000000002` である。`[1, 1.0000000000000002)` は `binary64` の範囲で `1` しか含まない。別の言い方をすれば、`1 < x < 1.0000000000000002` を満たすような `x` は、`binary64` で表せない。
@@ -79,8 +75,7 @@ IEEE 754 にはこのような用途に用いることができる `nextUp` と
これを使えば、ある数 `x` が与えられたとき、`[x, nextUp(x))` という半開区間を作ればちょうど `x` だけを含むような範囲を表すことができる。
-{#nextup-in-php}
-# PHP で nextUp を実装する
+# PHP で nextUp を実装する {#nextup-in-php}
プログラミング言語によっては標準ライブラリに `nextUp` 相当の操作が定められているものもある。
PHP には無かったので自作した。
@@ -124,7 +119,6 @@ PHP には無かったので自作した。
}
```
-{#outro}
-# おわりに
+# おわりに {#outro}
頻繁に必要になるようなものではないが、いつか誰かを救えれば幸いである。
diff --git a/services/nuldoc/content/posts/2025-11-09/rubiks-cube-blindfolded-first-success.dj b/services/nuldoc/content/posts/2025-11-09/rubiks-cube-blindfolded-first-success.md
index bba1529..0d14322 100644
--- a/services/nuldoc/content/posts/2025-11-09/rubiks-cube-blindfolded-first-success.dj
+++ b/services/nuldoc/content/posts/2025-11-09/rubiks-cube-blindfolded-first-success.md
@@ -11,8 +11,7 @@ tags = [
date = "2025-11-09"
remark = "公開"
---
-{#intro}
-# はじめに
+# はじめに {#intro}
ルービックキューブには blindfolded (目隠し; 以下略称の BLD と表記) という種目がある。
崩れたキューブを見てその状態を覚え (このときキューブは動かさない)、目隠しして揃えるという競技である。
@@ -20,8 +19,7 @@ remark = "公開"
4 日前から勉強と練習を始めて、本日初成功したのでここまでの過程や勉強法について書き残しておく。
-{#references}
-# 参考元
+# 参考元 {#references}
BLD の解法については、YouTube のこちらの動画を全面的に参考にした。
@@ -33,42 +31,31 @@ BLD の解法はいくつかあるようだが、この動画で取り上げら
OP 法では PLL の Y-perm (を少し変形させたもの) を使うため、最低限 Y-perm は何も考えずに回せる必要があるだろう。
-{#learning}
-# 学習の進め方
+# 学習の進め方 {#learning}
私は次のように学習を進めた。
* 1 日目
-
* エッジを揃える手順を学ぶ
-
* 2 日目
-
* エッジ手順を復習する
* コーナーを揃える手順を学ぶ
* パリティを解消する手順を学ぶ
-
* 3 日目
-
* エッジ手順を復習する
* コーナー手順を復習する
* パリティ手順を復習する
-
* 4 日目
-
* 目を開けた状態で崩して揃える
* ナンバリングと分析を学ぶ
-
* 5 日目
-
* 分析を紙に書き起こし、それを見ながらキューブは見ずに揃える
* **本番に挑戦**
5 日目の 4 回目のトライで、19 分 6 秒かけて初成功した。
-{#memorization}
-# 記憶法
+# 記憶法 {#memorization}
[使ったナンバリングは hinemos (BLD を支援するツールが多数公開されたウェブサービス) に登録している。](https://saxcy.info/hinemos/numbering3.html?useParam=true&BDL=%E3%81%A4&BDR=%E3%81%A6&BRU=%E3%81%9F&DBL=%E3%81%B8&DBR=%E3%81%B5&DFL=%E3%81%AF&DFR=%E3%81%B2&FDL=%E3%81%91&FDR=%E3%81%8F&FLU=%E3%81%8B&FRU=%E3%81%8D&LBD=%E3%82%81&LDF=%E3%82%80&LFU=%E3%81%BF&RBD=%E3%81%99&RBU=%E3%81%97&RDF=%E3%81%9B&RFU=%E3%81%95&UBL=@&UBR=%E3%81%84&UFL=%E3%81%88&UFR=%E3%81%86&BD=%E3%81%A4&BL=%E3%81%A1&BR=%E3%81%A6&BU=%E3%81%9F&DB=%E3%81%B5&DF=@&DL=%E3%81%B8&DR=%E3%81%B2&FL=%E3%81%91&FR=%E3%81%8D&FU=%E3%81%8B&LB=%E3%82%81&LD=%E3%82%80&LF=%E3%81%BF&LU=%E3%81%BE&RB=%E3%81%97&RD=%E3%81%99&RF=%E3%81%9B&RU=%E3%81%95&UB=%E3%81%82&UF=%E3%81%86&UL=%E3%81%88&UR=%E3%81%84)
@@ -84,8 +71,7 @@ OP 法では PLL の Y-perm (を少し変形させたもの) を使うため、
結局のところ、覚えておかなければならない時間が短いほど、それだけ記憶も楽になるということだ。
-{#next-step}
-# 次のステップ
+# 次のステップ {#next-step}
今は人前で披露するには速度と成功率がお話にならないので、宴会芸レベルになるように引き上げていきたい。
まずはレターペアでも決めようと思う。
diff --git a/services/nuldoc/content/posts/2025-11-27/anybatross-writeup.dj b/services/nuldoc/content/posts/2025-11-27/anybatross-writeup.md
index 04ba13f..739bcd0 100644
--- a/services/nuldoc/content/posts/2025-11-27/anybatross-writeup.dj
+++ b/services/nuldoc/content/posts/2025-11-27/anybatross-writeup.md
@@ -14,8 +14,7 @@ tags = [
date = "2025-11-27"
remark = "公開"
---
-{#intro}
-# はじめに
+# はじめに {#intro}
[YAPC::Fukuoka 2025](https://yapcjapan.org/2025fukuoka/) に際してカヤックさんが開催されていたコードゴルフコンテスト、[Anybatross](https://perlbatross.kayac.com/contest/2025fukuoka) に参加し、優勝した。
この記事では自分の回答について解説する。
@@ -24,11 +23,9 @@ remark = "公開"
このコンテストは何度か開催されており、[前々回](https://perlbatross.kayac.com/contest/2024hiroshima) に参加したときは総合 2 位だった (前回開催は不参加)。
-{#hole-1}
-# Hole 1
+# Hole 1 {#hole-1}
-{#answer}
-## 回答 (45 byte)
+## 回答 (45 byte) {#answer}
```perl
print$a+=$\=y/8B/0/+y/0469ADO-R//.$/,","for<>
@@ -37,13 +34,11 @@ print$a+=$\=y/8B/0/+y/0469ADO-R//.$/,","for<>
Hole 1 については同一言語・同一スコアの回答が複数あるので詳細は省略する。
-{#hole-2}
-# Hole 2
+# Hole 2 {#hole-2}
こちらは縮めていった過程も記載する。
-{#answer-a}
-## 回答 A (107 byte)
+## 回答 A (107 byte) {#answer-a}
最終スコアを見ると 4 位タイ (107 byte) が多く、3 位以上の回答と明確にアルゴリズムの差があるのでここから解説をスタートしようと思う。
@@ -72,8 +67,7 @@ x = [["la"], 3]
その他、`?A` や `String#*`、`it`、numbered parameters などの細かいテクニックについては、「Ruby コードゴルフ」で調べるか、最近の Ruby のリリースノートを読むと見つかるはず。
-{#answer-b}
-## 回答 B (107 byte)
+## 回答 B (107 byte) {#answer-b}
回答 A をぐっと睨むと、`m>1&&(...)` の括弧を削りたくなる。しかしそれには `m>1&&` がどうしても邪魔になる。というわけで終了条件を工夫することでなんとか `m` を排除できないかを考えた。それがこちら。
@@ -88,8 +82,7 @@ puts$**?,,s
しかし、これだけでは回答 A とスコアは変わらない。これを短縮したものがこちら。
-{#answer-c}
-## 回答 C (106 byte)
+## 回答 C (106 byte) {#answer-c}
`Kernel#gets` は、入力を特殊変数 `$_` へ代入する。これは Perl 由来の挙動で、Ruby にはいくつか `$_` を参照するものがある。これを使って変数 `s` を置き換えると次のようになる。
@@ -102,8 +95,7 @@ puts$**?,,$_
これで 1 byte 縮む。
-{#answer-d}
-## 回答 D (104 byte)
+## 回答 D (104 byte) {#answer-d}
回答 C を眺めると、`b` への代入に文字を費やしすぎている。これを `String#gsub!` の第一引数に直接書いてはどうか。更に、直前のマッチしたパターンを指す特殊変数 `$&` を使えば、変数 `b` を排除できる。それがこちら。
@@ -115,8 +107,7 @@ puts$**?,,$_
コードゴルフとして `[0][0]` は気になるところだが、それでも 2 byte 一気に縮まった。
-{#answer-e}
-## 回答 E (103 byte)
+## 回答 E (103 byte) {#answer-e}
回答 D を提出したことで tompng 氏のスコアを越え、氏のコードを閲覧できるようになった。そこから少し変更したものが、mame 氏と (変数名などの些事を除いて) 同じ以下のコードである。
@@ -138,8 +129,7 @@ puts$**?,,s
```
-{#answer-f}
-## 回答 F (103 byte)
+## 回答 F (103 byte) {#answer-f}
さて、ヒントを求めて [前回開催の公式解説ブログ](https://techblog.kayac.com/yapc2024hakodate-perlbatross-result) を読んでいたところ、次のような興味深い記述を発見した。
@@ -180,8 +170,7 @@ puts$**?,
外側の括弧を移動させてくれば `gsub` と `b` の間のスペースを消せるが、`;` を `&&` にせねばならず失敗する。この問題を解決したのが最終回答の 102 byte コードである。
-{#answer-g}
-## 最終回答 (102 byte)
+## 最終回答 (102 byte) {#answer-g}
```ruby
#!ruby -p
@@ -194,8 +183,7 @@ puts$**?,
これによって Hole 2 の単独 1 位スコアとなった。
-{#llm}
-# LLM のコードゴルフ性能
+# LLM のコードゴルフ性能 {#llm}
ところで、今回スコア短縮に行き詰まったあたりから LLM を使ってみた (コンテストのレギュレーションとして明示的に利用が許可されている)。
@@ -205,7 +193,6 @@ puts$**?,
なお、LLM は文字数カウントを大の苦手としているので、縮めた (と言い張っている) コードを `wc` コマンドに渡し、その結果を LLM に見てもらった方がよい。
-{#outro}
-# おわりに
+# おわりに {#outro}
楽しかったです。運営のみなさま、ありがとうございました!
diff --git a/services/nuldoc/deno.jsonc b/services/nuldoc/deno.jsonc
index aff8a05..3174e5c 100644
--- a/services/nuldoc/deno.jsonc
+++ b/services/nuldoc/deno.jsonc
@@ -1,6 +1,5 @@
{
"imports": {
- "@djot/djot": "npm:@djot/djot@^0.3.2",
"@std/assert": "jsr:@std/assert@^1.0.13",
"@std/cli": "jsr:@std/cli@^1.0.20",
"@std/fs": "jsr:@std/fs@^1.0.19",
@@ -9,6 +8,13 @@
"@std/toml": "jsr:@std/toml@^1.0.8",
"checksum/": "https://deno.land/x/checksum@1.4.0/",
"shiki": "npm:shiki@^3.7.0",
+ "unified": "npm:unified@^11.0.0",
+ "remark-parse": "npm:remark-parse@^11.0.0",
+ "remark-gfm": "npm:remark-gfm@^4.0.0",
+ "remark-directive": "npm:remark-directive@^3.0.0",
+ "remark-smartypants": "npm:remark-smartypants@^3.0.0",
+ "mdast": "npm:@types/mdast@^4.0.0",
+ "mdast-util-directive": "npm:mdast-util-directive@^3.0.0",
"zod/": "https://deno.land/x/zod@v3.24.2/"
},
"permissions": {
diff --git a/services/nuldoc/deno.lock b/services/nuldoc/deno.lock
index ac2480e..225fcf2 100644
--- a/services/nuldoc/deno.lock
+++ b/services/nuldoc/deno.lock
@@ -16,8 +16,14 @@
"jsr:@std/path@^1.1.1": "1.1.1",
"jsr:@std/streams@^1.0.10": "1.0.10",
"jsr:@std/toml@^1.0.8": "1.0.8",
- "npm:@djot/djot@~0.3.2": "0.3.2",
- "npm:shiki@^3.7.0": "3.7.0"
+ "npm:@types/mdast@4": "4.0.4",
+ "npm:mdast-util-directive@3": "3.1.0",
+ "npm:remark-directive@3": "3.0.1",
+ "npm:remark-gfm@4": "4.0.1",
+ "npm:remark-parse@11": "11.0.0",
+ "npm:remark-smartypants@3": "3.0.2",
+ "npm:shiki@^3.7.0": "3.7.0",
+ "npm:unified@11": "11.0.5"
},
"jsr": {
"@std/assert@1.0.13": {
@@ -91,10 +97,6 @@
}
},
"npm": {
- "@djot/djot@0.3.2": {
- "integrity": "sha512-joMKR24B8rxueyFiJbpZAqEiypjvOyzTxzkhyr0q5mM/sUBaOD3unna/9IxtOotFugViyYlkIRaiXg3xM//zxg==",
- "bin": true
- },
"@shikijs/core@3.7.0": {
"integrity": "sha512-yilc0S9HvTPyahHpcum8eonYrQtmGTU0lbtwxhA6jHv4Bm1cAdlPFRCJX4AHebkCm75aKTjjRAW+DezqD1b/cg==",
"dependencies": [
@@ -141,24 +143,48 @@
"@shikijs/vscode-textmate@10.0.2": {
"integrity": "sha512-83yeghZ2xxin3Nj8z1NMd/NCuca+gsYXswywDy5bHvwlWL8tpTQmzGeUuHd9FC3E/SBEMvzJRwWEOz5gGes9Qg=="
},
+ "@types/debug@4.1.12": {
+ "integrity": "sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==",
+ "dependencies": [
+ "@types/ms"
+ ]
+ },
"@types/hast@3.0.4": {
"integrity": "sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==",
"dependencies": [
- "@types/unist"
+ "@types/unist@3.0.3"
]
},
"@types/mdast@4.0.4": {
"integrity": "sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==",
"dependencies": [
- "@types/unist"
+ "@types/unist@3.0.3"
]
},
+ "@types/ms@2.1.0": {
+ "integrity": "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA=="
+ },
+ "@types/nlcst@2.0.3": {
+ "integrity": "sha512-vSYNSDe6Ix3q+6Z7ri9lyWqgGhJTmzRjZRqyq15N0Z/1/UnVsno9G/N40NBijoYx2seFDIl0+B2mgAb9mezUCA==",
+ "dependencies": [
+ "@types/unist@3.0.3"
+ ]
+ },
+ "@types/unist@2.0.11": {
+ "integrity": "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA=="
+ },
"@types/unist@3.0.3": {
"integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q=="
},
"@ungap/structured-clone@1.3.0": {
"integrity": "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g=="
},
+ "array-iterate@2.0.1": {
+ "integrity": "sha512-I1jXZMjAgCMmxT4qxXfPXa6SthSoE8h6gkSI9BGGNv8mP8G/v0blc+qFnZu6K42vTOiuME596QaLO0TP3Lk0xg=="
+ },
+ "bail@2.0.2": {
+ "integrity": "sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw=="
+ },
"ccount@2.0.1": {
"integrity": "sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg=="
},
@@ -168,9 +194,27 @@
"character-entities-legacy@3.0.0": {
"integrity": "sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ=="
},
+ "character-entities@2.0.2": {
+ "integrity": "sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ=="
+ },
+ "character-reference-invalid@2.0.1": {
+ "integrity": "sha512-iBZ4F4wRbyORVsu0jPV7gXkOsGYjGHPmAyv+HiHG8gi5PtC9KI2j1+v8/tlibRvjoWX027ypmG/n0HtO5t7unw=="
+ },
"comma-separated-tokens@2.0.3": {
"integrity": "sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg=="
},
+ "debug@4.4.0": {
+ "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==",
+ "dependencies": [
+ "ms"
+ ]
+ },
+ "decode-named-character-reference@1.2.0": {
+ "integrity": "sha512-c6fcElNV6ShtZXmsgNgFFV5tVX2PaV4g+MOAkb8eXHvn6sryJBrZa9r0zV6+dtTyoCKxtDy5tyQ5ZwQuidtd+Q==",
+ "dependencies": [
+ "character-entities"
+ ]
+ },
"dequal@2.0.3": {
"integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA=="
},
@@ -180,11 +224,17 @@
"dequal"
]
},
+ "escape-string-regexp@5.0.0": {
+ "integrity": "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw=="
+ },
+ "extend@3.0.2": {
+ "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g=="
+ },
"hast-util-to-html@9.0.5": {
"integrity": "sha512-OguPdidb+fbHQSU4Q4ZiLKnzWo8Wwsf5bZfbvu7//a9oTYoqD/fWpe96NuHkoS9h0ccGOTe0C4NGXdtS0iObOw==",
"dependencies": [
"@types/hast",
- "@types/unist",
+ "@types/unist@3.0.3",
"ccount",
"comma-separated-tokens",
"hast-util-whitespace",
@@ -205,6 +255,137 @@
"html-void-elements@3.0.0": {
"integrity": "sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg=="
},
+ "is-alphabetical@2.0.1": {
+ "integrity": "sha512-FWyyY60MeTNyeSRpkM2Iry0G9hpr7/9kD40mD/cGQEuilcZYS4okz8SN2Q6rLCJ8gbCt6fN+rC+6tMGS99LaxQ=="
+ },
+ "is-alphanumerical@2.0.1": {
+ "integrity": "sha512-hmbYhX/9MUMF5uh7tOXyK/n0ZvWpad5caBA17GsC6vyuCqaWliRG5K1qS9inmUhEMaOBIW7/whAnSwveW/LtZw==",
+ "dependencies": [
+ "is-alphabetical",
+ "is-decimal"
+ ]
+ },
+ "is-decimal@2.0.1": {
+ "integrity": "sha512-AAB9hiomQs5DXWcRB1rqsxGUstbRroFOPPVAomNk/3XHR5JyEZChOyTWe2oayKnsSsr/kcGqF+z6yuH6HHpN0A=="
+ },
+ "is-hexadecimal@2.0.1": {
+ "integrity": "sha512-DgZQp241c8oO6cA1SbTEWiXeoxV42vlcJxgH+B3hi1AiqqKruZR3ZGF8In3fj4+/y/7rHvlOZLZtgJ/4ttYGZg=="
+ },
+ "is-plain-obj@4.1.0": {
+ "integrity": "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg=="
+ },
+ "longest-streak@3.1.0": {
+ "integrity": "sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g=="
+ },
+ "markdown-table@3.0.4": {
+ "integrity": "sha512-wiYz4+JrLyb/DqW2hkFJxP7Vd7JuTDm77fvbM8VfEQdmSMqcImWeeRbHwZjBjIFki/VaMK2BhFi7oUUZeM5bqw=="
+ },
+ "mdast-util-directive@3.1.0": {
+ "integrity": "sha512-I3fNFt+DHmpWCYAT7quoM6lHf9wuqtI+oCOfvILnoicNIqjh5E3dEJWiXuYME2gNe8vl1iMQwyUHa7bgFmak6Q==",
+ "dependencies": [
+ "@types/mdast",
+ "@types/unist@3.0.3",
+ "ccount",
+ "devlop",
+ "mdast-util-from-markdown",
+ "mdast-util-to-markdown",
+ "parse-entities",
+ "stringify-entities",
+ "unist-util-visit-parents"
+ ]
+ },
+ "mdast-util-find-and-replace@3.0.2": {
+ "integrity": "sha512-Tmd1Vg/m3Xz43afeNxDIhWRtFZgM2VLyaf4vSTYwudTyeuTneoL3qtWMA5jeLyz/O1vDJmmV4QuScFCA2tBPwg==",
+ "dependencies": [
+ "@types/mdast",
+ "escape-string-regexp",
+ "unist-util-is",
+ "unist-util-visit-parents"
+ ]
+ },
+ "mdast-util-from-markdown@2.0.2": {
+ "integrity": "sha512-uZhTV/8NBuw0WHkPTrCqDOl0zVe1BIng5ZtHoDk49ME1qqcjYmmLmOf0gELgcRMxN4w2iuIeVso5/6QymSrgmA==",
+ "dependencies": [
+ "@types/mdast",
+ "@types/unist@3.0.3",
+ "decode-named-character-reference",
+ "devlop",
+ "mdast-util-to-string",
+ "micromark",
+ "micromark-util-decode-numeric-character-reference",
+ "micromark-util-decode-string",
+ "micromark-util-normalize-identifier",
+ "micromark-util-symbol",
+ "micromark-util-types",
+ "unist-util-stringify-position"
+ ]
+ },
+ "mdast-util-gfm-autolink-literal@2.0.1": {
+ "integrity": "sha512-5HVP2MKaP6L+G6YaxPNjuL0BPrq9orG3TsrZ9YXbA3vDw/ACI4MEsnoDpn6ZNm7GnZgtAcONJyPhOP8tNJQavQ==",
+ "dependencies": [
+ "@types/mdast",
+ "ccount",
+ "devlop",
+ "mdast-util-find-and-replace",
+ "micromark-util-character"
+ ]
+ },
+ "mdast-util-gfm-footnote@2.1.0": {
+ "integrity": "sha512-sqpDWlsHn7Ac9GNZQMeUzPQSMzR6Wv0WKRNvQRg0KqHh02fpTz69Qc1QSseNX29bhz1ROIyNyxExfawVKTm1GQ==",
+ "dependencies": [
+ "@types/mdast",
+ "devlop",
+ "mdast-util-from-markdown",
+ "mdast-util-to-markdown",
+ "micromark-util-normalize-identifier"
+ ]
+ },
+ "mdast-util-gfm-strikethrough@2.0.0": {
+ "integrity": "sha512-mKKb915TF+OC5ptj5bJ7WFRPdYtuHv0yTRxK2tJvi+BDqbkiG7h7u/9SI89nRAYcmap2xHQL9D+QG/6wSrTtXg==",
+ "dependencies": [
+ "@types/mdast",
+ "mdast-util-from-markdown",
+ "mdast-util-to-markdown"
+ ]
+ },
+ "mdast-util-gfm-table@2.0.0": {
+ "integrity": "sha512-78UEvebzz/rJIxLvE7ZtDd/vIQ0RHv+3Mh5DR96p7cS7HsBhYIICDBCu8csTNWNO6tBWfqXPWekRuj2FNOGOZg==",
+ "dependencies": [
+ "@types/mdast",
+ "devlop",
+ "markdown-table",
+ "mdast-util-from-markdown",
+ "mdast-util-to-markdown"
+ ]
+ },
+ "mdast-util-gfm-task-list-item@2.0.0": {
+ "integrity": "sha512-IrtvNvjxC1o06taBAVJznEnkiHxLFTzgonUdy8hzFVeDun0uTjxxrRGVaNFqkU1wJR3RBPEfsxmU6jDWPofrTQ==",
+ "dependencies": [
+ "@types/mdast",
+ "devlop",
+ "mdast-util-from-markdown",
+ "mdast-util-to-markdown"
+ ]
+ },
+ "mdast-util-gfm@3.1.0": {
+ "integrity": "sha512-0ulfdQOM3ysHhCJ1p06l0b0VKlhU0wuQs3thxZQagjcjPrlFRqY215uZGHHJan9GEAXd9MbfPjFJz+qMkVR6zQ==",
+ "dependencies": [
+ "mdast-util-from-markdown",
+ "mdast-util-gfm-autolink-literal",
+ "mdast-util-gfm-footnote",
+ "mdast-util-gfm-strikethrough",
+ "mdast-util-gfm-table",
+ "mdast-util-gfm-task-list-item",
+ "mdast-util-to-markdown"
+ ]
+ },
+ "mdast-util-phrasing@4.1.0": {
+ "integrity": "sha512-TqICwyvJJpBwvGAMZjj4J2n0X8QWp21b9l0o7eXyVJ25YNWYbJDVIyD1bZXE6WtV6RmKJVYmQAKWa0zWOABz2w==",
+ "dependencies": [
+ "@types/mdast",
+ "unist-util-is"
+ ]
+ },
"mdast-util-to-hast@13.2.0": {
"integrity": "sha512-QGYKEuUsYT9ykKBCMOEDLsU5JRObWQusAolFMeko/tYPufNkRffBAQjIE+99jbA87xv6FgmjLtwjh9wBWajwAA==",
"dependencies": [
@@ -219,6 +400,173 @@
"vfile"
]
},
+ "mdast-util-to-markdown@2.1.2": {
+ "integrity": "sha512-xj68wMTvGXVOKonmog6LwyJKrYXZPvlwabaryTjLh9LuvovB/KAH+kvi8Gjj+7rJjsFi23nkUxRQv1KqSroMqA==",
+ "dependencies": [
+ "@types/mdast",
+ "@types/unist@3.0.3",
+ "longest-streak",
+ "mdast-util-phrasing",
+ "mdast-util-to-string",
+ "micromark-util-classify-character",
+ "micromark-util-decode-string",
+ "unist-util-visit",
+ "zwitch"
+ ]
+ },
+ "mdast-util-to-string@4.0.0": {
+ "integrity": "sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg==",
+ "dependencies": [
+ "@types/mdast"
+ ]
+ },
+ "micromark-core-commonmark@2.0.3": {
+ "integrity": "sha512-RDBrHEMSxVFLg6xvnXmb1Ayr2WzLAWjeSATAoxwKYJV94TeNavgoIdA0a9ytzDSVzBy2YKFK+emCPOEibLeCrg==",
+ "dependencies": [
+ "decode-named-character-reference",
+ "devlop",
+ "micromark-factory-destination",
+ "micromark-factory-label",
+ "micromark-factory-space",
+ "micromark-factory-title",
+ "micromark-factory-whitespace",
+ "micromark-util-character",
+ "micromark-util-chunked",
+ "micromark-util-classify-character",
+ "micromark-util-html-tag-name",
+ "micromark-util-normalize-identifier",
+ "micromark-util-resolve-all",
+ "micromark-util-subtokenize",
+ "micromark-util-symbol",
+ "micromark-util-types"
+ ]
+ },
+ "micromark-extension-directive@3.0.2": {
+ "integrity": "sha512-wjcXHgk+PPdmvR58Le9d7zQYWy+vKEU9Se44p2CrCDPiLr2FMyiT4Fyb5UFKFC66wGB3kPlgD7q3TnoqPS7SZA==",
+ "dependencies": [
+ "devlop",
+ "micromark-factory-space",
+ "micromark-factory-whitespace",
+ "micromark-util-character",
+ "micromark-util-symbol",
+ "micromark-util-types",
+ "parse-entities"
+ ]
+ },
+ "micromark-extension-gfm-autolink-literal@2.1.0": {
+ "integrity": "sha512-oOg7knzhicgQ3t4QCjCWgTmfNhvQbDDnJeVu9v81r7NltNCVmhPy1fJRX27pISafdjL+SVc4d3l48Gb6pbRypw==",
+ "dependencies": [
+ "micromark-util-character",
+ "micromark-util-sanitize-uri",
+ "micromark-util-symbol",
+ "micromark-util-types"
+ ]
+ },
+ "micromark-extension-gfm-footnote@2.1.0": {
+ "integrity": "sha512-/yPhxI1ntnDNsiHtzLKYnE3vf9JZ6cAisqVDauhp4CEHxlb4uoOTxOCJ+9s51bIB8U1N1FJ1RXOKTIlD5B/gqw==",
+ "dependencies": [
+ "devlop",
+ "micromark-core-commonmark",
+ "micromark-factory-space",
+ "micromark-util-character",
+ "micromark-util-normalize-identifier",
+ "micromark-util-sanitize-uri",
+ "micromark-util-symbol",
+ "micromark-util-types"
+ ]
+ },
+ "micromark-extension-gfm-strikethrough@2.1.0": {
+ "integrity": "sha512-ADVjpOOkjz1hhkZLlBiYA9cR2Anf8F4HqZUO6e5eDcPQd0Txw5fxLzzxnEkSkfnD0wziSGiv7sYhk/ktvbf1uw==",
+ "dependencies": [
+ "devlop",
+ "micromark-util-chunked",
+ "micromark-util-classify-character",
+ "micromark-util-resolve-all",
+ "micromark-util-symbol",
+ "micromark-util-types"
+ ]
+ },
+ "micromark-extension-gfm-table@2.1.1": {
+ "integrity": "sha512-t2OU/dXXioARrC6yWfJ4hqB7rct14e8f7m0cbI5hUmDyyIlwv5vEtooptH8INkbLzOatzKuVbQmAYcbWoyz6Dg==",
+ "dependencies": [
+ "devlop",
+ "micromark-factory-space",
+ "micromark-util-character",
+ "micromark-util-symbol",
+ "micromark-util-types"
+ ]
+ },
+ "micromark-extension-gfm-tagfilter@2.0.0": {
+ "integrity": "sha512-xHlTOmuCSotIA8TW1mDIM6X2O1SiX5P9IuDtqGonFhEK0qgRI4yeC6vMxEV2dgyr2TiD+2PQ10o+cOhdVAcwfg==",
+ "dependencies": [
+ "micromark-util-types"
+ ]
+ },
+ "micromark-extension-gfm-task-list-item@2.1.0": {
+ "integrity": "sha512-qIBZhqxqI6fjLDYFTBIa4eivDMnP+OZqsNwmQ3xNLE4Cxwc+zfQEfbs6tzAo2Hjq+bh6q5F+Z8/cksrLFYWQQw==",
+ "dependencies": [
+ "devlop",
+ "micromark-factory-space",
+ "micromark-util-character",
+ "micromark-util-symbol",
+ "micromark-util-types"
+ ]
+ },
+ "micromark-extension-gfm@3.0.0": {
+ "integrity": "sha512-vsKArQsicm7t0z2GugkCKtZehqUm31oeGBV/KVSorWSy8ZlNAv7ytjFhvaryUiCUJYqs+NoE6AFhpQvBTM6Q4w==",
+ "dependencies": [
+ "micromark-extension-gfm-autolink-literal",
+ "micromark-extension-gfm-footnote",
+ "micromark-extension-gfm-strikethrough",
+ "micromark-extension-gfm-table",
+ "micromark-extension-gfm-tagfilter",
+ "micromark-extension-gfm-task-list-item",
+ "micromark-util-combine-extensions",
+ "micromark-util-types"
+ ]
+ },
+ "micromark-factory-destination@2.0.1": {
+ "integrity": "sha512-Xe6rDdJlkmbFRExpTOmRj9N3MaWmbAgdpSrBQvCFqhezUn4AHqJHbaEnfbVYYiexVSs//tqOdY/DxhjdCiJnIA==",
+ "dependencies": [
+ "micromark-util-character",
+ "micromark-util-symbol",
+ "micromark-util-types"
+ ]
+ },
+ "micromark-factory-label@2.0.1": {
+ "integrity": "sha512-VFMekyQExqIW7xIChcXn4ok29YE3rnuyveW3wZQWWqF4Nv9Wk5rgJ99KzPvHjkmPXF93FXIbBp6YdW3t71/7Vg==",
+ "dependencies": [
+ "devlop",
+ "micromark-util-character",
+ "micromark-util-symbol",
+ "micromark-util-types"
+ ]
+ },
+ "micromark-factory-space@2.0.1": {
+ "integrity": "sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg==",
+ "dependencies": [
+ "micromark-util-character",
+ "micromark-util-types"
+ ]
+ },
+ "micromark-factory-title@2.0.1": {
+ "integrity": "sha512-5bZ+3CjhAd9eChYTHsjy6TGxpOFSKgKKJPJxr293jTbfry2KDoWkhBb6TcPVB4NmzaPhMs1Frm9AZH7OD4Cjzw==",
+ "dependencies": [
+ "micromark-factory-space",
+ "micromark-util-character",
+ "micromark-util-symbol",
+ "micromark-util-types"
+ ]
+ },
+ "micromark-factory-whitespace@2.0.1": {
+ "integrity": "sha512-Ob0nuZ3PKt/n0hORHyvoD9uZhr+Za8sFoP+OnMcnWK5lngSzALgQYKMr9RJVOWLqQYuyn6ulqGWSXdwf6F80lQ==",
+ "dependencies": [
+ "micromark-factory-space",
+ "micromark-util-character",
+ "micromark-util-symbol",
+ "micromark-util-types"
+ ]
+ },
"micromark-util-character@2.1.1": {
"integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==",
"dependencies": [
@@ -226,9 +574,60 @@
"micromark-util-types"
]
},
+ "micromark-util-chunked@2.0.1": {
+ "integrity": "sha512-QUNFEOPELfmvv+4xiNg2sRYeS/P84pTW0TCgP5zc9FpXetHY0ab7SxKyAQCNCc1eK0459uoLI1y5oO5Vc1dbhA==",
+ "dependencies": [
+ "micromark-util-symbol"
+ ]
+ },
+ "micromark-util-classify-character@2.0.1": {
+ "integrity": "sha512-K0kHzM6afW/MbeWYWLjoHQv1sgg2Q9EccHEDzSkxiP/EaagNzCm7T/WMKZ3rjMbvIpvBiZgwR3dKMygtA4mG1Q==",
+ "dependencies": [
+ "micromark-util-character",
+ "micromark-util-symbol",
+ "micromark-util-types"
+ ]
+ },
+ "micromark-util-combine-extensions@2.0.1": {
+ "integrity": "sha512-OnAnH8Ujmy59JcyZw8JSbK9cGpdVY44NKgSM7E9Eh7DiLS2E9RNQf0dONaGDzEG9yjEl5hcqeIsj4hfRkLH/Bg==",
+ "dependencies": [
+ "micromark-util-chunked",
+ "micromark-util-types"
+ ]
+ },
+ "micromark-util-decode-numeric-character-reference@2.0.2": {
+ "integrity": "sha512-ccUbYk6CwVdkmCQMyr64dXz42EfHGkPQlBj5p7YVGzq8I7CtjXZJrubAYezf7Rp+bjPseiROqe7G6foFd+lEuw==",
+ "dependencies": [
+ "micromark-util-symbol"
+ ]
+ },
+ "micromark-util-decode-string@2.0.1": {
+ "integrity": "sha512-nDV/77Fj6eH1ynwscYTOsbK7rR//Uj0bZXBwJZRfaLEJ1iGBR6kIfNmlNqaqJf649EP0F3NWNdeJi03elllNUQ==",
+ "dependencies": [
+ "decode-named-character-reference",
+ "micromark-util-character",
+ "micromark-util-decode-numeric-character-reference",
+ "micromark-util-symbol"
+ ]
+ },
"micromark-util-encode@2.0.1": {
"integrity": "sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw=="
},
+ "micromark-util-html-tag-name@2.0.1": {
+ "integrity": "sha512-2cNEiYDhCWKI+Gs9T0Tiysk136SnR13hhO8yW6BGNyhOC4qYFnwF1nKfD3HFAIXA5c45RrIG1ub11GiXeYd1xA=="
+ },
+ "micromark-util-normalize-identifier@2.0.1": {
+ "integrity": "sha512-sxPqmo70LyARJs0w2UclACPUUEqltCkJ6PhKdMIDuJ3gSf/Q+/GIe3WKl0Ijb/GyH9lOpUkRAO2wp0GVkLvS9Q==",
+ "dependencies": [
+ "micromark-util-symbol"
+ ]
+ },
+ "micromark-util-resolve-all@2.0.1": {
+ "integrity": "sha512-VdQyxFWFT2/FGJgwQnJYbe1jjQoNTS4RjglmSjTUlpUMa95Htx9NHeYW4rGDJzbjvCsl9eLjMQwGeElsqmzcHg==",
+ "dependencies": [
+ "micromark-util-types"
+ ]
+ },
"micromark-util-sanitize-uri@2.0.1": {
"integrity": "sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ==",
"dependencies": [
@@ -237,12 +636,52 @@
"micromark-util-symbol"
]
},
+ "micromark-util-subtokenize@2.1.0": {
+ "integrity": "sha512-XQLu552iSctvnEcgXw6+Sx75GflAPNED1qx7eBJ+wydBb2KCbRZe+NwvIEEMM83uml1+2WSXpBAcp9IUCgCYWA==",
+ "dependencies": [
+ "devlop",
+ "micromark-util-chunked",
+ "micromark-util-symbol",
+ "micromark-util-types"
+ ]
+ },
"micromark-util-symbol@2.0.1": {
"integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q=="
},
"micromark-util-types@2.0.2": {
"integrity": "sha512-Yw0ECSpJoViF1qTU4DC6NwtC4aWGt1EkzaQB8KPPyCRR8z9TWeV0HbEFGTO+ZY1wB22zmxnJqhPyTpOVCpeHTA=="
},
+ "micromark@4.0.2": {
+ "integrity": "sha512-zpe98Q6kvavpCr1NPVSCMebCKfD7CA2NqZ+rykeNhONIJBpc1tFKt9hucLGwha3jNTNI8lHpctWJWoimVF4PfA==",
+ "dependencies": [
+ "@types/debug",
+ "debug",
+ "decode-named-character-reference",
+ "devlop",
+ "micromark-core-commonmark",
+ "micromark-factory-space",
+ "micromark-util-character",
+ "micromark-util-chunked",
+ "micromark-util-combine-extensions",
+ "micromark-util-decode-numeric-character-reference",
+ "micromark-util-encode",
+ "micromark-util-normalize-identifier",
+ "micromark-util-resolve-all",
+ "micromark-util-sanitize-uri",
+ "micromark-util-subtokenize",
+ "micromark-util-symbol",
+ "micromark-util-types"
+ ]
+ },
+ "ms@2.1.3": {
+ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="
+ },
+ "nlcst-to-string@4.0.0": {
+ "integrity": "sha512-YKLBCcUYKAg0FNlOBT6aI91qFmSiFKiluk655WzPF+DDMA02qIyy8uiRqI8QXtcFpEvll12LpL5MXqEmAZ+dcA==",
+ "dependencies": [
+ "@types/nlcst"
+ ]
+ },
"oniguruma-parser@0.12.1": {
"integrity": "sha512-8Unqkvk1RYc6yq2WBYRj4hdnsAxVze8i7iPfQr8e4uSP3tRv0rpZcbGUDvxfQQcdwHt/e9PrMvGCsa8OqG9X3w=="
},
@@ -254,6 +693,29 @@
"regex-recursion"
]
},
+ "parse-entities@4.0.2": {
+ "integrity": "sha512-GG2AQYWoLgL877gQIKeRPGO1xF9+eG1ujIb5soS5gPvLQ1y2o8FL90w2QWNdf9I361Mpp7726c+lj3U0qK1uGw==",
+ "dependencies": [
+ "@types/unist@2.0.11",
+ "character-entities-legacy",
+ "character-reference-invalid",
+ "decode-named-character-reference",
+ "is-alphanumerical",
+ "is-decimal",
+ "is-hexadecimal"
+ ]
+ },
+ "parse-latin@7.0.0": {
+ "integrity": "sha512-mhHgobPPua5kZ98EF4HWiH167JWBfl4pvAIXXdbaVohtK7a6YBOy56kvhCqduqyo/f3yrHFWmqmiMg/BkBkYYQ==",
+ "dependencies": [
+ "@types/nlcst",
+ "@types/unist@3.0.3",
+ "nlcst-to-string",
+ "unist-util-modify-children",
+ "unist-util-visit-children",
+ "vfile"
+ ]
+ },
"property-information@7.1.0": {
"integrity": "sha512-TwEZ+X+yCJmYfL7TPUOcvBZ4QfoT5YenQiJuX//0th53DE6w0xxLEtfK3iyryQFddXuvkIk51EEgrJQ0WJkOmQ=="
},
@@ -272,6 +734,85 @@
"regex-utilities"
]
},
+ "remark-directive@3.0.1": {
+ "integrity": "sha512-gwglrEQEZcZYgVyG1tQuA+h58EZfq5CSULw7J90AFuCTyib1thgHPoqQ+h9iFvU6R+vnZ5oNFQR5QKgGpk741A==",
+ "dependencies": [
+ "@types/mdast",
+ "mdast-util-directive",
+ "micromark-extension-directive",
+ "unified"
+ ]
+ },
+ "remark-gfm@4.0.1": {
+ "integrity": "sha512-1quofZ2RQ9EWdeN34S79+KExV1764+wCUGop5CPL1WGdD0ocPpu91lzPGbwWMECpEpd42kJGQwzRfyov9j4yNg==",
+ "dependencies": [
+ "@types/mdast",
+ "mdast-util-gfm",
+ "micromark-extension-gfm",
+ "remark-parse",
+ "remark-stringify",
+ "unified"
+ ]
+ },
+ "remark-parse@11.0.0": {
+ "integrity": "sha512-FCxlKLNGknS5ba/1lmpYijMUzX2esxW5xQqjWxw2eHFfS2MSdaHVINFmhjo+qN1WhZhNimq0dZATN9pH0IDrpA==",
+ "dependencies": [
+ "@types/mdast",
+ "mdast-util-from-markdown",
+ "micromark-util-types",
+ "unified"
+ ]
+ },
+ "remark-smartypants@3.0.2": {
+ "integrity": "sha512-ILTWeOriIluwEvPjv67v7Blgrcx+LZOkAUVtKI3putuhlZm84FnqDORNXPPm+HY3NdZOMhyDwZ1E+eZB/Df5dA==",
+ "dependencies": [
+ "retext",
+ "retext-smartypants",
+ "unified",
+ "unist-util-visit"
+ ]
+ },
+ "remark-stringify@11.0.0": {
+ "integrity": "sha512-1OSmLd3awB/t8qdoEOMazZkNsfVTeY4fTsgzcQFdXNq8ToTN4ZGwrMnlda4K6smTFKD+GRV6O48i6Z4iKgPPpw==",
+ "dependencies": [
+ "@types/mdast",
+ "mdast-util-to-markdown",
+ "unified"
+ ]
+ },
+ "retext-latin@4.0.0": {
+ "integrity": "sha512-hv9woG7Fy0M9IlRQloq/N6atV82NxLGveq+3H2WOi79dtIYWN8OaxogDm77f8YnVXJL2VD3bbqowu5E3EMhBYA==",
+ "dependencies": [
+ "@types/nlcst",
+ "parse-latin",
+ "unified"
+ ]
+ },
+ "retext-smartypants@6.2.0": {
+ "integrity": "sha512-kk0jOU7+zGv//kfjXEBjdIryL1Acl4i9XNkHxtM7Tm5lFiCog576fjNC9hjoR7LTKQ0DsPWy09JummSsH1uqfQ==",
+ "dependencies": [
+ "@types/nlcst",
+ "nlcst-to-string",
+ "unist-util-visit"
+ ]
+ },
+ "retext-stringify@4.0.0": {
+ "integrity": "sha512-rtfN/0o8kL1e+78+uxPTqu1Klt0yPzKuQ2BfWwwfgIUSayyzxpM1PJzkKt4V8803uB9qSy32MvI7Xep9khTpiA==",
+ "dependencies": [
+ "@types/nlcst",
+ "nlcst-to-string",
+ "unified"
+ ]
+ },
+ "retext@9.0.0": {
+ "integrity": "sha512-sbMDcpHCNjvlheSgMfEcVrZko3cDzdbe1x/e7G66dFp0Ff7Mldvi2uv6JkJQzdRcvLYE8CA8Oe8siQx8ZOgTcA==",
+ "dependencies": [
+ "@types/nlcst",
+ "retext-latin",
+ "retext-stringify",
+ "unified"
+ ]
+ },
"shiki@3.7.0": {
"integrity": "sha512-ZcI4UT9n6N2pDuM2n3Jbk0sR4Swzq43nLPgS/4h0E3B/NrFn2HKElrDtceSf8Zx/OWYOo7G1SAtBLypCp+YXqg==",
"dependencies": [
@@ -298,35 +839,63 @@
"trim-lines@3.0.1": {
"integrity": "sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg=="
},
+ "trough@2.2.0": {
+ "integrity": "sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw=="
+ },
+ "unified@11.0.5": {
+ "integrity": "sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA==",
+ "dependencies": [
+ "@types/unist@3.0.3",
+ "bail",
+ "devlop",
+ "extend",
+ "is-plain-obj",
+ "trough",
+ "vfile"
+ ]
+ },
"unist-util-is@6.0.0": {
"integrity": "sha512-2qCTHimwdxLfz+YzdGfkqNlH0tLi9xjTnHddPmJwtIG9MGsdbutfTc4P+haPD7l7Cjxf/WZj+we5qfVPvvxfYw==",
"dependencies": [
- "@types/unist"
+ "@types/unist@3.0.3"
+ ]
+ },
+ "unist-util-modify-children@4.0.0": {
+ "integrity": "sha512-+tdN5fGNddvsQdIzUF3Xx82CU9sMM+fA0dLgR9vOmT0oPT2jH+P1nd5lSqfCfXAw+93NhcXNY2qqvTUtE4cQkw==",
+ "dependencies": [
+ "@types/unist@3.0.3",
+ "array-iterate"
]
},
"unist-util-position@5.0.0": {
"integrity": "sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA==",
"dependencies": [
- "@types/unist"
+ "@types/unist@3.0.3"
]
},
"unist-util-stringify-position@4.0.0": {
"integrity": "sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==",
"dependencies": [
- "@types/unist"
+ "@types/unist@3.0.3"
+ ]
+ },
+ "unist-util-visit-children@3.0.0": {
+ "integrity": "sha512-RgmdTfSBOg04sdPcpTSD1jzoNBjt9a80/ZCzp5cI9n1qPzLZWF9YdvWGN2zmTumP1HWhXKdUWexjy/Wy/lJ7tA==",
+ "dependencies": [
+ "@types/unist@3.0.3"
]
},
"unist-util-visit-parents@6.0.1": {
"integrity": "sha512-L/PqWzfTP9lzzEa6CKs0k2nARxTdZduw3zyh8d2NVBnsyvHjSX4TWse388YrrQKbvI8w20fGjGlhgT96WwKykw==",
"dependencies": [
- "@types/unist",
+ "@types/unist@3.0.3",
"unist-util-is"
]
},
"unist-util-visit@5.0.0": {
"integrity": "sha512-MR04uvD+07cwl/yhVuVWAtw+3GOR/knlL55Nd/wAdblk27GCVt3lqpTivy/tkJcZoNPzTwS1Y+KMojlLDhoTzg==",
"dependencies": [
- "@types/unist",
+ "@types/unist@3.0.3",
"unist-util-is",
"unist-util-visit-parents"
]
@@ -334,14 +903,14 @@
"vfile-message@4.0.2": {
"integrity": "sha512-jRDZ1IMLttGj41KcZvlrYAaI3CfqpLpfpf+Mfig13viT6NKvRzWZ+lXz0Y5D60w6uJIBAOGq9mSHf0gktF0duw==",
"dependencies": [
- "@types/unist",
+ "@types/unist@3.0.3",
"unist-util-stringify-position"
]
},
"vfile@6.0.3": {
"integrity": "sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==",
"dependencies": [
- "@types/unist",
+ "@types/unist@3.0.3",
"vfile-message"
]
},
@@ -555,8 +1124,14 @@
"jsr:@std/http@^1.0.19",
"jsr:@std/path@^1.1.1",
"jsr:@std/toml@^1.0.8",
- "npm:@djot/djot@~0.3.2",
- "npm:shiki@^3.7.0"
+ "npm:@types/mdast@4",
+ "npm:mdast-util-directive@3",
+ "npm:remark-directive@3",
+ "npm:remark-gfm@4",
+ "npm:remark-parse@11",
+ "npm:remark-smartypants@3",
+ "npm:shiki@^3.7.0",
+ "npm:unified@11"
]
}
}
diff --git a/services/nuldoc/nuldoc-src/commands/build.ts b/services/nuldoc/nuldoc-src/commands/build.ts
index 5efff55..2b67d2b 100644
--- a/services/nuldoc/nuldoc-src/commands/build.ts
+++ b/services/nuldoc/nuldoc-src/commands/build.ts
@@ -2,7 +2,7 @@ import { dirname, join, joinGlobs, relative } from "@std/path";
import { ensureDir, expandGlob } from "@std/fs";
import { generateFeedPageFromEntries } from "../generators/atom.ts";
import { Config, getTagLabel } from "../config.ts";
-import { parseDjotFile } from "../djot/parse.ts";
+import { parseMarkdownFile } from "../markdown/parse.ts";
import { Page } from "../page.ts";
import { render } from "../render.ts";
import { dateToString } from "../revision.ts";
@@ -55,7 +55,7 @@ async function buildPostPages(config: Config): Promise<PostPage[]> {
async function collectPostFiles(sourceDir: string): Promise<string[]> {
const filePaths = [];
- const globPattern = joinGlobs([sourceDir, "**", "*.dj"]);
+ const globPattern = joinGlobs([sourceDir, "**", "*.md"]);
for await (const entry of expandGlob(globPattern)) {
filePaths.push(entry.path);
}
@@ -69,7 +69,7 @@ async function parsePosts(
const posts = [];
for (const postFile of postFiles) {
posts.push(
- await generatePostPage(await parseDjotFile(postFile, config), config),
+ await generatePostPage(await parseMarkdownFile(postFile, config), config),
);
}
return posts;
@@ -265,9 +265,9 @@ async function copyBlogAssetFiles(config: Config) {
for await (const { isFile, path } of expandGlob(globPattern)) {
if (!isFile) continue;
- // Skip .dj, .toml, .pdf files
+ // Skip .md, .toml, .pdf files
if (
- path.endsWith(".dj") ||
+ path.endsWith(".md") ||
path.endsWith(".toml") ||
path.endsWith(".pdf")
) {
@@ -290,9 +290,9 @@ async function copySlidesAssetFiles(config: Config) {
for await (const { isFile, path } of expandGlob(globPattern)) {
if (!isFile) continue;
- // Skip .dj, .toml, .pdf files
+ // Skip .md, .toml, .pdf files
if (
- path.endsWith(".dj") ||
+ path.endsWith(".md") ||
path.endsWith(".toml") ||
path.endsWith(".pdf")
) {
diff --git a/services/nuldoc/nuldoc-src/components/TableOfContents.ts b/services/nuldoc/nuldoc-src/components/TableOfContents.ts
index ac4205a..37796ff 100644
--- a/services/nuldoc/nuldoc-src/components/TableOfContents.ts
+++ b/services/nuldoc/nuldoc-src/components/TableOfContents.ts
@@ -1,4 +1,4 @@
-import { TocEntry, TocRoot } from "../djot/document.ts";
+import { TocEntry, TocRoot } from "../markdown/document.ts";
import { elem, Element } from "../dom.ts";
type Props = {
diff --git a/services/nuldoc/nuldoc-src/djot/djot2ndoc.ts b/services/nuldoc/nuldoc-src/djot/djot2ndoc.ts
deleted file mode 100644
index 627e8d6..0000000
--- a/services/nuldoc/nuldoc-src/djot/djot2ndoc.ts
+++ /dev/null
@@ -1,604 +0,0 @@
-import {
- Block as DjotBlock,
- BlockQuote as DjotBlockQuote,
- BulletList as DjotBulletList,
- CodeBlock as DjotCodeBlock,
- Definition as DjotDefinition,
- DefinitionList as DjotDefinitionList,
- DefinitionListItem as DjotDefinitionListItem,
- Delete as DjotDelete,
- DisplayMath as DjotDisplayMath,
- Div as DjotDiv,
- Doc as DjotDoc,
- DoubleQuoted as DjotDoubleQuoted,
- Email as DjotEmail,
- Emph as DjotEmph,
- FootnoteReference as DjotFootnoteReference,
- HardBreak as DjotHardBreak,
- Heading as DjotHeading,
- Image as DjotImage,
- Inline as DjotInline,
- InlineMath as DjotInlineMath,
- Insert as DjotInsert,
- Link as DjotLink,
- ListItem as DjotListItem,
- Mark as DjotMark,
- NonBreakingSpace as DjotNonBreakingSpace,
- OrderedList as DjotOrderedList,
- Para as DjotPara,
- RawBlock as DjotRawBlock,
- RawInline as DjotRawInline,
- Section as DjotSection,
- SingleQuoted as DjotSingleQuoted,
- SmartPunctuation as DjotSmartPunctuation,
- SoftBreak as DjotSoftBreak,
- Span as DjotSpan,
- Str as DjotStr,
- Strong as DjotStrong,
- Subscript as DjotSubscript,
- Superscript as DjotSuperscript,
- Symb as DjotSymb,
- Table as DjotTable,
- TaskList as DjotTaskList,
- TaskListItem as DjotTaskListItem,
- Term as DjotTerm,
- ThematicBreak as DjotThematicBreak,
- Url as DjotUrl,
- Verbatim as DjotVerbatim,
-} from "@djot/djot";
-import { addClass, elem, Element, Node, rawHTML, text } from "../dom.ts";
-
-function processBlock(node: DjotBlock): Element {
- switch (node.tag) {
- case "section":
- return processSection(node);
- case "para":
- return processPara(node);
- case "heading":
- return processHeading(node);
- case "thematic_break":
- return processThematicBreak(node);
- case "block_quote":
- return processBlockQuote(node);
- case "code_block":
- return processCodeBlock(node);
- case "bullet_list":
- return processBulletList(node);
- case "ordered_list":
- return processOrderedList(node);
- case "task_list":
- return processTaskList(node);
- case "definition_list":
- return processDefinitionList(node);
- case "table":
- return processTable(node);
- case "div":
- return processDiv(node);
- case "raw_block":
- return processRawBlock(node);
- }
-}
-
-function processSection(node: DjotSection): Element {
- return elem(
- "section",
- node.attributes,
- ...node.children.map(processBlock),
- );
-}
-
-function processPara(node: DjotPara): Element {
- return elem(
- "p",
- node.attributes,
- ...node.children.map(processInline),
- );
-}
-
-function processHeading(node: DjotHeading): Element {
- return elem("h", node.attributes, ...node.children.map(processInline));
-}
-
-function processThematicBreak(node: DjotThematicBreak): Element {
- return elem("hr", node.attributes);
-}
-
-function processBlockQuote(node: DjotBlockQuote): Element {
- return elem(
- "blockquote",
- node.attributes,
- ...node.children.map(processBlock),
- );
-}
-
-function processCodeBlock(node: DjotCodeBlock): Element {
- const attributes = node.attributes || {};
- if (node.lang) {
- attributes.language = node.lang;
- }
- if (node.attributes?.filename) {
- attributes.filename = node.attributes.filename;
- }
- if (node.attributes?.numbered) {
- attributes.numbered = "true";
- }
- return elem("codeblock", attributes, text(node.text));
-}
-
-function processBulletList(node: DjotBulletList): Element {
- const attributes = node.attributes || {};
- attributes.__tight = node.tight ? "true" : "false";
- return elem("ul", attributes, ...node.children.map(processListItem));
-}
-
-function processOrderedList(node: DjotOrderedList): Element {
- const attributes = node.attributes || {};
- attributes.__tight = node.tight ? "true" : "false";
- if (node.start !== undefined && node.start !== 1) {
- attributes.start = node.start.toString();
- }
- return elem("ol", attributes, ...node.children.map(processListItem));
-}
-
-function processTaskList(node: DjotTaskList): Element {
- const attributes = node.attributes || {};
- attributes.type = "task";
- attributes.__tight = node.tight ? "true" : "false";
- return elem("ul", attributes, ...node.children.map(processTaskListItem));
-}
-
-function processListItem(node: DjotListItem): Element {
- return elem(
- "li",
- node.attributes,
- ...node.children.map(processBlock),
- );
-}
-
-function processTaskListItem(node: DjotTaskListItem): Element {
- const attributes = node.attributes || {};
- attributes.checked = node.checkbox === "checked" ? "true" : "false";
- return elem("li", attributes, ...node.children.map(processBlock));
-}
-
-function processDefinitionList(node: DjotDefinitionList): Element {
- return elem(
- "dl",
- node.attributes,
- ...node.children.flatMap(processDefinitionListItem),
- );
-}
-
-function processDefinitionListItem(node: DjotDefinitionListItem): Element[] {
- return [
- processTerm(node.children[0]),
- processDefinition(node.children[1]),
- ];
-}
-
-function processTerm(node: DjotTerm): Element {
- return elem(
- "dt",
- node.attributes,
- ...node.children.map(processInline),
- );
-}
-
-function processDefinition(node: DjotDefinition): Element {
- return elem(
- "dd",
- node.attributes,
- ...node.children.map(processBlock),
- );
-}
-
-function processTable(node: DjotTable): Element {
- // Tables in Djot have a caption as first child and then rows
- // For now, we'll create a basic table structure and ignore caption
- const tableElement = elem("table", node.attributes);
-
- // Process caption if it exists (first child)
- if (node.children.length > 0 && node.children[0].tag === "caption") {
- const caption = elem(
- "caption",
- undefined,
- ...node.children[0].children.map(processInline),
- );
- tableElement.children.push(caption);
- }
-
- // Group rows into thead, tbody based on head property
- const headerRows: Element[] = [];
- const bodyRows: Element[] = [];
-
- // Start from index 1 to skip caption
- for (let i = 1; i < node.children.length; i++) {
- const row = node.children[i];
- if (row.tag === "row") {
- const rowElement = elem(
- "tr",
- row.attributes,
- ...row.children.map((cell) => {
- const cellAttributes = cell.attributes || {};
- // Set alignment attribute if needed
- if (cell.align !== "default") {
- cellAttributes.align = cell.align;
- }
- return elem(
- cell.head ? "th" : "td",
- cellAttributes,
- ...cell.children.map(processInline),
- );
- }),
- );
-
- if (row.head) {
- headerRows.push(rowElement);
- } else {
- bodyRows.push(rowElement);
- }
- }
- }
-
- // Add thead and tbody if needed
- if (headerRows.length > 0) {
- tableElement.children.push(elem("thead", undefined, ...headerRows));
- }
-
- if (bodyRows.length > 0) {
- tableElement.children.push(elem("tbody", undefined, ...bodyRows));
- }
-
- return tableElement;
-}
-
-function processInline(node: DjotInline): Node {
- switch (node.tag) {
- case "str":
- return processStr(node);
- case "soft_break":
- return processSoftBreak(node);
- case "hard_break":
- return processHardBreak(node);
- case "verbatim":
- return processVerbatim(node);
- case "emph":
- return processEmph(node);
- case "strong":
- return processStrong(node);
- case "link":
- return processLink(node);
- case "image":
- return processImage(node);
- case "mark":
- return processMark(node);
- case "superscript":
- return processSuperscript(node);
- case "subscript":
- return processSubscript(node);
- case "insert":
- return processInsert(node);
- case "delete":
- return processDelete(node);
- case "email":
- return processEmail(node);
- case "footnote_reference":
- return processFootnoteReference(node);
- case "url":
- return processUrl(node);
- case "span":
- return processSpan(node);
- case "inline_math":
- return processInlineMath(node);
- case "display_math":
- return processDisplayMath(node);
- case "non_breaking_space":
- return processNonBreakingSpace(node);
- case "symb":
- return processSymb(node);
- case "raw_inline":
- return processRawInline(node);
- case "double_quoted":
- return processDoubleQuoted(node);
- case "single_quoted":
- return processSingleQuoted(node);
- case "smart_punctuation":
- return processSmartPunctuation(node);
- }
-}
-
-function processStr(node: DjotStr): Node {
- return text(node.text);
-}
-
-function processSoftBreak(_node: DjotSoftBreak): Node {
- return text("\n");
-}
-
-function processHardBreak(_node: DjotHardBreak): Node {
- return elem("br");
-}
-
-function processVerbatim(node: DjotVerbatim): Element {
- return elem("code", node.attributes, text(node.text));
-}
-
-function processEmph(node: DjotEmph): Element {
- return elem(
- "em",
- node.attributes,
- ...node.children.map(processInline),
- );
-}
-
-function processStrong(node: DjotStrong): Element {
- return elem(
- "strong",
- node.attributes,
- ...node.children.map(processInline),
- );
-}
-
-function processLink(node: DjotLink): Element {
- const attributes = node.attributes || {};
- if (node.destination !== undefined) {
- attributes.href = node.destination;
- }
- return elem("a", attributes, ...node.children.map(processInline));
-}
-
-function processImage(node: DjotImage): Element {
- const attributes = node.attributes || {};
- if (node.destination !== undefined) {
- attributes.src = node.destination;
- }
-
- // Alt text is derived from children in Djot
- const alt = node.children
- .map((child) => {
- if (child.tag === "str") {
- return child.text;
- }
- return "";
- })
- .join("");
-
- if (alt) {
- attributes.alt = alt;
- }
-
- return elem("img", attributes);
-}
-
-function processMark(node: DjotMark): Element {
- return elem(
- "mark",
- node.attributes,
- ...node.children.map(processInline),
- );
-}
-
-function processSuperscript(node: DjotSuperscript): Element {
- return elem(
- "sup",
- node.attributes,
- ...node.children.map(processInline),
- );
-}
-
-function processSubscript(node: DjotSubscript): Element {
- return elem(
- "sub",
- node.attributes,
- ...node.children.map(processInline),
- );
-}
-
-function processInsert(node: DjotInsert): Element {
- return elem(
- "ins",
- node.attributes,
- ...node.children.map(processInline),
- );
-}
-
-function processDelete(node: DjotDelete): Element {
- return elem(
- "del",
- node.attributes,
- ...node.children.map(processInline),
- );
-}
-
-function processEmail(node: DjotEmail): Element {
- return elem("email", node.attributes, text(node.text));
-}
-
-function processFootnoteReference(node: DjotFootnoteReference): Element {
- return elem("footnoteref", { reference: node.text });
-}
-
-function processUrl(node: DjotUrl): Element {
- const e = elem(
- "a",
- {
- href: node.text,
- ...node.attributes,
- },
- text(node.text),
- );
- addClass(e, "url");
- return e;
-}
-
-function processSpan(node: DjotSpan): Element {
- return elem(
- "span",
- node.attributes,
- ...node.children.map(processInline),
- );
-}
-
-function processInlineMath(node: DjotInlineMath): Element {
- // For inline math, we'll wrap it in a span with a class
- return elem(
- "span",
- {
- class: "math inline",
- ...node.attributes,
- },
- text(node.text),
- );
-}
-
-function processDisplayMath(node: DjotDisplayMath): Element {
- // For display math, we'll wrap it in a div with a class
- return elem(
- "div",
- {
- class: "math display",
- ...node.attributes,
- },
- text(node.text),
- );
-}
-
-function processNonBreakingSpace(_node: DjotNonBreakingSpace): Node {
- return text("\u00A0"); // Unicode non-breaking space
-}
-
-function processSymb(node: DjotSymb): Node {
- // Map symbol aliases to their Unicode characters
- const symbolMap: Record<string, string> = {
- "->": "→",
- "<-": "←",
- "<->": "↔",
- "=>": "⇒",
- "<=": "⇐",
- "<=>": "⇔",
- "--": "–", // en dash
- "---": "—", // em dash
- "...": "…", // ellipsis
- // Add more symbol mappings as needed
- };
-
- const symbolText = symbolMap[node.alias] || node.alias;
-
- return text(symbolText);
-}
-
-function processRawInline(node: DjotRawInline): Node {
- // If the format is HTML, return as raw HTML
- if (node.format === "html" || node.format === "HTML") {
- return rawHTML(node.text);
- }
-
- // For other formats, just return as text
- return text(node.text);
-}
-
-function processDoubleQuoted(node: DjotDoubleQuoted): Node {
- const children = node.children.map(processInline);
- const attributes = node.attributes || {};
-
- if (
- children.length === 1 && children[0].kind === "text" &&
- Object.keys(attributes).length === 0
- ) {
- const content = children[0].content;
- return text(`\u201C${content}\u201D`);
- } else {
- return elem("span", node.attributes, ...children);
- }
-}
-
-function processSingleQuoted(node: DjotSingleQuoted): Node {
- const children = node.children.map(processInline);
- const attributes = node.attributes || {};
-
- if (
- children.length === 1 && children[0].kind === "text" &&
- Object.keys(attributes).length === 0
- ) {
- const content = children[0].content;
- return text(`\u2018${content}\u2019`);
- } else {
- return elem("span", node.attributes, ...children);
- }
-}
-
-function processSmartPunctuation(node: DjotSmartPunctuation): Node {
- // Map smart punctuation types to Unicode characters
- const punctuationMap: Record<string, string> = {
- left_single_quote: "\u2018", // '
- right_single_quote: "\u2019", // '
- left_double_quote: "\u201C", // "
- right_double_quote: "\u201D", // "
- ellipses: "\u2026", // …
- em_dash: "\u2014", // —
- en_dash: "\u2013", // –
- };
-
- return text(punctuationMap[node.type] || node.text);
-}
-
-function processDiv(node: DjotDiv): Element {
- if (node.attributes?.class === "note") {
- delete node.attributes.class;
- return elem(
- "note",
- node.attributes,
- ...node.children.map(processBlock),
- );
- }
-
- if (node.attributes?.class === "edit") {
- delete node.attributes.class;
- return elem(
- "note",
- node.attributes,
- ...node.children.map(processBlock),
- );
- }
-
- return elem(
- "div",
- node.attributes,
- ...node.children.map(processBlock),
- );
-}
-
-function processRawBlock(node: DjotRawBlock): Element {
- // If the format is HTML, wrap the HTML content in a div
- if (node.format === "html" || node.format === "HTML") {
- return elem("div", { class: "raw-html" }, rawHTML(node.text));
- }
-
- // For other formats, wrap in a pre tag
- return elem("pre", { "data-format": node.format }, text(node.text));
-}
-
-export function djot2ndoc(doc: DjotDoc): Element {
- const children: Node[] = [];
- for (const child of doc.children) {
- children.push(processBlock(child));
- }
-
- // Process footnotes if any exist
- if (doc.footnotes && Object.keys(doc.footnotes).length > 0) {
- const footnoteSection = elem("section", { class: "footnotes" });
-
- for (const [id, footnote] of Object.entries(doc.footnotes)) {
- const footnoteElement = elem(
- "footnote",
- { id },
- ...footnote.children.map(processBlock),
- );
- footnoteSection.children.push(footnoteElement);
- }
-
- children.push(footnoteSection);
- }
-
- return elem("__root__", undefined, elem("article", undefined, ...children));
-}
diff --git a/services/nuldoc/nuldoc-src/generators/post.ts b/services/nuldoc/nuldoc-src/generators/post.ts
index 11a3ce8..2f466b9 100644
--- a/services/nuldoc/nuldoc-src/generators/post.ts
+++ b/services/nuldoc/nuldoc-src/generators/post.ts
@@ -1,7 +1,7 @@
import { join } from "@std/path";
import PostPage from "../pages/PostPage.ts";
import { Config } from "../config.ts";
-import { Document } from "../djot/document.ts";
+import { Document } from "../markdown/document.ts";
import { Page } from "../page.ts";
import { Date, Revision } from "../revision.ts";
@@ -41,7 +41,7 @@ export async function generatePostPage(
const cwd = Deno.cwd();
const contentDir = join(cwd, config.locations.contentDir);
const destFilePath = join(
- doc.sourceFilePath.replace(contentDir, "").replace(".dj", ""),
+ doc.sourceFilePath.replace(contentDir, "").replace(".md", ""),
"index.html",
);
return {
diff --git a/services/nuldoc/nuldoc-src/djot/document.ts b/services/nuldoc/nuldoc-src/markdown/document.ts
index 3e8cd92..1aee87b 100644
--- a/services/nuldoc/nuldoc-src/djot/document.ts
+++ b/services/nuldoc/nuldoc-src/markdown/document.ts
@@ -1,10 +1,10 @@
-import { Doc as DjotDoc } from "@djot/djot";
+import type { Root as MdastRoot } from "mdast";
import { join } from "@std/path";
import { z } from "zod/mod.ts";
import { Config } from "../config.ts";
import { Element } from "../dom.ts";
import { Revision, stringToDate } from "../revision.ts";
-import { djot2ndoc } from "./djot2ndoc.ts";
+import { mdast2ndoc } from "./mdast2ndoc.ts";
export const PostMetadataSchema = z.object({
article: z.object({
@@ -40,15 +40,15 @@ export type Document = {
uuid: string;
link: string;
title: string;
- description: string; // TODO: should it be markup text?
+ description: string;
tags: string[];
revisions: Revision[];
toc?: TocRoot;
isTocEnabled: boolean;
};
-export function createNewDocumentFromDjotDocument(
- root: DjotDoc,
+export function createNewDocumentFromMdast(
+ root: MdastRoot,
meta: PostMetadata,
sourceFilePath: string,
config: Config,
@@ -57,7 +57,7 @@ export function createNewDocumentFromDjotDocument(
const contentDir = join(cwd, config.locations.contentDir);
const link = sourceFilePath.replace(contentDir, "").replace(".xml", "/");
return {
- root: djot2ndoc(root),
+ root: mdast2ndoc(root),
sourceFilePath,
uuid: meta.article.uuid,
link: link,
diff --git a/services/nuldoc/nuldoc-src/markdown/mdast2ndoc.ts b/services/nuldoc/nuldoc-src/markdown/mdast2ndoc.ts
new file mode 100644
index 0000000..367627c
--- /dev/null
+++ b/services/nuldoc/nuldoc-src/markdown/mdast2ndoc.ts
@@ -0,0 +1,575 @@
+import type {
+ Blockquote,
+ Code,
+ Definition,
+ Delete,
+ Emphasis,
+ FootnoteDefinition,
+ FootnoteReference,
+ Heading,
+ Html,
+ Image,
+ InlineCode,
+ Link,
+ List,
+ ListItem,
+ Paragraph,
+ PhrasingContent,
+ Root,
+ RootContent,
+ Strong,
+ Table,
+ TableCell,
+ TableRow,
+ Text as MdastText,
+ ThematicBreak,
+} from "mdast";
+import type {
+ ContainerDirective,
+ LeafDirective,
+ TextDirective,
+} from "mdast-util-directive";
+import { elem, Element, Node, rawHTML, text } from "../dom.ts";
+
+type DirectiveNode = ContainerDirective | LeafDirective | TextDirective;
+
+function isDirective(node: RootContent): node is DirectiveNode {
+ return (
+ node.type === "containerDirective" ||
+ node.type === "leafDirective" ||
+ node.type === "textDirective"
+ );
+}
+
+// Extract section ID and attributes from heading if present
+// Supports syntax like {#id} or {#id attr="value"}
+function extractSectionId(
+ node: Heading,
+): {
+ id: string | null;
+ attributes: Record<string, string>;
+ children: Heading["children"];
+} {
+ if (node.children.length === 0) {
+ return { id: null, attributes: {}, children: node.children };
+ }
+
+ const lastChild = node.children[node.children.length - 1];
+ if (lastChild && lastChild.type === "text") {
+ // Match {#id ...} or {#id attr="value" ...}
+ const match = lastChild.value.match(/\s*\{#([^\s}]+)([^}]*)\}\s*$/);
+ if (match) {
+ const id = match[1];
+ const attrString = match[2].trim();
+ const attributes: Record<string, string> = {};
+
+ // Parse attributes like toc="false" (supports smart quotes too)
+ // U+0022 = ", U+201C = ", U+201D = "
+ const attrRegex =
+ /(\w+)=["\u201c\u201d]([^"\u201c\u201d]*)["\u201c\u201d]/g;
+ let attrMatch;
+ while ((attrMatch = attrRegex.exec(attrString)) !== null) {
+ attributes[attrMatch[1]] = attrMatch[2];
+ }
+
+ const newValue = lastChild.value.replace(/\s*\{#[^}]+\}\s*$/, "");
+ if (newValue === "") {
+ return { id, attributes, children: node.children.slice(0, -1) };
+ } else {
+ const newChildren = [...node.children];
+ newChildren[newChildren.length - 1] = { ...lastChild, value: newValue };
+ return { id, attributes, children: newChildren };
+ }
+ }
+ }
+
+ return { id: null, attributes: {}, children: node.children };
+}
+
+function processBlock(node: RootContent): Element | Element[] | null {
+ switch (node.type) {
+ case "heading":
+ // Headings are handled specially in mdast2ndoc
+ return null;
+ case "paragraph":
+ return processParagraph(node);
+ case "thematicBreak":
+ return processThematicBreak(node);
+ case "blockquote":
+ return processBlockquote(node);
+ case "code":
+ return processCode(node);
+ case "list":
+ return processList(node);
+ case "table":
+ return processTable(node);
+ case "html":
+ return processHtmlBlock(node);
+ case "definition":
+ return processDefinition(node);
+ case "footnoteDefinition":
+ return processFootnoteDefinition(node);
+ default:
+ if (isDirective(node)) {
+ return processDirective(node);
+ }
+ return null;
+ }
+}
+
+function processParagraph(node: Paragraph): Element {
+ return elem("p", {}, ...node.children.map(processInline));
+}
+
+function processThematicBreak(_node: ThematicBreak): Element {
+ return elem("hr", {});
+}
+
+function processBlockquote(node: Blockquote): Element {
+ const children: Node[] = [];
+ for (const child of node.children) {
+ const result = processBlock(child);
+ if (result) {
+ if (Array.isArray(result)) {
+ children.push(...result);
+ } else {
+ children.push(result);
+ }
+ }
+ }
+ return elem("blockquote", {}, ...children);
+}
+
+function processCode(node: Code): Element {
+ const attributes: Record<string, string> = {};
+
+ if (node.lang) {
+ attributes.language = node.lang;
+ }
+
+ // Parse meta string for filename and numbered attributes
+ if (node.meta) {
+ const filenameMatch = node.meta.match(/filename="([^"]+)"/);
+ if (filenameMatch) {
+ attributes.filename = filenameMatch[1];
+ }
+
+ if (node.meta.includes("numbered")) {
+ attributes.numbered = "true";
+ }
+ }
+
+ return elem("codeblock", attributes, text(node.value));
+}
+
+function processList(node: List): Element {
+ const attributes: Record<string, string> = {};
+ attributes.__tight = node.spread === false ? "true" : "false";
+
+ const isTaskList = node.children.some(
+ (item) => item.checked !== null && item.checked !== undefined,
+ );
+
+ if (isTaskList) {
+ attributes.type = "task";
+ }
+
+ if (node.ordered && node.start !== null && node.start !== 1) {
+ attributes.start = node.start!.toString();
+ }
+
+ const children = node.children.map((item) =>
+ processListItem(item, isTaskList)
+ );
+
+ return elem(node.ordered ? "ol" : "ul", attributes, ...children);
+}
+
+function processListItem(node: ListItem, isTaskList: boolean): Element {
+ const attributes: Record<string, string> = {};
+
+ if (isTaskList) {
+ attributes.checked = node.checked ? "true" : "false";
+ }
+
+ const children: Node[] = [];
+ for (const child of node.children) {
+ const result = processBlock(child);
+ if (result) {
+ if (Array.isArray(result)) {
+ children.push(...result);
+ } else {
+ children.push(result);
+ }
+ }
+ }
+
+ return elem("li", attributes, ...children);
+}
+
+function processTable(node: Table): Element {
+ const tableElement = elem("table", {});
+ const headerRows: Element[] = [];
+ const bodyRows: Element[] = [];
+
+ node.children.forEach((row, rowIndex) => {
+ const rowElement = processTableRow(row, rowIndex === 0, node.align);
+ if (rowIndex === 0) {
+ headerRows.push(rowElement);
+ } else {
+ bodyRows.push(rowElement);
+ }
+ });
+
+ if (headerRows.length > 0) {
+ tableElement.children.push(elem("thead", undefined, ...headerRows));
+ }
+
+ if (bodyRows.length > 0) {
+ tableElement.children.push(elem("tbody", undefined, ...bodyRows));
+ }
+
+ return tableElement;
+}
+
+function processTableRow(
+ node: TableRow,
+ isHeader: boolean,
+ alignments: (string | null)[] | null | undefined,
+): Element {
+ const cells = node.children.map((cell, index) =>
+ processTableCell(cell, isHeader, alignments?.[index])
+ );
+ return elem("tr", {}, ...cells);
+}
+
+function processTableCell(
+ node: TableCell,
+ isHeader: boolean,
+ alignment: string | null | undefined,
+): Element {
+ const attributes: Record<string, string> = {};
+ if (alignment && alignment !== "none") {
+ attributes.align = alignment;
+ }
+
+ return elem(
+ isHeader ? "th" : "td",
+ attributes,
+ ...node.children.map(processInline),
+ );
+}
+
+function processHtmlBlock(node: Html): Element {
+ return elem("div", { class: "raw-html" }, rawHTML(node.value));
+}
+
+function processDefinition(_node: Definition): null {
+ // Link definitions are handled elsewhere
+ return null;
+}
+
+function processFootnoteDefinition(node: FootnoteDefinition): Element {
+ const children: Node[] = [];
+ for (const child of node.children) {
+ const result = processBlock(child);
+ if (result) {
+ if (Array.isArray(result)) {
+ children.push(...result);
+ } else {
+ children.push(result);
+ }
+ }
+ }
+ return elem("footnote", { id: node.identifier }, ...children);
+}
+
+function processDirective(node: DirectiveNode): Element | null {
+ const name = node.name;
+
+ if (name === "note" || name === "edit") {
+ const attributes: Record<string, string> = {};
+
+ // Copy directive attributes
+ if (node.attributes) {
+ for (const [key, value] of Object.entries(node.attributes)) {
+ if (value !== undefined && value !== null) {
+ attributes[key] = String(value);
+ }
+ }
+ }
+
+ const children: Node[] = [];
+ if ("children" in node && node.children) {
+ for (const child of node.children as RootContent[]) {
+ const result = processBlock(child);
+ if (result) {
+ if (Array.isArray(result)) {
+ children.push(...result);
+ } else {
+ children.push(result);
+ }
+ }
+ }
+ }
+
+ return elem("note", attributes, ...children);
+ }
+
+ // For other directives, treat as div
+ const children: Node[] = [];
+ if ("children" in node && node.children) {
+ for (const child of node.children as RootContent[]) {
+ const result = processBlock(child);
+ if (result) {
+ if (Array.isArray(result)) {
+ children.push(...result);
+ } else {
+ children.push(result);
+ }
+ }
+ }
+ }
+
+ return elem(
+ "div",
+ node.attributes as Record<string, string> || {},
+ ...children,
+ );
+}
+
+function processInline(node: PhrasingContent): Node {
+ switch (node.type) {
+ case "text":
+ return processText(node);
+ case "emphasis":
+ return processEmphasis(node);
+ case "strong":
+ return processStrong(node);
+ case "inlineCode":
+ return processInlineCode(node);
+ case "link":
+ return processLink(node);
+ case "image":
+ return processImage(node);
+ case "delete":
+ return processDelete(node);
+ case "break":
+ return elem("br");
+ case "html":
+ return rawHTML(node.value);
+ case "footnoteReference":
+ return processFootnoteReference(node);
+ default:
+ // Handle any unexpected node types
+ if ("value" in node) {
+ return text(String(node.value));
+ }
+ if ("children" in node && Array.isArray(node.children)) {
+ return elem(
+ "span",
+ {},
+ ...node.children.map((c: PhrasingContent) => processInline(c)),
+ );
+ }
+ return text("");
+ }
+}
+
+function processText(node: MdastText): Node {
+ return text(node.value);
+}
+
+function processEmphasis(node: Emphasis): Element {
+ return elem("em", {}, ...node.children.map(processInline));
+}
+
+function processStrong(node: Strong): Element {
+ return elem("strong", {}, ...node.children.map(processInline));
+}
+
+function processInlineCode(node: InlineCode): Element {
+ return elem("code", {}, text(node.value));
+}
+
+function processLink(node: Link): Element {
+ const attributes: Record<string, string> = {};
+ if (node.url) {
+ attributes.href = node.url;
+ }
+ if (node.title) {
+ attributes.title = node.title;
+ }
+ // Detect autolinks (URL equals link text)
+ const isAutolink = node.children.length === 1 &&
+ node.children[0].type === "text" &&
+ node.children[0].value === node.url;
+ if (isAutolink) {
+ attributes.class = "url";
+ }
+ return elem("a", attributes, ...node.children.map(processInline));
+}
+
+function processImage(node: Image): Element {
+ const attributes: Record<string, string> = {};
+ if (node.url) {
+ attributes.src = node.url;
+ }
+ if (node.alt) {
+ attributes.alt = node.alt;
+ }
+ if (node.title) {
+ attributes.title = node.title;
+ }
+ return elem("img", attributes);
+}
+
+function processDelete(node: Delete): Element {
+ return elem("del", {}, ...node.children.map(processInline));
+}
+
+function processFootnoteReference(node: FootnoteReference): Element {
+ return elem("footnoteref", { reference: node.identifier });
+}
+
+// Build hierarchical section structure from flat mdast
+// This mimics Djot's section structure where headings create nested sections
+export function mdast2ndoc(root: Root): Element {
+ const footnotes: Element[] = [];
+ const nonFootnoteChildren: RootContent[] = [];
+
+ // Separate footnotes from other content
+ for (const child of root.children) {
+ if (child.type === "footnoteDefinition") {
+ const footnote = processFootnoteDefinition(child);
+ footnotes.push(footnote);
+ } else {
+ nonFootnoteChildren.push(child);
+ }
+ }
+
+ // Build hierarchical sections
+ const articleContent = buildSectionHierarchy(nonFootnoteChildren);
+
+ // Add footnotes section if any exist
+ if (footnotes.length > 0) {
+ const footnoteSection = elem(
+ "section",
+ { class: "footnotes" },
+ ...footnotes,
+ );
+ articleContent.push(footnoteSection);
+ }
+
+ return elem(
+ "__root__",
+ undefined,
+ elem("article", undefined, ...articleContent),
+ );
+}
+
+type SectionInfo = {
+ id: string | null;
+ attributes: Record<string, string>;
+ level: number;
+ heading: Element;
+ children: Node[];
+};
+
+function buildSectionHierarchy(nodes: RootContent[]): Node[] {
+ // Group nodes into sections based on headings
+ // Each heading starts a new section at its level
+ const result: Node[] = [];
+ const sectionStack: SectionInfo[] = [];
+
+ for (const node of nodes) {
+ if (node.type === "heading") {
+ const level = node.depth;
+ const { id, attributes, children } = extractSectionId(node);
+
+ // Create heading element
+ const headingElement = elem(
+ "h",
+ {},
+ ...children.map(processInline),
+ );
+
+ // Close sections that are at same or deeper level
+ while (
+ sectionStack.length > 0 &&
+ sectionStack[sectionStack.length - 1].level >= level
+ ) {
+ const closedSection = sectionStack.pop()!;
+ const sectionElement = createSectionElement(closedSection);
+
+ if (sectionStack.length > 0) {
+ // Add to parent section
+ sectionStack[sectionStack.length - 1].children.push(sectionElement);
+ } else {
+ // Add to result
+ result.push(sectionElement);
+ }
+ }
+
+ // Start new section
+ const newSection: SectionInfo = {
+ id,
+ attributes,
+ level,
+ heading: headingElement,
+ children: [],
+ };
+ sectionStack.push(newSection);
+ } else {
+ // Non-heading content
+ const processed = processBlock(node);
+ if (processed) {
+ if (sectionStack.length > 0) {
+ // Add to current section
+ if (Array.isArray(processed)) {
+ sectionStack[sectionStack.length - 1].children.push(...processed);
+ } else {
+ sectionStack[sectionStack.length - 1].children.push(processed);
+ }
+ } else {
+ // Content before any heading
+ if (Array.isArray(processed)) {
+ result.push(...processed);
+ } else {
+ result.push(processed);
+ }
+ }
+ }
+ }
+ }
+
+ // Close remaining sections
+ while (sectionStack.length > 0) {
+ const closedSection = sectionStack.pop()!;
+ const sectionElement = createSectionElement(closedSection);
+
+ if (sectionStack.length > 0) {
+ // Add to parent section
+ sectionStack[sectionStack.length - 1].children.push(sectionElement);
+ } else {
+ // Add to result
+ result.push(sectionElement);
+ }
+ }
+
+ return result;
+}
+
+function createSectionElement(sectionInfo: SectionInfo): Element {
+ const attributes: Record<string, string> = { ...sectionInfo.attributes };
+ if (sectionInfo.id) {
+ attributes.id = sectionInfo.id;
+ }
+
+ return elem(
+ "section",
+ attributes,
+ sectionInfo.heading,
+ ...sectionInfo.children,
+ );
+}
diff --git a/services/nuldoc/nuldoc-src/djot/parse.ts b/services/nuldoc/nuldoc-src/markdown/parse.ts
index c79a670..c0875a2 100644
--- a/services/nuldoc/nuldoc-src/djot/parse.ts
+++ b/services/nuldoc/nuldoc-src/markdown/parse.ts
@@ -1,15 +1,20 @@
-import { parse as parseDjot } from "@djot/djot";
+import type { Root as MdastRoot } from "mdast";
+import { unified } from "unified";
+import remarkParse from "remark-parse";
+import remarkGfm from "remark-gfm";
+import remarkDirective from "remark-directive";
+import remarkSmartypants from "remark-smartypants";
import { parse as parseToml } from "@std/toml";
import { Config } from "../config.ts";
import {
- createNewDocumentFromDjotDocument,
+ createNewDocumentFromMdast,
Document,
PostMetadata,
PostMetadataSchema,
} from "./document.ts";
import toHtml from "./to_html.ts";
-export async function parseDjotFile(
+export async function parseMarkdownFile(
filePath: string,
config: Config,
): Promise<Document> {
@@ -17,8 +22,17 @@ export async function parseDjotFile(
const fileContent = await Deno.readTextFile(filePath);
const [, frontmatter, ...rest] = fileContent.split(/^---$/m);
const meta = parseMetadata(frontmatter);
- const root = parseDjot(rest.join("\n"));
- const doc = createNewDocumentFromDjotDocument(root, meta, filePath, config);
+ const content = rest.join("---");
+
+ const processor = unified()
+ .use(remarkParse)
+ .use(remarkGfm)
+ .use(remarkDirective)
+ .use(remarkSmartypants);
+
+ const root = await processor.run(processor.parse(content)) as MdastRoot;
+
+ const doc = createNewDocumentFromMdast(root, meta, filePath, config);
return await toHtml(doc);
} catch (e) {
if (e instanceof Error) {
diff --git a/services/nuldoc/nuldoc-src/djot/to_html.ts b/services/nuldoc/nuldoc-src/markdown/to_html.ts
index 8219b74..8219b74 100644
--- a/services/nuldoc/nuldoc-src/djot/to_html.ts
+++ b/services/nuldoc/nuldoc-src/markdown/to_html.ts
diff --git a/services/nuldoc/nuldoc-src/pages/PostPage.ts b/services/nuldoc/nuldoc-src/pages/PostPage.ts
index 84f58c3..fe67089 100644
--- a/services/nuldoc/nuldoc-src/pages/PostPage.ts
+++ b/services/nuldoc/nuldoc-src/pages/PostPage.ts
@@ -4,7 +4,7 @@ import PageLayout from "../components/PageLayout.ts";
import TableOfContents from "../components/TableOfContents.ts";
import { Config, getTagLabel } from "../config.ts";
import { elem, Element } from "../dom.ts";
-import { Document } from "../djot/document.ts";
+import { Document } from "../markdown/document.ts";
import { dateToString } from "../revision.ts";
import { getPostPublishedDate } from "../generators/post.ts";
diff --git a/services/nuldoc/public/blog/posts/2021-03-05/my-first-post/index.html b/services/nuldoc/public/blog/posts/2021-03-05/my-first-post/index.html
index 15d8ab0..0681e34 100644
--- a/services/nuldoc/public/blog/posts/2021-03-05/my-first-post/index.html
+++ b/services/nuldoc/public/blog/posts/2021-03-05/my-first-post/index.html
@@ -173,6 +173,7 @@
blockquote
</p>
</blockquote>
+ <hr>
<div class="codeblock">
<pre class="shiki github-light" style="background-color:#f5f5f5;color:#24292e" tabindex="0"><code><span class="line"><span style="color:#005CC5">puts</span><span style="color:#032F62"> "Hello, World!"</span></span></code></pre>
</div>
@@ -201,10 +202,7 @@
<code>code</code>
</p>
<p>
- <ins>
- inserted
- </ins>
-
+ <ins>inserted</ins>
<del>
deleted
</del>
@@ -223,8 +221,6 @@
</div>
</div>
<table>
- <caption>
- </caption>
<thead>
<tr>
<th>
diff --git a/services/nuldoc/public/blog/posts/2023-12-31/2023-reflections/index.html b/services/nuldoc/public/blog/posts/2023-12-31/2023-reflections/index.html
index 431c843..e7810ab 100644
--- a/services/nuldoc/public/blog/posts/2023-12-31/2023-reflections/index.html
+++ b/services/nuldoc/public/blog/posts/2023-12-31/2023-reflections/index.html
@@ -88,7 +88,9 @@
</p>
<ul>
<li>
- PHP 勉強会@東京での登壇 (計 8 回)
+ <p>
+ PHP 勉強会@東京での登壇 (計 8 回)
+ </p>
<ul>
<li>
<a href="/slides/2023-01-18/phpstudy-tokyo-148/">第 148 回</a>
@@ -117,7 +119,9 @@
</ul>
</li>
<li>
- PHPerKaigi 2023 での登壇
+ <p>
+ PHPerKaigi 2023 での登壇
+ </p>
<ul>
<li>
<a href="/slides/2023-03-24/phperkaigi-2023/">レギュラートーク</a>
@@ -128,13 +132,19 @@
</ul>
</li>
<li>
- PHPerKaigi 2023 での当日スタッフ業
+ <p>
+ PHPerKaigi 2023 での当日スタッフ業
+ </p>
</li>
<li>
- <a href="/slides/2023-06-23/phpconfuk-2023-eve/">非公式でおこなわれた PHP カンファレンス福岡 2023 の 前夜祭イベントでの登壇</a>
+ <p>
+ <a href="/slides/2023-06-23/phpconfuk-2023-eve/">非公式でおこなわれた PHP カンファレンス福岡 2023 の 前夜祭イベントでの登壇</a>
+ </p>
</li>
<li>
- PHPerKaigi 2024 でのコアスタッフ業
+ <p>
+ PHPerKaigi 2024 でのコアスタッフ業
+ </p>
</li>
</ul>
</section>
diff --git a/services/nuldoc/public/blog/posts/2024-04-14/phpcon-odawara-2024-report/index.html b/services/nuldoc/public/blog/posts/2024-04-14/phpcon-odawara-2024-report/index.html
index 24f1921..71c4ad3 100644
--- a/services/nuldoc/public/blog/posts/2024-04-14/phpcon-odawara-2024-report/index.html
+++ b/services/nuldoc/public/blog/posts/2024-04-14/phpcon-odawara-2024-report/index.html
@@ -157,7 +157,7 @@
プロポーザルリンク: <a class="url" href="https://fortee.jp/phpconodawara-2024/proposal/740b034a-81f0-4b7a-90e9-cd3fa01c651f" rel="noreferrer" target="_blank">https://fortee.jp/phpconodawara-2024/proposal/740b034a-81f0-4b7a-90e9-cd3fa01c651f</a>
</li>
<li>
- 感想: 前々から出そうとしている RFC があるので、RFC についての日本語情報が増えるのは大変ありがたいです。あとは作業を進めなければ……。
+ 感想: 前々から出そうとしている RFC があるので、RFC についての日本語情報が増えるのは大変ありがたいです。あとは作業を進めなければ…。
</li>
</ul>
</li>
diff --git a/services/nuldoc/public/blog/posts/2024-05-11/phpconkagawa-2024-report/index.html b/services/nuldoc/public/blog/posts/2024-05-11/phpconkagawa-2024-report/index.html
index 652866d..525ea33 100644
--- a/services/nuldoc/public/blog/posts/2024-05-11/phpconkagawa-2024-report/index.html
+++ b/services/nuldoc/public/blog/posts/2024-05-11/phpconkagawa-2024-report/index.html
@@ -168,7 +168,7 @@
午前中の発表に間に合わなかったことがとにかく心残りなのだが、それ以外は PHP カンファレンス小田原のスタッフの方々をはじめ多くの方と交流でき、非常に楽しいカンファレンスだった。来年もあるそうなので (この分だと来年も月刊 PHP カンファレンスにならないか?)、是非参加したい。
</p>
<p>
- あれ、そういえば香川でうどん食べてないな……。
+ あれ、そういえば香川でうどん食べてないな…。
</p>
</section>
</div>
diff --git a/services/nuldoc/public/blog/posts/2024-08-19/go-template-access-outer-scope-pipeline-within-with-or-range/index.html b/services/nuldoc/public/blog/posts/2024-08-19/go-template-access-outer-scope-pipeline-within-with-or-range/index.html
index e354a6b..1e0faeb 100644
--- a/services/nuldoc/public/blog/posts/2024-08-19/go-template-access-outer-scope-pipeline-within-with-or-range/index.html
+++ b/services/nuldoc/public/blog/posts/2024-08-19/go-template-access-outer-scope-pipeline-within-with-or-range/index.html
@@ -188,7 +188,7 @@
<h2><a href="#section--reference">参考</a></h2>
<ul>
<li>
- <a href="https://stackoverflow.com/questions/14800204/in-a-template-how-do-you-access-an-outer-scope-while-inside-of-a-with-or-rang" rel="noreferrer" target="_blank">直接の出典である Stack Overflow の回答: <span>In a template how do you access an outer scope while inside of a “with” or “range” scope?</span></a>
+ <a href="https://stackoverflow.com/questions/14800204/in-a-template-how-do-you-access-an-outer-scope-while-inside-of-a-with-or-rang" rel="noreferrer" target="_blank">直接の出典である Stack Overflow の回答: “In a template how do you access an outer scope while inside of a “with” or “range” scope?”</a>
</li>
<li>
<a href="https://pkg.go.dev/text/template#hdr-Variables" rel="noreferrer" target="_blank">大元の出典である <code>text/template</code> の公式ドキュメント</a>
diff --git a/services/nuldoc/public/blog/posts/2024-12-33/2024-reflections/index.html b/services/nuldoc/public/blog/posts/2024-12-33/2024-reflections/index.html
index 4fe8d96..11a1bc7 100644
--- a/services/nuldoc/public/blog/posts/2024-12-33/2024-reflections/index.html
+++ b/services/nuldoc/public/blog/posts/2024-12-33/2024-reflections/index.html
@@ -81,7 +81,7 @@
ご存じのとおり、4 と 11 と 23 で割り切れる年は閏年というやつで 12 月が 33 日まである。1年の振り返りを書く猶予が平年よりも長くなるので大変に都合がよい。
</p>
<p>
- 去年のやつ: <a href="/posts/2023-12-31/2023-reflections/">/posts/2023-12-31/2023-reflections/</a>
+ 去年のやつ: <a class="url" href="/posts/2023-12-31/2023-reflections/">/posts/2023-12-31/2023-reflections/</a>
</p>
</section>
<section id="section--conference">
diff --git a/services/nuldoc/public/blog/posts/2025-05-05/make-tiny-self-hosted-c-compiler/index.html b/services/nuldoc/public/blog/posts/2025-05-05/make-tiny-self-hosted-c-compiler/index.html
index edf24cd..01fb0ea 100644
--- a/services/nuldoc/public/blog/posts/2025-05-05/make-tiny-self-hosted-c-compiler/index.html
+++ b/services/nuldoc/public/blog/posts/2025-05-05/make-tiny-self-hosted-c-compiler/index.html
@@ -507,7 +507,7 @@
さて、第2世代コンパイラが手に入ったので、ここからは地獄のデバッグ作業が始まる。多段になっているために問題が起きている箇所の特定が難しい。
</p>
<p>
- ……と考えていたのだが、実際のところデバッグは1時間ほどで終わってしまった。修正したのは1点のみ。なんのことはない、2日目終了時点でほとんど完成していたわけだ。
+ …と考えていたのだが、実際のところデバッグは1時間ほどで終わってしまった。修正したのは1点のみ。なんのことはない、2日目終了時点でほとんど完成していたわけだ。
</p>
<p>
記念すべき (?) 最後のバグはこちら。
diff --git a/services/nuldoc/public/blog/posts/2025-06-14/baba-is-you/index.html b/services/nuldoc/public/blog/posts/2025-06-14/baba-is-you/index.html
index c3f1c36..fd87f7e 100644
--- a/services/nuldoc/public/blog/posts/2025-06-14/baba-is-you/index.html
+++ b/services/nuldoc/public/blog/posts/2025-06-14/baba-is-you/index.html
@@ -299,13 +299,19 @@
</p>
<ul>
<li>
- <code>BABA</code>: テキストとしての <code>BABA</code>
+ <p>
+ <code>BABA</code>: テキストとしての <code>BABA</code>
+ </p>
</li>
<li>
- Baba: オブジェクトとしての baba
+ <p>
+ Baba: オブジェクトとしての baba
+ </p>
</li>
<li>
- <code>A</code>、<code>B</code> など: 任意のテキスト
+ <p>
+ <code>A</code>、<code>B</code> など: 任意のテキスト
+ </p>
<ul>
<li>
そういうテキストが出てくる面もあるがその面の話はしない
@@ -313,10 +319,14 @@
</ul>
</li>
<li>
- A、B など: 任意のオブジェクト
+ <p>
+ A、B など: 任意のオブジェクト
+ </p>
</li>
<li>
- <code>A/B</code>: <code>A</code> と <code>B</code> のテキストが重なった状態
+ <p>
+ <code>A/B</code>: <code>A</code> と <code>B</code> のテキストが重なった状態
+ </p>
</li>
</ul>
<p>
@@ -414,7 +424,7 @@
<img alt="「HEAVY CLOUD」のスクリーンショット" src="/posts/2025-06-14/baba-is-you/LEVEL_HEAVY_CLOUD.jpeg">
</p>
<p>
- 難しい面ではあるのだが、それ以上に解法の美しさに感動した面。解き終わった後に思わず「美しい……」と呟いてしまったのはこの面だけだった。Baba Is You の好きな面はと聞かれれば真っ先にこれを挙げる。
+ 難しい面ではあるのだが、それ以上に解法の美しさに感動した面。解き終わった後に思わず「美しい…」と呟いてしまったのはこの面だけだった。Baba Is You の好きな面はと聞かれれば真っ先にこれを挙げる。
</p>
</section>
<section id="section--spoiler--impressive-levels--map--adventurers">
@@ -432,7 +442,7 @@
<img alt="「OUT AT SEA」のスクリーンショット" src="/posts/2025-06-14/baba-is-you/LEVEL_OUT_AT_SEA.jpeg">
</p>
<p>
- MAP の問題児。正攻法がテキスト重ねである最初の面。この面、<code>ICE/LAVA</code> <code>IS</code> <code>PUSH</code> を作ったあと重なった ice と lava を (<code>ICE</code> <code>IS</code> <code>PUSH</code> だけ作るなどして) 分離しないといけないのだが、意気揚々と ice on lava の状態で door に向かって push して push できなかったときの感情はよく覚えている。「テキスト同士を重ねて <code>A</code> <code>IS</code> <code>PUSH</code> と <code>B</code> <code>IS</code> <code>PUSH</code> を両立させるというぶっ飛んだアイデアを実現してもなお解けないのか?」「この方針がまさか間違っているなどということがあるのか、いやそんなはずはない……」 実際のところそこからのリカバリーはすぐできたが、そのときの絶望はこれまででも最大であった。
+ MAP の問題児。正攻法がテキスト重ねである最初の面。この面、<code>ICE/LAVA</code> <code>IS</code> <code>PUSH</code> を作ったあと重なった ice と lava を (<code>ICE</code> <code>IS</code> <code>PUSH</code> だけ作るなどして) 分離しないといけないのだが、意気揚々と ice on lava の状態で door に向かって push して push できなかったときの感情はよく覚えている。「テキスト同士を重ねて <code>A</code> <code>IS</code> <code>PUSH</code> と <code>B</code> <code>IS</code> <code>PUSH</code> を両立させるというぶっ飛んだアイデアを実現してもなお解けないのか?」「この方針がまさか間違っているなどということがあるのか、いやそんなはずはない…」 実際のところそこからのリカバリーはすぐできたが、そのときの絶望はこれまででも最大であった。
</p>
</section>
<section id="section--spoiler--impressive-levels--map--seeking-acceptance">
diff --git a/services/nuldoc/public/blog/posts/2025-07-15/partial-surrender-to-ebooks/index.html b/services/nuldoc/public/blog/posts/2025-07-15/partial-surrender-to-ebooks/index.html
index 83181fb..59f63d2 100644
--- a/services/nuldoc/public/blog/posts/2025-07-15/partial-surrender-to-ebooks/index.html
+++ b/services/nuldoc/public/blog/posts/2025-07-15/partial-surrender-to-ebooks/index.html
@@ -67,9 +67,14 @@
<p>
この基準だと『ヘイル・メアリー』の件は避けられないわけだが、幸いにして直近で読んだ本は本の山の山頂付近に居るので見失うことはないだろう。しばらくは。
</p>
- <p>
- <sup class="footnote"><a class="footnote" href="#footnote--thin-red-line" id="footnoteref--thin-red-line">[1]</a></sup> 『プロジェクト・ヘイル・メアリー』最序盤の一節
- </p>
+ <section class="footnotes">
+ <div class="footnote" id="footnote--thin-red-line">
+ <a href="#footnoteref--thin-red-line">1. </a>
+ <p>
+ 『プロジェクト・ヘイル・メアリー』最序盤の一節
+ </p>
+ </div>
+ </section>
</div>
</article>
</main>
diff --git a/services/nuldoc/public/blog/posts/2025-11-09/rubiks-cube-blindfolded-first-success/index.html b/services/nuldoc/public/blog/posts/2025-11-09/rubiks-cube-blindfolded-first-success/index.html
index ebf7055..4d84235 100644
--- a/services/nuldoc/public/blog/posts/2025-11-09/rubiks-cube-blindfolded-first-success/index.html
+++ b/services/nuldoc/public/blog/posts/2025-11-09/rubiks-cube-blindfolded-first-success/index.html
@@ -162,7 +162,7 @@
分析を紙に書き起こし、それを見ながらキューブは見ずに揃える
</li>
<li>
- <strong><strong>本番に挑戦</strong></strong>
+ <strong>本番に挑戦</strong>
</li>
</ul>
</li>