kotoshu 0.3.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.
- checksums.yaml +7 -0
- data/.rspec +3 -0
- data/.rubocop.yml +18 -0
- data/CHANGELOG.md +182 -0
- data/CLAUDE.md +172 -0
- data/CODE_OF_CONDUCT.md +132 -0
- data/LICENSE +31 -0
- data/README.adoc +955 -0
- data/Rakefile +12 -0
- data/SECURITY.md +93 -0
- data/examples/01_basic_word_checking.rb +38 -0
- data/examples/02_text_document_checking.rb +77 -0
- data/examples/03_dictionary_backends.rb +137 -0
- data/examples/04_trie_data_structure.rb +146 -0
- data/examples/05_suggestion_algorithms.rb +239 -0
- data/examples/06_configuration_advanced.rb +287 -0
- data/examples/07_multi_language_dictionaries.rb +278 -0
- data/exe/kotoshu +6 -0
- data/lib/kotoshu/algorithms/capitalization.rb +276 -0
- data/lib/kotoshu/algorithms/lookup.rb +876 -0
- data/lib/kotoshu/algorithms/ngram_suggest.rb +270 -0
- data/lib/kotoshu/algorithms/permutations.rb +283 -0
- data/lib/kotoshu/algorithms/phonet_suggest.rb +167 -0
- data/lib/kotoshu/algorithms/suggest.rb +575 -0
- data/lib/kotoshu/algorithms.rb +14 -0
- data/lib/kotoshu/analyzers/semantic_analyzer.rb +295 -0
- data/lib/kotoshu/cache/base_cache.rb +596 -0
- data/lib/kotoshu/cache/cache.rb +91 -0
- data/lib/kotoshu/cache/frequency_cache.rb +224 -0
- data/lib/kotoshu/cache/language_cache.rb +454 -0
- data/lib/kotoshu/cache/lookup_cache.rb +166 -0
- data/lib/kotoshu/cache/model_cache.rb +513 -0
- data/lib/kotoshu/cache/suggestion_cache.rb +113 -0
- data/lib/kotoshu/cache.rb +40 -0
- data/lib/kotoshu/cli/auto_setup.rb +71 -0
- data/lib/kotoshu/cli/batch_reporter.rb +315 -0
- data/lib/kotoshu/cli/cache_command.rb +356 -0
- data/lib/kotoshu/cli/display_formatter.rb +431 -0
- data/lib/kotoshu/cli/errors.rb +36 -0
- data/lib/kotoshu/cli/interactive_reviewer.rb +319 -0
- data/lib/kotoshu/cli/language_resolver.rb +91 -0
- data/lib/kotoshu/cli/navigation_manager.rb +272 -0
- data/lib/kotoshu/cli/progress_reporter.rb +114 -0
- data/lib/kotoshu/cli/status_report.rb +130 -0
- data/lib/kotoshu/cli.rb +627 -0
- data/lib/kotoshu/commands/cache_command.rb +424 -0
- data/lib/kotoshu/commands/check_command.rb +312 -0
- data/lib/kotoshu/commands/model_command.rb +295 -0
- data/lib/kotoshu/components/passthrough_spell_checker.rb +72 -0
- data/lib/kotoshu/components/pos_tagger.rb +98 -0
- data/lib/kotoshu/components/spell_checker.rb +73 -0
- data/lib/kotoshu/components/synthesizer.rb +60 -0
- data/lib/kotoshu/components/tokenizer.rb +58 -0
- data/lib/kotoshu/components/whitespace_tokenizer.rb +96 -0
- data/lib/kotoshu/configuration/builder.rb +209 -0
- data/lib/kotoshu/configuration/resolver.rb +124 -0
- data/lib/kotoshu/configuration.rb +702 -0
- data/lib/kotoshu/core/exceptions.rb +165 -0
- data/lib/kotoshu/core/indexed_dictionary.rb +291 -0
- data/lib/kotoshu/core/models/affix_rule.rb +260 -0
- data/lib/kotoshu/core/models/result/document_result.rb +263 -0
- data/lib/kotoshu/core/models/result/word_result.rb +203 -0
- data/lib/kotoshu/core/models/word.rb +142 -0
- data/lib/kotoshu/core/trie/builder.rb +119 -0
- data/lib/kotoshu/core/trie/node.rb +94 -0
- data/lib/kotoshu/core/trie/trie.rb +249 -0
- data/lib/kotoshu/core.rb +28 -0
- data/lib/kotoshu/data/common_words/de.yml +1800 -0
- data/lib/kotoshu/data/common_words/en.yml +1215 -0
- data/lib/kotoshu/data/common_words/es.yml +750 -0
- data/lib/kotoshu/data/common_words/fr.yml +1015 -0
- data/lib/kotoshu/data/common_words/pt.yml +870 -0
- data/lib/kotoshu/data/common_words/ru.yml +484 -0
- data/lib/kotoshu/data/common_words_loader.rb +152 -0
- data/lib/kotoshu/data_structures/bloom_filter.rb +176 -0
- data/lib/kotoshu/debug_logger.rb +146 -0
- data/lib/kotoshu/debug_mode.rb +134 -0
- data/lib/kotoshu/defaults.rb +86 -0
- data/lib/kotoshu/dictionaries/catalog.rb +817 -0
- data/lib/kotoshu/dictionary/base.rb +237 -0
- data/lib/kotoshu/dictionary/cspell.rb +254 -0
- data/lib/kotoshu/dictionary/custom.rb +224 -0
- data/lib/kotoshu/dictionary/hunspell.rb +526 -0
- data/lib/kotoshu/dictionary/plain_text.rb +282 -0
- data/lib/kotoshu/dictionary/repository.rb +248 -0
- data/lib/kotoshu/dictionary/unified.rb +260 -0
- data/lib/kotoshu/dictionary/unix_words.rb +218 -0
- data/lib/kotoshu/documents/asciidoc_document.rb +441 -0
- data/lib/kotoshu/documents/document.rb +229 -0
- data/lib/kotoshu/documents/location.rb +139 -0
- data/lib/kotoshu/documents/markdown_document.rb +389 -0
- data/lib/kotoshu/documents/plain_text_document.rb +147 -0
- data/lib/kotoshu/embeddings/embedding_pipeline.rb +244 -0
- data/lib/kotoshu/embeddings/lru_cache.rb +233 -0
- data/lib/kotoshu/embeddings/onnx_runtime_model.rb +388 -0
- data/lib/kotoshu/embeddings/protocol.rb +83 -0
- data/lib/kotoshu/embeddings/protocols.rb +17 -0
- data/lib/kotoshu/embeddings/registry.rb +182 -0
- data/lib/kotoshu/embeddings/search.rb +192 -0
- data/lib/kotoshu/embeddings/similarity_engine.rb +248 -0
- data/lib/kotoshu/embeddings/similarity_search.rb +331 -0
- data/lib/kotoshu/embeddings/vocabulary.rb +257 -0
- data/lib/kotoshu/embeddings.rb +97 -0
- data/lib/kotoshu/fluent_checker.rb +91 -0
- data/lib/kotoshu/grammar/pattern_matchers/base_matcher.rb +48 -0
- data/lib/kotoshu/grammar/pattern_matchers/double_negative_matcher.rb +105 -0
- data/lib/kotoshu/grammar/pattern_matchers/possessive_context_matcher.rb +77 -0
- data/lib/kotoshu/grammar/pattern_matchers/vowel_sound_matcher.rb +83 -0
- data/lib/kotoshu/grammar/rule.rb +95 -0
- data/lib/kotoshu/grammar/rule_engine.rb +111 -0
- data/lib/kotoshu/grammar/rule_loader.rb +31 -0
- data/lib/kotoshu/grammar.rb +18 -0
- data/lib/kotoshu/integrity/audit_log.rb +88 -0
- data/lib/kotoshu/integrity/manifest.rb +117 -0
- data/lib/kotoshu/integrity/net_http.rb +46 -0
- data/lib/kotoshu/integrity.rb +25 -0
- data/lib/kotoshu/keyboard/layout.rb +115 -0
- data/lib/kotoshu/keyboard/layouts/azerty.rb +57 -0
- data/lib/kotoshu/keyboard/layouts/dvorak.rb +56 -0
- data/lib/kotoshu/keyboard/layouts/jcuken.rb +59 -0
- data/lib/kotoshu/keyboard/layouts/qwerty.rb +54 -0
- data/lib/kotoshu/keyboard/layouts/qwertz.rb +57 -0
- data/lib/kotoshu/keyboard/registry.rb +146 -0
- data/lib/kotoshu/keyboard.rb +60 -0
- data/lib/kotoshu/language/detector.rb +242 -0
- data/lib/kotoshu/language/identifier.rb +378 -0
- data/lib/kotoshu/language/languages/base.rb +256 -0
- data/lib/kotoshu/language/normalizer/base.rb +137 -0
- data/lib/kotoshu/language/registry.rb +147 -0
- data/lib/kotoshu/language/resources/ar/common_words.txt +6753 -0
- data/lib/kotoshu/language/resources/ar/confusion_sets.txt +11 -0
- data/lib/kotoshu/language/resources/de/common_words.txt +10003 -0
- data/lib/kotoshu/language/resources/de/confusion_sets.txt +246 -0
- data/lib/kotoshu/language/resources/en/common_words.txt +9979 -0
- data/lib/kotoshu/language/resources/en/confusion_sets.txt +871 -0
- data/lib/kotoshu/language/resources/es/common_words.txt +9992 -0
- data/lib/kotoshu/language/resources/es/confusion_sets.txt +17 -0
- data/lib/kotoshu/language/resources/fr/common_words.txt +9993 -0
- data/lib/kotoshu/language/resources/fr/confusion_sets.txt +76 -0
- data/lib/kotoshu/language/resources/pt/common_words.txt +9977 -0
- data/lib/kotoshu/language/resources/pt/confusion_sets.txt +18 -0
- data/lib/kotoshu/language/resources/ru/common_words.txt +9951 -0
- data/lib/kotoshu/language/resources/ru/confusion_sets.txt +5 -0
- data/lib/kotoshu/language/tokenizer/base.rb +170 -0
- data/lib/kotoshu/language/tokenizer/french_tokenizer.rb +170 -0
- data/lib/kotoshu/language/tokenizer/german_tokenizer.rb +41 -0
- data/lib/kotoshu/language/tokenizer/japanese_tokenizer.rb +60 -0
- data/lib/kotoshu/language/tokenizer/latin_tokenizer.rb +141 -0
- data/lib/kotoshu/language/tokenizer/portuguese_tokenizer.rb +160 -0
- data/lib/kotoshu/language/tokenizer/russian_tokenizer.rb +95 -0
- data/lib/kotoshu/language/tokenizer/spanish_tokenizer.rb +122 -0
- data/lib/kotoshu/language.rb +99 -0
- data/lib/kotoshu/languages/de/language.rb +546 -0
- data/lib/kotoshu/languages/en/language.rb +448 -0
- data/lib/kotoshu/languages/es/language.rb +459 -0
- data/lib/kotoshu/languages/fr/language.rb +493 -0
- data/lib/kotoshu/languages/ja/language.rb +477 -0
- data/lib/kotoshu/languages/pt/language.rb +423 -0
- data/lib/kotoshu/languages/ru/language.rb +404 -0
- data/lib/kotoshu/languages.rb +43 -0
- data/lib/kotoshu/metrics_collector.rb +222 -0
- data/lib/kotoshu/metrics_module.rb +110 -0
- data/lib/kotoshu/models/context.rb +119 -0
- data/lib/kotoshu/models/embedding_model.rb +182 -0
- data/lib/kotoshu/models/fasttext_model.rb +220 -0
- data/lib/kotoshu/models/nearest_neighbor.rb +87 -0
- data/lib/kotoshu/models/onnx_model.rb +333 -0
- data/lib/kotoshu/models/semantic_error.rb +165 -0
- data/lib/kotoshu/models/suggestion.rb +106 -0
- data/lib/kotoshu/models/word_embedding.rb +107 -0
- data/lib/kotoshu/paths.rb +53 -0
- data/lib/kotoshu/personal_dictionary.rb +94 -0
- data/lib/kotoshu/plugins/plugin.rb +61 -0
- data/lib/kotoshu/plugins/registry.rb +120 -0
- data/lib/kotoshu/project_config.rb +76 -0
- data/lib/kotoshu/readers/aff_data.rb +356 -0
- data/lib/kotoshu/readers/aff_reader.rb +375 -0
- data/lib/kotoshu/readers/condition_checker.rb +142 -0
- data/lib/kotoshu/readers/dic_reader.rb +118 -0
- data/lib/kotoshu/readers/file_reader.rb +347 -0
- data/lib/kotoshu/readers/lookup_builder.rb +299 -0
- data/lib/kotoshu/readers/readers.rb +6 -0
- data/lib/kotoshu/readers.rb +9 -0
- data/lib/kotoshu/resource_bundle.rb +30 -0
- data/lib/kotoshu/resource_manager.rb +295 -0
- data/lib/kotoshu/results/result.rb +165 -0
- data/lib/kotoshu/scripts/fasttext_to_onnx.py +275 -0
- data/lib/kotoshu/source_registry.rb +74 -0
- data/lib/kotoshu/spellchecker/parallel_checker.rb +90 -0
- data/lib/kotoshu/spellchecker.rb +298 -0
- data/lib/kotoshu/string_metrics.rb +153 -0
- data/lib/kotoshu/suggestions/context.rb +55 -0
- data/lib/kotoshu/suggestions/generator.rb +175 -0
- data/lib/kotoshu/suggestions/pipeline.rb +135 -0
- data/lib/kotoshu/suggestions/strategies/base_strategy.rb +296 -0
- data/lib/kotoshu/suggestions/strategies/composite_strategy.rb +140 -0
- data/lib/kotoshu/suggestions/strategies/edit_distance_strategy.rb +671 -0
- data/lib/kotoshu/suggestions/strategies/keyboard_proximity_strategy.rb +228 -0
- data/lib/kotoshu/suggestions/strategies/ngram_strategy.rb +130 -0
- data/lib/kotoshu/suggestions/strategies/phonetic_strategy.rb +329 -0
- data/lib/kotoshu/suggestions/strategies/semantic_strategy.rb +316 -0
- data/lib/kotoshu/suggestions/strategies/symspell_strategy.rb +275 -0
- data/lib/kotoshu/suggestions/suggestion.rb +174 -0
- data/lib/kotoshu/suggestions/suggestion_set.rb +238 -0
- data/lib/kotoshu/version.rb +5 -0
- data/lib/kotoshu.rb +493 -0
- data/script/validate_all_dictionaries.rb +444 -0
- data/sig/kotoshu.rbs +4 -0
- data/test_oop.rb +79 -0
- metadata +298 -0
|
@@ -0,0 +1,431 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative '../models/context'
|
|
4
|
+
require_relative '../models/semantic_error'
|
|
5
|
+
|
|
6
|
+
module Kotoshu
|
|
7
|
+
module Cli
|
|
8
|
+
# Formats output for interactive review mode.
|
|
9
|
+
#
|
|
10
|
+
# Provides methods to display errors, context, suggestions,
|
|
11
|
+
# and navigation prompts in a user-friendly CLI format.
|
|
12
|
+
#
|
|
13
|
+
# @example Displaying an error
|
|
14
|
+
# formatter = DisplayFormatter.new(verbose: true)
|
|
15
|
+
# puts formatter.issue_screen(error, index: 1, total: 10)
|
|
16
|
+
class DisplayFormatter
|
|
17
|
+
# ANSI color codes for terminal output
|
|
18
|
+
COLORS = {
|
|
19
|
+
error: "\e[31m", # Red
|
|
20
|
+
warning: "\e[33m", # Yellow
|
|
21
|
+
success: "\e[32m", # Green
|
|
22
|
+
info: "\e[36m", # Cyan
|
|
23
|
+
dim: "\e[2m", # Dim
|
|
24
|
+
bold: "\e[1m", # Bold
|
|
25
|
+
reset: "\e[0m" # Reset
|
|
26
|
+
}.freeze
|
|
27
|
+
|
|
28
|
+
# Confidence level indicators
|
|
29
|
+
CONFIDENCE_LABELS = {
|
|
30
|
+
high: '✓ High',
|
|
31
|
+
medium: '~ Medium',
|
|
32
|
+
low: '? Low'
|
|
33
|
+
}.freeze
|
|
34
|
+
|
|
35
|
+
attr_reader :verbose, :color_enabled
|
|
36
|
+
|
|
37
|
+
# Create a new display formatter.
|
|
38
|
+
#
|
|
39
|
+
# @param verbose [Boolean] Enable verbose output
|
|
40
|
+
# @param color_enabled [Boolean] Enable ANSI colors (default: true for TTY)
|
|
41
|
+
def initialize(verbose: false, color_enabled: nil)
|
|
42
|
+
@verbose = verbose
|
|
43
|
+
@color_enabled = color_enabled.nil? ? $stdout.tty? : color_enabled
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# Display summary screen for document review.
|
|
47
|
+
#
|
|
48
|
+
# Shows error breakdown by type and confidence, plus navigation hints.
|
|
49
|
+
#
|
|
50
|
+
# @param document [Document] The document being reviewed
|
|
51
|
+
# @param navigation [NavigationManager] Navigation state
|
|
52
|
+
# @return [String] Formatted summary screen
|
|
53
|
+
def summary_screen(document, navigation)
|
|
54
|
+
stats = navigation.statistics
|
|
55
|
+
|
|
56
|
+
lines = []
|
|
57
|
+
lines << ""
|
|
58
|
+
lines << colorize("═══ Document Review Summary", :bold)
|
|
59
|
+
lines << ""
|
|
60
|
+
lines << "Document: #{document.name}"
|
|
61
|
+
lines << "Format: #{Document::FORMATS[document.format]}"
|
|
62
|
+
lines << "Language: #{document.language_code}"
|
|
63
|
+
lines << ""
|
|
64
|
+
lines << colorize("Error Summary", :bold)
|
|
65
|
+
lines << "─" * 40
|
|
66
|
+
|
|
67
|
+
# Total counts
|
|
68
|
+
lines << "Total errors found: #{stats[:total]}"
|
|
69
|
+
lines << " • Pending: #{stats[:pending]}"
|
|
70
|
+
lines << " • Modified: #{stats[:modified]}"
|
|
71
|
+
lines << " • Skipped: #{stats[:skipped]}"
|
|
72
|
+
lines << ""
|
|
73
|
+
|
|
74
|
+
# Breakdown by type
|
|
75
|
+
if stats[:by_type]&.any?
|
|
76
|
+
lines << colorize("By Type", :bold)
|
|
77
|
+
stats[:by_type].each do |type, count|
|
|
78
|
+
label = SemanticError::ERROR_TYPES[type] || type.to_s.capitalize
|
|
79
|
+
lines << " • #{label}: #{count}"
|
|
80
|
+
end
|
|
81
|
+
lines << ""
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
# Breakdown by confidence
|
|
85
|
+
if stats[:by_confidence]&.any?
|
|
86
|
+
lines << colorize("By Confidence", :bold)
|
|
87
|
+
lines << " • High (>0.8): #{stats[:by_confidence][:high]}"
|
|
88
|
+
lines << " • Medium (0.5-0.8): #{stats[:by_confidence][:medium]}"
|
|
89
|
+
lines << " • Low (≤0.5): #{stats[:by_confidence][:low]}"
|
|
90
|
+
lines << ""
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
# Navigation hints
|
|
94
|
+
lines << colorize("Navigation", :bold)
|
|
95
|
+
lines << " [Enter] Next error [s] Skip [a] Accept suggestion"
|
|
96
|
+
lines << " [b] Back [q] Quit [l] List all errors"
|
|
97
|
+
lines << " [j] Jump to error [h] Help [?] Show this summary"
|
|
98
|
+
lines << ""
|
|
99
|
+
|
|
100
|
+
lines.join("\n")
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
# Display individual error screen.
|
|
104
|
+
#
|
|
105
|
+
# Shows the error with context, suggestions, and action prompt.
|
|
106
|
+
#
|
|
107
|
+
# @param error [SemanticError] The error to display
|
|
108
|
+
# @param index [Integer] Current error index (1-based)
|
|
109
|
+
# @param total [Integer] Total number of errors
|
|
110
|
+
# @param show_context [Boolean] Show context window (default: true)
|
|
111
|
+
# @return [String] Formatted error screen
|
|
112
|
+
def issue_screen(error, index:, total:, show_context: true)
|
|
113
|
+
lines = []
|
|
114
|
+
lines << ""
|
|
115
|
+
lines << colorize("═" * 70, :bold)
|
|
116
|
+
lines << colorize("Error #{index} of #{total}", :bold) + " — #{error_type_label(error.error_type)}"
|
|
117
|
+
lines << colorize("═" * 70, :bold)
|
|
118
|
+
lines << ""
|
|
119
|
+
|
|
120
|
+
# Error location
|
|
121
|
+
lines << colorize("Location:", :bold) + " #{error.location}"
|
|
122
|
+
lines << ""
|
|
123
|
+
|
|
124
|
+
# Error with highlighting
|
|
125
|
+
lines << colorize("Found:", :error) + " #{highlight_in_context(error)}"
|
|
126
|
+
lines << ""
|
|
127
|
+
|
|
128
|
+
# Context window
|
|
129
|
+
if show_context && error.context
|
|
130
|
+
lines << colorize("Context:", :bold)
|
|
131
|
+
lines << format_context(error.context)
|
|
132
|
+
lines << ""
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
# Confidence indicator
|
|
136
|
+
conf_label = confidence_label(error.confidence)
|
|
137
|
+
lines << colorize("Confidence:", :bold) + " #{conf_label} (#{(error.confidence * 100).round(1)}%)"
|
|
138
|
+
lines << ""
|
|
139
|
+
|
|
140
|
+
# Suggestions
|
|
141
|
+
if error.suggestions&.any?
|
|
142
|
+
lines << colorize("Suggestions:", :bold)
|
|
143
|
+
lines << format_suggestions(error.suggestions)
|
|
144
|
+
lines << ""
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
# Action prompt
|
|
148
|
+
lines << colorize("Actions:", :bold) + " [Enter] Next [1-#{error.suggestions&.size || 0}] Accept [s] Skip [b] Back [q] Quit"
|
|
149
|
+
lines << ""
|
|
150
|
+
|
|
151
|
+
lines.join("\n")
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
# Highlight the error word within its context.
|
|
155
|
+
#
|
|
156
|
+
# @param error [SemanticError] The error to highlight
|
|
157
|
+
# @return [String] Highlighted error with context
|
|
158
|
+
def highlight_error(error)
|
|
159
|
+
return error.original unless error.context
|
|
160
|
+
|
|
161
|
+
ctx = error.context
|
|
162
|
+
highlighted = ctx.current.gsub(/\b#{Regexp.escape(error.original)}\b/i) do |match|
|
|
163
|
+
colorize(match, :error)
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
# Truncate if too long
|
|
167
|
+
if highlighted.length > 100
|
|
168
|
+
"..." + highlighted[-100..-1]
|
|
169
|
+
else
|
|
170
|
+
highlighted
|
|
171
|
+
end
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
# Format suggestions list with confidence scores.
|
|
175
|
+
#
|
|
176
|
+
# @param suggestions [Array<Suggestion>] List of suggestions
|
|
177
|
+
# @param max_display [Integer] Maximum suggestions to show (default: 5)
|
|
178
|
+
# @return [String] Formatted suggestions
|
|
179
|
+
def format_suggestions(suggestions, max_display: 5)
|
|
180
|
+
return colorize("No suggestions", :dim) unless suggestions&.any?
|
|
181
|
+
|
|
182
|
+
lines = []
|
|
183
|
+
suggestions.first(max_display).each_with_index do |suggestion, idx|
|
|
184
|
+
number = colorize((idx + 1).to_s + '.', :info)
|
|
185
|
+
word = colorize(suggestion.word, suggestion.high_confidence? ? :success : :warning)
|
|
186
|
+
confidence = "(#{(suggestion.confidence * 100).round(0)}%)"
|
|
187
|
+
|
|
188
|
+
line = " #{number} #{word} #{confidence}"
|
|
189
|
+
line += " [#{suggestion.source}]" if suggestion.source && @verbose
|
|
190
|
+
lines << line
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
if suggestions.size > max_display
|
|
194
|
+
remaining = suggestions.size - max_display
|
|
195
|
+
lines << colorize(" ... and #{remaining} more", :dim)
|
|
196
|
+
end
|
|
197
|
+
|
|
198
|
+
lines.join("\n")
|
|
199
|
+
end
|
|
200
|
+
|
|
201
|
+
# Display all errors with status indicators.
|
|
202
|
+
#
|
|
203
|
+
# @param navigation [NavigationManager] Navigation state
|
|
204
|
+
# @return [String] Formatted error list
|
|
205
|
+
def list_all_errors(navigation)
|
|
206
|
+
lines = []
|
|
207
|
+
lines << ""
|
|
208
|
+
lines << colorize("All Errors (#{navigation.errors.size})", :bold)
|
|
209
|
+
lines << "─" * 70
|
|
210
|
+
|
|
211
|
+
navigation.list_all.each do |line|
|
|
212
|
+
# Add color coding based on status
|
|
213
|
+
colored_line = if line.include?('[DONE]')
|
|
214
|
+
colorize(line, :success)
|
|
215
|
+
elsif line.include?('[SKIP]')
|
|
216
|
+
colorize(line, :dim)
|
|
217
|
+
else
|
|
218
|
+
line
|
|
219
|
+
end
|
|
220
|
+
lines << colored_line
|
|
221
|
+
end
|
|
222
|
+
|
|
223
|
+
lines << ""
|
|
224
|
+
lines.join("\n")
|
|
225
|
+
end
|
|
226
|
+
|
|
227
|
+
# Display statistics summary.
|
|
228
|
+
#
|
|
229
|
+
# @param navigation [NavigationManager] Navigation state
|
|
230
|
+
# @return [String] Formatted statistics
|
|
231
|
+
def statistics(navigation)
|
|
232
|
+
stats = navigation.statistics
|
|
233
|
+
|
|
234
|
+
lines = []
|
|
235
|
+
lines << ""
|
|
236
|
+
lines << colorize("Review Statistics", :bold)
|
|
237
|
+
lines << "─" * 40
|
|
238
|
+
lines << "Total: #{stats[:total]}"
|
|
239
|
+
lines << "Pending: #{stats[:pending]}"
|
|
240
|
+
lines << colorize("Modified: #{stats[:modified]}", :success)
|
|
241
|
+
lines << colorize("Skipped: #{stats[:skipped]}", :dim)
|
|
242
|
+
lines << ""
|
|
243
|
+
|
|
244
|
+
if stats[:by_type]&.any?
|
|
245
|
+
lines << colorize("By Type:", :bold)
|
|
246
|
+
stats[:by_type].each do |type, count|
|
|
247
|
+
label = SemanticError::ERROR_TYPES[type] || type.to_s.capitalize
|
|
248
|
+
lines << " #{label}: #{count}"
|
|
249
|
+
end
|
|
250
|
+
lines << ""
|
|
251
|
+
end
|
|
252
|
+
|
|
253
|
+
lines.join("\n")
|
|
254
|
+
end
|
|
255
|
+
|
|
256
|
+
# Display help screen.
|
|
257
|
+
#
|
|
258
|
+
# @return [String] Formatted help text
|
|
259
|
+
def help_screen
|
|
260
|
+
lines = []
|
|
261
|
+
lines << ""
|
|
262
|
+
lines << colorize("═══ Interactive Review Help ═══", :bold)
|
|
263
|
+
lines << ""
|
|
264
|
+
lines << colorize("Navigation Commands:", :bold)
|
|
265
|
+
lines << " [Enter] or [n] Move to next error"
|
|
266
|
+
lines << " [b] Move to previous error"
|
|
267
|
+
lines << " [j] <number> Jump to error by number"
|
|
268
|
+
lines << " [f] Jump to first error"
|
|
269
|
+
lines << " [l] Jump to last error"
|
|
270
|
+
lines << ""
|
|
271
|
+
lines << colorize("Error Actions:", :bold)
|
|
272
|
+
lines << " [1-9] Accept suggestion by number"
|
|
273
|
+
lines << " [s] Skip current error"
|
|
274
|
+
lines << " [e] Edit custom replacement"
|
|
275
|
+
lines << ""
|
|
276
|
+
lines << colorize("Display Commands:", :bold)
|
|
277
|
+
lines << " [l] List all errors with status"
|
|
278
|
+
lines << " [t] Toggle show/hide context"
|
|
279
|
+
lines << " [v] Toggle verbose mode"
|
|
280
|
+
lines << " [?] Show this summary"
|
|
281
|
+
lines << ""
|
|
282
|
+
lines << colorize("Program Commands:", :bold)
|
|
283
|
+
lines << " [q] Quit and save changes"
|
|
284
|
+
lines << " [Q] Quit without saving"
|
|
285
|
+
lines << " [!] Export corrections to file"
|
|
286
|
+
lines << ""
|
|
287
|
+
lines << colorize("Filter Commands:", :bold)
|
|
288
|
+
lines << " [c] <level> Filter by confidence (high, medium, low)"
|
|
289
|
+
lines << " [y] <type> Filter by error type"
|
|
290
|
+
lines << " [a] Show all errors (clear filters)"
|
|
291
|
+
lines << ""
|
|
292
|
+
|
|
293
|
+
lines.join("\n")
|
|
294
|
+
end
|
|
295
|
+
|
|
296
|
+
# Display export summary.
|
|
297
|
+
#
|
|
298
|
+
# @param navigation [NavigationManager] Navigation state
|
|
299
|
+
# @param export_path [String] Path to export file
|
|
300
|
+
# @return [String] Formatted export summary
|
|
301
|
+
def export_summary(navigation, export_path)
|
|
302
|
+
corrections = navigation.export_corrections
|
|
303
|
+
|
|
304
|
+
lines = []
|
|
305
|
+
lines << ""
|
|
306
|
+
lines << colorize("═══ Export Summary ═══", :bold)
|
|
307
|
+
lines << ""
|
|
308
|
+
lines << "Exported #{corrections.size} corrections to:"
|
|
309
|
+
lines << colorize(export_path, :info)
|
|
310
|
+
lines << ""
|
|
311
|
+
|
|
312
|
+
if corrections.any? && @verbose
|
|
313
|
+
lines << colorize("Corrections:", :bold)
|
|
314
|
+
corrections.first(10).each do |corr|
|
|
315
|
+
lines << " Line #{corr[:line]}: #{corr[:original]} → #{corr[:replacement]}"
|
|
316
|
+
end
|
|
317
|
+
|
|
318
|
+
if corrections.size > 10
|
|
319
|
+
lines << colorize(" ... and #{corrections.size - 10} more", :dim)
|
|
320
|
+
end
|
|
321
|
+
|
|
322
|
+
lines << ""
|
|
323
|
+
end
|
|
324
|
+
|
|
325
|
+
lines.join("\n")
|
|
326
|
+
end
|
|
327
|
+
|
|
328
|
+
# Format input prompt.
|
|
329
|
+
#
|
|
330
|
+
# @param text [String] Prompt text
|
|
331
|
+
# @return [String] Formatted prompt
|
|
332
|
+
def prompt(text)
|
|
333
|
+
colorize("#{text} ", :info)
|
|
334
|
+
end
|
|
335
|
+
|
|
336
|
+
# Display warning message.
|
|
337
|
+
#
|
|
338
|
+
# @param message [String] Warning message
|
|
339
|
+
# @return [String] Formatted warning
|
|
340
|
+
def warning(message)
|
|
341
|
+
colorize("Warning: #{message}", :warning)
|
|
342
|
+
end
|
|
343
|
+
|
|
344
|
+
# Display error message.
|
|
345
|
+
#
|
|
346
|
+
# @param message [String] Error message
|
|
347
|
+
# @return [String] Formatted error
|
|
348
|
+
def error(message)
|
|
349
|
+
colorize("Error: #{message}", :error)
|
|
350
|
+
end
|
|
351
|
+
|
|
352
|
+
# Display success message.
|
|
353
|
+
#
|
|
354
|
+
# @param message [String] Success message
|
|
355
|
+
# @return [String] Formatted success
|
|
356
|
+
def success(message)
|
|
357
|
+
colorize(message, :success)
|
|
358
|
+
end
|
|
359
|
+
|
|
360
|
+
private
|
|
361
|
+
|
|
362
|
+
# Highlight error in context with visual markers.
|
|
363
|
+
#
|
|
364
|
+
# @param error [SemanticError] The error to highlight
|
|
365
|
+
# @return [String] Error with context markers
|
|
366
|
+
def highlight_in_context(error)
|
|
367
|
+
# For now, just show the error word with color
|
|
368
|
+
# In full implementation, would extract from context and add markers
|
|
369
|
+
colorize(error.original, :error)
|
|
370
|
+
end
|
|
371
|
+
|
|
372
|
+
# Format context for display.
|
|
373
|
+
#
|
|
374
|
+
# @param context [Context] The context object
|
|
375
|
+
# @return [String] Formatted context with line numbers
|
|
376
|
+
def format_context(context)
|
|
377
|
+
lines = []
|
|
378
|
+
|
|
379
|
+
# Before context
|
|
380
|
+
if context.before && !context.before.empty?
|
|
381
|
+
lines << colorize(context.before.split("\n").last(2).join("\n"), :dim)
|
|
382
|
+
end
|
|
383
|
+
|
|
384
|
+
# Current line with marker
|
|
385
|
+
current = "→ #{context.current}"
|
|
386
|
+
lines << current
|
|
387
|
+
|
|
388
|
+
# After context
|
|
389
|
+
if context.after && !context.after.empty?
|
|
390
|
+
lines << colorize(context.after.split("\n").first(2).join("\n"), :dim)
|
|
391
|
+
end
|
|
392
|
+
|
|
393
|
+
lines.join("\n")
|
|
394
|
+
end
|
|
395
|
+
|
|
396
|
+
# Get confidence label with indicator.
|
|
397
|
+
#
|
|
398
|
+
# @param confidence [Float] Confidence score (0.0 to 1.0)
|
|
399
|
+
# @return [String] Confidence label
|
|
400
|
+
def confidence_label(confidence)
|
|
401
|
+
case confidence
|
|
402
|
+
when 0.8..1.0 then colorize(CONFIDENCE_LABELS[:high], :success)
|
|
403
|
+
when 0.5..0.8 then colorize(CONFIDENCE_LABELS[:medium], :warning)
|
|
404
|
+
else colorize(CONFIDENCE_LABELS[:low], :dim)
|
|
405
|
+
end
|
|
406
|
+
end
|
|
407
|
+
|
|
408
|
+
# Get human-readable error type label.
|
|
409
|
+
#
|
|
410
|
+
# @param error_type [Symbol] Error type symbol
|
|
411
|
+
# @return [String] Human-readable label
|
|
412
|
+
def error_type_label(error_type)
|
|
413
|
+
SemanticError::ERROR_TYPES[error_type] || error_type.to_s.capitalize
|
|
414
|
+
end
|
|
415
|
+
|
|
416
|
+
# Apply color to text if colors are enabled.
|
|
417
|
+
#
|
|
418
|
+
# @param text [String] Text to colorize
|
|
419
|
+
# @param color [Symbol] Color symbol
|
|
420
|
+
# @return [String] Colorized text or original if colors disabled
|
|
421
|
+
def colorize(text, color)
|
|
422
|
+
return text unless @color_enabled
|
|
423
|
+
|
|
424
|
+
code = COLORS[color]
|
|
425
|
+
return text unless code
|
|
426
|
+
|
|
427
|
+
"#{code}#{text}#{COLORS[:reset]}"
|
|
428
|
+
end
|
|
429
|
+
end
|
|
430
|
+
end
|
|
431
|
+
end
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "thor"
|
|
4
|
+
|
|
5
|
+
module Kotoshu
|
|
6
|
+
module Cli
|
|
7
|
+
# Semantic error classes for the CLI. Subclass Thor::Error so the CLI's
|
|
8
|
+
# top-level dispatcher can catch them and exit with the appropriate code.
|
|
9
|
+
# Each class carries an `exit_status` that the dispatcher reads.
|
|
10
|
+
module Errors
|
|
11
|
+
# Base class for all CLI errors. Carries an exit_status.
|
|
12
|
+
class CliError < Thor::Error
|
|
13
|
+
attr_reader :exit_status
|
|
14
|
+
|
|
15
|
+
def initialize(message, exit_status:)
|
|
16
|
+
super(message)
|
|
17
|
+
@exit_status = exit_status
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
# Usage error: bad flags, missing argument, file not found (exit 2).
|
|
22
|
+
class UsageError < CliError
|
|
23
|
+
def initialize(message)
|
|
24
|
+
super(message, exit_status: 2)
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# Resource unavailable: network down, offline+uncached, integrity failure (exit 3).
|
|
29
|
+
class ResourceUnavailable < CliError
|
|
30
|
+
def initialize(message)
|
|
31
|
+
super(message, exit_status: 3)
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|