[ANN] cmd 0.7.0: Library for Line-Oriented Command Interpreters (initial release)

M

Marcel Molina Jr.

= Cmd 0.7.0 (initial release)

I am very pleased to announce the initial public release of Cmd (version 0.7.0).

http://cmd.rubyforge.org/

== What is Cmd?

cmd is a library for building line-oriented command interpreters in Ruby.
Simply inherit from cmd's Cmd class, and methods whose names start with do_
become interactive commands. cmd is inspired by the Python library [1] of the
same name, but offers a distinctive Ruby feel and several additional features.

Documentation, examples, a mini tutorial and more can be found at:

http://code.vernix.org/cmd/

Take a look at the README:

http://code.vernix.org/cmd/rdoc/

== Download and Install

http://code.vernix.org/cmd/#download for various packaging formats.

With RubyGems:

$ [sudo] gem i cmd -r

From archive:

$ wget http://code.vernix.org/cmd/download/cmd-0.7.0.tar.gz
$ tar -zxvvf cmd-0.7.0.tar.gz
$ cd cmd*
$ [sudo] ruby setup.rb

== Examples

There are a few examples of Cmd in action here:

http://svn.vernix.org/main/library/cmd/trunk/example/

== Todo

Cmd is more usable than not at the moment, but I do have a lot of ideas for
where I'd like it to go. You can see these here.

http://svn.vernix.org/main/library/cmd/trunk/TODO

== License

MIT

== Thanks

A special thanks goes to Sam Stephenson (http://conio.net/).

More elaborate gushing at http://svn.vernix.org/main/library/cmd/trunk/THANKS

marcel
 
J

James Edward Gray II

= Cmd 0.7.0 (initial release)

I am very pleased to announce the initial public release of Cmd
(version 0.7.0).

http://cmd.rubyforge.org/

I've only had a few short minutes to play around with this, but this is
pretty darn nifty. I'll look into it more when I have some time, but
thanks for sharing!

James Edward Gray II
 
M

Marcel Molina Jr.

Thanks!

Any plans to go towards interactive commands e.g. some commands valid only
within scope of other commands?

By interactive commands do you mean something like?
# now you are in a context within 'foo' that opens you up to a whole other
# set of things

If so then I don't believe there are any plans for something like that but
please do provide a bit more information if you are inclined and I would very
gladly take some time to think about it.

FYI: you can take a peek at things that are planned by checking out the todo.

Browse on over to: http://svn.vernix.org/main/library/cmd/trunk/TODO

marcel
 
M

Marcel Molina Jr.

Jim Weirich was gracious enough to inform me that there was a subtle semantic
error in the way my Readline detection was performed in cmd. As a result I've
pushed a minor bug fix release, 0.7.1. The same information below applies:

== Download and Install

http://code.vernix.org/cmd/#download for various packaging formats.

With RubyGems:

$ [sudo] gem i cmd -r

From archive:

$ wget http://code.vernix.org/cmd/download/cmd-0.7.0.tar.gz
$ tar -zxvvf cmd-0.7.0.tar.gz
$ cd cmd*
$ [sudo] ruby setup.rb

Thanks,
marcel
 
L

Luke Graham

Cool!

Any plans to go towards interactive commands e.g. some commands valid only
within scope of other commands?

Wouldnt the best way to do this be to open another, specialised Cmd?
 
M

Marcel Molina Jr.

Marcel Molina Jr. said:
= Cmd 0.7.0 (initial release)

Gem install, Windows XP, Ruby 1.8.2, trying out phonebook.rb, I get:

[C:\ruby\lib\ruby\gems\1.8\gems\cmd-0.7.2\example]ruby phonebook.rb
phonebook.rb:11:in `expand_path': couldn't find HOME environment --
expanding `~/.pho
nebook' (ArgumentError)
from phonebook.rb:11

Yes, I was fairly certain when I wrote that that it would not work on
Windows. The phonebook.rb example is meant to be simple and illustrative so
I passed over my recognition that I was more than likely imposing a platform
dependency. A bit insensitive, sorry 'bout that :)

Thanks for reporting this. Just for kicks I've made phonebook.rb a bit more
platform agnostic. Mind you I don't have access to a Windows machine to test
this but it *seems* like it ought to cover most cases.

PHONEBOOK_FILE = begin
File.expand_path('~/.phonebook')
rescue
File.join((ENV['HOME'] || ENV['USERPROFILE']), '_phonebook')
end

Windows users that I asked knew many ways in which this could be made more
rigorous or "accurate" or what have you but for the purposes of the
phonebook example I'm assuming this will work well enough.

Thanks again. Please let me know if you implement something with cmd and
thanks for the interest.

marcel
 
M

Marcel Molina Jr.

Sort-of ... lousy description on my part. I can see the command interpreter
as a state machine consisting of a current object + the available commands
on the current object. I'll start with the latter.

The available commands on the current object could change as you do things
to it. Suppose each of its commands has some declared pre-condition. A
command is disabled if its precondition is false. Here is one of many ways
to do this.

class PhoneBook < Cmd
# ... define commands & docs
pre :add { |pb| pb.condition_on_phonebook_add }
pre :remove { |pb| pb.condition_on_phonebook_remove }
end

All commands whose pre is true are 'enabled', all others are 'greyed-out'.
Preconditions could have a descriptive string for help purposes.

Perhaps the already existent precmd and postcmd methods could be extracted
into something that was a bit more aware and versatile. Like above where
rather than having precmd be a hook that is called before all commands, the
pre macro here would set up a pre condition for the specified command. But
the example above is about what commands you are then afforded, which sort of
translates to what commands the completion proc will display you and not much
else that I can think of. I imagine the notion of setting pre condition hooks
on a per command basis could be generalized to offer more than just what
subcommands you are then afforded, like 'input actions' in FSM. Sort of seems
to get into the realm of Design by Contract.
Secondly the current object itself could be switched. e.g If you add some
special commands to "cd", "pushd", and "popd" to any command-able object, to
start working in a new command context.

PBook> pushd `find Sam`
- execute command "find Sam" in current context
- perhaps check return (e.g. a Person) is command-able
- make that the new command context, so ...
Sam> first Sammy
last Brown
popd
PBook>

This idea seems quite similar to irb's job management where you can create a
subirb which lets you essentially "enter" the object that you invoke the
subirb session onto. Perhaps rather than having to fall back on some exiting
convention like popd, something sort of like what Luke Graham was suggesting
could happen. A thread with a specialized Cmd. Or perhaps there could be come
simple protocol where by Cmd instances could interact with one another. It
all gets a bit sophisticated after a certain point. I don't know whether this
kind of stuff is better suited built into Cmd or on top of it. But I do
welcome the ideas and look forward to see what bubbles up as I take the time
to think about them.
And, lastly, every command has arguments. Each argument has a range of valid
values that can depend on current state. If you store an array of blocks
that computes the possible values for each argument given the current
context object (when applicable), you can even provide completion for
arguments to commands.

class PhoneBook < Cmd
# can only delete X if X currently in phonebook
command :delete do
args [ {|pb| pb.people.collect { |p| p.name }}
# even better if returned objects, not strings
# using to_s or some protocol to get strings for completion
# command intereter keeps map of string->object
# refuse to execute if no valid completion
# inner code just deals with objects
]
end
end

PBook> delete <TAB>
#--> Sammy Mary
PBook> delete S<TAB>
#--> delete Sammy

More DbC sort of stuff kind of. More simply, this functionality is actually
sort of already implemented. Or at least the blue print is there and a I've
commented on the hypothetical API in the TODO. Customized completion per
command should be as easy as providing a proc or block or method reference or
hard-coded array or whatever that provides a collection of things to complete
against. This can be set for commands, subcommands, etc.
btw: have you considered adding a Command class to keep all info about one
command?

Yes actually. Here is the branch at the point where I abandoned it for the
time being:

http://svn.vernix.org/main/library/cmd/branches/custom-classes-for-commands/lib/cmd.rb

I set out to make cmd way more compositional. There was a Command class, a
Commands container, Shortcut, SubCommand and I was thinking of Documentation.
It would be really useful to be able to have a command object and just ask it
if it has subcommands and get a list of them or ask it for its documentation.
That's what I was aiming for. It ended up just adding way more complexity
(which I was aiming to diminish by taking that approach). It was kludgy and
sucked. This was largely just because I did a bad job of it. I have the
feeling there could be a nice balance, where the composition of objects
approach is succinct and elegant and simple. I just had to back out and
approach it again from a different angle at the time. So after hanging out on
that branch for a few days I just moved back to the original approach. Now
the plan, enumerated in the TODO file, is to have Cmd be a module that you
mix in. When you mix it in you define methods with whatever name you want and
then you a 'command' class method to designate something a command. For
example:

command :foo
def foo
# ...
end

This makes it easy to take code that was developed previously or without Cmd
in mind and stick a Cmd type interface onto it.

Alternatively, there would be some Cmd::Base class that has the Cmd module
mixed in. You could instead inherit from this and rather than use the
'command' class method or have some naming system, work off the convention
that all public methods of the subclass are implicitly commands. After doing
that I think I would tackle wrapping command and subcommand discovery into
specialized instances of stuff like a Command class.

Thanks for the suggestions! Glad you're interested,
marcel
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,744
Messages
2,569,482
Members
44,901
Latest member
Noble71S45

Latest Threads

Top