executable 1.1.0 → 1.2.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.
- 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
@@ -0,0 +1,109 @@
|
|
1
|
+
== Command Help
|
2
|
+
|
3
|
+
Executable Commands can generate help output. It does this by extracting
|
4
|
+
the commenst associated with the option methods. A description of the
|
5
|
+
command itself is taken from the comment on the `#call` method. Only
|
6
|
+
the first line of a comment is used, so the reset of the comment can
|
7
|
+
still be catered to documention tools such as YARD and RDoc.
|
8
|
+
|
9
|
+
Let's setup an example CLI subclass to demonstrate this.
|
10
|
+
|
11
|
+
class MyCLI < Executable::Command
|
12
|
+
|
13
|
+
# This is global option -g.
|
14
|
+
# Yadda yadda yadda...
|
15
|
+
def g=(bool)
|
16
|
+
@g = bool
|
17
|
+
end
|
18
|
+
|
19
|
+
def g?; @g; end
|
20
|
+
|
21
|
+
# Subcommand `c1`.
|
22
|
+
class C1 < self
|
23
|
+
|
24
|
+
# This does c1.
|
25
|
+
def call(*args)
|
26
|
+
end
|
27
|
+
|
28
|
+
# This is option --o1 for c1.
|
29
|
+
def o1=(value)
|
30
|
+
end
|
31
|
+
|
32
|
+
# This is option --o2 for c1.
|
33
|
+
def o2=(value)
|
34
|
+
end
|
35
|
+
|
36
|
+
end
|
37
|
+
|
38
|
+
# Subcommand `c2`.
|
39
|
+
class C2 < self
|
40
|
+
|
41
|
+
# This does c2.
|
42
|
+
def call(*args)
|
43
|
+
end
|
44
|
+
|
45
|
+
# This is option --o1 for c2.
|
46
|
+
def o1=(value)
|
47
|
+
end
|
48
|
+
|
49
|
+
# This is option --o2 for c2.
|
50
|
+
def o2=(value)
|
51
|
+
end
|
52
|
+
|
53
|
+
end
|
54
|
+
|
55
|
+
end
|
56
|
+
|
57
|
+
=== Plain Text
|
58
|
+
|
59
|
+
The help output,
|
60
|
+
|
61
|
+
@out = MyCLI::C1.help.to_s
|
62
|
+
|
63
|
+
should be clearly laid out as follows:
|
64
|
+
|
65
|
+
Usage: mycli-c1 [options...] [subcommand]
|
66
|
+
|
67
|
+
This does c1.
|
68
|
+
|
69
|
+
OPTIONS
|
70
|
+
-g This is global option -g.
|
71
|
+
--o1=VALUE This is option --o1 for c1.
|
72
|
+
--o2=VALUE This is option --o2 for c1.
|
73
|
+
|
74
|
+
Copyright (c) 2012
|
75
|
+
|
76
|
+
=== Markdown
|
77
|
+
|
78
|
+
The help feature can also output ronn-style markdown,
|
79
|
+
|
80
|
+
@out = MyCLI::C1.help.markdown
|
81
|
+
|
82
|
+
should be clearly laid out as follows:
|
83
|
+
|
84
|
+
mycli-c1(1) - This does c1.
|
85
|
+
===========================
|
86
|
+
|
87
|
+
## SYNOPSIS
|
88
|
+
|
89
|
+
`mycli-c1` [options...] [subcommand]
|
90
|
+
|
91
|
+
## DESCRIPTION
|
92
|
+
|
93
|
+
This does c1.
|
94
|
+
|
95
|
+
## OPTIONS
|
96
|
+
|
97
|
+
* `-g`:
|
98
|
+
This is global option -g.
|
99
|
+
|
100
|
+
* `--o1=VALUE`:
|
101
|
+
This is option --o1 for c1.
|
102
|
+
|
103
|
+
* `--o2=VALUE`:
|
104
|
+
This is option --o2 for c1.
|
105
|
+
|
106
|
+
## COPYRIGHT
|
107
|
+
|
108
|
+
Copyright (c) 2012
|
109
|
+
|
@@ -0,0 +1,14 @@
|
|
1
|
+
== Manpage
|
2
|
+
|
3
|
+
If a man page is available for a given command using the #show_help
|
4
|
+
method will automatically find the manpage and display it.
|
5
|
+
|
6
|
+
sample = File.dirname(__FILE__) + '/samples'
|
7
|
+
|
8
|
+
load(sample + '/bin/hello')
|
9
|
+
|
10
|
+
manpage = Hello.cli.manpage
|
11
|
+
|
12
|
+
manpage.assert == sample + '/man/hello.1'
|
13
|
+
|
14
|
+
|
@@ -0,0 +1,152 @@
|
|
1
|
+
== OptionParser Example
|
2
|
+
|
3
|
+
This example mimics the one given in optparse.rb documentation.
|
4
|
+
|
5
|
+
require 'ostruct'
|
6
|
+
require 'time'
|
7
|
+
|
8
|
+
class ExampleCLI < Executable::Command
|
9
|
+
|
10
|
+
CODES = %w[iso-2022-jp shift_jis euc-jp utf8 binary]
|
11
|
+
CODE_ALIASES = { "jis" => "iso-2022-jp", "sjis" => "shift_jis" }
|
12
|
+
|
13
|
+
attr :options
|
14
|
+
|
15
|
+
def initialize
|
16
|
+
super
|
17
|
+
reset
|
18
|
+
end
|
19
|
+
|
20
|
+
def reset
|
21
|
+
@options = OpenStruct.new
|
22
|
+
@options.library = []
|
23
|
+
@options.inplace = false
|
24
|
+
@options.encoding = "utf8"
|
25
|
+
@options.transfer_type = :auto
|
26
|
+
@options.verbose = false
|
27
|
+
end
|
28
|
+
|
29
|
+
# Require the LIBRARY before executing your script
|
30
|
+
def require=(lib)
|
31
|
+
options.library << lib
|
32
|
+
end
|
33
|
+
alias :r= :require=
|
34
|
+
|
35
|
+
# Edit ARGV files in place (make backup if EXTENSION supplied)
|
36
|
+
def inplace=(ext)
|
37
|
+
options.inplace = true
|
38
|
+
options.extension = ext
|
39
|
+
options.extension.sub!(/\A\.?(?=.)/, ".") # ensure extension begins with dot.
|
40
|
+
end
|
41
|
+
alias :i= :inplace=
|
42
|
+
|
43
|
+
# Delay N seconds before executing
|
44
|
+
# Cast 'delay' argument to a Float.
|
45
|
+
def delay=(n)
|
46
|
+
options.delay = n.to_float
|
47
|
+
end
|
48
|
+
|
49
|
+
# Begin execution at given time
|
50
|
+
# Cast 'time' argument to a Time object.
|
51
|
+
def time=(time)
|
52
|
+
options.time = Time.parse(time)
|
53
|
+
end
|
54
|
+
alias :t= :time=
|
55
|
+
|
56
|
+
# Specify record separator (default \\0)
|
57
|
+
# Cast to octal integer.
|
58
|
+
def irs=(octal)
|
59
|
+
options.record_separator = octal.to_i(8)
|
60
|
+
end
|
61
|
+
alias :F= :irs=
|
62
|
+
|
63
|
+
# Example 'list' of arguments
|
64
|
+
# List of arguments.
|
65
|
+
def list=(args)
|
66
|
+
options.list = list.split(',')
|
67
|
+
end
|
68
|
+
|
69
|
+
# Keyword completion. We are specifying a specific set of arguments (CODES
|
70
|
+
# and CODE_ALIASES - notice the latter is a Hash), and the user may provide
|
71
|
+
# the shortest unambiguous text.
|
72
|
+
CODE_LIST = (CODE_ALIASES.keys + CODES)
|
73
|
+
|
74
|
+
help.option(:code, "Select encoding (#{CODE_LIST})")
|
75
|
+
|
76
|
+
# Select encoding
|
77
|
+
def code=(code)
|
78
|
+
codes = CODE_LIST.select{ |x| /^#{code}/ =~ x }
|
79
|
+
codes = codes.map{ |x| CODE_ALIASES.key?(x) ? CODE_ALIASES[x] : x }.uniq
|
80
|
+
raise ArgumentError unless codes.size == 1
|
81
|
+
options.encoding = codes.first
|
82
|
+
end
|
83
|
+
|
84
|
+
# Select transfer type (text, binary, auto)
|
85
|
+
# Optional argument with keyword completion.
|
86
|
+
def type=(type)
|
87
|
+
raise ArgumentError unless %w{text binary auto}.include(type.downcase)
|
88
|
+
options.transfer_type = type.downcase
|
89
|
+
end
|
90
|
+
|
91
|
+
# Run verbosely
|
92
|
+
# Boolean switch.
|
93
|
+
def verbose=(bool)
|
94
|
+
options.verbose = bool
|
95
|
+
end
|
96
|
+
def verbose?
|
97
|
+
@options.verbose
|
98
|
+
end
|
99
|
+
alias :v= :verbose=
|
100
|
+
alias :v? :verbose?
|
101
|
+
|
102
|
+
# Show this message
|
103
|
+
# No argument, shows at tail. This will print an options summary.
|
104
|
+
def help!
|
105
|
+
puts help_text
|
106
|
+
exit
|
107
|
+
end
|
108
|
+
alias :h! :help!
|
109
|
+
|
110
|
+
# Show version
|
111
|
+
# Another typical switch to print the version.
|
112
|
+
def version?
|
113
|
+
puts Executor::VERSION
|
114
|
+
exit
|
115
|
+
end
|
116
|
+
|
117
|
+
#
|
118
|
+
def call
|
119
|
+
# ... main procedure here ...
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
We will run some scenarios on this example to make sure it works.
|
124
|
+
|
125
|
+
cli = ExampleCLI.execute('-r=facets')
|
126
|
+
cli.options.library.assert == ['facets']
|
127
|
+
|
128
|
+
Make sure time option parses.
|
129
|
+
|
130
|
+
cli = ExampleCLI.execute('--time=2010-10-10')
|
131
|
+
cli.options.time.assert == Time.parse('2010-10-10')
|
132
|
+
|
133
|
+
Make sure code lookup words and is limted to the selections provided.
|
134
|
+
|
135
|
+
cli = ExampleCLI.execute('--code=ji')
|
136
|
+
cli.options.encoding.assert == 'iso-2022-jp'
|
137
|
+
|
138
|
+
expect ArgumentError do
|
139
|
+
ExampleCLI.execute('--code=xxx')
|
140
|
+
end
|
141
|
+
|
142
|
+
Ensure +irs+ is set to an octal number.
|
143
|
+
|
144
|
+
cli = ExampleCLI.execute('-F 32')
|
145
|
+
cli.options.record_separator.assert == 032
|
146
|
+
|
147
|
+
Ensure extension begins with dot and inplace is set to true.
|
148
|
+
|
149
|
+
cli = ExampleCLI.execute('--inplace txt')
|
150
|
+
cli.options.extension.assert == '.txt'
|
151
|
+
cli.options.inplace.assert == true
|
152
|
+
|
@@ -0,0 +1,40 @@
|
|
1
|
+
= Subclass Example
|
2
|
+
|
3
|
+
Lets say we have a class that we would like to work with on
|
4
|
+
the command line, but want to keep the class itself unchanaged
|
5
|
+
without mixin.
|
6
|
+
|
7
|
+
class Hello
|
8
|
+
attr_accessor :name
|
9
|
+
|
10
|
+
def initialize(name="World")
|
11
|
+
@name = name
|
12
|
+
end
|
13
|
+
|
14
|
+
def hello
|
15
|
+
@output = "Hello, #{name}!"
|
16
|
+
end
|
17
|
+
|
18
|
+
def output
|
19
|
+
@output
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
Rather then including Exectuable in the class directly, we can
|
24
|
+
create a subclass and use it instead.
|
25
|
+
|
26
|
+
class HelloCommand < Hello
|
27
|
+
include Executable
|
28
|
+
|
29
|
+
def call(*args)
|
30
|
+
hello
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
Now we can execute the command perfectly well.
|
35
|
+
|
36
|
+
cmd = HelloCommand.execute(['hello', '--name=Fred'])
|
37
|
+
cmd.output.assert == "Hello, Fred!"
|
38
|
+
|
39
|
+
And the original class remains undisturbed.
|
40
|
+
|
@@ -0,0 +1,36 @@
|
|
1
|
+
= README Example
|
2
|
+
|
3
|
+
This is the example used in the documentation.
|
4
|
+
|
5
|
+
class Example
|
6
|
+
include Executable
|
7
|
+
|
8
|
+
attr_switch :quiet
|
9
|
+
|
10
|
+
def bread(*args)
|
11
|
+
["bread", quiet?, *args]
|
12
|
+
end
|
13
|
+
|
14
|
+
def butter(*args)
|
15
|
+
["butter", quiet?, *args]
|
16
|
+
end
|
17
|
+
|
18
|
+
# Route call to methods.
|
19
|
+
def call(name, *args)
|
20
|
+
meth = public_method(name)
|
21
|
+
meth.call(*args)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
Use a subcommand and an argument.
|
26
|
+
|
27
|
+
c, a = Example.parse(['butter', 'yum'])
|
28
|
+
r = c.call(*a)
|
29
|
+
r.assert == ["butter", nil, "yum"]
|
30
|
+
|
31
|
+
A subcommand and a boolean option.
|
32
|
+
|
33
|
+
c, a = Example.parse(['bread', '--quiet'])
|
34
|
+
r = c.call(*a)
|
35
|
+
r.assert == ["bread", true]
|
36
|
+
|
@@ -0,0 +1,29 @@
|
|
1
|
+
== Legacy/Dispath
|
2
|
+
|
3
|
+
The Dispatch mixin, which is also called Legacy b/c this is how older
|
4
|
+
version of Executable worked, provides Executable with a `#call` method
|
5
|
+
that automatically routes the to a method given by the first argument.
|
6
|
+
|
7
|
+
class DispatchExample < Executable::Command
|
8
|
+
include Legacy
|
9
|
+
|
10
|
+
attr :result
|
11
|
+
|
12
|
+
def foo
|
13
|
+
@result = :foo
|
14
|
+
end
|
15
|
+
|
16
|
+
def bar
|
17
|
+
@result = :bar
|
18
|
+
end
|
19
|
+
|
20
|
+
end
|
21
|
+
|
22
|
+
Now when we invoke the command, the
|
23
|
+
|
24
|
+
eg = DispatchExample.run('foo')
|
25
|
+
eg.result.assert == :foo
|
26
|
+
|
27
|
+
eg = DispatchExample.run('bar')
|
28
|
+
eg.result.assert == :bar
|
29
|
+
|
data/demo/applique/ae.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require 'ae'
|
@@ -0,0 +1 @@
|
|
1
|
+
require 'executable'
|
@@ -0,0 +1,31 @@
|
|
1
|
+
#!/usr/bin/env
|
2
|
+
|
3
|
+
require 'executable'
|
4
|
+
|
5
|
+
class Hello < Executable::Command
|
6
|
+
# Say it in uppercase?
|
7
|
+
def load=(bool)
|
8
|
+
@loud = bool
|
9
|
+
end
|
10
|
+
|
11
|
+
#
|
12
|
+
def loud?
|
13
|
+
@loud
|
14
|
+
end
|
15
|
+
|
16
|
+
# Show this message.
|
17
|
+
def help?
|
18
|
+
cli.show_help
|
19
|
+
exit
|
20
|
+
end
|
21
|
+
alias :h? :help?
|
22
|
+
|
23
|
+
# Say hello.
|
24
|
+
def call(name)
|
25
|
+
name = name || 'World'
|
26
|
+
str = "Hello, #{name}!"
|
27
|
+
str = str.upcase if loud?
|
28
|
+
puts str
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
@@ -0,0 +1,22 @@
|
|
1
|
+
.\" generated with Ronn/v0.7.3
|
2
|
+
.\" http://github.com/rtomayko/ronn/tree/0.7.3
|
3
|
+
.
|
4
|
+
.TH "HELLOCMD" "1" "January 2012" "" ""
|
5
|
+
.
|
6
|
+
.SH "NAME"
|
7
|
+
\fBhellocmd\fR \- Say hello\.
|
8
|
+
.
|
9
|
+
.SH "SYNOPSIS"
|
10
|
+
\fBhellocmd\fR [options\.\.\.] [subcommand]
|
11
|
+
.
|
12
|
+
.SH "DESCRIPTION"
|
13
|
+
Say hello\.
|
14
|
+
.
|
15
|
+
.SH "OPTIONS"
|
16
|
+
.
|
17
|
+
.TP
|
18
|
+
\fB\-\-load=BOOL\fR
|
19
|
+
Say it in uppercase?
|
20
|
+
.
|
21
|
+
.SH "COPYRIGHT"
|
22
|
+
Copyright (c) 2012
|