brew-launchd 1.1.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,24 @@
1
+
2
+ require 'launchr/cli'
3
+ require 'launchr/commands'
4
+
5
+ module Launchr
6
+ # The Launchr Application Object. Instantiated for command-line mode
7
+ # @see Launchr::CLI
8
+ class Application
9
+
10
+ def initialize *args, &blk
11
+ @cli = Launchr::CLI.new
12
+
13
+ Launchr.load_default
14
+
15
+ Launchr.config[:args] = @cli.parse
16
+ Launchr.import_args
17
+
18
+ @commands = Launchr::Commands.new
19
+ @commands.run
20
+ end
21
+ end
22
+ end
23
+
24
+
@@ -0,0 +1,142 @@
1
+
2
+ require 'launchr/mixin/mixlib_cli'
3
+
4
+ module Launchr
5
+ # Defines options for the +launchr+ command line utility
6
+ class CLI
7
+ include Launchr::Mixlib::CLI
8
+
9
+ # The Launchr CLI Options
10
+ def self.launchr_cli_options
11
+
12
+ spaced_summary true
13
+ # banner false
14
+ summary_indent ""
15
+ summary_width 29
16
+
17
+ header "brew launchd - an extension to start and stop Launchd services."
18
+ header " `man brew-launchd` for more information"
19
+ header ""
20
+ banner "Usage: brew launchd [options]"
21
+
22
+ argument :start,
23
+ :long => "start service,(s)",
24
+ :type => Array,
25
+ :description => ["Start launchd service(s)",
26
+ "Equivalent to launchctl load -w files..."],
27
+ :example => "start dnsmasq memcached couchdb",
28
+ :default => nil
29
+
30
+ argument :stop,
31
+ :long => "stop service,(s)",
32
+ :type => Array,
33
+ :description => ["Stop launchd service(s)",
34
+ "Equivalent to launchctl unload -w files..."],
35
+ :example => "stop mamcached dnsmasq",
36
+ :default => nil
37
+
38
+ argument :restart,
39
+ :long => "restart service,(s)",
40
+ :type => Array,
41
+ :description => ["Restart launchd service(s)"],
42
+ :example => "restart couchdb",
43
+ :default => nil
44
+
45
+ option :user,
46
+ :indent => true,
47
+ :long => "--user",
48
+ :description => ["At user login.",
49
+ "Otherwise, the default setting will be used."],
50
+ :example => "start --user openvpn ddclient",
51
+ :requires => Proc.new { |args| (args[:boot] ^ args[:user]) && true || raise("--boot|--user") },
52
+ :default => nil
53
+
54
+ option :boot,
55
+ :indent => true,
56
+ :long => "--boot",
57
+ :description => ["At boot time. Requires sudo/root privelidges.",
58
+ "Otherwise, the default setting will be used."],
59
+ :example => "sudo brew start --boot nginx mysql",
60
+ :requires => Proc.new { |args| (args[:boot] ^ args[:user]) && true || raise("--boot|--user") },
61
+ :default => nil
62
+
63
+
64
+ argument :info,
65
+ :long => "info [service,(s)]",
66
+ :type => Array,
67
+ :proc => Proc.new { |l| (l == true) ? [] : l },
68
+ :description => ["Info for launchd service(s)","With no arguments prints info for all services."],
69
+ :example => "brew launchd info",
70
+ :default => nil
71
+
72
+ argument :clean,
73
+ :long => "clean",
74
+ :description => ["Clean missing/broken launchd service(s)."],
75
+ :example => ["brew launchd clean", "sudo brew launchd clean"],
76
+ :default => nil
77
+
78
+ argument :default,
79
+ :long => "default [--user|--boot]",
80
+ :description => [
81
+ "Set the default target to start launchd services.",
82
+ "The initial setting, --user will start daemons at",
83
+ "user login - from the Loginwindow (not over ssh).",
84
+ " ",
85
+ "Wheras --boot will set services to start at boot",
86
+ "time. But be aware that brew should be installed",
87
+ "to the root filesystem, not on a mounted volume."],
88
+
89
+ :example => [
90
+ "brew launchd default --boot",
91
+ "brew launchd default --user"
92
+ ],
93
+ :requires => Proc.new { |args| (args[:boot] ^ args[:user]) && true || raise("--boot|--user") },
94
+ :default => nil
95
+
96
+ option :help,
97
+ :long => "--help",
98
+ :description => "Show this message",
99
+ :show_options => true,
100
+ :exit => 0
101
+
102
+ option :version,
103
+ :long => "--version",
104
+ :description => "Print version information",
105
+ :default => nil
106
+ end
107
+ launchr_cli_options
108
+
109
+ def parse argv=ARGV
110
+ parse_options(argv)
111
+
112
+ [:start,:stop,:restart].each do |cmd|
113
+ case config[cmd]
114
+ when Array
115
+ [:user, :boot].each do |level|
116
+ if config[cmd].include? "--#{level}"
117
+ config[level] = true
118
+ end
119
+ end
120
+ config[cmd] -= ["--user","--boot"]
121
+ end
122
+ end
123
+
124
+ raise "Please choose one of --user|--boot" if config[:user] && config[:boot]
125
+
126
+ unless filtered_argv.empty?
127
+ start_stop_restart_value = [config[:start],config[:stop],config[:restart],config[:info]].compact!
128
+
129
+ if start_stop_restart_value.size == 1
130
+ services = *start_stop_restart_value
131
+ extra_services = filtered_argv
132
+
133
+ services << extra_services
134
+ services.flatten!
135
+ end
136
+ end
137
+
138
+ config
139
+ end
140
+
141
+ end
142
+ end
@@ -0,0 +1,118 @@
1
+
2
+ require 'launchr/service'
3
+
4
+ module Launchr
5
+ # This objects manages all of the commands to be executed by an instance of {Launchr::Application}
6
+ # @see Launchr::Application
7
+ # @see Launchr::CLI
8
+ class Commands
9
+ PriorityOrder = []
10
+
11
+ def preflight_checks
12
+ unless Launchr::Path.homebrew_prefix
13
+ puts "Preflight checks..."
14
+ raise "No homebrew prefix was found"
15
+ end
16
+ end
17
+
18
+ # To be executed once. Branches out to subroutines, and handles the order-of-execution of
19
+ # those main subrountines.
20
+ def run
21
+ preflight_checks
22
+
23
+ PriorityOrder.each do |command|
24
+ if self.class.method_defined?(command) && ! Launchr.config[:args][command].nil?
25
+ self.send command, Launchr.config[:args][command]
26
+ end
27
+ end
28
+
29
+ left_to_execute = Launchr.config[:args].keys - PriorityOrder
30
+ Launchr.config[:args].each do |command, value|
31
+ if left_to_execute.include?(command) && self.class.method_defined?(command) && ! value.nil?
32
+ self.send command, Launchr.config[:args][command]
33
+ end
34
+ end
35
+ end
36
+
37
+ def cmd cmd, services
38
+ Launchr::Service.cleanup
39
+ services.each do |svc|
40
+ service = Launchr::Service.find(svc)
41
+ service.send(cmd)
42
+ end
43
+ end
44
+
45
+ def start services
46
+ puts "Starting launchd services..."
47
+ cmd :start, services
48
+ end
49
+
50
+ def stop services
51
+ puts "Stopping launchd services..."
52
+ cmd :stop, services
53
+ end
54
+
55
+ def restart services
56
+ puts "Restarting launchd services..."
57
+ cmd :restart, services
58
+ end
59
+
60
+ def info services
61
+ Launchr::Service.cleanup
62
+
63
+ if services.empty?
64
+
65
+ level = Launchr.config[:boot] ? "--boot" : "--user"
66
+ puts "Launchd default target: #{level}"
67
+ puts ""
68
+
69
+ services = Launchr::Service.find_all
70
+ if services.empty?
71
+ puts "No launchd services installed"
72
+ else
73
+ puts Launchr::Service.header
74
+ end
75
+ services.each do |svc|
76
+ svc.send :info
77
+ end
78
+ else
79
+ puts Launchr::Service.header
80
+ services.map! do |svc|
81
+ Launchr::Service.find(svc)
82
+ end
83
+ services.uniq!
84
+
85
+ services.each do |svc|
86
+ svc.send :info
87
+ end
88
+ end
89
+ puts ""
90
+ end
91
+
92
+ def clean value
93
+ puts "Cleaning launchd services..."
94
+ Launchr::Service.cleanup
95
+ puts "Done."
96
+ end
97
+
98
+ def default value
99
+ if Launchr.config[:args][:boot]
100
+ puts "Setting default to --boot"
101
+ Launchr::Path.launchr_default_boot.touch
102
+ Launchr::Path.chown_down Launchr::Path.launchr_default_boot
103
+ Launchr.config[:boot] = true
104
+ else
105
+ puts "Setting default to --user"
106
+ if Launchr::Path.launchr_default_boot.exist?
107
+ Launchr::Path.launchr_default_boot.unlink
108
+ end
109
+ Launchr.config[:boot] = nil
110
+ end
111
+ end
112
+
113
+ def version value
114
+ puts "Launchd commands (for Brew) v#{Launchr.version}"
115
+ end
116
+ end
117
+ end
118
+
@@ -0,0 +1,27 @@
1
+
2
+ require 'pathname'
3
+
4
+ class Pathname
5
+ def include? partial
6
+ self.to_s.include? partial.to_s
7
+ end
8
+
9
+ def chown_R user, group
10
+ require 'fileutils'
11
+ FileUtils.chown_R user, group, self.to_s
12
+ end
13
+
14
+ def user
15
+ Etc.getpwuid(self.stat.uid).name
16
+ end
17
+
18
+ def group
19
+ Etc.getgrgid(self.stat.gid).name
20
+ end
21
+
22
+ def touch
23
+ require 'fileutils'
24
+ FileUtils.touch self.to_s
25
+ end
26
+ end
27
+
@@ -0,0 +1,693 @@
1
+
2
+ require 'optparse'
3
+ require 'optparse/date'
4
+ require 'optparse/shellwords'
5
+ require 'optparse/time'
6
+ require 'optparse/uri'
7
+
8
+ require 'launchr/mixin/ordered_hash'
9
+
10
+ module Launchr
11
+ module Mixlib
12
+ # <tt></tt>
13
+ # Author:: Adam Jacob (<adam@opscode.com>)
14
+ # Copyright:: Copyright (c) 2008 Opscode, Inc.
15
+ # License:: Apache License, Version 2.0
16
+ #
17
+ # Licensed under the Apache License, Version 2.0 (the "License");
18
+ # you may not use this file except in compliance with the License.
19
+ # You may obtain a copy of the License at
20
+ #
21
+ # http://www.apache.org/licenses/LICENSE-2.0
22
+ #
23
+ # Unless required by applicable law or agreed to in writing, software
24
+ # distributed under the License is distributed on an "AS IS" BASIS,
25
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
26
+ # See the License for the specific language governing permissions and
27
+ # limitations under the License.
28
+ #
29
+ module CLI
30
+ module ClassMethods
31
+ # Add a command line option.
32
+ #
33
+ # === Parameters
34
+ # name<Symbol>:: The name of the option to add
35
+ # args<Hash>:: A hash of arguments for the option, specifying how it should be parsed.
36
+ # === Returns
37
+ # true:: Always returns true.
38
+ def option(name, args)
39
+ @options ||= Launchr::OrderedHash.new
40
+ @options_arguments ||= Launchr::OrderedHash.new
41
+ raise(ArgumentError, "Option name must be a symbol") unless name.kind_of?(Symbol)
42
+
43
+ strip_arg(args,:short)
44
+ strip_arg(args,:long)
45
+
46
+ @options[name.to_sym] = args
47
+ @options_arguments[name.to_sym] = args
48
+ end
49
+
50
+ # Get the hash of current options.
51
+ #
52
+ # === Returns
53
+ # @options<Hash>:: The current options hash.
54
+ def options
55
+ @options ||= Launchr::OrderedHash.new
56
+ @options
57
+ end
58
+
59
+ # Set the current options hash
60
+ #
61
+ # === Parameters
62
+ # val<Hash>:: The hash to set the options to
63
+ #
64
+ # === Returns
65
+ # @options<Hash>:: The current options hash.
66
+ def options=(val)
67
+ raise(ArgumentError, "Options must recieve a hash") unless val.kind_of?(Hash)
68
+ @options = val
69
+ end
70
+
71
+ # Add a command line argument.
72
+ #
73
+ # === Parameters
74
+ # name<Symbol>:: The name of the argument to add
75
+ # args<Hash>:: A hash of arguments for the argument, specifying how it should be parsed.
76
+ # === Returns
77
+ # true:: Always returns true.
78
+ def argument(name, args)
79
+ @arguments ||= Launchr::OrderedHash.new
80
+ @options_arguments ||= Launchr::OrderedHash.new
81
+ raise(ArgumentError, "Argument name must be a symbol") unless name.kind_of?(Symbol)
82
+
83
+ strip_arg(args,:short)
84
+ strip_arg(args,:long)
85
+ convert_argument_to_option(args)
86
+
87
+ @arguments[name.to_sym] = args.dup
88
+ @options_arguments[name.to_sym] = args
89
+ end
90
+
91
+ def strip_arg args, arg
92
+ if args[arg]
93
+ args["#{arg}_strip".to_sym] = args[arg].sub(/\[no-\]/,"").sub(/\s*(\<|\[|=|[A-Z]|[a-zA-z]+\,|\s).*$/,"")
94
+ end
95
+ args
96
+ end
97
+
98
+ def convert_argument_to_option args
99
+ # args = args.dup
100
+ args[:short] = "-" + args[:short] if args[:short]
101
+ args[:short_strip] = "-" + args[:short_strip] if args[:short_strip]
102
+ args[:long] = "--" + args[:long] if args[:long]
103
+ args[:long_strip] = "--" + args[:long_strip] if args[:long_strip]
104
+ args
105
+ end
106
+
107
+ # Get the hash of current arguments.
108
+ #
109
+ # === Returns
110
+ # @arguments<Hash>:: The current arguments hash.
111
+ def arguments
112
+ @arguments ||= Launchr::OrderedHash.new
113
+ @arguments
114
+ end
115
+
116
+ # Set the current arguments hash
117
+ #
118
+ # === Parameters
119
+ # val<Hash>:: The hash to set the arguments to
120
+ #
121
+ # === Returns
122
+ # @arguments<Hash>:: The current arguments hash.
123
+ def arguments=(val)
124
+ raise(ArgumentError, "Arguments must recieve a hash") unless val.kind_of?(Hash)
125
+ @arguments = val
126
+ end
127
+
128
+ # Get the combined hash of combined current options plus current arguments.
129
+ #
130
+ # === Returns
131
+ # @options_arguments<Hash>:: The combined current options and current arguments hash.
132
+ def options_arguments
133
+ @options_arguments ||= Launchr::OrderedHash.new
134
+ @options_arguments
135
+ end
136
+
137
+ # Set the current options and current arguments combined hash
138
+ #
139
+ # === Parameters
140
+ # val<Hash>:: The hash to set the combined options and arguments to
141
+ #
142
+ # === Returns
143
+ # @options_arguments<Hash>:: The current options and current arguments hash.
144
+ def options_arguments=(val)
145
+ raise(ArgumentError, "Options must recieve a hash") unless val.kind_of?(Hash)
146
+ @options_arguments = val
147
+ end
148
+
149
+ # Return the hash of current arguments as human-readable string.
150
+ #
151
+ # === Returns
152
+ # <String>:: The arguments hash, one per line.
153
+ def show_arguments
154
+ @arguments ||= Launchr::OrderedHash.new
155
+ summarize_arguments
156
+ end
157
+
158
+ # Change the banner. Defaults to:
159
+ # Usage: #{0} (options)
160
+ #
161
+ # === Parameters
162
+ # bstring<String>:: The string to set the banner to
163
+ #
164
+ # === Returns
165
+ # @banner<String>:: The current banner
166
+ def banner(bstring=nil)
167
+ case bstring
168
+ when true
169
+ # @banner = "usage: #{File.basename $0} [options]"
170
+ @banner = "Usage: #{File.basename $0} [options]"
171
+ when false
172
+ @banner = ""
173
+ when String
174
+ @banner = bstring
175
+ else
176
+ # @banner ||= "usage: #{File.basename $0} [options]"
177
+ @banner ||= "Usage: #{File.basename $0} [options]"
178
+ # @banner ||= ""
179
+ @banner
180
+ end
181
+ end
182
+
183
+ # Add a line to the header.
184
+ #
185
+ # === Parameters
186
+ # hstring<String>:: The next string to push onto the header
187
+ #
188
+ # === Returns
189
+ # @header<Array>:: The current header, an array of strings
190
+ def header(hstring=nil)
191
+ @header ||= []
192
+ case hstring
193
+ when Array
194
+ @header = hstring
195
+ when String
196
+ @header << hstring
197
+ when nil
198
+ @header
199
+ end
200
+ end
201
+
202
+ # Add a line to the footer.
203
+ #
204
+ # === Parameters
205
+ # fstring<String>:: The next string to push onto the footer
206
+ #
207
+ # === Returns
208
+ # @footer<Array>:: The current footer, an array of strings
209
+ def footer(fstring=nil)
210
+ @footer ||= []
211
+ case fstring
212
+ when Array
213
+ @footer = fstring
214
+ when String
215
+ @footer << fstring
216
+ when nil
217
+ @footer
218
+ end
219
+ end
220
+
221
+ # Summary indent. Passed to option parser. Defaults to: ' ' * 4
222
+ #
223
+ # === Parameters
224
+ # i_string<String>:: Set to the indent string
225
+ #
226
+ # === Returns
227
+ # @summary_indent<String>:: The summary indent
228
+ def summary_indent(i_string=nil)
229
+ if i_string
230
+ @summary_indent = i_string
231
+ else
232
+ @summary_indent ||= ' ' * 4
233
+ @summary_indent
234
+ end
235
+ end
236
+
237
+ # Summary indent. Passed to option parser. Defaults to: 32
238
+ #
239
+ # === Parameters
240
+ # i_string<String>:: Set to the indent string
241
+ #
242
+ # === Returns
243
+ # @summary_indent<String>:: The summary indent
244
+ def summary_width(w_integer=nil)
245
+ if w_integer
246
+ @summary_width = w_integer
247
+ else
248
+ @summary_width ||= 32
249
+ @summary_width
250
+ end
251
+ end
252
+
253
+ # Seperate options with empty lines. Defaults to: false
254
+ #
255
+ # === Parameters
256
+ # bool<true,false>:: Set to true for newline spacing
257
+ #
258
+ # === Returns
259
+ # @spaced_summary<String>:: The current line spacing setting
260
+ def spaced_summary(bool=nil)
261
+ if bool
262
+ @spaced_summary = bool
263
+ else
264
+ @spaced_summary ||= false
265
+ @spaced_summary
266
+ end
267
+ end
268
+
269
+ # The remaining argv command line arguments, after parsing. Defaults to: [] (an empty array) if un-parsed
270
+ #
271
+ # === Returns
272
+ # @filtered_argv<Array>:: The remaining command line arguments, after CLI options parsing.
273
+ def filtered_argv
274
+ @filtered_argv ||= []
275
+ @filtered_argv
276
+ end
277
+ end
278
+
279
+ attr_accessor :options, :arguments, :options_arguments, :config, :banner, :header, :footer
280
+ attr_accessor :opt_parser, :filtered_argv, :summary_indent, :summary_width, :spaced_summary
281
+
282
+ # Create a new Mixlib::CLI class. If you override this, make sure you call super!
283
+ #
284
+ # === Parameters
285
+ # *args<Array>:: The array of arguments passed to the initializer
286
+ #
287
+ # === Returns
288
+ # object<Mixlib::Config>:: Returns an instance of whatever you wanted :)
289
+ def initialize(*args)
290
+ @options = Launchr::OrderedHash.new
291
+ @arguments = Launchr::OrderedHash.new
292
+ @options_arguments = Launchr::OrderedHash.new
293
+ @config = Hash.new
294
+ @filtered_argv = []
295
+
296
+ # Set the banner
297
+ @banner = self.class.banner
298
+ @header = self.class.header
299
+ @footer = self.class.footer
300
+
301
+ @summary_indent = self.class.summary_indent
302
+ @summary_width = self.class.summary_width
303
+ @spaced_summary = self.class.spaced_summary
304
+
305
+ # Dupe the class options for this instance
306
+ klass_options = self.class.options
307
+ klass_options.keys.inject(@options) { |memo, key| memo[key] = klass_options[key].dup; memo }
308
+
309
+ # Dupe the class arguments for this instance
310
+ klass_arguments = self.class.arguments
311
+ klass_arguments.keys.inject(@arguments) { |memo, key| memo[key] = klass_arguments[key].dup; memo }
312
+
313
+ # Dupe the class arguments for this instance
314
+ klass_options_arguments = self.class.options_arguments
315
+ klass_options_arguments.keys.inject(@options_arguments) { |memo, key| memo[key] = klass_options_arguments[key].dup; memo }
316
+
317
+ # check argument and option :name keys dont conflict
318
+ name_collision = klass_options.keys & klass_arguments.keys
319
+ raise ArgumentError, "An option cannot have the same name as an argument: #{name_collision.join(', ')}" unless name_collision.empty?
320
+
321
+ koso, kolo = [], []
322
+ klass_options.each do |name, kargs|
323
+ koso << (kargs[:short_strip] || "")
324
+ kolo << (kargs[:long_strip] || "")
325
+ end
326
+
327
+ kasa, kala = [], []
328
+ klass_arguments.each do |name, kargs|
329
+ kasa << (kargs[:short_strip] || "")
330
+ kala << (kargs[:long_strip] || "")
331
+ end
332
+
333
+ # Check that argument an option --long switches dont conflict
334
+ loa_collision = kolo & kala - [""]
335
+ opt_name = klass_options.keys[kolo.index(loa_collision.first) || 0]
336
+ arg_name = klass_arguments.keys[kala.index(loa_collision.first) || 0]
337
+ raise ArgumentError, "Collision: switch '#{loa_collision.first}' for option(#{opt_name.inspect}) and argument(#{arg_name.inspect}) cannot be the same" unless loa_collision.empty?
338
+
339
+ # Check that argument an option -s short switches dont conflict
340
+ soa_collision = koso & kasa - [""]
341
+ opt_name = klass_options.keys[kolo.index(soa_collision.first) || 0]
342
+ arg_name = klass_arguments.keys[kala.index(soa_collision.first) || 0]
343
+ raise ArgumentError, "Collision: switch '#{soa_collision.first}' for option(#{opt_name.inspect}) and argument(#{arg_name.inspect}) cannot be the same" unless soa_collision.empty?
344
+
345
+ # Set the default configuration values for this instance
346
+ @options.each do |config_key, config_opts|
347
+ config_opts[:on] ||= :on
348
+ config_opts[:boolean] ||= false
349
+ config_opts[:requires] ||= nil
350
+ config_opts[:proc] ||= nil
351
+ config_opts[:show_options] ||= false
352
+ config_opts[:exit] ||= nil
353
+
354
+ if config_opts.has_key?(:default)
355
+ @config[config_key] = config_opts[:default]
356
+ end
357
+ end
358
+
359
+ @arguments.each do |config_key, config_opts|
360
+ config_opts[:on] ||= :on
361
+ config_opts[:boolean] ||= false
362
+ config_opts[:requires] ||= nil
363
+ config_opts[:proc] ||= nil
364
+ config_opts[:show_options] ||= false
365
+ config_opts[:exit] ||= nil
366
+
367
+ if config_opts.has_key?(:default)
368
+ @config[config_key] = config_opts[:default]
369
+ end
370
+ end
371
+
372
+ @options_arguments.each do |config_key, config_opts|
373
+ config_opts[:on] ||= :on
374
+ config_opts[:boolean] ||= false
375
+ config_opts[:requires] ||= nil
376
+ config_opts[:proc] ||= nil
377
+ config_opts[:show_options] ||= false
378
+ config_opts[:exit] ||= nil
379
+
380
+ if config_opts.has_key?(:default)
381
+ @config[config_key] = config_opts[:default]
382
+ end
383
+ end
384
+
385
+ super(*args)
386
+ end
387
+
388
+
389
+ def guess_and_switchify_arguments argv
390
+ # collect argument declarations
391
+ short_args = @arguments.values.map { |args| args[:short_strip] }
392
+ long_args = @arguments.values.map { |args| args[:long_strip] }
393
+
394
+ short_opts_args = @options_arguments.values.map { |args| args[:short_strip] }
395
+ long_opts_args = @options_arguments.values.map { |args| args[:long_strip] }
396
+
397
+ short_opts_args_unfiltered = @options_arguments.values.map { |args| args[:short] }
398
+ long_opts_args_unfiltered = @options_arguments.values.map { |args| args[:long] }
399
+
400
+ i = 0
401
+ while i < argv.size
402
+
403
+ # switchify the argv argument if it looks like a recognised argument
404
+ if short_args.include?("-"+argv[i].sub(/^no-/,"").sub(/(=|\s).*/,""))
405
+ argv[i] = "-" + argv[i]
406
+ end
407
+
408
+ if long_args.include?("--"+argv[i].sub(/^no-/,"").sub(/(=|\s).*/,""))
409
+ argv[i] = "--" + argv[i]
410
+ end
411
+
412
+ # when the argv argument matches a recognised option or argument
413
+ # without the style =value, the following argument might have to be skipped...
414
+
415
+ # so find the index of the switch declaration
416
+ j = nil
417
+ if short_opts_args.include?(argv[i])
418
+ j = short_opts_args.index(argv[i])
419
+ end
420
+ if long_opts_args.include?(argv[i])
421
+ j = long_opts_args.index(argv[i])
422
+ end
423
+
424
+ if j
425
+ # when the switch declaration has a required argument
426
+ if short_opts_args_unfiltered[j] =~ /( .+|\<|\=|[A-Z])/
427
+ # skip forward one
428
+ i += 1
429
+ end
430
+ # when the switch declaration has a required argument
431
+ if long_opts_args_unfiltered[j] =~ /( .+|\<|\=|[A-Z])/
432
+ # skip forward one
433
+ i += 1
434
+ end
435
+ end
436
+ # next argument
437
+ i += 1
438
+ end
439
+
440
+ argv
441
+ end
442
+
443
+ # Parses an array, by default ARGV, for command line options (as configured at
444
+ # the class level).
445
+ # === Parameters
446
+ # argv<Array>:: The array of arguments to parse; defaults to ARGV
447
+ #
448
+ # === Returns
449
+ # argv<Array>:: Returns any un-parsed elements.
450
+ def parse_options(argv=ARGV)
451
+ argv = argv.dup
452
+ argv = guess_and_switchify_arguments(argv)
453
+ @opt_parser = OptionParser.new do |opts|
454
+ # Set the banner
455
+ opts.banner = banner
456
+
457
+ # Create new options
458
+ options_arguments.each do |opt_key, opt_val|
459
+ opt_args = build_option_arguments(opt_val)
460
+
461
+ opt_method = case opt_val[:on]
462
+ when :on
463
+ :on
464
+ when :tail
465
+ :on_tail
466
+ when :head
467
+ :on_head
468
+ else
469
+ raise ArgumentError, "You must pass :on, :tail, or :head to :on"
470
+ end
471
+
472
+ parse_block = \
473
+ Proc.new() do |*c|
474
+ if c.empty? || c == [nil]
475
+ c = true
476
+ config[opt_key] = (opt_val[:proc] && opt_val[:proc].call(c)) || c
477
+ else
478
+ c = c.first
479
+ config[opt_key] = (opt_val[:proc] && opt_val[:proc].call(c)) || c
480
+ end
481
+ puts filter_options_summary(opts.to_s) if opt_val[:show_options]
482
+ exit opt_val[:exit] if opt_val[:exit]
483
+ end
484
+
485
+ # opts.send(:on, *[opt_method,*opt_args, parse_block])
486
+ opt_args.unshift opt_method
487
+ opt_args << parse_block
488
+ opts.send(*opt_args)
489
+ end
490
+ end
491
+
492
+ @opt_parser.summary_indent = @summary_indent if @summary_indent
493
+ @opt_parser.summary_width = @summary_width if @summary_width
494
+
495
+ @opt_parser.parse!(argv)
496
+ @filtered_argv = argv
497
+
498
+ # Deal with any required values
499
+ fail = nil
500
+ options_arguments.each do |opt_key, opt_value|
501
+ next unless config[opt_key]
502
+ # next if config[opt_key] == opt_value[:default]
503
+
504
+ reqargs = []
505
+ case opt_value[:requires]
506
+ when nil
507
+ when Proc
508
+ begin
509
+ result = opt_value[:requires].call(config)
510
+ rescue
511
+ reqargs << $!.message
512
+ end
513
+ reqargs << result if result.class == String
514
+ when Array,Symbol
515
+ required_opts = [opt_value[:requires]].flatten
516
+ required_opts.each do |required_opt|
517
+ reqargs << required_opt.to_sym unless config[required_opt.to_sym]
518
+ end
519
+
520
+ reqargs.map! do |opt|
521
+ arg = (options_arguments[opt][:long_strip] || options_arguments[opt][:short_strip]).dup
522
+ arg.gsub!(/^-+/,"") if arguments.keys.include?(opt)
523
+ arg
524
+ end
525
+ end
526
+ unless reqargs.empty?
527
+ fail = true
528
+ opt = (opt_value[:long_strip] || opt_value[:short_strip]).dup
529
+ opt.gsub!(/^-+/,"") if arguments.keys.include?(opt_key)
530
+ puts "You must supply #{reqargs.join(", ")} with #{opt}!"
531
+ end
532
+
533
+ end
534
+ if fail
535
+ puts filter_options_summary(@opt_parser.to_s)
536
+ exit 2
537
+ end
538
+
539
+ argv
540
+ end
541
+
542
+ def build_option_arguments(opt_setting)
543
+ arguments = Array.new
544
+ arguments << opt_setting[:short] if opt_setting.has_key?(:short)
545
+ arguments << opt_setting[:long] if opt_setting.has_key?(:long)
546
+
547
+ if opt_setting.has_key?(:keywords)
548
+ arguments << opt_setting[:keywords]
549
+
550
+ elsif opt_setting.has_key?(:type)
551
+ arguments << opt_setting[:type]
552
+ end
553
+
554
+ case opt_setting[:description]
555
+ when Array
556
+ lines = opt_setting[:description].dup
557
+ # lines.first << " (required)" if opt_setting[:required]
558
+ lines.map! do |line|
559
+ if line == lines.first
560
+ line
561
+ else
562
+ line = " " + line if opt_setting[:indent]
563
+ @spaced_summary ? line : " " + line
564
+ end
565
+ end
566
+ arguments += lines
567
+ when String
568
+ description = opt_setting[:description]
569
+ # description = " " + description if opt_setting[:indent]
570
+ # description << " (required)" if opt_setting[:required]
571
+ arguments << description
572
+ end
573
+
574
+ case opt_setting[:example]
575
+ when Array
576
+ lines = opt_setting[:example]
577
+ example = lines.map do |line|
578
+ if line == lines.first
579
+ header = "Examples $ "
580
+ header = " " + header unless @spaced_summary
581
+ else
582
+ header = " $ "
583
+ header = " " + header unless @spaced_summary
584
+ end
585
+ header = " " + header if opt_setting[:indent]
586
+
587
+ line_parts = line.split("#")
588
+ if line_parts.first.include?("#{File.basename($0)} ")
589
+ header + line
590
+ else
591
+ header + "#{File.basename $0} " + line
592
+ end
593
+ end
594
+ arguments << " " if @spaced_summary
595
+ arguments += example
596
+ when /#{File.basename $0}/
597
+ line = opt_setting[:example]
598
+ line_parts = line.split("#")
599
+ if line_parts.first.include?("#{File.basename($0)} ")
600
+ line = "Example $ " + line
601
+ line = " " + line if opt_setting[:indent]
602
+ else
603
+ line = "Example $ #{File.basename $0} " + line
604
+ line = " " + line if opt_setting[:indent]
605
+ end
606
+ line = " " + line unless @spaced_summary
607
+ arguments << line
608
+ when nil
609
+ else
610
+ line = "Example $ #{File.basename $0} " + opt_setting[:example]
611
+ line = " " + line if opt_setting[:indent]
612
+ line = " " + line unless @spaced_summary
613
+ arguments << line
614
+ end
615
+
616
+ arguments
617
+ end
618
+
619
+ def filter_options_summary options_summary
620
+ os = options_summary.split("\n")
621
+ out = []
622
+
623
+ short_args = @arguments.values.map do |args|
624
+ args[:short] ? args[:short].sub(/([A-Z]|=|\s).*$/,"") : args[:short]
625
+ end
626
+ long_args = @arguments.values.map do |args|
627
+ args[:long] ? args[:long].sub(/([A-Z]|=|\s).*$/,"") : args[:long]
628
+ end
629
+
630
+ os.each do |line|
631
+ case line
632
+ when banner
633
+ out += [@header].flatten if @header
634
+ unless line =~ /^\s*$/
635
+ # line = " " + line if @spaced_summary
636
+ out << line
637
+ end
638
+ else
639
+ if @spaced_summary
640
+ out << "" unless line =~ /^#{@opt_parser.summary_indent}\s{#{@opt_parser.summary_width},}/
641
+ end
642
+
643
+ line =~ /^\s+-((\[no-\])?\w+)\,?/
644
+ short_opt = $1 || false
645
+ line =~ /^\s+(-(\[no-\])?\w+\,?)?\s--((\[no-\])?\w+)/
646
+ long_opt = $3 || false
647
+
648
+ # line.sub!("-"+short_opt," "+short_opt) if short_opt && short_args.include?("-#{short_opt}")
649
+ # line.sub!("--"+long_opt," "+long_opt) if long_opt && long_args.include?("--#{long_opt}")
650
+
651
+ opt_value = {}
652
+ @options_arguments.each do |key,value|
653
+ if long_opt && value[:long_strip]
654
+ if long_opt.sub(/^(-+)?\[no-\]/,"") == value[:long_strip].sub(/^-+/,"")
655
+ # puts long_opt
656
+ opt_value = value
657
+ end
658
+ elsif short_opt && value[:short_strip]
659
+ if short_opt.sub(/^(-+)?\[no-\]/,"") == value[:short_strip].sub(/^-+/,"")
660
+ # puts short_opt
661
+ opt_value = value
662
+ end
663
+ end
664
+ end
665
+ line = " " + line if opt_value[:indent]
666
+
667
+ if short_opt && short_args.include?("-#{short_opt}")
668
+ short_opt = @arguments.values[short_args.index("-#{short_opt}")][:short].sub(/^-+/,"")
669
+ # short_opt = opt_value[:short].sub(/^-+/,"")
670
+ line.sub!("-"+short_opt,short_opt+" ")
671
+ end
672
+ if long_opt && long_args.include?("--#{long_opt}")
673
+ long_opt = @arguments.values[long_args.index("--#{long_opt}")][:long].sub(/^-+/,"")
674
+ # long_opt = opt_value[:short].sub(/^-+/,"")
675
+ line.sub!("--"+long_opt,long_opt+" ")
676
+ end
677
+
678
+ out << line
679
+ end
680
+ end
681
+ out << " " if @spaced_summary
682
+ out += [@footer].flatten if @footer
683
+
684
+ out
685
+ end
686
+
687
+ def self.included(receiver)
688
+ receiver.extend(Mixlib::CLI::ClassMethods)
689
+ end
690
+
691
+ end
692
+ end
693
+ end