jlpt 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +21 -0
  3. data/README.ja.md +246 -0
  4. data/README.md +224 -0
  5. data/assets/logo.png +0 -0
  6. data/bin/jlpt +7 -0
  7. data/lib/jlpt/active_model/validator.rb +43 -0
  8. data/lib/jlpt/analyzers/comparer.rb +44 -0
  9. data/lib/jlpt/analyzers/conjugation.rb +60 -0
  10. data/lib/jlpt/analyzers/corpus_stats.rb +38 -0
  11. data/lib/jlpt/analyzers/dokkai.rb +53 -0
  12. data/lib/jlpt/analyzers/exam_readiness.rb +52 -0
  13. data/lib/jlpt/analyzers/grammar.rb +50 -0
  14. data/lib/jlpt/analyzers/jukugo.rb +46 -0
  15. data/lib/jlpt/analyzers/loanwords.rb +54 -0
  16. data/lib/jlpt/analyzers/onomatopeia.rb +29 -0
  17. data/lib/jlpt/analyzers/pitch_accent.rb +51 -0
  18. data/lib/jlpt/analyzers/pos_profiler.rb +47 -0
  19. data/lib/jlpt/analyzers/simplifier.rb +48 -0
  20. data/lib/jlpt/analyzers/style_profiler.rb +62 -0
  21. data/lib/jlpt/analyzers/syntax.rb +43 -0
  22. data/lib/jlpt/cli.rb +121 -0
  23. data/lib/jlpt/core/analysis_result.rb +45 -0
  24. data/lib/jlpt/core/analyzer.rb +58 -0
  25. data/lib/jlpt/core/cache_manager.rb +90 -0
  26. data/lib/jlpt/core/config.rb +45 -0
  27. data/lib/jlpt/core/dictionary.rb +113 -0
  28. data/lib/jlpt/core/engine.rb +142 -0
  29. data/lib/jlpt/core/kanji.rb +54 -0
  30. data/lib/jlpt/core/kanji_details.rb +60 -0
  31. data/lib/jlpt/core/preprocessor.rb +48 -0
  32. data/lib/jlpt/core/tokenizer.rb +64 -0
  33. data/lib/jlpt/core/vocab_classifier.rb +48 -0
  34. data/lib/jlpt/data/kanji.json +2213 -0
  35. data/lib/jlpt/data/vocab.json +50298 -0
  36. data/lib/jlpt/formatters/batch_processor.rb +41 -0
  37. data/lib/jlpt/formatters/exporter.rb +55 -0
  38. data/lib/jlpt/formatters/furigana.rb +69 -0
  39. data/lib/jlpt/formatters/reporter.rb +72 -0
  40. data/lib/jlpt/version.rb +5 -0
  41. data/lib/jlpt.rb +50 -0
  42. metadata +184 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 351c7b81b049e4ef39a34cacf7d20e1d7ce04f1d302b7ed0033f62d48dd6eef3
4
+ data.tar.gz: 903b1e3d8e7b5054a20ae4cb532325cd823dfe0e9de98800ec9705acd80b8651
5
+ SHA512:
6
+ metadata.gz: 6b5c45194f1469bc1c9e2345b8ea82766a36aa0d21e276f696baa560c7f02a1e4b57f252b9998d1d8e8cf15a2ee1ace6cda520c242ba5e39462fd413b5cb8567
7
+ data.tar.gz: b41d13beb80bcc12238ed986a5c516f5c3d7ea38a5fd2be6e994c39bdf10ffa62c1c0ab023bf0307bdc74ffdc44ff30b7054aba84fce6a7197db7eb585f8ea96
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Jakub Polak
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
data/README.ja.md ADDED
@@ -0,0 +1,246 @@
1
+ <p align="center">
2
+ <img src="assets/logo.png" alt="JLPT Ruby Gem Logo" width="500">
3
+ </p>
4
+
5
+ <h1 align="center">JLPT</h1>
6
+
7
+ <p align="center">
8
+ <a href="https://github.com/kupolak/jlpt/actions/workflows/ci.yml"><img src="https://github.com/kupolak/jlpt/actions/workflows/ci.yml/badge.svg" alt="CI"></a>
9
+ <a href="https://badge.fury.io/rb/jlpt"><img src="https://badge.fury.io/rb/jlpt.svg" alt="Gem Version"></a>
10
+ <a href="https://opensource.org/licenses/MIT"><img src="https://img.shields.io/badge/License-MIT-yellow.svg" alt="License: MIT"></a>
11
+ </p>
12
+
13
+ <p align="center">
14
+ <strong>日本語テキスト解析およびJLPT難易度判定(N5〜N1)のためのRuby Gem</strong>
15
+ </p>
16
+
17
+ <p align="center">
18
+ <a href="README.md">English</a> | <strong>日本語</strong>
19
+ </p>
20
+
21
+ JLPT Gemは、日本語テキストの形態素解析、語彙および漢字のJLPT難易度レベル分類(N5〜N1)、ルビ(ふりがな)生成、品詞分布プロファイリング、言い換え・簡略化提案、試験合格可能性評価、Anki用カード出力などを提供する包括的なライブラリです。
22
+
23
+ ---
24
+
25
+ ## 目次
26
+
27
+ - [インストール](#インストール)
28
+ - [クイックスタート](#クイックスタート)
29
+ - [1. 基本的なテキスト解析](#1-基本的なテキスト解析)
30
+ - [2. グローバル設定とオプション](#2-グローバル設定とオプション)
31
+ - [3. ルビ(ふりがな)・ローマ字生成](#3-ルビふりがなローマ字生成)
32
+ - [4. テキストプロファイラーと分析機能](#4-テキストプロファイラーと分析機能)
33
+ - [5. Ankiカード出力・一括処理](#5-ankiカード出力一括処理)
34
+ - [6. HTML / Markdown ビジュアルレポート](#6-html--markdown-ビジュアルレポート)
35
+ - [7. Rails ActiveModel バリデーション](#7-rails-activemodel-バリデーション)
36
+ - [8. CLI コマンドラインツール](#8-cli-コマンドラインツール)
37
+ - [開発](#開発)
38
+ - [ライセンス](#ライセンス)
39
+
40
+ ---
41
+
42
+ ## インストール
43
+
44
+ `Gemfile` に以下を追加してください:
45
+
46
+ ```ruby
47
+ gem 'jlpt', '~> 1.0.0'
48
+ ```
49
+
50
+ その後、以下のコマンドを実行します:
51
+
52
+ ```bash
53
+ bundle install
54
+ ```
55
+
56
+ または直接インストールします:
57
+
58
+ ```bash
59
+ gem install jlpt
60
+ ```
61
+
62
+ ---
63
+
64
+ ## クイックスタート
65
+
66
+ ### 1. 基本的なテキスト解析
67
+
68
+ ```ruby
69
+ require 'jlpt'
70
+
71
+ result = JLPT.analyze("日本語の勉強はとても面白いです。")
72
+
73
+ result.recommended_level #=> :n5
74
+ result.score #=> 25.0
75
+ result.word_count #=> 7
76
+ result.sentence_count #=> 1
77
+ result.kanji_density #=> 0.44
78
+ ```
79
+
80
+ ### 2. グローバル設定とオプション
81
+
82
+ `JLPT.configure` で動作オプションをカスタマイズできます:
83
+
84
+ ```ruby
85
+ JLPT.configure do |config|
86
+ # ふりがなのデフォルト出力フォーマット (:html, :anki, :markdown)
87
+ config.default_furigana_format = :anki # デフォルト: :html
88
+
89
+ # LRUキャッシュ容量 (メモリ上に保持する解析結果の件数)
90
+ config.cache_capacity = 2000 # デフォルト: 1000
91
+
92
+ # MeCab / natto が未インストール時のPure-Rubyフォールバック
93
+ config.mecab_fallback = true # デフォルト: true
94
+ end
95
+ ```
96
+
97
+ 設定をデフォルト値にリセットするには:
98
+
99
+ ```ruby
100
+ JLPT.reset_config!
101
+ ```
102
+
103
+ #### 設定オプション一覧
104
+
105
+ | オプション / パラメータ | 指定可能な値 | デフォルト | 説明 |
106
+ | :--- | :--- | :--- | :--- |
107
+ | `default_furigana_format` | `:html`, `:anki`, `:markdown` | `:html` | `JLPT::Furigana.render` のデフォルトフォーマット |
108
+ | `cache_capacity` | `Integer` (例: `500`, `2000`) | `1000` | LRUキャッシュの最大件数 |
109
+ | `mecab_fallback` | `true`, `false` | `true` | MeCab非搭載環境でのPure-Rubyトークナイザー利用 |
110
+ | `target_level` | `:n5`, `:n4`, `:n3`, `:n2`, `:n1` | `:n4` | 目標JLPTレベル(試験準備度およびエクスポート用) |
111
+ | `format` (CLI & Reporter) | `:text`, `:json`, `:html`, `:markdown` | `:text` | 出力フォーマット |
112
+
113
+ ---
114
+
115
+ ### 3. ルビ(ふりがな)・ローマ字生成
116
+
117
+ ```ruby
118
+ JLPT::Furigana.render("学校に行く", format: :html)
119
+ #=> "<ruby><rb>学校</rb><rt>がっこう</rt></ruby>に<ruby><rb>行</rb><rt>い</rt></ruby>く"
120
+
121
+ JLPT::Furigana.render("学校に行く", format: :anki)
122
+ #=> "学校[がっこう]に行[い]く"
123
+
124
+ JLPT::Furigana.to_romaji("がっこう")
125
+ #=> "gakkou"
126
+ ```
127
+
128
+ ---
129
+
130
+ ### 4. テキストプロファイラーと分析機能
131
+
132
+ ```ruby
133
+ # 品詞分布プロファイル
134
+ JLPT::PosProfiler.profile("学校に行きます。")
135
+
136
+ # 文法パターン抽出 (N5〜N1)
137
+ JLPT::Grammar.extract("食べてはいけない")
138
+ #=> [{ name: "〜てはいけない", level: :n5, pattern: /.../ }]
139
+
140
+ # 構文・節の複雑さ分析
141
+ JLPT::Syntax.analyze("雨が降ったから、学校に行かなかった。")
142
+ #=> { conjunction_count: 1, conjunctions: ["から"], avg_clauses_per_sentence: 2.0, complexity_index: 50.0 }
143
+
144
+ # JLPT目標レベルに対する準備度評価
145
+ JLPT::ExamReadiness.evaluate("割合", target_level: :n3)
146
+ #=> { target_level: :n3, current_level: :n4, readiness_score: 80.86, status: :moderate }
147
+
148
+ # 敬語・文体プロファイリング
149
+ JLPT::StyleProfiler.profile("ご覧になる")
150
+ #=> { formality_score: 90.0, register: :polite, sonkeigo_count: 1, ... }
151
+
152
+ # 外来語(カタカナ語)抽出
153
+ JLPT::Loanwords.extract("デパートでパンを買う")
154
+ #=> ["デパート", "パン"]
155
+
156
+ # 漢字詳細情報照会 (画数・部首・音訓読み・レベル)
157
+ JLPT::KanjiDetails.lookup("学")
158
+ #=> { character: "学", strokes: 8, radical: "子", onyomi: ["ガク"], kunyomi: ["まな.ぶ"], level: :n5 }
159
+
160
+ # 難解語の言い換え・簡略化提案
161
+ JLPT::Simplifier.simplify("著しい")
162
+ #=> [{ word: "著しい", level: :n1, reading: "いちじるしい", suggestions: ["大きい", "目立つ"] }]
163
+
164
+ # テキスト・教材間比較(ジャカード係数・共通語彙)
165
+ JLPT::Comparer.compare("私と学校に行く", "私と図書館に行く")
166
+ #=> { overlap_ratio: 0.71, shared_vocab: ["私", "と", "に", "行", "く"], shared_kanji: ["私", "行"] }
167
+ ```
168
+
169
+ ---
170
+
171
+ ### 5. Ankiカード出力・一括処理
172
+
173
+ ```ruby
174
+ # 学習目標レベルを超える未知語の抽出
175
+ JLPT::Exporter.find_unknown("急速な発展", target_level: :n4)
176
+
177
+ # Anki用TSV形式で出力
178
+ JLPT::Exporter.to_anki_tsv("急速な発展", target_level: :n4)
179
+
180
+ # 複数章・ドキュメントの一括解析
181
+ JLPT::BatchProcessor.process({ "Chapter 1" => "私と学校", "Chapter 2" => "急速な発展" })
182
+ ```
183
+
184
+ ---
185
+
186
+ ### 6. HTML / Markdown ビジュアルレポート
187
+
188
+ ```ruby
189
+ result = JLPT.analyze("日本語の勉強")
190
+
191
+ # 自己完結型HTMLレポート文字列の生成
192
+ html_report = JLPT::Reporter.render_html(result)
193
+
194
+ # Markdownサマリーテーブルの生成
195
+ md_report = JLPT::Reporter.render_markdown(result)
196
+ ```
197
+
198
+ ---
199
+
200
+ ### 7. Rails ActiveModel バリデーション
201
+
202
+ Railsモデルでユーザー入力の難易度制限を検証します:
203
+
204
+ ```ruby
205
+ class Lesson < ApplicationRecord
206
+ validates :content, jlpt_level: { max_level: :n3 }
207
+ end
208
+ ```
209
+
210
+ ---
211
+
212
+ ### 8. CLI コマンドラインツール
213
+
214
+ ```bash
215
+ # テキスト解析
216
+ jlpt analyze "日本語の勉強"
217
+
218
+ # ふりがな生成 (Anki形式)
219
+ jlpt furigana "漢字の読み方" --format markdown
220
+
221
+ # 未知語をAnki TSV形式でファイル出力
222
+ jlpt export "急速な発展" --target n4 > flashcards.tsv
223
+
224
+ # 漢字詳細・活用・簡略化・統計コマンド
225
+ jlpt kanji "学"
226
+ jlpt conjugate "食べさせられた"
227
+ jlpt simplify "著しい"
228
+ jlpt stats "私と学校"
229
+ ```
230
+
231
+ ---
232
+
233
+ ## 開発
234
+
235
+ テストおよびリンターの実行:
236
+
237
+ ```bash
238
+ bundle exec rspec
239
+ bundle exec rubocop
240
+ ```
241
+
242
+ ---
243
+
244
+ ## ライセンス
245
+
246
+ オープンソースソフトウェアとして [MIT License](LICENSE) の下で利用可能です。
data/README.md ADDED
@@ -0,0 +1,224 @@
1
+ <p align="center">
2
+ <img src="assets/logo.png" alt="JLPT Ruby Gem Logo" width="500">
3
+ </p>
4
+
5
+ <h1 align="center">JLPT</h1>
6
+
7
+ <p align="center">
8
+ <a href="https://github.com/kupolak/jlpt/actions/workflows/ci.yml"><img src="https://github.com/kupolak/jlpt/actions/workflows/ci.yml/badge.svg" alt="CI"></a>
9
+ <a href="https://badge.fury.io/rb/jlpt"><img src="https://badge.fury.io/rb/jlpt.svg" alt="Gem Version"></a>
10
+ <a href="https://opensource.org/licenses/MIT"><img src="https://img.shields.io/badge/License-MIT-yellow.svg" alt="License: MIT"></a>
11
+ </p>
12
+
13
+ <p align="center">
14
+ <strong>A Ruby gem for Japanese text analysis and JLPT difficulty classification (N5–N1).</strong>
15
+ </p>
16
+
17
+ <p align="center">
18
+ <strong>English</strong> | <a href="README.ja.md">日本語</a>
19
+ </p>
20
+
21
+ It parses Japanese text, classifies vocabulary and kanji by JLPT level, generates furigana, profiles part-of-speech distributions, and provides utilities for text simplification, exam readiness evaluation, and Anki card export.
22
+
23
+ ## Table of Contents
24
+
25
+ - [Installation](#installation)
26
+ - [Quick Start](#quick-start)
27
+ - [1. Basic Analysis](#1-basic-analysis)
28
+ - [2. Global Configuration & Options](#2-global-configuration--options)
29
+ - [3. Furigana & Romaji Generation](#3-furigana--romaji-generation)
30
+ - [4. Text Profiling & Analyzers](#4-text-profiling--analyzers)
31
+ - [5. Anki Exporting & Batch Processing](#5-anki-exporting--batch-processing)
32
+ - [6. HTML & Markdown Visual Reports](#6-html--markdown-visual-reports)
33
+ - [7. Rails ActiveModel Integration](#7-rails-activemodel-integration)
34
+ - [8. CLI Runner](#8-cli-runner)
35
+ - [Development](#development)
36
+ - [License](#license)
37
+
38
+ ## Installation
39
+
40
+ Add this line to your application's `Gemfile`:
41
+
42
+ ```ruby
43
+ gem 'jlpt'
44
+ ```
45
+
46
+ And then run:
47
+
48
+ ```bash
49
+ bundle install
50
+ ```
51
+
52
+ Or install it directly:
53
+
54
+ ```bash
55
+ gem install jlpt
56
+ ```
57
+
58
+ ## Quick Start
59
+
60
+ ### 1. Basic Analysis
61
+
62
+ ```ruby
63
+ require 'jlpt'
64
+
65
+ result = JLPT.analyze("日本語の勉強はとても面白いです。")
66
+
67
+ result.recommended_level #=> :n5
68
+ result.score #=> 25.0
69
+ result.word_count #=> 7
70
+ result.sentence_count #=> 1
71
+ result.kanji_density #=> 0.44
72
+ ```
73
+
74
+ ### 2. Global Configuration & Options
75
+
76
+ Customize global library settings using `JLPT.configure`:
77
+
78
+ ```ruby
79
+ JLPT.configure do |config|
80
+ # Default output format for Furigana (:html, :anki, or :markdown)
81
+ config.default_furigana_format = :anki # Default: :html
82
+
83
+ # Capacity for the LRU cache (number of analyzed texts stored in memory)
84
+ config.cache_capacity = 2000 # Default: 1000
85
+
86
+ # Automatic pure-Ruby tokenizer fallback when MeCab/natto gem is absent
87
+ config.mecab_fallback = true # Default: true
88
+ end
89
+ ```
90
+
91
+ To reset configuration back to defaults at any point:
92
+
93
+ ```ruby
94
+ JLPT.reset_config!
95
+ ```
96
+
97
+ #### Summary of Available Options
98
+
99
+ | Option / Parameter | Allowed Values / Symbols | Default | Description |
100
+ | :--- | :--- | :--- | :--- |
101
+ | `default_furigana_format` | `:html`, `:anki`, `:markdown` | `:html` | Default format used by `JLPT::Furigana.render` |
102
+ | `cache_capacity` | `Integer` (e.g. `500`, `2000`) | `1000` | Max entries stored in memory LRU cache |
103
+ | `mecab_fallback` | `true`, `false` | `true` | Enable pure-Ruby tokenizer fallback if MeCab is missing |
104
+ | `target_level` | `:n5`, `:n4`, `:n3`, `:n2`, `:n1` | `:n4` | Target JLPT level for exam readiness & exporter |
105
+ | `format` (CLI & Reporter) | `:text`, `:json`, `:html`, `:markdown` | `:text` | Output format for CLI runner and Reporter |
106
+
107
+ ### 3. Furigana & Romaji Generation
108
+
109
+ ```ruby
110
+ JLPT::Furigana.render("学校に行く", format: :html)
111
+ #=> "<ruby><rb>学校</rb><rt>がっこう</rt></ruby>に<ruby><rb>行</rb><rt>い</rt></ruby>く"
112
+
113
+ JLPT::Furigana.render("学校に行く", format: :anki)
114
+ #=> "学校[がっこう]に行[い]く"
115
+
116
+ JLPT::Furigana.to_romaji("がっこう")
117
+ #=> "gakkou"
118
+ ```
119
+
120
+ ### 4. Text Profiling & Analyzers
121
+
122
+ ```ruby
123
+ # Part-of-Speech Distribution
124
+ JLPT::PosProfiler.profile("学校に行きます。")
125
+
126
+ # Grammar Pattern Extraction
127
+ JLPT::Grammar.extract("食べてはいけない")
128
+ #=> [{ name: "〜てはいけない", level: :n5, pattern: /.../ }]
129
+
130
+ # Syntactic Clause Complexity
131
+ JLPT::Syntax.analyze("雨が降ったから、学校に行かなかった。")
132
+ #=> { conjunction_count: 1, conjunctions: ["から"], avg_clauses_per_sentence: 2.0, complexity_index: 50.0 }
133
+
134
+ # Exam Readiness Evaluation
135
+ JLPT::ExamReadiness.evaluate("割合", target_level: :n3)
136
+ #=> { target_level: :n3, current_level: :n4, readiness_score: 80.86, status: :moderate }
137
+
138
+ # Style & Keigo Profiling
139
+ JLPT::StyleProfiler.profile("ご覧になる")
140
+ #=> { formality_score: 90.0, register: :polite, sonkeigo_count: 1, ... }
141
+
142
+ # Katakana Loanwords Extraction
143
+ JLPT::Loanwords.extract("デパートでパンを買う")
144
+ #=> ["デパート", "パン"]
145
+
146
+ # Kanji Details Lookup
147
+ JLPT::KanjiDetails.lookup("学")
148
+ #=> { character: "学", strokes: 8, radical: "子", onyomi: ["ガク"], kunyomi: ["まな.ぶ"], level: :n5 }
149
+
150
+ # Beginner Text Simplification
151
+ JLPT::Simplifier.simplify("著しい")
152
+ #=> [{ word: "著しい", level: :n1, reading: "いちじるしい", suggestions: ["大きい", "目立つ"] }]
153
+
154
+ # Lesson & Text Comparison
155
+ JLPT::Comparer.compare("私と学校に行く", "私と図書館に行く")
156
+ #=> { overlap_ratio: 0.71, shared_vocab: ["私", "と", "に", "行", "く"], shared_kanji: ["私", "行"] }
157
+ ```
158
+
159
+ ### 5. Anki Exporting & Batch Processing
160
+
161
+ ```ruby
162
+ # Find words above learner target level
163
+ JLPT::Exporter.find_unknown("急速な発展", target_level: :n4)
164
+
165
+ # Export TSV for Anki flashcards
166
+ JLPT::Exporter.to_anki_tsv("急速な発展", target_level: :n4)
167
+
168
+ # Multi-chapter / Document Batch Processing
169
+ JLPT::BatchProcessor.process({ "Chapter 1" => "私と学校", "Chapter 2" => "急速な発展" })
170
+ ```
171
+
172
+ ### 6. HTML & Markdown Visual Reports
173
+
174
+ ```ruby
175
+ result = JLPT.analyze("日本語の勉強")
176
+
177
+ # Render self-contained HTML report string
178
+ html_report = JLPT::Reporter.render_html(result)
179
+
180
+ # Render Markdown summary table
181
+ md_report = JLPT::Reporter.render_markdown(result)
182
+ ```
183
+
184
+ ### 7. Rails ActiveModel Integration
185
+
186
+ Use the built-in ActiveModel validator to ensure user input does not exceed a given JLPT level:
187
+
188
+ ```ruby
189
+ class Lesson < ApplicationRecord
190
+ validates :content, jlpt_level: { max_level: :n3 }
191
+ end
192
+ ```
193
+
194
+ ### 8. CLI Runner
195
+
196
+ ```bash
197
+ # Analyze Japanese text
198
+ jlpt analyze "日本語の勉強"
199
+
200
+ # Generate Furigana annotations in Anki format
201
+ jlpt furigana "漢字の読み方" --format markdown
202
+
203
+ # Export unknown words exceeding N4 level to TSV
204
+ jlpt export "急速な発展" --target n4 > flashcards.tsv
205
+
206
+ # Inspect Kanji details or verb conjugations
207
+ jlpt kanji "学"
208
+ jlpt conjugate "食べさせられた"
209
+ jlpt simplify "著しい"
210
+ jlpt stats "私と学校"
211
+ ```
212
+
213
+ ## Development
214
+
215
+ Run tests and linter:
216
+
217
+ ```bash
218
+ bundle exec rspec
219
+ bundle exec rubocop
220
+ ```
221
+
222
+ ## License
223
+
224
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
data/assets/logo.png ADDED
Binary file
data/bin/jlpt ADDED
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require_relative '../lib/jlpt'
5
+ require_relative '../lib/jlpt/cli'
6
+
7
+ exit(JLPT::CLI.run(ARGV))
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ module JLPT
4
+ module ActiveModel
5
+ # ActiveModel Validator enforcing JLPT level difficulty constraints on Rails models.
6
+ #
7
+ # @example Usage in Rails Model
8
+ # class Lesson < ApplicationRecord
9
+ # validates :content, jlpt_level: { max_level: :n3 }
10
+ # end
11
+ #
12
+ class Validator
13
+ LEVEL_VALS = { n5: 1, n4: 2, n3: 3, n2: 4, n1: 5 }.freeze
14
+
15
+ class << self
16
+ # Validate model attribute against max_level constraint
17
+ #
18
+ # @param record [Object] Rails model instance
19
+ # @param attribute [Symbol] attribute name
20
+ # @param value [String] text content
21
+ # @param max_level [Symbol] maximum allowed level (:n5, :n4, :n3, :n2)
22
+ def validate(record, attribute, value, max_level: :n3)
23
+ return if value.nil? || value.to_s.strip.empty?
24
+
25
+ res = JLPT.analyze(value)
26
+ add_error_if_exceeds(record, attribute, res, max_level)
27
+ end
28
+
29
+ private
30
+
31
+ def add_error_if_exceeds(record, attribute, res, max_level)
32
+ current_val = LEVEL_VALS[res.recommended_level] || 1
33
+ max_val = LEVEL_VALS[max_level.to_s.downcase.to_sym] || 3
34
+ return unless current_val > max_val
35
+
36
+ msg = "difficulty level (#{res.recommended_level.to_s.upcase}) exceeds maximum allowed " \
37
+ "level (#{max_level.to_s.upcase})"
38
+ record.errors.add(attribute, msg) if record.respond_to?(:errors)
39
+ end
40
+ end
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+
3
+ module JLPT
4
+ # Text Similarity and Lesson Vocabulary Comparer.
5
+ #
6
+ # Compares two Japanese texts for vocabulary overlap (Jaccard similarity),
7
+ # shared kanji characters, and relative JLPT difficulty differences.
8
+ #
9
+ module Comparer
10
+ class << self
11
+ # Compare two Japanese texts
12
+ #
13
+ # @param text_a [String] first text
14
+ # @param text_b [String] second text
15
+ # @return [Hash] comparison metrics hash
16
+ def compare(text_a, text_b)
17
+ lem_a = Tokenizer.lemmata(text_a).to_set
18
+ lem_b = Tokenizer.lemmata(text_b).to_set
19
+ return empty_comparison if lem_a.empty? || lem_b.empty?
20
+
21
+ compute_metrics(lem_a, lem_b, text_a, text_b)
22
+ end
23
+
24
+ private
25
+
26
+ def compute_metrics(lem_a, lem_b, text_a, text_b)
27
+ shared_v = (lem_a & lem_b).to_a
28
+ jaccard = (shared_v.length.to_f / (lem_a | lem_b).length).round(2)
29
+
30
+ { overlap_ratio: jaccard, shared_vocab: shared_v, shared_kanji: extract_shared_kanji(text_a, text_b),
31
+ text_a_level: JLPT.analyze(text_a).recommended_level,
32
+ text_b_level: JLPT.analyze(text_b).recommended_level }
33
+ end
34
+
35
+ def extract_shared_kanji(text_a, text_b)
36
+ (Kanji.extract(text_a).to_set & Kanji.extract(text_b).to_set).to_a
37
+ end
38
+
39
+ def empty_comparison
40
+ { overlap_ratio: 0.0, shared_vocab: [], shared_kanji: [], text_a_level: :n5, text_b_level: :n5 }
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,60 @@
1
+ # frozen_string_literal: true
2
+
3
+ module JLPT
4
+ # Verb and Adjective Inflection & Conjugation Analyzer.
5
+ #
6
+ # Identifies grammatical conjugations including te-form, causative, passive,
7
+ # potential, volitional, conditional, and past tense.
8
+ #
9
+ module Conjugation
10
+ class << self
11
+ # Analyze inflection type and form tags of a verb or adjective
12
+ #
13
+ # @param word [String] conjugated Japanese verb or adjective
14
+ # @return [Hash] conjugation details hash
15
+ def analyze(word)
16
+ return empty_result if word.nil? || word.to_s.strip.empty?
17
+
18
+ str = word.to_s.strip
19
+ forms = detect_voice_forms(str) + detect_tense_and_mood_forms(str)
20
+ { word: str, type: detect_type(str), forms: forms.uniq }
21
+ end
22
+
23
+ private
24
+
25
+ def detect_voice_forms(str)
26
+ if str.end_with?('せられた', 'せられる', 'させられた', 'させられる')
27
+ [:causative_passive]
28
+ elsif str.end_with?('させる', 'させた', 'させ')
29
+ [:causative]
30
+ elsif str.end_with?('られる', 'られた', 'れる', 'れた')
31
+ [:passive]
32
+ else
33
+ []
34
+ end
35
+ end
36
+
37
+ def detect_tense_and_mood_forms(str)
38
+ forms = []
39
+ forms << :te_form if str.end_with?('て', 'で')
40
+ forms << :past if str.end_with?('た', 'だ', 'ました', 'でした')
41
+ forms << :volitional if str.end_with?('よう', 'おう', 'ましょう')
42
+ forms << :conditional if str.end_with?('たら', 'ば', 'なら')
43
+ forms
44
+ end
45
+
46
+ def detect_type(str)
47
+ return :suru_verb if str.end_with?('する', 'した', 'して', 'します')
48
+ return :kuru_verb if str.end_with?('くる', 'きた', 'きて', 'きます')
49
+ return :i_adjective if str.end_with?('い', 'かった', 'くて')
50
+ return :na_adjective if str.end_with?('だ', 'です', 'でした')
51
+
52
+ :verb
53
+ end
54
+
55
+ def empty_result
56
+ { word: '', type: :unknown, forms: [] }
57
+ end
58
+ end
59
+ end
60
+ end