Module: Fast
- Defined in:
- lib/fast.rb,
lib/fast/cli.rb,
lib/fast/git.rb,
lib/fast/version.rb,
lib/fast/rewriter.rb,
lib/fast/shortcut.rb,
lib/fast/experiment.rb
Overview
Allow to replace code managing multiple replacements and combining replacements. Useful for large codebase refactor and multiple replacements in the same file.
Defined Under Namespace
Classes: All, Any, Builder, Capture, Cli, Experiment, ExperimentCombinations, ExperimentFile, ExpressionParser, Find, FindFromArgument, FindString, FindWithCapture, InstanceMethodCall, Matcher, Maybe, MethodCall, Node, Not, Parent, Rewriter, Shortcut
Constant Summary collapse
- LITERAL =
Literals are shortcuts allowed inside ExpressionParser
{ '...' => ->(node) { node&.children&.any? }, '_' => ->(node) { !node.nil? }, 'nil' => nil }.freeze
- TOKENIZER =
Allowed tokens in the node pattern domain
%r/ [\+\-\/\*\\!] # operators or negation | ===? # == or === | \d+\.\d* # decimals and floats | "[^"]+" # strings | _ # something not nil: match | \.{3} # a node with children: ... | \[|\] # square brackets `[` and `]` for all | \^ # node has children with | \? # maybe expression | [\d\w_]+[=\\!\?]? # method names or numbers | \(|\) # parens `(` and `)` for tuples | \{|\} # curly brackets `{` and `}` for any | \$ # capture | \#\w[\d\w_]+[\\!\?]? # custom method call | \.\w[\d\w_]+\? # instance method call | \\\d # find using captured expression | %\d # bind extra arguments to the expression /x.freeze
- VERSION =
'0.2.0'
- LOOKUP_FAST_FILES_DIRECTORIES =
Where to search for ‘Fastfile` archives?
-
Current directory that the command is being runned
-
Home folder
-
Using the ‘FAST_FILE_DIR` variable to set an extra folder
-
[Dir.pwd, ENV['HOME'], ENV['FAST_FILE_DIR']].freeze
Class Attribute Summary collapse
-
.debugging ⇒ Object
Returns the value of attribute debugging.
-
.experiments ⇒ Object
readonly
Returns the value of attribute experiments.
Class Method Summary collapse
-
.ast(content, buffer_name: '(string)') ⇒ Fast::Node
From the parsed content.
-
.ast_from_file(file) ⇒ Fast::Node
caches the content based on the filename.
-
.build_grouped_search(method_name, pattern, on_result) ⇒ Proc
Binding ‘pattern` argument from a given `method_name`.
- .builder_for(buffer_name) ⇒ Object
-
.capture(pattern, node) ⇒ Array<Object>
Only captures from a search.
-
.capture_all(pattern, locations = ['.'], parallel: true, on_result: nil) ⇒ Hash<String,Object>
Capture with pattern on a directory or multiple files.
-
.capture_file(pattern, file) ⇒ Array<Object>
Capture elements from searches in files.
-
.debug ⇒ Object
Utility function to inspect search details using debug block.
-
.experiment(name, &block) ⇒ Object
Fast.experiment is a shortcut to define new experiments and allow them to work together in experiment combinations.
- .expression(string) ⇒ Object
-
.expression_from(node) ⇒ String
Extracts a node pattern expression from a given node supressing identifiers and primitive types.
-
.fast_files ⇒ Array<String>
With existent Fastfiles from LOOKUP_FAST_FILES_DIRECTORIES.
-
.group_results(group_files, locations, parallel: true) ⇒ Hash[String, Array]
Compact grouped results by file allowing parallel processing.
-
.highlight(node, show_sexp: false, colorize: true) ⇒ Object
Highligh some source code based on the node.
-
.load_fast_files! ⇒ Object
Loads ‘Fastfiles` from Fast.fast_files list.
-
.match?(pattern, ast, *args) ⇒ Boolean
Verify if a given AST matches with a specific pattern.
-
.replace(pattern, ast, source = nil, &replacement) ⇒ String
Replaces content based on a pattern.
-
.replace_file(pattern, file, &replacement) ⇒ Object
Replaces the source of an Fast.ast_from_file with and the same source if the pattern does not match.
-
.report(result, show_link: false, show_sexp: false, file: nil, headless: false, bodyless: false, colorize: true) ⇒ Object
Combines Fast.highlight with files printing file name in the head with the source line.
-
.rewrite_file(pattern, file, &replacement) ⇒ Object
Combines #replace_file output overriding the file if the output is different from the original file content.
- .rewriter_for(pattern, ast, source = nil, &replacement) ⇒ Fast::Rewriter
-
.ruby_files_from(*files) ⇒ Array<String>
When the argument is a folder, it recursively fetches all ‘.rb` files from it.
-
.search(pattern, node, *args) { ... } ⇒ Object
Search recursively into a node and its children.
-
.search_all(pattern, locations = ['.'], parallel: true, on_result: nil) ⇒ Hash<String,Array<Fast::Node>>
Search with pattern on a directory or multiple files.
-
.search_file(pattern, file) ⇒ Array<Fast::Node>
Search with pattern directly on file.
-
.shortcut(identifier, *args, &block) ⇒ Object
Store predefined searches with default paths through shortcuts.
-
.shortcuts ⇒ Hash<String,Shortcut>
Stores shortcuts in a simple hash where the key is the identifier and the value is the object itself.
Class Attribute Details
.debugging ⇒ Object
Returns the value of attribute debugging.
266 267 268 |
# File 'lib/fast.rb', line 266 def debugging @debugging end |
.experiments ⇒ Object (readonly)
Returns the value of attribute experiments.
30 31 32 |
# File 'lib/fast/experiment.rb', line 30 def experiments @experiments end |
Class Method Details
.ast(content, buffer_name: '(string)') ⇒ Fast::Node
Returns from the parsed content.
137 138 139 140 141 |
# File 'lib/fast.rb', line 137 def ast(content, buffer_name: '(string)') buffer = Parser::Source::Buffer.new(buffer_name) buffer.source = content Parser::CurrentRuby.new(builder_for(buffer_name)).parse(buffer) end |
.ast_from_file(file) ⇒ Fast::Node
caches the content based on the filename.
153 154 155 156 |
# File 'lib/fast.rb', line 153 def ast_from_file(file) @cache ||= {} @cache[file] ||= ast(IO.read(file), buffer_name: file) end |
.build_grouped_search(method_name, pattern, on_result) ⇒ Proc
Returns binding ‘pattern` argument from a given `method_name`.
197 198 199 200 201 202 203 204 205 206 |
# File 'lib/fast.rb', line 197 def build_grouped_search(method_name, pattern, on_result) search_pattern = method(method_name).curry.call(pattern) proc do |file| results = search_pattern.call(file) next if results.nil? || results.empty? on_result&.(file, results) { file => results } end end |
.builder_for(buffer_name) ⇒ Object
143 144 145 146 147 |
# File 'lib/fast.rb', line 143 def builder_for(buffer_name) builder = Builder.new builder.buffer_name = buffer_name builder end |
.capture(pattern, node) ⇒ Array<Object>
Only captures from a search
252 253 254 255 256 257 258 259 260 |
# File 'lib/fast.rb', line 252 def capture(pattern, node) if (match = match?(pattern, node)) match == true ? node : match else node.each_child_node .flat_map { |child| capture(pattern, child) } .compact.flatten end end |
.capture_all(pattern, locations = ['.'], parallel: true, on_result: nil) ⇒ Hash<String,Object>
Capture with pattern on a directory or multiple files
188 189 190 191 |
# File 'lib/fast.rb', line 188 def capture_all(pattern, locations = ['.'], parallel: true, on_result: nil) group_results(build_grouped_search(:capture_file, pattern, on_result), locations, parallel: parallel) end |
.capture_file(pattern, file) ⇒ Array<Object>
Capture elements from searches in files. Keep in mind you need to use ‘$` in the pattern to make it work.
228 229 230 231 232 233 |
# File 'lib/fast.rb', line 228 def capture_file(pattern, file) node = ast_from_file(file) return [] unless node capture pattern, node end |
.debug ⇒ Object
Utility function to inspect search details using debug block.
It prints output of all matching cases.
int == (int 1) # => true
1 == 1 # => true
278 279 280 281 282 283 284 285 286 287 288 289 290 291 |
# File 'lib/fast.rb', line 278 def debug return yield if debugging self.debugging = true result = nil Find.class_eval do alias_method :original_match_recursive, :match_recursive alias_method :match_recursive, :debug_match_recursive result = yield alias_method :match_recursive, :original_match_recursive # rubocop:disable Lint/DuplicateMethods end self.debugging = false result end |
.experiment(name, &block) ⇒ Object
Fast.experiment is a shortcut to define new experiments and allow them to work together in experiment combinations.
The following experiment look into ‘spec` folder and try to remove `before` and `after` blocks on testing code. Sometimes they’re not effective and we can avoid the hard work of do it manually.
If the spec does not fail, it keeps the change.
25 26 27 28 |
# File 'lib/fast/experiment.rb', line 25 def experiment(name, &block) @experiments ||= {} @experiments[name] = Experiment.new(name, &block) end |
.expression(string) ⇒ Object
262 263 264 |
# File 'lib/fast.rb', line 262 def expression(string) ExpressionParser.new(string).parse end |
.expression_from(node) ⇒ String
Extracts a node pattern expression from a given node supressing identifiers and primitive types. Useful to index abstract patterns or similar code structure.
317 318 319 320 321 322 323 324 325 326 327 |
# File 'lib/fast.rb', line 317 def expression_from(node) case node when Parser::AST::Node children_expression = node.children.map(&method(:expression_from)).join(' ') "(#{node.type}#{" #{children_expression}" if node.children.any?})" when nil, 'nil' 'nil' when Symbol, String, Numeric '_' end end |
.fast_files ⇒ Array<String>
Returns with existent Fastfiles from LOOKUP_FAST_FILES_DIRECTORIES.
29 30 31 32 33 |
# File 'lib/fast/shortcut.rb', line 29 def fast_files @fast_files ||= LOOKUP_FAST_FILES_DIRECTORIES.compact .map { |dir| File.join(dir, 'Fastfile') } .select(&File.method(:exists?)) end |
.group_results(group_files, locations, parallel: true) ⇒ Hash[String, Array]
Compact grouped results by file allowing parallel processing. parallel or not. while it process several locations in parallel.
215 216 217 218 219 220 221 222 223 |
# File 'lib/fast.rb', line 215 def group_results(group_files, locations, parallel: true) files = ruby_files_from(*locations) if parallel require 'parallel' unless defined?(Parallel) Parallel.map(files, &group_files) else files.map(&group_files) end.compact.inject(&:merge!) end |
.highlight(node, show_sexp: false, colorize: true) ⇒ Object
Highligh some source code based on the node. Useful for printing code with syntax highlight.
19 20 21 22 23 24 25 26 27 28 29 |
# File 'lib/fast/cli.rb', line 19 def highlight(node, show_sexp: false, colorize: true) output = if node.respond_to?(:loc) && !show_sexp node.loc.expression.source else node end return output unless colorize CodeRay.scan(output, :ruby).term end |
.load_fast_files! ⇒ Object
Loads ‘Fastfiles` from fast_files list
36 37 38 |
# File 'lib/fast/shortcut.rb', line 36 def load_fast_files! fast_files.each(&method(:load)) end |
.match?(pattern, ast, *args) ⇒ Boolean
Verify if a given AST matches with a specific pattern
162 163 164 |
# File 'lib/fast.rb', line 162 def match?(pattern, ast, *args) Matcher.new(pattern, ast, *args).match? end |
.replace(pattern, ast, source = nil, &replacement) ⇒ String
Replaces content based on a pattern.
17 18 19 |
# File 'lib/fast/rewriter.rb', line 17 def replace(pattern, ast, source = nil, &replacement) rewriter_for(pattern, ast, source, &replacement).rewrite! end |
.replace_file(pattern, file, &replacement) ⇒ Object
Replaces the source of an ast_from_file with and the same source if the pattern does not match.
33 34 35 36 |
# File 'lib/fast/rewriter.rb', line 33 def replace_file(pattern, file, &replacement) ast = ast_from_file(file) replace(pattern, ast, IO.read(file), &replacement) end |
.report(result, show_link: false, show_sexp: false, file: nil, headless: false, bodyless: false, colorize: true) ⇒ Object
Combines highlight with files printing file name in the head with the source line.
39 40 41 42 43 44 45 46 47 48 49 |
# File 'lib/fast/cli.rb', line 39 def report(result, show_link: false, show_sexp: false, file: nil, headless: false, bodyless: false, colorize: true) # rubocop:disable Metrics/ParameterLists if file line = result.loc.expression.line if result.is_a?(Parser::AST::Node) if show_link puts(result.link) elsif !headless puts(highlight("# #{file}:#{line}", colorize: colorize)) end end puts(highlight(result, show_sexp: show_sexp, colorize: colorize)) unless bodyless end |
.rewrite_file(pattern, file, &replacement) ⇒ Object
Combines #replace_file output overriding the file if the output is different from the original file content.
40 41 42 43 44 |
# File 'lib/fast/rewriter.rb', line 40 def rewrite_file(pattern, file, &replacement) previous_content = IO.read(file) content = replace_file(pattern, file, &replacement) File.open(file, 'w+') { |f| f.puts content } if content != previous_content end |
.rewriter_for(pattern, ast, source = nil, &replacement) ⇒ Fast::Rewriter
22 23 24 25 26 27 28 29 |
# File 'lib/fast/rewriter.rb', line 22 def rewriter_for(pattern, ast, source = nil, &replacement) rewriter = Rewriter.new rewriter.source = source rewriter.ast = ast rewriter.search = pattern rewriter.replacement = replacement rewriter end |
.ruby_files_from(*files) ⇒ Array<String>
When the argument is a folder, it recursively fetches all ‘.rb` files from it.
296 297 298 299 300 301 302 303 304 305 306 |
# File 'lib/fast.rb', line 296 def ruby_files_from(*files) dir_filter = File.method(:directory?) directories = files.select(&dir_filter) if directories.any? files -= directories files |= directories.flat_map { |dir| Dir["#{dir}/**/*.rb"] } files.uniq! end files.reject(&dir_filter) end |
.search(pattern, node, *args) { ... } ⇒ Object
Search recursively into a node and its children. If the node matches with the pattern it returns the node, otherwise it recursively collect possible children nodes
239 240 241 242 243 244 245 246 247 248 |
# File 'lib/fast.rb', line 239 def search(pattern, node, *args) if (match = match?(pattern, node, *args)) yield node, match if block_given? match != true ? [node, match] : [node] else node.each_child_node .flat_map { |child| search(pattern, child, *args) } .compact.flatten end end |
.search_all(pattern, locations = ['.'], parallel: true, on_result: nil) ⇒ Hash<String,Array<Fast::Node>>
Search with pattern on a directory or multiple files
179 180 181 182 |
# File 'lib/fast.rb', line 179 def search_all(pattern, locations = ['.'], parallel: true, on_result: nil) group_results(build_grouped_search(:search_file, pattern, on_result), locations, parallel: parallel) end |
.search_file(pattern, file) ⇒ Array<Fast::Node>
Search with pattern directly on file
168 169 170 171 172 173 |
# File 'lib/fast.rb', line 168 def search_file(pattern, file) node = ast_from_file(file) return [] unless node search pattern, node end |
.shortcut(identifier, *args, &block) ⇒ Object
Store predefined searches with default paths through shortcuts. define your Fastfile in you root folder or
16 17 18 19 |
# File 'lib/fast/shortcut.rb', line 16 def shortcut(identifier, *args, &block) puts "identifier #{identifier.inspect} will be override" if shortcuts.key?(identifier) shortcuts[identifier] = Shortcut.new(*args, &block) end |
.shortcuts ⇒ Hash<String,Shortcut>
Stores shortcuts in a simple hash where the key is the identifier and the value is the object itself.
24 25 26 |
# File 'lib/fast/shortcut.rb', line 24 def shortcuts @shortcuts ||= {} end |