clamp 0.0.1 → 0.0.7

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/.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