bovem 0.8.1 → 1.0.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.
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