clamp 0.0.1 → 0.0.7

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore CHANGED
@@ -3,3 +3,5 @@
3
3
  .yardoc
4
4
  doc
5
5
  pkg/*
6
+ Gemfile.lock
7
+
data/README.markdown CHANGED
@@ -17,24 +17,165 @@ Yeah, sorry. There are a bunch of existing command-line parsing libraries out t
17
17
  Quick Start
18
18
  -----------
19
19
 
20
- Clamp models a command as a Ruby class, and command invocations as instances of that class.
20
+ Clamp models a command as a Ruby class; a subclass of `Clamp::Command`. They look something like this:
21
21
 
22
- "Command classes" are subclasses of `Clamp::Command`. They look like this:
22
+ class SpeakCommand < Clamp::Command
23
23
 
24
- class InstallCommand < Clamp::Command
24
+ option "--loud", :flag, "say it loud"
25
+ option ["-n", "--iterations"], "N", "say it N times", :default => 1 do |s|
26
+ Integer(s)
27
+ end
28
+
29
+ argument "WORDS ...", "the thing to say"
30
+
31
+ def execute
32
+
33
+ signal_usage_error "I have nothing to say" if arguments.empty?
34
+ the_truth = arguments.join(" ")
35
+ the_truth.upcase! if loud?
36
+
37
+ iterations.times do
38
+ puts the_truth
39
+ end
40
+
41
+ end
42
+
43
+ end
44
+
45
+ Class-level methods (like `option` and `argument`) are available to declare command-line options, and document usage.
46
+
47
+ The command can be invoked by instantiating the class, and asking it to run:
48
+
49
+ SpeakCommand.new("speak").run(["--loud", "a", "b", "c"])
50
+
51
+ but it's more typical to use the class-level "`run`" method:
52
+
53
+ SpeakCommand.run
54
+
55
+ which takes arguments from `ARGV`, and includes some handy error-handling.
56
+
57
+ Declaring options
58
+ -----------------
59
+
60
+ Options are declared using the [`option`](../Clamp/Command.option) method. The three required arguments are:
61
+
62
+ 1. the option switch (or switches),
63
+ 2. a short description of the option argument type, and
64
+ 3. a description of the option itself
65
+
66
+ For example:
67
+
68
+ option "--flavour", "FLAVOUR", "ice-cream flavour"
69
+
70
+ It works a little like `attr_accessor`, defining reader and writer methods on the command class. The attribute name is derived from the switch (in this case, "`flavour`"). When you pass options to your command, Clamp will populate the attributes, which are then available for use in your `#execute` method.
71
+
72
+ def execute
73
+ puts "You chose #{flavour}. Excellent choice!"
74
+ end
75
+
76
+ If you don't like the inferred attribute name, you can override it:
77
+
78
+ option "--type", "TYPE", "type of widget", :attribute_name => :widget_type
79
+ # to avoid clobbering Object#type
80
+
81
+ ### Short/long option switches
82
+
83
+ The first argument to `option` can be an array, rather than a single string, in which case all the switches are treated as aliases:
84
+
85
+ option ["-s", "--subject"], "SUBJECT", "email subject line"
86
+
87
+ ### Flag options
88
+
89
+ Some options are just boolean flags. Pass "`:flag`" as the second parameter to tell Clamp not to expect an option argument:
90
+
91
+ option "--verbose", :flag, "be chatty"
92
+
93
+ For flag options, Clamp appends "`?`" to the generated reader method; ie. you get a method called "`verbose?`", rather than just "`verbose`".
94
+
95
+ Negatable flags are easy to generate, too:
96
+
97
+ option "--[no-]force", :flag, "be forceful (or not)"
98
+
99
+ Clamp will handle both "`--force`" and "`--no-force`" options, setting the value of "`#force?`" appropriately.
100
+
101
+ ### Validation and conversion of option arguments
102
+
103
+ If a block is passed to `option`, it will be called with the raw string option argument, and is expected to coerce that String to the correct type, e.g.
104
+
105
+ option "--port", "PORT", "port to listen on" do |s|
106
+ Integer(s)
107
+ end
108
+
109
+ If the block raises an ArgumentError, Clamp will catch it, and report that the option value was bad:
110
+
111
+ !!!plain
112
+ ERROR: option '--port': invalid value for Integer: "blah"
113
+
114
+ Declaring arguments
115
+ -------------------
116
+
117
+ The `argument` method is used to declare command arguments:
118
+
119
+ argument "FILE ...", "source files"
120
+ argument "DIR", "target directory"
121
+
122
+ Use of `argument` 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
+
124
+ Sub-commands
125
+ ------------
126
+
127
+ The `subcommand` method declares sub-commands:
128
+
129
+ class MainCommand < Clamp::Command
130
+
131
+ subcommand "init", "Initialize the repository" do
132
+
133
+ def execute
134
+ # ...
135
+ end
136
+
137
+ end
138
+
139
+ end
140
+
141
+ Clamp generates an anonymous sub-class of the current class, to represent the sub-command. Additional options may be declared within subcommand blocks, but all options declared on the parent class are also accepted.
142
+
143
+ Alternatively, you can provide an explicit sub-command class, rather than a block:
25
144
 
26
- option "--force", :flag, ""
27
-
145
+ class MainCommand < Clamp::Command
146
+
147
+ subcommand "init", "Initialize the repository", InitCommand
148
+
149
+ end
150
+
151
+ class InitCommand < Clamp::Command
152
+
28
153
  def execute
29
- # do something
154
+ # ...
30
155
  end
31
-
156
+
32
157
  end
33
158
 
34
- Class-level methods are available to declare command-line options, and document usage.
159
+ When a command has sub-commands, Clamp will attempt to delegate based on the first command-line argument, before options are parsed. Remaining arguments will be passed on to the sub-command.
160
+
161
+ Getting help
162
+ ------------
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 `argument`.
165
+
166
+ $ speak --help
167
+ Usage:
168
+ speak [OPTIONS] WORDS ...
169
+
170
+ Arguments:
171
+ WORDS ... the thing to say
35
172
 
36
- Clamp commands are invoked like so:
173
+ Options:
174
+ --loud say it loud
175
+ -n, --iterations N say it N times
176
+ --help print help
37
177
 
38
- InstallCommand.run
178
+ Contributing to Clamp
179
+ ---------------------
39
180
 
40
- This will instantiate a new `InstallCommand`, handle command-line args, and finally call the `#execute` method to do the real work.
181
+ Source-code for Clamp is [on Github](https://github.com/mdub/clamp).
data/examples/flipflop ADDED
@@ -0,0 +1,21 @@
1
+ #! /usr/bin/env ruby
2
+
3
+ require "clamp"
4
+
5
+ class FlipFlop < Clamp::Command
6
+
7
+ subcommand "flip", "flip it" do
8
+ def execute
9
+ puts "FLIPPED"
10
+ end
11
+ end
12
+
13
+ subcommand "flop", "flop it" do
14
+ def execute
15
+ puts "FLOPPED"
16
+ end
17
+ end
18
+
19
+ end
20
+
21
+ FlipFlop.run
data/examples/icecream ADDED
@@ -0,0 +1,16 @@
1
+ #! /usr/bin/env ruby
2
+
3
+ require "clamp"
4
+
5
+ class Icecream < Clamp::Command
6
+
7
+ option "--flavour", "FLAVOUR", "ice-cream flavour"
8
+
9
+ def execute
10
+ signal_usage_error "what flavour?" unless flavour
11
+ puts "You chose #{flavour}. Excellent choice!"
12
+ end
13
+
14
+ end
15
+
16
+ Icecream.run
data/examples/rename CHANGED
@@ -17,8 +17,6 @@ class Rename < Clamp::Command
17
17
  n
18
18
  end
19
19
 
20
- help_option
21
-
22
20
  def initialize(name)
23
21
  super
24
22
  @times = 1
data/examples/speak ADDED
@@ -0,0 +1,28 @@
1
+ #! /usr/bin/env ruby
2
+
3
+ require "clamp"
4
+
5
+ class SpeakCommand < Clamp::Command
6
+
7
+ option "--loud", :flag, "say it loud"
8
+ option ["-n", "--iterations"], "N", "say it N times", :default => 1 do |s|
9
+ Integer(s)
10
+ end
11
+
12
+ argument "WORDS ...", "the thing to say"
13
+
14
+ def execute
15
+
16
+ signal_usage_error "I have nothing to say" if arguments.empty?
17
+ the_truth = arguments.join(" ")
18
+ the_truth.upcase! if loud?
19
+
20
+ iterations.times do
21
+ puts the_truth
22
+ end
23
+
24
+ end
25
+
26
+ end
27
+
28
+ SpeakCommand.run
data/lib/clamp/command.rb CHANGED
@@ -1,17 +1,22 @@
1
- require 'clamp/argument'
2
- require 'clamp/option'
1
+ require 'clamp/help_support'
2
+ require 'clamp/option_support'
3
+ require 'clamp/subcommand_support'
3
4
 
4
5
  module Clamp
5
-
6
+
6
7
  class Command
7
-
8
- def initialize(name)
8
+
9
+ def initialize(name, context = {})
9
10
  @name = name
11
+ @context = context
10
12
  end
11
-
13
+
12
14
  attr_reader :name
13
15
  attr_reader :arguments
14
16
 
17
+ attr_accessor :context
18
+ attr_accessor :parent_command
19
+
15
20
  def parse(arguments)
16
21
  while arguments.first =~ /^-/
17
22
  case (switch = arguments.shift)
@@ -31,166 +36,110 @@ module Clamp
31
36
  rescue ArgumentError => e
32
37
  signal_usage_error "option '#{switch}': #{e.message}"
33
38
  end
34
-
39
+
35
40
  end
36
41
  end
37
42
  @arguments = arguments
38
43
  end
39
-
44
+
45
+ # default implementation
40
46
  def execute
41
- raise "you need to define #execute"
47
+ if self.class.has_subcommands?
48
+ execute_subcommand
49
+ else
50
+ raise "you need to define #execute"
51
+ end
42
52
  end
43
-
53
+
44
54
  def run(arguments)
45
55
  parse(arguments)
46
56
  execute
47
57
  end
48
58
 
49
59
  def help
50
- self.class.help.gsub("__COMMAND__", name)
60
+ self.class.help(name)
51
61
  end
52
-
62
+
53
63
  private
54
64
 
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
+
55
74
  def find_option(switch)
56
75
  self.class.find_option(switch) ||
57
76
  signal_usage_error("Unrecognised option '#{switch}'")
58
77
  end
59
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
+
60
89
  def signal_usage_error(message)
61
90
  e = UsageError.new(message, self)
62
91
  e.set_backtrace(caller)
63
92
  raise e
64
93
  end
65
-
66
- class << self
67
-
68
- def options
69
- @options ||= []
70
- end
71
-
72
- def option(switches, argument_type, description, opts = {}, &block)
73
- option = Clamp::Option.new(switches, argument_type, description, opts)
74
- self.options << option
75
- declare_option_reader(option)
76
- declare_option_writer(option, &block)
77
- end
78
-
79
- def help_option(switches = ["-h", "--help"])
80
- option(switches, :flag, "print help", :attribute_name => :help_requested) do
81
- raise Clamp::HelpWanted.new(self)
82
- end
83
- end
84
-
85
- def has_options?
86
- !options.empty?
87
- end
88
-
89
- def find_option(switch)
90
- options.find { |o| o.handles?(switch) }
91
- end
92
94
 
93
- def usage(usage)
94
- @usages ||= []
95
- @usages << usage
96
- end
95
+ def help_requested=(value)
96
+ raise Clamp::HelpWanted.new(self)
97
+ end
97
98
 
98
- def arguments
99
- @arguments ||= []
100
- end
101
-
102
- def argument(name, description)
103
- arguments << Argument.new(name, description)
104
- end
99
+ class << self
105
100
 
106
- def derived_usage
107
- parts = arguments.map { |a| a.name }
108
- parts.unshift("[OPTIONS]") if has_options?
109
- parts.join(" ")
110
- end
111
-
112
- def help
113
- help = StringIO.new
114
- help.puts "Usage:"
115
- usages = @usages || [derived_usage]
116
- usages.each_with_index do |usage, i|
117
- help.puts " __COMMAND__ #{usage}".rstrip
118
- end
119
- detail_format = " %-29s %s"
120
- unless arguments.empty?
121
- help.puts "\nArguments:"
122
- arguments.each do |argument|
123
- help.puts detail_format % [argument.name, argument.description]
124
- end
125
- end
126
- unless options.empty?
127
- help.puts "\nOptions:"
128
- options.each do |option|
129
- help.puts detail_format % option.help
130
- end
131
- end
132
- help.string
133
- end
134
-
135
- def run(name = $0, args = ARGV)
101
+ include OptionSupport
102
+ include SubcommandSupport
103
+ include HelpSupport
104
+
105
+ def run(name = $0, args = ARGV, context = {})
136
106
  begin
137
- new(name).run(args)
107
+ new(name, context).run(args)
138
108
  rescue Clamp::UsageError => e
139
109
  $stderr.puts "ERROR: #{e.message}"
140
110
  $stderr.puts ""
141
- $stderr.puts e.command.help
111
+ $stderr.puts "See: '#{name} --help'"
142
112
  exit(1)
143
113
  rescue Clamp::HelpWanted => e
144
114
  puts e.command.help
145
115
  end
146
116
  end
147
117
 
148
- private
149
-
150
- def declare_option_reader(option)
151
- reader_name = option.attribute_name
152
- reader_name += "?" if option.flag?
153
- class_eval <<-RUBY
154
- def #{reader_name}
155
- @#{option.attribute_name}
156
- end
157
- RUBY
158
- end
159
-
160
- def declare_option_writer(option, &block)
161
- define_method("#{option.attribute_name}=") do |value|
162
- if block
163
- value = instance_exec(value, &block)
164
- end
165
- instance_variable_set("@#{option.attribute_name}", value)
166
- end
167
- end
168
-
169
118
  end
170
-
119
+
171
120
  end
172
-
121
+
173
122
  class Error < StandardError
174
-
123
+
175
124
  def initialize(message, command)
176
125
  super(message)
177
126
  @command = command
178
127
  end
179
128
 
180
129
  attr_reader :command
181
-
130
+
182
131
  end
183
132
 
184
133
  # raise to signal incorrect command usage
185
134
  class UsageError < Error; end
186
-
135
+
187
136
  # raise to request usage help
188
137
  class HelpWanted < Error
189
-
138
+
190
139
  def initialize(command)
191
140
  super("I need help", command)
192
141
  end
193
-
142
+
194
143
  end
195
-
144
+
196
145
  end
@@ -0,0 +1,69 @@
1
+ module Clamp
2
+
3
+ class Argument < Struct.new(:name, :description)
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
20
+
21
+ def usage(usage)
22
+ @declared_usage_descriptions ||= []
23
+ @declared_usage_descriptions << usage
24
+ end
25
+
26
+ attr_reader :declared_usage_descriptions
27
+
28
+ def derived_usage_description
29
+ parts = declared_arguments.map { |a| a.name }
30
+ parts.unshift("SUBCOMMAND") if has_subcommands?
31
+ parts.unshift("[OPTIONS]") if has_options?
32
+ parts.join(" ")
33
+ end
34
+
35
+ def usage_descriptions
36
+ declared_usage_descriptions || [derived_usage_description]
37
+ end
38
+
39
+ def help(command_name)
40
+ help = StringIO.new
41
+ help.puts "Usage:"
42
+ usage_descriptions.each_with_index do |usage, i|
43
+ help.puts " #{command_name} #{usage}".rstrip
44
+ end
45
+ detail_format = " %-29s %s"
46
+ unless declared_arguments.empty?
47
+ help.puts "\nArguments:"
48
+ declared_arguments.each do |argument|
49
+ help.puts detail_format % argument.help
50
+ end
51
+ end
52
+ unless recognised_subcommands.empty?
53
+ help.puts "\nSubcommands:"
54
+ recognised_subcommands.each do |subcommand|
55
+ help.puts detail_format % subcommand.help
56
+ end
57
+ end
58
+ if has_options?
59
+ help.puts "\nOptions:"
60
+ recognised_options.each do |option|
61
+ help.puts detail_format % option.help
62
+ end
63
+ end
64
+ help.string
65
+ end
66
+
67
+ end
68
+
69
+ end
data/lib/clamp/option.rb CHANGED
@@ -6,10 +6,15 @@ module Clamp
6
6
  @switches = Array(switches)
7
7
  @argument_type = argument_type
8
8
  @description = description
9
- @attribute_name = options[:attribute_name].to_s if options.has_key?(:attribute_name)
9
+ if options.has_key?(:attribute_name)
10
+ @attribute_name = options[:attribute_name].to_s
11
+ end
12
+ if options.has_key?(:default)
13
+ @default_value = options[:default]
14
+ end
10
15
  end
11
16
 
12
- attr_reader :switches, :argument_type, :description
17
+ attr_reader :switches, :argument_type, :description, :default_value
13
18
 
14
19
  def attribute_name
15
20
  @attribute_name ||= long_switch.sub(/^--(\[no-\])?/, '').tr('-', '_')
@@ -0,0 +1,75 @@
1
+ require 'clamp/option'
2
+
3
+ module Clamp
4
+
5
+ module OptionSupport
6
+
7
+ def option(switches, argument_type, description, opts = {}, &block)
8
+ option = Clamp::Option.new(switches, argument_type, description, opts)
9
+ declare_option(option, &block)
10
+ end
11
+
12
+ def has_options?
13
+ !declared_options.empty?
14
+ end
15
+
16
+ def declared_options
17
+ my_declared_options + inherited_declared_options
18
+ end
19
+
20
+ def recognised_options
21
+ declared_options + standard_options
22
+ end
23
+
24
+ def find_option(switch)
25
+ recognised_options.find { |o| o.handles?(switch) }
26
+ end
27
+
28
+ private
29
+
30
+ def my_declared_options
31
+ @my_declared_options ||= []
32
+ end
33
+
34
+ def declare_option(option, &block)
35
+ my_declared_options << option
36
+ declare_option_reader(option)
37
+ declare_option_writer(option, &block)
38
+ end
39
+
40
+ def inherited_declared_options
41
+ if superclass.respond_to?(:declared_options)
42
+ superclass.declared_options
43
+ else
44
+ []
45
+ end
46
+ end
47
+
48
+ HELP_OPTION = Clamp::Option.new("--help", :flag, "print help", :attribute_name => :help_requested)
49
+
50
+ def standard_options
51
+ [HELP_OPTION]
52
+ end
53
+
54
+ def declare_option_reader(option)
55
+ reader_name = option.attribute_name
56
+ reader_name += "?" if option.flag?
57
+ define_method(reader_name) do
58
+ value = instance_variable_get("@#{option.attribute_name}")
59
+ value = option.default_value if value.nil?
60
+ value
61
+ end
62
+ end
63
+
64
+ def declare_option_writer(option, &block)
65
+ define_method("#{option.attribute_name}=") do |value|
66
+ if block
67
+ value = instance_exec(value, &block)
68
+ end
69
+ instance_variable_set("@#{option.attribute_name}", value)
70
+ end
71
+ end
72
+
73
+ end
74
+
75
+ end
@@ -0,0 +1,35 @@
1
+ module Clamp
2
+
3
+ class Subcommand < Struct.new(:name, :description, :subcommand_class)
4
+
5
+ def help
6
+ [name, description]
7
+ end
8
+
9
+ end
10
+
11
+ module SubcommandSupport
12
+
13
+ def recognised_subcommands
14
+ @recognised_subcommands ||= []
15
+ end
16
+
17
+ def subcommand(name, description, subcommand_class = self, &block)
18
+ if block
19
+ # generate a anonymous sub-class
20
+ subcommand_class = Class.new(subcommand_class, &block)
21
+ end
22
+ recognised_subcommands << Subcommand.new(name, description, subcommand_class)
23
+ end
24
+
25
+ def has_subcommands?
26
+ !recognised_subcommands.empty?
27
+ end
28
+
29
+ def find_subcommand(name)
30
+ recognised_subcommands.find { |sc| sc.name == name }
31
+ end
32
+
33
+ end
34
+
35
+ end
data/lib/clamp/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Clamp
2
- VERSION = "0.0.1".freeze
2
+ VERSION = "0.0.7".freeze
3
3
  end
@@ -0,0 +1,93 @@
1
+ require 'spec_helper'
2
+ require 'stringio'
3
+
4
+ describe Clamp::Command do
5
+
6
+ include OutputCapture
7
+
8
+ def self.given_command(name, &block)
9
+ before do
10
+ @command = Class.new(Clamp::Command, &block).new(name)
11
+ end
12
+ end
13
+
14
+ describe "with subcommands" do
15
+
16
+ given_command "flipflop" do
17
+
18
+ subcommand "flip", "flip it" do
19
+ def execute
20
+ puts "FLIPPED"
21
+ end
22
+ end
23
+
24
+ subcommand "flop", "flop it" do
25
+ def execute
26
+ puts "FLOPPED"
27
+ end
28
+ end
29
+
30
+ end
31
+
32
+ it "delegates to sub-commands" do
33
+
34
+ @command.run(["flip"])
35
+ stdout.should =~ /FLIPPED/
36
+
37
+ @command.run(["flop"])
38
+ stdout.should =~ /FLOPPED/
39
+
40
+ end
41
+
42
+ describe "#help" do
43
+
44
+ it "lists subcommands" do
45
+ @help = @command.help
46
+ @help.should =~ /Subcommands:/
47
+ @help.should =~ /flip +flip it/
48
+ @help.should =~ /flop +flop it/
49
+ end
50
+
51
+ end
52
+
53
+ end
54
+
55
+ describe "each subcommand" do
56
+
57
+ before do
58
+
59
+ @command_class = Class.new(Clamp::Command) do
60
+
61
+ option "--direction", "DIR", "which way"
62
+
63
+ subcommand "walk", "step carefully in the appointed direction" do
64
+
65
+ def execute
66
+ if direction
67
+ puts "walking #{direction}"
68
+ else
69
+ puts "wandering #{context[:default_direction]} by default"
70
+ end
71
+ end
72
+
73
+ end
74
+
75
+ end
76
+
77
+ @command = @command_class.new("go", :default_direction => "south")
78
+
79
+ end
80
+
81
+ it "accepts parents options (specified after the subcommand)" do
82
+ @command.run(["walk", "--direction", "north"])
83
+ stdout.should =~ /walking north/
84
+ end
85
+
86
+ it "has access to command context" do
87
+ @command.run(["walk"])
88
+ stdout.should =~ /wandering south by default/
89
+ end
90
+
91
+ end
92
+
93
+ end
@@ -3,23 +3,7 @@ require 'stringio'
3
3
 
4
4
  describe Clamp::Command do
5
5
 
6
- before do
7
- $stdout = @out = StringIO.new
8
- $stderr = @err = StringIO.new
9
- end
10
-
11
- after do
12
- $stdout = STDOUT
13
- $stderr = STDERR
14
- end
15
-
16
- def stdout
17
- @out.string
18
- end
19
-
20
- def stderr
21
- @err.string
22
- end
6
+ include OutputCapture
23
7
 
24
8
  def self.given_command(name, &block)
25
9
  before do
@@ -81,34 +65,43 @@ describe Clamp::Command do
81
65
 
82
66
  describe ".option" do
83
67
 
84
- before do
85
- @command.class.option "--flavour", "FLAVOUR", "Flavour of the month"
86
- end
87
-
88
68
  it "declares option argument accessors" do
69
+ @command.class.option "--flavour", "FLAVOUR", "Flavour of the month"
89
70
  @command.flavour.should == nil
90
71
  @command.flavour = "chocolate"
91
72
  @command.flavour.should == "chocolate"
92
73
  end
93
74
 
94
- end
75
+ describe "with explicit :attribute_name" do
95
76
 
96
- describe ".option", "with explicit :attribute_name" do
77
+ before do
78
+ @command.class.option "--foo", "FOO", "A foo", :attribute_name => :bar
79
+ end
97
80
 
98
- before do
99
- @command.class.option "--foo", "FOO", "A foo", :attribute_name => :bar
100
- end
81
+ it "uses the specified attribute_name name to name accessors" do
82
+ @command.bar = "chocolate"
83
+ @command.bar.should == "chocolate"
84
+ end
85
+
86
+ it "does not attempt to create the default accessors" do
87
+ @command.should_not respond_to(:foo)
88
+ @command.should_not respond_to(:foo=)
89
+ end
101
90
 
102
- it "uses the specified attribute_name name to name accessors" do
103
- @command.bar = "chocolate"
104
- @command.bar.should == "chocolate"
105
91
  end
106
92
 
107
- it "does not attempt to create the default accessors" do
108
- @command.should_not respond_to(:foo)
109
- @command.should_not respond_to(:foo=)
93
+ describe "with :default value" do
94
+
95
+ given_command("cmd") do
96
+ option "--nodes", "N", "number of nodes", :default => 2
97
+ end
98
+
99
+ it "sets the specified default value" do
100
+ @command.nodes.should == 2
101
+ end
102
+
110
103
  end
111
-
104
+
112
105
  end
113
106
 
114
107
  describe "with options declared" do
@@ -329,6 +322,20 @@ describe Clamp::Command do
329
322
  stdout.should == @xyz.inspect
330
323
  end
331
324
 
325
+ describe "invoked with a context hash" do
326
+
327
+ it "makes the context available within the command" do
328
+ @command.class.class_eval do
329
+ def execute
330
+ print context[:foo]
331
+ end
332
+ end
333
+ @command.class.run("xyz", [], :foo => "bar")
334
+ stdout.should == "bar"
335
+ end
336
+
337
+ end
338
+
332
339
  describe "when there's a UsageError" do
333
340
 
334
341
  before do
@@ -352,7 +359,7 @@ describe Clamp::Command do
352
359
  end
353
360
 
354
361
  it "outputs help" do
355
- stderr.should include "Usage:"
362
+ stderr.should include "See: 'cmd --help'"
356
363
  end
357
364
 
358
365
  it "exits with a non-zero status" do
@@ -364,17 +371,8 @@ describe Clamp::Command do
364
371
 
365
372
  describe "when help is requested" do
366
373
 
367
- before do
368
-
369
- @command.class.class_eval do
370
- help_option "--help"
371
- end
372
-
373
- @command.class.run("cmd", ["--help"])
374
-
375
- end
376
-
377
374
  it "outputs help" do
375
+ @command.class.run("cmd", ["--help"])
378
376
  stdout.should include "Usage:"
379
377
  end
380
378
 
@@ -382,4 +380,23 @@ describe Clamp::Command do
382
380
 
383
381
  end
384
382
 
383
+ describe "subclass" do
384
+
385
+ before do
386
+ @parent_command_class = Class.new(Clamp::Command) do
387
+ option "--verbose", :flag, "be louder"
388
+ end
389
+ @derived_command_class = Class.new(@parent_command_class) do
390
+ option "--iterations", "N", "number of times to go around"
391
+ end
392
+ @command = @derived_command_class.new("cmd")
393
+ end
394
+
395
+ it "inherits options from it's superclass" do
396
+ @command.parse(["--verbose"])
397
+ @command.should be_verbose
398
+ end
399
+
400
+ end
401
+
385
402
  end
@@ -33,6 +33,19 @@ describe Clamp::Option do
33
33
 
34
34
  end
35
35
 
36
+ describe "#default_value" do
37
+
38
+ it "defaults to nil" do
39
+ @option.default_value.should == nil
40
+ end
41
+
42
+ it "can be overridden" do
43
+ @option = Clamp::Option.new("-n", "N", "iterations", :default => 1)
44
+ @option.default_value.should == 1
45
+ end
46
+
47
+ end
48
+
36
49
  describe "#help" do
37
50
 
38
51
  it "combines switch, argument_type and description" do
data/spec/spec_helper.rb CHANGED
@@ -6,3 +6,29 @@ Rspec.configure do |config|
6
6
  config.mock_with :rr
7
7
 
8
8
  end
9
+
10
+ module OutputCapture
11
+
12
+ def self.included(target)
13
+
14
+ target.before do
15
+ $stdout = @out = StringIO.new
16
+ $stderr = @err = StringIO.new
17
+ end
18
+
19
+ target.after do
20
+ $stdout = STDOUT
21
+ $stderr = STDERR
22
+ end
23
+
24
+ end
25
+
26
+ def stdout
27
+ @out.string
28
+ end
29
+
30
+ def stderr
31
+ @err.string
32
+ end
33
+
34
+ end
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: clamp
3
3
  version: !ruby/object:Gem::Version
4
- hash: 29
4
+ hash: 17
5
5
  prerelease: false
6
6
  segments:
7
7
  - 0
8
8
  - 0
9
- - 1
10
- version: 0.0.1
9
+ - 7
10
+ version: 0.0.7
11
11
  platform: ruby
12
12
  authors:
13
13
  - Mike Williams
@@ -15,7 +15,7 @@ autorequire:
15
15
  bindir: bin
16
16
  cert_chain: []
17
17
 
18
- date: 2010-11-02 00:00:00 +11:00
18
+ date: 2010-11-08 00:00:00 +11:00
19
19
  default_executable:
20
20
  dependencies: []
21
21
 
@@ -33,16 +33,21 @@ extra_rdoc_files: []
33
33
  files:
34
34
  - .gitignore
35
35
  - Gemfile
36
- - Gemfile.lock
37
36
  - README.markdown
38
37
  - Rakefile
39
38
  - clamp.gemspec
39
+ - examples/flipflop
40
+ - examples/icecream
40
41
  - examples/rename
42
+ - examples/speak
41
43
  - lib/clamp.rb
42
- - lib/clamp/argument.rb
43
44
  - lib/clamp/command.rb
45
+ - lib/clamp/help_support.rb
44
46
  - lib/clamp/option.rb
47
+ - lib/clamp/option_support.rb
48
+ - lib/clamp/subcommand_support.rb
45
49
  - lib/clamp/version.rb
50
+ - spec/clamp/command_group_spec.rb
46
51
  - spec/clamp/command_spec.rb
47
52
  - spec/clamp/option_spec.rb
48
53
  - spec/spec_helper.rb
@@ -81,6 +86,7 @@ signing_key:
81
86
  specification_version: 3
82
87
  summary: a minimal framework for command-line utilities
83
88
  test_files:
89
+ - spec/clamp/command_group_spec.rb
84
90
  - spec/clamp/command_spec.rb
85
91
  - spec/clamp/option_spec.rb
86
92
  - spec/spec_helper.rb
data/Gemfile.lock DELETED
@@ -1,30 +0,0 @@
1
- PATH
2
- remote: .
3
- specs:
4
- clamp (0.0.1)
5
-
6
- GEM
7
- remote: http://rubygems.org/
8
- specs:
9
- diff-lcs (1.1.2)
10
- rake (0.8.7)
11
- rr (1.0.0)
12
- rspec (2.0.1)
13
- rspec-core (~> 2.0.1)
14
- rspec-expectations (~> 2.0.1)
15
- rspec-mocks (~> 2.0.1)
16
- rspec-core (2.0.1)
17
- rspec-expectations (2.0.1)
18
- diff-lcs (>= 1.1.2)
19
- rspec-mocks (2.0.1)
20
- rspec-core (~> 2.0.1)
21
- rspec-expectations (~> 2.0.1)
22
-
23
- PLATFORMS
24
- ruby
25
-
26
- DEPENDENCIES
27
- clamp!
28
- rake
29
- rr (~> 1.0.0)
30
- rspec (~> 2.0.1)
@@ -1,6 +0,0 @@
1
- module Clamp
2
-
3
- class Argument < Struct.new(:name, :description)
4
- end
5
-
6
- end