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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 3a45288c145d3c26618712866de56ebeebabc1034e29960877834d5fc0518c58
4
- data.tar.gz: 12ae5d9f8fcd5721f05bac232b9dd10b8de6a23c47c73eba19801e65b3b33143
3
+ metadata.gz: ef229a0a3efd1a66cdc57652ef80b173d19311652f376422a06bad6d61d3c7b5
4
+ data.tar.gz: 364bff243f157949a91b88c936a953e88cf275589312c664df1a9f6cc2d89c40
5
5
  SHA512:
6
- metadata.gz: b939dc31112ba51d86e88c66b0000ac3abcf7baa9d5b57db79af3ee8003dc2cafbb90e2d6c4d08ffb53c3aebd8bb3c1d2b1bec7609911d03f7ed12105621a2b4
7
- data.tar.gz: '00998b3ec8d70b87cf45177244ed9dc0e8bdb572f95eb33e07f422a137a67922a6add9ddb06a00777de1eb00aa69eb787930b2e6b33704b890064e4c1b2683c5'
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
- TODO: Write usage instructions here
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
 
@@ -2,6 +2,6 @@
2
2
 
3
3
  module RBS
4
4
  class Patch
5
- VERSION = "0.1.3"
5
+ VERSION = "0.1.4"
6
6
  end
7
7
  end
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:(.*)\Z/
12
- ANNOTATION_PREPEND_BEFORE = /\Apatch:prepend_before:(.*)\Z/
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
- name = if decl.is_a?(::RBS::AST::Declarations::AliasDecl) # rubocop:disable Style/CaseLikeIf
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
- walk(decl.members, name_stack, &block) if decl.is_a?(::RBS::AST::Declarations::NestedDeclarationHelper)
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]&.members # steep:ignore
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.name.to_s == after }
103
- target.insert(index + 1, decl) if index
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.name.to_s == before }
106
- target.insert(index, decl) if index
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.name.to_s == name }
128
- @decls[index] = with
142
+ index = @decls.find_index { |d| extract_name(d) == name }
143
+ @decls[index] = with # steep:ignore
129
144
  else
130
- index = map[namespace].members.find_index do |m|
131
- m.name.to_s == name
145
+ members = extract_members(map[namespace])
146
+ index = members.find_index do |m| # steep:ignore
147
+ extract_name(m) == name
132
148
  end
133
- map[namespace].members[index] = with
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.name.to_s == name }
163
+ @decls.delete_if { |d| extract_name(d) == name }
150
164
  else
151
- map[namespace].members.delete_if { |m| m.name.to_s == name }
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]
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.3
4
+ version: 0.1.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Koji NAKAMURA