clamp 0.5.1 → 0.6.0
Sign up to get free protection for your applications and to get access to all the features.
- 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 [![Build Status](https://secure.travis-ci.org/mdub/clamp.png?branch=master)](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
|