clamp 0.5.1 → 0.6.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,15 +1,15 @@
1
1
  ---
2
2
  !binary "U0hBMQ==":
3
3
  metadata.gz: !binary |-
4
- NDFlNTQ5ZTA5ODJmZjI4NjU3OTE0YzEwYzJjMzcwNTAzNzlhMGJjMA==
4
+ YzQ1YTM1YTRmM2Q2ZDRiMDEzY2M1YzBmM2Y5YjM4ZDlhZTYzNWFmZA==
5
5
  data.tar.gz: !binary |-
6
- MjhlMDdmNGYzY2E2ZDVmYTQxMzA2OTcwYmY1NDBmZjM1ZGJlMTczNA==
6
+ MDMzN2JhYTAxYjYwNmZhZjhlMzEwZDJmMTM0MTBhOWVhZjFkMjIxYQ==
7
7
  !binary "U0hBNTEy":
8
8
  metadata.gz: !binary |-
9
- YjNlMjQ0OTdiMzZlYzYzOGJiNGI3YmI0NGIwODA4NmRhYTVlMWMxMTE0NzVl
10
- NDRiYjIxN2JhNjMzMDliZDdjODgxMmJmOTU0MWY5NTUwYTg2YjY3MzBkZTU0
11
- MGExZmI3NzRkMWM1NWEyY2ZlODIxOTFkZDYyMGMxYjFhYTJkN2E=
9
+ NzA4ZmI2ZmNjMmE4YzZlZDY4ZjUwZTVlMDIyYTJjZmQ5NDkxNDZkMjI0NDhi
10
+ M2E3NmUwZDE3NTUxNWViM2FmMDc5ZDc3NzRiNjQ2YjY1NjlkMzgwMjM0NTgy
11
+ NWM0ODJmODdkYTY2MTc3ZDBiZDdhZTU5N2Q1OTRjYTcyYzNlYWM=
12
12
  data.tar.gz: !binary |-
13
- ZDc0NzQ1YTYyNzIzZTY2ODZlYzk4ZjVmYmE1OGU5OGYxODI2MzMzMDk5YzEx
14
- ZTBkMzM3ZjNhZGNkYTNlODhjZWFhMjI1ZTA4NmRlMGJhNmVkNmFjNGIxZDc4
15
- ZmY4ZGJhYWZhMWJlNzlkZWM1YzBjMGUzZGI2YWUxNzk3MmQyMzk=
13
+ ZDU4Yzg2ODViNjBlOWRmY2Y3OTZjNGEyYzZhMmFjZDZiYjc4MWI5ZjY5NTZl
14
+ OWZjN2I4YTEwYjE0ZTZiMTFkMmRmYjk3OTE3ODMyNzhjYWY4MDc1NjZiYTE4
15
+ OGNkZmYxODFiYjlhMzM5ZThiOTQ0M2U0NDllZDcyNWQyMjA0ZDk=
@@ -0,0 +1,9 @@
1
+ Autotest.add_hook :initialize do |at|
2
+
3
+ at.add_exception ".git"
4
+
5
+ at.add_mapping(%r{^lib/(.*)\.rb$}, :prepend) do |_, match|
6
+ ["spec/unit/#{match[1]}_spec.rb"] + Dir['spec/clamp/command*_spec.rb']
7
+ end
8
+
9
+ end
@@ -3,3 +3,4 @@ rvm:
3
3
  - 1.8.7
4
4
  - 1.9.2
5
5
  - 1.9.3
6
+ - 2.0.0
@@ -0,0 +1,10 @@
1
+ # Changelog
2
+
3
+ ## 0.6.0 (2013-04-??)
4
+
5
+ * Introduce "banner" to describe a command (replacing "self.description=").
6
+ * Allow parameters to be specified before a subcommand.
7
+ * Add support for :multivalued options.
8
+ * Multi valued options and parameters get an "#append_to_foo_list" method, rather than
9
+ "#foo_list=".
10
+
@@ -1,4 +1,4 @@
1
- Clamp
1
+ Clamp [![Build Status](https://secure.travis-ci.org/mdub/clamp.png?branch=master)](http://travis-ci.org/mdub/clamp)
2
2
  =====
3
3
 
4
4
  "Clamp" is a minimal framework for command-line utilities.
@@ -19,6 +19,8 @@ Quick Start
19
19
 
20
20
  Clamp models a command as a Ruby class; a subclass of `Clamp::Command`. They look something like this:
21
21
 
22
+ require 'clamp'
23
+
22
24
  class SpeakCommand < Clamp::Command
23
25
 
24
26
  option "--loud", :flag, "say it loud"
@@ -38,12 +40,16 @@ Clamp models a command as a Ruby class; a subclass of `Clamp::Command`. They lo
38
40
 
39
41
  end
40
42
 
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
43
  SpeakCommand.run
44
44
 
45
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
46
 
47
+ The call to `run` creates an instance of the command class, then invokes it with command-line arguments (from `ARGV`).
48
+
49
+ There are more examples demonstrating various features of Clamp [on Github][examples].
50
+
51
+ [examples]: https://github.com/mdub/clamp/tree/master/examples
52
+
47
53
  Declaring options
48
54
  -----------------
49
55
 
@@ -243,7 +249,7 @@ Then, if when no SUBCOMMAND argument is provided, the default will be selected.
243
249
 
244
250
  ### Subcommand options and parameters
245
251
 
246
- 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.
252
+ Options are inheritable, so any options declared for a command are supported by it's sub-classes (e.g. those created using the block form of `subcommand`). Parameters, on the other hand, are not inherited - each subcommand must declare it's own parameter list.
247
253
 
248
254
  Note that, if a subcommand accepts options, they must be specified on the command-line _after_ the subcommand name.
249
255
 
@@ -4,7 +4,7 @@
4
4
 
5
5
  require "clamp"
6
6
 
7
- class AdminCommand < Clamp::Command
7
+ Clamp do
8
8
 
9
9
  option "--timeout", "SECONDS", "connection timeout", :default => 5, :environment_variable => "MYAPP_TIMEOUT" do |x|
10
10
  Integer(x)
@@ -18,5 +18,3 @@ class AdminCommand < Clamp::Command
18
18
  end
19
19
 
20
20
  end
21
-
22
- AdminCommand.run
@@ -5,7 +5,7 @@
5
5
  require "clamp"
6
6
  require "clamp/version"
7
7
 
8
- class FlipFlop < Clamp::Command
8
+ Clamp do
9
9
 
10
10
  option ["--version", "-v"], :flag, "Show version" do
11
11
  puts "Powered by Clamp-#{Clamp::VERSION}"
@@ -27,5 +27,3 @@ class FlipFlop < Clamp::Command
27
27
  end
28
28
 
29
29
  end
30
-
31
- FlipFlop.run
@@ -4,7 +4,7 @@
4
4
 
5
5
  require "clamp"
6
6
 
7
- class Fubar < Clamp::Command
7
+ Clamp do
8
8
 
9
9
  subcommand "foo", "Foo!" do
10
10
 
@@ -19,5 +19,3 @@ class Fubar < Clamp::Command
19
19
  end
20
20
 
21
21
  end
22
-
23
- Fubar.run
@@ -0,0 +1,17 @@
1
+ #! /usr/bin/env ruby
2
+
3
+ # An example of multi-valued options
4
+
5
+ require "clamp"
6
+
7
+ Clamp do
8
+
9
+ option ["-f", "--flavour"], "FLAVOUR", "flavour",
10
+ :multivalued => true, :default => ['chocolate'],
11
+ :attribute_name => :flavours
12
+
13
+ def execute
14
+ puts "one #{flavours.join(' and ')} ice-cream"
15
+ end
16
+
17
+ end
@@ -4,9 +4,9 @@
4
4
 
5
5
  require "clamp"
6
6
 
7
- class SpeakCommand < Clamp::Command
7
+ Clamp do
8
8
 
9
- self.description = %{
9
+ banner %{
10
10
  Say something.
11
11
  }
12
12
 
@@ -29,5 +29,3 @@ class SpeakCommand < Clamp::Command
29
29
  end
30
30
 
31
31
  end
32
-
33
- SpeakCommand.run
@@ -1,3 +1,7 @@
1
1
  require 'clamp/version'
2
2
 
3
3
  require 'clamp/command'
4
+
5
+ def Clamp(&block)
6
+ Class.new(Clamp::Command, &block).run
7
+ end
@@ -0,0 +1,49 @@
1
+ module Clamp
2
+ module Attribute
3
+
4
+ module Declaration
5
+
6
+ protected
7
+
8
+ def define_accessors_for(attribute, &block)
9
+ define_reader_for(attribute)
10
+ define_default_for(attribute)
11
+ define_writer_for(attribute, &block)
12
+ end
13
+
14
+ def define_reader_for(attribute)
15
+ define_method(attribute.read_method) do
16
+ if instance_variable_defined?(attribute.ivar_name)
17
+ instance_variable_get(attribute.ivar_name)
18
+ else
19
+ send(attribute.default_method)
20
+ end
21
+ end
22
+ end
23
+
24
+ def define_default_for(attribute)
25
+ define_method(attribute.default_method) do
26
+ attribute.default_value
27
+ end
28
+ end
29
+
30
+ def define_writer_for(attribute, &block)
31
+ define_method(attribute.write_method) do |value|
32
+ if block
33
+ value = instance_exec(value, &block)
34
+ end
35
+ if attribute.multivalued?
36
+ unless instance_variable_defined?(attribute.ivar_name)
37
+ instance_variable_set(attribute.ivar_name, [])
38
+ end
39
+ instance_variable_get(attribute.ivar_name) << value
40
+ else
41
+ instance_variable_set(attribute.ivar_name, value)
42
+ end
43
+ end
44
+ end
45
+
46
+ end
47
+
48
+ end
49
+ end
@@ -0,0 +1,82 @@
1
+ module Clamp
2
+ module Attribute
3
+
4
+ class Definition
5
+
6
+ def initialize(options)
7
+ if options.has_key?(:attribute_name)
8
+ @attribute_name = options[:attribute_name].to_s
9
+ end
10
+ if options.has_key?(:default)
11
+ @default_value = options[:default]
12
+ end
13
+ if options.has_key?(:environment_variable)
14
+ @environment_variable = options[:environment_variable]
15
+ end
16
+ end
17
+
18
+ attr_reader :description, :environment_variable
19
+
20
+ def help_rhs
21
+ description + default_description
22
+ end
23
+
24
+ def help
25
+ [help_lhs, help_rhs]
26
+ end
27
+
28
+ def ivar_name
29
+ "@#{attribute_name}"
30
+ end
31
+
32
+ def read_method
33
+ attribute_name
34
+ end
35
+
36
+ def default_method
37
+ "default_#{read_method}"
38
+ end
39
+
40
+ def write_method
41
+ if multivalued?
42
+ "append_to_#{attribute_name}"
43
+ else
44
+ "#{attribute_name}="
45
+ end
46
+ end
47
+
48
+ def multivalued?
49
+ @multivalued
50
+ end
51
+
52
+ def required?
53
+ @required
54
+ end
55
+
56
+ def attribute_name
57
+ @attribute_name ||= infer_attribute_name
58
+ end
59
+
60
+ def default_value
61
+ if defined?(@default_value)
62
+ @default_value
63
+ elsif multivalued?
64
+ []
65
+ end
66
+ end
67
+
68
+ private
69
+
70
+ def default_description
71
+ default_sources = [
72
+ ("$#{@environment_variable}" if defined?(@environment_variable)),
73
+ (@default_value.inspect if defined?(@default_value))
74
+ ].compact
75
+ return "" if default_sources.empty?
76
+ " (default: " + default_sources.join(", or ") + ")"
77
+ end
78
+
79
+ end
80
+
81
+ end
82
+ end
@@ -24,9 +24,12 @@ module Clamp
24
24
  # @param [String] invocation_path the path used to invoke the command
25
25
  # @param [Hash] context additional data the command may need
26
26
  #
27
- def initialize(invocation_path, context = {})
27
+ def initialize(invocation_path, context = {}, parent_attribute_values = {})
28
28
  @invocation_path = invocation_path
29
29
  @context = context
30
+ parent_attribute_values.each do |attribute, value|
31
+ instance_variable_set(attribute.ivar_name, value)
32
+ end
30
33
  end
31
34
 
32
35
  # @return [String] the path used to invoke this command
@@ -1,6 +1,9 @@
1
1
  module Clamp
2
-
3
- class Error < StandardError
2
+
3
+ class DeclarationError < StandardError
4
+ end
5
+
6
+ class RuntimeError < StandardError
4
7
 
5
8
  def initialize(message, command)
6
9
  super(message)
@@ -12,10 +15,10 @@ module Clamp
12
15
  end
13
16
 
14
17
  # raise to signal incorrect command usage
15
- class UsageError < Error; end
18
+ class UsageError < RuntimeError; end
16
19
 
17
20
  # raise to request usage help
18
- class HelpWanted < Error
21
+ class HelpWanted < RuntimeError
19
22
 
20
23
  def initialize(command)
21
24
  super("I need help", command)
@@ -20,15 +20,15 @@ module Clamp
20
20
  @description.strip!
21
21
  end
22
22
 
23
+ def banner(description)
24
+ self.description = description
25
+ end
26
+
23
27
  attr_reader :description
24
28
 
25
29
  def derived_usage_description
26
30
  parts = ["[OPTIONS]"]
27
31
  parts += parameters.map { |a| a.name }
28
- if has_subcommands?
29
- parts << "SUBCOMMAND"
30
- parts << "[ARGS] ..."
31
- end
32
32
  parts.join(" ")
33
33
  end
34
34
 
@@ -1,17 +1,19 @@
1
- require 'clamp/attribute_declaration'
2
- require 'clamp/option'
1
+ require 'clamp/attribute/declaration'
2
+ require 'clamp/option/definition'
3
3
 
4
4
  module Clamp
5
- class Option
5
+ module Option
6
6
 
7
7
  module Declaration
8
8
 
9
- include Clamp::AttributeDeclaration
9
+ include Clamp::Attribute::Declaration
10
10
 
11
11
  def option(switches, type, description, opts = {}, &block)
12
- option = Clamp::Option.new(switches, type, description, opts)
13
- declared_options << option
14
- define_accessors_for(option, &block)
12
+ Option::Definition.new(switches, type, description, opts).tap do |option|
13
+ declared_options << option
14
+ block ||= option.default_conversion_block
15
+ define_accessors_for(option, &block)
16
+ end
15
17
  end
16
18
 
17
19
  def find_option(switch)
@@ -0,0 +1,94 @@
1
+ require 'clamp/attribute/definition'
2
+ require 'clamp/truthy'
3
+
4
+ module Clamp
5
+ module Option
6
+
7
+ class Definition < Attribute::Definition
8
+
9
+ def initialize(switches, type, description, options = {})
10
+ @switches = Array(switches)
11
+ @type = type
12
+ @description = description
13
+ super(options)
14
+ @multivalued = options[:multivalued]
15
+ if options.has_key?(:required)
16
+ @required = options[:required]
17
+ # Do some light validation for conflicting settings.
18
+ if options.has_key?(:default)
19
+ raise ArgumentError, "Specifying a :default value also :required doesn't make sense"
20
+ end
21
+ if type == :flag
22
+ raise ArgumentError, "A required flag (boolean) doesn't make sense."
23
+ end
24
+ end
25
+ end
26
+
27
+ attr_reader :switches, :type
28
+
29
+ def long_switch
30
+ switches.find { |switch| switch =~ /^--/ }
31
+ end
32
+
33
+ def handles?(switch)
34
+ recognised_switches.member?(switch)
35
+ end
36
+
37
+ def flag?
38
+ @type == :flag
39
+ end
40
+
41
+ def flag_value(switch)
42
+ !(switch =~ /^--no-(.*)/ && switches.member?("--\[no-\]#{$1}"))
43
+ end
44
+
45
+ def read_method
46
+ if flag?
47
+ super + "?"
48
+ else
49
+ super
50
+ end
51
+ end
52
+
53
+ def extract_value(switch, arguments)
54
+ if flag?
55
+ flag_value(switch)
56
+ else
57
+ arguments.shift
58
+ end
59
+ end
60
+
61
+ def default_conversion_block
62
+ if flag?
63
+ Clamp.method(:truthy?)
64
+ end
65
+ end
66
+
67
+ def help_lhs
68
+ lhs = switches.join(", ")
69
+ lhs += " " + type unless flag?
70
+ lhs
71
+ end
72
+
73
+ private
74
+
75
+ def recognised_switches
76
+ switches.map do |switch|
77
+ if switch =~ /^--\[no-\](.*)/
78
+ ["--#{$1}", "--no-#{$1}"]
79
+ else
80
+ switch
81
+ end
82
+ end.flatten
83
+ end
84
+
85
+ def infer_attribute_name
86
+ inferred_name = long_switch.sub(/^--(\[no-\])?/, '').tr('-', '_')
87
+ inferred_name += "_list" if multivalued?
88
+ inferred_name
89
+ end
90
+
91
+ end
92
+
93
+ end
94
+ end