How to write a Ruby command line app that supports tab completion?
Ah, it seems the standard library is my friend after all. What I was looking for is the Readline library.
Doc and examples here: http://www.ruby-doc.org/stdlib-1.9.3/libdoc/readline/rdoc/Readline.html
In particular, this is a good example from that page to show how completion works:
require 'readline'
LIST = [
'search', 'download', 'open',
'help', 'history', 'quit',
'url', 'next', 'clear',
'prev', 'past'
].sort
comp = proc { |s| LIST.grep(/^#{Regexp.escape(s)}/) }
Readline.completion_append_character = " "
Readline.completion_proc = comp
while line = Readline.readline('> ', true)
p line
end
NOTE: The proc receives only the last word entered. If you want the whole line typed so far (because you want to do context-specific completion), add the following line to the above code:
Readline.completer_word_break_characters = "" #Pass whole line to proc each time
(This is by default set to a list of characters that represent word boundaries and causes only the last word to be passed into your proc).
When using `gets` in Ruby for input, is there any way to get command history?
I started looking into tab completion and found this post which recommends using readline
from stdlib.
I discovered that it also supports command history.
Capistrano 3 execute arbitrary command on remote server
I suggest to write your own little rake task to do it. Use readline
gem
First of all thanks to follow materials:
https://thoughtbot.com/blog/tab-completion-in-gnu-readline-ruby-edition
How to write a Ruby command line app that supports tab completion?
desc "Remote console"
task :console do
require 'readline'
# https://thoughtbot.com/blog/tab-completion-in-gnu-readline-ruby-edition
host_args = (ENV['HOSTS'] || '').split(',').map { |r| r.to_sym }
role_args = (ENV['ROLES'] || '').split(',').map { |r| r.to_sym }
LIST = `ls /usr/bin`.split("\n").sort + `ls /bin`.split("\n").sort
comp = proc { |s| LIST.grep(/^#{Regexp.escape(s)}/) }
Readline.completion_append_character = " "
Readline.completion_proc = comp
while line = Readline.readline('cap> ', true)
begin
next if line.strip.empty?
exec_cmd(line, host_args, role_args)
rescue StandardError => e
puts e
puts e.backtrace
end
end
end
def exec_cmd(line, host_args, role_args)
line = "RAILS_ENV=#{fetch(:stage)} #{line}" if fetch(:stage)
cmd = "bash -lc '#{line}'"
puts "Final command: #{cmd}"
if host_args.any?
on hosts host_args do
execute cmd
end
elsif role_args.any?
on roles role_args do
execute cmd
end
else
on roles :all do
execute cmd
end
end
end
And do what you want with it, cheers! =))
Creating interactive ruby console application
What you want is a REPL – Read → Evaluate → Print Loop
.
IRB, for example, implements a REPL for the Ruby language.
Here's a very simple implementation of your application's REPL:
loop do
Application::Console.prompt.display
input = gets.chomp
command, *params = input.split /\s/
case command
when /\Ahelp\z/i
puts Application::Console.help_text
when /\Aopen\z/i
Application::Task.open params.first
when /\Ado\z/i
Application::Action.perform *params
else puts 'Invalid command'
end
end
\A
and \z
match the start of the string and the end of the string, respectively.
Auto-complete command line arguments
This is an example of BASH's smart completion. A basic description is here, a guide to writing your own extensions is here and another (Debian-based) guide is here. And here's a fuller featured introduction to the complete
command (the command that facilitates this behaviour).
Which testing technology to use for a command-line Ruby app?
Perhaps it might help to address HOW to write tests. There are lots of test frameworks, and lots of philosophies of how we should write tests but I try to keep it simple. I generally start with these:
- Test to see I got back nil or an object first.
- Test to see if the object is the right type.
- Test to see if mandatory attributes are set, then if they're the right types.
Once I've got those out of the way I'll start antagonizing the code, throwing out-of-bounds and evil values at it, forcing it to raise its exceptions if it's supposed to do that.
Then, as further use/testing reveal bugs I'd add specific tests to check to see those don't reappear as I screw around with the code. ("Code screwing-around" is gonna happen, so its important I know I didn't make the program go out in flames.)
ZenTest has the autotest
command which looks for a change in your test files and runs the tests automatically. It makes it really easy to make sure I haven't borked things, because in a separate console window autotest will be doing its thing each time I save. It's a great-big safety net you'll get used to having very quickly. From the docs:
autotest is a continous testing facility meant to be used during
development. As soon as you save a file, autotest will run the
corresponding dependent tests.
Writing tests are a necessary evil. They'll double your code-writing load, but they're very important to start early and continue maintaining. Trying to add them later to a large code base is a major problem, causing too many apps to never have unit tests. Icky.
Rookie - Ruby: Run file in terminal
Ruby programs generally use the '.rb' extension, so in order to run a ruby file that you've written, you need to save it somewhere with that extension first- eg. 'my-app.rb'.
It's a good idea when starting out to save it in a folder inside your "Home" directory (/Users/your user name/). You can find that in the mac "Finder" by clicking on the folder on the left hand list that's named "your username". In your terminal, your home directory is shortened to '~/' - and you can easily change directory into it with that shortcut:
cd ~
While I've been learning, I've stuck to a quick, short directory to store my files- '~/code/'. Anything will do, but it's much quicker to type 'cd ~/code/my-app.rb' than to type something long like 'cd ~/Documents/Programming/Ruby/my-app.rb' every time. So when you're deciding on where to save, think about how much you'll have to type in terminal! :)
Once you've saved your file, and used 'cd' to change into the directory you've saved it in, you use the command 'ruby' to run it.
ruby my-app.rb
That's about all there is to actually running your file! There's so much more to using the terminal, and writing code- but there's plenty of info out there on how to start.
I found Chris Pine's "Learn To Program" really simple and easy to follow. There are plenty of other resources out there, too! Try out Try Ruby to get going straight in your browser.
How can I do readline arguments completion?
After thinking a while, the solution was very simple:
comp = proc do |s|
if Readline.line_buffer =~ /^.* /
COLLECT.grep( /^#{Regexp.escape(s)}/ )
else
COMMANDS.grep( /^#{Regexp.escape(s)}/ )
end
end
Now I just need to turn it into something more flexible/usable.
How to turn off snippets in Atom?
Sadly, there's currently no built-in feature for this kind of thing.
Until some filter feature is added to the snippets package, the only way to access the snippets is to monkey-patch the package from your init script.
For instance something like that will allow you to filter the snippets returned for a given editor at runtime:
# we need a reference to the snippets package
snippetsPackage = require(atom.packages.getLoadedPackage('snippets').path)
# we need a reference to the original method we'll monkey patch
__oldGetSnippets = snippetsPackage.getSnippets
snippetsPackage.getSnippets = (editor) ->
snippets = __oldGetSnippets.call(this, editor)
# we're only concerned by ruby files
return snippets unless editor.getGrammar().scopeName is 'source.ruby'
# snippets is an object where keys are the snippets's prefixes and the values
# the snippets objects
console.log snippets
newSnippets = {}
excludedPrefixes = ['your','prefixes','exclusion','list']
for prefix, snippet of snippets
newSippets[prefix] = snippet unless prefix in excludedPrefixes
newSnippets
Related Topics
What Is '$:.Unshift File.Dirname(_File_)' Doing
Iconv Deprecation Warning with Ruby 1.9.3
How to Switch to an Older Version of Rails
Jekyll - Generating JSON Files Alongside the HTML Files
Heroku Wrongly Detecting My Node App as a Ruby App
Rbenv Irb History Is Not Saving
Convert Non-Breaking Spaces to Spaces in Ruby
Devise Custom Messages When Validation Fails
Factorygirl Association Model Trouble: "Systemstackerror: Stack Level Too Deep"
What's the Difference Between "Includes" and "Preload" in an Activerecord Query
Rebase Rails Migrations in a Long Running Project
Handling Namespace Models (Classes) in Namespace
Rails: Validation in Model VS Migration
Same Instance Variable for All Actions of a Controller
How to Wrap the Invocation of a Ruby Method by Including a Module