clamp 0.0.9 → 0.1.0

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