clamp 0.2.3 → 0.3.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 CHANGED
@@ -205,11 +205,13 @@ Clamp generates an anonymous subclass of the current class, to represent the sub
205
205
 
206
206
  ### Default subcommand
207
207
 
208
- You can mark a subcommand as "default" by using `default_subcommand` to declare it, rather than `subcommand`. Usually the SUBCOMMAND parameter is mandatory, but if a default subcommand is declared, it becomes optional.
208
+ You can set a default subcommand, at the class level, as follows:
209
209
 
210
210
  class MainCommand < Clamp::Command
211
211
 
212
- default_subcommand "status", "Display current status" do
212
+ self.default_subcommand = "status"
213
+
214
+ subcommand "status", "Display current status" do
213
215
 
214
216
  def execute
215
217
  # ...
@@ -219,6 +221,8 @@ You can mark a subcommand as "default" by using `default_subcommand` to declare
219
221
 
220
222
  end
221
223
 
224
+ Then, if when no SUBCOMMAND argument is provided, the default will be selected.
225
+
222
226
  ### Subcommand options and parameters
223
227
 
224
228
  Options are inheritable, so any options declared for a command are supported for it's sub-classes (e.g. those created using `subcommand`). Parameters, on the other hand, are not inherited - each subcommand must declare it's own parameter list.
data/examples/flipflop CHANGED
@@ -12,7 +12,9 @@ class FlipFlop < Clamp::Command
12
12
  exit(0)
13
13
  end
14
14
 
15
- default_subcommand "flip", "flip it" do
15
+ self.default_subcommand = "flip"
16
+
17
+ subcommand "flip", "flip it" do
16
18
  def execute
17
19
  puts "FLIPPED"
18
20
  end
@@ -23,7 +25,7 @@ class FlipFlop < Clamp::Command
23
25
  puts "FLOPPED"
24
26
  end
25
27
  end
26
-
28
+
27
29
  end
28
30
 
29
31
  FlipFlop.run
@@ -1,15 +1,15 @@
1
1
  module Clamp
2
-
2
+
3
3
  module AttributeDeclaration
4
4
 
5
5
  protected
6
-
6
+
7
7
  def define_accessors_for(attribute, &block)
8
8
  define_reader_for(attribute)
9
9
  define_default_for(attribute)
10
10
  define_writer_for(attribute, &block)
11
11
  end
12
-
12
+
13
13
  def define_reader_for(attribute)
14
14
  define_method(attribute.read_method) do
15
15
  if instance_variable_defined?(attribute.ivar_name)
@@ -36,5 +36,5 @@ module Clamp
36
36
  end
37
37
 
38
38
  end
39
-
39
+
40
40
  end
data/lib/clamp/command.rb CHANGED
@@ -5,15 +5,15 @@ require 'clamp/option/parsing'
5
5
  require 'clamp/parameter/declaration'
6
6
  require 'clamp/parameter/parsing'
7
7
  require 'clamp/subcommand/declaration'
8
- require 'clamp/subcommand/execution'
8
+ require 'clamp/subcommand/parsing'
9
9
 
10
10
  module Clamp
11
11
 
12
12
  # {Command} models a shell command. Each command invocation is a new object.
13
- # Command options and parameters are represented as attributes
13
+ # Command options and parameters are represented as attributes
14
14
  # (see {Command::Declaration}).
15
15
  #
16
- # The main entry-point is {#run}, which uses {#parse} to populate attributes based
16
+ # The main entry-point is {#run}, which uses {#parse} to populate attributes based
17
17
  # on an array of command-line arguments, then calls {#execute} (which you provide)
18
18
  # to make it go.
19
19
  #
@@ -32,7 +32,7 @@ module Clamp
32
32
  # @return [String] the path used to invoke this command
33
33
  #
34
34
  attr_reader :invocation_path
35
-
35
+
36
36
  # @return [Array<String>] unconsumed command-line arguments
37
37
  #
38
38
  def remaining_arguments
@@ -48,12 +48,13 @@ module Clamp
48
48
  @remaining_arguments = arguments.dup
49
49
  parse_options
50
50
  parse_parameters
51
- @remaining_arguments
51
+ parse_subcommand
52
+ handle_remaining_arguments
52
53
  end
53
54
 
54
55
  # Run the command, with the specified arguments.
55
56
  #
56
- # This calls {#parse} to process the command-line arguments,
57
+ # This calls {#parse} to process the command-line arguments,
57
58
  # then delegates to {#execute}.
58
59
  #
59
60
  # @param [Array<String>] arguments command-line arguments
@@ -64,12 +65,12 @@ module Clamp
64
65
  end
65
66
 
66
67
  # Execute the command (assuming that all options/parameters have been set).
67
- #
68
+ #
68
69
  # This method is designed to be overridden in sub-classes.
69
70
  #
70
71
  def execute
71
- if self.class.has_subcommands?
72
- execute_subcommand
72
+ if @subcommand
73
+ @subcommand.execute
73
74
  else
74
75
  raise "you need to define #execute"
75
76
  end
@@ -83,12 +84,18 @@ module Clamp
83
84
 
84
85
  include Clamp::Option::Parsing
85
86
  include Clamp::Parameter::Parsing
86
- include Clamp::Subcommand::Execution
87
+ include Clamp::Subcommand::Parsing
87
88
 
88
89
  protected
89
-
90
+
90
91
  attr_accessor :context
91
92
 
93
+ def handle_remaining_arguments
94
+ unless remaining_arguments.empty?
95
+ signal_usage_error "too many arguments"
96
+ end
97
+ end
98
+
92
99
  private
93
100
 
94
101
  def signal_usage_error(message)
@@ -97,8 +104,8 @@ module Clamp
97
104
  raise e
98
105
  end
99
106
 
100
- def help_requested=(value)
101
- raise Clamp::HelpWanted.new(self)
107
+ def request_help
108
+ raise HelpWanted, self
102
109
  end
103
110
 
104
111
  class << self
@@ -113,9 +120,9 @@ module Clamp
113
120
  # @param [String] invocation_path the path used to invoke the command
114
121
  # @param [Array<String>] arguments command-line arguments
115
122
  # @param [Hash] context additional data the command may need
116
- #
123
+ #
117
124
  def run(invocation_path = File.basename($0), arguments = ARGV, context = {})
118
- begin
125
+ begin
119
126
  new(invocation_path, context).run(arguments)
120
127
  rescue Clamp::UsageError => e
121
128
  $stderr.puts "ERROR: #{e.message}"
data/lib/clamp/help.rb CHANGED
@@ -23,8 +23,12 @@ module Clamp
23
23
  attr_reader :description
24
24
 
25
25
  def derived_usage_description
26
- parts = parameters.map { |a| a.name }
27
- parts.unshift("[OPTIONS]")
26
+ parts = ["[OPTIONS]"]
27
+ parts += parameters.map { |a| a.name }
28
+ if has_subcommands?
29
+ parts << "SUBCOMMAND"
30
+ parts << "[ARGS] ..."
31
+ end
28
32
  parts.join(" ")
29
33
  end
30
34
 
@@ -35,7 +35,7 @@ module Clamp
35
35
  help_switches = ["--help"]
36
36
  help_switches.unshift("-h") unless effective_options.find { |o| o.handles?("-h") }
37
37
  option help_switches, :flag, "print help" do
38
- raise Clamp::HelpWanted.new(self)
38
+ request_help
39
39
  end
40
40
  end
41
41
  @implicit_options_declared = true
@@ -4,7 +4,7 @@ module Clamp
4
4
  module Parsing
5
5
 
6
6
  protected
7
-
7
+
8
8
  def parse_parameters
9
9
 
10
10
  self.class.parameters.each do |parameter|
@@ -16,10 +16,6 @@ module Clamp
16
16
  end
17
17
  end
18
18
 
19
- unless remaining_arguments.empty?
20
- signal_usage_error "too many arguments"
21
- end
22
-
23
19
  end
24
20
 
25
21
  end
@@ -10,48 +10,33 @@ module Clamp
10
10
  end
11
11
 
12
12
  def subcommand(name, description, subcommand_class = self, &block)
13
- has_subcommands!
14
- declare_subcommand(name, description, subcommand_class, &block)
15
- end
16
-
17
- def default_subcommand(name, description, subcommand_class = self, &block)
18
- has_subcommands!(name)
19
- declare_subcommand(name, description, subcommand_class, &block)
13
+ if block
14
+ # generate a anonymous sub-class
15
+ subcommand_class = Class.new(subcommand_class, &block)
16
+ end
17
+ recognised_subcommands << Subcommand.new(name, description, subcommand_class)
20
18
  end
21
19
 
22
20
  def has_subcommands?
23
- @has_subcommands
21
+ !recognised_subcommands.empty?
24
22
  end
25
23
 
26
24
  def find_subcommand(name)
27
25
  recognised_subcommands.find { |sc| sc.is_called?(name) }
28
26
  end
29
-
30
- def has_subcommands!(default = nil)
31
- if @has_subcommands
32
- if default
33
- raise "You must declare the default_subcommand before any other subcommands"
34
- end
27
+
28
+ attr_writer :default_subcommand
29
+
30
+ def default_subcommand(*args, &block)
31
+ if args.empty?
32
+ @default_subcommand
35
33
  else
36
- if default
37
- parameter "[SUBCOMMAND]", "subcommand name", :attribute_name => :subcommand_name, :default => default
38
- else
39
- parameter "SUBCOMMAND", "subcommand name", :attribute_name => :subcommand_name
40
- end
41
- parameter "[ARGS] ...", "subcommand arguments", :attribute_name => :subcommand_arguments
42
- @has_subcommands = true
34
+ $stderr.puts "WARNING: Clamp default_subcommand syntax has changed; check the README."
35
+ $stderr.puts " (from #{caller.first})"
36
+ subcommand(*args, &block)
37
+ self.default_subcommand = args.first
43
38
  end
44
39
  end
45
-
46
- private
47
-
48
- def declare_subcommand(name, description, subcommand_class = self, &block)
49
- if block
50
- # generate a anonymous sub-class
51
- subcommand_class = Class.new(subcommand_class, &block)
52
- end
53
- recognised_subcommands << Subcommand.new(name, description, subcommand_class)
54
- end
55
40
 
56
41
  end
57
42
 
@@ -0,0 +1,42 @@
1
+ module Clamp
2
+ class Subcommand
3
+
4
+ module Parsing
5
+
6
+ protected
7
+
8
+ def parse_subcommand
9
+ return false unless self.class.has_subcommands?
10
+ subcommand_name = parse_subcommand_name
11
+ @subcommand = instatiate_subcommand(subcommand_name)
12
+ @subcommand.parse(remaining_arguments)
13
+ remaining_arguments.clear
14
+ end
15
+
16
+ private
17
+
18
+ def parse_subcommand_name
19
+ remaining_arguments.shift || self.class.default_subcommand || request_help
20
+ end
21
+
22
+ def find_subcommand(name)
23
+ self.class.find_subcommand(name) ||
24
+ signal_usage_error("No such sub-command '#{name}'")
25
+ end
26
+
27
+ def instatiate_subcommand(name)
28
+ subcommand_class = find_subcommand(name).subcommand_class
29
+ subcommand = subcommand_class.new("#{invocation_path} #{name}", context)
30
+ self.class.recognised_options.each do |option|
31
+ option_set = instance_variable_defined?(option.ivar_name)
32
+ if option_set && subcommand.respond_to?(option.write_method)
33
+ subcommand.send(option.write_method, self.send(option.read_method))
34
+ end
35
+ end
36
+ subcommand
37
+ end
38
+
39
+ end
40
+
41
+ end
42
+ end
data/lib/clamp/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Clamp
2
- VERSION = "0.2.3".freeze
2
+ VERSION = "0.3.0".freeze
3
3
  end
@@ -33,40 +33,67 @@ describe Clamp::Command do
33
33
 
34
34
  end
35
35
 
36
+ context "executed with no subcommand" do
37
+
38
+ it "triggers help" do
39
+ lambda do
40
+ @command.run([])
41
+ end.should raise_error(Clamp::HelpWanted)
42
+ end
43
+
44
+ end
45
+
46
+ describe "#parse" do
47
+
48
+ describe "with too many arguments" do
49
+
50
+ it "raises a UsageError" do
51
+ lambda do
52
+ @command.parse(["flip", "extra", "args"])
53
+ end.should raise_error(Clamp::UsageError, "too many arguments")
54
+ end
55
+
56
+ end
57
+
58
+ end
59
+
36
60
  describe "#help" do
37
-
61
+
62
+ it "shows subcommand parameters in usage" do
63
+ @command.help.should include("flipflop [OPTIONS] SUBCOMMAND [ARGS] ...")
64
+ end
65
+
38
66
  it "lists subcommands" do
39
67
  @help = @command.help
40
68
  @help.should =~ /Subcommands:/
41
69
  @help.should =~ /flip +flip it/
42
70
  @help.should =~ /flop +flop it/
43
71
  end
44
-
72
+
45
73
  it "handles new lines in subcommand descriptions" do
46
- @help = @command.help
47
- @help.should =~ /flop +flop it\n +for extra flop/
74
+ @command.help.should =~ /flop +flop it\n +for extra flop/
48
75
  end
49
-
76
+
50
77
  end
51
-
78
+
52
79
  end
53
80
 
54
81
  describe "with an aliased subcommand" do
55
-
82
+
56
83
  given_command "blah" do
57
84
 
58
85
  subcommand ["say", "talk"], "Say something" do
59
-
86
+
60
87
  parameter "WORD ...", "stuff to say"
61
-
88
+
62
89
  def execute
63
90
  puts word_list
64
91
  end
65
-
92
+
66
93
  end
67
-
94
+
68
95
  end
69
-
96
+
70
97
  it "responds to both aliases" do
71
98
 
72
99
  @command.run(["say", "boo"])
@@ -76,8 +103,8 @@ describe Clamp::Command do
76
103
  stdout.should =~ /jive/
77
104
 
78
105
  end
79
-
80
- describe "#help" do
106
+
107
+ describe "#help" do
81
108
 
82
109
  it "lists all aliases" do
83
110
  @help = @command.help
@@ -85,9 +112,9 @@ describe Clamp::Command do
85
112
  end
86
113
 
87
114
  end
88
-
115
+
89
116
  end
90
-
117
+
91
118
  describe "with nested subcommands" do
92
119
 
93
120
  given_command "fubar" do
@@ -110,9 +137,36 @@ describe Clamp::Command do
110
137
  end
111
138
 
112
139
  end
113
-
140
+
114
141
  describe "with a default subcommand" do
115
-
142
+
143
+ given_command "admin" do
144
+
145
+ subcommand "status", "Show status" do
146
+
147
+ def execute
148
+ puts "All good!"
149
+ end
150
+
151
+ end
152
+
153
+ self.default_subcommand = "status"
154
+
155
+ end
156
+
157
+ context "executed with no subcommand" do
158
+
159
+ it "invokes the default subcommand" do
160
+ @command.run([])
161
+ stdout.should =~ /All good/
162
+ end
163
+
164
+ end
165
+
166
+ end
167
+
168
+ describe "with a default subcommand, declared the old way" do
169
+
116
170
  given_command "admin" do
117
171
 
118
172
  default_subcommand "status", "Show status" do
@@ -125,13 +179,17 @@ describe Clamp::Command do
125
179
 
126
180
  end
127
181
 
128
- it "delegates multiple levels" do
129
- @command.run([])
130
- stdout.should =~ /All good/
182
+ context "executed with no subcommand" do
183
+
184
+ it "invokes the default subcommand" do
185
+ @command.run([])
186
+ stdout.should =~ /All good/
187
+ end
188
+
131
189
  end
132
-
190
+
133
191
  end
134
-
192
+
135
193
  describe "each subcommand" do
136
194
 
137
195
  before do
@@ -146,7 +204,7 @@ describe Clamp::Command do
146
204
  option "--direction", "DIR", "which way", :default => "home"
147
205
 
148
206
  include speed_options
149
-
207
+
150
208
  subcommand "move", "move in the appointed direction" do
151
209
 
152
210
  def execute
metadata CHANGED
@@ -1,30 +1,23 @@
1
- --- !ruby/object:Gem::Specification
1
+ --- !ruby/object:Gem::Specification
2
2
  name: clamp
3
- version: !ruby/object:Gem::Version
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.3.0
4
5
  prerelease:
5
- version: 0.2.3
6
6
  platform: ruby
7
- authors:
7
+ authors:
8
8
  - Mike Williams
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
-
13
- date: 2011-07-31 00:00:00 Z
12
+ date: 2011-10-30 00:00:00.000000000Z
14
13
  dependencies: []
15
-
16
- description: |
17
- Clamp provides an object-model for command-line utilities.
18
- It handles parsing of command-line options, and generation of usage help.
19
-
14
+ description: ! "Clamp provides an object-model for command-line utilities. \nIt handles
15
+ parsing of command-line options, and generation of usage help.\n"
20
16
  email: mdub@dogbiscuit.org
21
17
  executables: []
22
-
23
18
  extensions: []
24
-
25
19
  extra_rdoc_files: []
26
-
27
- files:
20
+ files:
28
21
  - .gitignore
29
22
  - Gemfile
30
23
  - README.markdown
@@ -48,7 +41,7 @@ files:
48
41
  - lib/clamp/parameter/parsing.rb
49
42
  - lib/clamp/subcommand.rb
50
43
  - lib/clamp/subcommand/declaration.rb
51
- - lib/clamp/subcommand/execution.rb
44
+ - lib/clamp/subcommand/parsing.rb
52
45
  - lib/clamp/version.rb
53
46
  - spec/clamp/command_group_spec.rb
54
47
  - spec/clamp/command_spec.rb
@@ -58,38 +51,35 @@ files:
58
51
  - spec/spec_helper.rb
59
52
  homepage: http://github.com/mdub/clamp
60
53
  licenses: []
61
-
62
54
  post_install_message:
63
55
  rdoc_options: []
64
-
65
- require_paths:
56
+ require_paths:
66
57
  - lib
67
- required_ruby_version: !ruby/object:Gem::Requirement
58
+ required_ruby_version: !ruby/object:Gem::Requirement
68
59
  none: false
69
- requirements:
70
- - - ">="
71
- - !ruby/object:Gem::Version
72
- hash: 1591530698992906617
73
- segments:
60
+ requirements:
61
+ - - ! '>='
62
+ - !ruby/object:Gem::Version
63
+ version: '0'
64
+ segments:
74
65
  - 0
75
- version: "0"
76
- required_rubygems_version: !ruby/object:Gem::Requirement
66
+ hash: 2140542804318955849
67
+ required_rubygems_version: !ruby/object:Gem::Requirement
77
68
  none: false
78
- requirements:
79
- - - ">="
80
- - !ruby/object:Gem::Version
81
- hash: 1591530698992906617
82
- segments:
69
+ requirements:
70
+ - - ! '>='
71
+ - !ruby/object:Gem::Version
72
+ version: '0'
73
+ segments:
83
74
  - 0
84
- version: "0"
75
+ hash: 2140542804318955849
85
76
  requirements: []
86
-
87
77
  rubyforge_project:
88
- rubygems_version: 1.7.2
78
+ rubygems_version: 1.8.10
89
79
  signing_key:
90
80
  specification_version: 3
91
81
  summary: a minimal framework for command-line utilities
92
- test_files:
82
+ test_files:
93
83
  - spec/clamp/command_group_spec.rb
94
84
  - spec/clamp/command_spec.rb
95
85
  - spec/clamp/option_module_spec.rb
@@ -1,36 +0,0 @@
1
- module Clamp
2
- class Subcommand
3
-
4
- module Execution
5
-
6
- protected
7
-
8
- def execute_subcommand
9
- signal_usage_error "no subcommand specified" unless subcommand_name
10
- subcommand_class = find_subcommand_class(subcommand_name)
11
- subcommand = subcommand_class.new("#{invocation_path} #{subcommand_name}", context)
12
- self.class.recognised_options.each do |option|
13
- option_set = instance_variable_defined?(option.ivar_name)
14
- if option_set && subcommand.respond_to?(option.write_method)
15
- subcommand.send(option.write_method, self.send(option.read_method))
16
- end
17
- end
18
- subcommand.run(subcommand_arguments)
19
- end
20
-
21
- private
22
-
23
- def find_subcommand(name)
24
- self.class.find_subcommand(name) ||
25
- signal_usage_error("No such sub-command '#{name}'")
26
- end
27
-
28
- def find_subcommand_class(name)
29
- subcommand = find_subcommand(name)
30
- subcommand.subcommand_class if subcommand
31
- end
32
-
33
- end
34
-
35
- end
36
- end