wycats-thor 0.9.2 → 0.9.5
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG.rdoc +35 -0
- data/README.markdown +47 -30
- data/Rakefile +1 -1
- data/lib/thor.rb +32 -15
- data/lib/thor/options.rb +238 -0
- data/lib/thor/runner.rb +63 -25
- data/lib/thor/task.rb +30 -36
- data/lib/thor/task_hash.rb +1 -1
- data/lib/thor/tasks.rb +11 -4
- data/lib/thor/tasks/package.rb +18 -0
- metadata +9 -5
- data/lib/getopt.rb +0 -238
data/CHANGELOG.rdoc
ADDED
@@ -0,0 +1,35 @@
|
|
1
|
+
== TODO
|
2
|
+
|
3
|
+
* Change Thor.start to parse ARGV in a single pass
|
4
|
+
* Improve spec coverage for Thor::Runner
|
5
|
+
* Improve help output to list shorthand switches, too
|
6
|
+
* Investigate and fix deep namespacing ("foo:bar:baz") issues
|
7
|
+
|
8
|
+
== 0.9.5, released 2008-08-27
|
9
|
+
|
10
|
+
* Improve Windows compatibility
|
11
|
+
* Update (incorrect) README and task.thor sample file
|
12
|
+
* Options hash is now frozen (once returned)
|
13
|
+
* Allow magic predicates on options object. For instance: `options.force?`
|
14
|
+
* Add support for :numeric type
|
15
|
+
* BACKWARDS INCOMPATIBLE: Refactor Thor::Options. You cannot access shorthand forms in options hash anymore (for instance, options[:f])
|
16
|
+
* Allow specifying optional args with default values: method_options(:user => "mislav")
|
17
|
+
* Don't write options for nil or false values. This allows, for example, turning color off when running specs.
|
18
|
+
* Exit with the status of the spec command to help CI stuff out some.
|
19
|
+
|
20
|
+
== 0.9.4, released 2008-08-13
|
21
|
+
|
22
|
+
* Try to add Windows compatibility.
|
23
|
+
* BACKWARDS INCOMPATIBLE: options hash is now accessed as a property in your class and is not passed as last argument anymore
|
24
|
+
* Allow options at the beginning of the argument list as well as the end.
|
25
|
+
* Make options available with symbol keys in addition to string keys.
|
26
|
+
* Allow true to be passed to Thor#method_options to denote a boolean option.
|
27
|
+
* If loading a thor file fails, don't give up, just print a warning and keep going.
|
28
|
+
* Make sure that we re-raise errors if they happened further down the pipe than we care about.
|
29
|
+
* Only delete the old file on updating when the installation of the new one is a success
|
30
|
+
* Make it Ruby 1.8.5 compatible.
|
31
|
+
* Don't raise an error if a boolean switch is defined multiple times.
|
32
|
+
* Thor::Options now doesn't parse through things that look like options but aren't.
|
33
|
+
* Add URI detection to install task, and make sure we don't append ".thor" to URIs
|
34
|
+
* Add rake2thor to the gem binfiles.
|
35
|
+
* Make sure local Thorfiles override system-wide ones.
|
data/README.markdown
CHANGED
@@ -4,56 +4,73 @@ thor
|
|
4
4
|
Map options to a class. Simply create a class with the appropriate annotations, and have options automatically map
|
5
5
|
to functions and parameters.
|
6
6
|
|
7
|
-
|
7
|
+
Example:
|
8
8
|
|
9
|
-
class MyApp < Thor
|
10
|
-
map "-L" => :list
|
11
|
-
|
12
|
-
desc "install APP_NAME", "install one of the available apps"
|
13
|
-
method_options :force => :boolean
|
14
|
-
def install(name
|
15
|
-
|
16
|
-
if
|
9
|
+
class MyApp < Thor # [1]
|
10
|
+
map "-L" => :list # [2]
|
11
|
+
|
12
|
+
desc "install APP_NAME", "install one of the available apps" # [3]
|
13
|
+
method_options :force => :boolean, :alias => :optional # [4]
|
14
|
+
def install(name)
|
15
|
+
user_alias = options[:alias]
|
16
|
+
if options.force?
|
17
17
|
# do something
|
18
18
|
end
|
19
|
+
# ... other code ...
|
19
20
|
end
|
20
21
|
|
21
22
|
desc "list [SEARCH]", "list all of the available apps, limited by SEARCH"
|
22
23
|
def list(search = "")
|
23
24
|
# list everything
|
24
25
|
end
|
25
|
-
|
26
26
|
end
|
27
27
|
|
28
|
-
|
29
|
-
|
30
|
-
Thor automatically maps commands as follows:
|
28
|
+
Thor automatically maps commands as such:
|
31
29
|
|
32
|
-
app install
|
30
|
+
app install myname --force
|
33
31
|
|
34
32
|
That gets converted to:
|
35
33
|
|
36
|
-
MyApp.new.install("
|
37
|
-
|
38
|
-
[1] Inherit from Thor to turn a class into an option mapper
|
34
|
+
MyApp.new.install("myname")
|
35
|
+
# with {'force' => true} as options hash
|
39
36
|
|
40
|
-
|
37
|
+
1. Inherit from Thor to turn a class into an option mapper
|
38
|
+
2. Map additional non-valid identifiers to specific methods. In this case,
|
41
39
|
convert -L to :list
|
42
|
-
|
43
|
-
[3] Describe the method immediately below. The first parameter is the usage information,
|
40
|
+
3. Describe the method immediately below. The first parameter is the usage information,
|
44
41
|
and the second parameter is the description.
|
45
|
-
|
46
|
-
|
47
|
-
In this case, a --force and a -f option is added.
|
42
|
+
4. Provide any additional options. These will be marshaled from `--` and `-` params.
|
43
|
+
In this case, a `--force` and a `-f` option is added.
|
48
44
|
|
49
45
|
Types for `method_options`
|
50
46
|
--------------------------
|
51
47
|
|
52
48
|
<dl>
|
53
|
-
<dt>:boolean</dt>
|
54
|
-
|
55
|
-
<dt
|
56
|
-
|
57
|
-
<dt>:
|
58
|
-
|
59
|
-
</
|
49
|
+
<dt><code>:boolean</code></dt>
|
50
|
+
<dd>true if the option is passed</dd>
|
51
|
+
<dt><code>true</code></dt>
|
52
|
+
<dd>same as <code>:boolean</code></dd>
|
53
|
+
<dt><code>:required</code></dt>
|
54
|
+
<dd>the value for this option MUST be provided</dd>
|
55
|
+
<dt><code>:optional</code></dt>
|
56
|
+
<dd>the value for this option MAY be provided</dd>
|
57
|
+
<dt><code>:numeric</code></dt>
|
58
|
+
<dd>the value MAY be provided, but MUST be in numeric form</dd>
|
59
|
+
<dt>a String or Numeric</dt>
|
60
|
+
<dd>same as <code>:optional</code>, but fall back to the given object as default value</dd>
|
61
|
+
</dl>
|
62
|
+
|
63
|
+
In case of unsatisfied requirements, `Thor::Options::Error` is raised.
|
64
|
+
|
65
|
+
Examples of option parsing:
|
66
|
+
|
67
|
+
# let's say this is how we defined options for a method:
|
68
|
+
method_options(:force => :boolean, :retries => :numeric)
|
69
|
+
|
70
|
+
# here is how the following command-line invocations would be parsed:
|
71
|
+
|
72
|
+
command -f --retries 5 # => {'force' => true, 'retries' => 5}
|
73
|
+
command --force -r=5 # => {'force' => true, 'retries' => 5}
|
74
|
+
command -fr 5 # => {'force' => true, 'retries' => 5}
|
75
|
+
command --retries=5 # => {'retries' => 5}
|
76
|
+
command -r5 # => {'retries' => 5}
|
data/Rakefile
CHANGED
data/lib/thor.rb
CHANGED
@@ -1,10 +1,12 @@
|
|
1
1
|
$:.unshift File.expand_path(File.dirname(__FILE__))
|
2
|
-
require "
|
2
|
+
require "thor/options"
|
3
3
|
require "thor/util"
|
4
4
|
require "thor/task"
|
5
5
|
require "thor/task_hash"
|
6
6
|
|
7
7
|
class Thor
|
8
|
+
attr_accessor :options
|
9
|
+
|
8
10
|
def self.map(map)
|
9
11
|
@map ||= superclass.instance_variable_get("@map") || {}
|
10
12
|
map.each do |key, value|
|
@@ -21,9 +23,7 @@ class Thor
|
|
21
23
|
end
|
22
24
|
|
23
25
|
def self.method_options(opts)
|
24
|
-
@method_options = opts
|
25
|
-
accum.merge("--" + k.to_s => v.to_s.upcase)
|
26
|
-
end
|
26
|
+
@method_options = opts
|
27
27
|
end
|
28
28
|
|
29
29
|
def self.subclass_files
|
@@ -38,6 +38,10 @@ class Thor
|
|
38
38
|
@tasks ||= TaskHash.new(self)
|
39
39
|
end
|
40
40
|
|
41
|
+
def self.opts
|
42
|
+
(@opts || {}).merge(self == Thor ? {} : superclass.opts)
|
43
|
+
end
|
44
|
+
|
41
45
|
def self.[](task)
|
42
46
|
namespaces = task.split(":")
|
43
47
|
klass = Thor::Util.constant_from_thor_path(namespaces[0...-1].join(":"))
|
@@ -49,17 +53,21 @@ class Thor
|
|
49
53
|
@maxima ||= begin
|
50
54
|
max_usage = tasks.map {|_, t| t.usage}.max {|x,y| x.to_s.size <=> y.to_s.size}.size
|
51
55
|
max_desc = tasks.map {|_, t| t.description}.max {|x,y| x.to_s.size <=> y.to_s.size}.size
|
52
|
-
max_opts = tasks.map {|_, t| t.
|
56
|
+
max_opts = tasks.map {|_, t| t.opts ? t.opts.formatted_usage : ""}.max {|x,y| x.to_s.size <=> y.to_s.size}.size
|
53
57
|
Struct.new(:description, :usage, :opt).new(max_desc, max_usage, max_opts)
|
54
58
|
end
|
55
59
|
end
|
56
60
|
|
57
61
|
def self.start(args = ARGV)
|
62
|
+
options = Thor::Options.new(self.opts)
|
63
|
+
opts = options.parse(args, false)
|
64
|
+
args = options.trailing_non_opts
|
65
|
+
|
58
66
|
meth = args.first
|
59
67
|
meth = @map[meth].to_s if @map && @map[meth]
|
60
68
|
meth ||= "help"
|
61
69
|
|
62
|
-
tasks[meth].parse args[1..-1]
|
70
|
+
tasks[meth].parse new(opts, *args), args[1..-1]
|
63
71
|
rescue Thor::Error => e
|
64
72
|
$stderr.puts e.message
|
65
73
|
end
|
@@ -72,6 +80,13 @@ class Thor
|
|
72
80
|
|
73
81
|
def method_added(meth)
|
74
82
|
meth = meth.to_s
|
83
|
+
|
84
|
+
if meth == "initialize"
|
85
|
+
@opts = @method_options
|
86
|
+
@method_options = nil
|
87
|
+
return
|
88
|
+
end
|
89
|
+
|
75
90
|
return if !public_instance_methods.include?(meth) || !@usage
|
76
91
|
register_klass_file self
|
77
92
|
|
@@ -91,6 +106,9 @@ class Thor
|
|
91
106
|
subclasses << klass unless subclasses.include?(klass)
|
92
107
|
end
|
93
108
|
end
|
109
|
+
|
110
|
+
def initialize(opts = {}, *args)
|
111
|
+
end
|
94
112
|
|
95
113
|
map ["-h", "-?", "--help", "-D"] => :help
|
96
114
|
|
@@ -106,15 +124,14 @@ class Thor
|
|
106
124
|
|
107
125
|
puts task.formatted_usage(namespace)
|
108
126
|
puts task.description
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
puts task.description.split("\n").first
|
127
|
+
else
|
128
|
+
puts "Options"
|
129
|
+
puts "-------"
|
130
|
+
self.class.tasks.each do |_, task|
|
131
|
+
format = "%-" + (self.class.maxima.usage + self.class.maxima.opt + 4).to_s + "s"
|
132
|
+
print format % ("#{task.formatted_usage}")
|
133
|
+
puts task.description.split("\n").first
|
134
|
+
end
|
118
135
|
end
|
119
136
|
end
|
120
137
|
|
data/lib/thor/options.rb
ADDED
@@ -0,0 +1,238 @@
|
|
1
|
+
# This is a modified version of Daniel Berger's Getopt::Long class,
|
2
|
+
# licensed under Ruby's license.
|
3
|
+
|
4
|
+
class Thor
|
5
|
+
class Options
|
6
|
+
class Error < StandardError; end
|
7
|
+
|
8
|
+
# simple Hash with indifferent access
|
9
|
+
class Hash < ::Hash
|
10
|
+
def initialize(hash)
|
11
|
+
super()
|
12
|
+
update hash
|
13
|
+
end
|
14
|
+
|
15
|
+
def [](key)
|
16
|
+
super convert_key(key)
|
17
|
+
end
|
18
|
+
|
19
|
+
def values_at(*indices)
|
20
|
+
indices.collect { |key| self[convert_key(key)] }
|
21
|
+
end
|
22
|
+
|
23
|
+
protected
|
24
|
+
def convert_key(key)
|
25
|
+
key.kind_of?(Symbol) ? key.to_s : key
|
26
|
+
end
|
27
|
+
|
28
|
+
# Magic predicates. For instance:
|
29
|
+
# options.force? # => !!options['force']
|
30
|
+
def method_missing(method, *args, &block)
|
31
|
+
method.to_s =~ /^(\w+)\?$/ ? !!self[$1] : super
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
NUMERIC = /(\d*\.\d+|\d+)/
|
36
|
+
LONG_RE = /^(--\w+[-\w+]*)$/
|
37
|
+
SHORT_RE = /^(-[a-z])$/i
|
38
|
+
EQ_RE = /^(--\w+[-\w+]*|-[a-z])=(.*)$/i
|
39
|
+
SHORT_SQ_RE = /^-([a-z]{2,})$/i # Allow either -x -v or -xv style for single char args
|
40
|
+
SHORT_NUM = /^(-[a-z])#{NUMERIC}$/i
|
41
|
+
|
42
|
+
attr_reader :leading_non_opts, :trailing_non_opts
|
43
|
+
|
44
|
+
def non_opts
|
45
|
+
leading_non_opts + trailing_non_opts
|
46
|
+
end
|
47
|
+
|
48
|
+
# Takes an array of switches. Each array consists of up to three
|
49
|
+
# elements that indicate the name and type of switch. Returns a hash
|
50
|
+
# containing each switch name, minus the '-', as a key. The value
|
51
|
+
# for each key depends on the type of switch and/or the value provided
|
52
|
+
# by the user.
|
53
|
+
#
|
54
|
+
# The long switch _must_ be provided. The short switch defaults to the
|
55
|
+
# first letter of the short switch. The default type is :boolean.
|
56
|
+
#
|
57
|
+
# Example:
|
58
|
+
#
|
59
|
+
# opts = Thor::Options.new(
|
60
|
+
# "--debug" => true,
|
61
|
+
# ["--verbose", "-v"] => true,
|
62
|
+
# ["--level", "-l"] => :numeric
|
63
|
+
# ).parse(args)
|
64
|
+
#
|
65
|
+
def initialize(switches)
|
66
|
+
@defaults = {}
|
67
|
+
@shorts = {}
|
68
|
+
|
69
|
+
@switches = switches.inject({}) do |mem, (name, type)|
|
70
|
+
if name.is_a?(Array)
|
71
|
+
name, *shorts = name
|
72
|
+
else
|
73
|
+
name = name.to_s
|
74
|
+
shorts = []
|
75
|
+
end
|
76
|
+
# we need both nice and dasherized form of switch name
|
77
|
+
if name.index('-') == 0
|
78
|
+
nice_name = undasherize name
|
79
|
+
else
|
80
|
+
nice_name = name
|
81
|
+
name = dasherize name
|
82
|
+
end
|
83
|
+
# if there are no shortcuts specified, generate one using the first character
|
84
|
+
shorts << "-" + nice_name[0,1] if shorts.empty? and nice_name.length > 1
|
85
|
+
shorts.each { |short| @shorts[short] = name }
|
86
|
+
|
87
|
+
# normalize type
|
88
|
+
case type
|
89
|
+
when TrueClass then type = :boolean
|
90
|
+
when String
|
91
|
+
@defaults[nice_name] = type
|
92
|
+
type = :optional
|
93
|
+
when Numeric
|
94
|
+
@defaults[nice_name] = type
|
95
|
+
type = :numeric
|
96
|
+
end
|
97
|
+
|
98
|
+
mem[name] = type
|
99
|
+
mem
|
100
|
+
end
|
101
|
+
|
102
|
+
# remove shortcuts that happen to coincide with any of the main switches
|
103
|
+
@shorts.keys.each do |short|
|
104
|
+
@shorts.delete(short) if @switches.key?(short)
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
def parse(args, skip_leading_non_opts = true)
|
109
|
+
@args = args
|
110
|
+
# start with Thor::Options::Hash pre-filled with defaults
|
111
|
+
hash = Hash.new @defaults
|
112
|
+
|
113
|
+
@leading_non_opts = []
|
114
|
+
if skip_leading_non_opts
|
115
|
+
@leading_non_opts << shift until current_is_option? || @args.empty?
|
116
|
+
end
|
117
|
+
|
118
|
+
while current_is_option?
|
119
|
+
case shift
|
120
|
+
when SHORT_SQ_RE
|
121
|
+
unshift $1.split('').map { |f| "-#{f}" }
|
122
|
+
next
|
123
|
+
when EQ_RE, SHORT_NUM
|
124
|
+
unshift $2
|
125
|
+
switch = $1
|
126
|
+
when LONG_RE, SHORT_RE
|
127
|
+
switch = $1
|
128
|
+
end
|
129
|
+
|
130
|
+
switch = normalize_switch(switch)
|
131
|
+
nice_name = undasherize(switch)
|
132
|
+
type = switch_type(switch)
|
133
|
+
|
134
|
+
case type
|
135
|
+
when :required
|
136
|
+
assert_value!(switch)
|
137
|
+
raise Error, "cannot pass switch '#{peek}' as an argument" if valid?(peek)
|
138
|
+
hash[nice_name] = shift
|
139
|
+
when :optional
|
140
|
+
hash[nice_name] = peek.nil? || valid?(peek) || shift
|
141
|
+
when :boolean
|
142
|
+
hash[nice_name] = true
|
143
|
+
when :numeric
|
144
|
+
assert_value!(switch)
|
145
|
+
unless peek =~ NUMERIC and $& == peek
|
146
|
+
raise Error, "expected numeric value for '#{switch}'; got #{peek.inspect}"
|
147
|
+
end
|
148
|
+
hash[nice_name] = $&.index('.') ? shift.to_f : shift.to_i
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
@trailing_non_opts = @args
|
153
|
+
|
154
|
+
check_required! hash
|
155
|
+
hash.freeze
|
156
|
+
hash
|
157
|
+
end
|
158
|
+
|
159
|
+
def formatted_usage
|
160
|
+
return "" if @switches.empty?
|
161
|
+
@switches.map do |opt, type|
|
162
|
+
case type
|
163
|
+
when :boolean
|
164
|
+
"[#{opt}]"
|
165
|
+
when :required
|
166
|
+
opt + "=" + opt.gsub(/\-/, "").upcase
|
167
|
+
else
|
168
|
+
sample = @defaults[undasherize(opt)]
|
169
|
+
sample ||= case type
|
170
|
+
when :optional then opt.gsub(/\-/, "").upcase
|
171
|
+
when :numeric then "N"
|
172
|
+
end
|
173
|
+
"[" + opt + "=" + sample.to_s + "]"
|
174
|
+
end
|
175
|
+
end.join(" ")
|
176
|
+
end
|
177
|
+
|
178
|
+
private
|
179
|
+
|
180
|
+
def assert_value!(switch)
|
181
|
+
raise Error, "no value provided for argument '#{switch}'" if peek.nil?
|
182
|
+
end
|
183
|
+
|
184
|
+
def undasherize(str)
|
185
|
+
str.sub(/^-{1,2}/, '')
|
186
|
+
end
|
187
|
+
|
188
|
+
def dasherize(str)
|
189
|
+
(str.length > 1 ? "--" : "-") + str
|
190
|
+
end
|
191
|
+
|
192
|
+
def peek
|
193
|
+
@args.first
|
194
|
+
end
|
195
|
+
|
196
|
+
def shift
|
197
|
+
@args.shift
|
198
|
+
end
|
199
|
+
|
200
|
+
def unshift(arg)
|
201
|
+
unless arg.kind_of?(Array)
|
202
|
+
@args.unshift(arg)
|
203
|
+
else
|
204
|
+
@args = arg + @args
|
205
|
+
end
|
206
|
+
end
|
207
|
+
|
208
|
+
def valid?(arg)
|
209
|
+
@switches.key?(arg) or @shorts.key?(arg)
|
210
|
+
end
|
211
|
+
|
212
|
+
def current_is_option?
|
213
|
+
case peek
|
214
|
+
when LONG_RE, SHORT_RE, EQ_RE, SHORT_NUM
|
215
|
+
valid?($1)
|
216
|
+
when SHORT_SQ_RE
|
217
|
+
$1.split('').any? { |f| valid?("-#{f}") }
|
218
|
+
end
|
219
|
+
end
|
220
|
+
|
221
|
+
def normalize_switch(switch)
|
222
|
+
@shorts.key?(switch) ? @shorts[switch] : switch
|
223
|
+
end
|
224
|
+
|
225
|
+
def switch_type(switch)
|
226
|
+
@switches[switch]
|
227
|
+
end
|
228
|
+
|
229
|
+
def check_required!(hash)
|
230
|
+
for name, type in @switches
|
231
|
+
if type == :required and !hash[undasherize(name)]
|
232
|
+
raise Error, "no value provided for required argument '#{name}'"
|
233
|
+
end
|
234
|
+
end
|
235
|
+
end
|
236
|
+
|
237
|
+
end
|
238
|
+
end
|
data/lib/thor/runner.rb
CHANGED
@@ -15,8 +15,8 @@ class Thor::Runner < Thor
|
|
15
15
|
map "-T" => :list, "-i" => :install, "-u" => :update
|
16
16
|
|
17
17
|
desc "install NAME", "install a Thor file into your system tasks, optionally named for future updates"
|
18
|
-
method_options :as => :optional
|
19
|
-
def install(name
|
18
|
+
method_options :as => :optional, :relative => :boolean
|
19
|
+
def install(name)
|
20
20
|
initialize_thorfiles
|
21
21
|
begin
|
22
22
|
contents = open(name).read
|
@@ -33,13 +33,13 @@ class Thor::Runner < Thor
|
|
33
33
|
print "Do you wish to continue [y/N]? "
|
34
34
|
response = Readline.readline
|
35
35
|
|
36
|
-
return unless response =~ /^\s*y/i
|
36
|
+
return false unless response =~ /^\s*y/i
|
37
37
|
|
38
38
|
constants = Thor::Util.constants_in_contents(contents)
|
39
39
|
|
40
|
-
name = name =~ /\.thor$/ || is_uri ? name : "#{name}.thor"
|
40
|
+
# name = name =~ /\.thor$/ || is_uri ? name : "#{name}.thor"
|
41
41
|
|
42
|
-
as =
|
42
|
+
as = options["as"] || begin
|
43
43
|
first_line = contents.split("\n")[0]
|
44
44
|
(match = first_line.match(/\s*#\s*module:\s*([^\n]*)/)) ? match[1].strip : nil
|
45
45
|
end
|
@@ -56,15 +56,18 @@ class Thor::Runner < Thor
|
|
56
56
|
FileUtils.touch(yaml_file)
|
57
57
|
yaml = thor_yaml
|
58
58
|
|
59
|
-
|
59
|
+
location = (options[:relative] || is_uri) ? name : File.expand_path(name)
|
60
|
+
yaml[as] = {:filename => Digest::MD5.hexdigest(name + as), :location => location, :constants => constants}
|
60
61
|
|
61
62
|
save_yaml(yaml)
|
62
63
|
|
63
64
|
puts "Storing thor file in your system repository"
|
64
65
|
|
65
|
-
File.open(File.join(thor_root, yaml[as][:filename]
|
66
|
+
File.open(File.join(thor_root, yaml[as][:filename]), "w") do |file|
|
66
67
|
file.puts contents
|
67
68
|
end
|
69
|
+
|
70
|
+
yaml[as][:filename] # Indicate sucess
|
68
71
|
end
|
69
72
|
|
70
73
|
desc "uninstall NAME", "uninstall a named Thor module"
|
@@ -74,7 +77,7 @@ class Thor::Runner < Thor
|
|
74
77
|
|
75
78
|
puts "Uninstalling #{name}."
|
76
79
|
|
77
|
-
file = File.join(thor_root, "#{yaml[name][:filename]}
|
80
|
+
file = File.join(thor_root, "#{yaml[name][:filename]}")
|
78
81
|
File.delete(file)
|
79
82
|
yaml.delete(name)
|
80
83
|
save_yaml(yaml)
|
@@ -88,24 +91,30 @@ class Thor::Runner < Thor
|
|
88
91
|
raise Error, "Can't find module `#{name}'" if !yaml[name] || !yaml[name][:location]
|
89
92
|
|
90
93
|
puts "Updating `#{name}' from #{yaml[name][:location]}"
|
91
|
-
|
94
|
+
old_filename = yaml[name][:filename]
|
95
|
+
options["as"] = name
|
96
|
+
filename = install(yaml[name][:location])
|
97
|
+
unless filename == old_filename
|
98
|
+
File.delete(File.join(thor_root, old_filename))
|
99
|
+
end
|
92
100
|
end
|
93
101
|
|
94
102
|
desc "installed", "list the installed Thor modules and tasks (--internal means list the built-in tasks as well)"
|
95
103
|
method_options :internal => :boolean
|
96
|
-
def installed
|
97
|
-
|
98
|
-
|
104
|
+
def installed
|
105
|
+
thor_root_glob.each do |f|
|
106
|
+
next if f =~ /thor\.yml$/
|
107
|
+
load_thorfile f unless Thor.subclass_files.keys.include?(File.expand_path(f))
|
99
108
|
end
|
100
109
|
|
101
110
|
klasses = Thor.subclasses
|
102
|
-
klasses -= [Thor, Thor::Runner] unless
|
111
|
+
klasses -= [Thor, Thor::Runner] unless options['internal']
|
103
112
|
display_klasses(true, klasses)
|
104
113
|
end
|
105
114
|
|
106
115
|
desc "list [SEARCH]", "list the available thor tasks (--substring means SEARCH can be anywhere in the module)"
|
107
116
|
method_options :substring => :boolean
|
108
|
-
def list(search = ""
|
117
|
+
def list(search = "")
|
109
118
|
initialize_thorfiles
|
110
119
|
search = ".*#{search}" if options["substring"]
|
111
120
|
search = /^#{search}.*/i
|
@@ -125,12 +134,30 @@ class Thor::Runner < Thor
|
|
125
134
|
super(meth.to_sym, *args) unless meth.include? ?:
|
126
135
|
|
127
136
|
initialize_thorfiles(meth)
|
128
|
-
Thor[meth]
|
137
|
+
task = Thor[meth]
|
138
|
+
task.parse task.klass.new, ARGV[1..-1]
|
139
|
+
end
|
140
|
+
|
141
|
+
def self.thor_root
|
142
|
+
File.join(ENV["HOME"] || ENV["APPDATA"], ".thor")
|
143
|
+
end
|
144
|
+
|
145
|
+
def self.thor_root_glob
|
146
|
+
# On Windows thor_root will be something like this:
|
147
|
+
#
|
148
|
+
# C:\Documents and Settings\james\.thor
|
149
|
+
#
|
150
|
+
# If we don't #gsub the \ character, Dir.glob will fail.
|
151
|
+
Dir["#{thor_root.gsub(/\\/, '/')}/**/*"]
|
129
152
|
end
|
130
153
|
|
131
154
|
private
|
132
155
|
def thor_root
|
133
|
-
|
156
|
+
self.class.thor_root
|
157
|
+
end
|
158
|
+
|
159
|
+
def thor_root_glob
|
160
|
+
self.class.thor_root_glob
|
134
161
|
end
|
135
162
|
|
136
163
|
def thor_yaml
|
@@ -150,14 +177,17 @@ class Thor::Runner < Thor
|
|
150
177
|
|
151
178
|
if with_modules && !(yaml = thor_yaml).empty?
|
152
179
|
max_name = yaml.max {|(xk,xv),(yk,yv)| xk.size <=> yk.size }.first.size
|
180
|
+
modules_label = "Modules"
|
181
|
+
namespaces_label = "Namespaces"
|
182
|
+
column_width = [max_name + 4, modules_label.size + 1].max
|
153
183
|
|
154
|
-
print "%-#{
|
155
|
-
puts
|
156
|
-
print "%-#{
|
157
|
-
puts "
|
184
|
+
print "%-#{column_width}s" % modules_label
|
185
|
+
puts namespaces_label
|
186
|
+
print "%-#{column_width}s" % ("-" * modules_label.size)
|
187
|
+
puts "-" * namespaces_label.size
|
158
188
|
|
159
189
|
yaml.each do |name, info|
|
160
|
-
print "%-#{
|
190
|
+
print "%-#{column_width}s" % name
|
161
191
|
puts info[:constants].map {|c| Thor::Util.constant_to_thor_path(c)}.join(", ")
|
162
192
|
end
|
163
193
|
|
@@ -192,7 +222,15 @@ class Thor::Runner < Thor
|
|
192
222
|
end
|
193
223
|
|
194
224
|
def initialize_thorfiles(relevant_to = nil)
|
195
|
-
thorfiles(relevant_to).each {|f|
|
225
|
+
thorfiles(relevant_to).each {|f| load_thorfile f unless Thor.subclass_files.keys.include?(File.expand_path(f))}
|
226
|
+
end
|
227
|
+
|
228
|
+
def load_thorfile(path)
|
229
|
+
begin
|
230
|
+
load path
|
231
|
+
rescue Object => e
|
232
|
+
$stderr.puts "WARNING: unable to load thorfile #{path.inspect}: #{e.message}"
|
233
|
+
end
|
196
234
|
end
|
197
235
|
|
198
236
|
def thorfiles(relevant_to = nil)
|
@@ -201,7 +239,7 @@ class Thor::Runner < Thor
|
|
201
239
|
|
202
240
|
# Look for Thorfile or *.thor in the current directory or a parent directory, until the root
|
203
241
|
while thorfiles.empty?
|
204
|
-
thorfiles =
|
242
|
+
thorfiles = Thor::Runner.globs_for(path).map {|g| Dir[g]}.flatten
|
205
243
|
path = File.dirname(path)
|
206
244
|
break if path == "/"
|
207
245
|
end
|
@@ -209,14 +247,14 @@ class Thor::Runner < Thor
|
|
209
247
|
# We want to load system-wide Thorfiles first
|
210
248
|
# so the local Thorfiles will override them.
|
211
249
|
(relevant_to ? thorfiles_relevant_to(relevant_to) :
|
212
|
-
|
250
|
+
thor_root_glob) + thorfiles - ["#{thor_root}/thor.yml"]
|
213
251
|
end
|
214
252
|
|
215
253
|
def thorfiles_relevant_to(meth)
|
216
254
|
klass_str = Thor::Util.to_constant(meth.split(":")[0...-1].join(":"))
|
217
255
|
thor_yaml.select do |k, v|
|
218
256
|
v[:constants] && v[:constants].include?(klass_str)
|
219
|
-
end.map { |k, v| File.join(thor_root, "#{v[:filename]}
|
257
|
+
end.map { |k, v| File.join(thor_root, "#{v[:filename]}") }
|
220
258
|
end
|
221
259
|
|
222
260
|
end
|
data/lib/thor/task.rb
CHANGED
@@ -7,21 +7,32 @@ class Thor
|
|
7
7
|
new(meth, "A dynamically-generated task", meth.to_s, nil, klass)
|
8
8
|
end
|
9
9
|
|
10
|
-
def parse(args)
|
11
|
-
|
10
|
+
def parse(obj, args)
|
11
|
+
list, hash = parse_args(args)
|
12
|
+
obj.options = hash
|
13
|
+
run(obj, *list)
|
12
14
|
end
|
13
15
|
|
14
|
-
def run(*params)
|
15
|
-
raise
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
klass.new.send(meth, *params)
|
16
|
+
def run(obj, *params)
|
17
|
+
raise NoMethodError, "the `#{meth}' task of #{obj.class} is private" if
|
18
|
+
(obj.private_methods + obj.protected_methods).include?(meth)
|
19
|
+
|
20
|
+
obj.send(meth, *params)
|
20
21
|
rescue ArgumentError => e
|
21
|
-
|
22
|
+
# backtrace sans anything in this file
|
23
|
+
backtrace = e.backtrace.reject {|frame| frame =~ /^#{Regexp.escape(__FILE__)}/}
|
24
|
+
# and sans anything that got us here
|
25
|
+
backtrace -= caller
|
26
|
+
raise e unless backtrace.empty?
|
27
|
+
|
28
|
+
# okay, they really did call it wrong
|
22
29
|
raise Error, "`#{meth}' was called incorrectly. Call as `#{formatted_usage}'"
|
23
30
|
rescue NoMethodError => e
|
24
|
-
|
31
|
+
begin
|
32
|
+
raise e unless e.message =~ /^undefined method `#{meth}' for #{Regexp.escape(obj.inspect)}$/
|
33
|
+
rescue
|
34
|
+
raise e
|
35
|
+
end
|
25
36
|
raise Error, "The #{namespace false} namespace doesn't have a `#{meth}' task"
|
26
37
|
end
|
27
38
|
|
@@ -34,41 +45,24 @@ class Thor
|
|
34
45
|
new.klass = klass
|
35
46
|
new
|
36
47
|
end
|
37
|
-
|
38
|
-
def
|
39
|
-
return
|
40
|
-
opts
|
41
|
-
if val == true || val == "BOOLEAN"
|
42
|
-
"[#{opt}]"
|
43
|
-
elsif val == "REQUIRED"
|
44
|
-
opt + "=" + opt.gsub(/\-/, "").upcase
|
45
|
-
elsif val == "OPTIONAL"
|
46
|
-
"[" + opt + "=" + opt.gsub(/\-/, "").upcase + "]"
|
47
|
-
end
|
48
|
-
end.join(" ")
|
48
|
+
|
49
|
+
def opts
|
50
|
+
return super unless super.kind_of? Hash
|
51
|
+
self.opts = Options.new(super)
|
49
52
|
end
|
50
53
|
|
51
54
|
def formatted_usage(namespace = false)
|
52
55
|
(namespace ? self.namespace + ':' : '') + usage +
|
53
|
-
(opts ? " " +
|
56
|
+
(opts ? " " + opts.formatted_usage : "")
|
54
57
|
end
|
55
58
|
|
56
59
|
protected
|
57
60
|
|
58
61
|
def parse_args(args)
|
59
|
-
return args unless opts
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
params << args.shift until args.empty? || args.first[0] == ?-
|
64
|
-
|
65
|
-
old_argv = ARGV.dup
|
66
|
-
ARGV.replace args
|
67
|
-
options = Getopt::Long.getopts(*opts.map do |opt, val|
|
68
|
-
[opt, val == true ? Getopt::BOOLEAN : Getopt.const_get(val)].flatten
|
69
|
-
end)
|
70
|
-
ARGV.replace old_argv
|
71
|
-
params + [options]
|
62
|
+
return [args, {}] unless opts
|
63
|
+
hash = opts.parse(args)
|
64
|
+
list = opts.non_opts
|
65
|
+
[list, hash]
|
72
66
|
end
|
73
67
|
end
|
74
68
|
end
|
data/lib/thor/task_hash.rb
CHANGED
data/lib/thor/tasks.rb
CHANGED
@@ -13,13 +13,16 @@ class Thor
|
|
13
13
|
|
14
14
|
def self.install_task(spec)
|
15
15
|
package_task spec
|
16
|
+
|
17
|
+
null, sudo, gem = RUBY_PLATFORM =~ /w(in)?32$/ ? ['NUL', '', 'gem.bat'] :
|
18
|
+
['/dev/null', 'sudo', 'gem']
|
16
19
|
|
17
20
|
desc "install", "install the gem"
|
18
21
|
define_method :install do
|
19
|
-
old_stderr, $stderr = $stderr.dup, File.open(
|
22
|
+
old_stderr, $stderr = $stderr.dup, File.open(null, "w")
|
20
23
|
package
|
21
24
|
$stderr = old_stderr
|
22
|
-
system %{sudo gem install pkg/#{spec.name}-#{spec.version} --no-rdoc --no-ri --no-update-sources}
|
25
|
+
system %{#{sudo} #{gem} install pkg/#{spec.name}-#{spec.version} --no-rdoc --no-ri --no-update-sources}
|
23
26
|
end
|
24
27
|
end
|
25
28
|
|
@@ -52,16 +55,20 @@ class Thor
|
|
52
55
|
cmd << options
|
53
56
|
puts cmd if verbose
|
54
57
|
system(cmd)
|
58
|
+
exit($?.exitstatus)
|
55
59
|
end
|
56
60
|
end
|
57
61
|
|
58
62
|
private
|
59
63
|
def self.convert_task_options(opts)
|
60
64
|
opts.map do |key, value|
|
61
|
-
|
65
|
+
case value
|
66
|
+
when true
|
62
67
|
"--#{key}"
|
63
|
-
|
68
|
+
when Array
|
64
69
|
value.map {|v| "--#{key} #{v.inspect}"}.join(" ")
|
70
|
+
when nil, false
|
71
|
+
""
|
65
72
|
else
|
66
73
|
"--#{key} #{value.inspect}"
|
67
74
|
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
require "thor/task"
|
2
|
+
|
3
|
+
class Thor::PackageTask < Thor::Task
|
4
|
+
attr_accessor :spec
|
5
|
+
attr_accessor :opts
|
6
|
+
|
7
|
+
def initialize(gemspec, opts = {})
|
8
|
+
super(:package, "build a gem package")
|
9
|
+
@spec = gemspec
|
10
|
+
@opts = {:dir => File.join(Dir.pwd, "pkg")}.merge(opts)
|
11
|
+
end
|
12
|
+
|
13
|
+
def run
|
14
|
+
FileUtils.mkdir_p(@opts[:dir])
|
15
|
+
Gem::Builder.new(spec).build
|
16
|
+
FileUtils.mv(spec.file_name, File.join(@opts[:dir], spec.file_name))
|
17
|
+
end
|
18
|
+
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: wycats-thor
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.9.
|
4
|
+
version: 0.9.5
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Yehuda Katz
|
@@ -9,7 +9,7 @@ autorequire:
|
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
11
|
|
12
|
-
date: 2008-
|
12
|
+
date: 2008-08-27 00:00:00 -07:00
|
13
13
|
default_executable:
|
14
14
|
dependencies: []
|
15
15
|
|
@@ -22,20 +22,24 @@ extensions: []
|
|
22
22
|
|
23
23
|
extra_rdoc_files:
|
24
24
|
- README.markdown
|
25
|
+
- CHANGELOG.rdoc
|
25
26
|
- LICENSE
|
26
27
|
files:
|
27
|
-
- LICENSE
|
28
28
|
- README.markdown
|
29
|
+
- LICENSE
|
30
|
+
- CHANGELOG.rdoc
|
29
31
|
- Rakefile
|
30
32
|
- bin/rake2thor
|
31
33
|
- bin/thor
|
32
|
-
- lib/getopt.rb
|
33
34
|
- lib/thor
|
34
35
|
- lib/thor/error.rb
|
36
|
+
- lib/thor/options.rb
|
35
37
|
- lib/thor/ordered_hash.rb
|
36
38
|
- lib/thor/runner.rb
|
37
39
|
- lib/thor/task.rb
|
38
40
|
- lib/thor/task_hash.rb
|
41
|
+
- lib/thor/tasks
|
42
|
+
- lib/thor/tasks/package.rb
|
39
43
|
- lib/thor/tasks.rb
|
40
44
|
- lib/thor/util.rb
|
41
45
|
- lib/thor.rb
|
@@ -61,7 +65,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
61
65
|
requirements: []
|
62
66
|
|
63
67
|
rubyforge_project: thor
|
64
|
-
rubygems_version: 1.0
|
68
|
+
rubygems_version: 1.2.0
|
65
69
|
signing_key:
|
66
70
|
specification_version: 2
|
67
71
|
summary: A gem that maps options to a class
|
data/lib/getopt.rb
DELETED
@@ -1,238 +0,0 @@
|
|
1
|
-
# The last time the Getopt gem was modified was August 2007, so it's safe to vendor (it does everything we need)
|
2
|
-
|
3
|
-
module Getopt
|
4
|
-
|
5
|
-
REQUIRED = 0
|
6
|
-
BOOLEAN = 1
|
7
|
-
OPTIONAL = 2
|
8
|
-
INCREMENT = 3
|
9
|
-
NEGATABLE = 4
|
10
|
-
NUMERIC = 5
|
11
|
-
|
12
|
-
class Long
|
13
|
-
class Error < StandardError; end
|
14
|
-
|
15
|
-
VERSION = '1.3.6'
|
16
|
-
|
17
|
-
# Takes an array of switches. Each array consists of up to three
|
18
|
-
# elements that indicate the name and type of switch. Returns a hash
|
19
|
-
# containing each switch name, minus the '-', as a key. The value
|
20
|
-
# for each key depends on the type of switch and/or the value provided
|
21
|
-
# by the user.
|
22
|
-
#
|
23
|
-
# The long switch _must_ be provided. The short switch defaults to the
|
24
|
-
# first letter of the short switch. The default type is BOOLEAN.
|
25
|
-
#
|
26
|
-
# Example:
|
27
|
-
#
|
28
|
-
# opts = Getopt::Long.getopts(
|
29
|
-
# ["--debug"],
|
30
|
-
# ["--verbose", "-v"],
|
31
|
-
# ["--level", "-l", NUMERIC]
|
32
|
-
# )
|
33
|
-
#
|
34
|
-
# See the README file for more information.
|
35
|
-
#
|
36
|
-
def self.getopts(*switches)
|
37
|
-
if switches.empty?
|
38
|
-
raise ArgumentError, "no switches provided"
|
39
|
-
end
|
40
|
-
|
41
|
-
hash = {} # Hash returned to user
|
42
|
-
valid = [] # Tracks valid switches
|
43
|
-
types = {} # Tracks argument types
|
44
|
-
syns = {} # Tracks long and short arguments, or multiple shorts
|
45
|
-
|
46
|
-
# If a string is passed, split it and convert it to an array of arrays
|
47
|
-
if switches.first.kind_of?(String)
|
48
|
-
switches = switches.join.split
|
49
|
-
switches.map!{ |switch| switch = [switch] }
|
50
|
-
end
|
51
|
-
|
52
|
-
# Set our list of valid switches, and proper types for each switch
|
53
|
-
switches.each{ |switch|
|
54
|
-
valid.push(switch[0]) # Set valid long switches
|
55
|
-
|
56
|
-
# Set type for long switch, default to BOOLEAN.
|
57
|
-
if switch[1].kind_of?(Fixnum)
|
58
|
-
switch[2] = switch[1]
|
59
|
-
types[switch[0]] = switch[2]
|
60
|
-
switch[1] = switch[0][1..2]
|
61
|
-
else
|
62
|
-
switch[2] ||= BOOLEAN
|
63
|
-
types[switch[0]] = switch[2]
|
64
|
-
switch[1] ||= switch[0][1..2]
|
65
|
-
end
|
66
|
-
|
67
|
-
# Create synonym hash. Default to first char of long switch for
|
68
|
-
# short switch, e.g. "--verbose" creates a "-v" synonym. The same
|
69
|
-
# synonym can only be used once - first one wins.
|
70
|
-
syns[switch[0]] = switch[1] unless syns[switch[1]]
|
71
|
-
syns[switch[1]] = switch[0] unless syns[switch[1]]
|
72
|
-
|
73
|
-
switch[1].each{ |char|
|
74
|
-
types[char] = switch[2] # Set type for short switch
|
75
|
-
valid.push(char) # Set valid short switches
|
76
|
-
}
|
77
|
-
|
78
|
-
if ARGV.empty? && switch[2] == REQUIRED
|
79
|
-
raise Error, "no value provided for required argument '#{switch[0]}'"
|
80
|
-
end
|
81
|
-
}
|
82
|
-
|
83
|
-
re_long = /^(--\w+[-\w+]*)?$/
|
84
|
-
re_short = /^(-\w)$/
|
85
|
-
re_long_eq = /^(--\w+[-\w+]*)?=(.*?)$|(-\w?)=(.*?)$/
|
86
|
-
re_short_sq = /^(-\w)(\S+?)$/
|
87
|
-
|
88
|
-
ARGV.each_with_index{ |opt, index|
|
89
|
-
|
90
|
-
# Allow either -x -v or -xv style for single char args
|
91
|
-
if re_short_sq.match(opt)
|
92
|
-
chars = opt.split("")[1..-1].map{ |s| s = "-#{s}" }
|
93
|
-
|
94
|
-
chars.each_with_index{ |char, i|
|
95
|
-
unless valid.include?(char)
|
96
|
-
raise Error, "invalid switch '#{char}'"
|
97
|
-
end
|
98
|
-
|
99
|
-
# Grab the next arg if the switch takes a required arg
|
100
|
-
if types[char] == REQUIRED
|
101
|
-
# Deal with a argument squished up against switch
|
102
|
-
if chars[i+1]
|
103
|
-
arg = chars[i+1..-1].join.tr("-","")
|
104
|
-
ARGV.push(char, arg)
|
105
|
-
break
|
106
|
-
else
|
107
|
-
arg = ARGV.delete_at(index+1)
|
108
|
-
if arg.nil? || valid.include?(arg) # Minor cheat here
|
109
|
-
err = "no value provided for required argument '#{char}'"
|
110
|
-
raise Error, err
|
111
|
-
end
|
112
|
-
ARGV.push(char, arg)
|
113
|
-
end
|
114
|
-
elsif types[char] == OPTIONAL
|
115
|
-
if chars[i+1] && !valid.include?(chars[i+1])
|
116
|
-
arg = chars[i+1..-1].join.tr("-","")
|
117
|
-
ARGV.push(char, arg)
|
118
|
-
break
|
119
|
-
elsif
|
120
|
-
if ARGV[index+1] && !valid.include?(ARGV[index+1])
|
121
|
-
arg = ARGV.delete_at(index+1)
|
122
|
-
ARGV.push(char, arg)
|
123
|
-
end
|
124
|
-
else
|
125
|
-
ARGV.push(char)
|
126
|
-
end
|
127
|
-
else
|
128
|
-
ARGV.push(char)
|
129
|
-
end
|
130
|
-
}
|
131
|
-
next
|
132
|
-
end
|
133
|
-
|
134
|
-
if match = re_long.match(opt) || match = re_short.match(opt)
|
135
|
-
switch = match.captures.first
|
136
|
-
end
|
137
|
-
|
138
|
-
if match = re_long_eq.match(opt)
|
139
|
-
switch, value = match.captures.compact
|
140
|
-
ARGV.push(switch, value)
|
141
|
-
next
|
142
|
-
end
|
143
|
-
|
144
|
-
# Make sure that all the switches are valid. If 'switch' isn't
|
145
|
-
# defined at this point, it means an option was passed without
|
146
|
-
# a preceding switch, e.g. --option foo bar.
|
147
|
-
unless valid.include?(switch)
|
148
|
-
switch ||= opt
|
149
|
-
raise Error, "invalid switch '#{switch}'"
|
150
|
-
end
|
151
|
-
|
152
|
-
# Required arguments
|
153
|
-
if types[switch] == REQUIRED
|
154
|
-
nextval = ARGV[index+1]
|
155
|
-
|
156
|
-
# Make sure there's a value for mandatory arguments
|
157
|
-
if nextval.nil?
|
158
|
-
err = "no value provided for required argument '#{switch}'"
|
159
|
-
raise Error, err
|
160
|
-
end
|
161
|
-
|
162
|
-
# If there is a value, make sure it's not another switch
|
163
|
-
if valid.include?(nextval)
|
164
|
-
err = "cannot pass switch '#{nextval}' as an argument"
|
165
|
-
raise Error, err
|
166
|
-
end
|
167
|
-
|
168
|
-
# If the same option appears more than once, put the values
|
169
|
-
# in array.
|
170
|
-
if hash[switch]
|
171
|
-
hash[switch] = [hash[switch], nextval].flatten
|
172
|
-
else
|
173
|
-
hash[switch] = nextval
|
174
|
-
end
|
175
|
-
ARGV.delete_at(index+1)
|
176
|
-
end
|
177
|
-
|
178
|
-
# For boolean arguments set the switch's value to true.
|
179
|
-
if types[switch] == BOOLEAN
|
180
|
-
if hash.has_key?(switch)
|
181
|
-
raise Error, "boolean switch already set"
|
182
|
-
end
|
183
|
-
hash[switch] = true
|
184
|
-
end
|
185
|
-
|
186
|
-
# For increment arguments, set the switch's value to 0, or
|
187
|
-
# increment it by one if it already exists.
|
188
|
-
if types[switch] == INCREMENT
|
189
|
-
if hash.has_key?(switch)
|
190
|
-
hash[switch] += 1
|
191
|
-
else
|
192
|
-
hash[switch] = 1
|
193
|
-
end
|
194
|
-
end
|
195
|
-
|
196
|
-
# For optional argument, there may be an argument. If so, it
|
197
|
-
# cannot be another switch. If not, it is set to true.
|
198
|
-
if types[switch] == OPTIONAL
|
199
|
-
nextval = ARGV[index+1]
|
200
|
-
if valid.include?(nextval)
|
201
|
-
hash[switch] = true
|
202
|
-
else
|
203
|
-
hash[switch] = nextval
|
204
|
-
ARGV.delete_at(index+1)
|
205
|
-
end
|
206
|
-
end
|
207
|
-
}
|
208
|
-
|
209
|
-
# Set synonymous switches to the same value, e.g. if -t is a synonym
|
210
|
-
# for --test, and the user passes "--test", then set "-t" to the same
|
211
|
-
# value that "--test" was set to.
|
212
|
-
#
|
213
|
-
# This allows users to refer to the long or short switch and get
|
214
|
-
# the same value
|
215
|
-
hash.each{ |switch, val|
|
216
|
-
if syns.keys.include?(switch)
|
217
|
-
syns[switch].each{ |key|
|
218
|
-
hash[key] = val
|
219
|
-
}
|
220
|
-
end
|
221
|
-
}
|
222
|
-
|
223
|
-
# Get rid of leading "--" and "-" to make it easier to reference
|
224
|
-
hash.each{ |key, value|
|
225
|
-
if key[0,2] == '--'
|
226
|
-
nkey = key.sub('--', '')
|
227
|
-
else
|
228
|
-
nkey = key.sub('-', '')
|
229
|
-
end
|
230
|
-
hash.delete(key)
|
231
|
-
hash[nkey] = value
|
232
|
-
}
|
233
|
-
|
234
|
-
hash
|
235
|
-
end
|
236
|
-
|
237
|
-
end
|
238
|
-
end
|