rbs-patch 0.1.3 → 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/lib/rbs/patch/version.rb +1 -1
- data/lib/rbs/patch.rb +50 -37
- data/sig/rbs/patch.rbs +4 -0
- metadata +1 -1
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/lib/rbs/patch/version.rb
CHANGED
data/lib/rbs/patch.rb
CHANGED
|
@@ -8,8 +8,8 @@ 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 = []
|
|
@@ -55,27 +55,35 @@ module RBS
|
|
|
55
55
|
|
|
56
56
|
private
|
|
57
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
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def extract_members(decl)
|
|
74
|
+
decl.members if decl.is_a?(::RBS::AST::Declarations::NestedDeclarationHelper)
|
|
75
|
+
end
|
|
76
|
+
|
|
58
77
|
def walk(decls, name_stack = [], &block)
|
|
59
78
|
decls.each do |decl|
|
|
60
|
-
|
|
61
|
-
decl.new_name.to_s
|
|
62
|
-
elsif decl.is_a?(::RBS::AST::Declarations::Base)
|
|
63
|
-
decl.name.to_s
|
|
64
|
-
elsif decl.is_a?(::RBS::AST::Members::LocationOnly)
|
|
65
|
-
""
|
|
66
|
-
elsif decl.is_a?(::RBS::AST::Members::Alias) # rubocop:disable Lint/DuplicateBranch
|
|
67
|
-
decl.new_name.to_s
|
|
68
|
-
else # rubocop:disable Lint/DuplicateBranch
|
|
69
|
-
# ::RBS::AST::Members::t
|
|
70
|
-
decl.name.to_s
|
|
71
|
-
end
|
|
72
|
-
name_stack << name
|
|
79
|
+
name_stack << extract_name(decl)
|
|
73
80
|
if decl.is_a?(::RBS::AST::Members::Base)
|
|
74
81
|
yield decl, "#{name_stack[..-2]&.join("::")}##{name_stack[-1]}"
|
|
75
82
|
else
|
|
76
83
|
yield decl, name_stack.join("::")
|
|
77
84
|
end
|
|
78
|
-
|
|
85
|
+
members = extract_members(decl)
|
|
86
|
+
walk(members, name_stack, &block) if members
|
|
79
87
|
name_stack.pop
|
|
80
88
|
end
|
|
81
89
|
end
|
|
@@ -94,24 +102,32 @@ module RBS
|
|
|
94
102
|
sep = decl.is_a?(::RBS::AST::Members::Base) ? "#" : "::"
|
|
95
103
|
namespace, = to.rpartition(sep)
|
|
96
104
|
|
|
97
|
-
target = namespace.empty? ? @decls : map[namespace]
|
|
105
|
+
target = namespace.empty? ? @decls : extract_members(map[namespace])
|
|
98
106
|
|
|
99
|
-
# steep:ignore:start
|
|
100
107
|
if target
|
|
108
|
+
decl.annotations.delete_if { |a| process_annotations([a]) } # steep:ignore
|
|
101
109
|
if after
|
|
102
|
-
index = target.find_index { |m| m
|
|
103
|
-
|
|
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
|
|
104
117
|
elsif before
|
|
105
|
-
index = target.find_index { |m| m
|
|
106
|
-
|
|
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
|
|
107
125
|
else
|
|
108
126
|
target << decl
|
|
109
127
|
end
|
|
110
|
-
decl.annotations.delete_if { |a| process_annotations([a]) }
|
|
111
128
|
else
|
|
112
|
-
@decls << decl
|
|
129
|
+
@decls << decl # steep:ignore
|
|
113
130
|
end
|
|
114
|
-
# steep:ignore:end
|
|
115
131
|
end
|
|
116
132
|
|
|
117
133
|
def override(name, with:)
|
|
@@ -121,19 +137,18 @@ module RBS
|
|
|
121
137
|
sep = with.is_a?(::RBS::AST::Members::Base) ? "#" : "::"
|
|
122
138
|
namespace, _, name = name.rpartition(sep)
|
|
123
139
|
|
|
124
|
-
# steep:ignore:start
|
|
125
140
|
if namespace.empty?
|
|
126
141
|
# top level decl
|
|
127
|
-
index = @decls.find_index { |d| d
|
|
128
|
-
@decls[index] = with
|
|
142
|
+
index = @decls.find_index { |d| extract_name(d) == name }
|
|
143
|
+
@decls[index] = with # steep:ignore
|
|
129
144
|
else
|
|
130
|
-
|
|
131
|
-
|
|
145
|
+
members = extract_members(map[namespace])
|
|
146
|
+
index = members.find_index do |m| # steep:ignore
|
|
147
|
+
extract_name(m) == name
|
|
132
148
|
end
|
|
133
|
-
|
|
149
|
+
members[index] = with # steep:ignore
|
|
134
150
|
end
|
|
135
|
-
with.annotations.delete_if { |a| process_annotations([a]) }
|
|
136
|
-
# steep:ignore:end
|
|
151
|
+
with.annotations.delete_if { |a| process_annotations([a]) } # steep:ignore
|
|
137
152
|
end
|
|
138
153
|
|
|
139
154
|
def delete(name)
|
|
@@ -143,14 +158,12 @@ module RBS
|
|
|
143
158
|
sep = name.index("#") ? "#" : "::"
|
|
144
159
|
namespace, _, name = name.rpartition(sep)
|
|
145
160
|
|
|
146
|
-
# steep:ignore:start
|
|
147
161
|
if namespace.empty?
|
|
148
162
|
# top level decl
|
|
149
|
-
@decls.delete_if { |d| d
|
|
163
|
+
@decls.delete_if { |d| extract_name(d) == name }
|
|
150
164
|
else
|
|
151
|
-
map[namespace]
|
|
165
|
+
extract_members(map[namespace])&.delete_if { |m| extract_name(m) == name }
|
|
152
166
|
end
|
|
153
|
-
# steep:ignore:end
|
|
154
167
|
end
|
|
155
168
|
|
|
156
169
|
def process_annotations(annotations) # steep:ignore
|
data/sig/rbs/patch.rbs
CHANGED
|
@@ -22,6 +22,10 @@ module RBS
|
|
|
22
22
|
|
|
23
23
|
private
|
|
24
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
|
+
|
|
25
29
|
def walk: (Array[t] decls, ?Array[String] name_stack) { (::RBS::AST::Declarations::t | ::RBS::AST::Members::t, String) -> void } -> void
|
|
26
30
|
|
|
27
31
|
def decl_map: () -> Hash[String, t]
|