Easiest way to parse gem-style command line arguments in Ruby - ruby

I would like to implement gem-style console app, and when I say gem (or apt-get etc) style, I mean that it will have invocation syntax like:
program.rb verb [argument] [--options ...]
For example
greeter.rb say "Hello world" --bold
I have used optparse but I think it is not suitable for anything except --option style arguments. Am I wrong about it or there is more suitable library to achieve this?

I suggest not to parse from scratch; I suggest to use GLI by which you can provide (via its DSL) a git like interface to your users. Get started here to see how it works.
You might also be interested in looking at a real (humble) implementation in a project of mine. Check these files:
https://github.com/empo/RuGPost/blob/master/bin/rugpost
https://github.com/empo/RuGPost/blob/master/lib/rugpost/commands.rb
https://github.com/empo/RuGPost/blob/master/lib/rugpost/commands/post_commands.rb
https://github.com/empo/RuGPost/blob/master/lib/rugpost/commands/project_commands.rb

Related

Providing an argument that looks like an option (rails runner / OptionParser / optparse)

I have a script magic which uses rails runner to invoke another script magic-foo.rb, which does it's own option parsing. Something like this:
exec("#rails", "runner", "#{some_path}/bin/magic-foo.rb", *arguments)
I want to expose the --help option from magic-foo.rb through magic, but rails runner consumes --help and dies with its own help message.
How can I get an argument that looks like an option through the use of OptionParser in runner.rb? Alternativel, how else can I get magic to evaluate magic-foo.rb in the right rails context with the right arguments?
Because an argument -- is the standard way to say "don't process following arguments" I might expect an invocation like this to work: rails runner .../magic-foo.rb -- [ARGS]. Unfortunately it does not.
Active version of rails runner: https://github.com/rails/rails/blob/880371ef2b4a2cb08f0c36ceba1eee41836bb739/railties/lib/rails/commands/runner.rb .

Minitest: Programmatically access name and time a test took to run

At the moment, when using minitest, if you do:
bundle exec rake TESTOPTS='--verbose'
you get an output like this:
Text you typed in the describe#test_0001_test description = 0.11 s = .
Text you typed in the describe#test_0003_another test description = 0.10 s = .
...etc.
I want to have acces to this programmatically, so that I can select the slowest tests, sort them by the time they took to run, and print them out to stdout in any format I want. Ideally I would define a new Rake task for this and then run something like:
bundle exec rake mytask
or something.
However, I can't seem to find anything online on how to access this information programmatically. I searched about custom reporters, but apparently you have to monkey-patch Minitest for that, and I don't want to do that. The other option is to install the minitest-reporters gem, but I don't want nor need all that functionality, what I want to do is very simple. I've also read through the code in the Minitest repo, but couldn't wrap my head around what to inherit from if I wanted to create my own class, and what to access in order to get the time spent running and the name of the test.
Is there any way to have access to this information programmatically? For example accessing the reports produced by minitest once all tests have finished running? How do you do it, those of you who write custom reporters without requiring a gem or monkey-patching minitest? I feel like this should be an easy thing to do.
You can write your own reporter (no need to monkey-patch, or use minitest-reporters), but there is an easier way. The verbose output is formatted in such a way that you can parse it using sort:
bundle exec rake TESTOPTS='--verbose' | sort -t = -k 2 -g

How to prevent capistrano replacing newlines?

I want to run some shell scripts remotely as part of my capistrano setup. To test that functionality, I use this code:
execute <<SHELL
cat <<TEST
something
TEST
SHELL
However, that is actually running /usr/bin/env cat <<TEST; something; TEST which is obviously not going to work. How do I tell capistrano to execute the heredoc as I have written it, without converting the newlines into semicolons?
I have Capistrano Version: 3.2.1 (Rake Version: 10.3.2) and do not know ruby particularly well, so there might be something obvious I missed.
I think it might work to just specify the arguments to cat as a second, er, argument to execute:
cat_args = <<SHELL
<<TEST
something
TEST
SHELL
execute "cat", cat_args
From the code #DavidGrayson posted, it looks like only the command (the first argument to execute) is sanitized.
I agree with David, though, that the simpler way might be to put the data in a file, which is what the SSHKit documentation suggests:
Upload a file from a stream
on hosts do |host|
file = File.open('/config/database.yml')
io = StringIO.new(....)
upload! file, '/opt/my_project/shared/database.yml'
upload! io, '/opt/my_project/shared/io.io.io'
end
The IO streaming is useful for uploading something rather than "cat"ing it, for example
on hosts do |host|
contents = StringIO.new('ALL ALL = (ALL) NOPASSWD: ALL')
upload! contents, '/etc/sudoers.d/yolo'
end
This spares one from having to figure out the correct escaping sequences for something like "echo(:cat, '...?...', '> /etc/sudoers.d/yolo')".
This seems like it would work perfectly for your use case.
The code responsible for this sanitization can be found in SSHKit::Command#sanitize_command!, which is called by that class's initialize method. You can see the source code here:
https://github.com/capistrano/sshkit/blob/9ac8298c6a62582455b1b55b5e742fd9e948cefe/lib/sshkit/command.rb#L216-226
You might consider monkeypatching it to do nothing by adding something like this to the top of your Rakefile:
SSHKit::Command # force the class to load so we can re-open it
class SSHKit::Command
def sanitize_command!
return if some_condition
super
end
end
This is risky and could introduce problems in other places; for example there might be parts of Capistrano that assume that the command has no newlines.
You are probably better off making a shell script that contains the heredoc or putting the heredoc in a file somewhere.
Ok, so this is the solution I figured out myself, in case it's useful for someone else:
str = %x(
base64 <<TEST
some
thing
TEST
).delete("\n")
execute "echo #{str} | base64 -d | cat -"
As you can see, I'm base64 encoding my command, sending it through, then decoding it on the server side where it can be evaluated intact. This works, but it's a real ugly hack - I hope someone can come up with a better solution.

Unable to figure out ruby method "directory" and what it does

I am very new to ruby and was trying to understand some code when I got stuck at this snippet:
directory "test_dir" do
action :create
recursive true
end
I tried googling directory class but was unsuccessful. I found a class Dir but its not the same. I see that intuitively this snippet should create a new directory and name it test_dir but I do not want to assume things and move forward.
EDIT
This was a part of a chef-recipe which is used to launch a particular task. For the purposes of launching, it needs to create a directory and download some jars to it. There is an execute method below which goes like this:
execute 'deploy' do
action :nothing
# ignore exit status of storm kill command
command <<-EOH
set -e
storm kill #{name} -w 1 || true
sleep 3
storm jar #{points to the jar}
EOH
end
Sorry I have to be a bit obfuscated as some of the things are not open sourced.
It is the Directory resource of the Chef framework. (DSL stands for domain-specific language. Ruby is well suited for them.)
It's the Chef internal DSL for Directory management. Read more here: http://wiki.opscode.com/display/chef/Resources#Resources-Directory
PS: The recursive true tells it to create the folder much like mkdir -p.
The snippet you pasted is not really enough information to go on (need context; where is the snippet from?)
That said directory looks more like a method than a class. First, it's lowercased and classes are CamelCased.
If it's a method, it's defined somewhere within the application. Have you tried something like this:
grep -r "def directory" ./ or
grep -r "directory" ./| grep "def"
If not in the application itself, it would be defined in one of the application's dependencies (grep -r "..." $GEM_HOME/gems instead)
directory is not a class, it is a method. I do not know what module it is a part of, but that snippet is about equivalent to this:
Kernel.directory.call("test_dir",lambda {action :create; recursive true})
That snippet uses some gem that adds a directory method to the Kernel object.
As others have mentioned, it is part of the directory management DSL Chef. A DSL is a set of methods integrated into the Kernel object; because of the very flexible method calling syntax of Ruby, method calls can look a lot like language keywords. That makes task specific commands (Domain Specific Languages: DSL) look very nice in Ruby; easy to use and flexible. Thus gems that add DSLs are very common.

Running ruby gem sprockets from command line

I am finding very little documentation on running sprockets from the command line.
Does anyone know how to setup the .sprocketsrc file?
Examples would be great especially on how to configure the minification.
If you read directly the source, you can see there https://github.com/sstephenson/sprockets/blob/master/bin/sprockets#L8 that it uses something named Shellwords which comes with the standard ruby library : http://www.ruby-doc.org/stdlib-1.9.3/libdoc/shellwords/rdoc/Shellwords.html and http://www.ruby-doc.org/stdlib-1.9.3/libdoc/shellwords/rdoc/Shellwords.html#method-c-shellsplit
So we can guess from :
unless ARGV.delete("--noenv")
if File.exist?(path = "./.sprocketsrc")
rcflags = Shellwords.split(File.read(path))
ARGV.unshift(*rcflags)
end
end
That it basically prepends whatever it find in the sprocketsrc to the command line arguments.
https://github.com/sstephenson/sprockets/blob/master/bin/sprockets#L22 gives us the list of the options, meaning if you want to configure the minification you can create a .sprocketsrc
with something like
--include=assets/javascripts --output build/assets/javascripts
Sadly, the command line don't look to have any option to configure the minifying options.

Resources