ANN: First release of Perl's Getopt::Declare for ruby

Okay, as a way of returning the favor for all my recent silly
questions, I’ve uploaded a first release of the ruby port of Damian
Conway’s Getopt::Declare (written originally for Perl).

It is located at:

http://rubyforge.org/projects/getoptdeclare/

Library has been tested on Windows only, albeit it should work on all
platforms.
Let me know how it goes and what could be improved, in either the
distribution, the code or whatever.

Getopt::Declare is yet another command-line argument parser, one which
is specifically designed to be powerful but exceptionally easy to use.
It supports many options not supported by optparser or getoptlong as
well as it has a simpler syntax.

To parse the command-line in +ARGV+, one simply creates a
Getopt::Declare object, by passing Getopt::Declare::new() a
specification of the various parameters that may be encountered:

args = Getopt::Declare.new(specification)

The specification is a single string such as this:

specification = %q(
    [tight]
	-a		Process all data

	-b <t:n>	Set mean byte length threshold to <t>
				{ bytelen = t }

	+c <FILE>	Create new file <FILE>

	--del 		Delete old file
				{ delold() }

	delete 		[ditto]

	e <h:i>x<w:i>	Expand image to height <h> and width <w>
				{ expand(h,w) }

	-F <file>...	Process named file(s)
				{ defer( proc { file.each {|i| 
                                               process(i) } } ) }

	=getrand [<n:i>] Get a random number
			 (or, optionally, <n> of them)
				{ n = 1 unless !n.empty? }

	--		Traditionally indicates end of arguments
				{ finish }
)

in which the syntax of each parameter is declared, along with a
description and (optionally) one or more actions to be performed
when
the parameter is encountered. The specification string may also
include other usage formatting information (such as group headings
or
separators) as well as standard Ruby comments (which are ignored).

Calling Getopt::Delare::new() parses the contents of the array
+ARGV+,
extracting any arguments which match the parameters defined in the
specification string, and storing the parsed values as hash elements
within the new Getopt::Declare object being created.

Other features of the Getopt::Declare package include:

  • The use of full Ruby regular expressions to constrain matching
    of parameter components.

  • Automatic generation of error, usage and version information.

  • Optional conditional execution of embedded actions (i.e. only on
    successful parsing of the entire command-line)

  • Strict or non-strict parsing (unrecognized command-line elements
    may either
    trigger an error or may simply be left in +ARGV+

  • Declarative specification of various inter-parameter relationships
    (for
    example, two parameters may be declared mutually exclusive and
    this
    relationship will then be automatically enforced).

  • Intelligent clustering of adjacent flags (for example: the
    command-line sequence “-a -b -c” may be abbreviated to “-abc”,
    unless
    there is also a -abc flag declared).

  • Selective or global case-insensitivity of parameters.

  • The ability to parse files (especially configuration files)
    instead of
    the command-line.

GGarramuno wrote:

Okay, as a way of returning the favor for all my recent silly
questions, I’ve uploaded a first release of the ruby port of Damian
Conway’s Getopt::Declare (written originally for Perl).

Darn, just when I’ve gotten in the habit of using optparse everywhere,
along comes this cool thing…

One suggestion: make it possible to apply to an arbitrary array, rather
than just ARGV (or is this already possible?).

Another suggestion, regarding:

  	-F <file>...	Process named file(s)
  				{ defer( proc { file.each {|i| 
                                               process(i) } } ) }

it’s probably possible to get the syntax to be:

					{ defer { file.each {|i|
                                                 process(i) } }

just by replacing

def defer(block) … end

with

def defer(&block) … end

One suggestion: make it possible to apply to an arbitrary array, rather
than just ARGV (or is this already possible?).

Yes, it’s possible, but you have to convert it to a space separated String
first (just use join ’ ').
If you pass an array, it is taken to be a list of files and will try to open
them for parsing the flags.

Just pass this string as the second optional parameter to new() or as the
optional parameter to parse().

I’ve updated the dist with a sample of it, too.

it’s probably possible to get the syntax to be:
defer { file.each {|i|

Indeed, it should be. That’s how the original perl one worked. Done.

I also fixed a mutex problem and some Perl references in the docs, too.

Put it back on rubyforge, as v1.09.2

Thanks.

Hi,

One suggestion: make it possible to apply to an arbitrary array, rather
than just ARGV (or is this already possible?).

Yes, it’s possible, but you have to convert it to a space separated String
first (just use join ’ ').

What about strings contain spaces?

And, you use this construnction several times, but I don’t
think you’d expect behavior as it works.

def foo(*args)
if defined?(args[0])
# …

Since args is defined here, so args[0] also is defined always.
Ruby’s defined?' operator differs from Perl's defined’
function.

BTW, I found a sample doesn’t work expectedly.

$ ruby …/samples/cmdline_pvtype.rb -blood AB Rh+
-blood => { ‘’ => “”, ‘’ => “A” }

···

At Fri, 9 Jan 2004 16:11:41 +0900, GGarramuno wrote:


Nobu Nakada

nobu.nokada@softhome.net wrote in message news:200401100221.i0A2LCr9008491@sharui.nakada.kanuma.tochigi.jp

Hi,

One suggestion: make it possible to apply to an arbitrary array, rather
than just ARGV (or is this already possible?).

Yes, it’s possible, but you have to convert it to a space separated String
first (just use join ’ ').

What about strings contain spaces?

I’d say pass them in quotes and then remove the quotes, but I know
that sucks.
I realize now that real issue is that ARGV in ruby is, unfortunately,
a read only variable and that’s why the issue does not come up in
perl. Hmmm… I’ll see how the interface can be changed to accomodate
this better perhaps. The code to add is simple, but have to figure
out what’s the nicest interface to it.

And, you use this construnction several times, but I don’t
think you’d expect behavior as it works.

def foo(*args)
if defined?(args[0])
# …

Since args is defined here, so args[0] also is defined always.
Ruby’s defined?' operator differs from Perl's defined’
function.

Yes, I should know better. This should only have effected the nocase
label adversely. The other places where it is used work regardless of
the if (there were mainly perl speed optimizations which I don’t know
if they are still such in ruby).
I’ll put up a new version once I think of a new interface to pass
arbitrary arrays. I’ll send an email when done.

BTW, I found a sample doesn’t work expectedly.

$ ruby …/samples/cmdline_pvtype.rb -blood AB Rh+
-blood => { ‘’ => “”, ‘’ => “A” }

yes, it seems broken in the original perl demo code (actually, the
original perl code does not even compile). The blood type is defined
in the wrong order. It should be AB|[OAB] instead of [OAB]|AB else
the first condition takes precedence in the regex, as it should. Or
are there other blood types? Medicine ain’t my strong point, I admit.

···

At Fri, 9 Jan 2004 16:11:41 +0900, > GGarramuno wrote:

One suggestion: make it possible to apply to an arbitrary array, rather
than just ARGV (or is this already possible?).

Okay, I added the option to parse() and new() to use:

[ ‘-ARGV’, array ]

just like -CONFIG and so.
With that, the parameter parsing will be taken from array instead of ARGV.
See samples/cmdline_noargv.rb for a full example.

Thanks for bringing up the issue.

GGarramuno wrote:

I realize now that real issue is that ARGV in ruby is, unfortunately,
a read only variable (…)

It is read only, but not frozen, so you can modify it. For example:
ARGV.clear
ARGV.shift
etc.

Regards,
Pit