ffast 0.1.3 → 0.1.4
Sign up to get free protection for your applications and to get access to all the features.
- 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
|