Class: VivlioStarter::CLI::Metrics::Formatter

Inherits:
Object
  • Object
show all
Defined in:
lib/vivlio_starter/cli/metrics/formatter.rb

Overview

メトリクス出力フォーマッタ

Constant Summary collapse

BAR_WIDTH =
12
BAR_CHAR =
'#'
BAR_EMPTY_CHAR =
'.'
CHAPTER_LABEL_WIDTH =
30
SECTION_LABEL_WIDTH =
36
ELLIPSIS =

切り詰めを示す記号。

''
AMBIGUOUS_WIDTH_CHARS =

非 ASCII だが端末では 1 幅で描かれる記号(East Asian Width = Ambiguous)。 fullwidth_char? は「ASCII でなければ全角」という粗い判定なので、 ここを通さないと 2 桁と数えて桁がずれる。EAW=A を 1 幅と見なすのは terminal-output-notes.md §1 の方針(矢印などは端末で 1 幅が期待値)に合わせたもの。

%w[      ±].freeze
CHAR_COUNT_WIDTH =
6
MATTR_MONOTONOUS_MAX =

「表現が単調」と評価する MATTR の上限(この値以下が単調帯)。 詳細分析の表示(#mattr_evaluation)と章別リストの品質警告 (WarningChecker#quality_warnings)が同じ値を参照することで、 「詳細では単調と出るのに警告は出ない」という乖離を構造的に防ぐ。

0.5
SNIPPET_LIMIT =
24
KANJI_LIST_TOP =
15
RHYTHM_RUN_LIMIT =
5

Instance Method Summary collapse

Constructor Details

#initialize(config_loader) ⇒ Formatter

Returns a new instance of Formatter.



41
42
43
44
45
# File 'lib/vivlio_starter/cli/metrics/formatter.rb', line 41

def initialize(config_loader)
  @config = config_loader
  @vocabulary = config_loader.vocabulary_thresholds
  @labels = config_loader.labels
end

Instance Method Details

#format_basic_info(basic, vocab) ⇒ Object

基本情報セクションを生成する。 主役は本文で、コードと記法は外数としてカッコ内に添える (chapter-volume-calibration-data.md §7.1)。文章が主でコード・記法は従、 という位置づけによる。本文は語彙分析の分母と同じ値なので vocab から採る。



57
58
59
60
61
62
63
# File 'lib/vivlio_starter/cli/metrics/formatter.rb', line 57

def format_basic_info(basic, vocab)
  <<~OUTPUT.chomp
    📊 文章統計 — 基本情報
    - 文字数: #{number_with_comma(vocab.total_char_count)} 文字#{char_breakdown(basic)}
    - 行数: #{number_with_comma(basic.lines)}  OUTPUT
end

#format_chapter_count_summary(count, total_chars) ⇒ Object

章別リスト直後の集計行(合計章数・平均文字数)を生成する。



48
49
50
51
# File 'lib/vivlio_starter/cli/metrics/formatter.rb', line 48

def format_chapter_count_summary(count, total_chars)
  avg = count.positive? ? (total_chars.to_f / count).round : 0
  "📚 合計 #{count} 章 / 平均 #{number_with_comma(avg)} 文字"
end

#format_chapter_line(chapter, max_chars, show_sections, extra_warnings: [], excluded: false) ⇒ Object

章行をフォーマットする(公開メソッド)。 extra_warnings は分量以外の指摘(文章の質)。分量とは算出タイミングが異なり 章構造体に入っていないため、ここで受け取って 1 行に組み立てる (metrics-quality-warnings-spec.md §2.2)。記号は系統ごとに分ける。 excluded は分量判定の対象外の章(前書き・付録・後書き)。✅ も出さない。



169
170
171
172
173
174
175
176
177
178
179
180
181
# File 'lib/vivlio_starter/cli/metrics/formatter.rb', line 169

def format_chapter_line(chapter, max_chars, show_sections, extra_warnings: [], excluded: false)
  bar = render_bar(chapter.chars, max_chars)
  warning = format_advice(chapter.warning, extra_warnings,
                          just_right: just_right?(chapter.chars, thresholds[:chapter], excluded:))

  if show_sections && chapter.sections.any?
    format_chapter_with_sections(chapter, bar, warning, max_chars, excluded:)
  else
    label = padded_label(chapter_label(chapter))
    char_count = format_char_count(chapter.chars)
    "#{label} #{bar} #{char_count}#{warning}"
  end
end

#format_consistency(metrics) ⇒ Object

章間のばらつきセクションを生成する。高め/低めは別行(二段)で示す。



154
155
156
157
158
159
160
161
162
# File 'lib/vivlio_starter/cli/metrics/formatter.rb', line 154

def format_consistency(metrics)
  lines = ['📐 章間のばらつき(本文の章のみ)', '']
  metrics.each do |m|
    lines << "- #{m.label}: 平均 #{format_metric_value(m.mean, m.unit)} / ばらつき ±#{m.stdev.round(1)}"
    lines << "  #{m.high_label}: #{format_outliers(m.high, m.unit)}"
    lines << "  #{m.low_label}: #{format_outliers(m.low, m.unit)}"
  end
  lines.join("\n")
end

#format_content_words(words) ⇒ Object

頻出内容語セクションを生成する。語・品詞は表示幅(全角=2)でそろえる。



130
131
132
133
134
135
136
137
138
139
140
141
# File 'lib/vivlio_starter/cli/metrics/formatter.rb', line 130

def format_content_words(words)
  word_w = words.map { display_width(it.word) }.max
  pos_w = words.map { display_width(it.pos) }.max
  count_w = words.map { it.count.to_s.length }.max

  lines = ["🔤 よく使う言葉(内容語 上位#{words.size}", '']
  words.each_with_index do |w, index|
    rank = (index + 1).to_s.rjust(2)
    lines << "#{rank}. #{pad_to_width(w.word, word_w)}  #{pad_to_width(w.pos, pos_w)}  #{w.count.to_s.rjust(count_w)}"
  end
  lines.join("\n")
end

#format_detailed_analysis(vocab, readability) ⇒ Object

詳細分析セクションを生成する



76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
# File 'lib/vivlio_starter/cli/metrics/formatter.rb', line 76

def format_detailed_analysis(vocab, readability)
  kanji = @vocabulary[:kanji_ratio]
  word = @vocabulary[:word_length]
  <<~OUTPUT.chomp
    📈 詳細分析

    【語彙難度】
    - 漢字比率: #{difficulty_evaluation(vocab.kanji_ratio, kanji)}#{vocab.kanji_ratio.round(1)}%) — 理想的な範囲 #{kanji[:ideal_min]}#{kanji[:ideal_max]}%
    - 平均語長: #{difficulty_evaluation(vocab.avg_word_length, word)}#{vocab.avg_word_length.round(1)} 文字) — 理想的な範囲 #{word[:ideal_min]}#{word[:ideal_max]} 文字
    - 文字種構成: #{character_composition(vocab)}

    【語彙多様度】
    - 使用語彙数: #{number_with_comma(vocab.unique_tokens)} 語(異なり語)/ 総語数 #{number_with_comma(vocab.total_tokens)}    - 語彙の豊かさ: #{mattr_evaluation(vocab.mattr)}(MATTR: #{vocab.mattr.round(2)}      - 評価基準: 0.7 以上=非常に豊富、0.6〜0.7=豊富、0.5〜0.6=標準的、0.5 未満=単調

    【読解難度】
    - 評価: #{readability.label}#{readability_description(readability.label)}    - スコア: #{readability.score.round(1)}  OUTPUT
end

#format_kanji_levels(report) ⇒ Object

漢字レベル(ルビ候補)セクションを生成する。



118
119
120
121
122
123
124
125
126
127
# File 'lib/vivlio_starter/cli/metrics/formatter.rb', line 118

def format_kanji_levels(report)
  lines = ['🈂 漢字レベル(ルビ候補)', '']
  lines << "- レベル内訳: #{report.ratios.map { |label, pct| "#{label} #{pct}%" }.join('')}"
  KanjiLevels::LIST_LABELS.each do |key, title|
    list = report.lists[key]
    lines << kanji_list_line(title, list) unless list.empty?
  end
  lines.concat(kanji_location_lines(report.locations))
  lines.join("\n")
end

#format_long_sentences(sentences) ⇒ Object

「見直したい長い文」セクションを生成する。 章番号・行番号・字数は右詰めで桁をそろえ、縦に位置が揃うようにする。



102
103
104
105
106
107
108
109
110
111
112
113
# File 'lib/vivlio_starter/cli/metrics/formatter.rb', line 102

def format_long_sentences(sentences)
  num_w = column_width(sentences, &:chapter_num)
  line_w = column_width(sentences, &:line)
  len_w = column_width(sentences, &:length)

  lines = ["📝 見直したい長い文(ワースト#{sentences.size}", '']
  sentences.each_with_index do |s, index|
    location = "#{s.chapter_num.to_s.rjust(num_w)}章 L#{s.line.to_s.rjust(line_w)}#{s.length.to_s.rjust(len_w)}字)"
    lines << "#{index + 1}. #{location}: #{snippet(s.text)}"
  end
  lines.join("\n")
end

#format_sentence_rhythm(distribution, runs) ⇒ Object

文末表現のリズムセクションを生成する。連続箇所は 1 件ずつ別行で示す。



146
147
148
149
150
151
# File 'lib/vivlio_starter/cli/metrics/formatter.rb', line 146

def format_sentence_rhythm(distribution, runs)
  lines = ['🎶 文末表現のリズム', '']
  lines << "- 文末の内訳: #{distribution.map { |category, pct| "#{category} #{pct}%" }.join('')}"
  lines << format_monotone_runs(runs)
  lines.join("\n")
end

#format_sentence_structure(basic) ⇒ Object

文構造セクションを生成する



66
67
68
69
70
71
72
73
# File 'lib/vivlio_starter/cli/metrics/formatter.rb', line 66

def format_sentence_structure(basic)
  <<~OUTPUT.chomp
    📊 文章統計 — 文構造
    - 文の数: #{basic.sentences} 文(平均 #{basic.avg_sentence_len.round(1)} 文字/文)
    - 節の数: #{basic.clauses} 節(平均 #{basic.avg_clause_len.round(1)} 文字/節)
    - 読点数: #{basic.commas}  OUTPUT
end