serialbench 0.1.2 → 0.1.3

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 (58) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/benchmark.yml +273 -228
  3. data/.github/workflows/rake.yml +11 -0
  4. data/.github/workflows/windows-debug.yml +171 -0
  5. data/.gitignore +32 -0
  6. data/.rubocop.yml +1 -0
  7. data/.rubocop_todo.yml +274 -0
  8. data/Gemfile +13 -1
  9. data/README.adoc +36 -0
  10. data/data/schemas/result.yml +29 -0
  11. data/docs/PLATFORM_VALIDATION_FIX.md +79 -0
  12. data/docs/SYCK_YAML_FIX.md +91 -0
  13. data/docs/WEBSITE_COMPLETION_PLAN.md +440 -0
  14. data/docs/WINDOWS_LIBXML_FIX.md +136 -0
  15. data/docs/WINDOWS_SETUP.md +122 -0
  16. data/lib/serialbench/benchmark_runner.rb +3 -3
  17. data/lib/serialbench/cli/benchmark_cli.rb +74 -1
  18. data/lib/serialbench/cli/environment_cli.rb +3 -3
  19. data/lib/serialbench/cli/resultset_cli.rb +72 -26
  20. data/lib/serialbench/cli/ruby_build_cli.rb +75 -88
  21. data/lib/serialbench/cli/validate_cli.rb +88 -0
  22. data/lib/serialbench/cli.rb +6 -2
  23. data/lib/serialbench/config_manager.rb +15 -26
  24. data/lib/serialbench/models/benchmark_config.rb +12 -0
  25. data/lib/serialbench/models/benchmark_result.rb +39 -3
  26. data/lib/serialbench/models/environment_config.rb +3 -2
  27. data/lib/serialbench/models/platform.rb +56 -4
  28. data/lib/serialbench/models/result.rb +28 -1
  29. data/lib/serialbench/models/result_set.rb +8 -0
  30. data/lib/serialbench/ruby_build_manager.rb +19 -23
  31. data/lib/serialbench/runners/asdf_runner.rb +1 -1
  32. data/lib/serialbench/runners/docker_runner.rb +2 -4
  33. data/lib/serialbench/runners/local_runner.rb +71 -0
  34. data/lib/serialbench/serializers/base_serializer.rb +1 -1
  35. data/lib/serialbench/serializers/json/rapidjson_serializer.rb +1 -1
  36. data/lib/serialbench/serializers/toml/base_toml_serializer.rb +0 -2
  37. data/lib/serialbench/serializers/toml/toml_rb_serializer.rb +1 -1
  38. data/lib/serialbench/serializers/toml/tomlib_serializer.rb +1 -1
  39. data/lib/serialbench/serializers/xml/libxml_serializer.rb +4 -8
  40. data/lib/serialbench/serializers/xml/nokogiri_serializer.rb +2 -2
  41. data/lib/serialbench/serializers/xml/oga_serializer.rb +4 -8
  42. data/lib/serialbench/serializers/xml/ox_serializer.rb +2 -2
  43. data/lib/serialbench/serializers/xml/rexml_serializer.rb +3 -3
  44. data/lib/serialbench/serializers/yaml/psych_serializer.rb +1 -1
  45. data/lib/serialbench/serializers/yaml/syck_serializer.rb +1 -1
  46. data/lib/serialbench/serializers.rb +2 -2
  47. data/lib/serialbench/site_generator.rb +180 -2
  48. data/lib/serialbench/templates/assets/css/format_based.css +1 -53
  49. data/lib/serialbench/templates/assets/css/themes.css +5 -4
  50. data/lib/serialbench/templates/assets/js/chart_helpers.js +44 -14
  51. data/lib/serialbench/templates/assets/js/dashboard.js +14 -15
  52. data/lib/serialbench/templates/format_based.liquid +480 -252
  53. data/lib/serialbench/version.rb +1 -1
  54. data/lib/serialbench/yaml_validator.rb +36 -0
  55. data/serialbench.gemspec +11 -2
  56. metadata +34 -23
  57. data/.github/workflows/ci.yml +0 -74
  58. data/.github/workflows/docker.yml +0 -272
@@ -0,0 +1,122 @@
1
+ # Windows Setup Guide for Serialbench
2
+
3
+ This guide explains how to set up serialbench on Windows, particularly for installing the `libxml-ruby` gem which requires native libxml2 libraries.
4
+
5
+ ## Problem
6
+
7
+ The `libxml-ruby` gem requires native C libraries (libxml2) that need to be installed separately on Windows. Without these libraries, bundler will fail when trying to install the gem.
8
+
9
+ ## Solution
10
+
11
+ ### Option 1: Using Chocolatey (Recommended)
12
+
13
+ 1. Install [Chocolatey](https://chocolatey.org/install) if you haven't already:
14
+
15
+ ```powershell
16
+ Set-ExecutionPolicy Bypass -Scope Process -Force
17
+ [System.Net.ServicePointManager]::SecurityProtocol = [System.Net.ServicePointManager]::SecurityProtocol -bor 3072
18
+ iex ((New-Object System.Net.WebClient).DownloadString('https://community.chocolatey.org/install.ps1'))
19
+ ```
20
+
21
+ 2. Install libxml2 and required tools:
22
+
23
+ ```powershell
24
+ choco install -y libxml2
25
+ choco install -y pkgconfiglite
26
+ ```
27
+
28
+ 3. Set environment variables (you may need to adjust paths based on your installation):
29
+
30
+ ```powershell
31
+ # Find the actual libxml2 installation path
32
+ $libxmlPath = (Get-ChildItem "C:\ProgramData\chocolatey\lib\libxml2" -Recurse -Filter "libxml2.dll" | Select-Object -First 1).Directory.Parent.FullName
33
+
34
+ # Set PKG_CONFIG_PATH
35
+ $env:PKG_CONFIG_PATH = "$libxmlPath\lib\pkgconfig"
36
+ [System.Environment]::SetEnvironmentVariable('PKG_CONFIG_PATH', $env:PKG_CONFIG_PATH, 'User')
37
+
38
+ # Add to PATH
39
+ $env:PATH = "$libxmlPath\bin;$env:PATH"
40
+ $currentPath = [System.Environment]::GetEnvironmentVariable('PATH', 'User')
41
+ [System.Environment]::SetEnvironmentVariable('PATH', "$libxmlPath\bin;$currentPath", 'User')
42
+ ```
43
+
44
+ 4. Install Ruby dependencies:
45
+
46
+ ```powershell
47
+ bundle install
48
+ ```
49
+
50
+ ### Option 2: Using vcpkg
51
+
52
+ 1. Install vcpkg:
53
+
54
+ ```powershell
55
+ git clone https://github.com/Microsoft/vcpkg.git
56
+ cd vcpkg
57
+ .\bootstrap-vcpkg.bat
58
+ .\vcpkg integrate install
59
+ ```
60
+
61
+ 2. Install libxml2:
62
+
63
+ ```powershell
64
+ .\vcpkg install libxml2:x64-windows
65
+ ```
66
+
67
+ 3. Set environment variables:
68
+
69
+ ```powershell
70
+ $vcpkgPath = "C:\path\to\vcpkg\installed\x64-windows"
71
+ $env:PKG_CONFIG_PATH = "$vcpkgPath\lib\pkgconfig"
72
+ $env:PATH = "$vcpkgPath\bin;$env:PATH"
73
+ ```
74
+
75
+ ### Option 3: Skip libxml-ruby (Testing Only)
76
+
77
+ If you don't need to test the libxml serializer, you can temporarily exclude it:
78
+
79
+ ```ruby
80
+ # In Gemfile or when installing
81
+ bundle install --without libxml
82
+ ```
83
+
84
+ Or remove it from the gemspec temporarily (not recommended for CI).
85
+
86
+ ## Troubleshooting
87
+
88
+ ### Error: "Cannot find libxml2"
89
+
90
+ This means the libxml2 libraries are not in the expected locations. Try:
91
+
92
+ 1. Verify libxml2 is installed:
93
+ ```powershell
94
+ where libxml2.dll
95
+ ```
96
+
97
+ 2. Check pkg-config can find it:
98
+ ```powershell
99
+ pkg-config --libs libxml-2.0
100
+ ```
101
+
102
+ 3. Manually specify the path when installing the gem:
103
+ ```powershell
104
+ gem install libxml-ruby -- --with-xml2-dir="C:\path\to\libxml2"
105
+ ```
106
+
107
+ ### Error: "mkmf.log shows missing headers"
108
+
109
+ The development headers aren't found. Make sure you installed the complete libxml2 package including headers.
110
+
111
+ ## GitHub Actions CI
112
+
113
+ For CI/CD on GitHub Actions, see `.github/workflows/windows-setup.yml` for the complete setup that:
114
+
115
+ 1. Installs libxml2 via Chocolatey
116
+ 2. Configures environment paths
117
+ 3. Runs bundle install
118
+ 4. Executes tests
119
+
120
+ ## Local Development
121
+
122
+ For local development on Windows, follow the Chocolatey or vcpkg installation steps above. There is no automated Rake task - you must manually install libxml2 before running `bundle install`.
@@ -36,7 +36,7 @@ module Serialbench
36
36
  serializers: Serializers.information,
37
37
  parsing: run_parsing_benchmarks,
38
38
  generation: run_generation_benchmarks,
39
- memory_usage: run_memory_benchmarks,
39
+ memory: run_memory_benchmarks,
40
40
  streaming: run_streaming_benchmarks
41
41
  )
42
42
  end
@@ -221,7 +221,7 @@ module Serialbench
221
221
  city: "City #{i % 100}",
222
222
  preferences: {
223
223
  theme: i.even? ? 'dark' : 'light',
224
- notifications: i % 3 == 0
224
+ notifications: (i % 3).zero?
225
225
  }
226
226
  }
227
227
  }
@@ -244,7 +244,7 @@ module Serialbench
244
244
  data: {
245
245
  field1: "Value #{i}",
246
246
  field2: i * 2,
247
- field3: i % 100 == 0 ? 'special' : 'normal',
247
+ field3: (i % 100).zero? ? 'special' : 'normal',
248
248
  nested: [
249
249
  "Item #{i}-1",
250
250
  "Item #{i}-2",
@@ -242,6 +242,79 @@ module Serialbench
242
242
 
243
243
  private
244
244
 
245
+ def execute_local_benchmark(environment, config, benchmark_config_path)
246
+ say '🏠 Executing local benchmark', :green
247
+
248
+ runner = Serialbench::BenchmarkRunner.new(
249
+ environment_config: environment,
250
+ benchmark_config: config
251
+ )
252
+
253
+ # Run benchmarks
254
+ results = runner.run_all_benchmarks
255
+
256
+ platform = Serialbench::Models::Platform.current_local
257
+
258
+ metadata = Models::RunMetadata.new(
259
+ benchmark_config_path: benchmark_config_path,
260
+ environment_config_path: "config/environments/#{environment.name}.yml",
261
+ tags: [
262
+ 'local',
263
+ platform.os,
264
+ platform.arch,
265
+ "ruby-#{environment.ruby_build_tag}"
266
+ ]
267
+ )
268
+
269
+ # Create results directory
270
+ result_dir = "results/runs/#{environment.name}-results"
271
+ FileUtils.mkdir_p(result_dir)
272
+
273
+ # Save results to single YAML file with platform and metadata merged in
274
+ results_model = Models::Result.new(
275
+ platform: platform,
276
+ metadata: metadata,
277
+ environment_config: environment,
278
+ benchmark_config: config,
279
+ benchmark_result: results
280
+ )
281
+
282
+ results_file = File.join(result_dir, 'results.yaml')
283
+ results_model.to_file(results_file)
284
+
285
+ say '✅ Local benchmark completed successfully!', :green
286
+ say "Results saved to: #{result_dir}", :cyan
287
+ say "Generate site: serialbench benchmark build-site #{result_dir}", :white
288
+ rescue StandardError => e
289
+ say "❌ Local benchmark failed: #{e.message}", :red
290
+ say "Details: #{e.backtrace.first(3).join("\n")}", :white if options[:verbose]
291
+ raise e
292
+ end
293
+
294
+ def execute_docker_benchmark(environment, config, benchmark_config_path)
295
+ say '🐳 Executing Docker benchmark', :green
296
+
297
+ require_relative '../runners/docker_runner'
298
+
299
+ environment_config_path = "config/environments/#{environment.name}.yml"
300
+ runner = Runners::DockerRunner.new(environment, environment_config_path)
301
+
302
+ # Create results directory
303
+ result_dir = "results/runs/#{environment.name}-results"
304
+ FileUtils.mkdir_p(result_dir)
305
+
306
+ # Run benchmark
307
+ runner.run_benchmark(config, benchmark_config_path, result_dir)
308
+
309
+ say '✅ Docker benchmark completed successfully!', :green
310
+ say "Results saved to: #{result_dir}", :cyan
311
+ say "Generate site: serialbench benchmark build-site #{result_dir}", :white
312
+ rescue StandardError => e
313
+ say "❌ Docker benchmark failed: #{e.message}", :red
314
+ say "Details: #{e.backtrace.first(3).join("\n")}", :white if options[:verbose]
315
+ raise e
316
+ end
317
+
245
318
  def show_execute_usage_and_exit
246
319
  say '❌ Error: Environment and config file arguments are required.', :red
247
320
  say ''
@@ -350,7 +423,7 @@ module Serialbench
350
423
  # raise e
351
424
  # end
352
425
 
353
- def execute_asdf_benchmark(environment, config, benchmark_config_path)
426
+ def execute_asdf_benchmark(environment, _config, benchmark_config_path)
354
427
  say '🔧 Executing ASDF benchmark', :green
355
428
 
356
429
  # Use the ASDF runner to execute the benchmark
@@ -169,9 +169,9 @@ module Serialbench
169
169
  when 'asdf'
170
170
  require_relative '../runners/asdf_runner'
171
171
  Runners::AsdfRunner.new(environment_config, environment_config_path)
172
- # when 'local'
173
- # require_relative '../runners/local_runner'
174
- # Runners::LocalRunner.new(environment_config, environment_config_path)
172
+ when 'local'
173
+ require_relative '../runners/local_runner'
174
+ Runners::LocalRunner.new(environment_config, environment_config_path)
175
175
  else
176
176
  raise "Unknown environment type: #{environment_config.kind}"
177
177
  end
@@ -20,7 +20,7 @@ module Serialbench
20
20
  serialbench resultset create cross-platform-test results/sets/cross-platform-test
21
21
  DESC
22
22
  def create(resultset_name, resultset_path)
23
- store = Serialbench::Models::ResultStore.default
23
+ Serialbench::Models::ResultStore.default
24
24
 
25
25
  # Check if resultset already exists
26
26
  definition_path = File.join(resultset_path, 'resultset.yaml')
@@ -49,36 +49,83 @@ module Serialbench
49
49
  exit 1
50
50
  end
51
51
 
52
- desc 'add-result RESULT_PATH RESULTSET_PATH', 'Add a run to a resultset'
52
+ desc 'add-result RESULTSET_PATH RESULT_PATH...', 'Add one or more runs to a resultset'
53
53
  long_desc <<~DESC
54
- Add an existing benchmark run to a resultset.
54
+ Add one or more benchmark runs to a resultset.
55
55
 
56
- RESULT_PATH should be the path to a run result directory
57
- RESULTSET_PATH must be specified explicitly
56
+ RESULTSET_PATH is the path to the resultset directory
57
+ RESULT_PATH... accepts multiple result paths (supports shell expansion)
58
58
 
59
59
  Examples:
60
- serialbench resultset add-result results/sets/performance-comparison results/runs/my-run-local-macos-arm64-ruby-3.3.8
61
- serialbench resultset add-result results/sets/cross-platform-test results/runs/my-docker-run
60
+ # Add single result
61
+ serialbench resultset add-result results/sets/weekly results/runs/my-run
62
+
63
+ # Add multiple results explicitly
64
+ serialbench resultset add-result results/sets/weekly results/runs/run1 results/runs/run2
65
+
66
+ # Add multiple results with shell expansion
67
+ serialbench resultset add-result results/sets/weekly artifacts/benchmark-results-*/
68
+ serialbench resultset add-result results/sets/weekly results/runs/*
62
69
  DESC
63
- def add_result(resultset_path, result_path)
70
+ def add_result(resultset_path, *result_paths)
71
+ if result_paths.empty?
72
+ say '❌ Error: At least one result path must be provided', :red
73
+ exit 1
74
+ end
75
+
64
76
  resultset = Serialbench::Models::ResultSet.load(resultset_path)
65
77
 
66
- # Validate that the run location exists
67
- unless Dir.exist?(result_path)
68
- say "Result directory not found: #{result_path}", :red
69
- return
78
+ say "📦 Adding #{result_paths.size} result(s) to resultset", :cyan
79
+ say "ResultSet: #{resultset_path}", :white
80
+ say ''
81
+
82
+ added_count = 0
83
+ failed_count = 0
84
+ skipped_count = 0
85
+
86
+ result_paths.each_with_index do |result_path, index|
87
+ say "#{index + 1}/#{result_paths.size} Processing: #{result_path}", :cyan
88
+
89
+ # Find results.yaml in the path or subdirectories
90
+ results_file = if File.exist?(File.join(result_path, 'results.yaml'))
91
+ File.join(result_path, 'results.yaml')
92
+ else
93
+ Dir.glob(File.join(result_path, '**/results.yaml')).first
94
+ end
95
+
96
+ unless results_file
97
+ say ' ⚠️ No results.yaml found - skipping', :yellow
98
+ skipped_count += 1
99
+ next
100
+ end
101
+
102
+ result_dir = File.dirname(results_file)
103
+
104
+ begin
105
+ resultset.add_result(result_dir)
106
+ say ' ✅ Added successfully', :green
107
+ added_count += 1
108
+ rescue StandardError => e
109
+ say " ❌ Failed: #{e.message}", :red
110
+ failed_count += 1
111
+ end
112
+ say ''
70
113
  end
71
114
 
72
- # Add run to resultset
73
- resultset.add_result(result_path)
74
115
  resultset.save(resultset_path)
75
116
 
76
- say ' Added run to resultset', :green
77
- say "Path: #{result_path}", :cyan
78
- say "ResultSet: #{resultset_path}", :cyan
79
- say "Total runs in set: #{resultset.results.count}", :white
117
+ say '=' * 60, :cyan
118
+ say 'Summary:', :green
119
+ say " Total processed: #{result_paths.size}", :white
120
+ say " Successfully added: #{added_count}", :green
121
+ say " ❌ Failed: #{failed_count}", :red if failed_count > 0
122
+ say " ⚠️ Skipped: #{skipped_count}", :yellow if skipped_count > 0
123
+ say " 📊 Total results in set: #{resultset.results.count}", :cyan
124
+ say '=' * 60, :cyan
125
+
126
+ exit 1 if failed_count > 0 && added_count == 0
80
127
  rescue StandardError => e
81
- say "Error adding run to resultset: #{e.message}", :red
128
+ say "Error: #{e.message}", :red
82
129
  exit 1
83
130
  end
84
131
 
@@ -93,8 +140,8 @@ module Serialbench
93
140
  serialbench resultset remove-result results/sets/performance-comparison results/runs/my-run-local-macos-arm64-ruby-3.3.8
94
141
  serialbench resultset remove-result results/sets/cross-platform-test results/runs/my-docker-run
95
142
  DESC
96
- def remove_result(resultset_path, result_path)
97
- store = Serialbench::Models::ResultStore.default
143
+ def remove_result(resultset_path, _result_path)
144
+ Serialbench::Models::ResultStore.default
98
145
 
99
146
  # Find the resultset
100
147
  resultset = Serialbench::Models::ResultSet.load(resultset_path)
@@ -136,8 +183,7 @@ module Serialbench
136
183
  serialbench resultset build-site results/sets/performance-comparison
137
184
  serialbench resultset build-site results/sets/cross-platform-test output/
138
185
  DESC
139
- option :output_dir, type: :string, default: '_site', desc: 'Output directory for generated site'
140
- def build_site(resultset_path)
186
+ def build_site(resultset_path, output_dir = '_site')
141
187
  unless Dir.exist?(resultset_path)
142
188
  say "ResultSet directory not found: #{resultset_path}", :red
143
189
  say "Please create a resultset first using 'serialbench resultset create'", :white
@@ -156,11 +202,11 @@ module Serialbench
156
202
  say "Runs in set: #{resultset.results.size}", :cyan
157
203
 
158
204
  # Use the unified site generator for resultsets
159
- Serialbench::SiteGenerator.generate_for_resultset(resultset, options[:output_dir])
205
+ Serialbench::SiteGenerator.generate_for_resultset(resultset, output_dir)
160
206
 
161
207
  say '✅ HTML site generated successfully!', :green
162
- say "Site location: #{options[:output_dir]}", :cyan
163
- say "Open: #{File.join(options[:output_dir], 'index.html')}", :white
208
+ say "Site location: #{output_dir}", :cyan
209
+ say "Open: #{File.join(output_dir, 'index.html')}", :white
164
210
  rescue StandardError => e
165
211
  say "Error generating site: #{e.message}", :red
166
212
  say "Details: #{e.backtrace.first(3).join("\n")}", :red if options[:verbose]
@@ -32,7 +32,6 @@ module Serialbench
32
32
  say "\n📋 Recent Ruby versions available:", :white
33
33
  recent_versions.each { |version| say " #{version}", :cyan }
34
34
  end
35
-
36
35
  rescue StandardError => e
37
36
  say "❌ Failed to update Ruby-Build definitions: #{e.message}", :red
38
37
  exit 1
@@ -52,42 +51,39 @@ module Serialbench
52
51
  DESC
53
52
  option :limit, type: :numeric, default: 50, desc: 'Maximum number of definitions to show'
54
53
  def list(filter = nil)
55
- begin
56
- definitions = RubyBuildManager.list_definitions(filter: filter)
57
-
58
- if definitions.empty?
59
- if filter
60
- say "No Ruby-Build definitions found matching '#{filter}'", :yellow
61
- else
62
- say 'No Ruby-Build definitions found in cache', :yellow
63
- say 'Update the cache first: serialbench ruby-build update', :white
64
- end
65
- return
66
- end
67
-
68
- # Limit results if there are many
69
- limited_definitions = definitions.first(options[:limit])
54
+ definitions = RubyBuildManager.list_definitions(filter: filter)
70
55
 
71
- say "Ruby-Build Definitions#{filter ? " (filtered by '#{filter}')" : ''}:", :green
72
- say '=' * 60, :green
73
-
74
- limited_definitions.each do |definition|
75
- say " #{definition}", :cyan
56
+ if definitions.empty?
57
+ if filter
58
+ say "No Ruby-Build definitions found matching '#{filter}'", :yellow
59
+ else
60
+ say 'No Ruby-Build definitions found in cache', :yellow
61
+ say 'Update the cache first: serialbench ruby-build update', :white
76
62
  end
63
+ return
64
+ end
77
65
 
78
- if definitions.length > options[:limit]
79
- remaining = definitions.length - options[:limit]
80
- say "\n... and #{remaining} more definitions", :yellow
81
- say "Use --limit to show more results", :white
82
- end
66
+ # Limit results if there are many
67
+ limited_definitions = definitions.first(options[:limit])
83
68
 
84
- say "\nTotal: #{definitions.length} definitions", :white
69
+ say "Ruby-Build Definitions#{filter ? " (filtered by '#{filter}')" : ''}:", :green
70
+ say '=' * 60, :green
85
71
 
86
- rescue StandardError => e
87
- say "❌ Failed to list Ruby-Build definitions: #{e.message}", :red
88
- say 'Try updating the cache: serialbench ruby-build update', :white
89
- exit 1
72
+ limited_definitions.each do |definition|
73
+ say " #{definition}", :cyan
74
+ end
75
+
76
+ if definitions.length > options[:limit]
77
+ remaining = definitions.length - options[:limit]
78
+ say "\n... and #{remaining} more definitions", :yellow
79
+ say 'Use --limit to show more results', :white
90
80
  end
81
+
82
+ say "\nTotal: #{definitions.length} definitions", :white
83
+ rescue StandardError => e
84
+ say "❌ Failed to list Ruby-Build definitions: #{e.message}", :red
85
+ say 'Try updating the cache: serialbench ruby-build update', :white
86
+ exit 1
91
87
  end
92
88
 
93
89
  desc 'show TAG', 'Show details for a specific Ruby-Build definition'
@@ -99,21 +95,18 @@ module Serialbench
99
95
  serialbench ruby-build show 3.2.4
100
96
  DESC
101
97
  def show(tag)
102
- begin
103
- definition = RubyBuildManager.show_definition(tag)
104
-
105
- say "Ruby-Build Definition: #{tag}", :green
106
- say '=' * 40, :green
107
- say "Tag: #{definition[:tag]}", :cyan
108
- say "Available: #{definition[:available] ? '✅ Yes' : '❌ No'}", :cyan
109
- say "Source: #{definition[:source]}", :cyan
110
- say "Cache file: #{definition[:cache_file]}", :white
111
-
112
- rescue StandardError => e
113
- say "❌ #{e.message}", :red
114
- say 'Available definitions: serialbench ruby-build list', :white
115
- exit 1
116
- end
98
+ definition = RubyBuildManager.show_definition(tag)
99
+
100
+ say "Ruby-Build Definition: #{tag}", :green
101
+ say '=' * 40, :green
102
+ say "Tag: #{definition[:tag]}", :cyan
103
+ say "Available: #{definition[:available] ? '✅ Yes' : '❌ No'}", :cyan
104
+ say "Source: #{definition[:source]}", :cyan
105
+ say "Cache file: #{definition[:cache_file]}", :white
106
+ rescue StandardError => e
107
+ say "❌ #{e.message}", :red
108
+ say 'Available definitions: serialbench ruby-build list', :white
109
+ exit 1
117
110
  end
118
111
 
119
112
  desc 'validate TAG', 'Validate a Ruby-Build tag'
@@ -125,31 +118,28 @@ module Serialbench
125
118
  serialbench ruby-build validate 3.2.4
126
119
  DESC
127
120
  def validate(tag)
128
- begin
129
- valid = RubyBuildManager.validate_tag(tag)
130
-
131
- if valid
132
- say "✅ Ruby-Build tag '#{tag}' is valid", :green
133
- else
134
- say "❌ Ruby-Build tag '#{tag}' is not valid", :red
121
+ valid = RubyBuildManager.validate_tag(tag)
135
122
 
136
- # Suggest similar tags
137
- definitions = RubyBuildManager.list_definitions
138
- similar = definitions.select { |d| d.include?(tag.split('.').first(2).join('.')) }.first(5)
123
+ if valid
124
+ say "✅ Ruby-Build tag '#{tag}' is valid", :green
125
+ else
126
+ say "❌ Ruby-Build tag '#{tag}' is not valid", :red
139
127
 
140
- if similar.any?
141
- say "\n💡 Similar available tags:", :yellow
142
- similar.each { |s| say " #{s}", :cyan }
143
- end
128
+ # Suggest similar tags
129
+ definitions = RubyBuildManager.list_definitions
130
+ similar = definitions.select { |d| d.include?(tag.split('.').first(2).join('.')) }.first(5)
144
131
 
145
- exit 1
132
+ if similar.any?
133
+ say "\n💡 Similar available tags:", :yellow
134
+ similar.each { |s| say " #{s}", :cyan }
146
135
  end
147
136
 
148
- rescue StandardError => e
149
- say "❌ Failed to validate tag: #{e.message}", :red
150
- say 'Try updating the cache: serialbench ruby-build update', :white
151
137
  exit 1
152
138
  end
139
+ rescue StandardError => e
140
+ say "❌ Failed to validate tag: #{e.message}", :red
141
+ say 'Try updating the cache: serialbench ruby-build update', :white
142
+ exit 1
153
143
  end
154
144
 
155
145
  desc 'suggest', 'Suggest Ruby-Build tag for current Ruby version'
@@ -163,26 +153,23 @@ module Serialbench
163
153
  serialbench ruby-build suggest
164
154
  DESC
165
155
  def suggest
166
- begin
167
- current_ruby = RUBY_VERSION
168
- suggested_tag = RubyBuildManager.suggest_current_ruby_tag
156
+ current_ruby = RUBY_VERSION
157
+ suggested_tag = RubyBuildManager.suggest_current_ruby_tag
169
158
 
170
- say "Current Ruby version: #{current_ruby}", :cyan
171
- say "Suggested ruby_build_tag: #{suggested_tag}", :green
172
-
173
- # Validate the suggestion
174
- if RubyBuildManager.validate_tag(suggested_tag)
175
- say "✅ Suggested tag is valid", :green
176
- else
177
- say "⚠️ Suggested tag not found in ruby-build definitions", :yellow
178
- say "You may need to update the cache or use a different tag", :white
179
- end
159
+ say "Current Ruby version: #{current_ruby}", :cyan
160
+ say "Suggested ruby_build_tag: #{suggested_tag}", :green
180
161
 
181
- rescue StandardError => e
182
- say "❌ Failed to suggest tag: #{e.message}", :red
183
- say 'Try updating the cache: serialbench ruby-build update', :white
184
- exit 1
162
+ # Validate the suggestion
163
+ if RubyBuildManager.validate_tag(suggested_tag)
164
+ say ' Suggested tag is valid', :green
165
+ else
166
+ say '⚠️ Suggested tag not found in ruby-build definitions', :yellow
167
+ say 'You may need to update the cache or use a different tag', :white
185
168
  end
169
+ rescue StandardError => e
170
+ say "❌ Failed to suggest tag: #{e.message}", :red
171
+ say 'Try updating the cache: serialbench ruby-build update', :white
172
+ exit 1
186
173
  end
187
174
 
188
175
  desc 'cache-info', 'Show information about the Ruby-Build definitions cache'
@@ -203,19 +190,19 @@ module Serialbench
203
190
  say "Location: #{RubyBuildManager::CACHE_FILE}", :cyan
204
191
  say "Definitions: #{definitions_count}", :cyan
205
192
  say "Age: #{format_cache_age(cache_age)}", :cyan
206
- say "Status: ✅ Available", :green
193
+ say 'Status: ✅ Available', :green
207
194
 
208
195
  if cache_age > 7 * 24 * 60 * 60 # 7 days
209
196
  say "\n💡 Cache is older than 7 days, consider updating:", :yellow
210
- say " serialbench ruby-build update", :white
197
+ say ' serialbench ruby-build update', :white
211
198
  end
212
199
  else
213
200
  say 'Ruby-Build Cache Information:', :green
214
201
  say '=' * 40, :green
215
202
  say "Location: #{RubyBuildManager::CACHE_FILE}", :cyan
216
- say "Status: ❌ Not found", :red
203
+ say 'Status: ❌ Not found', :red
217
204
  say "\n📥 Update the cache first:", :yellow
218
- say " serialbench ruby-build update", :white
205
+ say ' serialbench ruby-build update', :white
219
206
  end
220
207
  end
221
208
 
@@ -225,12 +212,12 @@ module Serialbench
225
212
  days = (seconds / (24 * 60 * 60)).to_i
226
213
  hours = ((seconds % (24 * 60 * 60)) / (60 * 60)).to_i
227
214
 
228
- if days > 0
215
+ if days.positive?
229
216
  "#{days} day#{'s' if days != 1}, #{hours} hour#{'s' if hours != 1}"
230
- elsif hours > 0
217
+ elsif hours.positive?
231
218
  "#{hours} hour#{'s' if hours != 1}"
232
219
  else
233
- "less than 1 hour"
220
+ 'less than 1 hour'
234
221
  end
235
222
  end
236
223
  end