clamp 0.5.1 → 0.6.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.
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