Skip to content

Command line

When you install the ffast gem, it will also create an executable named fast and you can use it to search and find code using the concept:

$ fast '(def match?)' lib/fast.rb
  • Use -d or --debug for enable debug mode.
  • Use --ast to output the AST instead of the original code
  • Use --pry to jump debugging the first result with pry
  • Use -c to search from code example
  • Use -s to search similar code
  • Use -p to or --parallel to use multi core search

--pry

$ fast '(block (send nil it))' spec --pry

And inside pry session, you can use result as the first result or results to use all occurrences found.

results.map{|e|e.children[0].children[2]}
# => [s(:str, "parses ... as Find"),
# s(:str, "parses $ as Capture"),
# s(:str, "parses quoted values as strings"),
# s(:str, "parses {} as Any"),
# s(:str, "parses [] as All"), ...]

Getting all it blocks without description:

$ fast '(block (send nil it (nil)) (args ) (!str)) ) )' spec
# spec/fast_spec.rb:166
it { expect(described_class).to be_match('(...)', s(:int, 1)) }
...

--debug

This option will print all matching details while validating each node.

$ echo 'object.method' > sample.rb
$ fast -d '(send (send nil _) _)' sample.rb

It will bring details of the expression compiled and each node being validated:

Expression: f[send] [#<Fast::Find:0x00007f8c53047158 @token="send">, #<Fast::Find:0x00007f8c530470e0 @token="nil">, #<Fast::Find:0x00007f8c53047090 @token="_">] f[_]
send == (send
  (send nil :object) :method) # => true
f[send] == (send
  (send nil :object) :method) # => true
send == (send nil :object) # => true
f[send] == (send nil :object) # => true
 ==  # => true
f[nil] ==  # => true
#<Proc:0x00007f8c53057af8@/Users/jonatasdp/.rbenv/versions/2.5.1/lib/ruby/gems/2.5.0/gems/ffast-0.0.2/lib/fast.rb:25 (lambda)> == object # => true
f[_] == object # => true
[#<Fast::Find:0x00007f8c53047158 @token="send">, #<Fast::Find:0x00007f8c530470e0 @token="nil">, #<Fast::Find:0x00007f8c53047090 @token="_">] == (send nil :object) # => true
#<Proc:0x00007f8c53057af8@/Users/jonatasdp/.rbenv/versions/2.5.1/lib/ruby/gems/2.5.0/gems/ffast-0.0.2/lib/fast.rb:25 (lambda)> == method # => true
f[_] == method # => true
# sample.rb:1
object.method

-s for similarity

Sometimes you want to search for some similar code like (send (send (send nil _) _) _) and we could simply say a.b.c.

The option -s build an expression from the code ignoring final values.

$ echo 'object.method' > sample.rb
$ fast -s 'a.b' sample.rb
# sample.rb:1
object.method

See also Code Similarity tutorial.

-c to search from code example

You can search for the exact expression with -c

$ fast -c 'object.method' sample.rb
# sample.rb:1
object.method

Combining with -d, in the header you can see the generated expression.

$ fast -d -c 'object.method' sample.rb | head -n 3

The generated expression from AST was:
(send
  (send nil :object) :method)

Fastfile

Fastfile will loaded when you start a pattern with a dot. It means the pattern will be a shortcut predefined on these Fastfiles.

It will make three attempts to load Fastfile defined in $PWD, $HOME or checking if the $FAST_FILE_DIR is configured.

You can define a Fastfile in any project with your custom shortcuts and easy check some code or run some task.

Shortcut examples

Create shortcuts with blocks enables introduce custom coding in the scope of the Fast module.

Let's say you'd like to show the version of your library. Your regular params in the command line will look like:

$ fast '(casgn nil VERSION)' lib/*/version.rb

It will output but the command is not very handy. In order to just say fast .version you can use the previous snippet in your Fastfile.

Fast.shortcut(:version, '(casgn nil VERSION)', 'lib/fast/version.rb')

And calling fast .version it will output something like this:

# lib/fast/version.rb:4
VERSION = '0.1.2'

We can also always override the files params passing some other target file like fast .version lib/other/file.rb and it will reuse the other arguments from command line but replace the target files.

Bumping a gem version

While releasing a new gem version, we always need to mechanical go through the lib/<your_gem>/version.rb and change the string value to bump the version of your library. It's pretty mechanical and here is an example that allow you to simple use fast .bump_version:

Fast.shortcut :bump_version do
  rewrite_file('lib/fast/version.rb', '(casgn nil VERSION (str _)') do |node|
    target = node.children.last.loc.expression
    pieces = target.source.split(".").map(&:to_i)
    pieces.reverse.each_with_index do |fragment,i|
      if fragment < 9
        pieces[-(i+1)] = fragment +1
        break
      else
        pieces[-(i+1)] = 0
      end
    end
    replace(target, "'#{pieces.join(".")}'")
  end
end

Note the shortcut scope

The shortcut calls rewrite_file from Fast scope as it use Fast.instance_exec for shortcuts that yields blocks.

Checking the version:

$ fast .version                                                                                                                                                                                                                            13:58:40
# lib/fast/version.rb:4
VERSION = '0.1.2'

Bumping the version:

$ fast .bump_version                                                                                                                                                                                                                       13:58:43

No output because we don't print anything. Checking version again:

$ fast .version                                                                                                                                                                                                                            13:58:54
# lib/fast/version.rb:4
VERSION = '0.1.3'

And now a fancy shortcut to report the other shortcuts :)

Fast.shortcut :shortcuts do
  report(shortcuts.keys)
end

Or we can make it a bit more friendly and also use Fast to process the shortcut positions and pick the comment that each shortcut have in the previous line:

# List all shortcut with comments
Fast.shortcut :shortcuts do
  fast_files.each do |file|
    lines = File.readlines(file).map{|line|line.chomp.gsub(/\s*#/,'').strip}
    result = capture_file('(send ... shortcut $(sym _))', file)
    result = [result] unless result.is_a?Array
    result.each do |capture|
      target = capture.loc.expression
      puts "fast .#{target.source[1..-1].ljust(30)} # #{lines[target.line-2]}"
    end
  end
end

And it will be printing all loaded shortcuts with comments:

$ fast .shortcuts
fast .version                        # Let's say you'd like to show the version that is over the version file
fast .parser                         # Simple shortcut that I used often to show how the expression parser works
fast .bump_version                   # Use `fast .bump_version` to rewrite the version file
fast .shortcuts                      # List all shortcut with comments

You can find more examples in the Fastfile.