rbs-patch 0.1.2 → 0.1.4
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/README.md +154 -1
- data/Rakefile +7 -1
- data/Steepfile +8 -0
- data/exe/rbs-patch +2 -0
- data/lib/rbs/patch/version.rb +1 -1
- data/lib/rbs/patch.rb +143 -66
- data/sig/rbs/patch.rbs +31 -3
- metadata +6 -5
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: ef229a0a3efd1a66cdc57652ef80b173d19311652f376422a06bad6d61d3c7b5
|
|
4
|
+
data.tar.gz: 364bff243f157949a91b88c936a953e88cf275589312c664df1a9f6cc2d89c40
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: ad22a7fbeff8115b185977f5d21bfdbd51b53485f1e1896dc0e692fccbf6e8de82b90f499e3702343dbb579798cbe86b4ad871926aa8089763d4e1599d9c29e2
|
|
7
|
+
data.tar.gz: 2850321317aa8b97317f315b4dce55af70330c724cd279dadbd5f8b0c8f9edc3e4305dbfaeca0aa85365123caa1772b4bb7137487df4b7fbf6c9b6d9ab66c7aa
|
data/README.md
CHANGED
|
@@ -27,7 +27,160 @@ gem install rbs-patch
|
|
|
27
27
|
|
|
28
28
|
## Usage
|
|
29
29
|
|
|
30
|
-
|
|
30
|
+
### Basic Usage
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
# Apply patches to RBS files
|
|
34
|
+
rbs-patch base.rbs patch1.rbs patch2.rbs
|
|
35
|
+
|
|
36
|
+
# Mix files and directories
|
|
37
|
+
rbs-patch lib/types/ sig/patches/
|
|
38
|
+
|
|
39
|
+
# Output goes to stdout - redirect to save
|
|
40
|
+
rbs-patch base.rbs patch.rbs > output.rbs
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
### Programmatic Usage
|
|
44
|
+
|
|
45
|
+
```ruby
|
|
46
|
+
require 'rbs/patch'
|
|
47
|
+
|
|
48
|
+
p = RBS::Patch.new
|
|
49
|
+
|
|
50
|
+
# Load from a single file
|
|
51
|
+
p.apply(path: Pathname("sig/user.rbs"))
|
|
52
|
+
|
|
53
|
+
# Load from a directory (all .rbs files)
|
|
54
|
+
p.apply(path: Pathname("sig/patches"))
|
|
55
|
+
|
|
56
|
+
# Apply from string
|
|
57
|
+
p.apply(<<~RBS)
|
|
58
|
+
class User
|
|
59
|
+
%a{patch:override}
|
|
60
|
+
def name: () -> String?
|
|
61
|
+
end
|
|
62
|
+
RBS
|
|
63
|
+
|
|
64
|
+
puts p.to_s
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
### Annotation Syntax
|
|
68
|
+
|
|
69
|
+
All patch operations use RBS annotations with the format `%a{patch:operation}` or `%a{patch:operation:target}`.
|
|
70
|
+
|
|
71
|
+
#### Method-level Operations
|
|
72
|
+
|
|
73
|
+
##### `override` - Replace existing method signature
|
|
74
|
+
|
|
75
|
+
```ruby
|
|
76
|
+
class User
|
|
77
|
+
%a{patch:override}
|
|
78
|
+
def name: () -> String? # Replaces existing method signature at the same position
|
|
79
|
+
end
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
##### `delete` - Remove method signature
|
|
83
|
+
|
|
84
|
+
```ruby
|
|
85
|
+
class User
|
|
86
|
+
%a{patch:delete}
|
|
87
|
+
def email: () -> String # Removes this method from the class
|
|
88
|
+
end
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
##### `append_after(method_name)` - Insert method after specified method
|
|
92
|
+
|
|
93
|
+
```ruby
|
|
94
|
+
class User
|
|
95
|
+
%a{patch:append_after(name)}
|
|
96
|
+
def nickname: () -> String? # Inserts after the 'name' method
|
|
97
|
+
end
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
##### `prepend_before(method_name)` - Insert method before specified method
|
|
101
|
+
|
|
102
|
+
```ruby
|
|
103
|
+
class User
|
|
104
|
+
%a{patch:prepend_before(name)}
|
|
105
|
+
def id: () -> Integer # Inserts before the 'name' method
|
|
106
|
+
end
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
#### Class/Module-level Operations
|
|
110
|
+
|
|
111
|
+
##### `override` - Replace entire class/module
|
|
112
|
+
|
|
113
|
+
```ruby
|
|
114
|
+
%a{patch:override}
|
|
115
|
+
class User
|
|
116
|
+
def name: () -> String # Completely replaces the User class definition
|
|
117
|
+
end
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
##### `delete` - Remove class/module
|
|
121
|
+
|
|
122
|
+
```ruby
|
|
123
|
+
%a{patch:delete}
|
|
124
|
+
class User
|
|
125
|
+
end # Removes the entire User class
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
##### `append_after(ClassName)` - Insert class/module after specified class
|
|
129
|
+
|
|
130
|
+
```ruby
|
|
131
|
+
%a{patch:append_after(User)}
|
|
132
|
+
class Admin
|
|
133
|
+
def permissions: () -> Array[String]
|
|
134
|
+
end # Inserts Admin class after User class
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
##### `prepend_before(ClassName)` - Insert class/module before specified class
|
|
138
|
+
|
|
139
|
+
```ruby
|
|
140
|
+
%a{patch:prepend_before(User)}
|
|
141
|
+
class Guest
|
|
142
|
+
def readonly: () -> bool
|
|
143
|
+
end # Inserts Guest class before User class
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
### Working with Nested Modules
|
|
147
|
+
|
|
148
|
+
Operations work correctly within nested module structures:
|
|
149
|
+
|
|
150
|
+
```ruby
|
|
151
|
+
module MyApp
|
|
152
|
+
module Models
|
|
153
|
+
%a{patch:append_after(User)}
|
|
154
|
+
class Admin
|
|
155
|
+
def role: () -> String
|
|
156
|
+
end
|
|
157
|
+
end
|
|
158
|
+
end
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
### Merging Multiple Definitions
|
|
162
|
+
|
|
163
|
+
Without annotations, multiple class definitions are merged:
|
|
164
|
+
|
|
165
|
+
```ruby
|
|
166
|
+
p.apply(<<~RBS)
|
|
167
|
+
class User
|
|
168
|
+
def name: () -> String
|
|
169
|
+
end
|
|
170
|
+
RBS
|
|
171
|
+
|
|
172
|
+
p.apply(<<~RBS)
|
|
173
|
+
class User
|
|
174
|
+
def email: () -> String # Adds to existing User class
|
|
175
|
+
end
|
|
176
|
+
RBS
|
|
177
|
+
|
|
178
|
+
# Result:
|
|
179
|
+
# class User
|
|
180
|
+
# def name: () -> String
|
|
181
|
+
# def email: () -> String
|
|
182
|
+
# end
|
|
183
|
+
```
|
|
31
184
|
|
|
32
185
|
## Development
|
|
33
186
|
|
data/Rakefile
CHANGED
data/Steepfile
ADDED
data/exe/rbs-patch
CHANGED
data/lib/rbs/patch/version.rb
CHANGED
data/lib/rbs/patch.rb
CHANGED
|
@@ -8,11 +8,18 @@ module RBS
|
|
|
8
8
|
class Patch # rubocop:disable Style/Documentation
|
|
9
9
|
ANNOTATION_OVERRIDE = "patch:override"
|
|
10
10
|
ANNOTATION_DELETE = "patch:delete"
|
|
11
|
-
ANNOTATION_APPEND_AFTER = /\Apatch:append_after
|
|
12
|
-
ANNOTATION_PREPEND_BEFORE = /\Apatch:prepend_before
|
|
11
|
+
ANNOTATION_APPEND_AFTER = /\Apatch:append_after\((.*)\)\Z/
|
|
12
|
+
ANNOTATION_PREPEND_BEFORE = /\Apatch:prepend_before\((.*)\)\Z/
|
|
13
13
|
|
|
14
14
|
def initialize
|
|
15
|
-
@
|
|
15
|
+
@decls = []
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def to_s
|
|
19
|
+
io = ::StringIO.new
|
|
20
|
+
::RBS::Writer.new(out: io).write(@decls)
|
|
21
|
+
io.rewind
|
|
22
|
+
io.read || ""
|
|
16
23
|
end
|
|
17
24
|
|
|
18
25
|
def apply(source = nil, path: nil)
|
|
@@ -22,83 +29,153 @@ module RBS
|
|
|
22
29
|
next if files.include?(path)
|
|
23
30
|
|
|
24
31
|
files << path
|
|
25
|
-
apply Buffer.new(name: path, content: path.read(encoding: "UTF-8"))
|
|
32
|
+
apply ::RBS::Buffer.new(name: path, content: path.read(encoding: "UTF-8"))
|
|
26
33
|
end
|
|
27
34
|
return
|
|
28
35
|
end
|
|
29
36
|
|
|
30
|
-
_,
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
next unless ope
|
|
46
|
-
|
|
47
|
-
case ope
|
|
48
|
-
when :override
|
|
49
|
-
index = decl_a.members.find_index { |member_a| member_a.name == member_b.name }
|
|
50
|
-
if index
|
|
51
|
-
decl_a.members[index] = decl_a.members[index].update(overloads: member_b.overloads)
|
|
52
|
-
true
|
|
53
|
-
else
|
|
54
|
-
false
|
|
55
|
-
end
|
|
56
|
-
when :delete
|
|
57
|
-
decl_a.members.reject! { |member_a| member_a.name == member_b.name }
|
|
58
|
-
when :append_after, :prepend_before
|
|
59
|
-
target_name = arg.to_sym
|
|
60
|
-
index = decl_a.members.find_index { |member_a| member_a.name == target_name }
|
|
61
|
-
if index
|
|
62
|
-
if ope == :append_after
|
|
63
|
-
offset = 1
|
|
64
|
-
annotations = member_b.annotations.reject { |a| a.string.match(ANNOTATION_APPEND_AFTER) }
|
|
65
|
-
else
|
|
66
|
-
offset = 0
|
|
67
|
-
annotations = member_b.annotations.reject { |a| a.string.match(ANNOTATION_PREPEND_BEFORE) }
|
|
68
|
-
end
|
|
69
|
-
decl_a.members.insert(index + offset, member_b.update(annotations:))
|
|
70
|
-
true
|
|
71
|
-
else
|
|
72
|
-
false
|
|
73
|
-
end
|
|
74
|
-
end
|
|
75
|
-
end
|
|
76
|
-
decl_a
|
|
37
|
+
_, _, decls = ::RBS::Parser.parse_signature(source)
|
|
38
|
+
walk(decls) do |decl, name|
|
|
39
|
+
ope, arg = process_annotations(decl.annotations) if decl.respond_to?(:annotations) # steep:ignore
|
|
40
|
+
|
|
41
|
+
case ope
|
|
42
|
+
when :override
|
|
43
|
+
override(name, with: decl)
|
|
44
|
+
when :delete
|
|
45
|
+
delete(name)
|
|
46
|
+
when :append_after
|
|
47
|
+
add(decl, to: name, after: arg)
|
|
48
|
+
when :prepend_before
|
|
49
|
+
add(decl, to: name, before: arg)
|
|
50
|
+
else
|
|
51
|
+
add(decl, to: name)
|
|
77
52
|
end
|
|
78
53
|
end
|
|
79
54
|
end
|
|
80
55
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
56
|
+
private
|
|
57
|
+
|
|
58
|
+
def extract_name(decl)
|
|
59
|
+
if decl.is_a?(::RBS::AST::Declarations::AliasDecl) # rubocop:disable Style/CaseLikeIf
|
|
60
|
+
decl.new_name.to_s
|
|
61
|
+
elsif decl.is_a?(::RBS::AST::Declarations::Base)
|
|
62
|
+
decl.name.to_s
|
|
63
|
+
elsif decl.is_a?(::RBS::AST::Members::LocationOnly)
|
|
64
|
+
""
|
|
65
|
+
elsif decl.is_a?(::RBS::AST::Members::Alias) # rubocop:disable Lint/DuplicateBranch
|
|
66
|
+
decl.new_name.to_s
|
|
67
|
+
else # rubocop:disable Lint/DuplicateBranch
|
|
68
|
+
# ::RBS::AST::Members::t
|
|
69
|
+
decl.name.to_s
|
|
88
70
|
end
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def extract_members(decl)
|
|
74
|
+
decl.members if decl.is_a?(::RBS::AST::Declarations::NestedDeclarationHelper)
|
|
75
|
+
end
|
|
89
76
|
|
|
90
|
-
|
|
77
|
+
def walk(decls, name_stack = [], &block)
|
|
91
78
|
decls.each do |decl|
|
|
92
|
-
|
|
93
|
-
|
|
79
|
+
name_stack << extract_name(decl)
|
|
80
|
+
if decl.is_a?(::RBS::AST::Members::Base)
|
|
81
|
+
yield decl, "#{name_stack[..-2]&.join("::")}##{name_stack[-1]}"
|
|
82
|
+
else
|
|
83
|
+
yield decl, name_stack.join("::")
|
|
94
84
|
end
|
|
85
|
+
members = extract_members(decl)
|
|
86
|
+
walk(members, name_stack, &block) if members
|
|
87
|
+
name_stack.pop
|
|
95
88
|
end
|
|
96
|
-
|
|
89
|
+
end
|
|
97
90
|
|
|
98
|
-
|
|
99
|
-
RBS::
|
|
100
|
-
|
|
101
|
-
|
|
91
|
+
def decl_map
|
|
92
|
+
# @type var map: Hash[String, ::RBS::Patch::t]
|
|
93
|
+
map = {}
|
|
94
|
+
walk(@decls) { |decl, name| map[name] = decl }
|
|
95
|
+
map
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
def add(decl, to:, after: nil, before: nil)
|
|
99
|
+
map = decl_map
|
|
100
|
+
return if map.key?(to)
|
|
101
|
+
|
|
102
|
+
sep = decl.is_a?(::RBS::AST::Members::Base) ? "#" : "::"
|
|
103
|
+
namespace, = to.rpartition(sep)
|
|
104
|
+
|
|
105
|
+
target = namespace.empty? ? @decls : extract_members(map[namespace])
|
|
106
|
+
|
|
107
|
+
if target
|
|
108
|
+
decl.annotations.delete_if { |a| process_annotations([a]) } # steep:ignore
|
|
109
|
+
if after
|
|
110
|
+
index = target.find_index { |m| extract_name(m) == after }
|
|
111
|
+
if index
|
|
112
|
+
# steep:ignore:start
|
|
113
|
+
decl = decl.update(location: target[index].location.dup) if decl.respond_to?(:update) # rubocop:disable Style/RedundantSelfAssignment
|
|
114
|
+
# steep:ignore:end
|
|
115
|
+
target.insert(index + 1, decl)
|
|
116
|
+
end
|
|
117
|
+
elsif before
|
|
118
|
+
index = target.find_index { |m| extract_name(m) == before }
|
|
119
|
+
if index
|
|
120
|
+
# steep:ignore:start
|
|
121
|
+
decl = decl.update(location: target[index].location.dup) if decl.respond_to?(:update) # rubocop:disable Style/RedundantSelfAssignment
|
|
122
|
+
# steep:ignore:end
|
|
123
|
+
target.insert(index, decl)
|
|
124
|
+
end
|
|
125
|
+
else
|
|
126
|
+
target << decl
|
|
127
|
+
end
|
|
128
|
+
else
|
|
129
|
+
@decls << decl # steep:ignore
|
|
130
|
+
end
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
def override(name, with:)
|
|
134
|
+
map = decl_map
|
|
135
|
+
return unless map.key?(name)
|
|
136
|
+
|
|
137
|
+
sep = with.is_a?(::RBS::AST::Members::Base) ? "#" : "::"
|
|
138
|
+
namespace, _, name = name.rpartition(sep)
|
|
139
|
+
|
|
140
|
+
if namespace.empty?
|
|
141
|
+
# top level decl
|
|
142
|
+
index = @decls.find_index { |d| extract_name(d) == name }
|
|
143
|
+
@decls[index] = with # steep:ignore
|
|
144
|
+
else
|
|
145
|
+
members = extract_members(map[namespace])
|
|
146
|
+
index = members.find_index do |m| # steep:ignore
|
|
147
|
+
extract_name(m) == name
|
|
148
|
+
end
|
|
149
|
+
members[index] = with # steep:ignore
|
|
150
|
+
end
|
|
151
|
+
with.annotations.delete_if { |a| process_annotations([a]) } # steep:ignore
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
def delete(name)
|
|
155
|
+
map = decl_map
|
|
156
|
+
return unless map.key?(name)
|
|
157
|
+
|
|
158
|
+
sep = name.index("#") ? "#" : "::"
|
|
159
|
+
namespace, _, name = name.rpartition(sep)
|
|
160
|
+
|
|
161
|
+
if namespace.empty?
|
|
162
|
+
# top level decl
|
|
163
|
+
@decls.delete_if { |d| extract_name(d) == name }
|
|
164
|
+
else
|
|
165
|
+
extract_members(map[namespace])&.delete_if { |m| extract_name(m) == name }
|
|
166
|
+
end
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
def process_annotations(annotations) # steep:ignore
|
|
170
|
+
if annotations.any? { |a| a.string == ANNOTATION_OVERRIDE }
|
|
171
|
+
[:override, nil]
|
|
172
|
+
elsif annotations.any? { |a| a.string == ANNOTATION_DELETE }
|
|
173
|
+
[:delete, nil]
|
|
174
|
+
elsif (anno = annotations.find { |a| a.string.match(ANNOTATION_APPEND_AFTER) })
|
|
175
|
+
[:append_after, anno.string.match(ANNOTATION_APPEND_AFTER)&.[](1) || ""]
|
|
176
|
+
elsif (anno = annotations.find { |a| a.string.match(ANNOTATION_PREPEND_BEFORE) })
|
|
177
|
+
[:prepend_before, anno.string.match(ANNOTATION_PREPEND_BEFORE)&.[](1) || ""]
|
|
178
|
+
end
|
|
102
179
|
end
|
|
103
180
|
end
|
|
104
181
|
end
|
data/sig/rbs/patch.rbs
CHANGED
|
@@ -2,12 +2,40 @@ module RBS
|
|
|
2
2
|
class Patch
|
|
3
3
|
VERSION: String
|
|
4
4
|
|
|
5
|
-
@
|
|
5
|
+
@decls: ::Array[::RBS::AST::Declarations::t]
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
ANNOTATION_OVERRIDE: "patch:override"
|
|
8
|
+
|
|
9
|
+
ANNOTATION_DELETE: "patch:delete"
|
|
10
|
+
|
|
11
|
+
ANNOTATION_APPEND_AFTER: ::Regexp
|
|
12
|
+
|
|
13
|
+
ANNOTATION_PREPEND_BEFORE: ::Regexp
|
|
8
14
|
|
|
9
|
-
def
|
|
15
|
+
def initialize: () -> void
|
|
10
16
|
|
|
11
17
|
def to_s: () -> String
|
|
18
|
+
|
|
19
|
+
def apply: (?untyped? String, ?path: Pathname?) -> void
|
|
20
|
+
|
|
21
|
+
type t = ::RBS::AST::Declarations::t | ::RBS::AST::Members::t
|
|
22
|
+
|
|
23
|
+
private
|
|
24
|
+
|
|
25
|
+
def extract_name: (t decl) -> String
|
|
26
|
+
|
|
27
|
+
def extract_members: (t decl) -> (Array[::RBS::AST::Declarations::Class::member] | Array[::RBS::AST::Declarations::Module::member] | nil)
|
|
28
|
+
|
|
29
|
+
def walk: (Array[t] decls, ?Array[String] name_stack) { (::RBS::AST::Declarations::t | ::RBS::AST::Members::t, String) -> void } -> void
|
|
30
|
+
|
|
31
|
+
def decl_map: () -> Hash[String, t]
|
|
32
|
+
|
|
33
|
+
def add: (t decl, to: String, ?after: String?, ?before: String?) -> void
|
|
34
|
+
|
|
35
|
+
def override: (String name, with: t) -> void
|
|
36
|
+
|
|
37
|
+
def delete: (String name) -> void
|
|
38
|
+
|
|
39
|
+
def process_annotations: (Array[::RBS::AST::Annotation] annotations) -> ([:override, nil] | [:delte, nil] | [:append_after, String] | [:prepend_before, String] | nil)
|
|
12
40
|
end
|
|
13
41
|
end
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: rbs-patch
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.1.
|
|
4
|
+
version: 0.1.4
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Koji NAKAMURA
|
|
@@ -13,16 +13,16 @@ dependencies:
|
|
|
13
13
|
name: rbs
|
|
14
14
|
requirement: !ruby/object:Gem::Requirement
|
|
15
15
|
requirements:
|
|
16
|
-
- -
|
|
16
|
+
- - "~>"
|
|
17
17
|
- !ruby/object:Gem::Version
|
|
18
|
-
version:
|
|
18
|
+
version: '3.0'
|
|
19
19
|
type: :runtime
|
|
20
20
|
prerelease: false
|
|
21
21
|
version_requirements: !ruby/object:Gem::Requirement
|
|
22
22
|
requirements:
|
|
23
|
-
- -
|
|
23
|
+
- - "~>"
|
|
24
24
|
- !ruby/object:Gem::Version
|
|
25
|
-
version:
|
|
25
|
+
version: '3.0'
|
|
26
26
|
description: RBS::Patch manages RBS (Ruby Signature) type definitions through patches.
|
|
27
27
|
It applies incremental changes to existing RBS signatures.
|
|
28
28
|
email:
|
|
@@ -35,6 +35,7 @@ files:
|
|
|
35
35
|
- LICENSE.txt
|
|
36
36
|
- README.md
|
|
37
37
|
- Rakefile
|
|
38
|
+
- Steepfile
|
|
38
39
|
- exe/rbs-patch
|
|
39
40
|
- lib/rbs/patch.rb
|
|
40
41
|
- lib/rbs/patch/version.rb
|