clamp 0.6.0 → 0.6.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +8 -8
- data/CHANGES.md +3 -2
- data/README.md +55 -13
- data/lib/clamp/attribute/declaration.rb +2 -13
- data/lib/clamp/attribute/definition.rb +6 -0
- data/lib/clamp/attribute/instance.rb +76 -0
- data/lib/clamp/command.rb +1 -3
- data/lib/clamp/option/parsing.rb +7 -10
- data/lib/clamp/parameter/parsing.rb +2 -10
- data/lib/clamp/subcommand/declaration.rb +4 -0
- data/lib/clamp/subcommand/execution.rb +3 -7
- data/lib/clamp/version.rb +1 -1
- data/spec/clamp/command_spec.rb +17 -13
- metadata +3 -2
checksums.yaml
CHANGED
@@ -1,15 +1,15 @@
|
|
1
1
|
---
|
2
2
|
!binary "U0hBMQ==":
|
3
3
|
metadata.gz: !binary |-
|
4
|
-
|
4
|
+
NGJhZjcxZjg4MjljZTZhODNlM2M0NzFhNGRmODFlMGQ1ZDdjYzFhNQ==
|
5
5
|
data.tar.gz: !binary |-
|
6
|
-
|
6
|
+
NGVhZDMyYzU2ODk5MjY0NjY0YWZhYjRlODhkYzZhZGQ5MzlhZTUzMA==
|
7
7
|
!binary "U0hBNTEy":
|
8
8
|
metadata.gz: !binary |-
|
9
|
-
|
10
|
-
|
11
|
-
|
9
|
+
ZTJhZjg3NWJmYmNmNjE2ODZmMmRhOWFmNWUwNWVhNmFlMmMwZGFhMjZlZjU4
|
10
|
+
NmM5ODRmNjIxNjU3MzRhNjAwY2YyZDk1YzcyNTU1OGUwMGQ1M2YxZGYwMmZl
|
11
|
+
MWI3OTc0MWRlYjkxZjA2N2ZkNmEyNmE1OWNjMjRjN2QwNjczNDU=
|
12
12
|
data.tar.gz: !binary |-
|
13
|
-
|
14
|
-
|
15
|
-
|
13
|
+
OTE5Y2Y0YWIyMzJmZmY5MGJjYzJiNTYyOGQ4NjQ4MjljNjRkM2M5YWE4NzU0
|
14
|
+
OTE0NzY0MzkzNzBiNGY4YmMwYWFmNzhkZmZmOWJkZjEwMzExYzVlMzFjZmI5
|
15
|
+
M2I2Y2MxM2Y5YzhiYmY4YjczMjVmY2Q5MzgxMmI0ZjQwNWQzN2I=
|
data/CHANGES.md
CHANGED
@@ -1,10 +1,11 @@
|
|
1
1
|
# Changelog
|
2
2
|
|
3
|
-
## 0.6.0 (2013-04
|
3
|
+
## 0.6.0 (2013-04-28)
|
4
4
|
|
5
5
|
* Introduce "banner" to describe a command (replacing "self.description=").
|
6
|
+
* Introduce "Clamp do ... end" syntax sugar.
|
6
7
|
* Allow parameters to be specified before a subcommand.
|
7
8
|
* Add support for :multivalued options.
|
8
9
|
* Multi valued options and parameters get an "#append_to_foo_list" method, rather than
|
9
10
|
"#foo_list=".
|
10
|
-
|
11
|
+
* default_subcommand must be specified before any subcommands.
|
data/README.md
CHANGED
@@ -17,7 +17,30 @@ Yeah, sorry. There are a bunch of existing command-line parsing libraries out t
|
|
17
17
|
Quick Start
|
18
18
|
-----------
|
19
19
|
|
20
|
-
|
20
|
+
A typical Clamp script looks like this:
|
21
|
+
|
22
|
+
require 'clamp'
|
23
|
+
|
24
|
+
Clamp do
|
25
|
+
|
26
|
+
option "--loud", :flag, "say it loud"
|
27
|
+
option ["-n", "--iterations"], "N", "say it N times", :default => 1 do |s|
|
28
|
+
Integer(s)
|
29
|
+
end
|
30
|
+
|
31
|
+
parameter "WORDS ...", "the thing to say", :attribute_name => :words
|
32
|
+
|
33
|
+
def execute
|
34
|
+
the_truth = words.join(" ")
|
35
|
+
the_truth.upcase! if loud?
|
36
|
+
iterations.times do
|
37
|
+
puts the_truth
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
end
|
42
|
+
|
43
|
+
Internally, Clamp models a command as a Ruby class (a subclass of `Clamp::Command`), and a command execution as an instance of that class. The example above is really just syntax-sugar for:
|
21
44
|
|
22
45
|
require 'clamp'
|
23
46
|
|
@@ -42,9 +65,7 @@ Clamp models a command as a Ruby class; a subclass of `Clamp::Command`. They lo
|
|
42
65
|
|
43
66
|
SpeakCommand.run
|
44
67
|
|
45
|
-
Class-level methods like `option` and `parameter` declare attributes
|
46
|
-
|
47
|
-
The call to `run` creates an instance of the command class, then invokes it with command-line arguments (from `ARGV`).
|
68
|
+
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 also used to generate `help` documentation.
|
48
69
|
|
49
70
|
There are more examples demonstrating various features of Clamp [on Github][examples].
|
50
71
|
|
@@ -63,7 +84,7 @@ For example:
|
|
63
84
|
|
64
85
|
option "--flavour", "FLAVOUR", "ice-cream flavour"
|
65
86
|
|
66
|
-
It works a little like `attr_accessor`, defining reader and writer methods on the command class. The attribute name is
|
87
|
+
It works a little like `attr_accessor`, defining reader and writer methods on the command class. The attribute name is inferred from the switch (in this case, "`flavour`"). When you pass options to your command, Clamp will populate the attributes, which are then available for use in your `#execute` method.
|
67
88
|
|
68
89
|
def execute
|
69
90
|
puts "You chose #{flavour}. Excellent choice!"
|
@@ -102,6 +123,14 @@ Although 'required option' is a an oxymoron, Clamp lets you mark an option as re
|
|
102
123
|
|
103
124
|
Note that it makes no sense to mark a `:flag` option, or one with a `:default`, as `:required`.
|
104
125
|
|
126
|
+
### Multivalued options
|
127
|
+
|
128
|
+
Declaring an option "`:multivalued`" allows it to be specified multiple times on the command line.
|
129
|
+
|
130
|
+
option "--format", "FORMAT", "output format", :multivalued => true
|
131
|
+
|
132
|
+
The underlying attribute becomes an Array, and the suffix "`_list`" is appended to the default attribute name. In this case, an attribute called "`format_list`" would be generated (unless you override the default by specifying an `:attribute_name`).
|
133
|
+
|
105
134
|
Declaring parameters
|
106
135
|
--------------------
|
107
136
|
|
@@ -122,13 +151,14 @@ Wrapping a parameter name in square brackets indicates that it's optional, e.g.
|
|
122
151
|
|
123
152
|
parameter "[TARGET_DIR]", "target directory"
|
124
153
|
|
125
|
-
###
|
154
|
+
### Multivalued (aka "greedy") parameters
|
126
155
|
|
127
156
|
Three dots at the end of a parameter name makes it "greedy" - it will consume all remaining command-line arguments. For example:
|
128
157
|
|
129
|
-
parameter "FILE ...", "input files"
|
158
|
+
parameter "FILE ...", "input files", :attribute_name => :files
|
130
159
|
|
131
|
-
|
160
|
+
|
161
|
+
Like multivalued options, greedy parameters are backed by an Array attribute (named with a "`_list`" suffix, by default).
|
132
162
|
|
133
163
|
Parsing and validation of options and parameters
|
134
164
|
------------------------------------------------
|
@@ -137,11 +167,12 @@ When you `#run` a command, it will first attempt to `#parse` command-line argume
|
|
137
167
|
|
138
168
|
Clamp will verify that all required (ie. non-optional) parameters are present, and signal a error if they aren't.
|
139
169
|
|
140
|
-
### Validation
|
170
|
+
### Validation
|
141
171
|
|
142
172
|
Both `option` and `parameter` accept an optional block. If present, the block will be
|
143
|
-
called with the raw string
|
144
|
-
|
173
|
+
called with the raw string argument, and is expected to validate it. The value returned by the block will be assigned to the underlying attribute, so it's also a good place to coerce the String to a different type, if appropriate.
|
174
|
+
|
175
|
+
For example:
|
145
176
|
|
146
177
|
option "--port", "PORT", "port to listen on" do |s|
|
147
178
|
Integer(s)
|
@@ -152,6 +183,17 @@ If the block raises an ArgumentError, Clamp will catch it, and report that the v
|
|
152
183
|
!!!plain
|
153
184
|
ERROR: option '--port': invalid value for Integer: "blah"
|
154
185
|
|
186
|
+
For multivalued options and parameters, the validation block will be called for each value specified.
|
187
|
+
|
188
|
+
More complex validation, e.g. those involving multiple options/parameters, should be performed within the `#execute` method. Use `#signal_usage_error` to tell the user what they did wrong, e.g.
|
189
|
+
|
190
|
+
def execute
|
191
|
+
if port < 1024 && user != 'root'
|
192
|
+
signal_usage_error "port restricted for non-root users"
|
193
|
+
end
|
194
|
+
# ... carry on ...
|
195
|
+
end
|
196
|
+
|
155
197
|
### Advanced option/parameter handling
|
156
198
|
|
157
199
|
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.
|
@@ -199,7 +241,7 @@ Subcommand support helps you wrap a number of related commands into a single scr
|
|
199
241
|
|
200
242
|
Unsuprisingly, subcommands are declared using the `subcommand` method. e.g.
|
201
243
|
|
202
|
-
|
244
|
+
Clamp do
|
203
245
|
|
204
246
|
subcommand "init", "Initialize the repository" do
|
205
247
|
|
@@ -231,7 +273,7 @@ Clamp generates an anonymous subclass of the current class, to represent the sub
|
|
231
273
|
|
232
274
|
You can set a default subcommand, at the class level, as follows:
|
233
275
|
|
234
|
-
|
276
|
+
Clamp do
|
235
277
|
|
236
278
|
self.default_subcommand = "status"
|
237
279
|
|
@@ -13,11 +13,7 @@ module Clamp
|
|
13
13
|
|
14
14
|
def define_reader_for(attribute)
|
15
15
|
define_method(attribute.read_method) do
|
16
|
-
|
17
|
-
instance_variable_get(attribute.ivar_name)
|
18
|
-
else
|
19
|
-
send(attribute.default_method)
|
20
|
-
end
|
16
|
+
attribute.of(self)._read
|
21
17
|
end
|
22
18
|
end
|
23
19
|
|
@@ -32,14 +28,7 @@ module Clamp
|
|
32
28
|
if block
|
33
29
|
value = instance_exec(value, &block)
|
34
30
|
end
|
35
|
-
|
36
|
-
unless instance_variable_defined?(attribute.ivar_name)
|
37
|
-
instance_variable_set(attribute.ivar_name, [])
|
38
|
-
end
|
39
|
-
instance_variable_get(attribute.ivar_name) << value
|
40
|
-
else
|
41
|
-
instance_variable_set(attribute.ivar_name, value)
|
42
|
-
end
|
31
|
+
attribute.of(self)._write(value)
|
43
32
|
end
|
44
33
|
end
|
45
34
|
|
@@ -0,0 +1,76 @@
|
|
1
|
+
module Clamp
|
2
|
+
module Attribute
|
3
|
+
|
4
|
+
# Represents an option/parameter of a Clamp::Command instance.
|
5
|
+
#
|
6
|
+
class Instance
|
7
|
+
|
8
|
+
def initialize(attribute, command)
|
9
|
+
@attribute = attribute
|
10
|
+
@command = command
|
11
|
+
end
|
12
|
+
|
13
|
+
attr_reader :attribute, :command
|
14
|
+
|
15
|
+
def defined?
|
16
|
+
command.instance_variable_defined?(attribute.ivar_name)
|
17
|
+
end
|
18
|
+
|
19
|
+
# get value directly
|
20
|
+
def get
|
21
|
+
command.instance_variable_get(attribute.ivar_name)
|
22
|
+
end
|
23
|
+
|
24
|
+
# set value directly
|
25
|
+
def set(value)
|
26
|
+
command.instance_variable_set(attribute.ivar_name, value)
|
27
|
+
end
|
28
|
+
|
29
|
+
def default
|
30
|
+
command.send(attribute.default_method)
|
31
|
+
end
|
32
|
+
|
33
|
+
# default implementation of read_method
|
34
|
+
def _read
|
35
|
+
if self.defined?
|
36
|
+
get
|
37
|
+
else
|
38
|
+
default
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
# default implementation of write_method
|
43
|
+
def _write(value)
|
44
|
+
if attribute.multivalued?
|
45
|
+
current_values = get || []
|
46
|
+
set(current_values + [value])
|
47
|
+
else
|
48
|
+
set(value)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def read
|
53
|
+
command.send(attribute.read_method)
|
54
|
+
end
|
55
|
+
|
56
|
+
def write(value)
|
57
|
+
command.send(attribute.write_method, value)
|
58
|
+
end
|
59
|
+
|
60
|
+
def default_from_environment
|
61
|
+
return if self.defined?
|
62
|
+
return if attribute.environment_variable.nil?
|
63
|
+
return unless ENV.has_key?(attribute.environment_variable)
|
64
|
+
# Set the parameter value if it's environment variable is present
|
65
|
+
value = ENV[attribute.environment_variable]
|
66
|
+
begin
|
67
|
+
write(value)
|
68
|
+
rescue ArgumentError => e
|
69
|
+
command.send(:signal_usage_error, "$#{attribute.environment_variable}: #{e.message}")
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
end
|
74
|
+
|
75
|
+
end
|
76
|
+
end
|
data/lib/clamp/command.rb
CHANGED
@@ -28,7 +28,7 @@ module Clamp
|
|
28
28
|
@invocation_path = invocation_path
|
29
29
|
@context = context
|
30
30
|
parent_attribute_values.each do |attribute, value|
|
31
|
-
|
31
|
+
attribute.of(self).set(value)
|
32
32
|
end
|
33
33
|
end
|
34
34
|
|
@@ -49,9 +49,7 @@ module Clamp
|
|
49
49
|
#
|
50
50
|
def parse(arguments)
|
51
51
|
@remaining_arguments = arguments.dup
|
52
|
-
parse_environment_options
|
53
52
|
parse_options
|
54
|
-
parse_environment_parameters
|
55
53
|
parse_parameters
|
56
54
|
parse_subcommand
|
57
55
|
handle_remaining_arguments
|
data/lib/clamp/option/parsing.rb
CHANGED
@@ -6,6 +6,7 @@ module Clamp
|
|
6
6
|
protected
|
7
7
|
|
8
8
|
def parse_options
|
9
|
+
|
9
10
|
while remaining_arguments.first =~ /\A-/
|
10
11
|
|
11
12
|
switch = remaining_arguments.shift
|
@@ -28,13 +29,18 @@ module Clamp
|
|
28
29
|
value = option.extract_value(switch, remaining_arguments)
|
29
30
|
|
30
31
|
begin
|
31
|
-
|
32
|
+
option.of(self).write(value)
|
32
33
|
rescue ArgumentError => e
|
33
34
|
signal_usage_error "option '#{switch}': #{e.message}"
|
34
35
|
end
|
35
36
|
|
36
37
|
end
|
37
38
|
|
39
|
+
# Fill in gap from environment
|
40
|
+
self.class.recognised_options.each do |option|
|
41
|
+
option.of(self).default_from_environment
|
42
|
+
end
|
43
|
+
|
38
44
|
# Verify that all required options are present
|
39
45
|
self.class.recognised_options.each do |option|
|
40
46
|
# If this option is required and the value is nil, there's an error.
|
@@ -49,15 +55,6 @@ module Clamp
|
|
49
55
|
end
|
50
56
|
end
|
51
57
|
|
52
|
-
def parse_environment_options
|
53
|
-
self.class.recognised_options.each do |option|
|
54
|
-
next if option.environment_variable.nil?
|
55
|
-
next unless ENV.has_key?(option.environment_variable)
|
56
|
-
value = ENV[option.environment_variable]
|
57
|
-
send(option.write_method, value)
|
58
|
-
end
|
59
|
-
end
|
60
|
-
|
61
58
|
private
|
62
59
|
|
63
60
|
def find_option(switch)
|
@@ -10,23 +10,15 @@ module Clamp
|
|
10
10
|
self.class.parameters.each do |parameter|
|
11
11
|
begin
|
12
12
|
parameter.consume(remaining_arguments).each do |value|
|
13
|
-
|
13
|
+
parameter.of(self).write(value)
|
14
14
|
end
|
15
15
|
rescue ArgumentError => e
|
16
16
|
signal_usage_error "parameter '#{parameter.name}': #{e.message}"
|
17
17
|
end
|
18
18
|
end
|
19
19
|
|
20
|
-
end
|
21
|
-
|
22
|
-
def parse_environment_parameters
|
23
|
-
|
24
20
|
self.class.parameters.each do |parameter|
|
25
|
-
|
26
|
-
next unless ENV.has_key?(parameter.environment_variable)
|
27
|
-
# Set the parameter value if it's environment variable is present
|
28
|
-
value = ENV[parameter.environment_variable]
|
29
|
-
send(parameter.write_method, value)
|
21
|
+
parameter.of(self).default_from_environment
|
30
22
|
end
|
31
23
|
|
32
24
|
end
|
@@ -39,6 +39,10 @@ module Clamp
|
|
39
39
|
parameters.take_while { |p| p != @subcommand_parameter }
|
40
40
|
end
|
41
41
|
|
42
|
+
def inheritable_attributes
|
43
|
+
recognised_options + parameters_before_subcommand
|
44
|
+
end
|
45
|
+
|
42
46
|
def default_subcommand=(name)
|
43
47
|
if has_subcommands?
|
44
48
|
raise Clamp::DeclarationError, "default_subcommand must be defined before subcommands"
|
@@ -16,18 +16,14 @@ module Clamp
|
|
16
16
|
def instatiate_subcommand(name)
|
17
17
|
subcommand_class = find_subcommand_class(name)
|
18
18
|
parent_attribute_values = {}
|
19
|
-
inheritable_attributes.each do |
|
20
|
-
if
|
21
|
-
parent_attribute_values[
|
19
|
+
self.class.inheritable_attributes.each do |attribute|
|
20
|
+
if attribute.of(self).defined?
|
21
|
+
parent_attribute_values[attribute] = attribute.of(self).get
|
22
22
|
end
|
23
23
|
end
|
24
24
|
subcommand_class.new("#{invocation_path} #{name}", context, parent_attribute_values)
|
25
25
|
end
|
26
26
|
|
27
|
-
def inheritable_attributes
|
28
|
-
self.class.recognised_options + self.class.parameters_before_subcommand
|
29
|
-
end
|
30
|
-
|
31
27
|
def find_subcommand_class(name)
|
32
28
|
subcommand_def = self.class.find_subcommand(name) || signal_usage_error("No such sub-command '#{name}'")
|
33
29
|
subcommand_def.subcommand_class
|
data/lib/clamp/version.rb
CHANGED
data/spec/clamp/command_spec.rb
CHANGED
@@ -283,6 +283,11 @@ describe Clamp::Command do
|
|
283
283
|
before do
|
284
284
|
command.class.option ["-f", "--flavour"], "FLAVOUR", "Flavour of the month"
|
285
285
|
command.class.option ["-c", "--color"], "COLOR", "Preferred hue"
|
286
|
+
command.class.option ["--scoops"], "N", "Number of scoops",
|
287
|
+
:default => 1,
|
288
|
+
:environment_variable => "DEFAULT_SCOOPS" do |arg|
|
289
|
+
Integer(arg)
|
290
|
+
end
|
286
291
|
command.class.option ["-n", "--[no-]nuts"], :flag, "Nuts (or not)\nMay include nuts"
|
287
292
|
command.class.parameter "[ARG] ...", "extra arguments", :attribute_name => :arguments
|
288
293
|
end
|
@@ -441,24 +446,23 @@ describe Clamp::Command do
|
|
441
446
|
|
442
447
|
end
|
443
448
|
|
444
|
-
describe "when option
|
449
|
+
describe "when a bad option value is specified on the command-line" do
|
445
450
|
|
446
|
-
|
447
|
-
|
451
|
+
it "signals a UsageError" do
|
452
|
+
lambda do
|
453
|
+
command.parse(%w(--scoops reginald))
|
454
|
+
end.should raise_error(Clamp::UsageError, /^option '--scoops': invalid value for Integer/)
|
455
|
+
end
|
448
456
|
|
449
|
-
|
450
|
-
unless c == "black"
|
451
|
-
raise ArgumentError, "sorry, we're out of #{c}"
|
452
|
-
end
|
453
|
-
end
|
457
|
+
end
|
454
458
|
|
455
|
-
|
456
|
-
end
|
459
|
+
describe "when a bad option value is specified in the environment" do
|
457
460
|
|
458
|
-
it "
|
461
|
+
it "signals a UsageError" do
|
462
|
+
ENV["DEFAULT_SCOOPS"] = "marjorie"
|
459
463
|
lambda do
|
460
|
-
command.parse(
|
461
|
-
end.should raise_error(Clamp::UsageError,
|
464
|
+
command.parse([])
|
465
|
+
end.should raise_error(Clamp::UsageError, /^\$DEFAULT_SCOOPS: invalid value for Integer/)
|
462
466
|
end
|
463
467
|
|
464
468
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: clamp
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.6.
|
4
|
+
version: 0.6.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Mike Williams
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2013-
|
11
|
+
date: 2013-05-07 00:00:00.000000000 Z
|
12
12
|
dependencies: []
|
13
13
|
description: ! "Clamp provides an object-model for command-line utilities. \nIt handles
|
14
14
|
parsing of command-line options, and generation of usage help.\n"
|
@@ -35,6 +35,7 @@ files:
|
|
35
35
|
- lib/clamp.rb
|
36
36
|
- lib/clamp/attribute/declaration.rb
|
37
37
|
- lib/clamp/attribute/definition.rb
|
38
|
+
- lib/clamp/attribute/instance.rb
|
38
39
|
- lib/clamp/command.rb
|
39
40
|
- lib/clamp/errors.rb
|
40
41
|
- lib/clamp/help.rb
|