ffast 0.2.0 → 0.2.2

Sign up to get free protection for your applications and to get access to all the features.
data/docs/git.md ADDED
@@ -0,0 +1,115 @@
1
+
2
+ You can overload the AST node with extra methods to get information from Git.
3
+
4
+ Let's start with some basic setup to reuse in the next examples:
5
+
6
+ ## Git require
7
+
8
+ By default, this extension is not loaded in the fast environment, so you should
9
+ require it.
10
+
11
+ ```ruby
12
+ require 'fast/git'
13
+ ```
14
+
15
+
16
+ Then it will work with any AST node.
17
+
18
+ ```ruby
19
+ ast = Fast.ast_from_file('lib/fast.rb')
20
+ ```
21
+
22
+ ## Log
23
+
24
+ First commit from git:
25
+
26
+ ```ruby
27
+ ast.git_log.first.author.name # => "Jonatas Davi Paganini"
28
+ ```
29
+
30
+ It uses [ruby-git](https://github.com/ruby-git/ruby-git#examples) gem, so all
31
+ methods are available:
32
+
33
+ ```ruby
34
+ ast.git_log.since(Time.mktime(2019)).entries.map(&:message)
35
+ ```
36
+
37
+ Counting commits per year:
38
+
39
+ ```ruby
40
+ ast.git_log.entries.group_by{|t|t.date.year}.transform_values(&:size)
41
+ # => {2020=>4, 2019=>22, 2018=>4}
42
+ ```
43
+
44
+ Counting commits per contributor:
45
+
46
+ ```ruby
47
+ ast.git_log.entries.group_by{|t|t.author.name}.transform_values(&:size)
48
+ # => {"Jônatas Davi Paganini"=>29, ...}
49
+ ```
50
+
51
+ Selecting last commit message:
52
+
53
+ ```ruby
54
+ ast.last_commit.message # => "Add node extensions for extracting info from git (#21)"
55
+ ```
56
+
57
+ Remote git URL:
58
+
59
+ ```ruby
60
+ ast.remote_url # => "git@github.com:jonatas/fast.git"
61
+ ast.project_url # => "https://github.com/jonatas/fast"
62
+ ```
63
+
64
+ The `sha` from last commit:
65
+
66
+ ```ruby
67
+ ast.sha # => "cd1c036b55ec1d41e5769ad73b282dd6429a90a6"
68
+ ```
69
+
70
+ Pick a link from the files to master version:
71
+
72
+ ```ruby
73
+ ast.link # => "https://github.com/jonatas/fast/blob/master/lib/fast.rb#L3-L776"
74
+ ```
75
+
76
+ Getting permalink from current commit:
77
+
78
+ ```ruby
79
+ ast.permalink # => "https://github.com/jonatas/fast/blob/cd1c036b55ec1d41e5769ad73b282dd6429a90a6/lib/fast.rb#L3-L776"
80
+ ```
81
+
82
+ ## Markdown link
83
+
84
+ Let's say you'd like to capture a list of class names that inherits the `Find`
85
+ class:
86
+
87
+ ```ruby
88
+ puts ast.capture("(class $(const nil _) (const nil Find)").map(&:md_link).join("\n* ")
89
+ ```
90
+
91
+ It will output the following links:
92
+
93
+ * [FindString](https://github.com/jonatas/fast/blob/master/lib/fast.rb#L485)
94
+ * [MethodCall](https://github.com/jonatas/fast/blob/master/lib/fast.rb#L496)
95
+ * [InstanceMethodCall](https://github.com/jonatas/fast/blob/master/lib/fast.rb#L507)
96
+ * [FindWithCapture](https://github.com/jonatas/fast/blob/master/lib/fast.rb#L524)
97
+ * [FindFromArgument](https://github.com/jonatas/fast/blob/master/lib/fast.rb#L551)
98
+ * [Capture](https://github.com/jonatas/fast/blob/master/lib/fast.rb#L598)
99
+ * [Parent](https://github.com/jonatas/fast/blob/master/lib/fast.rb#L622)
100
+ * [Any](https://github.com/jonatas/fast/blob/master/lib/fast.rb#L636)
101
+ * [All](https://github.com/jonatas/fast/blob/master/lib/fast.rb#L647)
102
+ * [Not](https://github.com/jonatas/fast/blob/master/lib/fast.rb#L659)
103
+ * [Maybe](https://github.com/jonatas/fast/blob/master/lib/fast.rb#L667)
104
+
105
+ ## Permalink
106
+
107
+ If you need to get a permanent link to the code, use the `permalink` method:
108
+
109
+ ```ruby
110
+ ast.search("(class (const nil _) (const nil Find)").map(&:permalink)
111
+ # => ["https://github.com/jonatas/fast/blob/cd1c036b55ec1d41e5769ad73b282dd6429a90a6/lib/fast.rb#L524-L541",
112
+ # "https://github.com/jonatas/fast/blob/cd1c036b55ec1d41e5769ad73b282dd6429a90a6/lib/fast.rb#L551-L571", ...]
113
+ ```
114
+
115
+
data/docs/ideas.md CHANGED
@@ -21,16 +21,6 @@ run the code you have and not the code that is overloading the project.
21
21
  Easy pipe fast results to Neo4J. It would facilitate to explore more complex
22
22
  scenarios and combine data from other sources.
23
23
 
24
- ## Git adapter
25
-
26
- Add extra tags to nodes with information from Git.
27
-
28
- * Revision
29
- * Author
30
- * Date
31
-
32
- Tag every node with the proper author.
33
-
34
24
  ## Ast Diff
35
25
 
36
26
  Allow to compare and return a summary of differences between two trees.
data/docs/index.md CHANGED
@@ -1,5 +1,7 @@
1
1
  # Fast
2
2
 
3
+ <center>![](assets/logo-small.png)</center>
4
+
3
5
  Fast is a "Find AST" tool to help you search in the code abstract syntax tree.
4
6
 
5
7
  Ruby allow us to do the same thing in a few ways then it's hard to check
@@ -0,0 +1,253 @@
1
+ # SQL Support
2
+
3
+ Fast is partially supporting SQL syntax. Behind the scenes it parses SQL using
4
+ [pg_query](https://github.com/pganalyze/pg_query) and simplifies it to AST Nodes
5
+ using the same interface. It's using Postgresql parser behind the scenes,
6
+ but probably could be useful for other SQL similar diallects.
7
+
8
+
9
+ !!! info "fast auto detects SQL files in the command line"
10
+
11
+ By default, this module is not included into the main library.
12
+ Fast can auto-detect file extensions and choose the sql path in case the
13
+ file relates to sql.
14
+
15
+ Use `fast --sql` in case you want to force the usage of the SQL parser.
16
+
17
+ ```
18
+
19
+ ```
20
+
21
+
22
+ # Parsing a sql content
23
+
24
+ ```ruby
25
+ require 'fast/sql'
26
+ ast = Fast.parse_sql('select 1')
27
+ # => s(:select_stmt,
28
+ # s(:target_list,
29
+ # s(:res_target,
30
+ # s(:val,
31
+ # s(:a_const,
32
+ # s(:val,
33
+ # s(:integer,
34
+ # s(:ival, 1))))))))
35
+ ```
36
+
37
+ ## Why it's interesting to use AST for SQL?
38
+
39
+ Both SQL are available and do the same thing:
40
+ ```sql
41
+ select * from customers
42
+ ```
43
+ or
44
+ ```sql
45
+ table customers
46
+ ```
47
+
48
+ they have exactly the same objective but written down in very different syntax.
49
+
50
+ Give a try:
51
+
52
+ ```ruby
53
+ Fast.parse_sql("select * from customers") == Fast.parse_sql("table customers") # => true
54
+ ```
55
+
56
+ ## Match
57
+
58
+ Use `match?` with your node pattern to traverse the abstract syntax tree.
59
+
60
+ ```ruby
61
+ Fast.match?("(select_stmt ...)", ast) # => true
62
+ ```
63
+
64
+ Use `$` to capture elements from the AST:
65
+
66
+ ```ruby
67
+ Fast.match?("(select_stmt $...)", ast)
68
+ => [s(:target_list,
69
+ s(:res_target,
70
+ s(:val,
71
+ s(:a_const,
72
+ s(:val,
73
+ s(:integer,
74
+ s(:ival, 1)))))))]
75
+
76
+ ```
77
+
78
+ You can dig deeper into the AST specifying nodes:
79
+
80
+ ```ruby
81
+ Fast.match?("(select_stmt (target_list (res_target (val ($...)))))", ast)
82
+ # => [s(:a_const,
83
+ # s(:val,
84
+ # s(:integer,
85
+ # s(:ival, 1))))]
86
+ ```
87
+
88
+ And ignoring node types or values using `_`. Check all [syntax](/syntax) options.
89
+
90
+ ```ruby
91
+ Fast.match?("(select_stmt (_ (_ (val ($...)))))", ast)
92
+ # => [s(:a_const,
93
+ # s(:val,
94
+ # s(:integer,
95
+ # s(:ival, 1))))]
96
+ ```
97
+
98
+ ## Search directly from the AST
99
+
100
+ You can also search directly from nodes and keep digging:
101
+
102
+ ```ruby
103
+ ast = Fast.parse_sql('select 1');
104
+ ast.search('ival') # => [s(:ival, s(:ival, 1))]
105
+ ```
106
+
107
+ Use first to return the node directly:
108
+
109
+ ```ruby
110
+ ast.first('(ival (ival _))') #=> s(:ival, s(:ival, 1))
111
+ ```
112
+
113
+ Combine the `capture` method with `$`:
114
+
115
+ ```ruby
116
+ ast.capture('(ival (ival $_))') # => [1]
117
+ ```
118
+
119
+
120
+ # Examples
121
+
122
+ Let's dive into a more complex example capturing fields and from clause of a
123
+ condition. Let's start parsing the sql:
124
+
125
+ ## Capturing fields and where clause
126
+
127
+
128
+ ```ruby
129
+ ast = Fast.parse_sql('select name from customer')
130
+ # => s(:select_stmt,
131
+ # s(:target_list,
132
+ # s(:res_target,
133
+ # s(:val,
134
+ # s(:column_ref,
135
+ # s(:fields,
136
+ # s(:string,
137
+ # s(:str, "name"))))))),
138
+ # s(:from_clause,
139
+ # s(:range_var,
140
+ # s(:relname, "customer"),
141
+ # s(:inh, true),
142
+ # s(:relpersistence, "p"))))
143
+ ```
144
+
145
+ Now, let's build the expression to get the fields and from_clause.
146
+
147
+ ```ruby
148
+ cols_and_from = "
149
+ (select_stmt
150
+ (target_list (res_target (val (column_ref (fields $...)))))
151
+ (from_clause (range_var $(relname _))))
152
+ "
153
+ ```
154
+
155
+ Now, we can use `Fast.capture` or `Fast.match?` to extract the values from the
156
+ AST.
157
+
158
+ ```ruby
159
+ Fast.capture(cols_and_from, ast)
160
+ # => [s(:string,
161
+ # s(:str, "name")), s(:relname, "customer")]
162
+ ```
163
+
164
+ ## Search inside
165
+
166
+ ```ruby
167
+ relname = Fast.parse_sql('select name from customer').search('relname').first
168
+ # => s(:relname, "customer")
169
+ ```
170
+
171
+ Find the location of a node.
172
+
173
+ ```ruby
174
+ relname.location # => #<Parser::Source::Map:0x00007fd3bcb0b7f0
175
+ # @expression=#<Parser::Source::Range (sql) 17...25>,
176
+ # @node=s(:relname, "customer")>
177
+ ```
178
+
179
+ The location can be useful to allow you to do refactorings and find specific
180
+ delimitations of objects in the string.
181
+
182
+ The attribute `expression` gives access to the source range.
183
+
184
+ ```ruby
185
+ relname.location.expression
186
+ # => #<Parser::Source::Range (sql) 17...25>
187
+ ```
188
+
189
+ The `source_buffer` is shared and can be accessed through the expression.
190
+
191
+ ```ruby
192
+ relname.location.expression.source_buffer
193
+ # => #<Fast::SQL::SourceBuffer:0x00007fd3bc2a6420
194
+ # @name="(sql)",
195
+ # @source="select name from customer",
196
+ # @tokens=
197
+ # [<PgQuery::ScanToken: start: 0, end: 6, token: :SELECT, keyword_kind: :RESERVED_KEYWORD>,
198
+ # <PgQuery::ScanToken: start: 7, end: 11, token: :NAME_P, keyword_kind: :UNRESERVED_KEYWORD>,
199
+ # <PgQuery::ScanToken: start: 12, end: 16, token: :FROM, keyword_kind: :RESERVED_KEYWORD>,
200
+ # <PgQuery::ScanToken: start: 17, end: 25, token: :IDENT, keyword_kind: :NO_KEYWORD>]>
201
+ ```
202
+
203
+ The tokens are useful to find the proper node location during the build but
204
+ they're not available for all the nodes, so, it can be very handy as an extra
205
+ reference.
206
+
207
+ ## Replace
208
+
209
+ Replace fragments of your SQL based on AST can also be done with all the work
210
+ inherited from Parser::TreeRewriter components.
211
+
212
+ ```ruby
213
+ Fast.parse_sql('select 1').replace('ival', '2') # => "select 2"
214
+ ```
215
+
216
+ The previous example is a syntax sugar for the following code:
217
+
218
+ ```ruby
219
+ Fast.replace_sql('ival',
220
+ Fast.parse_sql('select 1'),
221
+ &->(node){ replace(node.location.expression, '2') }
222
+ ) # => "select 2"
223
+ ```
224
+
225
+ The last argument is a proc that runs on the [parser tree rewriter](https://www.rubydoc.info/gems/parser/Parser/TreeRewriter
226
+ ) scope.
227
+
228
+ Let's break down the previous code:
229
+
230
+ ```ruby
231
+ ast = Fast.parse_sql("select 1")
232
+ # => s(:select_stmt,
233
+ # s(:target_list,
234
+ # s(:res_target,
235
+ # s(:val,
236
+ # s(:a_const,
237
+ # s(:ival,
238
+ # s(:ival, 1)))))))
239
+ ```
240
+
241
+ The pattern is simply matching node type that is `ival` but it could be a complex expression
242
+ like `(val (a_const (val (ival (ival _)))))`.
243
+
244
+ Completing the example:
245
+
246
+ ```ruby
247
+ Fast.replace_sql("ival", ast, &-> (n) { replace(n.loc.expression, "3") })
248
+ # => "select 3"
249
+ ```
250
+
251
+ `loc` is a shortcut for `location` attribute.
252
+
253
+
data/docs/videos.md CHANGED
@@ -1,8 +1,8 @@
1
1
  # Videos
2
2
 
3
- - [Ruby Kaigi TakeOut 2020: Grepping Ruby code like a boss](https://www.youtube.com/watch?v=YzcYXB4L2so&amp;feature=youtu.be&amp;t=11855)
3
+ - [Ruby Kaigi TakeOut 2020: Grepping Ruby code like a boss](https://www.youtube.com/watch?v=YczrZQC9aP8&amp;feature=youtu.be&amp)
4
4
 
5
- <iframe width="560" height="315" src="https://www.youtube.com/embed/YzcYXB4L2so?start=11855" frameborder="0" allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
5
+ <iframe width="560" height="315" src="https://www.youtube.com/embed/YczrZQC9aP8?" frameborder="0" allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
6
6
 
7
7
  Also, similar livecoding session at [RubyConf Brazil 2019 (Portuguese)](https://www.eventials.com/locaweb/jonatas-paganini-live-coding-grepping-ruby-code-like-a-boss/#_=_).
8
8
 
@@ -0,0 +1,135 @@
1
+ # Fast walkthrough
2
+
3
+ !!! note "This is the main interactive tutorial we have on `fast`. If you're reading it on the web, please consider also try it in the command line: `fast .intro` in the terminal to get a rapid pace on reading and testing on your own computer."
4
+
5
+ The objective here is give you some insights about how to use `ffast` gem in the
6
+ command line.
7
+
8
+ Let's start finding the main `fast.rb` file for the fast library:
9
+
10
+ ```
11
+ $ gem which fast
12
+ ```
13
+
14
+ And now, let's combine the previous expression that returns the path to the file
15
+ and take a quick look into the methods `match?` in the file using a regular grep:
16
+
17
+ ```
18
+ $ grep "def match\?" $(gem which fast)
19
+ ```
20
+
21
+ Boring results, no? The code here is not easy to digest because we just see a
22
+ fragment of the code block that we want.
23
+ Let's make it a bit more advanced with `grep -rn` to file name and line number:
24
+
25
+ ```
26
+ $ grep -rn "def match\?" $(gem which fast)
27
+ ```
28
+
29
+ Still hard to understand the scope of the search.
30
+
31
+ That's why fast exists! Now, let's take a look on how a method like this looks
32
+ like from the AST perspective. Let's use `ruby-parse` for it:
33
+
34
+ ```
35
+ $ ruby-parse -e "def match?(node); end"
36
+ ```
37
+
38
+ Now, let's make the same search with `fast` node pattern:
39
+
40
+ ```
41
+ fast "(def match?)" $(gem which fast)
42
+ ```
43
+
44
+ Wow! in this case you got all the `match?` methods, but you'd like to go one level upper
45
+ and understand the classes that implements the method with a single node as
46
+ argument. Let's first use `^` to jump into the parent:
47
+
48
+ ```
49
+ fast "^(def match?)" $(gem which fast)
50
+ ```
51
+
52
+ As you can see it still prints some `match?` methods that are not the ones that
53
+ we want, so, let's add a filter by the argument node `(args (arg node))`:
54
+
55
+ ```
56
+ fast "(def match? (args (arg node)))" $(gem which fast)
57
+ ```
58
+
59
+ Now, it looks closer to have some understanding of the scope, filtering only
60
+ methods that have the name `match?` and receive `node` as a parameter.
61
+
62
+ Now, let's do something different and find all methods that receives a `node` as
63
+ an argument:
64
+
65
+ ```
66
+ fast "(def _ (args (arg node)))" $(gem which fast)
67
+ ```
68
+
69
+ Looks like almost all of them are the `match?` and we can also skip the `match?`
70
+ methods negating the expression prefixing with `!`:
71
+
72
+ ```
73
+ fast "(def !match? (args (arg node)))" $(gem which fast)
74
+ ```
75
+
76
+ Let's move on and learn more about node pattern with the RuboCop project:
77
+
78
+ ```
79
+ $ VISUAL=echo gem open rubocop
80
+ ```
81
+
82
+ RuboCop contains `def_node_matcher` and `def_node_search`. Let's make a search
83
+ for both method names wrapping the query with `{}` selector:
84
+
85
+ ```
86
+ fast "(send nil {def_node_matcher def_node_search})" $(VISUAL=echo gem open rubocop)
87
+ ```
88
+
89
+ As you can see, node pattern is widely adopted in the cops to target code.
90
+ Rubocop contains a few projects with dedicated cops that can help you learn
91
+ more.
92
+
93
+ ## How to automate refactor using AST
94
+
95
+ Moving towards to the code automation, the next step after finding some target code
96
+ is refactor and change the code behavior.
97
+
98
+ Let's imagine that we already found some code that we want to edit or remove. If
99
+ we get the AST we can also cherry-pick any fragment of the expression to be
100
+ replaced. As you can imagine, RuboCop also benefits from automatic refactoring
101
+ offering the `--autocorrect` option.
102
+
103
+ All the hardcore algorithms are in the [parser](https://github.com/whitequark/parser)
104
+ rewriter, but we can find a lot of great examples on RuboCop project searching
105
+ for the `autocorrect` method.
106
+
107
+ ```
108
+ fast "(def autocorrect)" $(VISUAL=echo gem open rubocop)
109
+ ```
110
+
111
+ Look that most part of the methods are just returning a lambda with a
112
+ corrector. Now, let's use the `--ast` to get familiar with the tree details for the
113
+ implementation:
114
+
115
+ ```
116
+ fast --ast "(def autocorrect)" $(VISUAL=echo gem open rubocop)/lib/rubocop/cop/style
117
+ ```
118
+
119
+ As we can see, we have a `(send (lvar corrector))` that is the interface that we
120
+ can get the most interesting calls to overwrite files:
121
+
122
+ ```
123
+ fast "(send (lvar corrector)" $(VISUAL=echo gem open rubocop)
124
+ ```
125
+
126
+
127
+ ## That is all for now!
128
+
129
+ I hope you enjoyed to learn by example searching with fast. If you like it,
130
+ please [star the project](https://github.com/jonatas/fast/)!
131
+
132
+ You can also build your own tutorials simply using markdown files like I did
133
+ here, you can find this tutorial [here](https://github.com/jonatas/fast/tree/master/docs/walkthrough.md).
134
+
135
+
data/fast.gemspec CHANGED
@@ -17,9 +17,27 @@ Gem::Specification.new do |spec|
17
17
  spec.license = 'MIT'
18
18
 
19
19
  spec.files = `git ls-files -z`.split("\x0").reject do |f|
20
- f.match(%r{^(test|spec|features)/})
20
+ f.match(%r{^(test|spec|features|docs\/(assets|stylesheets))/})
21
21
  end
22
22
 
23
+ spec.post_install_message = <<~THANKS
24
+
25
+ ==========================================================
26
+ Yay! Thanks for installing
27
+
28
+ ___ __ ___
29
+ |__ /\ /__` |
30
+ | /~~\ .__/ |
31
+
32
+ To interactive learn about the gem in the terminal use:
33
+
34
+ fast .intro
35
+
36
+ More docs at: https://jonatas.github.io/fast/
37
+ ==========================================================
38
+
39
+ THANKS
40
+
23
41
  spec.bindir = 'bin'
24
42
  spec.executables = %w[fast fast-experiment]
25
43
  spec.require_paths = %w[lib experiments]
@@ -28,18 +46,19 @@ Gem::Specification.new do |spec|
28
46
  spec.add_dependency 'coderay'
29
47
  spec.add_dependency 'parallel'
30
48
  spec.add_dependency 'parser'
49
+ spec.add_dependency 'pg_query'
31
50
 
32
51
  spec.add_development_dependency 'bundler'
52
+ spec.add_development_dependency 'git'
33
53
  spec.add_development_dependency 'guard'
34
54
  spec.add_development_dependency 'guard-livereload'
35
55
  spec.add_development_dependency 'guard-rspec'
36
56
  spec.add_development_dependency 'pry'
37
- spec.add_development_dependency 'git'
38
57
  spec.add_development_dependency 'rake'
39
- spec.add_development_dependency 'rspec', '~> 3.0'
40
- spec.add_development_dependency 'rspec-its', '~> 1.2'
58
+ spec.add_development_dependency 'rspec'
59
+ spec.add_development_dependency 'rspec-its'
41
60
  spec.add_development_dependency 'rubocop'
42
61
  spec.add_development_dependency 'rubocop-performance'
43
62
  spec.add_development_dependency 'rubocop-rspec'
44
- spec.add_development_dependency 'simplecov', '~> 0.10', '< 0.18'
63
+ spec.add_development_dependency 'simplecov'
45
64
  end