climate 0.6.0 → 0.7.0

Sign up to get free protection for your applications and to get access to all the features.
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: