From 6dedddc545e2f1930bdc2256784eb1551bd4231d Mon Sep 17 00:00:00 2001 From: nsfisis Date: Sun, 1 Feb 2026 00:49:15 +0900 Subject: feat(nuldoc): rewrite nuldoc in Ruby --- services/nuldoc/.rubocop.yml | 20 + services/nuldoc/Gemfile | 13 + services/nuldoc/Gemfile.lock | 65 ++ services/nuldoc/NOTE.md | 6 +- services/nuldoc/Rakefile | 22 + .../cpp-you-can-use-keywords-in-attributes.md | 3 +- .../implementation-of-minimal-png-image-encoder.md | 18 +- services/nuldoc/deno.jsonc | 34 - services/nuldoc/deno.lock | 1137 -------------------- services/nuldoc/lib/nuldoc.rb | 62 ++ services/nuldoc/lib/nuldoc/cli.rb | 46 + services/nuldoc/lib/nuldoc/commands/build.rb | 216 ++++ services/nuldoc/lib/nuldoc/commands/new.rb | 81 ++ services/nuldoc/lib/nuldoc/commands/serve.rb | 63 ++ .../nuldoc/lib/nuldoc/components/global_footer.rb | 11 + .../nuldoc/lib/nuldoc/components/global_headers.rb | 54 + .../nuldoc/lib/nuldoc/components/page_layout.rb | 35 + .../nuldoc/lib/nuldoc/components/pagination.rb | 62 ++ .../lib/nuldoc/components/post_page_entry.rb | 25 + .../lib/nuldoc/components/slide_page_entry.rb | 25 + .../nuldoc/lib/nuldoc/components/static_script.rb | 16 + .../lib/nuldoc/components/static_stylesheet.rb | 13 + .../lib/nuldoc/components/table_of_contents.rb | 21 + services/nuldoc/lib/nuldoc/components/tag_list.rb | 15 + services/nuldoc/lib/nuldoc/components/utils.rb | 8 + services/nuldoc/lib/nuldoc/config.rb | 67 ++ services/nuldoc/lib/nuldoc/dom.rb | 136 +++ services/nuldoc/lib/nuldoc/generators/about.rb | 22 + services/nuldoc/lib/nuldoc/generators/atom.rb | 58 + services/nuldoc/lib/nuldoc/generators/home.rb | 21 + services/nuldoc/lib/nuldoc/generators/not_found.rb | 22 + services/nuldoc/lib/nuldoc/generators/post.rb | 57 + services/nuldoc/lib/nuldoc/generators/post_list.rb | 41 + services/nuldoc/lib/nuldoc/generators/slide.rb | 42 + .../nuldoc/lib/nuldoc/generators/slide_list.rb | 22 + services/nuldoc/lib/nuldoc/generators/tag.rb | 31 + services/nuldoc/lib/nuldoc/generators/tag_list.rb | 23 + services/nuldoc/lib/nuldoc/markdown/document.rb | 19 + services/nuldoc/lib/nuldoc/markdown/parse.rb | 51 + .../lib/nuldoc/markdown/parser/attributes.rb | 25 + .../lib/nuldoc/markdown/parser/block_parser.rb | 602 +++++++++++ .../lib/nuldoc/markdown/parser/inline_parser.rb | 383 +++++++ .../lib/nuldoc/markdown/parser/line_scanner.rb | 38 + services/nuldoc/lib/nuldoc/markdown/transform.rb | 418 +++++++ services/nuldoc/lib/nuldoc/page.rb | 3 + services/nuldoc/lib/nuldoc/pages/about_page.rb | 70 ++ services/nuldoc/lib/nuldoc/pages/atom_page.rb | 26 + services/nuldoc/lib/nuldoc/pages/home_page.rb | 36 + services/nuldoc/lib/nuldoc/pages/not_found_page.rb | 31 + services/nuldoc/lib/nuldoc/pages/post_list_page.rb | 34 + services/nuldoc/lib/nuldoc/pages/post_page.rb | 46 + .../nuldoc/lib/nuldoc/pages/slide_list_page.rb | 29 + services/nuldoc/lib/nuldoc/pages/slide_page.rb | 66 ++ services/nuldoc/lib/nuldoc/pages/tag_list_page.rb | 39 + services/nuldoc/lib/nuldoc/pages/tag_page.rb | 37 + services/nuldoc/lib/nuldoc/render.rb | 14 + services/nuldoc/lib/nuldoc/renderers/html.rb | 210 ++++ services/nuldoc/lib/nuldoc/renderers/xml.rb | 97 ++ services/nuldoc/lib/nuldoc/revision.rb | 18 + services/nuldoc/lib/nuldoc/slide/parse.rb | 16 + services/nuldoc/lib/nuldoc/slide/slide.rb | 44 + services/nuldoc/nuldoc | 2 - services/nuldoc/nuldoc-src/commands/build.ts | 332 ------ services/nuldoc/nuldoc-src/commands/new.ts | 96 -- services/nuldoc/nuldoc-src/commands/serve.ts | 57 - .../nuldoc-src/components/AboutGlobalHeader.ts | 15 - .../nuldoc-src/components/BlogGlobalHeader.ts | 28 - .../nuldoc-src/components/DefaultGlobalHeader.ts | 15 - .../nuldoc/nuldoc-src/components/GlobalFooter.ts | 9 - .../nuldoc/nuldoc-src/components/PageLayout.ts | 76 -- .../nuldoc/nuldoc-src/components/Pagination.ts | 93 -- .../nuldoc/nuldoc-src/components/PostPageEntry.ts | 55 - .../nuldoc/nuldoc-src/components/SlidePageEntry.ts | 55 - .../nuldoc-src/components/SlidesGlobalHeader.ts | 27 - .../nuldoc/nuldoc-src/components/StaticScript.ts | 26 - .../nuldoc-src/components/StaticStylesheet.ts | 21 - .../nuldoc-src/components/TableOfContents.ts | 30 - services/nuldoc/nuldoc-src/components/TagList.ts | 19 - services/nuldoc/nuldoc-src/components/utils.ts | 8 - services/nuldoc/nuldoc-src/config.ts | 52 - services/nuldoc/nuldoc-src/dom.ts | 283 ----- services/nuldoc/nuldoc-src/errors.ts | 17 - services/nuldoc/nuldoc-src/generators/about.ts | 21 - services/nuldoc/nuldoc-src/generators/atom.ts | 84 -- services/nuldoc/nuldoc-src/generators/home.ts | 17 - services/nuldoc/nuldoc-src/generators/not_found.ts | 20 - services/nuldoc/nuldoc-src/generators/post.ts | 63 -- services/nuldoc/nuldoc-src/generators/post_list.ts | 56 - services/nuldoc/nuldoc-src/generators/slide.ts | 51 - .../nuldoc/nuldoc-src/generators/slide_list.ts | 21 - services/nuldoc/nuldoc-src/generators/tag.ts | 32 - services/nuldoc/nuldoc-src/generators/tag_list.ts | 22 - .../nuldoc/nuldoc-src/generators/tagged_page.ts | 4 - services/nuldoc/nuldoc-src/main.ts | 19 - services/nuldoc/nuldoc-src/markdown/document.ts | 75 -- services/nuldoc/nuldoc-src/markdown/mdast2ndoc.ts | 589 ---------- services/nuldoc/nuldoc-src/markdown/parse.ts | 47 - services/nuldoc/nuldoc-src/markdown/to_html.ts | 496 --------- services/nuldoc/nuldoc-src/page.ts | 10 - services/nuldoc/nuldoc-src/pages/AboutPage.ts | 154 --- services/nuldoc/nuldoc-src/pages/AtomPage.ts | 27 - services/nuldoc/nuldoc-src/pages/HomePage.ts | 66 -- services/nuldoc/nuldoc-src/pages/NotFoundPage.ts | 40 - services/nuldoc/nuldoc-src/pages/PostListPage.ts | 48 - services/nuldoc/nuldoc-src/pages/PostPage.ts | 94 -- services/nuldoc/nuldoc-src/pages/SlideListPage.ts | 45 - services/nuldoc/nuldoc-src/pages/SlidePage.ts | 140 --- services/nuldoc/nuldoc-src/pages/TagListPage.ts | 67 -- services/nuldoc/nuldoc-src/pages/TagPage.ts | 50 - services/nuldoc/nuldoc-src/render.ts | 13 - services/nuldoc/nuldoc-src/renderers/html.ts | 311 ------ services/nuldoc/nuldoc-src/renderers/xml.ts | 128 --- services/nuldoc/nuldoc-src/revision.ts | 37 - services/nuldoc/nuldoc-src/slide/parse.ts | 20 - services/nuldoc/nuldoc-src/slide/slide.ts | 67 -- .../blog/posts/2021-03-05/my-first-post/index.html | 3 +- .../cpp-you-can-use-keywords-in-attributes.md | 3 +- .../index.html | 56 +- .../python-unbound-local-error/index.html | 49 +- .../ruby-detect-running-implementation/index.html | 43 +- .../ruby-then-keyword-and-case-in/index.html | 202 ++-- .../rust-where-are-primitive-types-from/index.html | 197 ++-- .../index.html | 39 +- .../vim-swap-order-of-selected-lines/index.html | 40 +- .../2022-04-09/phperkaigi-2022-tokens/index.html | 417 +++---- .../index.html | 3 +- .../php-conference-okinawa-code-golf/index.html | 22 +- .../index.html | 751 ++++++------- .../phperkaigi-2023-unused-token-quiz-1/index.html | 103 +- .../setup-server-for-this-site/index.html | 128 ++- .../phperkaigi-2023-unused-token-quiz-2/index.html | 93 +- .../phperkaigi-2023-unused-token-quiz-3/index.html | 331 +++--- .../implementation-of-minimal-png-image-encoder.md | 18 +- .../index.html | 694 ++++++------ .../compile-php-runtime-to-wasm/index.html | 250 ++--- .../index.html | 258 ++--- .../index.html | 98 +- .../pipefail-option-in-gitlab-ci-cd/index.html | 117 +- .../index.html | 21 +- .../reparojson-fix-only-json-formatter/index.html | 105 +- .../index.html | 85 +- .../posts/2024-12-04/cohackpp-report/index.html | 247 ++--- .../phperkaigi-2023-tokens-q1/index.html | 254 ++--- .../index.html | 17 +- .../index.html | 56 +- .../trick-2025-most-ruby-on-ruby-award/index.html | 153 +-- .../index.html | 5 +- .../make-tiny-self-hosted-c-compiler/index.html | 78 +- .../index.html | 59 +- .../posts/2025-11-27/anybatross-writeup/index.html | 92 +- .../archive-dynamic-site-with-wget/index.html | 92 +- 151 files changed, 6477 insertions(+), 7917 deletions(-) create mode 100644 services/nuldoc/.rubocop.yml create mode 100644 services/nuldoc/Gemfile create mode 100644 services/nuldoc/Gemfile.lock create mode 100644 services/nuldoc/Rakefile delete mode 100644 services/nuldoc/deno.jsonc delete mode 100644 services/nuldoc/deno.lock create mode 100644 services/nuldoc/lib/nuldoc.rb create mode 100644 services/nuldoc/lib/nuldoc/cli.rb create mode 100644 services/nuldoc/lib/nuldoc/commands/build.rb create mode 100644 services/nuldoc/lib/nuldoc/commands/new.rb create mode 100644 services/nuldoc/lib/nuldoc/commands/serve.rb create mode 100644 services/nuldoc/lib/nuldoc/components/global_footer.rb create mode 100644 services/nuldoc/lib/nuldoc/components/global_headers.rb create mode 100644 services/nuldoc/lib/nuldoc/components/page_layout.rb create mode 100644 services/nuldoc/lib/nuldoc/components/pagination.rb create mode 100644 services/nuldoc/lib/nuldoc/components/post_page_entry.rb create mode 100644 services/nuldoc/lib/nuldoc/components/slide_page_entry.rb create mode 100644 services/nuldoc/lib/nuldoc/components/static_script.rb create mode 100644 services/nuldoc/lib/nuldoc/components/static_stylesheet.rb create mode 100644 services/nuldoc/lib/nuldoc/components/table_of_contents.rb create mode 100644 services/nuldoc/lib/nuldoc/components/tag_list.rb create mode 100644 services/nuldoc/lib/nuldoc/components/utils.rb create mode 100644 services/nuldoc/lib/nuldoc/config.rb create mode 100644 services/nuldoc/lib/nuldoc/dom.rb create mode 100644 services/nuldoc/lib/nuldoc/generators/about.rb create mode 100644 services/nuldoc/lib/nuldoc/generators/atom.rb create mode 100644 services/nuldoc/lib/nuldoc/generators/home.rb create mode 100644 services/nuldoc/lib/nuldoc/generators/not_found.rb create mode 100644 services/nuldoc/lib/nuldoc/generators/post.rb create mode 100644 services/nuldoc/lib/nuldoc/generators/post_list.rb create mode 100644 services/nuldoc/lib/nuldoc/generators/slide.rb create mode 100644 services/nuldoc/lib/nuldoc/generators/slide_list.rb create mode 100644 services/nuldoc/lib/nuldoc/generators/tag.rb create mode 100644 services/nuldoc/lib/nuldoc/generators/tag_list.rb create mode 100644 services/nuldoc/lib/nuldoc/markdown/document.rb create mode 100644 services/nuldoc/lib/nuldoc/markdown/parse.rb create mode 100644 services/nuldoc/lib/nuldoc/markdown/parser/attributes.rb create mode 100644 services/nuldoc/lib/nuldoc/markdown/parser/block_parser.rb create mode 100644 services/nuldoc/lib/nuldoc/markdown/parser/inline_parser.rb create mode 100644 services/nuldoc/lib/nuldoc/markdown/parser/line_scanner.rb create mode 100644 services/nuldoc/lib/nuldoc/markdown/transform.rb create mode 100644 services/nuldoc/lib/nuldoc/page.rb create mode 100644 services/nuldoc/lib/nuldoc/pages/about_page.rb create mode 100644 services/nuldoc/lib/nuldoc/pages/atom_page.rb create mode 100644 services/nuldoc/lib/nuldoc/pages/home_page.rb create mode 100644 services/nuldoc/lib/nuldoc/pages/not_found_page.rb create mode 100644 services/nuldoc/lib/nuldoc/pages/post_list_page.rb create mode 100644 services/nuldoc/lib/nuldoc/pages/post_page.rb create mode 100644 services/nuldoc/lib/nuldoc/pages/slide_list_page.rb create mode 100644 services/nuldoc/lib/nuldoc/pages/slide_page.rb create mode 100644 services/nuldoc/lib/nuldoc/pages/tag_list_page.rb create mode 100644 services/nuldoc/lib/nuldoc/pages/tag_page.rb create mode 100644 services/nuldoc/lib/nuldoc/render.rb create mode 100644 services/nuldoc/lib/nuldoc/renderers/html.rb create mode 100644 services/nuldoc/lib/nuldoc/renderers/xml.rb create mode 100644 services/nuldoc/lib/nuldoc/revision.rb create mode 100644 services/nuldoc/lib/nuldoc/slide/parse.rb create mode 100644 services/nuldoc/lib/nuldoc/slide/slide.rb delete mode 100755 services/nuldoc/nuldoc delete mode 100644 services/nuldoc/nuldoc-src/commands/build.ts delete mode 100644 services/nuldoc/nuldoc-src/commands/new.ts delete mode 100644 services/nuldoc/nuldoc-src/commands/serve.ts delete mode 100644 services/nuldoc/nuldoc-src/components/AboutGlobalHeader.ts delete mode 100644 services/nuldoc/nuldoc-src/components/BlogGlobalHeader.ts delete mode 100644 services/nuldoc/nuldoc-src/components/DefaultGlobalHeader.ts delete mode 100644 services/nuldoc/nuldoc-src/components/GlobalFooter.ts delete mode 100644 services/nuldoc/nuldoc-src/components/PageLayout.ts delete mode 100644 services/nuldoc/nuldoc-src/components/Pagination.ts delete mode 100644 services/nuldoc/nuldoc-src/components/PostPageEntry.ts delete mode 100644 services/nuldoc/nuldoc-src/components/SlidePageEntry.ts delete mode 100644 services/nuldoc/nuldoc-src/components/SlidesGlobalHeader.ts delete mode 100644 services/nuldoc/nuldoc-src/components/StaticScript.ts delete mode 100644 services/nuldoc/nuldoc-src/components/StaticStylesheet.ts delete mode 100644 services/nuldoc/nuldoc-src/components/TableOfContents.ts delete mode 100644 services/nuldoc/nuldoc-src/components/TagList.ts delete mode 100644 services/nuldoc/nuldoc-src/components/utils.ts delete mode 100644 services/nuldoc/nuldoc-src/config.ts delete mode 100644 services/nuldoc/nuldoc-src/dom.ts delete mode 100644 services/nuldoc/nuldoc-src/errors.ts delete mode 100644 services/nuldoc/nuldoc-src/generators/about.ts delete mode 100644 services/nuldoc/nuldoc-src/generators/atom.ts delete mode 100644 services/nuldoc/nuldoc-src/generators/home.ts delete mode 100644 services/nuldoc/nuldoc-src/generators/not_found.ts delete mode 100644 services/nuldoc/nuldoc-src/generators/post.ts delete mode 100644 services/nuldoc/nuldoc-src/generators/post_list.ts delete mode 100644 services/nuldoc/nuldoc-src/generators/slide.ts delete mode 100644 services/nuldoc/nuldoc-src/generators/slide_list.ts delete mode 100644 services/nuldoc/nuldoc-src/generators/tag.ts delete mode 100644 services/nuldoc/nuldoc-src/generators/tag_list.ts delete mode 100644 services/nuldoc/nuldoc-src/generators/tagged_page.ts delete mode 100644 services/nuldoc/nuldoc-src/main.ts delete mode 100644 services/nuldoc/nuldoc-src/markdown/document.ts delete mode 100644 services/nuldoc/nuldoc-src/markdown/mdast2ndoc.ts delete mode 100644 services/nuldoc/nuldoc-src/markdown/parse.ts delete mode 100644 services/nuldoc/nuldoc-src/markdown/to_html.ts delete mode 100644 services/nuldoc/nuldoc-src/page.ts delete mode 100644 services/nuldoc/nuldoc-src/pages/AboutPage.ts delete mode 100644 services/nuldoc/nuldoc-src/pages/AtomPage.ts delete mode 100644 services/nuldoc/nuldoc-src/pages/HomePage.ts delete mode 100644 services/nuldoc/nuldoc-src/pages/NotFoundPage.ts delete mode 100644 services/nuldoc/nuldoc-src/pages/PostListPage.ts delete mode 100644 services/nuldoc/nuldoc-src/pages/PostPage.ts delete mode 100644 services/nuldoc/nuldoc-src/pages/SlideListPage.ts delete mode 100644 services/nuldoc/nuldoc-src/pages/SlidePage.ts delete mode 100644 services/nuldoc/nuldoc-src/pages/TagListPage.ts delete mode 100644 services/nuldoc/nuldoc-src/pages/TagPage.ts delete mode 100644 services/nuldoc/nuldoc-src/render.ts delete mode 100644 services/nuldoc/nuldoc-src/renderers/html.ts delete mode 100644 services/nuldoc/nuldoc-src/renderers/xml.ts delete mode 100644 services/nuldoc/nuldoc-src/revision.ts delete mode 100644 services/nuldoc/nuldoc-src/slide/parse.ts delete mode 100644 services/nuldoc/nuldoc-src/slide/slide.ts (limited to 'services') diff --git a/services/nuldoc/.rubocop.yml b/services/nuldoc/.rubocop.yml new file mode 100644 index 00000000..730d0009 --- /dev/null +++ b/services/nuldoc/.rubocop.yml @@ -0,0 +1,20 @@ +plugins: + - rubocop-performance + - rubocop-rake + +AllCops: + TargetRubyVersion: 3.4 + Enabled: true + NewCops: enable + +Metrics: + Enabled: false + +Naming/MethodParameterName: + MinNameLength: 1 + +Style/FrozenStringLiteralComment: + EnforcedStyle: never + +Style/Documentation: + Enabled: false diff --git a/services/nuldoc/Gemfile b/services/nuldoc/Gemfile new file mode 100644 index 00000000..52ca7a1c --- /dev/null +++ b/services/nuldoc/Gemfile @@ -0,0 +1,13 @@ +source 'https://rubygems.org' + +gem 'dry-cli' +gem 'rouge' +gem 'toml-rb' +gem 'webrick' + +group :development do + gem 'rake' + gem 'rubocop' + gem 'rubocop-performance' + gem 'rubocop-rake' +end diff --git a/services/nuldoc/Gemfile.lock b/services/nuldoc/Gemfile.lock new file mode 100644 index 00000000..6ed6b94d --- /dev/null +++ b/services/nuldoc/Gemfile.lock @@ -0,0 +1,65 @@ +GEM + remote: https://rubygems.org/ + specs: + ast (2.4.3) + citrus (3.0.2) + dry-cli (1.4.1) + json (2.18.0) + language_server-protocol (3.17.0.5) + lint_roller (1.1.0) + parallel (1.27.0) + parser (3.3.10.1) + ast (~> 2.4.1) + racc + prism (1.9.0) + racc (1.8.1) + rainbow (3.1.1) + rake (13.3.1) + regexp_parser (2.11.3) + rouge (4.7.0) + rubocop (1.84.0) + json (~> 2.3) + language_server-protocol (~> 3.17.0.2) + lint_roller (~> 1.1.0) + parallel (~> 1.10) + parser (>= 3.3.0.2) + rainbow (>= 2.2.2, < 4.0) + regexp_parser (>= 2.9.3, < 3.0) + rubocop-ast (>= 1.49.0, < 2.0) + ruby-progressbar (~> 1.7) + unicode-display_width (>= 2.4.0, < 4.0) + rubocop-ast (1.49.0) + parser (>= 3.3.7.2) + prism (~> 1.7) + rubocop-performance (1.26.1) + lint_roller (~> 1.1) + rubocop (>= 1.75.0, < 2.0) + rubocop-ast (>= 1.47.1, < 2.0) + rubocop-rake (0.7.1) + lint_roller (~> 1.1) + rubocop (>= 1.72.1) + ruby-progressbar (1.13.0) + toml-rb (4.1.0) + citrus (~> 3.0, > 3.0) + racc (~> 1.7) + unicode-display_width (3.2.0) + unicode-emoji (~> 4.1) + unicode-emoji (4.2.0) + webrick (1.9.2) + +PLATFORMS + ruby + x86_64-linux + +DEPENDENCIES + dry-cli + rake + rouge + rubocop + rubocop-performance + rubocop-rake + toml-rb + webrick + +BUNDLED WITH + 2.6.9 diff --git a/services/nuldoc/NOTE.md b/services/nuldoc/NOTE.md index 6345ff86..d632513f 100644 --- a/services/nuldoc/NOTE.md +++ b/services/nuldoc/NOTE.md @@ -5,19 +5,19 @@ Generate the site. ``` -$ ./nuldoc build +$ rake build ``` Create a new post. ``` -$ ./nuldoc new post +$ rake new post ``` Create a new slide. ``` -$ ./nuldoc new slide +$ rake new slide ``` Update PDF.js. diff --git a/services/nuldoc/Rakefile b/services/nuldoc/Rakefile new file mode 100644 index 00000000..811398f4 --- /dev/null +++ b/services/nuldoc/Rakefile @@ -0,0 +1,22 @@ +require 'rubocop/rake_task' +require_relative 'lib/nuldoc' + +RuboCop::RakeTask.new + +desc 'Build the site' +task :build do + config = Nuldoc::ConfigLoader.load_config(Nuldoc::ConfigLoader.default_config_path) + Nuldoc::Commands::Build.run(config) +end + +desc 'Start development server' +task :serve, [:site] do |_t, args| + config = Nuldoc::ConfigLoader.load_config(Nuldoc::ConfigLoader.default_config_path) + Nuldoc::Commands::Serve.run(config, site_name: args[:site], no_rebuild: false) +end + +desc 'Create new content' +task :new, [:type] do |_t, args| + config = Nuldoc::ConfigLoader.load_config(Nuldoc::ConfigLoader.default_config_path) + Nuldoc::Commands::New.run(config, type: args[:type], date: nil) +end diff --git a/services/nuldoc/content/posts/2021-10-02/cpp-you-can-use-keywords-in-attributes.md b/services/nuldoc/content/posts/2021-10-02/cpp-you-can-use-keywords-in-attributes.md index 70068754..fb8c8798 100644 --- a/services/nuldoc/content/posts/2021-10-02/cpp-you-can-use-keywords-in-attributes.md +++ b/services/nuldoc/content/posts/2021-10-02/cpp-you-can-use-keywords-in-attributes.md @@ -60,8 +60,7 @@ $ clang –std=c++17 hoge.cpp 別件で [cppreference.com の identifier のページ](https://en.cppreference.com/w/cpp/language/identifiers)を読んでいた時、次の文が目に止まった。 > * the identifiers that are keywords cannot be used for other purposes; -> -> * The only place they can be used as non-keywords is in an attribute-token. (e.g. [[private]] is a valid attribute) (since C++11) +> * The only place they can be used as non-keywords is in an attribute-token. (e.g. [[private]] is a valid attribute) (since C++11) キーワードでも属性として指定する場合は非キーワードとして使えるらしい。 実際にやってみる。 diff --git a/services/nuldoc/content/posts/2023-04-01/implementation-of-minimal-png-image-encoder.md b/services/nuldoc/content/posts/2023-04-01/implementation-of-minimal-png-image-encoder.md index 6f4fb3c8..2fd69590 100644 --- a/services/nuldoc/content/posts/2023-04-01/implementation-of-minimal-png-image-encoder.md +++ b/services/nuldoc/content/posts/2023-04-01/implementation-of-minimal-png-image-encoder.md @@ -189,19 +189,19 @@ 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 だけになる。コードは次のようになる。 diff --git a/services/nuldoc/deno.jsonc b/services/nuldoc/deno.jsonc deleted file mode 100644 index 3174e5c5..00000000 --- a/services/nuldoc/deno.jsonc +++ /dev/null @@ -1,34 +0,0 @@ -{ - "imports": { - "@std/assert": "jsr:@std/assert@^1.0.13", - "@std/cli": "jsr:@std/cli@^1.0.20", - "@std/fs": "jsr:@std/fs@^1.0.19", - "@std/http": "jsr:@std/http@^1.0.19", - "@std/path": "jsr:@std/path@^1.1.1", - "@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": { - "default": { - "read": ["."], - "write": ["."], - "net": ["127.0.0.1:8000"], - "env": [ - "VSCODE_TEXTMATE_DEBUG", // VSCODE_TEXTMATE_DEBUG is read by shiki. - ], - }, - } - "tasks": { - "check": "deno check nuldoc-src/main.ts && deno lint -- nuldoc-src/ && deno fmt --check -- nuldoc-src/", - "fmt": "deno fmt -- nuldoc-src", - }, -} diff --git a/services/nuldoc/deno.lock b/services/nuldoc/deno.lock deleted file mode 100644 index 225fcf2c..00000000 --- a/services/nuldoc/deno.lock +++ /dev/null @@ -1,1137 +0,0 @@ -{ - "version": "5", - "specifiers": { - "jsr:@std/assert@^1.0.13": "1.0.13", - "jsr:@std/cli@^1.0.20": "1.0.20", - "jsr:@std/collections@^1.1.1": "1.1.2", - "jsr:@std/encoding@^1.0.10": "1.0.10", - "jsr:@std/fmt@^1.0.8": "1.0.8", - "jsr:@std/fs@^1.0.19": "1.0.19", - "jsr:@std/html@^1.0.4": "1.0.4", - "jsr:@std/http@^1.0.19": "1.0.19", - "jsr:@std/internal@^1.0.6": "1.0.9", - "jsr:@std/internal@^1.0.9": "1.0.9", - "jsr:@std/media-types@^1.1.0": "1.1.0", - "jsr:@std/net@^1.0.4": "1.0.4", - "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:@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": { - "integrity": "ae0d31e41919b12c656c742b22522c32fb26ed0cba32975cb0de2a273cb68b29", - "dependencies": [ - "jsr:@std/internal@^1.0.6" - ] - }, - "@std/cli@1.0.13": { - "integrity": "5db2d95ab2dca3bca9fb6ad3c19908c314e93d6391c8b026725e4892d4615a69" - }, - "@std/cli@1.0.20": { - "integrity": "a8c384a2c98cec6ec6a2055c273a916e2772485eb784af0db004c5ab8ba52333" - }, - "@std/collections@1.1.2": { - "integrity": "f1685dd45c3ec27c39d0e8a642ccf810f391ec8a6cb5e7355926e6dacc64c43e" - }, - "@std/encoding@1.0.10": { - "integrity": "8783c6384a2d13abd5e9e87a7ae0520a30e9f56aeeaa3bdf910a3eaaf5c811a1" - }, - "@std/fmt@1.0.8": { - "integrity": "71e1fc498787e4434d213647a6e43e794af4fd393ef8f52062246e06f7e372b7" - }, - "@std/fs@1.0.19": { - "integrity": "051968c2b1eae4d2ea9f79a08a3845740ef6af10356aff43d3e2ef11ed09fb06", - "dependencies": [ - "jsr:@std/internal@^1.0.9", - "jsr:@std/path" - ] - }, - "@std/html@1.0.4": { - "integrity": "eff3497c08164e6ada49b7f81a28b5108087033823153d065e3f89467dd3d50e" - }, - "@std/http@1.0.19": { - "integrity": "52128c8d00a1f0b20019f8b72376e7ef5f3133375b6f805b5bc89b9de2ad4686", - "dependencies": [ - "jsr:@std/cli", - "jsr:@std/encoding", - "jsr:@std/fmt", - "jsr:@std/fs", - "jsr:@std/html", - "jsr:@std/media-types", - "jsr:@std/net", - "jsr:@std/path", - "jsr:@std/streams" - ] - }, - "@std/internal@1.0.9": { - "integrity": "bdfb97f83e4db7a13e8faab26fb1958d1b80cc64366501af78a0aee151696eb8" - }, - "@std/media-types@1.1.0": { - "integrity": "c9d093f0c05c3512932b330e3cc1fe1d627b301db33a4c2c2185c02471d6eaa4" - }, - "@std/net@1.0.4": { - "integrity": "2f403b455ebbccf83d8a027d29c5a9e3a2452fea39bb2da7f2c04af09c8bc852" - }, - "@std/path@1.1.1": { - "integrity": "fe00026bd3a7e6a27f73709b83c607798be40e20c81dde655ce34052fd82ec76", - "dependencies": [ - "jsr:@std/internal@^1.0.9" - ] - }, - "@std/streams@1.0.10": { - "integrity": "75c0b1431873cd0d8b3d679015220204d36d3c7420d93b60acfc379eb0dc30af" - }, - "@std/toml@1.0.8": { - "integrity": "eb8ae76b4cc1c6c13f2a91123675823adbec2380de75cd3748c628960d952164", - "dependencies": [ - "jsr:@std/collections" - ] - } - }, - "npm": { - "@shikijs/core@3.7.0": { - "integrity": "sha512-yilc0S9HvTPyahHpcum8eonYrQtmGTU0lbtwxhA6jHv4Bm1cAdlPFRCJX4AHebkCm75aKTjjRAW+DezqD1b/cg==", - "dependencies": [ - "@shikijs/types", - "@shikijs/vscode-textmate", - "@types/hast", - "hast-util-to-html" - ] - }, - "@shikijs/engine-javascript@3.7.0": { - "integrity": "sha512-0t17s03Cbv+ZcUvv+y33GtX75WBLQELgNdVghnsdhTgU3hVcWcMsoP6Lb0nDTl95ZJfbP1mVMO0p3byVh3uuzA==", - "dependencies": [ - "@shikijs/types", - "@shikijs/vscode-textmate", - "oniguruma-to-es" - ] - }, - "@shikijs/engine-oniguruma@3.7.0": { - "integrity": "sha512-5BxcD6LjVWsGu4xyaBC5bu8LdNgPCVBnAkWTtOCs/CZxcB22L8rcoWfv7Hh/3WooVjBZmFtyxhgvkQFedPGnFw==", - "dependencies": [ - "@shikijs/types", - "@shikijs/vscode-textmate" - ] - }, - "@shikijs/langs@3.7.0": { - "integrity": "sha512-1zYtdfXLr9xDKLTGy5kb7O0zDQsxXiIsw1iIBcNOO8Yi5/Y1qDbJ+0VsFoqTlzdmneO8Ij35g7QKF8kcLyznCQ==", - "dependencies": [ - "@shikijs/types" - ] - }, - "@shikijs/themes@3.7.0": { - "integrity": "sha512-VJx8497iZPy5zLiiCTSIaOChIcKQwR0FebwE9S3rcN0+J/GTWwQ1v/bqhTbpbY3zybPKeO8wdammqkpXc4NVjQ==", - "dependencies": [ - "@shikijs/types" - ] - }, - "@shikijs/types@3.7.0": { - "integrity": "sha512-MGaLeaRlSWpnP0XSAum3kP3a8vtcTsITqoEPYdt3lQG3YCdQH4DnEhodkYcNMcU0uW0RffhoD1O3e0vG5eSBBg==", - "dependencies": [ - "@shikijs/vscode-textmate", - "@types/hast" - ] - }, - "@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@3.0.3" - ] - }, - "@types/mdast@4.0.4": { - "integrity": "sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==", - "dependencies": [ - "@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==" - }, - "character-entities-html4@2.1.0": { - "integrity": "sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA==" - }, - "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==" - }, - "devlop@1.1.0": { - "integrity": "sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==", - "dependencies": [ - "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@3.0.3", - "ccount", - "comma-separated-tokens", - "hast-util-whitespace", - "html-void-elements", - "mdast-util-to-hast", - "property-information", - "space-separated-tokens", - "stringify-entities", - "zwitch" - ] - }, - "hast-util-whitespace@3.0.0": { - "integrity": "sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw==", - "dependencies": [ - "@types/hast" - ] - }, - "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": [ - "@types/hast", - "@types/mdast", - "@ungap/structured-clone", - "devlop", - "micromark-util-sanitize-uri", - "trim-lines", - "unist-util-position", - "unist-util-visit", - "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": [ - "micromark-util-symbol", - "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": [ - "micromark-util-character", - "micromark-util-encode", - "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==" - }, - "oniguruma-to-es@4.3.3": { - "integrity": "sha512-rPiZhzC3wXwE59YQMRDodUwwT9FZ9nNBwQQfsd1wfdtlKEyCdRV0avrTcSZ5xlIvGRVPd/cx6ZN45ECmS39xvg==", - "dependencies": [ - "oniguruma-parser", - "regex", - "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==" - }, - "regex-recursion@6.0.2": { - "integrity": "sha512-0YCaSCq2VRIebiaUviZNs0cBz1kg5kVS2UKUfNIx8YVs1cN3AV7NTctO5FOKBA+UT2BPJIWZauYHPqJODG50cg==", - "dependencies": [ - "regex-utilities" - ] - }, - "regex-utilities@2.3.0": { - "integrity": "sha512-8VhliFJAWRaUiVvREIiW2NXXTmHs4vMNnSzuJVhscgmGav3g9VDxLrQndI3dZZVVdp0ZO/5v0xmX516/7M9cng==" - }, - "regex@6.0.1": { - "integrity": "sha512-uorlqlzAKjKQZ5P+kTJr3eeJGSVroLKoHmquUj4zHWuR+hEyNqlXsSKlYYF5F4NI6nl7tWCs0apKJ0lmfsXAPA==", - "dependencies": [ - "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": [ - "@shikijs/core", - "@shikijs/engine-javascript", - "@shikijs/engine-oniguruma", - "@shikijs/langs", - "@shikijs/themes", - "@shikijs/types", - "@shikijs/vscode-textmate", - "@types/hast" - ] - }, - "space-separated-tokens@2.0.2": { - "integrity": "sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==" - }, - "stringify-entities@4.0.4": { - "integrity": "sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg==", - "dependencies": [ - "character-entities-html4", - "character-entities-legacy" - ] - }, - "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@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@3.0.3" - ] - }, - "unist-util-stringify-position@4.0.0": { - "integrity": "sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==", - "dependencies": [ - "@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@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@3.0.3", - "unist-util-is", - "unist-util-visit-parents" - ] - }, - "vfile-message@4.0.2": { - "integrity": "sha512-jRDZ1IMLttGj41KcZvlrYAaI3CfqpLpfpf+Mfig13viT6NKvRzWZ+lXz0Y5D60w6uJIBAOGq9mSHf0gktF0duw==", - "dependencies": [ - "@types/unist@3.0.3", - "unist-util-stringify-position" - ] - }, - "vfile@6.0.3": { - "integrity": "sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==", - "dependencies": [ - "@types/unist@3.0.3", - "vfile-message" - ] - }, - "zwitch@2.0.4": { - "integrity": "sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==" - } - }, - "remote": { - "https://deno.land/std@0.224.0/assert/_constants.ts": "a271e8ef5a573f1df8e822a6eb9d09df064ad66a4390f21b3e31f820a38e0975", - "https://deno.land/std@0.224.0/assert/assert.ts": "09d30564c09de846855b7b071e62b5974b001bb72a4b797958fe0660e7849834", - "https://deno.land/std@0.224.0/assert/assert_almost_equals.ts": "9e416114322012c9a21fa68e187637ce2d7df25bcbdbfd957cd639e65d3cf293", - "https://deno.land/std@0.224.0/assert/assert_array_includes.ts": "14c5094471bc8e4a7895fc6aa5a184300d8a1879606574cb1cd715ef36a4a3c7", - "https://deno.land/std@0.224.0/assert/assert_equals.ts": "3bbca947d85b9d374a108687b1a8ba3785a7850436b5a8930d81f34a32cb8c74", - "https://deno.land/std@0.224.0/assert/assert_exists.ts": "43420cf7f956748ae6ed1230646567b3593cb7a36c5a5327269279c870c5ddfd", - "https://deno.land/std@0.224.0/assert/assert_false.ts": "3e9be8e33275db00d952e9acb0cd29481a44fa0a4af6d37239ff58d79e8edeff", - "https://deno.land/std@0.224.0/assert/assert_greater.ts": "5e57b201fd51b64ced36c828e3dfd773412c1a6120c1a5a99066c9b261974e46", - "https://deno.land/std@0.224.0/assert/assert_greater_or_equal.ts": "9870030f997a08361b6f63400273c2fb1856f5db86c0c3852aab2a002e425c5b", - "https://deno.land/std@0.224.0/assert/assert_instance_of.ts": "e22343c1fdcacfaea8f37784ad782683ec1cf599ae9b1b618954e9c22f376f2c", - "https://deno.land/std@0.224.0/assert/assert_is_error.ts": "f856b3bc978a7aa6a601f3fec6603491ab6255118afa6baa84b04426dd3cc491", - "https://deno.land/std@0.224.0/assert/assert_less.ts": "60b61e13a1982865a72726a5fa86c24fad7eb27c3c08b13883fb68882b307f68", - "https://deno.land/std@0.224.0/assert/assert_less_or_equal.ts": "d2c84e17faba4afe085e6c9123a63395accf4f9e00150db899c46e67420e0ec3", - "https://deno.land/std@0.224.0/assert/assert_match.ts": "ace1710dd3b2811c391946954234b5da910c5665aed817943d086d4d4871a8b7", - "https://deno.land/std@0.224.0/assert/assert_not_equals.ts": "78d45dd46133d76ce624b2c6c09392f6110f0df9b73f911d20208a68dee2ef29", - "https://deno.land/std@0.224.0/assert/assert_not_instance_of.ts": "3434a669b4d20cdcc5359779301a0588f941ffdc2ad68803c31eabdb4890cf7a", - "https://deno.land/std@0.224.0/assert/assert_not_match.ts": "df30417240aa2d35b1ea44df7e541991348a063d9ee823430e0b58079a72242a", - "https://deno.land/std@0.224.0/assert/assert_not_strict_equals.ts": "37f73880bd672709373d6dc2c5f148691119bed161f3020fff3548a0496f71b8", - "https://deno.land/std@0.224.0/assert/assert_object_match.ts": "411450fd194fdaabc0089ae68f916b545a49d7b7e6d0026e84a54c9e7eed2693", - "https://deno.land/std@0.224.0/assert/assert_rejects.ts": "4bee1d6d565a5b623146a14668da8f9eb1f026a4f338bbf92b37e43e0aa53c31", - "https://deno.land/std@0.224.0/assert/assert_strict_equals.ts": "b4f45f0fd2e54d9029171876bd0b42dd9ed0efd8f853ab92a3f50127acfa54f5", - "https://deno.land/std@0.224.0/assert/assert_string_includes.ts": "496b9ecad84deab72c8718735373feb6cdaa071eb91a98206f6f3cb4285e71b8", - "https://deno.land/std@0.224.0/assert/assert_throws.ts": "c6508b2879d465898dab2798009299867e67c570d7d34c90a2d235e4553906eb", - "https://deno.land/std@0.224.0/assert/assertion_error.ts": "ba8752bd27ebc51f723702fac2f54d3e94447598f54264a6653d6413738a8917", - "https://deno.land/std@0.224.0/assert/equal.ts": "bddf07bb5fc718e10bb72d5dc2c36c1ce5a8bdd3b647069b6319e07af181ac47", - "https://deno.land/std@0.224.0/assert/fail.ts": "0eba674ffb47dff083f02ced76d5130460bff1a9a68c6514ebe0cdea4abadb68", - "https://deno.land/std@0.224.0/assert/mod.ts": "48b8cb8a619ea0b7958ad7ee9376500fe902284bb36f0e32c598c3dc34cbd6f3", - "https://deno.land/std@0.224.0/assert/unimplemented.ts": "8c55a5793e9147b4f1ef68cd66496b7d5ba7a9e7ca30c6da070c1a58da723d73", - "https://deno.land/std@0.224.0/assert/unreachable.ts": "5ae3dbf63ef988615b93eb08d395dda771c96546565f9e521ed86f6510c29e19", - "https://deno.land/std@0.224.0/async/delay.ts": "f90dd685b97c2f142b8069082993e437b1602b8e2561134827eeb7c12b95c499", - "https://deno.land/std@0.224.0/cli/parse_args.ts": "5250832fb7c544d9111e8a41ad272c016f5a53f975ef84d5a9fe5fcb70566ece", - "https://deno.land/std@0.224.0/collections/_utils.ts": "b2ec8ada31b5a72ebb1d99774b849b4c09fe4b3a38d07794bd010bd218a16e0b", - "https://deno.land/std@0.224.0/collections/deep_merge.ts": "04f8d2a6cfa15c7580e788689bcb5e162512b9ccb18bab1241824b432a78551e", - "https://deno.land/std@0.224.0/encoding/_util.ts": "beacef316c1255da9bc8e95afb1fa56ed69baef919c88dc06ae6cb7a6103d376", - "https://deno.land/std@0.224.0/encoding/base64.ts": "dd59695391584c8ffc5a296ba82bcdba6dd8a84d41a6a539fbee8e5075286eaf", - "https://deno.land/std@0.224.0/encoding/hex.ts": "6270f25e5d85f99fcf315278670ba012b04b7c94b67715b53f30d03249687c07", - "https://deno.land/std@0.224.0/flags/mod.ts": "88553267f34519c8982212185339efdb2d2e62c159ec558f47eb50c8952a6be3", - "https://deno.land/std@0.224.0/fmt/bytes.ts": "7b294a4b9cf0297efa55acb55d50610f3e116a0ac772d1df0ae00f0b833ccd4a", - "https://deno.land/std@0.224.0/fmt/colors.ts": "508563c0659dd7198ba4bbf87e97f654af3c34eb56ba790260f252ad8012e1c5", - "https://deno.land/std@0.224.0/fs/_create_walk_entry.ts": "5d9d2aaec05bcf09a06748b1684224d33eba7a4de24cf4cf5599991ca6b5b412", - "https://deno.land/std@0.224.0/fs/_get_file_info_type.ts": "da7bec18a7661dba360a1db475b826b18977582ce6fc9b25f3d4ee0403fe8cbd", - "https://deno.land/std@0.224.0/fs/_is_same_path.ts": "709c95868345fea051c58b9e96af95cff94e6ae98dfcff2b66dee0c212c4221f", - "https://deno.land/std@0.224.0/fs/_is_subdir.ts": "c68b309d46cc8568ed83c000f608a61bbdba0943b7524e7a30f9e450cf67eecd", - "https://deno.land/std@0.224.0/fs/_to_path_string.ts": "29bfc9c6c112254961d75cbf6ba814d6de5349767818eb93090cecfa9665591e", - "https://deno.land/std@0.224.0/fs/copy.ts": "7ab12a16adb65d155d4943c88081ca16ce3b0b5acada64c1ce93800653678039", - "https://deno.land/std@0.224.0/fs/empty_dir.ts": "e400e96e1d2c8c558a5a1712063bd43939e00619c1d1cc29959babc6f1639418", - "https://deno.land/std@0.224.0/fs/ensure_dir.ts": "51a6279016c65d2985f8803c848e2888e206d1b510686a509fa7cc34ce59d29f", - "https://deno.land/std@0.224.0/fs/ensure_file.ts": "67608cf550529f3d4aa1f8b6b36bf817bdc40b14487bf8f60e61cbf68f507cf3", - "https://deno.land/std@0.224.0/fs/ensure_link.ts": "5c98503ebfa9cc05e2f2efaa30e91e60b4dd5b43ebbda82f435c0a5c6e3ffa01", - "https://deno.land/std@0.224.0/fs/ensure_symlink.ts": "cafe904cebacb9a761977d6dbf5e3af938be946a723bb394080b9a52714fafe4", - "https://deno.land/std@0.224.0/fs/eol.ts": "18c4ac009d0318504c285879eb7f47942643f13619e0ff070a0edc59353306bd", - "https://deno.land/std@0.224.0/fs/exists.ts": "3d38cb7dcbca3cf313be343a7b8af18a87bddb4b5ca1bd2314be12d06533b50f", - "https://deno.land/std@0.224.0/fs/expand_glob.ts": "2e428d90acc6676b2aa7b5c78ef48f30641b13f1fe658e7976c9064fb4b05309", - "https://deno.land/std@0.224.0/fs/mod.ts": "c25e6802cbf27f3050f60b26b00c2d8dba1cb7fcdafe34c66006a7473b7b34d4", - "https://deno.land/std@0.224.0/fs/move.ts": "ca205d848908d7f217353bc5c623627b1333490b8b5d3ef4cab600a700c9bd8f", - "https://deno.land/std@0.224.0/fs/walk.ts": "cddf87d2705c0163bff5d7767291f05b0f46ba10b8b28f227c3849cace08d303", - "https://deno.land/std@0.224.0/http/_negotiation/common.ts": "051a9f6edd1ed69507df89bbc16fc1b13b7654b9b8fd38072ec33ae4c185fc13", - "https://deno.land/std@0.224.0/http/_negotiation/encoding.ts": "fdedea1145c1dea3b3de2d5217e8eb927e764083eebc8c52d09a1ed3d9bb7a93", - "https://deno.land/std@0.224.0/http/_negotiation/language.ts": "300a5c586f844c97f246ab72c948e9fde9a8f45e92ec08e1cc9a9df80259e2a3", - "https://deno.land/std@0.224.0/http/_negotiation/media_type.ts": "87a1ecb22c1b268d0fa23d798e1ea238343505268cb1ff82bd038638de29ce31", - "https://deno.land/std@0.224.0/http/cookie.ts": "a377fa60175ba5f61dd4b8a70b34f2bbfbc70782dfd5faf36d314c42e4306006", - "https://deno.land/std@0.224.0/http/etag.ts": "9ca56531be682f202e4239971931060b688ee5c362688e239eeaca39db9e72cb", - "https://deno.land/std@0.224.0/http/file_server.ts": "2a5392195b8e7713288f274d071711b705bb5b3220294d76cce495d456c61a93", - "https://deno.land/std@0.224.0/http/mod.ts": "b0e06293a8a2f71b041add53d4674d4997bd2b83a4760bddad1b5e552130bcc8", - "https://deno.land/std@0.224.0/http/negotiation.ts": "d06ef2958ca712a7dbe4538eed6a46abfa2b87f8e150b7c89d83a6055dabd7cc", - "https://deno.land/std@0.224.0/http/server.ts": "f9313804bf6467a1704f45f76cb6cd0a3396a3b31c316035e6a4c2035d1ea514", - "https://deno.land/std@0.224.0/http/server_sent_event_stream.ts": "d9c20b46f986d78f60c38dbd91e95c71d73b45f29739a8ef4216dfa5f2e71eb3", - "https://deno.land/std@0.224.0/http/status.ts": "ed61b4882af2514a81aefd3245e8df4c47b9a8e54929a903577643d2d1ebf514", - "https://deno.land/std@0.224.0/http/unstable_signed_cookie.ts": "2a5bfbdf6b4aa35ef1464300fe1ba4eb89eb79f535c9cb28401d55fbb7038479", - "https://deno.land/std@0.224.0/http/user_agent.ts": "05f8849c7e27b898793bfc70204f0c72b6be9bee7accbe98e18a1c413bd4ace3", - "https://deno.land/std@0.224.0/internal/diff.ts": "6234a4b493ebe65dc67a18a0eb97ef683626a1166a1906232ce186ae9f65f4e6", - "https://deno.land/std@0.224.0/internal/format.ts": "0a98ee226fd3d43450245b1844b47003419d34d210fa989900861c79820d21c2", - "https://deno.land/std@0.224.0/internal/mod.ts": "534125398c8e7426183e12dc255bb635d94e06d0f93c60a297723abe69d3b22e", - "https://deno.land/std@0.224.0/media_types/_db.ts": "19563a2491cd81b53b9c1c6ffd1a9145c355042d4a854c52f6e1424f73ff3923", - "https://deno.land/std@0.224.0/media_types/_util.ts": "e0b8da0c7d8ad2015cf27ac16ddf0809ac984b2f3ec79f7fa4206659d4f10deb", - "https://deno.land/std@0.224.0/media_types/content_type.ts": "ed3f2e1f243b418ad3f441edc95fd92efbadb0f9bde36219c7564c67f9639513", - "https://deno.land/std@0.224.0/media_types/format_media_type.ts": "ffef4718afa2489530cb94021bb865a466eb02037609f7e82899c017959d288a", - "https://deno.land/std@0.224.0/media_types/get_charset.ts": "277ebfceb205bd34e616fe6764ef03fb277b77f040706272bea8680806ae3f11", - "https://deno.land/std@0.224.0/media_types/parse_media_type.ts": "487f000a38c230ccbac25420a50f600862e06796d0eee19d19631b9e84ee9654", - "https://deno.land/std@0.224.0/media_types/type_by_extension.ts": "bf4e3f5d6b58b624d5daa01cbb8b1e86d9939940a77e7c26e796a075b60ec73b", - "https://deno.land/std@0.224.0/media_types/vendor/mime-db.v1.52.0.ts": "0218d2c7d900e8cd6fa4a866e0c387712af4af9a1bae55d6b2546c73d273a1e6", - "https://deno.land/std@0.224.0/path/_common/assert_path.ts": "dbdd757a465b690b2cc72fc5fb7698c51507dec6bfafce4ca500c46b76ff7bd8", - "https://deno.land/std@0.224.0/path/_common/basename.ts": "569744855bc8445f3a56087fd2aed56bdad39da971a8d92b138c9913aecc5fa2", - "https://deno.land/std@0.224.0/path/_common/common.ts": "ef73c2860694775fe8ffcbcdd387f9f97c7a656febf0daa8c73b56f4d8a7bd4c", - "https://deno.land/std@0.224.0/path/_common/constants.ts": "dc5f8057159f4b48cd304eb3027e42f1148cf4df1fb4240774d3492b5d12ac0c", - "https://deno.land/std@0.224.0/path/_common/dirname.ts": "684df4aa71a04bbcc346c692c8485594fc8a90b9408dfbc26ff32cf3e0c98cc8", - "https://deno.land/std@0.224.0/path/_common/format.ts": "92500e91ea5de21c97f5fe91e178bae62af524b72d5fcd246d6d60ae4bcada8b", - "https://deno.land/std@0.224.0/path/_common/from_file_url.ts": "d672bdeebc11bf80e99bf266f886c70963107bdd31134c4e249eef51133ceccf", - "https://deno.land/std@0.224.0/path/_common/glob_to_reg_exp.ts": "6cac16d5c2dc23af7d66348a7ce430e5de4e70b0eede074bdbcf4903f4374d8d", - "https://deno.land/std@0.224.0/path/_common/normalize.ts": "684df4aa71a04bbcc346c692c8485594fc8a90b9408dfbc26ff32cf3e0c98cc8", - "https://deno.land/std@0.224.0/path/_common/normalize_string.ts": "33edef773c2a8e242761f731adeb2bd6d683e9c69e4e3d0092985bede74f4ac3", - "https://deno.land/std@0.224.0/path/_common/relative.ts": "faa2753d9b32320ed4ada0733261e3357c186e5705678d9dd08b97527deae607", - "https://deno.land/std@0.224.0/path/_common/strip_trailing_separators.ts": "7024a93447efcdcfeaa9339a98fa63ef9d53de363f1fbe9858970f1bba02655a", - "https://deno.land/std@0.224.0/path/_common/to_file_url.ts": "7f76adbc83ece1bba173e6e98a27c647712cab773d3f8cbe0398b74afc817883", - "https://deno.land/std@0.224.0/path/_interface.ts": "8dfeb930ca4a772c458a8c7bbe1e33216fe91c253411338ad80c5b6fa93ddba0", - "https://deno.land/std@0.224.0/path/_os.ts": "8fb9b90fb6b753bd8c77cfd8a33c2ff6c5f5bc185f50de8ca4ac6a05710b2c15", - "https://deno.land/std@0.224.0/path/basename.ts": "7ee495c2d1ee516ffff48fb9a93267ba928b5a3486b550be73071bc14f8cc63e", - "https://deno.land/std@0.224.0/path/common.ts": "03e52e22882402c986fe97ca3b5bb4263c2aa811c515ce84584b23bac4cc2643", - "https://deno.land/std@0.224.0/path/constants.ts": "0c206169ca104938ede9da48ac952de288f23343304a1c3cb6ec7625e7325f36", - "https://deno.land/std@0.224.0/path/dirname.ts": "85bd955bf31d62c9aafdd7ff561c4b5fb587d11a9a5a45e2b01aedffa4238a7c", - "https://deno.land/std@0.224.0/path/extname.ts": "593303db8ae8c865cbd9ceec6e55d4b9ac5410c1e276bfd3131916591b954441", - "https://deno.land/std@0.224.0/path/format.ts": "6ce1779b0980296cf2bc20d66436b12792102b831fd281ab9eb08fa8a3e6f6ac", - "https://deno.land/std@0.224.0/path/from_file_url.ts": "911833ae4fd10a1c84f6271f36151ab785955849117dc48c6e43b929504ee069", - "https://deno.land/std@0.224.0/path/glob_to_regexp.ts": "7f30f0a21439cadfdae1be1bf370880b415e676097fda584a63ce319053b5972", - "https://deno.land/std@0.224.0/path/is_absolute.ts": "4791afc8bfd0c87f0526eaa616b0d16e7b3ab6a65b62942e50eac68de4ef67d7", - "https://deno.land/std@0.224.0/path/is_glob.ts": "a65f6195d3058c3050ab905705891b412ff942a292bcbaa1a807a74439a14141", - "https://deno.land/std@0.224.0/path/join.ts": "ae2ec5ca44c7e84a235fd532e4a0116bfb1f2368b394db1c4fb75e3c0f26a33a", - "https://deno.land/std@0.224.0/path/join_globs.ts": "5b3bf248b93247194f94fa6947b612ab9d3abd571ca8386cf7789038545e54a0", - "https://deno.land/std@0.224.0/path/mod.ts": "f6bd79cb08be0e604201bc9de41ac9248582699d1b2ee0ab6bc9190d472cf9cd", - "https://deno.land/std@0.224.0/path/normalize.ts": "4155743ccceeed319b350c1e62e931600272fad8ad00c417b91df093867a8352", - "https://deno.land/std@0.224.0/path/normalize_glob.ts": "cc89a77a7d3b1d01053b9dcd59462b75482b11e9068ae6c754b5cf5d794b374f", - "https://deno.land/std@0.224.0/path/parse.ts": "77ad91dcb235a66c6f504df83087ce2a5471e67d79c402014f6e847389108d5a", - "https://deno.land/std@0.224.0/path/posix/_util.ts": "1e3937da30f080bfc99fe45d7ed23c47dd8585c5e473b2d771380d3a6937cf9d", - "https://deno.land/std@0.224.0/path/posix/basename.ts": "d2fa5fbbb1c5a3ab8b9326458a8d4ceac77580961b3739cd5bfd1d3541a3e5f0", - "https://deno.land/std@0.224.0/path/posix/common.ts": "26f60ccc8b2cac3e1613000c23ac5a7d392715d479e5be413473a37903a2b5d4", - "https://deno.land/std@0.224.0/path/posix/constants.ts": "93481efb98cdffa4c719c22a0182b994e5a6aed3047e1962f6c2c75b7592bef1", - "https://deno.land/std@0.224.0/path/posix/dirname.ts": "76cd348ffe92345711409f88d4d8561d8645353ac215c8e9c80140069bf42f00", - "https://deno.land/std@0.224.0/path/posix/extname.ts": "e398c1d9d1908d3756a7ed94199fcd169e79466dd88feffd2f47ce0abf9d61d2", - "https://deno.land/std@0.224.0/path/posix/format.ts": "185e9ee2091a42dd39e2a3b8e4925370ee8407572cee1ae52838aed96310c5c1", - "https://deno.land/std@0.224.0/path/posix/from_file_url.ts": "951aee3a2c46fd0ed488899d024c6352b59154c70552e90885ed0c2ab699bc40", - "https://deno.land/std@0.224.0/path/posix/glob_to_regexp.ts": "76f012fcdb22c04b633f536c0b9644d100861bea36e9da56a94b9c589a742e8f", - "https://deno.land/std@0.224.0/path/posix/is_absolute.ts": "cebe561ad0ae294f0ce0365a1879dcfca8abd872821519b4fcc8d8967f888ede", - "https://deno.land/std@0.224.0/path/posix/is_glob.ts": "8a8b08c08bf731acf2c1232218f1f45a11131bc01de81e5f803450a5914434b9", - "https://deno.land/std@0.224.0/path/posix/join.ts": "7fc2cb3716aa1b863e990baf30b101d768db479e70b7313b4866a088db016f63", - "https://deno.land/std@0.224.0/path/posix/join_globs.ts": "a9475b44645feddceb484ee0498e456f4add112e181cb94042cdc6d47d1cdd25", - "https://deno.land/std@0.224.0/path/posix/mod.ts": "2301fc1c54a28b349e20656f68a85f75befa0ee9b6cd75bfac3da5aca9c3f604", - "https://deno.land/std@0.224.0/path/posix/normalize.ts": "baeb49816a8299f90a0237d214cef46f00ba3e95c0d2ceb74205a6a584b58a91", - "https://deno.land/std@0.224.0/path/posix/normalize_glob.ts": "9c87a829b6c0f445d03b3ecadc14492e2864c3ebb966f4cea41e98326e4435c6", - "https://deno.land/std@0.224.0/path/posix/parse.ts": "09dfad0cae530f93627202f28c1befa78ea6e751f92f478ca2cc3b56be2cbb6a", - "https://deno.land/std@0.224.0/path/posix/relative.ts": "3907d6eda41f0ff723d336125a1ad4349112cd4d48f693859980314d5b9da31c", - "https://deno.land/std@0.224.0/path/posix/resolve.ts": "08b699cfeee10cb6857ccab38fa4b2ec703b0ea33e8e69964f29d02a2d5257cf", - "https://deno.land/std@0.224.0/path/posix/to_file_url.ts": "7aa752ba66a35049e0e4a4be5a0a31ac6b645257d2e031142abb1854de250aaf", - "https://deno.land/std@0.224.0/path/posix/to_namespaced_path.ts": "28b216b3c76f892a4dca9734ff1cc0045d135532bfd9c435ae4858bfa5a2ebf0", - "https://deno.land/std@0.224.0/path/relative.ts": "ab739d727180ed8727e34ed71d976912461d98e2b76de3d3de834c1066667add", - "https://deno.land/std@0.224.0/path/resolve.ts": "a6f977bdb4272e79d8d0ed4333e3d71367cc3926acf15ac271f1d059c8494d8d", - "https://deno.land/std@0.224.0/path/to_file_url.ts": "88f049b769bce411e2d2db5bd9e6fd9a185a5fbd6b9f5ad8f52bef517c4ece1b", - "https://deno.land/std@0.224.0/path/to_namespaced_path.ts": "b706a4103b104cfadc09600a5f838c2ba94dbcdb642344557122dda444526e40", - "https://deno.land/std@0.224.0/path/windows/_util.ts": "d5f47363e5293fced22c984550d5e70e98e266cc3f31769e1710511803d04808", - "https://deno.land/std@0.224.0/path/windows/basename.ts": "6bbc57bac9df2cec43288c8c5334919418d784243a00bc10de67d392ab36d660", - "https://deno.land/std@0.224.0/path/windows/common.ts": "26f60ccc8b2cac3e1613000c23ac5a7d392715d479e5be413473a37903a2b5d4", - "https://deno.land/std@0.224.0/path/windows/constants.ts": "5afaac0a1f67b68b0a380a4ef391bf59feb55856aa8c60dfc01bd3b6abb813f5", - "https://deno.land/std@0.224.0/path/windows/dirname.ts": "33e421be5a5558a1346a48e74c330b8e560be7424ed7684ea03c12c21b627bc9", - "https://deno.land/std@0.224.0/path/windows/extname.ts": "165a61b00d781257fda1e9606a48c78b06815385e7d703232548dbfc95346bef", - "https://deno.land/std@0.224.0/path/windows/format.ts": "bbb5ecf379305b472b1082cd2fdc010e44a0020030414974d6029be9ad52aeb6", - "https://deno.land/std@0.224.0/path/windows/from_file_url.ts": "ced2d587b6dff18f963f269d745c4a599cf82b0c4007356bd957cb4cb52efc01", - "https://deno.land/std@0.224.0/path/windows/glob_to_regexp.ts": "e45f1f89bf3fc36f94ab7b3b9d0026729829fabc486c77f414caebef3b7304f8", - "https://deno.land/std@0.224.0/path/windows/is_absolute.ts": "4a8f6853f8598cf91a835f41abed42112cebab09478b072e4beb00ec81f8ca8a", - "https://deno.land/std@0.224.0/path/windows/is_glob.ts": "8a8b08c08bf731acf2c1232218f1f45a11131bc01de81e5f803450a5914434b9", - "https://deno.land/std@0.224.0/path/windows/join.ts": "8d03530ab89195185103b7da9dfc6327af13eabdcd44c7c63e42e27808f50ecf", - "https://deno.land/std@0.224.0/path/windows/join_globs.ts": "a9475b44645feddceb484ee0498e456f4add112e181cb94042cdc6d47d1cdd25", - "https://deno.land/std@0.224.0/path/windows/mod.ts": "2301fc1c54a28b349e20656f68a85f75befa0ee9b6cd75bfac3da5aca9c3f604", - "https://deno.land/std@0.224.0/path/windows/normalize.ts": "78126170ab917f0ca355a9af9e65ad6bfa5be14d574c5fb09bb1920f52577780", - "https://deno.land/std@0.224.0/path/windows/normalize_glob.ts": "9c87a829b6c0f445d03b3ecadc14492e2864c3ebb966f4cea41e98326e4435c6", - "https://deno.land/std@0.224.0/path/windows/parse.ts": "08804327b0484d18ab4d6781742bf374976de662f8642e62a67e93346e759707", - "https://deno.land/std@0.224.0/path/windows/relative.ts": "3e1abc7977ee6cc0db2730d1f9cb38be87b0ce4806759d271a70e4997fc638d7", - "https://deno.land/std@0.224.0/path/windows/resolve.ts": "8dae1dadfed9d46ff46cc337c9525c0c7d959fb400a6308f34595c45bdca1972", - "https://deno.land/std@0.224.0/path/windows/to_file_url.ts": "40e560ee4854fe5a3d4d12976cef2f4e8914125c81b11f1108e127934ced502e", - "https://deno.land/std@0.224.0/path/windows/to_namespaced_path.ts": "4ffa4fb6fae321448d5fe810b3ca741d84df4d7897e61ee29be961a6aac89a4c", - "https://deno.land/std@0.224.0/streams/byte_slice_stream.ts": "5bbdcadb118390affa9b3d0a0f73ef8e83754f59bb89df349add669dd9369713", - "https://deno.land/std@0.224.0/testing/asserts.ts": "d0cdbabadc49cc4247a50732ee0df1403fdcd0f95360294ad448ae8c240f3f5c", - "https://deno.land/std@0.224.0/toml/_parser.ts": "187560eb4465977808b18c68299e1f5a6e4631c0a181d868c8f24722cf9146d1", - "https://deno.land/std@0.224.0/toml/mod.ts": "a457ea7877a6d5e7f3d6985c43da4d2ecd7461038d5c4c7a0089737e90a7ee90", - "https://deno.land/std@0.224.0/toml/parse.ts": "2f0729a8f62c7e508af8dfada0386a4bc2c0d664ef4d26090df03cf495dcb25a", - "https://deno.land/std@0.224.0/toml/stringify.ts": "8b9ba3c1bf8fa7d58d7b62ad62b3174dbbc51050d5cc302aa8e2834089c00d73", - "https://deno.land/std@0.224.0/version.ts": "f6a28c9704d82d1c095988777e30e6172eb674a6570974a0d27a653be769bbbe", - "https://deno.land/x/checksum@1.4.0/hash.ts": "89ebbf7c57576e20462badc05bfb4a7ac01fe7668b6908cd547c79067c4221b9", - "https://deno.land/x/checksum@1.4.0/md5.ts": "1bb0889eaec838d5c1f219e0533277fe5ea10806d022920b6f0f2f8c398fec77", - "https://deno.land/x/checksum@1.4.0/mod.ts": "df8282cc1ecfd0f148535337c8bec077a1630e96a2012f2d19b2ca380f2c88c2", - "https://deno.land/x/checksum@1.4.0/sha1.ts": "a608a6493694b7d734acfc4eee83e09bd9deaa4909cdac07f3eed3db427f97ae", - "https://deno.land/x/zod@v3.23.8/ZodError.ts": "528da200fbe995157b9ae91498b103c4ef482217a5c086249507ac850bd78f52", - "https://deno.land/x/zod@v3.23.8/errors.ts": "5285922d2be9700cc0c70c95e4858952b07ae193aa0224be3cbd5cd5567eabef", - "https://deno.land/x/zod@v3.23.8/external.ts": "a6cfbd61e9e097d5f42f8a7ed6f92f93f51ff927d29c9fbaec04f03cbce130fe", - "https://deno.land/x/zod@v3.23.8/helpers/enumUtil.ts": "54efc393cc9860e687d8b81ff52e980def00fa67377ad0bf8b3104f8a5bf698c", - "https://deno.land/x/zod@v3.23.8/helpers/errorUtil.ts": "7a77328240be7b847af6de9189963bd9f79cab32bbc61502a9db4fe6683e2ea7", - "https://deno.land/x/zod@v3.23.8/helpers/parseUtil.ts": "c14814d167cc286972b6e094df88d7d982572a08424b7cd50f862036b6fcaa77", - "https://deno.land/x/zod@v3.23.8/helpers/partialUtil.ts": "998c2fe79795257d4d1cf10361e74492f3b7d852f61057c7c08ac0a46488b7e7", - "https://deno.land/x/zod@v3.23.8/helpers/typeAliases.ts": "0fda31a063c6736fc3cf9090dd94865c811dfff4f3cb8707b932bf937c6f2c3e", - "https://deno.land/x/zod@v3.23.8/helpers/util.ts": "30c273131661ca5dc973f2cfb196fa23caf3a43e224cdde7a683b72e101a31fc", - "https://deno.land/x/zod@v3.23.8/index.ts": "d27aabd973613985574bc31f39e45cb5d856aa122ef094a9f38a463b8ef1a268", - "https://deno.land/x/zod@v3.23.8/locales/en.ts": "a7a25cd23563ccb5e0eed214d9b31846305ddbcdb9c5c8f508b108943366ab4c", - "https://deno.land/x/zod@v3.23.8/mod.ts": "ec6e2b1255c1a350b80188f97bd0a6bac45801bb46fc48f50b9763aa66046039", - "https://deno.land/x/zod@v3.23.8/types.ts": "1b172c90782b1eaa837100ebb6abd726d79d6c1ec336350c8e851e0fd706bf5c", - "https://deno.land/x/zod@v3.24.2/ZodError.ts": "27b41119736fcdc69cc72e63838bed1e9e1210c7ce721211f02256e06b443b55", - "https://deno.land/x/zod@v3.24.2/errors.ts": "5285922d2be9700cc0c70c95e4858952b07ae193aa0224be3cbd5cd5567eabef", - "https://deno.land/x/zod@v3.24.2/external.ts": "a6cfbd61e9e097d5f42f8a7ed6f92f93f51ff927d29c9fbaec04f03cbce130fe", - "https://deno.land/x/zod@v3.24.2/helpers/enumUtil.ts": "54efc393cc9860e687d8b81ff52e980def00fa67377ad0bf8b3104f8a5bf698c", - "https://deno.land/x/zod@v3.24.2/helpers/errorUtil.ts": "7a77328240be7b847af6de9189963bd9f79cab32bbc61502a9db4fe6683e2ea7", - "https://deno.land/x/zod@v3.24.2/helpers/parseUtil.ts": "c14814d167cc286972b6e094df88d7d982572a08424b7cd50f862036b6fcaa77", - "https://deno.land/x/zod@v3.24.2/helpers/partialUtil.ts": "998c2fe79795257d4d1cf10361e74492f3b7d852f61057c7c08ac0a46488b7e7", - "https://deno.land/x/zod@v3.24.2/helpers/typeAliases.ts": "0fda31a063c6736fc3cf9090dd94865c811dfff4f3cb8707b932bf937c6f2c3e", - "https://deno.land/x/zod@v3.24.2/helpers/util.ts": "30c273131661ca5dc973f2cfb196fa23caf3a43e224cdde7a683b72e101a31fc", - "https://deno.land/x/zod@v3.24.2/index.ts": "d27aabd973613985574bc31f39e45cb5d856aa122ef094a9f38a463b8ef1a268", - "https://deno.land/x/zod@v3.24.2/locales/en.ts": "a7a25cd23563ccb5e0eed214d9b31846305ddbcdb9c5c8f508b108943366ab4c", - "https://deno.land/x/zod@v3.24.2/mod.ts": "ec6e2b1255c1a350b80188f97bd0a6bac45801bb46fc48f50b9763aa66046039", - "https://deno.land/x/zod@v3.24.2/standard-schema.ts": "4abb2e7bd784fb95d219584673971bb317e74fb4fd0c74c196b558ba46df4456", - "https://deno.land/x/zod@v3.24.2/types.ts": "91f825106bcf5b2ba08daa108283aadc32c0ac332f09c9a90db3d88b142476a3" - }, - "workspace": { - "dependencies": [ - "jsr:@std/assert@^1.0.13", - "jsr:@std/cli@^1.0.20", - "jsr:@std/fs@^1.0.19", - "jsr:@std/http@^1.0.19", - "jsr:@std/path@^1.1.1", - "jsr:@std/toml@^1.0.8", - "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/lib/nuldoc.rb b/services/nuldoc/lib/nuldoc.rb new file mode 100644 index 00000000..2cd2a032 --- /dev/null +++ b/services/nuldoc/lib/nuldoc.rb @@ -0,0 +1,62 @@ +require 'date' +require 'digest' +require 'English' +require 'fileutils' +require 'securerandom' + +require 'dry/cli' +require 'rouge' +require 'toml-rb' +require 'webrick' + +require_relative 'nuldoc/dom' +require_relative 'nuldoc/revision' +require_relative 'nuldoc/config' +require_relative 'nuldoc/page' +require_relative 'nuldoc/render' +require_relative 'nuldoc/renderers/html' +require_relative 'nuldoc/renderers/xml' +require_relative 'nuldoc/markdown/document' +require_relative 'nuldoc/markdown/parser/line_scanner' +require_relative 'nuldoc/markdown/parser/attributes' +require_relative 'nuldoc/markdown/parser/inline_parser' +require_relative 'nuldoc/markdown/parser/block_parser' +require_relative 'nuldoc/markdown/parse' +require_relative 'nuldoc/markdown/transform' +require_relative 'nuldoc/slide/slide' +require_relative 'nuldoc/slide/parse' +require_relative 'nuldoc/components/utils' +require_relative 'nuldoc/components/page_layout' +require_relative 'nuldoc/components/global_footer' +require_relative 'nuldoc/components/global_headers' +require_relative 'nuldoc/components/post_page_entry' +require_relative 'nuldoc/components/slide_page_entry' +require_relative 'nuldoc/components/pagination' +require_relative 'nuldoc/components/table_of_contents' +require_relative 'nuldoc/components/tag_list' +require_relative 'nuldoc/components/static_stylesheet' +require_relative 'nuldoc/components/static_script' +require_relative 'nuldoc/pages/home_page' +require_relative 'nuldoc/pages/about_page' +require_relative 'nuldoc/pages/post_page' +require_relative 'nuldoc/pages/post_list_page' +require_relative 'nuldoc/pages/slide_page' +require_relative 'nuldoc/pages/slide_list_page' +require_relative 'nuldoc/pages/tag_page' +require_relative 'nuldoc/pages/tag_list_page' +require_relative 'nuldoc/pages/atom_page' +require_relative 'nuldoc/pages/not_found_page' +require_relative 'nuldoc/generators/home' +require_relative 'nuldoc/generators/about' +require_relative 'nuldoc/generators/post' +require_relative 'nuldoc/generators/post_list' +require_relative 'nuldoc/generators/slide' +require_relative 'nuldoc/generators/slide_list' +require_relative 'nuldoc/generators/tag' +require_relative 'nuldoc/generators/tag_list' +require_relative 'nuldoc/generators/atom' +require_relative 'nuldoc/generators/not_found' +require_relative 'nuldoc/commands/build' +require_relative 'nuldoc/commands/serve' +require_relative 'nuldoc/commands/new' +require_relative 'nuldoc/cli' diff --git a/services/nuldoc/lib/nuldoc/cli.rb b/services/nuldoc/lib/nuldoc/cli.rb new file mode 100644 index 00000000..f3a18da9 --- /dev/null +++ b/services/nuldoc/lib/nuldoc/cli.rb @@ -0,0 +1,46 @@ +module Nuldoc + module CLI + extend Dry::CLI::Registry + + class BuildCommand < Dry::CLI::Command + desc 'Build the site' + + def call(**) + config = ConfigLoader.load_config(ConfigLoader.default_config_path) + Commands::Build.run(config) + end + end + + class ServeCommand < Dry::CLI::Command + desc 'Start development server' + + argument :site, required: true, desc: 'Site to serve (default, about, blog, slides)' + option :no_rebuild, type: :boolean, default: false, desc: 'Skip rebuilding on each request' + + def call(site:, **options) + config = ConfigLoader.load_config(ConfigLoader.default_config_path) + Commands::Serve.run(config, site_name: site, no_rebuild: options[:no_rebuild]) + end + end + + class NewCommand < Dry::CLI::Command + desc 'Create new content' + + argument :type, required: true, desc: 'Content type (post or slide)' + option :date, desc: 'Date (YYYY-MM-DD)' + + def call(type:, **options) + config = ConfigLoader.load_config(ConfigLoader.default_config_path) + Commands::New.run(config, type: type, date: options[:date]) + end + end + + register 'build', BuildCommand + register 'serve', ServeCommand + register 'new', NewCommand + + def self.call + Dry::CLI.new(self).call + end + end +end diff --git a/services/nuldoc/lib/nuldoc/commands/build.rb b/services/nuldoc/lib/nuldoc/commands/build.rb new file mode 100644 index 00000000..868493c3 --- /dev/null +++ b/services/nuldoc/lib/nuldoc/commands/build.rb @@ -0,0 +1,216 @@ +module Nuldoc + module Commands + class Build + def self.run(config) + new(config).run + end + + def initialize(config) + @config = config + end + + def run + posts = build_post_pages + build_post_list_page(posts) + slides = build_slide_pages + build_slide_list_page(slides) + post_tags = build_tag_pages(posts, 'blog') + build_tag_list_page(post_tags, 'blog') + slides_tags = build_tag_pages(slides, 'slides') + build_tag_list_page(slides_tags, 'slides') + build_home_page + build_about_page(slides) + %w[default about blog slides].each { |site| build_not_found_page(site) } + copy_static_files + copy_slides_files(slides) + copy_blog_asset_files + copy_slides_asset_files + copy_post_source_files(posts) + end + + private + + def build_post_pages + source_dir = File.join(Dir.pwd, @config.locations.content_dir, 'posts') + post_files = Dir.glob(File.join(source_dir, '**', '*.md')) + posts = post_files.map do |file| + doc = MarkdownParser.new(file, @config).parse + Generators::Post.new(doc, @config).generate + end + posts.each { |post| write_page(post) } + posts + end + + def build_post_list_page(posts) + sorted_posts = posts.sort do |a, b| + [GeneratorUtils.published_date(b), a.href] <=> [GeneratorUtils.published_date(a), b.href] + end + + post_list_pages = Generators::PostList.new(sorted_posts, @config).generate + post_list_pages.each { |page| write_page(page) } + + post_feed = Generators::Atom.new( + '/posts/', 'posts', + "投稿一覧|#{@config.sites.blog.site_name}", + posts, 'blog', @config + ).generate + write_page(post_feed) + end + + def build_slide_pages + source_dir = File.join(Dir.pwd, @config.locations.content_dir, 'slides') + slide_files = Dir.glob(File.join(source_dir, '**', '*.toml')) + slides = slide_files.map do |file| + slide = SlideParser.new(file).parse + Generators::Slide.new(slide, @config).generate + end + slides.each { |slide| write_page(slide) } + slides + end + + def build_slide_list_page(slides) + slide_list_page = Generators::SlideList.new(slides, @config).generate + write_page(slide_list_page) + + slide_feed = Generators::Atom.new( + slide_list_page.href, 'slides', + "スライド一覧|#{@config.sites.slides.site_name}", + slides, 'slides', @config + ).generate + write_page(slide_feed) + end + + def build_home_page + write_page(Generators::Home.new(@config).generate) + end + + def build_about_page(slides) + write_page(Generators::About.new(slides, @config).generate) + end + + def build_not_found_page(site) + write_page(Generators::NotFound.new(site, @config).generate) + end + + def build_tag_pages(pages, site) + tags_and_pages = collect_tags(pages) + tags = [] + tags_and_pages.each do |tag, tag_pages| + tag_page = Generators::Tag.new(tag, tag_pages, site, @config).generate + write_page(tag_page) + + tag_feed = Generators::Atom.new( + tag_page.href, "tag-#{tag}", + "タグ「#{@config.tag_label(tag)}」一覧|#{@config.site_entry(site).site_name}", + tag_pages, site, @config + ).generate + write_page(tag_feed) + tags.push(tag_page) + end + tags + end + + def build_tag_list_page(tags, site) + write_page(Generators::TagList.new(tags, site, @config).generate) + end + + def collect_tags(tagged_pages) + tags_and_pages = {} + tagged_pages.each do |page| + page.tags.each do |tag| + tags_and_pages[tag] ||= [] + tags_and_pages[tag].push(page) + end + end + + tags_and_pages.sort_by { |tag, _| tag }.map do |tag, pages| + sorted_pages = pages.sort do |a, b| + [GeneratorUtils.published_date(b), a.href] <=> [GeneratorUtils.published_date(a), b.href] + end + [tag, sorted_pages] + end + end + + def copy_static_files + static_dir = File.join(Dir.pwd, @config.locations.static_dir) + + %w[default about blog slides].each do |site| + dest_dir = File.join(Dir.pwd, @config.locations.dest_dir, site) + + Dir.glob(File.join(static_dir, '_all', '*')).each do |entry| + next unless File.file?(entry) + + FileUtils.cp(entry, File.join(dest_dir, File.basename(entry))) + end + + Dir.glob(File.join(static_dir, site, '*')).each do |entry| + next unless File.file?(entry) + + FileUtils.cp(entry, File.join(dest_dir, File.basename(entry))) + end + end + end + + def copy_slides_files(slides) + content_dir = File.join(Dir.pwd, @config.locations.content_dir) + dest_dir = File.join(Dir.pwd, @config.locations.dest_dir) + + slides.each do |slide| + src = File.join(content_dir, slide.slide_link) + dst = File.join(dest_dir, 'slides', slide.slide_link) + FileUtils.mkdir_p(File.dirname(dst)) + FileUtils.cp(src, dst) + end + end + + def copy_blog_asset_files + content_dir = File.join(Dir.pwd, @config.locations.content_dir, 'posts') + dest_dir = File.join(Dir.pwd, @config.locations.dest_dir, 'blog') + + Dir.glob(File.join(content_dir, '**', '*')).each do |path| + next unless File.file?(path) + next if path.end_with?('.md', '.toml', '.pdf') + + relative = path.sub("#{content_dir}/", '') + dst = File.join(dest_dir, 'posts', relative) + FileUtils.mkdir_p(File.dirname(dst)) + FileUtils.cp(path, dst) + end + end + + def copy_slides_asset_files + content_dir = File.join(Dir.pwd, @config.locations.content_dir, 'slides') + dest_dir = File.join(Dir.pwd, @config.locations.dest_dir, 'slides') + + Dir.glob(File.join(content_dir, '**', '*')).each do |path| + next unless File.file?(path) + next if path.end_with?('.md', '.toml', '.pdf') + + relative = path.sub("#{content_dir}/", '') + dst = File.join(dest_dir, 'slides', relative) + FileUtils.mkdir_p(File.dirname(dst)) + FileUtils.cp(path, dst) + end + end + + def write_page(page) + dest_file_path = File.join(Dir.pwd, @config.locations.dest_dir, page.site, page.dest_file_path) + FileUtils.mkdir_p(File.dirname(dest_file_path)) + File.write(dest_file_path, Renderer.new.render(page.root, page.renderer)) + end + + def copy_post_source_files(posts) + content_dir = File.join(Dir.pwd, @config.locations.content_dir) + dest_dir = File.join(Dir.pwd, @config.locations.dest_dir, 'blog') + + posts.each do |post| + src = post.source_file_path + relative = src.sub("#{content_dir}/", '') + dst = File.join(dest_dir, relative) + FileUtils.mkdir_p(File.dirname(dst)) + FileUtils.cp(src, dst) + end + end + end + end +end diff --git a/services/nuldoc/lib/nuldoc/commands/new.rb b/services/nuldoc/lib/nuldoc/commands/new.rb new file mode 100644 index 00000000..13919a75 --- /dev/null +++ b/services/nuldoc/lib/nuldoc/commands/new.rb @@ -0,0 +1,81 @@ +module Nuldoc + module Commands + class New + def self.run(config, type:, date: nil) + new(config).run(type: type, date: date) + end + + def initialize(config) + @config = config + end + + def run(type:, date: nil) + unless %w[post slide].include?(type) + warn <<~USAGE + Usage: nuldoc new + + must be either "post" or "slide". + + OPTIONS: + --date + USAGE + exit 1 + end + + ymd = date || Time.now.strftime('%Y-%m-%d') + + dir_path = type == 'post' ? 'posts' : 'slides' + filename = type == 'post' ? 'TODO.md' : 'TODO.toml' + + dest_file_path = File.join(Dir.pwd, @config.locations.content_dir, dir_path, ymd, filename) + FileUtils.mkdir_p(File.dirname(dest_file_path)) + File.write(dest_file_path, template(type, ymd)) + + relative = dest_file_path.sub(Dir.pwd, '') + puts "New file #{relative} was successfully created." + end + + private + + def template(type, date) + uuid = SecureRandom.uuid + if type == 'post' + <<~TEMPLATE + --- + [article] + uuid = "#{uuid}" + title = "TODO" + description = "TODO" + tags = [ + "TODO", + ] + + [[article.revisions]] + date = "#{date}" + remark = "公開" + --- + # はじめに {#intro} + + TODO + TEMPLATE + else + <<~TEMPLATE + [slide] + uuid = "#{uuid}" + title = "TODO" + event = "TODO" + talkType = "TODO" + link = "TODO" + tags = [ + "TODO", + ] + + [[slide.revisions]] + date = "#{date}" + remark = "登壇" + TEMPLATE + end + end + end + end +end diff --git a/services/nuldoc/lib/nuldoc/commands/serve.rb b/services/nuldoc/lib/nuldoc/commands/serve.rb new file mode 100644 index 00000000..060e51b8 --- /dev/null +++ b/services/nuldoc/lib/nuldoc/commands/serve.rb @@ -0,0 +1,63 @@ +module Nuldoc + module Commands + class Serve + def self.run(config, site_name:, no_rebuild: false) + new(config).run(site_name: site_name, no_rebuild: no_rebuild) + end + + def initialize(config) + @config = config + end + + def run(site_name:, no_rebuild: false) + raise 'Usage: nuldoc serve ' if site_name.nil? || site_name.empty? + + root_dir = File.join(Dir.pwd, @config.locations.dest_dir, site_name) + + server = WEBrick::HTTPServer.new( + Port: 8000, + BindAddress: '127.0.0.1', + DocumentRoot: root_dir, + Logger: WEBrick::Log.new($stderr, WEBrick::Log::INFO) + ) + + server.mount_proc '/' do |req, res| + pathname = req.path + + unless resource_path?(pathname) || no_rebuild + Build.run(@config) + warn 'rebuild' + end + + file_path = File.expand_path(File.join(root_dir, pathname)) + unless file_path.start_with?(File.realpath(root_dir)) + res.status = 403 + res.body = '403 Forbidden' + next + end + file_path = File.join(file_path, 'index.html') if File.directory?(file_path) + + if File.exist?(file_path) + res.body = File.read(file_path) + res['Content-Type'] = WEBrick::HTTPUtils.mime_type(file_path, WEBrick::HTTPUtils::DefaultMimeTypes) + else + not_found_path = File.join(root_dir, '404.html') + res.status = 404 + res.body = File.exist?(not_found_path) ? File.read(not_found_path) : '404 Not Found' + res['Content-Type'] = 'text/html' + end + end + + trap('INT') { server.shutdown } + server.start + end + + private + + def resource_path?(pathname) + extensions = %w[.css .gif .ico .jpeg .jpg .js .mjs .png .svg] + extensions.any? { |ext| pathname.end_with?(ext) } + end + end + end +end diff --git a/services/nuldoc/lib/nuldoc/components/global_footer.rb b/services/nuldoc/lib/nuldoc/components/global_footer.rb new file mode 100644 index 00000000..8d4143fb --- /dev/null +++ b/services/nuldoc/lib/nuldoc/components/global_footer.rb @@ -0,0 +1,11 @@ +module Nuldoc + module Components + class GlobalFooter + extend Dom + + def self.render(config:) + footer({ 'class' => 'footer' }, "© #{config.site.copyright_year} #{config.site.author}") + end + end + end +end diff --git a/services/nuldoc/lib/nuldoc/components/global_headers.rb b/services/nuldoc/lib/nuldoc/components/global_headers.rb new file mode 100644 index 00000000..b06c6173 --- /dev/null +++ b/services/nuldoc/lib/nuldoc/components/global_headers.rb @@ -0,0 +1,54 @@ +module Nuldoc + module Components + class DefaultGlobalHeader + extend Dom + + def self.render(config:) + header({ 'class' => 'header' }, + div({ 'class' => 'site-logo' }, + a({ 'href' => "https://#{config.sites.default.fqdn}/" }, 'nsfisis.dev'))) + end + end + + class AboutGlobalHeader + extend Dom + + def self.render(config:) + header({ 'class' => 'header' }, + div({ 'class' => 'site-logo' }, + a({ 'href' => "https://#{config.sites.default.fqdn}/" }, 'nsfisis.dev'))) + end + end + + class BlogGlobalHeader + extend Dom + + def self.render(config:) + header({ 'class' => 'header' }, + div({ 'class' => 'site-logo' }, + a({ 'href' => "https://#{config.sites.default.fqdn}/" }, 'nsfisis.dev')), + div({ 'class' => 'site-name' }, config.sites.blog.site_name), + nav({ 'class' => 'nav' }, + ul({}, + li({}, a({ 'href' => "https://#{config.sites.about.fqdn}/" }, 'About')), + li({}, a({ 'href' => '/posts/' }, 'Posts')), + li({}, a({ 'href' => '/tags/' }, 'Tags'))))) + end + end + + class SlidesGlobalHeader + extend Dom + + def self.render(config:) + header({ 'class' => 'header' }, + div({ 'class' => 'site-logo' }, + a({ 'href' => "https://#{config.sites.default.fqdn}/" }, 'nsfisis.dev')), + nav({ 'class' => 'nav' }, + ul({}, + li({}, a({ 'href' => "https://#{config.sites.about.fqdn}/" }, 'About')), + li({}, a({ 'href' => '/slides/' }, 'Slides')), + li({}, a({ 'href' => '/tags/' }, 'Tags'))))) + end + end + end +end diff --git a/services/nuldoc/lib/nuldoc/components/page_layout.rb b/services/nuldoc/lib/nuldoc/components/page_layout.rb new file mode 100644 index 00000000..5d14ec0d --- /dev/null +++ b/services/nuldoc/lib/nuldoc/components/page_layout.rb @@ -0,0 +1,35 @@ +module Nuldoc + module Components + class PageLayout + extend Dom + + def self.render(meta_copyright_year:, meta_description:, meta_title:, site:, config:, children:, + meta_keywords: nil, meta_atom_feed_href: nil) + site_entry = config.site_entry(site) + + elem('html', { 'lang' => 'ja-JP' }, + elem('head', {}, + meta({ 'charset' => 'UTF-8' }), + meta({ 'name' => 'viewport', 'content' => 'width=device-width, initial-scale=1.0' }), + meta({ 'name' => 'author', 'content' => config.site.author }), + meta({ 'name' => 'copyright', + 'content' => "© #{meta_copyright_year} #{config.site.author}" }), + meta({ 'name' => 'description', 'content' => meta_description }), + meta_keywords && !meta_keywords.empty? ? meta({ 'name' => 'keywords', + 'content' => meta_keywords.join(',') }) : nil, + meta({ 'property' => 'og:type', 'content' => 'article' }), + meta({ 'property' => 'og:title', 'content' => meta_title }), + meta({ 'property' => 'og:description', 'content' => meta_description }), + meta({ 'property' => 'og:site_name', 'content' => site_entry.site_name }), + meta({ 'property' => 'og:locale', 'content' => 'ja_JP' }), + meta({ 'name' => 'Hatena::Bookmark', 'content' => 'nocomment' }), + meta_atom_feed_href ? link({ 'rel' => 'alternate', 'href' => meta_atom_feed_href, + 'type' => 'application/atom+xml' }) : nil, + link({ 'rel' => 'icon', 'href' => '/favicon.svg', 'type' => 'image/svg+xml' }), + elem('title', {}, meta_title), + StaticStylesheet.render(file_name: '/style.css', config: config)), + children) + end + end + end +end diff --git a/services/nuldoc/lib/nuldoc/components/pagination.rb b/services/nuldoc/lib/nuldoc/components/pagination.rb new file mode 100644 index 00000000..500b81f9 --- /dev/null +++ b/services/nuldoc/lib/nuldoc/components/pagination.rb @@ -0,0 +1,62 @@ +module Nuldoc + module Components + class Pagination + extend Dom + + def self.render(current_page:, total_pages:, base_path:) + return div({}) if total_pages <= 1 + + pages = generate_page_numbers(current_page, total_pages) + + nav({ 'class' => 'pagination' }, + div({ 'class' => 'pagination-prev' }, + current_page > 1 ? a({ 'href' => page_url_at(base_path, current_page - 1) }, '前へ') : nil), + *pages.map do |page| + if page == '...' + div({ 'class' => 'pagination-elipsis' }, "\u2026") + elsif page == current_page + div({ 'class' => 'pagination-page pagination-page-current' }, + span({}, page.to_s)) + else + div({ 'class' => 'pagination-page' }, + a({ 'href' => page_url_at(base_path, page) }, page.to_s)) + end + end, + div({ 'class' => 'pagination-next' }, + current_page < total_pages ? a({ 'href' => page_url_at(base_path, current_page + 1) }, '次へ') : nil)) + end + + def self.generate_page_numbers(current_page, total_pages) + pages = Set.new + pages.add(1) + pages.add([1, current_page - 1].max) + pages.add(current_page) + pages.add([total_pages, current_page + 1].min) + pages.add(total_pages) + + sorted = pages.sort + + result = [] + sorted.each_with_index do |page, i| + if i.positive? + gap = page - sorted[i - 1] + if gap == 2 + result.push(sorted[i - 1] + 1) + elsif gap > 2 + result.push('...') + end + end + result.push(page) + end + + result + end + + def self.page_url_at(base_path, page) + page == 1 ? base_path : "#{base_path}#{page}/" + end + + private_class_method :generate_page_numbers, :page_url_at + end + end +end diff --git a/services/nuldoc/lib/nuldoc/components/post_page_entry.rb b/services/nuldoc/lib/nuldoc/components/post_page_entry.rb new file mode 100644 index 00000000..5232bc6b --- /dev/null +++ b/services/nuldoc/lib/nuldoc/components/post_page_entry.rb @@ -0,0 +1,25 @@ +module Nuldoc + module Components + class PostPageEntry + extend Dom + + def self.render(post:, config:) + published = Revision.date_to_string(GeneratorUtils.published_date(post)) + updated = Revision.date_to_string(GeneratorUtils.updated_date(post)) + has_updates = GeneratorUtils.any_updates?(post) + + article({ 'class' => 'post-entry' }, + a({ 'href' => post.href }, + header({ 'class' => 'entry-header' }, h2({}, post.title)), + section({ 'class' => 'entry-content' }, p({}, post.description)), + footer({ 'class' => 'entry-footer' }, + elem('time', { 'datetime' => published }, published), + ' 投稿', + has_updates ? '、' : nil, + has_updates ? elem('time', { 'datetime' => updated }, updated) : nil, + has_updates ? ' 更新' : nil, + post.tags.length.positive? ? TagList.render(tags: post.tags, config: config) : nil))) + end + end + end +end diff --git a/services/nuldoc/lib/nuldoc/components/slide_page_entry.rb b/services/nuldoc/lib/nuldoc/components/slide_page_entry.rb new file mode 100644 index 00000000..b80f52c8 --- /dev/null +++ b/services/nuldoc/lib/nuldoc/components/slide_page_entry.rb @@ -0,0 +1,25 @@ +module Nuldoc + module Components + class SlidePageEntry + extend Dom + + def self.render(slide:, config:) + published = Revision.date_to_string(GeneratorUtils.published_date(slide)) + updated = Revision.date_to_string(GeneratorUtils.updated_date(slide)) + has_updates = GeneratorUtils.any_updates?(slide) + + article({ 'class' => 'post-entry' }, + a({ 'href' => slide.href }, + header({ 'class' => 'entry-header' }, h2({}, slide.title)), + section({ 'class' => 'entry-content' }, p({}, slide.description)), + footer({ 'class' => 'entry-footer' }, + elem('time', { 'datetime' => published }, published), + ' 登壇', + has_updates ? '、' : nil, + has_updates ? elem('time', { 'datetime' => updated }, updated) : nil, + has_updates ? ' 更新' : nil, + slide.tags.length.positive? ? TagList.render(tags: slide.tags, config: config) : nil))) + end + end + end +end diff --git a/services/nuldoc/lib/nuldoc/components/static_script.rb b/services/nuldoc/lib/nuldoc/components/static_script.rb new file mode 100644 index 00000000..755dc3fc --- /dev/null +++ b/services/nuldoc/lib/nuldoc/components/static_script.rb @@ -0,0 +1,16 @@ +module Nuldoc + module Components + class StaticScript + extend Dom + + def self.render(file_name:, config:, site: nil, type: nil, defer: nil) + file_path = File.join(Dir.pwd, config.locations.static_dir, site || '_all', file_name) + hash = ComponentUtils.calculate_file_hash(file_path) + attrs = { 'src' => "#{file_name}?h=#{hash}" } + attrs['type'] = type if type + attrs['defer'] = defer if defer + script(attrs) + end + end + end +end diff --git a/services/nuldoc/lib/nuldoc/components/static_stylesheet.rb b/services/nuldoc/lib/nuldoc/components/static_stylesheet.rb new file mode 100644 index 00000000..3127286d --- /dev/null +++ b/services/nuldoc/lib/nuldoc/components/static_stylesheet.rb @@ -0,0 +1,13 @@ +module Nuldoc + module Components + class StaticStylesheet + extend Dom + + def self.render(file_name:, config:, site: nil) + file_path = File.join(Dir.pwd, config.locations.static_dir, site || '_all', file_name) + hash = ComponentUtils.calculate_file_hash(file_path) + link({ 'rel' => 'stylesheet', 'href' => "#{file_name}?h=#{hash}" }) + end + end + end +end diff --git a/services/nuldoc/lib/nuldoc/components/table_of_contents.rb b/services/nuldoc/lib/nuldoc/components/table_of_contents.rb new file mode 100644 index 00000000..b3a5b531 --- /dev/null +++ b/services/nuldoc/lib/nuldoc/components/table_of_contents.rb @@ -0,0 +1,21 @@ +module Nuldoc + module Components + class TableOfContents + extend Dom + + def self.render(toc:) + nav({ 'class' => 'toc' }, + h2({}, '目次'), + ul({}, *toc.items.map { |entry| toc_entry_component(entry) })) + end + + def self.toc_entry_component(entry) + li({}, + a({ 'href' => "##{entry.id}" }, entry.text), + entry.children.length.positive? ? ul({}, *entry.children.map { |child| toc_entry_component(child) }) : nil) + end + + private_class_method :toc_entry_component + end + end +end diff --git a/services/nuldoc/lib/nuldoc/components/tag_list.rb b/services/nuldoc/lib/nuldoc/components/tag_list.rb new file mode 100644 index 00000000..0c566f32 --- /dev/null +++ b/services/nuldoc/lib/nuldoc/components/tag_list.rb @@ -0,0 +1,15 @@ +module Nuldoc + module Components + class TagList + extend Dom + + def self.render(tags:, config:) + ul({ 'class' => 'entry-tags' }, + *tags.map do |slug| + li({ 'class' => 'tag' }, + span({ 'class' => 'tag-inner' }, text(config.tag_label(slug)))) + end) + end + end + end +end diff --git a/services/nuldoc/lib/nuldoc/components/utils.rb b/services/nuldoc/lib/nuldoc/components/utils.rb new file mode 100644 index 00000000..18337c86 --- /dev/null +++ b/services/nuldoc/lib/nuldoc/components/utils.rb @@ -0,0 +1,8 @@ +module Nuldoc + class ComponentUtils + def self.calculate_file_hash(file_path) + content = File.binread(file_path) + Digest::MD5.hexdigest(content) + end + end +end diff --git a/services/nuldoc/lib/nuldoc/config.rb b/services/nuldoc/lib/nuldoc/config.rb new file mode 100644 index 00000000..b4334bcd --- /dev/null +++ b/services/nuldoc/lib/nuldoc/config.rb @@ -0,0 +1,67 @@ +module Nuldoc + Config = Data.define(:locations, :site, :sites, :tag_labels) do + def tag_label(slug) + label = tag_labels[slug] + raise "Unknown tag: #{slug}" if label.nil? + + label + end + + def site_entry(site_key) + sites.public_send(site_key) + end + end + + LocationsConfig = Data.define(:content_dir, :dest_dir, :static_dir) + + SiteConfig = Data.define(:author, :copyright_year) + + SiteEntry = Data.define(:fqdn, :site_name, :posts_per_page) + + SitesConfig = Data.define(:default, :about, :blog, :slides) + + class ConfigLoader + def self.default_config_path + File.join(Dir.pwd, 'nuldoc.toml') + end + + def self.load_config(file_path) + raw = TomlRB.load_file(file_path) + + locations = LocationsConfig.new( + content_dir: raw.dig('locations', 'contentDir'), + dest_dir: raw.dig('locations', 'destDir'), + static_dir: raw.dig('locations', 'staticDir') + ) + + site = SiteConfig.new( + author: raw.dig('site', 'author'), + copyright_year: raw.dig('site', 'copyrightYear') + ) + + sites = SitesConfig.new( + default: build_site_entry(raw.dig('sites', 'default')), + about: build_site_entry(raw.dig('sites', 'about')), + blog: build_site_entry(raw.dig('sites', 'blog')), + slides: build_site_entry(raw.dig('sites', 'slides')) + ) + + Config.new( + locations: locations, + site: site, + sites: sites, + tag_labels: raw['tagLabels'] || {} + ) + end + + def self.build_site_entry(hash) + SiteEntry.new( + fqdn: hash['fqdn'], + site_name: hash['siteName'], + posts_per_page: hash['postsPerPage'] + ) + end + + private_class_method :build_site_entry + end +end diff --git a/services/nuldoc/lib/nuldoc/dom.rb b/services/nuldoc/lib/nuldoc/dom.rb new file mode 100644 index 00000000..7e28ac06 --- /dev/null +++ b/services/nuldoc/lib/nuldoc/dom.rb @@ -0,0 +1,136 @@ +module Nuldoc + Text = Struct.new(:content, keyword_init: true) do + def kind = :text + end + + RawHTML = Struct.new(:html, keyword_init: true) do + def kind = :raw + end + + Element = Struct.new(:name, :attributes, :children, keyword_init: true) do + def kind = :element + end + + module Dom + module_function + + def text(content) + Text.new(content: content) + end + + def raw_html(html) + RawHTML.new(html: html) + end + + def elem(name, attributes = {}, *children) + Element.new( + name: name, + attributes: attributes || {}, + children: flatten_children(children) + ) + end + + def a(attributes = {}, *children) = elem('a', attributes, *children) + def article(attributes = {}, *children) = elem('article', attributes, *children) + def button(attributes = {}, *children) = elem('button', attributes, *children) + def div(attributes = {}, *children) = elem('div', attributes, *children) + def footer(attributes = {}, *children) = elem('footer', attributes, *children) + def h1(attributes = {}, *children) = elem('h1', attributes, *children) + def h2(attributes = {}, *children) = elem('h2', attributes, *children) + def h3(attributes = {}, *children) = elem('h3', attributes, *children) + def h4(attributes = {}, *children) = elem('h4', attributes, *children) + def h5(attributes = {}, *children) = elem('h5', attributes, *children) + def h6(attributes = {}, *children) = elem('h6', attributes, *children) + def header(attributes = {}, *children) = elem('header', attributes, *children) + def img(attributes = {}) = elem('img', attributes) + def li(attributes = {}, *children) = elem('li', attributes, *children) + def link(attributes = {}) = elem('link', attributes) + def meta(attributes = {}) = elem('meta', attributes) + def nav(attributes = {}, *children) = elem('nav', attributes, *children) + def ol(attributes = {}, *children) = elem('ol', attributes, *children) + def p(attributes = {}, *children) = elem('p', attributes, *children) + def script(attributes = {}, *children) = elem('script', attributes, *children) + def section(attributes = {}, *children) = elem('section', attributes, *children) + def span(attributes = {}, *children) = elem('span', attributes, *children) + def ul(attributes = {}, *children) = elem('ul', attributes, *children) + + def add_class(element, klass) + classes = element.attributes['class'] + if classes.nil? + element.attributes['class'] = klass + else + class_list = classes.split + class_list.push(klass) + class_list.sort! + element.attributes['class'] = class_list.join(' ') + end + end + + def find_first_child_element(element, name) + element.children.find { |c| c.kind == :element && c.name == name } + end + + def find_child_elements(element, name) + element.children.select { |c| c.kind == :element && c.name == name } + end + + def inner_text(element) + t = +'' + for_each_child(element) do |c| + t << c.content if c.kind == :text + end + t + end + + def for_each_child(element, &) + element.children.each(&) + end + + def for_each_child_recursively(element) + g = proc do |c| + yield(c) + for_each_child(c, &g) if c.kind == :element + end + for_each_child(element, &g) + end + + def for_each_element_of_type(root, element_name) + for_each_child_recursively(root) do |n| + yield(n) if n.kind == :element && n.name == element_name + end + end + + def process_text_nodes_in_element(element) + new_children = [] + element.children.each do |child| + if child.kind == :text + new_children.concat(yield(child.content)) + else + new_children.push(child) + end + end + element.children.replace(new_children) + end + + private + + def flatten_children(children) + result = [] + children.each do |child| + case child + when nil, false + next + when String + result.push(text(child)) + when Array + result.concat(flatten_children(child)) + else + result.push(child) + end + end + result + end + + module_function :flatten_children + end +end diff --git a/services/nuldoc/lib/nuldoc/generators/about.rb b/services/nuldoc/lib/nuldoc/generators/about.rb new file mode 100644 index 00000000..e64f0d1d --- /dev/null +++ b/services/nuldoc/lib/nuldoc/generators/about.rb @@ -0,0 +1,22 @@ +module Nuldoc + module Generators + class About + def initialize(slides, config) + @slides = slides + @config = config + end + + def generate + html = Pages::AboutPage.render(slides: @slides, config: @config) + + Page.new( + root: html, + renderer: :html, + site: 'about', + dest_file_path: '/index.html', + href: '/' + ) + end + end + end +end diff --git a/services/nuldoc/lib/nuldoc/generators/atom.rb b/services/nuldoc/lib/nuldoc/generators/atom.rb new file mode 100644 index 00000000..74750eb7 --- /dev/null +++ b/services/nuldoc/lib/nuldoc/generators/atom.rb @@ -0,0 +1,58 @@ +module Nuldoc + Feed = Data.define(:author, :icon, :id, :link_to_self, :link_to_alternate, :title, :updated, :entries) + + FeedEntry = Data.define(:id, :link_to_alternate, :published, :summary, :title, :updated) + + module Generators + class Atom + BASE_NAME = 'atom.xml'.freeze + + def initialize(alternate_link, feed_slug, feed_title, entries, site, config) + @alternate_link = alternate_link + @feed_slug = feed_slug + @feed_title = feed_title + @entries = entries + @site = site + @config = config + end + + def generate + feed_entries = @entries.map do |entry| + fqdn = entry.respond_to?(:event) ? @config.sites.slides.fqdn : @config.sites.blog.fqdn + FeedEntry.new( + id: "urn:uuid:#{entry.uuid}", + link_to_alternate: "https://#{fqdn}#{entry.href}", + title: entry.title, + summary: entry.description, + published: Revision.date_to_rfc3339_string(entry.published), + updated: Revision.date_to_rfc3339_string(entry.updated) + ) + end + + feed_entries.sort! { |a, b| [b.published, a.link_to_alternate] <=> [a.published, b.link_to_alternate] } + + site_entry = @config.site_entry(@site) + feed_path = "#{@alternate_link}#{BASE_NAME}" + + feed = Feed.new( + author: @config.site.author, + icon: "https://#{site_entry.fqdn}/favicon.svg", + id: "tag:#{site_entry.fqdn},#{@config.site.copyright_year}:#{@feed_slug}", + link_to_self: "https://#{site_entry.fqdn}#{feed_path}", + link_to_alternate: "https://#{site_entry.fqdn}#{@alternate_link}", + title: @feed_title, + updated: feed_entries.map(&:updated).max || feed_entries.first&.updated || '', + entries: feed_entries + ) + + Page.new( + root: Pages::AtomPage.render(feed: feed), + renderer: :xml, + site: @site, + dest_file_path: feed_path, + href: feed_path + ) + end + end + end +end diff --git a/services/nuldoc/lib/nuldoc/generators/home.rb b/services/nuldoc/lib/nuldoc/generators/home.rb new file mode 100644 index 00000000..54f8753f --- /dev/null +++ b/services/nuldoc/lib/nuldoc/generators/home.rb @@ -0,0 +1,21 @@ +module Nuldoc + module Generators + class Home + def initialize(config) + @config = config + end + + def generate + html = Pages::HomePage.render(config: @config) + + Page.new( + root: html, + renderer: :html, + site: 'default', + dest_file_path: '/index.html', + href: '/' + ) + end + end + end +end diff --git a/services/nuldoc/lib/nuldoc/generators/not_found.rb b/services/nuldoc/lib/nuldoc/generators/not_found.rb new file mode 100644 index 00000000..cffe1df8 --- /dev/null +++ b/services/nuldoc/lib/nuldoc/generators/not_found.rb @@ -0,0 +1,22 @@ +module Nuldoc + module Generators + class NotFound + def initialize(site, config) + @site = site + @config = config + end + + def generate + html = Pages::NotFoundPage.render(site: @site, config: @config) + + Page.new( + root: html, + renderer: :html, + site: @site, + dest_file_path: '/404.html', + href: '/404.html' + ) + end + end + end +end diff --git a/services/nuldoc/lib/nuldoc/generators/post.rb b/services/nuldoc/lib/nuldoc/generators/post.rb new file mode 100644 index 00000000..0d5a3afc --- /dev/null +++ b/services/nuldoc/lib/nuldoc/generators/post.rb @@ -0,0 +1,57 @@ +module Nuldoc + PostPage = Data.define(:root, :renderer, :site, :dest_file_path, :href, + :title, :description, :tags, :revisions, :published, :updated, + :uuid, :source_file_path) + + class GeneratorUtils + def self.published_date(page) + page.revisions.each do |rev| + return rev.date unless rev.is_internal + end + page.revisions[0].date + end + + def self.updated_date(page) + page.revisions.last.date + end + + def self.any_updates?(page) + page.revisions.count { |rev| !rev.is_internal } >= 2 + end + end + + module Generators + class Post + def initialize(doc, config) + @doc = doc + @config = config + end + + def generate + html = Pages::PostPage.render(doc: @doc, config: @config) + + content_dir = File.join(Dir.pwd, @config.locations.content_dir) + dest_file_path = File.join( + @doc.source_file_path.sub(content_dir, '').sub('.md', ''), + 'index.html' + ) + + PostPage.new( + root: html, + renderer: :html, + site: 'blog', + dest_file_path: dest_file_path, + href: dest_file_path.sub('index.html', ''), + title: @doc.title, + description: @doc.description, + tags: @doc.tags, + revisions: @doc.revisions, + published: GeneratorUtils.published_date(@doc), + updated: GeneratorUtils.updated_date(@doc), + uuid: @doc.uuid, + source_file_path: @doc.source_file_path + ) + end + end + end +end diff --git a/services/nuldoc/lib/nuldoc/generators/post_list.rb b/services/nuldoc/lib/nuldoc/generators/post_list.rb new file mode 100644 index 00000000..680a0c32 --- /dev/null +++ b/services/nuldoc/lib/nuldoc/generators/post_list.rb @@ -0,0 +1,41 @@ +module Nuldoc + module Generators + class PostList + def initialize(posts, config) + @posts = posts + @config = config + end + + def generate + posts_per_page = @config.sites.blog.posts_per_page + total_pages = (@posts.length.to_f / posts_per_page).ceil + pages = [] + + (0...total_pages).each do |page_index| + page_posts = @posts[page_index * posts_per_page, posts_per_page] + current_page = page_index + 1 + + html = Pages::PostListPage.render( + posts: page_posts, + config: @config, + current_page: current_page, + total_pages: total_pages + ) + + dest_file_path = current_page == 1 ? '/posts/index.html' : "/posts/#{current_page}/index.html" + href = current_page == 1 ? '/posts/' : "/posts/#{current_page}/" + + pages.push(Page.new( + root: html, + renderer: :html, + site: 'blog', + dest_file_path: dest_file_path, + href: href + )) + end + + pages + end + end + end +end diff --git a/services/nuldoc/lib/nuldoc/generators/slide.rb b/services/nuldoc/lib/nuldoc/generators/slide.rb new file mode 100644 index 00000000..58fde56e --- /dev/null +++ b/services/nuldoc/lib/nuldoc/generators/slide.rb @@ -0,0 +1,42 @@ +module Nuldoc + SlidePageData = Data.define(:root, :renderer, :site, :dest_file_path, :href, + :title, :description, :event, :talk_type, :slide_link, + :tags, :revisions, :published, :updated, :uuid) + + module Generators + class Slide + def initialize(slide, config) + @slide = slide + @config = config + end + + def generate + html = Pages::SlidePage.render(slide: @slide, config: @config) + + content_dir = File.join(Dir.pwd, @config.locations.content_dir) + dest_file_path = File.join( + @slide.source_file_path.sub(content_dir, '').sub('.toml', ''), + 'index.html' + ) + + SlidePageData.new( + root: html, + renderer: :html, + site: 'slides', + dest_file_path: dest_file_path, + href: dest_file_path.sub('index.html', ''), + title: @slide.title, + description: "#{@slide.event} (#{@slide.talk_type})", + event: @slide.event, + talk_type: @slide.talk_type, + slide_link: @slide.slide_link, + tags: @slide.tags, + revisions: @slide.revisions, + published: GeneratorUtils.published_date(@slide), + updated: GeneratorUtils.updated_date(@slide), + uuid: @slide.uuid + ) + end + end + end +end diff --git a/services/nuldoc/lib/nuldoc/generators/slide_list.rb b/services/nuldoc/lib/nuldoc/generators/slide_list.rb new file mode 100644 index 00000000..8d23e4b4 --- /dev/null +++ b/services/nuldoc/lib/nuldoc/generators/slide_list.rb @@ -0,0 +1,22 @@ +module Nuldoc + module Generators + class SlideList + def initialize(slides, config) + @slides = slides + @config = config + end + + def generate + html = Pages::SlideListPage.render(slides: @slides, config: @config) + + Page.new( + root: html, + renderer: :html, + site: 'slides', + dest_file_path: '/slides/index.html', + href: '/slides/' + ) + end + end + end +end diff --git a/services/nuldoc/lib/nuldoc/generators/tag.rb b/services/nuldoc/lib/nuldoc/generators/tag.rb new file mode 100644 index 00000000..7a5f7a7b --- /dev/null +++ b/services/nuldoc/lib/nuldoc/generators/tag.rb @@ -0,0 +1,31 @@ +module Nuldoc + TagPageData = Data.define(:root, :renderer, :site, :dest_file_path, :href, + :tag_slug, :tag_label, :num_of_posts, :num_of_slides) + + module Generators + class Tag + def initialize(tag_slug, pages, site, config) + @tag_slug = tag_slug + @pages = pages + @site = site + @config = config + end + + def generate + html = Pages::TagPage.render(tag_slug: @tag_slug, pages: @pages, site: @site, config: @config) + + TagPageData.new( + root: html, + renderer: :html, + site: @site, + dest_file_path: "/tags/#{@tag_slug}/index.html", + href: "/tags/#{@tag_slug}/", + tag_slug: @tag_slug, + tag_label: @config.tag_label(@tag_slug), + num_of_posts: @pages.count { |p| !p.respond_to?(:event) }, + num_of_slides: @pages.count { |p| p.respond_to?(:event) } + ) + end + end + end +end diff --git a/services/nuldoc/lib/nuldoc/generators/tag_list.rb b/services/nuldoc/lib/nuldoc/generators/tag_list.rb new file mode 100644 index 00000000..089b6f0c --- /dev/null +++ b/services/nuldoc/lib/nuldoc/generators/tag_list.rb @@ -0,0 +1,23 @@ +module Nuldoc + module Generators + class TagList + def initialize(tags, site, config) + @tags = tags + @site = site + @config = config + end + + def generate + html = Pages::TagListPage.render(tags: @tags, site: @site, config: @config) + + Page.new( + root: html, + renderer: :html, + site: @site, + dest_file_path: '/tags/index.html', + href: '/tags/' + ) + end + end + end +end diff --git a/services/nuldoc/lib/nuldoc/markdown/document.rb b/services/nuldoc/lib/nuldoc/markdown/document.rb new file mode 100644 index 00000000..1268a7f8 --- /dev/null +++ b/services/nuldoc/lib/nuldoc/markdown/document.rb @@ -0,0 +1,19 @@ +module Nuldoc + TocEntry = Struct.new(:id, :text, :level, :children, keyword_init: true) + + TocRoot = Struct.new(:items, keyword_init: true) + + Document = Struct.new( + :root, + :source_file_path, + :uuid, + :link, + :title, + :description, + :tags, + :revisions, + :toc, + :is_toc_enabled, + keyword_init: true + ) +end diff --git a/services/nuldoc/lib/nuldoc/markdown/parse.rb b/services/nuldoc/lib/nuldoc/markdown/parse.rb new file mode 100644 index 00000000..fc96352d --- /dev/null +++ b/services/nuldoc/lib/nuldoc/markdown/parse.rb @@ -0,0 +1,51 @@ +module Nuldoc + class MarkdownParser + def initialize(file_path, config) + @file_path = file_path + @config = config + end + + def parse + file_content = File.read(@file_path) + _, frontmatter, *rest = file_content.split(/^---$/m) + meta = parse_metadata(frontmatter) + content = rest.join('---') + + dom_root = Parser::BlockParser.parse(content) + content_dir = File.join(Dir.pwd, @config.locations.content_dir) + link_path = @file_path.sub(content_dir, '').sub('.md', '/') + + revisions = meta['article']['revisions'].each_with_index.map do |r, i| + Revision.new( + number: i, + date: Revision.string_to_date(r['date']), + remark: r['remark'], + is_internal: !r['isInternal'].nil? + ) + end + + doc = Document.new( + root: dom_root, + source_file_path: @file_path, + uuid: meta['article']['uuid'], + link: link_path, + title: meta['article']['title'], + description: meta['article']['description'], + tags: meta['article']['tags'], + revisions: revisions, + toc: nil, + is_toc_enabled: meta['article']['toc'] != false + ) + + Transform.to_html(doc) + rescue StandardError => e + raise e.class, "#{e.message} in #{@file_path}" + end + + private + + def parse_metadata(s) + TomlRB.parse(s) + end + end +end diff --git a/services/nuldoc/lib/nuldoc/markdown/parser/attributes.rb b/services/nuldoc/lib/nuldoc/markdown/parser/attributes.rb new file mode 100644 index 00000000..328c2446 --- /dev/null +++ b/services/nuldoc/lib/nuldoc/markdown/parser/attributes.rb @@ -0,0 +1,25 @@ +module Nuldoc + module Parser + class Attributes + def self.parse_trailing_attributes(text) + match = text.match(/\s*\{([^}]+)\}\s*$/) + return [text, nil, {}] unless match + + attr_string = match[1] + text_before = text[0...match.begin(0)] + + id = nil + attributes = {} + + id_match = attr_string.match(/#([\w-]+)/) + id = id_match[1] if id_match + + attr_string.scan(/([\w-]+)="([^"]*)"/) do |key, value| + attributes[key] = value + end + + [text_before, id, attributes] + end + end + end +end diff --git a/services/nuldoc/lib/nuldoc/markdown/parser/block_parser.rb b/services/nuldoc/lib/nuldoc/markdown/parser/block_parser.rb new file mode 100644 index 00000000..6a135b5b --- /dev/null +++ b/services/nuldoc/lib/nuldoc/markdown/parser/block_parser.rb @@ -0,0 +1,602 @@ +module Nuldoc + module Parser + class BlockParser + extend Dom + + HeaderBlock = Struct.new(:level, :id, :attributes, :heading_element, keyword_init: true) + FootnoteBlock = Struct.new(:id, :children, keyword_init: true) + + class << self + def parse(text) + scanner = LineScanner.new(text) + blocks = parse_blocks(scanner) + build_document(blocks) + end + + private + + # --- Block parsing --- + + def parse_blocks(scanner) + blocks = [] + until scanner.eof? + block = parse_block(scanner) + blocks << block if block + end + blocks + end + + def parse_block(scanner) + return nil if scanner.eof? + + line = scanner.peek + + # 1. Blank line + if line.strip.empty? + scanner.advance + return nil + end + + # 2. HTML comment + if (result = try_html_comment(scanner)) + return result + end + + # 3. Fenced code block + if (result = try_fenced_code(scanner)) + return result + end + + # 4. Note/Edit block + if (result = try_note_block(scanner)) + return result + end + + # 5. Heading + if (result = try_heading(scanner)) + return result + end + + # 6. Horizontal rule + if (result = try_hr(scanner)) + return result + end + + # 7. Footnote definition + if (result = try_footnote_def(scanner)) + return result + end + + # 8. Table + if (result = try_table(scanner)) + return result + end + + # 9. Blockquote + if (result = try_blockquote(scanner)) + return result + end + + # 10. Ordered list + if (result = try_ordered_list(scanner)) + return result + end + + # 11. Unordered list + if (result = try_unordered_list(scanner)) + return result + end + + # 12. HTML block + if (result = try_html_block(scanner)) + return result + end + + # 13. Paragraph + parse_paragraph(scanner) + end + + def try_html_comment(scanner) + line = scanner.peek + return nil unless line.strip.start_with?('') + end + nil # skip comments + end + + def try_fenced_code(scanner) + match = scanner.match(/^```(\S*)(.*)$/) + return nil unless match + + scanner.advance + language = match[1].empty? ? nil : match[1] + meta_string = match[2].strip + + attributes = {} + attributes['language'] = language if language + + if meta_string && !meta_string.empty? + filename_match = meta_string.match(/filename="([^"]+)"/) + attributes['filename'] = filename_match[1] if filename_match + attributes['numbered'] = 'true' if meta_string.include?('numbered') + end + + code_lines = [] + until scanner.eof? + l = scanner.peek + if l.start_with?('```') + scanner.advance + break + end + code_lines << scanner.advance + end + + code = code_lines.join("\n") + elem('codeblock', attributes, text(code)) + end + + def try_note_block(scanner) + match = scanner.match(/^:::(note|edit)(.*)$/) + return nil unless match + + scanner.advance + block_type = match[1] + attr_string = match[2].strip + + attributes = {} + if block_type == 'edit' + # Parse {editat="..." operation="..."} + editat_match = attr_string.match(/editat="([^"]+)"/) + operation_match = attr_string.match(/operation="([^"]+)"/) + attributes['editat'] = editat_match[1] if editat_match + attributes['operation'] = operation_match[1] if operation_match + end + + # Collect content until ::: + content_lines = [] + until scanner.eof? + l = scanner.peek + if l.strip == ':::' + scanner.advance + break + end + content_lines << scanner.advance + end + + inner_text = content_lines.join("\n") + inner_scanner = LineScanner.new(inner_text) + children = parse_blocks(inner_scanner) + + # Convert children - they are block elements already + child_elements = children.compact.select { |c| c.is_a?(Element) || c.is_a?(Text) || c.is_a?(RawHTML) } + elem('note', attributes, *child_elements) + end + + def try_heading(scanner) + match = scanner.match(/^(\#{1,5})\s+(.+)$/) + return nil unless match + + scanner.advance + level = match[1].length + raw_text = match[2] + + text_before, id, attributes = Attributes.parse_trailing_attributes(raw_text) + + inline_nodes = InlineParser.parse(text_before.strip) + heading_element = elem('h', {}, *inline_nodes) + + HeaderBlock.new(level: level, id: id, attributes: attributes, heading_element: heading_element) + end + + def try_hr(scanner) + match = scanner.match(/^---+\s*$/) + return nil unless match + + scanner.advance + elem('hr', {}) + end + + def try_footnote_def(scanner) + match = scanner.match(/^\[\^([^\]]+)\]:\s*(.*)$/) + return nil unless match + + scanner.advance + id = match[1] + first_line = match[2] + + content_lines = [first_line] + # Continuation lines: 4-space indent + until scanner.eof? + l = scanner.peek + break unless l.start_with?(' ') + + content_lines << scanner.advance[4..] + + end + + inner_text = content_lines.join("\n").strip + inner_scanner = LineScanner.new(inner_text) + children = parse_blocks(inner_scanner) + + child_elements = children.compact.select { |c| c.is_a?(Element) || c.is_a?(Text) || c.is_a?(RawHTML) } + FootnoteBlock.new(id: id, children: child_elements) + end + + def try_table(scanner) + # Check if this looks like a table + return nil unless scanner.peek.start_with?('|') + + # Quick lookahead: second line must be a separator + return nil if scanner.pos + 1 >= scanner.lines.length + return nil unless scanner.lines[scanner.pos + 1].match?(/^\|[\s:|-]+\|$/) + + # Collect table lines + lines = [] + while !scanner.eof? && scanner.peek.start_with?('|') + lines << scanner.peek + scanner.advance + end + + header_line = lines[0] + separator_line = lines[1] + body_lines = lines[2..] || [] + + alignment = parse_table_alignment(separator_line) + header_cells = parse_table_row_cells(header_line) + header_row = build_table_row(header_cells, true, alignment) + + body_rows = body_lines.map do |bl| + cells = parse_table_row_cells(bl) + build_table_row(cells, false, alignment) + end + + table_children = [] + table_children << elem('thead', {}, header_row) + table_children << elem('tbody', {}, *body_rows) unless body_rows.empty? + + elem('table', {}, *table_children) + end + + def parse_table_alignment(separator_line) + cells = separator_line.split('|').map(&:strip).reject(&:empty?) + cells.map do |cell| + left = cell.start_with?(':') + right = cell.end_with?(':') + if left && right + 'center' + elsif right + 'right' + elsif left + 'left' + end + end + end + + def parse_table_row_cells(line) + # Strip leading and trailing |, then split by | + stripped = line.strip + stripped = stripped[1..] if stripped.start_with?('|') + stripped = stripped[0...-1] if stripped.end_with?('|') + stripped.split('|').map(&:strip) + end + + def build_table_row(cells, is_header, alignment) + cell_elements = cells.each_with_index.map do |cell_text, i| + attributes = {} + align = alignment[i] + attributes['align'] = align if align && align != 'default' + + tag = is_header ? 'th' : 'td' + inline_nodes = InlineParser.parse(cell_text) + elem(tag, attributes, *inline_nodes) + end + elem('tr', {}, *cell_elements) + end + + def try_blockquote(scanner) + return nil unless scanner.peek.start_with?('> ') || scanner.peek == '>' + + lines = [] + while !scanner.eof? && (scanner.peek.start_with?('> ') || scanner.peek == '>') + line = scanner.advance + lines << (line == '>' ? '' : line[2..]) + end + + inner_text = lines.join("\n") + inner_scanner = LineScanner.new(inner_text) + children = parse_blocks(inner_scanner) + child_elements = children.compact.select { |c| c.is_a?(Element) || c.is_a?(Text) || c.is_a?(RawHTML) } + + elem('blockquote', {}, *child_elements) + end + + def try_ordered_list(scanner) + match = scanner.match(/^(\d+)\.\s+(.*)$/) + return nil unless match + + items = parse_list_items(scanner, :ordered) + return nil if items.empty? + + build_list(:ordered, items) + end + + def try_unordered_list(scanner) + match = scanner.match(/^\*\s+(.*)$/) + return nil unless match + + items = parse_list_items(scanner, :unordered) + return nil if items.empty? + + build_list(:unordered, items) + end + + def parse_list_items(scanner, type) + items = [] + marker_re = type == :ordered ? /^(\d+)\.\s+(.*)$/ : /^\*\s+(.*)$/ + indent_size = 4 + + while !scanner.eof? && (m = scanner.match(marker_re)) + scanner.advance + first_line = type == :ordered ? m[2] : m[1] + + content_lines = [first_line] + has_blank = false + + # Collect continuation lines and sub-items + until scanner.eof? + l = scanner.peek + + # Blank line might be part of loose list + if l.strip.empty? + # Check if next non-blank line is still part of the list + next_pos = scanner.pos + 1 + next_pos += 1 while next_pos < scanner.lines.length && scanner.lines[next_pos].strip.empty? + + if next_pos < scanner.lines.length + next_line = scanner.lines[next_pos] + if next_line.start_with?(' ' * indent_size) || next_line.match?(marker_re) + has_blank = true + content_lines << '' + scanner.advance + next + end + end + break + end + + # Indented continuation (sub-items or content) + if l.start_with?(' ' * indent_size) + content_lines << scanner.advance[indent_size..] + next + end + + # New list item at same level + break if l.match?(marker_re) + + # Non-indented, non-marker line - might be continuation of tight paragraph + break if l.strip.empty? + break if l.match?(/^[#>*\d]/) && !l.match?(/^\d+\.\s/) # another block element + + # Paragraph continuation + content_lines << scanner.advance + end + + items << { lines: content_lines, has_blank: has_blank } + end + + items + end + + def build_list(type, items) + # Determine tight/loose + is_tight = items.none? { |item| item[:has_blank] } + + attributes = {} + attributes['__tight'] = is_tight ? 'true' : 'false' + + # Check for task list items + is_task_list = false + if type == :unordered + is_task_list = items.any? { |item| item[:lines].first&.match?(/^\[[ xX]\]\s/) } + attributes['type'] = 'task' if is_task_list + end + + list_items = items.map do |item| + build_list_item(item, is_task_list) + end + + if type == :ordered + ol(attributes, *list_items) + else + ul(attributes, *list_items) + end + end + + def build_list_item(item, is_task_list) + attributes = {} + content = item[:lines].join("\n") + + if is_task_list + task_match = content.match(/^\[( |[xX])\]\s(.*)$/m) + if task_match + attributes['checked'] = task_match[1] == ' ' ? 'false' : 'true' + content = task_match[2] + end + end + + # Parse inner content as blocks + inner_scanner = LineScanner.new(content) + children = parse_blocks(inner_scanner) + + # If no block-level elements were created, wrap in paragraph + child_elements = children.compact.select { |c| c.is_a?(Element) || c.is_a?(Text) || c.is_a?(RawHTML) } + child_elements = [p({}, *InlineParser.parse(content))] if child_elements.empty? + + li(attributes, *child_elements) + end + + def try_html_block(scanner) + line = scanner.peek + match = line.match(/^<(div|details|summary)(\s[^>]*)?>/) + return nil unless match + + tag = match[1] + lines = [] + close_tag = "" + + until scanner.eof? + l = scanner.advance + lines << l + break if l.include?(close_tag) + end + + html_content = lines.join("\n") + + if tag == 'div' + # Parse inner content for div blocks + inner_match = html_content.match(%r{]*)>(.*)}m) + if inner_match + attr_str = inner_match[1] + inner_content = inner_match[2].strip + + attributes = {} + attr_str.scan(/([\w-]+)="([^"]*)"/) do |key, value| + attributes[key] = value + end + + if inner_content.empty? + div(attributes) + else + inner_scanner = LineScanner.new(inner_content) + children = parse_blocks(inner_scanner) + child_elements = children.compact.select { |c| c.is_a?(Element) || c.is_a?(Text) || c.is_a?(RawHTML) } + div(attributes, *child_elements) + end + else + div({ 'class' => 'raw-html' }, raw_html(html_content)) + end + else + div({ 'class' => 'raw-html' }, raw_html(html_content)) + end + end + + def parse_paragraph(scanner) + lines = [] + until scanner.eof? + l = scanner.peek + break if l.strip.empty? + break if l.match?(/^```/) + break if l.match?(/^\#{1,5}\s/) + break if l.match?(/^---+\s*$/) + break if l.match?(/^>\s/) + break if l.match?(/^\*\s/) + break if l.match?(/^\d+\.\s/) + if l.match?(/^\|/) && + scanner.pos + 1 < scanner.lines.length && + scanner.lines[scanner.pos + 1].match?(/^\|[\s:|-]+\|$/) + break + end + break if l.match?(/^:::/) + break if l.match?(/^