diff options
| author | nsfisis <nsfisis@gmail.com> | 2022-11-19 14:23:32 +0900 |
|---|---|---|
| committer | nsfisis <nsfisis@gmail.com> | 2022-11-19 14:25:59 +0900 |
| commit | 6209453817da9922f28bac1bb1522c6d380630ab (patch) | |
| tree | 19e0699e751af387d549d6720ca215c8065b3c0c /lib | |
| parent | 0cafa073914b5e0b162b735a7f8445fb2aa8a604 (diff) | |
| download | blog.nsfisis.dev-6209453817da9922f28bac1bb1522c6d380630ab.tar.gz blog.nsfisis.dev-6209453817da9922f28bac1bb1522c6d380630ab.tar.zst blog.nsfisis.dev-6209453817da9922f28bac1bb1522c6d380630ab.zip | |
Hugo to Asciidoctor
Diffstat (limited to 'lib')
| -rw-r--r-- | lib/command.rb | 152 | ||||
| -rw-r--r-- | lib/extensions/document_title_processor.rb | 15 | ||||
| -rw-r--r-- | lib/extensions/lang_attribute_processor.rb | 9 | ||||
| -rw-r--r-- | lib/extensions/revision_history_processor.rb | 27 | ||||
| -rw-r--r-- | lib/extensions/section_id_validator.rb | 29 | ||||
| -rw-r--r-- | lib/extensions/source_id_processor.rb | 12 | ||||
| -rw-r--r-- | lib/extensions/source_id_validator.rb | 32 | ||||
| -rw-r--r-- | lib/extensions/tags_processor.rb | 20 | ||||
| -rw-r--r-- | lib/parser.rb | 54 | ||||
| -rw-r--r-- | lib/revision.rb | 3 | ||||
| -rw-r--r-- | lib/tag.rb | 26 |
11 files changed, 379 insertions, 0 deletions
diff --git a/lib/command.rb b/lib/command.rb new file mode 100644 index 0000000..81692b3 --- /dev/null +++ b/lib/command.rb @@ -0,0 +1,152 @@ +module NulDoc + class Command + def initialize(config) + @config = config + @content_dir = @config[:content_dir] + @dest_dir = @config[:dest_dir] + @template_dir = @config[:template_dir] + @parser = NulDoc::Parser.new( + { + 'author' => @config[:author], + 'site-copyright-year' => @config[:site_copyright_year], + 'site-name' => @config[:site_name], + 'source-highlighter' => 'rouge', + 'reproducible' => true, + 'sectids' => false, + }, + @content_dir, + @template_dir, + ) + end + + def run + posts = generate_posts(@content_dir + '/posts') + generate_tags(posts) + generate_posts_list(posts) + end + + private + + def generate_posts(source_dir) + post_files = collect_post_files(source_dir) + posts = parse_posts(post_files) + output_posts(posts) + posts + end + + def collect_post_files(source_dir) + file_paths = [] + Dir.glob('**/*.adoc', base: source_dir, sort: true) do |path| + file_paths << "#{source_dir}/#{path}" + end + file_paths + end + + def parse_posts(post_file_paths) + post_file_paths.map { @parser.parse_file(_1) } + end + + def output_posts(posts) + posts.each do |post| + destination_file_path = post.attributes['source-file-path'] + .sub(@content_dir, @dest_dir) + .sub('.adoc', '/index.html') + destination_dir = File.dirname(destination_file_path) + unless Dir.exist?(destination_dir) + FileUtils.makedirs(destination_dir) + end + open(destination_file_path, 'w') do |f| + f.puts(post.convert) + end + end + end + + def generate_tags(posts) + tags_and_posts = collect_tags(posts) + tag_docs = build_tag_docs(tags_and_posts) + output_tags(tag_docs) + end + + def collect_tags(posts) + tags_and_posts = {} + posts.each do |post| + post.attributes['tags'].each do |tag| + tags_and_posts[tag] ||= [] + tags_and_posts[tag] << post + end + end + tags_and_posts + .transform_values {|posts| + posts.sort_by {|post| + post.attributes['revision-history'].first.date # created_at + }.reverse + } + .sort_by {|tag, _| tag.slug } + .to_h + end + + def build_tag_docs(tags_and_posts) + tags_and_posts.map do |tag, posts| + [tag, build_tag_doc(tag, posts)] + end + end + + def build_tag_doc(tag, posts) + erb = ERB.new(File.read(@template_dir + '/tag.html.erb')) + erb.result_with_hash({ + tag: tag, + posts: posts, + author: @config[:author], + site_copyright_year: @config[:site_copyright_year], + site_name: @config[:site_name], + lang: 'ja-JP', # TODO + copyright_year: posts.last.attributes['revision-history'].first.date.year, + description: "タグ「#{tag.label}」のついた記事一覧", + }) + end + + def output_tags(tag_docs) + tag_docs.each do |tag, html| + destination_file_path = "#{@dest_dir}/tags/#{tag.slug}/index.html" + destination_dir = File.dirname(destination_file_path) + unless Dir.exist?(destination_dir) + FileUtils.makedirs(destination_dir) + end + open(destination_file_path, 'w') do |f| + f.puts(html) + end + end + end + + def generate_posts_list(posts) + html = build_posts_list_doc(posts) + output_posts_list(html) + end + + def build_posts_list_doc(posts) + erb = ERB.new(File.read(@template_dir + '/posts_list.html.erb')) + erb.result_with_hash({ + posts: posts.reverse, + author: @config[:author], + site_copyright_year: @config[:site_copyright_year], + site_name: @config[:site_name], + lang: 'ja-JP', # TODO + copyright_year: @config[:site_copyright_year], + description: "記事一覧", + doctitle: "Posts", + header_title: "Posts", + }) + end + + def output_posts_list(html) + destination_file_path = "#{@dest_dir}/posts/index.html" + destination_dir = File.dirname(destination_file_path) + unless Dir.exist?(destination_dir) + FileUtils.makedirs(destination_dir) + end + open(destination_file_path, 'w') do |f| + f.puts(html) + end + end + end +end diff --git a/lib/extensions/document_title_processor.rb b/lib/extensions/document_title_processor.rb new file mode 100644 index 0000000..fd25844 --- /dev/null +++ b/lib/extensions/document_title_processor.rb @@ -0,0 +1,15 @@ +module Nuldoc + module Extensions + class DocumentTitleProcessor < Asciidoctor::Extensions::TreeProcessor + def process(doc) + doc.title = substitute_document_title(doc.title) + end + + private + + def substitute_document_title(title) + title.sub(/\A\[(.+?)\] /, '【\1】') + end + end + end +end diff --git a/lib/extensions/lang_attribute_processor.rb b/lib/extensions/lang_attribute_processor.rb new file mode 100644 index 0000000..65511bc --- /dev/null +++ b/lib/extensions/lang_attribute_processor.rb @@ -0,0 +1,9 @@ +module Nuldoc + module Extensions + class LangAttributeProcessor < Asciidoctor::Extensions::TreeProcessor + def process(doc) + doc.attributes['lang'] ||= 'ja-JP' + end + end + end +end diff --git a/lib/extensions/revision_history_processor.rb b/lib/extensions/revision_history_processor.rb new file mode 100644 index 0000000..f416de0 --- /dev/null +++ b/lib/extensions/revision_history_processor.rb @@ -0,0 +1,27 @@ +module Nuldoc + module Extensions + class RevisionHistoryProcessor < Asciidoctor::Extensions::TreeProcessor + def process(doc) + revisions = [] + i = 1 + loop do + break unless (rev = doc.attributes["revision-#{i}"]) + revisions << parse_revision(rev) + i += 1 + end + doc.attributes['revision-history'] = revisions + end + + private + + def parse_revision(rev) + m = rev.match(/\A(\d\d\d\d-\d\d-\d\d) (.*)\z/) + raise unless m + Revision.new( + date: Date.parse(m[1], '%Y-%m-%d'), + remark: m[2], + ) + end + end + end +end diff --git a/lib/extensions/section_id_validator.rb b/lib/extensions/section_id_validator.rb new file mode 100644 index 0000000..2ad496c --- /dev/null +++ b/lib/extensions/section_id_validator.rb @@ -0,0 +1,29 @@ +module Nuldoc + module Extensions + class SectionIdValidator < Asciidoctor::Extensions::TreeProcessor + def process(doc) + errors = [] + (doc.find_by(context: :section) {_1.level > 0}).each do |section| + errors << validate_section(section) + end + error_message = errors.compact.join("\n") + unless error_message.empty? + raise "SectionIdValidator (#{doc.attributes['source-file-path']}):\n#{error_message}" + end + end + + private + + def validate_section(section) + id = section.id + unless id + return "Section '#{section.title}': each section MUST have an id." + end + unless id.match?(/\A[-0-9a-z]+\z/) + return "Section '#{section.title}' (##{id}): section id MUST consist of either hyphen, digits or lowercases." + end + nil + end + end + end +end diff --git a/lib/extensions/source_id_processor.rb b/lib/extensions/source_id_processor.rb new file mode 100644 index 0000000..13813e0 --- /dev/null +++ b/lib/extensions/source_id_processor.rb @@ -0,0 +1,12 @@ +module Nuldoc + module Extensions + class SourceIdProcessor < Asciidoctor::Extensions::TreeProcessor + def process(doc) + errors = [] + (doc.find_by(context: :listing) {_1.style == 'source'}).each do |source| + source.id = "source.#{source.id}" + end + end + end + end +end diff --git a/lib/extensions/source_id_validator.rb b/lib/extensions/source_id_validator.rb new file mode 100644 index 0000000..6e04deb --- /dev/null +++ b/lib/extensions/source_id_validator.rb @@ -0,0 +1,32 @@ +module Nuldoc + module Extensions + class SourceIdValidator < Asciidoctor::Extensions::TreeProcessor + def process(doc) + errors = [] + (doc.find_by(context: :listing) {_1.style == 'source'}).each do |source| + errors << validate_section(source) + end + error_message = errors.compact.join("\n") + unless error_message.empty? + raise "SourceIdValidator (#{doc.attributes['source-file-path']}):\n#{error_message}" + end + end + + private + + def validate_section(source) + id = source.id + unless id + return "Each source MUST have an id." + end + if id.start_with?('source.') + return "Source id (##{id}) MUST NOT start with 'source.', which is appended by `nul`." + end + unless id.match?(/\A[-0-9a-z]+\z/) + return "Source id (##{id}) MUST consist of either hypen, digits or lowercases." + end + nil + end + end + end +end diff --git a/lib/extensions/tags_processor.rb b/lib/extensions/tags_processor.rb new file mode 100644 index 0000000..efbd2a8 --- /dev/null +++ b/lib/extensions/tags_processor.rb @@ -0,0 +1,20 @@ +module Nuldoc + module Extensions + class TagsProcessor < Asciidoctor::Extensions::TreeProcessor + def process(doc) + doc.attributes['tags'] = convert_tags(doc.attributes['tags']) + end + + private + + def convert_tags(tags) + return [] unless tags + + tags + .split(',') + .map(&:strip) + .map { Tag.from_slug(_1) } + end + end + end +end diff --git a/lib/parser.rb b/lib/parser.rb new file mode 100644 index 0000000..8ae3303 --- /dev/null +++ b/lib/parser.rb @@ -0,0 +1,54 @@ +module NulDoc + class Parser + def initialize(common_attributes, content_dir, template_dir) + @common_attributes = common_attributes + @content_dir = content_dir + @template_dir = template_dir + end + + def parse_file(file_path) + Asciidoctor.load_file( + file_path, + backend: :html5, + doctype: :article, + standalone: true, + safe: :unsafe, + template_dirs: [@template_dir], + template_engine: 'erb', + attributes: @common_attributes.merge({ + 'source-file-path' => file_path, + 'href' => file_path.sub(@content_dir, '').sub('.adoc', '/'), + }), + extension_registry: Asciidoctor::Extensions.create do + tree_processor Nuldoc::Extensions::RevisionHistoryProcessor + tree_processor Nuldoc::Extensions::DocumentTitleProcessor + tree_processor Nuldoc::Extensions::LangAttributeProcessor + # tree_processor Nuldoc::Extensions::SectionIdValidator + # tree_processor Nuldoc::Extensions::SourceIdValidator + tree_processor Nuldoc::Extensions::TagsProcessor + + # MUST BE AT THE END + tree_processor Nuldoc::Extensions::SourceIdProcessor + end, + ) + end + + def parse_string(s, copyright_year) + Asciidoctor.convert( + s, + backend: :html5, + doctype: :article, + safe: :unsafe, + template_dirs: [@template_dir], + template_engine: 'erb', + attributes: @common_attributes.merge({ + 'copyright-year' => copyright_year, + }), + extension_registry: Asciidoctor::Extensions.create do + tree_processor Nuldoc::Extensions::LangAttributeProcessor + tree_processor Nuldoc::Extensions::TagsProcessor + end, + ) + end + end +end diff --git a/lib/revision.rb b/lib/revision.rb new file mode 100644 index 0000000..b986a14 --- /dev/null +++ b/lib/revision.rb @@ -0,0 +1,3 @@ +module Nuldoc + Revision = Struct.new(:date, :remark, keyword_init: true) +end diff --git a/lib/tag.rb b/lib/tag.rb new file mode 100644 index 0000000..a7c13b2 --- /dev/null +++ b/lib/tag.rb @@ -0,0 +1,26 @@ +module Nuldoc + Tag = Struct.new(:slug, :label, keyword_init: true) do + LABELS = { + 'conference' => 'カンファレンス', + 'cpp' => 'C++', + 'cpp17' => 'C++ 17', + 'note-to-self' => '備忘録', + 'php' => 'PHP', + 'phpcon' => 'PHP カンファレンス', + 'phperkaigi' => 'PHPerKaigi', + 'python' => 'Python', + 'python3' => 'Python 3', + 'ruby' => 'Ruby', + 'ruby3' => 'Ruby 3', + 'rust' => 'Rust', + 'vim' => 'Vim', + } + + def self.from_slug(slug) + Tag.new( + slug: slug, + label: (LABELS[slug] || raise("No label for tag '#{slug}'")), + ) + end + end +end |
