climate 0.6.0 → 0.7.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.
data/README.md CHANGED
@@ -62,3 +62,68 @@ There is a working example, `example.rb` that you can test out with
62
62
  or
63
63
 
64
64
  ruby -rrubygems -rclimate example.rb --help
65
+
66
+ # rack-middleware like filtering
67
+
68
+ Quite often in commands you might want some shared logic to do with setting up
69
+ or tearing down an environment for your child commands. For instance, you might
70
+ want to setup an application environment, start a database transaction, or check
71
+ some arguments are semantically correct in a way that is common based on a parent
72
+ command, i.e:
73
+
74
+ class Parent < Climate::Command('parent')
75
+ def run(chain)
76
+ setup_logger
77
+ begin
78
+ chain.run
79
+ rescue => e
80
+ logger.error("oh noes!")
81
+ raise
82
+ end
83
+ end
84
+ end
85
+
86
+ class Child < Climate::Command('child')
87
+ subcommand_of Parent
88
+
89
+ def run
90
+ if all_ok?
91
+ do_the_thing
92
+ else
93
+ raise 'it went badly'
94
+ end
95
+ end
96
+ end
97
+
98
+ The `run` method on non-leaf commands can also omit the chain argument, in which
99
+ case you are not responsible for calling the next link in the chain, but you can
100
+ still do some setup.
101
+
102
+ # Accessing your parent command at execution time
103
+
104
+ In the case where you have some parent command that gets all the top level
105
+ options for your application, (i.e. config location, log mode), you may need to
106
+ access this from your child commands. You can do this with the `ancestor`
107
+ instance method, or by proxying the method to your child class with
108
+ `expose_ancestor_method`. For example:
109
+
110
+ class Parent < Climate::Command('parent')
111
+ def useful_stuff ; end
112
+ def handy_service ; end
113
+ end
114
+
115
+ class Child < Climate::Command('child')
116
+ subcommand_of Parent
117
+
118
+ expose_ancestor_method Parent, :handy_service
119
+
120
+ def run
121
+ # finds the parent command instance so you can interrogate it
122
+ ancestor(Parent).useful_stuff
123
+
124
+ # handy_service was defined as an instance method using the
125
+ # #expose_ancestor_method above, but ultimately it is syntactic sugar
126
+ # for the above
127
+ handy_service.send_the_things
128
+ end
129
+ end
@@ -24,14 +24,52 @@ module Climate
24
24
  #
25
25
  class Command
26
26
 
27
+ # Chain is a helper class for allowing parent commands to participate in
28
+ # command execution
29
+ class Chain
30
+
31
+ def initialize(commands)
32
+ @commands = commands
33
+ end
34
+
35
+ def empty?
36
+ @commands.empty?
37
+ end
38
+
39
+ def run
40
+ raise "Can't call run on an empty command chain" if empty?
41
+
42
+ next_command = @commands.shift
43
+
44
+ begin
45
+ if next_command.method(:run).arity == 1
46
+ next_command.run(self)
47
+ else
48
+ result = next_command.run
49
+ # we ignore the result from a run method with arity == 0 unless
50
+ # it is the last command in the chain. Really this is only a
51
+ # convenience for testing, so we can assert the chain was followed
52
+ # properly
53
+ if empty?
54
+ result
55
+ else
56
+ self.run
57
+ end
58
+ end
59
+ rescue Climate::CommandError => e
60
+ e.command_class = next_command.class if e.command_class.nil?
61
+ raise
62
+ end
63
+ end
64
+ end
65
+
27
66
  class << self
28
67
  include ParsingMethods
29
68
 
30
- # Create an instance of this command class and run it against the given
31
- # arguments
69
+ # Recursively construct an array of commands
32
70
  # @param [Array<String>] argv A list of arguments, ARGV style
33
71
  # @param [Hash] options see {#initialize}
34
- def run(argv, options={})
72
+ def build(argv, options={})
35
73
  begin
36
74
  instance = new(argv, options)
37
75
  rescue Trollop::HelpNeeded
@@ -39,18 +77,24 @@ module Climate
39
77
  end
40
78
 
41
79
  if subcommands.empty?
42
- begin
43
- instance.run
44
- rescue Climate::CommandError => e
45
- # make it easier on users
46
- e.command_class = self if e.command_class.nil?
47
- raise
48
- end
80
+ [instance]
49
81
  else
50
- find_and_run_subcommand(instance, options)
82
+ [instance, *find_and_build_subcommand(instance, options)]
51
83
  end
52
84
  end
53
85
 
86
+ def run(argv, options={})
87
+ command_instance_list = build(argv, options)
88
+ final_command = command_instance_list.last
89
+
90
+ if ! final_command.has_run?
91
+ raise NotImplementedError, "leaf command #{final_command} must implement #run"
92
+ end
93
+
94
+ runnable_commands = command_instance_list.select {|command| command.has_run? }
95
+ Chain.new(runnable_commands).run
96
+ end
97
+
54
98
  def ancestors(exclude_self=false)
55
99
  our_list = exclude_self ? [] : [self]
56
100
  parent.nil?? our_list : parent.ancestors + our_list
@@ -125,9 +169,21 @@ module Climate
125
169
  def has_subcommands? ; not subcommands.empty? ; end
126
170
  def subcommands ; @subcommands ||= [] ; end
127
171
 
172
+ def expose_ancestor_method(ancestor_class, method_name)
173
+ define_method(method_name) do |*args|
174
+ ancestor(ancestor_class).send(method_name, *args)
175
+ end
176
+ end
177
+
178
+ def expose_ancestor_methods(ancestor_class, *method_names)
179
+ method_names.each do |method_name|
180
+ expose_ancestor_method(ancestor_class, method_name)
181
+ end
182
+ end
183
+
128
184
  private
129
185
 
130
- def find_and_run_subcommand(parent, options)
186
+ def find_and_build_subcommand(parent, options)
131
187
  command_name, *arguments = parent.leftovers
132
188
 
133
189
  if command_name.nil?
@@ -138,7 +194,7 @@ module Climate
138
194
  found = subcommands.find {|c| c.command_name == command_name }
139
195
 
140
196
  if found
141
- found.run(arguments, options.merge(:parent => parent))
197
+ found.build(arguments, options.merge(:parent => parent))
142
198
  else
143
199
  raise UnknownCommandError.new(command_name, parent)
144
200
  end
@@ -147,7 +203,7 @@ module Climate
147
203
  end
148
204
 
149
205
  # Create an instance of this command to be run. You'll probably want to use
150
- # {Command.run}
206
+ # {Command.run
151
207
  # @param [Array<String>] arguments ARGV style arguments to be parsed
152
208
  # @option options [Command] :parent The parent command, made available as {#parent}
153
209
  # @option options [IO] :stdout stream to use as stdout, defaulting to `$stdout`
@@ -202,10 +258,8 @@ module Climate
202
258
  end
203
259
  end
204
260
 
205
- # Run the command, must be implemented by all commands that are not parent
206
- # commands (leaf commands)
207
- def run
208
- raise NotImplementedError, "Leaf commands must implement a run method"
261
+ def has_run?
262
+ self.methods.include? :run
209
263
  end
210
264
 
211
265
  def exit(status)
@@ -1,3 +1,3 @@
1
1
  module Climate
2
- VERSION = '0.6.0'
2
+ VERSION = '0.7.0'
3
3
  end
metadata CHANGED
@@ -1,95 +1,95 @@
1
- --- !ruby/object:Gem::Specification
1
+ --- !ruby/object:Gem::Specification
2
2
  name: climate
3
- version: !ruby/object:Gem::Version
4
- hash: 7
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.7.0
5
5
  prerelease:
6
- segments:
7
- - 0
8
- - 6
9
- - 0
10
- version: 0.6.0
11
6
  platform: ruby
12
- authors:
7
+ authors:
13
8
  - Nick Griffiths
14
9
  autorequire:
15
10
  bindir: bin
16
11
  cert_chain: []
17
-
18
- date: 2013-02-18 00:00:00 Z
19
- dependencies:
20
- - !ruby/object:Gem::Dependency
12
+ date: 2013-12-10 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
21
15
  name: trollop
22
- prerelease: false
23
- requirement: &id001 !ruby/object:Gem::Requirement
16
+ requirement: !ruby/object:Gem::Requirement
24
17
  none: false
25
- requirements:
18
+ requirements:
26
19
  - - ~>
27
- - !ruby/object:Gem::Version
28
- hash: 3
29
- segments:
30
- - 2
31
- - 0
32
- version: "2.0"
20
+ - !ruby/object:Gem::Version
21
+ version: '2.0'
33
22
  type: :runtime
34
- version_requirements: *id001
35
- - !ruby/object:Gem::Dependency
36
- name: minitest
37
23
  prerelease: false
38
- requirement: &id002 !ruby/object:Gem::Requirement
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ~>
28
+ - !ruby/object:Gem::Version
29
+ version: '2.0'
30
+ - !ruby/object:Gem::Dependency
31
+ name: minitest
32
+ requirement: !ruby/object:Gem::Requirement
39
33
  none: false
40
- requirements:
41
- - - ">="
42
- - !ruby/object:Gem::Version
43
- hash: 3
44
- segments:
45
- - 0
46
- version: "0"
34
+ requirements:
35
+ - - ! '>='
36
+ - !ruby/object:Gem::Version
37
+ version: '0'
47
38
  type: :development
48
- version_requirements: *id002
49
- - !ruby/object:Gem::Dependency
50
- name: yard
51
39
  prerelease: false
52
- requirement: &id003 !ruby/object:Gem::Requirement
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ! '>='
44
+ - !ruby/object:Gem::Version
45
+ version: '0'
46
+ - !ruby/object:Gem::Dependency
47
+ name: yard
48
+ requirement: !ruby/object:Gem::Requirement
53
49
  none: false
54
- requirements:
55
- - - ">="
56
- - !ruby/object:Gem::Version
57
- hash: 3
58
- segments:
59
- - 0
60
- version: "0"
50
+ requirements:
51
+ - - ! '>='
52
+ - !ruby/object:Gem::Version
53
+ version: '0'
61
54
  type: :development
62
- version_requirements: *id003
63
- - !ruby/object:Gem::Dependency
64
- name: popen4
65
55
  prerelease: false
66
- requirement: &id004 !ruby/object:Gem::Requirement
56
+ version_requirements: !ruby/object:Gem::Requirement
67
57
  none: false
68
- requirements:
69
- - - ">="
70
- - !ruby/object:Gem::Version
71
- hash: 3
72
- segments:
73
- - 0
74
- version: "0"
58
+ requirements:
59
+ - - ! '>='
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ - !ruby/object:Gem::Dependency
63
+ name: popen4
64
+ requirement: !ruby/object:Gem::Requirement
65
+ none: false
66
+ requirements:
67
+ - - ! '>='
68
+ - !ruby/object:Gem::Version
69
+ version: '0'
75
70
  type: :development
76
- version_requirements: *id004
77
- description: Library, not a framework, for building command line interfaces to your ruby application
78
- email:
71
+ prerelease: false
72
+ version_requirements: !ruby/object:Gem::Requirement
73
+ none: false
74
+ requirements:
75
+ - - ! '>='
76
+ - !ruby/object:Gem::Version
77
+ version: '0'
78
+ description: Library, not a framework, for building command line interfaces to your
79
+ ruby application
80
+ email:
79
81
  - nicobrevin@gmail.com
80
- executables:
82
+ executables:
81
83
  - climate
82
84
  extensions: []
83
-
84
85
  extra_rdoc_files: []
85
-
86
- files:
87
- - lib/climate/version.rb
86
+ files:
87
+ - lib/climate/command.rb
88
88
  - lib/climate/help/man.rb
89
89
  - lib/climate/command_compat.rb
90
- - lib/climate/command.rb
91
90
  - lib/climate/parser.rb
92
91
  - lib/climate/errors.rb
92
+ - lib/climate/version.rb
93
93
  - lib/climate/script.rb
94
94
  - lib/climate/help.rb
95
95
  - lib/climate.rb
@@ -97,37 +97,27 @@ files:
97
97
  - bin/climate
98
98
  homepage:
99
99
  licenses: []
100
-
101
100
  post_install_message:
102
101
  rdoc_options: []
103
-
104
- require_paths:
102
+ require_paths:
105
103
  - lib
106
- required_ruby_version: !ruby/object:Gem::Requirement
104
+ required_ruby_version: !ruby/object:Gem::Requirement
107
105
  none: false
108
- requirements:
109
- - - ">="
110
- - !ruby/object:Gem::Version
111
- hash: 3
112
- segments:
113
- - 0
114
- version: "0"
115
- required_rubygems_version: !ruby/object:Gem::Requirement
106
+ requirements:
107
+ - - ! '>='
108
+ - !ruby/object:Gem::Version
109
+ version: '0'
110
+ required_rubygems_version: !ruby/object:Gem::Requirement
116
111
  none: false
117
- requirements:
118
- - - ">="
119
- - !ruby/object:Gem::Version
120
- hash: 3
121
- segments:
122
- - 0
123
- version: "0"
112
+ requirements:
113
+ - - ! '>='
114
+ - !ruby/object:Gem::Version
115
+ version: '0'
124
116
  requirements: []
125
-
126
117
  rubyforge_project:
127
- rubygems_version: 1.8.10
118
+ rubygems_version: 1.8.25
128
119
  signing_key:
129
120
  specification_version: 3
130
121
  summary: Library for building command line interfaces
131
122
  test_files: []
132
-
133
123
  has_rdoc: