wycats-thor 0.9.8 → 0.10.26

Sign up to get free protection for your applications and to get access to all the features.
data/lib/thor.rb CHANGED
@@ -1,146 +1,234 @@
1
1
  $:.unshift File.expand_path(File.dirname(__FILE__))
2
- require "thor/options"
3
- require "thor/util"
4
- require "thor/task"
5
- require "thor/task_hash"
2
+ require 'thor/base'
3
+ require 'thor/group'
4
+ require 'thor/actions'
6
5
 
7
6
  class Thor
8
- attr_accessor :options
9
-
10
- def self.map(map)
11
- @map ||= superclass.instance_variable_get("@map") || {}
12
- map.each do |key, value|
13
- if key.respond_to?(:each)
14
- key.each {|subkey| @map[subkey] = value}
7
+
8
+ class << self
9
+
10
+ # Sets the default task when thor is executed without an explicit task to be called.
11
+ #
12
+ # ==== Parameters
13
+ # meth<Symbol>:: name of the defaut task
14
+ #
15
+ def default_task(meth=nil)
16
+ case meth
17
+ when :none
18
+ @default_task = 'help'
19
+ when nil
20
+ @default_task ||= from_superclass(:default_task, 'help')
21
+ else
22
+ @default_task = meth.to_s
23
+ end
24
+ end
25
+
26
+ # Defines the usage and the description of the next task.
27
+ #
28
+ # ==== Parameters
29
+ # usage<String>
30
+ # description<String>
31
+ #
32
+ def desc(usage, description, options={})
33
+ if options[:for]
34
+ task = find_and_refresh_task(options[:for])
35
+ task.usage = usage if usage
36
+ task.description = description if description
15
37
  else
16
- @map[key] = value
38
+ @usage, @desc = usage, description
17
39
  end
18
40
  end
19
- end
20
41
 
21
- def self.desc(usage, description)
22
- @usage, @desc = usage, description
23
- end
24
-
25
- def self.group(name)
26
- @group_name = name.to_s
27
- end
28
-
29
- def self.group_name
30
- @group_name || 'standard'
31
- end
32
-
33
- def self.method_options(opts)
34
- @method_options = opts
35
- end
42
+ # Maps an input to a task. If you define:
43
+ #
44
+ # map "-T" => "list"
45
+ #
46
+ # Running:
47
+ #
48
+ # thor -T
49
+ #
50
+ # Will invoke the list task.
51
+ #
52
+ # ==== Parameters
53
+ # Hash[String|Array => Symbol]:: Maps the string or the strings in the array to the given task.
54
+ #
55
+ def map(mappings=nil)
56
+ @map ||= from_superclass(:map, {})
57
+
58
+ if mappings
59
+ mappings.each do |key, value|
60
+ if key.respond_to?(:each)
61
+ key.each {|subkey| @map[subkey] = value}
62
+ else
63
+ @map[key] = value
64
+ end
65
+ end
66
+ end
36
67
 
37
- def self.subclass_files
38
- @subclass_files ||= Hash.new {|h,k| h[k] = []}
39
- end
40
-
41
- def self.subclasses
42
- @subclasses ||= []
43
- end
44
-
45
- def self.tasks
46
- @tasks ||= TaskHash.new(self)
47
- end
68
+ @map
69
+ end
48
70
 
49
- def self.opts
50
- (@opts || {}).merge(self == Thor ? {} : superclass.opts)
51
- end
71
+ # Declares the options for the next task to be declared.
72
+ #
73
+ # ==== Parameters
74
+ # Hash[Symbol => Object]:: The hash key is the name of the option and the value
75
+ # is the type of the option. Can be :string, :array, :hash, :boolean, :numeric
76
+ # or :required (string). If you give a value, the type of the value is used.
77
+ #
78
+ def method_options(options=nil)
79
+ @method_options ||= {}
80
+ build_options(options, @method_options) if options
81
+ @method_options
82
+ end
52
83
 
53
- def self.[](task)
54
- namespaces = task.split(":")
55
- klass = Thor::Util.constant_from_thor_path(namespaces[0...-1].join(":"))
56
- raise Error, "`#{klass}' is not a Thor class" unless klass <= Thor
57
- klass.tasks[namespaces.last]
58
- end
84
+ # Adds an option to the set of class options. If :for is given as option,
85
+ # it allows you to change the options from a previous defined task.
86
+ #
87
+ # def previous_task
88
+ # # magic
89
+ # end
90
+ #
91
+ # method_options :foo => :bar, :for => :previous_task
92
+ #
93
+ # def next_task
94
+ # # magic
95
+ # end
96
+ #
97
+ # ==== Parameters
98
+ # name<Symbol>:: The name of the argument.
99
+ # options<Hash>:: Described below.
100
+ #
101
+ # ==== Options
102
+ # :desc - Description for the argument.
103
+ # :required - If the argument is required or not.
104
+ # :default - Default value for this argument. It cannot be required and have default values.
105
+ # :aliases - Aliases for this option.
106
+ # :type - The type of the argument, can be :string, :hash, :array, :numeric, :boolean or :default.
107
+ # Default accepts arguments as booleans (--switch) or as strings (--switch=VALUE).
108
+ # :group - The group for this options. Use by class options to output options in different levels.
109
+ # :banner - String to show on usage notes.
110
+ #
111
+ def method_option(name, options)
112
+ scope = if options[:for]
113
+ find_and_refresh_task(options[:for]).options
114
+ else
115
+ method_options
116
+ end
59
117
 
60
- def self.maxima
61
- @maxima ||= begin
62
- max_usage = tasks.map {|_, t| t.usage}.max {|x,y| x.to_s.size <=> y.to_s.size}.size
63
- max_desc = tasks.map {|_, t| t.description}.max {|x,y| x.to_s.size <=> y.to_s.size}.size
64
- max_opts = tasks.map {|_, t| t.opts ? t.opts.formatted_usage : ""}.max {|x,y| x.to_s.size <=> y.to_s.size}.size
65
- Struct.new(:description, :usage, :opt).new(max_desc, max_usage, max_opts)
118
+ build_option(name, options, scope)
66
119
  end
67
- end
68
-
69
- def self.start(args = ARGV)
70
- options = Thor::Options.new(self.opts)
71
- opts = options.parse(args, false)
72
- args = options.trailing_non_opts
73
-
74
- meth = args.first
75
- meth = @map[meth].to_s if @map && @map[meth]
76
- meth ||= "help"
77
-
78
- tasks[meth].parse new(opts, *args), args[1..-1]
79
- rescue Thor::Error => e
80
- $stderr.puts e.message
81
- end
82
120
 
83
- class << self
84
- protected
85
- def inherited(klass)
86
- register_klass_file klass
121
+ # Parses the task and options from the given args, instantiate the class
122
+ # and invoke the task. This method is used when the arguments must be parsed
123
+ # from an array. If you are inside Ruby and want to use a Thor class, you
124
+ # can simply initialize it:
125
+ #
126
+ # script = MyScript.new(args, options, config)
127
+ # script.invoke(:task, first_arg, second_arg, third_arg)
128
+ #
129
+ def start(given_args=ARGV, config={})
130
+ super do
131
+ meth = normalize_task_name(given_args.shift)
132
+ task = all_tasks[meth]
133
+
134
+ if task
135
+ args, opts = Thor::Options.split(given_args)
136
+ config.merge!(:task_options => task.options)
137
+ else
138
+ args, opts = given_args, {}
139
+ end
140
+
141
+ task ||= Task.dynamic(meth)
142
+ trailing = args[Range.new(arguments.size, -1)]
143
+ new(args, opts, config).invoke(task, trailing || [])
144
+ end
87
145
  end
88
146
 
89
- def method_added(meth)
90
- meth = meth.to_s
91
-
92
- if meth == "initialize"
93
- @opts = @method_options
94
- @method_options = nil
95
- return
147
+ # Prints help information. If a task name is given, it shows information
148
+ # only about the specific task.
149
+ #
150
+ # ==== Parameters
151
+ # meth<String>:: An optional task name to print usage information about.
152
+ #
153
+ # ==== Options
154
+ # namespace:: When true, shows the namespace in the output before the usage.
155
+ # skip_inherited:: When true, does not show tasks from superclass.
156
+ #
157
+ def help(shell, meth=nil, options={})
158
+ meth, options = nil, meth if meth.is_a?(Hash)
159
+
160
+ if meth
161
+ task = all_tasks[meth]
162
+ raise UndefinedTaskError, "task '#{meth}' could not be found in namespace '#{self.namespace}'" unless task
163
+
164
+ shell.say "Usage:"
165
+ shell.say " #{banner(task, options[:namespace])}"
166
+ shell.say
167
+ class_options_help(shell, "Class")
168
+ shell.say task.description
169
+ else
170
+ list = (options[:short] ? tasks : all_tasks).map do |_, task|
171
+ [ banner(task, options[:namespace]), task.short_description || '' ]
172
+ end
173
+
174
+ if options[:short]
175
+ shell.print_table(list, :emphasize_last => true)
176
+ else
177
+ shell.say "Tasks:"
178
+ shell.print_table(list, :ident => 2, :emphasize_last => true)
179
+ shell.say
180
+
181
+ class_options_help(shell, "Class")
182
+ end
96
183
  end
184
+ end
97
185
 
98
- return if !public_instance_methods.include?(meth) || !@usage
99
- register_klass_file self
186
+ protected
100
187
 
101
- tasks[meth] = Task.new(meth, @desc, @usage, @method_options)
188
+ # The banner for this class. You can customize it if you are invoking the
189
+ # thor class by another means which is not the Thor::Runner. It receives
190
+ # the task that is going to be invoked and if the namespace should be
191
+ # displayed.
192
+ #
193
+ def banner(task, namespace=true) #:nodoc:
194
+ task.formatted_usage(self, namespace)
195
+ end
102
196
 
103
- @usage, @desc, @method_options = nil
104
- end
197
+ def baseclass #:nodoc:
198
+ Thor
199
+ end
105
200
 
106
- def register_klass_file(klass, file = caller[1].split(":")[0])
107
- unless self == Thor
108
- superclass.register_klass_file(klass, file)
109
- return
201
+ def valid_task?(meth) #:nodoc:
202
+ @usage && @desc
110
203
  end
111
204
 
112
- file_subclasses = subclass_files[File.expand_path(file)]
113
- file_subclasses << klass unless file_subclasses.include?(klass)
114
- subclasses << klass unless subclasses.include?(klass)
115
- end
116
- end
205
+ def create_task(meth) #:nodoc:
206
+ tasks[meth.to_s] = Thor::Task.new(meth, @desc, @usage, method_options)
207
+ @usage, @desc, @method_options = nil
208
+ end
117
209
 
118
- def initialize(opts = {}, *args)
119
- end
120
-
121
- map ["-h", "-?", "--help", "-D"] => :help
122
-
123
- desc "help [TASK]", "describe available tasks or one specific task"
124
- def help(task = nil)
125
- if task
126
- if task.include? ?:
127
- task = self.class[task]
128
- namespace = true
129
- else
130
- task = self.class.tasks[task]
210
+ def initialize_added #:nodoc:
211
+ class_options.merge!(method_options)
212
+ @method_options = nil
131
213
  end
132
214
 
133
- puts task.formatted_usage(namespace)
134
- puts task.description
135
- else
136
- puts "Options"
137
- puts "-------"
138
- self.class.tasks.each do |_, task|
139
- format = "%-" + (self.class.maxima.usage + self.class.maxima.opt + 4).to_s + "s"
140
- print format % ("#{task.formatted_usage}")
141
- puts task.description.split("\n").first
215
+ # Receives a task name (can be nil), and try to get a map from it.
216
+ # If a map can't be found use the sent name or the default task.
217
+ #
218
+ def normalize_task_name(meth) #:nodoc:
219
+ mapping = map[meth.to_s]
220
+ meth = mapping || meth || default_task
221
+ meth.to_s.gsub('-','_') # treat foo-bar > foo_bar
142
222
  end
143
- end
223
+
224
+ end
225
+
226
+ include Thor::Base
227
+
228
+ map HELP_MAPPINGS => :help
229
+
230
+ desc "help [TASK]", "Describe available tasks or one specific task"
231
+ def help(task=nil)
232
+ self.class.help(shell, task, :namespace => task && task.include?(?:))
144
233
  end
145
-
146
234
  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.8
4
+ version: 0.10.26
5
5
  platform: ruby
6
6
  authors:
7
7
  - Yehuda Katz
@@ -9,11 +9,11 @@ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
11
 
12
- date: 2008-08-27 00:00:00 -07:00
12
+ date: 2009-07-04 00:00:00 -07:00
13
13
  default_executable:
14
14
  dependencies: []
15
15
 
16
- description: A gem that maps options to a class
16
+ description: A scripting framework that replaces rake, sake and rubigen
17
17
  email: wycats@gmail.com
18
18
  executables:
19
19
  - thor
@@ -22,8 +22,8 @@ extensions: []
22
22
 
23
23
  extra_rdoc_files:
24
24
  - README.markdown
25
- - CHANGELOG.rdoc
26
25
  - LICENSE
26
+ - CHANGELOG.rdoc
27
27
  files:
28
28
  - README.markdown
29
29
  - LICENSE
@@ -31,18 +31,43 @@ files:
31
31
  - Rakefile
32
32
  - bin/rake2thor
33
33
  - bin/thor
34
+ - lib/thor.rb
34
35
  - lib/thor
35
36
  - lib/thor/error.rb
36
- - lib/thor/options.rb
37
- - lib/thor/ordered_hash.rb
37
+ - lib/thor/base.rb
38
+ - lib/thor/group.rb
39
+ - lib/thor/actions
40
+ - lib/thor/actions/templater.rb
41
+ - lib/thor/actions/copy_file.rb
42
+ - lib/thor/actions/inject_into_file.rb
43
+ - lib/thor/actions/directory.rb
44
+ - lib/thor/actions/template.rb
45
+ - lib/thor/actions/get.rb
46
+ - lib/thor/actions/create_file.rb
47
+ - lib/thor/actions/empty_directory.rb
48
+ - lib/thor/util.rb
38
49
  - lib/thor/runner.rb
39
- - lib/thor/task.rb
40
- - lib/thor/task_hash.rb
50
+ - lib/thor/actions.rb
51
+ - lib/thor/parser.rb
52
+ - lib/thor/shell
53
+ - lib/thor/shell/basic.rb
54
+ - lib/thor/shell/color.rb
55
+ - lib/thor/invocation.rb
56
+ - lib/thor/parser
57
+ - lib/thor/parser/argument.rb
58
+ - lib/thor/parser/option.rb
59
+ - lib/thor/parser/options.rb
60
+ - lib/thor/parser/arguments.rb
61
+ - lib/thor/tasks.rb
62
+ - lib/thor/core_ext
63
+ - lib/thor/core_ext/hash_with_indifferent_access.rb
64
+ - lib/thor/core_ext/ordered_hash.rb
41
65
  - lib/thor/tasks
66
+ - lib/thor/tasks/install.rb
67
+ - lib/thor/tasks/spec.rb
42
68
  - lib/thor/tasks/package.rb
43
- - lib/thor/tasks.rb
44
- - lib/thor/util.rb
45
- - lib/thor.rb
69
+ - lib/thor/shell.rb
70
+ - lib/thor/task.rb
46
71
  has_rdoc: true
47
72
  homepage: http://yehudakatz.com
48
73
  post_install_message:
@@ -67,7 +92,7 @@ requirements: []
67
92
  rubyforge_project: thor
68
93
  rubygems_version: 1.2.0
69
94
  signing_key:
70
- specification_version: 2
71
- summary: A gem that maps options to a class
95
+ specification_version: 3
96
+ summary: A scripting framework that replaces rake, sake and rubigen
72
97
  test_files: []
73
98
 
data/lib/thor/options.rb DELETED
@@ -1,242 +0,0 @@
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
- @leading_non_opts, @trailing_non_opts = [], []
70
-
71
- @switches = switches.inject({}) do |mem, (name, type)|
72
- if name.is_a?(Array)
73
- name, *shorts = name
74
- else
75
- name = name.to_s
76
- shorts = []
77
- end
78
- # we need both nice and dasherized form of switch name
79
- if name.index('-') == 0
80
- nice_name = undasherize name
81
- else
82
- nice_name = name
83
- name = dasherize name
84
- end
85
- # if there are no shortcuts specified, generate one using the first character
86
- shorts << "-" + nice_name[0,1] if shorts.empty? and nice_name.length > 1
87
- shorts.each { |short| @shorts[short] = name }
88
-
89
- # normalize type
90
- case type
91
- when TrueClass then type = :boolean
92
- when String
93
- @defaults[nice_name] = type
94
- type = :optional
95
- when Numeric
96
- @defaults[nice_name] = type
97
- type = :numeric
98
- end
99
-
100
- mem[name] = type
101
- mem
102
- end
103
-
104
- # remove shortcuts that happen to coincide with any of the main switches
105
- @shorts.keys.each do |short|
106
- @shorts.delete(short) if @switches.key?(short)
107
- end
108
- end
109
-
110
- def parse(args, skip_leading_non_opts = true)
111
- @args = args
112
- # start with Thor::Options::Hash pre-filled with defaults
113
- hash = Hash.new @defaults
114
-
115
- @leading_non_opts = []
116
- if skip_leading_non_opts
117
- @leading_non_opts << shift until current_is_option? || @args.empty?
118
- end
119
-
120
- while current_is_option?
121
- case shift
122
- when SHORT_SQ_RE
123
- unshift $1.split('').map { |f| "-#{f}" }
124
- next
125
- when EQ_RE, SHORT_NUM
126
- unshift $2
127
- switch = $1
128
- when LONG_RE, SHORT_RE
129
- switch = $1
130
- end
131
-
132
- switch = normalize_switch(switch)
133
- nice_name = undasherize(switch)
134
- type = switch_type(switch)
135
-
136
- case type
137
- when :required
138
- assert_value!(switch)
139
- raise Error, "cannot pass switch '#{peek}' as an argument" if valid?(peek)
140
- hash[nice_name] = shift
141
- when :optional
142
- hash[nice_name] = peek.nil? || valid?(peek) || shift
143
- when :boolean
144
- hash[nice_name] = true
145
- when :numeric
146
- assert_value!(switch)
147
- unless peek =~ NUMERIC and $& == peek
148
- raise Error, "expected numeric value for '#{switch}'; got #{peek.inspect}"
149
- end
150
- hash[nice_name] = $&.index('.') ? shift.to_f : shift.to_i
151
- end
152
- end
153
-
154
- @trailing_non_opts = @args
155
-
156
- check_required! hash
157
- hash.freeze
158
- hash
159
- end
160
-
161
- def formatted_usage
162
- return "" if @switches.empty?
163
- @switches.map do |opt, type|
164
- case type
165
- when :boolean
166
- "[#{opt}]"
167
- when :required
168
- opt + "=" + opt.gsub(/\-/, "").upcase
169
- else
170
- sample = @defaults[undasherize(opt)]
171
- sample ||= case type
172
- when :optional then undasherize(opt).gsub(/\-/, "_").upcase
173
- when :numeric then "N"
174
- end
175
- "[" + opt + "=" + sample.to_s + "]"
176
- end
177
- end.join(" ")
178
- end
179
-
180
- alias :to_s :formatted_usage
181
-
182
- private
183
-
184
- def assert_value!(switch)
185
- raise Error, "no value provided for argument '#{switch}'" if peek.nil?
186
- end
187
-
188
- def undasherize(str)
189
- str.sub(/^-{1,2}/, '')
190
- end
191
-
192
- def dasherize(str)
193
- (str.length > 1 ? "--" : "-") + str
194
- end
195
-
196
- def peek
197
- @args.first
198
- end
199
-
200
- def shift
201
- @args.shift
202
- end
203
-
204
- def unshift(arg)
205
- unless arg.kind_of?(Array)
206
- @args.unshift(arg)
207
- else
208
- @args = arg + @args
209
- end
210
- end
211
-
212
- def valid?(arg)
213
- @switches.key?(arg) or @shorts.key?(arg)
214
- end
215
-
216
- def current_is_option?
217
- case peek
218
- when LONG_RE, SHORT_RE, EQ_RE, SHORT_NUM
219
- valid?($1)
220
- when SHORT_SQ_RE
221
- $1.split('').any? { |f| valid?("-#{f}") }
222
- end
223
- end
224
-
225
- def normalize_switch(switch)
226
- @shorts.key?(switch) ? @shorts[switch] : switch
227
- end
228
-
229
- def switch_type(switch)
230
- @switches[switch]
231
- end
232
-
233
- def check_required!(hash)
234
- for name, type in @switches
235
- if type == :required and !hash[undasherize(name)]
236
- raise Error, "no value provided for required argument '#{name}'"
237
- end
238
- end
239
- end
240
-
241
- end
242
- end