railst 0.2.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.
data/lib/railst/cli.rb ADDED
@@ -0,0 +1,742 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "pathname"
4
+ require "fileutils"
5
+ require "socket"
6
+ require "rbconfig"
7
+ require "etc"
8
+
9
+ require_relative "version"
10
+ require_relative "terminal"
11
+ require_relative "environment"
12
+ require_relative "templates"
13
+
14
+ module Railst
15
+ class CLI
16
+ def self.start(args = ARGV)
17
+ new(args).run
18
+ end
19
+
20
+ def initialize(args)
21
+ @args = args.dup
22
+ @command = @args.shift || "help"
23
+ end
24
+
25
+ def run
26
+ case @command
27
+ when "s", "server", "run"
28
+ run_server
29
+ when "c", "console"
30
+ run_console
31
+ when "g", "generate"
32
+ run_generate
33
+ when "d", "destroy"
34
+ run_destroy
35
+ when "new"
36
+ run_new
37
+ when "t", "test"
38
+ run_test
39
+ when "test:rust", "t:rust"
40
+ run_rust_test
41
+ when "test:all", "t:all"
42
+ run_all_tests
43
+ when "r", "runner"
44
+ run_runner
45
+ when "routes"
46
+ run_routes
47
+ when "db"
48
+ run_db
49
+ when /\Adb:/
50
+ run_db_task
51
+ when "build", "rust:build"
52
+ run_build
53
+ when "clean", "rust:clean"
54
+ run_clean
55
+ when "doctor", "check"
56
+ run_doctor
57
+ when "bench", "benchmark"
58
+ run_benchmark
59
+ when "stats"
60
+ run_stats
61
+ when "ci"
62
+ run_ci
63
+ when "setup"
64
+ run_setup
65
+ when "install"
66
+ run_install_cli
67
+ when "version", "--version", "-v"
68
+ show_version
69
+ when "help", "--help", "-h"
70
+ show_help
71
+ else
72
+ run_passthrough(@command)
73
+ end
74
+ end
75
+
76
+ private
77
+
78
+ def project_root
79
+ @project_root ||= begin
80
+ current = Pathname.pwd
81
+ loop do
82
+ return current if current.join("bin/rails").exist? && current.join("ext/rust_backend").exist?
83
+ break if current.root?
84
+
85
+ current = current.parent
86
+ end
87
+ nil
88
+ end
89
+ end
90
+
91
+ def require_project_root!
92
+ unless project_root
93
+ puts ""
94
+ puts " #{Terminal.badge("ERROR", bg: :red, fg: :white)} You must run this command inside a Rust on Rails project directory."
95
+ puts ""
96
+ exit 1
97
+ end
98
+ end
99
+
100
+ def system_in_root(*cmd, exit_on_fail: true)
101
+ require_project_root!
102
+ success = false
103
+ exit_code = 0
104
+
105
+ Dir.chdir(project_root) do
106
+ success = Kernel.system(Environment.toolchains_env, *cmd)
107
+ exit_code = $?.exitstatus || (success ? 0 : 1)
108
+ end
109
+
110
+ exit(exit_code) if exit_on_fail && !success
111
+ success
112
+ end
113
+
114
+ def exec_in_root(*cmd)
115
+ require_project_root!
116
+ Dir.chdir(project_root) do
117
+ Kernel.exec(Environment.toolchains_env, *cmd)
118
+ end
119
+ end
120
+
121
+ def rust_backend_stale?
122
+ return false unless project_root
123
+
124
+ so_path = project_root.join("lib/rust_backend/rust_backend.so")
125
+ return true unless so_path.exist?
126
+
127
+ so_mtime = so_path.mtime
128
+ src_pattern = project_root.join("ext/rust_backend/**/*.{rs,toml,rb}").to_s
129
+ Dir[src_pattern].any? do |f|
130
+ next false if f.include?("/target/")
131
+
132
+ File.mtime(f) > so_mtime
133
+ end
134
+ end
135
+
136
+ def ensure_rust_built!
137
+ require_project_root!
138
+ return unless rust_backend_stale?
139
+
140
+ puts ""
141
+ puts " #{Terminal.badge("INFO", bg: :cyan, fg: :black)} Rust extension is missing or modified. Recompiling..."
142
+ puts ""
143
+
144
+ success = system_in_root("bin/rake", "rust:build", exit_on_fail: false)
145
+ unless success
146
+ puts ""
147
+ puts " #{Terminal.badge("ERROR", bg: :red, fg: :white)} Rust compilation failed."
148
+ puts ""
149
+ exit 1
150
+ end
151
+ end
152
+
153
+ # --- Server Command ---
154
+
155
+ def parse_server_options(args)
156
+ options = {
157
+ host: "127.0.0.1",
158
+ port: 3000,
159
+ environment: ENV.fetch("RAILS_ENV", "development"),
160
+ find_port: false,
161
+ passthrough: []
162
+ }
163
+
164
+ i = 0
165
+ while i < args.length
166
+ arg = args[i]
167
+ case arg
168
+ when "-p", "--port"
169
+ i += 1
170
+ options[:port] = args[i].to_i
171
+ when /\A-p(\d+)\z/, /\A--port=(\d+)\z/
172
+ options[:port] = $1.to_i
173
+ when "-b", "-h", "--binding", "--host"
174
+ i += 1
175
+ options[:host] = args[i]
176
+ when /\A-b(.+)\z/, /\A--binding=(.+)\z/, /\A--host=(.+)\z/
177
+ options[:host] = $1
178
+ when "-e", "--environment"
179
+ i += 1
180
+ options[:environment] = args[i]
181
+ when /\A-e(.+)\z/, /\A--environment=(.+)\z/
182
+ options[:environment] = $1
183
+ when "-f", "--find-port"
184
+ options[:find_port] = true
185
+ else
186
+ options[:passthrough] << arg
187
+ end
188
+ i += 1
189
+ end
190
+
191
+ options
192
+ end
193
+
194
+ def port_in_use?(host, port)
195
+ check_hosts = [ host ]
196
+ check_hosts << "127.0.0.1" if host == "0.0.0.0" || host == "localhost"
197
+ check_hosts << "0.0.0.0" if host == "127.0.0.1"
198
+
199
+ check_hosts.uniq.each do |h|
200
+ server = TCPServer.new(h, port)
201
+ server.close
202
+ rescue Errno::EADDRINUSE, Errno::EACCES
203
+ return true
204
+ rescue StandardError
205
+ # ignore other connection errors during check
206
+ end
207
+ false
208
+ end
209
+
210
+ def find_available_port(host, starting_port, max_tries = 20)
211
+ candidate = starting_port
212
+ max_tries.times do
213
+ return candidate unless port_in_use?(host, candidate)
214
+
215
+ candidate += 1
216
+ end
217
+ nil
218
+ end
219
+
220
+ def run_server
221
+ require_project_root!
222
+ ensure_rust_built!
223
+
224
+ opts = parse_server_options(@args)
225
+ host = opts[:host]
226
+ port = opts[:port]
227
+ env_name = opts[:environment]
228
+
229
+ if port_in_use?(host, port)
230
+ if opts[:find_port]
231
+ next_port = find_available_port(host, port + 1)
232
+ if next_port
233
+ puts ""
234
+ puts " #{Terminal.badge("WARN", bg: :yellow, fg: :black)} Port #{port} is occupied. Using port #{next_port} instead."
235
+ port = next_port
236
+ else
237
+ print_port_error(host, port)
238
+ exit 1
239
+ end
240
+ else
241
+ suggested_port = find_available_port(host, port + 1) || (port + 1)
242
+ print_port_error(host, port, suggested_port)
243
+ exit 1
244
+ end
245
+ end
246
+
247
+ display_host = (host == "0.0.0.0") ? "127.0.0.1" : host
248
+ puts ""
249
+ puts " #{Terminal.badge("INFO", bg: :green, fg: :black)} Server running on #{Terminal.bold_cyan("[http://#{display_host}:#{port}]")}."
250
+ puts ""
251
+ puts " #{Terminal.dim("Press")} #{Terminal.bold("Ctrl+C")} #{Terminal.dim("to stop the server")}"
252
+ puts ""
253
+ puts " #{Terminal.dim("Environment")} #{Terminal.dim(".........................................")} #{env_name}"
254
+ puts " #{Terminal.dim("Rust Backend")} #{Terminal.dim(".......................")} #{Terminal.green("Rayon + Magnus 0.9 (active)")}"
255
+ puts ""
256
+
257
+ exec_in_root("bin/rails", "server", "-b", host, "-p", port.to_s, "-e", env_name, *opts[:passthrough])
258
+ end
259
+
260
+ def print_port_error(host, port, suggested_port = nil)
261
+ puts ""
262
+ puts " #{Terminal.badge("ERROR", bg: :red, fg: :white)} Failed to listen on #{host}:#{port} (Address already in use)."
263
+ puts ""
264
+ puts " #{Terminal.dim("Port")} #{Terminal.bold(port)} #{Terminal.dim("is currently occupied by another process.")}"
265
+ if suggested_port
266
+ puts " #{Terminal.dim("Try specifying a different port:")}"
267
+ puts " #{Terminal.bold_yellow("railst s -p #{suggested_port}")}"
268
+ puts ""
269
+ puts " #{Terminal.dim("Or run with automatic port selection:")}"
270
+ puts " #{Terminal.bold_yellow("railst s --find-port")}"
271
+ end
272
+ puts ""
273
+ end
274
+
275
+ # --- Core Commands ---
276
+
277
+ def run_console
278
+ require_project_root!
279
+ ensure_rust_built!
280
+ exec_in_root("bin/rails", "console", *@args)
281
+ end
282
+
283
+ def run_generate
284
+ require_project_root!
285
+ ensure_rust_built!
286
+ if @args.empty?
287
+ exec_in_root("bin/rails", "generate", "--help")
288
+ else
289
+ system_in_root("bin/rails", "generate", *@args)
290
+ end
291
+ end
292
+
293
+ def run_destroy
294
+ require_project_root!
295
+ system_in_root("bin/rails", "destroy", *@args)
296
+ end
297
+
298
+ def run_runner
299
+ require_project_root!
300
+ ensure_rust_built!
301
+ exec_in_root("bin/rails", "runner", *@args)
302
+ end
303
+
304
+ def run_routes
305
+ require_project_root!
306
+ system_in_root("bin/rails", "routes", *@args)
307
+ end
308
+
309
+ def run_db
310
+ require_project_root!
311
+ sub = @args.shift || "migrate"
312
+ system_in_root("bin/rails", "db:#{sub}", *@args)
313
+ end
314
+
315
+ def run_db_task
316
+ require_project_root!
317
+ system_in_root("bin/rails", @command, *@args)
318
+ end
319
+
320
+ def run_test
321
+ require_project_root!
322
+ if @args.empty?
323
+ run_all_tests
324
+ elsif @args.first == "rust"
325
+ @args.shift
326
+ run_rust_test
327
+ elsif @args.first == "all"
328
+ @args.shift
329
+ run_all_tests
330
+ else
331
+ ensure_rust_built!
332
+ system_in_root("bin/rails", "test", *@args)
333
+ end
334
+ end
335
+
336
+ def run_rust_test
337
+ require_project_root!
338
+ puts ""
339
+ puts " #{Terminal.badge("INFO", bg: :cyan, fg: :black)} Running native Rust crate unit tests..."
340
+ puts ""
341
+ system_in_root("bin/rake", "rust:test")
342
+ end
343
+
344
+ def run_all_tests
345
+ require_project_root!
346
+ puts ""
347
+ puts " #{Terminal.badge("INFO", bg: :cyan, fg: :black)} Running native Rust tests..."
348
+ puts ""
349
+ rust_ok = system_in_root("bin/rake", "rust:test", exit_on_fail: false)
350
+
351
+ puts ""
352
+ puts " #{Terminal.badge("INFO", bg: :green, fg: :black)} Running Rails test suite..."
353
+ puts ""
354
+ rails_ok = system_in_root("bin/rails", "test", exit_on_fail: false)
355
+
356
+ exit((rust_ok && rails_ok) ? 0 : 1)
357
+ end
358
+
359
+ def run_build
360
+ require_project_root!
361
+ puts ""
362
+ puts " #{Terminal.badge("INFO", bg: :cyan, fg: :black)} Compiling Magnus Rust backend extension..."
363
+ puts ""
364
+ ok = system_in_root("bin/rake", "rust:build", exit_on_fail: false)
365
+ if ok
366
+ puts ""
367
+ puts " #{Terminal.badge("INFO", bg: :green, fg: :black)} Rust backend compiled successfully."
368
+ puts ""
369
+ else
370
+ puts ""
371
+ puts " #{Terminal.badge("ERROR", bg: :red, fg: :white)} Rust compilation failed."
372
+ puts ""
373
+ exit 1
374
+ end
375
+ end
376
+
377
+ def run_clean
378
+ require_project_root!
379
+ puts ""
380
+ puts " #{Terminal.badge("INFO", bg: :cyan, fg: :black)} Cleaning Rust build artifacts..."
381
+ puts ""
382
+ system_in_root("bin/rake", "rust:clean")
383
+ end
384
+
385
+ def run_setup
386
+ require_project_root!
387
+ system_in_root("bin/setup", *@args)
388
+ end
389
+
390
+ def run_ci
391
+ require_project_root!
392
+ system_in_root("bin/ci", *@args)
393
+ end
394
+
395
+ def run_passthrough(cmd)
396
+ require_project_root!
397
+ exec_in_root("bin/rails", cmd, *@args)
398
+ end
399
+
400
+ def run_new
401
+ app_name = @args.shift
402
+ if app_name.nil? || app_name.empty? || app_name.start_with?("-")
403
+ puts ""
404
+ puts " #{Terminal.badge("ERROR", bg: :red, fg: :white)} Missing application name."
405
+ puts ""
406
+ puts " Usage:"
407
+ puts " #{Terminal.bold_cyan("railst new <app_name> [rails_options]")}"
408
+ puts ""
409
+ puts " Examples:"
410
+ puts " #{Terminal.bold_yellow("railst new my_app")}"
411
+ puts " #{Terminal.bold_yellow("railst new analytics_service -d postgresql")}"
412
+ puts ""
413
+ exit 1
414
+ end
415
+
416
+ target_dir = Pathname.pwd.join(app_name)
417
+ if target_dir.exist?
418
+ puts ""
419
+ puts " #{Terminal.badge("ERROR", bg: :red, fg: :white)} Destination directory '#{app_name}' already exists."
420
+ puts ""
421
+ exit 1
422
+ end
423
+
424
+ puts ""
425
+ puts " #{Terminal.badge("INFO", bg: :cyan, fg: :black)} Creating new Rails application: #{Terminal.bold(app_name)}..."
426
+ puts ""
427
+
428
+ rails_cmd = [ "rails", "new", app_name, *@args ]
429
+ rails_success = Kernel.system(Environment.toolchains_env, *rails_cmd)
430
+ unless rails_success
431
+ puts ""
432
+ puts " #{Terminal.badge("ERROR", bg: :red, fg: :white)} Failed to create base Rails application."
433
+ puts ""
434
+ exit 1
435
+ end
436
+
437
+ puts ""
438
+ puts " #{Terminal.badge("INFO", bg: :cyan, fg: :black)} Injecting Rust on Rails architecture (Magnus 0.9 + Rayon + GVL)..."
439
+ puts ""
440
+
441
+ Templates.inject_into(target_dir, project_root)
442
+
443
+ # Append rb_sys into Gemfile if not present
444
+ gemfile = target_dir.join("Gemfile")
445
+ if gemfile.exist?
446
+ gem_content = gemfile.read
447
+ unless gem_content.include?("rb_sys")
448
+ File.open(gemfile, "a") do |f|
449
+ f.puts "\n# Rust C-extension build tooling\ngroup :development, :test do\n gem \"rb_sys\", \"~> 0.9\"\nend\n"
450
+ end
451
+ end
452
+ end
453
+
454
+ # Copy railst CLI executable into bin/railst
455
+ cli_source = File.expand_path($PROGRAM_NAME)
456
+ dest_cli = target_dir.join("bin/railst")
457
+ FileUtils.cp(cli_source, dest_cli)
458
+ FileUtils.chmod(0755, dest_cli)
459
+
460
+ puts ""
461
+ puts " #{Terminal.badge("INFO", bg: :green, fg: :black)} Compiling native Rust backend in #{app_name}..."
462
+ puts ""
463
+
464
+ Dir.chdir(target_dir) do
465
+ Kernel.system(Environment.toolchains_env, "bundle", "install")
466
+ Kernel.system(Environment.toolchains_env, "bin/rake", "rust:build")
467
+ end
468
+
469
+ puts ""
470
+ puts " #{Terminal.badge("SUCCESS", bg: :green, fg: :black)} #{Terminal.bold("Rust on Rails application created successfully!")}"
471
+ puts ""
472
+ puts " #{Terminal.dim("Get started:")}"
473
+ puts " #{Terminal.bold_yellow("cd #{app_name}")}"
474
+ puts " #{Terminal.bold_yellow("railst s")}"
475
+ puts ""
476
+ end
477
+
478
+ def run_doctor
479
+ puts ""
480
+ puts " #{Terminal.bold_cyan("=== Rust on Rails System Diagnostics ===")}"
481
+ puts ""
482
+
483
+ # 1. Ruby & C Headers
484
+ ruby_ver = "#{RUBY_VERSION} (#{RUBY_PLATFORM})"
485
+ hdr_dir = RbConfig::CONFIG["rubyhdrdir"]
486
+ hdr_file = hdr_dir ? File.join(hdr_dir, "ruby.h") : nil
487
+ hdr_ok = hdr_file && File.exist?(hdr_file)
488
+
489
+ if hdr_ok
490
+ puts " #{Terminal.green("✓")} #{Terminal.bold("Ruby")}: #{ruby_ver}"
491
+ puts " #{Terminal.green("✓")} #{Terminal.bold("Ruby C-Headers")}: #{hdr_file}"
492
+ else
493
+ puts " #{Terminal.yellow("!")} #{Terminal.bold("Ruby")}: #{ruby_ver}"
494
+ puts " #{Terminal.red("✗")} #{Terminal.bold("Ruby C-Headers")}: Not found in #{hdr_dir || 'unknown'}"
495
+ end
496
+
497
+ # 2. Rust & Cargo
498
+ env = Environment.toolchains_env
499
+ cargo_ver = `cargo --version 2>/dev/null`.strip
500
+ rustc_ver = `rustc --version 2>/dev/null`.strip
501
+
502
+ if !cargo_ver.empty? && !rustc_ver.empty?
503
+ puts " #{Terminal.green("✓")} #{Terminal.bold("Rust Toolchain")}: #{rustc_ver}, #{cargo_ver}"
504
+ else
505
+ puts " #{Terminal.red("✗")} #{Terminal.bold("Rust Toolchain")}: cargo/rustc not found in PATH."
506
+ puts " #{Terminal.dim("Hint: Install Rust via: curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh")}"
507
+ end
508
+
509
+ # 3. LLVM / Clang
510
+ libclang = env["LIBCLANG_PATH"]
511
+ libclang_so = libclang ? Dir[File.join(libclang, "libclang.so*")].first : nil
512
+
513
+ if libclang_so
514
+ puts " #{Terminal.green("✓")} #{Terminal.bold("LLVM / Clang")}: #{libclang_so}"
515
+ else
516
+ puts " #{Terminal.yellow("!")} #{Terminal.bold("LLVM / Clang")}: libclang.so not detected in standard paths."
517
+ puts " #{Terminal.dim("Hint: Install libclang-dev (e.g. sudo apt install libclang-dev)")}"
518
+ end
519
+
520
+ # 4. Multi-Core Hardware Detection (Rayon)
521
+ cores = Etc.nprocessors
522
+ puts " #{Terminal.green("✓")} #{Terminal.bold("Multi-Core Hardware")}: #{cores} physical/logical CPU cores (Rayon ready)"
523
+
524
+ # 5. Project-Specific Status (if inside project)
525
+ if project_root
526
+ puts ""
527
+ puts " #{Terminal.bold_cyan("Project Status:")} #{project_root}"
528
+
529
+ so_path = project_root.join("lib/rust_backend/rust_backend.so")
530
+ if so_path.exist?
531
+ stale = rust_backend_stale?
532
+ size_kb = (so_path.size / 1024.0).round(1)
533
+ if stale
534
+ puts " #{Terminal.yellow("!")} #{Terminal.bold("Rust Backend")}: Compiled (#{size_kb} KB) but #{Terminal.yellow("source files are newer")}."
535
+ puts " #{Terminal.dim("Hint: Run 'railst build' to recompile.")}"
536
+ else
537
+ puts " #{Terminal.green("✓")} #{Terminal.bold("Rust Backend")}: Compiled and up-to-date (#{size_kb} KB)"
538
+ end
539
+ else
540
+ puts " #{Terminal.red("✗")} #{Terminal.bold("Rust Backend")}: Not yet compiled."
541
+ puts " #{Terminal.dim("Hint: Run 'railst build' to compile.")}"
542
+ end
543
+
544
+ schema_file = project_root.join("db/schema.rb")
545
+ if schema_file.exist?
546
+ puts " #{Terminal.green("✓")} #{Terminal.bold("Database Schema")}: Present (#{schema_file})"
547
+ end
548
+ else
549
+ puts ""
550
+ puts " #{Terminal.dim("(Run inside a Rust on Rails project directory for local extension diagnostics)")}"
551
+ end
552
+
553
+ puts ""
554
+ puts " #{Terminal.dim("System ready for high-performance Rust on Rails computing.")}"
555
+ puts ""
556
+ end
557
+
558
+ def run_benchmark
559
+ require_project_root!
560
+ ensure_rust_built!
561
+
562
+ count = (@args.first || 25_000).to_i.clamp(1_000, 200_000)
563
+
564
+ puts ""
565
+ puts " #{Terminal.badge("BENCH", bg: :magenta, fg: :white)} #{Terminal.bold("Running Rust on Rails In-Process Performance Benchmark")}"
566
+ puts " Dataset: #{Terminal.bold(count.to_s.reverse.gsub(/(\d{3})(?=\d)/, '\\1,').reverse)} in-memory record hashes"
567
+ puts ""
568
+
569
+ runner_code = <<~RUBY
570
+ count = #{count}
571
+ data = Array.new(count) { |i| { "id" => i, "value" => (i * 1.5).round(2) } }
572
+ cfg = { "multiplier" => 1.25, "compute_field" => "value" }
573
+
574
+ # 1. Pure Ruby Sequential Batch
575
+ t0 = Process.clock_gettime(Process::CLOCK_MONOTONIC)
576
+ ruby_res = data.map do |d|
577
+ d.merge("value_computed" => d["value"] * 1.25, "processed_by" => "ruby")
578
+ end
579
+ t1 = Process.clock_gettime(Process::CLOCK_MONOTONIC)
580
+ ruby_batch_ms = ((t1 - t0) * 1000.0).round(2)
581
+
582
+ # 2. Rust Multi-Core Rayon Batch
583
+ t2 = Process.clock_gettime(Process::CLOCK_MONOTONIC)
584
+ rust_res = RustBackend.process_batch(data, cfg)
585
+ t3 = Process.clock_gettime(Process::CLOCK_MONOTONIC)
586
+ rust_batch_ms = ((t3 - t2) * 1000.0).round(2)
587
+
588
+ # 3. Rust Parallel Reduction Metrics
589
+ t4 = Process.clock_gettime(Process::CLOCK_MONOTONIC)
590
+ metrics = RustBackend.aggregate_metrics(data, "value")
591
+ t5 = Process.clock_gettime(Process::CLOCK_MONOTONIC)
592
+ rust_metric_ms = ((t5 - t4) * 1000.0).round(2)
593
+
594
+ cores = Etc.nprocessors
595
+ ruby_thru = (count / [ruby_batch_ms / 1000.0, 0.001].max).round
596
+ rust_thru = (count / [rust_batch_ms / 1000.0, 0.001].max).round
597
+
598
+ puts "RESULTS:"
599
+ puts "\#{ruby_batch_ms}|\#{rust_batch_ms}|\#{rust_metric_ms}|\#{ruby_thru}|\#{rust_thru}|\#{cores}"
600
+ RUBY
601
+
602
+ out = nil
603
+ Dir.chdir(project_root) do
604
+ out = `bin/rails runner '#{runner_code}' 2>/dev/null`
605
+ end
606
+
607
+ if out && out.include?("RESULTS:")
608
+ line = out.lines.find { |l| l.include?("|") }&.strip
609
+ if line
610
+ ruby_ms, rust_ms, metric_ms, ruby_thru, rust_thru, cores = line.split("|")
611
+
612
+ puts " +---------------------------+----------------+----------------+"
613
+ puts " | Operation | Pure Ruby | Rust (Rayon) |"
614
+ puts " +---------------------------+----------------+----------------+"
615
+ puts " | Batch Compute Latency | #{ruby_ms.rjust(11)} ms | #{Terminal.bold_green(rust_ms.rjust(11) + ' ms')} |"
616
+ puts " | Parallel Metrics (5 ops) | N/A | #{Terminal.bold_cyan(metric_ms.rjust(11) + ' ms')} |"
617
+ puts " | Processing Throughput | #{ruby_thru.rjust(9)} rec/s | #{Terminal.bold_green(rust_thru.rjust(9) + ' rec/s')} |"
618
+ puts " +---------------------------+----------------+----------------+"
619
+ puts ""
620
+ puts " #{Terminal.dim("Hardware Acceleration:")} #{Terminal.bold(cores)} CPU cores #{Terminal.dim("(Rayon parallel pool, Ruby GVL released)")}"
621
+ puts ""
622
+ end
623
+ else
624
+ puts " #{Terminal.badge("ERROR", bg: :red, fg: :white)} Failed to execute benchmark runner."
625
+ end
626
+ end
627
+
628
+ def run_stats
629
+ require_project_root!
630
+ system_in_root("bin/rails", "stats")
631
+
632
+ puts ""
633
+ puts " #{Terminal.bold_cyan("Rust Native Code Statistics:")}"
634
+ crate_dir = project_root.join("ext/rust_backend")
635
+ if crate_dir.exist?
636
+ rs_files = Dir[crate_dir.join("src/**/*.rs")]
637
+ total_lines = 0
638
+ test_count = 0
639
+
640
+ puts " +-------------------------------+-------+-------+"
641
+ puts " | Rust Module | Lines | Tests |"
642
+ puts " +-------------------------------+-------+-------+"
643
+
644
+ rs_files.sort.each do |f|
645
+ name = Pathname.new(f).relative_path_from(crate_dir).to_s
646
+ content = File.read(f)
647
+ lines = content.lines.count
648
+ tests = content.scan(/#\[test\]/).count
649
+ total_lines += lines
650
+ test_count += tests
651
+ puts " | #{name.ljust(29)} | #{lines.to_s.rjust(5)} | #{tests.to_s.rjust(5)} |"
652
+ end
653
+
654
+ puts " +-------------------------------+-------+-------+"
655
+ puts " | #{Terminal.bold('Total Rust Backend')} | #{Terminal.bold(total_lines.to_s.rjust(5))} | #{Terminal.bold(test_count.to_s.rjust(5))} |"
656
+ puts " +-------------------------------+-------+-------+"
657
+ puts ""
658
+ end
659
+ end
660
+
661
+ def run_install_cli
662
+ user_gem_bin = `ruby -r rubygems -e "puts Gem.user_dir" 2>/dev/null`.strip
663
+ dest_dirs = []
664
+ dest_dirs << File.join(user_gem_bin, "bin") if !user_gem_bin.empty? && File.directory?(user_gem_bin)
665
+ dest_dirs << File.expand_path("~/.local/bin")
666
+
667
+ source_script = File.expand_path($PROGRAM_NAME)
668
+
669
+ dest_dirs.uniq.each do |dest_dir|
670
+ FileUtils.mkdir_p(dest_dir)
671
+ dest_path = File.join(dest_dir, "railst")
672
+ FileUtils.cp(source_script, dest_path)
673
+ File.chmod(0755, dest_path)
674
+ puts " #{Terminal.badge("INFO", bg: :green, fg: :black)} Installed railst CLI to: #{dest_path}"
675
+ end
676
+
677
+ puts ""
678
+ puts " #{Terminal.dim("You can now run")} #{Terminal.bold_cyan("railst <command>")} #{Terminal.dim("from any directory!")}"
679
+ puts ""
680
+ end
681
+
682
+ def show_version
683
+ puts "Rust on Rails CLI v#{VERSION} (Rails 8 + Magnus 0.9 + Rayon)"
684
+ if project_root
685
+ rails_ver = `bin/rails --version 2>/dev/null`.strip
686
+ rust_ver = `rustc --version 2>/dev/null`.strip
687
+ puts " Rails: #{rails_ver}" unless rails_ver.empty?
688
+ puts " Rust: #{rust_ver}" unless rust_ver.empty?
689
+ end
690
+ end
691
+
692
+ def show_help
693
+ puts <<~HELP
694
+
695
+ Usage: railst <command> [options]
696
+
697
+ Core Rails Commands (with Rust Acceleration):
698
+ railst s, server Start the Rails server (auto-builds Rust backend)
699
+ railst c, console Start the interactive Rails console
700
+ railst g, generate Run Rails & Rust code generators
701
+ railst d, destroy Remove generated code
702
+ railst t, test [path] Run test suite (Rust crate tests + Rails tests)
703
+ railst r, runner <code> Run Ruby code in application context
704
+ railst new <app_name> Create a new Rust on Rails application
705
+
706
+ Database Commands:
707
+ railst db [action] Run database task (migrate, rollback, prepare, seed, reset)
708
+ railst db:migrate Run database migrations (supports all db:* tasks)
709
+
710
+ Rust on Rails Tooling:
711
+ railst build Compile the native Rust crate (cargo build --release)
712
+ railst clean Clean Rust build artifacts (cargo clean)
713
+ railst doctor Diagnose Ruby, Rust, LLVM/Clang, and Magnus environment
714
+ railst bench [count] Run CLI multi-core vs Ruby benchmark (default: 25,000)
715
+ railst ci Run the full Continuous Integration verification suite
716
+ railst routes List all application routes
717
+ railst stats Report code statistics (Ruby + Rust lines of code)
718
+ railst install Install railst globally into user's PATH
719
+ railst version, -v Display framework version
720
+ railst help, -h Show this help message
721
+
722
+ Options for 'railst server':
723
+ -p, --port <port> Specify server port (default: 3000)
724
+ -b, --binding <host> Specify IP binding (default: 127.0.0.1)
725
+ -e, --environment <env> Specify environment (default: development)
726
+ -f, --find-port Auto-select next available port if default is occupied
727
+
728
+ Examples:
729
+ railst s
730
+ railst s -p 3001 --find-port
731
+ railst g rust:resource Transaction amount:decimal status:string
732
+ railst c
733
+ railst t
734
+ railst t:rust
735
+ railst doctor
736
+ railst bench 50000
737
+ railst db:migrate
738
+
739
+ HELP
740
+ end
741
+ end
742
+ end