clamp 0.0.7 → 0.0.9
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 +9 -9
- data/examples/rename +2 -2
- data/examples/speak +1 -1
- data/lib/clamp/attribute_declaration.rb +38 -0
- data/lib/clamp/command.rb +15 -75
- data/lib/clamp/command/declaration.rb +15 -0
- data/lib/clamp/errors.rb +26 -0
- data/lib/clamp/{help_support.rb → help.rb} +6 -22
- data/lib/clamp/option.rb +14 -6
- data/lib/clamp/option/declaration.rb +56 -0
- data/lib/clamp/option/parsing.rb +36 -0
- data/lib/clamp/parameter.rb +63 -0
- data/lib/clamp/parameter/declaration.rb +24 -0
- data/lib/clamp/parameter/parsing.rb +30 -0
- data/lib/clamp/subcommand/declaration.rb +35 -0
- data/lib/clamp/subcommand/execution.rb +32 -0
- data/lib/clamp/version.rb +1 -1
- data/spec/clamp/command_group_spec.rb +5 -0
- data/spec/clamp/command_spec.rb +170 -92
- data/spec/clamp/option_spec.rb +3 -3
- data/spec/clamp/parameter_spec.rb +126 -0
- metadata +17 -7
- data/lib/clamp/option_support.rb +0 -75
- data/lib/clamp/subcommand_support.rb +0 -35
data/README.markdown
CHANGED
@@ -26,7 +26,7 @@ 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
|
-
|
29
|
+
parameter "WORDS ...", "the thing to say"
|
30
30
|
|
31
31
|
def execute
|
32
32
|
|
@@ -42,7 +42,7 @@ Clamp models a command as a Ruby class; a subclass of `Clamp::Command`. They lo
|
|
42
42
|
|
43
43
|
end
|
44
44
|
|
45
|
-
Class-level methods (like `option` and `
|
45
|
+
Class-level methods (like `option` and `parameter`) are available to declare command-line options, and document usage.
|
46
46
|
|
47
47
|
The command can be invoked by instantiating the class, and asking it to run:
|
48
48
|
|
@@ -111,15 +111,15 @@ If the block raises an ArgumentError, Clamp will catch it, and report that the o
|
|
111
111
|
!!!plain
|
112
112
|
ERROR: option '--port': invalid value for Integer: "blah"
|
113
113
|
|
114
|
-
Declaring
|
115
|
-
|
114
|
+
Declaring parameters
|
115
|
+
--------------------
|
116
116
|
|
117
|
-
The `
|
117
|
+
The `parameter` method is used to declare positional command parameters:
|
118
118
|
|
119
|
-
|
120
|
-
|
119
|
+
parameter "FILE ...", "source files"
|
120
|
+
parameter "DIR", "target directory"
|
121
121
|
|
122
|
-
Use of `
|
122
|
+
Use of `parameter` is entirely for documentation purposes. Whether or not you declare and describe your expected arguments, the actual arguments that remain after option parsing will be available as `arguments` when your `#execute` method is called.
|
123
123
|
|
124
124
|
Sub-commands
|
125
125
|
------------
|
@@ -161,7 +161,7 @@ When a command has sub-commands, Clamp will attempt to delegate based on the fir
|
|
161
161
|
Getting help
|
162
162
|
------------
|
163
163
|
|
164
|
-
All Clamp commands support a "`--help`" option, which outputs brief usage documentation, based on those seemingly useless extra parameters that you had to pass to `option` and `
|
164
|
+
All Clamp commands support a "`--help`" option, which outputs brief usage documentation, based on those seemingly useless extra parameters that you had to pass to `option` and `parameter`.
|
165
165
|
|
166
166
|
$ speak --help
|
167
167
|
Usage:
|
data/examples/rename
CHANGED
@@ -6,8 +6,8 @@ class Rename < Clamp::Command
|
|
6
6
|
|
7
7
|
usage "[OPTIONS] TRANSFORM FILE ..."
|
8
8
|
|
9
|
-
|
10
|
-
|
9
|
+
parameter "TRANSFORM", "a Ruby expression"
|
10
|
+
parameter "FILE", "a file to rename"
|
11
11
|
|
12
12
|
option ["-v", "--verbose"], :flag, "be verbose"
|
13
13
|
|
data/examples/speak
CHANGED
@@ -0,0 +1,38 @@
|
|
1
|
+
module Clamp
|
2
|
+
|
3
|
+
module AttributeDeclaration
|
4
|
+
|
5
|
+
protected
|
6
|
+
|
7
|
+
def define_accessors_for(attribute, &block)
|
8
|
+
define_reader_for(attribute)
|
9
|
+
define_writer_for(attribute, &block)
|
10
|
+
end
|
11
|
+
|
12
|
+
def define_reader_for(attribute)
|
13
|
+
reader_name = attribute.attribute_name
|
14
|
+
reader_name += "?" if attribute.respond_to?(:flag?) && attribute.flag?
|
15
|
+
ivar_name = "@#{attribute.attribute_name}"
|
16
|
+
define_method(reader_name) do
|
17
|
+
if instance_variable_defined?(ivar_name)
|
18
|
+
instance_variable_get(ivar_name)
|
19
|
+
elsif parent_command && parent_command.respond_to?(reader_name)
|
20
|
+
parent_command.send(reader_name)
|
21
|
+
elsif attribute.respond_to?(:default_value)
|
22
|
+
attribute.default_value
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def define_writer_for(attribute, &block)
|
28
|
+
define_method("#{attribute.attribute_name}=") do |value|
|
29
|
+
if block
|
30
|
+
value = instance_exec(value, &block)
|
31
|
+
end
|
32
|
+
instance_variable_set("@#{attribute.attribute_name}", value)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
end
|
37
|
+
|
38
|
+
end
|
data/lib/clamp/command.rb
CHANGED
@@ -1,6 +1,9 @@
|
|
1
|
-
require 'clamp/
|
2
|
-
require 'clamp/
|
3
|
-
require 'clamp/
|
1
|
+
require 'clamp/command/declaration'
|
2
|
+
require 'clamp/errors'
|
3
|
+
require 'clamp/help'
|
4
|
+
require 'clamp/option/parsing'
|
5
|
+
require 'clamp/parameter/parsing'
|
6
|
+
require 'clamp/subcommand/execution'
|
4
7
|
|
5
8
|
module Clamp
|
6
9
|
|
@@ -18,28 +21,9 @@ module Clamp
|
|
18
21
|
attr_accessor :parent_command
|
19
22
|
|
20
23
|
def parse(arguments)
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
when /\A--\z/
|
25
|
-
break
|
26
|
-
|
27
|
-
else
|
28
|
-
option = find_option(switch)
|
29
|
-
value = if option.flag?
|
30
|
-
option.flag_value(switch)
|
31
|
-
else
|
32
|
-
arguments.shift
|
33
|
-
end
|
34
|
-
begin
|
35
|
-
send("#{option.attribute_name}=", value)
|
36
|
-
rescue ArgumentError => e
|
37
|
-
signal_usage_error "option '#{switch}': #{e.message}"
|
38
|
-
end
|
39
|
-
|
40
|
-
end
|
41
|
-
end
|
42
|
-
@arguments = arguments
|
24
|
+
@arguments = arguments.dup
|
25
|
+
parse_options
|
26
|
+
parse_parameters
|
43
27
|
end
|
44
28
|
|
45
29
|
# default implementation
|
@@ -60,32 +44,12 @@ module Clamp
|
|
60
44
|
self.class.help(name)
|
61
45
|
end
|
62
46
|
|
47
|
+
include Option::Parsing
|
48
|
+
include Parameter::Parsing
|
49
|
+
include Subcommand::Execution
|
50
|
+
|
63
51
|
private
|
64
52
|
|
65
|
-
def execute_subcommand
|
66
|
-
signal_usage_error "no subcommand specified" if arguments.empty?
|
67
|
-
subcommand_name = arguments.shift
|
68
|
-
subcommand_class = find_subcommand_class(subcommand_name)
|
69
|
-
subcommand = subcommand_class.new("#{name} #{subcommand_name}", context)
|
70
|
-
subcommand.parent_command = self
|
71
|
-
subcommand.run(arguments)
|
72
|
-
end
|
73
|
-
|
74
|
-
def find_option(switch)
|
75
|
-
self.class.find_option(switch) ||
|
76
|
-
signal_usage_error("Unrecognised option '#{switch}'")
|
77
|
-
end
|
78
|
-
|
79
|
-
def find_subcommand(name)
|
80
|
-
self.class.find_subcommand(name) ||
|
81
|
-
signal_usage_error("No such sub-command '#{name}'")
|
82
|
-
end
|
83
|
-
|
84
|
-
def find_subcommand_class(name)
|
85
|
-
subcommand = find_subcommand(name)
|
86
|
-
subcommand.subcommand_class if subcommand
|
87
|
-
end
|
88
|
-
|
89
53
|
def signal_usage_error(message)
|
90
54
|
e = UsageError.new(message, self)
|
91
55
|
e.set_backtrace(caller)
|
@@ -98,9 +62,8 @@ module Clamp
|
|
98
62
|
|
99
63
|
class << self
|
100
64
|
|
101
|
-
include
|
102
|
-
include
|
103
|
-
include HelpSupport
|
65
|
+
include Command::Declaration
|
66
|
+
include Help
|
104
67
|
|
105
68
|
def run(name = $0, args = ARGV, context = {})
|
106
69
|
begin
|
@@ -119,27 +82,4 @@ module Clamp
|
|
119
82
|
|
120
83
|
end
|
121
84
|
|
122
|
-
class Error < StandardError
|
123
|
-
|
124
|
-
def initialize(message, command)
|
125
|
-
super(message)
|
126
|
-
@command = command
|
127
|
-
end
|
128
|
-
|
129
|
-
attr_reader :command
|
130
|
-
|
131
|
-
end
|
132
|
-
|
133
|
-
# raise to signal incorrect command usage
|
134
|
-
class UsageError < Error; end
|
135
|
-
|
136
|
-
# raise to request usage help
|
137
|
-
class HelpWanted < Error
|
138
|
-
|
139
|
-
def initialize(command)
|
140
|
-
super("I need help", command)
|
141
|
-
end
|
142
|
-
|
143
|
-
end
|
144
|
-
|
145
85
|
end
|
@@ -0,0 +1,15 @@
|
|
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
|
data/lib/clamp/errors.rb
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
module Clamp
|
2
|
+
|
3
|
+
class Error < StandardError
|
4
|
+
|
5
|
+
def initialize(message, command)
|
6
|
+
super(message)
|
7
|
+
@command = command
|
8
|
+
end
|
9
|
+
|
10
|
+
attr_reader :command
|
11
|
+
|
12
|
+
end
|
13
|
+
|
14
|
+
# raise to signal incorrect command usage
|
15
|
+
class UsageError < Error; end
|
16
|
+
|
17
|
+
# raise to request usage help
|
18
|
+
class HelpWanted < Error
|
19
|
+
|
20
|
+
def initialize(command)
|
21
|
+
super("I need help", command)
|
22
|
+
end
|
23
|
+
|
24
|
+
end
|
25
|
+
|
26
|
+
end
|
@@ -1,22 +1,6 @@
|
|
1
1
|
module Clamp
|
2
2
|
|
3
|
-
|
4
|
-
|
5
|
-
def help
|
6
|
-
[name, description]
|
7
|
-
end
|
8
|
-
|
9
|
-
end
|
10
|
-
|
11
|
-
module HelpSupport
|
12
|
-
|
13
|
-
def declared_arguments
|
14
|
-
@declared_arguments ||= []
|
15
|
-
end
|
16
|
-
|
17
|
-
def argument(name, description)
|
18
|
-
declared_arguments << Argument.new(name, description)
|
19
|
-
end
|
3
|
+
module Help
|
20
4
|
|
21
5
|
def usage(usage)
|
22
6
|
@declared_usage_descriptions ||= []
|
@@ -26,7 +10,7 @@ module Clamp
|
|
26
10
|
attr_reader :declared_usage_descriptions
|
27
11
|
|
28
12
|
def derived_usage_description
|
29
|
-
parts =
|
13
|
+
parts = parameters.map { |a| a.name }
|
30
14
|
parts.unshift("SUBCOMMAND") if has_subcommands?
|
31
15
|
parts.unshift("[OPTIONS]") if has_options?
|
32
16
|
parts.join(" ")
|
@@ -43,10 +27,10 @@ module Clamp
|
|
43
27
|
help.puts " #{command_name} #{usage}".rstrip
|
44
28
|
end
|
45
29
|
detail_format = " %-29s %s"
|
46
|
-
unless
|
47
|
-
help.puts "\
|
48
|
-
|
49
|
-
help.puts detail_format %
|
30
|
+
unless parameters.empty?
|
31
|
+
help.puts "\nParameters:"
|
32
|
+
parameters.each do |parameter|
|
33
|
+
help.puts detail_format % parameter.help
|
50
34
|
end
|
51
35
|
end
|
52
36
|
unless recognised_subcommands.empty?
|
data/lib/clamp/option.rb
CHANGED
@@ -2,9 +2,9 @@ module Clamp
|
|
2
2
|
|
3
3
|
class Option
|
4
4
|
|
5
|
-
def initialize(switches,
|
5
|
+
def initialize(switches, type, description, options = {})
|
6
6
|
@switches = Array(switches)
|
7
|
-
@
|
7
|
+
@type = type
|
8
8
|
@description = description
|
9
9
|
if options.has_key?(:attribute_name)
|
10
10
|
@attribute_name = options[:attribute_name].to_s
|
@@ -14,7 +14,7 @@ module Clamp
|
|
14
14
|
end
|
15
15
|
end
|
16
16
|
|
17
|
-
attr_reader :switches, :
|
17
|
+
attr_reader :switches, :type, :description, :default_value
|
18
18
|
|
19
19
|
def attribute_name
|
20
20
|
@attribute_name ||= long_switch.sub(/^--(\[no-\])?/, '').tr('-', '_')
|
@@ -29,21 +29,29 @@ module Clamp
|
|
29
29
|
end
|
30
30
|
|
31
31
|
def flag?
|
32
|
-
@
|
32
|
+
@type == :flag
|
33
33
|
end
|
34
34
|
|
35
35
|
def flag_value(switch)
|
36
36
|
!(switch =~ /^--no-(.*)/ && switches.member?("--\[no-\]#{$1}"))
|
37
37
|
end
|
38
38
|
|
39
|
+
def extract_value(switch, arguments)
|
40
|
+
if flag?
|
41
|
+
flag_value(switch)
|
42
|
+
else
|
43
|
+
arguments.shift
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
39
47
|
def help
|
40
48
|
lhs = switches.join(", ")
|
41
|
-
lhs += " " +
|
49
|
+
lhs += " " + type unless flag?
|
42
50
|
[lhs, description]
|
43
51
|
end
|
44
52
|
|
45
53
|
private
|
46
|
-
|
54
|
+
|
47
55
|
def recognised_switches
|
48
56
|
switches.map do |switch|
|
49
57
|
if switch =~ /^--\[no-\](.*)/
|
@@ -0,0 +1,56 @@
|
|
1
|
+
require 'clamp/attribute_declaration'
|
2
|
+
require 'clamp/option'
|
3
|
+
|
4
|
+
module Clamp
|
5
|
+
class Option
|
6
|
+
|
7
|
+
module Declaration
|
8
|
+
|
9
|
+
include AttributeDeclaration
|
10
|
+
|
11
|
+
def option(switches, type, description, opts = {}, &block)
|
12
|
+
option = Clamp::Option.new(switches, type, description, opts)
|
13
|
+
my_declared_options << option
|
14
|
+
define_accessors_for(option, &block)
|
15
|
+
end
|
16
|
+
|
17
|
+
def has_options?
|
18
|
+
!declared_options.empty?
|
19
|
+
end
|
20
|
+
|
21
|
+
def declared_options
|
22
|
+
my_declared_options + inherited_declared_options
|
23
|
+
end
|
24
|
+
|
25
|
+
def recognised_options
|
26
|
+
declared_options + standard_options
|
27
|
+
end
|
28
|
+
|
29
|
+
def find_option(switch)
|
30
|
+
recognised_options.find { |o| o.handles?(switch) }
|
31
|
+
end
|
32
|
+
|
33
|
+
private
|
34
|
+
|
35
|
+
def my_declared_options
|
36
|
+
@my_declared_options ||= []
|
37
|
+
end
|
38
|
+
|
39
|
+
def inherited_declared_options
|
40
|
+
if superclass.respond_to?(:declared_options)
|
41
|
+
superclass.declared_options
|
42
|
+
else
|
43
|
+
[]
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
HELP_OPTION = Clamp::Option.new("--help", :flag, "print help", :attribute_name => :help_requested)
|
48
|
+
|
49
|
+
def standard_options
|
50
|
+
[HELP_OPTION]
|
51
|
+
end
|
52
|
+
|
53
|
+
end
|
54
|
+
|
55
|
+
end
|
56
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
module Clamp
|
2
|
+
class Option
|
3
|
+
|
4
|
+
module Parsing
|
5
|
+
|
6
|
+
protected
|
7
|
+
|
8
|
+
def parse_options
|
9
|
+
while arguments.first =~ /^-/
|
10
|
+
|
11
|
+
switch = arguments.shift
|
12
|
+
break if switch == "--"
|
13
|
+
|
14
|
+
option = find_option(switch)
|
15
|
+
value = option.extract_value(switch, arguments)
|
16
|
+
|
17
|
+
begin
|
18
|
+
send("#{option.attribute_name}=", value)
|
19
|
+
rescue ArgumentError => e
|
20
|
+
signal_usage_error "option '#{switch}': #{e.message}"
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
private
|
27
|
+
|
28
|
+
def find_option(switch)
|
29
|
+
self.class.find_option(switch) ||
|
30
|
+
signal_usage_error("Unrecognised option '#{switch}'")
|
31
|
+
end
|
32
|
+
|
33
|
+
end
|
34
|
+
|
35
|
+
end
|
36
|
+
end
|