cmd 0.7.0

Sign up to get free protection for your applications and to get access to all the features.
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.