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
-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
- Use --validate-pattern PATTERN to check if a pattern is syntactically correct.
- Use .gains to show search efficiency statistics.
.gains¶
The .gains command allows you to track and visualize the efficiency of your code searches. It measures "bytes searched" (the total size of files scanned) versus "bytes reported" (the actual results returned).
$ fast .gains
This is particularly useful when using Fast with LLMs and Agents, as it quantifies the reduction in context data that the agent had to process.
You can also filter by category:
$ fast .gains cli # Show only CLI search history
$ fast .gains mcp # Show only MCP (Agent) search history
See Fast Gains (Efficiency Tracking) for more details.
--validate-pattern¶
This option allows you to verify if an AST pattern is valid without running a search. It checks for balanced parentheses, brackets, and braces, as well as valid token types.
$ fast --validate-pattern "(def process (args) ...)"
# Pattern is valid.
$ fast --validate-pattern "(def {process)"
# Invalid pattern: Unclosed nesting: expected }, )
--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.
Print library version.¶
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'
$ 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.