kmuto’s blog

はてな社でMackerel CREをやっています。料理と旅行といろんなIT技術

技術書典15に向けて『Hatena Tech Book Vol.2』を作っていた(その2)

その1からの続き。

kmuto.hatenablog.com

テーマについては「あなたの『推し』の技術を教えてください!」に決め、社内チャットで執筆よろしくをお願いして、id:mazco さんと表紙の雰囲気を相談し、id:toya さんにはキャッチコピーを丸投げ(すいません)。

自分は制作全般は任せろーということで、紙面レイアウトの用意とビルド環境を固めていく。制作システムは(当然ながら?)Re:VIEW。手慣れたTeXで作るとして、review-jsbook/review-jlreqについてはカスタマイズのしやすさの観点でreview-jlreqのほうにした。

サイズはB5。A5かB5かどっちかという選択で、ある程度の分量になりそうという推測と、コードの折り返しを減らしたいという観点からチョイス。紙面デザインについては id:mazco さんの時間確保が厳しめ & 自分にはデザインセンスがないので、王道的な級数・文字数を参考にしつつ、少しばかりのはてなっぽさをイメージして章・節・項の飾りを付けていった。

基本版面の作り方としては、review-init --wizardで全体レイアウトワイヤーフレームを見ながら級数・行あたり文字数・行数・天・ノドを調整し、電子版/同人誌系を一式チェックして書き出し。書き出されたconfig.ymlとconfig-ebook.ymlで微妙な小数点以下数値になっているところは扱いやすいよう整数値やQ/H単位に直した。そうそう、review-jlreqのドキュメントでpaperパラメータを「b5」と書いていたのだが、review-jlreqの場合はこれだとひとまわり小さいISO B5になってしまうのでJIS B5のために「b5j」とする必要があった。あとでドキュメントなどいくつか直しとこう……。

review-init --wizardモード

ほかにもいろいろTeXまわりの調整をしたけど、長くなるので次回以降でポツポツ拾って紹介しこうと思う。

また、Re:VIEWフォーマットで原稿を書くのはまぁまぁハードル高いと思うし、私もそんなにお勧めしないので、Markdownで書いたものをpandoc2reviewで内部変換する仕組みも含めておいた。商業書籍であれば細かな調整のために終盤は完全にRe:VIEWフォーマットに置き換えることもしていたけど、今回はスピード優先で各自の直しやすさを重点に皆Markdownで書いてそのまま最後まで済ませている(自分はRe:VIEWでそのまま書いたが)。

github.com

MarkdownRe:VIEWを混ぜる仕組みについては Re:VIEW knowledgeの記事で書いたとおり。ちなみに技術書典向けでよく使われているTechBooster/ReVIEW-Templateでもこの仕組みは入れてある(私が仕込んだとも言う)。

とはいえ、いくつか追加したので以下に挙げておこう。

lib/tasks/z01_pandoc2review.rake

# Copyright (c) 2020-2022 Kenshi Muto
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.

require 'fileutils'
require 'yaml'
require 'date'

def make_mdre(ch, p2r, path)
  # 2023/10/1 サブパス有効化
  subch = "articles/#{ch}"
  if File.exist?(subch) # re file
    FileUtils.cp(subch, path)
  elsif File.exist?(subch.sub(/\.re\Z/, '.md')) # md file
    system("#{p2r} #{subch.sub(/\.re\Z/, '.md')} > #{path}/#{ch}")
  end
end

def yaml_load_file_compatible(file)
  if YAML.respond_to?(:safe_load_file)
    YAML.safe_load_file(file, aliases: true, permitted_classes: [Date])
  else
    File.open(file, 'rt:bom|utf-8') do |f|
      begin
        # < Ruby 3.1
        YAML.safe_load(f, filename: file, aliases: true, permitted_classes: [Date])
      rescue ArgumentError
        # < Ruby 2.7
        YAML.safe_load(f, [Date])
      rescue Psych::DisallowedClass
        # < Ruby 2.5
        YAML.safe_load(File.read(file), [Date])
      end
    end
  end
end

desc 'run pandoc2review'
task :pandoc2review do
  config = yaml_load_file_compatible('config.yml')
  if config['contentdir'] == '_refiles'
    path = '_refiles'
    p2r = './pandoc2review-wrapper.rb'

    unless File.exist?(path)
      Dir.mkdir(path)
      File.write("#{path}/THIS_FOLDER_IS_TEMPORARY", '')
    end

    catalog = yaml_load_file_compatible('catalog.yml')
    %w(PREDEF CHAPS APPENDIX POSTDEF).each do |block|
      if catalog[block].kind_of?(Array)
        catalog[block].each do |ch|
          if ch.kind_of?(Hash) # Parts
            ch.each_pair do |k, v|
              make_mdre(k, p2r, path)
              # Chapters
              v.each {|subch| make_mdre(subch, p2r, path) }
            end
          elsif ch.kind_of?(String) # Chapters
            make_mdre(ch, p2r, path)
          end
        end
      end
    end
  end
end

CLEAN.include('_refiles')
Rake::Task[BOOK_PDF].enhance([:pandoc2review])
Rake::Task[BOOK_EPUB].enhance([:pandoc2review])
Rake::Task[WEBROOT].enhance([:pandoc2review])
Rake::Task[TEXTROOT].enhance([:pandoc2review])
Rake::Task[TOPROOT].enhance([:pandoc2review])
Rake::Task[IDGXMLROOT].enhance([:pandoc2review])

lib/tasks/z02_tbfsetup.rake

require 'fileutils'

desc 'startup for hatena-techbook'
task :startup do
  unless File.exist?('images')
    FileUtils.ln_s('articles/images', 'images')
  end
end

Rake::Task[BOOK_PDF].enhance([:startup])
Rake::Task[BOOK_EPUB].enhance([:startup])
Rake::Task[WEBROOT].enhance([:startup])
Rake::Task[TEXTROOT].enhance([:startup])
Rake::Task[TOPROOT].enhance([:startup])
Rake::Task[IDGXMLROOT].enhance([:startup])

config.yml

 ...
contentdir: _refiles

あとはarticlesフォルダにMarkdownファイルやRe:VIEWファイル、articles/imagesに図版(サブフォルダも可能)を置いていき、catalog.ymlにはMarkdownファイルでも「〜.re」の名前で登録していくだけで、ビルド実行時にMarkdownRe:VIEW変換が内部で行われる。あ、作業フォルダ直下にあるimagesはシンボリックリンクで作るようにしているので、articles/imagesのほうに移動しておいてね。

執筆者向けにはRe:VIEWファイルとMarkdownファイルで書いた例も入れておいた。

Linux環境でおおよそ紙面デザイン・スタイル調整ができたところで、あとは業務用のmacOSやCIなどどこでもビルドできるように、Dockerイメージを用意していく。最初はvvakame/reviewそのまま使えるじゃろと思っていたんだけど、BizUDとNewPxTextを使っているので追加パッケージが必要だった。kenshimuto/reviewとして派生させてDockerHubにころがした。

hub.docker.com