josevalim-thor 0.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,59 @@
1
+ class Thor
2
+ module CoreExt
3
+
4
+ # A hash with indifferent access and magic predicates.
5
+ #
6
+ # hash = Thor::CoreExt::HashWithIndifferentAccess.new 'foo' => 'bar', 'baz' => 'bee', 'force' => true
7
+ #
8
+ # hash[:foo] #=> 'bar'
9
+ # hash['foo'] #=> 'bar'
10
+ # hash.foo? #=> true
11
+ #
12
+ class HashWithIndifferentAccess < ::Hash
13
+
14
+ def initialize(hash)
15
+ super()
16
+
17
+ hash.each do |key, value|
18
+ if key.is_a?(Symbol)
19
+ self[key.to_s] = value
20
+ else
21
+ self[key] = value
22
+ end
23
+ end
24
+ end
25
+
26
+ def [](key)
27
+ super(convert_key(key))
28
+ end
29
+
30
+ def delete(key)
31
+ super(convert_key(key))
32
+ end
33
+
34
+ def values_at(*indices)
35
+ indices.collect { |key| self[convert_key(key)] }
36
+ end
37
+
38
+ protected
39
+
40
+ def convert_key(key)
41
+ key.is_a?(Symbol) ? key.to_s : key
42
+ end
43
+
44
+ # Magic predicates. For instance:
45
+ #
46
+ # options.force? # => !!options['force']
47
+ #
48
+ def method_missing(method, *args, &block)
49
+ method = method.to_s
50
+ if method =~ /^(\w+)\?$/
51
+ !!self[$1]
52
+ else
53
+ self[method]
54
+ end
55
+ end
56
+
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,133 @@
1
+ class Thor #:nodoc:
2
+ module CoreExt #:nodoc:
3
+
4
+ # This class is based on the Ruby 1.9 ordered hashes.
5
+ #
6
+ # It keeps the semantics and most of the efficiency of normal hashes
7
+ # while also keeping track of the order in which elements were set.
8
+ #
9
+ class OrderedHash #:nodoc:
10
+ Node = Struct.new(:key, :value, :next, :prev)
11
+ include Enumerable
12
+
13
+ def initialize
14
+ @hash = {}
15
+ end
16
+
17
+ # Called on clone. It gets all the notes from the cloned object, dup them
18
+ # and assign the duped objects siblings.
19
+ #
20
+ def initialize_copy(other)
21
+ @hash = {}
22
+
23
+ array = []
24
+ other.each do |key, value|
25
+ array << (@hash[key] = Node.new(key, value))
26
+ end
27
+
28
+ array.each_with_index do |node, i|
29
+ node.next = array[i + 1]
30
+ node.prev = array[i - 1] if i > 0
31
+ end
32
+
33
+ @first = array.first
34
+ @last = array.last
35
+ end
36
+
37
+ def [](key)
38
+ @hash[key] && @hash[key].value
39
+ end
40
+
41
+ def []=(key, value)
42
+ if old = @hash[key]
43
+ node = old.dup
44
+ node.value = value
45
+
46
+ @first = node if @first == old
47
+ @last = node if @last == old
48
+
49
+ old.prev.next = node if old.prev
50
+ old.next.prev = node if old.next
51
+ else
52
+ node = Node.new(key, value)
53
+
54
+ if @first.nil?
55
+ @first = @last = node
56
+ else
57
+ node.prev = @last
58
+ @last.next = node
59
+ @last = node
60
+ end
61
+ end
62
+
63
+ @hash[key] = node
64
+ value
65
+ end
66
+
67
+ def delete(key)
68
+ if node = @hash[key]
69
+ prev_node = node.prev
70
+ next_node = node.next
71
+
72
+ next_node.prev = prev_node if next_node
73
+ prev_node.next = next_node if prev_node
74
+
75
+ @first = next_node if @first == node
76
+ @last = prev_node if @last == node
77
+
78
+ value = node.value
79
+ end
80
+
81
+ @hash[key] = nil
82
+ value
83
+ end
84
+
85
+ def each
86
+ return unless @first
87
+ yield [@first.key, @first.value]
88
+ node = @first
89
+ yield [node.key, node.value] while node = node.next
90
+ self
91
+ end
92
+
93
+ def keys
94
+ self.map { |k, v| k }
95
+ end
96
+
97
+ def values
98
+ self.map { |k, v| v }
99
+ end
100
+
101
+ def merge(other)
102
+ new = clone
103
+ other.each do |key, value|
104
+ new[key] = value
105
+ end
106
+ new
107
+ end
108
+
109
+ def merge!(other)
110
+ other.each do |key, value|
111
+ self[key] = value
112
+ end
113
+ self
114
+ end
115
+
116
+ def empty?
117
+ @hash.empty?
118
+ end
119
+
120
+ def to_a
121
+ inject([]) do |array, (key, value)|
122
+ array << [key, value]
123
+ array
124
+ end
125
+ end
126
+
127
+ def to_s
128
+ to_a.inspect
129
+ end
130
+ alias :inspect :to_s
131
+ end
132
+ end
133
+ end
data/lib/thor/error.rb ADDED
@@ -0,0 +1,27 @@
1
+ class Thor
2
+ # Thor::Error is raised when it's caused by the user invoking the task and
3
+ # only errors that inherit from it are rescued.
4
+ #
5
+ # So, for example, if the developer declares a required argument after an
6
+ # option, it should raise an ::ArgumentError and not ::Thor::ArgumentError,
7
+ # because it was caused by the developer and not the "final user".
8
+ #
9
+ class Error < StandardError #:nodoc:
10
+ end
11
+
12
+ # Raised when a task was not found.
13
+ #
14
+ class UndefinedTaskError < Error #:nodoc:
15
+ end
16
+
17
+ # Raised when a task was found, but not invoked properly.
18
+ #
19
+ class InvocationError < Error #:nodoc:
20
+ end
21
+
22
+ class RequiredArgumentMissingError < InvocationError #:nodoc:
23
+ end
24
+
25
+ class MalformattedArgumentError < InvocationError #:nodoc:
26
+ end
27
+ end
data/lib/thor/group.rb ADDED
@@ -0,0 +1,90 @@
1
+ class Thor::Group
2
+
3
+ class << self
4
+
5
+ # The descrition for this Thor::Group as a whole.
6
+ #
7
+ # ==== Parameters
8
+ # description<String>:: The description for this Thor::Group.
9
+ #
10
+ def desc(description=nil)
11
+ case description
12
+ # TODO When a symbol is given, read a file in the current directory
13
+ # when Symbol
14
+ # @desc = File.read
15
+ when nil
16
+ @desc ||= from_superclass(:desc, nil)
17
+ else
18
+ @desc = description
19
+ end
20
+ end
21
+
22
+ # Start in Thor::Group works differently. It invokes all tasks inside the
23
+ # class and does not have to parse task options.
24
+ #
25
+ def start(args=ARGV, config={})
26
+ config[:shell] ||= Thor::Base.shell.new
27
+
28
+ if Thor::HELP_MAPPINGS.include?(args.first)
29
+ help(config[:shell])
30
+ else
31
+ opts = Thor::Options.new(class_options)
32
+ opts.parse(args)
33
+
34
+ new(opts.arguments, opts.options, config).invoke_all
35
+ end
36
+ rescue Thor::Error => e
37
+ config[:shell].error e.message
38
+ end
39
+
40
+ # Prints help information.
41
+ #
42
+ # ==== Options
43
+ # short:: When true, shows only usage.
44
+ #
45
+ def help(shell, options={})
46
+ if options[:short]
47
+ shell.say "#{self.namespace} #{self.class_options.map {|_,o| o.usage}.join(' ')}"
48
+ else
49
+ shell.say "Usage:"
50
+ shell.say " #{self.namespace} #{self.arguments.map{|o| o.usage}.join(' ')}"
51
+ shell.say
52
+
53
+ list = self.class_options.map do |_, option|
54
+ next if option.argument?
55
+ [ option.usage, option.description || '' ]
56
+ end.compact
57
+
58
+ unless list.empty?
59
+ shell.say "Global options:"
60
+ shell.print_table(list, :emphasize_last => true, :ident => 2)
61
+ shell.say
62
+ end
63
+
64
+ shell.say self.desc if self.desc
65
+ end
66
+ end
67
+
68
+ protected
69
+
70
+ def baseclass #:nodoc:
71
+ Thor::Group
72
+ end
73
+
74
+ def valid_task?(meth) #:nodoc:
75
+ public_instance_methods.include?(meth)
76
+ end
77
+
78
+ def create_task(meth) #:nodoc:
79
+ tasks[meth.to_s] = Thor::Task.new(meth, nil, nil, nil)
80
+ end
81
+ end
82
+
83
+ # Invokes all tasks in the instance.
84
+ #
85
+ def invoke_all
86
+ self.class.all_tasks.map { |_, task| task.run(self) }
87
+ end
88
+
89
+ include Thor::Base
90
+ end
@@ -0,0 +1,210 @@
1
+ class Thor
2
+ class Option
3
+ attr_reader :name, :description, :required, :type, :default, :aliases
4
+
5
+ VALID_TYPES = [:boolean, :numeric, :hash, :array, :string, :default]
6
+
7
+ def initialize(name, description=nil, required=nil, type=nil, default=nil, aliases=nil)
8
+ raise ArgumentError, "Option name can't be nil." if name.nil?
9
+ raise ArgumentError, "Option cannot be required and have default values." if required && !default.nil?
10
+ raise ArgumentError, "Type :#{type} is not valid for options." if type && !VALID_TYPES.include?(type.to_sym)
11
+
12
+ @name = name.to_s
13
+ @description = description
14
+ @required = required || false
15
+ @type = (type || :default).to_sym
16
+ @default = default
17
+ @aliases = [*aliases].compact
18
+ end
19
+
20
+ # This parse quick options given as method_options. It makes several
21
+ # assumptions, but you can be more specific using the option method.
22
+ #
23
+ # parse :foo => "bar"
24
+ # #=> Option foo with default value bar
25
+ #
26
+ # parse [:foo, :baz] => "bar"
27
+ # #=> Option foo with default value bar and alias :baz
28
+ #
29
+ # parse :foo => :required
30
+ # #=> Required option foo without default value
31
+ #
32
+ # parse :foo => :optional
33
+ # #=> Optional foo without default value
34
+ #
35
+ # parse :foo => 2
36
+ # #=> Option foo with default value 2 and type numeric
37
+ #
38
+ # parse :foo => :numeric
39
+ # #=> Option foo without default value and type numeric
40
+ #
41
+ # parse :foo => true
42
+ # #=> Option foo with default value true and type boolean
43
+ #
44
+ # The valid types are :boolean, :numeric, :hash, :array and :string. If none
45
+ # is given a default type is assumed. This default type accepts arguments as
46
+ # string (--foo=value) or booleans (just --foo).
47
+ #
48
+ # By default all options are optional, unless :required is given.
49
+ #
50
+ def self.parse(key, value)
51
+ if key.is_a?(Array)
52
+ name, *aliases = key
53
+ else
54
+ name, aliases = key, []
55
+ end
56
+
57
+ name = name.to_s
58
+ default = value
59
+
60
+ type = case value
61
+ when Symbol
62
+ default = nil
63
+
64
+ if VALID_TYPES.include?(value)
65
+ value
66
+ elsif required = (value == :required)
67
+ :string
68
+ end
69
+ when TrueClass, FalseClass
70
+ :boolean
71
+ when Numeric
72
+ :numeric
73
+ when Hash, Array, String
74
+ value.class.name.downcase.to_sym
75
+ end
76
+
77
+ self.new(name.to_s, nil, required, type, default, aliases)
78
+ end
79
+
80
+ def argument?
81
+ false
82
+ end
83
+
84
+ def required?
85
+ required
86
+ end
87
+
88
+ def optional?
89
+ !required
90
+ end
91
+
92
+ def <=>(other)
93
+ self.position <=> other.position
94
+ end
95
+
96
+ # Returns true if this type requires an input to be given. Just :default
97
+ # and :boolean does not require an input.
98
+ #
99
+ def input_required?
100
+ [ :numeric, :hash, :array, :string ].include?(type)
101
+ end
102
+
103
+ def switch_name
104
+ @switch_name ||= dasherized? ? name : dasherize(name)
105
+ end
106
+
107
+ def human_name
108
+ @human_name ||= dasherized? ? undasherize(name) : name
109
+ end
110
+
111
+ def dasherized?
112
+ name.index('-') == 0
113
+ end
114
+
115
+ def undasherize(str)
116
+ str.sub(/^-{1,2}/, '')
117
+ end
118
+
119
+ def dasherize(str)
120
+ (str.length > 1 ? "--" : "-") + str
121
+ end
122
+
123
+ def usage
124
+ sample = formatted_default || formatted_value
125
+
126
+ sample = if sample
127
+ "#{switch_name}=#{sample}"
128
+ else
129
+ switch_name
130
+ end
131
+
132
+ sample = "[#{sample}]" unless required?
133
+ sample = "#{aliases.join(', ')}, #{sample}" unless aliases.empty?
134
+ sample
135
+ end
136
+
137
+ protected
138
+
139
+ def position
140
+ if argument?
141
+ -1
142
+ elsif required?
143
+ 0
144
+ else
145
+ 1
146
+ end
147
+ end
148
+
149
+ def formatted_default
150
+ return unless default
151
+
152
+ case type
153
+ when :boolean
154
+ nil
155
+ when :numeric
156
+ default.to_s
157
+ when :string, :default
158
+ default.empty? ? formatted_value : default.to_s
159
+ when :hash
160
+ if default.empty?
161
+ formatted_value
162
+ else
163
+ default.inject([]) do |mem, (key, value)|
164
+ mem << "#{key}:#{value}".gsub(/\s/, '_')
165
+ mem
166
+ end.join(' ')
167
+ end
168
+ when :array
169
+ default.empty? ? formatted_value : default.join(" ")
170
+ end
171
+ end
172
+
173
+ def formatted_value
174
+ case type
175
+ when :boolean
176
+ nil
177
+ when :string, :default
178
+ human_name.upcase
179
+ when :numeric
180
+ "N"
181
+ when :hash
182
+ "key:value"
183
+ when :array
184
+ "one two three"
185
+ end
186
+ end
187
+ end
188
+
189
+ # Argument is a subset of option. It does not support :boolean and :default
190
+ # as types.
191
+ #
192
+ class Argument < Option
193
+ VALID_TYPES = [:numeric, :hash, :array, :string]
194
+
195
+ def initialize(name, description=nil, required=true, type=:string, default=nil)
196
+ raise ArgumentError, "Argument name can't be nil." if name.nil?
197
+ raise ArgumentError, "Type :#{type} is not valid for arguments." if type && !VALID_TYPES.include?(type.to_sym)
198
+
199
+ super(name, description, required, type || :string, default, [])
200
+ end
201
+
202
+ def argument?
203
+ true
204
+ end
205
+
206
+ def usage
207
+ required? ? formatted_value : "[#{formatted_value}]"
208
+ end
209
+ end
210
+ end