clamp 0.0.9 → 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/README.markdown +67 -36
- data/examples/flipflop +2 -0
- data/examples/speak +4 -3
- data/lib/clamp/command.rb +72 -22
- data/lib/clamp/help.rb +4 -4
- data/lib/clamp/option/declaration.rb +9 -7
- data/lib/clamp/parameter.rb +6 -4
- data/lib/clamp/parameter/declaration.rb +5 -1
- data/lib/clamp/subcommand/execution.rb +1 -1
- data/lib/clamp/version.rb +1 -1
- metadata +4 -7
- data/examples/icecream +0 -16
- data/examples/rename +0 -33
- data/lib/clamp/command/declaration.rb +0 -15
data/README.markdown
CHANGED
@@ -26,42 +26,32 @@ Clamp models a command as a Ruby class; a subclass of `Clamp::Command`. They lo
|
|
26
26
|
Integer(s)
|
27
27
|
end
|
28
28
|
|
29
|
-
parameter "WORDS ...", "the thing to say"
|
30
|
-
|
31
|
-
def execute
|
29
|
+
parameter "WORDS ...", "the thing to say", :attribute_name => :words
|
32
30
|
|
33
|
-
|
34
|
-
the_truth =
|
31
|
+
def execute
|
32
|
+
the_truth = words.join(" ")
|
35
33
|
the_truth.upcase! if loud?
|
36
|
-
|
37
34
|
iterations.times do
|
38
35
|
puts the_truth
|
39
36
|
end
|
40
|
-
|
41
37
|
end
|
42
38
|
|
43
39
|
end
|
44
40
|
|
45
|
-
|
46
|
-
|
47
|
-
The command can be invoked by instantiating the class, and asking it to run:
|
48
|
-
|
49
|
-
SpeakCommand.new("speak").run(["--loud", "a", "b", "c"])
|
50
|
-
|
51
|
-
but it's more typical to use the class-level "`run`" method:
|
41
|
+
Calling `run` on a command class creates an instance of it, then invokes it using command-line arguments (from ARGV, by default).
|
52
42
|
|
53
43
|
SpeakCommand.run
|
54
|
-
|
55
|
-
|
44
|
+
|
45
|
+
Class-level methods like `option` and `parameter` declare attributes (in a similar way to `attr_accessor`), and arrange for them to be populated automatically based on command-line arguments. They are aso used to generate `help` documentation.
|
56
46
|
|
57
47
|
Declaring options
|
58
48
|
-----------------
|
59
49
|
|
60
|
-
Options are declared using the
|
50
|
+
Options are declared using the `option` method. The three required arguments are:
|
61
51
|
|
62
52
|
1. the option switch (or switches),
|
63
|
-
2.
|
64
|
-
3. a description
|
53
|
+
2. an option argument name
|
54
|
+
3. a short description
|
65
55
|
|
66
56
|
For example:
|
67
57
|
|
@@ -90,7 +80,7 @@ Some options are just boolean flags. Pass "`:flag`" as the second parameter to
|
|
90
80
|
|
91
81
|
option "--verbose", :flag, "be chatty"
|
92
82
|
|
93
|
-
For flag options, Clamp appends "`?`" to the generated reader method; ie. you get a method called "
|
83
|
+
For flag options, Clamp appends "`?`" to the generated reader method; ie. you get a method called "`#verbose?`", rather than just "`#verbose`".
|
94
84
|
|
95
85
|
Negatable flags are easy to generate, too:
|
96
86
|
|
@@ -98,33 +88,72 @@ Negatable flags are easy to generate, too:
|
|
98
88
|
|
99
89
|
Clamp will handle both "`--force`" and "`--no-force`" options, setting the value of "`#force?`" appropriately.
|
100
90
|
|
101
|
-
|
91
|
+
Declaring parameters
|
92
|
+
--------------------
|
93
|
+
|
94
|
+
Positional parameters can be declared using `parameter`, specifying
|
95
|
+
|
96
|
+
1. the parameter name, and
|
97
|
+
2. a short description
|
98
|
+
|
99
|
+
For example:
|
100
|
+
|
101
|
+
parameter "SRC", "source file"
|
102
|
+
|
103
|
+
Like options, parameters are implemented as attributes of the command, with the default attribute name derived from the parameter name (in this case, "`src`"). By convention, parameter names are specified in uppercase, to make them obvious in usage help.
|
104
|
+
|
105
|
+
### Optional parameters
|
102
106
|
|
103
|
-
|
107
|
+
Wrapping a parameter name in square brackets indicates that it's optional, e.g.
|
108
|
+
|
109
|
+
parameter "[TARGET_DIR]", "target directory"
|
110
|
+
|
111
|
+
### Greedy parameters
|
112
|
+
|
113
|
+
Three dots at the end of a parameter name makes it "greedy" - it will consume all remaining command-line arguments. For example:
|
114
|
+
|
115
|
+
parameter "FILE ...", "input files"
|
116
|
+
|
117
|
+
The suffix "`_list`" is appended to the default attribute name for greedy parameters; in this case, an attribute called "`file_list`" would be generated.
|
118
|
+
|
119
|
+
Parsing and validation of options and parameters
|
120
|
+
------------------------------------------------
|
121
|
+
|
122
|
+
When you `#run` a command, it will first attempt to `#parse` command-line arguments, and map them onto the declared options and parameters, before invoking your `#execute` method.
|
123
|
+
|
124
|
+
If parameters are declared, Clamp will verify that all required (ie. non-optional) parameters are present, and signal a error if they aren't. Otherwise, arguments that remain after option parsing will be made available via `#arguments`.
|
125
|
+
|
126
|
+
### Validation block
|
127
|
+
|
128
|
+
Both `option` and `parameter` accept an optional block. If present, the block will be
|
129
|
+
called with the raw string option argument, and is expected to coerce it to
|
130
|
+
the correct type, e.g.
|
104
131
|
|
105
132
|
option "--port", "PORT", "port to listen on" do |s|
|
106
133
|
Integer(s)
|
107
134
|
end
|
108
135
|
|
109
|
-
If the block raises an ArgumentError, Clamp will catch it, and report that the
|
136
|
+
If the block raises an ArgumentError, Clamp will catch it, and report that the value was bad:
|
110
137
|
|
111
138
|
!!!plain
|
112
139
|
ERROR: option '--port': invalid value for Integer: "blah"
|
113
140
|
|
114
|
-
|
115
|
-
--------------------
|
141
|
+
### Advanced option/parameter handling
|
116
142
|
|
117
|
-
|
143
|
+
While Clamp provides an attribute-writer method for each declared option or parameter, you always have the option of overriding it to provide custom argument-handling logic, e.g.
|
118
144
|
|
119
|
-
parameter "
|
120
|
-
|
145
|
+
parameter "SERVER", "location of server"
|
146
|
+
|
147
|
+
def server=(server)
|
148
|
+
@server_address, @server_port = server.split(":")
|
149
|
+
end
|
121
150
|
|
122
|
-
|
151
|
+
Declaring Subcommands
|
152
|
+
---------------------
|
123
153
|
|
124
|
-
|
125
|
-
------------
|
154
|
+
Subcommand support helps you wrap a number of related commands into a single script (ala tools like "`git`"). Clamp will inspect the first command-line argument (after options are parsed), and delegate to the named subcommand.
|
126
155
|
|
127
|
-
|
156
|
+
Unsuprisingly, subcommands are declared using the `subcommand` method. e.g.
|
128
157
|
|
129
158
|
class MainCommand < Clamp::Command
|
130
159
|
|
@@ -138,9 +167,7 @@ The `subcommand` method declares sub-commands:
|
|
138
167
|
|
139
168
|
end
|
140
169
|
|
141
|
-
Clamp generates an anonymous
|
142
|
-
|
143
|
-
Alternatively, you can provide an explicit sub-command class, rather than a block:
|
170
|
+
Clamp generates an anonymous subclass of the current class, to represent the subcommand. Alternatively, you can provide an explicit subcommand class:
|
144
171
|
|
145
172
|
class MainCommand < Clamp::Command
|
146
173
|
|
@@ -156,7 +183,11 @@ Alternatively, you can provide an explicit sub-command class, rather than a bloc
|
|
156
183
|
|
157
184
|
end
|
158
185
|
|
159
|
-
|
186
|
+
### Subcommand options and parameters
|
187
|
+
|
188
|
+
Options are inheritable, so any options declared for a parent command are supported for it's subcommands. Parameters, on the other hand, are not inherited - each subcommand must declare it's own parameter list.
|
189
|
+
|
190
|
+
Note that, if a subcommand accepts options, they must be specified on the command-line _after_ the subcommand name.
|
160
191
|
|
161
192
|
Getting help
|
162
193
|
------------
|
data/examples/flipflop
CHANGED
data/examples/speak
CHANGED
@@ -1,5 +1,7 @@
|
|
1
1
|
#! /usr/bin/env ruby
|
2
2
|
|
3
|
+
# A simple Clamp command, with options and parameters
|
4
|
+
|
3
5
|
require "clamp"
|
4
6
|
|
5
7
|
class SpeakCommand < Clamp::Command
|
@@ -9,12 +11,11 @@ class SpeakCommand < Clamp::Command
|
|
9
11
|
Integer(s)
|
10
12
|
end
|
11
13
|
|
12
|
-
parameter "WORDS ...", "the thing to say"
|
14
|
+
parameter "WORDS ...", "the thing to say", :attribute_name => :words
|
13
15
|
|
14
16
|
def execute
|
15
17
|
|
16
|
-
|
17
|
-
the_truth = arguments.join(" ")
|
18
|
+
the_truth = words.join(" ")
|
18
19
|
the_truth.upcase! if loud?
|
19
20
|
|
20
21
|
iterations.times do
|
data/lib/clamp/command.rb
CHANGED
@@ -1,32 +1,72 @@
|
|
1
|
-
require 'clamp/command/declaration'
|
2
1
|
require 'clamp/errors'
|
3
2
|
require 'clamp/help'
|
3
|
+
require 'clamp/option/declaration'
|
4
4
|
require 'clamp/option/parsing'
|
5
|
+
require 'clamp/parameter/declaration'
|
5
6
|
require 'clamp/parameter/parsing'
|
7
|
+
require 'clamp/subcommand/declaration'
|
6
8
|
require 'clamp/subcommand/execution'
|
7
9
|
|
8
10
|
module Clamp
|
9
11
|
|
12
|
+
# {Command} models a shell command. Each command invocation is a new object.
|
13
|
+
# Command options and parameters are represented as attributes
|
14
|
+
# (see {Command::Declaration}).
|
15
|
+
#
|
16
|
+
# The main entry-point is {#run}, which uses {#parse} to populate attributes based
|
17
|
+
# on an array of command-line arguments, then calls {#execute} (which you provide)
|
18
|
+
# to make it go.
|
19
|
+
#
|
10
20
|
class Command
|
11
21
|
|
12
|
-
|
13
|
-
|
22
|
+
# Create a command execution.
|
23
|
+
#
|
24
|
+
# @param [String] invocation_path the path used to invoke the command
|
25
|
+
# @param [Hash] context additional data the command may need
|
26
|
+
#
|
27
|
+
def initialize(invocation_path, context = {})
|
28
|
+
@invocation_path = invocation_path
|
14
29
|
@context = context
|
15
30
|
end
|
16
31
|
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
32
|
+
# @return [String] the path used to invoke this command
|
33
|
+
#
|
34
|
+
attr_reader :invocation_path
|
35
|
+
|
36
|
+
# @return [Array<String>] unconsumed command-line arguments
|
37
|
+
#
|
38
|
+
def arguments
|
39
|
+
@arguments
|
40
|
+
end
|
22
41
|
|
42
|
+
# Parse command-line arguments.
|
43
|
+
#
|
44
|
+
# @param [Array<String>] arguments command-line arguments
|
45
|
+
# @return [Array<String>] unconsumed arguments
|
46
|
+
#
|
23
47
|
def parse(arguments)
|
24
48
|
@arguments = arguments.dup
|
25
49
|
parse_options
|
26
50
|
parse_parameters
|
51
|
+
@arguments
|
52
|
+
end
|
53
|
+
|
54
|
+
# Run the command, with the specified arguments.
|
55
|
+
#
|
56
|
+
# This calls {#parse} to process the command-line arguments,
|
57
|
+
# then delegates to {#execute}.
|
58
|
+
#
|
59
|
+
# @param [Array<String>] arguments command-line arguments
|
60
|
+
#
|
61
|
+
def run(arguments)
|
62
|
+
parse(arguments)
|
63
|
+
execute
|
27
64
|
end
|
28
65
|
|
29
|
-
#
|
66
|
+
# Execute the command (assuming that all options/parameters have been set).
|
67
|
+
#
|
68
|
+
# This method is designed to be overridden in sub-classes.
|
69
|
+
#
|
30
70
|
def execute
|
31
71
|
if self.class.has_subcommands?
|
32
72
|
execute_subcommand
|
@@ -35,19 +75,21 @@ module Clamp
|
|
35
75
|
end
|
36
76
|
end
|
37
77
|
|
38
|
-
|
39
|
-
|
40
|
-
execute
|
41
|
-
end
|
42
|
-
|
78
|
+
# @return [String] usage documentation for this command
|
79
|
+
#
|
43
80
|
def help
|
44
|
-
self.class.help(
|
81
|
+
self.class.help(invocation_path)
|
45
82
|
end
|
46
83
|
|
47
|
-
include Option::Parsing
|
48
|
-
include Parameter::Parsing
|
49
|
-
include Subcommand::Execution
|
84
|
+
include Clamp::Option::Parsing
|
85
|
+
include Clamp::Parameter::Parsing
|
86
|
+
include Clamp::Subcommand::Execution
|
87
|
+
|
88
|
+
protected
|
50
89
|
|
90
|
+
attr_accessor :context
|
91
|
+
attr_accessor :parent_command
|
92
|
+
|
51
93
|
private
|
52
94
|
|
53
95
|
def signal_usage_error(message)
|
@@ -62,16 +104,24 @@ module Clamp
|
|
62
104
|
|
63
105
|
class << self
|
64
106
|
|
65
|
-
include
|
107
|
+
include Clamp::Option::Declaration
|
108
|
+
include Clamp::Parameter::Declaration
|
109
|
+
include Clamp::Subcommand::Declaration
|
66
110
|
include Help
|
67
111
|
|
68
|
-
|
112
|
+
# Create an instance of this command class, and run it.
|
113
|
+
#
|
114
|
+
# @param [String] invocation_path the path used to invoke the command
|
115
|
+
# @param [Array<String>] arguments command-line arguments
|
116
|
+
# @param [Hash] context additional data the command may need
|
117
|
+
#
|
118
|
+
def run(invocation_path = $0, arguments = ARGV, context = {})
|
69
119
|
begin
|
70
|
-
new(
|
120
|
+
new(invocation_path, context).run(arguments)
|
71
121
|
rescue Clamp::UsageError => e
|
72
122
|
$stderr.puts "ERROR: #{e.message}"
|
73
123
|
$stderr.puts ""
|
74
|
-
$stderr.puts "See: '#{
|
124
|
+
$stderr.puts "See: '#{invocation_path} --help'"
|
75
125
|
exit(1)
|
76
126
|
rescue Clamp::HelpWanted => e
|
77
127
|
puts e.command.help
|
data/lib/clamp/help.rb
CHANGED
@@ -20,20 +20,20 @@ module Clamp
|
|
20
20
|
declared_usage_descriptions || [derived_usage_description]
|
21
21
|
end
|
22
22
|
|
23
|
-
def help(
|
23
|
+
def help(invocation_path)
|
24
24
|
help = StringIO.new
|
25
25
|
help.puts "Usage:"
|
26
26
|
usage_descriptions.each_with_index do |usage, i|
|
27
|
-
help.puts " #{
|
27
|
+
help.puts " #{invocation_path} #{usage}".rstrip
|
28
28
|
end
|
29
29
|
detail_format = " %-29s %s"
|
30
|
-
|
30
|
+
if has_parameters?
|
31
31
|
help.puts "\nParameters:"
|
32
32
|
parameters.each do |parameter|
|
33
33
|
help.puts detail_format % parameter.help
|
34
34
|
end
|
35
35
|
end
|
36
|
-
|
36
|
+
if has_subcommands?
|
37
37
|
help.puts "\nSubcommands:"
|
38
38
|
recognised_subcommands.each do |subcommand|
|
39
39
|
help.puts detail_format % subcommand.help
|
@@ -6,7 +6,7 @@ module Clamp
|
|
6
6
|
|
7
7
|
module Declaration
|
8
8
|
|
9
|
-
include AttributeDeclaration
|
9
|
+
include Clamp::AttributeDeclaration
|
10
10
|
|
11
11
|
def option(switches, type, description, opts = {}, &block)
|
12
12
|
option = Clamp::Option.new(switches, type, description, opts)
|
@@ -18,20 +18,22 @@ module Clamp
|
|
18
18
|
!declared_options.empty?
|
19
19
|
end
|
20
20
|
|
21
|
+
def find_option(switch)
|
22
|
+
recognised_options.find { |o| o.handles?(switch) }
|
23
|
+
end
|
24
|
+
|
25
|
+
protected
|
26
|
+
|
21
27
|
def declared_options
|
22
28
|
my_declared_options + inherited_declared_options
|
23
29
|
end
|
24
30
|
|
31
|
+
private
|
32
|
+
|
25
33
|
def recognised_options
|
26
34
|
declared_options + standard_options
|
27
35
|
end
|
28
36
|
|
29
|
-
def find_option(switch)
|
30
|
-
recognised_options.find { |o| o.handles?(switch) }
|
31
|
-
end
|
32
|
-
|
33
|
-
private
|
34
|
-
|
35
37
|
def my_declared_options
|
36
38
|
@my_declared_options ||= []
|
37
39
|
end
|
data/lib/clamp/parameter.rb
CHANGED
@@ -30,18 +30,20 @@ module Clamp
|
|
30
30
|
|
31
31
|
private
|
32
32
|
|
33
|
+
NAME_PATTERN = "([A-Za-z0-9_-]+)"
|
34
|
+
|
33
35
|
def infer_attribute_name_and_multiplicity
|
34
36
|
case @name
|
35
|
-
when /^\[
|
37
|
+
when /^\[#{NAME_PATTERN}\]$/
|
36
38
|
@attribute_name = $1
|
37
|
-
when /^\[
|
39
|
+
when /^\[#{NAME_PATTERN}\] ...$/
|
38
40
|
@attribute_name = "#{$1}_list"
|
39
41
|
@multivalued = true
|
40
|
-
when
|
42
|
+
when /^#{NAME_PATTERN} ...$/
|
41
43
|
@attribute_name = "#{$1}_list"
|
42
44
|
@multivalued = true
|
43
45
|
@required = true
|
44
|
-
when
|
46
|
+
when /^#{NAME_PATTERN}$/
|
45
47
|
@attribute_name = @name
|
46
48
|
@required = true
|
47
49
|
else
|
@@ -6,12 +6,16 @@ module Clamp
|
|
6
6
|
|
7
7
|
module Declaration
|
8
8
|
|
9
|
-
include AttributeDeclaration
|
9
|
+
include Clamp::AttributeDeclaration
|
10
10
|
|
11
11
|
def parameters
|
12
12
|
@parameters ||= []
|
13
13
|
end
|
14
14
|
|
15
|
+
def has_parameters?
|
16
|
+
!parameters.empty?
|
17
|
+
end
|
18
|
+
|
15
19
|
def parameter(name, description, options = {}, &block)
|
16
20
|
parameter = Parameter.new(name, description, options)
|
17
21
|
parameters << parameter
|
@@ -9,7 +9,7 @@ module Clamp
|
|
9
9
|
signal_usage_error "no subcommand specified" if arguments.empty?
|
10
10
|
subcommand_name = arguments.shift
|
11
11
|
subcommand_class = find_subcommand_class(subcommand_name)
|
12
|
-
subcommand = subcommand_class.new("#{
|
12
|
+
subcommand = subcommand_class.new("#{invocation_path} #{subcommand_name}", context)
|
13
13
|
subcommand.parent_command = self
|
14
14
|
subcommand.run(arguments)
|
15
15
|
end
|
data/lib/clamp/version.rb
CHANGED
metadata
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: clamp
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
hash:
|
4
|
+
hash: 27
|
5
5
|
prerelease: false
|
6
6
|
segments:
|
7
7
|
- 0
|
8
|
+
- 1
|
8
9
|
- 0
|
9
|
-
|
10
|
-
version: 0.0.9
|
10
|
+
version: 0.1.0
|
11
11
|
platform: ruby
|
12
12
|
authors:
|
13
13
|
- Mike Williams
|
@@ -15,7 +15,7 @@ autorequire:
|
|
15
15
|
bindir: bin
|
16
16
|
cert_chain: []
|
17
17
|
|
18
|
-
date: 2010-11-
|
18
|
+
date: 2010-11-15 00:00:00 +11:00
|
19
19
|
default_executable:
|
20
20
|
dependencies: []
|
21
21
|
|
@@ -37,13 +37,10 @@ files:
|
|
37
37
|
- Rakefile
|
38
38
|
- clamp.gemspec
|
39
39
|
- examples/flipflop
|
40
|
-
- examples/icecream
|
41
|
-
- examples/rename
|
42
40
|
- examples/speak
|
43
41
|
- lib/clamp.rb
|
44
42
|
- lib/clamp/attribute_declaration.rb
|
45
43
|
- lib/clamp/command.rb
|
46
|
-
- lib/clamp/command/declaration.rb
|
47
44
|
- lib/clamp/errors.rb
|
48
45
|
- lib/clamp/help.rb
|
49
46
|
- lib/clamp/option.rb
|
data/examples/icecream
DELETED
@@ -1,16 +0,0 @@
|
|
1
|
-
#! /usr/bin/env ruby
|
2
|
-
|
3
|
-
require "clamp"
|
4
|
-
|
5
|
-
class Icecream < Clamp::Command
|
6
|
-
|
7
|
-
option "--flavour", "FLAVOUR", "ice-cream flavour"
|
8
|
-
|
9
|
-
def execute
|
10
|
-
signal_usage_error "what flavour?" unless flavour
|
11
|
-
puts "You chose #{flavour}. Excellent choice!"
|
12
|
-
end
|
13
|
-
|
14
|
-
end
|
15
|
-
|
16
|
-
Icecream.run
|
data/examples/rename
DELETED
@@ -1,33 +0,0 @@
|
|
1
|
-
#! /usr/bin/env ruby
|
2
|
-
|
3
|
-
require "clamp"
|
4
|
-
|
5
|
-
class Rename < Clamp::Command
|
6
|
-
|
7
|
-
usage "[OPTIONS] TRANSFORM FILE ..."
|
8
|
-
|
9
|
-
parameter "TRANSFORM", "a Ruby expression"
|
10
|
-
parameter "FILE", "a file to rename"
|
11
|
-
|
12
|
-
option ["-v", "--verbose"], :flag, "be verbose"
|
13
|
-
|
14
|
-
option ["-n", "--times"], "TIMES", "repetitions" do |n|
|
15
|
-
n = Integer(n)
|
16
|
-
raise ArgumentError, "too big" if n > 9
|
17
|
-
n
|
18
|
-
end
|
19
|
-
|
20
|
-
def initialize(name)
|
21
|
-
super
|
22
|
-
@times = 1
|
23
|
-
end
|
24
|
-
|
25
|
-
def execute
|
26
|
-
@times.times do
|
27
|
-
puts "Blah blah blah" if verbose?
|
28
|
-
end
|
29
|
-
end
|
30
|
-
|
31
|
-
end
|
32
|
-
|
33
|
-
Rename.run
|
@@ -1,15 +0,0 @@
|
|
1
|
-
require 'clamp/option/declaration'
|
2
|
-
require 'clamp/parameter/declaration'
|
3
|
-
require 'clamp/subcommand/declaration'
|
4
|
-
|
5
|
-
module Clamp
|
6
|
-
class Command
|
7
|
-
|
8
|
-
module Declaration
|
9
|
-
include Option::Declaration
|
10
|
-
include Parameter::Declaration
|
11
|
-
include Subcommand::Declaration
|
12
|
-
end
|
13
|
-
|
14
|
-
end
|
15
|
-
end
|