jls-clamp 0.3.1

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,8 @@
1
+ *.gem
2
+ .bundle
3
+ .rvmrc
4
+ .yardoc
5
+ doc
6
+ pkg/*
7
+ Gemfile.lock
8
+
data/.travis.yml ADDED
@@ -0,0 +1,5 @@
1
+ language: ruby
2
+ rvm:
3
+ - 1.8.7
4
+ - 1.9.2
5
+ - 1.9.3
data/Gemfile ADDED
@@ -0,0 +1,9 @@
1
+ source "http://rubygems.org"
2
+
3
+ gemspec
4
+
5
+ group :test do
6
+ gem "rake"
7
+ gem "rspec", "~> 2.6.0"
8
+ gem "rr", "~> 1.0.4"
9
+ end
data/README.markdown ADDED
@@ -0,0 +1,274 @@
1
+ Clamp
2
+ =====
3
+
4
+ "Clamp" is a minimal framework for command-line utilities.
5
+
6
+ It handles boring stuff like parsing the command-line, and generating help, so you can get on with making your command actually do stuff.
7
+
8
+ Not another one!
9
+ ----------------
10
+
11
+ Yeah, sorry. There are a bunch of existing command-line parsing libraries out there, and Clamp draws inspiration from a variety of sources, including [Thor], [optparse], and [Clip]. In the end, though, I wanted a slightly rounder wheel.
12
+
13
+ [optparse]: http://ruby-doc.org/stdlib/libdoc/optparse/rdoc/index.html
14
+ [Thor]: http://github.com/wycats/thor
15
+ [Clip]: http://clip.rubyforge.org/
16
+
17
+ Quick Start
18
+ -----------
19
+
20
+ Clamp models a command as a Ruby class; a subclass of `Clamp::Command`. They look something like this:
21
+
22
+ class SpeakCommand < Clamp::Command
23
+
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
+ parameter "WORDS ...", "the thing to say", :attribute_name => :words
30
+
31
+ def execute
32
+ the_truth = words.join(" ")
33
+ the_truth.upcase! if loud?
34
+ iterations.times do
35
+ puts the_truth
36
+ end
37
+ end
38
+
39
+ end
40
+
41
+ Calling `run` on a command class creates an instance of it, then invokes it using command-line arguments (from ARGV, by default).
42
+
43
+ SpeakCommand.run
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 also used to generate `help` documentation.
46
+
47
+ Declaring options
48
+ -----------------
49
+
50
+ Options are declared using the `option` method. The three required arguments are:
51
+
52
+ 1. the option switch (or switches),
53
+ 2. an option argument name
54
+ 3. a short description
55
+
56
+ For example:
57
+
58
+ option "--flavour", "FLAVOUR", "ice-cream flavour"
59
+
60
+ 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.
61
+
62
+ def execute
63
+ puts "You chose #{flavour}. Excellent choice!"
64
+ end
65
+
66
+ If you don't like the inferred attribute name, you can override it:
67
+
68
+ option "--type", "TYPE", "type of widget", :attribute_name => :widget_type
69
+ # to avoid clobbering Object#type
70
+
71
+ ### Short/long option switches
72
+
73
+ The first argument to `option` can be an array, rather than a single string, in which case all the switches are treated as aliases:
74
+
75
+ option ["-s", "--subject"], "SUBJECT", "email subject line"
76
+
77
+ ### Flag options
78
+
79
+ Some options are just boolean flags. Pass "`:flag`" as the second parameter to tell Clamp not to expect an option argument:
80
+
81
+ option "--verbose", :flag, "be chatty"
82
+
83
+ For flag options, Clamp appends "`?`" to the generated reader method; ie. you get a method called "`#verbose?`", rather than just "`#verbose`".
84
+
85
+ Negatable flags are easy to generate, too:
86
+
87
+ option "--[no-]force", :flag, "be forceful (or not)"
88
+
89
+ Clamp will handle both "`--force`" and "`--no-force`" options, setting the value of "`#force?`" appropriately.
90
+
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
106
+
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
+ Clamp will verify that all required (ie. non-optional) parameters are present, and signal a error if they aren't.
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.
131
+
132
+ option "--port", "PORT", "port to listen on" do |s|
133
+ Integer(s)
134
+ end
135
+
136
+ If the block raises an ArgumentError, Clamp will catch it, and report that the value was bad:
137
+
138
+ !!!plain
139
+ ERROR: option '--port': invalid value for Integer: "blah"
140
+
141
+ ### Advanced option/parameter handling
142
+
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.
144
+
145
+ parameter "SERVER", "location of server"
146
+
147
+ def server=(server)
148
+ @server_address, @server_port = server.split(":")
149
+ end
150
+
151
+ ### Default values
152
+
153
+ Default values can be specified for options:
154
+
155
+ option "--flavour", "FLAVOUR", "ice-cream flavour", :default => "chocolate"
156
+
157
+ and also for optional parameters
158
+
159
+ parameter "[HOST]", "server host", :default => "localhost"
160
+
161
+ For more advanced cases, you can also specify default values by defining a method called "`default_#{attribute_name}`":
162
+
163
+ option "--http-port", "PORT", "web-server port", :default => 9000
164
+
165
+ option "--admin-port", "PORT", "admin port"
166
+
167
+ def default_admin_port
168
+ http_port + 1
169
+ end
170
+
171
+ Declaring Subcommands
172
+ ---------------------
173
+
174
+ 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.
175
+
176
+ Unsuprisingly, subcommands are declared using the `subcommand` method. e.g.
177
+
178
+ class MainCommand < Clamp::Command
179
+
180
+ subcommand "init", "Initialize the repository" do
181
+
182
+ def execute
183
+ # ...
184
+ end
185
+
186
+ end
187
+
188
+ end
189
+
190
+ Clamp generates an anonymous subclass of the current class, to represent the subcommand. Alternatively, you can provide an explicit subcommand class:
191
+
192
+ class MainCommand < Clamp::Command
193
+
194
+ subcommand "init", "Initialize the repository", InitCommand
195
+
196
+ end
197
+
198
+ class InitCommand < Clamp::Command
199
+
200
+ def execute
201
+ # ...
202
+ end
203
+
204
+ end
205
+
206
+ ### Default subcommand
207
+
208
+ You can set a default subcommand, at the class level, as follows:
209
+
210
+ class MainCommand < Clamp::Command
211
+
212
+ self.default_subcommand = "status"
213
+
214
+ subcommand "status", "Display current status" do
215
+
216
+ def execute
217
+ # ...
218
+ end
219
+
220
+ end
221
+
222
+ end
223
+
224
+ Then, if when no SUBCOMMAND argument is provided, the default will be selected.
225
+
226
+ ### Subcommand options and parameters
227
+
228
+ Options are inheritable, so any options declared for a command are supported for it's sub-classes (e.g. those created using `subcommand`). Parameters, on the other hand, are not inherited - each subcommand must declare it's own parameter list.
229
+
230
+ Note that, if a subcommand accepts options, they must be specified on the command-line _after_ the subcommand name.
231
+
232
+ Getting help
233
+ ------------
234
+
235
+ 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 `parameter`.
236
+
237
+ $ speak --help
238
+ Usage:
239
+ speak [OPTIONS] WORDS ...
240
+
241
+ Arguments:
242
+ WORDS ... the thing to say
243
+
244
+ Options:
245
+ --loud say it loud
246
+ -n, --iterations N say it N times (default: 1)
247
+ -h, --help print help
248
+
249
+ License
250
+ -------
251
+
252
+ Copyright (C) 2011 [Mike Williams](mailto:mdub@dogbiscuit.org)
253
+
254
+ Permission is hereby granted, free of charge, to any person obtaining a copy
255
+ of this software and associated documentation files (the "Software"), to
256
+ deal in the Software without restriction, including without limitation the
257
+ rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
258
+ sell copies of the Software, and to permit persons to whom the Software is
259
+ furnished to do so, subject to the following conditions:
260
+
261
+ The above copyright notice and this permission notice shall be included in
262
+ all copies or substantial portions of the Software.
263
+
264
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
265
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
266
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
267
+ THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
268
+ IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
269
+ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
270
+
271
+ Contributing to Clamp
272
+ ---------------------
273
+
274
+ Source-code for Clamp is [on Github](https://github.com/mdub/clamp).
data/Rakefile ADDED
@@ -0,0 +1,12 @@
1
+ require 'bundler'
2
+
3
+ Bundler::GemHelper.install_tasks
4
+
5
+ require "rspec/core/rake_task"
6
+
7
+ task "default" => "spec"
8
+
9
+ RSpec::Core::RakeTask.new do |t|
10
+ t.pattern = 'spec/**/*_spec.rb'
11
+ t.rspec_opts = ["--colour", "--format", "nested"]
12
+ end
data/clamp.gemspec ADDED
@@ -0,0 +1,24 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+ require "clamp/version"
4
+
5
+ Gem::Specification.new do |s|
6
+
7
+ s.name = "jls-clamp"
8
+ s.version = Clamp::VERSION.dup
9
+ s.platform = Gem::Platform::RUBY
10
+ s.authors = ["Mike Williams"]
11
+ s.email = "mdub@dogbiscuit.org"
12
+ s.homepage = "http://github.com/mdub/clamp"
13
+
14
+ s.summary = %q{a minimal framework for command-line utilities}
15
+ s.description = <<EOF
16
+ Clamp provides an object-model for command-line utilities.
17
+ It handles parsing of command-line options, and generation of usage help.
18
+ EOF
19
+
20
+ s.files = `git ls-files`.split("\n")
21
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
22
+ s.require_paths = ["lib"]
23
+
24
+ end
data/examples/flipflop ADDED
@@ -0,0 +1,31 @@
1
+ #! /usr/bin/env ruby
2
+
3
+ # An example of subcommands
4
+
5
+ require "clamp"
6
+ require "clamp/version"
7
+
8
+ class FlipFlop < Clamp::Command
9
+
10
+ option ["--version", "-v"], :flag, "Show version" do
11
+ puts "Powered by Clamp-#{Clamp::VERSION}"
12
+ exit(0)
13
+ end
14
+
15
+ self.default_subcommand = "flip"
16
+
17
+ subcommand "flip", "flip it" do
18
+ def execute
19
+ puts "FLIPPED"
20
+ end
21
+ end
22
+
23
+ subcommand "flop", "flop it" do
24
+ def execute
25
+ puts "FLOPPED"
26
+ end
27
+ end
28
+
29
+ end
30
+
31
+ FlipFlop.run
data/examples/fubar ADDED
@@ -0,0 +1,23 @@
1
+ #! /usr/bin/env ruby
2
+
3
+ # An example of subcommands
4
+
5
+ require "clamp"
6
+
7
+ class Fubar < Clamp::Command
8
+
9
+ subcommand "foo", "Foo!" do
10
+
11
+ subcommand "bar", "Baaaa!" do
12
+
13
+ def execute
14
+ puts "FUBAR"
15
+ end
16
+
17
+ end
18
+
19
+ end
20
+
21
+ end
22
+
23
+ Fubar.run
data/examples/gitdown ADDED
@@ -0,0 +1,61 @@
1
+ #! /usr/bin/env ruby
2
+
3
+ # Demonstrate how subcommands can be declared as classes
4
+
5
+ require "clamp"
6
+
7
+ module GitDown
8
+
9
+ class AbstractCommand < Clamp::Command
10
+
11
+ option ["-v", "--verbose"], :flag, "be verbose"
12
+
13
+ option "--version", :flag, "show version" do
14
+ puts "GitDown-0.0.0a"
15
+ exit(0)
16
+ end
17
+
18
+ end
19
+
20
+ class CloneCommand < AbstractCommand
21
+
22
+ parameter "REPOSITORY", "repository to clone"
23
+ parameter "[DIR]", "working directory", :default => "."
24
+
25
+ def execute
26
+ raise NotImplementedError
27
+ end
28
+
29
+ end
30
+
31
+ class PullCommand < AbstractCommand
32
+
33
+ option "--[no-]commit", :flag, "Perform the merge and commit the result."
34
+
35
+ def execute
36
+ raise NotImplementedError
37
+ end
38
+
39
+ end
40
+
41
+ class StatusCommand < AbstractCommand
42
+
43
+ option ["-s", "--short"], :flag, "Give the output in the short-format."
44
+
45
+ def execute
46
+ raise NotImplementedError
47
+ end
48
+
49
+ end
50
+
51
+ class MainCommand < AbstractCommand
52
+
53
+ subcommand "clone", "Clone a remote repository.", CloneCommand
54
+ subcommand "pull", "Fetch and merge updates.", PullCommand
55
+ subcommand "status", "Display status of local repository.", StatusCommand
56
+
57
+ end
58
+
59
+ end
60
+
61
+ GitDown::MainCommand.run
data/examples/speak ADDED
@@ -0,0 +1,33 @@
1
+ #! /usr/bin/env ruby
2
+
3
+ # A simple Clamp command, with options and parameters
4
+
5
+ require "clamp"
6
+
7
+ class SpeakCommand < Clamp::Command
8
+
9
+ self.description = %{
10
+ Say something.
11
+ }
12
+
13
+ option "--loud", :flag, "say it loud"
14
+ option ["-n", "--iterations"], "N", "say it N times", :default => 1 do |s|
15
+ Integer(s)
16
+ end
17
+
18
+ parameter "WORDS ...", "the thing to say", :attribute_name => :words
19
+
20
+ def execute
21
+
22
+ the_truth = words.join(" ")
23
+ the_truth.upcase! if loud?
24
+
25
+ iterations.times do
26
+ puts the_truth
27
+ end
28
+
29
+ end
30
+
31
+ end
32
+
33
+ SpeakCommand.run
@@ -0,0 +1,40 @@
1
+ module Clamp
2
+
3
+ class Attribute
4
+
5
+ attr_reader :description, :attribute_name, :default_value, :env_var
6
+
7
+ def help_rhs
8
+ rhs = description
9
+ if defined?(@default_value)
10
+ rhs += " (default: #{@default_value.inspect})"
11
+ end
12
+ if defined?(@env_var)
13
+ rhs += " (env: #{@env_var.inspect})"
14
+ end
15
+ rhs
16
+ end
17
+
18
+ def help
19
+ [help_lhs, help_rhs]
20
+ end
21
+
22
+ def ivar_name
23
+ "@#{attribute_name}"
24
+ end
25
+
26
+ def read_method
27
+ attribute_name
28
+ end
29
+
30
+ def default_method
31
+ "default_#{read_method}"
32
+ end
33
+
34
+ def write_method
35
+ "#{attribute_name}="
36
+ end
37
+
38
+ end
39
+
40
+ end
@@ -0,0 +1,40 @@
1
+ module Clamp
2
+
3
+ module AttributeDeclaration
4
+
5
+ protected
6
+
7
+ def define_accessors_for(attribute, &block)
8
+ define_reader_for(attribute)
9
+ define_default_for(attribute)
10
+ define_writer_for(attribute, &block)
11
+ end
12
+
13
+ def define_reader_for(attribute)
14
+ define_method(attribute.read_method) do
15
+ if instance_variable_defined?(attribute.ivar_name)
16
+ instance_variable_get(attribute.ivar_name)
17
+ else
18
+ send(attribute.default_method)
19
+ end
20
+ end
21
+ end
22
+
23
+ def define_default_for(attribute)
24
+ define_method(attribute.default_method) do
25
+ attribute.default_value
26
+ end
27
+ end
28
+
29
+ def define_writer_for(attribute, &block)
30
+ define_method(attribute.write_method) do |value|
31
+ if block
32
+ value = instance_exec(value, &block)
33
+ end
34
+ instance_variable_set(attribute.ivar_name, value)
35
+ end
36
+ end
37
+
38
+ end
39
+
40
+ end