clamp 0.5.1 → 0.6.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.
- checksums.yaml +8 -8
- data/.autotest +9 -0
- data/.travis.yml +1 -0
- data/CHANGES.md +10 -0
- data/{README.markdown → README.md} +10 -4
- data/examples/admin +1 -3
- data/examples/flipflop +1 -3
- data/examples/fubar +1 -3
- data/examples/scoop +17 -0
- data/examples/speak +2 -4
- data/lib/clamp.rb +4 -0
- data/lib/clamp/attribute/declaration.rb +49 -0
- data/lib/clamp/attribute/definition.rb +82 -0
- data/lib/clamp/command.rb +4 -1
- data/lib/clamp/errors.rb +7 -4
- data/lib/clamp/help.rb +4 -4
- data/lib/clamp/option/declaration.rb +9 -7
- data/lib/clamp/option/definition.rb +94 -0
- data/lib/clamp/option/parsing.rb +3 -26
- data/lib/clamp/parameter/declaration.rb +8 -7
- data/lib/clamp/parameter/definition.rb +48 -0
- data/lib/clamp/parameter/parsing.rb +5 -4
- data/lib/clamp/subcommand/declaration.rb +25 -6
- data/lib/clamp/subcommand/definition.rb +25 -0
- data/lib/clamp/subcommand/execution.rb +23 -4
- data/lib/clamp/subcommand/parsing.rb +3 -21
- data/lib/clamp/truthy.rb +9 -0
- data/lib/clamp/version.rb +1 -1
- data/spec/clamp/command_group_spec.rb +50 -3
- data/spec/clamp/command_spec.rb +94 -65
- data/spec/clamp/{option_spec.rb → option/definition_spec.rb} +76 -13
- data/spec/clamp/{parameter_spec.rb → parameter/definition_spec.rb} +21 -13
- metadata +16 -12
- data/lib/clamp/attribute.rb +0 -44
- data/lib/clamp/attribute_declaration.rb +0 -40
- data/lib/clamp/option.rb +0 -94
- data/lib/clamp/parameter.rb +0 -77
- data/lib/clamp/subcommand.rb +0 -23
checksums.yaml
CHANGED
@@ -1,15 +1,15 @@
|
|
1
1
|
---
|
2
2
|
!binary "U0hBMQ==":
|
3
3
|
metadata.gz: !binary |-
|
4
|
-
|
4
|
+
YzQ1YTM1YTRmM2Q2ZDRiMDEzY2M1YzBmM2Y5YjM4ZDlhZTYzNWFmZA==
|
5
5
|
data.tar.gz: !binary |-
|
6
|
-
|
6
|
+
MDMzN2JhYTAxYjYwNmZhZjhlMzEwZDJmMTM0MTBhOWVhZjFkMjIxYQ==
|
7
7
|
!binary "U0hBNTEy":
|
8
8
|
metadata.gz: !binary |-
|
9
|
-
|
10
|
-
|
11
|
-
|
9
|
+
NzA4ZmI2ZmNjMmE4YzZlZDY4ZjUwZTVlMDIyYTJjZmQ5NDkxNDZkMjI0NDhi
|
10
|
+
M2E3NmUwZDE3NTUxNWViM2FmMDc5ZDc3NzRiNjQ2YjY1NjlkMzgwMjM0NTgy
|
11
|
+
NWM0ODJmODdkYTY2MTc3ZDBiZDdhZTU5N2Q1OTRjYTcyYzNlYWM=
|
12
12
|
data.tar.gz: !binary |-
|
13
|
-
|
14
|
-
|
15
|
-
|
13
|
+
ZDU4Yzg2ODViNjBlOWRmY2Y3OTZjNGEyYzZhMmFjZDZiYjc4MWI5ZjY5NTZl
|
14
|
+
OWZjN2I4YTEwYjE0ZTZiMTFkMmRmYjk3OTE3ODMyNzhjYWY4MDc1NjZiYTE4
|
15
|
+
OGNkZmYxODFiYjlhMzM5ZThiOTQ0M2U0NDllZDcyNWQyMjA0ZDk=
|
data/.autotest
ADDED
data/.travis.yml
CHANGED
data/CHANGES.md
ADDED
@@ -0,0 +1,10 @@
|
|
1
|
+
# Changelog
|
2
|
+
|
3
|
+
## 0.6.0 (2013-04-??)
|
4
|
+
|
5
|
+
* Introduce "banner" to describe a command (replacing "self.description=").
|
6
|
+
* Allow parameters to be specified before a subcommand.
|
7
|
+
* Add support for :multivalued options.
|
8
|
+
* Multi valued options and parameters get an "#append_to_foo_list" method, rather than
|
9
|
+
"#foo_list=".
|
10
|
+
|
@@ -1,4 +1,4 @@
|
|
1
|
-
Clamp
|
1
|
+
Clamp [](http://travis-ci.org/mdub/clamp)
|
2
2
|
=====
|
3
3
|
|
4
4
|
"Clamp" is a minimal framework for command-line utilities.
|
@@ -19,6 +19,8 @@ Quick Start
|
|
19
19
|
|
20
20
|
Clamp models a command as a Ruby class; a subclass of `Clamp::Command`. They look something like this:
|
21
21
|
|
22
|
+
require 'clamp'
|
23
|
+
|
22
24
|
class SpeakCommand < Clamp::Command
|
23
25
|
|
24
26
|
option "--loud", :flag, "say it loud"
|
@@ -38,12 +40,16 @@ Clamp models a command as a Ruby class; a subclass of `Clamp::Command`. They lo
|
|
38
40
|
|
39
41
|
end
|
40
42
|
|
41
|
-
Calling `run` on a command class creates an instance of it, then invokes it using command-line arguments (from ARGV, by default).
|
42
|
-
|
43
43
|
SpeakCommand.run
|
44
44
|
|
45
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 also used to generate `help` documentation.
|
46
46
|
|
47
|
+
The call to `run` creates an instance of the command class, then invokes it with command-line arguments (from `ARGV`).
|
48
|
+
|
49
|
+
There are more examples demonstrating various features of Clamp [on Github][examples].
|
50
|
+
|
51
|
+
[examples]: https://github.com/mdub/clamp/tree/master/examples
|
52
|
+
|
47
53
|
Declaring options
|
48
54
|
-----------------
|
49
55
|
|
@@ -243,7 +249,7 @@ Then, if when no SUBCOMMAND argument is provided, the default will be selected.
|
|
243
249
|
|
244
250
|
### Subcommand options and parameters
|
245
251
|
|
246
|
-
Options are inheritable, so any options declared for a command are supported
|
252
|
+
Options are inheritable, so any options declared for a command are supported by it's sub-classes (e.g. those created using the block form of `subcommand`). Parameters, on the other hand, are not inherited - each subcommand must declare it's own parameter list.
|
247
253
|
|
248
254
|
Note that, if a subcommand accepts options, they must be specified on the command-line _after_ the subcommand name.
|
249
255
|
|
data/examples/admin
CHANGED
@@ -4,7 +4,7 @@
|
|
4
4
|
|
5
5
|
require "clamp"
|
6
6
|
|
7
|
-
|
7
|
+
Clamp do
|
8
8
|
|
9
9
|
option "--timeout", "SECONDS", "connection timeout", :default => 5, :environment_variable => "MYAPP_TIMEOUT" do |x|
|
10
10
|
Integer(x)
|
@@ -18,5 +18,3 @@ class AdminCommand < Clamp::Command
|
|
18
18
|
end
|
19
19
|
|
20
20
|
end
|
21
|
-
|
22
|
-
AdminCommand.run
|
data/examples/flipflop
CHANGED
@@ -5,7 +5,7 @@
|
|
5
5
|
require "clamp"
|
6
6
|
require "clamp/version"
|
7
7
|
|
8
|
-
|
8
|
+
Clamp do
|
9
9
|
|
10
10
|
option ["--version", "-v"], :flag, "Show version" do
|
11
11
|
puts "Powered by Clamp-#{Clamp::VERSION}"
|
@@ -27,5 +27,3 @@ class FlipFlop < Clamp::Command
|
|
27
27
|
end
|
28
28
|
|
29
29
|
end
|
30
|
-
|
31
|
-
FlipFlop.run
|
data/examples/fubar
CHANGED
data/examples/scoop
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
#! /usr/bin/env ruby
|
2
|
+
|
3
|
+
# An example of multi-valued options
|
4
|
+
|
5
|
+
require "clamp"
|
6
|
+
|
7
|
+
Clamp do
|
8
|
+
|
9
|
+
option ["-f", "--flavour"], "FLAVOUR", "flavour",
|
10
|
+
:multivalued => true, :default => ['chocolate'],
|
11
|
+
:attribute_name => :flavours
|
12
|
+
|
13
|
+
def execute
|
14
|
+
puts "one #{flavours.join(' and ')} ice-cream"
|
15
|
+
end
|
16
|
+
|
17
|
+
end
|
data/examples/speak
CHANGED
data/lib/clamp.rb
CHANGED
@@ -0,0 +1,49 @@
|
|
1
|
+
module Clamp
|
2
|
+
module Attribute
|
3
|
+
|
4
|
+
module Declaration
|
5
|
+
|
6
|
+
protected
|
7
|
+
|
8
|
+
def define_accessors_for(attribute, &block)
|
9
|
+
define_reader_for(attribute)
|
10
|
+
define_default_for(attribute)
|
11
|
+
define_writer_for(attribute, &block)
|
12
|
+
end
|
13
|
+
|
14
|
+
def define_reader_for(attribute)
|
15
|
+
define_method(attribute.read_method) do
|
16
|
+
if instance_variable_defined?(attribute.ivar_name)
|
17
|
+
instance_variable_get(attribute.ivar_name)
|
18
|
+
else
|
19
|
+
send(attribute.default_method)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def define_default_for(attribute)
|
25
|
+
define_method(attribute.default_method) do
|
26
|
+
attribute.default_value
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def define_writer_for(attribute, &block)
|
31
|
+
define_method(attribute.write_method) do |value|
|
32
|
+
if block
|
33
|
+
value = instance_exec(value, &block)
|
34
|
+
end
|
35
|
+
if attribute.multivalued?
|
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
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
end
|
47
|
+
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,82 @@
|
|
1
|
+
module Clamp
|
2
|
+
module Attribute
|
3
|
+
|
4
|
+
class Definition
|
5
|
+
|
6
|
+
def initialize(options)
|
7
|
+
if options.has_key?(:attribute_name)
|
8
|
+
@attribute_name = options[:attribute_name].to_s
|
9
|
+
end
|
10
|
+
if options.has_key?(:default)
|
11
|
+
@default_value = options[:default]
|
12
|
+
end
|
13
|
+
if options.has_key?(:environment_variable)
|
14
|
+
@environment_variable = options[:environment_variable]
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
attr_reader :description, :environment_variable
|
19
|
+
|
20
|
+
def help_rhs
|
21
|
+
description + default_description
|
22
|
+
end
|
23
|
+
|
24
|
+
def help
|
25
|
+
[help_lhs, help_rhs]
|
26
|
+
end
|
27
|
+
|
28
|
+
def ivar_name
|
29
|
+
"@#{attribute_name}"
|
30
|
+
end
|
31
|
+
|
32
|
+
def read_method
|
33
|
+
attribute_name
|
34
|
+
end
|
35
|
+
|
36
|
+
def default_method
|
37
|
+
"default_#{read_method}"
|
38
|
+
end
|
39
|
+
|
40
|
+
def write_method
|
41
|
+
if multivalued?
|
42
|
+
"append_to_#{attribute_name}"
|
43
|
+
else
|
44
|
+
"#{attribute_name}="
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def multivalued?
|
49
|
+
@multivalued
|
50
|
+
end
|
51
|
+
|
52
|
+
def required?
|
53
|
+
@required
|
54
|
+
end
|
55
|
+
|
56
|
+
def attribute_name
|
57
|
+
@attribute_name ||= infer_attribute_name
|
58
|
+
end
|
59
|
+
|
60
|
+
def default_value
|
61
|
+
if defined?(@default_value)
|
62
|
+
@default_value
|
63
|
+
elsif multivalued?
|
64
|
+
[]
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
private
|
69
|
+
|
70
|
+
def default_description
|
71
|
+
default_sources = [
|
72
|
+
("$#{@environment_variable}" if defined?(@environment_variable)),
|
73
|
+
(@default_value.inspect if defined?(@default_value))
|
74
|
+
].compact
|
75
|
+
return "" if default_sources.empty?
|
76
|
+
" (default: " + default_sources.join(", or ") + ")"
|
77
|
+
end
|
78
|
+
|
79
|
+
end
|
80
|
+
|
81
|
+
end
|
82
|
+
end
|
data/lib/clamp/command.rb
CHANGED
@@ -24,9 +24,12 @@ module Clamp
|
|
24
24
|
# @param [String] invocation_path the path used to invoke the command
|
25
25
|
# @param [Hash] context additional data the command may need
|
26
26
|
#
|
27
|
-
def initialize(invocation_path, context = {})
|
27
|
+
def initialize(invocation_path, context = {}, parent_attribute_values = {})
|
28
28
|
@invocation_path = invocation_path
|
29
29
|
@context = context
|
30
|
+
parent_attribute_values.each do |attribute, value|
|
31
|
+
instance_variable_set(attribute.ivar_name, value)
|
32
|
+
end
|
30
33
|
end
|
31
34
|
|
32
35
|
# @return [String] the path used to invoke this command
|
data/lib/clamp/errors.rb
CHANGED
@@ -1,6 +1,9 @@
|
|
1
1
|
module Clamp
|
2
|
-
|
3
|
-
class
|
2
|
+
|
3
|
+
class DeclarationError < StandardError
|
4
|
+
end
|
5
|
+
|
6
|
+
class RuntimeError < StandardError
|
4
7
|
|
5
8
|
def initialize(message, command)
|
6
9
|
super(message)
|
@@ -12,10 +15,10 @@ module Clamp
|
|
12
15
|
end
|
13
16
|
|
14
17
|
# raise to signal incorrect command usage
|
15
|
-
class UsageError <
|
18
|
+
class UsageError < RuntimeError; end
|
16
19
|
|
17
20
|
# raise to request usage help
|
18
|
-
class HelpWanted <
|
21
|
+
class HelpWanted < RuntimeError
|
19
22
|
|
20
23
|
def initialize(command)
|
21
24
|
super("I need help", command)
|
data/lib/clamp/help.rb
CHANGED
@@ -20,15 +20,15 @@ module Clamp
|
|
20
20
|
@description.strip!
|
21
21
|
end
|
22
22
|
|
23
|
+
def banner(description)
|
24
|
+
self.description = description
|
25
|
+
end
|
26
|
+
|
23
27
|
attr_reader :description
|
24
28
|
|
25
29
|
def derived_usage_description
|
26
30
|
parts = ["[OPTIONS]"]
|
27
31
|
parts += parameters.map { |a| a.name }
|
28
|
-
if has_subcommands?
|
29
|
-
parts << "SUBCOMMAND"
|
30
|
-
parts << "[ARGS] ..."
|
31
|
-
end
|
32
32
|
parts.join(" ")
|
33
33
|
end
|
34
34
|
|
@@ -1,17 +1,19 @@
|
|
1
|
-
require 'clamp/
|
2
|
-
require 'clamp/option'
|
1
|
+
require 'clamp/attribute/declaration'
|
2
|
+
require 'clamp/option/definition'
|
3
3
|
|
4
4
|
module Clamp
|
5
|
-
|
5
|
+
module Option
|
6
6
|
|
7
7
|
module Declaration
|
8
8
|
|
9
|
-
include Clamp::
|
9
|
+
include Clamp::Attribute::Declaration
|
10
10
|
|
11
11
|
def option(switches, type, description, opts = {}, &block)
|
12
|
-
|
13
|
-
|
14
|
-
|
12
|
+
Option::Definition.new(switches, type, description, opts).tap do |option|
|
13
|
+
declared_options << option
|
14
|
+
block ||= option.default_conversion_block
|
15
|
+
define_accessors_for(option, &block)
|
16
|
+
end
|
15
17
|
end
|
16
18
|
|
17
19
|
def find_option(switch)
|
@@ -0,0 +1,94 @@
|
|
1
|
+
require 'clamp/attribute/definition'
|
2
|
+
require 'clamp/truthy'
|
3
|
+
|
4
|
+
module Clamp
|
5
|
+
module Option
|
6
|
+
|
7
|
+
class Definition < Attribute::Definition
|
8
|
+
|
9
|
+
def initialize(switches, type, description, options = {})
|
10
|
+
@switches = Array(switches)
|
11
|
+
@type = type
|
12
|
+
@description = description
|
13
|
+
super(options)
|
14
|
+
@multivalued = options[:multivalued]
|
15
|
+
if options.has_key?(:required)
|
16
|
+
@required = options[:required]
|
17
|
+
# Do some light validation for conflicting settings.
|
18
|
+
if options.has_key?(:default)
|
19
|
+
raise ArgumentError, "Specifying a :default value also :required doesn't make sense"
|
20
|
+
end
|
21
|
+
if type == :flag
|
22
|
+
raise ArgumentError, "A required flag (boolean) doesn't make sense."
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
attr_reader :switches, :type
|
28
|
+
|
29
|
+
def long_switch
|
30
|
+
switches.find { |switch| switch =~ /^--/ }
|
31
|
+
end
|
32
|
+
|
33
|
+
def handles?(switch)
|
34
|
+
recognised_switches.member?(switch)
|
35
|
+
end
|
36
|
+
|
37
|
+
def flag?
|
38
|
+
@type == :flag
|
39
|
+
end
|
40
|
+
|
41
|
+
def flag_value(switch)
|
42
|
+
!(switch =~ /^--no-(.*)/ && switches.member?("--\[no-\]#{$1}"))
|
43
|
+
end
|
44
|
+
|
45
|
+
def read_method
|
46
|
+
if flag?
|
47
|
+
super + "?"
|
48
|
+
else
|
49
|
+
super
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def extract_value(switch, arguments)
|
54
|
+
if flag?
|
55
|
+
flag_value(switch)
|
56
|
+
else
|
57
|
+
arguments.shift
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
def default_conversion_block
|
62
|
+
if flag?
|
63
|
+
Clamp.method(:truthy?)
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
def help_lhs
|
68
|
+
lhs = switches.join(", ")
|
69
|
+
lhs += " " + type unless flag?
|
70
|
+
lhs
|
71
|
+
end
|
72
|
+
|
73
|
+
private
|
74
|
+
|
75
|
+
def recognised_switches
|
76
|
+
switches.map do |switch|
|
77
|
+
if switch =~ /^--\[no-\](.*)/
|
78
|
+
["--#{$1}", "--no-#{$1}"]
|
79
|
+
else
|
80
|
+
switch
|
81
|
+
end
|
82
|
+
end.flatten
|
83
|
+
end
|
84
|
+
|
85
|
+
def infer_attribute_name
|
86
|
+
inferred_name = long_switch.sub(/^--(\[no-\])?/, '').tr('-', '_')
|
87
|
+
inferred_name += "_list" if multivalued?
|
88
|
+
inferred_name
|
89
|
+
end
|
90
|
+
|
91
|
+
end
|
92
|
+
|
93
|
+
end
|
94
|
+
end
|