cowtech-lib 1.9.8.1 → 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.
@@ -1,119 +1,98 @@
1
1
  # encoding: utf-8
2
2
  #
3
- # cowtech-lib
4
- # Author: Shogun <shogun_panda@me.com>
5
- # Copyright © 2011 and above Shogun
6
- # Released under the MIT License, which follows.
7
- #
8
- # The MIT License
9
- # Permission is hereby granted, free of charge, to any person obtaining a copy
10
- # of this software and associated documentation files (the "Software"), to deal
11
- # in the Software without restriction, including without limitation the rights
12
- # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
13
- # copies of the Software, and to permit persons to whom the Software is
14
- # furnished to do so, subject to the following conditions:
15
- #
16
- # The above copyright notice and this permission notice shall be included in
17
- # all copies or substantial portions of the Software.
18
- #
19
- # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
20
- # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
21
- # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
22
- # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
23
- # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
24
- # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
25
- # THE SOFTWARE.
3
+ # This file is part of the cowtech-lib gem. Copyright (C) 2011 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.
26
5
  #
27
6
 
28
7
  module Cowtech
29
- module Lib
30
- # Class which implements a script to execute general tasks.
31
- # @author Shogun
32
-
33
- class Script
34
- # Console object
35
- attr_reader :console
36
-
37
- # Shell object
38
- attr_reader :shell
39
-
40
- # Options parser
41
- attr_reader :options_parser
42
-
43
- # Creates a new script.
44
- #
45
- # Arguments:
46
- # * <em>name</em>: Script name
47
- # * <em>version</em>: Script version
48
- # * <em>name</em>: Script description
49
- # * <em>name</em>: Script usage
50
- # * <em>name</em>: Script message for help switch. Supported keys are
51
- # * <em>pre_usage</em>: Message to print before the usage string
52
- # * <em>pre_options</em>: Message to print before the options list
53
- # * <em>post_options</em>: Message to print after the options list
54
- def initialize(args)
55
- @console = Console.new
56
- @shell = Shell.new(@console)
57
- @options_parser = OptionParser.new(args)
58
-
59
- self.add_options()
60
- @options_parser << [
61
- {:name => "command-echo", :short => "-z", :long => "--command-echo", :type => :bool, :help => "Show executed commands."},
62
- {:name => "command-show", :short => "-V", :long => "--command-show", :type => :bool, :help => "Show executed commands' output."},
63
- {:name => "command-skip", :short => "-Z", :long => "--command-skip", :type => :bool, :help => "Don't really execut commands, only print them."}
64
- ]
65
-
66
- @options_parser.parse
67
-
68
- @console.show_commands = @options_parser["command-echo"]
69
- @console.show_outputs = @options_parser["command-show"]
70
- @console.skip_commands = @options_parser["command-skip"]
71
-
72
- self.run
73
- end
74
-
75
- # Execute a task, showing a message.
76
- #
77
- # Arguments:
78
- # * <em>msg</em>: The message to show
79
- # * <em>show_message</em>: If show task description
80
- # * <em>show_end</em>: If show message exit status
81
- # * <em>go_up</em>: If go up one line to show exit status
82
- # * <em>dots</em>: If show dots after message
83
- def task(args)
84
- if args.fetch(:show_msg, true) then
85
- @console.write(:begin => args[:msg], :dots => args[:dots])
86
- @console.indent_set(3)
87
- end
88
-
89
- # Run the block
90
- rv = yield
91
- rv = :ok if !rv.is_a?(Symbol)
92
- rv = [rv, true] if !rv.is_a?(Array)
93
-
94
- # Show the result
95
- @console.status(:status => rv[0], :fatal => false) if args.fetch(:show_result, true)
96
- @console.indent_set(-3) if args.fetch(:show_msg, true)
97
-
98
- exit(1) if rv[0] != :ok && rv[1]
99
- rv
100
- end
101
-
102
- # Run the script.
103
- #<b> MUST BY OVERRIDEN BY SUBCLASSES!</b>
104
- def run
105
- self.console.fatal("Cowtech::Lib::Script#run must be overidden by subclasses.")
106
- end
107
-
108
- # Adds the command line options.
109
- # <b>MUST BE OVERRIDEN BY SUBCLASSES!</b>
110
- def add_options
111
- self.console.fatal("Cowtech::Lib::Script#add_options must be overidden by subclasses.")
112
- end
113
-
114
- def get_binding
115
- binding
116
- end
117
- end
118
- end
8
+ module Lib
9
+ # Class which implements a script to execute general tasks.
10
+ # @author Shogun
11
+
12
+ class Script
13
+ # Console object
14
+ attr_reader :console
15
+
16
+ # Shell object
17
+ attr_reader :shell
18
+
19
+ # Options parser
20
+ attr_reader :options_parser
21
+
22
+ # Creates a new script.
23
+ #
24
+ # Arguments:
25
+ # * <em>name</em>: Script name
26
+ # * <em>version</em>: Script version
27
+ # * <em>name</em>: Script description
28
+ # * <em>name</em>: Script usage
29
+ # * <em>name</em>: Script message for help switch. Supported keys are
30
+ # * <em>pre_usage</em>: Message to print before the usage string
31
+ # * <em>pre_options</em>: Message to print before the options list
32
+ # * <em>post_options</em>: Message to print after the options list
33
+ def initialize(args)
34
+ @console = Console.new
35
+ @shell = Shell.new(@console)
36
+ @options_parser = OptionParser.new(args)
37
+
38
+ self.add_options()
39
+ @options_parser << [
40
+ {name: "command-echo", short: "-z", long: "--command-echo", type: :bool, help: "Show executed commands."},
41
+ {name: "command-show", short: "-V", long: "--command-show", type: :bool, help: "Show executed commands' output."},
42
+ {name: "command-skip", short: "-Z", long: "--command-skip", type: :bool, help: "Don't really execut commands, only print them."}
43
+ ]
44
+
45
+ @options_parser.parse
46
+
47
+ @console.show_commands = @options_parser["command-echo"]
48
+ @console.show_outputs = @options_parser["command-show"]
49
+ @console.skip_commands = @options_parser["command-skip"]
50
+
51
+ self.run
52
+ end
53
+
54
+ # Execute a task, showing a message.
55
+ #
56
+ # Arguments:
57
+ # * <em>msg</em>: The message to show
58
+ # * <em>show_message</em>: If show task description
59
+ # * <em>show_end</em>: If show message exit status
60
+ # * <em>go_up</em>: If go up one line to show exit status
61
+ # * <em>dots</em>: If show dots after message
62
+ def task(args)
63
+ if args.fetch(:show_msg, true) then
64
+ @console.write(begin: args[:msg], dots: args[:dots])
65
+ @console.indent_set(3)
66
+ end
67
+
68
+ # Run the block
69
+ rv = yield
70
+ rv = :ok if !rv.is_a?(Symbol)
71
+ rv = [rv, true] if !rv.is_a?(Array)
72
+
73
+ # Show the result
74
+ @console.status(status: rv[0], fatal: false) if args.fetch(:show_result, true)
75
+ @console.indent_set(-3) if args.fetch(:show_msg, true)
76
+
77
+ exit(1) if rv[0] != :ok && rv[1]
78
+ rv
79
+ end
80
+
81
+ # Run the script.
82
+ #<b> MUST BY OVERRIDEN BY SUBCLASSES!</b>
83
+ def run
84
+ self.console.fatal("Cowtech::Lib::Script#run must be overidden by subclasses.")
85
+ end
86
+
87
+ # Adds the command line options.
88
+ # <b>MUST BE OVERRIDEN BY SUBCLASSES!</b>
89
+ def add_options
90
+ self.console.fatal("Cowtech::Lib::Script#add_options must be overidden by subclasses.")
91
+ end
92
+
93
+ def get_binding
94
+ binding
95
+ end
96
+ end
97
+ end
119
98
  end
@@ -1,28 +1,7 @@
1
1
  # encoding: utf-8
2
2
  #
3
- # cowtech-lib
4
- # Author: Shogun <shogun_panda@me.com>
5
- # Copyright © 2011 and above Shogun
6
- # Released under the MIT License, which follows.
7
- #
8
- # The MIT License
9
- # Permission is hereby granted, free of charge, to any person obtaining a copy
10
- # of this software and associated documentation files (the "Software"), to deal
11
- # in the Software without restriction, including without limitation the rights
12
- # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
13
- # copies of the Software, and to permit persons to whom the Software is
14
- # furnished to do so, subject to the following conditions:
15
- #
16
- # The above copyright notice and this permission notice shall be included in
17
- # all copies or substantial portions of the Software.
18
- #
19
- # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
20
- # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
21
- # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
22
- # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
23
- # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
24
- # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
25
- # THE SOFTWARE.
3
+ # This file is part of the cowtech-lib gem. Copyright (C) 2011 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.
26
5
  #
27
6
 
28
7
  require "open4"
@@ -30,353 +9,353 @@ require "find"
30
9
  require "fileutils"
31
10
 
32
11
  module Cowtech
33
- module Lib
34
- # A class which provides some useful method for interaction with files and processes.
35
- # @author Shogun
36
- class Shell
37
- # Run a command.
38
- #
39
- # Arguments:
40
- # * <em>msg</em>: The message to show
41
- # * <em>command</em>: The command to run
42
- # * <em>show_msg</em>: If show the exit message
43
- # * <em>show_exit</em>: If show the exit code
44
- # * <em>fatal</em>: If abort on failure
45
- #
46
- # Returns: An object which the status attribute is the exit status of the process, and the output attribute is the output of the command
47
- def run(args)
48
- rv = {:status => 0, :output => []}
49
- command = args[:command]
50
-
51
- @console.write(:begin => args[:msg]) if args[:show_msg]
52
-
53
- if @console.show_commands then
54
- @console.warn("Will run command: \"#{command}\"", :dots => false)
55
- @console.status(:ok)
56
- end
57
-
58
- if !@console.skip_commands then
59
- rv[:status] = Open4::open4(command + " 2>&1") { |pid, stdin, stdout, stderr|
60
- stdout.each_line do |line|
61
- rv[:output] << line
62
- print line if @console.show_outputs
63
- end
64
- }.exitstatus
65
- end
66
- rv[:output] = rv[:output].join("\n")
67
-
68
- @console.status(:status => rv[:status] == 0 ? :ok : :fail, :fatal => false) if args[:show_exit]
69
- exit(1) if args[:fatal] && rv[:status] != 0
70
- rv
71
- end
72
-
73
- # Opens a file.
74
- #
75
- # Arguments:
76
- # * <em>name</em>: The file path
77
- # * <em>mode</em>: Open mode, like the one in File.new
78
- # * <em>codec</em>: The encoding used to open the file. <b>UNUSED FOR NOW!</b>
79
- #
80
- # Returns: A new <em>File</em> object
81
- def open_file(args)
82
- begin
83
- File.new(args[:name], (args[:mode] || "r") + (args[:codec] ? ":#{args[:codec]}" : ""))
84
- rescue Exception => e
85
- @console.write(:error => "Unable to open file #{name}: #{e}")
86
- nil
87
- end
88
- end
89
-
90
- # Perform a check on a file or directory.
91
- #
92
- # Arguments:
93
- # * <em>name</em>: The file/directory path
94
- # * <em>tests</em>: A list of tests to execute
95
- #
96
- # Valid tests are:
97
- #
98
- # * <em>:fc_exists</em>: Check if the file exists
99
- # * <em>:fc_read</em>: Check if the file is readable
100
- # * <em>:fc_write</em>: Check if the file writeable
101
- # * <em>:fc_exec</em>: Check if the file executable
102
- # * <em>:fc_dir</em>: Check if the file is a directory
103
- # * <em>:fc_symlink</em>: Check if the file is symbolic link
104
- #
105
- # Returns: <em>true</em> if any of tests had success, <em>false</em> otherwise
106
- def file_check?(args)
107
- rv = false
108
- tests = (args[:tests] || [:exists]).force_array
109
-
110
- if args[:file] then
111
- rv = true
112
- tests.each do |test|
113
- method = (test.to_s + "?").to_sym
114
- rv = rv && (FileTest.respond_to?(method) ? FileTest.send(method, args[:file]) : false)
115
- end
116
- end
117
-
118
- rv
119
- end
120
-
121
- # Delete files/directories.
122
- #
123
- # Arguments:
124
- # * <em>files</em>: List of entries to delete
125
- # * <em>exit_on_fail</em>: Whether abort on failure
126
- # * <em>show_error</em>: Whether show errors occurred
127
- #
128
- # Returns: <em>true</em> if operation had success, <em>false</em> otherwise.
129
- def delete_files!(args)
130
- rv = true
131
- files = args[:files].force_array
132
-
133
- begin
134
- FileUtils.rm_r(files, {:noop => @console.skip_commands, :verbose => @console.skip_commands, :secure => true})
135
- rescue StandardError => e
136
- if args[:show_errors] == true then
137
- if e.message =~ /^Permission denied - (.+)/ then
138
- @console.error("Cannot remove following non writable entry: #{$1}", :dots => false, :fatal => args[:fatal])
139
- elsif e.message =~ /^No such file or directory - (.+)/ then
140
- @console.error("Cannot remove following non existent entry: #{$1}", :dots => false, :fatal => args[:fatal])
141
- end
142
- end
143
-
144
- rv = false
145
- rescue Exception => e
146
- if args[:show_error] == true then
147
- @console.error("Cannot remove following entries:", :dots => false)
148
- @console.indent_region(3) do
149
- files.each do |afile|
150
- @console.write(:msg => afile, :dots => false)
151
- end
152
- end
153
- @console.write(:msg => "#{@console.indentator * @console.indent_level}due to an error: #{e}\n", :dots => false, :fatal => args[:fatal])
154
- end
155
-
156
- rv = false
157
- end
158
-
159
- rv ? rv : @console.status(:fail, :fatal => args[:fatal])
160
- end
161
-
162
- # Create directories (and any missing parent directory).
163
- #
164
- # Arguments:
165
- # * <em>files</em>: List of directories to create
166
- # * <em>mode</em>: Octal mode for newly created directories
167
- # * <em>exit_on_fail</em>: Whether abort on failure
168
- # * <em>show_error</em>: Whether show errors occurred
169
- #
170
- # Returns: <em>true</em> if operation had success, <em>false</em> otherwise.
171
- def create_directories(args)
172
- rv = true
173
- files = args[:files].force_array
174
-
175
- files.each do |file|
176
- if self.file_check?(:files => file, :tests => :exists) then
177
- if self.file_check?(:files => file, :tests => :directory) == true then
178
- @console.error("Cannot create following directory <text style=\"bold white\">#{file}</text> because it already exists.", :dots => false, :fatal => args[:fatal])
179
- else
180
- @console.error("Cannot create following directory <text style=\"bold white\">#{file}</text> because it already exists as a file", :dots => false, :fatal => args[:fatal])
181
- end
182
-
183
- rv = false
184
- end
185
-
186
- if rv then
187
- begin
188
- FileUtils.makedirs(file, {:mode => args[:mode] || 0755, :noop => @console.skip_commands, :verbose => @console.skip_commands})
189
- rescue StandardError => e
190
- if args[:show_errors] && e.message =~ /^Permission denied - (.+)/ then
191
- @console.error("Cannot create following directory in non writable parent: <text style=\"bold white\">#{$1}</text>.", :dots => false, :fatal => args[:fatal])
192
- end
193
-
194
- rv = false
195
- rescue Exception => e
196
- if args[:show_errors] == true then
197
- @console.error("Cannot create following directory:", :dots => false)
198
- @console.indent_region(3) do
199
- files.each do |afile|
200
- @console.write(:msg => afile, :dots => false)
201
- end
202
- end
203
- @console.write("#{@console.indentator * @console.indent_level}due to an error: #{e}\n", :dots => false, :fatal => args[:fatal])
204
- end
205
-
206
- rv = false
207
- end
208
- end
209
-
210
- break if !rv
211
- end
212
-
213
- rv ? rv : @console.status(:fail, :fatal => args[:fatal])
214
- end
215
-
216
- # Copy files to a destination directory.
217
- #
218
- # Arguments:
219
- # * <em>files</em>: List of entries to copy/move
220
- # * <em>dest</em>: Destination file/directory
221
- # * <em>must_move</em>: Move instead of copy
222
- # * <em>dest_is_dir</em>: Whether destination is a directory
223
- # * <em>exit_on_fail</em>: Whether abort on failure
224
- # * <em>show_error</em>: Whether show errors occurred
225
- #
226
- # Returns: <em>true</em> if operation had success, <em>false</em> otherwise.
227
- def copy(args)
228
- rv = true
229
- move = args[:move]
230
- dest = args[:dest]
231
-
232
- # If we are copy or moving to a directory
233
- if args[:destination_is_directory] then
234
- files = (args[:files] || []).force_array
235
- dest += "/" if self.file_check?(:file => dest, :tests => :directory) && dest !~ /\/$/
236
-
237
- files.each do |file|
238
- begin
239
- if move == true then
240
- FileUtils.move(file, dest, {:noop => @console.skip_commands, :verbose => @console.skip_commands})
241
- else
242
- FileUtils.cp_r(file, dest, {:noop => @console.skip_commands, :verbose => @console.skip_commands, :remove_destination => true})
243
- end
244
- rescue StandardError => e
245
- if args[:show_errors] && e.message =~ /^Permission denied - (.+)/ then
246
- self.error("Cannot #{must_move ? "move" : "copy"} entry <text style=\"bold white\">#{file}</text> to non-writable entry <text style=\"bold white\">#{dest}</text>", false, false, false) if m != nil
247
- end
248
-
249
- rv = false
250
- rescue Exception => e
251
- if args[:show_errors] then
252
- @console.error("Cannot #{move ? "move" : "copy"} following entries to <text style=\"bold white\">#{dest}</text>:", :dots => false)
253
- @console.indent_region(3) do
254
- files.each do |afile|
255
- @console.write(:msg => afile, :dots => false)
256
- end
257
- end
258
- @console.write("#{@console.indentator * @console.indent_level}due to an error: #{e}\n", :dots => false, :fatal => args[:fatal])
259
- end
260
-
261
- rv = false
262
- end
263
-
264
- break if !rv
265
- end
266
- else # If we are copying or moving to a file
267
- files = args[:files]
268
-
269
- if !files.kind_of?(String) && !dest.kind_of?(String) == true then
270
- @console.error("Cowtech::Lib::Shell#copy: To copy a single file, both files and dest arguments must be a string.", :dots => false, :fatal => args[:fatal])
271
- rv = false
272
- else
273
- dst_dir = File.dirname(dest)
274
-
275
- if !self.file_check?(:file => dst_dir, :tests => [:exists, :directory]) then
276
- self.create_directories(:files => dst_dir, :mode => 0755, :fatal => args[:fatal], :show_errors => args[:show_errors])
277
- end
278
-
279
- begin
280
- if move then
281
- FileUtils.move(files, dest, {:noop => @console.skip_commands, :verbose => @console.skip_commands})
282
- else
283
- FileUtils.cp(files, dest, {:noop => @console.skip_commands, :verbose => @console.skip_commands})
284
- end
285
- rescue StandardError => e
286
- @console.error("Cannot #{move ? "move" : "copy"} entry <text style=\"bold white\">#{files}</text> to non-writable entry<text style=\"bold white\"> #{dest}</text>", :dots => false, :fatal => args[:fatal]) if args[:show_errors] && (e.message =~ /^Permission denied - (.+)/)
287
- rv = false
288
- rescue Exception => e
289
- @console.error("Cannot #{move ? "move" : "copy"} <text style=\"bold white\">#{files}</text> to <text style=\"bold_white\">#{dest}</text> due to an error: <text style=\"bold red\">#{e}</text>", :dots => false, :fatal => args[:fatal]) if args[:show_errors]
290
- rv = false
291
- end
292
- end
293
- end
294
-
295
- rv ? rv : @console.status(:fail, :fatal => args[:fatal])
296
- end
297
-
298
- # Rename a file.
299
- #
300
- # Arguments:
301
- # * <em>src</em>: The file to rename
302
- # * <em>dst</em>: The new name
303
- # * <em>exit_on_fail</em>: Whether abort on failure
304
- # * <em>show_error</em>: Whether show errors occurred
305
- #
306
- # Returns: <em>true</em> if operation had success, <em>false</em> otherwise.
307
- def rename(args)
308
- rv = true
309
-
310
- if src.is_a?(String) && dst.is_a?(String) then
311
- rv = self.copy(:from => src, :to => dst, :show_errors => args[:show_errors], :fatal => args[:fatal])
312
- else
313
- @console.error("Cowtech::Lib::Shell#rename: Both :src and :dst arguments must be a string.", :dots => false, :fatal => args[:fatal]) if args[:show_error]
314
- rv = false
315
- end
316
-
317
- rv ? rv : @console.status(:fail, :fatal => args[:fatal])
318
- end
319
-
320
- # Returns a list of files in specified paths which matchs one of requested patterns.
321
- #
322
- # Arguments:
323
- # * <em>paths</em>: List of path in which seach
324
- # * <em>patterns</em>: List of requested patterns
325
- #
326
- # Returns: List of found files.
327
- def find_by_pattern(args)
328
- # TODO: E se patterns è vuoto?
329
- rv = []
330
-
331
- paths = args[:paths].force_array
332
- string_patterns = args[:patterns].force_array
333
-
334
- if paths.length > 0 then
335
- # Convert patterns to regexp
336
- patterns = string_patterns.collect do |pattern|
337
- pattern.is_a?(Regexp) ? pattern : Regexp.new(pattern, Regexp::IGNORECASE)
338
- end
339
-
340
- # For each path
341
- paths.each do |path|
342
- Find.find(path) do |file| # Find files
343
- matchs = false
344
-
345
- patterns.each do |pattern| # Match patterns
346
- if file =~ pattern then
347
- matchs = true
348
- break
349
- end
350
- end
351
-
352
- rv << file if matchs
353
- end
354
- end
355
-
356
- rv
357
- end
358
- end
359
-
360
- # Returns a list of files in specified paths which have one of requested extensions.
361
- #
362
- # Arguments:
363
- # * <em>paths</em>: List of path in which seach
364
- # * <em>extensions</em>: List of requested extensions
365
- #
366
- # Returns: List of found files.
367
- def find_by_extension(args)
368
- args[:patterns] = (args[:extensions] || "").force_array.collect do |extension|
369
- Regexp.new(extension + "$", Regexp::IGNORECASE)
370
- end
371
-
372
- self.find_by_pattern(args)
373
- end
374
-
375
- # Creates a new shell
376
- def initialize(console = nil)
377
- console ||= Console.new
378
- @console = console
379
- end
380
- end
381
- end
12
+ module Lib
13
+ # A class which provides some useful method for interaction with files and processes.
14
+ # @author Shogun
15
+ class Shell
16
+ # Run a command.
17
+ #
18
+ # Arguments:
19
+ # * <em>msg</em>: The message to show
20
+ # * <em>command</em>: The command to run
21
+ # * <em>show_msg</em>: If show the exit message
22
+ # * <em>show_exit</em>: If show the exit code
23
+ # * <em>fatal</em>: If abort on failure
24
+ #
25
+ # Returns: An object which the status attribute is the exit status of the process, and the output attribute is the output of the command
26
+ def run(args)
27
+ rv = {status: 0, output: []}
28
+ command = args[:command]
29
+
30
+ @console.write(begin: args[:msg]) if args[:show_msg]
31
+
32
+ if @console.show_commands then
33
+ @console.warn("Will run command: \"#{command}\"", dots: false)
34
+ @console.status(:ok)
35
+ end
36
+
37
+ if !@console.skip_commands then
38
+ rv[:status] = Open4::open4(command + " 2>&1") { |pid, stdin, stdout, stderr|
39
+ stdout.each_line do |line|
40
+ rv[:output] << line
41
+ print line if @console.show_outputs
42
+ end
43
+ }.exitstatus
44
+ end
45
+ rv[:output] = rv[:output].join("\n")
46
+
47
+ @console.status(status: rv[:status] == 0 ? :ok : :fail, fatal: false) if args[:show_exit]
48
+ exit(1) if args[:fatal] && rv[:status] != 0
49
+ rv
50
+ end
51
+
52
+ # Opens a file.
53
+ #
54
+ # Arguments:
55
+ # * <em>name</em>: The file path
56
+ # * <em>mode</em>: Open mode, like the one in File.new
57
+ # * <em>codec</em>: The encoding used to open the file. <b>UNUSED FOR NOW!</b>
58
+ #
59
+ # Returns: A new <em>File</em> object
60
+ def open_file(args)
61
+ begin
62
+ File.new(args[:name], (args[:mode] || "r") + (args[:codec] ? ":#{args[:codec]}" : ""))
63
+ rescue Exception => e
64
+ @console.write(error: "Unable to open file #{name}: #{e}")
65
+ nil
66
+ end
67
+ end
68
+
69
+ # Perform a check on a file or directory.
70
+ #
71
+ # Arguments:
72
+ # * <em>name</em>: The file/directory path
73
+ # * <em>tests</em>: A list of tests to execute
74
+ #
75
+ # Valid tests are:
76
+ #
77
+ # * <em>:fc_exists</em>: Check if the file exists
78
+ # * <em>:fc_read</em>: Check if the file is readable
79
+ # * <em>:fc_write</em>: Check if the file writeable
80
+ # * <em>:fc_exec</em>: Check if the file executable
81
+ # * <em>:fc_dir</em>: Check if the file is a directory
82
+ # * <em>:fc_symlink</em>: Check if the file is symbolic link
83
+ #
84
+ # Returns: <em>true</em> if any of tests had success, <em>false</em> otherwise
85
+ def file_check?(args)
86
+ rv = false
87
+ tests = (args[:tests] || [:exists]).force_array
88
+
89
+ if args[:file] then
90
+ rv = true
91
+ tests.each do |test|
92
+ method = (test.to_s + "?").to_sym
93
+ rv = rv && (FileTest.respond_to?(method) ? FileTest.send(method, args[:file]) : false)
94
+ end
95
+ end
96
+
97
+ rv
98
+ end
99
+
100
+ # Delete files/directories.
101
+ #
102
+ # Arguments:
103
+ # * <em>files</em>: List of entries to delete
104
+ # * <em>exit_on_fail</em>: Whether abort on failure
105
+ # * <em>show_error</em>: Whether show errors occurred
106
+ #
107
+ # Returns: <em>true</em> if operation had success, <em>false</em> otherwise.
108
+ def delete_files!(args)
109
+ rv = true
110
+ files = args[:files].force_array
111
+
112
+ begin
113
+ FileUtils.rm_r(files, {noop: @console.skip_commands, verbose: @console.skip_commands, secure: true})
114
+ rescue StandardError => e
115
+ if args[:show_errors] == true then
116
+ if e.message =~ /^Permission denied - (.+)/ then
117
+ @console.error("Cannot remove following non writable entry: #{$1}", dots: false, fatal: args[:fatal])
118
+ elsif e.message =~ /^No such file or directory - (.+)/ then
119
+ @console.error("Cannot remove following non existent entry: #{$1}", dots: false, fatal: args[:fatal])
120
+ end
121
+ end
122
+
123
+ rv = false
124
+ rescue Exception => e
125
+ if args[:show_error] == true then
126
+ @console.error("Cannot remove following entries:", dots: false)
127
+ @console.indent_region(3) do
128
+ files.each do |afile|
129
+ @console.write(msg: afile, dots: false)
130
+ end
131
+ end
132
+ @console.write(msg: "#{@console.indentator * @console.indent_level}due to an error: #{e}\n", dots: false, fatal: args[:fatal])
133
+ end
134
+
135
+ rv = false
136
+ end
137
+
138
+ rv ? rv : @console.status(:fail, fatal: args[:fatal])
139
+ end
140
+
141
+ # Create directories (and any missing parent directory).
142
+ #
143
+ # Arguments:
144
+ # * <em>files</em>: List of directories to create
145
+ # * <em>mode</em>: Octal mode for newly created directories
146
+ # * <em>exit_on_fail</em>: Whether abort on failure
147
+ # * <em>show_error</em>: Whether show errors occurred
148
+ #
149
+ # Returns: <em>true</em> if operation had success, <em>false</em> otherwise.
150
+ def create_directories(args)
151
+ rv = true
152
+ files = args[:files].force_array
153
+
154
+ files.each do |file|
155
+ if self.file_check?(files: file, tests: :exists) then
156
+ if self.file_check?(files: file, tests: :directory) == true then
157
+ @console.error("Cannot create following directory <text style=\"bold white\">#{file}</text> because it already exists.", dots: false, fatal: args[:fatal])
158
+ else
159
+ @console.error("Cannot create following directory <text style=\"bold white\">#{file}</text> because it already exists as a file", dots: false, fatal: args[:fatal])
160
+ end
161
+
162
+ rv = false
163
+ end
164
+
165
+ if rv then
166
+ begin
167
+ FileUtils.makedirs(file, {mode: args[:mode] || 0755, noop: @console.skip_commands, verbose: @console.skip_commands})
168
+ rescue StandardError => e
169
+ if args[:show_errors] && e.message =~ /^Permission denied - (.+)/ then
170
+ @console.error("Cannot create following directory in non writable parent: <text style=\"bold white\">#{$1}</text>.", dots: false, fatal: args[:fatal])
171
+ end
172
+
173
+ rv = false
174
+ rescue Exception => e
175
+ if args[:show_errors] == true then
176
+ @console.error("Cannot create following directory:", dots: false)
177
+ @console.indent_region(3) do
178
+ files.each do |afile|
179
+ @console.write(msg: afile, dots: false)
180
+ end
181
+ end
182
+ @console.write("#{@console.indentator * @console.indent_level}due to an error: #{e}\n", dots: false, fatal: args[:fatal])
183
+ end
184
+
185
+ rv = false
186
+ end
187
+ end
188
+
189
+ break if !rv
190
+ end
191
+
192
+ rv ? rv : @console.status(:fail, fatal: args[:fatal])
193
+ end
194
+
195
+ # Copy files to a destination directory.
196
+ #
197
+ # Arguments:
198
+ # * <em>files</em>: List of entries to copy/move
199
+ # * <em>dest</em>: Destination file/directory
200
+ # * <em>must_move</em>: Move instead of copy
201
+ # * <em>dest_is_dir</em>: Whether destination is a directory
202
+ # * <em>exit_on_fail</em>: Whether abort on failure
203
+ # * <em>show_error</em>: Whether show errors occurred
204
+ #
205
+ # Returns: <em>true</em> if operation had success, <em>false</em> otherwise.
206
+ def copy(args)
207
+ rv = true
208
+ move = args[:move]
209
+ dest = args[:dest]
210
+
211
+ # If we are copy or moving to a directory
212
+ if args[:destination_is_directory] then
213
+ files = (args[:files] || []).force_array
214
+ dest += "/" if self.file_check?(file: dest, tests: :directory) && dest !~ /\/$/
215
+
216
+ files.each do |file|
217
+ begin
218
+ if move == true then
219
+ FileUtils.move(file, dest, {noop: @console.skip_commands, verbose: @console.skip_commands})
220
+ else
221
+ FileUtils.cp_r(file, dest, {noop: @console.skip_commands, verbose: @console.skip_commands, remove_destination: true})
222
+ end
223
+ rescue StandardError => e
224
+ if args[:show_errors] && e.message =~ /^Permission denied - (.+)/ then
225
+ self.error("Cannot #{must_move ? "move" : "copy"} entry <text style=\"bold white\">#{file}</text> to non-writable entry <text style=\"bold white\">#{dest}</text>", false, false, false) if m != nil
226
+ end
227
+
228
+ rv = false
229
+ rescue Exception => e
230
+ if args[:show_errors] then
231
+ @console.error("Cannot #{move ? "move" : "copy"} following entries to <text style=\"bold white\">#{dest}</text>:", dots: false)
232
+ @console.indent_region(3) do
233
+ files.each do |afile|
234
+ @console.write(msg: afile, dots: false)
235
+ end
236
+ end
237
+ @console.write("#{@console.indentator * @console.indent_level}due to an error: #{e}\n", dots: false, fatal: args[:fatal])
238
+ end
239
+
240
+ rv = false
241
+ end
242
+
243
+ break if !rv
244
+ end
245
+ else # If we are copying or moving to a file
246
+ files = args[:files]
247
+
248
+ if !files.kind_of?(String) && !dest.kind_of?(String) == true then
249
+ @console.error("Cowtech::Lib::Shell#copy: To copy a single file, both files and dest arguments must be a string.", dots: false, fatal: args[:fatal])
250
+ rv = false
251
+ else
252
+ dst_dir = File.dirname(dest)
253
+
254
+ if !self.file_check?(file: dst_dir, tests: [:exists, :directory]) then
255
+ self.create_directories(files: dst_dir, mode: 0755, fatal: args[:fatal], show_errors: args[:show_errors])
256
+ end
257
+
258
+ begin
259
+ if move then
260
+ FileUtils.move(files, dest, {noop: @console.skip_commands, verbose: @console.skip_commands})
261
+ else
262
+ FileUtils.cp(files, dest, {noop: @console.skip_commands, verbose: @console.skip_commands})
263
+ end
264
+ rescue StandardError => e
265
+ @console.error("Cannot #{move ? "move" : "copy"} entry <text style=\"bold white\">#{files}</text> to non-writable entry<text style=\"bold white\"> #{dest}</text>", dots: false, fatal: args[:fatal]) if args[:show_errors] && (e.message =~ /^Permission denied - (.+)/)
266
+ rv = false
267
+ rescue Exception => e
268
+ @console.error("Cannot #{move ? "move" : "copy"} <text style=\"bold white\">#{files}</text> to <text style=\"bold_white\">#{dest}</text> due to an error: <text style=\"bold red\">#{e}</text>", dots: false, fatal: args[:fatal]) if args[:show_errors]
269
+ rv = false
270
+ end
271
+ end
272
+ end
273
+
274
+ rv ? rv : @console.status(:fail, fatal: args[:fatal])
275
+ end
276
+
277
+ # Rename a file.
278
+ #
279
+ # Arguments:
280
+ # * <em>src</em>: The file to rename
281
+ # * <em>dst</em>: The new name
282
+ # * <em>exit_on_fail</em>: Whether abort on failure
283
+ # * <em>show_error</em>: Whether show errors occurred
284
+ #
285
+ # Returns: <em>true</em> if operation had success, <em>false</em> otherwise.
286
+ def rename(args)
287
+ rv = true
288
+
289
+ if src.is_a?(String) && dst.is_a?(String) then
290
+ rv = self.copy(from: src, to: dst, show_errors: args[:show_errors], fatal: args[:fatal])
291
+ else
292
+ @console.error("Cowtech::Lib::Shell#rename: Both :src and :dst arguments must be a string.", dots: false, fatal: args[:fatal]) if args[:show_error]
293
+ rv = false
294
+ end
295
+
296
+ rv ? rv : @console.status(:fail, fatal: args[:fatal])
297
+ end
298
+
299
+ # Returns a list of files in specified paths which matchs one of requested patterns.
300
+ #
301
+ # Arguments:
302
+ # * <em>paths</em>: List of path in which seach
303
+ # * <em>patterns</em>: List of requested patterns
304
+ #
305
+ # Returns: List of found files.
306
+ def find_by_pattern(args)
307
+ # TODO: E se patterns è vuoto?
308
+ rv = []
309
+
310
+ paths = args[:paths].force_array
311
+ string_patterns = args[:patterns].force_array
312
+
313
+ if paths.length > 0 then
314
+ # Convert patterns to regexp
315
+ patterns = string_patterns.collect do |pattern|
316
+ pattern.is_a?(Regexp) ? pattern : Regexp.new(pattern, Regexp::IGNORECASE)
317
+ end
318
+
319
+ # For each path
320
+ paths.each do |path|
321
+ Find.find(path) do |file| # Find files
322
+ matchs = false
323
+
324
+ patterns.each do |pattern| # Match patterns
325
+ if file =~ pattern then
326
+ matchs = true
327
+ break
328
+ end
329
+ end
330
+
331
+ rv << file if matchs
332
+ end
333
+ end
334
+
335
+ rv
336
+ end
337
+ end
338
+
339
+ # Returns a list of files in specified paths which have one of requested extensions.
340
+ #
341
+ # Arguments:
342
+ # * <em>paths</em>: List of path in which seach
343
+ # * <em>extensions</em>: List of requested extensions
344
+ #
345
+ # Returns: List of found files.
346
+ def find_by_extension(args)
347
+ args[:patterns] = (args[:extensions] || "").force_array.collect do |extension|
348
+ Regexp.new(extension + "$", Regexp::IGNORECASE)
349
+ end
350
+
351
+ self.find_by_pattern(args)
352
+ end
353
+
354
+ # Creates a new shell
355
+ def initialize(console = nil)
356
+ console ||= Console.new
357
+ @console = console
358
+ end
359
+ end
360
+ end
382
361
  end