clamp 0.2.3 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
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