cowtech-lib 1.9.8.1 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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