clamp 1.2.0.beta1 → 1.3.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/.editorconfig +9 -0
- data/.gitignore +1 -1
- data/.rspec +1 -0
- data/.rubocop.yml +26 -19
- data/.travis.yml +3 -6
- data/CHANGES.md +17 -1
- data/Gemfile +8 -6
- data/Guardfile +3 -1
- data/README.md +36 -43
- data/Rakefile +8 -0
- data/clamp.gemspec +8 -6
- data/examples/admin +3 -2
- data/examples/defaulted +4 -3
- data/examples/flipflop +1 -0
- data/examples/fubar +1 -0
- data/examples/gitdown +2 -1
- data/examples/scoop +3 -2
- data/examples/speak +3 -2
- data/examples/subcommand_missing +1 -0
- data/examples/word +1 -0
- data/lib/clamp.rb +3 -1
- data/lib/clamp/attribute/declaration.rb +5 -0
- data/lib/clamp/attribute/definition.rb +25 -11
- data/lib/clamp/attribute/instance.rb +25 -3
- data/lib/clamp/command.rb +9 -1
- data/lib/clamp/errors.rb +7 -3
- data/lib/clamp/help.rb +38 -17
- data/lib/clamp/messages.rb +25 -15
- data/lib/clamp/option/declaration.rb +5 -1
- data/lib/clamp/option/definition.rb +9 -3
- data/lib/clamp/option/parsing.rb +38 -43
- data/lib/clamp/parameter/declaration.rb +4 -0
- data/lib/clamp/parameter/definition.rb +9 -3
- data/lib/clamp/parameter/parsing.rb +5 -1
- data/lib/clamp/subcommand/declaration.rb +17 -15
- data/lib/clamp/subcommand/definition.rb +5 -6
- data/lib/clamp/subcommand/execution.rb +12 -1
- data/lib/clamp/subcommand/parsing.rb +4 -0
- data/lib/clamp/truthy.rb +4 -2
- data/lib/clamp/version.rb +3 -1
- data/spec/clamp/command_group_spec.rb +29 -11
- data/spec/clamp/command_spec.rb +130 -48
- data/spec/clamp/help_spec.rb +63 -0
- data/spec/clamp/messages_spec.rb +5 -4
- data/spec/clamp/option/definition_spec.rb +13 -11
- data/spec/clamp/option_module_spec.rb +3 -1
- data/spec/clamp/option_reordering_spec.rb +6 -4
- data/spec/clamp/parameter/definition_spec.rb +14 -12
- data/spec/spec_helper.rb +3 -3
- metadata +9 -7
data/examples/admin
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
#! /usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
2
3
|
|
3
4
|
# An example of options and parameters
|
4
5
|
|
@@ -6,12 +7,12 @@ require "clamp"
|
|
6
7
|
|
7
8
|
Clamp do
|
8
9
|
|
9
|
-
option "--timeout", "SECONDS", "connection timeout", :
|
10
|
+
option "--timeout", "SECONDS", "connection timeout", default: 5, environment_variable: "MYAPP_TIMEOUT" do |x|
|
10
11
|
Integer(x)
|
11
12
|
end
|
12
13
|
|
13
14
|
parameter "HOST", "server address"
|
14
|
-
parameter "[PORT]", "server port", :
|
15
|
+
parameter "[PORT]", "server port", default: 80, environment_variable: "MYAPP_PORT"
|
15
16
|
|
16
17
|
def execute
|
17
18
|
puts "trying to connect to #{host} on port #{port} (waiting up to #{timeout} seconds)"
|
data/examples/defaulted
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
#! /usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
2
3
|
|
3
4
|
# An example of default values and methods
|
4
5
|
|
@@ -8,10 +9,10 @@ require "highline"
|
|
8
9
|
Clamp do
|
9
10
|
|
10
11
|
option ["-U", "--user"], "USER", "user name",
|
11
|
-
:
|
12
|
-
:
|
12
|
+
environment_variable: "THE_USER",
|
13
|
+
default: "bob"
|
13
14
|
option ["-P", "--password"], "PASSWORD", "password",
|
14
|
-
:
|
15
|
+
environment_variable: "THE_PASSWORD"
|
15
16
|
|
16
17
|
def execute
|
17
18
|
puts "User: #{user}, Password: #{password}"
|
data/examples/flipflop
CHANGED
data/examples/fubar
CHANGED
data/examples/gitdown
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
#! /usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
2
3
|
|
3
4
|
# Demonstrate how subcommands can be declared as classes
|
4
5
|
|
@@ -25,7 +26,7 @@ module GitDown
|
|
25
26
|
class CloneCommand < AbstractCommand
|
26
27
|
|
27
28
|
parameter "REPOSITORY", "repository to clone"
|
28
|
-
parameter "[DIR]", "working directory", :
|
29
|
+
parameter "[DIR]", "working directory", default: "."
|
29
30
|
|
30
31
|
def execute
|
31
32
|
say "cloning to #{dir}"
|
data/examples/scoop
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
#! /usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
2
3
|
|
3
4
|
# An example of multi-valued options
|
4
5
|
|
@@ -7,8 +8,8 @@ require "clamp"
|
|
7
8
|
Clamp do
|
8
9
|
|
9
10
|
option ["-f", "--flavour"], "FLAVOUR", "flavour",
|
10
|
-
:
|
11
|
-
:
|
11
|
+
multivalued: true, default: ["chocolate"],
|
12
|
+
attribute_name: :flavours
|
12
13
|
|
13
14
|
def execute
|
14
15
|
puts "one #{flavours.join(' and ')} ice-cream"
|
data/examples/speak
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
#! /usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
2
3
|
|
3
4
|
# A simple Clamp command, with options and parameters
|
4
5
|
|
@@ -11,11 +12,11 @@ Clamp do
|
|
11
12
|
)
|
12
13
|
|
13
14
|
option "--loud", :flag, "say it loud"
|
14
|
-
option ["-n", "--iterations"], "N", "say it N times", :
|
15
|
+
option ["-n", "--iterations"], "N", "say it N times", default: 1 do |s|
|
15
16
|
Integer(s)
|
16
17
|
end
|
17
18
|
|
18
|
-
parameter "WORDS ...", "the thing to say", :
|
19
|
+
parameter "WORDS ...", "the thing to say", attribute_name: :words
|
19
20
|
|
20
21
|
def execute
|
21
22
|
the_truth = words.join(" ")
|
data/examples/subcommand_missing
CHANGED
data/examples/word
CHANGED
data/lib/clamp.rb
CHANGED
@@ -1,6 +1,10 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Clamp
|
2
4
|
module Attribute
|
3
5
|
|
6
|
+
# Methods to generate attribute accessors.
|
7
|
+
#
|
4
8
|
module Declaration
|
5
9
|
|
6
10
|
protected
|
@@ -25,6 +29,7 @@ module Clamp
|
|
25
29
|
end
|
26
30
|
|
27
31
|
def define_default_for(attribute)
|
32
|
+
return false if attribute.default_value.nil?
|
28
33
|
define_method(attribute.default_method) do
|
29
34
|
attribute.default_value
|
30
35
|
end
|
@@ -1,25 +1,28 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require "clamp/attribute/instance"
|
2
4
|
|
3
5
|
module Clamp
|
4
6
|
module Attribute
|
5
7
|
|
8
|
+
# Represents an attribute of a Clamp::Command class.
|
9
|
+
#
|
6
10
|
class Definition
|
7
11
|
|
8
12
|
def initialize(options)
|
9
|
-
if options.key?(:attribute_name)
|
10
|
-
@attribute_name = options[:attribute_name].to_s
|
11
|
-
end
|
13
|
+
@attribute_name = options[:attribute_name].to_s if options.key?(:attribute_name)
|
12
14
|
@default_value = options[:default] if options.key?(:default)
|
13
|
-
if options.key?(:environment_variable)
|
14
|
-
@environment_variable = options[:environment_variable]
|
15
|
-
end
|
15
|
+
@environment_variable = options[:environment_variable] if options.key?(:environment_variable)
|
16
16
|
@hidden = options[:hidden] if options.key?(:hidden)
|
17
17
|
end
|
18
18
|
|
19
19
|
attr_reader :description, :environment_variable
|
20
20
|
|
21
21
|
def help_rhs
|
22
|
-
|
22
|
+
rhs = description
|
23
|
+
comments = required_indicator || default_description
|
24
|
+
rhs += " (#{comments})" if comments
|
25
|
+
rhs
|
23
26
|
end
|
24
27
|
|
25
28
|
def help
|
@@ -51,11 +54,11 @@ module Clamp
|
|
51
54
|
end
|
52
55
|
|
53
56
|
def required?
|
54
|
-
@required
|
57
|
+
@required ||= false
|
55
58
|
end
|
56
59
|
|
57
60
|
def hidden?
|
58
|
-
@hidden
|
61
|
+
@hidden ||= false
|
59
62
|
end
|
60
63
|
|
61
64
|
def attribute_name
|
@@ -74,6 +77,17 @@ module Clamp
|
|
74
77
|
Attribute::Instance.new(self, command)
|
75
78
|
end
|
76
79
|
|
80
|
+
def option_missing_message
|
81
|
+
if environment_variable
|
82
|
+
Clamp.message(:option_or_env_required,
|
83
|
+
option: switches.first,
|
84
|
+
env: environment_variable)
|
85
|
+
else
|
86
|
+
Clamp.message(:option_required,
|
87
|
+
option: switches.first)
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
77
91
|
private
|
78
92
|
|
79
93
|
def default_description
|
@@ -81,8 +95,8 @@ module Clamp
|
|
81
95
|
("$#{@environment_variable}" if defined?(@environment_variable)),
|
82
96
|
(@default_value.inspect if defined?(@default_value))
|
83
97
|
].compact
|
84
|
-
return
|
85
|
-
"
|
98
|
+
return nil if default_sources.empty?
|
99
|
+
"#{Clamp.message(:default)}: " + default_sources.join(", #{Clamp.message(:or)} ")
|
86
100
|
end
|
87
101
|
|
88
102
|
end
|
@@ -1,7 +1,9 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Clamp
|
2
4
|
module Attribute
|
3
5
|
|
4
|
-
# Represents an
|
6
|
+
# Represents an attribute of a Clamp::Command instance.
|
5
7
|
#
|
6
8
|
class Instance
|
7
9
|
|
@@ -27,7 +29,7 @@ module Clamp
|
|
27
29
|
end
|
28
30
|
|
29
31
|
def default
|
30
|
-
command.send(attribute.default_method)
|
32
|
+
command.send(attribute.default_method) if command.respond_to?(attribute.default_method, true)
|
31
33
|
end
|
32
34
|
|
33
35
|
# default implementation of read_method
|
@@ -60,6 +62,10 @@ module Clamp
|
|
60
62
|
end
|
61
63
|
end
|
62
64
|
|
65
|
+
def signal_usage_error(*args)
|
66
|
+
command.send(:signal_usage_error, *args)
|
67
|
+
end
|
68
|
+
|
63
69
|
def default_from_environment
|
64
70
|
return if self.defined?
|
65
71
|
return if attribute.environment_variable.nil?
|
@@ -69,10 +75,26 @@ module Clamp
|
|
69
75
|
begin
|
70
76
|
take(value)
|
71
77
|
rescue ArgumentError => e
|
72
|
-
|
78
|
+
signal_usage_error Clamp.message(:env_argument_error, env: attribute.environment_variable, message: e.message)
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
def unset?
|
83
|
+
if attribute.multivalued?
|
84
|
+
read.empty?
|
85
|
+
else
|
86
|
+
read.nil?
|
73
87
|
end
|
74
88
|
end
|
75
89
|
|
90
|
+
def missing?
|
91
|
+
attribute.required? && unset?
|
92
|
+
end
|
93
|
+
|
94
|
+
def verify_not_missing
|
95
|
+
signal_usage_error attribute.option_missing_message if missing?
|
96
|
+
end
|
97
|
+
|
76
98
|
end
|
77
99
|
|
78
100
|
end
|
data/lib/clamp/command.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require "clamp/messages"
|
2
4
|
require "clamp/errors"
|
3
5
|
require "clamp/help"
|
@@ -48,6 +50,7 @@ module Clamp
|
|
48
50
|
parse_options
|
49
51
|
parse_parameters
|
50
52
|
parse_subcommand
|
53
|
+
verify_required_options_are_set
|
51
54
|
handle_remaining_arguments
|
52
55
|
end
|
53
56
|
|
@@ -81,7 +84,7 @@ module Clamp
|
|
81
84
|
#
|
82
85
|
# @ param [String] name subcommand_name
|
83
86
|
def subcommand_missing(name)
|
84
|
-
signal_usage_error(Clamp.message(:no_such_subcommand, :
|
87
|
+
signal_usage_error(Clamp.message(:no_such_subcommand, name: name))
|
85
88
|
end
|
86
89
|
|
87
90
|
include Clamp::Option::Parsing
|
@@ -122,6 +125,11 @@ module Clamp
|
|
122
125
|
include Clamp::Subcommand::Declaration
|
123
126
|
include Help
|
124
127
|
|
128
|
+
# An alternative to "def execute"
|
129
|
+
def execute(&block)
|
130
|
+
define_method(:execute, &block)
|
131
|
+
end
|
132
|
+
|
125
133
|
# Create an instance of this command class, and run it.
|
126
134
|
#
|
127
135
|
# @param [String] invocation_path the path used to invoke the command
|
data/lib/clamp/errors.rb
CHANGED
@@ -1,8 +1,12 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Clamp
|
2
4
|
|
5
|
+
# raised to indicate invalid option/parameter declaration
|
3
6
|
class DeclarationError < StandardError
|
4
7
|
end
|
5
8
|
|
9
|
+
# abstract command runtime error
|
6
10
|
class RuntimeError < StandardError
|
7
11
|
|
8
12
|
def initialize(message, command)
|
@@ -14,10 +18,10 @@ module Clamp
|
|
14
18
|
|
15
19
|
end
|
16
20
|
|
17
|
-
#
|
21
|
+
# raised to signal incorrect command usage
|
18
22
|
class UsageError < RuntimeError; end
|
19
23
|
|
20
|
-
#
|
24
|
+
# raised to request usage help
|
21
25
|
class HelpWanted < RuntimeError
|
22
26
|
|
23
27
|
def initialize(command)
|
@@ -26,7 +30,7 @@ module Clamp
|
|
26
30
|
|
27
31
|
end
|
28
32
|
|
29
|
-
#
|
33
|
+
# raised to signal error during execution
|
30
34
|
class ExecutionError < RuntimeError
|
31
35
|
|
32
36
|
def initialize(message, command, status = 1)
|
data/lib/clamp/help.rb
CHANGED
@@ -1,8 +1,12 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require "stringio"
|
2
4
|
require "clamp/messages"
|
3
5
|
|
4
6
|
module Clamp
|
5
7
|
|
8
|
+
# Command help generation.
|
9
|
+
#
|
6
10
|
module Help
|
7
11
|
|
8
12
|
def usage(usage)
|
@@ -41,47 +45,66 @@ module Clamp
|
|
41
45
|
help = builder
|
42
46
|
help.add_usage(invocation_path, usage_descriptions)
|
43
47
|
help.add_description(description)
|
44
|
-
if has_parameters?
|
45
|
-
|
46
|
-
end
|
47
|
-
if has_subcommands?
|
48
|
-
help.add_list(Clamp.message(:subcommands_heading), recognised_subcommands)
|
49
|
-
end
|
48
|
+
help.add_list(Clamp.message(:parameters_heading), parameters) if has_parameters?
|
49
|
+
help.add_list(Clamp.message(:subcommands_heading), recognised_subcommands) if has_subcommands?
|
50
50
|
help.add_list(Clamp.message(:options_heading), recognised_options)
|
51
51
|
help.string
|
52
52
|
end
|
53
53
|
|
54
|
+
# A builder for auto-generated help.
|
55
|
+
#
|
54
56
|
class Builder
|
55
57
|
|
56
58
|
def initialize
|
57
|
-
@
|
59
|
+
@lines = []
|
58
60
|
end
|
59
61
|
|
60
62
|
def string
|
61
|
-
|
63
|
+
left_column_width = lines.grep(Array).map(&:first).map(&:size).max
|
64
|
+
StringIO.new.tap do |out|
|
65
|
+
lines.each do |line|
|
66
|
+
case line
|
67
|
+
when Array
|
68
|
+
line[0] = line[0].ljust(left_column_width)
|
69
|
+
line.unshift("")
|
70
|
+
out.puts(line.join(" "))
|
71
|
+
else
|
72
|
+
out.puts(line)
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end.string
|
76
|
+
end
|
77
|
+
|
78
|
+
def line(text = "")
|
79
|
+
@lines << text
|
80
|
+
end
|
81
|
+
|
82
|
+
def row(lhs, rhs)
|
83
|
+
@lines << [lhs, rhs]
|
62
84
|
end
|
63
85
|
|
64
86
|
def add_usage(invocation_path, usage_descriptions)
|
65
|
-
|
87
|
+
line Clamp.message(:usage_heading) + ":"
|
66
88
|
usage_descriptions.each do |usage|
|
67
|
-
|
89
|
+
line " #{invocation_path} #{usage}".rstrip
|
68
90
|
end
|
69
91
|
end
|
70
92
|
|
71
93
|
def add_description(description)
|
72
94
|
return unless description
|
73
|
-
|
74
|
-
|
95
|
+
line
|
96
|
+
line description.gsub(/^/, " ")
|
75
97
|
end
|
76
98
|
|
77
99
|
DETAIL_FORMAT = " %-29s %s".freeze
|
78
100
|
|
79
101
|
def add_list(heading, items)
|
80
|
-
|
102
|
+
line
|
103
|
+
line "#{heading}:"
|
81
104
|
items.reject { |i| i.respond_to?(:hidden?) && i.hidden? }.each do |item|
|
82
105
|
label, description = item.help
|
83
106
|
description.each_line do |line|
|
84
|
-
|
107
|
+
row(label, line)
|
85
108
|
label = ""
|
86
109
|
end
|
87
110
|
end
|
@@ -89,9 +112,7 @@ module Clamp
|
|
89
112
|
|
90
113
|
private
|
91
114
|
|
92
|
-
|
93
|
-
@out.puts(*args)
|
94
|
-
end
|
115
|
+
attr_accessor :lines
|
95
116
|
|
96
117
|
end
|
97
118
|
|