Given a Ruby program using Thor, how can I implement a method that gets called when an argument that looks like a flag is called.
For example, if I run this on the command line:
mycmd --version
I would like to execute the code:
desc 'version', 'Print version number'
def version
puts "mycmd version #{Mycmd::VERSION}"
end
You can make a "top level" default task, which examines its arguments and outputs the correct thing:
class MyThing < Thor
desc "meta", "Information about the task itself"
argument :name
def meta
if name == "--version"
puts "v 1.1.1"
elsif name == "--author"
puts "meagar"
end
end
default_task :meta
end
Related
I have to use this command to execute the script:
$ruby file.rb keyword --format oneline --no-country-code --api-key=API
Where, the format, no-country-code, api-key are thor options. The keyword is the argument in my method:
class TwitterTrendReader < Thor
method_option :'api-key', :required => true
method_option :format
method_option :'no-country-code', :type => :boolean
def execute (keyword)
#read file then display the results matching `keyword`
end
default_task :execute
end
The problem is the keyword is optional, if I run the command without the keyword, the script should print all entries in the file, otherwise, it only display the entries matching the keyword.
so I have this code:
if ARGV.empty?
TwitterTrendReader.start ''
else
TwitterTrendReader.start ARGV
end
It only works when I specify a keyword but without keyword, I get this:
$ruby s3493188_p3.rb --api-key="abC9hsk9"
ERROR: "s3493188_p3.rb execute" was called with no arguments
Usage: "s3493188_p3.rb [keyword] --format oneline --no-country-code --api-key=API-KEY"
So please tell me what is the proper way that I could make the argument optional. Thank you!
Your current implementation of def execute (keyword) is of arity 1 (that said, it declares one mandatory parameter.) Make the parameter optional, if you want to have an ability to omit it.
Change
def execute (keyword)
#read file then display the results matching `keyword`
end
to:
def execute (keyword = nil)
if keyword.nil?
# NO KEYWORD PASSED
else
# NORMAL PROCESSING
end
end
I'm converting a small project to use Thor, and being rather absent-minded I want to document the available tasks using built-in help.
But if I define a task with an argument the task-level help reverts to help for the class - which means no task description or details of the expected parameters are displayed.
I want to be be able to have a method I can call with an argument and not a parameter so it can be used like this
$ thor broke:foo hello
in a.thor broke:foo arg1=hello
I've boiled the problem down to the following thorfile which works as I want except the broken help output. I've stripped out any other parameters as they make no difference to the problem.
The first task ok:foo will display help normally, the second task broke:foo is rather less helpful :
class Ok < Thor
desc "foo", "ok test2"
def foo
puts "in a.thor ok:foo\n"
end
end
class Broke < Thor
argument :arg1, :type=>:string, :desc => "arg1"
desc "foo", "broke test1"
def foo
puts "in a.thor broke:foo arg1=#{self.arg1}\n"
end
end
Requesting help for the ok:foo task method gives :
$ thor help ok:foo
Usage:
thor ok:foo
ok test
Requesting help for the broke:foo task is rather less helpful :
$ thor help broke:foo
Tasks:
thor broke:foo ARG1 # broke test1
thor broke:help ARG1 [TASK] # Describe available tasks or one specific task
How can I define an argument and get the proper task help displayed ?
You have small mistake in your thorfile.
You have argument instead of method_option.
Here correct version:
class Ok < Thor
desc "foo", "ok test2"
def foo
puts "in a.thor ok:foo\n"
end
end
class Broke < Thor
method_option :arg1, :type=>:string, :desc => "arg1"
desc "foo", "broke test1"
def foo
puts "in a.thor broke:foo arg1=#{self.arg1}\n"
end
end
Wiki about method_option on github.com
When calling thor commands on the command line, the methods are namespaced by their module/class structure, e.g.
class App < Thor
desc 'hello', 'prints hello'
def hello
puts 'hello'
end
end
would be run with the command
thor app:hello
However, if you make that self executable by putting
App.start
at the bottom you can run the command like:
app hello
Is there any way to namespace those commands? So that you could call, for example
app say:hello
app say:goodbye
Another way of doing this is to use register:
class CLI < Thor
register(SubTask, 'sub', 'sub <command>', 'Description.')
end
class SubTask < Thor
desc "bar", "..."
def bar()
# ...
end
end
CLI.start
Now - assuming your executable is called foo - you can call:
$ foo sub bar
In the current thor version (0.15.0.rc2) there is a bug though, which causes the help texts to skip the namespace of sub commands:
$ foo sub
Tasks:
foo help [COMMAND] # Describe subcommands or one specific subcommand
foo bar #
You can fix that by overriding self.banner and explicitly setting the namespace.
class SubTask < Thor
namespace :sub
def bar ...
def self.banner(task, namespace = true, subcommand = false)
"#{basename} #{task.formatted_usage(self, true, subcommand)}"
end
end
The second parameter of formatted_usage is the only difference to the original implemtation of banner. You can also do this once and have other sub command thor classes inherit from SubTask. Now you get:
$ foo sub
Tasks:
foo sub help [COMMAND] # Describe subcommands or one specific subcommand
foo sub bar #
Hope that helps.
This is one way with App as the default namespace (quite hacky though):
#!/usr/bin/env ruby
require "rubygems"
require "thor"
class Say < Thor
# ./app say:hello
desc 'hello', 'prints hello'
def hello
puts 'hello'
end
end
class App < Thor
# ./app nothing
desc 'nothing', 'does nothing'
def nothing
puts 'doing nothing'
end
end
begin
parts = ARGV[0].split(':')
namespace = Kernel.const_get(parts[0].capitalize)
parts.shift
ARGV[0] = parts.join
namespace.start
rescue
App.start
end
Or, also not ideal:
define_method 'say:hello'
I'm trying to figure out how the Thor gem creates a DSL like this (first example from their README)
class App < Thor # [1]
map "-L" => :list # [2]
desc "install APP_NAME", "install one of the available apps" # [3]
method_options :force => :boolean, :alias => :string # [4]
def install(name)
user_alias = options[:alias]
if options.force?
# do something
end
# other code
end
desc "list [SEARCH]", "list all of the available apps, limited by SEARCH"
def list(search="")
# list everything
end
end
Specifically, how does it know which method to map the desc and method_options call to?
desc is pretty easy to implement, the trick is to use Module.method_added:
class DescMethods
def self.desc(m)
#last_message = m
end
def self.method_added(m)
puts "#{m} described as #{#last_message}"
end
end
any class that inherits from DescMethods will have a desc method like Thor. For each method a message will be printed with the method name and description. For example:
class Test < DescMethods
desc 'Hello world'
def test
end
end
when this class is defined the string "test described as Hello world" will be printed.
Rake allows for the following syntax:
task :my_task, :arg1, :arg2 do |t, args|
puts "Args were: #{args}"
end
I'd like to be able to do the same, but with RSpecs SpecTask.
The following unfortunately fails:
desc "Run example with argument"
SpecTask.new('my_task'), :datafile do |t, args|
t.spec_files = FileList['*_spec.rb -datafile=#{args}']
t.spec_opts = ["-c -f specdoc"]
end
Is it possible to achieve this with a SpecTask, or is there an alternative approach?
if rspec doesn't support the args variable, you could pass it in as a command line parameter and/or a variable from another location.
rake datafile=somevalue
#datafile = ENV["datafile"]
desc "Run example with argument"
SpecTask.new :my_task do |t|
t.spec_files = FileList["*._spec.rb -datafile=#{#datafile}"]
#... etc
end