Question about GetoptLong

I’m trying to recreate a Perl script in Ruby and have run into a problem.
The perl script has several layered subroutines. The main entry point
has code like this:

Getopt::Long::Configure(pass_through, no_auto_abbrev);
GetOptions('logfile' => \$logfile, 'log' => \$log, 'leave_tempfiles' 

=> $leave_tempfiles, ‘batch_length=i’ => $batch_length);
Getopt::Long::Configure(no_pass_through, auto_abbrev);

This sets some global flags that the user may (optionally) set on the
command line. Other subroutines called after this perform a straight
GetOptions call
ie: GetOptions(“a” => $added, “e” => $edited, “d” => $deleted,
“c” => $change);

Well, with Ruby, if I try to have my entry code scan the command-line
arguments for a --log / -log argument, then when it sees say, a '-c’
instead, it raises an exception (which I can toss) since it’s not part
of that GetoptLong argument array.
That’s all fine and well, but unfortunately, it’s also removed the
argument from ARGV at this point, so the next method that wants to scan
for >its< command-line arguments (perhaps to parse the -c) won’t have
anything there.
Additionally, the exception thrown by GetoptLang doesn’t even contain
information about what didn’t match (or what was removed from ARGV), so
that I can add it back into argv.

Any suggestions on how I can implement ‘passthrough’ command-line
arguments with Ruby?

Thanks,
Patrick Bennett

I use getoptlong alot, but I’m not quite sure I understand your problem.
Can you provide a short example?

···

On Wed, Jul 31, 2002 at 01:31:07PM +0900, Patrick Bennett wrote:

I’m trying to recreate a Perl script in Ruby and have run into a problem.
The perl script has several layered subroutines. The main entry point
has code like this:

Getopt::Long::Configure(pass_through, no_auto_abbrev);
GetOptions('logfile' => \$logfile, 'log' => \$log, 'leave_tempfiles' 

=> $leave_tempfiles, ‘batch_length=i’ => $batch_length);
Getopt::Long::Configure(no_pass_through, auto_abbrev);

This sets some global flags that the user may (optionally) set on the
command line. Other subroutines called after this perform a straight
GetOptions call
ie: GetOptions(“a” => $added, “e” => $edited, “d” => $deleted,
“c” => $change);

Well, with Ruby, if I try to have my entry code scan the command-line
arguments for a --log / -log argument, then when it sees say, a '-c’
instead, it raises an exception (which I can toss) since it’s not part
of that GetoptLong argument array.
That’s all fine and well, but unfortunately, it’s also removed the
argument from ARGV at this point, so the next method that wants to scan
for >its< command-line arguments (perhaps to parse the -c) won’t have
anything there.
Additionally, the exception thrown by GetoptLang doesn’t even contain
information about what didn’t match (or what was removed from ARGV), so
that I can add it back into argv.

Any suggestions on how I can implement ‘passthrough’ command-line
arguments with Ruby?


Jim Freeze
If only I had something clever to say for my comment…
~

Jim Freeze wrote:

I use getoptlong alot, but I’m not quite sure I understand your problem.
Can you provide a short example?

I thought my message was fairly self-explanatory, but I guess not. Here
’ya go.

···

I want the user to be able to specify:
somecommand.rb -l -c (other args)

and I have two methods (each only caring about certain arguments)

def method1
opts = GetoptLong.new( [ “-l”, GetoptLong::NO_ARGUMENT] )

    opts.each{ |opt,arg| ..... }

end

(called later)
def method2
opts = GetoptLong.new( [ “-c”, GetoptLong::NO_ARGUMENT] )

    opts.each{ |opt,arg| ..... }

end

When method1 is called it will raise an exception when it runs into the
first argument it doesn’t understand (the -c argument), and remove it
from ARGV at the same time.
Later, when method2 is called, the argument(s) it cared about are no
longer there to parse.

Additionally, while I’m on the subject of GetoptLong - in Perl, if I
define an option as ‘logfile’ then getopts will accept --l, --lo, --log,
–logf, --logfi, etc. as well as -l, -log, etc.
Ruby’s GetoptLong only allows long names if the user explicitly
specifies --xxxx. A minor (but somewhat annoying) nit.

Jim Freeze wrote:

I use getoptlong alot, but I’m not quite sure I understand your problem.
Can you provide a short example?

I thought my message was fairly self-explanatory, but I guess not. Here
’ya go.

I want the user to be able to specify:
somecommand.rb -l -c (other args)

and I have two methods (each only caring about certain arguments)

def method1
opts = GetoptLong.new( [ “-l”, GetoptLong::NO_ARGUMENT] )

    opts.each{ |opt,arg| ..... }

end

(called later)
def method2
opts = GetoptLong.new( [ “-c”, GetoptLong::NO_ARGUMENT] )

    opts.each{ |opt,arg| ..... }

end

Ok, now I see. My question is why do you call it twice?
In all of my usage, I collect all the arguments up front.
So, for the above I would do:

def get_options
opts = GetoptLong.new(
[ “–log”, “-l”, GetoptLong::NO_ARGUMENT ],
[ “–cxyz”, “-c”, GetoptLong::NO_ARGUMENT ]
)

opts.each do |opt, arg|
  case opt
    when "--log"
      ...do log setup
      @do_log = true
    when "--cxyz"
      ...do cxyz stuff
    else
      raise "Invalid option '#{opt}'"
  end#case
end#each

raise "some error" unless ARGV.size == whatItShouldBe

file = ARGV.shift
...

end

Then, get_options is called at app launch.

class App
def initialize
get_optioins
rescue => err
STDERR.puts err
STDERR.puts usage
exit 1
end

end

App.new.run

When method1 is called it will raise an exception when it runs into the
first argument it doesn’t understand (the -c argument), and remove it
from ARGV at the same time.
Later, when method2 is called, the argument(s) it cared about are no
longer there to parse.

Additionally, while I’m on the subject of GetoptLong - in Perl, if I
define an option as ‘logfile’ then getopts will accept --l, --lo, --log,
–logf, --logfi, etc. as well as -l, -log, etc.
Ruby’s GetoptLong only allows long names if the user explicitly
specifies --xxxx. A minor (but somewhat annoying) nit.

I agree that it has its limitations, but they have not been sufficient
for me to take the time to do anything about them.
One nice feature I would like to see is support of multiple
options in short form such as in tar:

tar xzvf blah blah

instead of

tar -x -z -v -f blah blah

Jim

···

On Wed, Jul 31, 2002 at 02:17:13PM +0900, Patrick Bennett wrote:

Jim Freeze
If only I had something clever to say for my comment…
~

Jim Freeze wrote:

Ok, now I see. My question is why do you call it twice?

Because I have separate methods/classes that are standalone (OO
remember? ;>) ‘sub-commands’ for an outer shell program.
xxx.rb “command” (global args) (args)
Each class or method is more or less independent of the other and they
each have arguments that only mean something to them.

To be specific, this is a wrapper script for perforce. Perforce has a
command-line program called ‘p4’ that takes a command followed by
multiple arguments (as well as some ‘global’ arguments).
I need to handle perforce native commands by passing through
commands/arguments onto the regular p4 command (where appropriate).
I also need to handle my ‘extended’ commands that have been created for
our users. These commands act just like the regular p4 commands, so
p dircheck -a -e -c -d --log
would go through several layers
one would need to pick off the --log (a global option) so that
diagnostic logging could be enabled.
the rest (in this case) would be handled by the dircheck method which
would pull off the -a -e -c options.

In all of my usage, I collect all the arguments up front.
So, for the above I would do:

If I did that, then I’d end up with an unmaintainable mess
unfortunately. Each ‘command’ may be a relatively complex set of
actions with its own completely independent set of options.
-c for one command might not take any arguments. For another it might

···

require< an argument. Specifying them all in one place isn’t feasible.

Hi,

···

At Wed, 31 Jul 2002 14:47:17 +0900, Patrick Bennett wrote:

Because I have separate methods/classes that are standalone (OO
remember? ;>) ‘sub-commands’ for an outer shell program.
xxx.rb “command” (global args) (args)
Each class or method is more or less independent of the other and they
each have arguments that only mean something to them.

So set opts.ordering to GetoptLong::REQUIRE_ORDER, and make new
GetoptLong instance according to the remained first argument.


Nobu Nakada

Hmm, I would not consider many of the options very pretty.
If you know the possible arguments that is good. If you
know what parameters they take, that is even better.
In such a case, you can go ahead and tell the wrapper about
these arguments.
If you don’t know the number of arguments, then you could
list the options as having optional arguments.

Then, in the sample I gave earlier, I added the following:

not_handled = []
opts.each do |opt, arg|
case opt
## We handle log
when "–log"
puts “parsing: #{opt}: #{arg}”
## We pass cc
else
## Unknown option (probably need some error checking here)
not_handled << [opt, arg]
end#case
end#each

When your done, add the not_handled array back to ARGV

ARGV.unshift(not_handled).flatten!

···

On Wed, Jul 31, 2002 at 02:47:17PM +0900, Patrick Bennett wrote:

Jim Freeze wrote:

To be specific, this is a wrapper script for perforce. Perforce has a
command-line program called ‘p4’ that takes a command followed by
multiple arguments (as well as some ‘global’ arguments).
I need to handle perforce native commands by passing through
commands/arguments onto the regular p4 command (where appropriate).
I also need to handle my ‘extended’ commands that have been created for
our users. These commands act just like the regular p4 commands, so
p dircheck -a -e -c -d --log
would go through several layers
one would need to pick off the --log (a global option) so that
diagnostic logging could be enabled.
the rest (in this case) would be handled by the dircheck method which
would pull off the -a -e -c options.

In all of my usage, I collect all the arguments up front.
So, for the above I would do:

If I did that, then I’d end up with an unmaintainable mess
unfortunately. Each ‘command’ may be a relatively complex set of
actions with its own completely independent set of options.
-c for one command might not take any arguments. For another it might

require< an argument. Specifying them all in one place isn’t feasible.


Jim Freeze
If only I had something clever to say for my comment…
~

Sorry, that won’t work.
Even with REQUIRE_ORDER GetoptLong still eats the first non-option it
hits so further processing won’t see that argument.
That, plus, the ordering of the arguments isn’t always going to be
conveniently in the ‘right order.’

···

nobu.nokada@softhome.net wrote:

So set opts.ordering to GetoptLong::REQUIRE_ORDER, and make new
GetoptLong instance according to the remained first argument.

Then, in the sample I gave earlier, I added the following:

not_handled = []
opts.each do |opt, arg|
case opt
## We handle log
when "–log"
puts “parsing: #{opt}: #{arg}”
## We pass cc
else
## Unknown option (probably need some error checking here)
not_handled << [opt, arg]
end#case
end#each

When your done, add the not_handled array back to ARGV

ARGV.unshift(not_handled).flatten!

Reasonable idea, but… it still won’t work since GetoptLong (wrongly
so IMO) raises an exception whenever it sees something it doesn’t
understand, as well as eating the ‘non-option’ it just parsed - which in
my case would most likely be an option somebody else is going to care about!
If you could defeat GetoptLong’s desire to raise an exception (and eat
the option/non-option from ARGV) on every thing it didn’t understand,
then something ‘might’ be workable.
Either way, it’s frustrating to have something be so trivial in Perl,
yet be a major PITA in Ruby. sigh

(in global settings)
Getopt::Long::Configure(pass_through, no_auto_abbrev);
GetOptions(‘logfile’ => $logfile, ‘log’ => $log, ‘leave_tempfiles’
=> $leave_tempfiles, ‘batch_length=i’ => $batch_length);
Getopt::Long::Configure(no_pass_through, auto_abbrev);

(in ‘one’ of the local commands)
GetOptions(‘v’ => $integ_option_v, ‘quiet’ => $quiet, ‘noop’ =>
$noop, ‘force’ => $force, ‘team_project=s’ => $team_project);
[note that it supports ‘types’ for options with arguments as well]

If I was a more experienced Ruby programmer, I might look at fixing
GetoptLong, but right now, I’m just trying to port a working Perl
script, and I’m running into some unexpected roadblocks over things I
thought would be a non-issue. :<

Hi Patrick,

I can think of two approaches to your problem:

  1. call GetoptLong in the beginning of the program with all possible
    options, store them in a hash, and pass the hash to the rest of the
    program

  2. write a wrapper around the useage of GetoptLong that preserves
    ARGV, something like (not tested)

def with_ARGV_saved
saved_ARGV = ARGV.dup
yield
ensure
ARGV.clear.concat saved_ARGV
end

Regards,
Pit

···

On 31 Jul 2002, at 15:13, Patrick Bennett wrote:

Sorry, that won’t work.
Even with REQUIRE_ORDER GetoptLong still eats the first non-option it
hits so further processing won’t see that argument. That, plus, the
ordering of the arguments isn’t always going to be conveniently in the
’right order.’

nobu.nokada@softhome.net wrote:

So set opts.ordering to GetoptLong::REQUIRE_ORDER, and make new
GetoptLong instance according to the remained first argument.

Hi,

Sorry, that won’t work.
Even with REQUIRE_ORDER GetoptLong still eats the first non-option it
hits so further processing won’t see that argument.

AFAIK, GetoptLong never eat non-option arguments regardless
ordering mode.

What about this?

require ‘getoptlong’

$suboptions = {}
class Foo
attr_reader :foo
def parse_option(opt, arg)
case opt
when “–foo”
@foo = arg
end
end
$suboptions[‘foo’] = [
self,
GetoptLong.new(["–foo", GetoptLong::REQUIRED_ARGUMENT])
]
end

class Bar
attr_reader :bar
def parse_option(opt, arg)
case opt
when “–bar”
@bar = arg
end
end
$suboptions[‘bar’] = [
self,
GetoptLong.new(["–bar", GetoptLong::OPTIONAL_ARGUMENT]),
]
end

$logfile = nil
opts = GetoptLong.new(["-l", “–logfile”, GetoptLong::NO_ARGUMENT],
[ “-c”, GetoptLong::NO_ARGUMENT])
opts.ordering = GetoptLong::REQUIRE_ORDER
opts.each do |opt, arg|
case opt
when "-c"
opts.terminate
when “-l”
$logfile = true
end
end
raise “missing command” if ARGV.empty?
command, opts = $suboptions[ARGV[0]]
command or raise "unknown command: #{ARGV[0]}"
ARGV.shift
cmd = command.new
opts.each(&cmd.method(:parse_option))
p :$logfile => $logfile, :cmd => cmd

That, plus, the ordering of the arguments isn’t always going to be
conveniently in the ‘right order.’

Don’t you want to separate arguments by the order, before
subcommand and after?

···

At Wed, 31 Jul 2002 15:13:19 +0900, Patrick Bennett wrote:


Nobu Nakada

Yes. This method only works if you know all possible command options.

Jim

···

On Wed, Jul 31, 2002 at 03:31:51PM +0900, Patrick Bennett wrote:

Then, in the sample I gave earlier, I added the following:

not_handled = []
opts.each do |opt, arg|
case opt
## We handle log
when "–log"
puts “parsing: #{opt}: #{arg}”
## We pass cc
else
## Unknown option (probably need some error checking here)
not_handled << [opt, arg]
end#case
end#each

When your done, add the not_handled array back to ARGV

ARGV.unshift(not_handled).flatten!

Reasonable idea, but… it still won’t work since GetoptLong (wrongly
so IMO) raises an exception whenever it sees something it doesn’t
understand, as well as eating the ‘non-option’ it just parsed - which in


Jim Freeze
If only I had something clever to say for my comment…
~

def with_ARGV_saved
saved_ARGV = ARGV.dup
yield
ensure
ARGV.clear.concat saved_ARGV

FYI: There is Array#replace instead of the idiom above,

  ARGV.replace(saved_ARGV)

end

And builtin container classes have #replace.

···

At Wed, 31 Jul 2002 16:16:10 +0900, Pit Capitain wrote:

Pit Capitain wrote:

Hi Patrick,

I can think of two approaches to your problem:

  1. call GetoptLong in the beginning of the program with all possible
    options, store them in a hash, and pass the hash to the rest of the
    program

Not possible since some of the arguments have different meanings (and
requirements) depending on the command interpreting it.
p.rb dircheck -c -a -e
p.rb promote -c 550
-c takes no arguments in one case, and is required in another. No can do.
That, plus it severely reduces the maintainability of the code by
forcing any changes in any sub-class / method and its 'per-command’
command-line options
to also be maintained in one central location. Yuck. :<

  1. write a wrapper around the useage of GetoptLong that preserves
    ARGV, something like (not tested)

def with_ARGV_saved
saved_ARGV = ARGV.dup
yield
ensure
ARGV.clear.concat saved_ARGV
end

So far, I’ve implemented a similar hack (and a hack it truly is) to work
around the problem in the very short term - but I really like your block
approach to preserving argv. I might be able to get something to work
there.
The only problem is, I still need to remove the options that each layer
actually matched on. :\

AFAIK, GetoptLong never eat non-option arguments regardless
ordering mode.

Well with Ruby 1.6.6 it does. If I have XXXX -c -a and the GetoptLong
is set to only understand -c, then -a gets eaten.

What about this?

(snipped elegant solution - but one that still doesn’t quite do what the
perl code does. :<)
Thanks for the message and code!
Unfortunately this solution will require the users to enter 'global’
options before the “command” (always), but at this point I think it’s
something I may have to live with.
sigh

Thanks for all the messages everybody. Hopefully my plight has pointed
out some of the deficiencies in the GetoptLong class.
Raising exceptions and eating arguments for one thing is bad, bad, bad -
it could at least be a gentleman about it and tell us [via the exception
class] what it just ate!
Anyway, perhaps once my Ruby skills improve, I’ll create a new version
that is more flexible and send it out.

···

nobu.nokada@softhome.net wrote:

out some of the deficiencies in the GetoptLong class.
Raising exceptions and eating arguments for one thing is bad, bad,
bad -

Consuming all the options so they are out of the way is a feature
of the Getopt utils that I have seen, it means you can just use
ARGF and friends to plough through the supplied files.

Anyway, perhaps once my Ruby skills improve, I’ll create a new
version that is more flexible and send it out.

So, I’m suggesting “have ‘eat the options’ as an on/off feature”.

    Hugh
···

On Thu, 1 Aug 2002, Patrick Bennett wrote:

Hi,

AFAIK, GetoptLong never eat non-option arguments regardless
ordering mode.

Well with Ruby 1.6.6 it does. If I have XXXX -c -a and the GetoptLong
is set to only understand -c, then -a gets eaten.

You need to call GetoptLong#terminate seeing “-c”.

Unfortunately this solution will require the users to enter 'global’
options before the “command” (always), but at this point I think it’s
something I may have to live with.

Then, you need to remember global options, and append
per-command options to them.

Alternatively, you can use optparse.rb, it allows option
definitions at multiple places.

···

At Thu, 1 Aug 2002 00:19:58 +0900, Patrick Bennett wrote:


Nobu Nakada

Hugh Sasse Staff Elec Eng wrote:

Consuming all the options so they are out of the way is a feature
of the Getopt utils that I have seen, it means you can just use
ARGF and friends to plough through the supplied files.

It certainly makes sense for it to eat the options it understands, but I
don’t see any reason why it should (always) remove options it doesn’t
understand and provide no way of getting the option back for later
processing.

So, I’m suggesting “have ‘eat the options’ as an on/off feature”.

That’s certainly a start. :>
That, plus the exception that GetoptLong throws (I think the fact that
it raises exceptions should be optional as well) should contain an
attribute with the option that it choked on.

Using the rubycentral/1.6.6 install of Ruby, I’m having problems reading
from $stdin in some cases.

ie:
test.rb

···

$stdin.each { |line|
puts “STDIN:#{line}”
}


c:> test.rb < SomeFile
D:\ruby\samples\test.rb:1:in `each’: Bad file descriptor (Errno::EBADF)
from D:\ruby\samples\test.rb:1

however, if I run it like:
c:> ruby test.rb < SomeFile
STDIN:a
STDIN:b
STDIN:c
STDIN:d

then it works. Unfortunately, all of the users of my (upcoming) scripts
will be running the scripts by simply typing the name of the script
(without .rb), and windows (via the PATHEXT extensions) will
automatically treat it as an executable and call ruby for that argument.
Is it not possible to read from stdin when the programs are launched
this way? Any ideas on why it doesn’t work?

I suppose, worst case, I can create a .cmd file
test.cmd

@ruby xxxxx.rb %*

which seems to work, but I’d prefer to know what the problem really is.

Is there any way (windows / Ruby 1.6.6) to disable the automatic
filename expansion?
If a user passes a wildcard, I need it to be passed through as-is.

ie:
test.rb

···

ARGV.each{ |val| puts “Arg:#{val}” }


c:> test.rb *

Arg:test.rb
Arg:FXRuby
Arg:hello.rb
Arg:opengl
Arg:tk

This can certainly be a handy feature, but with no way to turn it off,
it’s posing a real problem for me.

Thanks,
Patrick Bennett