ffast 0.2.0 → 0.2.2
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 +59 -3
- data/README.md +227 -129
- data/bin/console +5 -0
- data/docs/experiments.md +2 -0
- data/docs/git.md +115 -0
- data/docs/ideas.md +0 -10
- data/docs/index.md +2 -0
- data/docs/sql-support.md +253 -0
- data/docs/videos.md +2 -2
- data/docs/walkthrough.md +135 -0
- data/fast.gemspec +24 -5
- data/lib/fast/cli.rb +78 -19
- data/lib/fast/shortcut.rb +8 -3
- data/lib/fast/sql/rewriter.rb +69 -0
- data/lib/fast/sql.rb +167 -0
- data/lib/fast/version.rb +1 -1
- data/lib/fast.rb +36 -7
- data/mkdocs.yml +10 -1
- metadata +58 -29
data/lib/fast/cli.rb
CHANGED
@@ -2,6 +2,7 @@
|
|
2
2
|
|
3
3
|
require 'fast'
|
4
4
|
require 'fast/version'
|
5
|
+
require 'fast/sql'
|
5
6
|
require 'coderay'
|
6
7
|
require 'optparse'
|
7
8
|
require 'ostruct'
|
@@ -16,16 +17,44 @@ module Fast
|
|
16
17
|
# Useful for printing code with syntax highlight.
|
17
18
|
# @param show_sexp [Boolean] prints node expression instead of code
|
18
19
|
# @param colorize [Boolean] skips `CodeRay` processing when false.
|
19
|
-
def highlight(node, show_sexp: false, colorize: true)
|
20
|
+
def highlight(node, show_sexp: false, colorize: true, sql: false)
|
20
21
|
output =
|
21
22
|
if node.respond_to?(:loc) && !show_sexp
|
22
|
-
node.
|
23
|
+
wrap_source_range(node).source
|
23
24
|
else
|
24
25
|
node
|
25
26
|
end
|
26
27
|
return output unless colorize
|
27
28
|
|
28
|
-
CodeRay.scan(output, :ruby).term
|
29
|
+
CodeRay.scan(output, sql ? :sql : :ruby).term
|
30
|
+
end
|
31
|
+
|
32
|
+
# Fixes initial spaces to print the line since the beginning
|
33
|
+
# and fixes end of the expression including heredoc strings.
|
34
|
+
def wrap_source_range(node)
|
35
|
+
expression = node.loc.expression
|
36
|
+
Parser::Source::Range.new(
|
37
|
+
expression.source_buffer,
|
38
|
+
first_position_from_expression(node),
|
39
|
+
last_position_from_expression(node) || expression.end_pos
|
40
|
+
)
|
41
|
+
end
|
42
|
+
|
43
|
+
# If a method call contains a heredoc, it should print the STR around it too.
|
44
|
+
def last_position_from_expression(node)
|
45
|
+
internal_heredoc = node.each_descendant(:str).select { |n| n.loc.respond_to?(:heredoc_end) }
|
46
|
+
internal_heredoc.map { |n| n.loc.heredoc_end.end_pos }.max if internal_heredoc.any?
|
47
|
+
end
|
48
|
+
|
49
|
+
# If a node is the first on it's line, print since the beginning of the line
|
50
|
+
# to show the proper whitespaces for identing the next lines of the code.
|
51
|
+
def first_position_from_expression(node)
|
52
|
+
expression = node.loc.expression
|
53
|
+
if node.parent && node.parent.loc.expression.line != expression.line
|
54
|
+
expression.begin_pos - expression.column
|
55
|
+
else
|
56
|
+
expression.begin_pos
|
57
|
+
end
|
29
58
|
end
|
30
59
|
|
31
60
|
# Combines {.highlight} with files printing file name in the head with the
|
@@ -35,12 +64,14 @@ module Fast
|
|
35
64
|
# @param file [String] Show the file name and result line before content
|
36
65
|
# @param headless [Boolean] Skip printing the file name and line before content
|
37
66
|
# @example
|
38
|
-
# Fast.
|
39
|
-
def report(result, show_link: false, show_sexp: false, file: nil, headless: false, bodyless: false, colorize: true) # rubocop:disable Metrics/ParameterLists
|
67
|
+
# Fast.report(result, file: 'file.rb')
|
68
|
+
def report(result, show_link: false, show_permalink: false, show_sexp: false, file: nil, headless: false, bodyless: false, colorize: true) # rubocop:disable Metrics/ParameterLists
|
40
69
|
if file
|
41
70
|
line = result.loc.expression.line if result.is_a?(Parser::AST::Node)
|
42
71
|
if show_link
|
43
72
|
puts(result.link)
|
73
|
+
elsif show_permalink
|
74
|
+
puts(result.permalink)
|
44
75
|
elsif !headless
|
45
76
|
puts(highlight("# #{file}:#{line}", colorize: colorize))
|
46
77
|
end
|
@@ -60,6 +91,9 @@ module Fast
|
|
60
91
|
option_parser.parse! args
|
61
92
|
|
62
93
|
@files = [*@files].reject { |arg| arg.start_with?('-') }
|
94
|
+
@sql ||= @files.any? && @files.all? { |file| file.end_with?('.sql') }
|
95
|
+
|
96
|
+
require 'fast/sql' if @sql
|
63
97
|
end
|
64
98
|
|
65
99
|
def option_parser # rubocop:disable Metrics/MethodLength, Metrics/AbcSize
|
@@ -73,15 +107,24 @@ module Fast
|
|
73
107
|
@show_sexp = true
|
74
108
|
end
|
75
109
|
|
76
|
-
opts.on('--link', 'Print link to repository URL
|
110
|
+
opts.on('--link', 'Print link to repository URL') do
|
77
111
|
require 'fast/git'
|
78
112
|
@show_link = true
|
79
113
|
end
|
80
114
|
|
115
|
+
opts.on('--permalink', 'Print permalink to repository URL') do
|
116
|
+
require 'fast/git'
|
117
|
+
@show_permalink = true
|
118
|
+
end
|
119
|
+
|
81
120
|
opts.on('-p', '--parallel', 'Paralelize search') do
|
82
121
|
@parallel = true
|
83
122
|
end
|
84
123
|
|
124
|
+
opts.on("--sql", "Use SQL instead of Ruby") do
|
125
|
+
@sql = true
|
126
|
+
end
|
127
|
+
|
85
128
|
opts.on('--captures', 'Print only captures of the patterns and skip node results') do
|
86
129
|
@captures = true
|
87
130
|
end
|
@@ -99,24 +142,18 @@ module Fast
|
|
99
142
|
require 'pry'
|
100
143
|
end
|
101
144
|
|
102
|
-
opts.on('-c', '--code', 'Create a pattern from code example') do
|
103
|
-
if @pattern
|
104
|
-
@from_code = true
|
105
|
-
@pattern = Fast.ast(@pattern).to_sexp
|
106
|
-
debug 'Expression from AST:', @pattern
|
107
|
-
end
|
108
|
-
end
|
109
|
-
|
110
145
|
opts.on('-s', '--similar', 'Search for similar code.') do
|
111
146
|
@similar = true
|
112
|
-
@pattern = Fast.expression_from(Fast.ast(@pattern))
|
113
|
-
debug "Looking for code similar to #{@pattern}"
|
114
147
|
end
|
115
148
|
|
116
149
|
opts.on('--no-color', 'Disable color output') do
|
117
150
|
@colorize = false
|
118
151
|
end
|
119
152
|
|
153
|
+
opts.on('--from-code', 'From code') do
|
154
|
+
@from_code = true
|
155
|
+
end
|
156
|
+
|
120
157
|
opts.on_tail('--version', 'Show version') do
|
121
158
|
puts Fast::VERSION
|
122
159
|
exit
|
@@ -151,6 +188,25 @@ module Fast
|
|
151
188
|
|
152
189
|
if @help || @files.empty? && @pattern.nil?
|
153
190
|
puts option_parser.help
|
191
|
+
return
|
192
|
+
end
|
193
|
+
|
194
|
+
if @similar
|
195
|
+
ast = Fast.public_send( @sql ? :parse_sql : :ast, @pattern)
|
196
|
+
@pattern = Fast.expression_from(ast)
|
197
|
+
debug "Search similar to #{@pattern}"
|
198
|
+
elsif @from_code
|
199
|
+
ast = Fast.public_send( @sql ? :parse_sql : :ast, @pattern)
|
200
|
+
@pattern = ast.to_sexp
|
201
|
+
if @sql
|
202
|
+
@pattern.gsub!(/\b-\b/,'_')
|
203
|
+
end
|
204
|
+
debug "Search from code to #{@pattern}"
|
205
|
+
end
|
206
|
+
|
207
|
+
if @files.empty?
|
208
|
+
ast ||= Fast.public_send( @sql ? :parse_sql : :ast, @pattern)
|
209
|
+
puts Fast.highlight(ast, show_sexp: @show_sexp, colorize: @colorize, sql: @sql)
|
154
210
|
else
|
155
211
|
search
|
156
212
|
end
|
@@ -181,7 +237,7 @@ module Fast
|
|
181
237
|
# @yieldparam [String, Array] with file and respective search results
|
182
238
|
def execute_search(&on_result)
|
183
239
|
Fast.public_send(search_method_name,
|
184
|
-
|
240
|
+
@pattern,
|
185
241
|
@files,
|
186
242
|
parallel: parallel?,
|
187
243
|
on_result: on_result)
|
@@ -212,6 +268,7 @@ module Fast
|
|
212
268
|
Fast.report(result,
|
213
269
|
file: file,
|
214
270
|
show_link: @show_link,
|
271
|
+
show_permalink: @show_permalink,
|
215
272
|
show_sexp: @show_sexp,
|
216
273
|
headless: @headless,
|
217
274
|
bodyless: @bodyless,
|
@@ -222,8 +279,10 @@ module Fast
|
|
222
279
|
# @param name [String]
|
223
280
|
# @return [Fast::Shortcut]
|
224
281
|
def find_shortcut(name)
|
225
|
-
|
226
|
-
|
282
|
+
unless defined? Fast::Shortcut
|
283
|
+
require 'fast/shortcut'
|
284
|
+
Fast.load_fast_files!
|
285
|
+
end
|
227
286
|
|
228
287
|
shortcut = Fast.shortcuts[name] || Fast.shortcuts[name.to_sym]
|
229
288
|
shortcut || exit_shortcut_not_found(name)
|
data/lib/fast/shortcut.rb
CHANGED
@@ -7,7 +7,12 @@ module Fast
|
|
7
7
|
# 1. Current directory that the command is being runned
|
8
8
|
# 2. Home folder
|
9
9
|
# 3. Using the `FAST_FILE_DIR` variable to set an extra folder
|
10
|
-
LOOKUP_FAST_FILES_DIRECTORIES = [
|
10
|
+
LOOKUP_FAST_FILES_DIRECTORIES = [
|
11
|
+
Dir.pwd,
|
12
|
+
ENV['HOME'],
|
13
|
+
ENV['FAST_FILE_DIR'],
|
14
|
+
File.join(File.dirname(__FILE__), '..', '..')
|
15
|
+
].compact.map(&File.method(:expand_path)).uniq.freeze
|
11
16
|
|
12
17
|
# Store predefined searches with default paths through shortcuts.
|
13
18
|
# define your Fastfile in you root folder or
|
@@ -29,7 +34,7 @@ module Fast
|
|
29
34
|
def fast_files
|
30
35
|
@fast_files ||= LOOKUP_FAST_FILES_DIRECTORIES.compact
|
31
36
|
.map { |dir| File.join(dir, 'Fastfile') }
|
32
|
-
.select(&File.method(:
|
37
|
+
.select(&File.method(:exist?))
|
33
38
|
end
|
34
39
|
|
35
40
|
# Loads `Fastfiles` from {.fast_files} list
|
@@ -60,7 +65,7 @@ module Fast
|
|
60
65
|
def merge_args(extra_args)
|
61
66
|
all_args = (@args + extra_args).uniq
|
62
67
|
options = all_args.select { |arg| arg.start_with? '-' }
|
63
|
-
files = extra_args.select(&File.method(:
|
68
|
+
files = extra_args.select(&File.method(:exist?))
|
64
69
|
command = (@args - options - files).first
|
65
70
|
|
66
71
|
[command, *options, *files]
|
@@ -0,0 +1,69 @@
|
|
1
|
+
module Fast
|
2
|
+
module SQL
|
3
|
+
class << self
|
4
|
+
# @see Fast::SQLRewriter
|
5
|
+
# @return string with the content updated in case the pattern matches.
|
6
|
+
def replace(pattern, ast, &replacement)
|
7
|
+
sql_rewriter_for(pattern, ast, &replacement).rewrite!
|
8
|
+
end
|
9
|
+
|
10
|
+
# @return [Fast::SQL::Rewriter]
|
11
|
+
# @see Fast::Rewriter
|
12
|
+
def sql_rewriter_for(pattern, ast, &replacement)
|
13
|
+
rewriter = Rewriter.new
|
14
|
+
rewriter.ast = ast
|
15
|
+
rewriter.search = pattern
|
16
|
+
rewriter.replacement = replacement
|
17
|
+
rewriter
|
18
|
+
end
|
19
|
+
|
20
|
+
# @return Fast::SQL::Node with the parsed content
|
21
|
+
def parse_file(file)
|
22
|
+
parse(IO.read(file), buffer_name: file)
|
23
|
+
end
|
24
|
+
|
25
|
+
# Replace a SQL file with the given pattern.
|
26
|
+
# Use a replacement code block to change the content.
|
27
|
+
# @return nil in case does not update the file
|
28
|
+
# @return true in case the file is updated
|
29
|
+
# @see Fast::SQL::Rewriter
|
30
|
+
def replace_file(pattern, file, &replacement)
|
31
|
+
original = IO.read(file)
|
32
|
+
ast = parse_file(file)
|
33
|
+
content = replace(pattern, ast, &replacement)
|
34
|
+
if content != original
|
35
|
+
File.open(file, 'w+') { |f| f.print content }
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
# Extends fast rewriter to support SQL
|
41
|
+
# @see Fast::Rewriter
|
42
|
+
class Rewriter < Fast::Rewriter
|
43
|
+
|
44
|
+
# @return [Array<Symbol>] with all types that matches
|
45
|
+
def types
|
46
|
+
ast.type
|
47
|
+
end
|
48
|
+
|
49
|
+
# Generate methods for all affected types.
|
50
|
+
# Note the strategy is different from parent class, it if matches the root node, it executes otherwise it search pattern on
|
51
|
+
# all matching elements.
|
52
|
+
# @see Fast.replace
|
53
|
+
def replace_on(*types)
|
54
|
+
types.map do |type|
|
55
|
+
self.instance_exec do
|
56
|
+
self.class.define_method :"on_#{ast.type}" do |node|
|
57
|
+
# SQL nodes are not being automatically invoked by the rewriter,
|
58
|
+
# so we need to match the root node and invoke on matching inner elements.
|
59
|
+
node.search(search).each_with_index do |node, i|
|
60
|
+
@match_index += 1
|
61
|
+
execute_replacement(node, nil)
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
data/lib/fast/sql.rb
ADDED
@@ -0,0 +1,167 @@
|
|
1
|
+
require 'pg_query'
|
2
|
+
require_relative 'sql/rewriter'
|
3
|
+
|
4
|
+
module Fast
|
5
|
+
|
6
|
+
module_function
|
7
|
+
|
8
|
+
# Shortcut to parse a sql file
|
9
|
+
# @example Fast.parse_sql_file('spec/fixtures/sql/select.sql')
|
10
|
+
# @return [Fast::Node] the AST representation of the sql statements from a file
|
11
|
+
def parse_sql_file(file)
|
12
|
+
SQL.parse_file(file)
|
13
|
+
end
|
14
|
+
|
15
|
+
# @return [Fast::SQLRewriter] which can be used to rewrite the SQL
|
16
|
+
# @see Fast::SQLRewriter
|
17
|
+
def sql_rewriter_for(pattern, ast, &replacement)
|
18
|
+
SQL.rewriter_for(pattern, ast, &replacement)
|
19
|
+
end
|
20
|
+
|
21
|
+
# @return string with the sql content updated in case the pattern matches.
|
22
|
+
# @see Fast::SQLRewriter
|
23
|
+
# @example
|
24
|
+
# Fast.replace_sql('ival', Fast.parse_sql('select 1'), &->(node){ replace(node.location.expression, '2') }) # => "select 2"
|
25
|
+
def replace_sql(pattern, ast, &replacement)
|
26
|
+
SQL.replace(pattern, ast, &replacement)
|
27
|
+
end
|
28
|
+
|
29
|
+
# @return string with the sql content updated in case the pattern matches.
|
30
|
+
def replace_sql_file(pattern, file, &replacement)
|
31
|
+
SQL.replace_file(pattern, file, &replacement)
|
32
|
+
end
|
33
|
+
|
34
|
+
# @return [Fast::Node] the AST representation of the sql statement
|
35
|
+
# @example
|
36
|
+
# ast = Fast.parse_sql("select 'hello AST'")
|
37
|
+
# => s(:select_stmt,
|
38
|
+
# s(:target_list,
|
39
|
+
# s(:res_target,
|
40
|
+
# s(:val,
|
41
|
+
# s(:a_const,
|
42
|
+
# s(:val,
|
43
|
+
# s(:string,
|
44
|
+
# s(:str, "hello AST"))))))))
|
45
|
+
# `s` represents a Fast::Node which is a subclass of Parser::AST::Node and
|
46
|
+
# has additional methods to access the tokens and location of the node.
|
47
|
+
# ast.search(:string).first.location.expression
|
48
|
+
# => #<Parser::Source::Range (sql) 7...18>
|
49
|
+
def parse_sql(statement, buffer_name: "(sql)")
|
50
|
+
SQL.parse(statement, buffer_name: buffer_name)
|
51
|
+
end
|
52
|
+
|
53
|
+
# This module contains methods to parse SQL statements and rewrite them.
|
54
|
+
# It uses PGQuery to parse the SQL statements.
|
55
|
+
# It uses Parser to rewrite the SQL statements.
|
56
|
+
# It uses Parser::Source::Map to map the AST nodes to the SQL tokens.
|
57
|
+
#
|
58
|
+
# @example
|
59
|
+
# Fast::SQL.parse("select 1")
|
60
|
+
# => s(:select_stmt, s(:target_list, ...
|
61
|
+
# @see Fast::SQL::Node
|
62
|
+
module SQL
|
63
|
+
# The SQL source buffer is a subclass of Parser::Source::Buffer
|
64
|
+
# which contains the tokens of the SQL statement.
|
65
|
+
# When you call `ast.location.expression` it will return a range
|
66
|
+
# which is mapped to the tokens.
|
67
|
+
# @example
|
68
|
+
# ast = Fast::SQL.parse("select 1")
|
69
|
+
# ast.location.expression # => #<Parser::Source::Range (sql) 0...9>
|
70
|
+
# ast.location.expression.source_buffer.tokens
|
71
|
+
# => [
|
72
|
+
# <PgQuery::ScanToken: start: 0, end: 6, token: :SELECT, keyword_kind: :RESERVED_KEYWORD>,
|
73
|
+
# <PgQuery::ScanToken: start: 7, end: 8, token: :ICONST, keyword_kind: :NO_KEYWORD>]
|
74
|
+
# @see Fast::SQL::Node
|
75
|
+
class SourceBuffer < Parser::Source::Buffer
|
76
|
+
def tokens
|
77
|
+
@tokens ||= PgQuery.scan(source).first.tokens
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
# The SQL node is an AST node with additional tokenization info
|
82
|
+
class Node < Fast::Node
|
83
|
+
|
84
|
+
def first(pattern)
|
85
|
+
search(pattern).first
|
86
|
+
end
|
87
|
+
|
88
|
+
def replace(pattern, with=nil, &replacement)
|
89
|
+
replacement ||= -> (n) { replace(n.loc.expression, with) }
|
90
|
+
if root?
|
91
|
+
SQL.replace(pattern, self, &replacement)
|
92
|
+
else
|
93
|
+
parent.replace(pattern, &replacement)
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
def token
|
98
|
+
tokens.find{|e|e.start == location.begin}
|
99
|
+
end
|
100
|
+
|
101
|
+
def tokens
|
102
|
+
location.expression.source_buffer.tokens
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
module_function
|
107
|
+
|
108
|
+
# Parses SQL statements Using PGQuery
|
109
|
+
# @see sql_to_h
|
110
|
+
def parse(statement, buffer_name: "(sql)")
|
111
|
+
return [] if statement.nil?
|
112
|
+
source_buffer = SQL::SourceBuffer.new(buffer_name, source: statement)
|
113
|
+
tree = PgQuery.parse(statement).tree
|
114
|
+
stmts = tree.stmts.map do |stmt|
|
115
|
+
v = clean_structure(stmt.stmt.to_h)
|
116
|
+
inner_stmt = statement[stmt.stmt_location, stmt.stmt_len]
|
117
|
+
first, *, last = source_buffer.tokens
|
118
|
+
from = stmt.stmt_location
|
119
|
+
to = from.zero? ? last.end : from + stmt.stmt_len
|
120
|
+
expression = Parser::Source::Range.new(source_buffer, from, to)
|
121
|
+
source_map = Parser::Source::Map.new(expression)
|
122
|
+
sql_tree_to_ast(v, source_buffer: source_buffer, source_map: source_map)
|
123
|
+
end.flatten
|
124
|
+
stmts.one? ? stmts.first : stmts
|
125
|
+
end
|
126
|
+
|
127
|
+
# Clean up the hash structure returned by PgQuery
|
128
|
+
# @arg [Hash] hash the hash representation of the sql statement
|
129
|
+
# @return [Hash] the hash representation of the sql statement
|
130
|
+
def clean_structure(stmt)
|
131
|
+
res_hash = stmt.map do |key, value|
|
132
|
+
value = clean_structure(value) if value.is_a?(Hash)
|
133
|
+
value = value.map(&Fast::SQL.method(:clean_structure)) if value.is_a?(Array)
|
134
|
+
value = nil if [{}, [], "", :SETOP_NONE, :LIMIT_OPTION_DEFAULT, false].include?(value)
|
135
|
+
key = key.to_s.tr('-','_').to_sym
|
136
|
+
[key, value]
|
137
|
+
end
|
138
|
+
res_hash.to_h.compact
|
139
|
+
end
|
140
|
+
|
141
|
+
# Transform a sql tree into an AST.
|
142
|
+
# Populates the location of the AST nodes with the source map.
|
143
|
+
# @arg [Hash] obj the hash representation of the sql statement
|
144
|
+
# @return [Array] the AST representation of the sql statement
|
145
|
+
def sql_tree_to_ast(obj, source_buffer: nil, source_map: nil)
|
146
|
+
recursive = -> (e) { sql_tree_to_ast(e, source_buffer: source_buffer, source_map: source_map.dup) }
|
147
|
+
case obj
|
148
|
+
when Array
|
149
|
+
obj.map(&recursive).flatten.compact
|
150
|
+
when Hash
|
151
|
+
if (start = obj.delete(:location))
|
152
|
+
if (token = source_buffer.tokens.find{|e|e.start == start})
|
153
|
+
expression = Parser::Source::Range.new(source_buffer, token.start, token.end)
|
154
|
+
source_map = Parser::Source::Map.new(expression)
|
155
|
+
end
|
156
|
+
end
|
157
|
+
obj.map do |key, value|
|
158
|
+
children = [*recursive.call(value)]
|
159
|
+
Node.new(key, children, location: source_map)
|
160
|
+
end.compact
|
161
|
+
else
|
162
|
+
obj
|
163
|
+
end
|
164
|
+
end
|
165
|
+
end
|
166
|
+
end
|
167
|
+
|
data/lib/fast/version.rb
CHANGED
data/lib/fast.rb
CHANGED
@@ -148,11 +148,22 @@ module Fast
|
|
148
148
|
|
149
149
|
# @return [Fast::Node] parsed from file content
|
150
150
|
# caches the content based on the filename.
|
151
|
+
# Also, it can parse SQL files.
|
151
152
|
# @example
|
152
153
|
# Fast.ast_from_file("example.rb") # => s(...)
|
153
154
|
def ast_from_file(file)
|
154
155
|
@cache ||= {}
|
155
|
-
@cache[file] ||=
|
156
|
+
@cache[file] ||=
|
157
|
+
begin
|
158
|
+
method =
|
159
|
+
if file.end_with?('.sql')
|
160
|
+
require_relative 'fast/sql' unless respond_to?(:parse_sql)
|
161
|
+
:parse_sql
|
162
|
+
else
|
163
|
+
:ast
|
164
|
+
end
|
165
|
+
Fast.public_send(method, IO.read(file), buffer_name: file)
|
166
|
+
end
|
156
167
|
end
|
157
168
|
|
158
169
|
# Verify if a given AST matches with a specific pattern
|
@@ -169,7 +180,12 @@ module Fast
|
|
169
180
|
node = ast_from_file(file)
|
170
181
|
return [] unless node
|
171
182
|
|
172
|
-
|
183
|
+
case node
|
184
|
+
when Array
|
185
|
+
node.map { |n| search(pattern, n) }.flatten.compact
|
186
|
+
else
|
187
|
+
search pattern, node
|
188
|
+
end
|
173
189
|
end
|
174
190
|
|
175
191
|
# Search with pattern on a directory or multiple files
|
@@ -228,8 +244,12 @@ module Fast
|
|
228
244
|
def capture_file(pattern, file)
|
229
245
|
node = ast_from_file(file)
|
230
246
|
return [] unless node
|
231
|
-
|
232
|
-
|
247
|
+
case node
|
248
|
+
when Array
|
249
|
+
node.map { |n| capture(pattern, n) }.flatten.compact
|
250
|
+
else
|
251
|
+
capture pattern, node
|
252
|
+
end
|
233
253
|
end
|
234
254
|
|
235
255
|
# Search recursively into a node and its children.
|
@@ -241,9 +261,14 @@ module Fast
|
|
241
261
|
yield node, match if block_given?
|
242
262
|
match != true ? [node, match] : [node]
|
243
263
|
else
|
244
|
-
node
|
245
|
-
|
246
|
-
.
|
264
|
+
case node
|
265
|
+
when Array
|
266
|
+
node.flat_map { |child| search(pattern, child, *args) }
|
267
|
+
else
|
268
|
+
node.each_child_node
|
269
|
+
.flat_map { |child| search(pattern, child, *args) }
|
270
|
+
.compact.flatten
|
271
|
+
end
|
247
272
|
end
|
248
273
|
end
|
249
274
|
|
@@ -435,6 +460,10 @@ module Fast
|
|
435
460
|
node.type == expression.to_sym
|
436
461
|
when String
|
437
462
|
node == expression.to_s
|
463
|
+
when TrueClass
|
464
|
+
expression == :true
|
465
|
+
when FalseClass
|
466
|
+
expression == :false
|
438
467
|
else
|
439
468
|
node == expression
|
440
469
|
end
|
data/mkdocs.yml
CHANGED
@@ -1,12 +1,18 @@
|
|
1
1
|
site_name: Fast
|
2
2
|
repo_url: https://github.com/jonatas/fast
|
3
3
|
edit_uri: edit/master/docs/
|
4
|
-
google_analytics: ['
|
4
|
+
google_analytics: ['G-YKZDZDNRG2', 'auto']
|
5
|
+
|
5
6
|
theme:
|
6
7
|
name: material
|
7
8
|
palette:
|
8
9
|
primary: indigo
|
9
10
|
accent: pink
|
11
|
+
logo: assets/logo.png
|
12
|
+
favicon: assets/favicon.png
|
13
|
+
extra_css:
|
14
|
+
- stylesheets/custom.css
|
15
|
+
|
10
16
|
markdown_extensions:
|
11
17
|
- admonition
|
12
18
|
- codehilite:
|
@@ -15,13 +21,16 @@ markdown_extensions:
|
|
15
21
|
permalink: true
|
16
22
|
nav:
|
17
23
|
- Introduction: index.md
|
24
|
+
- Walkthrough: walkthrough.md
|
18
25
|
- Syntax: syntax.md
|
19
26
|
- Command Line: command_line.md
|
20
27
|
- Experiments: experiments.md
|
21
28
|
- Shortcuts: shortcuts.md
|
29
|
+
- Git Integration: git.md
|
22
30
|
- Code Similarity: similarity_tutorial.md
|
23
31
|
- Pry Integration: pry-integration.md
|
24
32
|
- Editors' Integration: editors-integration.md
|
25
33
|
- Research: research.md
|
26
34
|
- Ideas: ideas.md
|
27
35
|
- Videos: videos.md
|
36
|
+
- SQL Support: sql-support.md
|