clamp 0.0.9 → 0.1.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.
- 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
|