josevalim-thor 0.10.10 → 0.10.11

Sign up to get free protection for your applications and to get access to all the features.
data/lib/thor/actions.rb CHANGED
@@ -306,10 +306,10 @@ class Thor
306
306
 
307
307
  protected
308
308
 
309
- # Update dump_config to dump also behavior and root.
309
+ # Allow current root to be sent as configuration value to the invoked class.
310
310
  #
311
- def _dump_config #:nodoc:
312
- super.merge!(:behavior => self.behavior, :root => @root_stack[0])
311
+ def _overrides_config #:nodoc:
312
+ super.merge!(:root => self.root)
313
313
  end
314
314
 
315
315
  end
data/lib/thor/base.rb CHANGED
@@ -1,64 +1,81 @@
1
1
  require 'thor/core_ext/hash_with_indifferent_access'
2
2
  require 'thor/core_ext/ordered_hash'
3
- require 'thor/shell/basic'
4
3
  require 'thor/error'
4
+ require 'thor/shell'
5
+ require 'thor/invocation'
5
6
  require 'thor/options'
6
7
  require 'thor/task'
7
8
  require 'thor/util'
8
9
 
9
10
  class Thor
10
11
  HELP_MAPPINGS = %w(-h -? --help -D)
11
- RESERVED_TASK_NAMES = %w(all invoke shell options behavior root destination_root relative_root source_root)
12
+ THOR_RESERVED_WORDS = %w(all invoke shell options behavior root destination_root relative_root source_root)
12
13
 
13
14
  class Maxima < Struct.new(:usage, :options, :class_options)
14
15
  end
15
16
 
16
17
  module Base
18
+ attr_accessor :options
17
19
 
18
- def self.included(base) #:nodoc:
19
- base.send :extend, ClassMethods
20
- base.send :include, SingletonMethods
21
- end
22
-
23
- # Returns the classes that inherits from Thor or Thor::Group.
20
+ # It receives arguments in an Array and two hashes, one for options and
21
+ # other for configuration.
24
22
  #
25
- # ==== Returns
26
- # Array[Class]
23
+ # Notice that it does not check arguments type neither if all required
24
+ # arguments were supplied. It should be done by the parser.
27
25
  #
28
- def self.subclasses
29
- @subclasses ||= []
30
- end
31
-
32
- # Returns the files where the subclasses are kept.
26
+ # ==== Parameters
27
+ # args<Array[Object]>:: An array of objects. The objects are applied to their
28
+ # respective accessors declared with <tt>argument</tt>.
33
29
  #
34
- # ==== Returns
35
- # Hash[path<String> => Class]
30
+ # options<Hash>:: An options hash that will be available as self.options.
31
+ # The hash given is converted to a hash with indifferent
32
+ # access, magic predicates (options.skip?) and then frozen.
36
33
  #
37
- def self.subclass_files
38
- @subclass_files ||= Hash.new{ |h,k| h[k] = [] }
39
- end
40
-
41
- # Returns the shell used in all Thor classes.
34
+ # config<Hash>:: Configuration for this Thor class.
42
35
  #
43
- def self.shell
44
- @shell || Thor::Shell::Basic
45
- end
36
+ def initialize(args=[], options={}, config={})
37
+ self.class.arguments.zip(args).each do |argument, value|
38
+ send("#{argument.human_name}=", value)
39
+ end
46
40
 
47
- # Sets the shell used in all Thor classes.
48
- #
49
- def self.shell=(klass)
50
- @shell = klass
41
+ self.options = Thor::CoreExt::HashWithIndifferentAccess.new(options).freeze
51
42
  end
52
43
 
53
- # Whenever a class inherits from Thor or Thor::Group, we should track the
54
- # class and the file on Thor::Base. This is the method responsable for it.
55
- #
56
- def self.register_klass_file(klass) #:nodoc:
57
- file = caller[1].match(/(.*):\d+/)[1]
58
- Thor::Base.subclasses << klass unless Thor::Base.subclasses.include?(klass)
44
+ class << self
45
+ def included(base) #:nodoc:
46
+ base.send :extend, ClassMethods
47
+ base.send :include, Invocation
48
+ base.send :include, Shell
49
+ end
50
+
51
+ # Returns the classes that inherits from Thor or Thor::Group.
52
+ #
53
+ # ==== Returns
54
+ # Array[Class]
55
+ #
56
+ def subclasses
57
+ @subclasses ||= []
58
+ end
59
+
60
+ # Returns the files where the subclasses are kept.
61
+ #
62
+ # ==== Returns
63
+ # Hash[path<String> => Class]
64
+ #
65
+ def subclass_files
66
+ @subclass_files ||= Hash.new{ |h,k| h[k] = [] }
67
+ end
59
68
 
60
- file_subclasses = Thor::Base.subclass_files[File.expand_path(file)]
61
- file_subclasses << klass unless file_subclasses.include?(klass)
69
+ # Whenever a class inherits from Thor or Thor::Group, we should track the
70
+ # class and the file on Thor::Base. This is the method responsable for it.
71
+ #
72
+ def register_klass_file(klass) #:nodoc:
73
+ file = caller[1].match(/(.*):\d+/)[1]
74
+ Thor::Base.subclasses << klass unless Thor::Base.subclasses.include?(klass)
75
+
76
+ file_subclasses = Thor::Base.subclass_files[File.expand_path(file)]
77
+ file_subclasses << klass unless file_subclasses.include?(klass)
78
+ end
62
79
  end
63
80
 
64
81
  module ClassMethods
@@ -97,6 +114,7 @@ class Thor
97
114
  # ArgumentError:: Raised if you supply a required argument after a non required one.
98
115
  #
99
116
  def argument(name, options={})
117
+ is_thor_reserved_word?(name, :argument)
100
118
  no_tasks { attr_accessor name }
101
119
 
102
120
  required = if options.key?(:optional)
@@ -308,6 +326,26 @@ class Thor
308
326
  end
309
327
  end
310
328
 
329
+ # Parses the task and options from the given args, instantiate the class
330
+ # and invoke the task. This method is used when the arguments must be parsed
331
+ # from an array. If you are inside Ruby and want to use a Thor class, you
332
+ # can simply initialize it:
333
+ #
334
+ # script = MyScript.new(args, options, config)
335
+ # script.invoke(:task, first_arg, second_arg, third_arg)
336
+ #
337
+ def start(args=ARGV, config={})
338
+ config[:shell] ||= Thor::Base.shell.new
339
+
340
+ task = normalize_arguments(args, config)
341
+ return unless task
342
+
343
+ instance, trailing = prepare(task, args, config)
344
+ instance.invoke(task, trailing)
345
+ rescue Thor::Error => e
346
+ config[:shell].error e.message
347
+ end
348
+
311
349
  protected
312
350
 
313
351
  # Prints the class optins per group. If a class options does not belong
@@ -350,6 +388,13 @@ class Thor
350
388
  end
351
389
  end
352
390
 
391
+ # Raises an error if the word given is a Thor reserved word.
392
+ #
393
+ def is_thor_reserved_word?(word, type)
394
+ return false unless THOR_RESERVED_WORDS.include?(word.to_s)
395
+ raise ScriptError, "'#{word}' is a Thor reserved word and cannot be defined as #{type}"
396
+ end
397
+
353
398
  # Build an option and adds it to the given scope.
354
399
  #
355
400
  # ==== Parameters
@@ -408,11 +453,7 @@ class Thor
408
453
  end
409
454
 
410
455
  return if @no_tasks || !valid_task?(meth)
411
-
412
- if RESERVED_TASK_NAMES.include?(meth)
413
- raise ScriptError, "'#{meth}' is a Thor reserved word and cannot be defined as task"
414
- end
415
-
456
+ is_thor_reserved_word?(meth, :task)
416
457
  Thor::Base.register_klass_file(self)
417
458
  create_task(meth)
418
459
  end
@@ -445,192 +486,18 @@ class Thor
445
486
  # class.
446
487
  def initialize_added #:nodoc:
447
488
  end
448
- end
449
-
450
- module SingletonMethods
451
- attr_accessor :options
452
-
453
- SHELL_DELEGATED_METHODS = [:ask, :yes?, :no?, :say, :say_status, :print_list, :print_table]
454
489
 
455
- # It receives arguments in an Array and two hashes, one for options and
456
- # other for configuration.
457
- #
458
- # Notice that it does not check arguments type neither if all required
459
- # arguments were supplied. It should be done by the parser.
460
- #
461
- # ==== Parameters
462
- # args<Array[Object]>:: An array of objects. The objects are applied to their
463
- # respective accessors declared with <tt>argument</tt>.
464
- #
465
- # options<Hash>:: An options hash that will be available as self.options.
466
- # The hash given is converted to a hash with indifferent
467
- # access, magic predicates (options.skip?) and then frozen.
468
- #
469
- # config<Hash>:: Configuration for this Thor class.
470
- #
471
- # ==== Configuration
472
- # shell<Object>:: An instance of the shell to be used.
473
- #
474
- # ==== Examples
475
- #
476
- # class MyScript < Thor
477
- # argument :first, :type => :numeric
478
- # end
479
- #
480
- # MyScript.new [1.0], { :foo => :bar }, :shell => Thor::Shell::Basic.new
481
- #
482
- def initialize(args=[], options={}, config={})
483
- self.class.arguments.zip(args).each do |argument, value|
484
- send("#{argument.human_name}=", value)
490
+ # SIGNATURE: Normalize arguments when invoked through start. Should
491
+ # return the name of the task to be invoked. Returning nil makes the
492
+ # start process to exist without error message.
493
+ def normalize_arguments(args, config) #:nodoc:
485
494
  end
486
495
 
487
- self.options = Thor::CoreExt::HashWithIndifferentAccess.new(options).freeze
488
-
489
- # Configure shell and set base if not already
490
- self.shell = config[:shell]
491
- self.shell.base ||= self if self.shell.respond_to?(:base)
492
- end
493
-
494
- # Holds the shell for the given Thor instance. If no shell is given,
495
- # it gets a default shell from Thor::Base.shell.
496
- #
497
- def shell
498
- @shell ||= Thor::Base.shell.new
499
- end
500
-
501
- # Sets the shell for this thor class.
502
- #
503
- def shell=(shell)
504
- @shell = shell
505
- end
506
-
507
- # Common methods that are delegated to the shell.
508
- #
509
- SHELL_DELEGATED_METHODS.each do |method|
510
- module_eval <<-METHOD, __FILE__, __LINE__
511
- def #{method}(*args)
512
- shell.#{method}(*args)
513
- end
514
- METHOD
515
- end
516
-
517
- # Receives a name and invokes it. The name can be either a namespaced name,
518
- # a current class task or even a class. Arguments are given in an array and
519
- # options given are merged with the invoker options.
520
- #
521
- # ==== Examples
522
- #
523
- # class A < Thor
524
- # def foo
525
- # invoke :bar
526
- # invoke "b:lib", ["merb", "rails"]
527
- # end
528
- #
529
- # def bar
530
- # invoke "b:lib", ["merb", "rails"]
531
- # # magic
532
- # end
533
- # end
534
- #
535
- # class B < Thor
536
- # argument :preferred_framework, :type => :string
537
- #
538
- # def lib(second_framework)
539
- # # magic
540
- # end
541
- # end
542
- #
543
- # You can notice that the method "foo" above invokes two tasks: "bar",
544
- # which belongs to the same class and "lib" that belongs to the class B.
545
- #
546
- # By using an invocation system you ensure that a task is invoked only once.
547
- # In the example above, invoking foo will invoke "b:lib" just once, even if
548
- # it's invoked later by "bar" method.
549
- #
550
- # When invoking another class, there are a few things to keep in mind:
551
- #
552
- # 1) Class arguments are parsed first. In the example above, preferred
553
- # framework is going to consume "merb" and second framework is going
554
- # to be set to "rails".
555
- #
556
- # 2) All options and configurations are sent to the invoked class.
557
- # So the invoked class is going to use the same shell instance, will
558
- # have the same behavior (:invoke or :revoke) and so on.
559
- #
560
- # Invoking a Thor::Group happens in the same away as above:
561
- #
562
- # class C < Thor::Group
563
- # def one
564
- # end
565
- # end
566
- #
567
- # Is invoked as:
568
- #
569
- # invoke "c"
570
- #
571
- # Or even as:
572
- #
573
- # invoke C
574
- #
575
- def invoke(name, method_args=[], options={})
576
- @_invocations ||= Hash.new { |h,k| h[k] = [] }
577
- instance, task = _setup_for_invoke(name, method_args, options)
578
-
579
- current = @_invocations[instance.class]
580
- return if current.include?("all")
581
-
582
- if task
583
- task = self.class.all_tasks[task.to_s] || Task.dynamic(task) unless task.is_a?(Thor::Task)
584
- return if current.include?(task.name)
585
-
586
- current << task.name
587
- task.run(instance, method_args)
588
- else
589
- current << "all"
590
- instance.class.all_tasks.collect { |_, task| task.run(instance) }
496
+ # SIGNATURE: Receives a task, arguments to be parsed and configuration
497
+ # values and initializes the current class. Trailing arguments are
498
+ # returned to be sent to the invoked task.
499
+ def prepare(task, args, config) #:nodoc:
591
500
  end
592
- end
593
-
594
- protected
595
-
596
- # This is the method responsable for retrieving and setting up an
597
- # instance to be used in invoke.
598
- #
599
- def _setup_for_invoke(name, method_args, options) #:nodoc:
600
- case name
601
- when NilClass, Thor::Task
602
- # Do nothing, we already have what we want
603
- when Class
604
- klass = name
605
- else
606
- name = name.to_s
607
- unless self.class.all_tasks[name]
608
- klass, task = Thor::Util.namespace_to_thor_class(name) rescue Thor::Error
609
- end
610
- end
611
-
612
- if klass.nil?
613
- return self, name
614
- elsif klass <= Thor::Base
615
- size = klass.arguments.size
616
- class_args = method_args.slice!(0, size)
617
- instance = klass.new(class_args, self.options.merge(options), _dump_config)
618
-
619
- task ||= klass.default_task if klass <= Thor
620
- instance.instance_variable_set("@_invocations", @_invocations)
621
-
622
- return instance, task
623
- else
624
- raise ScriptError, "Expected Thor class, got #{klass}"
625
- end
626
- end
627
-
628
- # Dump the configuration values for this current class.
629
- #
630
- def _dump_config #:nodoc:
631
- { :shell => self.shell }
632
- end
633
-
634
501
  end
635
502
  end
636
503
  end
data/lib/thor/group.rb CHANGED
@@ -3,8 +3,8 @@ class Thor::Group
3
3
  class << self
4
4
 
5
5
  # The descrition for this Thor::Group. If none is provided, but a source root
6
- # exists and we have an USAGE inside, this file is used as description. This
7
- # is good because we will load such files only when we need it.
6
+ # exists, tries to find the USAGE one folder above it, otherwise searches
7
+ # in the superclass.
8
8
  #
9
9
  # ==== Parameters
10
10
  # description<String>:: The description for this Thor::Group.
@@ -12,8 +12,9 @@ class Thor::Group
12
12
  def desc(description=nil)
13
13
  case description
14
14
  when nil
15
- @desc ||= if respond_to?(:source_root) && File.exist?(File.join(source_root, "USAGE"))
16
- File.read(File.join(source_root, "USAGE"))
15
+ usage = File.join(source_root, "..", "USAGE") if respond_to?(:source_root)
16
+ @desc ||= if usage && File.exist?(usage)
17
+ File.read(usage)
17
18
  else
18
19
  from_superclass(:desc, nil)
19
20
  end
@@ -22,22 +23,17 @@ class Thor::Group
22
23
  end
23
24
  end
24
25
 
25
- # Start in Thor::Group works differently. It invokes all tasks inside the
26
- # class and does not have to parse task options.
26
+ # Implements the prepare interface being used by start.
27
27
  #
28
- def start(args=ARGV, config={})
29
- config[:shell] ||= Thor::Base.shell.new
28
+ def prepare(task, args, config) #:nodoc:
29
+ opts = Thor::Options.new(class_options)
30
+ opts.parse(args)
30
31
 
31
- if Thor::HELP_MAPPINGS.include?(args.first)
32
- help(config[:shell])
33
- else
34
- opts = Thor::Options.new(class_options)
35
- opts.parse(args)
36
-
37
- new(opts.arguments, opts.options, config).invoke(:all)
32
+ instance = new(opts.arguments, opts.options, config) do |klass, invoke|
33
+ klass.prepare(invoke, args, config)
38
34
  end
39
- rescue Thor::Error => e
40
- config[:shell].error e.message
35
+
36
+ return instance, nil
41
37
  end
42
38
 
43
39
  # Prints help information.
@@ -77,6 +73,15 @@ class Thor::Group
77
73
  def create_task(meth) #:nodoc:
78
74
  tasks[meth.to_s] = Thor::Task.new(meth, nil, nil, nil)
79
75
  end
76
+
77
+ def normalize_arguments(args, config) #:nodoc:
78
+ if Thor::HELP_MAPPINGS.include?(args.first)
79
+ help(config[:shell])
80
+ nil
81
+ else
82
+ :all
83
+ end
84
+ end
80
85
  end
81
86
 
82
87
  include Thor::Base
@@ -86,9 +91,8 @@ class Thor::Group
86
91
  # Overwrite _setup_for_invoke to force invocation of all tasks when :all is
87
92
  # supplied.
88
93
  #
89
- def _setup_for_invoke(name, method_args, options)
90
- name = nil if name.to_s == "all"
91
- super(name, method_args, options)
94
+ def _setup_for_invoke(object, task=nil)
95
+ super(object.to_s == "all" ? nil : object)
92
96
  end
93
97
 
94
98
  end
@@ -0,0 +1,148 @@
1
+ class Thor
2
+ module Invocation
3
+
4
+ # Make initializer aware of invocations and the initializer proc.
5
+ #
6
+ def initialize(args=[], options={}, config={}, &block) #:nodoc:
7
+ @_invocations = config[:invocations] || Hash.new { |h,k| h[k] = [] }
8
+ @_initializer = block || lambda do |klass, invoke, overrides|
9
+ klass.new(args, options, config.merge(overrides))
10
+ end
11
+ super
12
+ end
13
+
14
+ # Receives a name and invokes it. The name can be a string (either "task" or
15
+ # "namespace:task"), a Thor::Task, a Class or a Thor instance. If the task
16
+ # cannot be guessed name, it can also be supplied as second argument. The
17
+ # arguments used to invoke the task are always supplied as the last argument.
18
+ #
19
+ # ==== Examples
20
+ #
21
+ # class A < Thor
22
+ # def foo
23
+ # invoke :bar
24
+ # invoke "b:hello", ["José"]
25
+ # end
26
+ #
27
+ # def bar
28
+ # invoke "b:hello", ["José"]
29
+ # end
30
+ # end
31
+ #
32
+ # class B < Thor
33
+ # def hello(name)
34
+ # puts "hello #{name}"
35
+ # end
36
+ # end
37
+ #
38
+ # You can notice that the method "foo" above invokes two tasks: "bar",
39
+ # which belongs to the same class and "hello" which belongs to the class B.
40
+ #
41
+ # By using an invocation system you ensure that a task is invoked only once.
42
+ # In the example above, invoking "foo" will invoke "b:hello" just once, even
43
+ # if it's invoked later by "bar" method.
44
+ #
45
+ # When class A invokes class B, all arguments used on A initialization are
46
+ # supplied to B. This allows lazy parse of options. Let's suppose you have
47
+ # some rspec tasks:
48
+ #
49
+ # class Rspec < Thor::Group
50
+ # class_option :mock_framework, :type => :string, :default => :rr
51
+ #
52
+ # def invoke_mock_framework
53
+ # invoke "rspec:#{options[:mock_framework]}"
54
+ # end
55
+ # end
56
+ #
57
+ # As you notice, it invokes the given mock framework, which might have its
58
+ # own options:
59
+ #
60
+ # class Rspec::RR < Thor::Group
61
+ # class_option :style, :type => :string, :default => :mock
62
+ # end
63
+ #
64
+ # Since it's not rspec concern to parse mock framework options, when RR
65
+ # is invoked all options are parsed again, so RR can extract only the options
66
+ # that it's going to use.
67
+ #
68
+ # If you want Rspec::RR to be initialized with its own set of options, you
69
+ # have to do that explicitely:
70
+ #
71
+ # invoke Rspec::RR.new([], :style => :foo)
72
+ #
73
+ # Besides giving an instance, you can also give a class to invoke:
74
+ #
75
+ # invoke Rspec::RR
76
+ #
77
+ # Or even a class, the task to invoke from it and its arguments:
78
+ #
79
+ # invoke Rspec::RR, :foo, [ args ]
80
+ #
81
+ def invoke(name, task=nil, method_args=nil)
82
+ task, method_args = nil, task if task.is_a?(Array)
83
+
84
+ object, task = _setup_for_invoke(name, task)
85
+ klass = object.is_a?(Class) ? object : object.class
86
+
87
+ current = @_invocations[klass]
88
+ return if current.include?("all")
89
+
90
+ if object.is_a?(Class)
91
+ instance, trailing = @_initializer.call(klass, task, _overrides_config)
92
+ method_args ||= trailing
93
+ else
94
+ instance = object
95
+ end
96
+
97
+ method_args ||= []
98
+
99
+ if task
100
+ return if current.include?(task.name)
101
+ current << task.name
102
+ task.run(instance, method_args)
103
+ else
104
+ current << "all"
105
+ klass.all_tasks.collect { |_, task| task.run(instance) }
106
+ end
107
+ end
108
+
109
+ protected
110
+
111
+ # Values that are sent to overwrite defined configuration values.
112
+ #
113
+ def _overrides_config #:nodoc:
114
+ { :invocations => @_invocations }
115
+ end
116
+
117
+ # This is the method responsable for retrieving and setting up an
118
+ # instance to be used in invoke.
119
+ #
120
+ def _setup_for_invoke(name, sent_task=nil) #:nodoc:
121
+ case name
122
+ when Thor::Task
123
+ task = name
124
+ when Symbol, String
125
+ name = name.to_s
126
+
127
+ begin
128
+ task = self.class.all_tasks[name]
129
+ object, task = Thor::Util.namespace_to_thor_class(name) unless task
130
+ task = task || sent_task
131
+ rescue Thor::Error
132
+ task = name
133
+ end
134
+ else
135
+ object, task = name, sent_task
136
+ end
137
+
138
+ object ||= self
139
+ klass = object.is_a?(Class) ? object : object.class
140
+ raise "Expected Thor class, got #{klass}" unless klass <= Thor::Base
141
+
142
+ task ||= klass.default_task if klass <= Thor
143
+ task = klass.all_tasks[task.to_s] || Task.dynamic(task) if task && !task.is_a?(Thor::Task)
144
+ return object, task
145
+ end
146
+
147
+ end
148
+ end
data/lib/thor/options.rb CHANGED
@@ -77,7 +77,7 @@ class Thor
77
77
  end
78
78
 
79
79
  def parse(args)
80
- @pile, @trailing = args, []
80
+ @pile, @trailing = args.dup, []
81
81
 
82
82
  while peek
83
83
  if current_is_switch?
@@ -125,30 +125,28 @@ class Thor
125
125
  #
126
126
  def file_collision(destination)
127
127
  return true if @always_force
128
-
129
128
  options = block_given? ? "[Ynaqdh]" : "[Ynaqh]"
130
- answer = ask %[Overwrite #{destination}? (enter "h" for help) #{options}]
131
-
132
- case answer
133
- when is?(:yes), is?(:force)
134
- true
135
- when is?(:no), is?(:skip)
136
- false
137
- when is?(:always)
138
- @always_force = true
139
- when is?(:quit)
140
- say 'Aborting...'
141
- raise SystemExit
142
- when is?(:diff)
143
- show_diff(destination, yield) if block_given?
144
- say 'Retrying...'
145
- raise ScriptError
146
- else
147
- say file_collision_help
148
- raise ScriptError
129
+
130
+ while true
131
+ answer = ask %[Overwrite #{destination}? (enter "h" for help) #{options}]
132
+
133
+ case answer
134
+ when is?(:yes), is?(:force)
135
+ return true
136
+ when is?(:no), is?(:skip)
137
+ return false
138
+ when is?(:always)
139
+ return @always_force = true
140
+ when is?(:quit)
141
+ say 'Aborting...'
142
+ raise SystemExit
143
+ when is?(:diff)
144
+ show_diff(destination, yield) if block_given?
145
+ say 'Retrying...'
146
+ else
147
+ say file_collision_help
148
+ end
149
149
  end
150
- rescue ScriptError
151
- retry
152
150
  end
153
151
 
154
152
  # Called if something goes wrong during the execution. This is used by Thor
data/lib/thor/shell.rb ADDED
@@ -0,0 +1,71 @@
1
+ require 'thor/shell/basic'
2
+
3
+ class Thor
4
+ module Base
5
+ # Returns the shell used in all Thor classes.
6
+ #
7
+ def self.shell
8
+ @shell || Thor::Shell::Basic
9
+ end
10
+
11
+ # Sets the shell used in all Thor classes.
12
+ #
13
+ def self.shell=(klass)
14
+ @shell = klass
15
+ end
16
+ end
17
+
18
+ module Shell
19
+ SHELL_DELEGATED_METHODS = [:ask, :yes?, :no?, :say, :say_status, :print_list, :print_table]
20
+
21
+ # Add shell to initialize config values.
22
+ #
23
+ # ==== Configuration
24
+ # shell<Object>:: An instance of the shell to be used.
25
+ #
26
+ # ==== Examples
27
+ #
28
+ # class MyScript < Thor
29
+ # argument :first, :type => :numeric
30
+ # end
31
+ #
32
+ # MyScript.new [1.0], { :foo => :bar }, :shell => Thor::Shell::Basic.new
33
+ #
34
+ def initialize(args=[], options={}, config={})
35
+ super
36
+ self.shell = config[:shell]
37
+ self.shell.base ||= self if self.shell.respond_to?(:base)
38
+ end
39
+
40
+ # Holds the shell for the given Thor instance. If no shell is given,
41
+ # it gets a default shell from Thor::Base.shell.
42
+ #
43
+ def shell
44
+ @shell ||= Thor::Base.shell.new
45
+ end
46
+
47
+ # Sets the shell for this thor class.
48
+ #
49
+ def shell=(shell)
50
+ @shell = shell
51
+ end
52
+
53
+ # Common methods that are delegated to the shell.
54
+ #
55
+ SHELL_DELEGATED_METHODS.each do |method|
56
+ module_eval <<-METHOD, __FILE__, __LINE__
57
+ def #{method}(*args)
58
+ shell.#{method}(*args)
59
+ end
60
+ METHOD
61
+ end
62
+
63
+ protected
64
+
65
+ # Send the current shell to be used by the invoked class.
66
+ #
67
+ def _overrides_config #:nodoc:
68
+ super.merge!(:shell => self.shell)
69
+ end
70
+ end
71
+ end
data/lib/thor.rb CHANGED
@@ -115,28 +115,18 @@ class Thor
115
115
  build_option(name, options, scope)
116
116
  end
117
117
 
118
- # Parses the task and options from the given args, instantiate the class
119
- # and invoke the task. This method is used when the arguments must be parsed
120
- # from an array. If you are inside Ruby and want to use a Thor class, you
121
- # can simply initialize it:
118
+ # Implements the prepare interface being used by start.
122
119
  #
123
- # script = MyScript.new(args, options, config)
124
- # script.invoke(:task, first_arg, second_arg, third_arg)
125
- #
126
- def start(args=ARGV, config={})
127
- config[:shell] ||= Thor::Base.shell.new
128
-
129
- meth = normalize_task_name(args.shift)
130
- task = all_tasks[meth] || Task.dynamic(meth)
131
-
120
+ def prepare(task, args, config) #:nodoc:
132
121
  options = class_options.merge(task.options)
133
122
  opts = Thor::Options.new(options)
134
123
  opts.parse(args)
135
124
 
136
- instance = new(opts.arguments, opts.options, config)
137
- instance.invoke(task, opts.trailing)
138
- rescue Thor::Error => e
139
- config[:shell].error e.message
125
+ instance = new(opts.arguments, opts.options, config) do |klass, invoke, overrides|
126
+ klass.prepare(invoke, args, config.merge(overrides))
127
+ end
128
+
129
+ return instance, opts.trailing
140
130
  end
141
131
 
142
132
  # Prints help information. If a task name is given, it shows information
@@ -207,6 +197,11 @@ class Thor
207
197
  @method_options = nil
208
198
  end
209
199
 
200
+ def normalize_arguments(args, config) #:nodoc:
201
+ meth = normalize_task_name(args.shift)
202
+ all_tasks[meth] || Task.dynamic(meth)
203
+ end
204
+
210
205
  # Receives a task name (can be nil), and try to get a map from it.
211
206
  # If a map can't be found use the sent name or the default task.
212
207
  #
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: josevalim-thor
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.10.10
4
+ version: 0.10.11
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: 2009-06-20 00:00:00 -07:00
12
+ date: 2009-06-23 00:00:00 -07:00
13
13
  default_executable:
14
14
  dependencies: []
15
15
 
@@ -52,6 +52,7 @@ files:
52
52
  - lib/thor/options.rb
53
53
  - lib/thor/shell
54
54
  - lib/thor/shell/basic.rb
55
+ - lib/thor/invocation.rb
55
56
  - lib/thor/tasks.rb
56
57
  - lib/thor/core_ext
57
58
  - lib/thor/core_ext/hash_with_indifferent_access.rb
@@ -60,6 +61,7 @@ files:
60
61
  - lib/thor/tasks/install.rb
61
62
  - lib/thor/tasks/spec.rb
62
63
  - lib/thor/tasks/package.rb
64
+ - lib/thor/shell.rb
63
65
  - lib/thor/task.rb
64
66
  has_rdoc: true
65
67
  homepage: http://yehudakatz.com