oriole 0.1.0.alpha.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (80) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +56 -0
  3. data/.rubocop.yml +32 -0
  4. data/.ruby-version +1 -0
  5. data/CHANGELOG.md +1 -0
  6. data/Gemfile +20 -0
  7. data/Gemfile.lock +121 -0
  8. data/LICENSE +21 -0
  9. data/README.md +84 -0
  10. data/Rakefile +12 -0
  11. data/bench/bench.rb +35 -0
  12. data/bench/execute.rb +66 -0
  13. data/bench/schemas/bluejay.rb +149 -0
  14. data/bench/schemas/models.rb +216 -0
  15. data/bench/schemas/oriole.rb +144 -0
  16. data/bin/console +15 -0
  17. data/bin/setup +8 -0
  18. data/bin/style +2 -0
  19. data/bin/tapioca +27 -0
  20. data/bin/typecheck +2 -0
  21. data/lib/oriole/builtin_scalar_definition.rb +53 -0
  22. data/lib/oriole/definition/abstract/definition.rb +21 -0
  23. data/lib/oriole/definition/abstract/field_definition.rb +22 -0
  24. data/lib/oriole/definition/abstract/object_type_definition.rb +21 -0
  25. data/lib/oriole/definition/abstract/schema_definition.rb +18 -0
  26. data/lib/oriole/definition/abstract/visibility.rb +18 -0
  27. data/lib/oriole/definition/base_output_type.rb +13 -0
  28. data/lib/oriole/definition/field_definition.rb +27 -0
  29. data/lib/oriole/definition/object_type.rb +40 -0
  30. data/lib/oriole/definition/output_type.rb +79 -0
  31. data/lib/oriole/definition/schema.rb +45 -0
  32. data/lib/oriole/definition/visibility_scoped/object_type_definition.rb +62 -0
  33. data/lib/oriole/definition/visibility_scoped/schema_definition.rb +26 -0
  34. data/lib/oriole/execution/coerce_result.rb +16 -0
  35. data/lib/oriole/execution/engine.rb +207 -0
  36. data/lib/oriole/version.rb +6 -0
  37. data/lib/oriole.rb +14 -0
  38. data/oriole.gemspec +38 -0
  39. data/rakelib/bench.rake +69 -0
  40. data/sorbet/config +5 -0
  41. data/sorbet/rbi/annotations/rainbow.rbi +269 -0
  42. data/sorbet/rbi/gems/ast@2.4.2.rbi +584 -0
  43. data/sorbet/rbi/gems/base64@0.1.1.rbi +172 -0
  44. data/sorbet/rbi/gems/bluejay@0.1.0.alpha.2-a45c1b3b47aa4524f94bbe03db5ff1ae792afd09.rbi +1206 -0
  45. data/sorbet/rbi/gems/diff-lcs@1.5.0.rbi +1083 -0
  46. data/sorbet/rbi/gems/erubi@1.12.0.rbi +145 -0
  47. data/sorbet/rbi/gems/json@2.6.3.rbi +1533 -0
  48. data/sorbet/rbi/gems/language_server-protocol@3.17.0.3.rbi +14237 -0
  49. data/sorbet/rbi/gems/minitest@5.19.0.rbi +1491 -0
  50. data/sorbet/rbi/gems/netrc@0.11.0.rbi +158 -0
  51. data/sorbet/rbi/gems/parallel@1.23.0.rbi +273 -0
  52. data/sorbet/rbi/gems/parser@3.2.2.3.rbi +7253 -0
  53. data/sorbet/rbi/gems/prettier_print@1.2.1.rbi +951 -0
  54. data/sorbet/rbi/gems/racc@1.7.1.rbi +161 -0
  55. data/sorbet/rbi/gems/rainbow@3.1.1.rbi +402 -0
  56. data/sorbet/rbi/gems/rake@13.0.6.rbi +3024 -0
  57. data/sorbet/rbi/gems/rbi@0.0.17.rbi +2972 -0
  58. data/sorbet/rbi/gems/regexp_parser@2.8.1.rbi +3749 -0
  59. data/sorbet/rbi/gems/rexml@3.2.6.rbi +4781 -0
  60. data/sorbet/rbi/gems/rubocop-ast@1.29.0.rbi +7006 -0
  61. data/sorbet/rbi/gems/rubocop-minitest@0.27.0.rbi +2371 -0
  62. data/sorbet/rbi/gems/rubocop-rake@0.6.0.rbi +328 -0
  63. data/sorbet/rbi/gems/rubocop-shopify@2.14.0.rbi +8 -0
  64. data/sorbet/rbi/gems/rubocop-sorbet@0.6.11.rbi +975 -0
  65. data/sorbet/rbi/gems/rubocop@1.56.1.rbi +56525 -0
  66. data/sorbet/rbi/gems/ruby-lsp@0.4.5.rbi +11 -0
  67. data/sorbet/rbi/gems/ruby-progressbar@1.13.0.rbi +1317 -0
  68. data/sorbet/rbi/gems/spoom@1.2.3.rbi +3203 -0
  69. data/sorbet/rbi/gems/syntax_tree@6.1.1.rbi +22855 -0
  70. data/sorbet/rbi/gems/tapioca@0.11.8.rbi +3349 -0
  71. data/sorbet/rbi/gems/thor@1.2.2.rbi +3965 -0
  72. data/sorbet/rbi/gems/tinygql@0.1.4.rbi +2363 -0
  73. data/sorbet/rbi/gems/unicode-display_width@2.4.2.rbi +65 -0
  74. data/sorbet/rbi/gems/unparser@0.6.8.rbi +4525 -0
  75. data/sorbet/rbi/gems/yard-sorbet@0.8.1.rbi +428 -0
  76. data/sorbet/rbi/gems/yard@0.9.34.rbi +18219 -0
  77. data/sorbet/rbi/gems/zeitwerk@2.6.11.rbi +999 -0
  78. data/sorbet/tapioca/config.yml +13 -0
  79. data/sorbet/tapioca/require.rb +5 -0
  80. metadata +208 -0
@@ -0,0 +1,79 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module Oriole
5
+ module Definition
6
+ module OutputType
7
+ extend(T::Sig)
8
+ extend(T::Helpers)
9
+
10
+ class << self
11
+ extend(T::Sig)
12
+
13
+ sig { params(inner: BaseOutputType).returns(OutputType) }
14
+ def named(inner) = Named.new(required: false, inner:)
15
+
16
+ sig { params(inner: BaseOutputType).returns(OutputType) }
17
+ def named!(inner) = Named.new(required: true, inner:)
18
+
19
+ sig { params(inner: OutputType).returns(OutputType) }
20
+ def list(inner) = List.new(required: false, inner:)
21
+
22
+ sig { params(inner: OutputType).returns(OutputType) }
23
+ def list!(inner) = List.new(required: true, inner:)
24
+ end
25
+
26
+ sealed!
27
+ abstract!
28
+
29
+ sig { abstract.returns(T::Boolean) }
30
+ def required?; end
31
+
32
+ sig { abstract.returns(T::Boolean) }
33
+ def list?; end
34
+
35
+ sig { abstract.returns(BaseOutputType) }
36
+ def base; end
37
+
38
+ sig { abstract.params(base: BaseOutputType).returns(T.self_type) }
39
+ def with_base(base); end
40
+
41
+ class List < T::Struct
42
+ extend(T::Sig)
43
+ include(OutputType)
44
+
45
+ const(:required, T::Boolean)
46
+ const(:inner, OutputType)
47
+
48
+ alias_method(:required?, :required)
49
+
50
+ sig { override.returns(T::Boolean) }
51
+ def list? = true
52
+
53
+ sig { override.returns(BaseOutputType) }
54
+ def base = inner.base
55
+
56
+ sig { override.params(base: BaseOutputType).returns(T.self_type) }
57
+ def with_base(base) = List.new(required:, inner: inner.with_base(base))
58
+ end
59
+
60
+ class Named < T::Struct
61
+ extend(T::Sig)
62
+ include(OutputType)
63
+
64
+ const(:required, T::Boolean)
65
+ const(:inner, BaseOutputType)
66
+
67
+ alias_method(:required?, :required)
68
+
69
+ sig { override.returns(T::Boolean) }
70
+ def list? = false
71
+
72
+ alias_method(:base, :inner)
73
+
74
+ sig { override.params(base: BaseOutputType).returns(T.self_type) }
75
+ def with_base(base) = Named.new(required:, inner: base)
76
+ end
77
+ end
78
+ end
79
+ end
@@ -0,0 +1,45 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module Oriole
5
+ module Definition
6
+ class Schema
7
+ class << self
8
+ extend(T::Sig)
9
+ extend(T::Helpers)
10
+ include(Abstract::SchemaDefinition)
11
+
12
+ abstract!
13
+
14
+ sig { abstract.returns(T.class_of(ObjectType)) }
15
+ def query; end
16
+
17
+ sig do
18
+ params(
19
+ query: String,
20
+ context: Object,
21
+ root_value: Object,
22
+ operation_name: T.nilable(String),
23
+ ).returns(Object)
24
+ end
25
+ def execute(
26
+ query:,
27
+ context:,
28
+ root_value:,
29
+ operation_name: nil
30
+ )
31
+ Execution::Engine.execute(
32
+ schema_definition: VisibilityScoped::SchemaDefinition.new(
33
+ definition: self,
34
+ context:,
35
+ ),
36
+ query:,
37
+ context:,
38
+ root_value:,
39
+ operation_name:,
40
+ )
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,62 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module Oriole
5
+ module Definition
6
+ module VisibilityScoped
7
+ class ObjectTypeDefinition
8
+ extend(T::Sig)
9
+ include(Abstract::ObjectTypeDefinition)
10
+
11
+ sig { params(definition: T.class_of(ObjectType), context: Object).void }
12
+ def initialize(definition:, context:)
13
+ @definition = definition
14
+ @context = context
15
+ @field_definitions = T.let(nil, T.nilable(T::Array[FieldDefinition]))
16
+ end
17
+
18
+ class << self
19
+ extend(T::Sig)
20
+
21
+ sig { params(definition: T.class_of(ObjectType), context: Object).returns(T.nilable(T.attached_class)) }
22
+ def build(definition:, context:)
23
+ if definition.visible?(context:)
24
+ new(definition: definition, context: context)
25
+ end
26
+ end
27
+ end
28
+
29
+ sig { override.returns(String) }
30
+ def graphql_name = @definition.graphql_name
31
+
32
+ sig { override.returns(T.nilable(String)) }
33
+ def description = @definition.description
34
+
35
+ sig { override.returns(T::Array[FieldDefinition]) }
36
+ def field_definitions
37
+ @field_definitions ||= @definition.field_definitions.filter_map do |field_definition|
38
+ next nil unless field_definition.visible?(context: @context)
39
+
40
+ base_type = field_definition.type.base
41
+
42
+ scoped_base_type = if base_type.is_a?(Class) && base_type < ObjectType
43
+ ObjectTypeDefinition.build(definition: base_type, context: @context)
44
+ else
45
+ base_type
46
+ end
47
+
48
+ if scoped_base_type
49
+ FieldDefinition.new(
50
+ graphql_name: field_definition.graphql_name,
51
+ description: field_definition.description,
52
+ type: field_definition.type.with_base(scoped_base_type),
53
+ visibility: field_definition.visibility,
54
+ method_name: field_definition.method_name,
55
+ )
56
+ end
57
+ end
58
+ end
59
+ end
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,26 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module Oriole
5
+ module Definition
6
+ module VisibilityScoped
7
+ class SchemaDefinition
8
+ extend(T::Sig)
9
+ include(Abstract::SchemaDefinition)
10
+
11
+ sig { params(definition: T.class_of(Schema), context: Object).void }
12
+ def initialize(definition:, context:)
13
+ @definition = definition
14
+ @context = context
15
+ @query = T.let(
16
+ ObjectTypeDefinition.new(definition: @definition.query, context: @context),
17
+ ObjectTypeDefinition,
18
+ )
19
+ end
20
+
21
+ sig { override.returns(ObjectTypeDefinition) }
22
+ attr_reader(:query)
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,16 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module Oriole
5
+ module Execution
6
+ module CoerceResult
7
+ extend(T::Sig)
8
+ extend(T::Helpers)
9
+
10
+ abstract!
11
+
12
+ sig { abstract.params(result: Object).returns(Object) }
13
+ def coerce_result(result:); end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,207 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ require "tinygql"
5
+ require "set"
6
+
7
+ module Oriole
8
+ module Execution
9
+ class Engine
10
+ extend(T::Sig)
11
+
12
+ sig do
13
+ params(
14
+ schema_definition: Definition::Abstract::SchemaDefinition,
15
+ document: TinyGQL::Nodes::Document,
16
+ context: Object,
17
+ ).void
18
+ end
19
+ def initialize(schema_definition:, document:, context:)
20
+ @schema_definition = schema_definition
21
+ @document = document
22
+ @context = context
23
+ @collect_fields_cache = T.let(
24
+ {},
25
+ T::Hash[Integer, T::Hash[String, T::Array[TinyGQL::Nodes::Field]]],
26
+ )
27
+ end
28
+
29
+ private_class_method(:new)
30
+
31
+ class << self
32
+ extend(T::Sig)
33
+
34
+ sig do
35
+ params(
36
+ schema_definition: Definition::Abstract::SchemaDefinition,
37
+ query: String,
38
+ context: Object,
39
+ operation_name: T.nilable(String),
40
+ root_value: Object,
41
+ ).returns(Object)
42
+ end
43
+ def execute(schema_definition:, query:, context:, operation_name:, root_value:)
44
+ document = TinyGQL.parse(query)
45
+
46
+ new(schema_definition:, document:, context:).execute(operation_name:, root_value:)
47
+ end
48
+ end
49
+
50
+ sig { params(operation_name: T.nilable(String), root_value: Object).returns(Object) }
51
+ def execute(operation_name:, root_value:)
52
+ operation_definition = if operation_name
53
+ @document.definitions.find do |definition|
54
+ definition.is_a?(TinyGQL::Nodes::OperationDefinition) && definition.name == operation_name
55
+ end || raise("Cannot find operation definition with name #{operation_name}")
56
+ else
57
+ operation_definitions = @document.definitions.select do |definition|
58
+ definition.is_a?(TinyGQL::Nodes::OperationDefinition)
59
+ end
60
+ if operation_definitions.one?
61
+ operation_definitions.first
62
+ else
63
+ raise "Cannot use anonymous operation with multiple operation definitions"
64
+ end
65
+ end
66
+
67
+ execute_operation(operation_definition: operation_definition, initial_value: root_value)
68
+ end
69
+
70
+ private
71
+
72
+ sig { params(operation_definition: TinyGQL::Nodes::OperationDefinition, initial_value: Object).returns(Object) }
73
+ def execute_operation(operation_definition:, initial_value:)
74
+ case operation_definition.type
75
+ when "query", nil
76
+ execute_selection_set(
77
+ selection_set: operation_definition.selection_set,
78
+ object_type: @schema_definition.query,
79
+ object_value: initial_value,
80
+ )
81
+ else
82
+ raise(NotImplementedError)
83
+ end
84
+ end
85
+
86
+ sig do
87
+ params(
88
+ selection_set: T::Array[TinyGQL::Nodes::Node],
89
+ object_type: Definition::Abstract::ObjectTypeDefinition,
90
+ object_value: Object,
91
+ ).returns(Object)
92
+ end
93
+ def execute_selection_set(selection_set:, object_type:, object_value:)
94
+ grouped_fields = collect_fields(object_type:, selection_set:, visited_fragments: Set.new)
95
+
96
+ result_map = {}
97
+
98
+ has_null_for_required = T.let(false, T::Boolean)
99
+
100
+ grouped_fields.each do |_response_key, fields|
101
+ field_name = T.must(fields.first).name
102
+ field_definition = object_type.field_definitions.find do |definition|
103
+ definition.graphql_name == field_name
104
+ end || raise("Cannot find field definition for #{field_name}")
105
+
106
+ field_value = execute_field(
107
+ object_type:,
108
+ field_definition:,
109
+ object_value:,
110
+ fields:,
111
+ )
112
+
113
+ result_map[field_name] = field_value
114
+
115
+ if field_definition.type.required? && field_value.nil?
116
+ has_null_for_required = true
117
+ end
118
+ end
119
+
120
+ result_map
121
+ end
122
+
123
+ sig do
124
+ params(
125
+ object_type: Definition::Abstract::ObjectTypeDefinition,
126
+ selection_set: T::Array[TinyGQL::Nodes::Node],
127
+ visited_fragments: T::Set[String],
128
+ ).returns(T::Hash[String, T::Array[TinyGQL::Nodes::Field]])
129
+ end
130
+ def collect_fields(object_type:, selection_set:, visited_fragments:)
131
+ if (cached_value = @collect_fields_cache[selection_set.object_id])
132
+ return cached_value
133
+ end
134
+
135
+ grouped_fields = {}
136
+
137
+ selection_set.each do |selection|
138
+ # TODO: handle include/skip directives
139
+
140
+ case selection
141
+ when TinyGQL::Nodes::Field
142
+ response_key = selection.aliaz || selection.name
143
+ grouped_fields[response_key] ||= []
144
+ grouped_fields[response_key] << selection
145
+ else
146
+ raise NotImplementedError
147
+ end
148
+ end
149
+
150
+ @collect_fields_cache[selection_set.object_id] = grouped_fields
151
+
152
+ grouped_fields
153
+ end
154
+
155
+ sig do
156
+ params(
157
+ object_type: Definition::Abstract::ObjectTypeDefinition,
158
+ object_value: Object,
159
+ field_definition: Definition::Abstract::FieldDefinition,
160
+ fields: T::Array[TinyGQL::Nodes::Field],
161
+ ).returns(Object)
162
+ end
163
+ def execute_field(object_type:, object_value:, field_definition:, fields:)
164
+ resolved_value = object_value.send(field_definition.resolver_method_name)
165
+
166
+ complete_value(field_type: field_definition.type, fields:, resolved_value:)
167
+ end
168
+
169
+ sig do
170
+ params(
171
+ field_type: Definition::OutputType,
172
+ fields: T::Array[TinyGQL::Nodes::Field],
173
+ resolved_value: Object,
174
+ ).returns(Object)
175
+ end
176
+ def complete_value(field_type:, fields:, resolved_value:)
177
+ if resolved_value.nil? && field_type.required?
178
+ raise "Cannot return null for non-null list type"
179
+ elsif resolved_value.nil?
180
+ return
181
+ end
182
+
183
+ case field_type
184
+ when Definition::OutputType::Named
185
+ case (inner_type = field_type.inner)
186
+ when BuiltinScalarDefinition
187
+ inner_type.coerce_result(result: resolved_value)
188
+ when Definition::Abstract::ObjectTypeDefinition
189
+ execute_selection_set(
190
+ selection_set: T.must(fields.first).selection_set,
191
+ object_type: inner_type,
192
+ object_value: resolved_value,
193
+ )
194
+ end
195
+ when Definition::OutputType::List
196
+ if resolved_value.is_a?(Array)
197
+ resolved_value.map do |value|
198
+ complete_value(field_type: field_type.inner, fields: fields, resolved_value: value)
199
+ end
200
+ else
201
+ raise "Cannot return non-list value for list type"
202
+ end
203
+ end
204
+ end
205
+ end
206
+ end
207
+ end
@@ -0,0 +1,6 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module Oriole
5
+ VERSION = "0.1.0.alpha.0"
6
+ end
data/lib/oriole.rb ADDED
@@ -0,0 +1,14 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ require "sorbet-runtime"
5
+ require "zeitwerk"
6
+ loader = Zeitwerk::Loader.for_gem
7
+ loader.setup
8
+
9
+ module Oriole
10
+ class Error < StandardError; end
11
+ # Your code goes here...
12
+ end
13
+
14
+ loader.eager_load
data/oriole.gemspec ADDED
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ lib = File.expand_path("lib", __dir__)
4
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
5
+ require "oriole/version"
6
+
7
+ Gem::Specification.new do |spec|
8
+ spec.name = "oriole"
9
+ spec.version = Oriole::VERSION
10
+ spec.authors = ["Adam Petro"]
11
+ spec.email = ["adamapetro@gmail.com"]
12
+
13
+ spec.summary = "A fast GraphQL engine written in pure Ruby."
14
+ spec.description = "A fast GraphQL engine written in pure Ruby."
15
+ spec.homepage = "https://github.com/adampetro/oriole"
16
+ spec.license = "MIT"
17
+
18
+ spec.metadata["homepage_uri"] = spec.homepage
19
+ spec.metadata["source_code_uri"] = "https://github.com/adampetro/oriole"
20
+ spec.metadata["changelog_uri"] = "https://github.com/adampetro/oriole/blob/main/CHANGELOG"
21
+
22
+ # Specify which files should be added to the gem when it is released.
23
+ # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
24
+ spec.files = Dir.chdir(File.expand_path(__dir__)) do
25
+ %x(git ls-files -z).split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
26
+ end
27
+ spec.bindir = "exe"
28
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
29
+ spec.require_paths = ["lib"]
30
+
31
+ spec.add_development_dependency("bundler", "~> 2.4.10")
32
+ spec.add_development_dependency("minitest", "~> 5.0")
33
+ spec.add_development_dependency("rake", "~> 13.0")
34
+
35
+ spec.add_dependency("sorbet-runtime", "~> 0.5")
36
+ spec.add_dependency("tinygql", "~> 0.1.4")
37
+ spec.add_dependency("zeitwerk", "~> 2.6")
38
+ end
@@ -0,0 +1,69 @@
1
+ # typed: ignore
2
+ # frozen_string_literal: true
3
+
4
+ require "open3"
5
+ require "sorbet-runtime"
6
+
7
+ class BenchmarkToDocument < T::Struct
8
+ const(:path, String)
9
+ const(:description, String)
10
+ end
11
+
12
+ BENCHMARKS_TO_DOCUMENT = [
13
+ BenchmarkToDocument.new(path: "execute", description: "Parse + Execute"),
14
+ ]
15
+
16
+ namespace :bench do
17
+ all_tasks = [:compile]
18
+
19
+ Dir.glob("bench/*.rb").each do |path|
20
+ task_name = File.basename(path, ".rb")
21
+ next if task_name == "bench" # Bench helper
22
+
23
+ desc "Run #{path} benchmark"
24
+ task task_name do
25
+ sh "ruby -Ilib #{path}"
26
+ puts
27
+ end
28
+
29
+ all_tasks << task_name
30
+ end
31
+
32
+ desc "Run all benchmarks"
33
+ task all: all_tasks
34
+
35
+ desc "Document benchmark results"
36
+ task :doc do
37
+ output = ""
38
+
39
+ BENCHMARKS_TO_DOCUMENT.each do |benchmark|
40
+ puts "Benchmarking #{benchmark.path}"
41
+ [true, false].each do |yjit|
42
+ env = yjit ? { "RUBY_YJIT_ENABLE" => "1" } : {}
43
+ stdout, status = Open3.capture2e(env, "ruby", "-Ilib", "bench/#{benchmark.path}.rb")
44
+ unless status.success?
45
+ abort("Encountered an error: #{stdout}")
46
+ end
47
+ padded_stdout = stdout.lines.map { |line| " #{line.chomp}".tap(&:rstrip!) }.join("\n")
48
+ output += <<~END
49
+ <details>
50
+ <summary>#{benchmark.description} (Ruby 3.2, YJIT #{yjit ? "enabled" : "disabled"})</summary>
51
+
52
+ ```
53
+ #{padded_stdout}
54
+ ```
55
+ </details>
56
+ END
57
+ output += "\n"
58
+ end
59
+ end
60
+
61
+ readme = "README.md"
62
+ contents = File.read(readme)
63
+ pattern = /(?<=<!---benchmark result start-->\n).*?(?=<!---benchmark result end-->)/m
64
+ File.write(readme, contents.gsub!(pattern, output.chomp))
65
+ end
66
+ end
67
+
68
+ desc "Run all benchmarks"
69
+ task bench: "bench:all"
data/sorbet/config ADDED
@@ -0,0 +1,5 @@
1
+ --dir
2
+ .
3
+ --ignore=tmp/
4
+ --ignore=vendor/
5
+ --enable-experimental-requires-ancestor