Pretty new to Ruby and OO. Studying the text books, and all the articles that google found on Thor.
I have Thor working to capture multiple command line arguments and options. I'd like to do the rest of my programming from outside the Cli < Thor class though, and am having trouble accessing the command line arguments from outside the Cli class.
Questions:
Q1. Can the Cli < Thor class be treated like any other ruby class, or does inheriting from Thor, or the "Cli.start" command, cripple certain functionality of the Cli class versus not using Thor? Asking because I may simply not know how to access an instance variable from outside a class that doesn't use the initialize method. Thor will not let me use the initialize method to bring in the command line variables, probably because initialize is a reserved method name in ruby.
Q2. How can I access the command line argument variables a and b from outside the Thor class?
Here's my code
#!/usr/bin/env ruby
require 'thor'
class Cli < Thor
attr_reader :a, :b
method_option :add, :type => :string, :desc => 'add servers'
method_option :prod, :type => :string, :desc => 'production stack'
desc "tier <stack folder name> <app | web>", "creates an app or web server tier for the stack"
def tier(a,b)
#a = a
#b = b
puts a
puts b
end
end
Cli.start
arguments = Cli.new
puts "the first argument is #{arguments.a}"
Here's the result. Close (maybe). No errors, but arguments.a is nil.
$ ./create.rb tier a b
a
b
the first argument is
--
puts arguments.tier.a
threw the error:
./create.rb:11:in `tier': wrong number of arguments (0 for 2) (ArgumentError)
from ./create.rb:23:in `<main>'
The following works without Thor and using an initialize method and attr_reader, straight out of the text books. Can't figure out how to access the variables from a non-initialize method though.
#!/usr/bin/env ruby
class Cli
attr_reader :a, :b
def initialize(a,b)
#a = a
#b = b
end
end
arguments = Cli.new("a","b")
puts arguments.a
outupt:
$ ./create_wo_thor.rb
a
Instantiating your Cli class doesn't make much sense; that's not how Thor is designed.
You have a few options to access internal data from outside the class. If there are only a few variables that you want access to, storing them as class variables and making them available through getters (and setters, if you need them) would work:
require 'thor'
class Cli < Thor
method_option :add, :type => :string, :desc => 'add servers'
method_option :prod, :type => :string, :desc => 'production stack'
desc "tier <stack folder name> <app | web>", "creates an app or web server tier for the stack"
def tier(a,b)
##a = a
##b = b
puts a
puts b
end
def self.get_a
##a
end
def self.get_b
##b
end
end
Cli.start
puts "the first argument is #{Cli.get_a}"
This works as you hope:
$ ./thor.rb tier a b
a
b
the first argument is a
I prefer the following, using a global Hash:
require 'thor'
$args = {}
class Cli < Thor
method_option :add, :type => :string, :desc => 'add servers'
method_option :prod, :type => :string, :desc => 'production stack'
desc "tier <stack folder name> <app | web>", "creates an app or web server tier for the stack"
def tier(a,b)
$args[:a] = a
$args[:b] = b
puts a
puts b
end
end
Cli.start
puts "the first argument is #{$args[:a]}"
Last, I'd be remiss not to point out that all the command line arguments are available in the global ARGV, anyway:
require 'thor'
class Cli < Thor
method_option :add, :type => :string, :desc => 'add servers'
method_option :prod, :type => :string, :desc => 'production stack'
desc "tier <stack folder name> <app | web>", "creates an app or web server tier for the stack"
def tier(a,b)
puts a
puts b
end
end
Cli.start
puts "The first argugment is #{ARGV[1]}"
What would be best depends on how you intend to use it. If you just want raw access to the command line arguments, ARGV is the way to go. If you want to access certain pieces after Thor has done some processing for you, one of the first two might be more helpful.
Here's my code with all three options included for accessing the command line arguments from outside the Cli < Thor class. Compliments to Darshan.
#!/usr/bin/env ruby
require 'thor'
$args = {}
class Cli < Thor
attr_reader :a, :b
method_option :add, :type => :string, :desc => 'add servers'
method_option :prod, :type => :string, :desc => 'production stack'
desc "tier <stack folder name> <app | web>", "creates an app or web server tier for the stack"
def tier(a,b)
# store a and b in a global hash
$args[:a] = a
$args[:b] = b
# store a and b in class variables
##a = a
##b = b
end
# getter methods, for access of the class variables from outside the class
def self.get_a
##a
end
def self.get_b
##b
end
end
Cli.start
# three ways now to access the command line arguments from outside the Cli < Thor class
puts "the first argument, from $args[:a], is #{$args[:a]}"
puts "the second argument, from Cli.get_b, is #{Cli.get_b}"
puts "the first argument, from ARGV[1], is #{ARGV[1]}"
Results:
$ ./create.rb tier a b
the first argument, from $args[:a], is a
the second argument, from Cli.get_b, is b
the first argument, from ARGV[1], is a
Related
While trying to develop a simple gem to learn the process, I happened to stumble on this issue: Thor DSL takes in options to a command using the syntax: option :some_option, :type => :boolean, just prior to the method definition.
I am trying to have a dynamic set of options loaded from a file. I do this file read operation in the constructor, but it seems the option keyword for the Thor class is getting processed before the initialize method.
Any ideas to resolve this? Also it would be great if someone can explain how the option keyword works? I mean is option a method call? I don't get the design. (This is the first DSL I am trying out and am a total newbie to Ruby Gems)
#!/usr/bin/env ruby
require 'thor'
require 'yaml'
require 'tinynews'
class TinyNewsCLI < Thor
attr_reader :sources
#sources = {}
def initialize *args
super
f = File.open( "sources.yml", "r" ).read
#sources = YAML::load( f )
end
desc "list", "Lists the available news feeds."
def list
puts "List of news feed sources: "
#sources.each do |symbol, source|
puts "- #{source[:title]}"
end
end
desc "show --source SOURCE", "Show news from SOURCE feed"
option :source, :required => true
def show
if options[:source]
TinyNews.print_to_cli( options[:source].to_sym )
end
end
desc "tinynews --NEWS_SOURCE", "Show news for NEWS_SOURCE"
#sources.keys.each do |source_symbol| # ERROR: States that #sources.keys is nil
#[:hindu, :cnn, :bbc].each do |source_symbol| # I expected the above to work like this
option source_symbol, :type => :boolean
end
def news_from_option
p #sources.keys
TinyNews.print_to_cli( options.keys.last.to_sym )
end
default_task :news_from_option
end
TinyNewsCLI.start( ARGV )
After a bit of tweaking, I think I ended upon a solution that doesn't look too bad. But not sure placing code in module like that is a good practice. But anyways:
#!/usr/bin/env ruby
require 'thor'
require 'yaml'
require 'tinynews'
module TinyNews
# ***** SOLUTION *******
f = File.open( "../sources.yml", "r" ).read
SOURCES = YAML::load( f )
class TinyNewsCLI < Thor
default_task :news_from_source
desc "list", "Lists the available news feeds."
def list
puts "List of news feed sources: "
SOURCES.each do |symbol, source|
puts "- #{source[:title]}"
end
end
desc "--source NEWS_SOURCE", "Show news for NEWS_SOURCE"
option :source, :required => true, :aliases => :s
def news_from_source
TinyNews.print_to_cli( options[:source].to_sym )
end
end
end
TinyNews::TinyNewsCLI.start( ARGV )
This is my Ruby code:
require 'thor'
require 'thor/group'
module CLI
class Greet < Thor
desc 'hi', 'Say hi!'
method_option :name, :type => :string, :description => 'Name to greet', :default => 'there'
def hi
puts "Hi, #{options[:name]}!"
end
desc 'bye', 'say bye!'
def bye
puts "Bye!"
end
end
class Root < Thor
register CLI::Greet, 'greet', 'greet [COMMAND]', 'Greet with a command'
end
end
CLI::Root.start
This is the output:
C:\temp>ruby greet.rb help greet
Usage:
greet.rb greet [COMMAND]
Greet with a command
How do I get that to look something like this?
C:\temp>ruby greet.rb help greet
Usage:
greet.rb greet [COMMAND]
--name Name to greet
Greet with a command
You've got two things going on here. First, you've assigned --name to a method, not to the entire CLI::Greet class. So if you use the command:
ruby greet.rb greet help hi
you get
Usage:
greet.rb hi
Options:
[--name=NAME]
# Default: there
Say hi!
Which, yes, is wrong-- it doesn't have the subcommand in the help. There's a bug filed for this in Thor. It, however, is showing the method option properly.
What it seems like you're looking for, however, is a class method. This is a method defined for the entire CLI::Greet class, not just for the #hi method.
You'd do this as such:
require 'thor'
require 'thor/group'
module CLI
class Greet < Thor
desc 'hi', 'Say hi!'
class_option :number, :type => :string, :description => 'Number to call', :default => '555-1212'
method_option :name, :type => :string, :description => 'Name to greet', :default => 'there'
def hi
puts "Hi, #{options[:name]}! Call me at #{options[:number]}"
end
desc 'bye', 'say bye!'
def bye
puts "Bye! Call me at #{options[:number]}"
end
end
class Root < Thor
register CLI::Greet, 'greet', 'greet [COMMAND]', 'Greet with a command'
tasks["greet"].options = CLI::Greet.class_options
end
end
CLI::Root.start
With this, ruby greet.rb help greet returns
Usage:
greet.rb greet [COMMAND]
Options:
[--number=NUMBER]
# Default: 555-1212
Greet with a command
Note there is still a hack needed here: the tasks["greet"].options = CLI::Greet.class_options line in CLI::Root. There's a bug filed for this in Thor, too.
class B12 < Thor
desc "write", "write data into the index"
method_option :methods, :desc => "The methods to call on each RawData", :type => :array
def write(methods)
end
end
When I call the file via
thor b12:write --methods=foo
I get
"write" was called incorrectly. Call as "thor b12:write".
Where's the problem?
You've got a couple things going on here that'll cause issues.
First, you're using methods, which is a keyword in ruby. That's going to cause all sorts of nonsense. Use something else, like my_methods.
Second, you don't need to pass my_methods into write. That creates a default option, not a named option. So you'd call thor b12:write foo if you wanted access to my_methods in that context.
This works if you call it with: thor b12:write --my_methods=foo
class B12 < Thor
desc "write", "write data into the index"
method_option :my_methods, :type => :array, :desc => "The methods to call on each RawData"
def write
puts options.my_methods
end
end
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.
Can any one volunteer why the class below fails?
... src/model/user.rb:18: undefined method `set_schema' for User:Class (NoMethodError)
I've looked in the Sequel-3.0 lib/ folder and the set_schema method is defined in a ClassMethods module.
I'm sure the solution is simple. I was thinking it should work "as is":
require 'sequel'
class User < Sequel::Model(:user)
set_schema do
set_primary_key :id
String :name
end
end
Recommended way ...
LOGGER = Object.new()
def LOGGER.method_missing( name, args )
puts "[#{name}] #{args}"
end
Sequel::Model.plugin(:schema) # I worked this out, but I can't find it documented
DB = Sequel.sqlite('sql_test.db', :loggers => [LOGGER] )
unless DB.table_exists?( :user )
DB.create_table :user do
set_primary_key :id
String :name
String :password
String :eMail
end #create_table
end #table exists
class User < Sequel::Model(:user)
The answer is to call up the plug-in for schema managing. Viz.
require 'sequel'
require 'logger'
LOGGER = Object.new()
def LOGGER.method_missing( name, args )
puts "[#{name}] #{args}"
end
**Sequel::Model.plugin(:schema)** # I still didn't find this documented
DB = Sequel.sqlite('sql_test.db', :loggers => [LOGGER] )
class User < Sequel::Model(:user)
set_schema do
set_primary_key :id
String :name
end
end
Yep Sequel::Model.plugin(:schema) worked for me too. Can't see it in the docs and I'm perplexed as to why, since I have another working project that uses set_schema without fuss.