ing 0.1.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 (59) hide show
  1. data/.gitignore +5 -0
  2. data/GENERATORS.md +2 -0
  3. data/LICENSE +18 -0
  4. data/OPTIONS.md +2 -0
  5. data/README.md +251 -0
  6. data/TASKS.md +21 -0
  7. data/bin/ing +5 -0
  8. data/examples/rspec_convert.rb +102 -0
  9. data/ing.gemspec +29 -0
  10. data/ing.rb +102 -0
  11. data/lib/ing.rb +78 -0
  12. data/lib/ing/actions/create_file.rb +105 -0
  13. data/lib/ing/actions/create_link.rb +57 -0
  14. data/lib/ing/actions/directory.rb +98 -0
  15. data/lib/ing/actions/empty_directory.rb +155 -0
  16. data/lib/ing/actions/file_manipulation.rb +308 -0
  17. data/lib/ing/actions/inject_into_file.rb +109 -0
  18. data/lib/ing/commands/boot.rb +76 -0
  19. data/lib/ing/commands/generate.rb +64 -0
  20. data/lib/ing/commands/help.rb +87 -0
  21. data/lib/ing/commands/implicit.rb +59 -0
  22. data/lib/ing/commands/list.rb +108 -0
  23. data/lib/ing/dispatcher.rb +132 -0
  24. data/lib/ing/files.rb +190 -0
  25. data/lib/ing/lib_trollop.rb +782 -0
  26. data/lib/ing/shell.rb +390 -0
  27. data/lib/ing/trollop/parser.rb +17 -0
  28. data/lib/ing/util.rb +61 -0
  29. data/lib/ing/version.rb +3 -0
  30. data/lib/thor/actions/file_manipulation.rb +30 -0
  31. data/lib/thor/shell/basic.rb +44 -0
  32. data/test/acceptance/ing_run_tests.rb +164 -0
  33. data/test/actions/create_file_spec.rb +209 -0
  34. data/test/actions/create_link_spec.rb +90 -0
  35. data/test/actions/directory_spec.rb +167 -0
  36. data/test/actions/empty_directory_spec.rb +146 -0
  37. data/test/actions/file_manipulation_spec.rb +433 -0
  38. data/test/actions/inject_into_file_spec.rb +147 -0
  39. data/test/fixtures/application.rb +2 -0
  40. data/test/fixtures/app{1}/README +3 -0
  41. data/test/fixtures/bundle/execute.rb +6 -0
  42. data/test/fixtures/bundle/main.thor +1 -0
  43. data/test/fixtures/doc/%file_name%.rb.tt +1 -0
  44. data/test/fixtures/doc/COMMENTER +10 -0
  45. data/test/fixtures/doc/README +3 -0
  46. data/test/fixtures/doc/block_helper.rb +3 -0
  47. data/test/fixtures/doc/components/.empty_directory +0 -0
  48. data/test/fixtures/doc/config.rb +1 -0
  49. data/test/fixtures/doc/config.yaml.tt +1 -0
  50. data/test/fixtures/group.ing.rb +76 -0
  51. data/test/fixtures/invok.ing.rb +50 -0
  52. data/test/fixtures/namespace.ing.rb +52 -0
  53. data/test/fixtures/require.ing.rb +7 -0
  54. data/test/fixtures/task.ing.rb +36 -0
  55. data/test/fixtures/task.thor +10 -0
  56. data/test/spec_helper.rb +2 -0
  57. data/test/test_helper.rb +41 -0
  58. data/todo.yml +7 -0
  59. metadata +147 -0
data/lib/ing/shell.rb ADDED
@@ -0,0 +1,390 @@
1
+ require 'fileutils'
2
+ require 'tempfile'
3
+
4
+ module Ing
5
+ module Shell
6
+ class Basic
7
+ attr_accessor :base
8
+ attr_reader :padding
9
+
10
+ # Initialize base, mute and padding to nil.
11
+ #
12
+ def initialize #:nodoc:
13
+ @base, @mute, @padding = nil, false, 0
14
+ end
15
+
16
+ # Mute everything that's inside given block
17
+ #
18
+ def mute
19
+ @mute = true
20
+ yield
21
+ ensure
22
+ @mute = false
23
+ end
24
+
25
+ # Check if base is muted
26
+ #
27
+ def mute?
28
+ @mute
29
+ end
30
+
31
+ # Sets the output padding, not allowing less than zero values.
32
+ #
33
+ def padding=(value)
34
+ @padding = [0, value].max
35
+ end
36
+
37
+ # Asks something to the user and receives a response.
38
+ #
39
+ # If asked to limit the correct responses, you can pass in an
40
+ # array of acceptable answers. If one of those is not supplied,
41
+ # they will be shown a message stating that one of those answers
42
+ # must be given and re-asked the question.
43
+ #
44
+ # ==== Example
45
+ # ask("What is your name?")
46
+ #
47
+ # ask("What is your favorite Neopolitan flavor?", :limited_to => ["strawberry", "chocolate", "vanilla"])
48
+ #
49
+ def ask(statement, *args)
50
+ options = args.last.is_a?(Hash) ? args.pop : {}
51
+
52
+ options[:limited_to] ? ask_filtered(statement, options[:limited_to], *args) : ask_simply(statement, *args)
53
+ end
54
+
55
+ # Say (print) something to the user. If the sentence ends with a whitespace
56
+ # or tab character, a new line is not appended (print + flush). Otherwise
57
+ # are passed straight to puts (behavior got from Highline).
58
+ #
59
+ # ==== Example
60
+ # say("I know you knew that.")
61
+ #
62
+ def say(message="", color=nil, force_new_line=(message.to_s !~ /( |\t)$/))
63
+ message = message.to_s
64
+
65
+ message = set_color(message, *color) if color
66
+
67
+ spaces = " " * padding
68
+
69
+ if force_new_line
70
+ stdout.puts(spaces + message)
71
+ else
72
+ stdout.print(spaces + message)
73
+ end
74
+ stdout.flush
75
+ end
76
+
77
+ # Say a status with the given color and appends the message. Since this
78
+ # method is used frequently by actions, it allows nil or false to be given
79
+ # in log_status, avoiding the message from being shown. If a Symbol is
80
+ # given in log_status, it's used as the color.
81
+ #
82
+ def say_status(status, message, log_status=true)
83
+ return if quiet? || log_status == false
84
+ spaces = " " * (padding + 1)
85
+ color = log_status.is_a?(Symbol) ? log_status : :green
86
+
87
+ status = status.to_s.rjust(12)
88
+ status = set_color status, color, true if color
89
+
90
+ stdout.puts "#{status}#{spaces}#{message}"
91
+ stdout.flush
92
+ end
93
+
94
+ # Make a question the to user and returns true if the user replies "y" or
95
+ # "yes".
96
+ #
97
+ def yes?(statement, color=nil)
98
+ !!(ask(statement, color) =~ is?(:yes))
99
+ end
100
+
101
+ # Make a question the to user and returns true if the user replies "n" or
102
+ # "no".
103
+ #
104
+ def no?(statement, color=nil)
105
+ !yes?(statement, color)
106
+ end
107
+
108
+ # Prints values in columns
109
+ #
110
+ # ==== Parameters
111
+ # Array[String, String, ...]
112
+ #
113
+ def print_in_columns(array)
114
+ return if array.empty?
115
+ colwidth = (array.map{|el| el.to_s.size}.max || 0) + 2
116
+ array.each_with_index do |value, index|
117
+ # Don't output trailing spaces when printing the last column
118
+ if ((((index + 1) % (terminal_width / colwidth))).zero? && !index.zero?) || index + 1 == array.length
119
+ stdout.puts value
120
+ else
121
+ stdout.printf("%-#{colwidth}s", value)
122
+ end
123
+ end
124
+ end
125
+
126
+ # Prints a table.
127
+ #
128
+ # ==== Parameters
129
+ # Array[Array[String, String, ...]]
130
+ #
131
+ # ==== Options
132
+ # indent<Integer>:: Indent the first column by indent value.
133
+ # colwidth<Integer>:: Force the first column to colwidth spaces wide.
134
+ #
135
+ def print_table(array, options={})
136
+ return if array.empty?
137
+
138
+ formats, indent, colwidth = [], options[:indent].to_i, options[:colwidth]
139
+ options[:truncate] = terminal_width if options[:truncate] == true
140
+
141
+ formats << "%-#{colwidth + 2}s" if colwidth
142
+ start = colwidth ? 1 : 0
143
+
144
+ colcount = array.max{|a,b| a.size <=> b.size }.size
145
+
146
+ maximas = []
147
+
148
+ start.upto(colcount - 1) do |index|
149
+ maxima = array.map {|row| row[index] ? row[index].to_s.size : 0 }.max
150
+ maximas << maxima
151
+ if index == colcount - 1
152
+ # Don't output 2 trailing spaces when printing the last column
153
+ formats << "%-s"
154
+ else
155
+ formats << "%-#{maxima + 2}s"
156
+ end
157
+ end
158
+
159
+ formats[0] = formats[0].insert(0, " " * indent)
160
+ formats << "%s"
161
+
162
+ array.each do |row|
163
+ sentence = ""
164
+
165
+ row.each_with_index do |column, index|
166
+ maxima = maximas[index]
167
+
168
+ if column.is_a?(Numeric)
169
+ if index == row.size - 1
170
+ # Don't output 2 trailing spaces when printing the last column
171
+ f = "%#{maxima}s"
172
+ else
173
+ f = "%#{maxima}s "
174
+ end
175
+ else
176
+ f = formats[index]
177
+ end
178
+ sentence << f % column.to_s
179
+ end
180
+
181
+ sentence = truncate(sentence, options[:truncate]) if options[:truncate]
182
+ stdout.puts sentence
183
+ end
184
+ end
185
+
186
+ # Prints a long string, word-wrapping the text to the current width of the
187
+ # terminal display. Ideal for printing heredocs.
188
+ #
189
+ # ==== Parameters
190
+ # String
191
+ #
192
+ # ==== Options
193
+ # indent<Integer>:: Indent each line of the printed paragraph by indent value.
194
+ #
195
+ def print_wrapped(message, options={})
196
+ indent = options[:indent] || 0
197
+ width = terminal_width - indent
198
+ paras = message.split("\n\n")
199
+
200
+ paras.map! do |unwrapped|
201
+ unwrapped.strip.gsub(/\n/, " ").squeeze(" ").
202
+ gsub(/.{1,#{width}}(?:\s|\Z)/){($& + 5.chr).
203
+ gsub(/\n\005/,"\n").gsub(/\005/,"\n")}
204
+ end
205
+
206
+ paras.each do |para|
207
+ para.split("\n").each do |line|
208
+ stdout.puts line.insert(0, " " * indent)
209
+ end
210
+ stdout.puts unless para == paras.last
211
+ end
212
+ end
213
+
214
+ # Deals with file collision and returns true if the file should be
215
+ # overwritten and false otherwise. If a block is given, it uses the block
216
+ # response as the content for the diff.
217
+ #
218
+ # ==== Parameters
219
+ # destination<String>:: the destination file to solve conflicts
220
+ # block<Proc>:: an optional block that returns the value to be used in diff
221
+ #
222
+ def file_collision(destination)
223
+ return true if @always_force
224
+ options = block_given? ? "[Ynaqdh]" : "[Ynaqh]"
225
+
226
+ while true
227
+ answer = ask %[Overwrite #{destination}? (enter "h" for help) #{options}]
228
+
229
+ case answer
230
+ when is?(:yes), is?(:force), ""
231
+ return true
232
+ when is?(:no), is?(:skip)
233
+ return false
234
+ when is?(:always)
235
+ return @always_force = true
236
+ when is?(:quit)
237
+ say 'Aborting...'
238
+ raise SystemExit
239
+ when is?(:diff)
240
+ show_diff(destination, yield) if block_given?
241
+ say 'Retrying...'
242
+ else
243
+ say file_collision_help
244
+ end
245
+ end
246
+ end
247
+
248
+ # This code was copied from Rake, available under MIT-LICENSE
249
+ # Copyright (c) 2003, 2004 Jim Weirich
250
+ def terminal_width
251
+ if ENV['THOR_COLUMNS']
252
+ result = ENV['THOR_COLUMNS'].to_i
253
+ else
254
+ result = unix? ? dynamic_width : 80
255
+ end
256
+ (result < 10) ? 80 : result
257
+ rescue
258
+ 80
259
+ end
260
+
261
+ # Called if something goes wrong during the execution. This is used by Thor
262
+ # internally and should not be used inside your scripts. If something went
263
+ # wrong, you can always raise an exception. If you raise a Thor::Error, it
264
+ # will be rescued and wrapped in the method below.
265
+ #
266
+ def error(statement)
267
+ stderr.puts statement
268
+ end
269
+
270
+ # Apply color to the given string with optional bold. Disabled in the
271
+ # Thor::Shell::Basic class.
272
+ #
273
+ def set_color(string, *args) #:nodoc:
274
+ string
275
+ end
276
+
277
+ protected
278
+
279
+ def lookup_color(color)
280
+ return color unless color.is_a?(Symbol)
281
+ self.class.const_get(color.to_s.upcase)
282
+ end
283
+
284
+ def stdout
285
+ $stdout
286
+ end
287
+
288
+ def stdin
289
+ $stdin
290
+ end
291
+
292
+ def stderr
293
+ $stderr
294
+ end
295
+
296
+ def is?(value) #:nodoc:
297
+ value = value.to_s
298
+
299
+ if value.size == 1
300
+ /\A#{value}\z/i
301
+ else
302
+ /\A(#{value}|#{value[0,1]})\z/i
303
+ end
304
+ end
305
+
306
+ def file_collision_help #:nodoc:
307
+ <<HELP
308
+ Y - yes, overwrite
309
+ n - no, do not overwrite
310
+ a - all, overwrite this and all others
311
+ q - quit, abort
312
+ d - diff, show the differences between the old and the new
313
+ h - help, show this help
314
+ HELP
315
+ end
316
+
317
+ def show_diff(destination, content) #:nodoc:
318
+ diff_cmd = ENV['THOR_DIFF'] || ENV['RAILS_DIFF'] || 'diff -u'
319
+
320
+ Tempfile.open(File.basename(destination), File.dirname(destination)) do |temp|
321
+ temp.write content
322
+ temp.rewind
323
+ system %(#{diff_cmd} "#{destination}" "#{temp.path}")
324
+ end
325
+ end
326
+
327
+ def quiet? #:nodoc:
328
+ mute? || (base && base.options[:quiet])
329
+ end
330
+
331
+ # Calculate the dynamic width of the terminal
332
+ def dynamic_width
333
+ @dynamic_width ||= (dynamic_width_stty.nonzero? || dynamic_width_tput)
334
+ end
335
+
336
+ def dynamic_width_stty
337
+ %x{stty size 2>/dev/null}.split[1].to_i
338
+ end
339
+
340
+ def dynamic_width_tput
341
+ %x{tput cols 2>/dev/null}.to_i
342
+ end
343
+
344
+ def unix?
345
+ RUBY_PLATFORM =~ /(aix|darwin|linux|(net|free|open)bsd|cygwin|solaris|irix|hpux)/i
346
+ end
347
+
348
+ def truncate(string, width)
349
+ as_unicode do
350
+ chars = string.chars.to_a
351
+ if chars.length <= width
352
+ chars.join
353
+ else
354
+ ( chars[0, width-3].join ) + "..."
355
+ end
356
+ end
357
+ end
358
+
359
+ if "".respond_to?(:encode)
360
+ def as_unicode
361
+ yield
362
+ end
363
+ else
364
+ def as_unicode
365
+ old, $KCODE = $KCODE, "U"
366
+ yield
367
+ ensure
368
+ $KCODE = old
369
+ end
370
+ end
371
+
372
+ def ask_simply(statement, color=nil)
373
+ say("#{statement} ", color)
374
+ stdin.gets.strip
375
+ end
376
+
377
+ def ask_filtered(statement, answer_set, *args)
378
+ correct_answer = nil
379
+ until correct_answer
380
+ answer = ask_simply("#{statement} #{answer_set.inspect}", *args)
381
+ correct_answer = answer_set.include?(answer) ? answer : nil
382
+ answers = answer_set.map(&:inspect).join(", ")
383
+ say("Your response must be one of: [#{answers}]. Please try again.") unless correct_answer
384
+ end
385
+ correct_answer
386
+ end
387
+
388
+ end
389
+ end
390
+ end
@@ -0,0 +1,17 @@
1
+ # Extensions to Trollop::Parser
2
+
3
+ module Trollop
4
+
5
+ class Parser
6
+
7
+ def educate_banner stream=$stdout
8
+ width
9
+ @order.select {|what, _| what == :text}.each do |what, opt|
10
+ stream.puts wrap(opt)
11
+ end
12
+ end
13
+ alias :educate_text :educate_banner
14
+
15
+ end
16
+
17
+ end
data/lib/ing/util.rb ADDED
@@ -0,0 +1,61 @@
1
+ module Ing
2
+ module Util
3
+ extend self
4
+
5
+ def to_class_names(str)
6
+ str.split(':').map {|c| c.gsub(/(?:\A|_+)(\w)/) {$1.upcase} }
7
+ end
8
+ alias decode_class_names to_class_names
9
+
10
+ def encode_class_names(list)
11
+ list.map {|c| c.to_s.gsub(/([A-Z])/) {
12
+ ($`.empty? ? "" : "_") + $1.downcase
13
+ }
14
+ }.join(':')
15
+ end
16
+
17
+ def encode_class(klass)
18
+ encode_class_names(klass.to_s.split('::'))
19
+ end
20
+
21
+ def to_classes(str, base=::Object)
22
+ namespaced_const_get( to_class_names(str), base )
23
+ end
24
+
25
+ def namespaced_const_get(list, base=::Object)
26
+ list.inject(base) {|m, klass| m.const_get(klass, false)}
27
+ end
28
+
29
+ def option?(arg)
30
+ !!(/^-{1,2}/ =~ arg)
31
+ end
32
+
33
+ # not used
34
+ def split_method_args(args)
35
+ if option?(args.first)
36
+ [nil, args]
37
+ else
38
+ [args.first, args[1..-1]]
39
+ end
40
+ end
41
+
42
+ # Returns a string that has had any glob characters escaped.
43
+ # The glob characters are `* ? { } [ ]`.
44
+ #
45
+ # ==== Examples
46
+ #
47
+ # Util.escape_globs('[apps]') # => '\[apps\]'
48
+ #
49
+ # ==== Parameters
50
+ # String
51
+ #
52
+ # ==== Returns
53
+ # String
54
+ #
55
+ def escape_globs(path)
56
+ path.to_s.gsub(/[*?{}\[\]]/, '\\\\\\&')
57
+ end
58
+
59
+ end
60
+
61
+ end