clamp 0.6.0 → 0.6.1
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.
- 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
|