ffast 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/Fastfile +1 -6
- data/README.md +22 -26
- data/docs/command_line.md +34 -11
- data/docs/index.md +116 -73
- data/docs/similarity_tutorial.md +1 -1
- data/docs/syntax.md +34 -51
- data/lib/fast.rb +92 -68
- data/lib/fast/cli.rb +34 -52
- data/lib/fast/experiment.rb +2 -2
- data/lib/fast/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: fd904f00cff76125f3b6b79455b1fda97040acbe1e631d4842a26d5e126defde
|
4
|
+
data.tar.gz: 426d0dfd4d17fe9e491921ae431f75d28c683da1bf6f37e27d64d9808ff86f0a
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 773ec583e5c2dc7edf71d519ed0a09ace7f3714b85483f78a4ace150a85479a6cfcef20cf3724f5105204b3f26a4fb4f2a8e4040aebf366f049a3d11a7e84fcf
|
7
|
+
data.tar.gz: 2da3d58ad7036f74e245de58e654d736cc0f97f30bf116934166b1393132f3820e4b55f40039b99ea1589369dd650b694d874763fc36fd995bd521498b5e8257
|
data/Fastfile
CHANGED
@@ -18,7 +18,7 @@ Fast.shortcut(:parser, '(class (const nil ExpressionParser)', 'lib/fast.rb')
|
|
18
18
|
|
19
19
|
# Use `fast .bump_version` to rewrite the version file
|
20
20
|
Fast.shortcut :bump_version do
|
21
|
-
rewrite_file('
|
21
|
+
rewrite_file('(casgn nil VERSION (str _)', 'lib/fast/version.rb') do |node|
|
22
22
|
target = node.children.last.loc.expression
|
23
23
|
pieces = target.source.split('.').map(&:to_i)
|
24
24
|
pieces.reverse.each_with_index do |fragment, i|
|
@@ -32,8 +32,3 @@ Fast.shortcut :bump_version do
|
|
32
32
|
replace(target, "'#{pieces.join('.')}'")
|
33
33
|
end
|
34
34
|
end
|
35
|
-
|
36
|
-
# And now a shortcut to print the other shortcuts :)
|
37
|
-
Fast.shortcut :shortcuts do
|
38
|
-
report(shortcuts.keys)
|
39
|
-
end
|
data/README.md
CHANGED
@@ -79,14 +79,14 @@ It's corresponding s-expression would be:
|
|
79
79
|
If we wanted to find this particular assignment somewhere in our AST, we can use
|
80
80
|
Fast to look for a local variable named `value` with a value `42`:
|
81
81
|
|
82
|
-
Fast.match?
|
82
|
+
Fast.match? '(lvasgn value (int 42))', ast # => true
|
83
83
|
|
84
84
|
### Wildcard Token
|
85
85
|
|
86
86
|
If we wanted to find a variable named `value` that was assigned any integer value
|
87
87
|
we could replace `42` in our query with an underscore ( `_` ) as a shortcut:
|
88
88
|
|
89
|
-
Fast.match?
|
89
|
+
Fast.match? '(lvasgn value (int _))', ast # => true
|
90
90
|
|
91
91
|
### Set Inclusion Token
|
92
92
|
|
@@ -94,7 +94,7 @@ If we weren't sure the type of the value we're assigning, we can use our set
|
|
94
94
|
inclusion token (`{}`) from earlier to tell Fast that we expect either a `Float`
|
95
95
|
or an `Integer`:
|
96
96
|
|
97
|
-
Fast.match?
|
97
|
+
Fast.match? '(lvasgn value ({float int} _))', ast # => true
|
98
98
|
|
99
99
|
### All Matching Token
|
100
100
|
|
@@ -103,28 +103,28 @@ all matching token (`[]`) to express multiple conditions that need to be true.
|
|
103
103
|
In this case we don't want the value to be a `String`, `Hash`, or an `Array` by
|
104
104
|
prefixing all of the types with `!`:
|
105
105
|
|
106
|
-
Fast.match?
|
106
|
+
Fast.match? '(lvasgn value ([!str !hash !array] _))', ast # => true
|
107
107
|
|
108
108
|
### Node Child Token
|
109
109
|
|
110
110
|
We can match any node with children by using the child token ( `...` ):
|
111
111
|
|
112
|
-
Fast.match?
|
112
|
+
Fast.match? '(lvasgn value ...)', ast # => true
|
113
113
|
|
114
114
|
We could even match any local variable assignment combining both `_` and `...`:
|
115
115
|
|
116
|
-
Fast.match?
|
116
|
+
Fast.match? '(lvasgn _ ...)', ast # => true
|
117
117
|
|
118
118
|
### Capturing the Value of an Expression
|
119
119
|
|
120
120
|
You can use `$` to capture the contents of an expression for later use:
|
121
121
|
|
122
|
-
Fast.match?(
|
122
|
+
Fast.match?('(lvasgn value $...)', ast) # => [s(:int, 42)]
|
123
123
|
|
124
124
|
Captures can be used in any position as many times as you want to capture whatever
|
125
125
|
information you might need:
|
126
126
|
|
127
|
-
Fast.match?(
|
127
|
+
Fast.match?('(lvasgn $_ $...)', ast) # => [:value, s(:int, 42)]
|
128
128
|
|
129
129
|
> Keep in mind that `_` means something not nil and `...` means a node with
|
130
130
|
> children.
|
@@ -142,7 +142,7 @@ method names and guarantee they are unique.
|
|
142
142
|
already_exists
|
143
143
|
end
|
144
144
|
|
145
|
-
puts Fast.search_file(
|
145
|
+
puts Fast.search_file('(def #duplicated)', 'example.rb')
|
146
146
|
|
147
147
|
The same principle can be used in the node level or for debugging purposes.
|
148
148
|
|
@@ -195,35 +195,31 @@ a single `Object`, like `a.b.c.d`. Its corresponding s-expression would be:
|
|
195
195
|
You can also search using nested arrays with **pure values**, or **shortcuts** or
|
196
196
|
**procs**:
|
197
197
|
|
198
|
-
Fast.match?
|
199
|
-
Fast.match?
|
200
|
-
Fast.match?(ast, [:send, [:send, [:send, '...'], :c], :d]) # => true
|
198
|
+
Fast.match? [:send, [:send, '...'], :d], ast # => true
|
199
|
+
Fast.match? [:send, [:send, '...'], :c], ast # => false
|
201
200
|
|
202
201
|
Shortcut tokens like child nodes `...` and wildcards `_` are just placeholders
|
203
202
|
for procs. If you want, you can even use procs directly like so:
|
204
203
|
|
205
|
-
Fast.match?(
|
204
|
+
Fast.match?([
|
206
205
|
:send, [
|
207
206
|
-> (node) { node.type == :send },
|
208
207
|
[:send, '...'],
|
209
208
|
:c
|
210
209
|
],
|
211
210
|
:d
|
212
|
-
]) # => true
|
211
|
+
], ast) # => true
|
213
212
|
|
214
213
|
This also works with expressions:
|
215
214
|
|
216
|
-
Fast.match?(
|
217
|
-
ast,
|
218
|
-
'(send (send (send (send nil $_) $_) $_) $_)'
|
219
|
-
) # => [:a, :b, :c, :d]
|
215
|
+
Fast.match?('(send (send (send (send nil $_) $_) $_) $_)', ast) # => [:a, :b, :c, :d]
|
220
216
|
|
221
217
|
### Debugging
|
222
218
|
|
223
219
|
If you find that a particular expression isn't working, you can use `debug` to
|
224
220
|
take a look at what Fast is doing:
|
225
221
|
|
226
|
-
Fast.debug { Fast.match?(
|
222
|
+
Fast.debug { Fast.match?([:int, 1], s(:int, 1)) }
|
227
223
|
|
228
224
|
Each comparison made while searching will be logged to your console (STDOUT) as
|
229
225
|
Fast goes through the AST:
|
@@ -237,7 +233,7 @@ We can also dynamically interpolate arguments into our queries using the
|
|
237
233
|
interpolation token `%`. This works much like `sprintf` using indexes starting
|
238
234
|
from `1`:
|
239
235
|
|
240
|
-
Fast.match?
|
236
|
+
Fast.match? '(lvasgn %1 (int _))', ('a = 1'), :a # => true
|
241
237
|
|
242
238
|
## Using previous captures in search
|
243
239
|
|
@@ -264,18 +260,18 @@ We can create a query that searches for such a method:
|
|
264
260
|
Search allows you to go search the entire AST, collecting nodes that matches given
|
265
261
|
expression. Any matching node is then returned:
|
266
262
|
|
267
|
-
Fast.search(
|
263
|
+
Fast.search(Fast.ast('a = 1'), '(int _)') # => s(:int, 1)
|
268
264
|
|
269
265
|
If you use captures along with a search, both the matching nodes and the
|
270
266
|
captures will be returned:
|
271
267
|
|
272
|
-
Fast.search(
|
268
|
+
Fast.search(Fast.ast('a = 1'), '(int $_)') # => [s(:int, 1), 1]
|
273
269
|
|
274
270
|
## Fast.capture
|
275
271
|
|
276
272
|
To only pick captures and ignore the nodes, use `Fast.capture`:
|
277
273
|
|
278
|
-
Fast.capture(
|
274
|
+
Fast.capture(Fast.ast('a = 1'), '(int $_)') # => 1
|
279
275
|
|
280
276
|
## Fast.replace
|
281
277
|
|
@@ -325,7 +321,7 @@ Or only the method name:
|
|
325
321
|
In the context of the rewriter, the objective is removing the method and inserting the new
|
326
322
|
delegate content. Then, the scope is `node.location.expression`:
|
327
323
|
|
328
|
-
Fast.replace
|
324
|
+
Fast.replace '(def $_ ... (send (send nil $_) \1))', ast do |node, captures|
|
329
325
|
attribute, object = captures
|
330
326
|
|
331
327
|
replace(
|
@@ -360,7 +356,7 @@ To refactor and reach the proposed example, follow a few steps:
|
|
360
356
|
#### Entire example
|
361
357
|
|
362
358
|
assignment = nil
|
363
|
-
Fast.replace_file '
|
359
|
+
Fast.replace_file '({ lvasgn lvar } message )', 'sample.rb' do |node, _|
|
364
360
|
# Find a variable assignment
|
365
361
|
if node.type == :lvasgn
|
366
362
|
assignment = node.children.last
|
@@ -466,7 +462,7 @@ and add the snippet to your library fixing the filename.
|
|
466
462
|
|
467
463
|
```ruby
|
468
464
|
Fast.shortcut :bump_version do
|
469
|
-
rewrite_file('
|
465
|
+
rewrite_file('(casgn nil VERSION (str _)', 'lib/fast/version.rb') do |node|
|
470
466
|
target = node.children.last.loc.expression
|
471
467
|
pieces = target.source.split(".").map(&:to_i)
|
472
468
|
pieces.reverse.each_with_index do |fragment,i|
|
data/docs/command_line.md
CHANGED
@@ -6,7 +6,6 @@ and you can use it to search and find code using the concept:
|
|
6
6
|
```
|
7
7
|
$ fast '(def match?)' lib/fast.rb
|
8
8
|
```
|
9
|
-
|
10
9
|
- Use `-d` or `--debug` for enable debug mode.
|
11
10
|
- Use `--ast` to output the AST instead of the original code
|
12
11
|
- Use `--pry` to jump debugging the first result with pry
|
@@ -35,12 +34,8 @@ Getting all `it` blocks without description:
|
|
35
34
|
|
36
35
|
```ruby
|
37
36
|
# spec/fast_spec.rb:166
|
38
|
-
it { expect(described_class).to be_match(s(:int, 1)
|
39
|
-
|
40
|
-
it { expect(described_class).to be_match(s(:int, 1), '(_ _)') }
|
41
|
-
# spec/fast_spec.rb:168
|
42
|
-
it { expect(described_class).to be_match(code['"string"'], '(str "string")') }
|
43
|
-
# ... more results
|
37
|
+
it { expect(described_class).to be_match('(...)', s(:int, 1)) }
|
38
|
+
...
|
44
39
|
```
|
45
40
|
|
46
41
|
## `--debug`
|
@@ -122,12 +117,12 @@ You can define a `Fastfile` in any project with your custom shortcuts and easy
|
|
122
117
|
check some code or run some task.
|
123
118
|
|
124
119
|
|
125
|
-
##
|
120
|
+
## Shortcut examples
|
126
121
|
|
127
122
|
Create shortcuts with blocks enables introduce custom coding in
|
128
123
|
the scope of the `Fast` module.
|
129
124
|
|
130
|
-
|
125
|
+
### Print library version.
|
131
126
|
|
132
127
|
Let's say you'd like to show the version of your library. Your regular params
|
133
128
|
in the command line will look like:
|
@@ -152,7 +147,7 @@ We can also always override the files params passing some other target file
|
|
152
147
|
like `fast .version lib/other/file.rb` and it will reuse the other arguments
|
153
148
|
from command line but replace the target files.
|
154
149
|
|
155
|
-
###
|
150
|
+
### Bumping a gem version
|
156
151
|
|
157
152
|
While releasing a new gem version, we always need to mechanical go through the
|
158
153
|
`lib/<your_gem>/version.rb` and change the string value to bump the version
|
@@ -210,5 +205,33 @@ Fast.shortcut :shortcuts do
|
|
210
205
|
end
|
211
206
|
```
|
212
207
|
|
213
|
-
|
208
|
+
Or we can make it a bit more friendly and also use Fast to process the shortcut
|
209
|
+
positions and pick the comment that each shortcut have in the previous line:
|
210
|
+
|
211
|
+
```ruby
|
212
|
+
# List all shortcut with comments
|
213
|
+
Fast.shortcut :shortcuts do
|
214
|
+
fast_files.each do |file|
|
215
|
+
lines = File.readlines(file).map{|line|line.chomp.gsub(/\s*#/,'').strip}
|
216
|
+
result = capture_file('(send ... shortcut $(sym _))', file)
|
217
|
+
result = [result] unless result.is_a?Array
|
218
|
+
result.each do |capture|
|
219
|
+
target = capture.loc.expression
|
220
|
+
puts "fast .#{target.source[1..-1].ljust(30)} # #{lines[target.line-2]}"
|
221
|
+
end
|
222
|
+
end
|
223
|
+
end
|
224
|
+
```
|
225
|
+
|
226
|
+
And it will be printing all loaded shortcuts with comments:
|
227
|
+
|
228
|
+
```
|
229
|
+
$ fast .shortcuts
|
230
|
+
fast .version # Let's say you'd like to show the version that is over the version file
|
231
|
+
fast .parser # Simple shortcut that I used often to show how the expression parser works
|
232
|
+
fast .bump_version # Use `fast .bump_version` to rewrite the version file
|
233
|
+
fast .shortcuts # List all shortcut with comments
|
234
|
+
```
|
235
|
+
|
236
|
+
You can find more examples in the [Fastfile](https://github.com/jonatas/fast/tree/master/Fastfile).
|
214
237
|
|
data/docs/index.md
CHANGED
@@ -64,9 +64,36 @@ The current version cover the following elements:
|
|
64
64
|
|
65
65
|
Jump to [Syntax](syntax.md).
|
66
66
|
|
67
|
-
##
|
67
|
+
## ast
|
68
68
|
|
69
|
-
`
|
69
|
+
Use `Fast.ast` to convert simple code to AST objects. You can use it as
|
70
|
+
`ruby-parse` but directly from the console.
|
71
|
+
|
72
|
+
```ruby
|
73
|
+
Fast.ast("1") # => s(:int, 1)
|
74
|
+
Fast.ast("method") # => s(:send, nil, :method)
|
75
|
+
Fast.ast("a.b") # => s(:send, s(:send, nil, :a), :b)
|
76
|
+
Fast.ast("1 + 1") # => s(:send, s(:int, 1), :+, s(:int, 1))
|
77
|
+
Fast.ast("a = 2") # => s(:lvasgn, :a, s(:int, 2))
|
78
|
+
Fast.ast("b += 2") # => s(:op_asgn, s(:lvasgn, :b), :+, s(:int, 2))
|
79
|
+
```
|
80
|
+
|
81
|
+
It uses [astrolable](https://github.com/yujinakayama/astrolabe) gem behind the scenes:
|
82
|
+
|
83
|
+
```ruby
|
84
|
+
Fast.ast(Fast.ast("1")).class
|
85
|
+
=> Astrolabe::Node
|
86
|
+
Fast.ast(Fast.ast("1")).type
|
87
|
+
=> :int
|
88
|
+
Fast.ast(Fast.ast("1")).children
|
89
|
+
=> [1]
|
90
|
+
```
|
91
|
+
|
92
|
+
See also [ast_from_file](#ast_from_file).
|
93
|
+
|
94
|
+
## match?
|
95
|
+
|
96
|
+
`Fast.match?` is the most granular function that tries to compare a node with an
|
70
97
|
expression. It returns true or false and some node captures case it find
|
71
98
|
something.
|
72
99
|
|
@@ -107,25 +134,25 @@ ast = s(:lvasgn, :value, s(:int, 42))
|
|
107
134
|
Now, lets find local variable named `value` with an value `42`:
|
108
135
|
|
109
136
|
```ruby
|
110
|
-
Fast.match?(
|
137
|
+
Fast.match?('(lvasgn value (int 42))', ast) # true
|
111
138
|
```
|
112
139
|
|
113
140
|
Lets abstract a bit and allow some integer value using `_` as a shortcut:
|
114
141
|
|
115
142
|
```ruby
|
116
|
-
Fast.match?(
|
143
|
+
Fast.match?('(lvasgn value (int _))', ast) # true
|
117
144
|
```
|
118
145
|
|
119
146
|
Lets abstract more and allow float or integer:
|
120
147
|
|
121
148
|
```ruby
|
122
|
-
Fast.match?(
|
149
|
+
Fast.match?('(lvasgn value ({float int} _))', ast) # true
|
123
150
|
```
|
124
151
|
|
125
152
|
Or combine multiple assertions using `[]` to join conditions:
|
126
153
|
|
127
154
|
```ruby
|
128
|
-
Fast.match?(
|
155
|
+
Fast.match?('(lvasgn value ([!str !hash !array] _))', ast) # true
|
129
156
|
```
|
130
157
|
|
131
158
|
Matches all local variables not string **and** not hash **and** not array.
|
@@ -133,25 +160,25 @@ Matches all local variables not string **and** not hash **and** not array.
|
|
133
160
|
We can match "a node with children" using `...`:
|
134
161
|
|
135
162
|
```ruby
|
136
|
-
Fast.match?(
|
163
|
+
Fast.match?('(lvasgn value ...)', ast) # true
|
137
164
|
```
|
138
165
|
|
139
166
|
You can use `$` to capture a node:
|
140
167
|
|
141
168
|
```ruby
|
142
|
-
Fast.match?(
|
169
|
+
Fast.match?('(lvasgn value $...)', ast) # => [s(:int), 42]
|
143
170
|
```
|
144
171
|
|
145
172
|
Or match whatever local variable assignment combining both `_` and `...`:
|
146
173
|
|
147
174
|
```ruby
|
148
|
-
Fast.match?(
|
175
|
+
Fast.match?('(lvasgn _ ...)', ast) # true
|
149
176
|
```
|
150
177
|
|
151
178
|
You can also use captures in any levels you want:
|
152
179
|
|
153
180
|
```ruby
|
154
|
-
Fast.match?(
|
181
|
+
Fast.match?('(lvasgn $_ $...)', ast) # [:value, s(:int), 42]
|
155
182
|
```
|
156
183
|
|
157
184
|
Keep in mind that `_` means something not nil and `...` means a node with
|
@@ -196,31 +223,28 @@ You can search using sub-arrays with **pure values**, or **shortcuts** or
|
|
196
223
|
**procs**:
|
197
224
|
|
198
225
|
```ruby
|
199
|
-
Fast.match?(
|
200
|
-
Fast.match?(
|
201
|
-
Fast.match?(
|
226
|
+
Fast.match?([:send, [:send, '...'], :d], ast) # => true
|
227
|
+
Fast.match?([:send, [:send, '...'], :c], ast) # => false
|
228
|
+
Fast.match?([:send, [:send, [:send, '...'], :c], :d], ast) # => true
|
202
229
|
```
|
203
230
|
|
204
231
|
Shortcuts like `...` and `_` are just literals for procs. Then you can use
|
205
232
|
procs directly too:
|
206
233
|
|
207
234
|
```ruby
|
208
|
-
Fast.match?(
|
235
|
+
Fast.match?([:send, [ -> (node) { node.type == :send }, [:send, '...'], :c], :d], ast) # => true
|
209
236
|
```
|
210
237
|
|
211
238
|
And also work with expressions:
|
212
239
|
|
213
240
|
```ruby
|
214
|
-
Fast.match?(
|
215
|
-
ast,
|
216
|
-
'(send (send (send (send nil $_) $_) $_) $_)'
|
217
|
-
) # => [:a, :b, :c, :d]
|
241
|
+
Fast.match?('(send (send (send (send nil $_) $_) $_) $_)', ast) # => [:a, :b, :c, :d]
|
218
242
|
```
|
219
243
|
|
220
244
|
If something does not work you can debug with a block:
|
221
245
|
|
222
246
|
```ruby
|
223
|
-
Fast.debug { Fast.match?(
|
247
|
+
Fast.debug { Fast.match?([:int, 1], s(:int, 1)) }
|
224
248
|
```
|
225
249
|
|
226
250
|
It will output each comparison to stdout:
|
@@ -230,70 +254,40 @@ int == (int 1) # => true
|
|
230
254
|
1 == 1 # => true
|
231
255
|
```
|
232
256
|
|
233
|
-
##
|
234
|
-
|
235
|
-
Imagine you're looking for a method that is just delegating something to
|
236
|
-
another method, like:
|
237
|
-
|
238
|
-
```ruby
|
239
|
-
def name
|
240
|
-
person.name
|
241
|
-
end
|
242
|
-
```
|
243
|
-
|
244
|
-
This can be represented as the following AST:
|
245
|
-
|
246
|
-
```
|
247
|
-
(def :name
|
248
|
-
(args)
|
249
|
-
(send
|
250
|
-
(send nil :person) :name))
|
251
|
-
```
|
252
|
-
|
253
|
-
Then, let's build a search for methods that calls an attribute with the same
|
254
|
-
name:
|
255
|
-
|
256
|
-
```ruby
|
257
|
-
Fast.match?(ast,'(def $_ ... (send (send nil _) \1))') # => [:name]
|
258
|
-
```
|
259
|
-
|
260
|
-
## Fast.search
|
257
|
+
## search
|
261
258
|
|
262
259
|
Search allows you to go deeply in the AST, collecting nodes that matches with
|
263
260
|
the expression. It also returns captures if they exist.
|
264
261
|
|
265
262
|
```ruby
|
266
|
-
Fast.search(
|
263
|
+
Fast.search('(int _)', Fast.ast('a = 1')) # => s(:int, 1)
|
267
264
|
```
|
268
265
|
|
269
266
|
If you use captures, it returns the node and the captures respectively:
|
270
267
|
|
271
268
|
```ruby
|
272
|
-
Fast.search(
|
269
|
+
Fast.search('(int $_)', Fast.ast('a = 1')) # => [s(:int, 1), 1]
|
273
270
|
```
|
274
271
|
|
275
|
-
##
|
272
|
+
## capture
|
276
273
|
|
277
274
|
To pick just the captures and ignore the nodes, use `Fast.capture`:
|
278
275
|
|
279
276
|
```ruby
|
280
|
-
Fast.capture(
|
277
|
+
Fast.capture('(int $_)', Fast.ast('a = 1')) # => 1
|
281
278
|
```
|
282
|
-
##
|
279
|
+
## replace
|
283
280
|
|
284
281
|
And if I want to refactor a code and use `delegate <attribute>, to: <object>`, try with replace:
|
285
282
|
|
286
283
|
```ruby
|
287
|
-
Fast.replace
|
288
|
-
|
289
|
-
|
290
|
-
|
291
|
-
"delegate :#{attribute}, to: :#{object}"
|
292
|
-
)
|
293
|
-
end
|
284
|
+
Fast.replace '(def $_ ... (send (send nil $_) \1))', ast do |node, captures|
|
285
|
+
attribute, object = captures
|
286
|
+
replace(node.location.expression, "delegate :#{attribute}, to: :#{object}")
|
287
|
+
end
|
294
288
|
```
|
295
289
|
|
296
|
-
##
|
290
|
+
## replace_file
|
297
291
|
|
298
292
|
Now let's imagine we have real files like `sample.rb` with the following code:
|
299
293
|
|
@@ -312,19 +306,49 @@ is being called.
|
|
312
306
|
|
313
307
|
```ruby
|
314
308
|
assignment = nil
|
315
|
-
Fast.replace_file('
|
316
|
-
|
317
|
-
|
318
|
-
|
319
|
-
|
320
|
-
|
321
|
-
|
322
|
-
|
323
|
-
}
|
324
|
-
)
|
309
|
+
Fast.replace_file('({ lvasgn lvar } message )','sample.rb') do |node, _|
|
310
|
+
if node.type == :lvasgn
|
311
|
+
assignment = node.children.last
|
312
|
+
remove(node.location.expression)
|
313
|
+
elsif node.type == :lvar
|
314
|
+
replace(node.location.expression, assignment.location.expression.source)
|
315
|
+
end
|
316
|
+
end
|
325
317
|
```
|
326
318
|
|
327
|
-
|
319
|
+
It will return an output of the new source code with the changes but not save
|
320
|
+
the file. You can use ()[#rewrite_file] if you're confident about the changes.
|
321
|
+
|
322
|
+
## capture_file
|
323
|
+
|
324
|
+
`Fast.capture_file` can be used to combine [capture](#capture) and file system.
|
325
|
+
|
326
|
+
```ruby
|
327
|
+
Fast.capture_file("$(casgn)", "lib/fast/version.rb") # => s(:casgn, nil, :VERSION, s(:str, "0.1.3"))
|
328
|
+
Fast.capture_file("(casgn nil _ (str $_))", "lib/fast/version.rb") # => "0.1.3"
|
329
|
+
```
|
330
|
+
|
331
|
+
## capture_all
|
332
|
+
|
333
|
+
`Fast.capture_all` can be used to combine [capture_file](#capture_file) from multiple sources:
|
334
|
+
|
335
|
+
```ruby
|
336
|
+
Fast.capture_all("(casgn nil $_)") # => { "./lib/fast/version.rb"=>:VERSION, "./lib/fast.rb"=>[:LITERAL, :TOKENIZER], ...}
|
337
|
+
```
|
338
|
+
|
339
|
+
The second parameter can also be passed with to filter specific folders:
|
340
|
+
|
341
|
+
```ruby
|
342
|
+
Fast.capture_all("(casgn nil $_)", "lib/fast") # => {"lib/fast/shortcut.rb"=>:LOOKUP_FAST_FILES_DIRECTORIES, "lib/fast/version.rb"=>:VERSION}
|
343
|
+
```
|
344
|
+
|
345
|
+
|
346
|
+
## rewrite_file
|
347
|
+
|
348
|
+
`Fast.rewrite_file` works exactly as the `replace` but it will override the file
|
349
|
+
from the input.
|
350
|
+
|
351
|
+
## ast_from_file
|
328
352
|
|
329
353
|
This method parses the code and load into a AST representation.
|
330
354
|
|
@@ -332,7 +356,7 @@ This method parses the code and load into a AST representation.
|
|
332
356
|
Fast.ast_from_file('sample.rb')
|
333
357
|
```
|
334
358
|
|
335
|
-
##
|
359
|
+
## search_file
|
336
360
|
|
337
361
|
You can use `search_file` and pass the path for search for expressions inside
|
338
362
|
files.
|
@@ -343,7 +367,7 @@ Fast.search_file(expression, 'file.rb')
|
|
343
367
|
|
344
368
|
It's simple combination of `Fast.ast_from_file` with `Fast.search`.
|
345
369
|
|
346
|
-
##
|
370
|
+
## ruby_files_from
|
347
371
|
|
348
372
|
You'll be probably looking for multiple ruby files, then this method fetches
|
349
373
|
all internal `.rb` files
|
@@ -352,3 +376,22 @@ all internal `.rb` files
|
|
352
376
|
Fast.ruby_files_from(['lib']) # => ["lib/fast.rb"]
|
353
377
|
```
|
354
378
|
|
379
|
+
## search_all
|
380
|
+
|
381
|
+
Combines the [search_file](#search_file) with [ruby_files_from](#ruby_files_from)
|
382
|
+
multiple locations and returns tuples with files and results.
|
383
|
+
|
384
|
+
```ruby
|
385
|
+
Fast.search_all("(def ast_from_file)")
|
386
|
+
=> {"./lib/fast.rb"=>[s(:def, :ast_from_file,
|
387
|
+
s(:args,
|
388
|
+
s(:arg, :file)),
|
389
|
+
s(:begin,
|
390
|
+
```
|
391
|
+
|
392
|
+
You can also override the second param and pass target files or folders:
|
393
|
+
|
394
|
+
```ruby
|
395
|
+
Fast.search_all("(def _)", '../other-folder')
|
396
|
+
```
|
397
|
+
|
data/docs/similarity_tutorial.md
CHANGED
@@ -53,7 +53,7 @@ observe some specific node types and try to group the similarities
|
|
53
53
|
using the pattern generated.
|
54
54
|
|
55
55
|
```ruby
|
56
|
-
Fast.search_file('lib/fast.rb'
|
56
|
+
Fast.search_file('class', 'lib/fast.rb')
|
57
57
|
```
|
58
58
|
Capturing the constant name and filtering only for symbols is easy and we can
|
59
59
|
see that we have a few classes defined in the the same file.
|
data/docs/syntax.md
CHANGED
@@ -251,56 +251,8 @@ def duplicate(value)
|
|
251
251
|
# example.rb:
|
252
252
|
duplicate
|
253
253
|
```
|
254
|
-
One extra method name was printed because of `$` is capturing the element.
|
255
|
-
|
256
|
-
Let's use the `--pry` for inspecting the results.
|
257
|
-
|
258
|
-
$ fast '(def $_)' example.rb --pry
|
259
|
-
|
260
|
-
It will open pry with access to `result` as the first result and
|
261
|
-
`results` with all matching results.
|
262
|
-
|
263
|
-
```
|
264
|
-
From: /Users/jonatasdp/.rbenv/versions/2.5.1/lib/ruby/gems/2.5.0/gems/ffast-0.0.2/bin/fast @ line 60 :
|
265
|
-
|
266
|
-
55:
|
267
|
-
56: results.each do |result|
|
268
|
-
57: next if result.nil? || result == []
|
269
|
-
58: if pry
|
270
|
-
59: require 'pry'
|
271
|
-
=> 60: binding.pry # rubocop:disable Lint/Debugger
|
272
|
-
61: else
|
273
|
-
62: Fast.report(result, file: file, show_sexp: show_sexp)
|
274
|
-
63: end
|
275
|
-
64: end
|
276
|
-
65: end
|
277
|
-
```
|
278
254
|
|
279
|
-
|
280
|
-
captures.
|
281
|
-
|
282
|
-
```ruby
|
283
|
-
[1] pry(main)> results
|
284
|
-
=> [s(:def, :magic,
|
285
|
-
s(:args),
|
286
|
-
s(:send, nil, :rand,
|
287
|
-
s(:const, nil, :ANSWER))),
|
288
|
-
:magic,
|
289
|
-
s(:def, :duplicate,
|
290
|
-
s(:args,
|
291
|
-
s(:arg, :value)),
|
292
|
-
s(:send,
|
293
|
-
s(:lvar, :value), :*,
|
294
|
-
s(:int, 2))),
|
295
|
-
:duplicate]
|
296
|
-
```
|
297
|
-
|
298
|
-
We can filter the captures to make it easy to analyze.
|
299
|
-
|
300
|
-
```ruby
|
301
|
-
[2] pry(main)> results.grep(Symbol)
|
302
|
-
=> [:magic, :duplicate]
|
303
|
-
```
|
255
|
+
One extra method name was printed because of `$` is capturing the element.
|
304
256
|
|
305
257
|
## `nil` matches exactly **nil**
|
306
258
|
|
@@ -368,7 +320,7 @@ ANSWER = 42
|
|
368
320
|
ANSWER
|
369
321
|
```
|
370
322
|
|
371
|
-
##
|
323
|
+
## `#` for custom methods
|
372
324
|
|
373
325
|
Custom methods can let you into ruby doman for more complicated rules. Let's say
|
374
326
|
we're looking for duplicated methods in the same class. We need to collect
|
@@ -403,10 +355,41 @@ more details of the node
|
|
403
355
|
```ruby
|
404
356
|
puts Fast.search_file('[ (def a) #debug ]', 'example.rb')
|
405
357
|
```
|
406
|
-
|
358
|
+
|
359
|
+
## `.` for instance methods
|
407
360
|
|
408
361
|
You can also call instance methods using `.<method-name>`.
|
409
362
|
|
410
363
|
Example `nil` is the same of calling `nil?` and you can also use `(int .odd?)`
|
411
364
|
to pick only odd integers. The `int` fragment can also be `int_type?`.
|
412
365
|
|
366
|
+
## `\1` for first previous capture
|
367
|
+
|
368
|
+
Imagine you're looking for a method that is just delegating something to
|
369
|
+
another method, like:
|
370
|
+
|
371
|
+
```ruby
|
372
|
+
def name
|
373
|
+
person.name
|
374
|
+
end
|
375
|
+
```
|
376
|
+
|
377
|
+
This can be represented as the following AST:
|
378
|
+
|
379
|
+
```
|
380
|
+
(def :name
|
381
|
+
(args)
|
382
|
+
(send
|
383
|
+
(send nil :person) :name))
|
384
|
+
```
|
385
|
+
|
386
|
+
Then, let's build a search for methods that calls an attribute with the same
|
387
|
+
name:
|
388
|
+
|
389
|
+
```ruby
|
390
|
+
Fast.match?('(def $_ ... (send (send nil _) \1))', ast) # => [:name]
|
391
|
+
```
|
392
|
+
|
393
|
+
With the method name being captured with `$_` it can be later referenced in the
|
394
|
+
expression with `\1`. If the search contains multiple captures, the `\2`,`\3`
|
395
|
+
can be used as the sequence of captures.
|
data/lib/fast.rb
CHANGED
@@ -89,9 +89,9 @@ module Fast
|
|
89
89
|
# Verify if a given AST matches with a specific pattern
|
90
90
|
# @return [Boolean] case matches ast with the current expression
|
91
91
|
# @example
|
92
|
-
# Fast.match?(Fast.ast("1")
|
93
|
-
def match?(
|
94
|
-
Matcher.new(
|
92
|
+
# Fast.match?("int", Fast.ast("1")) # => true
|
93
|
+
def match?(pattern, ast, *args)
|
94
|
+
Matcher.new(pattern, ast, *args).match?
|
95
95
|
end
|
96
96
|
|
97
97
|
# Replaces content based on a pattern.
|
@@ -104,10 +104,10 @@ module Fast
|
|
104
104
|
# end # => variable_renamed = 1
|
105
105
|
# @return [String] with the new source code after apply the replacement
|
106
106
|
# @see Fast::Rewriter
|
107
|
-
def replace(
|
107
|
+
def replace(pattern, ast, source = nil, &replacement)
|
108
108
|
buffer = Parser::Source::Buffer.new('replacement')
|
109
109
|
buffer.source = source || ast.loc.expression.source
|
110
|
-
to_replace = search(
|
110
|
+
to_replace = search(pattern, ast)
|
111
111
|
types = to_replace.grep(Parser::AST::Node).map(&:type).uniq
|
112
112
|
|
113
113
|
rewriter = Rewriter.new
|
@@ -122,21 +122,46 @@ module Fast
|
|
122
122
|
# @return [Array<Astrolabe::Node>] that matches the pattern
|
123
123
|
def search_file(pattern, file)
|
124
124
|
node = ast_from_file(file)
|
125
|
-
search
|
125
|
+
search pattern, node
|
126
|
+
end
|
127
|
+
|
128
|
+
# Search with pattern on a directory or multiple files
|
129
|
+
# @param [String] pattern
|
130
|
+
# @param [Array<String>] *locations where to search. Default is '.'
|
131
|
+
# @return [Hash<String,Array<Astrolabe::Node>>] with files and results
|
132
|
+
def search_all(pattern, locations = ['.'])
|
133
|
+
search_pattern = method(:search_file).curry.call(pattern)
|
134
|
+
group_results(search_pattern, locations)
|
135
|
+
end
|
136
|
+
|
137
|
+
# Capture with pattern on a directory or multiple files
|
138
|
+
# @param [String] pattern
|
139
|
+
# @param [Array<String>] locations where to search. Default is '.'
|
140
|
+
# @return [Hash<String,Object>] with files and captures
|
141
|
+
def capture_all(pattern, locations = ['.'])
|
142
|
+
capture_pattern = method(:capture_file).curry.call(pattern)
|
143
|
+
group_results(capture_pattern, locations)
|
144
|
+
end
|
145
|
+
|
146
|
+
def group_results(search_pattern, locations)
|
147
|
+
ruby_files_from(*locations)
|
148
|
+
.map { |f| { f => search_pattern.call(f) } }
|
149
|
+
.reject { |results| results.values.all?(&:empty?) }
|
150
|
+
.inject(&:merge!)
|
126
151
|
end
|
127
152
|
|
128
153
|
# Replaces the source of an {Fast#ast_from_file} with
|
129
154
|
# and the same source if the pattern does not match.
|
130
|
-
def replace_file(
|
155
|
+
def replace_file(pattern, file, &replacement)
|
131
156
|
ast = ast_from_file(file)
|
132
|
-
replace(
|
157
|
+
replace(pattern, ast, IO.read(file), &replacement)
|
133
158
|
end
|
134
159
|
|
135
160
|
# Combines #replace_file output overriding the file if the output is different
|
136
161
|
# from the original file content.
|
137
|
-
def rewrite_file(
|
162
|
+
def rewrite_file(pattern, file, &replacement)
|
138
163
|
previous_content = IO.read(file)
|
139
|
-
content = replace_file(
|
164
|
+
content = replace_file(pattern, file, &replacement)
|
140
165
|
File.open(file, 'w+') { |f| f.puts content } if content != previous_content
|
141
166
|
end
|
142
167
|
|
@@ -144,21 +169,20 @@ module Fast
|
|
144
169
|
# in the pattern to make it work.
|
145
170
|
# @return [Array<Object>] captured from the pattern matched in the file
|
146
171
|
def capture_file(pattern, file)
|
147
|
-
|
148
|
-
capture node, pattern
|
172
|
+
capture pattern, ast_from_file(file)
|
149
173
|
end
|
150
174
|
|
151
175
|
# Search recursively into a node and its children.
|
152
176
|
# If the node matches with the pattern it returns the node,
|
153
177
|
# otherwise it recursively collect possible children nodes
|
154
178
|
# @yield node and capture if block given
|
155
|
-
def search(
|
156
|
-
if (match = match?(
|
179
|
+
def search(pattern, node)
|
180
|
+
if (match = match?(pattern, node))
|
157
181
|
yield node, match if block_given?
|
158
182
|
match != true ? [node, match] : [node]
|
159
183
|
else
|
160
184
|
node.each_child_node
|
161
|
-
.flat_map { |
|
185
|
+
.flat_map { |child| search(pattern, child) }
|
162
186
|
.compact.flatten
|
163
187
|
end
|
164
188
|
end
|
@@ -166,16 +190,16 @@ module Fast
|
|
166
190
|
# Return only captures from a search
|
167
191
|
# @return [Array<Object>] with all captured elements.
|
168
192
|
# @return [Object] with single element when single capture.
|
169
|
-
def capture(
|
170
|
-
res =
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
res
|
193
|
+
def capture(pattern, node)
|
194
|
+
res = if (match = match?(pattern, node))
|
195
|
+
match == true ? node : match
|
196
|
+
else
|
197
|
+
node.each_child_node
|
198
|
+
.flat_map { |child| capture(pattern, child) }
|
199
|
+
.compact.flatten
|
200
|
+
end
|
201
|
+
res = [res] unless res.is_a?(Array)
|
202
|
+
res.one? ? res.first : res
|
179
203
|
end
|
180
204
|
|
181
205
|
def expression(string)
|
@@ -190,7 +214,7 @@ module Fast
|
|
190
214
|
#
|
191
215
|
# @example
|
192
216
|
# Fast.debug do
|
193
|
-
# Fast.match?(
|
217
|
+
# Fast.match?([:int, 1], s(:int, 1))
|
194
218
|
# end
|
195
219
|
# int == (int 1) # => true
|
196
220
|
# 1 == 1 # => true
|
@@ -268,7 +292,7 @@ module Fast
|
|
268
292
|
end
|
269
293
|
|
270
294
|
def match?(node)
|
271
|
-
Fast.match?(
|
295
|
+
Fast.match?(search, node)
|
272
296
|
end
|
273
297
|
|
274
298
|
# Generate methods for all affected types.
|
@@ -382,24 +406,24 @@ module Fast
|
|
382
406
|
end
|
383
407
|
|
384
408
|
def match?(node)
|
385
|
-
match_recursive(
|
409
|
+
match_recursive(valuate(token), node)
|
386
410
|
end
|
387
411
|
|
388
|
-
def match_recursive(
|
412
|
+
def match_recursive(expression, node)
|
389
413
|
case expression
|
390
414
|
when Proc then expression.call(node)
|
391
415
|
when Find then expression.match?(node)
|
392
|
-
when Symbol then compare_symbol_or_head(
|
416
|
+
when Symbol then compare_symbol_or_head(expression, node)
|
393
417
|
when Enumerable
|
394
418
|
expression.each_with_index.all? do |exp, i|
|
395
|
-
match_recursive(i.zero? ? node : node.children[i - 1]
|
419
|
+
match_recursive(exp, i.zero? ? node : node.children[i - 1])
|
396
420
|
end
|
397
421
|
else
|
398
422
|
node == expression
|
399
423
|
end
|
400
424
|
end
|
401
425
|
|
402
|
-
def compare_symbol_or_head(
|
426
|
+
def compare_symbol_or_head(expression, node)
|
403
427
|
case node
|
404
428
|
when Parser::AST::Node
|
405
429
|
node.type == expression.to_sym
|
@@ -410,13 +434,13 @@ module Fast
|
|
410
434
|
end
|
411
435
|
end
|
412
436
|
|
413
|
-
def debug_match_recursive(
|
414
|
-
match = original_match_recursive(
|
415
|
-
debug(
|
437
|
+
def debug_match_recursive(expression, node)
|
438
|
+
match = original_match_recursive(expression, node)
|
439
|
+
debug(expression, node, match)
|
416
440
|
match
|
417
441
|
end
|
418
442
|
|
419
|
-
def debug(
|
443
|
+
def debug(expression, node, match)
|
420
444
|
puts "#{expression} == #{node} # => #{match}"
|
421
445
|
end
|
422
446
|
|
@@ -490,7 +514,7 @@ module Fast
|
|
490
514
|
#
|
491
515
|
# @example check comparision of integers that will always return true
|
492
516
|
# ast = Fast.ast("1 == 1") => s(:send, s(:int, 1), :==, s(:int, 1))
|
493
|
-
# Fast.match?(
|
517
|
+
# Fast.match?("(send $(int _) == \1)", ast) # => [s(:int, 1)]
|
494
518
|
class FindWithCapture < Find
|
495
519
|
attr_writer :previous_captures
|
496
520
|
|
@@ -514,10 +538,10 @@ module Fast
|
|
514
538
|
# Use `%1` in the expression and the Matcher#prepare_arguments will
|
515
539
|
# interpolate the argument in the expression.
|
516
540
|
# @example interpolate the node value 1
|
517
|
-
# Fast.match?(Fast.ast("1"),
|
518
|
-
# Fast.match?(Fast.ast("1"),
|
541
|
+
# Fast.match?("(int %1)", Fast.ast("1"), 1) # => true
|
542
|
+
# Fast.match?("(int %1)", Fast.ast("1"), 2) # => false
|
519
543
|
# @example interpolate multiple arguments
|
520
|
-
# Fast.match?(
|
544
|
+
# Fast.match?("(%1 %2)", Fast.ast("1"), :int, 1) # => true
|
521
545
|
class FindFromArgument < Find
|
522
546
|
attr_writer :arguments
|
523
547
|
|
@@ -532,7 +556,7 @@ module Fast
|
|
532
556
|
def match?(node)
|
533
557
|
raise 'You must define arguments to match' unless @arguments
|
534
558
|
|
535
|
-
compare_symbol_or_head
|
559
|
+
compare_symbol_or_head @arguments[@capture_argument], node
|
536
560
|
end
|
537
561
|
|
538
562
|
def to_s
|
@@ -605,7 +629,7 @@ module Fast
|
|
605
629
|
# Fast.expression("{int float}")
|
606
630
|
class Any < Find
|
607
631
|
def match?(node)
|
608
|
-
token.any? { |expression| Fast.match?(
|
632
|
+
token.any? { |expression| Fast.match?(expression, node) }
|
609
633
|
end
|
610
634
|
|
611
635
|
def to_s
|
@@ -651,51 +675,51 @@ module Fast
|
|
651
675
|
# @example simple match
|
652
676
|
# ast = Fast.ast("a = 1")
|
653
677
|
# expression = Fast.expression("(lvasgn _ (int _))")
|
654
|
-
# Matcher.new(ast
|
678
|
+
# Matcher.new(expression, ast).match? # true
|
655
679
|
#
|
656
680
|
# @example simple capture
|
657
681
|
# ast = Fast.ast("a = 1")
|
658
682
|
# expression = Fast.expression("(lvasgn _ (int $_))")
|
659
|
-
# Matcher.new(ast
|
683
|
+
# Matcher.new(expression, ast).match? # => [1]
|
660
684
|
#
|
661
685
|
class Matcher
|
662
|
-
def initialize(
|
686
|
+
def initialize(pattern, ast, *args)
|
663
687
|
@ast = ast
|
664
|
-
@
|
665
|
-
|
666
|
-
|
667
|
-
|
668
|
-
|
688
|
+
@expression = if pattern.is_a?(String)
|
689
|
+
Fast.expression(pattern)
|
690
|
+
else
|
691
|
+
[*pattern].map(&Find.method(:new))
|
692
|
+
end
|
669
693
|
@captures = []
|
670
|
-
prepare_arguments(@
|
694
|
+
prepare_arguments(@expression, args) if args.any?
|
671
695
|
end
|
672
696
|
|
673
697
|
# @return [true] if the @param ast recursively matches with expression.
|
674
698
|
# @return #find_captures case matches
|
675
|
-
def match?(
|
676
|
-
head, *
|
699
|
+
def match?(expression = @expression, ast = @ast)
|
700
|
+
head, *tail_expression = expression
|
677
701
|
return false unless head.match?(ast)
|
678
|
-
return find_captures if
|
702
|
+
return find_captures if tail_expression.empty?
|
679
703
|
|
680
|
-
match_tail?(ast.children
|
704
|
+
match_tail?(tail_expression, ast.children)
|
681
705
|
end
|
682
706
|
|
683
707
|
# @return [true] if all children matches with tail
|
684
|
-
def match_tail?(
|
708
|
+
def match_tail?(tail, child)
|
685
709
|
tail.each_with_index.all? do |token, i|
|
686
710
|
prepare_token(token)
|
687
|
-
token.is_a?(Array) ? match?(child[i]
|
711
|
+
token.is_a?(Array) ? match?(token, child[i]) : token.match?(child[i])
|
688
712
|
end && find_captures
|
689
713
|
end
|
690
714
|
|
691
|
-
# Look recursively into @param
|
715
|
+
# Look recursively into @param expression to check if the expression is have
|
692
716
|
# captures.
|
693
717
|
# @return [true] if any sub expression have captures.
|
694
|
-
def captures?(
|
695
|
-
case
|
718
|
+
def captures?(expression = @expression)
|
719
|
+
case expression
|
696
720
|
when Capture then true
|
697
|
-
when Array then
|
698
|
-
when Find then captures?(
|
721
|
+
when Array then expression.any?(&method(:captures?))
|
722
|
+
when Find then captures?(expression.token)
|
699
723
|
end
|
700
724
|
end
|
701
725
|
|
@@ -705,13 +729,13 @@ module Fast
|
|
705
729
|
# @return [true] in case of no captures in the expression
|
706
730
|
# @see Fast::Capture
|
707
731
|
# @see Fast::FindFromArgument
|
708
|
-
def find_captures(
|
709
|
-
return true if
|
732
|
+
def find_captures(expression = @expression)
|
733
|
+
return true if expression == @expression && !captures?(expression)
|
710
734
|
|
711
|
-
case
|
712
|
-
when Capture then
|
713
|
-
when Array then
|
714
|
-
when Find then find_captures(
|
735
|
+
case expression
|
736
|
+
when Capture then expression.captures
|
737
|
+
when Array then expression.flat_map(&method(:find_captures)).compact
|
738
|
+
when Find then find_captures(expression.token)
|
715
739
|
end
|
716
740
|
end
|
717
741
|
|
data/lib/fast/cli.rb
CHANGED
@@ -35,19 +35,26 @@ module Fast
|
|
35
35
|
def report(result, show_sexp: false, file: nil, headless: false)
|
36
36
|
if file
|
37
37
|
line = result.loc.expression.line if result.is_a?(Parser::AST::Node)
|
38
|
-
puts(
|
38
|
+
puts(highlight("# #{file}:#{line}")) unless headless
|
39
39
|
end
|
40
|
-
puts
|
40
|
+
puts highlight(result, show_sexp: show_sexp)
|
41
41
|
end
|
42
42
|
|
43
43
|
# Command Line Interface for Fast
|
44
44
|
class Cli # rubocop:disable Metrics/ClassLength
|
45
45
|
attr_reader :pattern, :show_sexp, :pry, :from_code, :similar, :help
|
46
|
-
|
47
|
-
# rubocop:disable Metrics/MethodLength
|
48
|
-
# rubocop:disable Metrics/AbcSize
|
49
46
|
def initialize(args)
|
50
|
-
|
47
|
+
args = replace_args_with_shortcut(args) if args.first&.start_with?('.')
|
48
|
+
|
49
|
+
@pattern, *@files = args.reject { |arg| arg.start_with? '-' }
|
50
|
+
|
51
|
+
option_parser.parse! args
|
52
|
+
|
53
|
+
@files = [*@files].reject { |arg| arg.start_with?('-') }
|
54
|
+
end
|
55
|
+
|
56
|
+
def option_parser # rubocop:disable Metrics/MethodLength, Metrics/AbcSize
|
57
|
+
@option_parser ||= OptionParser.new do |opts| # rubocop:disable Metrics/BlockLength
|
51
58
|
opts.banner = 'Usage: fast expression <files> [options]'
|
52
59
|
opts.on('-d', '--debug', 'Debug fast engine') do
|
53
60
|
@debug = true
|
@@ -67,6 +74,7 @@ module Fast
|
|
67
74
|
|
68
75
|
opts.on('--pry', 'Jump into a pry session with results') do
|
69
76
|
@pry = true
|
77
|
+
require 'pry'
|
70
78
|
end
|
71
79
|
|
72
80
|
opts.on('-c', '--code', 'Create a pattern from code example') do
|
@@ -92,25 +100,17 @@ module Fast
|
|
92
100
|
@help = true
|
93
101
|
end
|
94
102
|
end
|
103
|
+
end
|
95
104
|
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
end
|
105
|
+
def replace_args_with_shortcut(args)
|
106
|
+
shortcut = find_shortcut args.first[1..-1]
|
107
|
+
if shortcut.single_run_with_block?
|
108
|
+
shortcut.run
|
109
|
+
exit
|
110
|
+
else
|
111
|
+
args.one? ? shortcut.args : shortcut.merge_args(args[1..-1])
|
104
112
|
end
|
105
|
-
|
106
|
-
@pattern, *@files = args.reject { |arg| arg.start_with? '-' }
|
107
|
-
|
108
|
-
@opt.parse! args
|
109
|
-
|
110
|
-
@files = [*@files].reject { |arg| arg.start_with?('-') }
|
111
113
|
end
|
112
|
-
# rubocop:enable Metrics/MethodLength
|
113
|
-
# rubocop:enable Metrics/AbcSize
|
114
114
|
|
115
115
|
# Run a new command line interface digesting the arguments
|
116
116
|
def self.run!(argv)
|
@@ -121,7 +121,7 @@ module Fast
|
|
121
121
|
# Show help or search for node patterns
|
122
122
|
def run!
|
123
123
|
if @help || @files.empty? && @pattern.nil?
|
124
|
-
puts
|
124
|
+
puts option_parser.help
|
125
125
|
else
|
126
126
|
search
|
127
127
|
end
|
@@ -136,43 +136,25 @@ module Fast
|
|
136
136
|
# If -d (debug option) is enabled, it will output details of each search.
|
137
137
|
# If capture option is enabled it will only print the captures, otherwise it
|
138
138
|
# prints all the results.
|
139
|
-
def
|
139
|
+
def search
|
140
140
|
if debug_mode?
|
141
|
-
Fast.debug {
|
141
|
+
Fast.debug { execute_search }
|
142
142
|
else
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
debug "Ops! An error occurred trying to search in #{expression.inspect} in #{file}", $ERROR_INFO, $ERROR_POSITION
|
147
|
-
[]
|
148
|
-
end
|
149
|
-
end
|
150
|
-
end
|
151
|
-
|
152
|
-
# Search for the {#expression} on all the {#files}.
|
153
|
-
# It {#report} results if no options are passed.
|
154
|
-
# It binds pry if option "--pry" is passed.
|
155
|
-
def search
|
156
|
-
files.each do |file|
|
157
|
-
results = [*search_file(file)]
|
158
|
-
|
159
|
-
next if results.empty?
|
160
|
-
|
161
|
-
results.each do |result|
|
162
|
-
if @pry
|
163
|
-
require 'pry'
|
164
|
-
binding.pry # rubocop:disable Lint/Debugger
|
165
|
-
else
|
143
|
+
execute_search do |file, results|
|
144
|
+
results.each do |result|
|
145
|
+
binding.pry if @pry # rubocop:disable Lint/Debugger
|
166
146
|
report(result, file)
|
167
147
|
end
|
168
148
|
end
|
169
149
|
end
|
170
150
|
end
|
171
151
|
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
152
|
+
def execute_search
|
153
|
+
method_name = @captures ? :capture_all : :search_all
|
154
|
+
(Fast.public_send(method_name, expression, @files) || []).each do |file, results|
|
155
|
+
results = [results] unless results.is_a?(Array)
|
156
|
+
yield file, results
|
157
|
+
end
|
176
158
|
end
|
177
159
|
|
178
160
|
# @return [Boolean] true when "-d" or "--debug" option is passed
|
data/lib/fast/experiment.rb
CHANGED
@@ -287,7 +287,7 @@ module Fast
|
|
287
287
|
|
288
288
|
# @return [Array<Astrolabe::Node>]
|
289
289
|
def search_cases
|
290
|
-
Fast.search(
|
290
|
+
Fast.search(experiment.expression, @ast) || []
|
291
291
|
end
|
292
292
|
|
293
293
|
# rubocop:disable Metrics/AbcSize, Metrics/MethodLength
|
@@ -297,7 +297,7 @@ module Fast
|
|
297
297
|
# @return [void]
|
298
298
|
def partial_replace(*indices)
|
299
299
|
replacement = experiment.replacement
|
300
|
-
new_content = Fast.replace_file
|
300
|
+
new_content = Fast.replace_file experiment.expression, @file do |node, *captures|
|
301
301
|
if indices.nil? || indices.empty? || indices.include?(match_index)
|
302
302
|
if replacement.parameters.length == 1
|
303
303
|
instance_exec node, &replacement
|
data/lib/fast/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: ffast
|
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
|
- Jônatas Davi Paganini
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2019-10-
|
11
|
+
date: 2019-10-28 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: astrolabe
|