lively 0.21.0 → 0.23.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 8430df315a92af764a65128e479adf71cd0971e27edfc259ba9ec5c7bbc21cbc
4
- data.tar.gz: c48c124558fd175a016cd60a38e5f9773821d0c9fe260ed5a60218ee21e4f08d
3
+ metadata.gz: 02c1d603877c33f60d054f04e6d9ef68d5385b23d3f165b23520e2ea47eefa80
4
+ data.tar.gz: 6a48a1a8753f2a35adb4da6abb5527a7f95c2462903499f4a4b2ae968f0e0ebb
5
5
  SHA512:
6
- metadata.gz: 529d3090d929d951da18f7f58df1cb6df958f1489b54bdcbf370003b24a103dcee9ec72071be2b50c0ca327c66486cb44ed3954064cd38180774166c50e319b8
7
- data.tar.gz: 23a4576c7fd367afbe32c761f6e039c74b5bef211d35f1be48690a30c68c2546d4c2530101fbe7798a526b497b864ec98e52d6b6cea29bb30fa3c6bcbb433b43
6
+ metadata.gz: 63c191f0001969c89b0b8b7f0fcf6de08757b202a7d53423d6d2ddd91aa05eea34f8b66f4e2e7c9363546b6c1064afa35878904722bd19f9d82436c58f6f5c62
7
+ data.tar.gz: efd50bf88258523e7a95dbec278ec95a448ded6849ab77209cf6fc1158805dc0b0f06ed3228b0b616806ff8af2bc27124171eb56ff10d6f277d4143c78b8989a
checksums.yaml.gz.sig CHANGED
Binary file
@@ -82,7 +82,7 @@ class GameState
82
82
  end
83
83
 
84
84
  class GameView < Live::View
85
- def initialize(id = self.class.unique_id, data = {}, game_state: nil)
85
+ def initialize(id, data, game_state: nil)
86
86
  super(id, data)
87
87
  @game_state = game_state
88
88
  end
@@ -117,22 +117,24 @@ class Application < Lively::Application
117
117
  end
118
118
 
119
119
  def configure_routes(router)
120
- router.get("/") do |request|
121
- render_view(request, DisplayView)
120
+ router.get("/") do
121
+ body = resolver.root(DisplayView)
122
+ Lively::Pages::Index.new(title: title, body: body).call
122
123
  end
123
124
 
124
- router.get("/control") do |request, parameters|
125
- render_view(request, ControlView, mode: parameters["mode"])
125
+ router.get("/control") do
126
+ body = resolver.root(ControlView)
127
+ Lively::Pages::Index.new(title: title, body: body).call
126
128
  end
127
129
  end
128
130
  end
129
131
  ```
130
132
 
131
- `allowed_views` defines the view classes the live resolver may construct. Routes select which view to display at each path, including `/`. `make_view` constructs a view with shared state, `make_page` wraps it in the default page, and `render_view` composes both operations. A custom route can construct any {ruby Lively::Page} around `make_view` and call it with the request. Lively installs its `/live` WebSocket route independently, so overriding `configure_routes` does not remove it.
133
+ `allowed_views` defines the view classes the shared resolver may construct. Each matched route constructs its root view using the shared resolver, wraps that concrete body in a page, and invokes `Page#call` to produce the response. This keeps view construction lazy without making the page body itself callable. Lively installs its `/live` WebSocket route independently, so overriding `configure_routes` does not remove it.
132
134
 
133
- Routes match exact paths and may accept one or more HTTP methods. Query parameters are decoded using `protocol-url` and passed to the handler as its second argument. Routes without an explicit method accept every method.
135
+ Routes match exact paths and may accept one or more HTTP methods. Handlers receive the original request and can parse query parameters when needed. Routes without an explicit method accept every method.
134
136
 
135
- Requests which do not match a route are passed to the application's delegate. An application using client-side history routing can instead override `#handle` to render an application page for unmatched paths.
137
+ Requests which do not match a route are passed to the application's delegate. Applications that need custom fallback behavior can supply an appropriate delegate when they are constructed.
136
138
 
137
139
  ## Live Reloading
138
140
 
@@ -23,7 +23,7 @@ module Lively
23
23
  #
24
24
  # Use {.[]} to create a simple application class for a single view, optionally
25
25
  # with shared state. For more complex applications, subclass and override
26
- # {#allowed_views}, {#state}, and {#configure_routes}.
26
+ # {#allowed_views}, {#state}, and {#configure_routes} to route requests to pages.
27
27
  class Application < Protocol::HTTP::Middleware
28
28
  VIEWS = [HelloWorld].freeze
29
29
  STATE = {}.freeze
@@ -43,15 +43,6 @@ module Lively
43
43
  return klass
44
44
  end
45
45
 
46
- # Initialize a new Lively application.
47
- # @parameter delegate [Protocol::HTTP::Middleware] The next middleware in the chain.
48
- def initialize(delegate)
49
- super(delegate)
50
- end
51
-
52
- # @attribute [Protocol::HTTP::Middleware] The delegate middleware for request handling.
53
- attr :delegate
54
-
55
46
  # The shared state for this application, passed to all views via the resolver.
56
47
  # Override this in subclasses to provide custom state.
57
48
  # @returns [Hash] Key-value pairs passed as keyword arguments to view constructors.
@@ -87,52 +78,22 @@ module Lively
87
78
  self.class.name
88
79
  end
89
80
 
90
- # Construct a view with shared application state.
91
- # @parameter view_class [Class] The view class to construct.
92
- # @parameter arguments [Hash] Additional keyword arguments for the view.
93
- # @returns [Live::View] A new view instance.
94
- def make_view(view_class, **arguments)
95
- view_class.new(**self.state, **arguments)
96
- end
97
-
98
- # Construct the default page for a view.
99
- # Override this to customize the document surrounding live views.
100
- # @parameter view [Live::View] The root view for the page.
101
- # @returns [Page] A new page instance.
102
- def make_page(view)
103
- Pages::Index.new(title: self.title, body: view)
104
- end
105
-
106
- # Construct a view and its default page, then render an HTTP response.
107
- # @parameter request [Protocol::HTTP::Request] The incoming request.
108
- # @parameter view_class [Class] The view class to render.
109
- # @parameter arguments [Hash] Additional keyword arguments for the view.
110
- # @returns [Protocol::HTTP::Response] A successful HTML response.
111
- def render_view(request, view_class, **arguments)
112
- make_page(make_view(view_class, **arguments)).call(request)
113
- end
114
-
115
- # Handle a standard HTTP request which did not match a configured route.
116
- # @parameter request [Protocol::HTTP::Request] The incoming HTTP request.
117
- # @returns [Protocol::HTTP::Response] The delegate response.
118
- def handle(request)
119
- return delegate.call(request)
120
- end
121
-
122
81
  # Add application routes to the given router.
123
82
  # Override this method to map paths to views or other handlers.
124
- # @parameter router [Router] The router to configure.
83
+ # @parameter router [Router::Builder] The router to configure.
125
84
  def configure_routes(router)
126
- router.get("/") do |request|
127
- self.render_view(request, self.allowed_views.first)
85
+ router.get("/") do
86
+ view_class = self.allowed_views.first
87
+ body = self.resolver.root(view_class) if view_class
88
+ Pages::Index.new(title: self.title, body: body).call
128
89
  end
129
90
  end
130
91
 
131
- # The router for this application. Unmatched requests are handled separately
132
- # by {#handle}.
92
+ # The router for this application. Unmatched requests are passed to the
93
+ # application delegate.
133
94
  # @returns [Router] The configured router.
134
95
  def router
135
- @router ||= Router.new.tap do |router|
96
+ @router ||= Router.build(delegate) do |router|
136
97
  configure_system_routes(router)
137
98
  configure_routes(router)
138
99
  end
@@ -142,13 +103,13 @@ module Lively
142
103
  # @parameter request [Protocol::HTTP::Request] The incoming HTTP request.
143
104
  # @returns [Protocol::HTTP::Response] The appropriate response for the request.
144
105
  def call(request)
145
- return router.call(request) || handle(request)
106
+ return router.call(request)
146
107
  end
147
108
 
148
109
  private
149
110
 
150
111
  # Add framework-owned routes to the given router.
151
- # @parameter router [Router] The router to configure.
112
+ # @parameter router [Router::Builder] The router to configure.
152
113
  def configure_system_routes(router)
153
114
  router.route("/live") do |request|
154
115
  Async::WebSocket::Adapters::HTTP.open(request, &self.method(:live)) || Protocol::HTTP::Response[400]
@@ -64,11 +64,7 @@ module Lively
64
64
  builder.text(<<~TEXT)
65
65
  #!/usr/bin/env lively
66
66
 
67
- class Application < Lively::Application
68
- def body
69
- Lively::HelloWorld.new
70
- end
71
- end
67
+ Application = Lively::Application[Lively::HelloWorld]
72
68
  TEXT
73
69
  end
74
70
 
data/lib/lively/page.rb CHANGED
@@ -12,15 +12,15 @@ require "xrb/template"
12
12
  module Lively
13
13
  # Represents a complete HTML document.
14
14
  #
15
- # A page combines application content with the stylesheets, import map,
16
- # JavaScript modules, and body attributes required to present it. Applications
17
- # can use this class directly or subclass it to provide shared defaults.
15
+ # A page combines a renderable body with the stylesheets, import map,
16
+ # JavaScript modules, and body attributes required to present it.
17
+ # Pages render complete HTTP responses after a route has selected their content.
18
18
  class Page
19
19
  TEMPLATE = XRB::Template.load_file(File.expand_path("page.xrb", __dir__))
20
20
 
21
21
  # Initialize a new page.
22
22
  # @parameter title [String] The document title.
23
- # @parameter body [Object | Nil] The document body. The result of `to_html` is interpolated into the template.
23
+ # @parameter body [Object | Nil] The renderable document body.
24
24
  # @parameter icon [String | Nil] The favicon URL.
25
25
  # @parameter stylesheets [Array(String | Hash)] Stylesheets in document order. Hash entries specify link attributes.
26
26
  # @parameter imports [Hash] JavaScript import map entries.
@@ -40,7 +40,7 @@ module Lively
40
40
  # @attribute [String] The document title.
41
41
  attr :title
42
42
 
43
- # @attribute [Object | Nil] The document body.
43
+ # @attribute [Object | Nil] The renderable document body.
44
44
  attr :body
45
45
 
46
46
  # @attribute [String | Nil] The favicon URL.
@@ -80,12 +80,6 @@ module Lively
80
80
  XRB::Tag.closed("link", {rel: "stylesheet", type: "text/css"}.merge(attributes))
81
81
  end
82
82
 
83
- # The rendered body content.
84
- # @returns [Object]
85
- def body_content
86
- @body&.to_html || "No body specified!"
87
- end
88
-
89
83
  # The serialized JavaScript import map.
90
84
  # @returns [XRB::MarkupString]
91
85
  def import_map
@@ -102,9 +96,8 @@ module Lively
102
96
  end
103
97
 
104
98
  # Render this page as an HTTP response.
105
- # @parameter request [Protocol::HTTP::Request] The incoming request.
106
99
  # @returns [Protocol::HTTP::Response] A successful HTML response.
107
- def call(request)
100
+ def call
108
101
  Protocol::HTTP::Response[200, {"content-type" => "text/html; charset=utf-8"}, [to_html]]
109
102
  end
110
103
  end
data/lib/lively/page.xrb CHANGED
@@ -25,6 +25,6 @@
25
25
  </head>
26
26
 
27
27
  #{self.body_tag}
28
- #{self.body_content}
28
+ #{self.body&.to_html}
29
29
  </body>
30
30
  </html>
@@ -29,7 +29,7 @@ module Lively
29
29
 
30
30
  # Initialize a new index page.
31
31
  # @parameter title [String] The title of the page.
32
- # @parameter body [Object] The body content of the page.
32
+ # @parameter body [Object | Nil] The renderable document body.
33
33
  def initialize(title: "Lively", body: nil)
34
34
  super(
35
35
  title: title,
@@ -37,7 +37,7 @@ module Lively
37
37
  icon: ICON,
38
38
  stylesheets: STYLESHEETS,
39
39
  imports: IMPORTS,
40
- modules: MODULES,
40
+ modules: MODULES
41
41
  )
42
42
  end
43
43
  end
@@ -21,14 +21,11 @@ module Lively
21
21
  # @attribute [Hash] The shared state passed to view constructors.
22
22
  attr :state
23
23
 
24
- # Resolve a client-side element to a server-side instance with shared state.
25
- # @parameter id [String] The unique element identifier.
26
- # @parameter data [Hash] The element data attributes.
27
- # @returns [Live::Element | Nil] The resolved element, or `nil`.
28
- def call(id, data)
29
- if klass = @allowed[data[:class]]
30
- return klass.new(id, data, **@state)
31
- end
24
+ private
25
+
26
+ # Construct a view with shared application state.
27
+ def make(view_class, id, data, **options)
28
+ super(view_class, id, data, **@state, **options)
32
29
  end
33
30
  end
34
31
  end
data/lib/lively/router.rb CHANGED
@@ -3,136 +3,155 @@
3
3
  # Released under the MIT License.
4
4
  # Copyright, 2026, by Samuel Williams.
5
5
 
6
- require "protocol/http"
6
+ require "protocol/http/middleware"
7
7
  require "protocol/url"
8
8
 
9
9
  module Lively
10
10
  # Dispatches HTTP requests to handlers using exact path and method matches.
11
11
  #
12
12
  # Request targets are parsed with {Protocol::URL::Reference}. Handlers receive
13
- # the original request and the decoded query parameters.
14
- class Router
15
- EMPTY_PARAMETERS = {}.freeze
13
+ # the original request.
14
+ class Router < Protocol::HTTP::Middleware
16
15
  ANY_METHOD = nil
17
- private_constant :EMPTY_PARAMETERS, :ANY_METHOD
16
+ private_constant :ANY_METHOD
18
17
 
19
- # Initialize a router.
20
- # @yields {|router| ...} The router to configure.
21
- def initialize
22
- @routes = {}
18
+ # Builds an immutable route table.
19
+ class Builder
20
+ # Initialize a route builder.
21
+ def initialize
22
+ @routes = {}
23
+ end
23
24
 
24
- yield self if block_given?
25
- end
26
-
27
- # Add a route.
28
- #
29
- # When `methods` is omitted, the handler accepts every HTTP method. Otherwise,
30
- # it may be a single method or an array of methods.
31
- #
32
- # @parameter path [String] The absolute path to match.
33
- # @parameter methods [String | Symbol | Array(String | Symbol) | Nil] The accepted HTTP methods.
34
- # @yields {|request, parameters| ...} The route handler.
35
- # @parameter request [Protocol::HTTP::Request] The original request.
36
- # @parameter parameters [Hash] The decoded query parameters.
37
- # @returns [Router] The router.
38
- def route(path, methods: nil, &handler)
39
- raise ArgumentError, "A route handler is required!" unless handler
25
+ # A frozen snapshot of the configured routes.
26
+ # @returns [Hash] The route table.
27
+ def routes
28
+ @routes.transform_values do |handlers|
29
+ handlers.dup.freeze
30
+ end.freeze
31
+ end
32
+
33
+ # Add a route.
34
+ #
35
+ # When `methods` is omitted, the handler accepts every HTTP method. Otherwise,
36
+ # it may be a single method or an array of methods.
37
+ #
38
+ # @parameter path [String] The absolute path to match.
39
+ # @parameter handler [Interface(:call) | Nil] A callable route handler.
40
+ # @parameter methods [String | Symbol | Array(String | Symbol) | Nil] The accepted HTTP methods.
41
+ # @yields {|request| ...} The route handler.
42
+ # @parameter request [Protocol::HTTP::Request] The original request.
43
+ # @returns [Builder] The builder.
44
+ def route(path, handler = nil, methods: nil, &block)
45
+ raise ArgumentError, "Provide a route handler or block, not both!" if handler && block
46
+ handler ||= block
47
+ raise ArgumentError, "A route handler is required!" unless handler
48
+
49
+ path = route_path(path)
50
+ methods = route_methods(methods)
51
+ handlers = (@routes[path] ||= {})
52
+
53
+ methods.each do |method|
54
+ if handlers.key?(method)
55
+ raise ArgumentError, "Route already defined for #{method || "any method"} #{path}!"
56
+ end
57
+
58
+ handlers[method] = handler
59
+ end
60
+
61
+ return self
62
+ end
40
63
 
41
- path = route_path(path)
42
- methods = route_methods(methods)
43
- handlers = (@routes[path] ||= {})
64
+ Protocol::HTTP::Methods.each do |name, method|
65
+ # Add a route for this HTTP method.
66
+ # @parameter path [String] The absolute path to match.
67
+ # @yields {|request| ...} The route handler.
68
+ # @returns [Builder] The builder.
69
+ define_method(name) do |path, handler = nil, &block|
70
+ route(path, handler, methods: method, &block)
71
+ end
72
+ end
73
+
74
+ private
44
75
 
45
- methods.each do |method|
46
- if handlers.key?(method)
47
- raise ArgumentError, "Route already defined for #{method || "any method"} #{path}!"
76
+ def route_path(path)
77
+ url = Protocol::URL[path]
78
+
79
+ unless url && !url.is_a?(Protocol::URL::Absolute)
80
+ raise ArgumentError, "Route must be an absolute path: #{path.inspect}!"
81
+ end
82
+
83
+ reference = Protocol::URL::Reference[url]
84
+
85
+ unless reference.path.absolute?
86
+ raise ArgumentError, "Route must be an absolute path: #{path.inspect}!"
87
+ end
88
+
89
+ if reference.query? || reference.fragment?
90
+ raise ArgumentError, "Route path cannot include a query or fragment: #{path.inspect}!"
48
91
  end
49
92
 
50
- handlers[method] = handler
93
+ return reference.path.freeze
51
94
  end
52
95
 
53
- return self
96
+ def route_methods(methods)
97
+ return [ANY_METHOD] unless methods
98
+
99
+ methods = Array(methods)
100
+ raise ArgumentError, "At least one HTTP method is required!" if methods.empty?
101
+
102
+ return methods.map do |method|
103
+ raise ArgumentError, "HTTP method cannot be nil!" unless method
104
+
105
+ method.to_s.upcase
106
+ end.uniq
107
+ end
54
108
  end
55
109
 
56
- Protocol::HTTP::Methods.each do |name, method|
57
- # Add a route for this HTTP method.
58
- # @parameter path [String] The absolute path to match.
59
- # @yields {|request, parameters| ...} The route handler.
60
- # @returns [Router] The router.
61
- define_method(name) do |path, &handler|
62
- route(path, methods: method, &handler)
63
- end
110
+ # Build and configure a frozen router.
111
+ # @parameter delegate [Protocol::HTTP::Middleware] The middleware for unmatched requests.
112
+ # @yields {|builder| ...} Configures the routes.
113
+ # @parameter builder [Builder] The route builder.
114
+ # @returns [Router] The configured router.
115
+ def self.build(delegate = Protocol::HTTP::Middleware::NotFound)
116
+ builder = Builder.new
117
+ yield builder
118
+
119
+ return new(delegate, builder.routes).freeze
120
+ end
121
+
122
+ # Initialize a router.
123
+ # @parameter delegate [Protocol::HTTP::Middleware] The middleware for unmatched requests.
124
+ # @parameter routes [Hash] The frozen route table.
125
+ def initialize(delegate, routes)
126
+ super(delegate)
127
+ @routes = routes
64
128
  end
65
129
 
66
130
  # Dispatch a request to a matching route.
67
131
  # @parameter request [Protocol::HTTP::Request] The request to dispatch.
68
- # @returns [Protocol::HTTP::Response | Nil] The handler or error response, or `nil` when no path matches.
132
+ # @returns [Protocol::HTTP::Response] The handler, error, or delegate response.
69
133
  def call(request)
70
134
  reference = parse_reference(request.path)
71
135
  return Protocol::HTTP::Response[400] unless reference
72
136
 
73
- unless handlers = @routes[reference.path]
74
- return nil
75
- end
137
+ return super unless handlers = @routes[reference.path]
76
138
 
77
139
  unless handler = handlers[request.method] || handlers[ANY_METHOD]
78
140
  allowed_methods = handlers.keys.compact.sort.join(", ")
79
141
  return Protocol::HTTP::Response[405, [["allow", allowed_methods]]]
80
142
  end
81
143
 
82
- parameters = parse_query(reference)
83
- return Protocol::HTTP::Response[400] unless parameters
84
-
85
- return handler.call(request, parameters)
144
+ return handler.call(request)
86
145
  end
87
146
 
88
147
  private
89
148
 
90
- def route_path(path)
91
- url = Protocol::URL[path]
92
-
93
- unless url && !url.is_a?(Protocol::URL::Absolute)
94
- raise ArgumentError, "Route must be an absolute path: #{path.inspect}!"
95
- end
96
-
97
- reference = Protocol::URL::Reference[url]
98
-
99
- unless reference.path.absolute?
100
- raise ArgumentError, "Route must be an absolute path: #{path.inspect}!"
101
- end
102
-
103
- if reference.query? || reference.fragment?
104
- raise ArgumentError, "Route path cannot include a query or fragment: #{path.inspect}!"
105
- end
106
-
107
- return reference.path.freeze
108
- end
109
-
110
- def route_methods(methods)
111
- return [ANY_METHOD] unless methods
112
-
113
- methods = Array(methods)
114
- raise ArgumentError, "At least one HTTP method is required!" if methods.empty?
115
-
116
- return methods.map do |method|
117
- raise ArgumentError, "HTTP method cannot be nil!" unless method
118
-
119
- method.to_s.upcase
120
- end.uniq
121
- end
122
-
123
149
  def parse_reference(path)
124
150
  reference = Protocol::URL::Reference[path]
125
151
  return if reference&.fragment?
126
152
 
127
153
  return reference
128
- rescue ArgumentError
129
- nil
130
154
  end
131
155
 
132
- def parse_query(reference)
133
- reference.parse_query! || EMPTY_PARAMETERS
134
- rescue ArgumentError
135
- nil
136
- end
137
156
  end
138
157
  end
@@ -5,5 +5,5 @@
5
5
 
6
6
  # @namespace
7
7
  module Lively
8
- VERSION = "0.21.0"
8
+ VERSION = "0.23.0"
9
9
  end
@@ -297,8 +297,10 @@ export class Live {
297
297
  forwardFormEvent(id, event, detail, preventDefault = true) {
298
298
  if (preventDefault) event.preventDefault();
299
299
 
300
- let form = event.form;
301
- let formData = new FormData(form);
300
+ let form = event.target;
301
+ let formData = event.submitter ?
302
+ new this.#window.FormData(form, event.submitter) :
303
+ new this.#window.FormData(form);
302
304
 
303
305
  this.forward(id, {type: event.type, detail: detail, formData: [...formData]});
304
306
  }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@socketry/live",
3
3
  "type": "module",
4
- "version": "0.16.2",
4
+ "version": "0.17.0",
5
5
  "description": "Live HTML tags for Ruby.",
6
6
  "main": "Live.js",
7
7
  "exports": {
data.tar.gz.sig CHANGED
Binary file
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: lively
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.21.0
4
+ version: 0.23.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Samuel Williams
@@ -114,28 +114,28 @@ dependencies:
114
114
  requirements:
115
115
  - - "~>"
116
116
  - !ruby/object:Gem::Version
117
- version: '0.18'
117
+ version: '0.19'
118
118
  type: :runtime
119
119
  prerelease: false
120
120
  version_requirements: !ruby/object:Gem::Requirement
121
121
  requirements:
122
122
  - - "~>"
123
123
  - !ruby/object:Gem::Version
124
- version: '0.18'
124
+ version: '0.19'
125
125
  - !ruby/object:Gem::Dependency
126
126
  name: protocol-url
127
127
  requirement: !ruby/object:Gem::Requirement
128
128
  requirements:
129
129
  - - "~>"
130
130
  - !ruby/object:Gem::Version
131
- version: '0.18'
131
+ version: '0.19'
132
132
  type: :runtime
133
133
  prerelease: false
134
134
  version_requirements: !ruby/object:Gem::Requirement
135
135
  requirements:
136
136
  - - "~>"
137
137
  - !ruby/object:Gem::Version
138
- version: '0.18'
138
+ version: '0.19'
139
139
  - !ruby/object:Gem::Dependency
140
140
  name: xrb
141
141
  requirement: !ruby/object:Gem::Requirement
metadata.gz.sig CHANGED
Binary file