bovem 0.8.1 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
data/Gemfile.lock CHANGED
@@ -1,8 +1,9 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- bovem (0.8.1)
4
+ bovem (1.0.0)
5
5
  lazier (~> 1.0)
6
+ open4 (~> 1.3.0)
6
7
 
7
8
  GEM
8
9
  remote: http://rubygems.org/
@@ -38,6 +39,7 @@ GEM
38
39
  tzinfo (~> 0.3.0)
39
40
  method_source (0.8)
40
41
  multi_json (1.3.6)
42
+ open4 (1.3.0)
41
43
  pry (0.9.10)
42
44
  coderay (~> 1.0.5)
43
45
  method_source (~> 0.8)
data/bovem.gemspec CHANGED
@@ -22,6 +22,7 @@ Gem::Specification.new do |gem|
22
22
  gem.require_paths = ["lib"]
23
23
 
24
24
  gem.add_dependency("lazier", "~> 1.0")
25
+ gem.add_dependency("open4", "~> 1.3.0")
25
26
 
26
27
  gem.add_development_dependency("rspec", "~> 2.11.0")
27
28
  gem.add_development_dependency("rake", "~> 0.9.0")
data/lib/bovem.rb CHANGED
@@ -6,6 +6,8 @@
6
6
 
7
7
  require "logger"
8
8
  require "lazier"
9
+ require "open4"
10
+ require "find"
9
11
 
10
12
  Lazier.load!(:object)
11
13
 
@@ -13,4 +15,5 @@ require "bovem/version" if !defined?(Bovem::Version)
13
15
  require "bovem/errors"
14
16
  require "bovem/configuration"
15
17
  require "bovem/logger"
16
- require "bovem/console"
18
+ require "bovem/console"
19
+ require "bovem/shell"
@@ -5,7 +5,7 @@
5
5
  #
6
6
 
7
7
  module Bovem
8
- # This class holds the configuration of the applicaton.
8
+ # This class holds the configuration of an applicaton.
9
9
  #
10
10
  # Extend this class and add valid properties via {property property} method.
11
11
  # Example:
@@ -31,7 +31,7 @@ module Bovem
31
31
  self.parse(file, overrides, logger)
32
32
  end
33
33
 
34
- # Creates a new configuration.
34
+ # Parses a configuration file.
35
35
  #
36
36
  # A configuration file is a plain Ruby file with a top-level {Configuration config} object.
37
37
  #
@@ -69,7 +69,7 @@ module Bovem
69
69
  self
70
70
  end
71
71
 
72
- # Defines a new property for the configuration
72
+ # Defines a new property for the configuration.
73
73
  #
74
74
  # @param name [Symbol] The name of the property.
75
75
  # @param options [Hash] A set of options for the property. Currently, only `:default` (which holds the default value) is supported.
data/lib/bovem/console.rb CHANGED
@@ -61,14 +61,15 @@ module Bovem
61
61
 
62
62
  # Parse a style and returns terminal codes.
63
63
  #
64
- # Supported styles and colors are those in {Bovem::TERM_COLORS} and {Bovem::TERM_EFFECTS}. You can also prefix colors with `bg_` (like `bg_red`) for background colors.
64
+ # Supported styles and colors are those in {Bovem::TERM\_COLORS} and {Bovem::TERM\_EFFECTS}. You can also prefix colors with `bg_` (like `bg_red`) for background colors.
65
65
  #
66
66
  # @param style [String] The style to parse.
67
67
  # @return [String] A string with ANSI color codes.
68
68
  def self.parse_style(style)
69
69
  rv = ""
70
+ style = style.ensure_string.strip.parameterize
70
71
 
71
- if style.present? then
72
+ if style.present? && style !~ /^[,-]$/ then
72
73
  style = style.ensure_string
73
74
  sym = style.to_sym
74
75
 
@@ -87,16 +88,25 @@ module Bovem
87
88
 
88
89
  # Replaces colors markers in a string.
89
90
  #
91
+ # You can specify markers by enclosing in `{mark=[style]}` and `{/mark}` tags. Separate styles with spaces, dashes or commas. Nesting markers is supported.
92
+ #
93
+ # Example:
94
+ #
95
+ # ```ruby
96
+ # Bovem::Console.new.replace_markers("{mark=bright bg_red}{mark=green}Hello world!{/mark}{/mark}")
97
+ # # => "\e[1m\e[41m\e[32mHello world!\e[1m\e[41m\e[0m"
98
+ # ```
99
+ #
90
100
  # @param message [String] The message to analyze.
91
101
  # @param plain [Boolean] If ignore (cleanify) color markers into the message.
92
102
  # @return [String] The replaced message.
93
103
  # @see #parse_style
94
104
  def self.replace_markers(message, plain = false)
95
105
  stack = []
106
+ mark_regexp = /((\{mark=([a-z\-_\s,]+)\})|(\{\/mark\}))/mi
107
+ split_regex = /\s*[\s,-]\s*/
96
108
 
97
- regexp = /((\{mark=([a-z\-_]+)\})|(\{\/mark\}))/mi
98
-
99
- message = message.gsub(regexp) do
109
+ message = message.gsub(mark_regexp) do
100
110
  tag = $1
101
111
  styles = $3
102
112
  replacement = ""
@@ -104,9 +114,9 @@ module Bovem
104
114
  if tag == "{/mark}" then # If it is a tag, pop from the latest opened.
105
115
  stack.pop
106
116
  styles = stack.last
107
- replacement = plain || stack.blank? ? "" : styles.split("-").collect { |s| self.parse_style(s) }.join("")
117
+ replacement = plain || stack.blank? ? "" : styles.split(split_regex).collect { |s| self.parse_style(s) }.join("")
108
118
  else
109
- replacement = plain ? "" : styles.split("-").collect { |s| self.parse_style(s) }.join("")
119
+ replacement = plain ? "" : styles.split(split_regex).collect { |s| self.parse_style(s) }.join("")
110
120
 
111
121
  if replacement.length > 0 then
112
122
  stack << "reset" if stack.blank?
@@ -165,7 +175,7 @@ module Bovem
165
175
  # @param width [Fixnum] The new width.
166
176
  # @param is_absolute [Boolean] If the new width should not be added to the current one but rather replace it.
167
177
  # @return [Fixnum] The new indentation width.
168
- def with_indentation(width, is_absolute = false)
178
+ def with_indentation(width = 3, is_absolute = false)
169
179
  old = self.indentation
170
180
  self.set_indentation(width, is_absolute)
171
181
  yield
@@ -194,7 +204,7 @@ module Bovem
194
204
  # Indents a message.
195
205
  #
196
206
  # @param message [String] The message to indent.
197
- # @param width [Fixnum] The indentation width. `true` means to use thecurrent indentation, a negative value of `-x` will indent of `x` absolute spaces. `nil` or `false` will skip indentation.
207
+ # @param width [Fixnum] The indentation width. `true` means to use the current indentation, a negative value of `-x` will indent of `x` absolute spaces. `nil` or `false` will skip indentation.
198
208
  # @param newline_separator [String] The character used for newlines.
199
209
  # @return [String] The indentend message.
200
210
  def indent(message, width = true, newline_separator = "\n")
@@ -212,10 +222,10 @@ module Bovem
212
222
 
213
223
  # Replaces colors markers in a string.
214
224
  #
215
- # Supported styles and colors are those in {Bovem::TERM_COLORS} and {Bovem::TERM_EFFECTS}. You can also prefix colors with `bg_` (like `bg_red`) for background colors.
225
+ # @see .replace_markers
216
226
  #
217
227
  # @param message [String] The message to analyze.
218
- # @param plain [Boolean] If ignore (cleanify )color markers into the message.
228
+ # @param plain [Boolean] If ignore (cleanify) color markers into the message.
219
229
  # @return [String] The replaced message.
220
230
  def replace_markers(message, plain = false)
221
231
  ::Bovem::Console.replace_markers(message, plain)
@@ -223,11 +233,13 @@ module Bovem
223
233
 
224
234
  # Formats a message.
225
235
  #
226
- # You can style text by using `{color}` and `{reset}` syntax.
236
+ # You can style text by using `{mark}` and `{/mark}` syntax.
237
+ #
238
+ # @see #replace_markers
227
239
  #
228
240
  # @param message [String] The message to format.
229
241
  # @param suffix [Object] If not `nil` or `false`, a suffix to add to the message. `true` means to add `\n`.
230
- # @param indent [Object] If not `nil` or `false`, the width to use for indentation. `true` means to use thecurrent indentation, a negative value of `-x` will indent of `x` absolute spaces.
242
+ # @param indent [Object] If not `nil` or `false`, the width to use for indentation. `true` means to use the current indentation, a negative value of `-x` will indent of `x` absolute spaces.
231
243
  # @param wrap [Object] If not `nil` or `false`, the maximum length of a line. `true` means the current line width.
232
244
  # @param plain [Boolean] If ignore color markers into the message.
233
245
  # @return [String] The formatted message.
@@ -268,7 +280,7 @@ module Bovem
268
280
  #
269
281
  # @param message [String] The message to format.
270
282
  # @param suffix [Object] If not `nil` or `false`, a suffix to add to the message. `true` means to add `\n`.
271
- # @param indent [Object] If not `nil` or `false`, the width to use for indentation. `true` means to use thecurrent indentation, a negative value of `-x` will indent of `x` absolute spaces.
283
+ # @param indent [Object] If not `nil` or `false`, the width to use for indentation. `true` means to use the current indentation, a negative value of `-x` will indent of `x` absolute spaces.
272
284
  # @param wrap [Object] If not `nil` or `false`, the maximum length of a line for wrapped text. `true` means the current line width.
273
285
  # @param plain [Boolean] If ignore color markers into the message.
274
286
  # @param print [Boolean] If `false`, the result will be returned instead of be printed.
@@ -296,7 +308,7 @@ module Bovem
296
308
  :warn => {:label => "WARN", :color => "bright yellow"},
297
309
  :fail => {:label => "FAIL", :color => "bright red"}
298
310
  }
299
- statuses.default = statuses[:pass]
311
+ statuses.default = statuses[:ok]
300
312
 
301
313
  rv = statuses[status]
302
314
 
@@ -328,11 +340,11 @@ module Bovem
328
340
  "{mark=%s}%s{mark=%s}%s{/mark}%s{/mark}" % [bracket_color.parameterize, brackets[0], base_color.parameterize, label, brackets[1]]
329
341
  end
330
342
 
331
- # Writes a message prepending a cyan type banner.
343
+ # Writes a message prepending a cyan banner.
332
344
  #
333
345
  # @param message [String] The message to format.
334
346
  # @param suffix [Object] If not `nil` or `false`, a suffix to add to the message. `true` means to add `\n`.
335
- # @param indent [Object] If not `nil` or `false`, the width to use for indentation. `true` means to use thecurrent indentation, a negative value of `-x` will indent of `x` absolute spaces.
347
+ # @param indent [Object] If not `nil` or `false`, the width to use for indentation. `true` means to use the current indentation, a negative value of `-x` will indent of `x` absolute spaces.
336
348
  # @param wrap [Object] If not `nil` or `false`, the maximum length of a line for wrapped text. `true` means the current line width.
337
349
  # @param plain [Boolean] If ignore color markers into the message.
338
350
  # @param indented_banner [Boolean] If also the banner should be indented.
@@ -346,11 +358,11 @@ module Bovem
346
358
  self.write(banner + " " + message, suffix, indented_banner ? indent : 0, wrap, plain, print)
347
359
  end
348
360
 
349
- # Writes a message prepending a green type banner.
361
+ # Writes a message prepending a green banner.
350
362
  #
351
363
  # @param message [String] The message to format.
352
364
  # @param suffix [Object] If not `nil` or `false`, a suffix to add to the message. `true` means to add `\n`.
353
- # @param indent [Object] If not `nil` or `false`, the width to use for indentation. `true` means to use thecurrent indentation, a negative value of `-x` will indent of `x` absolute spaces.
365
+ # @param indent [Object] If not `nil` or `false`, the width to use for indentation. `true` means to use the current indentation, a negative value of `-x` will indent of `x` absolute spaces.
354
366
  # @param wrap [Object] If not `nil` or `false`, the maximum length of a line for wrapped text. `true` means the current line width.
355
367
  # @param plain [Boolean] If ignore color markers into the message.
356
368
  # @param indented_banner [Boolean] If also the banner should be indented.
@@ -364,11 +376,11 @@ module Bovem
364
376
  self.write(banner + " " + message, suffix, indented_banner ? indent : 0, wrap, plain, print)
365
377
  end
366
378
 
367
- # Writes a message prepending a yellow type banner.
379
+ # Writes a message prepending a yellow banner.
368
380
  #
369
381
  # @param message [String] The message to format.
370
382
  # @param suffix [Object] If not `nil` or `false`, a suffix to add to the message. `true` means to add `\n`.
371
- # @param indent [Object] If not `nil` or `false`, the width to use for indentation. `true` means to use thecurrent indentation, a negative value of `-x` will indent of `x` absolute spaces.
383
+ # @param indent [Object] If not `nil` or `false`, the width to use for indentation. `true` means to use the current indentation, a negative value of `-x` will indent of `x` absolute spaces.
372
384
  # @param wrap [Object] If not `nil` or `false`, the maximum length of a line for wrapped text. `true` means the current line width.
373
385
  # @param plain [Boolean] If ignore color markers into the message.
374
386
  # @param indented_banner [Boolean] If also the banner should be indented.
@@ -382,11 +394,11 @@ module Bovem
382
394
  self.write(banner + " " + message, suffix, indented_banner ? indent : 0, wrap, plain, print)
383
395
  end
384
396
 
385
- # Writes a message prepending a red type banner.
397
+ # Writes a message prepending a red banner.
386
398
  #
387
399
  # @param message [String] The message to format.
388
400
  # @param suffix [Object] If not `nil` or `false`, a suffix to add to the message. `true` means to add `\n`.
389
- # @param indent [Object] If not `nil` or `false`, the width to use for indentation. `true` means to use thecurrent indentation, a negative value of `-x` will indent of `x` absolute spaces.
401
+ # @param indent [Object] If not `nil` or `false`, the width to use for indentation. `true` means to use the current indentation, a negative value of `-x` will indent of `x` absolute spaces.
390
402
  # @param wrap [Object] If not `nil` or `false`, the maximum length of a line for wrapped text. `true` means the current line width.
391
403
  # @param plain [Boolean] If ignore color markers into the message.
392
404
  # @param indented_banner [Boolean] If also the banner should be indented.
@@ -400,11 +412,11 @@ module Bovem
400
412
  self.write(banner + " " + message, suffix, indented_banner ? indent : 0, wrap, plain, print)
401
413
  end
402
414
 
403
- # Writes a message prepending a red type banner and then quits the application.
415
+ # Writes a message prepending a red banner and then quits the application.
404
416
  #
405
417
  # @param message [String] The message to format.
406
418
  # @param suffix [Object] If not `nil` or `false`, a suffix to add to the message. `true` means to add `\n`.
407
- # @param indent [Object] If not `nil` or `false`, the width to use for indentation. `true` means to use thecurrent indentation, a negative value of `-x` will indent of `x` absolute spaces.
419
+ # @param indent [Object] If not `nil` or `false`, the width to use for indentation. `true` means to use the current indentation, a negative value of `-x` will indent of `x` absolute spaces.
408
420
  # @param wrap [Object] If not `nil` or `false`, the maximum length of a line for wrapped text. `true` means the current line width.
409
421
  # @param plain [Boolean] If ignore color markers into the message.
410
422
  # @param indented_banner [Boolean] If also the banner should be indented.
@@ -415,14 +427,14 @@ module Bovem
415
427
  # @see #format
416
428
  def fatal(message, suffix = "\n", indent = true, wrap = false, plain = false, indented_banner = false, full_colored = false, return_code = -1, print = true)
417
429
  self.error(message, suffix, indent, wrap, plain, indented_banner, full_colored, print)
418
- Kernel.abort(return_code)
430
+ Kernel.exit(return_code.to_integer(-1))
419
431
  end
420
432
 
421
- # Writes a message prepending a magenta type banner.
433
+ # Writes a message prepending a magenta banner.
422
434
  #
423
435
  # @param message [String] The message to format.
424
436
  # @param suffix [Object] If not `nil` or `false`, a suffix to add to the message. `true` means to add `\n`.
425
- # @param indent [Object] If not `nil` or `false`, the width to use for indentation. `true` means to use thecurrent indentation, a negative value of `-x` will indent of `x` absolute spaces.
437
+ # @param indent [Object] If not `nil` or `false`, the width to use for indentation. `true` means to use the current indentation, a negative value of `-x` will indent of `x` absolute spaces.
426
438
  # @param wrap [Object] If not `nil` or `false`, the maximum length of a line for wrapped text. `true` means the current line width.
427
439
  # @param plain [Boolean] If ignore color markers into the message.
428
440
  # @param indented_banner [Boolean] If also the banner should be indented.
@@ -443,8 +455,6 @@ module Bovem
443
455
  # @param validator [Array|Regexp] An array of values or a Regexp to match the submitted value against.
444
456
  # @param echo [Boolean] If to show submitted text to the user.
445
457
  def read(prompt = true, default_value = nil, validator = nil, echo = true)
446
- # TODO: Echo and print prompt without newline
447
-
448
458
  # Write the prompt
449
459
  prompt = "Please insert a value" if prompt == true
450
460
  final_prompt = !prompt.nil? ? prompt.gsub(/:?\s*$/, "") + ": " : nil
@@ -495,11 +505,11 @@ module Bovem
495
505
  end
496
506
  end
497
507
 
498
- # Execute a block of code in a indentation region and then prints out and ending status message.
508
+ # Executes a block of code in a indentation region and then prints out and ending status message.
499
509
  #
500
510
  # @param message [String] The message to format.
501
511
  # @param suffix [Object] If not `nil` or `false`, a suffix to add to the message. `true` means to add `\n`.
502
- # @param indent [Object] If not `nil` or `false`, the width to use for indentation. `true` means to use thecurrent indentation, a negative value of `-x` will indent of `x` absolute spaces.
512
+ # @param indent [Object] If not `nil` or `false`, the width to use for indentation. `true` means to use the current indentation, a negative value of `-x` will indent of `x` absolute spaces.
503
513
  # @param wrap [Object] If not `nil` or `false`, the maximum length of a line for wrapped text. `true` means the current line width.
504
514
  # @param plain [Boolean] If ignore color markers into the message.
505
515
  # @param indented_banner [Boolean] If also the banner should be indented.
data/lib/bovem/logger.rb CHANGED
@@ -13,7 +13,8 @@ module Bovem
13
13
  # The file or device to log messages to.
14
14
  attr_reader :device
15
15
 
16
- # Creates a new logger
16
+ # Creates a new logger.
17
+ #
17
18
  # @see http://www.ruby-doc.org/stdlib-1.9.3/libdoc/logger/rdoc/Logger.html
18
19
  #
19
20
  # @param logdev [String|IO] The log device. This is a filename (String) or IO object (typically STDOUT, STDERR, or an open file).
@@ -24,7 +25,7 @@ module Bovem
24
25
  super(logdev, shift_age, shift_size)
25
26
  end
26
27
 
27
- # Creates a new logger
28
+ # Creates a new logger.
28
29
  #
29
30
  # @param file [String|IO] The log device. This is a filename (String) or IO object (typically STDOUT, STDERR, or an open file).
30
31
  # @param level [Fixnum] The minimum severity to log. See http://www.ruby-doc.org/stdlib-1.9.3/libdoc/logger/rdoc/Logger.html for valid levels.
@@ -0,0 +1,377 @@
1
+ # encoding: utf-8
2
+ #
3
+ # This file is part of the bovem gem. Copyright (C) 2012 and above Shogun <shogun_panda@me.com>.
4
+ # Licensed under the MIT license, which can be found at http://www.opensource.org/licenses/mit-license.php.
5
+ #
6
+
7
+ module Bovem
8
+ # A utility class for most common shell operation.
9
+ class Shell
10
+ # A {Console Console} instance.
11
+ attr_accessor :console
12
+
13
+ # Returns a unique instance for Shell.
14
+ #
15
+ # @return [Shell] A new instance.
16
+ def self.instance
17
+ @instance ||= ::Bovem::Shell.new
18
+ end
19
+
20
+ # Initializes a new Shell.
21
+ def initialize
22
+ @console = ::Bovem::Console.instance
23
+ end
24
+
25
+ # Runs a command into the shell.
26
+ #
27
+ # @param command [String] The string to run.
28
+ # @param message [String] A message to show before running.
29
+ # @param run [Boolean] If `false`, it will just print a message with the full command that will be run.
30
+ # @param show_exit [Boolean] If show the exit status.
31
+ # @param show_output [Boolean] If show command output.
32
+ # @param fatal [Boolean] If quit in case of fatal errors.
33
+ # @return [Hash] An hash with `status` and `output` keys.
34
+ def run(command, message = nil, run = true, show_exit = true, show_output = false, fatal = true)
35
+ rv = {:status => 0, :output => ""}
36
+ command = command.ensure_string
37
+
38
+ # Show the command
39
+ self.console.begin(message) if message.present?
40
+
41
+
42
+ if !run then # Print a message
43
+ self.console.warn("Will run command: {mark=bright}\"#{command}\"{/mark}...")
44
+ self.console.status(:ok) if show_exit
45
+ else # Run
46
+ output = ""
47
+
48
+ rv[:status] = ::Open4::open4(command + " 2>&1") { |pid, stdin, stdout, stderr|
49
+ stdout.each_line do |line|
50
+ output << line
51
+ Kernel.print line if show_output
52
+ end
53
+ }.exitstatus
54
+
55
+ rv[:output] = output
56
+ end
57
+
58
+ # Return
59
+ self.console.status(rv[:status] == 0 ? :ok : :fail) if show_exit
60
+ exit(rv[:status]) if fatal && rv[:status] != 0
61
+ rv
62
+ end
63
+
64
+ # Tests a path against a list of test.
65
+ #
66
+ # Valid tests are every method available in http://www.ruby-doc.org/core-1.9.3/FileTest.html (plus `read`, `write`, `execute`, `exec`, `dir`). Trailing question mark can be omitted.
67
+ #
68
+ #
69
+ # @param path [String] The path to test.
70
+ # @param tests [Array] The list of tests to perform.
71
+ def check(path, tests)
72
+ path = path.ensure_string
73
+
74
+ tests.ensure_array.all? {|test|
75
+ # Adjust test name
76
+ test = test.ensure_string.strip
77
+
78
+ test = case test
79
+ when "read" then "readable"
80
+ when "write" then "writable"
81
+ when "execute", "exec" then "executable"
82
+ when "dir" then "directory"
83
+ else test
84
+ end
85
+
86
+ # Execute test
87
+ test += "?" if test !~ /\?$/
88
+ FileTest.respond_to?(test) ? FileTest.send(test, path) : nil
89
+ }
90
+ end
91
+
92
+ # Deletes a list of files.
93
+ #
94
+ # @param files [Array] The list of files to remove
95
+ # @param run [Boolean] If `false`, it will just print a list of message that would be deleted.
96
+ # @param show_errors [Boolean] If show errors.
97
+ # @param fatal [Boolean] If quit in case of fatal errors.
98
+ # @return [Boolean] `true` if operation succeeded, `false` otherwise.
99
+ def delete(files, run = true, show_errors = false, fatal = true)
100
+ rv = true
101
+ files = files.ensure_array.compact.collect {|f| File.expand_path(f.ensure_string) }
102
+
103
+ if !run then
104
+ self.console.warn("Will remove file(s):")
105
+ self.console.with_indentation(11) do
106
+ files.each do |file| self.console.write(file) end
107
+ end
108
+ else
109
+ rv = catch(:rv) do
110
+ begin
111
+ FileUtils.rm_r(files, {:noop => false, :verbose => false, :secure => true})
112
+ throw(:rv, true)
113
+ rescue Errno::EACCES => e
114
+ self.console.send(fatal ? :fatal : :error, "Cannot remove following non writable file: {mark=bright}#{e.message.gsub(/.+ - (.+)/, "\\1")}{/mark}")
115
+ rescue Errno::ENOENT => e
116
+ self.console.send(fatal ? :fatal : :error, "Cannot remove following non existent file: {mark=bright}#{e.message.gsub(/.+ - (.+)/, "\\1")}{/mark}")
117
+ rescue Exception => e
118
+ if show_errors then
119
+ self.console.error("Cannot remove following file(s):")
120
+ self.console.with_indentation(11) do
121
+ files.each do |file| self.console.write(file) end
122
+ end
123
+ self.console.write("due to this error: [#{e.class.to_s}] #{e}.", "\n", 5)
124
+ Kernel.exit(-1) if fatal
125
+ end
126
+ end
127
+
128
+ false
129
+ end
130
+ end
131
+
132
+ rv
133
+ end
134
+
135
+ # Copies or moves a set of files or directory to another location.
136
+ #
137
+ # @param src [String|Array] The entries to copy or move. If is an Array, `dst` is assumed to be a directory.
138
+ # @param dst [String] The destination. **Any existing entries will be overwritten.** Any required directory will be created.
139
+ # @param operation [Symbol] The operation to perform. Valid values are `:copy` or `:move`.
140
+ # @param run [Boolean] If `false`, it will just print a list of message that would be copied or moved.
141
+ # @param show_errors [Boolean] If show errors.
142
+ # @param fatal [Boolean] If quit in case of fatal errors.
143
+ # @return [Boolean] `true` if operation succeeded, `false` otherwise.
144
+ def copy_or_move(src, dst, operation, run = true, show_errors = false, fatal = true)
145
+ rv = true
146
+ operation = :copy if operation != :move
147
+ single = !src.is_a?(Array)
148
+
149
+ if single then
150
+ src = File.expand_path(src)
151
+ else
152
+ src = src.collect {|s| File.expand_path(s) }
153
+ end
154
+
155
+ dst = File.expand_path(dst.ensure_string)
156
+
157
+ if !run then
158
+ if single then
159
+ self.console.warn("Will #{operation} a file:")
160
+ self.console.write("From: {mark=bright}#{File.expand_path(src.ensure_string)}{/mark}", "\n", 11)
161
+ self.console.write("To: {mark=bright}#{dst}{/mark}", "\n", 11)
162
+ else
163
+ self.console.warn("Will #{operation} following entries:")
164
+ self.console.with_indentation(11) do
165
+ src.each do |s| self.console.write(s) end
166
+ end
167
+ self.console.write("to directory: {mark=bright}#{dst}{/mark}", "\n", 5)
168
+ end
169
+ else
170
+ rv = catch(:rv) do
171
+ dst_dir = single ? File.dirname(dst) : dst
172
+ has_dir = self.check(dst_dir, :dir)
173
+
174
+ # Create directory
175
+ has_dir = self.create_directories(dst_dir, 0755, true, show_errors, fatal) if !has_dir
176
+ throw(:rv, false) if !has_dir
177
+
178
+ if single && self.check(dst, :dir) then
179
+ @console.send(fatal ? :fatal : :error, "Cannot #{operation} file {mark=bright}#{src}{/mark} to {mark=bright}#{dst}{/mark} because it is currently a directory.")
180
+ throw(:rv, false)
181
+ end
182
+
183
+ # Check that every file is existing
184
+ src.ensure_array.each do |s|
185
+ if !self.check(s, :exists) then
186
+ @console.send(fatal ? :fatal : :error, "Cannot #{operation} non existent file {mark=bright}#{s}{/mark}.")
187
+ throw(:rv, false)
188
+ end
189
+ end
190
+
191
+ # Do operation
192
+ begin
193
+ FileUtils.send(operation == :move ? :move : :cp_r, src, dst, {:noop => false, :verbose => false})
194
+ rescue Errno::EACCES => e
195
+ if single then
196
+ @console.send(fatal ? :fatal : :error, "Cannot #{operation} file {mark=bright}#{src}{/mark} to non writable directory {mark=bright}#{dst_dir}{/mark}.")
197
+ else
198
+ self.console.error("Cannot #{operation} following file(s) to non writable directory {mark=bright}#{dst}{/mark}:")
199
+ self.console.with_indentation(11) do
200
+ src.each do |s| self.console.write(s) end
201
+ end
202
+ Kernel.exit(-1) if fatal
203
+ end
204
+
205
+ throw(:rv, false)
206
+ rescue Exception => e
207
+ if single then
208
+ @console.send(fatal ? :fatal : :error, "Cannot #{operation} file {mark=bright}#{src}{/mark} to directory {mark=bright}#{dst_dir}{/mark} due to this error: [#{e.class.to_s}] #{e}.", "\n", 5)
209
+ else
210
+ self.console.error("Cannot #{operation} following entries to {mark=bright}#{dst}{/mark}:")
211
+ self.console.with_indentation(11) do
212
+ src.each do |s| self.console.write(s) end
213
+ end
214
+ self.console.write("due to this error: [#{e.class.to_s}] #{e}.", "\n", 5)
215
+ Kernel.exit(-1) if fatal
216
+ end
217
+
218
+ throw(:rv, false)
219
+ end
220
+
221
+ true
222
+ end
223
+ end
224
+
225
+ rv
226
+ end
227
+
228
+ # Copies a set of files or directory to another location.
229
+ #
230
+ # @param src [String|Array] The entries to copy. If is an Array, `dst` is assumed to be a directory.
231
+ # @param dst [String] The destination. **Any existing entries will be overwritten.** Any required directory will be created.
232
+ # @param run [Boolean] If `false`, it will just print a list of message that would be copied or moved.
233
+ # @param show_errors [Boolean] If show errors.
234
+ # @param fatal [Boolean] If quit in case of fatal errors.
235
+ # @return [Boolean] `true` if operation succeeded, `false` otherwise.
236
+ def copy(src, dst, run = true, show_errors = false, fatal = true)
237
+ self.copy_or_move(src, dst, :copy, run, show_errors, fatal)
238
+ end
239
+
240
+ # Moves a set of files or directory to another location.
241
+ #
242
+ # @param src [String|Array] The entries to move. If is an Array, `dst` is assumed to be a directory.
243
+ # @param dst [String] The destination. **Any existing entries will be overwritten.** Any required directory will be created.
244
+ # @param run [Boolean] If `false`, it will just print a list of message that would be deleted.
245
+ # @param show_errors [Boolean] If show errors.
246
+ # @param fatal [Boolean] If quit in case of fatal errors.
247
+ # @return [Boolean] `true` if operation succeeded, `false` otherwise.
248
+ def move(src, dst, run = true, show_errors = false, fatal = true)
249
+ self.copy_or_move(src, dst, :move, run, show_errors, fatal)
250
+ end
251
+
252
+ # Executes a block of code in another directory.
253
+ #
254
+ # @param directory [String] The new working directory.
255
+ # @param restore [Boolean] If to restore the original working directory.
256
+ # @param show_messages [Boolean] Show informative messages about working directory changes.
257
+ # @return [Boolean] `true` if the directory was valid and the code executed, `false` otherwise.
258
+ def within_directory(directory, restore = true, show_messages = false)
259
+ rv = false
260
+ original = Dir.pwd
261
+ directory = File.expand_path(directory.ensure_string)
262
+
263
+ if self.check(directory, [:directory, :executable]) then
264
+ begin
265
+ self.console.info("Moving into directory {mark=bright}#{directory}{/mark}")
266
+ Dir.chdir(directory)
267
+ rv = true
268
+ rescue Exception => e
269
+ end
270
+ end
271
+
272
+ yield if rv && block_given?
273
+
274
+ if rv && original then
275
+ begin
276
+ self.console.info("Moving back into directory {mark=bright}#{original}{/mark}")
277
+ Dir.chdir(original) if restore
278
+ rescue Exception => e
279
+ rv = false
280
+ end
281
+ end
282
+
283
+ rv
284
+ end
285
+
286
+ # Creates a list of directories, included missing parent directories.
287
+ #
288
+ # @param directories [Array] The list of directories to create.
289
+ # @param mode [Fixnum] Initial permissions for the new directories.
290
+ # @param run [Boolean] If `false`, it will just print a list of directories that would be created.
291
+ # @param show_errors [Boolean] If show errors.
292
+ # @param fatal [Boolean] If quit in case of fatal errors.
293
+ # @return [Boolean] `true` if operation succeeded, `false` otherwise.
294
+ def create_directories(directories, mode = 0755, run = true, show_errors = false, fatal = true)
295
+ rv = true
296
+
297
+ # Adjust directory
298
+ directories = directories.ensure_array.compact {|d| File.expand_path(d.ensure_string) }
299
+
300
+ if !run then # Just print
301
+ self.console.warn("Will create directories:")
302
+ self.console.with_indentation(11) do
303
+ directories.each do |directory| self.console.write(directory) end
304
+ end
305
+ else
306
+ directories.each do |directory|
307
+ rv = catch(:rv) do
308
+ # Perform tests
309
+ if self.check(directory, :directory) then
310
+ self.console.send(fatal ? :fatal : :error, "The directory {mark=bright}#{directory}{/mark} already exists.")
311
+ elsif self.check(directory, :exist) then
312
+ self.console.send(fatal ? :fatal : :error, "Path {mark=bright}#{directory}{/mark} is currently a file.")
313
+ else
314
+ begin # Create directory
315
+ FileUtils.mkdir_p(directory, {:mode => mode, :noop => false, :verbose => false})
316
+ throw(:rv, true)
317
+ rescue Errno::EACCES => e
318
+ self.console.send(fatal ? :fatal : :error, "Cannot create following directory due to permission denied: {mark=bright}#{e.message.gsub(/.+ - (.+)/, "\\1")}{/mark}.")
319
+ rescue Exception => e
320
+ if show_errors then
321
+ self.console.error("Cannot create following directories:")
322
+ self.console.with_indentation(11) do
323
+ directories.each do |directory| self.console.write(directory) end
324
+ end
325
+ self.console.write("due to this error: [#{e.class.to_s}] #{e}.", "\n", 5)
326
+ Kernel.exit(-1) if fatal
327
+ end
328
+ end
329
+ end
330
+
331
+ false
332
+ end
333
+
334
+ break if !rv
335
+ end
336
+ end
337
+
338
+ rv
339
+ end
340
+
341
+ # Find a list of files in directories matching given regexps or patterns.
342
+ #
343
+ # @param directories [String] A list of directories where to search files.
344
+ # @param patterns [Array] A list of regexps or patterns to match files. If empty, every file is returned.
345
+ # @param by_extension [Boolean] If to only search in extensions.
346
+ # @param case_sensitive [Boolean] If the search is case sensitive. Only meaningful for string patterns.
347
+ def find(directories, patterns = [], by_extension = false, case_sensitive = false)
348
+ rv = []
349
+
350
+ # Adjust directory
351
+ directories = directories.ensure_array.compact {|d| File.expand_path(d.ensure_string) }
352
+
353
+ # Adjust patterns
354
+ patterns = patterns.ensure_array.compact.collect {|p| p.is_a?(::Regexp) ? p : Regexp.new(Regexp.quote(p.ensure_string)) }
355
+ patterns = patterns.collect {|p| /(#{p.source})$/ } if by_extension
356
+ patterns = patterns.collect {|p| /#{p.source}/i } if !case_sensitive
357
+
358
+ directories.each do |directory|
359
+ if self.check(directory, [:directory, :readable, :executable]) then
360
+ Find.find(directory) do |entry|
361
+ found = patterns.blank? ? true : catch(:found) do
362
+ patterns.each do |pattern|
363
+ throw(:found, true) if pattern.match(entry) && (!by_extension || !File.directory?(entry))
364
+ end
365
+
366
+ false
367
+ end
368
+
369
+ rv << entry if found
370
+ end
371
+ end
372
+ end
373
+
374
+ rv
375
+ end
376
+ end
377
+ end