clamp 0.0.9 → 0.1.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
@@ -26,42 +26,32 @@ Clamp models a command as a Ruby class; a subclass of `Clamp::Command`. They lo
26
26
  Integer(s)
27
27
  end
28
28
 
29
- parameter "WORDS ...", "the thing to say"
30
-
31
- def execute
29
+ parameter "WORDS ...", "the thing to say", :attribute_name => :words
32
30
 
33
- signal_usage_error "I have nothing to say" if arguments.empty?
34
- the_truth = arguments.join(" ")
31
+ def execute
32
+ the_truth = words.join(" ")
35
33
  the_truth.upcase! if loud?
36
-
37
34
  iterations.times do
38
35
  puts the_truth
39
36
  end
40
-
41
37
  end
42
38
 
43
39
  end
44
40
 
45
- Class-level methods (like `option` and `parameter`) 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:
41
+ Calling `run` on a command class creates an instance of it, then invokes it using command-line arguments (from ARGV, by default).
52
42
 
53
43
  SpeakCommand.run
54
-
55
- which takes arguments from `ARGV`, and includes some handy error-handling.
44
+
45
+ Class-level methods like `option` and `parameter` declare attributes (in a similar way to `attr_accessor`), and arrange for them to be populated automatically based on command-line arguments. They are aso used to generate `help` documentation.
56
46
 
57
47
  Declaring options
58
48
  -----------------
59
49
 
60
- Options are declared using the [`option`](../Clamp/Command.option) method. The three required arguments are:
50
+ Options are declared using the `option` method. The three required arguments are:
61
51
 
62
52
  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
53
+ 2. an option argument name
54
+ 3. a short description
65
55
 
66
56
  For example:
67
57
 
@@ -90,7 +80,7 @@ Some options are just boolean flags. Pass "`:flag`" as the second parameter to
90
80
 
91
81
  option "--verbose", :flag, "be chatty"
92
82
 
93
- For flag options, Clamp appends "`?`" to the generated reader method; ie. you get a method called "`verbose?`", rather than just "`verbose`".
83
+ For flag options, Clamp appends "`?`" to the generated reader method; ie. you get a method called "`#verbose?`", rather than just "`#verbose`".
94
84
 
95
85
  Negatable flags are easy to generate, too:
96
86
 
@@ -98,33 +88,72 @@ Negatable flags are easy to generate, too:
98
88
 
99
89
  Clamp will handle both "`--force`" and "`--no-force`" options, setting the value of "`#force?`" appropriately.
100
90
 
101
- ### Validation and conversion of option arguments
91
+ Declaring parameters
92
+ --------------------
93
+
94
+ Positional parameters can be declared using `parameter`, specifying
95
+
96
+ 1. the parameter name, and
97
+ 2. a short description
98
+
99
+ For example:
100
+
101
+ parameter "SRC", "source file"
102
+
103
+ Like options, parameters are implemented as attributes of the command, with the default attribute name derived from the parameter name (in this case, "`src`"). By convention, parameter names are specified in uppercase, to make them obvious in usage help.
104
+
105
+ ### Optional parameters
102
106
 
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.
107
+ Wrapping a parameter name in square brackets indicates that it's optional, e.g.
108
+
109
+ parameter "[TARGET_DIR]", "target directory"
110
+
111
+ ### Greedy parameters
112
+
113
+ Three dots at the end of a parameter name makes it "greedy" - it will consume all remaining command-line arguments. For example:
114
+
115
+ parameter "FILE ...", "input files"
116
+
117
+ The suffix "`_list`" is appended to the default attribute name for greedy parameters; in this case, an attribute called "`file_list`" would be generated.
118
+
119
+ Parsing and validation of options and parameters
120
+ ------------------------------------------------
121
+
122
+ When you `#run` a command, it will first attempt to `#parse` command-line arguments, and map them onto the declared options and parameters, before invoking your `#execute` method.
123
+
124
+ If parameters are declared, Clamp will verify that all required (ie. non-optional) parameters are present, and signal a error if they aren't. Otherwise, arguments that remain after option parsing will be made available via `#arguments`.
125
+
126
+ ### Validation block
127
+
128
+ Both `option` and `parameter` accept an optional block. If present, the block will be
129
+ called with the raw string option argument, and is expected to coerce it to
130
+ the correct type, e.g.
104
131
 
105
132
  option "--port", "PORT", "port to listen on" do |s|
106
133
  Integer(s)
107
134
  end
108
135
 
109
- If the block raises an ArgumentError, Clamp will catch it, and report that the option value was bad:
136
+ If the block raises an ArgumentError, Clamp will catch it, and report that the value was bad:
110
137
 
111
138
  !!!plain
112
139
  ERROR: option '--port': invalid value for Integer: "blah"
113
140
 
114
- Declaring parameters
115
- --------------------
141
+ ### Advanced option/parameter handling
116
142
 
117
- The `parameter` method is used to declare positional command parameters:
143
+ While Clamp provides an attribute-writer method for each declared option or parameter, you always have the option of overriding it to provide custom argument-handling logic, e.g.
118
144
 
119
- parameter "FILE ...", "source files"
120
- parameter "DIR", "target directory"
145
+ parameter "SERVER", "location of server"
146
+
147
+ def server=(server)
148
+ @server_address, @server_port = server.split(":")
149
+ end
121
150
 
122
- Use of `parameter` 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.
151
+ Declaring Subcommands
152
+ ---------------------
123
153
 
124
- Sub-commands
125
- ------------
154
+ Subcommand support helps you wrap a number of related commands into a single script (ala tools like "`git`"). Clamp will inspect the first command-line argument (after options are parsed), and delegate to the named subcommand.
126
155
 
127
- The `subcommand` method declares sub-commands:
156
+ Unsuprisingly, subcommands are declared using the `subcommand` method. e.g.
128
157
 
129
158
  class MainCommand < Clamp::Command
130
159
 
@@ -138,9 +167,7 @@ The `subcommand` method declares sub-commands:
138
167
 
139
168
  end
140
169
 
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:
170
+ Clamp generates an anonymous subclass of the current class, to represent the subcommand. Alternatively, you can provide an explicit subcommand class:
144
171
 
145
172
  class MainCommand < Clamp::Command
146
173
 
@@ -156,7 +183,11 @@ Alternatively, you can provide an explicit sub-command class, rather than a bloc
156
183
 
157
184
  end
158
185
 
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.
186
+ ### Subcommand options and parameters
187
+
188
+ Options are inheritable, so any options declared for a parent command are supported for it's subcommands. Parameters, on the other hand, are not inherited - each subcommand must declare it's own parameter list.
189
+
190
+ Note that, if a subcommand accepts options, they must be specified on the command-line _after_ the subcommand name.
160
191
 
161
192
  Getting help
162
193
  ------------
data/examples/flipflop CHANGED
@@ -1,5 +1,7 @@
1
1
  #! /usr/bin/env ruby
2
2
 
3
+ # An example of subcommands
4
+
3
5
  require "clamp"
4
6
 
5
7
  class FlipFlop < Clamp::Command
data/examples/speak CHANGED
@@ -1,5 +1,7 @@
1
1
  #! /usr/bin/env ruby
2
2
 
3
+ # A simple Clamp command, with options and parameters
4
+
3
5
  require "clamp"
4
6
 
5
7
  class SpeakCommand < Clamp::Command
@@ -9,12 +11,11 @@ class SpeakCommand < Clamp::Command
9
11
  Integer(s)
10
12
  end
11
13
 
12
- parameter "WORDS ...", "the thing to say"
14
+ parameter "WORDS ...", "the thing to say", :attribute_name => :words
13
15
 
14
16
  def execute
15
17
 
16
- signal_usage_error "I have nothing to say" if arguments.empty?
17
- the_truth = arguments.join(" ")
18
+ the_truth = words.join(" ")
18
19
  the_truth.upcase! if loud?
19
20
 
20
21
  iterations.times do
data/lib/clamp/command.rb CHANGED
@@ -1,32 +1,72 @@
1
- require 'clamp/command/declaration'
2
1
  require 'clamp/errors'
3
2
  require 'clamp/help'
3
+ require 'clamp/option/declaration'
4
4
  require 'clamp/option/parsing'
5
+ require 'clamp/parameter/declaration'
5
6
  require 'clamp/parameter/parsing'
7
+ require 'clamp/subcommand/declaration'
6
8
  require 'clamp/subcommand/execution'
7
9
 
8
10
  module Clamp
9
11
 
12
+ # {Command} models a shell command. Each command invocation is a new object.
13
+ # Command options and parameters are represented as attributes
14
+ # (see {Command::Declaration}).
15
+ #
16
+ # The main entry-point is {#run}, which uses {#parse} to populate attributes based
17
+ # on an array of command-line arguments, then calls {#execute} (which you provide)
18
+ # to make it go.
19
+ #
10
20
  class Command
11
21
 
12
- def initialize(name, context = {})
13
- @name = name
22
+ # Create a command execution.
23
+ #
24
+ # @param [String] invocation_path the path used to invoke the command
25
+ # @param [Hash] context additional data the command may need
26
+ #
27
+ def initialize(invocation_path, context = {})
28
+ @invocation_path = invocation_path
14
29
  @context = context
15
30
  end
16
31
 
17
- attr_reader :name
18
- attr_reader :arguments
19
-
20
- attr_accessor :context
21
- attr_accessor :parent_command
32
+ # @return [String] the path used to invoke this command
33
+ #
34
+ attr_reader :invocation_path
35
+
36
+ # @return [Array<String>] unconsumed command-line arguments
37
+ #
38
+ def arguments
39
+ @arguments
40
+ end
22
41
 
42
+ # Parse command-line arguments.
43
+ #
44
+ # @param [Array<String>] arguments command-line arguments
45
+ # @return [Array<String>] unconsumed arguments
46
+ #
23
47
  def parse(arguments)
24
48
  @arguments = arguments.dup
25
49
  parse_options
26
50
  parse_parameters
51
+ @arguments
52
+ end
53
+
54
+ # Run the command, with the specified arguments.
55
+ #
56
+ # This calls {#parse} to process the command-line arguments,
57
+ # then delegates to {#execute}.
58
+ #
59
+ # @param [Array<String>] arguments command-line arguments
60
+ #
61
+ def run(arguments)
62
+ parse(arguments)
63
+ execute
27
64
  end
28
65
 
29
- # default implementation
66
+ # Execute the command (assuming that all options/parameters have been set).
67
+ #
68
+ # This method is designed to be overridden in sub-classes.
69
+ #
30
70
  def execute
31
71
  if self.class.has_subcommands?
32
72
  execute_subcommand
@@ -35,19 +75,21 @@ module Clamp
35
75
  end
36
76
  end
37
77
 
38
- def run(arguments)
39
- parse(arguments)
40
- execute
41
- end
42
-
78
+ # @return [String] usage documentation for this command
79
+ #
43
80
  def help
44
- self.class.help(name)
81
+ self.class.help(invocation_path)
45
82
  end
46
83
 
47
- include Option::Parsing
48
- include Parameter::Parsing
49
- include Subcommand::Execution
84
+ include Clamp::Option::Parsing
85
+ include Clamp::Parameter::Parsing
86
+ include Clamp::Subcommand::Execution
87
+
88
+ protected
50
89
 
90
+ attr_accessor :context
91
+ attr_accessor :parent_command
92
+
51
93
  private
52
94
 
53
95
  def signal_usage_error(message)
@@ -62,16 +104,24 @@ module Clamp
62
104
 
63
105
  class << self
64
106
 
65
- include Command::Declaration
107
+ include Clamp::Option::Declaration
108
+ include Clamp::Parameter::Declaration
109
+ include Clamp::Subcommand::Declaration
66
110
  include Help
67
111
 
68
- def run(name = $0, args = ARGV, context = {})
112
+ # Create an instance of this command class, and run it.
113
+ #
114
+ # @param [String] invocation_path the path used to invoke the command
115
+ # @param [Array<String>] arguments command-line arguments
116
+ # @param [Hash] context additional data the command may need
117
+ #
118
+ def run(invocation_path = $0, arguments = ARGV, context = {})
69
119
  begin
70
- new(name, context).run(args)
120
+ new(invocation_path, context).run(arguments)
71
121
  rescue Clamp::UsageError => e
72
122
  $stderr.puts "ERROR: #{e.message}"
73
123
  $stderr.puts ""
74
- $stderr.puts "See: '#{name} --help'"
124
+ $stderr.puts "See: '#{invocation_path} --help'"
75
125
  exit(1)
76
126
  rescue Clamp::HelpWanted => e
77
127
  puts e.command.help
data/lib/clamp/help.rb CHANGED
@@ -20,20 +20,20 @@ module Clamp
20
20
  declared_usage_descriptions || [derived_usage_description]
21
21
  end
22
22
 
23
- def help(command_name)
23
+ def help(invocation_path)
24
24
  help = StringIO.new
25
25
  help.puts "Usage:"
26
26
  usage_descriptions.each_with_index do |usage, i|
27
- help.puts " #{command_name} #{usage}".rstrip
27
+ help.puts " #{invocation_path} #{usage}".rstrip
28
28
  end
29
29
  detail_format = " %-29s %s"
30
- unless parameters.empty?
30
+ if has_parameters?
31
31
  help.puts "\nParameters:"
32
32
  parameters.each do |parameter|
33
33
  help.puts detail_format % parameter.help
34
34
  end
35
35
  end
36
- unless recognised_subcommands.empty?
36
+ if has_subcommands?
37
37
  help.puts "\nSubcommands:"
38
38
  recognised_subcommands.each do |subcommand|
39
39
  help.puts detail_format % subcommand.help
@@ -6,7 +6,7 @@ module Clamp
6
6
 
7
7
  module Declaration
8
8
 
9
- include AttributeDeclaration
9
+ include Clamp::AttributeDeclaration
10
10
 
11
11
  def option(switches, type, description, opts = {}, &block)
12
12
  option = Clamp::Option.new(switches, type, description, opts)
@@ -18,20 +18,22 @@ module Clamp
18
18
  !declared_options.empty?
19
19
  end
20
20
 
21
+ def find_option(switch)
22
+ recognised_options.find { |o| o.handles?(switch) }
23
+ end
24
+
25
+ protected
26
+
21
27
  def declared_options
22
28
  my_declared_options + inherited_declared_options
23
29
  end
24
30
 
31
+ private
32
+
25
33
  def recognised_options
26
34
  declared_options + standard_options
27
35
  end
28
36
 
29
- def find_option(switch)
30
- recognised_options.find { |o| o.handles?(switch) }
31
- end
32
-
33
- private
34
-
35
37
  def my_declared_options
36
38
  @my_declared_options ||= []
37
39
  end
@@ -30,18 +30,20 @@ module Clamp
30
30
 
31
31
  private
32
32
 
33
+ NAME_PATTERN = "([A-Za-z0-9_-]+)"
34
+
33
35
  def infer_attribute_name_and_multiplicity
34
36
  case @name
35
- when /^\[(\S+)\]$/
37
+ when /^\[#{NAME_PATTERN}\]$/
36
38
  @attribute_name = $1
37
- when /^\[(\S+)\] ...$/
39
+ when /^\[#{NAME_PATTERN}\] ...$/
38
40
  @attribute_name = "#{$1}_list"
39
41
  @multivalued = true
40
- when /^(\S+) ...$/
42
+ when /^#{NAME_PATTERN} ...$/
41
43
  @attribute_name = "#{$1}_list"
42
44
  @multivalued = true
43
45
  @required = true
44
- when /^\S+$/
46
+ when /^#{NAME_PATTERN}$/
45
47
  @attribute_name = @name
46
48
  @required = true
47
49
  else
@@ -6,12 +6,16 @@ module Clamp
6
6
 
7
7
  module Declaration
8
8
 
9
- include AttributeDeclaration
9
+ include Clamp::AttributeDeclaration
10
10
 
11
11
  def parameters
12
12
  @parameters ||= []
13
13
  end
14
14
 
15
+ def has_parameters?
16
+ !parameters.empty?
17
+ end
18
+
15
19
  def parameter(name, description, options = {}, &block)
16
20
  parameter = Parameter.new(name, description, options)
17
21
  parameters << parameter
@@ -9,7 +9,7 @@ module Clamp
9
9
  signal_usage_error "no subcommand specified" if arguments.empty?
10
10
  subcommand_name = arguments.shift
11
11
  subcommand_class = find_subcommand_class(subcommand_name)
12
- subcommand = subcommand_class.new("#{name} #{subcommand_name}", context)
12
+ subcommand = subcommand_class.new("#{invocation_path} #{subcommand_name}", context)
13
13
  subcommand.parent_command = self
14
14
  subcommand.run(arguments)
15
15
  end
data/lib/clamp/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Clamp
2
- VERSION = "0.0.9".freeze
2
+ VERSION = "0.1.0".freeze
3
3
  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: 13
4
+ hash: 27
5
5
  prerelease: false
6
6
  segments:
7
7
  - 0
8
+ - 1
8
9
  - 0
9
- - 9
10
- version: 0.0.9
10
+ version: 0.1.0
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-11 00:00:00 +11:00
18
+ date: 2010-11-15 00:00:00 +11:00
19
19
  default_executable:
20
20
  dependencies: []
21
21
 
@@ -37,13 +37,10 @@ files:
37
37
  - Rakefile
38
38
  - clamp.gemspec
39
39
  - examples/flipflop
40
- - examples/icecream
41
- - examples/rename
42
40
  - examples/speak
43
41
  - lib/clamp.rb
44
42
  - lib/clamp/attribute_declaration.rb
45
43
  - lib/clamp/command.rb
46
- - lib/clamp/command/declaration.rb
47
44
  - lib/clamp/errors.rb
48
45
  - lib/clamp/help.rb
49
46
  - lib/clamp/option.rb
data/examples/icecream DELETED
@@ -1,16 +0,0 @@
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 DELETED
@@ -1,33 +0,0 @@
1
- #! /usr/bin/env ruby
2
-
3
- require "clamp"
4
-
5
- class Rename < Clamp::Command
6
-
7
- usage "[OPTIONS] TRANSFORM FILE ..."
8
-
9
- parameter "TRANSFORM", "a Ruby expression"
10
- parameter "FILE", "a file to rename"
11
-
12
- option ["-v", "--verbose"], :flag, "be verbose"
13
-
14
- option ["-n", "--times"], "TIMES", "repetitions" do |n|
15
- n = Integer(n)
16
- raise ArgumentError, "too big" if n > 9
17
- n
18
- end
19
-
20
- def initialize(name)
21
- super
22
- @times = 1
23
- end
24
-
25
- def execute
26
- @times.times do
27
- puts "Blah blah blah" if verbose?
28
- end
29
- end
30
-
31
- end
32
-
33
- Rename.run
@@ -1,15 +0,0 @@
1
- require 'clamp/option/declaration'
2
- require 'clamp/parameter/declaration'
3
- require 'clamp/subcommand/declaration'
4
-
5
- module Clamp
6
- class Command
7
-
8
- module Declaration
9
- include Option::Declaration
10
- include Parameter::Declaration
11
- include Subcommand::Declaration
12
- end
13
-
14
- end
15
- end