https://github.com/vito/mothership
big honkin' extensible command-line application library
https://github.com/vito/mothership
Last synced: 12 months ago
JSON representation
big honkin' extensible command-line application library
- Host: GitHub
- URL: https://github.com/vito/mothership
- Owner: vito
- License: bsd-3-clause
- Created: 2012-06-28T11:04:34.000Z (about 14 years ago)
- Default Branch: master
- Last Pushed: 2013-09-06T18:09:59.000Z (almost 13 years ago)
- Last Synced: 2025-06-21T12:47:27.307Z (about 1 year ago)
- Language: Ruby
- Size: 164 KB
- Stars: 3
- Watchers: 1
- Forks: 6
- Open Issues: 1
-
Metadata Files:
- Readme: README.md
- License: LICENSE
Awesome Lists containing this project
README
Mothership
==========
This gem is for defining a big honkin' extensible command-line application.
It's similar to Thor, but instead of focusing on having multiple composable
command sets, there's simply a global command set that can be trivially
extended while keeping the extensions isolated.
Commands are defined in subclasses of `Mothership`. Because commands are
isolated into their own classes, helper methods and constants can be safely
defined in it without risking a name collision with other extensions.
All inputs for a command are defined declaratively. They all have a name,
which is used for the flag name, and a few options that I'll go over later.
In a more 'functional' style, commands take all of their inputs as a single
argument, rather than having the inputs embedded in a stateful object. This
makes it easier for one command to invoke another with a given set of inputs,
without risking any sort of collision based on their input names.
For example, to define a 'insult' command which insults the user, with an
optional censoring flag:
class Insults < Mothership
desc "Insults the user."
input :censor, :type => :boolean, :alias => "-c"
def insult
puts "Hey man, #{curse_word(input[:censor])} you."
end
private
def curse_word(censor = false)
if censor
"$%#^*"
else
"foo"
end
end
end
When the 'insult' command is defined, it is added to the `Mothership`. When
the user invokes `myapp insult`, the `Insults` class is instantiated, the
inputs are parsed, and the `insult` method is called with the user's inputs as
a hash-like object.
A default value may be provided for an input by calling it with a block that
gets called when the value is needed but not provided by the user:
input(:name) { ask("What's your name?") }
The block is called on the instance of the object, the first time you try to
do `input[:foo]`. To pass values to the block, do `input[:foo, arg1, ...]`.
The returned value is cached, so you can just use `input[:name]` to access the
value, and it will only ask the first time. For example:
desc "Delete something."
input(:name) { ask("Delete what?") }
input(:really) { |name| ask("Really delete #{name}?") }
def delete(input)
return unless input[:really, input[:name]]
puts "BAM, #{input[:name]} is gone forever."
end
# => myapp delete
# Delete what?> foo
# Really delete foo?> y
# BAM, foo is gone forever.
An input can be accepted as an argument or splat-argument form by passing
`:argument => true` or `:argument => :splat`:
desc "Deleting something with the given reasons, if any."
input :name, :argument => true
input :reasons, :argument => :splat, :alias => "--reason"
def delete(input)
puts "Deleting #{input[:name]} because: #{input[:reasons].join ", "}"
end
This will accept the following equivalent forms:
delete foo bar baz
delete --name foo bar baz
delete --name foo --reasons bar,baz
delete --name foo --reason bar,baz
delete bar baz --name foo
delete foo --reasons bar,baz
delete foo --reason bar,baz
Note that that "--reason" alias accepts the more natural "--reason foo" for
when there is only a single reason. Also note that flags are parsed before
arguments, so it doesn't matter if they're tacked on to the end or if they
appear before the others.
Because both flags and arguments are defined with the same mechanic, the usage
string for a command can be auto-generated based on its input metadata, rather
than being hardcoded and possibly becoming inaccurate if a plugin changes the
input structure. For example, a command 'foo' with an optional argument `:bar`
and a splat argument `:bazes` will have a usage string of `foo BAR BAZES...`.