thor 0.18.1 → 0.19.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (92) hide show
  1. checksums.yaml +7 -0
  2. data/README.md +13 -7
  3. data/Thorfile +4 -5
  4. data/bin/thor +1 -1
  5. data/lib/thor.rb +78 -67
  6. data/lib/thor/actions.rb +57 -56
  7. data/lib/thor/actions/create_file.rb +33 -35
  8. data/lib/thor/actions/create_link.rb +2 -3
  9. data/lib/thor/actions/directory.rb +37 -38
  10. data/lib/thor/actions/empty_directory.rb +67 -69
  11. data/lib/thor/actions/file_manipulation.rb +17 -15
  12. data/lib/thor/actions/inject_into_file.rb +27 -29
  13. data/lib/thor/base.rb +193 -189
  14. data/lib/thor/command.rb +20 -23
  15. data/lib/thor/core_ext/hash_with_indifferent_access.rb +21 -24
  16. data/lib/thor/core_ext/io_binary_read.rb +2 -4
  17. data/lib/thor/core_ext/ordered_hash.rb +9 -11
  18. data/lib/thor/error.rb +5 -1
  19. data/lib/thor/group.rb +53 -54
  20. data/lib/thor/invocation.rb +44 -38
  21. data/lib/thor/line_editor.rb +17 -0
  22. data/lib/thor/line_editor/basic.rb +35 -0
  23. data/lib/thor/line_editor/readline.rb +88 -0
  24. data/lib/thor/parser.rb +4 -4
  25. data/lib/thor/parser/argument.rb +28 -29
  26. data/lib/thor/parser/arguments.rb +102 -98
  27. data/lib/thor/parser/option.rb +26 -22
  28. data/lib/thor/parser/options.rb +86 -86
  29. data/lib/thor/rake_compat.rb +9 -10
  30. data/lib/thor/runner.rb +141 -141
  31. data/lib/thor/shell.rb +27 -34
  32. data/lib/thor/shell/basic.rb +91 -63
  33. data/lib/thor/shell/color.rb +44 -43
  34. data/lib/thor/shell/html.rb +59 -60
  35. data/lib/thor/util.rb +24 -27
  36. data/lib/thor/version.rb +1 -1
  37. data/spec/actions/create_file_spec.rb +25 -27
  38. data/spec/actions/create_link_spec.rb +19 -18
  39. data/spec/actions/directory_spec.rb +31 -31
  40. data/spec/actions/empty_directory_spec.rb +18 -18
  41. data/spec/actions/file_manipulation_spec.rb +38 -28
  42. data/spec/actions/inject_into_file_spec.rb +13 -13
  43. data/spec/actions_spec.rb +43 -43
  44. data/spec/base_spec.rb +45 -38
  45. data/spec/command_spec.rb +13 -14
  46. data/spec/core_ext/hash_with_indifferent_access_spec.rb +19 -19
  47. data/spec/core_ext/ordered_hash_spec.rb +6 -6
  48. data/spec/exit_condition_spec.rb +4 -4
  49. data/spec/fixtures/invoke.thor +19 -0
  50. data/spec/fixtures/script.thor +1 -1
  51. data/spec/group_spec.rb +30 -24
  52. data/spec/helper.rb +28 -15
  53. data/spec/invocation_spec.rb +39 -19
  54. data/spec/line_editor/basic_spec.rb +28 -0
  55. data/spec/line_editor/readline_spec.rb +69 -0
  56. data/spec/line_editor_spec.rb +43 -0
  57. data/spec/parser/argument_spec.rb +12 -12
  58. data/spec/parser/arguments_spec.rb +11 -11
  59. data/spec/parser/option_spec.rb +33 -25
  60. data/spec/parser/options_spec.rb +66 -52
  61. data/spec/quality_spec.rb +75 -0
  62. data/spec/rake_compat_spec.rb +10 -10
  63. data/spec/register_spec.rb +60 -30
  64. data/spec/runner_spec.rb +67 -62
  65. data/spec/sandbox/application.rb +2 -0
  66. data/spec/sandbox/app{1}/README +3 -0
  67. data/spec/sandbox/bundle/execute.rb +6 -0
  68. data/spec/sandbox/bundle/main.thor +1 -0
  69. data/spec/sandbox/command.thor +10 -0
  70. data/spec/sandbox/doc/%file_name%.rb.tt +1 -0
  71. data/spec/sandbox/doc/COMMENTER +11 -0
  72. data/spec/sandbox/doc/README +3 -0
  73. data/spec/sandbox/doc/block_helper.rb +3 -0
  74. data/spec/sandbox/doc/config.rb +1 -0
  75. data/spec/sandbox/doc/config.yaml.tt +1 -0
  76. data/spec/sandbox/doc/excluding/%file_name%.rb.tt +1 -0
  77. data/spec/sandbox/enum.thor +10 -0
  78. data/spec/sandbox/group.thor +128 -0
  79. data/spec/sandbox/invoke.thor +131 -0
  80. data/spec/sandbox/path with spaces b/data/spec/sandbox/path with → spaces +0 -0
  81. data/spec/sandbox/preserve/script.sh +3 -0
  82. data/spec/sandbox/script.thor +220 -0
  83. data/spec/sandbox/subcommand.thor +17 -0
  84. data/spec/shell/basic_spec.rb +107 -86
  85. data/spec/shell/color_spec.rb +32 -8
  86. data/spec/shell/html_spec.rb +3 -4
  87. data/spec/shell_spec.rb +7 -7
  88. data/spec/subcommand_spec.rb +20 -2
  89. data/spec/thor_spec.rb +111 -97
  90. data/spec/util_spec.rb +30 -30
  91. data/thor.gemspec +14 -14
  92. metadata +69 -25
@@ -1,8 +1,7 @@
1
- require 'thor/actions/empty_directory'
1
+ require "thor/actions/empty_directory"
2
2
 
3
3
  class Thor
4
4
  module Actions
5
-
6
5
  # Create a new file relative to the destination root with the given data,
7
6
  # which is the return value of a block or a data string.
8
7
  #
@@ -25,7 +24,7 @@ class Thor
25
24
  data = args.first
26
25
  action CreateFile.new(self, destination, block || data.to_s, config)
27
26
  end
28
- alias :add_file :create_file
27
+ alias_method :add_file, :create_file
29
28
 
30
29
  # CreateFile is a subset of Template, which instead of rendering a file with
31
30
  # ERB, it gets the content from the user.
@@ -33,7 +32,7 @@ class Thor
33
32
  class CreateFile < EmptyDirectory #:nodoc:
34
33
  attr_reader :data
35
34
 
36
- def initialize(base, destination, data, config={})
35
+ def initialize(base, destination, data, config = {})
37
36
  @data = data
38
37
  super(base, destination, config)
39
38
  end
@@ -60,46 +59,45 @@ class Thor
60
59
  def invoke!
61
60
  invoke_with_conflict_check do
62
61
  FileUtils.mkdir_p(File.dirname(destination))
63
- File.open(destination, 'wb') { |f| f.write render }
62
+ File.open(destination, "wb") { |f| f.write render }
64
63
  end
65
64
  given_destination
66
65
  end
67
66
 
68
- protected
69
-
70
- # Now on conflict we check if the file is identical or not.
71
- #
72
- def on_conflict_behavior(&block)
73
- if identical?
74
- say_status :identical, :blue
75
- else
76
- options = base.options.merge(config)
77
- force_or_skip_or_conflict(options[:force], options[:skip], &block)
78
- end
79
- end
67
+ protected
80
68
 
81
- # If force is true, run the action, otherwise check if it's not being
82
- # skipped. If both are false, show the file_collision menu, if the menu
83
- # returns true, force it, otherwise skip.
84
- #
85
- def force_or_skip_or_conflict(force, skip, &block)
86
- if force
87
- say_status :force, :yellow
88
- block.call unless pretend?
89
- elsif skip
90
- say_status :skip, :yellow
91
- else
92
- say_status :conflict, :red
93
- force_or_skip_or_conflict(force_on_collision?, true, &block)
94
- end
69
+ # Now on conflict we check if the file is identical or not.
70
+ #
71
+ def on_conflict_behavior(&block)
72
+ if identical?
73
+ say_status :identical, :blue
74
+ else
75
+ options = base.options.merge(config)
76
+ force_or_skip_or_conflict(options[:force], options[:skip], &block)
95
77
  end
78
+ end
96
79
 
97
- # Shows the file collision menu to the user and gets the result.
98
- #
99
- def force_on_collision?
100
- base.shell.file_collision(destination){ render }
80
+ # If force is true, run the action, otherwise check if it's not being
81
+ # skipped. If both are false, show the file_collision menu, if the menu
82
+ # returns true, force it, otherwise skip.
83
+ #
84
+ def force_or_skip_or_conflict(force, skip, &block)
85
+ if force
86
+ say_status :force, :yellow
87
+ block.call unless pretend?
88
+ elsif skip
89
+ say_status :skip, :yellow
90
+ else
91
+ say_status :conflict, :red
92
+ force_or_skip_or_conflict(force_on_collision?, true, &block)
101
93
  end
94
+ end
102
95
 
96
+ # Shows the file collision menu to the user and gets the result.
97
+ #
98
+ def force_on_collision?
99
+ base.shell.file_collision(destination) { render }
100
+ end
103
101
  end
104
102
  end
105
103
  end
@@ -1,8 +1,7 @@
1
- require 'thor/actions/create_file'
1
+ require "thor/actions/create_file"
2
2
 
3
3
  class Thor
4
4
  module Actions
5
-
6
5
  # Create a new file relative to the destination root from the given source.
7
6
  #
8
7
  # ==== Parameters
@@ -20,7 +19,7 @@ class Thor
20
19
  source = args.first
21
20
  action CreateLink.new(self, destination, source, config)
22
21
  end
23
- alias :add_link :create_link
22
+ alias_method :add_link, :create_link
24
23
 
25
24
  # CreateLink is a subset of CreateFile, which instead of taking a block of
26
25
  # data, just takes a source string from the user.
@@ -1,4 +1,4 @@
1
- require 'thor/actions/empty_directory'
1
+ require "thor/actions/empty_directory"
2
2
 
3
3
  class Thor
4
4
  module Actions
@@ -55,10 +55,10 @@ class Thor
55
55
  class Directory < EmptyDirectory #:nodoc:
56
56
  attr_reader :source
57
57
 
58
- def initialize(base, source, destination=nil, config={}, &block)
58
+ def initialize(base, source, destination = nil, config = {}, &block)
59
59
  @source = File.expand_path(base.find_in_source_paths(source.to_s))
60
60
  @block = block
61
- super(base, destination, { :recursive => true }.merge(config))
61
+ super(base, destination, {:recursive => true}.merge(config))
62
62
  end
63
63
 
64
64
  def invoke!
@@ -70,50 +70,49 @@ class Thor
70
70
  execute!
71
71
  end
72
72
 
73
- protected
73
+ protected
74
74
 
75
- def execute!
76
- lookup = Util.escape_globs(source)
77
- lookup = config[:recursive] ? File.join(lookup, '**') : lookup
78
- lookup = file_level_lookup(lookup)
75
+ def execute! # rubocop:disable MethodLength
76
+ lookup = Util.escape_globs(source)
77
+ lookup = config[:recursive] ? File.join(lookup, "**") : lookup
78
+ lookup = file_level_lookup(lookup)
79
79
 
80
- files(lookup).sort.each do |file_source|
81
- next if File.directory?(file_source)
82
- next if config[:exclude_pattern] && file_source.match(config[:exclude_pattern])
83
- file_destination = File.join(given_destination, file_source.gsub(source, '.'))
84
- file_destination.gsub!('/./', '/')
80
+ files(lookup).sort.each do |file_source|
81
+ next if File.directory?(file_source)
82
+ next if config[:exclude_pattern] && file_source.match(config[:exclude_pattern])
83
+ file_destination = File.join(given_destination, file_source.gsub(source, "."))
84
+ file_destination.gsub!("/./", "/")
85
85
 
86
- case file_source
87
- when /\.empty_directory$/
88
- dirname = File.dirname(file_destination).gsub(/\/\.$/, '')
89
- next if dirname == given_destination
90
- base.empty_directory(dirname, config)
91
- when /\.tt$/
92
- destination = base.template(file_source, file_destination[0..-4], config, &@block)
93
- else
94
- destination = base.copy_file(file_source, file_destination, config, &@block)
95
- end
86
+ case file_source
87
+ when /\.empty_directory$/
88
+ dirname = File.dirname(file_destination).gsub(/\/\.$/, "")
89
+ next if dirname == given_destination
90
+ base.empty_directory(dirname, config)
91
+ when /#{TEMPLATE_EXTNAME}$/
92
+ base.template(file_source, file_destination[0..-4], config, &@block)
93
+ else
94
+ base.copy_file(file_source, file_destination, config, &@block)
96
95
  end
97
96
  end
97
+ end
98
98
 
99
- if RUBY_VERSION < '2.0'
100
- def file_level_lookup(previous_lookup)
101
- File.join(previous_lookup, '{*,.[a-z]*}')
102
- end
103
-
104
- def files(lookup)
105
- Dir[lookup]
106
- end
107
- else
108
- def file_level_lookup(previous_lookup)
109
- File.join(previous_lookup, '*')
110
- end
99
+ if RUBY_VERSION < "2.0"
100
+ def file_level_lookup(previous_lookup)
101
+ File.join(previous_lookup, "{*,.[a-z]*}")
102
+ end
111
103
 
112
- def files(lookup)
113
- Dir.glob(lookup, File::FNM_DOTMATCH)
114
- end
104
+ def files(lookup)
105
+ Dir[lookup]
106
+ end
107
+ else
108
+ def file_level_lookup(previous_lookup)
109
+ File.join(previous_lookup, "*")
115
110
  end
116
111
 
112
+ def files(lookup)
113
+ Dir.glob(lookup, File::FNM_DOTMATCH)
114
+ end
115
+ end
117
116
  end
118
117
  end
119
118
  end
@@ -1,6 +1,5 @@
1
1
  class Thor
2
2
  module Actions
3
-
4
3
  # Creates an empty directory.
5
4
  #
6
5
  # ==== Parameters
@@ -11,7 +10,7 @@ class Thor
11
10
  #
12
11
  # empty_directory "doc"
13
12
  #
14
- def empty_directory(destination, config={})
13
+ def empty_directory(destination, config = {})
15
14
  action EmptyDirectory.new(self, destination, config)
16
15
  end
17
16
 
@@ -32,8 +31,8 @@ class Thor
32
31
  # destination<String>:: Relative path to the destination of this file
33
32
  # config<Hash>:: give :verbose => false to not log the status.
34
33
  #
35
- def initialize(base, destination, config={})
36
- @base, @config = base, { :verbose => true }.merge(config)
34
+ def initialize(base, destination, config = {})
35
+ @base, @config = base, {:verbose => true}.merge(config)
37
36
  self.destination = destination
38
37
  end
39
38
 
@@ -43,7 +42,7 @@ class Thor
43
42
  # Boolean:: true if the file exists, false otherwise.
44
43
  #
45
44
  def exists?
46
- ::File.exists?(destination)
45
+ ::File.exist?(destination)
47
46
  end
48
47
 
49
48
  def invoke!
@@ -58,80 +57,79 @@ class Thor
58
57
  given_destination
59
58
  end
60
59
 
61
- protected
60
+ protected
62
61
 
63
- # Shortcut for pretend.
64
- #
65
- def pretend?
66
- base.options[:pretend]
67
- end
62
+ # Shortcut for pretend.
63
+ #
64
+ def pretend?
65
+ base.options[:pretend]
66
+ end
68
67
 
69
- # Sets the absolute destination value from a relative destination value.
70
- # It also stores the given and relative destination. Let's suppose our
71
- # script is being executed on "dest", it sets the destination root to
72
- # "dest". The destination, given_destination and relative_destination
73
- # are related in the following way:
74
- #
75
- # inside "bar" do
76
- # empty_directory "baz"
77
- # end
78
- #
79
- # destination #=> dest/bar/baz
80
- # relative_destination #=> bar/baz
81
- # given_destination #=> baz
82
- #
83
- def destination=(destination)
84
- if destination
85
- @given_destination = convert_encoded_instructions(destination.to_s)
86
- @destination = ::File.expand_path(@given_destination, base.destination_root)
87
- @relative_destination = base.relative_to_original_destination_root(@destination)
88
- end
68
+ # Sets the absolute destination value from a relative destination value.
69
+ # It also stores the given and relative destination. Let's suppose our
70
+ # script is being executed on "dest", it sets the destination root to
71
+ # "dest". The destination, given_destination and relative_destination
72
+ # are related in the following way:
73
+ #
74
+ # inside "bar" do
75
+ # empty_directory "baz"
76
+ # end
77
+ #
78
+ # destination #=> dest/bar/baz
79
+ # relative_destination #=> bar/baz
80
+ # given_destination #=> baz
81
+ #
82
+ def destination=(destination)
83
+ if destination
84
+ @given_destination = convert_encoded_instructions(destination.to_s)
85
+ @destination = ::File.expand_path(@given_destination, base.destination_root)
86
+ @relative_destination = base.relative_to_original_destination_root(@destination)
89
87
  end
88
+ end
90
89
 
91
- # Filenames in the encoded form are converted. If you have a file:
92
- #
93
- # %file_name%.rb
94
- #
95
- # It calls #file_name from the base and replaces %-string with the
96
- # return value (should be String) of #file_name:
97
- #
98
- # user.rb
99
- #
100
- # The method referenced can be either public or private.
101
- #
102
- def convert_encoded_instructions(filename)
103
- filename.gsub(/%(.*?)%/) do |initial_string|
104
- method = $1.strip
105
- base.respond_to?(method, true) ? base.send(method) : initial_string
106
- end
90
+ # Filenames in the encoded form are converted. If you have a file:
91
+ #
92
+ # %file_name%.rb
93
+ #
94
+ # It calls #file_name from the base and replaces %-string with the
95
+ # return value (should be String) of #file_name:
96
+ #
97
+ # user.rb
98
+ #
99
+ # The method referenced can be either public or private.
100
+ #
101
+ def convert_encoded_instructions(filename)
102
+ filename.gsub(/%(.*?)%/) do |initial_string|
103
+ method = $1.strip
104
+ base.respond_to?(method, true) ? base.send(method) : initial_string
107
105
  end
106
+ end
108
107
 
109
- # Receives a hash of options and just execute the block if some
110
- # conditions are met.
111
- #
112
- def invoke_with_conflict_check(&block)
113
- if exists?
114
- on_conflict_behavior(&block)
115
- else
116
- say_status :create, :green
117
- block.call unless pretend?
118
- end
119
-
120
- destination
108
+ # Receives a hash of options and just execute the block if some
109
+ # conditions are met.
110
+ #
111
+ def invoke_with_conflict_check(&block)
112
+ if exists?
113
+ on_conflict_behavior(&block)
114
+ else
115
+ say_status :create, :green
116
+ block.call unless pretend?
121
117
  end
122
118
 
123
- # What to do when the destination file already exists.
124
- #
125
- def on_conflict_behavior(&block)
126
- say_status :exist, :blue
127
- end
119
+ destination
120
+ end
128
121
 
129
- # Shortcut to say_status shell method.
130
- #
131
- def say_status(status, color)
132
- base.shell.say_status status, relative_destination, color if config[:verbose]
133
- end
122
+ # What to do when the destination file already exists.
123
+ #
124
+ def on_conflict_behavior(&block)
125
+ say_status :exist, :blue
126
+ end
134
127
 
128
+ # Shortcut to say_status shell method.
129
+ #
130
+ def say_status(status, color)
131
+ base.shell.say_status status, relative_destination, color if config[:verbose]
132
+ end
135
133
  end
136
134
  end
137
135
  end
@@ -1,9 +1,8 @@
1
- require 'erb'
2
- require 'open-uri'
1
+ require "erb"
2
+ require "open-uri"
3
3
 
4
4
  class Thor
5
5
  module Actions
6
-
7
6
  # Copies the file from the relative source to the relative destination. If
8
7
  # the destination is not given it's assumed to be equal to the source.
9
8
  #
@@ -79,8 +78,8 @@ class Thor
79
78
  config = args.last.is_a?(Hash) ? args.pop : {}
80
79
  destination = args.first
81
80
 
82
- source = File.expand_path(find_in_source_paths(source.to_s)) unless source =~ /^https?\:\/\//
83
- render = open(source) {|input| input.binmode.read }
81
+ source = File.expand_path(find_in_source_paths(source.to_s)) unless source =~ %r{^https?\://}
82
+ render = open(source) { |input| input.binmode.read }
84
83
 
85
84
  destination ||= if block_given?
86
85
  block.arity == 1 ? block.call(render) : block.call
@@ -108,13 +107,13 @@ class Thor
108
107
  #
109
108
  def template(source, *args, &block)
110
109
  config = args.last.is_a?(Hash) ? args.pop : {}
111
- destination = args.first || source.sub(/\.tt$/, '')
110
+ destination = args.first || source.sub(/#{TEMPLATE_EXTNAME}$/, "")
112
111
 
113
112
  source = File.expand_path(find_in_source_paths(source.to_s))
114
- context = instance_eval('binding')
113
+ context = instance_eval("binding")
115
114
 
116
115
  create_file destination, nil, config do
117
- content = ERB.new(::File.binread(source), nil, '-', '@output_buffer').result(context)
116
+ content = ERB.new(::File.binread(source), nil, "-", "@output_buffer").result(context)
118
117
  content = block.call(content) if block
119
118
  content
120
119
  end
@@ -131,7 +130,7 @@ class Thor
131
130
  #
132
131
  # chmod "script/server", 0755
133
132
  #
134
- def chmod(path, mode, config={})
133
+ def chmod(path, mode, config = {})
135
134
  return unless behavior == :invoke
136
135
  path = File.expand_path(path, destination_root)
137
136
  say_status :chmod, relative_to_original_destination_root(path), config.fetch(:verbose, true)
@@ -231,7 +230,7 @@ class Thor
231
230
  unless options[:pretend]
232
231
  content = File.binread(path)
233
232
  content.gsub!(flag, *args, &block)
234
- File.open(path, 'wb') { |file| file.write(content) }
233
+ File.open(path, "wb") { |file| file.write(content) }
235
234
  end
236
235
  end
237
236
 
@@ -284,17 +283,20 @@ class Thor
284
283
  # remove_file 'README'
285
284
  # remove_file 'app/controllers/application_controller.rb'
286
285
  #
287
- def remove_file(path, config={})
286
+ def remove_file(path, config = {})
288
287
  return unless behavior == :invoke
289
288
  path = File.expand_path(path, destination_root)
290
289
 
291
290
  say_status :remove, relative_to_original_destination_root(path), config.fetch(:verbose, true)
292
- ::FileUtils.rm_rf(path) if !options[:pretend] && File.exists?(path)
291
+ ::FileUtils.rm_rf(path) if !options[:pretend] && File.exist?(path)
293
292
  end
294
- alias :remove_dir :remove_file
293
+ alias_method :remove_dir, :remove_file
295
294
 
296
- private
297
295
  attr_accessor :output_buffer
296
+ private :output_buffer, :output_buffer=
297
+
298
+ private
299
+
298
300
  def concat(string)
299
301
  @output_buffer.concat(string)
300
302
  end
@@ -303,7 +305,7 @@ class Thor
303
305
  with_output_buffer { block.call(*args) }
304
306
  end
305
307
 
306
- def with_output_buffer(buf = '') #:nodoc:
308
+ def with_output_buffer(buf = "") #:nodoc:
307
309
  self.output_buffer, old_buffer = buf, output_buffer
308
310
  yield
309
311
  output_buffer