jls-clamp 0.3.1

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