Looking for better/familiar approach to command line opts

So I guess the warning to the reader upfront is… I’m a bit of a Perl
hack who should have moved to Ruby a decade ago and just couldn’t let go
of Perl. Perl still does stuff that I use extensively that I can’t
(take the time to) figure out how to do in Ruby, so… that’s why I’m
here.

I’ve got a small framework I wrote in Perl I’ve been using for years
that is lightweight and yet powerful. A lot of it centers around the
use of AUTOLOAD, which some Perlers say is bad – I say it’s extremely
powerful if used properly, and actually, AUTOLOAD forms a lot of the
backbone for my framework. And honestly, it’s helped me do some things
in Perl that I see in Ruby. So there’s some irony here in that I wrote
a framework to give me some of the power I see in Ruby in Perl. :slight_smile:

With all that as a backdrop, here’s how I can process command line
arguments in Perl (everything I do in Perl is OO!!) and I want the
same thing in Ruby. OptParser in Ruby is WAY more than I’m looking for
– not as easy as what I’m used to (or maybe I don’t understand
OptParser, which is likely).

In Perl, I can do this:

options = MyFramework::Options->new(
sourcePath => ‘s:’
destPath => ‘d:’
verbose => ‘v’
);

What ends up happening behind the scenes is a class instance is created
dynamically that essentially gives me the ability to say:

I actually have a puts() I created in Perl as well,

borrowed from Ruby!

puts(
"Source path…: " . $options->sourcePath,
"Destination path…: " . $options->destPath,
"Verbose is…: " . ($options->verbose ? ‘ON’ : ‘OFF’),
);

So I have a getter for sourcePath, a getter for destPath and a getter
for verbose, automagically created for that instance loaded with the
values from the command line. If I said:

whatever = MyFramework::Options->new(
foo => ‘f:’,
bar => ‘b:’,
);

Then I would have an instance of ‘whatever’ with foo and bar as getters,
etc. with the values of ‘f’ and ‘b’ from the command line loaded
appropriately.

Now how to do this in Ruby? Like I said, the OptParser seems WAY too
complicated for what I want and am trying to do. It seems like you
still are coding some kind of OptParser class for the specific options
you want – I don’t want to create classes that mirror the command line
options. I want to tell the class instance at the time I create it
(kind of like OpenStruct) what it looks like and it just creates the
right instance. I want to be able to do this in Ruby, assuming I would
“define” this dynamically on the fly using maybe a hash as initial
input… I like the flexibility of hashes:

options = Options.new({
:sPath => ‘s:’,
:dPath => ‘d:’,
:verbose => ‘v’,
})

puts “Source path…: #{options.sPath}”
puts “Destination path…: #{options.dPath}”
puts “Verbose is…: #{options.verbose ? ‘ON’ : ‘OFF’}”

What this is saying is “map the value of ‘s’ from the command line into
@sPath, map the value of ‘d’ from the command line into @dPath, and map
whether ‘v’ exists, true or false, into @verbose.” Now how much more
simple can you get??!!

Again, the warning of “trying to do Ruby stuff in a Perl way” comes to
me, but what I do in Perl is so stinking simple, it’s unbelievable, and
this is one reason I haven’t switched over to Ruby yet. But now I’m
trying.

Things I’ve tried: A lot of different combinations of…
– missing_method()
– define_method()
(I don’t understand why a lot of d_m() examples use self.class.send()!!)
– Struct()
– OpenStruct()

So far, requiring “optparse” and doing ARGV.getopts() does some of what
I want. This is as close as I’ve gotten and it’s not working:

require “optparse”

class Options
attr_reader :cmdline, :options_list
def initialize( options )
@cmdline = ARGV.join( ’ ’ )
@options_list = options.values.join
params = ARGV.getopts( @options_list )
options.each do |key,value|
## Why do I need to send( :define_method, … ) here?
## Why can’t I just say self.define_method?
## This seems like it would create proper closure
## on params[] elements?!
self.class.send( :define_method, key ) { params[value] }
end
end
end

options = Options.new({
:sPath => ‘s:’,
:dPath => ‘d:’,
:verbose => ‘v’,
})

If someone can tell me how to do this using OptParser, so be it. But
from an encapsulation standpoint, I’d like to be able to call it as I do
above. If the innards of the Options class use OptParser, fine.

-pj

On Sun, Sep 25, 2011 at 3:48 PM, Perl J. [email protected] wrote:

backbone for my framework. And honestly, it’s helped me do some things

borrowed from Ruby!

whatever = MyFramework::Options->new(
still are coding some kind of OptParser class for the specific options
:verbose => ‘v’,

– OpenStruct()
@options_list = options.values.join

-pj


Posted via http://www.ruby-forum.com/.

Ruby has a builtin -s flag, which will parse args

I don’t know Perl so I can’t say if it will meet your needs, but does
this
program do what you want?


#!/usr/bin/env ruby -s
options = {
:source_path => ‘s’
:dest_path => ‘d’
:verbose => ‘v’
}
p options

I meant to say “from an ABSTRACTION standpoint…” :slight_smile: -pj

On Sun, Sep 25, 2011 at 4:03 PM, Josh C. [email protected]
wrote:

use of AUTOLOAD, which some Perlers say is bad – I say it’s extremely

values from the command line. If I said:
Now how to do this in Ruby? Like I said, the OptParser seems WAY too
:sPath => ‘s:’,
whether ‘v’ exists, true or false, into @verbose." Now how much more
(I don’t understand why a lot of d_m() examples use self.class.send()!!)
def initialize( options )
end
above. If the innards of the Options class use OptParser, fine.
program do what you want?

Sorry, that should be like this:


#!/usr/bin/env ruby -s
options = {
:source_path => $s,
:dest_path => $d,
:verbose => $v,
}
p options


And you could invoke it like this

$ ./script.rb -s=path/to/input.txt -d=path/to/output.txt -v
{:source_path=>“path/to/input.txt”, :dest_path=>“path/to/output.txt”,
:verbose=>true}

Josh C. wrote in post #1023694:

On Sun, Sep 25, 2011 at 4:03 PM, Josh C. [email protected]
wrote:

use of AUTOLOAD, which some Perlers say is bad – I say it’s extremely

values from the command line. If I said:
Now how to do this in Ruby? Like I said, the OptParser seems WAY too
:sPath => ‘s:’,
whether ‘v’ exists, true or false, into @verbose." Now how much more
(I don’t understand why a lot of d_m() examples use self.class.send()!!)
def initialize( options )
end
above. If the innards of the Options class use OptParser, fine.
program do what you want?

Sorry, that should be like this:


#!/usr/bin/env ruby -s
options = {
:source_path => $s,
:dest_path => $d,
:verbose => $v,
}
p options


And you could invoke it like this

$ ./script.rb -s=path/to/input.txt -d=path/to/output.txt -v
{:source_path=>“path/to/input.txt”, :dest_path=>“path/to/output.txt”,
:verbose=>true}

Ehh, it’s interesting and simple, for sure. But now I’m changing the
way I generally call and use DSLs I create. Generally I call as (as per
must Unix utils):

$ myutil -f foo -b bar

and not:

$ myutil -f=foo -b=bar

If the later were how I called things, then in my mind it would actually
be easier to just grab command line parameters and parse them myself on
the ‘=’ symbol.

Also, if -v isn’t specified it becomes nil in the resulting hash, but I
see nil evals as false essentially, so that seems to still work.

I might play with this some. I want a object instance, not a hash, but
I might be able to get there using this. It’s much simpler, that’s for
sure.

-pj

On Sun, Sep 25, 2011 at 4:08 PM, Perl J. [email protected] wrote:

I meant to say “from an ABSTRACTION standpoint…” :slight_smile: -pj

I found the Trollop gem really easy to work with
http://trollop.rubyforge.org/

Check out the getoptions gem by my friend Delaney Parker, it was modeled
after Perl’s Getopt::Long.

Jos
On Sep 25, 2011 2:24 PM, “Perl J.” [email protected] wrote:

whether ‘v’ exists, true or false, into @verbose." Now how much more
(I don’t understand why a lot of d_m() examples use
self.class.send()!!)

On Mon, Sep 26, 2011 at 08:08:21AM +0900, Perl J. wrote:

Now, if someone can explain to me WHY I had to call the define_method()
method using send() instead of calling it outright, I’d really like to
know why that is.

Module.define_method is a private method. I’m pretty sure that’s why
you
need to use send.

Wow, time being what it is, I just need to post to a forum sooner. :slight_smile:
It never fails that when I post to a forum asking for help, immediately
thereafter I figure it out.

It turns out that the original code I posted in Ruby… WORKED. Exactly
as I wanted it to. I was just making a stupid, dumb rookie mistake.
This is exactly what I want. Now there might be a better way to do it,
but this produces the results I want:

require “optparse”

class Options
attr_reader :args, :cmdline, :options_list
def initialize( options )
@cmdline = ARGV.join( ’ ’ )
@options_list = options.values.join
params = ARGV.getopts( @options_list )
options.each do |key,value|
value.sub!( /:/, ‘’ )
self.class.send( :define_method, key ) { params[value] }
end
@args = Array.new( ARGV )
end
end

options = Options.new({
:source_path => ‘s:’,
:dest_path => ‘d:’,
:verbose => ‘v’,
})

puts “Source path…: #{options.source_path}”
puts “Destination path…: #{options.dest_path}”
puts “Verbose is…: #{options.verbose ? ‘ON’ : ‘OFF’}”
puts “Command line was…: #{options.cmdline}”
puts “Arguments…: #{options.args.join( ’ ’ )}”
puts “getopts() list…: #{options.options_list}”

Returns:

pj@matrix-dhcp-191:~/Work/Clients/Nunya/Business> ./bkupct.rb -s dude -d
yo purge them all split
Source path…: dude
Destination path…: yo
Verbose is…: OFF
Command line was…: -s dude -d yo purge them all split
Arguments…: purge them all split
getopts list…: s:d:v

The rookie mistake is that there was a mismatch between the values in
the options I passed into the class to initialize() and the value I am
using to index the elements of the returned params hash in initialize().
The params hash is indexed using just the letters, whereas options still
has the colons which getopts needs. The closure I was expecting to
create with the params hash was working, it was just indexing wrongly.
So a simple sub! took care of this and now it works EXACTLY as I would
like.

Now, if someone can explain to me WHY I had to call the define_method()
method using send() instead of calling it outright, I’d really like to
know why that is. Why can’t I do this?:

class Options
attr_reader :args, :cmdline, :options_list
def initialize( options )
@cmdline = ARGV.join( ’ ’ )
@options_list = options.values.join
params = ARGV.getopts( @options_list )
options.each do |key,value|
value.sub!( /:/, ‘’ )
## Why can’t I just do this?!
self.define_method( key ) { params[value] }
end
@args = Array.new( ARGV )
end
end

Thanks all!
-pj

I’m not sure where that syntax for OptionParser came, and Trollop is a
fine lib, but if you want to stick with just Ruby standard library
stuff:

require ‘optparse’

options = {}
opts = OptionParser.new do |opts|

opts.on(’-s SOURCE_PATH’,‘Source path’) do |path|
options[:source_path] = path
end
opts.on(’-d DEST_PATH’,‘Destination path’) do |path|
option[:dest_path] = path
end
opts.on(’-v’,‘Be verbose’) do
options[:verbose] = true
end
end
opts.parse!

puts “Source Path is: #{options[:source_path]”
puts “Dest Path is: #{options[:dest_path]”
puts “We are being verbose” if options[:verbose]

It’s a bit more code, but you also get built-in help:

$ my_app --help
Usage: my_app [options]

-s SOURCE_PATH   Source path
-d DEST_PATH       Dest path
-v                          Be verbose

You can use long-form options as well (e.g. --verbose)

On Sun, Sep 25, 2011 at 10:48 PM, Perl J. [email protected] wrote:

backbone for my framework. And honestly, it’s helped me do some things

borrowed from Ruby!

whatever = MyFramework::Options->new(
still are coding some kind of OptParser class for the specific options
:verbose => ‘v’,

– OpenStruct()
@options_list = options.values.join

options = Options.new({
:sPath => ‘s:’,
:dPath => ‘d:’,
:verbose => ‘v’,
})

If someone can tell me how to do this using OptParser, so be it. But
from an encapsulation standpoint, I’d like to be able to call it as I do
above. If the innards of the Options class use OptParser, fine.

Does this what you need?

11:15:19 Temp$ ./opt.rb -s abc.def – -x
{:source_path=>“abc.def”}
11:15:22 Temp$ ./opt.rb -s abc.def – -x
{:source_path=>“abc.def”}
[“-x”]
11:15:28 Temp$ ./opt.rb -s abc.def
{:source_path=>“abc.def”}
[]
11:15:31 Temp$ ./opt.rb -s abc.def -d .
{:source_path=>“abc.def”, :dest_path=>“.”}
[]
11:15:36 Temp$ ./opt.rb -s abc.def -d . -v
{:source_path=>“abc.def”, :dest_path=>“.”, :verbose=>true}
[]
11:15:39 Temp$ ./opt.rb -s abc.def -d . -v a b c
{:source_path=>“abc.def”, :dest_path=>“.”, :verbose=>true}
[“a”, “b”, “c”]

Kind regards

robert

On Wed, Sep 28, 2011 at 6:25 PM, Perl J. [email protected]
wrote:

Someone else posted a solution, and I appreciate it, but
no time to look at it.

Really? We’re talking about 20 lines for an implementation that uses
an option specification format pretty similar to your original format.

Thanks all. I think this has been completely cracked and solved.
Exactly as I would like plus a clear(er) OptionParser example.

Did you check the example in OptionParser’s documentation?

Kind regards

robert

David C. wrote in post #1023941:

I’m not sure where that syntax for OptionParser came, and Trollop is a
fine lib, but if you want to stick with just Ruby standard library
stuff:

require ‘optparse’

options = {}
opts = OptionParser.new do |opts|

opts.on(’-s SOURCE_PATH’,‘Source path’) do |path|
options[:source_path] = path
end
opts.on(’-d DEST_PATH’,‘Destination path’) do |path|
option[:dest_path] = path
end
opts.on(’-v’,‘Be verbose’) do
options[:verbose] = true
end
end
opts.parse!

puts “Source Path is: #{options[:source_path]”
puts “Dest Path is: #{options[:dest_path]”
puts “We are being verbose” if options[:verbose]

It’s a bit more code, but you also get built-in help:

$ my_app --help
Usage: my_app [options]

-s SOURCE_PATH   Source path
-d DEST_PATH       Dest path
-v                          Be verbose

You can use long-form options as well (e.g. --verbose)

Now that’s a better example of OptionParser and that could come in handy
some time. I may even switch over to it since now I want to present
syntax help. The version I posted works EXACTLY as I would like, so
that’s good. Someone else posted a solution, and I appreciate it, but
no time to look at it.

To the person who explained why I need to use .send(), thanks. That’s
very interesting and I’ll have to keep that in mind. That could come in
handy some day in some situations. I know that would drive a pure OO
person crazy, but I can see some uses for that.

Thanks all. I think this has been completely cracked and solved.
Exactly as I would like plus a clear(er) OptionParser example.

Cheers!
-pj
(soon, hopefully to be “rj”… RubyJunkie)

Robert K. wrote in post #1024201:

On Wed, Sep 28, 2011 at 6:25 PM, Perl J. [email protected]
wrote:

Someone else posted a solution, and I appreciate it, but
no time to look at it.

Really? We’re talking about 20 lines for an implementation that uses
an option specification format pretty similar to your original format.

I post a question, someone takes the time to answer it, but I don’t look
at it. Didn’t seem fair. :slight_smile:

I looked. I liked. Very nice. I like how you took the approach to
extend the existing class and it was simple yet elegant. Kudos. I’m
glad I looked today.

Thanks all. I think this has been completely cracked and solved.
Exactly as I would like plus a clear(er) OptionParser example.

Did you check the example in OptionParser’s documentation?

I did, but frankly, it still doesn’t make sense to me do it that way.
I’m used to defining the command line options as I have before and I get
a specific class instance like magic that just does what I want. I
didn’t want to build a specific class by hand, which is how OptionParser
still strikes me somewhat. But… this kind of thinking can be an
impediment to really learning something new – trying to do something in
one language “like” you did in another. So that has to be taken into
consideration. (Then again, another language can help pick and point
out unnecessary implementations in another language. This is why I have
Java, because it has such high, time-consuming overhead. Wanting to “do
it better” is what got Ruby started in the first place.)

I think your solution was a perfect middle ground. Thanks for the time
you took and it’s a great overall contribution to Ruby, doing it the
Ruby way, outside of just my question.

-pj