launchr 1.1.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/.document +5 -0
- data/.gitignore +44 -0
- data/.yardopts +4 -0
- data/Gemfile +24 -0
- data/LICENSE +20 -0
- data/README.rdoc +63 -0
- data/Rakefile +78 -0
- data/VERSION +1 -0
- data/bin/brew-launchd.rb +24 -0
- data/bin/brew-restart.rb +22 -0
- data/bin/brew-start.rb +24 -0
- data/bin/brew-stop.rb +24 -0
- data/ext/Makefile +157 -0
- data/ext/extconf.rb +14 -0
- data/ext/launchd-socket-listener-unload.cpp +160 -0
- data/features/launchr.feature +9 -0
- data/features/step_definitions/launchr_steps.rb +0 -0
- data/features/support/env.rb +14 -0
- data/lib/launchr.rb +105 -0
- data/lib/launchr/application.rb +24 -0
- data/lib/launchr/cli.rb +142 -0
- data/lib/launchr/commands.rb +118 -0
- data/lib/launchr/extend/pathname.rb +27 -0
- data/lib/launchr/mixin/mixlib_cli.rb +693 -0
- data/lib/launchr/mixin/ordered_hash.rb +189 -0
- data/lib/launchr/mixin/popen4.rb +219 -0
- data/lib/launchr/path.rb +106 -0
- data/lib/launchr/service.rb +522 -0
- data/man1/brew-launchd.1 +95 -0
- data/man1/brew-launchd.1.html +140 -0
- data/man1/brew-launchd.1.ronn +71 -0
- data/spec/launchr/application_spec.rb +37 -0
- data/spec/launchr/cli_spec.rb +25 -0
- data/spec/launchr/commands_spec.rb +20 -0
- data/spec/launchr/config_spec.rb +38 -0
- data/spec/launchr_spec.rb +7 -0
- data/spec/spec.opts +1 -0
- data/spec/spec_helper.rb +10 -0
- metadata +216 -0
@@ -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
|