josevalim-thor 0.10.10 → 0.10.11

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/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