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.
Files changed (14) hide show
  1. data/AUTHORS +1 -0
  2. data/CHANGELOG +5 -0
  3. data/INSTALL +11 -0
  4. data/MIT-LICENSE +20 -0
  5. data/README +411 -0
  6. data/Rakefile +141 -0
  7. data/THANKS +12 -0
  8. data/TODO +124 -0
  9. data/example/calc.rb +86 -0
  10. data/example/phonebook.rb +69 -0
  11. data/lib/cmd.rb +557 -0
  12. data/setup.rb +1360 -0
  13. data/test/tc_cmd.rb +284 -0
  14. metadata +67 -0
data/AUTHORS ADDED
@@ -0,0 +1 @@
1
+ Marcel Molina Jr. -- http://vernix.org/marcel/
@@ -0,0 +1,5 @@
1
+ = Cmd Changelog
2
+
3
+ == Version 0.7.0
4
+
5
+ * Initial public release
data/INSTALL ADDED
@@ -0,0 +1,11 @@
1
+ = Installing cmd
2
+
3
+ == With RubyGems
4
+
5
+ $ sudo gem i cmd -r
6
+
7
+ == From tarball
8
+
9
+ $ tar -zxvvf cmd-x.x.x.tar.gz
10
+ $ cd cmd-x.x.x
11
+ $ sudo ruby setup.rb
@@ -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.