cmd 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/AUTHORS +1 -0
- data/CHANGELOG +5 -0
- data/INSTALL +11 -0
- data/MIT-LICENSE +20 -0
- data/README +411 -0
- data/Rakefile +141 -0
- data/THANKS +12 -0
- data/TODO +124 -0
- data/example/calc.rb +86 -0
- data/example/phonebook.rb +69 -0
- data/lib/cmd.rb +557 -0
- data/setup.rb +1360 -0
- data/test/tc_cmd.rb +284 -0
- metadata +67 -0
data/AUTHORS
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
Marcel Molina Jr. -- http://vernix.org/marcel/
|
data/CHANGELOG
ADDED
data/INSTALL
ADDED
data/MIT-LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2005 Marcel Molina Jr.
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README
ADDED
@@ -0,0 +1,411 @@
|
|
1
|
+
= Cmd
|
2
|
+
|
3
|
+
== What is Cmd?
|
4
|
+
|
5
|
+
cmd is a library for building line-oriented command interpreters in Ruby.
|
6
|
+
Simply inherit from cmd's Cmd class, and methods whose names start with +do_+
|
7
|
+
become interactive commands. cmd is inspired by the
|
8
|
+
{Python library}[http://docs.python.org/lib/module-cmd.html] of the same name, but
|
9
|
+
offers a distinctive Ruby feel and several additional features.
|
10
|
+
|
11
|
+
== An Example
|
12
|
+
|
13
|
+
Consider the following example of a small program to manage a lightweight phone
|
14
|
+
book.
|
15
|
+
|
16
|
+
We want to be able to add, find, list and delete phone book entries.
|
17
|
+
|
18
|
+
We are keeping it realy simple so the entries will be stored in a Hash with
|
19
|
+
names as keys and numbers as values. Let's assume that <tt>@numbers</tt> is
|
20
|
+
our hash. First we'll write a command to add an entry. Entries will be entered
|
21
|
+
like so:
|
22
|
+
|
23
|
+
PhoneBook> add Sam, 312-555-1212
|
24
|
+
|
25
|
+
So we define a +do_add+ method.
|
26
|
+
|
27
|
+
def do_add(args)
|
28
|
+
name, number = args.to_s.split(/, +/)
|
29
|
+
@numbers[name.strip] = number
|
30
|
+
end
|
31
|
+
|
32
|
+
We add another entry for good measure.
|
33
|
+
|
34
|
+
PhoneBook> add Amy, 227-328-2868
|
35
|
+
|
36
|
+
We make a +print_name_and_number+ method to format our entries.
|
37
|
+
|
38
|
+
protected
|
39
|
+
|
40
|
+
def print_name_and_number(*args)
|
41
|
+
puts "%-25s %s" % args
|
42
|
+
end
|
43
|
+
|
44
|
+
Writing a command to list all numbers is straightforward:
|
45
|
+
|
46
|
+
def do_list
|
47
|
+
@numbers.sort.each do |name, number|
|
48
|
+
print_name_and_number(name, number)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
We run our +list+ command:
|
53
|
+
|
54
|
+
PhoneBook> list
|
55
|
+
Amy 227-328-2868
|
56
|
+
Sam 312-555-1212
|
57
|
+
|
58
|
+
Then we write a find command to get the number for a given person.
|
59
|
+
|
60
|
+
def do_find(name)
|
61
|
+
name.to_s.strip!
|
62
|
+
if @numbers[name]
|
63
|
+
print_name_and_number(name, @numbers[name])
|
64
|
+
else
|
65
|
+
puts "#{name} isn't in the phone book"
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
PhoneBook> find Sam
|
70
|
+
Sam 312-555-1212
|
71
|
+
PhoneBook> find Matz
|
72
|
+
Matz isn't in the phone book
|
73
|
+
|
74
|
+
Well we are cruising for burgers. But say we have a falling out with Amy (she
|
75
|
+
was taking up too much disk space anyway). No reason to keep her in the phone
|
76
|
+
book, so we'll define a +delete+ command.
|
77
|
+
|
78
|
+
def do_delete(name)
|
79
|
+
@numbers.delete(name) || write("No entry for '#{name}'")
|
80
|
+
end
|
81
|
+
|
82
|
+
PhoneBook> delete Amy
|
83
|
+
PhoneBook> list
|
84
|
+
Sam 312-555-1212
|
85
|
+
|
86
|
+
== Shortcuts
|
87
|
+
|
88
|
+
Commands like +add+ and +delete+ have clear names. They are self-documenting.
|
89
|
+
But it can get tedious to type them all out all the time.
|
90
|
+
|
91
|
+
You can add shortcuts for commands using Cmd::ClassMethods.shortcut.
|
92
|
+
|
93
|
+
shortcut '+', :add
|
94
|
+
|
95
|
+
The default +help+ command lists shortcuts for a given command. The +help+
|
96
|
+
command itself has a shortcut: +?+.
|
97
|
+
|
98
|
+
PhoneBook> ? add
|
99
|
+
add -- Add an entry (ex: add Sam, 312-555-1212) (aliases: +)
|
100
|
+
|
101
|
+
Additionally, any unambiguous abbreviation of a command will be translated
|
102
|
+
to the full command (so aliases that simply shorten the name of a given command
|
103
|
+
are unnecessary).
|
104
|
+
|
105
|
+
Since we only have one command that starts with +h+, the above could have been
|
106
|
+
written as:
|
107
|
+
|
108
|
+
PhoneBook> h add
|
109
|
+
add -- Add an entry (ex: add Sam, 312-555-1212) (aliases: +)
|
110
|
+
|
111
|
+
Furthermore, abbreviations are acceptable in any place a command name appears,
|
112
|
+
so you could write the above in an even more abbreviated way:
|
113
|
+
|
114
|
+
PhoneBook> h a
|
115
|
+
add -- Add an entry (ex: add Sam, 312-555-1212) (aliases: +)
|
116
|
+
|
117
|
+
== Documenting your commands
|
118
|
+
|
119
|
+
Our phone list now has its basic functionality. Let's add some documentation so
|
120
|
+
that someone other than you can figure out how to use it.
|
121
|
+
|
122
|
+
You document your commands using Cmd::ClassMethods.doc. We'll add docs for our
|
123
|
+
four commands so far:
|
124
|
+
|
125
|
+
doc :add, 'Add an entry (ex: add Sam, 312-555-1212)'
|
126
|
+
doc :find, 'Look up an entry (ex: find Sam)'
|
127
|
+
doc :list, 'List all entries'
|
128
|
+
doc :delete, 'Remove an entry'
|
129
|
+
|
130
|
+
== Getting Help
|
131
|
+
|
132
|
+
As illustrated above, there is a predefined +help+ command. Called without
|
133
|
+
arguments, it displays a help line for each command that has been documented
|
134
|
+
using the Cmd::ClassMethods.doc class method. (See <tt>Documenting your
|
135
|
+
commands</tt> for more on this.) By default, commands without documentation are
|
136
|
+
listed at the end of the +help+ output; this can be turned off by setting
|
137
|
+
YourCmdClass.hide_undocumented_commands = true. You can get help for a single
|
138
|
+
command by passing it as an argument to the +help+ command.
|
139
|
+
|
140
|
+
PhoneBook> help add
|
141
|
+
add -- Add an entry (ex: add Sam, 312-555-1212)
|
142
|
+
|
143
|
+
Typing +help+ affords you tab completion on all available commands with
|
144
|
+
documentation, so the above could be accomplished (assuming there are no other
|
145
|
+
documented commands that start with the letter +a+) by typing:
|
146
|
+
|
147
|
+
PhoneBook> help a<Tab>
|
148
|
+
|
149
|
+
The help command is aliased to +?+.
|
150
|
+
|
151
|
+
== Subcommands
|
152
|
+
|
153
|
+
Any method that is of the form +do_command_subcommand+ will be interpreted as a
|
154
|
+
subcommand of +command+. For example, if there was an +add+ command, a
|
155
|
+
+do_add_cellphone+ method would be invoked if 'add cellphone' was entered at
|
156
|
+
the prompt. If there was no +add+ command, +do_add_cellphone+ would not be
|
157
|
+
interpreted as a subcommand; you'd need to enter 'add_cellphone' to invoke it.
|
158
|
+
|
159
|
+
== Missing commands
|
160
|
+
|
161
|
+
Much like +method_missing+, there is a +command_missing+ method which is called
|
162
|
+
if an undefined command is entered in at the prompt. By default it simply
|
163
|
+
reports that the command does not exist; subclasses can override this behavior.
|
164
|
+
+command_missing+ is passed the entered command name as well as any arguments.
|
165
|
+
You must define your +command_missing+ this way.
|
166
|
+
|
167
|
+
Let's make phone book entry lookups more convenient by having +command_missing+
|
168
|
+
delegate to the +find+ command.
|
169
|
+
|
170
|
+
protected
|
171
|
+
|
172
|
+
def command_missing(command, args)
|
173
|
+
do_find(command)
|
174
|
+
end
|
175
|
+
|
176
|
+
Now we can do
|
177
|
+
|
178
|
+
PhoneBook> Sam
|
179
|
+
Sam 312-555-1212
|
180
|
+
|
181
|
+
== Lifecycle callbacks
|
182
|
+
|
183
|
+
Right now our phone book isn't really useful as the hash gets lost any time you
|
184
|
+
quit the program. Let's implement a simple storage scheme so that our phone
|
185
|
+
book entries will persist between invocations. A simple solution is just to
|
186
|
+
serialize the phone book hash to YAML in a file.
|
187
|
+
|
188
|
+
First we'll choose a place to store the file (apologies to people running
|
189
|
+
Windows).
|
190
|
+
|
191
|
+
PHONEBOOK_FILE = File.expand_path('~/.phonebook')
|
192
|
+
|
193
|
+
When the command loop is started, your subclass's +setup+ method is called.
|
194
|
+
Consider this your +initialize+. We can use this to grab the contents of our
|
195
|
+
phone book file.
|
196
|
+
|
197
|
+
protected
|
198
|
+
|
199
|
+
def setup
|
200
|
+
@numbers = get_store || {}
|
201
|
+
end
|
202
|
+
|
203
|
+
def get_store
|
204
|
+
File.open(PHONEBOOK_FILE) {|store| YAML.load(store)} rescue nil
|
205
|
+
end
|
206
|
+
|
207
|
+
Now when we start up our phone book it will grab our entries or create a fresh
|
208
|
+
Hash in which to add entries. But we don't have any code to save our phone book
|
209
|
+
entries!
|
210
|
+
|
211
|
+
A Cmd session happens mostly inside a loop. This loop accepts commands until it
|
212
|
+
is told to stop. Like +setup+, there are several methods that are called
|
213
|
+
automatically during the lifetime of this loop. One such method is +postloop+,
|
214
|
+
which, as the name suggests, is called after the loop is done, or in other
|
215
|
+
words, once the Cmd session is completed. This turns out to be a good candidate
|
216
|
+
for the task of saving our phone book entries.
|
217
|
+
|
218
|
+
protected
|
219
|
+
|
220
|
+
def postloop
|
221
|
+
File.open(PHONEBOOK_FILE, 'w') {|store| store.write YAML.dump(@numbers)}
|
222
|
+
end
|
223
|
+
|
224
|
+
And that is that. Now when we exit the phone book our numbers will be saved to
|
225
|
+
our phone book file.
|
226
|
+
|
227
|
+
$ ruby phonebook.rb
|
228
|
+
PhoneBook> l
|
229
|
+
PhoneBook> a Sam, 312-555-1212
|
230
|
+
PhoneBook> Sam
|
231
|
+
Sam 312-555-1212
|
232
|
+
PhoneBook> exit
|
233
|
+
$ ruby phonebook.rb
|
234
|
+
PhoneBook> l
|
235
|
+
Sam 312-555-1212
|
236
|
+
|
237
|
+
There are five life-cycle callbacks. The complete list is below:
|
238
|
+
|
239
|
+
+setup+:: Called when your Cmd subclass is created, like +initialize+.
|
240
|
+
+preloop+:: Called before the command loop begins
|
241
|
+
+precmd+:: Called before each command
|
242
|
+
+postcmd+:: Called after each command; has access to the +current_command+
|
243
|
+
method, which returns the name of the current command
|
244
|
+
+postloop+:: Called after the command loop ends
|
245
|
+
|
246
|
+
Here we can have a look at a working copy of our
|
247
|
+
{PhoneBook}[http://svn.vernix.org/main/library/cmd/trunk/example/phonebook.rb].
|
248
|
+
|
249
|
+
== Customizing command completion
|
250
|
+
|
251
|
+
By default Cmd supports readline functionality if it is enabled on your system.
|
252
|
+
This affords you command line history as well as command completion. The
|
253
|
+
default completion procedure will complete command names for you when you hit
|
254
|
+
the Tab key.
|
255
|
+
|
256
|
+
PhoneBook> l<Tab>
|
257
|
+
PhoneBook> list
|
258
|
+
|
259
|
+
As is the case in your standard shell, hitting tab twice when there is nothing
|
260
|
+
to complete will list all commands.
|
261
|
+
|
262
|
+
PhoneBook> <Tab><Tab>
|
263
|
+
add delete exit find help list shell
|
264
|
+
|
265
|
+
Completion can be customized on a per-command basis by defining a method of
|
266
|
+
the form +complete_command+ (where +command+ is a command name) which
|
267
|
+
returns an array with zero or more strings. The following (contrived) example
|
268
|
+
illustrates the idea:
|
269
|
+
|
270
|
+
$ grep -A 3 complete_add
|
271
|
+
def complete_add
|
272
|
+
%w{ cellphone fax home office }
|
273
|
+
end
|
274
|
+
|
275
|
+
PhoneBook> add <Tab>
|
276
|
+
cellphone fax home office
|
277
|
+
PhoneBook> add o<Tab>
|
278
|
+
PhoneBook> add office
|
279
|
+
|
280
|
+
If a given command has subcommands, Cmd's built in completion method will
|
281
|
+
complete with those subcommands automatically, so the above example would be
|
282
|
+
redundant were there to be command methods such as +do_add_office+ and
|
283
|
+
+do_add_home+, etc.
|
284
|
+
|
285
|
+
FIXME These docs lie I'm afraid. The API is not that simple yet, though the
|
286
|
+
above is the intended API. Check out the part of the link:files/TODO.html
|
287
|
+
file that talks about improving how completion works.
|
288
|
+
|
289
|
+
== Setting your prompt
|
290
|
+
|
291
|
+
By default the prompt will look like 'YourSubclass> '. So in the example above,
|
292
|
+
where we have been writing all that code inside a PhoneBook class that
|
293
|
+
inherits from Cmd, the prompt reads 'PhoneBook> '. The
|
294
|
+
Cmd::ClassMethods.prompt_with macro style method can be used to set a custom
|
295
|
+
prompt. The simplest prompt would just be a static string:
|
296
|
+
|
297
|
+
prompt_with '> '
|
298
|
+
|
299
|
+
You can, alternatively, pass Cmd::ClassMethods.prompt_with a Proc or method reference.
|
300
|
+
|
301
|
+
=== Proc
|
302
|
+
|
303
|
+
# Contrived...
|
304
|
+
prompt_with { "#{Time.now}> " }
|
305
|
+
|
306
|
+
=== Method reference
|
307
|
+
|
308
|
+
prompt_with :set_prompt
|
309
|
+
|
310
|
+
protected
|
311
|
+
|
312
|
+
# This assumes current_directory is defined by your Cmd subclass
|
313
|
+
def set_prompt
|
314
|
+
"#{ENV['USER']:#{current_directory}$ "
|
315
|
+
end
|
316
|
+
|
317
|
+
Using a method reference affords you access to all the state of your Cmd
|
318
|
+
instance.
|
319
|
+
|
320
|
+
N.B. The result of whatever is passed to Cmd::ClassMethods.prompt_with has +to_s+ called on it.
|
321
|
+
|
322
|
+
== Trapping user interrupts
|
323
|
+
|
324
|
+
If a user attempts to exit the command loop (using, for example, Ctrl-C), the
|
325
|
+
Cmd.user_interrupt method is called. Subclasses may override this. By default
|
326
|
+
it simply exits.
|
327
|
+
|
328
|
+
== Customizing passed arguments
|
329
|
+
|
330
|
+
For commands that take arguments (determined by whether or not you define your
|
331
|
+
+do_+ method with arguments), a method Cmd#tokenize_args is called on the passed
|
332
|
+
arguments. The default implementation has no side effects.
|
333
|
+
|
334
|
+
N.B. This API will more than likely change to something far more useful and
|
335
|
+
Rubyesque. Please checkout the link:files/TODO.html for details.
|
336
|
+
|
337
|
+
== Handling exceptions
|
338
|
+
|
339
|
+
All exceptions raised within the command loop are caught. You can specify what
|
340
|
+
action should be taken if a specific exception is raised by using the
|
341
|
+
Cmd::ClassMethods.handle method.
|
342
|
+
|
343
|
+
handle StackOverflowError, :handle_stack_overflow
|
344
|
+
|
345
|
+
If you specify a symbol the referenced method will be called. If you supply a
|
346
|
+
string, such as
|
347
|
+
|
348
|
+
handle StackOverflowError, 'Stack underflowed'
|
349
|
+
|
350
|
+
the string will be displayed to the user.
|
351
|
+
|
352
|
+
All other exceptions are passed to a Cmd.handle_exception method which by
|
353
|
+
default simply reraises the exception. Subclasses may use this to customize how
|
354
|
+
exceptions are handled.
|
355
|
+
|
356
|
+
def handle_exception(exception)
|
357
|
+
write 'Error'
|
358
|
+
end
|
359
|
+
|
360
|
+
== Not running interactively
|
361
|
+
|
362
|
+
Though subclasses of Cmd are meant to be run interactively, you may find that
|
363
|
+
you'd like to have access to a given command without starting up a session with
|
364
|
+
the interactive interpreter. Cmd allows you to run commands from the command
|
365
|
+
line. If you supply a command (with optional arguments) when invoking the
|
366
|
+
program that runs your command loop, the supplied command will be invoked, and
|
367
|
+
execution will stop.
|
368
|
+
|
369
|
+
$ ruby phonebook.rb Sam
|
370
|
+
Sam 312-555-1212
|
371
|
+
|
372
|
+
== Empty command lines
|
373
|
+
|
374
|
+
If a user enters an empty line at the command prompt, the +empty_line+ method
|
375
|
+
is called. By default it does nothing.
|
376
|
+
|
377
|
+
== Stopping the loop
|
378
|
+
|
379
|
+
Calling the +stoploop+ method will stop the command loop once the current
|
380
|
+
command is complete.
|
381
|
+
|
382
|
+
== Hidding undocumented commands
|
383
|
+
|
384
|
+
By default undocumented commands (if any) are listed at the bottom of the
|
385
|
+
default help message. This behaviour can be disabled by setting
|
386
|
+
+hide_undocumented_commands+ to +true+.
|
387
|
+
|
388
|
+
MyCmdClass.hide_undocumented_commands = true
|
389
|
+
|
390
|
+
== Callback reference
|
391
|
+
|
392
|
+
+handle_exception+:: see Handling exceptions
|
393
|
+
+user_interrupt+:: see Trapping user interrupts
|
394
|
+
+tokenize_args+:: see Customizing passed in arguments
|
395
|
+
+setup+:: see Setting up your environment
|
396
|
+
+command_missing+:: see Missing commands
|
397
|
+
+empty_line+:: see Emtpy command lines
|
398
|
+
|
399
|
+
== Download
|
400
|
+
|
401
|
+
Subversion
|
402
|
+
|
403
|
+
* http://svn.vernix.org/main/library/cmd/trunk
|
404
|
+
|
405
|
+
Documentation can be found at
|
406
|
+
|
407
|
+
* http://code.vernix.org/cmd
|
408
|
+
|
409
|
+
== Install
|
410
|
+
|
411
|
+
See the link:files/INSTALL.html doc.
|