Module: VivlioStarter::CLI::Lint::MazegakiScanner
- Defined in:
- lib/vivlio_starter/cli/lint/mazegaki_scanner.rb
Overview
MeCab の形態素境界を使う交ぜ書き走査。
Constant Summary collapse
- PARTICLE =
交ぜ書きの語が助詞で始まったり終わったりすることは、慣用句を除いて無い。
%w[助詞 助動詞].freeze
- DATA_PATH =
File.('data/mazegaki.tsv', __dir__)
Class Method Summary collapse
-
.available? ⇒ Boolean
MeCab が使えるか。使えない環境では第 2 層を丸ごと諦める—— 導入を促す案内は索引機能(YomiInferrer)がすでに出しており、 lint でも毎回鳴らすと同じ助言が二重になる。.
-
.load_data ⇒ Object
生成物(
rake mazegaki:build)を読む。壊れていても lint 全体は止めない。. -
.longest_at(line, start) ⇒ Object
形態素の頭から、辞書に載っている最長の語を切り出す。 2,000 語を Regexp.union にすると遅いので、長いほうから Hash を引く。.
-
.max_length ⇒ Object
走査の対象になりうる最大の見出し長(Hash を引く回数の上限).
-
.morphemes_of(line) ⇒ Object
形態素の位置と品詞を、元の文字列の添字つきで返す。
-Owakatiの出力を文字数で累積してはならない——MeCab は空白を落とすので、 行に半角空白が 1 つでもあると、それ以降の境界が全部ずれる。 表層形を原文から引き直して添字を取る。. -
.reset! ⇒ Object
テストが辞書を差し替えるための入口。.
-
.scan(line) ⇒ Array<Array>
1 行から [見出し, 漢字表記, 開始, 終了] を拾う。MeCab が無ければ常に空。.
-
.table ⇒ Object
辞書。第 2 層のデータと MECAB_ONLY を 1 つの Hash にする。.
-
.word?(_line, morphemes, start, finish) ⇒ Boolean
マッチが語として成立しているか(判定 3 条件)。 第 2 層の見出しはすべて名詞なので、終了は形態素の切れ目と厳密に一致させる。.
Class Method Details
.available? ⇒ Boolean
MeCab が使えるか。使えない環境では第 2 層を丸ごと諦める—— 導入を促す案内は索引機能(YomiInferrer)がすでに出しており、 lint でも毎回鳴らすと同じ助言が二重になる。
63 64 65 66 67 68 69 70 71 72 73 74 |
# File 'lib/vivlio_starter/cli/lint/mazegaki_scanner.rb', line 63 def available? return @available unless @available.nil? @available = begin require 'natto' @mecab = Natto::MeCab.new true rescue LoadError, StandardError => e Common.log_debug("[lint] 交ぜ書きの第 2 層は無効です(MeCab 不在): #{e.}") false end end |
.load_data ⇒ Object
生成物(rake mazegaki:build)を読む。壊れていても lint 全体は止めない。
139 140 141 142 143 144 145 146 147 148 149 |
# File 'lib/vivlio_starter/cli/lint/mazegaki_scanner.rb', line 139 def load_data File.readlines(DATA_PATH, chomp: true).each_with_object({}) do |line, acc| next if line.start_with?('#') || line.empty? word, expected = line.split("\t", 2) acc[word] = expected if word && expected end rescue SystemCallError => e Common.log_warn("[lint] 交ぜ書き辞書を読み込めませんでした: #{DATA_PATH} (#{e.})") {} end |
.longest_at(line, start) ⇒ Object
形態素の頭から、辞書に載っている最長の語を切り出す。 2,000 語を Regexp.union にすると遅いので、長いほうから Hash を引く。
119 120 121 122 123 124 125 126 |
# File 'lib/vivlio_starter/cli/lint/mazegaki_scanner.rb', line 119 def longest_at(line, start) [max_length, line.size - start].min.downto(1) do |length| word = line[start, length] expected = table[word] return [word, expected, start, start + length] if expected end nil end |
.max_length ⇒ Object
走査の対象になりうる最大の見出し長(Hash を引く回数の上限)
82 |
# File 'lib/vivlio_starter/cli/lint/mazegaki_scanner.rb', line 82 def max_length = @max_length ||= table.keys.map(&:size).max |
.morphemes_of(line) ⇒ Object
形態素の位置と品詞を、元の文字列の添字つきで返す。
-Owakati の出力を文字数で累積してはならない——MeCab は空白を落とすので、
行に半角空白が 1 つでもあると、それ以降の境界が全部ずれる。
表層形を原文から引き直して添字を取る。
98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 |
# File 'lib/vivlio_starter/cli/lint/mazegaki_scanner.rb', line 98 def morphemes_of(line) out = [] pos = 0 @mecab.parse(line) do |node| surface = node.surface next if surface.nil? || surface.empty? index = line.index(surface, pos) break if index.nil? out << [index, index + surface.size, node.feature.split(',').first] pos = index + surface.size end out rescue StandardError => e Common.log_debug("[lint] 形態素解析に失敗しました: #{e.}") [] end |
.reset! ⇒ Object
テストが辞書を差し替えるための入口。
85 86 87 88 89 90 |
# File 'lib/vivlio_starter/cli/lint/mazegaki_scanner.rb', line 85 def reset! @table = nil @max_length = nil @available = nil @mecab = nil end |
.scan(line) ⇒ Array<Array>
1 行から [見出し, 漢字表記, 開始, 終了] を拾う。MeCab が無ければ常に空。
45 46 47 48 49 50 51 52 53 54 55 56 57 58 |
# File 'lib/vivlio_starter/cli/lint/mazegaki_scanner.rb', line 45 def scan(line) return [] if line.nil? || line.empty? return [] unless available? morphemes = morphemes_of(line) return [] if morphemes.empty? hits = [] morphemes.each do |start, _finish, _pos| found = longest_at(line, start) hits << found if found && word?(line, morphemes, found[2], found[3]) end hits end |
.table ⇒ Object
辞書。第 2 層のデータと MECAB_ONLY を 1 つの Hash にする。
77 78 79 |
# File 'lib/vivlio_starter/cli/lint/mazegaki_scanner.rb', line 77 def table @table ||= MazegakiDictionary::MECAB_ONLY.merge(load_data) end |
.word?(_line, morphemes, start, finish) ⇒ Boolean
マッチが語として成立しているか(判定 3 条件)。 第 2 層の見出しはすべて名詞なので、終了は形態素の切れ目と厳密に一致させる。
130 131 132 133 134 135 136 |
# File 'lib/vivlio_starter/cli/lint/mazegaki_scanner.rb', line 130 def word?(_line, morphemes, start, finish) head = morphemes.find { it[0] == start } tail = morphemes.find { it[1] == finish } return false if head.nil? || tail.nil? !PARTICLE.include?(head[2]) && !PARTICLE.include?(tail[2]) end |