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
@@ -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
|