bovem 1.2.6 → 2.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/.travis.yml +1 -1
- data/Gemfile +1 -1
- data/README.md +5 -2
- data/bovem.gemspec +11 -9
- data/doc/Bovem.html +7 -25
- data/doc/Bovem/Configuration.html +30 -43
- data/doc/Bovem/Console.html +225 -4360
- data/doc/Bovem/ConsoleMethods.html +125 -0
- data/doc/Bovem/ConsoleMethods/Interactions.html +575 -0
- data/doc/Bovem/ConsoleMethods/Interactions/ClassMethods.html +231 -0
- data/doc/Bovem/ConsoleMethods/Logging.html +2218 -0
- data/doc/Bovem/ConsoleMethods/Logging/ClassMethods.html +212 -0
- data/doc/Bovem/ConsoleMethods/Output.html +1213 -0
- data/doc/Bovem/ConsoleMethods/StyleHandling.html +274 -0
- data/doc/Bovem/ConsoleMethods/StyleHandling/ClassMethods.html +653 -0
- data/doc/Bovem/Errors.html +3 -3
- data/doc/Bovem/Errors/InvalidConfiguration.html +3 -3
- data/doc/Bovem/Errors/InvalidLogger.html +3 -3
- data/doc/Bovem/Logger.html +112 -12
- data/doc/Bovem/Shell.html +58 -1888
- data/doc/Bovem/ShellMethods.html +125 -0
- data/doc/Bovem/ShellMethods/Directories.html +484 -0
- data/doc/Bovem/ShellMethods/Execute.html +565 -0
- data/doc/Bovem/ShellMethods/General.html +450 -0
- data/doc/Bovem/ShellMethods/Read.html +451 -0
- data/doc/Bovem/ShellMethods/Write.html +676 -0
- data/doc/Bovem/Version.html +6 -6
- data/doc/_index.html +145 -4
- data/doc/class_list.html +1 -1
- data/doc/file.README.html +10 -7
- data/doc/frames.html +1 -1
- data/doc/index.html +10 -7
- data/doc/method_list.html +119 -79
- data/doc/top-level-namespace.html +3 -3
- data/lib/bovem.rb +1 -1
- data/lib/bovem/configuration.rb +24 -13
- data/lib/bovem/console.rb +566 -497
- data/lib/bovem/errors.rb +1 -1
- data/lib/bovem/logger.rb +4 -4
- data/lib/bovem/shell.rb +482 -305
- data/lib/bovem/version.rb +4 -4
- data/locales/en.yml +43 -0
- data/locales/it.yml +43 -0
- data/spec/bovem/configuration_spec.rb +3 -3
- data/spec/bovem/console_spec.rb +17 -10
- data/spec/bovem/logger_spec.rb +1 -1
- data/spec/bovem/shell_spec.rb +9 -5
- data/spec/coverage_helper.rb +1 -1
- data/spec/spec_helper.rb +1 -1
- metadata +32 -22
data/lib/bovem/errors.rb
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
# encoding: utf-8
|
2
2
|
#
|
3
|
-
# This file is part of the bovem gem. Copyright (C)
|
3
|
+
# This file is part of the bovem gem. Copyright (C) 2013 and above Shogun <shogun_panda@me.com>.
|
4
4
|
# Licensed under the MIT license, which can be found at http://www.opensource.org/licenses/mit-license.php.
|
5
5
|
#
|
6
6
|
|
data/lib/bovem/logger.rb
CHANGED
@@ -1,16 +1,16 @@
|
|
1
1
|
# encoding: utf-8
|
2
2
|
#
|
3
|
-
# This file is part of the bovem gem. Copyright (C)
|
3
|
+
# This file is part of the bovem gem. Copyright (C) 2013 and above Shogun <shogun_panda@me.com>.
|
4
4
|
# Licensed under the MIT license, which can be found at http://www.opensource.org/licenses/mit-license.php.
|
5
5
|
#
|
6
6
|
|
7
7
|
module Bovem
|
8
8
|
# A custom logger.
|
9
|
+
#
|
10
|
+
# @attr [Time] start_time The start time of first line. This allows to show a `T+0.1234` information into the log.
|
11
|
+
# @attr [IO|String] device The file or device to log messages to.
|
9
12
|
class Logger < ::Logger
|
10
|
-
# The start time of first line. This allows to show a `T+0.1234` information into the log.
|
11
13
|
mattr_accessor :start_time
|
12
|
-
|
13
|
-
# The file or device to log messages to.
|
14
14
|
attr_reader :device
|
15
15
|
|
16
16
|
# Creates a new logger.
|
data/lib/bovem/shell.rb
CHANGED
@@ -1,175 +1,249 @@
|
|
1
1
|
# encoding: utf-8
|
2
2
|
#
|
3
|
-
# This file is part of the bovem gem. Copyright (C)
|
3
|
+
# This file is part of the bovem gem. Copyright (C) 2013 and above Shogun <shogun_panda@me.com>.
|
4
4
|
# Licensed under the MIT license, which can be found at http://www.opensource.org/licenses/mit-license.php.
|
5
5
|
#
|
6
6
|
|
7
7
|
module Bovem
|
8
|
-
#
|
9
|
-
|
10
|
-
#
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
@
|
18
|
-
|
8
|
+
# Methods of the {Shell Shell} class.
|
9
|
+
module ShellMethods
|
10
|
+
# General methods.
|
11
|
+
module General
|
12
|
+
# Handles general failure of a file/directory method.
|
13
|
+
#
|
14
|
+
# @param e [Exception] The occurred exception.
|
15
|
+
# @param access_error [String|Symbol] The message to show in case of access errors.
|
16
|
+
# @param not_found_error [String|Symbol] The message to show in case of a not found entry.
|
17
|
+
# @param general_error [String|Symbol] The message to show in case of other errors.
|
18
|
+
# @param entries [Array] The list of entries which failed.
|
19
|
+
# @param fatal [Boolean] If quit in case of fatal errors.
|
20
|
+
# @param show_errors [Boolean] Whether to show errors.
|
21
|
+
def handle_failure(e, access_error, not_found_error, general_error, entries, fatal, show_errors)
|
22
|
+
error_type = fatal ? :fatal : :error
|
23
|
+
message = e.message.gsub(/.+ - (.+)/, "\\1")
|
24
|
+
locale = self.i18n.shell
|
25
|
+
|
26
|
+
case e.class.to_s
|
27
|
+
when "Errno::EACCES" then @console.send(error_type, locale.send(access_error, message))
|
28
|
+
when "Errno::ENOENT" then @console.send(error_type, locale.send(not_found_error, message))
|
29
|
+
else show_general_failure(e, general_error, entries, fatal) if show_errors
|
30
|
+
end
|
31
|
+
end
|
19
32
|
|
20
|
-
|
21
|
-
|
22
|
-
@
|
33
|
+
# Shows errors when a directory creation failed.
|
34
|
+
#
|
35
|
+
# @param e [Exception] The occurred exception.
|
36
|
+
# @param entries [Array] The list of entries which failed.
|
37
|
+
# @param fatal [Boolean] If quit in case of fatal errors.
|
38
|
+
def show_general_failure(e, general_error, entries, fatal)
|
39
|
+
locale = self.i18n.shell
|
40
|
+
|
41
|
+
@console.error(locale.send(general_error))
|
42
|
+
@console.with_indentation(11) do
|
43
|
+
entries.each do |entry| @console.write(entry) end
|
44
|
+
end
|
45
|
+
@console.write(locale.error(e.class.to_s, e), "\n", 5)
|
46
|
+
Kernel.exit(-1) if fatal
|
47
|
+
end
|
23
48
|
end
|
24
49
|
|
25
|
-
#
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
else # Run
|
47
|
-
output = ""
|
48
|
-
|
49
|
-
self.console.info("Running command: {mark=bright}\"#{command}\"{/mark}...") if show_command
|
50
|
-
rv[:status] = ::Open4::popen4(command + " 2>&1") { |pid, stdin, stdout, stderr|
|
51
|
-
stdout.each_line do |line|
|
52
|
-
output << line
|
53
|
-
Kernel.print line if show_output
|
50
|
+
# Methods to find or check entries.
|
51
|
+
module Read
|
52
|
+
# Tests a path against a list of test.
|
53
|
+
#
|
54
|
+
# 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.
|
55
|
+
#
|
56
|
+
# @param path [String] The path to test.
|
57
|
+
# @param tests [Array] The list of tests to perform.
|
58
|
+
def check(path, tests)
|
59
|
+
path = path.ensure_string
|
60
|
+
|
61
|
+
tests.ensure_array.all? {|test|
|
62
|
+
# Adjust test name
|
63
|
+
test = test.ensure_string.strip
|
64
|
+
|
65
|
+
test = case test
|
66
|
+
when "read" then "readable"
|
67
|
+
when "write" then "writable"
|
68
|
+
when "execute", "exec" then "executable"
|
69
|
+
when "dir" then "directory"
|
70
|
+
else test
|
54
71
|
end
|
55
|
-
}.exitstatus
|
56
72
|
|
57
|
-
|
73
|
+
# Execute test
|
74
|
+
test += "?" if test !~ /\?$/
|
75
|
+
FileTest.respond_to?(test) ? FileTest.send(test, path) : nil
|
76
|
+
}
|
58
77
|
end
|
59
78
|
|
60
|
-
#
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
79
|
+
# Find a list of files in directories matching given regexps or patterns.
|
80
|
+
#
|
81
|
+
# You can also pass a block to perform matching. The block will receive a single argument and the path will be considered matched if the blocks not evaluates to `nil` or `false`.
|
82
|
+
#
|
83
|
+
# Inside the block, you can call `Find.prune` to stop searching in the current directory.
|
84
|
+
#
|
85
|
+
# @param directories [String] A list of directories where to search files.
|
86
|
+
# @param patterns [Array] A list of regexps or patterns to match files. If empty, every file is returned. Ignored if a block is provided.
|
87
|
+
# @param by_extension [Boolean] If to only search in extensions. Ignored if a block is provided.
|
88
|
+
# @param case_sensitive [Boolean] If the search is case sensitive. Only meaningful for string patterns.
|
89
|
+
# @param block [Proc] An optional block to perform matching instead of pattern matching.
|
90
|
+
def find(directories, patterns = [], by_extension = false, case_sensitive = false, &block)
|
91
|
+
rv = []
|
92
|
+
|
93
|
+
directories = directories.ensure_array.compact {|d| File.expand_path(d.ensure_string) }
|
94
|
+
patterns = normalize_patterns(patterns, by_extension, case_sensitive)
|
65
95
|
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
path = path.ensure_string
|
75
|
-
|
76
|
-
tests.ensure_array.all? {|test|
|
77
|
-
# Adjust test name
|
78
|
-
test = test.ensure_string.strip
|
79
|
-
|
80
|
-
test = case test
|
81
|
-
when "read" then "readable"
|
82
|
-
when "write" then "writable"
|
83
|
-
when "execute", "exec" then "executable"
|
84
|
-
when "dir" then "directory"
|
85
|
-
else test
|
96
|
+
directories.each do |directory|
|
97
|
+
if self.check(directory, [:directory, :readable, :executable]) then
|
98
|
+
Find.find(directory) do |entry|
|
99
|
+
found = patterns.blank? ? true : match_pattern(entry, patterns, by_extension, &block)
|
100
|
+
|
101
|
+
rv << entry if found
|
102
|
+
end
|
103
|
+
end
|
86
104
|
end
|
87
105
|
|
88
|
-
|
89
|
-
|
90
|
-
FileTest.respond_to?(test) ? FileTest.send(test, path) : nil
|
91
|
-
}
|
92
|
-
end
|
106
|
+
rv
|
107
|
+
end
|
93
108
|
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
self.console.with_indentation(11) do
|
108
|
-
files.each do |file| self.console.write(file) end
|
109
|
+
private
|
110
|
+
# Normalizes a set of patterns to find.
|
111
|
+
#
|
112
|
+
# @param patterns [Array] A list of regexps or patterns to match files. If empty, every file is returned. Ignored if a block is provided.
|
113
|
+
# @param by_extension [Boolean] If to only search in extensions. Ignored if a block is provided.
|
114
|
+
# @param case_sensitive [Boolean] If the search is case sensitive. Only meaningful for string patterns.
|
115
|
+
# @return [Array] The normalized patterns.
|
116
|
+
def normalize_patterns(patterns, by_extension, case_sensitive)
|
117
|
+
# Adjust patterns
|
118
|
+
patterns = patterns.ensure_array.compact.collect {|p| p.is_a?(::Regexp) ? p : Regexp.new(Regexp.quote(p.ensure_string)) }
|
119
|
+
patterns = patterns.collect {|p| /(#{p.source})$/ } if by_extension
|
120
|
+
patterns = patterns.collect {|p| /#{p.source}/i } if !case_sensitive
|
121
|
+
patterns
|
109
122
|
end
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
if
|
121
|
-
|
122
|
-
|
123
|
-
|
123
|
+
|
124
|
+
# Matches an entry against a set of patterns or a procedure.
|
125
|
+
#
|
126
|
+
# @param entry [String] The entry to search.
|
127
|
+
# @param patterns [Array] A list of regexps or patterns to match files. If empty, every file is returned. Ignored if a block is provided.
|
128
|
+
# @param by_extension [Boolean] If to only search in extensions. Ignored if a block is provided.
|
129
|
+
# @param block [Proc|nil] An optional block to run instead of pattern matching.
|
130
|
+
# @return [Boolean] `true` if entry matched, `false` otherwise.
|
131
|
+
def match_pattern(entry, patterns, by_extension, &block)
|
132
|
+
catch(:found) do
|
133
|
+
if block then
|
134
|
+
throw(:found, true) if block.call(entry)
|
135
|
+
else
|
136
|
+
patterns.each do |pattern|
|
137
|
+
throw(:found, true) if pattern.match(entry) && (!by_extension || !File.directory?(entry))
|
124
138
|
end
|
125
|
-
self.console.write("due to this error: [#{e.class.to_s}] #{e}.", "\n", 5)
|
126
|
-
Kernel.exit(-1) if fatal
|
127
139
|
end
|
128
|
-
end
|
129
140
|
|
130
|
-
|
141
|
+
false
|
142
|
+
end
|
131
143
|
end
|
132
|
-
end
|
133
|
-
|
134
|
-
rv
|
135
144
|
end
|
136
145
|
|
137
|
-
#
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
single = !src.is_a?(Array)
|
150
|
-
|
151
|
-
if single then
|
152
|
-
src = File.expand_path(src)
|
153
|
-
else
|
154
|
-
src = src.collect {|s| File.expand_path(s) }
|
146
|
+
# Methods to copy or move entries.
|
147
|
+
module Write
|
148
|
+
# Copies a set of files or directory to another location.
|
149
|
+
#
|
150
|
+
# @param src [String|Array] The entries to copy. If is an Array, `dst` is assumed to be a directory.
|
151
|
+
# @param dst [String] The destination. **Any existing entries will be overwritten.** Any required directory will be created.
|
152
|
+
# @param run [Boolean] If `false`, it will just print a list of message that would be copied or moved.
|
153
|
+
# @param show_errors [Boolean] If show errors.
|
154
|
+
# @param fatal [Boolean] If quit in case of fatal errors.
|
155
|
+
# @return [Boolean] `true` if operation succeeded, `false` otherwise.
|
156
|
+
def copy(src, dst, run = true, show_errors = false, fatal = true)
|
157
|
+
self.copy_or_move(src, dst, :copy, run, show_errors, fatal)
|
155
158
|
end
|
156
159
|
|
157
|
-
|
160
|
+
# Moves a set of files or directory to another location.
|
161
|
+
#
|
162
|
+
# @param src [String|Array] The entries to move. If is an Array, `dst` is assumed to be a directory.
|
163
|
+
# @param dst [String] The destination. **Any existing entries will be overwritten.** Any required directory will be created.
|
164
|
+
# @param run [Boolean] If `false`, it will just print a list of message that would be deleted.
|
165
|
+
# @param show_errors [Boolean] If show errors.
|
166
|
+
# @param fatal [Boolean] If quit in case of fatal errors.
|
167
|
+
# @return [Boolean] `true` if operation succeeded, `false` otherwise.
|
168
|
+
def move(src, dst, run = true, show_errors = false, fatal = true)
|
169
|
+
self.copy_or_move(src, dst, :move, run, show_errors, fatal)
|
170
|
+
end
|
158
171
|
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
172
|
+
# Copies or moves a set of files or directory to another location.
|
173
|
+
#
|
174
|
+
# @param src [String|Array] The entries to copy or move. If is an Array, `dst` is assumed to be a directory.
|
175
|
+
# @param dst [String] The destination. **Any existing entries will be overwritten.** Any required directory will be created.
|
176
|
+
# @param operation [Symbol] The operation to perform. Valid values are `:copy` or `:move`.
|
177
|
+
# @param run [Boolean] If `false`, it will just print a list of message that would be copied or moved.
|
178
|
+
# @param show_errors [Boolean] If show errors.
|
179
|
+
# @param fatal [Boolean] If quit in case of fatal errors.
|
180
|
+
# @return [Boolean] `true` if operation succeeded, `false` otherwise.
|
181
|
+
def copy_or_move(src, dst, operation, run = true, show_errors = false, fatal = true)
|
182
|
+
rv = true
|
183
|
+
locale = self.i18n.shell
|
184
|
+
operation, operation_s, single, src, dst = sanitize_copy_or_move_arguments(operation, src, dst)
|
185
|
+
|
186
|
+
if !run then
|
187
|
+
dry_run_copy_or_move(single, operation_s, src, dst)
|
164
188
|
else
|
165
|
-
|
166
|
-
|
167
|
-
src
|
189
|
+
rv = catch(:rv) do
|
190
|
+
dst_dir = prepare_destination(single, src, dst, operation_s, show_errors, fatal)
|
191
|
+
check_sources(src, operation_s, fatal)
|
192
|
+
execute_copy_or_move(src, dst, dst_dir, single, operation, operation_s, show_errors, fatal)
|
193
|
+
true
|
168
194
|
end
|
169
|
-
self.console.write("to directory: {mark=bright}#{dst}{/mark}", "\n", 5)
|
170
195
|
end
|
171
|
-
|
172
|
-
rv
|
196
|
+
|
197
|
+
rv
|
198
|
+
end
|
199
|
+
|
200
|
+
private
|
201
|
+
# Sanitizes arguments for copy or move.
|
202
|
+
#
|
203
|
+
# @param operation [String] The operation which will be executed.
|
204
|
+
# @param src [String|Array] The entries to copy or move. If is an Array, `dst` is assumed to be a directory.
|
205
|
+
# @param dst [String] The destination. **Any existing entries will be overwritten.** Any required directory will be created.
|
206
|
+
# @return [Array] A list of sanitized arguments.
|
207
|
+
def sanitize_copy_or_move_arguments(operation, src, dst)
|
208
|
+
operation = :copy if operation != :move
|
209
|
+
single = !src.is_a?(Array)
|
210
|
+
src = single ? File.expand_path(src) : src.collect {|s| File.expand_path(s) }
|
211
|
+
|
212
|
+
[operation, self.i18n.shell.send(operation), single, src, File.expand_path(dst.ensure_string)]
|
213
|
+
end
|
214
|
+
|
215
|
+
# Shows which copy or move operation are going to executed.
|
216
|
+
#
|
217
|
+
# @param single [Boolean] Whether `src` is a single file or directory.
|
218
|
+
# @param operation [String] The operation which will be executed.
|
219
|
+
# @param src [String|Array] The entries to copy or move. If is an Array, `dst` is assumed to be a directory.
|
220
|
+
# @param dst [String] The destination. **Any existing entries will be overwritten.** Any required directory will be created.
|
221
|
+
def dry_run_copy_or_move(single, operation, src, dst)
|
222
|
+
locale = self.i18n.shell
|
223
|
+
|
224
|
+
if single then
|
225
|
+
@console.warn(locale.copy_move_single_dry(operation))
|
226
|
+
@console.write(locale.copy_move_from(File.expand_path(src.ensure_string)), "\n", 11)
|
227
|
+
@console.write(locale.copy_move_to(dst), "\n", 11)
|
228
|
+
else
|
229
|
+
@console.warn(locale.copy_move_multi_dry(operation))
|
230
|
+
@console.with_indentation(11) do
|
231
|
+
src.each do |s| @console.write(s) end
|
232
|
+
end
|
233
|
+
@console.write(locale.copy_move_to_multi(dst), "\n", 5)
|
234
|
+
end
|
235
|
+
end
|
236
|
+
|
237
|
+
# Prepares a destination directory for a copy or move.
|
238
|
+
#
|
239
|
+
# @param single [Boolean] Whether `src` is a single file or directory.
|
240
|
+
# @param src [String|Array] The entries to copy or move. If is an Array, `dst` is assumed to be a directory.
|
241
|
+
# @param dst [String] The destination. **Any existing entries will be overwritten.** Any required directory will be created.
|
242
|
+
# @param operation [String] The operation which will be executed.
|
243
|
+
# @param show_errors [Boolean] If show errors.
|
244
|
+
# @param fatal [Boolean] If quit in case of fatal errors.
|
245
|
+
# @return [String] The prepared destination.
|
246
|
+
def prepare_destination(single, src, dst, operation, show_errors, fatal)
|
173
247
|
dst_dir = single ? File.dirname(dst) : dst
|
174
248
|
has_dir = self.check(dst_dir, :dir)
|
175
249
|
|
@@ -178,210 +252,313 @@ module Bovem
|
|
178
252
|
throw(:rv, false) if !has_dir
|
179
253
|
|
180
254
|
if single && self.check(dst, :dir) then
|
181
|
-
@console.send(fatal ? :fatal : :error,
|
255
|
+
@console.send(fatal ? :fatal : :error, self.i18n.shell.copy_move_single_to_directory(operation, src, dst))
|
182
256
|
throw(:rv, false)
|
183
257
|
end
|
184
258
|
|
259
|
+
dst_dir
|
260
|
+
end
|
261
|
+
|
262
|
+
# Checks every sources for a copy or move.
|
263
|
+
#
|
264
|
+
# @param src [String|Array] The entries to copy or move. If is an Array, `dst` is assumed to be a directory.
|
265
|
+
# @param operation [String] The operation which will be executed.
|
266
|
+
# @param fatal [Boolean] If quit in case of fatal errors.
|
267
|
+
def check_sources(src, operation, fatal)
|
185
268
|
# Check that every file is existing
|
186
269
|
src.ensure_array.each do |s|
|
187
270
|
if !self.check(s, :exists) then
|
188
|
-
@console.send(fatal ? :fatal : :error,
|
271
|
+
@console.send(fatal ? :fatal : :error, self.i18n.shell.copy_move_src_not_found(operation, s))
|
189
272
|
throw(:rv, false)
|
190
273
|
end
|
191
274
|
end
|
275
|
+
end
|
276
|
+
|
277
|
+
# Executes the copy or move operation.
|
278
|
+
#
|
279
|
+
# @param src [String|Array] The entries to copy or move. If is an Array, `dst` is assumed to be a directory.
|
280
|
+
# @param dst [String] The destination. **Any existing entries will be overwritten.** Any required directory will be created.
|
281
|
+
# @param dst_dir [String] The destination directory.
|
282
|
+
# @param single [Boolean] Whether `src` is a single file or directory.
|
283
|
+
# @param operation [Symbol] The operation to perform. Valid values are `:copy` or `:move`.
|
284
|
+
# @param operation_s [String] The string representation of the operation to perform.
|
285
|
+
# @param show_errors [Boolean] If show errors.
|
286
|
+
# @param fatal [Boolean] If quit in case of fatal errors.
|
287
|
+
def execute_copy_or_move(src, dst, dst_dir, single, operation, operation_s, show_errors, fatal)
|
288
|
+
locale = self.i18n.shell
|
192
289
|
|
193
|
-
# Do operation
|
194
290
|
begin
|
195
|
-
FileUtils.send(operation == :move ? :mv : :cp_r, src, dst, {:
|
291
|
+
FileUtils.send(operation == :move ? :mv : :cp_r, src, dst, {noop: false, verbose: false})
|
196
292
|
rescue Errno::EACCES => e
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
self.console.error("Cannot #{operation} following file(s) to non writable directory {mark=bright}#{dst}{/mark}:")
|
201
|
-
self.console.with_indentation(11) do
|
202
|
-
src.each do |s| self.console.write(s) end
|
203
|
-
end
|
204
|
-
Kernel.exit(-1) if fatal
|
205
|
-
end
|
206
|
-
|
207
|
-
throw(:rv, false)
|
293
|
+
single_msg = locale.copy_move_dst_not_writable_single(operation_s, src, dst_dir)
|
294
|
+
multi_msg = locale.copy_move_dst_not_writable_single(operation_s, dst)
|
295
|
+
handle_copy_move_failure(single, src, show_errors, fatal, single_msg, multi_msg, nil)
|
208
296
|
rescue Exception => e
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
self.console.error("Cannot #{operation} following entries to {mark=bright}#{dst}{/mark}:")
|
213
|
-
self.console.with_indentation(11) do
|
214
|
-
src.each do |s| self.console.write(s) end
|
215
|
-
end
|
216
|
-
self.console.write("due to this error: [#{e.class.to_s}] #{e}.", "\n", 5)
|
217
|
-
Kernel.exit(-1) if fatal
|
218
|
-
end
|
219
|
-
|
220
|
-
throw(:rv, false)
|
297
|
+
single_msg = locale.copy_move_error_single(operation_s, src, dst_dir, e.class.to_s, e)
|
298
|
+
multi_msg = locale.copy_move_error_multi(operation_s, dst)
|
299
|
+
handle_copy_move_failure(single, src, show_errors, fatal, single_msg, multi_msg, locale.error(e.class.to_s, e))
|
221
300
|
end
|
222
|
-
|
223
|
-
true
|
224
301
|
end
|
225
|
-
end
|
226
302
|
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
|
239
|
-
|
240
|
-
|
303
|
+
# Handles a failure on copy or move.
|
304
|
+
#
|
305
|
+
# @param single [Boolean] Whether `src` is a single file or directory.
|
306
|
+
# @param src [String|Array] The entries to copy or move. If is an Array, `dst` is assumed to be a directory.
|
307
|
+
# @param show_errors [Boolean] If show errors.
|
308
|
+
# @param fatal [Boolean] If quit in case of fatal errors.
|
309
|
+
# @param single_msg [String] The message to show in case of a single source.
|
310
|
+
# @param multi_msg [String] The starting message to show in case of multiple sources.
|
311
|
+
# @param error [String|nil] The ending message to show in case of multiple sources.
|
312
|
+
def handle_copy_move_failure(single, src, show_errors, fatal, single_msg, multi_msg, error)
|
313
|
+
if single then
|
314
|
+
@console.send(fatal ? :fatal : :error, single_msg, "\n", 5)
|
315
|
+
else
|
316
|
+
@console.error(multi_msg)
|
317
|
+
@console.with_indentation(11) do
|
318
|
+
src.each do |s| @console.write(s) end
|
319
|
+
end
|
320
|
+
@console.write(error, "\n", 5) if error
|
321
|
+
Kernel.exit(-1) if fatal
|
322
|
+
end
|
241
323
|
|
242
|
-
|
243
|
-
|
244
|
-
# @param src [String|Array] The entries to move. If is an Array, `dst` is assumed to be a directory.
|
245
|
-
# @param dst [String] The destination. **Any existing entries will be overwritten.** Any required directory will be created.
|
246
|
-
# @param run [Boolean] If `false`, it will just print a list of message that would be deleted.
|
247
|
-
# @param show_errors [Boolean] If show errors.
|
248
|
-
# @param fatal [Boolean] If quit in case of fatal errors.
|
249
|
-
# @return [Boolean] `true` if operation succeeded, `false` otherwise.
|
250
|
-
def move(src, dst, run = true, show_errors = false, fatal = true)
|
251
|
-
self.copy_or_move(src, dst, :move, run, show_errors, fatal)
|
324
|
+
throw(:rv, false)
|
325
|
+
end
|
252
326
|
end
|
253
327
|
|
254
|
-
#
|
255
|
-
|
256
|
-
|
257
|
-
|
258
|
-
|
259
|
-
|
260
|
-
|
261
|
-
|
262
|
-
|
263
|
-
|
264
|
-
|
265
|
-
|
266
|
-
|
267
|
-
|
268
|
-
|
269
|
-
|
270
|
-
|
328
|
+
# Methods to run commands or delete entries.
|
329
|
+
module Execute
|
330
|
+
# Runs a command into the shell.
|
331
|
+
#
|
332
|
+
# @param command [String] The string to run.
|
333
|
+
# @param message [String] A message to show before running.
|
334
|
+
# @param run [Boolean] If `false`, it will just print a message with the full command that will be run.
|
335
|
+
# @param show_exit [Boolean] If show the exit status.
|
336
|
+
# @param show_output [Boolean] If show command output.
|
337
|
+
# @param show_command [Boolean] If show the command that will be run.
|
338
|
+
# @param fatal [Boolean] If quit in case of fatal errors.
|
339
|
+
# @return [Hash] An hash with `status` and `output` keys.
|
340
|
+
def run(command, message = nil, run = true, show_exit = true, show_output = false, show_command = false, fatal = true)
|
341
|
+
rv = {status: 0, output: ""}
|
342
|
+
command = command.ensure_string
|
343
|
+
locale = self.i18n.shell
|
344
|
+
|
345
|
+
# Show the command
|
346
|
+
@console.begin(message) if message.present?
|
347
|
+
|
348
|
+
if !run then # Print a message
|
349
|
+
@console.warn(locale.run_dry(command))
|
350
|
+
@console.status(:ok) if show_exit
|
351
|
+
else # Run
|
352
|
+
rv = execute_command(command, show_command, show_output)
|
271
353
|
end
|
354
|
+
|
355
|
+
# Return
|
356
|
+
@console.status(rv[:status] == 0 ? :ok : :fail) if show_exit
|
357
|
+
exit(rv[:status]) if fatal && rv[:status] != 0
|
358
|
+
rv
|
272
359
|
end
|
273
360
|
|
274
|
-
|
361
|
+
# Deletes a list of files.
|
362
|
+
#
|
363
|
+
# @param files [Array] The list of files to remove
|
364
|
+
# @param run [Boolean] If `false`, it will just print a list of message that would be deleted.
|
365
|
+
# @param show_errors [Boolean] If show errors.
|
366
|
+
# @param fatal [Boolean] If quit in case of fatal errors.
|
367
|
+
# @return [Boolean] `true` if operation succeeded, `false` otherwise.
|
368
|
+
def delete(files, run = true, show_errors = false, fatal = true)
|
369
|
+
rv = true
|
370
|
+
locale = self.i18n.shell
|
371
|
+
files = files.ensure_array.compact.collect {|f| File.expand_path(f.ensure_string) }
|
372
|
+
|
373
|
+
if !run then
|
374
|
+
@console.warn(locale.remove_dry)
|
375
|
+
@console.with_indentation(11) do
|
376
|
+
files.each do |file| @console.write(file) end
|
377
|
+
end
|
378
|
+
else
|
379
|
+
rv = catch(:rv) do
|
380
|
+
begin
|
381
|
+
FileUtils.rm_r(files, {noop: false, verbose: false, secure: true})
|
382
|
+
throw(:rv, true)
|
383
|
+
rescue => e
|
384
|
+
handle_failure(e, :remove_unwritable, :remove_not_found, :remove_error, files, fatal, show_errors)
|
385
|
+
end
|
275
386
|
|
276
|
-
|
277
|
-
|
278
|
-
self.console.info("Moving back into directory {mark=bright}#{original}{/mark}") if show_messages
|
279
|
-
Dir.chdir(original) if restore
|
280
|
-
rescue Exception => e
|
281
|
-
rv = false
|
387
|
+
false
|
388
|
+
end
|
282
389
|
end
|
390
|
+
|
391
|
+
rv
|
283
392
|
end
|
284
393
|
|
285
|
-
|
394
|
+
private
|
395
|
+
# Runs a command into the shell.
|
396
|
+
#
|
397
|
+
# @param command [String] The string to run.
|
398
|
+
# @param show_command [Boolean] If show the command that will be run.
|
399
|
+
# @param show_output [Boolean] If show command output.
|
400
|
+
# @return [Hash] An hash with `status` and `output` keys.
|
401
|
+
def execute_command(command, show_command, show_output)
|
402
|
+
output = ""
|
403
|
+
|
404
|
+
@console.info(self.i18n.shell.run(command)) if show_command
|
405
|
+
status = ::Open4::popen4(command + " 2>&1") { |pid, stdin, stdout, stderr|
|
406
|
+
stdout.each_line do |line|
|
407
|
+
output << line
|
408
|
+
Kernel.print line if show_output
|
409
|
+
end
|
410
|
+
}.exitstatus
|
411
|
+
|
412
|
+
{status: status, output: output}
|
413
|
+
end
|
286
414
|
end
|
287
415
|
|
288
|
-
#
|
289
|
-
|
290
|
-
|
291
|
-
|
292
|
-
|
293
|
-
|
294
|
-
|
295
|
-
|
296
|
-
|
297
|
-
|
298
|
-
|
299
|
-
|
300
|
-
|
301
|
-
|
302
|
-
|
303
|
-
|
304
|
-
|
305
|
-
|
416
|
+
# Methods to interact with directories.
|
417
|
+
module Directories
|
418
|
+
# Executes a block of code in another directory.
|
419
|
+
#
|
420
|
+
# @param directory [String] The new working directory.
|
421
|
+
# @param restore [Boolean] If to restore the original working directory.
|
422
|
+
# @param show_messages [Boolean] Show informative messages about working directory changes.
|
423
|
+
# @return [Boolean] `true` if the directory was valid and the code executed, `false` otherwise.
|
424
|
+
def within_directory(directory, restore = true, show_messages = false)
|
425
|
+
rv = false
|
426
|
+
locale = self.i18n.shell
|
427
|
+
|
428
|
+
directory = File.expand_path(directory.ensure_string)
|
429
|
+
original = Dir.pwd
|
430
|
+
|
431
|
+
rv = enter_directory(directory, show_messages, locale.move_in(directory))
|
432
|
+
yield if rv && block_given?
|
433
|
+
rv = enter_directory(original, show_messages, locale.move_out(directory)) if rv && restore
|
434
|
+
|
435
|
+
rv
|
436
|
+
end
|
437
|
+
|
438
|
+
# Creates a list of directories, included missing parent directories.
|
439
|
+
#
|
440
|
+
# @param directories [Array] The list of directories to create.
|
441
|
+
# @param mode [Fixnum] Initial permissions for the new directories.
|
442
|
+
# @param run [Boolean] If `false`, it will just print a list of directories that would be created.
|
443
|
+
# @param show_errors [Boolean] If show errors.
|
444
|
+
# @param fatal [Boolean] If quit in case of fatal errors.
|
445
|
+
# @return [Boolean] `true` if operation succeeded, `false` otherwise.
|
446
|
+
def create_directories(directories, mode = 0755, run = true, show_errors = false, fatal = true)
|
447
|
+
rv = true
|
448
|
+
|
449
|
+
# Adjust directory
|
450
|
+
directories = directories.ensure_array.compact {|d| File.expand_path(d.ensure_string) }
|
451
|
+
|
452
|
+
if !run then # Just print
|
453
|
+
dry_run_directory_creation(directories)
|
454
|
+
else
|
455
|
+
directories.each do |directory|
|
456
|
+
rv = rv && try_create_directory(directory, mode, fatal, directories, show_errors)
|
457
|
+
break if !rv
|
458
|
+
end
|
306
459
|
end
|
307
|
-
else
|
308
|
-
directories.each do |directory|
|
309
|
-
rv = catch(:rv) do
|
310
|
-
# Perform tests
|
311
|
-
if self.check(directory, :directory) then
|
312
|
-
self.console.send(fatal ? :fatal : :error, "The directory {mark=bright}#{directory}{/mark} already exists.")
|
313
|
-
elsif self.check(directory, :exist) then
|
314
|
-
self.console.send(fatal ? :fatal : :error, "Path {mark=bright}#{directory}{/mark} is currently a file.")
|
315
|
-
else
|
316
|
-
begin # Create directory
|
317
|
-
FileUtils.mkdir_p(directory, {:mode => mode, :noop => false, :verbose => false})
|
318
|
-
throw(:rv, true)
|
319
|
-
rescue Errno::EACCES => e
|
320
|
-
self.console.send(fatal ? :fatal : :error, "Cannot create following directory due to permission denied: {mark=bright}#{e.message.gsub(/.+ - (.+)/, "\\1")}{/mark}.")
|
321
|
-
rescue Exception => e
|
322
|
-
if show_errors then
|
323
|
-
self.console.error("Cannot create following directories:")
|
324
|
-
self.console.with_indentation(11) do
|
325
|
-
directories.each do |directory| self.console.write(directory) end
|
326
|
-
end
|
327
|
-
self.console.write("due to this error: [#{e.class.to_s}] #{e}.", "\n", 5)
|
328
|
-
Kernel.exit(-1) if fatal
|
329
|
-
end
|
330
|
-
end
|
331
|
-
end
|
332
460
|
|
461
|
+
rv
|
462
|
+
end
|
463
|
+
|
464
|
+
private
|
465
|
+
# Change current working directory.
|
466
|
+
#
|
467
|
+
# @param directory [String] The directory which move into.
|
468
|
+
# @param show_message [Boolean] Whether to show or not message.
|
469
|
+
# @param message [String] The message to show.
|
470
|
+
# @return [Boolean] `true` if operation succeeded, `false` otherwise.
|
471
|
+
def enter_directory(directory, show_message, message)
|
472
|
+
begin
|
473
|
+
raise ArgumentError if !self.check(directory, [:directory, :executable])
|
474
|
+
@console.info(message) if show_message
|
475
|
+
Dir.chdir(directory)
|
476
|
+
true
|
477
|
+
rescue Exception => e
|
333
478
|
false
|
334
479
|
end
|
480
|
+
end
|
335
481
|
|
336
|
-
|
482
|
+
# Show which directory are going to be created.
|
483
|
+
# @param directories [Array] The list of directories to create.
|
484
|
+
def dry_run_directory_creation(directories)
|
485
|
+
@console.warn(self.i18n.shell.mkdir_dry)
|
486
|
+
@console.with_indentation(11) do
|
487
|
+
directories.each do |directory| @console.write(directory) end
|
488
|
+
end
|
337
489
|
end
|
338
|
-
end
|
339
490
|
|
340
|
-
|
341
|
-
|
491
|
+
# Tries to creates a directory.
|
492
|
+
#
|
493
|
+
# @param directory [String] The directory to create.
|
494
|
+
# @param mode [Fixnum] Initial permissions for the new directories.
|
495
|
+
# @param fatal [Boolean] If quit in case of fatal errors.
|
496
|
+
# @param directories [Array] The list of directories to create.
|
497
|
+
# @param show_errors [Boolean] If show errors.
|
498
|
+
# @return [Boolean] `true` if operation succeeded, `false` otherwise.
|
499
|
+
def try_create_directory(directory, mode, fatal, directories, show_errors)
|
500
|
+
rv = false
|
501
|
+
locale = self.i18n.shell
|
502
|
+
|
503
|
+
# Perform tests
|
504
|
+
if self.check(directory, :directory) then
|
505
|
+
@console.send(fatal ? :fatal : :error, locale.mkdir_existing(directory))
|
506
|
+
elsif self.check(directory, :exist) then
|
507
|
+
@console.send(fatal ? :fatal : :error, locale.mkdir_file(directory))
|
508
|
+
else
|
509
|
+
rv = create_directory(directory, mode, fatal, directories, show_errors)
|
510
|
+
end
|
342
511
|
|
343
|
-
|
344
|
-
|
345
|
-
# You can also pass a block to perform matching. The block will receive a single argument and the path will be considered matched if the blocks not evaluates to `nil` or `false`.
|
346
|
-
#
|
347
|
-
# Inside the block, you can call `Find.prune` to stop searching in the current directory.
|
348
|
-
#
|
349
|
-
# @param directories [String] A list of directories where to search files.
|
350
|
-
# @param patterns [Array] A list of regexps or patterns to match files. If empty, every file is returned. Ignored if a block is provided.
|
351
|
-
# @param by_extension [Boolean] If to only search in extensions. Ignored if a block is provided.
|
352
|
-
# @param case_sensitive [Boolean] If the search is case sensitive. Only meaningful for string patterns.
|
353
|
-
def find(directories, patterns = [], by_extension = false, case_sensitive = false)
|
354
|
-
rv = []
|
355
|
-
|
356
|
-
# Adjust directory
|
357
|
-
directories = directories.ensure_array.compact {|d| File.expand_path(d.ensure_string) }
|
358
|
-
|
359
|
-
# Adjust patterns
|
360
|
-
patterns = patterns.ensure_array.compact.collect {|p| p.is_a?(::Regexp) ? p : Regexp.new(Regexp.quote(p.ensure_string)) }
|
361
|
-
patterns = patterns.collect {|p| /(#{p.source})$/ } if by_extension
|
362
|
-
patterns = patterns.collect {|p| /#{p.source}/i } if !case_sensitive
|
363
|
-
|
364
|
-
directories.each do |directory|
|
365
|
-
if self.check(directory, [:directory, :readable, :executable]) then
|
366
|
-
Find.find(directory) do |entry|
|
367
|
-
found = patterns.blank? ? true : catch(:found) do
|
368
|
-
if block_given? then
|
369
|
-
throw(:found, true) if yield(entry)
|
370
|
-
else
|
371
|
-
patterns.each do |pattern|
|
372
|
-
throw(:found, true) if pattern.match(entry) && (!by_extension || !File.directory?(entry))
|
373
|
-
end
|
374
|
-
end
|
512
|
+
rv
|
513
|
+
end
|
375
514
|
|
376
|
-
|
377
|
-
|
515
|
+
# Creates a directory.
|
516
|
+
#
|
517
|
+
# @param directory [String] The directory to create.
|
518
|
+
# @param mode [Fixnum] Initial permissions for the new directories.
|
519
|
+
# @param fatal [Boolean] If quit in case of fatal errors.
|
520
|
+
# @param directories [Array] The list of directories to create.
|
521
|
+
# @param show_errors [Boolean] If show errors.
|
522
|
+
# @return [Boolean] `true` if operation succeeded, `false` otherwise.
|
523
|
+
def create_directory(directory, mode, fatal, directories, show_errors)
|
524
|
+
rv = false
|
378
525
|
|
379
|
-
|
526
|
+
begin # Create directory
|
527
|
+
FileUtils.mkdir_p(directory, {mode: mode, noop: false, verbose: false})
|
528
|
+
rv = true
|
529
|
+
rescue Exception => e
|
530
|
+
handle_failure(e, :mkdir_denied, nil, :mkdir_error, directories, fatal, show_errors)
|
380
531
|
end
|
532
|
+
|
533
|
+
rv
|
381
534
|
end
|
382
|
-
|
535
|
+
end
|
536
|
+
end
|
537
|
+
|
538
|
+
# A utility class for most common shell operation.
|
539
|
+
#
|
540
|
+
# @attr [Console] console # A console instance.
|
541
|
+
class Shell
|
542
|
+
include Lazier::I18n
|
543
|
+
include Bovem::ShellMethods::General
|
544
|
+
include Bovem::ShellMethods::Read
|
545
|
+
include Bovem::ShellMethods::Write
|
546
|
+
include Bovem::ShellMethods::Execute
|
547
|
+
include Bovem::ShellMethods::Directories
|
548
|
+
|
549
|
+
attr_accessor :console
|
550
|
+
|
551
|
+
# Returns a unique instance for Shell.
|
552
|
+
#
|
553
|
+
# @return [Shell] A new instance.
|
554
|
+
def self.instance
|
555
|
+
@instance ||= ::Bovem::Shell.new
|
556
|
+
end
|
383
557
|
|
384
|
-
|
558
|
+
# Initializes a new Shell.
|
559
|
+
def initialize
|
560
|
+
@console = ::Bovem::Console.instance
|
561
|
+
self.i18n_setup(:bovem, ::File.absolute_path(::Pathname.new(::File.dirname(__FILE__)).to_s + "/../../locales/"))
|
385
562
|
end
|
386
563
|
end
|
387
564
|
end
|