Previous | ToC | Up | Next |
Alice: Have you tested the whole parsing mechanism that you have written?
Bob: Yes, and as far as I can see, it all works as advertised. At first,
while I was writing the Clop class, I put the definition of the class,
as well as the definition of the helper class Clop_Option, in the
file clop.rb, and I used a different file to test the whole
thing. That other file contained a driver with a long `here document',
and a call to parse_command_line, the only method that is used
to let Clop do its work.
But after a while, it occurred to me that that I might as well add the
driver to the end of the clop.rb file, in a clever way. You see,
the reason I did not do it right away, is that I did not want to prevent
another program from including the clop.rb file. A typical
application program, such as an N-body code, can include a line at the
top that reads:
Alice: And you found a solution, to have your cake and eat it, that is
to include the driver part and yet make it invisible for the application
program?
Bob: How did you guess! That is exactly what I did. The trick was to
add the following statement, after the definition of the classes and
everything else that has to be included in the application program,
and before the start of the driver block:
The key here is that the variable _FILE_ always gets the
value of the file in which it occurs. So the variable _FILE_
in the file clop.rb will always get the content "clop.rb",
independently of whether you run clop.rb directly, or whether you
run another program that includes a require statement for clop.rb.
Alice: Therefore, only when you run clop.rb directly, with the
explicit command ruby clop.rb, is the equality guaranteed. Clever
indeed! So if that is what you did, can I try it?
Bob: Please do!
Alice: I will start without any options, to see what happens:
Bob: In defining the option blocks, you can specify the default value
none, which means that no default is given, which in turn means that the
user should provide one. I included various options in the here
document of my test driver, and two of them I gave the the default value
none.
The fact that you see so much output is a result of the action of the method
check_required_options, which prints out the whole long description,
to tell you in detail what type of options you should minimally provide.
Alice: Let me see what happens if I provide the first option:
Bob: Thanks! I like to get such details straight. A matter of
craftsmanship, as they used to call it. Why don't you add the second
option too? No output file will be created here; it is just a test.
Alice: Okay, this should stop the complaints:
Bob: Glad you like it! How about trying out a boolean value?
Alice: Sure. Let's see. But, wait a minute. All boolean values by default
are set to false. And boolean options are only reported in the initial state
printout if they are true. So we have a catch 22 here: if I don't
give the boolean options, they will never appear, so how do I know
how to ask for those options. How do I find out what their command
line names are?
Bob: Ahem.
Alice: You're not going to help me and give me a hint at least?
Bob: Going to help you?
Alice: Ah, of course, the help facility. I can ask the program itself.
Okay:
Bob: Try it!
Alice: By just adding -x at the end, yes? That's easy,
as long as I keep remembering to add the required -n and -o
options as well.
Bob: Don't worry, the program will remind you if you don't.
Alice: Let there be boolean initial state output:
Bob: How about shifting the velocity of the center of mass?
Alice: With -v or --shift_velocity, I see from the
short help output. Let me try the longer form. I see that the default
value for this vector has three components. Am I allowed to work
in two dimensions as well, for a three-body scattering experiment in a
plane, say, or do you insist on working in three dimensions, in which
case I could set the third component equal to zero?
Bob: Why don't you ask the program?
Alice: You're getting mischievous. Ask the program? Ah, with the
long version of help perhaps? Will that tell me?
Bob: Remember, if you type --help --some_option, you will
get a long form of help for that option.
Alice: Let me try:
Bob: I can't guarantee that all my programs will be that helpful, but
at least I made a start here.
Alice: Here is my attempt at a two-dimensional shift:
Bob: Why don't you try to play with the type I introduced for classifying
stars. I added that as another test for the whole concept of a general
parser.
Alice: Classifying stars? Okay, I got it now: I won't ask you any questions
anymore, I'll just ask the program. First I have to find which option
I should be dealing with:
The next step is to find out more about this particular option:
Hey, what happened? Your long help facility must have given me the wrong
answer! I tried one of the examples given there.
Bob: Not quite.
Alice: What do you mean? I did not make any spelling mistake: here it is:
star_type is spelled correctly, and so is star. And I
thought I had a freedom in choosing whatever else would follow.
Bob: Why don't you look at what the error message is telling you.
Alice: You really want the program to help me, and I must admit, it would
be an accomplishment if your error messages would tell me what went wrong,
without you doing the hand holding. Let's see. The program complains that
the option : is not recognized. But you told me that the non-greedy
operator would take care of any and all extra colons!
Bob: No comment.
Alice: Okay, okay, I'll struggle on. The first : I gave was the
one following the word star. I have a space between star and :,
but that space also occurred in the example suggested by the long help output.
Still, I have to do something, wiggle some wires somewhere, to learn
more, so let me write the same thing without a space, just to see whether
that makes a difference.
I'm still in the dark. Let me look again at the error message. I can just
add a space somewhere, and I'm sure the program will complain again:
9. A Built-In Test Facility
9.1. Testing Without a Driver
require "clop.rb"
And if the file clop.rb includes a test driver at the end, that
driver will be included in the application file, which is not what we want.
if _FILE_ == $0
Here the global variable $0 contains the name of the program
that you are running. If you run the clop.rb file directly,
by typing:
|gravity> ruby clop.rb
then Ruby will give the string "clop.rb" to the variable $0.
However, if you include the line
require "clop.rb"
in a file called some_other_program.rb, the value of $0
will be the string "some_other_program.rb".
9.2. Required Options
|gravity> ruby clop.rb
option "-n" or "--number_of_particles" required. Description:
Number of particles in an N-body snapshot.
option "-o" or "--output_file_name" required. Description:
Name of the snapshot output file.
The snapshot contains the mass, position, and velocity values
for all particles in an N-body system.
Please provide the required command line options.
Wow, that's a lot more than I expected. Where did that all come from?
|gravity> ruby clop.rb -n3
option "-o" or "--output_file_name" required. Description:
Name of the snapshot output file.
The snapshot contains the mass, position, and velocity values
for all particles in an N-body system.
Please provide the required command line option.
It still complains about the other option, as it should. And it even knows
about English grammar. Look: it talks at the end about the missing `option'
rather than the missing `options', as it did when two options were missing.
Nice touch.
|gravity> ruby clop.rb -n3 -o tmp.out
Command line option parser
Softening length : eps = 0.0
Time to stop integration : t = 10.0
Number of particles : N = 3
Shifts center of mass velocity : vcom =
3.04.05.0
Name of the outputfile : tmp.out
Star type : star_type = star: MS
Ah, the whole list of default options, and on top a one liner that describes
what this program is doing. And everything is lined up perfectly.
Indeed a matter of craftmanship, I would say.
9.3. A Boolean Option
|gravity> ruby clop.rb -h
Command line option parser
-s --softening_length : Softening length [default: 0.0]
-t --end_time : Time to stop integration [default: 10]
-n --number_of_particles: Number of particles [default: none]
-x --extra_diagnostics : Extra diagnostics
-v --shift_velocity : Shifts center of mass velocity [default: [3, 4, 5]]
-o --output_file_name : Name of the outputfile [default: none]
--star_type : Star type [default: star: MS]
Ah, there it is: our good friend --extra_diagnostics, or simply
-x. That must be a boolean option.
|gravity> ruby clop.rb -n3 -o tmp.out -x
Command line option parser
Softening length : eps = 0.0
Time to stop integration : t = 10.0
Number of particles : N = 3
Extra diagnostics
Shifts center of mass velocity : vcom =
3.04.05.0
Name of the outputfile : tmp.out
Star type : star_type = star: MS
And so there is! This is fun.
9.4. A Vector Option
|gravity> ruby clop.rb --help --shift_velocity
-v --shift_velocity : Shifts center of mass velocity [default: [3, 4, 5]]
The center of mass of the N-body system will be shifted by this amount.
If the vector has fewer components than the dimensionality of the N-body
system, zeroes will be added to the vector.
If the vector has more components than the dimensionality of the N-body
system, the extra components will be disgarded.
Now that is impressive. It answers all that I wanted to know and more!
|gravity> ruby clop.rb -o tmp.out -n 3 --shift_velocity [2, 3]
Command line option parser
Softening length : eps = 0.0
Time to stop integration : t = 10.0
Number of particles : N = 3
Shifts center of mass velocity : vcom =
2.03.0
Name of the outputfile : tmp.out
Star type : star_type = star: MS
And it behaves as I expected it to.
9.5. A Star Type Option
|gravity> ruby clop.rb -h
Command line option parser
-s --softening_length : Softening length [default: 0.0]
-t --end_time : Time to stop integration [default: 10]
-n --number_of_particles: Number of particles [default: none]
-x --extra_diagnostics : Extra diagnostics
-v --shift_velocity : Shifts center of mass velocity [default: [3, 4, 5]]
-o --output_file_name : Name of the outputfile [default: none]
--star_type : Star type [default: star: MS]
I see: star_type. And this happens to be an example of an option
that does not have a short name. After all, both -s and -t
are taken already, and rather than inventing an unmemorable name like
-q for the star type, it makes more sense to stick to a longer
name that has a clearer meaning. I completely agree.
|gravity> ruby clop.rb --help --star_type
--star_type : Star type [default: star: MS]
This options allows you to specify that a particle is a star, of a
certain type T, and possibly of subtypes t1, t2, ..., tk by specifying
--star_type "star: T: t1: t2: ...: tk". The ":" separators are allowed
to have blank spaces before and after them.
Examples: --star_type "star: MS"
--star_type "star : MS : ZAMS"
--star_type "star: giant: AGB"
--star_type "star:NS:pulsar:millisecond pulsar"
And here we have an example of allowing extra colons in the content of the
definition of an option, as you discussed when you introduced the concept
of a non-greedy operator in a regular expression. Let me try one of the
examples:
|gravity> ruby clop.rb -o tmp.out -n 3 --star_type star : MS : ZAMS
clop.rb:143:in `parse_command_line_options': (RuntimeError)
option ":" not recognized; try "-h" or "--help"
from clop.rb:115:in `initialize'
from clop.rb:267:in `new'
from clop.rb:267:in `parse_command_line'
from clop.rb:377
9.6. No Comment
|gravity> ruby clop.rb -o tmp.out -n 3 --star_type star: MS : ZAMS
clop.rb:143:in `parse_command_line_options': (RuntimeError)
option "MS" not recognized; try "-h" or "--help"
from clop.rb:115:in `initialize'
from clop.rb:267:in `new'
from clop.rb:267:in `parse_command_line'
from clop.rb:377
Now it complains that option MS is not recognized. Hmmm. What would
happen if I don't give any spaces at all?
|gravity> ruby clop.rb -o tmp.out -n 3 --star_type star:MS:ZAMS
Command line option parser
Softening length : eps = 0.0
Time to stop integration : t = 10.0
Number of particles : N = 3
Shifts center of mass velocity : vcom =
3.04.05.0
Name of the outputfile : tmp.out
Star type : star_type = star:MS:ZAMS
Hey, now it works! And the star type is being assigned correctly.
It seems that adding a space somewhere triggers a protest, and the
specific protest is that whatever comes after the first space is not
recognized.
|gravity> ruby clop.rb -o tmp.out -n 3 --star_type star:MS :ZAMS
clop.rb:143:in `parse_command_line_options': (RuntimeError)
option ":ZAMS" not recognized; try "-h" or "--help"
from clop.rb:115:in `initialize'
from clop.rb:267:in `new'
from clop.rb:267:in `parse_command_line'
from clop.rb:377
Hmmmm. option ":ZAMS" not recognized. AAH!! It is an
option that is not recognized. The command line parser receives the
command line in a form that is already cut up wherever there is white space.
So only the star:MS is considered to be the value of the option
--star_type and whatever comes next would normally be a new option,
starting with a hyphen, and more than that, an option that can be recognized
by the program. And :ZAMS is not even an option.
Previous | ToC | Up | Next |