serialbench 0.1.1 → 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.
- checksums.yaml +4 -4
- data/.github/workflows/benchmark.yml +273 -220
- data/.github/workflows/rake.yml +26 -0
- data/.github/workflows/windows-debug.yml +171 -0
- data/.gitignore +32 -0
- data/.rubocop.yml +1 -0
- data/.rubocop_todo.yml +274 -0
- data/Gemfile +14 -1
- data/README.adoc +292 -1118
- 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/data/schemas/result.yml +29 -0
- data/docker/Dockerfile.alpine +33 -0
- data/docker/{Dockerfile.benchmark → Dockerfile.ubuntu} +4 -3
- data/docker/README.md +2 -2
- data/docs/PLATFORM_VALIDATION_FIX.md +79 -0
- data/docs/SYCK_YAML_FIX.md +91 -0
- data/docs/WEBSITE_COMPLETION_PLAN.md +440 -0
- data/docs/WINDOWS_LIBXML_FIX.md +136 -0
- data/docs/WINDOWS_SETUP.md +122 -0
- data/exe/serialbench +1 -1
- data/lib/serialbench/benchmark_runner.rb +261 -423
- data/lib/serialbench/cli/base_cli.rb +51 -0
- data/lib/serialbench/cli/benchmark_cli.rb +453 -0
- data/lib/serialbench/cli/environment_cli.rb +181 -0
- data/lib/serialbench/cli/resultset_cli.rb +261 -0
- data/lib/serialbench/cli/ruby_build_cli.rb +225 -0
- data/lib/serialbench/cli/validate_cli.rb +88 -0
- data/lib/serialbench/cli.rb +61 -600
- data/lib/serialbench/config_manager.rb +129 -0
- data/lib/serialbench/models/benchmark_config.rb +75 -0
- data/lib/serialbench/models/benchmark_result.rb +81 -0
- data/lib/serialbench/models/environment_config.rb +72 -0
- data/lib/serialbench/models/platform.rb +111 -0
- data/lib/serialbench/models/result.rb +80 -0
- data/lib/serialbench/models/result_set.rb +79 -0
- data/lib/serialbench/models/result_store.rb +108 -0
- data/lib/serialbench/models.rb +54 -0
- data/lib/serialbench/ruby_build_manager.rb +149 -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 +140 -0
- data/lib/serialbench/runners/local_runner.rb +71 -0
- data/lib/serialbench/serializers/base_serializer.rb +9 -17
- 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 +1 -1
- data/lib/serialbench/serializers/json/yajl_serializer.rb +0 -2
- data/lib/serialbench/serializers/toml/base_toml_serializer.rb +5 -5
- data/lib/serialbench/serializers/toml/toml_rb_serializer.rb +1 -3
- data/lib/serialbench/serializers/toml/tomlib_serializer.rb +1 -3
- 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 +4 -10
- data/lib/serialbench/serializers/xml/nokogiri_serializer.rb +2 -4
- data/lib/serialbench/serializers/xml/oga_serializer.rb +4 -10
- data/lib/serialbench/serializers/xml/ox_serializer.rb +2 -4
- data/lib/serialbench/serializers/xml/rexml_serializer.rb +3 -5
- data/lib/serialbench/serializers/yaml/base_yaml_serializer.rb +5 -1
- data/lib/serialbench/serializers/yaml/psych_serializer.rb +1 -1
- data/lib/serialbench/serializers/yaml/syck_serializer.rb +60 -23
- data/lib/serialbench/serializers.rb +23 -6
- data/lib/serialbench/site_generator.rb +283 -0
- data/lib/serialbench/templates/assets/css/benchmark_report.css +535 -0
- data/lib/serialbench/templates/assets/css/format_based.css +474 -0
- data/lib/serialbench/templates/assets/css/themes.css +589 -0
- data/lib/serialbench/templates/assets/js/chart_helpers.js +411 -0
- data/lib/serialbench/templates/assets/js/dashboard.js +795 -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 +507 -0
- data/lib/serialbench/templates/partials/chart_section.liquid +4 -0
- data/lib/serialbench/version.rb +1 -1
- data/lib/serialbench/yaml_validator.rb +36 -0
- data/lib/serialbench.rb +2 -31
- data/serialbench.gemspec +15 -3
- metadata +106 -25
- data/.github/workflows/ci.yml +0 -74
- data/.github/workflows/docker.yml +0 -246
- data/config/ci.yml +0 -22
- data/config/full.yml +0 -30
- data/docker/run-benchmarks.sh +0 -356
- 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,140 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'fileutils'
|
|
4
|
+
require 'json'
|
|
5
|
+
require_relative '../ruby_build_manager'
|
|
6
|
+
require_relative 'base'
|
|
7
|
+
|
|
8
|
+
module Serialbench
|
|
9
|
+
module Runners
|
|
10
|
+
# Handles Docker-based benchmark execution
|
|
11
|
+
class DockerRunner < Base
|
|
12
|
+
class DockerError < StandardError; end
|
|
13
|
+
|
|
14
|
+
def initialize(environment_config, environment_config_path)
|
|
15
|
+
super
|
|
16
|
+
|
|
17
|
+
@docker_image = @environment_config.docker&.image
|
|
18
|
+
raise 'docker.image is required' unless @docker_image
|
|
19
|
+
|
|
20
|
+
# Handle dockerfile path relative to environment file
|
|
21
|
+
dockerfile_path = @environment_config.docker&.dockerfile
|
|
22
|
+
raise 'docker.dockerfile path is required' unless dockerfile_path
|
|
23
|
+
|
|
24
|
+
@dockerfile = Pathname.new(environment_config_path).dirname.join(dockerfile_path).to_s
|
|
25
|
+
raise "path '#{@dockerfile}' specified in docker.dockerfile is not found" unless File.exist?(@dockerfile)
|
|
26
|
+
|
|
27
|
+
validate_docker_available
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# Build Docker image for this environment
|
|
31
|
+
def prepare
|
|
32
|
+
puts "🐳 Preparing Docker image for #{@name}..."
|
|
33
|
+
|
|
34
|
+
puts " 🐍 Using Docker image: #{base_image_string}"
|
|
35
|
+
puts " 🔨 Building Docker image: #{docker_image_string}"
|
|
36
|
+
|
|
37
|
+
cmd = [
|
|
38
|
+
'docker', 'build',
|
|
39
|
+
'--build-arg', "BASE_IMAGE=#{base_image_string}",
|
|
40
|
+
'-t', docker_image_string,
|
|
41
|
+
'-f', @dockerfile,
|
|
42
|
+
'.'
|
|
43
|
+
]
|
|
44
|
+
|
|
45
|
+
# Show command being run
|
|
46
|
+
puts " 🔧 Running command: #{cmd.join(' ')}"
|
|
47
|
+
success = system(*cmd)
|
|
48
|
+
|
|
49
|
+
raise DockerError, "Failed to build Docker image '#{docker_image_string}'" unless success
|
|
50
|
+
|
|
51
|
+
puts "✅ Docker image prepared successfully for #{docker_image_string}"
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def base_image_string
|
|
55
|
+
@environment_config.docker.image
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def docker_image_string
|
|
59
|
+
"serialbench:#{@environment_config.ruby_build_tag}"
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
# Run benchmark for this environment
|
|
63
|
+
def run_benchmark(benchmark_config, benchmark_config_path, result_dir)
|
|
64
|
+
puts "🚀 Running benchmark at #{@environment_config_path}..."
|
|
65
|
+
|
|
66
|
+
FileUtils.mkdir_p(result_dir)
|
|
67
|
+
|
|
68
|
+
# Build image if not already built
|
|
69
|
+
prepare unless image_exists?
|
|
70
|
+
|
|
71
|
+
# Run the benchmark
|
|
72
|
+
run_benchmark_in_container(benchmark_config, benchmark_config_path, result_dir)
|
|
73
|
+
|
|
74
|
+
puts "✅ Benchmark completed for #{@environment_config_path}"
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
private
|
|
78
|
+
|
|
79
|
+
# Validate Docker is available
|
|
80
|
+
def validate_docker_available
|
|
81
|
+
raise DockerError, 'Docker is not installed or not available in PATH' unless system('docker --version > /dev/null 2>&1')
|
|
82
|
+
|
|
83
|
+
return if system('docker info > /dev/null 2>&1')
|
|
84
|
+
|
|
85
|
+
raise DockerError, 'Docker daemon is not running'
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
# Check if Docker image exists
|
|
89
|
+
def image_exists?
|
|
90
|
+
image_name = "serialbench:#{@name}"
|
|
91
|
+
system("docker image inspect #{image_name} > /dev/null 2>&1")
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
# Run benchmark in container
|
|
95
|
+
def run_benchmark_in_container(_benchmark_config, benchmark_config_path, result_dir)
|
|
96
|
+
puts '🏃 Running benchmark in Docker container...'
|
|
97
|
+
puts " 📁 Results will be saved to: #{result_dir}"
|
|
98
|
+
puts " 🐳 Using Docker image: #{docker_image_string}"
|
|
99
|
+
|
|
100
|
+
benchmark_log = File.join(result_dir, 'benchmark.log')
|
|
101
|
+
|
|
102
|
+
cmd = [
|
|
103
|
+
'docker', 'run', '--rm',
|
|
104
|
+
'-v', "#{File.expand_path(result_dir)}:/app/results",
|
|
105
|
+
'-v', "#{File.expand_path(@environment_config_path)}:/app/environment.yml",
|
|
106
|
+
'-v', "#{File.expand_path(benchmark_config_path)}:/app/benchmark_config.yml",
|
|
107
|
+
# TODO: do not hard code this path in the docker image
|
|
108
|
+
'-v', "#{RubyBuildManager::CACHE_FILE}:/root/.serialbench/ruby-build-definitions.yaml",
|
|
109
|
+
docker_image_string,
|
|
110
|
+
'benchmark',
|
|
111
|
+
'_docker_execute',
|
|
112
|
+
'--result_dir=/app/results',
|
|
113
|
+
'/app/environment.yml',
|
|
114
|
+
'/app/benchmark_config.yml'
|
|
115
|
+
]
|
|
116
|
+
|
|
117
|
+
puts " 🔧 Running command: #{cmd.join(' ')}"
|
|
118
|
+
puts " 📝 Benchmark output will be logged to: #{benchmark_log}"
|
|
119
|
+
puts ' ⏳ This may take several minutes...'
|
|
120
|
+
|
|
121
|
+
success = system("#{cmd.join(' ')} > #{benchmark_log} 2>&1")
|
|
122
|
+
puts " ✅ Command executed with status: #{success ? 'success' : 'failure'}"
|
|
123
|
+
puts " 📊 Checking if results saved to: #{File.join(result_dir, 'results.yaml')}"
|
|
124
|
+
|
|
125
|
+
if success && File.exist?(File.join(result_dir, 'results.yaml'))
|
|
126
|
+
puts '✅ Benchmark completed successfully'
|
|
127
|
+
puts " 📊 Results saved to: #{File.join(result_dir, 'results.yaml')}"
|
|
128
|
+
else
|
|
129
|
+
puts "❌ Benchmark failed (see #{benchmark_log})"
|
|
130
|
+
puts " 🔍 Check log file for details: #{benchmark_log}"
|
|
131
|
+
if File.exist?(benchmark_log)
|
|
132
|
+
puts ' 📄 Last few lines of log:'
|
|
133
|
+
system("tail -10 #{benchmark_log} | sed 's/^/ /'")
|
|
134
|
+
end
|
|
135
|
+
raise DockerError, 'Benchmark execution failed'
|
|
136
|
+
end
|
|
137
|
+
end
|
|
138
|
+
end
|
|
139
|
+
end
|
|
140
|
+
end
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'fileutils'
|
|
4
|
+
require_relative 'base'
|
|
5
|
+
require_relative '../benchmark_runner'
|
|
6
|
+
require_relative '../models/result'
|
|
7
|
+
require_relative '../models/platform'
|
|
8
|
+
|
|
9
|
+
module Serialbench
|
|
10
|
+
module Runners
|
|
11
|
+
# Handles local benchmark execution using the current Ruby environment
|
|
12
|
+
class LocalRunner < Base
|
|
13
|
+
def prepare
|
|
14
|
+
# For local environments, no preparation is needed
|
|
15
|
+
# The current Ruby environment is already set up
|
|
16
|
+
puts "✅ Local environment ready (using current Ruby: #{RUBY_VERSION})"
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def run_benchmark(benchmark_config, benchmark_config_path, result_dir)
|
|
20
|
+
puts "🏠 Running benchmark locally with Ruby #{RUBY_VERSION}..."
|
|
21
|
+
puts " 📁 Results will be saved to: #{result_dir}"
|
|
22
|
+
|
|
23
|
+
FileUtils.mkdir_p(result_dir)
|
|
24
|
+
|
|
25
|
+
# Run the benchmark
|
|
26
|
+
runner = Serialbench::BenchmarkRunner.new(
|
|
27
|
+
environment_config: @environment_config,
|
|
28
|
+
benchmark_config: benchmark_config
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
results = runner.run_all_benchmarks
|
|
32
|
+
|
|
33
|
+
# Get platform information with correct Ruby version from environment config
|
|
34
|
+
platform = Serialbench::Models::Platform.current_local(
|
|
35
|
+
ruby_version: @environment_config.ruby_build_tag
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
# Create metadata
|
|
39
|
+
metadata = Models::RunMetadata.new(
|
|
40
|
+
benchmark_config_path: benchmark_config_path,
|
|
41
|
+
environment_config_path: @environment_config_path,
|
|
42
|
+
tags: [
|
|
43
|
+
'local',
|
|
44
|
+
platform.os,
|
|
45
|
+
platform.arch,
|
|
46
|
+
"ruby-#{@environment_config.ruby_build_tag}"
|
|
47
|
+
]
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
# Save results
|
|
51
|
+
results_model = Models::Result.new(
|
|
52
|
+
platform: platform,
|
|
53
|
+
metadata: metadata,
|
|
54
|
+
environment_config: @environment_config,
|
|
55
|
+
benchmark_config: benchmark_config,
|
|
56
|
+
benchmark_result: results
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
# Restore YAML to use Psych for output, otherwise lutaml-model's to_yaml
|
|
60
|
+
# will have no output (Syck gem overrides YAML constant)
|
|
61
|
+
Object.const_set(:YAML, Psych)
|
|
62
|
+
|
|
63
|
+
results_file = File.join(result_dir, 'results.yaml')
|
|
64
|
+
results_model.to_file(results_file)
|
|
65
|
+
|
|
66
|
+
puts "✅ Benchmark completed successfully"
|
|
67
|
+
puts " 📊 Results saved to: #{results_file}"
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
end
|
|
@@ -1,26 +1,20 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require 'singleton'
|
|
4
|
+
|
|
3
5
|
module Serialbench
|
|
4
6
|
module Serializers
|
|
5
7
|
class BaseSerializer
|
|
8
|
+
include Singleton
|
|
9
|
+
|
|
6
10
|
def initialize
|
|
7
11
|
# Override in subclasses
|
|
8
12
|
end
|
|
9
13
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
def name
|
|
15
|
-
raise NotImplementedError, 'Subclasses must implement #name'
|
|
16
|
-
end
|
|
17
|
-
|
|
18
|
-
def version
|
|
19
|
-
raise NotImplementedError, 'Subclasses must implement #version'
|
|
20
|
-
end
|
|
21
|
-
|
|
22
|
-
def format
|
|
23
|
-
raise NotImplementedError, 'Subclasses must implement #format'
|
|
14
|
+
%w[name version format library_require_name available?].each do |method_name|
|
|
15
|
+
define_method(method_name) do
|
|
16
|
+
self.class.send(method_name)
|
|
17
|
+
end
|
|
24
18
|
end
|
|
25
19
|
|
|
26
20
|
def parse(data)
|
|
@@ -31,7 +25,7 @@ module Serialbench
|
|
|
31
25
|
raise NotImplementedError, 'Subclasses must implement #generate'
|
|
32
26
|
end
|
|
33
27
|
|
|
34
|
-
def stream_parse(data
|
|
28
|
+
def stream_parse(data)
|
|
35
29
|
# Default implementation falls back to regular parsing
|
|
36
30
|
# Override in subclasses that support streaming
|
|
37
31
|
result = parse(data)
|
|
@@ -44,8 +38,6 @@ module Serialbench
|
|
|
44
38
|
false
|
|
45
39
|
end
|
|
46
40
|
|
|
47
|
-
protected
|
|
48
|
-
|
|
49
41
|
def require_library(library_name)
|
|
50
42
|
require library_name
|
|
51
43
|
true
|
|
@@ -6,7 +6,7 @@ module Serialbench
|
|
|
6
6
|
module Serializers
|
|
7
7
|
module Json
|
|
8
8
|
class BaseJsonSerializer < BaseSerializer
|
|
9
|
-
def format
|
|
9
|
+
def self.format
|
|
10
10
|
:json
|
|
11
11
|
end
|
|
12
12
|
|
|
@@ -29,7 +29,9 @@ module Serialbench
|
|
|
29
29
|
}
|
|
30
30
|
end
|
|
31
31
|
|
|
32
|
-
|
|
32
|
+
def supports_generation?
|
|
33
|
+
true
|
|
34
|
+
end
|
|
33
35
|
|
|
34
36
|
def supports_pretty_print?
|
|
35
37
|
true
|
|
@@ -48,8 +50,6 @@ module Serialbench
|
|
|
48
50
|
raise NotImplementedError, 'Subclasses must implement #library_require_name'
|
|
49
51
|
end
|
|
50
52
|
|
|
51
|
-
public
|
|
52
|
-
|
|
53
53
|
# Check if the JSON library is available
|
|
54
54
|
def available?
|
|
55
55
|
return @available if defined?(@available)
|
|
@@ -6,7 +6,7 @@ module Serialbench
|
|
|
6
6
|
module Serializers
|
|
7
7
|
module Toml
|
|
8
8
|
class BaseTomlSerializer < BaseSerializer
|
|
9
|
-
def format
|
|
9
|
+
def self.format
|
|
10
10
|
:toml
|
|
11
11
|
end
|
|
12
12
|
|
|
@@ -29,13 +29,15 @@ module Serialbench
|
|
|
29
29
|
}
|
|
30
30
|
end
|
|
31
31
|
|
|
32
|
+
def supports_generation?
|
|
33
|
+
true
|
|
34
|
+
end
|
|
35
|
+
|
|
32
36
|
def supports_streaming?
|
|
33
37
|
# TOML is typically not streamed due to its structure
|
|
34
38
|
false
|
|
35
39
|
end
|
|
36
40
|
|
|
37
|
-
protected
|
|
38
|
-
|
|
39
41
|
def supports_comments?
|
|
40
42
|
false
|
|
41
43
|
end
|
|
@@ -57,8 +59,6 @@ module Serialbench
|
|
|
57
59
|
raise NotImplementedError, 'Subclasses must implement #library_require_name'
|
|
58
60
|
end
|
|
59
61
|
|
|
60
|
-
public
|
|
61
|
-
|
|
62
62
|
# Check if the TOML library is available
|
|
63
63
|
def available?
|
|
64
64
|
return @available if defined?(@available)
|
|
@@ -27,13 +27,11 @@ module Serialbench
|
|
|
27
27
|
TomlRB.parse(toml_string)
|
|
28
28
|
end
|
|
29
29
|
|
|
30
|
-
def generate(object,
|
|
30
|
+
def generate(object, _options = {})
|
|
31
31
|
require 'toml-rb'
|
|
32
32
|
TomlRB.dump(object)
|
|
33
33
|
end
|
|
34
34
|
|
|
35
|
-
protected
|
|
36
|
-
|
|
37
35
|
def supports_comments?
|
|
38
36
|
false
|
|
39
37
|
end
|
|
@@ -24,7 +24,7 @@ module Serialbench
|
|
|
24
24
|
# TOML doesn't typically support streaming parsing
|
|
25
25
|
# Parse the entire document and yield it
|
|
26
26
|
result = parse(toml_string)
|
|
27
|
-
block&.call(result)
|
|
27
|
+
block&.call(result)
|
|
28
28
|
1 # Return 1 document processed
|
|
29
29
|
end
|
|
30
30
|
|
|
@@ -39,8 +39,6 @@ module Serialbench
|
|
|
39
39
|
Tomlib::VERSION
|
|
40
40
|
end
|
|
41
41
|
|
|
42
|
-
protected
|
|
43
|
-
|
|
44
42
|
def library_require_name
|
|
45
43
|
'tomlib'
|
|
46
44
|
end
|
|
@@ -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
|
LibXML::XML::VERSION
|
|
50
50
|
end
|
|
51
51
|
|
|
52
|
-
protected
|
|
53
|
-
|
|
54
52
|
def library_require_name
|
|
55
53
|
'libxml'
|
|
56
54
|
end
|
|
@@ -60,26 +58,22 @@ module Serialbench
|
|
|
60
58
|
def build_xml_from_data(data, name = 'root')
|
|
61
59
|
require 'libxml'
|
|
62
60
|
|
|
61
|
+
element = LibXML::XML::Node.new(name.to_s)
|
|
63
62
|
case data
|
|
64
63
|
when Hash
|
|
65
|
-
element = LibXML::XML::Node.new(name.to_s)
|
|
66
64
|
data.each do |key, value|
|
|
67
65
|
child = build_xml_from_data(value, key.to_s)
|
|
68
66
|
element << child
|
|
69
67
|
end
|
|
70
|
-
element
|
|
71
68
|
when Array
|
|
72
|
-
element = LibXML::XML::Node.new(name.to_s)
|
|
73
69
|
data.each_with_index do |item, index|
|
|
74
70
|
child = build_xml_from_data(item, "item_#{index}")
|
|
75
71
|
element << child
|
|
76
72
|
end
|
|
77
|
-
element
|
|
78
73
|
else
|
|
79
|
-
element = LibXML::XML::Node.new(name.to_s)
|
|
80
74
|
element.content = data.to_s
|
|
81
|
-
element
|
|
82
75
|
end
|
|
76
|
+
element
|
|
83
77
|
end
|
|
84
78
|
|
|
85
79
|
# SAX handler for streaming
|
|
@@ -102,10 +96,10 @@ module Serialbench
|
|
|
102
96
|
@element_stack.push(@current_element)
|
|
103
97
|
end
|
|
104
98
|
|
|
105
|
-
def on_end_element(
|
|
99
|
+
def on_end_element(_element)
|
|
106
100
|
element_data = @element_stack.pop
|
|
107
101
|
if @element_stack.empty?
|
|
108
|
-
@block&.call(element_data)
|
|
102
|
+
@block&.call(element_data)
|
|
109
103
|
else
|
|
110
104
|
@element_stack.last[:children] << element_data
|
|
111
105
|
end
|
|
@@ -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
|
|
@@ -111,10 +109,10 @@ module Serialbench
|
|
|
111
109
|
@element_stack.push(@current_element)
|
|
112
110
|
end
|
|
113
111
|
|
|
114
|
-
def end_element(
|
|
112
|
+
def end_element(_name)
|
|
115
113
|
element = @element_stack.pop
|
|
116
114
|
if @element_stack.empty?
|
|
117
|
-
@block&.call(element)
|
|
115
|
+
@block&.call(element)
|
|
118
116
|
else
|
|
119
117
|
@element_stack.last[:children] << element
|
|
120
118
|
end
|
|
@@ -49,8 +49,6 @@ module Serialbench
|
|
|
49
49
|
Oga::VERSION
|
|
50
50
|
end
|
|
51
51
|
|
|
52
|
-
protected
|
|
53
|
-
|
|
54
52
|
def library_require_name
|
|
55
53
|
'oga'
|
|
56
54
|
end
|
|
@@ -60,27 +58,23 @@ module Serialbench
|
|
|
60
58
|
def build_xml_from_data(data, name = 'root')
|
|
61
59
|
require 'oga'
|
|
62
60
|
|
|
61
|
+
element = Oga::XML::Element.new(name: name)
|
|
63
62
|
case data
|
|
64
63
|
when Hash
|
|
65
|
-
element = Oga::XML::Element.new(name: name)
|
|
66
64
|
data.each do |key, value|
|
|
67
65
|
child = build_xml_from_data(value, key.to_s)
|
|
68
66
|
element.children << child
|
|
69
67
|
end
|
|
70
|
-
element
|
|
71
68
|
when Array
|
|
72
|
-
element = Oga::XML::Element.new(name: name)
|
|
73
69
|
data.each_with_index do |item, index|
|
|
74
70
|
child = build_xml_from_data(item, "item_#{index}")
|
|
75
71
|
element.children << child
|
|
76
72
|
end
|
|
77
|
-
element
|
|
78
73
|
else
|
|
79
|
-
element = Oga::XML::Element.new(name: name)
|
|
80
74
|
text_node = Oga::XML::Text.new(text: data.to_s)
|
|
81
75
|
element.children << text_node
|
|
82
|
-
element
|
|
83
76
|
end
|
|
77
|
+
element
|
|
84
78
|
end
|
|
85
79
|
|
|
86
80
|
# SAX handler for streaming
|
|
@@ -110,10 +104,10 @@ module Serialbench
|
|
|
110
104
|
@element_stack.last[:text] += text if @element_stack.any?
|
|
111
105
|
end
|
|
112
106
|
|
|
113
|
-
def after_element(
|
|
107
|
+
def after_element(_namespace, _name)
|
|
114
108
|
element = @element_stack.pop
|
|
115
109
|
if @element_stack.empty?
|
|
116
|
-
@block&.call(element)
|
|
110
|
+
@block&.call(element)
|
|
117
111
|
else
|
|
118
112
|
@element_stack.last[:children] << element
|
|
119
113
|
end
|
|
@@ -40,8 +40,6 @@ module Serialbench
|
|
|
40
40
|
Ox::VERSION
|
|
41
41
|
end
|
|
42
42
|
|
|
43
|
-
protected
|
|
44
|
-
|
|
45
43
|
def library_require_name
|
|
46
44
|
'ox'
|
|
47
45
|
end
|
|
@@ -63,10 +61,10 @@ module Serialbench
|
|
|
63
61
|
@element_stack.push(@current_element)
|
|
64
62
|
end
|
|
65
63
|
|
|
66
|
-
def end_element(
|
|
64
|
+
def end_element(_name)
|
|
67
65
|
element = @element_stack.pop
|
|
68
66
|
if @element_stack.empty?
|
|
69
|
-
@block&.call(element)
|
|
67
|
+
@block&.call(element)
|
|
70
68
|
else
|
|
71
69
|
@element_stack.last[:children] << element
|
|
72
70
|
end
|
|
@@ -41,7 +41,7 @@ module Serialbench
|
|
|
41
41
|
|
|
42
42
|
indent = options.fetch(:indent, 0)
|
|
43
43
|
output = String.new
|
|
44
|
-
if indent
|
|
44
|
+
if indent.positive?
|
|
45
45
|
document.write(output, indent)
|
|
46
46
|
else
|
|
47
47
|
document.write(output)
|
|
@@ -77,8 +77,6 @@ module Serialbench
|
|
|
77
77
|
false
|
|
78
78
|
end
|
|
79
79
|
|
|
80
|
-
protected
|
|
81
|
-
|
|
82
80
|
def supports_xpath?
|
|
83
81
|
true
|
|
84
82
|
end
|
|
@@ -122,7 +120,7 @@ module Serialbench
|
|
|
122
120
|
@result = nil
|
|
123
121
|
end
|
|
124
122
|
|
|
125
|
-
def start_element(
|
|
123
|
+
def start_element(_uri, _localname, qname, attributes)
|
|
126
124
|
@elements_processed += 1
|
|
127
125
|
@block&.call(:start_element, { name: qname, attributes: attributes })
|
|
128
126
|
end
|
|
@@ -134,7 +132,7 @@ module Serialbench
|
|
|
134
132
|
@block&.call(:text, text)
|
|
135
133
|
end
|
|
136
134
|
|
|
137
|
-
def end_element(
|
|
135
|
+
def end_element(_uri, _localname, qname)
|
|
138
136
|
@block&.call(:end_element, { name: qname })
|
|
139
137
|
end
|
|
140
138
|
end
|