mislav-thor 0.9.5 → 0.9.10
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 +52 -0
- data/README.markdown +36 -18
- data/Rakefile +1 -1
- data/lib/thor.rb +47 -18
- data/lib/thor/options.rb +211 -100
- data/lib/thor/runner.rb +89 -39
- data/lib/thor/task.rb +26 -23
- data/lib/thor/tasks.rb +5 -2
- data/lib/thor/util.rb +41 -9
- metadata +5 -3
data/CHANGELOG.rdoc
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
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.8, released 2008-10-20
|
|
9
|
+
|
|
10
|
+
* Fixed some tiny issues that were introduced lately.
|
|
11
|
+
|
|
12
|
+
== 0.9.7, released 2008-10-13
|
|
13
|
+
|
|
14
|
+
* Setting global method options on the initialize method works as expected:
|
|
15
|
+
All other tasks will accept these global options in addition to their own.
|
|
16
|
+
* Added 'group' notion to Thor task sets (class Thor); by default all tasks
|
|
17
|
+
are in the 'standard' group. Running 'thor -T' will only show the standard
|
|
18
|
+
tasks - adding --all will show all tasks. You can also filter on a specific
|
|
19
|
+
group using the --group option: thor -T --group advanced
|
|
20
|
+
|
|
21
|
+
== 0.9.6, released 2008-09-13
|
|
22
|
+
|
|
23
|
+
* Generic improvements
|
|
24
|
+
|
|
25
|
+
== 0.9.5, released 2008-08-27
|
|
26
|
+
|
|
27
|
+
* Improve Windows compatibility
|
|
28
|
+
* Update (incorrect) README and task.thor sample file
|
|
29
|
+
* Options hash is now frozen (once returned)
|
|
30
|
+
* Allow magic predicates on options object. For instance: `options.force?`
|
|
31
|
+
* Add support for :numeric type
|
|
32
|
+
* BACKWARDS INCOMPATIBLE: Refactor Thor::Options. You cannot access shorthand forms in options hash anymore (for instance, options[:f])
|
|
33
|
+
* Allow specifying optional args with default values: method_options(:user => "mislav")
|
|
34
|
+
* Don't write options for nil or false values. This allows, for example, turning color off when running specs.
|
|
35
|
+
* Exit with the status of the spec command to help CI stuff out some.
|
|
36
|
+
|
|
37
|
+
== 0.9.4, released 2008-08-13
|
|
38
|
+
|
|
39
|
+
* Try to add Windows compatibility.
|
|
40
|
+
* BACKWARDS INCOMPATIBLE: options hash is now accessed as a property in your class and is not passed as last argument anymore
|
|
41
|
+
* Allow options at the beginning of the argument list as well as the end.
|
|
42
|
+
* Make options available with symbol keys in addition to string keys.
|
|
43
|
+
* Allow true to be passed to Thor#method_options to denote a boolean option.
|
|
44
|
+
* If loading a thor file fails, don't give up, just print a warning and keep going.
|
|
45
|
+
* Make sure that we re-raise errors if they happened further down the pipe than we care about.
|
|
46
|
+
* Only delete the old file on updating when the installation of the new one is a success
|
|
47
|
+
* Make it Ruby 1.8.5 compatible.
|
|
48
|
+
* Don't raise an error if a boolean switch is defined multiple times.
|
|
49
|
+
* Thor::Options now doesn't parse through things that look like options but aren't.
|
|
50
|
+
* Add URI detection to install task, and make sure we don't append ".thor" to URIs
|
|
51
|
+
* Add rake2thor to the gem binfiles.
|
|
52
|
+
* Make sure local Thorfiles override system-wide ones.
|
data/README.markdown
CHANGED
|
@@ -10,49 +10,67 @@ Example:
|
|
|
10
10
|
map "-L" => :list # [2]
|
|
11
11
|
|
|
12
12
|
desc "install APP_NAME", "install one of the available apps" # [3]
|
|
13
|
-
method_options :force => :boolean
|
|
14
|
-
def install(name
|
|
15
|
-
|
|
16
|
-
if
|
|
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("
|
|
34
|
+
MyApp.new.install("myname")
|
|
35
|
+
# with {'force' => true} as options hash
|
|
37
36
|
|
|
38
37
|
1. Inherit from Thor to turn a class into an option mapper
|
|
39
38
|
2. Map additional non-valid identifiers to specific methods. In this case,
|
|
40
39
|
convert -L to :list
|
|
41
40
|
3. Describe the method immediately below. The first parameter is the usage information,
|
|
42
41
|
and the second parameter is the description.
|
|
43
|
-
4. Provide any additional options. These will be marshaled from
|
|
44
|
-
In this case, a
|
|
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.
|
|
45
44
|
|
|
46
45
|
Types for `method_options`
|
|
47
46
|
--------------------------
|
|
48
47
|
|
|
49
48
|
<dl>
|
|
50
49
|
<dt><code>:boolean</code></dt>
|
|
51
|
-
|
|
50
|
+
<dd>true if the option is passed</dd>
|
|
51
|
+
<dt><code>true or false</code></dt>
|
|
52
|
+
<dd>same as <code>:boolean</code>, but fall back to given boolean as default value</dd>
|
|
52
53
|
<dt><code>:required</code></dt>
|
|
53
|
-
|
|
54
|
+
<dd>the value for this option MUST be provided</dd>
|
|
54
55
|
<dt><code>:optional</code></dt>
|
|
55
|
-
|
|
56
|
-
<dt
|
|
57
|
-
|
|
58
|
-
</
|
|
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
|
@@ -7,6 +7,13 @@ require "thor/task_hash"
|
|
|
7
7
|
class Thor
|
|
8
8
|
attr_accessor :options
|
|
9
9
|
|
|
10
|
+
def self.default_task(meth=nil)
|
|
11
|
+
unless meth.nil?
|
|
12
|
+
@default_task = (meth == :none) ? 'help' : meth.to_s
|
|
13
|
+
end
|
|
14
|
+
@default_task ||= (self == Thor ? 'help' : superclass.default_task)
|
|
15
|
+
end
|
|
16
|
+
|
|
10
17
|
def self.map(map)
|
|
11
18
|
@map ||= superclass.instance_variable_get("@map") || {}
|
|
12
19
|
map.each do |key, value|
|
|
@@ -22,10 +29,16 @@ class Thor
|
|
|
22
29
|
@usage, @desc = usage, description
|
|
23
30
|
end
|
|
24
31
|
|
|
32
|
+
def self.group(name)
|
|
33
|
+
@group_name = name.to_s
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def self.group_name
|
|
37
|
+
@group_name || 'standard'
|
|
38
|
+
end
|
|
39
|
+
|
|
25
40
|
def self.method_options(opts)
|
|
26
|
-
@method_options =
|
|
27
|
-
accum.merge("--" + k.to_s => v)
|
|
28
|
-
end
|
|
41
|
+
@method_options = (@method_options || {}).merge(opts)
|
|
29
42
|
end
|
|
30
43
|
|
|
31
44
|
def self.subclass_files
|
|
@@ -55,25 +68,42 @@ class Thor
|
|
|
55
68
|
@maxima ||= begin
|
|
56
69
|
max_usage = tasks.map {|_, t| t.usage}.max {|x,y| x.to_s.size <=> y.to_s.size}.size
|
|
57
70
|
max_desc = tasks.map {|_, t| t.description}.max {|x,y| x.to_s.size <=> y.to_s.size}.size
|
|
58
|
-
max_opts = tasks.map {|_, t| t.
|
|
71
|
+
max_opts = tasks.map {|_, t| t.opts ? t.opts.formatted_usage : ""}.max {|x,y| x.to_s.size <=> y.to_s.size}.size
|
|
59
72
|
Struct.new(:description, :usage, :opt).new(max_desc, max_usage, max_opts)
|
|
60
73
|
end
|
|
61
74
|
end
|
|
62
75
|
|
|
63
76
|
def self.start(args = ARGV)
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
77
|
+
|
|
78
|
+
options = Thor::Options.new(self.opts)
|
|
79
|
+
opts = options.parse(args, false)
|
|
80
|
+
args = options.trailing_non_opts
|
|
67
81
|
|
|
68
82
|
meth = args.first
|
|
69
83
|
meth = @map[meth].to_s if @map && @map[meth]
|
|
70
|
-
meth ||=
|
|
84
|
+
meth ||= default_task
|
|
85
|
+
meth = meth.to_s.gsub('-','_') # treat foo-bar > foo_bar
|
|
71
86
|
|
|
72
87
|
tasks[meth].parse new(opts, *args), args[1..-1]
|
|
73
88
|
rescue Thor::Error => e
|
|
74
89
|
$stderr.puts e.message
|
|
75
90
|
end
|
|
91
|
+
|
|
92
|
+
# Invokes a specific task. You can use this method instead of start()
|
|
93
|
+
# to run a thor task if you know the specific task you want to invoke.
|
|
94
|
+
def self.invoke(task_name=nil, args = ARGV)
|
|
95
|
+
args = args.dup
|
|
96
|
+
args.unshift(task_name || default_task)
|
|
97
|
+
start(args)
|
|
98
|
+
end
|
|
76
99
|
|
|
100
|
+
# Main entry point method that should actually invoke the method. You
|
|
101
|
+
# can override this to provide some class-wide processing. The default
|
|
102
|
+
# implementation simply invokes the named method
|
|
103
|
+
def invoke(meth, *args)
|
|
104
|
+
self.send(meth, *args)
|
|
105
|
+
end
|
|
106
|
+
|
|
77
107
|
class << self
|
|
78
108
|
protected
|
|
79
109
|
def inherited(klass)
|
|
@@ -82,7 +112,7 @@ class Thor
|
|
|
82
112
|
|
|
83
113
|
def method_added(meth)
|
|
84
114
|
meth = meth.to_s
|
|
85
|
-
|
|
115
|
+
|
|
86
116
|
if meth == "initialize"
|
|
87
117
|
@opts = @method_options
|
|
88
118
|
@method_options = nil
|
|
@@ -126,15 +156,14 @@ class Thor
|
|
|
126
156
|
|
|
127
157
|
puts task.formatted_usage(namespace)
|
|
128
158
|
puts task.description
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
puts task.description.split("\n").first
|
|
159
|
+
else
|
|
160
|
+
puts "Options"
|
|
161
|
+
puts "-------"
|
|
162
|
+
self.class.tasks.each do |_, task|
|
|
163
|
+
puts task.formatted_usage
|
|
164
|
+
puts ' ' * 4 + task.description.split("\n").first
|
|
165
|
+
puts
|
|
166
|
+
end
|
|
138
167
|
end
|
|
139
168
|
end
|
|
140
169
|
|
data/lib/thor/options.rb
CHANGED
|
@@ -1,57 +1,55 @@
|
|
|
1
|
-
# This is a modified version of Daniel Berger's Getopt::
|
|
1
|
+
# This is a modified version of Daniel Berger's Getopt::Long class,
|
|
2
2
|
# licensed under Ruby's license.
|
|
3
3
|
|
|
4
|
-
require 'set'
|
|
5
|
-
|
|
6
4
|
class Thor
|
|
7
5
|
class Options
|
|
8
6
|
class Error < StandardError; end
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
attr_accessor :args
|
|
16
|
-
|
|
17
|
-
def initialize(args, switches)
|
|
18
|
-
@args = args
|
|
19
|
-
@defaults = {}
|
|
20
|
-
|
|
21
|
-
switches = switches.map do |names, type|
|
|
22
|
-
case type
|
|
23
|
-
when TrueClass then type = :boolean
|
|
24
|
-
when String
|
|
25
|
-
@defaults[names] = type
|
|
26
|
-
type = :optional
|
|
27
|
-
end
|
|
28
|
-
|
|
29
|
-
if names.is_a?(String)
|
|
30
|
-
if names =~ LONG_RE
|
|
31
|
-
names = [names, "-" + names[2].chr]
|
|
32
|
-
else
|
|
33
|
-
names = [names]
|
|
34
|
-
end
|
|
35
|
-
end
|
|
36
|
-
|
|
37
|
-
[names, type]
|
|
7
|
+
|
|
8
|
+
# simple Hash with indifferent access
|
|
9
|
+
class Hash < ::Hash
|
|
10
|
+
def initialize(hash)
|
|
11
|
+
super()
|
|
12
|
+
update hash
|
|
38
13
|
end
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
forms.each {|f| h[f] ||= v}
|
|
43
|
-
h
|
|
14
|
+
|
|
15
|
+
def [](key)
|
|
16
|
+
super convert_key(key)
|
|
44
17
|
end
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
18
|
+
|
|
19
|
+
def values_at(*indices)
|
|
20
|
+
indices.collect { |key| self[convert_key(key)] }
|
|
48
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 = method.to_s
|
|
32
|
+
if method =~ /^(\w+)=$/
|
|
33
|
+
self[$1] = args.first
|
|
34
|
+
elsif method =~ /^(\w+)\?$/
|
|
35
|
+
!!self[$1]
|
|
36
|
+
else
|
|
37
|
+
self[method]
|
|
38
|
+
end
|
|
39
|
+
end
|
|
49
40
|
end
|
|
50
41
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
42
|
+
NUMERIC = /(\d*\.\d+|\d+)/
|
|
43
|
+
LONG_RE = /^(--\w+[-\w+]*)$/
|
|
44
|
+
SHORT_RE = /^(-[a-z])$/i
|
|
45
|
+
EQ_RE = /^(--\w+[-\w+]*|-[a-z])=(.*)$/i
|
|
46
|
+
SHORT_SQ_RE = /^-([a-z]{2,})$/i # Allow either -x -v or -xv style for single char args
|
|
47
|
+
SHORT_NUM = /^(-[a-z])#{NUMERIC}$/i
|
|
48
|
+
|
|
49
|
+
attr_reader :leading_non_opts, :trailing_non_opts
|
|
50
|
+
|
|
51
|
+
def non_opts
|
|
52
|
+
leading_non_opts + trailing_non_opts
|
|
55
53
|
end
|
|
56
54
|
|
|
57
55
|
# Takes an array of switches. Each array consists of up to three
|
|
@@ -65,92 +63,205 @@ class Thor
|
|
|
65
63
|
#
|
|
66
64
|
# Example:
|
|
67
65
|
#
|
|
68
|
-
#
|
|
69
|
-
#
|
|
70
|
-
#
|
|
71
|
-
#
|
|
72
|
-
#
|
|
66
|
+
# opts = Thor::Options.new(
|
|
67
|
+
# "--debug" => true,
|
|
68
|
+
# ["--verbose", "-v"] => true,
|
|
69
|
+
# ["--level", "-l"] => :numeric
|
|
70
|
+
# ).parse(args)
|
|
73
71
|
#
|
|
74
|
-
def
|
|
75
|
-
|
|
72
|
+
def initialize(switches)
|
|
73
|
+
@defaults = {}
|
|
74
|
+
@shorts = {}
|
|
75
|
+
|
|
76
|
+
@leading_non_opts, @trailing_non_opts = [], []
|
|
77
|
+
|
|
78
|
+
@switches = switches.inject({}) do |mem, (name, type)|
|
|
79
|
+
if name.is_a?(Array)
|
|
80
|
+
name, *shorts = name
|
|
81
|
+
else
|
|
82
|
+
name = name.to_s
|
|
83
|
+
shorts = []
|
|
84
|
+
end
|
|
85
|
+
# we need both nice and dasherized form of switch name
|
|
86
|
+
if name.index('-') == 0
|
|
87
|
+
nice_name = undasherize name
|
|
88
|
+
else
|
|
89
|
+
nice_name = name
|
|
90
|
+
name = dasherize name
|
|
91
|
+
end
|
|
92
|
+
# if there are no shortcuts specified, generate one using the first character
|
|
93
|
+
shorts << "-" + nice_name[0,1] if shorts.empty? and nice_name.length > 1
|
|
94
|
+
shorts.each { |short| @shorts[short] = name }
|
|
95
|
+
|
|
96
|
+
# normalize type
|
|
97
|
+
case type
|
|
98
|
+
when TrueClass
|
|
99
|
+
@defaults[nice_name] = true
|
|
100
|
+
type = :boolean
|
|
101
|
+
when FalseClass
|
|
102
|
+
@defaults[nice_name] = false
|
|
103
|
+
type = :boolean
|
|
104
|
+
when String
|
|
105
|
+
@defaults[nice_name] = type
|
|
106
|
+
type = :optional
|
|
107
|
+
when Numeric
|
|
108
|
+
@defaults[nice_name] = type
|
|
109
|
+
type = :numeric
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
mem[name] = type
|
|
113
|
+
mem
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
# remove shortcuts that happen to coincide with any of the main switches
|
|
117
|
+
@shorts.keys.each do |short|
|
|
118
|
+
@shorts.delete(short) if @switches.key?(short)
|
|
119
|
+
end
|
|
120
|
+
end
|
|
76
121
|
|
|
77
|
-
|
|
78
|
-
|
|
122
|
+
def parse(args, skip_leading_non_opts = true)
|
|
123
|
+
@args = args
|
|
124
|
+
# start with Thor::Options::Hash pre-filled with defaults
|
|
125
|
+
hash = Hash.new @defaults
|
|
126
|
+
|
|
127
|
+
@leading_non_opts = []
|
|
128
|
+
if skip_leading_non_opts
|
|
129
|
+
@leading_non_opts << shift until current_is_option? || @args.empty?
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
while current_is_option?
|
|
133
|
+
case shift
|
|
79
134
|
when SHORT_SQ_RE
|
|
80
|
-
|
|
81
|
-
next
|
|
82
|
-
when LONG_EQ_RE
|
|
83
|
-
push($1, $2)
|
|
135
|
+
unshift $1.split('').map { |f| "-#{f}" }
|
|
84
136
|
next
|
|
137
|
+
when EQ_RE, SHORT_NUM
|
|
138
|
+
unshift $2
|
|
139
|
+
switch = $1
|
|
85
140
|
when LONG_RE, SHORT_RE
|
|
86
141
|
switch = $1
|
|
87
142
|
end
|
|
88
|
-
|
|
89
|
-
|
|
143
|
+
|
|
144
|
+
switch = normalize_switch(switch)
|
|
145
|
+
nice_name = undasherize(switch)
|
|
146
|
+
type = switch_type(switch)
|
|
147
|
+
|
|
148
|
+
case type
|
|
90
149
|
when :required
|
|
91
|
-
|
|
92
|
-
raise Error, "cannot pass switch '#{peek}' as an argument" if
|
|
93
|
-
hash[
|
|
94
|
-
when :boolean
|
|
95
|
-
hash[switch] = true
|
|
150
|
+
assert_value!(switch)
|
|
151
|
+
raise Error, "cannot pass switch '#{peek}' as an argument" if valid?(peek)
|
|
152
|
+
hash[nice_name] = shift
|
|
96
153
|
when :optional
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
154
|
+
hash[nice_name] = peek.nil? || valid?(peek) || shift
|
|
155
|
+
when :boolean
|
|
156
|
+
if !@switches.key?(switch) && nice_name =~ /^no-(\w+)$/
|
|
157
|
+
hash[$1] = false
|
|
158
|
+
else
|
|
159
|
+
hash[nice_name] = true
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
when :numeric
|
|
163
|
+
assert_value!(switch)
|
|
164
|
+
unless peek =~ NUMERIC and $& == peek
|
|
165
|
+
raise Error, "expected numeric value for '#{switch}'; got #{peek.inspect}"
|
|
166
|
+
end
|
|
167
|
+
hash[nice_name] = $&.index('.') ? shift.to_f : shift.to_i
|
|
100
168
|
end
|
|
101
169
|
end
|
|
170
|
+
|
|
171
|
+
@trailing_non_opts = @args
|
|
102
172
|
|
|
103
|
-
|
|
104
|
-
|
|
173
|
+
check_required! hash
|
|
174
|
+
hash.freeze
|
|
105
175
|
hash
|
|
106
176
|
end
|
|
107
|
-
|
|
108
|
-
def
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
177
|
+
|
|
178
|
+
def formatted_usage
|
|
179
|
+
return "" if @switches.empty?
|
|
180
|
+
@switches.map do |opt, type|
|
|
181
|
+
case type
|
|
182
|
+
when :boolean
|
|
183
|
+
"[#{opt}]"
|
|
184
|
+
when :required
|
|
185
|
+
opt + "=" + opt.gsub(/\-/, "").upcase
|
|
186
|
+
else
|
|
187
|
+
sample = @defaults[undasherize(opt)]
|
|
188
|
+
sample ||= case type
|
|
189
|
+
when :optional then undasherize(opt).gsub(/\-/, "_").upcase
|
|
190
|
+
when :numeric then "N"
|
|
191
|
+
end
|
|
192
|
+
"[" + opt + "=" + sample.to_s + "]"
|
|
112
193
|
end
|
|
113
|
-
end
|
|
194
|
+
end.join(" ")
|
|
114
195
|
end
|
|
196
|
+
|
|
197
|
+
alias :to_s :formatted_usage
|
|
115
198
|
|
|
116
199
|
private
|
|
117
|
-
|
|
200
|
+
|
|
201
|
+
def assert_value!(switch)
|
|
202
|
+
raise Error, "no value provided for argument '#{switch}'" if peek.nil?
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
def undasherize(str)
|
|
206
|
+
str.sub(/^-{1,2}/, '')
|
|
207
|
+
end
|
|
208
|
+
|
|
209
|
+
def dasherize(str)
|
|
210
|
+
(str.length > 1 ? "--" : "-") + str
|
|
211
|
+
end
|
|
212
|
+
|
|
118
213
|
def peek
|
|
119
214
|
@args.first
|
|
120
215
|
end
|
|
121
216
|
|
|
122
|
-
def
|
|
123
|
-
|
|
124
|
-
@args = @args[1..-1] || []
|
|
125
|
-
arg
|
|
217
|
+
def shift
|
|
218
|
+
@args.shift
|
|
126
219
|
end
|
|
127
220
|
|
|
128
|
-
def
|
|
129
|
-
|
|
221
|
+
def unshift(arg)
|
|
222
|
+
unless arg.kind_of?(Array)
|
|
223
|
+
@args.unshift(arg)
|
|
224
|
+
else
|
|
225
|
+
@args = arg + @args
|
|
226
|
+
end
|
|
227
|
+
end
|
|
228
|
+
|
|
229
|
+
def valid?(arg)
|
|
230
|
+
if arg.to_s =~ /^--no-(\w+)$/
|
|
231
|
+
@switches.key?(arg) or (@switches["--#{$1}"] == :boolean)
|
|
232
|
+
else
|
|
233
|
+
@switches.key?(arg) or @shorts.key?(arg)
|
|
234
|
+
end
|
|
130
235
|
end
|
|
131
236
|
|
|
132
|
-
def
|
|
237
|
+
def current_is_option?
|
|
133
238
|
case peek
|
|
134
|
-
when LONG_RE, SHORT_RE,
|
|
135
|
-
|
|
239
|
+
when LONG_RE, SHORT_RE, EQ_RE, SHORT_NUM
|
|
240
|
+
valid?($1)
|
|
136
241
|
when SHORT_SQ_RE
|
|
137
|
-
$1.split(
|
|
242
|
+
$1.split('').any? { |f| valid?("-#{f}") }
|
|
138
243
|
end
|
|
139
244
|
end
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
# value that "--test" was set to.
|
|
144
|
-
#
|
|
145
|
-
# This allows users to refer to the long or short switch and get
|
|
146
|
-
# the same value
|
|
147
|
-
def normalize_hash(hash)
|
|
148
|
-
hash.map do |switch, val|
|
|
149
|
-
@syns[switch].map {|key| [key, val]}
|
|
150
|
-
end.inject([]) {|a, v| a + v}.map do |key, value|
|
|
151
|
-
[key.sub(/^-+/, ''), value]
|
|
152
|
-
end.inject({}) {|h, (k,v)| h[k] = v; h[k.to_sym] = v; h}
|
|
245
|
+
|
|
246
|
+
def normalize_switch(switch)
|
|
247
|
+
@shorts.key?(switch) ? @shorts[switch] : switch
|
|
153
248
|
end
|
|
154
|
-
|
|
249
|
+
|
|
250
|
+
def switch_type(switch)
|
|
251
|
+
if switch =~ /^--no-(\w+)$/
|
|
252
|
+
@switches[switch] || @switches["--#{$1}"]
|
|
253
|
+
else
|
|
254
|
+
@switches[switch]
|
|
255
|
+
end
|
|
256
|
+
end
|
|
257
|
+
|
|
258
|
+
def check_required!(hash)
|
|
259
|
+
for name, type in @switches
|
|
260
|
+
if type == :required and !hash[undasherize(name)]
|
|
261
|
+
raise Error, "no value provided for required argument '#{name}'"
|
|
262
|
+
end
|
|
263
|
+
end
|
|
264
|
+
end
|
|
265
|
+
|
|
155
266
|
end
|
|
156
267
|
end
|
data/lib/thor/runner.rb
CHANGED
|
@@ -18,8 +18,17 @@ class Thor::Runner < Thor
|
|
|
18
18
|
method_options :as => :optional, :relative => :boolean
|
|
19
19
|
def install(name)
|
|
20
20
|
initialize_thorfiles
|
|
21
|
+
|
|
22
|
+
base = name
|
|
23
|
+
package = :file
|
|
24
|
+
|
|
21
25
|
begin
|
|
22
|
-
|
|
26
|
+
if File.directory?(File.expand_path(name))
|
|
27
|
+
base, package = File.join(name, "main.thor"), :directory
|
|
28
|
+
contents = open(base).read
|
|
29
|
+
else
|
|
30
|
+
contents = open(name).read
|
|
31
|
+
end
|
|
23
32
|
rescue OpenURI::HTTPError
|
|
24
33
|
raise Error, "Error opening URI `#{name}'"
|
|
25
34
|
rescue Errno::ENOENT
|
|
@@ -35,9 +44,7 @@ class Thor::Runner < Thor
|
|
|
35
44
|
|
|
36
45
|
return false unless response =~ /^\s*y/i
|
|
37
46
|
|
|
38
|
-
constants = Thor::Util.constants_in_contents(contents)
|
|
39
|
-
|
|
40
|
-
# name = name =~ /\.thor$/ || is_uri ? name : "#{name}.thor"
|
|
47
|
+
constants = Thor::Util.constants_in_contents(contents, base)
|
|
41
48
|
|
|
42
49
|
as = options["as"] || begin
|
|
43
50
|
first_line = contents.split("\n")[0]
|
|
@@ -63,8 +70,12 @@ class Thor::Runner < Thor
|
|
|
63
70
|
|
|
64
71
|
puts "Storing thor file in your system repository"
|
|
65
72
|
|
|
66
|
-
File.
|
|
67
|
-
|
|
73
|
+
destination = File.join(thor_root, yaml[as][:filename])
|
|
74
|
+
|
|
75
|
+
if package == :file
|
|
76
|
+
File.open(destination, "w") {|f| f.puts contents }
|
|
77
|
+
else
|
|
78
|
+
FileUtils.cp_r(name, destination)
|
|
68
79
|
end
|
|
69
80
|
|
|
70
81
|
yaml[as][:filename] # Indicate sucess
|
|
@@ -78,7 +89,7 @@ class Thor::Runner < Thor
|
|
|
78
89
|
puts "Uninstalling #{name}."
|
|
79
90
|
|
|
80
91
|
file = File.join(thor_root, "#{yaml[name][:filename]}")
|
|
81
|
-
|
|
92
|
+
FileUtils.rm_rf(file)
|
|
82
93
|
yaml.delete(name)
|
|
83
94
|
save_yaml(yaml)
|
|
84
95
|
|
|
@@ -92,7 +103,7 @@ class Thor::Runner < Thor
|
|
|
92
103
|
|
|
93
104
|
puts "Updating `#{name}' from #{yaml[name][:location]}"
|
|
94
105
|
old_filename = yaml[name][:filename]
|
|
95
|
-
options
|
|
106
|
+
self.options = self.options.merge("as" => name)
|
|
96
107
|
filename = install(yaml[name][:location])
|
|
97
108
|
unless filename == old_filename
|
|
98
109
|
File.delete(File.join(thor_root, old_filename))
|
|
@@ -102,7 +113,7 @@ class Thor::Runner < Thor
|
|
|
102
113
|
desc "installed", "list the installed Thor modules and tasks (--internal means list the built-in tasks as well)"
|
|
103
114
|
method_options :internal => :boolean
|
|
104
115
|
def installed
|
|
105
|
-
|
|
116
|
+
thor_root_glob.each do |f|
|
|
106
117
|
next if f =~ /thor\.yml$/
|
|
107
118
|
load_thorfile f unless Thor.subclass_files.keys.include?(File.expand_path(f))
|
|
108
119
|
end
|
|
@@ -113,14 +124,21 @@ class Thor::Runner < Thor
|
|
|
113
124
|
end
|
|
114
125
|
|
|
115
126
|
desc "list [SEARCH]", "list the available thor tasks (--substring means SEARCH can be anywhere in the module)"
|
|
116
|
-
method_options :substring => :boolean
|
|
127
|
+
method_options :substring => :boolean,
|
|
128
|
+
:group => :optional,
|
|
129
|
+
:all => :boolean,
|
|
130
|
+
['--descriptions', '-D'] => :boolean
|
|
117
131
|
def list(search = "")
|
|
118
132
|
initialize_thorfiles
|
|
119
133
|
search = ".*#{search}" if options["substring"]
|
|
120
134
|
search = /^#{search}.*/i
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
135
|
+
group = options[:group] || 'standard'
|
|
136
|
+
|
|
137
|
+
classes = Thor.subclasses.select do |k|
|
|
138
|
+
(options[:all] || k.group_name == group) &&
|
|
139
|
+
Thor::Util.constant_to_thor_path(k.name) =~ search
|
|
140
|
+
end
|
|
141
|
+
display_klasses(false, classes, options.descriptions?)
|
|
124
142
|
end
|
|
125
143
|
|
|
126
144
|
# Override Thor#help so we can give info about not-yet-loaded tasks
|
|
@@ -141,11 +159,27 @@ class Thor::Runner < Thor
|
|
|
141
159
|
def self.thor_root
|
|
142
160
|
File.join(ENV["HOME"] || ENV["APPDATA"], ".thor")
|
|
143
161
|
end
|
|
162
|
+
|
|
163
|
+
def self.thor_root_glob
|
|
164
|
+
# On Windows thor_root will be something like this:
|
|
165
|
+
#
|
|
166
|
+
# C:\Documents and Settings\james\.thor
|
|
167
|
+
#
|
|
168
|
+
# If we don't #gsub the \ character, Dir.glob will fail.
|
|
169
|
+
files = Dir["#{thor_root.gsub(/\\/, '/')}/*"]
|
|
170
|
+
files.map! do |file|
|
|
171
|
+
File.directory?(file) ? File.join(file, "main.thor") : file
|
|
172
|
+
end
|
|
173
|
+
end
|
|
144
174
|
|
|
145
175
|
private
|
|
146
176
|
def thor_root
|
|
147
177
|
self.class.thor_root
|
|
148
178
|
end
|
|
179
|
+
|
|
180
|
+
def thor_root_glob
|
|
181
|
+
self.class.thor_root_glob
|
|
182
|
+
end
|
|
149
183
|
|
|
150
184
|
def thor_yaml
|
|
151
185
|
yaml_file = File.join(thor_root, "thor.yml")
|
|
@@ -158,12 +192,12 @@ class Thor::Runner < Thor
|
|
|
158
192
|
File.open(yaml_file, "w") {|f| f.puts yaml.to_yaml }
|
|
159
193
|
end
|
|
160
194
|
|
|
161
|
-
def display_klasses(with_modules = false, klasses = Thor.subclasses)
|
|
195
|
+
def display_klasses(with_modules = false, klasses = Thor.subclasses, show_descriptions = false)
|
|
162
196
|
klasses -= [Thor, Thor::Runner] unless with_modules
|
|
163
197
|
raise Error, "No Thor tasks available" if klasses.empty?
|
|
164
198
|
|
|
165
199
|
if with_modules && !(yaml = thor_yaml).empty?
|
|
166
|
-
max_name = yaml.max {|(xk,xv),(yk,yv)| xk.size <=> yk.size }.first.size
|
|
200
|
+
max_name = yaml.max {|(xk,xv),(yk,yv)| xk.to_s.size <=> yk.to_s.size }.first.size
|
|
167
201
|
modules_label = "Modules"
|
|
168
202
|
namespaces_label = "Namespaces"
|
|
169
203
|
column_width = [max_name + 4, modules_label.size + 1].max
|
|
@@ -181,30 +215,41 @@ class Thor::Runner < Thor
|
|
|
181
215
|
puts
|
|
182
216
|
end
|
|
183
217
|
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
Thor::Util.constant_to_thor_path(x.name).size <=> Thor::Util.constant_to_thor_path(y.name).size
|
|
190
|
-
end.name.size
|
|
191
|
-
|
|
192
|
-
# Calculate the size of the largest option description
|
|
193
|
-
max_left_item = klasses.max do |x,y|
|
|
194
|
-
(x.maxima.usage + x.maxima.opt).to_i <=> (y.maxima.usage + y.maxima.opt).to_i
|
|
218
|
+
unless klasses.empty?
|
|
219
|
+
puts # add some spacing
|
|
220
|
+
klasses.each { |klass| display_tasks(klass, show_descriptions) }
|
|
221
|
+
else
|
|
222
|
+
puts "\033[1;34mNo Thor tasks available\033[0m"
|
|
195
223
|
end
|
|
196
|
-
|
|
197
|
-
max_left = max_left_item.maxima.usage + max_left_item.maxima.opt
|
|
198
|
-
|
|
199
|
-
klasses.each {|k| display_tasks(k, max_base, max_left)}
|
|
200
224
|
end
|
|
201
225
|
|
|
202
|
-
def display_tasks(klass,
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
226
|
+
def display_tasks(klass, show_descriptions)
|
|
227
|
+
if klass.tasks.values.length > 1
|
|
228
|
+
|
|
229
|
+
base = Thor::Util.constant_to_thor_path(klass.name)
|
|
230
|
+
|
|
231
|
+
if base.to_a.empty?
|
|
232
|
+
base = 'default'
|
|
233
|
+
puts "\033[1;35m#{base}\033[0m"
|
|
234
|
+
else
|
|
235
|
+
puts "\033[1;34m#{base}\033[0m"
|
|
236
|
+
end
|
|
237
|
+
puts "-" * base.length
|
|
238
|
+
|
|
239
|
+
klass.tasks.each true do |name, task|
|
|
240
|
+
puts task.formatted_usage(true)
|
|
241
|
+
if show_descriptions
|
|
242
|
+
puts ' ' * 4 + task.description
|
|
243
|
+
puts
|
|
244
|
+
end
|
|
245
|
+
end
|
|
246
|
+
|
|
247
|
+
puts unless show_descriptions
|
|
248
|
+
|
|
249
|
+
unless klass.opts.empty?
|
|
250
|
+
puts "\nglobal options: #{Options.new(klass.opts)}"
|
|
251
|
+
puts # add some spacing
|
|
252
|
+
end
|
|
208
253
|
end
|
|
209
254
|
end
|
|
210
255
|
|
|
@@ -213,8 +258,9 @@ class Thor::Runner < Thor
|
|
|
213
258
|
end
|
|
214
259
|
|
|
215
260
|
def load_thorfile(path)
|
|
261
|
+
txt = File.read(path)
|
|
216
262
|
begin
|
|
217
|
-
|
|
263
|
+
Thor::Tasks.class_eval txt, path
|
|
218
264
|
rescue Object => e
|
|
219
265
|
$stderr.puts "WARNING: unable to load thorfile #{path.inspect}: #{e.message}"
|
|
220
266
|
end
|
|
@@ -233,8 +279,12 @@ class Thor::Runner < Thor
|
|
|
233
279
|
|
|
234
280
|
# We want to load system-wide Thorfiles first
|
|
235
281
|
# so the local Thorfiles will override them.
|
|
236
|
-
(relevant_to ? thorfiles_relevant_to(relevant_to) :
|
|
237
|
-
|
|
282
|
+
files = (relevant_to ? thorfiles_relevant_to(relevant_to) :
|
|
283
|
+
thor_root_glob) + thorfiles - ["#{thor_root}/thor.yml"]
|
|
284
|
+
|
|
285
|
+
files.map! do |file|
|
|
286
|
+
File.directory?(file) ? File.join(file, "main.thor") : file
|
|
287
|
+
end
|
|
238
288
|
end
|
|
239
289
|
|
|
240
290
|
def thorfiles_relevant_to(meth)
|
data/lib/thor/task.rb
CHANGED
|
@@ -3,9 +3,16 @@ require 'thor/util'
|
|
|
3
3
|
|
|
4
4
|
class Thor
|
|
5
5
|
class Task < Struct.new(:meth, :description, :usage, :opts, :klass)
|
|
6
|
+
|
|
6
7
|
def self.dynamic(meth, klass)
|
|
7
8
|
new(meth, "A dynamically-generated task", meth.to_s, nil, klass)
|
|
8
9
|
end
|
|
10
|
+
|
|
11
|
+
def initialize(*args)
|
|
12
|
+
# keep the original opts - we need them later on
|
|
13
|
+
@options = args[3] || {}
|
|
14
|
+
super
|
|
15
|
+
end
|
|
9
16
|
|
|
10
17
|
def parse(obj, args)
|
|
11
18
|
list, hash = parse_args(args)
|
|
@@ -17,14 +24,18 @@ class Thor
|
|
|
17
24
|
raise NoMethodError, "the `#{meth}' task of #{obj.class} is private" if
|
|
18
25
|
(obj.private_methods + obj.protected_methods).include?(meth)
|
|
19
26
|
|
|
20
|
-
obj.
|
|
27
|
+
obj.invoke(meth, *params)
|
|
21
28
|
rescue ArgumentError => e
|
|
29
|
+
|
|
22
30
|
# backtrace sans anything in this file
|
|
23
31
|
backtrace = e.backtrace.reject {|frame| frame =~ /^#{Regexp.escape(__FILE__)}/}
|
|
32
|
+
# also nix anything in thor.rb
|
|
33
|
+
backtrace = backtrace.reject { |frame| frame =~ /\/thor.rb/ }
|
|
34
|
+
|
|
24
35
|
# and sans anything that got us here
|
|
25
36
|
backtrace -= caller
|
|
26
37
|
raise e unless backtrace.empty?
|
|
27
|
-
|
|
38
|
+
|
|
28
39
|
# okay, they really did call it wrong
|
|
29
40
|
raise Error, "`#{meth}' was called incorrectly. Call as `#{formatted_usage}'"
|
|
30
41
|
rescue NoMethodError => e
|
|
@@ -45,35 +56,27 @@ class Thor
|
|
|
45
56
|
new.klass = klass
|
|
46
57
|
new
|
|
47
58
|
end
|
|
48
|
-
|
|
49
|
-
def
|
|
50
|
-
return
|
|
51
|
-
|
|
52
|
-
if val == true || val == :boolean
|
|
53
|
-
"[#{opt}]"
|
|
54
|
-
elsif val == :required
|
|
55
|
-
opt + "=" + opt.gsub(/\-/, "").upcase
|
|
56
|
-
else
|
|
57
|
-
sample = val == :optional ? opt.gsub(/\-/, "").upcase : val
|
|
58
|
-
"[" + opt + "=" + sample + "]"
|
|
59
|
-
end
|
|
60
|
-
end.join(" ")
|
|
59
|
+
|
|
60
|
+
def opts
|
|
61
|
+
return super unless super.kind_of? Hash
|
|
62
|
+
@_opts ||= Options.new(super)
|
|
61
63
|
end
|
|
62
|
-
|
|
64
|
+
|
|
65
|
+
def full_opts
|
|
66
|
+
@_full_opts ||= Options.new((klass.opts || {}).merge(@options))
|
|
67
|
+
end
|
|
68
|
+
|
|
63
69
|
def formatted_usage(namespace = false)
|
|
64
70
|
(namespace ? self.namespace + ':' : '') + usage +
|
|
65
|
-
(opts ? " " +
|
|
71
|
+
(opts ? " " + opts.formatted_usage : "")
|
|
66
72
|
end
|
|
67
73
|
|
|
68
74
|
protected
|
|
69
75
|
|
|
70
76
|
def parse_args(args)
|
|
71
|
-
return [
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
list = options.skip_non_opts
|
|
75
|
-
hash.update options.getopts(false)
|
|
76
|
-
options.check_required_args hash
|
|
77
|
+
return [[], {}] if args.nil?
|
|
78
|
+
hash = full_opts.parse(args)
|
|
79
|
+
list = full_opts.non_opts
|
|
77
80
|
[list, hash]
|
|
78
81
|
end
|
|
79
82
|
end
|
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.ruby} -S #{gem} install pkg/#{spec.name}-#{spec.version} --no-rdoc --no-ri --no-update-sources}
|
|
23
26
|
end
|
|
24
27
|
end
|
|
25
28
|
|
data/lib/thor/util.rb
CHANGED
|
@@ -1,10 +1,38 @@
|
|
|
1
1
|
require 'thor/error'
|
|
2
2
|
|
|
3
|
+
module ObjectSpace
|
|
4
|
+
|
|
5
|
+
class << self
|
|
6
|
+
|
|
7
|
+
# @return <Array[Class]> All the classes in the object space.
|
|
8
|
+
def classes
|
|
9
|
+
klasses = []
|
|
10
|
+
ObjectSpace.each_object(Class) {|o| klasses << o}
|
|
11
|
+
klasses
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
end
|
|
16
|
+
|
|
3
17
|
class Thor
|
|
18
|
+
module Tasks; end
|
|
19
|
+
|
|
4
20
|
module Util
|
|
5
21
|
|
|
22
|
+
def self.full_const_get(obj, name)
|
|
23
|
+
list = name.split("::")
|
|
24
|
+
list.shift if list.first.empty?
|
|
25
|
+
list.each do |x|
|
|
26
|
+
# This is required because const_get tries to look for constants in the
|
|
27
|
+
# ancestor chain, but we only want constants that are HERE
|
|
28
|
+
obj = obj.const_defined?(x) ? obj.const_get(x) : obj.const_missing(x)
|
|
29
|
+
end
|
|
30
|
+
obj
|
|
31
|
+
end
|
|
32
|
+
|
|
6
33
|
def self.constant_to_thor_path(str, remove_default = true)
|
|
7
|
-
str =
|
|
34
|
+
str = str.to_s.gsub(/^Thor::Tasks::/, "")
|
|
35
|
+
str = snake_case(str).squeeze(":")
|
|
8
36
|
str.gsub!(/^default/, '') if remove_default
|
|
9
37
|
str
|
|
10
38
|
end
|
|
@@ -21,16 +49,20 @@ class Thor
|
|
|
21
49
|
str.gsub(/:(.?)/) { "::#{$1.upcase}" }.gsub(/(?:^|_)(.)/) { $1.upcase }
|
|
22
50
|
end
|
|
23
51
|
|
|
24
|
-
def self.constants_in_contents(str)
|
|
25
|
-
klasses =
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
52
|
+
def self.constants_in_contents(str, file = __FILE__)
|
|
53
|
+
klasses = ObjectSpace.classes.dup
|
|
54
|
+
Module.new.class_eval(str, file)
|
|
55
|
+
klasses = ObjectSpace.classes - klasses
|
|
56
|
+
klasses = klasses.select {|k| k < Thor }
|
|
57
|
+
klasses.map! {|k| k.to_s.gsub(/#<Module:\w+>::/, '')}
|
|
30
58
|
end
|
|
31
59
|
|
|
32
|
-
def self.make_constant(str)
|
|
33
|
-
|
|
60
|
+
def self.make_constant(str, base = [Thor::Tasks, Object])
|
|
61
|
+
which = base.find do |obj|
|
|
62
|
+
full_const_get(obj, str) rescue nil
|
|
63
|
+
end
|
|
64
|
+
return full_const_get(which, str) if which
|
|
65
|
+
raise NameError, "uninitialized constant #{str}"
|
|
34
66
|
end
|
|
35
67
|
|
|
36
68
|
def self.snake_case(str)
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: mislav-thor
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.9.
|
|
4
|
+
version: 0.9.10
|
|
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:
|
|
12
|
+
date: 2009-01-27 00:00:00 -08:00
|
|
13
13
|
default_executable:
|
|
14
14
|
dependencies: []
|
|
15
15
|
|
|
@@ -23,9 +23,11 @@ extensions: []
|
|
|
23
23
|
extra_rdoc_files:
|
|
24
24
|
- README.markdown
|
|
25
25
|
- LICENSE
|
|
26
|
+
- CHANGELOG.rdoc
|
|
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
|