atli 0.1.2
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.
- checksums.yaml +7 -0
- data/.yardopts +8 -0
- data/CHANGELOG.md +193 -0
- data/CONTRIBUTING.md +20 -0
- data/LICENSE.md +24 -0
- data/README.md +44 -0
- data/atli.gemspec +30 -0
- data/bin/thor +6 -0
- data/lib/thor.rb +868 -0
- data/lib/thor/actions.rb +322 -0
- data/lib/thor/actions/create_file.rb +104 -0
- data/lib/thor/actions/create_link.rb +60 -0
- data/lib/thor/actions/directory.rb +118 -0
- data/lib/thor/actions/empty_directory.rb +143 -0
- data/lib/thor/actions/file_manipulation.rb +364 -0
- data/lib/thor/actions/inject_into_file.rb +109 -0
- data/lib/thor/base.rb +773 -0
- data/lib/thor/command.rb +192 -0
- data/lib/thor/core_ext/hash_with_indifferent_access.rb +97 -0
- data/lib/thor/core_ext/io_binary_read.rb +12 -0
- data/lib/thor/core_ext/ordered_hash.rb +129 -0
- data/lib/thor/error.rb +32 -0
- data/lib/thor/group.rb +281 -0
- data/lib/thor/invocation.rb +182 -0
- data/lib/thor/line_editor.rb +17 -0
- data/lib/thor/line_editor/basic.rb +37 -0
- data/lib/thor/line_editor/readline.rb +88 -0
- data/lib/thor/parser.rb +5 -0
- data/lib/thor/parser/argument.rb +70 -0
- data/lib/thor/parser/arguments.rb +175 -0
- data/lib/thor/parser/option.rb +146 -0
- data/lib/thor/parser/options.rb +221 -0
- data/lib/thor/parser/shared_option.rb +23 -0
- data/lib/thor/rake_compat.rb +71 -0
- data/lib/thor/runner.rb +324 -0
- data/lib/thor/shell.rb +81 -0
- data/lib/thor/shell/basic.rb +439 -0
- data/lib/thor/shell/color.rb +149 -0
- data/lib/thor/shell/html.rb +126 -0
- data/lib/thor/util.rb +268 -0
- data/lib/thor/version.rb +22 -0
- metadata +114 -0
data/lib/thor/command.rb
ADDED
@@ -0,0 +1,192 @@
|
|
1
|
+
require 'semantic_logger'
|
2
|
+
|
3
|
+
class Thor
|
4
|
+
class Command < Struct.new( :name,
|
5
|
+
:description,
|
6
|
+
:long_description,
|
7
|
+
:usage,
|
8
|
+
:options,
|
9
|
+
:ancestor_name )
|
10
|
+
include SemanticLogger::Loggable
|
11
|
+
|
12
|
+
FILE_REGEXP = /^#{Regexp.escape(File.dirname(__FILE__))}/
|
13
|
+
|
14
|
+
def initialize(name, description, long_description, usage, options = nil)
|
15
|
+
super(name.to_s, description, long_description, usage, options || {})
|
16
|
+
end
|
17
|
+
|
18
|
+
def initialize_copy(other) #:nodoc:
|
19
|
+
super(other)
|
20
|
+
self.options = other.options.dup if other.options
|
21
|
+
end
|
22
|
+
|
23
|
+
def hidden?
|
24
|
+
false
|
25
|
+
end
|
26
|
+
|
27
|
+
# By default, a command invokes a method in the thor class. You can change
|
28
|
+
# this implementation to create custom commands.
|
29
|
+
def run(instance, args = [])
|
30
|
+
logger.trace "Command#run",
|
31
|
+
self: self,
|
32
|
+
instance: instance,
|
33
|
+
args: args
|
34
|
+
|
35
|
+
arity = nil
|
36
|
+
|
37
|
+
if private_method?(instance)
|
38
|
+
instance.class.handle_no_command_error(name)
|
39
|
+
elsif public_method?(instance)
|
40
|
+
arity = instance.method(name).arity
|
41
|
+
instance.__send__(name, *args)
|
42
|
+
elsif local_method?(instance, :method_missing)
|
43
|
+
instance.__send__(:method_missing, name.to_sym, *args)
|
44
|
+
else
|
45
|
+
instance.class.handle_no_command_error(name)
|
46
|
+
end
|
47
|
+
rescue ArgumentError => e
|
48
|
+
if handle_argument_error?(instance, e, caller)
|
49
|
+
instance.class.handle_argument_error(self, e, args, arity)
|
50
|
+
else
|
51
|
+
raise e
|
52
|
+
end
|
53
|
+
rescue NoMethodError => e
|
54
|
+
if handle_no_method_error?(instance, e, caller)
|
55
|
+
instance.class.handle_no_command_error(name)
|
56
|
+
else
|
57
|
+
raise e
|
58
|
+
end
|
59
|
+
rescue Exception => error
|
60
|
+
instance.send :on_run_error, error, self, args
|
61
|
+
|
62
|
+
# We should not get here!!!
|
63
|
+
# {Thor::Base#on_run_error} should exit or re-raise :(
|
64
|
+
logger.error "#on_run_error failed to exit or re-raise", error: error
|
65
|
+
|
66
|
+
# If you want something done right...
|
67
|
+
raise error
|
68
|
+
end
|
69
|
+
|
70
|
+
# Returns the formatted usage by injecting given required arguments
|
71
|
+
# and required options into the given usage.
|
72
|
+
def formatted_usage(klass, namespace = true, subcommand = false)
|
73
|
+
logger.trace "Formatting usage",
|
74
|
+
self: self,
|
75
|
+
klass: klass,
|
76
|
+
namespace: namespace,
|
77
|
+
subcommand: subcommand,
|
78
|
+
ancestor_name: ancestor_name
|
79
|
+
|
80
|
+
if ancestor_name
|
81
|
+
formatted = "#{ancestor_name} ".dup # add space
|
82
|
+
elsif namespace
|
83
|
+
namespace = klass.namespace
|
84
|
+
formatted = "#{namespace.gsub(/^(default)/, '')}:".dup
|
85
|
+
end
|
86
|
+
formatted ||= "#{klass.namespace.split(':').last} ".dup if subcommand
|
87
|
+
|
88
|
+
formatted ||= "".dup
|
89
|
+
|
90
|
+
# Add usage with required arguments
|
91
|
+
formatted << if klass && !klass.arguments.empty?
|
92
|
+
usage.to_s.gsub(/^#{name}/) do |match|
|
93
|
+
match << " " \
|
94
|
+
<< klass.arguments.map(&:usage).compact.join(" ")
|
95
|
+
end
|
96
|
+
else
|
97
|
+
usage.to_s
|
98
|
+
end
|
99
|
+
|
100
|
+
# Add required options
|
101
|
+
formatted << " #{required_options}"
|
102
|
+
|
103
|
+
# Strip and go!
|
104
|
+
formatted.strip
|
105
|
+
end
|
106
|
+
|
107
|
+
protected
|
108
|
+
|
109
|
+
def not_debugging?(instance)
|
110
|
+
!(instance.class.respond_to?(:debugging) && instance.class.debugging)
|
111
|
+
end
|
112
|
+
|
113
|
+
def required_options
|
114
|
+
@required_options ||= options.
|
115
|
+
map { |_, o| o.usage if o.required? }.
|
116
|
+
compact.
|
117
|
+
sort.
|
118
|
+
join(" ")
|
119
|
+
end
|
120
|
+
|
121
|
+
# Given a target, checks if this class name is a public method.
|
122
|
+
def public_method?(instance) #:nodoc:
|
123
|
+
!(instance.public_methods & [name.to_s, name.to_sym]).empty?
|
124
|
+
end
|
125
|
+
|
126
|
+
def private_method?(instance)
|
127
|
+
!(instance.private_methods & [name.to_s, name.to_sym]).empty?
|
128
|
+
end
|
129
|
+
|
130
|
+
def local_method?(instance, name)
|
131
|
+
methods = instance.public_methods(false) +
|
132
|
+
instance.private_methods(false) +
|
133
|
+
instance.protected_methods(false)
|
134
|
+
!(methods & [name.to_s, name.to_sym]).empty?
|
135
|
+
end
|
136
|
+
|
137
|
+
def sans_backtrace(backtrace, caller) #:nodoc:
|
138
|
+
saned = backtrace.reject { |frame|
|
139
|
+
(frame =~ FILE_REGEXP) ||
|
140
|
+
(frame =~ /\.java:/ && RUBY_PLATFORM =~ /java/) ||
|
141
|
+
(frame =~ %r{^kernel/} && RUBY_ENGINE =~ /rbx/)
|
142
|
+
}
|
143
|
+
saned - caller
|
144
|
+
end
|
145
|
+
|
146
|
+
def handle_argument_error?(instance, error, caller)
|
147
|
+
not_debugging?(instance) \
|
148
|
+
&& ( error.message =~ /wrong number of arguments/ \
|
149
|
+
|| error.message =~ /given \d*, expected \d*/ ) \
|
150
|
+
&& begin
|
151
|
+
saned = sans_backtrace(error.backtrace, caller)
|
152
|
+
# Ruby 1.9 always include the called method in the backtrace
|
153
|
+
saned.empty? || (saned.size == 1 && RUBY_VERSION >= "1.9")
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
157
|
+
def handle_no_method_error?(instance, error, caller)
|
158
|
+
not_debugging?(instance) &&
|
159
|
+
error.message =~ \
|
160
|
+
/^undefined method `#{name}' for #{Regexp.escape(instance.to_s)}$/
|
161
|
+
end
|
162
|
+
end
|
163
|
+
Task = Command
|
164
|
+
|
165
|
+
# A command that is hidden in help messages but still invocable.
|
166
|
+
class HiddenCommand < Command
|
167
|
+
def hidden?
|
168
|
+
true
|
169
|
+
end
|
170
|
+
end
|
171
|
+
HiddenTask = HiddenCommand
|
172
|
+
|
173
|
+
# A dynamic command that handles method missing scenarios.
|
174
|
+
class DynamicCommand < Command
|
175
|
+
def initialize(name, options = nil)
|
176
|
+
super( name.to_s,
|
177
|
+
"A dynamically-generated command",
|
178
|
+
name.to_s,
|
179
|
+
name.to_s,
|
180
|
+
options )
|
181
|
+
end
|
182
|
+
|
183
|
+
def run(instance, args = [])
|
184
|
+
if (instance.methods & [name.to_s, name.to_sym]).empty?
|
185
|
+
super
|
186
|
+
else
|
187
|
+
instance.class.handle_no_command_error(name)
|
188
|
+
end
|
189
|
+
end
|
190
|
+
end
|
191
|
+
DynamicTask = DynamicCommand
|
192
|
+
end
|
@@ -0,0 +1,97 @@
|
|
1
|
+
class Thor
|
2
|
+
module CoreExt #:nodoc:
|
3
|
+
# A hash with indifferent access and magic predicates.
|
4
|
+
#
|
5
|
+
# hash = Thor::CoreExt::HashWithIndifferentAccess.new 'foo' => 'bar', 'baz' => 'bee', 'force' => true
|
6
|
+
#
|
7
|
+
# hash[:foo] #=> 'bar'
|
8
|
+
# hash['foo'] #=> 'bar'
|
9
|
+
# hash.foo? #=> true
|
10
|
+
#
|
11
|
+
class HashWithIndifferentAccess < ::Hash #:nodoc:
|
12
|
+
def initialize(hash = {})
|
13
|
+
super()
|
14
|
+
hash.each do |key, value|
|
15
|
+
self[convert_key(key)] = value
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def [](key)
|
20
|
+
super(convert_key(key))
|
21
|
+
end
|
22
|
+
|
23
|
+
def []=(key, value)
|
24
|
+
super(convert_key(key), value)
|
25
|
+
end
|
26
|
+
|
27
|
+
def delete(key)
|
28
|
+
super(convert_key(key))
|
29
|
+
end
|
30
|
+
|
31
|
+
def fetch(key, *args)
|
32
|
+
super(convert_key(key), *args)
|
33
|
+
end
|
34
|
+
|
35
|
+
def key?(key)
|
36
|
+
super(convert_key(key))
|
37
|
+
end
|
38
|
+
|
39
|
+
def values_at(*indices)
|
40
|
+
indices.map { |key| self[convert_key(key)] }
|
41
|
+
end
|
42
|
+
|
43
|
+
def merge(other)
|
44
|
+
dup.merge!(other)
|
45
|
+
end
|
46
|
+
|
47
|
+
def merge!(other)
|
48
|
+
other.each do |key, value|
|
49
|
+
self[convert_key(key)] = value
|
50
|
+
end
|
51
|
+
self
|
52
|
+
end
|
53
|
+
|
54
|
+
def reverse_merge(other)
|
55
|
+
self.class.new(other).merge(self)
|
56
|
+
end
|
57
|
+
|
58
|
+
def reverse_merge!(other_hash)
|
59
|
+
replace(reverse_merge(other_hash))
|
60
|
+
end
|
61
|
+
|
62
|
+
def replace(other_hash)
|
63
|
+
super(other_hash)
|
64
|
+
end
|
65
|
+
|
66
|
+
# Convert to a Hash with String keys.
|
67
|
+
def to_hash
|
68
|
+
Hash.new(default).merge!(self)
|
69
|
+
end
|
70
|
+
|
71
|
+
protected
|
72
|
+
|
73
|
+
def convert_key(key)
|
74
|
+
key.is_a?(Symbol) ? key.to_s : key
|
75
|
+
end
|
76
|
+
|
77
|
+
# Magic predicates. For instance:
|
78
|
+
#
|
79
|
+
# options.force? # => !!options['force']
|
80
|
+
# options.shebang # => "/usr/lib/local/ruby"
|
81
|
+
# options.test_framework?(:rspec) # => options[:test_framework] == :rspec
|
82
|
+
#
|
83
|
+
def method_missing(method, *args)
|
84
|
+
method = method.to_s
|
85
|
+
if method =~ /^(\w+)\?$/
|
86
|
+
if args.empty?
|
87
|
+
!!self[$1]
|
88
|
+
else
|
89
|
+
self[$1] == args.first
|
90
|
+
end
|
91
|
+
else
|
92
|
+
self[method]
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
class IO #:nodoc:
|
2
|
+
class << self
|
3
|
+
unless method_defined? :binread
|
4
|
+
def binread(file, *args)
|
5
|
+
raise ArgumentError, "wrong number of arguments (#{1 + args.size} for 1..3)" unless args.size < 3
|
6
|
+
File.open(file, "rb") do |f|
|
7
|
+
f.read(*args)
|
8
|
+
end
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
@@ -0,0 +1,129 @@
|
|
1
|
+
class Thor
|
2
|
+
module CoreExt
|
3
|
+
class OrderedHash < ::Hash
|
4
|
+
if RUBY_VERSION < "1.9"
|
5
|
+
def initialize(*args, &block)
|
6
|
+
super
|
7
|
+
@keys = []
|
8
|
+
end
|
9
|
+
|
10
|
+
def initialize_copy(other)
|
11
|
+
super
|
12
|
+
# make a deep copy of keys
|
13
|
+
@keys = other.keys
|
14
|
+
end
|
15
|
+
|
16
|
+
def []=(key, value)
|
17
|
+
@keys << key unless key?(key)
|
18
|
+
super
|
19
|
+
end
|
20
|
+
|
21
|
+
def delete(key)
|
22
|
+
if key? key
|
23
|
+
index = @keys.index(key)
|
24
|
+
@keys.delete_at index
|
25
|
+
end
|
26
|
+
super
|
27
|
+
end
|
28
|
+
|
29
|
+
def delete_if
|
30
|
+
super
|
31
|
+
sync_keys!
|
32
|
+
self
|
33
|
+
end
|
34
|
+
|
35
|
+
alias_method :reject!, :delete_if
|
36
|
+
|
37
|
+
def reject(&block)
|
38
|
+
dup.reject!(&block)
|
39
|
+
end
|
40
|
+
|
41
|
+
def keys
|
42
|
+
@keys.dup
|
43
|
+
end
|
44
|
+
|
45
|
+
def values
|
46
|
+
@keys.map { |key| self[key] }
|
47
|
+
end
|
48
|
+
|
49
|
+
def to_hash
|
50
|
+
self
|
51
|
+
end
|
52
|
+
|
53
|
+
def to_a
|
54
|
+
@keys.map { |key| [key, self[key]] }
|
55
|
+
end
|
56
|
+
|
57
|
+
def each_key
|
58
|
+
return to_enum(:each_key) unless block_given?
|
59
|
+
@keys.each { |key| yield(key) }
|
60
|
+
self
|
61
|
+
end
|
62
|
+
|
63
|
+
def each_value
|
64
|
+
return to_enum(:each_value) unless block_given?
|
65
|
+
@keys.each { |key| yield(self[key]) }
|
66
|
+
self
|
67
|
+
end
|
68
|
+
|
69
|
+
def each
|
70
|
+
return to_enum(:each) unless block_given?
|
71
|
+
@keys.each { |key| yield([key, self[key]]) }
|
72
|
+
self
|
73
|
+
end
|
74
|
+
|
75
|
+
def each_pair
|
76
|
+
return to_enum(:each_pair) unless block_given?
|
77
|
+
@keys.each { |key| yield(key, self[key]) }
|
78
|
+
self
|
79
|
+
end
|
80
|
+
|
81
|
+
alias_method :select, :find_all
|
82
|
+
|
83
|
+
def clear
|
84
|
+
super
|
85
|
+
@keys.clear
|
86
|
+
self
|
87
|
+
end
|
88
|
+
|
89
|
+
def shift
|
90
|
+
k = @keys.first
|
91
|
+
v = delete(k)
|
92
|
+
[k, v]
|
93
|
+
end
|
94
|
+
|
95
|
+
def merge!(other_hash)
|
96
|
+
if block_given?
|
97
|
+
other_hash.each { |k, v| self[k] = key?(k) ? yield(k, self[k], v) : v }
|
98
|
+
else
|
99
|
+
other_hash.each { |k, v| self[k] = v }
|
100
|
+
end
|
101
|
+
self
|
102
|
+
end
|
103
|
+
|
104
|
+
alias_method :update, :merge!
|
105
|
+
|
106
|
+
def merge(other_hash, &block)
|
107
|
+
dup.merge!(other_hash, &block)
|
108
|
+
end
|
109
|
+
|
110
|
+
# When replacing with another hash, the initial order of our keys must come from the other hash -ordered or not.
|
111
|
+
def replace(other)
|
112
|
+
super
|
113
|
+
@keys = other.keys
|
114
|
+
self
|
115
|
+
end
|
116
|
+
|
117
|
+
def inspect
|
118
|
+
"#<#{self.class} #{super}>"
|
119
|
+
end
|
120
|
+
|
121
|
+
private
|
122
|
+
|
123
|
+
def sync_keys!
|
124
|
+
@keys.delete_if { |k| !key?(k) }
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end
|
129
|
+
end
|
data/lib/thor/error.rb
ADDED
@@ -0,0 +1,32 @@
|
|
1
|
+
class Thor
|
2
|
+
# Thor::Error is raised when it's caused by wrong usage of thor classes. Those
|
3
|
+
# errors have their backtrace suppressed and are nicely shown to the user.
|
4
|
+
#
|
5
|
+
# Errors that are caused by the developer, like declaring a method which
|
6
|
+
# overwrites a thor keyword, SHOULD NOT raise a Thor::Error. This way, we
|
7
|
+
# ensure that developer errors are shown with full backtrace.
|
8
|
+
class Error < StandardError
|
9
|
+
end
|
10
|
+
|
11
|
+
# Raised when a command was not found.
|
12
|
+
class UndefinedCommandError < Error
|
13
|
+
end
|
14
|
+
UndefinedTaskError = UndefinedCommandError
|
15
|
+
|
16
|
+
class AmbiguousCommandError < Error
|
17
|
+
end
|
18
|
+
AmbiguousTaskError = AmbiguousCommandError
|
19
|
+
|
20
|
+
# Raised when a command was found, but not invoked properly.
|
21
|
+
class InvocationError < Error
|
22
|
+
end
|
23
|
+
|
24
|
+
class UnknownArgumentError < Error
|
25
|
+
end
|
26
|
+
|
27
|
+
class RequiredArgumentMissingError < InvocationError
|
28
|
+
end
|
29
|
+
|
30
|
+
class MalformattedArgumentError < InvocationError
|
31
|
+
end
|
32
|
+
end
|