thor 0.20.3 → 1.3.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.
Files changed (43) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +3 -9
  3. data/lib/thor/actions/create_file.rb +4 -3
  4. data/lib/thor/actions/create_link.rb +3 -2
  5. data/lib/thor/actions/directory.rb +8 -18
  6. data/lib/thor/actions/empty_directory.rb +1 -1
  7. data/lib/thor/actions/file_manipulation.rb +22 -24
  8. data/lib/thor/actions/inject_into_file.rb +34 -13
  9. data/lib/thor/actions.rb +39 -30
  10. data/lib/thor/base.rb +196 -49
  11. data/lib/thor/command.rb +34 -18
  12. data/lib/thor/core_ext/hash_with_indifferent_access.rb +10 -0
  13. data/lib/thor/error.rb +14 -22
  14. data/lib/thor/group.rb +13 -2
  15. data/lib/thor/invocation.rb +2 -1
  16. data/lib/thor/line_editor/basic.rb +1 -1
  17. data/lib/thor/line_editor/readline.rb +6 -6
  18. data/lib/thor/line_editor.rb +2 -2
  19. data/lib/thor/nested_context.rb +29 -0
  20. data/lib/thor/parser/argument.rb +17 -1
  21. data/lib/thor/parser/arguments.rb +35 -15
  22. data/lib/thor/parser/option.rb +45 -13
  23. data/lib/thor/parser/options.rb +79 -11
  24. data/lib/thor/parser.rb +4 -4
  25. data/lib/thor/rake_compat.rb +3 -2
  26. data/lib/thor/runner.rb +43 -32
  27. data/lib/thor/shell/basic.rb +68 -162
  28. data/lib/thor/shell/color.rb +9 -43
  29. data/lib/thor/shell/column_printer.rb +29 -0
  30. data/lib/thor/shell/html.rb +7 -49
  31. data/lib/thor/shell/lcs_diff.rb +49 -0
  32. data/lib/thor/shell/table_printer.rb +118 -0
  33. data/lib/thor/shell/terminal.rb +42 -0
  34. data/lib/thor/shell/wrapped_printer.rb +38 -0
  35. data/lib/thor/shell.rb +5 -5
  36. data/lib/thor/util.rb +25 -8
  37. data/lib/thor/version.rb +1 -1
  38. data/lib/thor.rb +182 -17
  39. data/thor.gemspec +22 -10
  40. metadata +25 -11
  41. data/CHANGELOG.md +0 -204
  42. data/lib/thor/core_ext/io_binary_read.rb +0 -12
  43. data/lib/thor/core_ext/ordered_hash.rb +0 -129
@@ -0,0 +1,29 @@
1
+ class Thor
2
+ class NestedContext
3
+ def initialize
4
+ @depth = 0
5
+ end
6
+
7
+ def enter
8
+ push
9
+
10
+ yield
11
+ ensure
12
+ pop
13
+ end
14
+
15
+ def entered?
16
+ @depth.positive?
17
+ end
18
+
19
+ private
20
+
21
+ def push
22
+ @depth += 1
23
+ end
24
+
25
+ def pop
26
+ @depth -= 1
27
+ end
28
+ end
29
+ end
@@ -24,6 +24,14 @@ class Thor
24
24
  validate! # Trigger specific validations
25
25
  end
26
26
 
27
+ def print_default
28
+ if @type == :array and @default.is_a?(Array)
29
+ @default.map(&:dump).join(" ")
30
+ else
31
+ @default
32
+ end
33
+ end
34
+
27
35
  def usage
28
36
  required? ? banner : "[#{banner}]"
29
37
  end
@@ -41,11 +49,19 @@ class Thor
41
49
  end
42
50
  end
43
51
 
52
+ def enum_to_s
53
+ if enum.respond_to? :join
54
+ enum.join(", ")
55
+ else
56
+ "#{enum.first}..#{enum.last}"
57
+ end
58
+ end
59
+
44
60
  protected
45
61
 
46
62
  def validate!
47
63
  raise ArgumentError, "An argument cannot be required and have default value." if required? && !default.nil?
48
- raise ArgumentError, "An argument cannot have an enum other than an array." if @enum && !@enum.is_a?(Array)
64
+ raise ArgumentError, "An argument cannot have an enum other than an enumerable." if @enum && !@enum.is_a?(Enumerable)
49
65
  end
50
66
 
51
67
  def valid_type?(type)
@@ -1,5 +1,5 @@
1
1
  class Thor
2
- class Arguments #:nodoc: # rubocop:disable ClassLength
2
+ class Arguments #:nodoc:
3
3
  NUMERIC = /[-+]?(\d*\.\d+|\d+)/
4
4
 
5
5
  # Receives an array of args and returns two arrays, one with arguments
@@ -9,7 +9,7 @@ class Thor
9
9
  arguments = []
10
10
 
11
11
  args.each do |item|
12
- break if item =~ /^-/
12
+ break if item.is_a?(String) && item =~ /^-/
13
13
  arguments << item
14
14
  end
15
15
 
@@ -30,7 +30,7 @@ class Thor
30
30
 
31
31
  arguments.each do |argument|
32
32
  if !argument.default.nil?
33
- @assigns[argument.human_name] = argument.default
33
+ @assigns[argument.human_name] = argument.default.dup
34
34
  elsif argument.required?
35
35
  @non_assigned_required << argument
36
36
  end
@@ -82,7 +82,7 @@ class Thor
82
82
  end
83
83
 
84
84
  def current_is_value?
85
- peek && peek.to_s !~ /^-/
85
+ peek && peek.to_s !~ /^-{1,2}\S+/
86
86
  end
87
87
 
88
88
  # Runs through the argument array getting strings that contains ":" and
@@ -117,8 +117,18 @@ class Thor
117
117
  #
118
118
  def parse_array(name)
119
119
  return shift if peek.is_a?(Array)
120
+
120
121
  array = []
121
- array << shift while current_is_value?
122
+
123
+ while current_is_value?
124
+ value = shift
125
+
126
+ if !value.empty?
127
+ validate_enum_value!(name, value, "Expected all values of '%s' to be one of %s; got %s")
128
+ end
129
+
130
+ array << value
131
+ end
122
132
  array
123
133
  end
124
134
 
@@ -134,11 +144,9 @@ class Thor
134
144
  end
135
145
 
136
146
  value = $&.index(".") ? shift.to_f : shift.to_i
137
- if @switches.is_a?(Hash) && switch = @switches[name]
138
- if switch.enum && !switch.enum.include?(value)
139
- raise MalformattedArgumentError, "Expected '#{name}' to be one of #{switch.enum.join(', ')}; got #{value}"
140
- end
141
- end
147
+
148
+ validate_enum_value!(name, value, "Expected '%s' to be one of %s; got %s")
149
+
142
150
  value
143
151
  end
144
152
 
@@ -152,15 +160,27 @@ class Thor
152
160
  nil
153
161
  else
154
162
  value = shift
155
- if @switches.is_a?(Hash) && switch = @switches[name]
156
- if switch.enum && !switch.enum.include?(value)
157
- raise MalformattedArgumentError, "Expected '#{name}' to be one of #{switch.enum.join(', ')}; got #{value}"
158
- end
159
- end
163
+
164
+ validate_enum_value!(name, value, "Expected '%s' to be one of %s; got %s")
165
+
160
166
  value
161
167
  end
162
168
  end
163
169
 
170
+ # Raises an error if the switch is an enum and the values aren't included on it.
171
+ #
172
+ def validate_enum_value!(name, value, message)
173
+ return unless @switches.is_a?(Hash)
174
+
175
+ switch = @switches[name]
176
+
177
+ return unless switch
178
+
179
+ if switch.enum && !switch.enum.include?(value)
180
+ raise MalformattedArgumentError, message % [name, switch.enum_to_s, value]
181
+ end
182
+ end
183
+
164
184
  # Raises an error if @non_assigned_required array is not empty.
165
185
  #
166
186
  def check_requirement!
@@ -1,17 +1,18 @@
1
1
  class Thor
2
2
  class Option < Argument #:nodoc:
3
- attr_reader :aliases, :group, :lazy_default, :hide
3
+ attr_reader :aliases, :group, :lazy_default, :hide, :repeatable
4
4
 
5
5
  VALID_TYPES = [:boolean, :numeric, :hash, :array, :string]
6
6
 
7
7
  def initialize(name, options = {})
8
8
  @check_default_type = options[:check_default_type]
9
9
  options[:required] = false unless options.key?(:required)
10
+ @repeatable = options.fetch(:repeatable, false)
10
11
  super
11
- @lazy_default = options[:lazy_default]
12
- @group = options[:group].to_s.capitalize if options[:group]
13
- @aliases = Array(options[:aliases])
14
- @hide = options[:hide]
12
+ @lazy_default = options[:lazy_default]
13
+ @group = options[:group].to_s.capitalize if options[:group]
14
+ @aliases = normalize_aliases(options[:aliases])
15
+ @hide = options[:hide]
15
16
  end
16
17
 
17
18
  # This parse quick options given as method_options. It makes several
@@ -57,7 +58,7 @@ class Thor
57
58
  default = nil
58
59
  if VALID_TYPES.include?(value)
59
60
  value
60
- elsif required = (value == :required) # rubocop:disable AssignmentInCondition
61
+ elsif required = (value == :required) # rubocop:disable Lint/AssignmentInCondition
61
62
  :string
62
63
  end
63
64
  when TrueClass, FalseClass
@@ -68,7 +69,7 @@ class Thor
68
69
  value.class.name.downcase.to_sym
69
70
  end
70
71
 
71
- new(name.to_s, :required => required, :type => type, :default => default, :aliases => aliases)
72
+ new(name.to_s, required: required, type: type, default: default, aliases: aliases)
72
73
  end
73
74
 
74
75
  def switch_name
@@ -88,14 +89,27 @@ class Thor
88
89
 
89
90
  sample = "[#{sample}]".dup unless required?
90
91
 
91
- if boolean?
92
- sample << ", [#{dasherize('no-' + human_name)}]" unless (name == "force") || name.start_with?("no-")
92
+ if boolean? && name != "force" && !name.match(/\A(no|skip)[\-_]/)
93
+ sample << ", [#{dasherize('no-' + human_name)}], [#{dasherize('skip-' + human_name)}]"
93
94
  end
94
95
 
96
+ aliases_for_usage.ljust(padding) + sample
97
+ end
98
+
99
+ def aliases_for_usage
95
100
  if aliases.empty?
96
- (" " * padding) << sample
101
+ ""
102
+ else
103
+ "#{aliases.join(', ')}, "
104
+ end
105
+ end
106
+
107
+ def show_default?
108
+ case default
109
+ when TrueClass, FalseClass
110
+ true
97
111
  else
98
- "#{aliases.join(', ')}, #{sample}"
112
+ super
99
113
  end
100
114
  end
101
115
 
@@ -111,7 +125,7 @@ class Thor
111
125
 
112
126
  def validate!
113
127
  raise ArgumentError, "An option cannot be boolean and required." if boolean? && required?
114
- validate_default_type! if @check_default_type
128
+ validate_default_type!
115
129
  end
116
130
 
117
131
  def validate_default_type!
@@ -128,7 +142,19 @@ class Thor
128
142
  @default.class.name.downcase.to_sym
129
143
  end
130
144
 
131
- raise ArgumentError, "Expected #{@type} default value for '#{switch_name}'; got #{@default.inspect} (#{default_type})" unless default_type == @type
145
+ expected_type = (@repeatable && @type != :hash) ? :array : @type
146
+
147
+ if default_type != expected_type
148
+ err = "Expected #{expected_type} default value for '#{switch_name}'; got #{@default.inspect} (#{default_type})"
149
+
150
+ if @check_default_type
151
+ raise ArgumentError, err
152
+ elsif @check_default_type == nil
153
+ Thor.deprecation_warning "#{err}.\n" +
154
+ "This will be rejected in the future unless you explicitly pass the options `check_default_type: false`" +
155
+ " or call `allow_incompatible_default_type!` in your code"
156
+ end
157
+ end
132
158
  end
133
159
 
134
160
  def dasherized?
@@ -142,5 +168,11 @@ class Thor
142
168
  def dasherize(str)
143
169
  (str.length > 1 ? "--" : "-") + str.tr("_", "-")
144
170
  end
171
+
172
+ private
173
+
174
+ def normalize_aliases(aliases)
175
+ Array(aliases).map { |short| short.to_s.sub(/^(?!\-)/, "-") }
176
+ end
145
177
  end
146
178
  end
@@ -1,5 +1,5 @@
1
1
  class Thor
2
- class Options < Arguments #:nodoc: # rubocop:disable ClassLength
2
+ class Options < Arguments #:nodoc:
3
3
  LONG_RE = /^(--\w+(?:-\w+)*)$/
4
4
  SHORT_RE = /^(-[a-z])$/i
5
5
  EQ_RE = /^(--\w+(?:-\w+)*|-[a-z])=(.*)$/i
@@ -29,8 +29,10 @@ class Thor
29
29
  #
30
30
  # If +stop_on_unknown+ is true, #parse will stop as soon as it encounters
31
31
  # an unknown option or a regular argument.
32
- def initialize(hash_options = {}, defaults = {}, stop_on_unknown = false, disable_required_check = false)
32
+ def initialize(hash_options = {}, defaults = {}, stop_on_unknown = false, disable_required_check = false, relations = {})
33
33
  @stop_on_unknown = stop_on_unknown
34
+ @exclusives = (relations[:exclusive_option_names] || []).select{|array| !array.empty?}
35
+ @at_least_ones = (relations[:at_least_one_option_names] || []).select{|array| !array.empty?}
34
36
  @disable_required_check = disable_required_check
35
37
  options = hash_options.values
36
38
  super(options)
@@ -45,12 +47,12 @@ class Thor
45
47
  @switches = {}
46
48
  @extra = []
47
49
  @stopped_parsing_after_extra_index = nil
50
+ @is_treated_as_value = false
48
51
 
49
52
  options.each do |option|
50
53
  @switches[option.switch_name] = option
51
54
 
52
- option.aliases.each do |short|
53
- name = short.to_s.sub(/^(?!\-)/, "-")
55
+ option.aliases.each do |name|
54
56
  @shorts[name] ||= option.switch_name
55
57
  end
56
58
  end
@@ -74,8 +76,19 @@ class Thor
74
76
  end
75
77
  end
76
78
 
77
- def parse(args) # rubocop:disable MethodLength
79
+ def shift
80
+ @is_treated_as_value = false
81
+ super
82
+ end
83
+
84
+ def unshift(arg, is_value: false)
85
+ @is_treated_as_value = is_value
86
+ super(arg)
87
+ end
88
+
89
+ def parse(args) # rubocop:disable Metrics/MethodLength
78
90
  @pile = args.dup
91
+ @is_treated_as_value = false
79
92
  @parsing_options = true
80
93
 
81
94
  while peek
@@ -88,7 +101,10 @@ class Thor
88
101
  when SHORT_SQ_RE
89
102
  unshift($1.split("").map { |f| "-#{f}" })
90
103
  next
91
- when EQ_RE, SHORT_NUM
104
+ when EQ_RE
105
+ unshift($2, is_value: true)
106
+ switch = $1
107
+ when SHORT_NUM
92
108
  unshift($2)
93
109
  switch = $1
94
110
  when LONG_RE, SHORT_RE
@@ -97,7 +113,8 @@ class Thor
97
113
 
98
114
  switch = normalize_switch(switch)
99
115
  option = switch_option(switch)
100
- @assigns[option.human_name] = parse_peek(switch, option)
116
+ result = parse_peek(switch, option)
117
+ assign_result!(option, result)
101
118
  elsif @stop_on_unknown
102
119
  @parsing_options = false
103
120
  @extra << shifted
@@ -116,12 +133,38 @@ class Thor
116
133
  end
117
134
 
118
135
  check_requirement! unless @disable_required_check
136
+ check_exclusive!
137
+ check_at_least_one!
119
138
 
120
139
  assigns = Thor::CoreExt::HashWithIndifferentAccess.new(@assigns)
121
140
  assigns.freeze
122
141
  assigns
123
142
  end
124
143
 
144
+ def check_exclusive!
145
+ opts = @assigns.keys
146
+ # When option A and B are exclusive, if A and B are given at the same time,
147
+ # the diffrence of argument array size will decrease.
148
+ found = @exclusives.find{ |ex| (ex - opts).size < ex.size - 1 }
149
+ if found
150
+ names = names_to_switch_names(found & opts).map{|n| "'#{n}'"}
151
+ class_name = self.class.name.split("::").last.downcase
152
+ fail ExclusiveArgumentError, "Found exclusive #{class_name} #{names.join(", ")}"
153
+ end
154
+ end
155
+
156
+ def check_at_least_one!
157
+ opts = @assigns.keys
158
+ # When at least one is required of the options A and B,
159
+ # if the both options were not given, none? would be true.
160
+ found = @at_least_ones.find{ |one_reqs| one_reqs.none?{ |o| opts.include? o} }
161
+ if found
162
+ names = names_to_switch_names(found).map{|n| "'#{n}'"}
163
+ class_name = self.class.name.split("::").last.downcase
164
+ fail AtLeastOneRequiredArgumentError, "Not found at least one of required #{class_name} #{names.join(", ")}"
165
+ end
166
+ end
167
+
125
168
  def check_unknown!
126
169
  to_check = @stopped_parsing_after_extra_index ? @extra[0...@stopped_parsing_after_extra_index] : @extra
127
170
 
@@ -132,11 +175,33 @@ class Thor
132
175
 
133
176
  protected
134
177
 
178
+ # Option names changes to swith name or human name
179
+ def names_to_switch_names(names = [])
180
+ @switches.map do |_, o|
181
+ if names.include? o.name
182
+ o.respond_to?(:switch_name) ? o.switch_name : o.human_name
183
+ else
184
+ nil
185
+ end
186
+ end.compact
187
+ end
188
+
189
+ def assign_result!(option, result)
190
+ if option.repeatable && option.type == :hash
191
+ (@assigns[option.human_name] ||= {}).merge!(result)
192
+ elsif option.repeatable
193
+ (@assigns[option.human_name] ||= []) << result
194
+ else
195
+ @assigns[option.human_name] = result
196
+ end
197
+ end
198
+
135
199
  # Check if the current value in peek is a registered switch.
136
200
  #
137
201
  # Two booleans are returned. The first is true if the current value
138
202
  # starts with a hyphen; the second is true if it is a registered switch.
139
203
  def current_is_switch?
204
+ return [false, false] if @is_treated_as_value
140
205
  case peek
141
206
  when LONG_RE, SHORT_RE, EQ_RE, SHORT_NUM
142
207
  [true, switch?($1)]
@@ -148,6 +213,7 @@ class Thor
148
213
  end
149
214
 
150
215
  def current_is_switch_formatted?
216
+ return false if @is_treated_as_value
151
217
  case peek
152
218
  when LONG_RE, SHORT_RE, EQ_RE, SHORT_NUM, SHORT_SQ_RE
153
219
  true
@@ -157,15 +223,16 @@ class Thor
157
223
  end
158
224
 
159
225
  def current_is_value?
226
+ return true if @is_treated_as_value
160
227
  peek && (!parsing_options? || super)
161
228
  end
162
229
 
163
230
  def switch?(arg)
164
- switch_option(normalize_switch(arg))
231
+ !switch_option(normalize_switch(arg)).nil?
165
232
  end
166
233
 
167
234
  def switch_option(arg)
168
- if match = no_or_skip?(arg) # rubocop:disable AssignmentInCondition
235
+ if match = no_or_skip?(arg) # rubocop:disable Lint/AssignmentInCondition
169
236
  @switches[arg] || @switches["--#{match}"]
170
237
  else
171
238
  @switches[arg]
@@ -183,7 +250,8 @@ class Thor
183
250
  @parsing_options
184
251
  end
185
252
 
186
- # Parse boolean values which can be given as --foo=true, --foo or --no-foo.
253
+ # Parse boolean values which can be given as --foo=true or --foo for true values, or
254
+ # --foo=false, --no-foo or --skip-foo for false values.
187
255
  #
188
256
  def parse_boolean(switch)
189
257
  if current_is_value?
@@ -194,7 +262,7 @@ class Thor
194
262
  shift
195
263
  false
196
264
  else
197
- !no_or_skip?(switch)
265
+ @switches.key?(switch) || !no_or_skip?(switch)
198
266
  end
199
267
  else
200
268
  @switches.key?(switch) || !no_or_skip?(switch)
data/lib/thor/parser.rb CHANGED
@@ -1,4 +1,4 @@
1
- require "thor/parser/argument"
2
- require "thor/parser/arguments"
3
- require "thor/parser/option"
4
- require "thor/parser/options"
1
+ require_relative "parser/argument"
2
+ require_relative "parser/arguments"
3
+ require_relative "parser/option"
4
+ require_relative "parser/options"
@@ -25,6 +25,7 @@ class Thor
25
25
  end
26
26
 
27
27
  def self.included(base)
28
+ super(base)
28
29
  # Hack. Make rakefile point to invoker, so rdoc task is generated properly.
29
30
  rakefile = File.basename(caller[0].match(/(.*):\d+/)[1])
30
31
  Rake.application.instance_variable_set(:@rakefile, rakefile)
@@ -40,7 +41,7 @@ instance_eval do
40
41
  def task(*)
41
42
  task = super
42
43
 
43
- if klass = Thor::RakeCompat.rake_classes.last # rubocop:disable AssignmentInCondition
44
+ if klass = Thor::RakeCompat.rake_classes.last # rubocop:disable Lint/AssignmentInCondition
44
45
  non_namespaced_name = task.name.split(":").last
45
46
 
46
47
  description = non_namespaced_name
@@ -58,7 +59,7 @@ instance_eval do
58
59
  end
59
60
 
60
61
  def namespace(name)
61
- if klass = Thor::RakeCompat.rake_classes.last # rubocop:disable AssignmentInCondition
62
+ if klass = Thor::RakeCompat.rake_classes.last # rubocop:disable Lint/AssignmentInCondition
62
63
  const_name = Thor::Util.camel_case(name.to_s).to_sym
63
64
  klass.const_set(const_name, Class.new(Thor))
64
65
  new_klass = klass.const_get(const_name)
data/lib/thor/runner.rb CHANGED
@@ -1,12 +1,11 @@
1
- require "thor"
2
- require "thor/group"
3
- require "thor/core_ext/io_binary_read"
1
+ require_relative "../thor"
2
+ require_relative "group"
4
3
 
5
4
  require "yaml"
6
- require "digest/md5"
5
+ require "digest/sha2"
7
6
  require "pathname"
8
7
 
9
- class Thor::Runner < Thor #:nodoc: # rubocop:disable ClassLength
8
+ class Thor::Runner < Thor #:nodoc:
10
9
  map "-T" => :list, "-i" => :install, "-u" => :update, "-v" => :version
11
10
 
12
11
  def self.banner(command, all = false, subcommand = false)
@@ -24,7 +23,7 @@ class Thor::Runner < Thor #:nodoc: # rubocop:disable ClassLength
24
23
  initialize_thorfiles(meth)
25
24
  klass, command = Thor::Util.find_class_and_command_by_namespace(meth)
26
25
  self.class.handle_no_command_error(command, false) if klass.nil?
27
- klass.start(["-h", command].compact, :shell => shell)
26
+ klass.start(["-h", command].compact, shell: shell)
28
27
  else
29
28
  super
30
29
  end
@@ -39,30 +38,42 @@ class Thor::Runner < Thor #:nodoc: # rubocop:disable ClassLength
39
38
  klass, command = Thor::Util.find_class_and_command_by_namespace(meth)
40
39
  self.class.handle_no_command_error(command, false) if klass.nil?
41
40
  args.unshift(command) if command
42
- klass.start(args, :shell => shell)
41
+ klass.start(args, shell: shell)
43
42
  end
44
43
 
45
44
  desc "install NAME", "Install an optionally named Thor file into your system commands"
46
- method_options :as => :string, :relative => :boolean, :force => :boolean
47
- def install(name) # rubocop:disable MethodLength
45
+ method_options as: :string, relative: :boolean, force: :boolean
46
+ def install(name) # rubocop:disable Metrics/MethodLength
48
47
  initialize_thorfiles
49
48
 
50
- # If a directory name is provided as the argument, look for a 'main.thor'
51
- # command in said directory.
52
- begin
53
- if File.directory?(File.expand_path(name))
54
- base = File.join(name, "main.thor")
55
- package = :directory
56
- contents = open(base, &:read)
57
- else
58
- base = name
59
- package = :file
60
- contents = open(name, &:read)
49
+ is_uri = name =~ %r{^https?\://}
50
+
51
+ if is_uri
52
+ base = name
53
+ package = :file
54
+ require "open-uri"
55
+ begin
56
+ contents = URI.open(name, &:read)
57
+ rescue OpenURI::HTTPError
58
+ raise Error, "Error opening URI '#{name}'"
59
+ end
60
+ else
61
+ # If a directory name is provided as the argument, look for a 'main.thor'
62
+ # command in said directory.
63
+ begin
64
+ if File.directory?(File.expand_path(name))
65
+ base = File.join(name, "main.thor")
66
+ package = :directory
67
+ contents = File.open(base, &:read)
68
+ else
69
+ base = name
70
+ package = :file
71
+ require "open-uri"
72
+ contents = URI.open(name, &:read)
73
+ end
74
+ rescue Errno::ENOENT
75
+ raise Error, "Error opening file '#{name}'"
61
76
  end
62
- rescue OpenURI::HTTPError
63
- raise Error, "Error opening URI '#{name}'"
64
- rescue Errno::ENOENT
65
- raise Error, "Error opening file '#{name}'"
66
77
  end
67
78
 
68
79
  say "Your Thorfile contains:"
@@ -83,16 +94,16 @@ class Thor::Runner < Thor #:nodoc: # rubocop:disable ClassLength
83
94
  as = basename if as.empty?
84
95
  end
85
96
 
86
- location = if options[:relative] || name =~ %r{^https?://}
97
+ location = if options[:relative] || is_uri
87
98
  name
88
99
  else
89
100
  File.expand_path(name)
90
101
  end
91
102
 
92
103
  thor_yaml[as] = {
93
- :filename => Digest::MD5.hexdigest(name + as),
94
- :location => location,
95
- :namespaces => Thor::Util.namespaces_in_content(contents, base)
104
+ filename: Digest::SHA256.hexdigest(name + as),
105
+ location: location,
106
+ namespaces: Thor::Util.namespaces_in_content(contents, base)
96
107
  }
97
108
 
98
109
  save_yaml(thor_yaml)
@@ -111,7 +122,7 @@ class Thor::Runner < Thor #:nodoc: # rubocop:disable ClassLength
111
122
 
112
123
  desc "version", "Show Thor version"
113
124
  def version
114
- require "thor/version"
125
+ require_relative "version"
115
126
  say "Thor #{Thor::VERSION}"
116
127
  end
117
128
 
@@ -153,14 +164,14 @@ class Thor::Runner < Thor #:nodoc: # rubocop:disable ClassLength
153
164
  end
154
165
 
155
166
  desc "installed", "List the installed Thor modules and commands"
156
- method_options :internal => :boolean
167
+ method_options internal: :boolean
157
168
  def installed
158
169
  initialize_thorfiles(nil, true)
159
170
  display_klasses(true, options["internal"])
160
171
  end
161
172
 
162
173
  desc "list [SEARCH]", "List the available thor commands (--substring means .*SEARCH)"
163
- method_options :substring => :boolean, :group => :string, :all => :boolean, :debug => :boolean
174
+ method_options substring: :boolean, group: :string, all: :boolean, debug: :boolean
164
175
  def list(search = "")
165
176
  initialize_thorfiles
166
177
 
@@ -302,7 +313,7 @@ private
302
313
  say shell.set_color(namespace, :blue, true)
303
314
  say "-" * namespace.size
304
315
 
305
- print_table(list, :truncate => true)
316
+ print_table(list, truncate: true)
306
317
  say
307
318
  end
308
319
  alias_method :display_tasks, :display_commands