executable 1.1.0 → 1.2.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.ruby +61 -0
- data/.yardopts +7 -0
- data/COPYING.rdoc +35 -0
- data/DEMO.rdoc +568 -0
- data/HISTORY.rdoc +55 -0
- data/README.rdoc +101 -51
- data/Schedule.reap +17 -0
- data/demo/00_introduction.rdoc +6 -0
- data/demo/01_single_command.rdoc +44 -0
- data/demo/02_multiple_commands.rdoc +125 -0
- data/demo/03_help_text.rdoc +109 -0
- data/demo/04_manpage.rdoc +14 -0
- data/demo/05_optparse_example.rdoc +152 -0
- data/demo/06_delegate_example.rdoc +40 -0
- data/demo/07_command_methods.rdoc +36 -0
- data/demo/08_dispatach.rdoc +29 -0
- data/demo/applique/ae.rb +1 -0
- data/demo/applique/compare.rb +4 -0
- data/demo/applique/exec.rb +1 -0
- data/demo/samples/bin/hello +31 -0
- data/demo/samples/man/hello.1 +22 -0
- data/demo/samples/man/hello.1.html +102 -0
- data/demo/samples/man/hello.1.ronn +19 -0
- data/lib/executable.rb +67 -128
- data/lib/executable/core_ext.rb +102 -0
- data/lib/executable/dispatch.rb +30 -0
- data/lib/executable/domain.rb +106 -0
- data/lib/executable/errors.rb +22 -0
- data/lib/executable/help.rb +430 -0
- data/lib/executable/parser.rb +208 -0
- data/lib/executable/utils.rb +41 -0
- data/lib/executable/version.rb +23 -0
- data/meta/authors +2 -0
- data/meta/copyrights +3 -0
- data/meta/created +1 -0
- data/meta/description +6 -0
- data/meta/name +1 -0
- data/meta/organization +1 -0
- data/meta/repositories +2 -0
- data/meta/requirements +6 -0
- data/meta/resources +7 -0
- data/meta/summary +1 -0
- data/meta/version +1 -0
- data/test/test_executable.rb +40 -19
- metadata +124 -68
- data/History.rdoc +0 -35
- data/NOTICE.rdoc +0 -23
- data/Profile +0 -30
- data/Version +0 -1
- data/meta/license/Apache2.txt +0 -177
data/HISTORY.rdoc
ADDED
@@ -0,0 +1,55 @@
|
|
1
|
+
= RELEASE HISTORY
|
2
|
+
|
3
|
+
== 1.2.0 / 2012-01-31
|
4
|
+
|
5
|
+
Version 1.2.0 is complete rewrite of Executable. Actually it was decided that
|
6
|
+
the old design was too simplistic in it design concept, so another library
|
7
|
+
that was in the works, called Executioner, and briefly CLI::Base, was
|
8
|
+
ported over. And with some API changes, it is now the new Executable project.
|
9
|
+
The idea of the project is generally the same, but Executable now offers
|
10
|
+
more features, such as good help output and namespace-based subcomamnds.
|
11
|
+
Of course, to accommodate all this the API had to change some over
|
12
|
+
the previous version, so be sure to read the API documentation.
|
13
|
+
|
14
|
+
Changes:
|
15
|
+
|
16
|
+
* Deprecate old implementation.
|
17
|
+
* Port Executioner project over to become new Executable project.
|
18
|
+
* Supports namespace-base subcommmands.
|
19
|
+
* Supports formatted help output in plain text and markdown.
|
20
|
+
* Supports manpage look-up and display.
|
21
|
+
|
22
|
+
|
23
|
+
== 1.1.0 / 2011-04-21
|
24
|
+
|
25
|
+
This release simplifies Executable, removing the #option_missing method
|
26
|
+
and using the standard #method_missing callback instead. Along with this
|
27
|
+
the special error class, +NoOptionError+, has been removed. This release
|
28
|
+
also fixes an issue with inconsistent arguments being passed to the callback.
|
29
|
+
Finally it renames the #execute_command method to simple #execute!.
|
30
|
+
|
31
|
+
Changes:
|
32
|
+
|
33
|
+
* Name Changes
|
34
|
+
|
35
|
+
* Renamed `#execute_command` method to `#execute!`.
|
36
|
+
|
37
|
+
* Deprecations
|
38
|
+
|
39
|
+
* Rely on #method_missing callback instead of special #option_missing method.
|
40
|
+
* The +NoOptionError+ exception class is no longer needed because of above.
|
41
|
+
|
42
|
+
* Bug Fixes
|
43
|
+
|
44
|
+
* The #method_missing callback takes the value of the option being set.
|
45
|
+
|
46
|
+
|
47
|
+
== 1.0.0 / 2011-04-15
|
48
|
+
|
49
|
+
This is the initialize release of Executable (as a stand alone project).
|
50
|
+
Executable is a mixin that can turn any class into an commandline interface.
|
51
|
+
|
52
|
+
* 1 Major Enhancement
|
53
|
+
|
54
|
+
* Birthday!
|
55
|
+
|
data/README.rdoc
CHANGED
@@ -1,94 +1,144 @@
|
|
1
1
|
= Executable
|
2
2
|
|
3
|
+
{Website}[http://rubyworks.github.com/executable] |
|
4
|
+
{Source Code}[http://github.com/rubyworks/executable] |
|
5
|
+
{Report Issue}[http://github.com/rubyworks/executable/features] |
|
6
|
+
{#rubyworks}[irc://irc.freenode.org/rubyworks]
|
7
|
+
|
8
|
+
{<img src="http://travis-ci.org/rubyworks/executable.png"/>}[http://travis-ci.org/rubyworks/executable]
|
9
|
+
|
10
|
+
|
3
11
|
== DESCRIPTION
|
4
12
|
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
13
|
+
Executable is to the commandline, what ActiveRecord is the database.
|
14
|
+
You can think of Executable as a *COM*, a Command-line Object Mapper,
|
15
|
+
just as ActiveRecord is an ORM (Object Relational Mapper). Any class
|
16
|
+
mixing in Executable or subclassing Executable::Command can define
|
17
|
+
a complete command line tool using nothing more than Ruby's standard
|
18
|
+
syntax. No special DSL is required.
|
11
19
|
|
12
20
|
|
13
21
|
== FEATURES
|
14
22
|
|
15
|
-
*
|
23
|
+
* Easy to use, just mixin or subclass.
|
24
|
+
* Define #call to control the command procedure.
|
16
25
|
* Public writers become options.
|
17
|
-
*
|
26
|
+
* Namespace children become subcommands.
|
27
|
+
* Or easily dispatch subcommands to public methods.
|
28
|
+
* Generate help in plain text or markdown.
|
18
29
|
|
19
30
|
|
20
|
-
==
|
31
|
+
== LIMITATIONS
|
21
32
|
|
22
|
-
*
|
23
|
-
*
|
33
|
+
* Ruby 1.9+ only.
|
34
|
+
* Help doesn't handle aliases well (yet).
|
24
35
|
|
25
36
|
|
26
37
|
== RELEASE NOTES
|
27
38
|
|
28
|
-
Please see HISTORY file.
|
39
|
+
Please see HISTORY.rdoc file.
|
29
40
|
|
30
41
|
|
31
42
|
== SYNOPSIS
|
32
43
|
|
33
|
-
|
44
|
+
CLIs can be built by using a Executable as a mixin, or by subclassing
|
45
|
+
`Executable::Command`. Methods seemlessly handle command-line options.
|
46
|
+
Writer methods (those ending in '=') correspond to options and query
|
47
|
+
methods (those ending in '?') modify them to be boolean switches.
|
48
|
+
|
49
|
+
For example, here is a simple "Hello, World!" commandline tool.
|
50
|
+
|
51
|
+
require 'executable'
|
52
|
+
|
53
|
+
class HelloCommand
|
54
|
+
include Executable
|
55
|
+
|
56
|
+
# Say it in uppercase?
|
57
|
+
def load=(bool)
|
58
|
+
@loud = bool
|
59
|
+
end
|
60
|
+
|
61
|
+
#
|
62
|
+
def loud?
|
63
|
+
@loud
|
64
|
+
end
|
65
|
+
|
66
|
+
# Show this message.
|
67
|
+
def help?
|
68
|
+
cli.show_help
|
69
|
+
exit
|
70
|
+
end
|
71
|
+
alias :h? :help?
|
72
|
+
|
73
|
+
# Say hello.
|
74
|
+
def call(name)
|
75
|
+
name = name || 'World'
|
76
|
+
str = "Hello, #{name}!"
|
77
|
+
str = str.upcase if loud?
|
78
|
+
puts str
|
79
|
+
end
|
80
|
+
end
|
34
81
|
|
35
|
-
|
36
|
-
|
82
|
+
To make the command available on the command line, add an executable
|
83
|
+
to your project passing ARGV to the #execute or #run methods.
|
37
84
|
|
38
|
-
|
85
|
+
#!usr/bin/env ruby
|
86
|
+
require 'hello.rb'
|
87
|
+
HelloCommand.run
|
39
88
|
|
40
|
-
|
89
|
+
If we named this file `hello`, set its execute flag and made it available
|
90
|
+
on our systems $PATH, then:
|
41
91
|
|
42
|
-
|
43
|
-
|
44
|
-
end
|
92
|
+
$ hello
|
93
|
+
Hello, World!
|
45
94
|
|
46
|
-
|
47
|
-
|
48
|
-
end
|
49
|
-
end
|
95
|
+
$ hello John
|
96
|
+
Hello, John!
|
50
97
|
|
51
|
-
|
98
|
+
$ hello --loud John
|
99
|
+
HELLO, JOHN!
|
52
100
|
|
53
|
-
|
54
|
-
=> ["butter", nil, nil, "yum"]
|
101
|
+
Executable can also generate help text for commands.
|
55
102
|
|
56
|
-
|
57
|
-
|
103
|
+
$ hello --help
|
104
|
+
USAGE: hello [options]
|
58
105
|
|
106
|
+
Say hello.
|
59
107
|
|
60
|
-
|
61
|
-
|
108
|
+
--loud Say it in uppercase?
|
109
|
+
--help Show this message
|
62
110
|
|
63
|
-
|
64
|
-
to
|
111
|
+
If you look back at the class definition you can see it's pulling
|
112
|
+
comments from the source to provide descriptions. It pulls the
|
113
|
+
description the command itself from the `#call` method.
|
65
114
|
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
115
|
+
Basic help like this is fine for personal tools, but for public facing
|
116
|
+
production applications it is desirable to utilize manpages. To this end,
|
117
|
+
Executable provides Markdown formatted help as well. We can access this,
|
118
|
+
for example, via `HelloCommand.help.markdown`. The idea with this is that
|
119
|
+
we can save the output to `man/hello.ronn` or copy it the top of our `bin/`
|
120
|
+
file, edit it to perfection and then use tools such a {ronn}[https://github.com/rtomayko/ronn],
|
121
|
+
{binman}[https://github.com/sunaku/binman] or {md2man}[https://github.com/sunaku/md2man]
|
122
|
+
to generate the manpages. What's particularly cool about Executable,
|
123
|
+
is that once we have a manpage in the standard `man/` location in our project,
|
124
|
+
the `#show_help` method will use it instead of the plain text.
|
70
125
|
|
126
|
+
For a more detail example see {QED}[demo.html]
|
127
|
+
and {API}[http://rubydoc.info/gems/executable/frames] documentation.
|
71
128
|
|
72
|
-
== INSTALL
|
73
129
|
|
74
|
-
|
130
|
+
== INSTALLATION
|
75
131
|
|
132
|
+
Install with RubyGems in the usual fashion.
|
76
133
|
|
77
|
-
|
134
|
+
$ gem install executable
|
78
135
|
|
79
|
-
(Apache 2.0)
|
80
136
|
|
81
|
-
|
137
|
+
== LEGAL
|
82
138
|
|
83
|
-
|
84
|
-
you may not use this program except in compliance with the License.
|
85
|
-
You may obtain a copy of the License at
|
139
|
+
Copyright (c) 2008 Rubyworks
|
86
140
|
|
87
|
-
|
141
|
+
Distributable in accordance with the *BSD-2-Clause* license.
|
88
142
|
|
89
|
-
|
90
|
-
distributed under the License is distributed on an "AS IS" BASIS,
|
91
|
-
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
92
|
-
See the License for the specific language governing permissions and
|
93
|
-
limitations under the License.
|
143
|
+
See COPYING.rdoc for licensing details.
|
94
144
|
|
data/Schedule.reap
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
task :default => [:test]
|
2
|
+
|
3
|
+
desc "run unit tests (needs rubytest)"
|
4
|
+
task :test do
|
5
|
+
sh "rubytest -Ilib test/*.rb"
|
6
|
+
end
|
7
|
+
|
8
|
+
desc "render README.rdoc to web/readme.html (need malt)"
|
9
|
+
task :readme do
|
10
|
+
sh "malt README.rdoc > web/readme.html"
|
11
|
+
end
|
12
|
+
|
13
|
+
# if `README.rdoc` changes generate `web/readme.html`.
|
14
|
+
file 'README.rdoc' do
|
15
|
+
sh "malt README.rdoc > web/readme.html"
|
16
|
+
end
|
17
|
+
|
@@ -0,0 +1,44 @@
|
|
1
|
+
== No Subcommmands
|
2
|
+
|
3
|
+
This example demonstrates using Executable::Command to create a simple command line
|
4
|
+
interface without subcommands. (Note the Executable mixin could be used just
|
5
|
+
as well).
|
6
|
+
|
7
|
+
class NoSubCommandCLI < Executable::Command
|
8
|
+
|
9
|
+
attr :result
|
10
|
+
|
11
|
+
def o?
|
12
|
+
@o
|
13
|
+
end
|
14
|
+
|
15
|
+
def o=(flag)
|
16
|
+
@o = flag
|
17
|
+
end
|
18
|
+
|
19
|
+
def call
|
20
|
+
if o?
|
21
|
+
@result = "with"
|
22
|
+
else
|
23
|
+
@result = "without"
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
28
|
+
|
29
|
+
Execute the CLI on an example command line.
|
30
|
+
|
31
|
+
cli = NoSubCommandCLI.run('')
|
32
|
+
cli.result.assert == 'without'
|
33
|
+
|
34
|
+
Execute the CLI on an example command line.
|
35
|
+
|
36
|
+
cli = NoSubCommandCLI.run('-o')
|
37
|
+
cli.result.assert == 'with'
|
38
|
+
|
39
|
+
There are two important things to notices heres. Frist, that #main is being
|
40
|
+
called in each case. It is the method called with no other subcommands are
|
41
|
+
defined. And second, the fact the a `o?` method is defined to compliment the
|
42
|
+
`o=` writer, informs Executable that `-o` is an option _flag_, not taking
|
43
|
+
any parameters.
|
44
|
+
|
@@ -0,0 +1,125 @@
|
|
1
|
+
== Multiple Subcommmands
|
2
|
+
|
3
|
+
Setup an example CLI subclass.
|
4
|
+
|
5
|
+
class MyCLI < Executable::Command
|
6
|
+
attr :result
|
7
|
+
|
8
|
+
def initialize
|
9
|
+
@result = []
|
10
|
+
end
|
11
|
+
|
12
|
+
def g=(value)
|
13
|
+
@result << "g" if value
|
14
|
+
end
|
15
|
+
|
16
|
+
def g?
|
17
|
+
@result.include?("g")
|
18
|
+
end
|
19
|
+
|
20
|
+
#
|
21
|
+
class C1 < self
|
22
|
+
def call
|
23
|
+
@result << "c1"
|
24
|
+
end
|
25
|
+
|
26
|
+
def o1=(value)
|
27
|
+
@result << "c1_o1 #{value}"
|
28
|
+
end
|
29
|
+
|
30
|
+
def o2=(value)
|
31
|
+
@result << "c1_o2 #{value}"
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
#
|
36
|
+
class C2 < Executable::Command
|
37
|
+
attr :result
|
38
|
+
|
39
|
+
def initialize
|
40
|
+
@result = []
|
41
|
+
end
|
42
|
+
|
43
|
+
def call
|
44
|
+
@result << "c2"
|
45
|
+
end
|
46
|
+
|
47
|
+
def o1=(value)
|
48
|
+
@result << "c2_o1 #{value}"
|
49
|
+
end
|
50
|
+
|
51
|
+
def o2=(value)
|
52
|
+
@result << "c2_o2" if value
|
53
|
+
end
|
54
|
+
|
55
|
+
def o2?
|
56
|
+
@result.include?("c2_o2")
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
end
|
61
|
+
|
62
|
+
Instantiate and run the class on an example command line.
|
63
|
+
|
64
|
+
Just a command.
|
65
|
+
|
66
|
+
cli = MyCLI.run('c1')
|
67
|
+
cli.result.assert == ['c1']
|
68
|
+
|
69
|
+
Command with global option.
|
70
|
+
|
71
|
+
cli = MyCLI.run('c1 -g')
|
72
|
+
cli.result.assert == ['g', 'c1']
|
73
|
+
|
74
|
+
Command with an option.
|
75
|
+
|
76
|
+
cli = MyCLI.run('c1 --o1 A')
|
77
|
+
cli.result.assert == ['c1_o1 A', 'c1']
|
78
|
+
|
79
|
+
Command with two options.
|
80
|
+
|
81
|
+
cli = MyCLI.run('c1 --o1 A --o2 B')
|
82
|
+
cli.result.assert == ['c1_o1 A', 'c1_o2 B', 'c1']
|
83
|
+
|
84
|
+
Try out the second command.
|
85
|
+
|
86
|
+
cli = MyCLI.run('c2')
|
87
|
+
cli.result.assert == ['c2']
|
88
|
+
|
89
|
+
Seoncd command with an option.
|
90
|
+
|
91
|
+
cli = MyCLI.run('c2 --o1 A')
|
92
|
+
cli.result.assert == ['c2_o1 A', 'c2']
|
93
|
+
|
94
|
+
Second command with two options.
|
95
|
+
|
96
|
+
cli = MyCLI.run('c2 --o1 A --o2')
|
97
|
+
cli.result.assert == ['c2_o1 A', 'c2_o2', 'c2']
|
98
|
+
|
99
|
+
Since C1#main takes not arguments, if we try to issue a command
|
100
|
+
that will have left over arguments, then an ArgumentError will be raised.
|
101
|
+
|
102
|
+
expect ArgumentError do
|
103
|
+
cli = MyCLI.run('c1 a')
|
104
|
+
end
|
105
|
+
|
106
|
+
How about a non-existenct subcommand.
|
107
|
+
|
108
|
+
expect NotImplementedError do
|
109
|
+
cli = MyCLI.run('q')
|
110
|
+
cli.result.assert == ['q']
|
111
|
+
end
|
112
|
+
|
113
|
+
How about an option only.
|
114
|
+
|
115
|
+
expect NotImplementedError do
|
116
|
+
cli = MyCLI.run('-g')
|
117
|
+
cli.result.assert == ['-g']
|
118
|
+
end
|
119
|
+
|
120
|
+
How about a non-existant options.
|
121
|
+
|
122
|
+
expect Executable::NoOptionError do
|
123
|
+
MyCLI.run('c1 --foo')
|
124
|
+
end
|
125
|
+
|