Module: VivlioStarter::CLI::PrismLinesCommands

Defined in:
lib/vivlio_starter/cli/prism_lines.rb

Overview

==============================================================================

Module: PrismLinesCommands

Prism.js のコードブロック(

)に行番号を付与するコマンド群。
入力HTMLを解析し、必要なクラスと line-numbers-rows を自動追加して保存する。</p>
<p>提供コマンド:</p>
<pre><code>- prism:lines INPUT_FILE [OUTPUT_FILE]
入力HTML内の Prism.js コードブロックに行番号を追加する。

備考:

- -v/--verbose で詳細ログを表示(ENV には反映しません)。
- OUTPUT_FILE を省略した場合、INPUT_FILE を上書きします。

==============================================================================

Constant Summary collapse

PRISM_LINES_DESC =
{
  short: 'HTMLファイル内のPrism.jsコードブロックに行番号を追加します',
  long: <<~DESC
    指定したHTMLファイル内のPrism.jsコードブロックに行番号を追加します。

    引数:
      INPUT_FILE     入力HTMLファイル(必須)
      OUTPUT_FILE    出力HTMLファイル(省略可、省略時は入力ファイルを上書き)

    オプション:
      -v, --verbose  詳細な処理情報を表示

    使用例:
      vs prism:lines prime.html
      vs prism:lines prime.html prime_with_lines.html
  DESC
}.freeze
ALERT_COMMENT_PATTERN =

Prism コメントトークンの [!] マーカーを赤強調クラスに変換する。 コメント記号(# / // / -- / /* / <!--)は保持し、[!] とその前後の空白 1 つを除去する。 旧 post_replace_list.yml の [!] 赤強調ルール(Prism 出力狙い)をここへ移設したもの。

%r{\A(\#|//|--|/\*|<!--)\s*\[!\]\s?}
LINE_NUMBER_EXEMPT_ANCESTORS =

行番号を付けない枠。実行結果(.output)・端末転写(.terminal)・ テキストの図/アスキーアート(.diagram)の

 は「コードの提示」ではないため、
Prism 行番号の対象外とする(後処理はコンテナ div 化の後に走るため祖先で判定できる)。

'.output, .terminal, .diagram'
START_LINE_MARKER_PATTERN =

figcaption 末尾の開始行マーカー(例: prime.rb#L22-L25)。 パスに # が含まれ得るため、末尾アンカーで最後のマーカーのみ解釈する。

/#L(\d+)(?:-L(\d+))?\z/

Class Method Summary collapse

Class Method Details

.add_prism_line_numbers(input_file, output_file = nil) ⇒ Object

Prism.jsの行番号を追加する処理



73
74
75
76
77
78
79
80
81
82
83
84
85
86
# File 'lib/vivlio_starter/cli/prism_lines.rb', line 73

def add_prism_line_numbers(input_file, output_file = nil)
  document = parse_html(input_file)
  highlight_alert_comments!(document)
  document.css('pre').each do |pre|
    next if line_number_exempt?(pre)

    decorate_pre_tag(pre, document)
  end
  remove_legacy_meta(document)

  target = output_file || input_file
  PostProcessCommands::HtmlParser.save_html_document(target, document)
  log_result(input_file, target)
end

.build_line_numbers_span(document, lines) ⇒ Object

行数分の line-numbers-rows 構造を生成



156
157
158
159
160
161
162
163
164
165
166
# File 'lib/vivlio_starter/cli/prism_lines.rb', line 156

def build_line_numbers_span(document, lines)
  span = Nokogiri::XML::Node.new('span', document)
  span['aria-hidden'] = 'true'
  span['class'] = 'line-numbers-rows'

  lines.times do
    span.add_child(Nokogiri::XML::Node.new('span', document))
  end

  span
end

.combine_class(original, addition) ⇒ Object

既存クラス文字列に安全にクラスを追加



174
175
176
177
# File 'lib/vivlio_starter/cli/prism_lines.rb', line 174

def combine_class(original, addition)
  classes = [original, addition].compact.reject(&:empty?)
  classes.join(' ')
end

.consume_start_line_marker(pre) ⇒ Object

figcaption 末尾の #L 開始行マーカーを消費し、行番号ガターの開始値へ変換する。 マーカーは pre_process の範囲 include(または著者手書きの ```ruby:foo.rb#L5)が フェンス情報文字列に載せたもので、VFM を経て figcaption テキストとして届く。 インライン style の counter-reset は prism.css の counter-reset: linenumber (クラスセレクタ)より優先されるため、CSS 側の変更なしで開始値が変わる。 表示テキストは従来どおりパスのみへ戻す(R8)。



138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
# File 'lib/vivlio_starter/cli/prism_lines.rb', line 138

def consume_start_line_marker(pre)
  figure = pre.parent
  return unless figure&.name == 'figure'

  figcaption = figure.at_css('figcaption')
  return unless figcaption
  return unless (m = figcaption.text.match(START_LINE_MARKER_PATTERN))

  figcaption.content = figcaption.text.sub(START_LINE_MARKER_PATTERN, '')
  start = m[1].to_i
  return if start < 1 # 不正な開始値はマーカー除去のみ行い従来動作(1 始まり)

  pre['data-start'] = start.to_s
  reset = "counter-reset: linenumber #{start - 1}"
  pre['style'] = [pre['style'], reset].compact.reject(&:empty?).join('; ')
end

.decorate_pre_tag(pre, document) ⇒ Object

 要素と内包する  に行番号用クラスと要素を付与

  


122
123
124
125
126
127
128
129
130
# File 'lib/vivlio_starter/cli/prism_lines.rb', line 122

def decorate_pre_tag(pre, document)
  consume_start_line_marker(pre)
  pre[:class] = combine_class(pre[:class], 'line-numbers')
  code = pre.at_css('code')
  return unless code

  code[:class] = combine_class(code[:class], 'line-numbers')
  code.add_child(build_line_numbers_span(document, line_count(pre)))
end

.execute_prism_lines(input_file, output_file = nil) ⇒ Object

Samovar/直接呼び出し用エントリポイント



46
47
48
49
50
51
52
53
54
55
# File 'lib/vivlio_starter/cli/prism_lines.rb', line 46

def execute_prism_lines(input_file, output_file = nil)
  output_file ||= input_file

  unless File.exist?(input_file)
    Common.log_error("エラー: 入力ファイル '#{input_file}' が存在しません")
    return
  end

  add_prism_line_numbers(input_file, output_file)
end

.highlight_alert_comments!(document) ⇒ Object

Prism コメントトークン内の [!] マーカーを赤強調(codered)に変換する。 旧実装は Prism のエンティティ出力 <!-- を文字列マッチしていたが、 Nokogiri のテキストノードではデコード済みの <!-- になるため素の <!-- で書く (旧ルール 2 本=一般コメント+HTML コメントが 1 パターンに統合される)。



98
99
100
101
102
103
104
105
106
107
108
109
110
# File 'lib/vivlio_starter/cli/prism_lines.rb', line 98

def highlight_alert_comments!(document)
  document.css('pre span.token.comment').each do |span|
    # 記法解説用のネストコード(language-markdown の pre 内)は対象外
    next if span.ancestors('pre').any? { |pre| pre['class'].to_s.include?('language-markdown') }

    text_node = span.children.find(&:text?)
    next unless text_node
    next unless (m = text_node.text.match(ALERT_COMMENT_PATTERN))

    text_node.content = text_node.text.sub(ALERT_COMMENT_PATTERN) { "#{m[1]} " }
    span['class'] = "#{span['class']} codered"
  end
end

.included(base) ⇒ Object



43
# File 'lib/vivlio_starter/cli/prism_lines.rb', line 43

def included(base); end

.line_count(pre) ⇒ Object

コードの行数を返す



58
59
60
# File 'lib/vivlio_starter/cli/prism_lines.rb', line 58

def line_count(pre)
  pre.text.count("\n") + 1
end

.line_number_exempt?(pre) ⇒ Boolean

 が行番号免除枠(.output / .terminal / .diagram)の中にあるか。

  

Returns:

  • (Boolean)


117
118
119
# File 'lib/vivlio_starter/cli/prism_lines.rb', line 117

def line_number_exempt?(pre)
  pre.ancestors(LINE_NUMBER_EXEMPT_ANCESTORS).any?
end

.log_result(input_file, output_file) ⇒ Object

処理完了メッセージを出力



180
181
182
183
# File 'lib/vivlio_starter/cli/prism_lines.rb', line 180

def log_result(input_file, output_file)
  suffix = input_file == output_file ? '' : " -> #{output_file}"
  Common.log_success("行番号付与完了: #{input_file}#{suffix}")
end

.parse_html(path) ⇒ Object

HTMLファイルを Nokogiri ドキュメントに変換(HtmlParser に委譲)



89
90
91
92
# File 'lib/vivlio_starter/cli/prism_lines.rb', line 89

def parse_html(path)
  html = File.read(path, encoding: 'UTF-8')
  PostProcessCommands::HtmlParser.parse_html_document(html)
end

.remove_legacy_meta(document) ⇒ Object

不要な Content-Type メタタグを除去



169
170
171
# File 'lib/vivlio_starter/cli/prism_lines.rb', line 169

def remove_legacy_meta(document)
  document.css('meta[http-equiv="Content-Type"]').each(&:remove)
end