wycats-thor 0.9.2 → 0.9.5
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/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
|