github_cli 0.5.0 → 0.5.1

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 (41) hide show
  1. data/CHANGELOG.md +5 -0
  2. data/Gemfile.lock +1 -1
  3. data/Rakefile +4 -1
  4. data/github_cli.gemspec +3 -1
  5. data/lib/github_cli/dsl.rb +7 -3
  6. data/lib/github_cli/thor_ext.rb +1 -3
  7. data/lib/github_cli/vendor/thor/actions/create_file.rb +105 -0
  8. data/lib/github_cli/vendor/thor/actions/create_link.rb +57 -0
  9. data/lib/github_cli/vendor/thor/actions/directory.rb +98 -0
  10. data/lib/github_cli/vendor/thor/actions/empty_directory.rb +153 -0
  11. data/lib/github_cli/vendor/thor/actions/file_manipulation.rb +308 -0
  12. data/lib/github_cli/vendor/thor/actions/inject_into_file.rb +109 -0
  13. data/lib/github_cli/vendor/thor/actions.rb +318 -0
  14. data/lib/github_cli/vendor/thor/base.rb +641 -0
  15. data/lib/github_cli/vendor/thor/core_ext/dir_escape.rb +0 -0
  16. data/lib/github_cli/vendor/thor/core_ext/file_binary_read.rb +9 -0
  17. data/lib/github_cli/vendor/thor/core_ext/hash_with_indifferent_access.rb +75 -0
  18. data/lib/github_cli/vendor/thor/core_ext/ordered_hash.rb +100 -0
  19. data/lib/github_cli/vendor/thor/empty.txt +0 -0
  20. data/lib/github_cli/vendor/thor/error.rb +35 -0
  21. data/lib/github_cli/vendor/thor/group.rb +285 -0
  22. data/lib/github_cli/vendor/thor/invocation.rb +170 -0
  23. data/lib/github_cli/vendor/thor/parser/argument.rb +74 -0
  24. data/lib/github_cli/vendor/thor/parser/arguments.rb +171 -0
  25. data/lib/github_cli/vendor/thor/parser/option.rb +121 -0
  26. data/lib/github_cli/vendor/thor/parser/options.rb +178 -0
  27. data/lib/github_cli/vendor/thor/parser.rb +4 -0
  28. data/lib/github_cli/vendor/thor/rake_compat.rb +71 -0
  29. data/lib/github_cli/vendor/thor/runner.rb +321 -0
  30. data/lib/github_cli/vendor/thor/shell/basic.rb +389 -0
  31. data/lib/github_cli/vendor/thor/shell/color.rb +144 -0
  32. data/lib/github_cli/vendor/thor/shell/html.rb +123 -0
  33. data/lib/github_cli/vendor/thor/shell.rb +88 -0
  34. data/lib/github_cli/vendor/thor/task.rb +132 -0
  35. data/lib/github_cli/vendor/thor/util.rb +266 -0
  36. data/lib/github_cli/vendor/thor/version.rb +3 -0
  37. data/lib/github_cli/vendor/thor.rb +379 -0
  38. data/lib/github_cli/vendor.rb +7 -5
  39. data/lib/github_cli/version.rb +3 -1
  40. metadata +45 -22
  41. data/bin/ghc +0 -2
@@ -0,0 +1,321 @@
1
+ require 'thor'
2
+ require 'thor/group'
3
+ require 'thor/core_ext/file_binary_read'
4
+
5
+ require 'fileutils'
6
+ require 'open-uri'
7
+ require 'yaml'
8
+ require 'digest/md5'
9
+ require 'pathname'
10
+
11
+ class Thor::Runner < Thor #:nodoc:
12
+ map "-T" => :list, "-i" => :install, "-u" => :update, "-v" => :version
13
+
14
+ # Override Thor#help so it can give information about any class and any method.
15
+ #
16
+ def help(meth = nil)
17
+ if meth && !self.respond_to?(meth)
18
+ initialize_thorfiles(meth)
19
+ klass, task = Thor::Util.find_class_and_task_by_namespace(meth)
20
+ self.class.handle_no_task_error(task, false) if klass.nil?
21
+ klass.start(["-h", task].compact, :shell => self.shell)
22
+ else
23
+ super
24
+ end
25
+ end
26
+
27
+ # If a task is not found on Thor::Runner, method missing is invoked and
28
+ # Thor::Runner is then responsable for finding the task in all classes.
29
+ #
30
+ def method_missing(meth, *args)
31
+ meth = meth.to_s
32
+ initialize_thorfiles(meth)
33
+ klass, task = Thor::Util.find_class_and_task_by_namespace(meth)
34
+ self.class.handle_no_task_error(task, false) if klass.nil?
35
+ args.unshift(task) if task
36
+ klass.start(args, :shell => self.shell)
37
+ end
38
+
39
+ desc "install NAME", "Install an optionally named Thor file into your system tasks"
40
+ method_options :as => :string, :relative => :boolean, :force => :boolean
41
+ def install(name)
42
+ initialize_thorfiles
43
+
44
+ # If a directory name is provided as the argument, look for a 'main.thor'
45
+ # task in said directory.
46
+ begin
47
+ if File.directory?(File.expand_path(name))
48
+ base, package = File.join(name, "main.thor"), :directory
49
+ contents = open(base) {|input| input.read }
50
+ else
51
+ base, package = name, :file
52
+ contents = open(name) {|input| input.read }
53
+ end
54
+ rescue OpenURI::HTTPError
55
+ raise Error, "Error opening URI '#{name}'"
56
+ rescue Errno::ENOENT
57
+ raise Error, "Error opening file '#{name}'"
58
+ end
59
+
60
+ say "Your Thorfile contains:"
61
+ say contents
62
+
63
+ unless options["force"]
64
+ return false if no?("Do you wish to continue [y/N]?")
65
+ end
66
+
67
+ as = options["as"] || begin
68
+ first_line = contents.split("\n")[0]
69
+ (match = first_line.match(/\s*#\s*module:\s*([^\n]*)/)) ? match[1].strip : nil
70
+ end
71
+
72
+ unless as
73
+ basename = File.basename(name)
74
+ as = ask("Please specify a name for #{name} in the system repository [#{basename}]:")
75
+ as = basename if as.empty?
76
+ end
77
+
78
+ location = if options[:relative] || name =~ /^https?:\/\//
79
+ name
80
+ else
81
+ File.expand_path(name)
82
+ end
83
+
84
+ thor_yaml[as] = {
85
+ :filename => Digest::MD5.hexdigest(name + as),
86
+ :location => location,
87
+ :namespaces => Thor::Util.namespaces_in_content(contents, base)
88
+ }
89
+
90
+ save_yaml(thor_yaml)
91
+ say "Storing thor file in your system repository"
92
+ destination = File.join(thor_root, thor_yaml[as][:filename])
93
+
94
+ if package == :file
95
+ File.open(destination, "w") { |f| f.puts contents }
96
+ else
97
+ FileUtils.cp_r(name, destination)
98
+ end
99
+
100
+ thor_yaml[as][:filename] # Indicate success
101
+ end
102
+
103
+ desc "version", "Show Thor version"
104
+ def version
105
+ require 'thor/version'
106
+ say "Thor #{Thor::VERSION}"
107
+ end
108
+
109
+ desc "uninstall NAME", "Uninstall a named Thor module"
110
+ def uninstall(name)
111
+ raise Error, "Can't find module '#{name}'" unless thor_yaml[name]
112
+ say "Uninstalling #{name}."
113
+ FileUtils.rm_rf(File.join(thor_root, "#{thor_yaml[name][:filename]}"))
114
+
115
+ thor_yaml.delete(name)
116
+ save_yaml(thor_yaml)
117
+
118
+ puts "Done."
119
+ end
120
+
121
+ desc "update NAME", "Update a Thor file from its original location"
122
+ def update(name)
123
+ raise Error, "Can't find module '#{name}'" if !thor_yaml[name] || !thor_yaml[name][:location]
124
+
125
+ say "Updating '#{name}' from #{thor_yaml[name][:location]}"
126
+
127
+ old_filename = thor_yaml[name][:filename]
128
+ self.options = self.options.merge("as" => name)
129
+
130
+ if File.directory? File.expand_path(name)
131
+ FileUtils.rm_rf(File.join(thor_root, old_filename))
132
+
133
+ thor_yaml.delete(old_filename)
134
+ save_yaml(thor_yaml)
135
+
136
+ filename = install(name)
137
+ else
138
+ filename = install(thor_yaml[name][:location])
139
+ end
140
+
141
+ unless filename == old_filename
142
+ File.delete(File.join(thor_root, old_filename))
143
+ end
144
+ end
145
+
146
+ desc "installed", "List the installed Thor modules and tasks"
147
+ method_options :internal => :boolean
148
+ def installed
149
+ initialize_thorfiles(nil, true)
150
+ display_klasses(true, options["internal"])
151
+ end
152
+
153
+ desc "list [SEARCH]", "List the available thor tasks (--substring means .*SEARCH)"
154
+ method_options :substring => :boolean, :group => :string, :all => :boolean, :debug => :boolean
155
+ def list(search="")
156
+ initialize_thorfiles
157
+
158
+ search = ".*#{search}" if options["substring"]
159
+ search = /^#{search}.*/i
160
+ group = options[:group] || "standard"
161
+
162
+ klasses = Thor::Base.subclasses.select do |k|
163
+ (options[:all] || k.group == group) && k.namespace =~ search
164
+ end
165
+
166
+ display_klasses(false, false, klasses)
167
+ end
168
+
169
+ private
170
+
171
+ def self.banner(task, all = false, subcommand = false)
172
+ "thor " + task.formatted_usage(self, all, subcommand)
173
+ end
174
+
175
+ def thor_root
176
+ Thor::Util.thor_root
177
+ end
178
+
179
+ def thor_yaml
180
+ @thor_yaml ||= begin
181
+ yaml_file = File.join(thor_root, "thor.yml")
182
+ yaml = YAML.load_file(yaml_file) if File.exists?(yaml_file)
183
+ yaml || {}
184
+ end
185
+ end
186
+
187
+ # Save the yaml file. If none exists in thor root, creates one.
188
+ #
189
+ def save_yaml(yaml)
190
+ yaml_file = File.join(thor_root, "thor.yml")
191
+
192
+ unless File.exists?(yaml_file)
193
+ FileUtils.mkdir_p(thor_root)
194
+ yaml_file = File.join(thor_root, "thor.yml")
195
+ FileUtils.touch(yaml_file)
196
+ end
197
+
198
+ File.open(yaml_file, "w") { |f| f.puts yaml.to_yaml }
199
+ end
200
+
201
+ def self.exit_on_failure?
202
+ true
203
+ end
204
+
205
+ # Load the Thorfiles. If relevant_to is supplied, looks for specific files
206
+ # in the thor_root instead of loading them all.
207
+ #
208
+ # By default, it also traverses the current path until find Thor files, as
209
+ # described in thorfiles. This look up can be skipped by suppliying
210
+ # skip_lookup true.
211
+ #
212
+ def initialize_thorfiles(relevant_to=nil, skip_lookup=false)
213
+ thorfiles(relevant_to, skip_lookup).each do |f|
214
+ Thor::Util.load_thorfile(f, nil, options[:debug]) unless Thor::Base.subclass_files.keys.include?(File.expand_path(f))
215
+ end
216
+ end
217
+
218
+ # Finds Thorfiles by traversing from your current directory down to the root
219
+ # directory of your system. If at any time we find a Thor file, we stop.
220
+ #
221
+ # We also ensure that system-wide Thorfiles are loaded first, so local
222
+ # Thorfiles can override them.
223
+ #
224
+ # ==== Example
225
+ #
226
+ # If we start at /Users/wycats/dev/thor ...
227
+ #
228
+ # 1. /Users/wycats/dev/thor
229
+ # 2. /Users/wycats/dev
230
+ # 3. /Users/wycats <-- we find a Thorfile here, so we stop
231
+ #
232
+ # Suppose we start at c:\Documents and Settings\james\dev\thor ...
233
+ #
234
+ # 1. c:\Documents and Settings\james\dev\thor
235
+ # 2. c:\Documents and Settings\james\dev
236
+ # 3. c:\Documents and Settings\james
237
+ # 4. c:\Documents and Settings
238
+ # 5. c:\ <-- no Thorfiles found!
239
+ #
240
+ def thorfiles(relevant_to=nil, skip_lookup=false)
241
+ thorfiles = []
242
+
243
+ unless skip_lookup
244
+ Pathname.pwd.ascend do |path|
245
+ thorfiles = Thor::Util.globs_for(path).map { |g| Dir[g] }.flatten
246
+ break unless thorfiles.empty?
247
+ end
248
+ end
249
+
250
+ files = (relevant_to ? thorfiles_relevant_to(relevant_to) : Thor::Util.thor_root_glob)
251
+ files += thorfiles
252
+ files -= ["#{thor_root}/thor.yml"]
253
+
254
+ files.map! do |file|
255
+ File.directory?(file) ? File.join(file, "main.thor") : file
256
+ end
257
+ end
258
+
259
+ # Load Thorfiles relevant to the given method. If you provide "foo:bar" it
260
+ # will load all thor files in the thor.yaml that has "foo" e "foo:bar"
261
+ # namespaces registered.
262
+ #
263
+ def thorfiles_relevant_to(meth)
264
+ lookup = [ meth, meth.split(":")[0...-1].join(":") ]
265
+
266
+ files = thor_yaml.select do |k, v|
267
+ v[:namespaces] && !(v[:namespaces] & lookup).empty?
268
+ end
269
+
270
+ files.map { |k, v| File.join(thor_root, "#{v[:filename]}") }
271
+ end
272
+
273
+ # Display information about the given klasses. If with_module is given,
274
+ # it shows a table with information extracted from the yaml file.
275
+ #
276
+ def display_klasses(with_modules=false, show_internal=false, klasses=Thor::Base.subclasses)
277
+ klasses -= [Thor, Thor::Runner, Thor::Group] unless show_internal
278
+
279
+ raise Error, "No Thor tasks available" if klasses.empty?
280
+ show_modules if with_modules && !thor_yaml.empty?
281
+
282
+ list = Hash.new { |h,k| h[k] = [] }
283
+ groups = klasses.select { |k| k.ancestors.include?(Thor::Group) }
284
+
285
+ # Get classes which inherit from Thor
286
+ (klasses - groups).each { |k| list[k.namespace.split(":").first] += k.printable_tasks(false) }
287
+
288
+ # Get classes which inherit from Thor::Base
289
+ groups.map! { |k| k.printable_tasks(false).first }
290
+ list["root"] = groups
291
+
292
+ # Order namespaces with default coming first
293
+ list = list.sort{ |a,b| a[0].sub(/^default/, '') <=> b[0].sub(/^default/, '') }
294
+ list.each { |n, tasks| display_tasks(n, tasks) unless tasks.empty? }
295
+ end
296
+
297
+ def display_tasks(namespace, list) #:nodoc:
298
+ list.sort!{ |a,b| a[0] <=> b[0] }
299
+
300
+ say shell.set_color(namespace, :blue, true)
301
+ say "-" * namespace.size
302
+
303
+ print_table(list, :truncate => true)
304
+ say
305
+ end
306
+
307
+ def show_modules #:nodoc:
308
+ info = []
309
+ labels = ["Modules", "Namespaces"]
310
+
311
+ info << labels
312
+ info << [ "-" * labels[0].size, "-" * labels[1].size ]
313
+
314
+ thor_yaml.each do |name, hash|
315
+ info << [ name, hash[:namespaces].join(", ") ]
316
+ end
317
+
318
+ print_table info
319
+ say ""
320
+ end
321
+ end
@@ -0,0 +1,389 @@
1
+ require 'tempfile'
2
+
3
+ class Thor
4
+ module Shell
5
+ class Basic
6
+ attr_accessor :base
7
+ attr_reader :padding
8
+
9
+ # Initialize base, mute and padding to nil.
10
+ #
11
+ def initialize #:nodoc:
12
+ @base, @mute, @padding = nil, false, 0
13
+ end
14
+
15
+ # Mute everything that's inside given block
16
+ #
17
+ def mute
18
+ @mute = true
19
+ yield
20
+ ensure
21
+ @mute = false
22
+ end
23
+
24
+ # Check if base is muted
25
+ #
26
+ def mute?
27
+ @mute
28
+ end
29
+
30
+ # Sets the output padding, not allowing less than zero values.
31
+ #
32
+ def padding=(value)
33
+ @padding = [0, value].max
34
+ end
35
+
36
+ # Asks something to the user and receives a response.
37
+ #
38
+ # If asked to limit the correct responses, you can pass in an
39
+ # array of acceptable answers. If one of those is not supplied,
40
+ # they will be shown a message stating that one of those answers
41
+ # must be given and re-asked the question.
42
+ #
43
+ # ==== Example
44
+ # ask("What is your name?")
45
+ #
46
+ # ask("What is your favorite Neopolitan flavor?", :limited_to => ["strawberry", "chocolate", "vanilla"])
47
+ #
48
+ def ask(statement, *args)
49
+ options = args.last.is_a?(Hash) ? args.pop : {}
50
+
51
+ options[:limited_to] ? ask_filtered(statement, options[:limited_to], *args) : ask_simply(statement, *args)
52
+ end
53
+
54
+ # Say (print) something to the user. If the sentence ends with a whitespace
55
+ # or tab character, a new line is not appended (print + flush). Otherwise
56
+ # are passed straight to puts (behavior got from Highline).
57
+ #
58
+ # ==== Example
59
+ # say("I know you knew that.")
60
+ #
61
+ def say(message="", color=nil, force_new_line=(message.to_s !~ /( |\t)$/))
62
+ message = message.to_s
63
+
64
+ message = set_color(message, *color) if color
65
+
66
+ spaces = " " * padding
67
+
68
+ if force_new_line
69
+ stdout.puts(spaces + message)
70
+ else
71
+ stdout.print(spaces + message)
72
+ end
73
+ stdout.flush
74
+ end
75
+
76
+ # Say a status with the given color and appends the message. Since this
77
+ # method is used frequently by actions, it allows nil or false to be given
78
+ # in log_status, avoiding the message from being shown. If a Symbol is
79
+ # given in log_status, it's used as the color.
80
+ #
81
+ def say_status(status, message, log_status=true)
82
+ return if quiet? || log_status == false
83
+ spaces = " " * (padding + 1)
84
+ color = log_status.is_a?(Symbol) ? log_status : :green
85
+
86
+ status = status.to_s.rjust(12)
87
+ status = set_color status, color, true if color
88
+
89
+ stdout.puts "#{status}#{spaces}#{message}"
90
+ stdout.flush
91
+ end
92
+
93
+ # Make a question the to user and returns true if the user replies "y" or
94
+ # "yes".
95
+ #
96
+ def yes?(statement, color=nil)
97
+ !!(ask(statement, color) =~ is?(:yes))
98
+ end
99
+
100
+ # Make a question the to user and returns true if the user replies "n" or
101
+ # "no".
102
+ #
103
+ def no?(statement, color=nil)
104
+ !yes?(statement, color)
105
+ end
106
+
107
+ # Prints values in columns
108
+ #
109
+ # ==== Parameters
110
+ # Array[String, String, ...]
111
+ #
112
+ def print_in_columns(array)
113
+ return if array.empty?
114
+ colwidth = (array.map{|el| el.to_s.size}.max || 0) + 2
115
+ array.each_with_index do |value, index|
116
+ # Don't output trailing spaces when printing the last column
117
+ if ((((index + 1) % (terminal_width / colwidth))).zero? && !index.zero?) || index + 1 == array.length
118
+ stdout.puts value
119
+ else
120
+ stdout.printf("%-#{colwidth}s", value)
121
+ end
122
+ end
123
+ end
124
+
125
+ # Prints a table.
126
+ #
127
+ # ==== Parameters
128
+ # Array[Array[String, String, ...]]
129
+ #
130
+ # ==== Options
131
+ # indent<Integer>:: Indent the first column by indent value.
132
+ # colwidth<Integer>:: Force the first column to colwidth spaces wide.
133
+ #
134
+ def print_table(array, options={})
135
+ return if array.empty?
136
+
137
+ formats, indent, colwidth = [], options[:indent].to_i, options[:colwidth]
138
+ options[:truncate] = terminal_width if options[:truncate] == true
139
+
140
+ formats << "%-#{colwidth + 2}s" if colwidth
141
+ start = colwidth ? 1 : 0
142
+
143
+ colcount = array.max{|a,b| a.size <=> b.size }.size
144
+
145
+ maximas = []
146
+
147
+ start.upto(colcount - 1) do |index|
148
+ maxima = array.map {|row| row[index] ? row[index].to_s.size : 0 }.max
149
+ maximas << maxima
150
+ if index == colcount - 1
151
+ # Don't output 2 trailing spaces when printing the last column
152
+ formats << "%-s"
153
+ else
154
+ formats << "%-#{maxima + 2}s"
155
+ end
156
+ end
157
+
158
+ formats[0] = formats[0].insert(0, " " * indent)
159
+ formats << "%s"
160
+
161
+ array.each do |row|
162
+ sentence = ""
163
+
164
+ row.each_with_index do |column, index|
165
+ maxima = maximas[index]
166
+
167
+ if column.is_a?(Numeric)
168
+ if index == row.size - 1
169
+ # Don't output 2 trailing spaces when printing the last column
170
+ f = "%#{maxima}s"
171
+ else
172
+ f = "%#{maxima}s "
173
+ end
174
+ else
175
+ f = formats[index]
176
+ end
177
+ sentence << f % column.to_s
178
+ end
179
+
180
+ sentence = truncate(sentence, options[:truncate]) if options[:truncate]
181
+ stdout.puts sentence
182
+ end
183
+ end
184
+
185
+ # Prints a long string, word-wrapping the text to the current width of the
186
+ # terminal display. Ideal for printing heredocs.
187
+ #
188
+ # ==== Parameters
189
+ # String
190
+ #
191
+ # ==== Options
192
+ # indent<Integer>:: Indent each line of the printed paragraph by indent value.
193
+ #
194
+ def print_wrapped(message, options={})
195
+ indent = options[:indent] || 0
196
+ width = terminal_width - indent
197
+ paras = message.split("\n\n")
198
+
199
+ paras.map! do |unwrapped|
200
+ unwrapped.strip.gsub(/\n/, " ").squeeze(" ").
201
+ gsub(/.{1,#{width}}(?:\s|\Z)/){($& + 5.chr).
202
+ gsub(/\n\005/,"\n").gsub(/\005/,"\n")}
203
+ end
204
+
205
+ paras.each do |para|
206
+ para.split("\n").each do |line|
207
+ stdout.puts line.insert(0, " " * indent)
208
+ end
209
+ stdout.puts unless para == paras.last
210
+ end
211
+ end
212
+
213
+ # Deals with file collision and returns true if the file should be
214
+ # overwritten and false otherwise. If a block is given, it uses the block
215
+ # response as the content for the diff.
216
+ #
217
+ # ==== Parameters
218
+ # destination<String>:: the destination file to solve conflicts
219
+ # block<Proc>:: an optional block that returns the value to be used in diff
220
+ #
221
+ def file_collision(destination)
222
+ return true if @always_force
223
+ options = block_given? ? "[Ynaqdh]" : "[Ynaqh]"
224
+
225
+ while true
226
+ answer = ask %[Overwrite #{destination}? (enter "h" for help) #{options}]
227
+
228
+ case answer
229
+ when is?(:yes), is?(:force), ""
230
+ return true
231
+ when is?(:no), is?(:skip)
232
+ return false
233
+ when is?(:always)
234
+ return @always_force = true
235
+ when is?(:quit)
236
+ say 'Aborting...'
237
+ raise SystemExit
238
+ when is?(:diff)
239
+ show_diff(destination, yield) if block_given?
240
+ say 'Retrying...'
241
+ else
242
+ say file_collision_help
243
+ end
244
+ end
245
+ end
246
+
247
+ # This code was copied from Rake, available under MIT-LICENSE
248
+ # Copyright (c) 2003, 2004 Jim Weirich
249
+ def terminal_width
250
+ if ENV['THOR_COLUMNS']
251
+ result = ENV['THOR_COLUMNS'].to_i
252
+ else
253
+ result = unix? ? dynamic_width : 80
254
+ end
255
+ (result < 10) ? 80 : result
256
+ rescue
257
+ 80
258
+ end
259
+
260
+ # Called if something goes wrong during the execution. This is used by Thor
261
+ # internally and should not be used inside your scripts. If something went
262
+ # wrong, you can always raise an exception. If you raise a Thor::Error, it
263
+ # will be rescued and wrapped in the method below.
264
+ #
265
+ def error(statement)
266
+ stderr.puts statement
267
+ end
268
+
269
+ # Apply color to the given string with optional bold. Disabled in the
270
+ # Thor::Shell::Basic class.
271
+ #
272
+ def set_color(string, *args) #:nodoc:
273
+ string
274
+ end
275
+
276
+ protected
277
+
278
+ def lookup_color(color)
279
+ return color unless color.is_a?(Symbol)
280
+ self.class.const_get(color.to_s.upcase)
281
+ end
282
+
283
+ def stdout
284
+ $stdout
285
+ end
286
+
287
+ def stdin
288
+ $stdin
289
+ end
290
+
291
+ def stderr
292
+ $stderr
293
+ end
294
+
295
+ def is?(value) #:nodoc:
296
+ value = value.to_s
297
+
298
+ if value.size == 1
299
+ /\A#{value}\z/i
300
+ else
301
+ /\A(#{value}|#{value[0,1]})\z/i
302
+ end
303
+ end
304
+
305
+ def file_collision_help #:nodoc:
306
+ <<HELP
307
+ Y - yes, overwrite
308
+ n - no, do not overwrite
309
+ a - all, overwrite this and all others
310
+ q - quit, abort
311
+ d - diff, show the differences between the old and the new
312
+ h - help, show this help
313
+ HELP
314
+ end
315
+
316
+ def show_diff(destination, content) #:nodoc:
317
+ diff_cmd = ENV['THOR_DIFF'] || ENV['RAILS_DIFF'] || 'diff -u'
318
+
319
+ Tempfile.open(File.basename(destination), File.dirname(destination)) do |temp|
320
+ temp.write content
321
+ temp.rewind
322
+ system %(#{diff_cmd} "#{destination}" "#{temp.path}")
323
+ end
324
+ end
325
+
326
+ def quiet? #:nodoc:
327
+ mute? || (base && base.options[:quiet])
328
+ end
329
+
330
+ # Calculate the dynamic width of the terminal
331
+ def dynamic_width
332
+ @dynamic_width ||= (dynamic_width_stty.nonzero? || dynamic_width_tput)
333
+ end
334
+
335
+ def dynamic_width_stty
336
+ %x{stty size 2>/dev/null}.split[1].to_i
337
+ end
338
+
339
+ def dynamic_width_tput
340
+ %x{tput cols 2>/dev/null}.to_i
341
+ end
342
+
343
+ def unix?
344
+ RUBY_PLATFORM =~ /(aix|darwin|linux|(net|free|open)bsd|cygwin|solaris|irix|hpux)/i
345
+ end
346
+
347
+ def truncate(string, width)
348
+ as_unicode do
349
+ chars = string.chars.to_a
350
+ if chars.length <= width
351
+ chars.join
352
+ else
353
+ ( chars[0, width-3].join ) + "..."
354
+ end
355
+ end
356
+ end
357
+
358
+ if "".respond_to?(:encode)
359
+ def as_unicode
360
+ yield
361
+ end
362
+ else
363
+ def as_unicode
364
+ old, $KCODE = $KCODE, "U"
365
+ yield
366
+ ensure
367
+ $KCODE = old
368
+ end
369
+ end
370
+
371
+ def ask_simply(statement, color=nil)
372
+ say("#{statement} ", color)
373
+ stdin.gets.tap{|text| text.strip! if text}
374
+ end
375
+
376
+ def ask_filtered(statement, answer_set, *args)
377
+ correct_answer = nil
378
+ until correct_answer
379
+ answer = ask_simply("#{statement} #{answer_set.inspect}", *args)
380
+ correct_answer = answer_set.include?(answer) ? answer : nil
381
+ answers = answer_set.map(&:inspect).join(", ")
382
+ say("Your response must be one of: [#{answers}]. Please try again.") unless correct_answer
383
+ end
384
+ correct_answer
385
+ end
386
+
387
+ end
388
+ end
389
+ end