konpeito 0.1.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 (180) hide show
  1. checksums.yaml +7 -0
  2. data/.ruby-version +1 -0
  3. data/CHANGELOG.md +75 -0
  4. data/CONTRIBUTING.md +123 -0
  5. data/LICENSE +21 -0
  6. data/README.md +257 -0
  7. data/Rakefile +11 -0
  8. data/bin/konpeito +6 -0
  9. data/konpeito.gemspec +43 -0
  10. data/lib/konpeito/ast/typed_ast.rb +620 -0
  11. data/lib/konpeito/ast/visitor.rb +78 -0
  12. data/lib/konpeito/cache/cache_manager.rb +230 -0
  13. data/lib/konpeito/cache/dependency_graph.rb +192 -0
  14. data/lib/konpeito/cache.rb +8 -0
  15. data/lib/konpeito/cli/base_command.rb +187 -0
  16. data/lib/konpeito/cli/build_command.rb +220 -0
  17. data/lib/konpeito/cli/check_command.rb +104 -0
  18. data/lib/konpeito/cli/config.rb +231 -0
  19. data/lib/konpeito/cli/deps_command.rb +128 -0
  20. data/lib/konpeito/cli/doctor_command.rb +340 -0
  21. data/lib/konpeito/cli/fmt_command.rb +199 -0
  22. data/lib/konpeito/cli/init_command.rb +312 -0
  23. data/lib/konpeito/cli/lsp_command.rb +40 -0
  24. data/lib/konpeito/cli/run_command.rb +150 -0
  25. data/lib/konpeito/cli/test_command.rb +248 -0
  26. data/lib/konpeito/cli/watch_command.rb +212 -0
  27. data/lib/konpeito/cli.rb +301 -0
  28. data/lib/konpeito/codegen/builtin_methods.rb +229 -0
  29. data/lib/konpeito/codegen/cruby_backend.rb +1090 -0
  30. data/lib/konpeito/codegen/debug_info.rb +352 -0
  31. data/lib/konpeito/codegen/inliner.rb +486 -0
  32. data/lib/konpeito/codegen/jvm_backend.rb +197 -0
  33. data/lib/konpeito/codegen/jvm_generator.rb +13412 -0
  34. data/lib/konpeito/codegen/llvm_generator.rb +13191 -0
  35. data/lib/konpeito/codegen/loop_optimizer.rb +363 -0
  36. data/lib/konpeito/codegen/monomorphizer.rb +359 -0
  37. data/lib/konpeito/codegen/profile_runtime.c +341 -0
  38. data/lib/konpeito/codegen/profiler.rb +99 -0
  39. data/lib/konpeito/compiler.rb +592 -0
  40. data/lib/konpeito/dependency_resolver.rb +296 -0
  41. data/lib/konpeito/diagnostics/collector.rb +127 -0
  42. data/lib/konpeito/diagnostics/diagnostic.rb +237 -0
  43. data/lib/konpeito/diagnostics/renderer.rb +144 -0
  44. data/lib/konpeito/formatter/formatter.rb +1214 -0
  45. data/lib/konpeito/hir/builder.rb +7167 -0
  46. data/lib/konpeito/hir/nodes.rb +2465 -0
  47. data/lib/konpeito/lsp/document_manager.rb +820 -0
  48. data/lib/konpeito/lsp/server.rb +183 -0
  49. data/lib/konpeito/lsp/transport.rb +38 -0
  50. data/lib/konpeito/parser/prism_adapter.rb +65 -0
  51. data/lib/konpeito/platform.rb +103 -0
  52. data/lib/konpeito/profile/report.rb +136 -0
  53. data/lib/konpeito/rbs_inline/preprocessor.rb +199 -0
  54. data/lib/konpeito/stdlib/compression/compression.rb +72 -0
  55. data/lib/konpeito/stdlib/compression/compression.rbs +60 -0
  56. data/lib/konpeito/stdlib/compression/compression_native.c +415 -0
  57. data/lib/konpeito/stdlib/compression/extconf.rb +19 -0
  58. data/lib/konpeito/stdlib/crypto/crypto.rb +85 -0
  59. data/lib/konpeito/stdlib/crypto/crypto.rbs +74 -0
  60. data/lib/konpeito/stdlib/crypto/crypto_native.c +312 -0
  61. data/lib/konpeito/stdlib/crypto/extconf.rb +40 -0
  62. data/lib/konpeito/stdlib/http/extconf.rb +19 -0
  63. data/lib/konpeito/stdlib/http/http.rb +125 -0
  64. data/lib/konpeito/stdlib/http/http.rbs +57 -0
  65. data/lib/konpeito/stdlib/http/http_native.c +440 -0
  66. data/lib/konpeito/stdlib/json/extconf.rb +17 -0
  67. data/lib/konpeito/stdlib/json/json.rb +44 -0
  68. data/lib/konpeito/stdlib/json/json.rbs +33 -0
  69. data/lib/konpeito/stdlib/json/json_native.c +286 -0
  70. data/lib/konpeito/stdlib/ui/extconf.rb +216 -0
  71. data/lib/konpeito/stdlib/ui/konpeito_ui_native.cpp +1625 -0
  72. data/lib/konpeito/stdlib/ui/konpeito_ui_native.h +162 -0
  73. data/lib/konpeito/stdlib/ui/ui.rb +318 -0
  74. data/lib/konpeito/stdlib/ui/ui.rbs +247 -0
  75. data/lib/konpeito/type_checker/annotation_parser.rb +67 -0
  76. data/lib/konpeito/type_checker/hm_inferrer.rb +2565 -0
  77. data/lib/konpeito/type_checker/inferrer.rb +565 -0
  78. data/lib/konpeito/type_checker/rbs_loader.rb +1621 -0
  79. data/lib/konpeito/type_checker/type_resolver.rb +276 -0
  80. data/lib/konpeito/type_checker/types.rb +1434 -0
  81. data/lib/konpeito/type_checker/unification.rb +323 -0
  82. data/lib/konpeito/ui/animation/animated_state.rb +80 -0
  83. data/lib/konpeito/ui/animation/easing.rb +59 -0
  84. data/lib/konpeito/ui/animation/value_tween.rb +66 -0
  85. data/lib/konpeito/ui/app.rb +379 -0
  86. data/lib/konpeito/ui/box.rb +38 -0
  87. data/lib/konpeito/ui/castella.rb +70 -0
  88. data/lib/konpeito/ui/castella_native.rb +76 -0
  89. data/lib/konpeito/ui/chart/area_chart.rb +305 -0
  90. data/lib/konpeito/ui/chart/bar_chart.rb +288 -0
  91. data/lib/konpeito/ui/chart/base_chart.rb +210 -0
  92. data/lib/konpeito/ui/chart/chart_helpers.rb +79 -0
  93. data/lib/konpeito/ui/chart/gauge_chart.rb +171 -0
  94. data/lib/konpeito/ui/chart/heatmap_chart.rb +222 -0
  95. data/lib/konpeito/ui/chart/line_chart.rb +289 -0
  96. data/lib/konpeito/ui/chart/pie_chart.rb +219 -0
  97. data/lib/konpeito/ui/chart/scales.rb +77 -0
  98. data/lib/konpeito/ui/chart/scatter_chart.rb +303 -0
  99. data/lib/konpeito/ui/chart/stacked_bar_chart.rb +276 -0
  100. data/lib/konpeito/ui/column.rb +271 -0
  101. data/lib/konpeito/ui/core.rb +2199 -0
  102. data/lib/konpeito/ui/dsl.rb +443 -0
  103. data/lib/konpeito/ui/frame.rb +171 -0
  104. data/lib/konpeito/ui/frame_native.rb +494 -0
  105. data/lib/konpeito/ui/markdown/ast.rb +124 -0
  106. data/lib/konpeito/ui/markdown/mermaid/layout.rb +387 -0
  107. data/lib/konpeito/ui/markdown/mermaid/models.rb +232 -0
  108. data/lib/konpeito/ui/markdown/mermaid/parser.rb +519 -0
  109. data/lib/konpeito/ui/markdown/mermaid/renderer.rb +336 -0
  110. data/lib/konpeito/ui/markdown/parser.rb +805 -0
  111. data/lib/konpeito/ui/markdown/renderer.rb +639 -0
  112. data/lib/konpeito/ui/markdown/theme.rb +165 -0
  113. data/lib/konpeito/ui/render_node.rb +260 -0
  114. data/lib/konpeito/ui/row.rb +207 -0
  115. data/lib/konpeito/ui/spacer.rb +18 -0
  116. data/lib/konpeito/ui/style.rb +799 -0
  117. data/lib/konpeito/ui/theme.rb +563 -0
  118. data/lib/konpeito/ui/themes/material.rb +35 -0
  119. data/lib/konpeito/ui/themes/tokyo_night.rb +6 -0
  120. data/lib/konpeito/ui/widgets/button.rb +103 -0
  121. data/lib/konpeito/ui/widgets/calendar.rb +1034 -0
  122. data/lib/konpeito/ui/widgets/checkbox.rb +119 -0
  123. data/lib/konpeito/ui/widgets/container.rb +91 -0
  124. data/lib/konpeito/ui/widgets/data_table.rb +667 -0
  125. data/lib/konpeito/ui/widgets/divider.rb +29 -0
  126. data/lib/konpeito/ui/widgets/image.rb +105 -0
  127. data/lib/konpeito/ui/widgets/input.rb +485 -0
  128. data/lib/konpeito/ui/widgets/markdown.rb +57 -0
  129. data/lib/konpeito/ui/widgets/modal.rb +163 -0
  130. data/lib/konpeito/ui/widgets/multiline_input.rb +968 -0
  131. data/lib/konpeito/ui/widgets/multiline_text.rb +180 -0
  132. data/lib/konpeito/ui/widgets/net_image.rb +100 -0
  133. data/lib/konpeito/ui/widgets/progress_bar.rb +70 -0
  134. data/lib/konpeito/ui/widgets/radio_buttons.rb +93 -0
  135. data/lib/konpeito/ui/widgets/slider.rb +133 -0
  136. data/lib/konpeito/ui/widgets/switch.rb +84 -0
  137. data/lib/konpeito/ui/widgets/tabs.rb +157 -0
  138. data/lib/konpeito/ui/widgets/text.rb +110 -0
  139. data/lib/konpeito/ui/widgets/tree.rb +426 -0
  140. data/lib/konpeito/version.rb +5 -0
  141. data/lib/konpeito.rb +109 -0
  142. data/test_native_array.rb +172 -0
  143. data/test_native_array_class.rb +197 -0
  144. data/test_native_class.rb +151 -0
  145. data/tools/konpeito-asm/build.sh +65 -0
  146. data/tools/konpeito-asm/lib/asm-9.7.1.jar +0 -0
  147. data/tools/konpeito-asm/runtime-classes/konpeito/runtime/KArray.class +0 -0
  148. data/tools/konpeito-asm/runtime-classes/konpeito/runtime/KCompression.class +0 -0
  149. data/tools/konpeito-asm/runtime-classes/konpeito/runtime/KConditionVariable.class +0 -0
  150. data/tools/konpeito-asm/runtime-classes/konpeito/runtime/KCrypto.class +0 -0
  151. data/tools/konpeito-asm/runtime-classes/konpeito/runtime/KFile.class +0 -0
  152. data/tools/konpeito-asm/runtime-classes/konpeito/runtime/KHTTP.class +0 -0
  153. data/tools/konpeito-asm/runtime-classes/konpeito/runtime/KHash.class +0 -0
  154. data/tools/konpeito-asm/runtime-classes/konpeito/runtime/KJSON$Parser.class +0 -0
  155. data/tools/konpeito-asm/runtime-classes/konpeito/runtime/KJSON.class +0 -0
  156. data/tools/konpeito-asm/runtime-classes/konpeito/runtime/KMath.class +0 -0
  157. data/tools/konpeito-asm/runtime-classes/konpeito/runtime/KRactor.class +0 -0
  158. data/tools/konpeito-asm/runtime-classes/konpeito/runtime/KRactorPort.class +0 -0
  159. data/tools/konpeito-asm/runtime-classes/konpeito/runtime/KSizedQueue.class +0 -0
  160. data/tools/konpeito-asm/runtime-classes/konpeito/runtime/KThread.class +0 -0
  161. data/tools/konpeito-asm/runtime-classes/konpeito/runtime/KTime.class +0 -0
  162. data/tools/konpeito-asm/runtime-classes/konpeito/runtime/RubyDispatch.class +0 -0
  163. data/tools/konpeito-asm/src/ClassIntrospector.java +312 -0
  164. data/tools/konpeito-asm/src/KonpeitoAssembler.java +659 -0
  165. data/tools/konpeito-asm/src/konpeito/runtime/KArray.java +390 -0
  166. data/tools/konpeito-asm/src/konpeito/runtime/KCompression.java +168 -0
  167. data/tools/konpeito-asm/src/konpeito/runtime/KConditionVariable.java +48 -0
  168. data/tools/konpeito-asm/src/konpeito/runtime/KCrypto.java +151 -0
  169. data/tools/konpeito-asm/src/konpeito/runtime/KFile.java +100 -0
  170. data/tools/konpeito-asm/src/konpeito/runtime/KHTTP.java +113 -0
  171. data/tools/konpeito-asm/src/konpeito/runtime/KHash.java +228 -0
  172. data/tools/konpeito-asm/src/konpeito/runtime/KJSON.java +405 -0
  173. data/tools/konpeito-asm/src/konpeito/runtime/KMath.java +54 -0
  174. data/tools/konpeito-asm/src/konpeito/runtime/KRactor.java +244 -0
  175. data/tools/konpeito-asm/src/konpeito/runtime/KRactorPort.java +53 -0
  176. data/tools/konpeito-asm/src/konpeito/runtime/KSizedQueue.java +49 -0
  177. data/tools/konpeito-asm/src/konpeito/runtime/KThread.java +49 -0
  178. data/tools/konpeito-asm/src/konpeito/runtime/KTime.java +53 -0
  179. data/tools/konpeito-asm/src/konpeito/runtime/RubyDispatch.java +416 -0
  180. metadata +267 -0
@@ -0,0 +1,340 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Konpeito
4
+ module Commands
5
+ # Doctor command - check development environment
6
+ class DoctorCommand < BaseCommand
7
+ def self.command_name
8
+ "doctor"
9
+ end
10
+
11
+ def self.description
12
+ "Check development environment and dependencies"
13
+ end
14
+
15
+ def run
16
+ parse_options!
17
+
18
+ $stderr.puts "Konpeito v#{Konpeito::VERSION} environment check:"
19
+ $stderr.puts ""
20
+
21
+ checks = []
22
+ checks.concat(core_checks)
23
+ checks.concat(native_checks) if check_native?
24
+ checks.concat(jvm_checks) if check_jvm?
25
+ checks.concat(ui_checks) if check_ui?
26
+ checks.concat(optional_checks)
27
+
28
+ # Display results
29
+ max_name_len = checks.map { |c| c[:name].length }.max || 10
30
+ checks.each do |check|
31
+ display_check(check, max_name_len)
32
+ end
33
+
34
+ $stderr.puts ""
35
+
36
+ issues = checks.select { |c| c[:status] == :missing }
37
+ warnings = checks.select { |c| c[:status] == :warning }
38
+
39
+ if issues.empty? && warnings.empty?
40
+ emit("All checks", "passed.")
41
+ else
42
+ emit_warn("Warnings", "#{warnings.size} warning(s)") unless warnings.empty?
43
+ emit_error("Issues", "#{issues.size} issue(s) found.") unless issues.empty?
44
+ exit 1 unless issues.empty?
45
+ end
46
+ end
47
+
48
+ protected
49
+
50
+ def default_options
51
+ {
52
+ verbose: false,
53
+ color: $stderr.tty?,
54
+ target: nil # nil = check both
55
+ }
56
+ end
57
+
58
+ def setup_option_parser(opts)
59
+ opts.on("--target TARGET", %i[native jvm ui], "Check only native, jvm, or ui dependencies") do |target|
60
+ options[:target] = target
61
+ end
62
+
63
+ super
64
+ end
65
+
66
+ def banner
67
+ "Usage: konpeito doctor [options]"
68
+ end
69
+
70
+ private
71
+
72
+ def check_native?
73
+ options[:target].nil? || options[:target] == :native
74
+ end
75
+
76
+ def check_jvm?
77
+ options[:target].nil? || options[:target] == :jvm
78
+ end
79
+
80
+ def check_ui?
81
+ options[:target] == :ui
82
+ end
83
+
84
+ def core_checks
85
+ checks = []
86
+
87
+ # Ruby version
88
+ checks << {
89
+ name: "Ruby",
90
+ detail: RUBY_VERSION,
91
+ status: ruby_version_ok? ? :ok : :missing,
92
+ hint: "Ruby 4.0+ required. Install with: rbenv install 4.0.1"
93
+ }
94
+
95
+ # Prism
96
+ checks << check_require("Prism", "prism", hint: "Prism is bundled with Ruby 4.0+")
97
+
98
+ # RBS
99
+ checks << check_require("RBS", "rbs", hint: "Install with: gem install rbs")
100
+
101
+ checks
102
+ end
103
+
104
+ def native_checks
105
+ checks = []
106
+
107
+ # ruby-llvm (optional — only needed for native target)
108
+ checks << check_require("ruby-llvm", "llvm/core",
109
+ required: false,
110
+ hint: "Optional for JVM-only. Required for --target native. Install: gem install ruby-llvm")
111
+
112
+ # clang
113
+ clang_path = Platform.find_llvm_tool("clang")
114
+ checks << {
115
+ name: "clang",
116
+ detail: clang_path || "not found",
117
+ status: clang_path ? :ok : :missing,
118
+ hint: "Install: #{Platform.llvm_install_hint}"
119
+ }
120
+
121
+ # opt
122
+ opt_path = Platform.find_llvm_tool("opt")
123
+ checks << {
124
+ name: "opt",
125
+ detail: opt_path || "not found",
126
+ status: opt_path ? :ok : :missing,
127
+ hint: "Install: #{Platform.llvm_install_hint}"
128
+ }
129
+
130
+ # libLLVM (for debug info)
131
+ llvm_lib = Platform.find_llvm_lib
132
+ checks << {
133
+ name: "libLLVM",
134
+ detail: llvm_lib || "not found",
135
+ status: llvm_lib ? :ok : :warning,
136
+ hint: "Needed for -g (debug info). #{Platform.llvm_install_hint}"
137
+ }
138
+
139
+ checks
140
+ end
141
+
142
+ def jvm_checks
143
+ checks = []
144
+
145
+ # Java
146
+ java_path = find_java_path
147
+ java_version = java_path ? detect_java_version(java_path) : nil
148
+ java_ok = java_version && java_version >= 21
149
+ checks << {
150
+ name: "Java",
151
+ detail: java_path ? "#{java_version || '?'} (#{java_path})" : "not found",
152
+ status: java_ok ? :ok : (java_path ? :warning : :missing),
153
+ hint: "Java 21+ required. Install: #{Platform.java_install_hint}"
154
+ }
155
+
156
+ # ASM tool - check both gem install path and local path
157
+ asm_jar_gem = File.expand_path("../../../tools/konpeito-asm/konpeito-asm.jar", __dir__)
158
+ asm_jar_local = File.join("tools", "konpeito-asm", "konpeito-asm.jar")
159
+ asm_jar = if File.exist?(asm_jar_gem)
160
+ asm_jar_gem
161
+ elsif File.exist?(asm_jar_local)
162
+ asm_jar_local
163
+ end
164
+ asm_exists = !asm_jar.nil?
165
+ checks << {
166
+ name: "ASM tool",
167
+ detail: asm_exists ? asm_jar : "not found (built automatically on first JVM compile)",
168
+ status: asm_exists ? :ok : :warning,
169
+ hint: asm_exists ? nil : "Run: konpeito build --target jvm hello.rb"
170
+ }
171
+
172
+ checks
173
+ end
174
+
175
+ def optional_checks
176
+ checks = []
177
+
178
+ # listen gem (for watch)
179
+ checks << check_require("listen", "listen",
180
+ required: false,
181
+ hint: "Optional, for 'konpeito watch'. Install: gem install listen")
182
+
183
+ # Config file
184
+ config_exists = File.exist?("konpeito.toml")
185
+ checks << {
186
+ name: "Config",
187
+ detail: config_exists ? "konpeito.toml" : "not found",
188
+ status: config_exists ? :ok : :warning,
189
+ hint: "Create with: konpeito init"
190
+ }
191
+
192
+ checks
193
+ end
194
+
195
+ def ui_checks
196
+ checks = []
197
+
198
+ # SDL3
199
+ sdl3_found = false
200
+ sdl3_detail = "not found"
201
+ sdl3_hint = "See docs for installation"
202
+ case RUBY_PLATFORM
203
+ when /darwin/
204
+ sdl3_prefix = `brew --prefix sdl3 2>/dev/null`.chomp rescue ""
205
+ if !sdl3_prefix.empty? && File.directory?(sdl3_prefix)
206
+ sdl3_found = true
207
+ sdl3_detail = sdl3_prefix
208
+ end
209
+ sdl3_hint = "Install: brew install sdl3"
210
+ when /linux/
211
+ sdl3_pkg = `pkg-config --modversion sdl3 2>/dev/null`.chomp rescue ""
212
+ if !sdl3_pkg.empty?
213
+ sdl3_found = true
214
+ sdl3_detail = "#{sdl3_pkg} (pkg-config)"
215
+ end
216
+ sdl3_hint = "Install: apt install libsdl3-dev"
217
+ when /mingw|mswin/
218
+ sdl3_dir = ENV["SDL3_DIR"]
219
+ if sdl3_dir && File.directory?(sdl3_dir)
220
+ sdl3_found = true
221
+ sdl3_detail = sdl3_dir
222
+ else
223
+ sdl3_pkg = `pkg-config --modversion sdl3 2>NUL`.chomp rescue ""
224
+ if !sdl3_pkg.empty?
225
+ sdl3_found = true
226
+ sdl3_detail = "#{sdl3_pkg} (pkg-config)"
227
+ end
228
+ end
229
+ sdl3_hint = "Install: pacman -S mingw-w64-ucrt-x86_64-SDL3"
230
+ end
231
+ checks << {
232
+ name: "SDL3",
233
+ detail: sdl3_detail,
234
+ status: sdl3_found ? :ok : :missing,
235
+ hint: sdl3_hint
236
+ }
237
+
238
+ # Skia
239
+ skia_dir = ENV["SKIA_DIR"] || File.expand_path("~/skia-prebuilt")
240
+ skia_found = false
241
+ if File.directory?(File.join(skia_dir, "out"))
242
+ # Search for libskia.a (Unix) or skia.lib (Windows)
243
+ Dir.glob(File.join(skia_dir, "out", "*", "{libskia.a,skia.lib}")).each do |path|
244
+ skia_found = true
245
+ break
246
+ end
247
+ end
248
+ skia_found ||= File.exist?(File.join(skia_dir, "lib", "libskia.a"))
249
+ skia_found ||= File.exist?(File.join(skia_dir, "lib", "skia.lib"))
250
+ skia_detail = skia_found ? skia_dir : "not found"
251
+ checks << {
252
+ name: "Skia",
253
+ detail: skia_detail,
254
+ status: skia_found ? :ok : :missing,
255
+ hint: "Set SKIA_DIR env var or place at ~/skia-prebuilt. See docs for build instructions."
256
+ }
257
+
258
+ # konpeito_ui extension
259
+ ui_ext_found = false
260
+ ui_ext_detail = "not found"
261
+ begin
262
+ require "konpeito/stdlib/ui/konpeito_ui"
263
+ ui_ext_found = true
264
+ ui_ext_detail = "loaded"
265
+ rescue LoadError
266
+ ui_makefile = File.join(__dir__, "..", "stdlib", "ui", "Makefile")
267
+ if File.exist?(ui_makefile)
268
+ ui_ext_detail = "not compiled (run: cd lib/konpeito/stdlib/ui && make)"
269
+ end
270
+ end
271
+ checks << {
272
+ name: "konpeito_ui",
273
+ detail: ui_ext_detail,
274
+ status: ui_ext_found ? :ok : :missing,
275
+ hint: "Build: cd lib/konpeito/stdlib/ui && ruby extconf.rb && make"
276
+ }
277
+
278
+ checks
279
+ end
280
+
281
+ def display_check(check, max_name_len)
282
+ name = check[:name].ljust(max_name_len)
283
+ detail = check[:detail] || ""
284
+
285
+ case check[:status]
286
+ when :ok
287
+ status_str = options[:color] ? "\e[32mok\e[0m" : "ok"
288
+ when :warning
289
+ status_str = options[:color] ? "\e[33mWARNING\e[0m" : "WARNING"
290
+ when :missing
291
+ status_str = options[:color] ? "\e[31mMISSING\e[0m" : "MISSING"
292
+ end
293
+
294
+ # Truncate detail to fit in terminal
295
+ detail_max = 50
296
+ detail = detail[0, detail_max] + "..." if detail.length > detail_max + 3
297
+
298
+ $stderr.puts " %-*s %-55s %s" % [max_name_len, name, detail, status_str]
299
+
300
+ # Show hint for non-ok status
301
+ if check[:status] != :ok && check[:hint]
302
+ $stderr.puts " %-*s %s" % [max_name_len, "", check[:hint]]
303
+ end
304
+ end
305
+
306
+ def check_require(name, lib, required: true, hint: nil)
307
+ begin
308
+ require lib
309
+ { name: name, detail: "available", status: :ok, hint: hint }
310
+ rescue LoadError
311
+ { name: name, detail: "not found", status: required ? :missing : :warning, hint: hint }
312
+ end
313
+ end
314
+
315
+ def ruby_version_ok?
316
+ parts = RUBY_VERSION.split(".").map(&:to_i)
317
+ parts[0] > 4 || (parts[0] == 4 && parts[1] >= 0)
318
+ end
319
+
320
+ def find_java_path
321
+ # Check JAVA_HOME first
322
+ java_home = ENV["JAVA_HOME"] || Platform.default_java_home
323
+ java_path = File.join(java_home, "bin", "java")
324
+ return java_path if File.exist?(java_path)
325
+
326
+ # Fall back to PATH
327
+ Platform.find_executable("java")
328
+ end
329
+
330
+ def detect_java_version(java_path)
331
+ output = `"#{java_path}" -version 2>&1`
332
+ if output =~ /version "(\d+)/
333
+ Regexp.last_match(1).to_i
334
+ end
335
+ rescue
336
+ nil
337
+ end
338
+ end
339
+ end
340
+ end
@@ -0,0 +1,199 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../formatter/formatter"
4
+
5
+ module Konpeito
6
+ module Commands
7
+ # Fmt command - format Ruby source files with built-in Prism-based formatter
8
+ class FmtCommand < BaseCommand
9
+ def self.command_name
10
+ "fmt"
11
+ end
12
+
13
+ def self.description
14
+ "Format Ruby source files"
15
+ end
16
+
17
+ def run
18
+ parse_options!
19
+
20
+ @files = args.empty? ? find_ruby_files : args
21
+
22
+ if @files.empty?
23
+ $stderr.puts "No Ruby files found to format."
24
+ return
25
+ end
26
+
27
+ emit("Formatting", "#{@files.size} file(s)...") unless options[:quiet]
28
+
29
+ changed = 0
30
+ unchanged = 0
31
+ errored = 0
32
+
33
+ @files.each do |file|
34
+ result = format_file(file)
35
+ case result
36
+ when :changed
37
+ changed += 1
38
+ when :unchanged
39
+ unchanged += 1
40
+ when :error
41
+ errored += 1
42
+ end
43
+ end
44
+
45
+ unless options[:quiet]
46
+ parts = []
47
+ parts << "#{changed} changed" if changed > 0
48
+ parts << "#{unchanged} unchanged" if unchanged > 0
49
+ parts << "#{errored} error(s)" if errored > 0
50
+ emit("Finished", parts.join(", "))
51
+ end
52
+
53
+ if options[:check] && changed > 0
54
+ exit 1
55
+ end
56
+
57
+ exit 1 if errored > 0
58
+ end
59
+
60
+ protected
61
+
62
+ def default_options
63
+ {
64
+ verbose: false,
65
+ color: $stderr.tty?,
66
+ check: false,
67
+ diff: false,
68
+ quiet: false,
69
+ exclude: []
70
+ }
71
+ end
72
+
73
+ def setup_option_parser(opts)
74
+ opts.on("--check", "Check formatting without modifying files") do
75
+ options[:check] = true
76
+ end
77
+
78
+ opts.on("--diff", "Show what would change (unified diff)") do
79
+ options[:diff] = true
80
+ options[:check] = true # diff implies check
81
+ end
82
+
83
+ opts.on("-q", "--quiet", "Suppress non-error output") do
84
+ options[:quiet] = true
85
+ end
86
+
87
+ opts.on("--exclude PATTERN", "Exclude files matching pattern") do |pattern|
88
+ options[:exclude] << pattern
89
+ end
90
+
91
+ super
92
+ end
93
+
94
+ def banner
95
+ <<~BANNER.chomp
96
+ Usage: konpeito fmt [options] [files...]
97
+
98
+ Examples:
99
+ konpeito fmt Format all Ruby files
100
+ konpeito fmt src/main.rb Format specific file
101
+ konpeito fmt --check Check without modifying
102
+ konpeito fmt --diff Show what would change
103
+ BANNER
104
+ end
105
+
106
+ private
107
+
108
+ def find_ruby_files
109
+ default_exclude = ["vendor/", ".bundle/", ".konpeito_cache/", "tools/"]
110
+ all_exclude = default_exclude + options[:exclude]
111
+
112
+ Dir.glob("**/*.rb").reject do |f|
113
+ all_exclude.any? { |pat| f.start_with?(pat) || File.fnmatch?(pat, f) }
114
+ end
115
+ end
116
+
117
+ def format_file(file)
118
+ unless File.exist?(file)
119
+ $stderr.puts "Warning: #{file} not found, skipping"
120
+ return :error
121
+ end
122
+
123
+ source = File.read(file)
124
+ formatter = Formatter::Formatter.new(source, filepath: file)
125
+ formatted = formatter.format
126
+
127
+ if source == formatted
128
+ return :unchanged
129
+ end
130
+
131
+ if options[:diff]
132
+ show_diff(file, source, formatted)
133
+ emit("Formatted", file) unless options[:quiet]
134
+ return :changed
135
+ end
136
+
137
+ if options[:check]
138
+ emit_warn("Unformatted", file) unless options[:quiet]
139
+ return :changed
140
+ end
141
+
142
+ # Write formatted content
143
+ File.write(file, formatted)
144
+ emit("Formatted", file) unless options[:quiet]
145
+ :changed
146
+ rescue => e
147
+ $stderr.puts "Error formatting #{file}: #{e.message}"
148
+ :error
149
+ end
150
+
151
+ def show_diff(file, original, formatted)
152
+ orig_lines = original.lines
153
+ fmt_lines = formatted.lines
154
+
155
+ # Simple unified diff
156
+ $stdout.puts "--- #{file}"
157
+ $stdout.puts "+++ #{file} (formatted)"
158
+
159
+ # Find differing regions
160
+ max_len = [orig_lines.size, fmt_lines.size].max
161
+ i = 0
162
+ while i < max_len
163
+ if orig_lines[i] != fmt_lines[i]
164
+ # Find the end of this diff hunk
165
+ hunk_start = i
166
+ while i < max_len && orig_lines[i] != fmt_lines[i]
167
+ i += 1
168
+ end
169
+ hunk_end = i
170
+
171
+ # Context
172
+ ctx_start = [hunk_start - 3, 0].max
173
+ ctx_end = [hunk_end + 3, max_len].min
174
+
175
+ $stdout.puts "@@ -#{ctx_start + 1},#{hunk_end - ctx_start} +#{ctx_start + 1},#{hunk_end - ctx_start} @@"
176
+
177
+ (ctx_start...ctx_end).each do |j|
178
+ if j >= hunk_start && j < hunk_end
179
+ if j < orig_lines.size && orig_lines[j]
180
+ line = orig_lines[j].chomp
181
+ $stdout.puts options[:color] ? "\e[31m-#{line}\e[0m" : "-#{line}"
182
+ end
183
+ if j < fmt_lines.size && fmt_lines[j]
184
+ line = fmt_lines[j].chomp
185
+ $stdout.puts options[:color] ? "\e[32m+#{line}\e[0m" : "+#{line}"
186
+ end
187
+ else
188
+ line = (orig_lines[j] || fmt_lines[j] || "").chomp
189
+ $stdout.puts " #{line}"
190
+ end
191
+ end
192
+ else
193
+ i += 1
194
+ end
195
+ end
196
+ end
197
+ end
198
+ end
199
+ end