serialbench 0.1.0 → 0.1.2
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 +4 -4
- data/.github/workflows/benchmark.yml +181 -30
- data/.github/workflows/ci.yml +3 -3
- data/.github/workflows/docker.yml +272 -0
- data/.github/workflows/rake.yml +15 -0
- data/.github/workflows/release.yml +25 -0
- data/Gemfile +6 -30
- data/README.adoc +381 -415
- data/Rakefile +0 -55
- data/config/benchmarks/full.yml +29 -0
- data/config/benchmarks/short.yml +26 -0
- data/config/environments/asdf-ruby-3.2.yml +8 -0
- data/config/environments/asdf-ruby-3.3.yml +8 -0
- data/config/environments/docker-ruby-3.0.yml +9 -0
- data/config/environments/docker-ruby-3.1.yml +9 -0
- data/config/environments/docker-ruby-3.2.yml +9 -0
- data/config/environments/docker-ruby-3.3.yml +9 -0
- data/config/environments/docker-ruby-3.4.yml +9 -0
- data/docker/Dockerfile.alpine +33 -0
- data/docker/Dockerfile.ubuntu +32 -0
- data/docker/README.md +214 -0
- data/exe/serialbench +1 -1
- data/lib/serialbench/benchmark_runner.rb +270 -350
- data/lib/serialbench/cli/base_cli.rb +51 -0
- data/lib/serialbench/cli/benchmark_cli.rb +380 -0
- data/lib/serialbench/cli/environment_cli.rb +181 -0
- data/lib/serialbench/cli/resultset_cli.rb +215 -0
- data/lib/serialbench/cli/ruby_build_cli.rb +238 -0
- data/lib/serialbench/cli.rb +59 -410
- data/lib/serialbench/config_manager.rb +140 -0
- data/lib/serialbench/models/benchmark_config.rb +63 -0
- data/lib/serialbench/models/benchmark_result.rb +45 -0
- data/lib/serialbench/models/environment_config.rb +71 -0
- data/lib/serialbench/models/platform.rb +59 -0
- data/lib/serialbench/models/result.rb +53 -0
- data/lib/serialbench/models/result_set.rb +71 -0
- data/lib/serialbench/models/result_store.rb +108 -0
- data/lib/serialbench/models.rb +54 -0
- data/lib/serialbench/ruby_build_manager.rb +153 -0
- data/lib/serialbench/runners/asdf_runner.rb +296 -0
- data/lib/serialbench/runners/base.rb +32 -0
- data/lib/serialbench/runners/docker_runner.rb +142 -0
- data/lib/serialbench/serializers/base_serializer.rb +8 -16
- data/lib/serialbench/serializers/json/base_json_serializer.rb +4 -4
- data/lib/serialbench/serializers/json/json_serializer.rb +0 -2
- data/lib/serialbench/serializers/json/oj_serializer.rb +0 -2
- data/lib/serialbench/serializers/json/rapidjson_serializer.rb +50 -0
- data/lib/serialbench/serializers/json/yajl_serializer.rb +6 -4
- data/lib/serialbench/serializers/toml/base_toml_serializer.rb +5 -3
- data/lib/serialbench/serializers/toml/toml_rb_serializer.rb +0 -2
- data/lib/serialbench/serializers/toml/tomlib_serializer.rb +0 -2
- data/lib/serialbench/serializers/toml/tomlrb_serializer.rb +56 -0
- data/lib/serialbench/serializers/xml/base_xml_serializer.rb +4 -9
- data/lib/serialbench/serializers/xml/libxml_serializer.rb +0 -2
- data/lib/serialbench/serializers/xml/nokogiri_serializer.rb +21 -5
- data/lib/serialbench/serializers/xml/oga_serializer.rb +0 -2
- data/lib/serialbench/serializers/xml/ox_serializer.rb +0 -2
- data/lib/serialbench/serializers/xml/rexml_serializer.rb +32 -4
- data/lib/serialbench/serializers/yaml/base_yaml_serializer.rb +59 -0
- data/lib/serialbench/serializers/yaml/psych_serializer.rb +54 -0
- data/lib/serialbench/serializers/yaml/syck_serializer.rb +102 -0
- data/lib/serialbench/serializers.rb +34 -6
- data/lib/serialbench/site_generator.rb +105 -0
- data/lib/serialbench/templates/assets/css/benchmark_report.css +535 -0
- data/lib/serialbench/templates/assets/css/format_based.css +526 -0
- data/lib/serialbench/templates/assets/css/themes.css +588 -0
- data/lib/serialbench/templates/assets/js/chart_helpers.js +381 -0
- data/lib/serialbench/templates/assets/js/dashboard.js +796 -0
- data/lib/serialbench/templates/assets/js/navigation.js +142 -0
- data/lib/serialbench/templates/base.liquid +49 -0
- data/lib/serialbench/templates/format_based.liquid +279 -0
- data/lib/serialbench/templates/partials/chart_section.liquid +4 -0
- data/lib/serialbench/version.rb +1 -1
- data/lib/serialbench.rb +2 -31
- data/serialbench.gemspec +28 -17
- metadata +192 -55
- data/lib/serialbench/chart_generator.rb +0 -821
- data/lib/serialbench/result_formatter.rb +0 -182
- data/lib/serialbench/result_merger.rb +0 -1201
- data/lib/serialbench/serializers/xml/base_parser.rb +0 -69
- data/lib/serialbench/serializers/xml/libxml_parser.rb +0 -98
- data/lib/serialbench/serializers/xml/nokogiri_parser.rb +0 -111
- data/lib/serialbench/serializers/xml/oga_parser.rb +0 -85
- data/lib/serialbench/serializers/xml/ox_parser.rb +0 -64
- data/lib/serialbench/serializers/xml/rexml_parser.rb +0 -129
@@ -0,0 +1,56 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'base_toml_serializer'
|
4
|
+
|
5
|
+
module Serialbench
|
6
|
+
module Serializers
|
7
|
+
module Toml
|
8
|
+
class TomlrbSerializer < BaseTomlSerializer
|
9
|
+
def available?
|
10
|
+
require_library('tomlrb')
|
11
|
+
end
|
12
|
+
|
13
|
+
def name
|
14
|
+
'tomlrb'
|
15
|
+
end
|
16
|
+
|
17
|
+
def version
|
18
|
+
require 'tomlrb'
|
19
|
+
# tomlrb doesn't expose a VERSION constant, so we'll use gem version
|
20
|
+
Gem.loaded_specs['tomlrb']&.version&.to_s || 'unknown'
|
21
|
+
rescue LoadError, NameError
|
22
|
+
'unknown'
|
23
|
+
end
|
24
|
+
|
25
|
+
def parse(toml_string)
|
26
|
+
require 'tomlrb'
|
27
|
+
Tomlrb.parse(toml_string)
|
28
|
+
end
|
29
|
+
|
30
|
+
def generate(object, options = {})
|
31
|
+
raise NotImplementedError, 'tomlrb gem does not support TOML generation/dumping'
|
32
|
+
end
|
33
|
+
|
34
|
+
def supports_generation?
|
35
|
+
false
|
36
|
+
end
|
37
|
+
|
38
|
+
def supports_comments?
|
39
|
+
false
|
40
|
+
end
|
41
|
+
|
42
|
+
def supports_arrays_of_tables?
|
43
|
+
true
|
44
|
+
end
|
45
|
+
|
46
|
+
def supports_inline_tables?
|
47
|
+
true
|
48
|
+
end
|
49
|
+
|
50
|
+
def supports_multiline_strings?
|
51
|
+
true
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
@@ -6,7 +6,7 @@ module Serialbench
|
|
6
6
|
module Serializers
|
7
7
|
module Xml
|
8
8
|
class BaseXmlSerializer < BaseSerializer
|
9
|
-
def format
|
9
|
+
def self.format
|
10
10
|
:xml
|
11
11
|
end
|
12
12
|
|
@@ -33,7 +33,9 @@ module Serialbench
|
|
33
33
|
}
|
34
34
|
end
|
35
35
|
|
36
|
-
|
36
|
+
def supports_generation?
|
37
|
+
true
|
38
|
+
end
|
37
39
|
|
38
40
|
def supports_xpath?
|
39
41
|
false
|
@@ -47,13 +49,6 @@ module Serialbench
|
|
47
49
|
false
|
48
50
|
end
|
49
51
|
|
50
|
-
# Subclasses should override this to specify their library name
|
51
|
-
def library_require_name
|
52
|
-
raise NotImplementedError, 'Subclasses must implement #library_require_name'
|
53
|
-
end
|
54
|
-
|
55
|
-
public
|
56
|
-
|
57
52
|
# Check if the XML library is available
|
58
53
|
def available?
|
59
54
|
return @available if defined?(@available)
|
@@ -49,8 +49,6 @@ module Serialbench
|
|
49
49
|
Nokogiri::VERSION
|
50
50
|
end
|
51
51
|
|
52
|
-
protected
|
53
|
-
|
54
52
|
def library_require_name
|
55
53
|
'nokogiri'
|
56
54
|
end
|
@@ -60,9 +58,9 @@ module Serialbench
|
|
60
58
|
def build_xml_from_data(xml, data, root_name = 'root')
|
61
59
|
case data
|
62
60
|
when Hash
|
63
|
-
xml.send(root_name) do
|
61
|
+
xml.send(sanitize_element_name(root_name)) do
|
64
62
|
data.each do |key, value|
|
65
|
-
build_xml_from_data(xml, value, key)
|
63
|
+
build_xml_from_data(xml, value, sanitize_element_name(key.to_s))
|
66
64
|
end
|
67
65
|
end
|
68
66
|
when Array
|
@@ -70,10 +68,28 @@ module Serialbench
|
|
70
68
|
build_xml_from_data(xml, item, "item_#{index}")
|
71
69
|
end
|
72
70
|
else
|
73
|
-
|
71
|
+
# Use a safe method that always works
|
72
|
+
element_name = sanitize_element_name(root_name)
|
73
|
+
if xml.respond_to?(element_name)
|
74
|
+
xml.send(element_name, data.to_s)
|
75
|
+
else
|
76
|
+
# Fallback: create element manually
|
77
|
+
xml.tag!(element_name, data.to_s)
|
78
|
+
end
|
74
79
|
end
|
75
80
|
end
|
76
81
|
|
82
|
+
def sanitize_element_name(name)
|
83
|
+
# Ensure element name is valid XML and safe to use as method name
|
84
|
+
sanitized = name.to_s.gsub(/[^a-zA-Z0-9_]/, '_')
|
85
|
+
# Ensure it starts with a letter
|
86
|
+
sanitized = "element_#{sanitized}" if sanitized.empty? || sanitized =~ /\A\d/
|
87
|
+
# Avoid conflicts with common Nokogiri methods
|
88
|
+
reserved_words = %w[text comment cdata parent children attributes namespace]
|
89
|
+
sanitized = "data_#{sanitized}" if reserved_words.include?(sanitized)
|
90
|
+
sanitized
|
91
|
+
end
|
92
|
+
|
77
93
|
# SAX handler for streaming
|
78
94
|
class StreamingHandler
|
79
95
|
attr_reader :elements_processed
|
@@ -26,8 +26,19 @@ module Serialbench
|
|
26
26
|
REXML::Document.new(xml_string)
|
27
27
|
end
|
28
28
|
|
29
|
-
def generate(
|
29
|
+
def generate(data, options = {})
|
30
30
|
require 'rexml/document'
|
31
|
+
|
32
|
+
# If data is already a REXML::Document, use it directly
|
33
|
+
if data.is_a?(REXML::Document)
|
34
|
+
document = data
|
35
|
+
else
|
36
|
+
# Convert Hash/other data to XML document
|
37
|
+
document = REXML::Document.new
|
38
|
+
root = document.add_element('root')
|
39
|
+
hash_to_xml(data, root)
|
40
|
+
end
|
41
|
+
|
31
42
|
indent = options.fetch(:indent, 0)
|
32
43
|
output = String.new
|
33
44
|
if indent > 0
|
@@ -63,11 +74,9 @@ module Serialbench
|
|
63
74
|
end
|
64
75
|
|
65
76
|
def supports_streaming?
|
66
|
-
|
77
|
+
false
|
67
78
|
end
|
68
79
|
|
69
|
-
protected
|
70
|
-
|
71
80
|
def supports_xpath?
|
72
81
|
true
|
73
82
|
end
|
@@ -79,6 +88,25 @@ module Serialbench
|
|
79
88
|
def supports_validation?
|
80
89
|
false
|
81
90
|
end
|
91
|
+
|
92
|
+
private
|
93
|
+
|
94
|
+
def hash_to_xml(data, parent)
|
95
|
+
case data
|
96
|
+
when Hash
|
97
|
+
data.each do |key, value|
|
98
|
+
element = parent.add_element(key.to_s)
|
99
|
+
hash_to_xml(value, element)
|
100
|
+
end
|
101
|
+
when Array
|
102
|
+
data.each_with_index do |item, index|
|
103
|
+
element = parent.add_element("item_#{index}")
|
104
|
+
hash_to_xml(item, element)
|
105
|
+
end
|
106
|
+
else
|
107
|
+
parent.text = data.to_s
|
108
|
+
end
|
109
|
+
end
|
82
110
|
end
|
83
111
|
|
84
112
|
# SAX handler for REXML streaming
|
@@ -0,0 +1,59 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative '../base_serializer'
|
4
|
+
|
5
|
+
module Serialbench
|
6
|
+
module Serializers
|
7
|
+
module Yaml
|
8
|
+
# Base class for YAML serializers
|
9
|
+
class BaseYamlSerializer < BaseSerializer
|
10
|
+
def self.format
|
11
|
+
:yaml
|
12
|
+
end
|
13
|
+
|
14
|
+
def supports_streaming?
|
15
|
+
false # Most YAML parsers don't support streaming
|
16
|
+
end
|
17
|
+
|
18
|
+
def features
|
19
|
+
features = %w[parsing generation]
|
20
|
+
features << 'streaming' if supports_streaming?
|
21
|
+
features
|
22
|
+
end
|
23
|
+
|
24
|
+
# Default YAML generation options
|
25
|
+
def default_generation_options
|
26
|
+
{}
|
27
|
+
end
|
28
|
+
|
29
|
+
# Parse YAML string into Ruby object
|
30
|
+
def parse(yaml_string)
|
31
|
+
raise NotImplementedError, 'Subclasses must implement parse method'
|
32
|
+
end
|
33
|
+
|
34
|
+
# Generate YAML string from Ruby object
|
35
|
+
def generate(object, options = {})
|
36
|
+
raise NotImplementedError, 'Subclasses must implement generate method'
|
37
|
+
end
|
38
|
+
|
39
|
+
# Stream parse YAML (if supported)
|
40
|
+
def stream_parse(yaml_string, &block)
|
41
|
+
raise NotImplementedError, 'Streaming not supported by this YAML serializer'
|
42
|
+
end
|
43
|
+
|
44
|
+
def supports_generation?
|
45
|
+
true
|
46
|
+
end
|
47
|
+
|
48
|
+
private
|
49
|
+
|
50
|
+
def require_library(library_name)
|
51
|
+
require library_name
|
52
|
+
true
|
53
|
+
rescue LoadError
|
54
|
+
false
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'base_yaml_serializer'
|
4
|
+
|
5
|
+
module Serialbench
|
6
|
+
module Serializers
|
7
|
+
module Yaml
|
8
|
+
# Psych YAML serializer - Ruby's built-in YAML parser
|
9
|
+
class PsychSerializer < BaseYamlSerializer
|
10
|
+
def available?
|
11
|
+
require_library('psych')
|
12
|
+
end
|
13
|
+
|
14
|
+
def name
|
15
|
+
'psych'
|
16
|
+
end
|
17
|
+
|
18
|
+
def version
|
19
|
+
require 'psych'
|
20
|
+
Psych::VERSION
|
21
|
+
end
|
22
|
+
|
23
|
+
def parse(yaml_string)
|
24
|
+
require 'psych'
|
25
|
+
# Handle Ruby version compatibility for permitted_classes parameter
|
26
|
+
if RUBY_VERSION >= '3.1.0'
|
27
|
+
Psych.load(yaml_string, permitted_classes: [Date, Time, Symbol])
|
28
|
+
else
|
29
|
+
# For older Ruby versions, use the old API
|
30
|
+
Psych.load(yaml_string)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def generate(object, options = {})
|
35
|
+
require 'psych'
|
36
|
+
Psych.dump(object)
|
37
|
+
end
|
38
|
+
|
39
|
+
def features
|
40
|
+
%w[parsing generation built-in]
|
41
|
+
end
|
42
|
+
|
43
|
+
private
|
44
|
+
|
45
|
+
def require_library(library_name)
|
46
|
+
require library_name
|
47
|
+
true
|
48
|
+
rescue LoadError
|
49
|
+
false
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,102 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'base_yaml_serializer'
|
4
|
+
|
5
|
+
module Serialbench
|
6
|
+
module Serializers
|
7
|
+
module Yaml
|
8
|
+
class SyckSerializer < BaseYamlSerializer
|
9
|
+
def name
|
10
|
+
'syck'
|
11
|
+
end
|
12
|
+
|
13
|
+
def version
|
14
|
+
require 'syck'
|
15
|
+
# Try to get version from gem specification
|
16
|
+
spec = Gem.loaded_specs['syck']
|
17
|
+
return spec.version.to_s if spec
|
18
|
+
|
19
|
+
# Fallback to a default version if no gem spec found
|
20
|
+
'1.0.0'
|
21
|
+
rescue StandardError
|
22
|
+
'N/A'
|
23
|
+
end
|
24
|
+
|
25
|
+
def available?
|
26
|
+
require 'syck'
|
27
|
+
# Verify that Syck module and methods are actually available
|
28
|
+
return false unless defined?(Syck) && Syck.respond_to?(:dump) && Syck.respond_to?(:load)
|
29
|
+
|
30
|
+
# Check for known problematic configurations
|
31
|
+
if problematic_environment?
|
32
|
+
warn_about_segfault_issue
|
33
|
+
return false
|
34
|
+
end
|
35
|
+
|
36
|
+
true
|
37
|
+
rescue LoadError
|
38
|
+
false
|
39
|
+
end
|
40
|
+
|
41
|
+
def parse(yaml_string)
|
42
|
+
return nil unless available?
|
43
|
+
|
44
|
+
begin
|
45
|
+
require 'syck'
|
46
|
+
Syck.load(yaml_string)
|
47
|
+
rescue StandardError => e
|
48
|
+
if e.message.include?('Segmentation fault') || e.is_a?(SystemExit)
|
49
|
+
handle_segfault_error
|
50
|
+
return nil
|
51
|
+
end
|
52
|
+
raise e
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def generate(object, options = {})
|
57
|
+
return nil unless available?
|
58
|
+
|
59
|
+
begin
|
60
|
+
require 'syck'
|
61
|
+
Syck.dump(object)
|
62
|
+
rescue StandardError => e
|
63
|
+
if e.message.include?('Segmentation fault') || e.is_a?(SystemExit)
|
64
|
+
handle_segfault_error
|
65
|
+
return nil
|
66
|
+
end
|
67
|
+
raise e
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
def supports_streaming?
|
72
|
+
false
|
73
|
+
end
|
74
|
+
|
75
|
+
def features
|
76
|
+
%w[parsing generation legacy]
|
77
|
+
end
|
78
|
+
|
79
|
+
def description
|
80
|
+
'Legacy YAML parser (Ruby < 1.9.3)'
|
81
|
+
end
|
82
|
+
|
83
|
+
def problematic_environment?
|
84
|
+
# Ruby 3.1+ has issues with Syck
|
85
|
+
(Gem::Version.new(RUBY_VERSION) >= Gem::Version.new('3.1.0')) &&
|
86
|
+
(Gem::Version.new(version) >= Gem::Version.new('1.4.0'))
|
87
|
+
end
|
88
|
+
|
89
|
+
def warn_about_segfault_issue
|
90
|
+
puts "⚠️ WARNING: The Syck YAML serializer is known to cause segmentation faults with Ruby #{RUBY_VERSION}"
|
91
|
+
puts ' This is a known issue with the syck gem on newer Ruby versions systems.'
|
92
|
+
puts ' Skipping Syck benchmarks to prevent crashes. See README for more details.'
|
93
|
+
end
|
94
|
+
|
95
|
+
def handle_segfault_error
|
96
|
+
puts '❌ ERROR: Syck YAML serializer encountered a segmentation fault'
|
97
|
+
puts " This is a known issue on Ruby #{RUBY_VERSION}. Skipping remaining Syck tests."
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
@@ -1,6 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require_relative 'serializers/base_serializer'
|
4
|
+
require_relative 'models/benchmark_result'
|
4
5
|
|
5
6
|
# XML Serializers
|
6
7
|
require_relative 'serializers/xml/base_xml_serializer'
|
@@ -15,16 +16,23 @@ require_relative 'serializers/json/base_json_serializer'
|
|
15
16
|
require_relative 'serializers/json/json_serializer'
|
16
17
|
require_relative 'serializers/json/oj_serializer'
|
17
18
|
require_relative 'serializers/json/yajl_serializer'
|
19
|
+
require_relative 'serializers/json/rapidjson_serializer'
|
20
|
+
|
21
|
+
# YAML Serializers
|
22
|
+
require_relative 'serializers/yaml/base_yaml_serializer'
|
23
|
+
require_relative 'serializers/yaml/psych_serializer'
|
24
|
+
require_relative 'serializers/yaml/syck_serializer'
|
18
25
|
|
19
26
|
# TOML Serializers
|
20
27
|
require_relative 'serializers/toml/base_toml_serializer'
|
21
28
|
require_relative 'serializers/toml/toml_rb_serializer'
|
22
29
|
require_relative 'serializers/toml/tomlib_serializer'
|
30
|
+
require_relative 'serializers/toml/tomlrb_serializer'
|
23
31
|
|
24
32
|
module Serialbench
|
25
33
|
module Serializers
|
26
34
|
# Registry of all available serializers
|
27
|
-
|
35
|
+
REGISTER = {
|
28
36
|
xml: [
|
29
37
|
Xml::RexmlSerializer,
|
30
38
|
Xml::OxSerializer,
|
@@ -35,28 +43,48 @@ module Serialbench
|
|
35
43
|
json: [
|
36
44
|
Json::JsonSerializer,
|
37
45
|
Json::OjSerializer,
|
46
|
+
Json::RapidjsonSerializer,
|
38
47
|
Json::YajlSerializer
|
39
48
|
],
|
49
|
+
yaml: [
|
50
|
+
Yaml::PsychSerializer,
|
51
|
+
Yaml::SyckSerializer
|
52
|
+
],
|
40
53
|
toml: [
|
41
54
|
Toml::TomlRbSerializer,
|
42
|
-
Toml::TomlibSerializer
|
55
|
+
Toml::TomlibSerializer,
|
56
|
+
Toml::TomlrbSerializer
|
43
57
|
]
|
44
58
|
}.freeze
|
45
59
|
|
46
60
|
def self.all
|
47
|
-
|
61
|
+
REGISTER.values.flatten.map(&:instance)
|
48
62
|
end
|
49
63
|
|
50
64
|
def self.for_format(format)
|
51
|
-
|
65
|
+
REGISTER[format.to_sym]&.map(&:instance) || []
|
66
|
+
end
|
67
|
+
|
68
|
+
def self.information
|
69
|
+
return @information if @information
|
70
|
+
|
71
|
+
@information = available.map do |serializer_singleton|
|
72
|
+
Models::SerializerInformation.new(
|
73
|
+
name: serializer_singleton.name,
|
74
|
+
format: serializer_singleton.format.to_s,
|
75
|
+
version: serializer_singleton.version
|
76
|
+
)
|
77
|
+
end
|
78
|
+
|
79
|
+
@information
|
52
80
|
end
|
53
81
|
|
54
82
|
def self.available_for_format(format)
|
55
|
-
for_format(format).select { |
|
83
|
+
for_format(format).select { |serializer_singleton| serializer_singleton.available? }
|
56
84
|
end
|
57
85
|
|
58
86
|
def self.available
|
59
|
-
all.select { |
|
87
|
+
all.select { |serializer_singleton| serializer_singleton.available? }
|
60
88
|
end
|
61
89
|
end
|
62
90
|
end
|
@@ -0,0 +1,105 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'fileutils'
|
4
|
+
require 'erb'
|
5
|
+
require 'json'
|
6
|
+
require 'liquid'
|
7
|
+
require 'yaml'
|
8
|
+
|
9
|
+
module Serialbench
|
10
|
+
# Unified site generator for creating static HTML sites from benchmark results
|
11
|
+
class SiteGenerator
|
12
|
+
TEMPLATE_DIR = File.join(__dir__, 'templates')
|
13
|
+
|
14
|
+
attr_reader :output_path, :result, :resultset
|
15
|
+
|
16
|
+
def initialize(output_path:, result: nil, resultset: nil)
|
17
|
+
@output_path = File.expand_path(output_path)
|
18
|
+
@result = result if result
|
19
|
+
@resultset = resultset if resultset
|
20
|
+
setup_liquid_environment
|
21
|
+
end
|
22
|
+
|
23
|
+
def self.generate_for_result(result, output_path)
|
24
|
+
generator = new(output_path: output_path, result: result)
|
25
|
+
generator.generate_site
|
26
|
+
end
|
27
|
+
|
28
|
+
def self.generate_for_resultset(resultset, output_path)
|
29
|
+
generator = new(output_path: output_path, resultset: resultset)
|
30
|
+
generator.generate_site
|
31
|
+
end
|
32
|
+
|
33
|
+
def generate_site
|
34
|
+
target_name = @result ? @result.environment_config.name : @resultset.name
|
35
|
+
data = @result ? @result.to_json : @resultset.to_json
|
36
|
+
|
37
|
+
puts "🏗️ Generating HTML site for #{@result ? 'run' : 'resultset'}: #{target_name}"
|
38
|
+
puts "Output: #{@output_path}"
|
39
|
+
|
40
|
+
prepare_output_directory
|
41
|
+
render_site(
|
42
|
+
{
|
43
|
+
'data' => data,
|
44
|
+
'kind' => @result ? 'run' : 'resultset'
|
45
|
+
},
|
46
|
+
'format_based.liquid'
|
47
|
+
)
|
48
|
+
|
49
|
+
puts "✅ Site generated successfully at: #{@output_path}"
|
50
|
+
@output_path
|
51
|
+
end
|
52
|
+
|
53
|
+
private
|
54
|
+
|
55
|
+
def setup_liquid_environment
|
56
|
+
@liquid_env = Liquid::Environment.new
|
57
|
+
@liquid_env.file_system = Liquid::LocalFileSystem.new(TEMPLATE_DIR)
|
58
|
+
end
|
59
|
+
|
60
|
+
def prepare_output_directory
|
61
|
+
if Dir.exist?(@output_path)
|
62
|
+
puts 'Cleaning existing output directory...'
|
63
|
+
FileUtils.rm_rf(Dir.glob(File.join(@output_path, '*')))
|
64
|
+
else
|
65
|
+
FileUtils.mkdir_p(@output_path)
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
def render_site(template_data, template_name)
|
70
|
+
# Load and render content template
|
71
|
+
content_template = load_template(template_name)
|
72
|
+
content = content_template.render(template_data)
|
73
|
+
|
74
|
+
# Load and render base template
|
75
|
+
base_template = load_template('base.liquid')
|
76
|
+
html = base_template.render(template_data.merge('content' => content))
|
77
|
+
|
78
|
+
# Write HTML file
|
79
|
+
write_file(html, 'index.html')
|
80
|
+
|
81
|
+
# Copy assets
|
82
|
+
copy_assets
|
83
|
+
end
|
84
|
+
|
85
|
+
def load_template(template_name)
|
86
|
+
template_path = File.join(TEMPLATE_DIR, template_name)
|
87
|
+
template_content = File.read(template_path)
|
88
|
+
Liquid::Template.parse(template_content)
|
89
|
+
end
|
90
|
+
|
91
|
+
def write_file(content, filename)
|
92
|
+
FileUtils.mkdir_p(@output_path)
|
93
|
+
File.write(File.join(@output_path, filename), content)
|
94
|
+
end
|
95
|
+
|
96
|
+
def copy_assets
|
97
|
+
assets_source = File.join(TEMPLATE_DIR, 'assets')
|
98
|
+
assets_dest = File.join(@output_path, 'assets')
|
99
|
+
|
100
|
+
return unless Dir.exist?(assets_source)
|
101
|
+
|
102
|
+
FileUtils.cp_r(assets_source, assets_dest)
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|