ptools 1.2.2-universal-mingw32 → 1.2.3-universal-mingw32

Sign up to get free protection for your applications and to get access to all the features.
data/lib/ptools.rb CHANGED
@@ -1,429 +1,429 @@
1
- require 'rbconfig'
2
- require 'win32/file' if File::ALT_SEPARATOR
3
-
4
- class File
5
- # The version of the ptools library.
6
- PTOOLS_VERSION = '1.2.2'
7
-
8
- # :stopdoc:
9
-
10
- # The WIN32EXTS string is used as part of a Dir[] call in certain methods.
11
- if File::ALT_SEPARATOR
12
- MSWINDOWS = true
13
- if ENV['PATHEXT']
14
- WIN32EXTS = ('.{' + ENV['PATHEXT'].tr(';', ',').tr('.','') + '}').downcase
15
- else
16
- WIN32EXTS = '.{exe,com,bat}'
17
- end
18
- else
19
- MSWINDOWS = false
20
- end
21
-
22
- IMAGE_EXT = %w[.bmp .gif .jpg .jpeg .png]
23
-
24
- # :startdoc:
25
-
26
- # Returns whether or not the file is an image. Only JPEG, PNG, BMP and
27
- # GIF are checked against.
28
- #
29
- # This method does some simple read and extension checks. For a version
30
- # that is more robust, but which depends on a 3rd party C library (and is
31
- # difficult to build on MS Windows), see the 'filemagic' library, available
32
- # on the RAA.
33
- #
34
- # Examples:
35
- #
36
- # File.image?('somefile.jpg') # => true
37
- # File.image?('somefile.txt') # => true
38
- #--
39
- # The approach I used here is based on information found at
40
- # http://en.wikipedia.org/wiki/Magic_number_(programming)
41
- #
42
- def self.image?(file)
43
- bool = IMAGE_EXT.include?(File.extname(file).downcase) # Match ext
44
- bool = bmp?(file) || jpg?(file) || png?(file) || gif?(file) # Check data
45
- bool
46
- end
47
-
48
- # Returns the name of the null device (aka bitbucket) on your platform.
49
- #
50
- # Examples:
51
- #
52
- # # On Linux
53
- # File.null # => '/dev/null'
54
- #
55
- # # On MS Windows
56
- # File.null # => 'NUL'
57
- #--
58
- # The values I used here are based on information from
59
- # http://en.wikipedia.org/wiki//dev/null
60
- #
61
- def self.null
62
- case RbConfig::CONFIG['host_os']
63
- when /mswin|win32|msdos|cygwin|mingw|windows/i
64
- 'NUL'
65
- when /amiga/i
66
- 'NIL:'
67
- when /openvms/i
68
- 'NL:'
69
- else
70
- '/dev/null'
71
- end
72
- end
73
-
74
- class << self
75
- alias null_device null
76
- end
77
-
78
- # Returns whether or not +file+ is a binary file. Note that this is
79
- # not guaranteed to be 100% accurate. It performs a "best guess" based
80
- # on a simple test of the first +File.blksize+ characters.
81
- #
82
- # Example:
83
- #
84
- # File.binary?('somefile.exe') # => true
85
- # File.binary?('somefile.txt') # => false
86
- #--
87
- # Based on code originally provided by Ryan Davis (which, in turn, is
88
- # based on Perl's -B switch).
89
- #
90
- def self.binary?(file)
91
- s = (File.read(file, File.stat(file).blksize) || "").split(//)
92
- ((s.size - s.grep(" ".."~").size) / s.size.to_f) > 0.30
93
- end
94
-
95
- # Looks for the first occurrence of +program+ within +path+.
96
- #
97
- # On Windows, it looks for executables ending with the suffixes defined
98
- # in your PATHEXT environment variable, or '.exe', '.bat' and '.com' if
99
- # that isn't defined, which you may optionally include in +program+.
100
- #
101
- # Returns nil if not found.
102
- #
103
- # Examples:
104
- #
105
- # File.which('ruby') # => '/usr/local/bin/ruby'
106
- # File.which('foo') # => nil
107
- #
108
- def self.which(program, path=ENV['PATH'])
109
- if path.nil? || path.empty?
110
- raise ArgumentError, "path cannot be empty"
111
- end
112
-
113
- # Bail out early if an absolute path is provided.
114
- if program =~ /^\/|^[a-z]:[\\\/]/i
115
- program += WIN32EXTS if MSWINDOWS && File.extname(program).empty?
116
- found = Dir[program].first
117
- if found && File.executable?(found) && !File.directory?(found)
118
- return found
119
- else
120
- return nil
121
- end
122
- end
123
-
124
- # Iterate over each path glob the dir + program.
125
- path.split(File::PATH_SEPARATOR).each{ |dir|
126
- next unless File.exists?(dir) # In case of bogus second argument
127
- file = File.join(dir, program)
128
-
129
- # Dir[] doesn't handle backslashes properly, so convert them. Also, if
130
- # the program name doesn't have an extension, try them all.
131
- if MSWINDOWS
132
- file = file.tr("\\", "/")
133
- file += WIN32EXTS if File.extname(program).empty?
134
- end
135
-
136
- found = Dir[file].first
137
-
138
- # Convert all forward slashes to backslashes if supported
139
- if found && File.executable?(found) && !File.directory?(found)
140
- found.tr!(File::SEPARATOR, File::ALT_SEPARATOR) if File::ALT_SEPARATOR
141
- return found
142
- end
143
- }
144
-
145
- nil
146
- end
147
-
148
- # Returns an array of each +program+ within +path+, or nil if it cannot
149
- # be found.
150
- #
151
- # On Windows, it looks for executables ending with the suffixes defined
152
- # in your PATHEXT environment variable, or '.exe', '.bat' and '.com' if
153
- # that isn't defined, which you may optionally include in +program+.
154
- #
155
- # Examples:
156
- #
157
- # File.whereis('ruby') # => ['/usr/bin/ruby', '/usr/local/bin/ruby']
158
- # File.whereis('foo') # => nil
159
- #
160
- def self.whereis(program, path=ENV['PATH'])
161
- if path.nil? || path.empty?
162
- raise ArgumentError, "path cannot be empty"
163
- end
164
-
165
- paths = []
166
-
167
- # Bail out early if an absolute path is provided.
168
- if program =~ /^\/|^[a-z]:[\\\/]/i
169
- program += WIN32EXTS if MSWINDOWS && File.extname(program).empty?
170
- program = program.tr("\\", '/') if MSWINDOWS
171
- found = Dir[program]
172
- if found[0] && File.executable?(found[0]) && !File.directory?(found[0])
173
- if File::ALT_SEPARATOR
174
- return found.map{ |f| f.tr('/', "\\") }
175
- else
176
- return found
177
- end
178
- else
179
- return nil
180
- end
181
- end
182
-
183
- # Iterate over each path glob the dir + program.
184
- path.split(File::PATH_SEPARATOR).each{ |dir|
185
- next unless File.exists?(dir) # In case of bogus second argument
186
- file = File.join(dir, program)
187
-
188
- # Dir[] doesn't handle backslashes properly, so convert them. Also, if
189
- # the program name doesn't have an extension, try them all.
190
- if MSWINDOWS
191
- file = file.tr("\\", "/")
192
- file += WIN32EXTS if File.extname(program).empty?
193
- end
194
-
195
- found = Dir[file].first
196
-
197
- # Convert all forward slashes to backslashes if supported
198
- if found && File.executable?(found) && !File.directory?(found)
199
- found.tr!(File::SEPARATOR, File::ALT_SEPARATOR) if File::ALT_SEPARATOR
200
- paths << found
201
- end
202
- }
203
-
204
- paths.empty? ? nil : paths.uniq
205
- end
206
-
207
- # In block form, yields the first +num_lines+ from +filename+. In non-block
208
- # form, returns an Array of +num_lines+
209
- #
210
- # Examples:
211
- #
212
- # # Return an array
213
- # File.head('somefile.txt') # => ['This is line1', 'This is line2', ...]
214
- #
215
- # # Use a block
216
- # File.head('somefile.txt'){ |line| puts line }
217
- #
218
- def self.head(filename, num_lines=10)
219
- a = []
220
-
221
- IO.foreach(filename){ |line|
222
- break if num_lines <= 0
223
- num_lines -= 1
224
- if block_given?
225
- yield line
226
- else
227
- a << line
228
- end
229
- }
230
-
231
- return a.empty? ? nil : a # Return nil in block form
232
- end
233
-
234
- # In block form, yields line +from+ up to line +to+. In non-block form
235
- # returns an Array of lines from +from+ to +to+.
236
- #
237
- def self.middle(filename, from=10, to=20)
238
- if block_given?
239
- IO.readlines(filename)[from-1..to-1].each{ |line| yield line }
240
- else
241
- IO.readlines(filename)[from-1..to-1]
242
- end
243
- end
244
-
245
- # In block form, yields the last +num_lines+ of file +filename+.
246
- # In non-block form, it returns the lines as an array.
247
- #
248
- # Note that this method slurps the entire file, so I don't recommend it
249
- # for very large files. Also note that 'tail -f' functionality is not
250
- # present. See the 'file-tail' library for that.
251
- #
252
- # Example:
253
- #
254
- # File.tail('somefile.txt') # => ['This is line7', 'This is line8', ...]
255
- #
256
- def self.tail(filename, num_lines=10)
257
- if block_given?
258
- IO.readlines(filename).reverse[0..num_lines-1].reverse.each{ |line|
259
- yield line
260
- }
261
- else
262
- IO.readlines(filename).reverse[0..num_lines-1].reverse
263
- end
264
- end
265
-
266
- # Converts a text file from one OS platform format to another, ala
267
- # 'dos2unix'. The possible values for +platform+ include:
268
- #
269
- # * MS Windows -> dos, windows, win32, mswin
270
- # * Unix/BSD -> unix, linux, bsd
271
- # * Mac -> mac, macintosh, apple, osx
272
- #
273
- # Note that this method is only valid for an ftype of "file". Otherwise a
274
- # TypeError will be raised. If an invalid format value is received, an
275
- # ArgumentError is raised.
276
- #
277
- def self.nl_convert(old_file, new_file = old_file, platform = 'dos')
278
- unless File::Stat.new(old_file).file?
279
- raise ArgumentError, 'Only valid for plain text files'
280
- end
281
-
282
- case platform
283
- when /dos|windows|win32|mswin|cygwin|mingw/i
284
- format = "\cM\cJ"
285
- when /unix|linux|bsd/i
286
- format = "\cJ"
287
- when /mac|apple|macintosh|osx/i
288
- format = "\cM"
289
- else
290
- raise ArgumentError, "Invalid platform string"
291
- end
292
-
293
- orig = $\ # AKA $OUTPUT_RECORD_SEPARATOR
294
- $\ = format
295
-
296
- if old_file == new_file
297
- require 'fileutils'
298
- require 'tempfile'
299
-
300
- begin
301
- temp_name = Time.new.strftime("%Y%m%d%H%M%S")
302
- tf = Tempfile.new('ruby_temp_' + temp_name)
303
- tf.open
304
-
305
- IO.foreach(old_file){ |line|
306
- line.chomp!
307
- tf.print line
308
- }
309
- ensure
310
- tf.close if tf && !tf.closed?
311
- end
312
-
313
- File.delete(old_file)
314
- FileUtils.cp(tf.path, old_file)
315
- else
316
- begin
317
- nf = File.new(new_file, 'w')
318
- IO.foreach(old_file){ |line|
319
- line.chomp!
320
- nf.print line
321
- }
322
- ensure
323
- nf.close if nf && !nf.closed?
324
- end
325
- end
326
-
327
- $\ = orig
328
- self
329
- end
330
-
331
- # Changes the access and modification time if present, or creates a 0
332
- # byte file +filename+ if it doesn't already exist.
333
- #
334
- def self.touch(filename)
335
- if File.exists?(filename)
336
- time = Time.now
337
- File.utime(time, time, filename)
338
- else
339
- File.open(filename, 'w'){}
340
- end
341
- self
342
- end
343
-
344
- # With no arguments, returns a four element array consisting of the number
345
- # of bytes, characters, words and lines in filename, respectively.
346
- #
347
- # Valid options are 'bytes', 'characters' (or just 'chars'), 'words' and
348
- # 'lines'.
349
- #
350
- def self.wc(filename, option='all')
351
- option.downcase!
352
- valid = %w/all bytes characters chars lines words/
353
-
354
- unless valid.include?(option)
355
- raise ArgumentError, "Invalid option: '#{option}'"
356
- end
357
-
358
- n = 0
359
-
360
- if option == 'lines'
361
- IO.foreach(filename){ n += 1 }
362
- return n
363
- elsif option == 'bytes'
364
- File.open(filename){ |f|
365
- f.each_byte{ n += 1 }
366
- }
367
- return n
368
- elsif option == 'characters' || option == 'chars'
369
- File.open(filename){ |f|
370
- while f.getc
371
- n += 1
372
- end
373
- }
374
- return n
375
- elsif option == 'words'
376
- IO.foreach(filename){ |line|
377
- n += line.split.length
378
- }
379
- return n
380
- else
381
- bytes,chars,lines,words = 0,0,0,0
382
- IO.foreach(filename){ |line|
383
- lines += 1
384
- words += line.split.length
385
- chars += line.split('').length
386
- }
387
- File.open(filename){ |f|
388
- while f.getc
389
- bytes += 1
390
- end
391
- }
392
- return [bytes,chars,words,lines]
393
- end
394
- end
395
-
396
- # Already provided by win32-file on MS Windows
397
- unless respond_to?(:sparse?)
398
- # Returns whether or not +file+ is a sparse file.
399
- #
400
- # A sparse file is a any file where its size is greater than the number
401
- # of 512k blocks it consumes, i.e. its apparent and actual file size is
402
- # not the same.
403
- #
404
- # See http://en.wikipedia.org/wiki/Sparse_file for more information.
405
- #
406
- def self.sparse?(file)
407
- stats = File.stat(file)
408
- stats.size > stats.blocks * 512
409
- end
410
- end
411
-
412
- private
413
-
414
- def self.bmp?(file)
415
- IO.read(file, 3) == "BM6"
416
- end
417
-
418
- def self.jpg?(file)
419
- IO.read(file, 10) == "\377\330\377\340\000\020JFIF"
420
- end
421
-
422
- def self.png?(file)
423
- IO.read(file, 4) == "\211PNG"
424
- end
425
-
426
- def self.gif?(file)
427
- ['GIF89a', 'GIF97a'].include?(IO.read(file, 6))
428
- end
429
- end
1
+ require 'rbconfig'
2
+ require 'win32/file' if File::ALT_SEPARATOR
3
+
4
+ class File
5
+ # The version of the ptools library.
6
+ PTOOLS_VERSION = '1.2.3'
7
+
8
+ # :stopdoc:
9
+
10
+ # The WIN32EXTS string is used as part of a Dir[] call in certain methods.
11
+ if File::ALT_SEPARATOR
12
+ MSWINDOWS = true
13
+ if ENV['PATHEXT']
14
+ WIN32EXTS = ('.{' + ENV['PATHEXT'].tr(';', ',').tr('.','') + '}').downcase
15
+ else
16
+ WIN32EXTS = '.{exe,com,bat}'
17
+ end
18
+ else
19
+ MSWINDOWS = false
20
+ end
21
+
22
+ IMAGE_EXT = %w[.bmp .gif .jpg .jpeg .png]
23
+
24
+ # :startdoc:
25
+
26
+ # Returns whether or not the file is an image. Only JPEG, PNG, BMP and
27
+ # GIF are checked against.
28
+ #
29
+ # This method does some simple read and extension checks. For a version
30
+ # that is more robust, but which depends on a 3rd party C library (and is
31
+ # difficult to build on MS Windows), see the 'filemagic' library, available
32
+ # on the RAA.
33
+ #
34
+ # Examples:
35
+ #
36
+ # File.image?('somefile.jpg') # => true
37
+ # File.image?('somefile.txt') # => true
38
+ #--
39
+ # The approach I used here is based on information found at
40
+ # http://en.wikipedia.org/wiki/Magic_number_(programming)
41
+ #
42
+ def self.image?(file)
43
+ bool = IMAGE_EXT.include?(File.extname(file).downcase) # Match ext
44
+ bool = bmp?(file) || jpg?(file) || png?(file) || gif?(file) # Check data
45
+ bool
46
+ end
47
+
48
+ # Returns the name of the null device (aka bitbucket) on your platform.
49
+ #
50
+ # Examples:
51
+ #
52
+ # # On Linux
53
+ # File.null # => '/dev/null'
54
+ #
55
+ # # On MS Windows
56
+ # File.null # => 'NUL'
57
+ #--
58
+ # The values I used here are based on information from
59
+ # http://en.wikipedia.org/wiki//dev/null
60
+ #
61
+ def self.null
62
+ case RbConfig::CONFIG['host_os']
63
+ when /mswin|win32|msdos|cygwin|mingw|windows/i
64
+ 'NUL'
65
+ when /amiga/i
66
+ 'NIL:'
67
+ when /openvms/i
68
+ 'NL:'
69
+ else
70
+ '/dev/null'
71
+ end
72
+ end
73
+
74
+ class << self
75
+ alias null_device null
76
+ end
77
+
78
+ # Returns whether or not +file+ is a binary file. Note that this is
79
+ # not guaranteed to be 100% accurate. It performs a "best guess" based
80
+ # on a simple test of the first +File.blksize+ characters.
81
+ #
82
+ # Example:
83
+ #
84
+ # File.binary?('somefile.exe') # => true
85
+ # File.binary?('somefile.txt') # => false
86
+ #--
87
+ # Based on code originally provided by Ryan Davis (which, in turn, is
88
+ # based on Perl's -B switch).
89
+ #
90
+ def self.binary?(file)
91
+ s = (File.read(file, File.stat(file).blksize) || "").encode('US-ASCII', :undef => :replace).split(//)
92
+ ((s.size - s.grep(" ".."~").size) / s.size.to_f) > 0.30
93
+ end
94
+
95
+ # Looks for the first occurrence of +program+ within +path+.
96
+ #
97
+ # On Windows, it looks for executables ending with the suffixes defined
98
+ # in your PATHEXT environment variable, or '.exe', '.bat' and '.com' if
99
+ # that isn't defined, which you may optionally include in +program+.
100
+ #
101
+ # Returns nil if not found.
102
+ #
103
+ # Examples:
104
+ #
105
+ # File.which('ruby') # => '/usr/local/bin/ruby'
106
+ # File.which('foo') # => nil
107
+ #
108
+ def self.which(program, path=ENV['PATH'])
109
+ if path.nil? || path.empty?
110
+ raise ArgumentError, "path cannot be empty"
111
+ end
112
+
113
+ # Bail out early if an absolute path is provided.
114
+ if program =~ /^\/|^[a-z]:[\\\/]/i
115
+ program += WIN32EXTS if MSWINDOWS && File.extname(program).empty?
116
+ found = Dir[program].first
117
+ if found && File.executable?(found) && !File.directory?(found)
118
+ return found
119
+ else
120
+ return nil
121
+ end
122
+ end
123
+
124
+ # Iterate over each path glob the dir + program.
125
+ path.split(File::PATH_SEPARATOR).each{ |dir|
126
+ next unless File.exists?(dir) # In case of bogus second argument
127
+ file = File.join(dir, program)
128
+
129
+ # Dir[] doesn't handle backslashes properly, so convert them. Also, if
130
+ # the program name doesn't have an extension, try them all.
131
+ if MSWINDOWS
132
+ file = file.tr("\\", "/")
133
+ file += WIN32EXTS if File.extname(program).empty?
134
+ end
135
+
136
+ found = Dir[file].first
137
+
138
+ # Convert all forward slashes to backslashes if supported
139
+ if found && File.executable?(found) && !File.directory?(found)
140
+ found.tr!(File::SEPARATOR, File::ALT_SEPARATOR) if File::ALT_SEPARATOR
141
+ return found
142
+ end
143
+ }
144
+
145
+ nil
146
+ end
147
+
148
+ # Returns an array of each +program+ within +path+, or nil if it cannot
149
+ # be found.
150
+ #
151
+ # On Windows, it looks for executables ending with the suffixes defined
152
+ # in your PATHEXT environment variable, or '.exe', '.bat' and '.com' if
153
+ # that isn't defined, which you may optionally include in +program+.
154
+ #
155
+ # Examples:
156
+ #
157
+ # File.whereis('ruby') # => ['/usr/bin/ruby', '/usr/local/bin/ruby']
158
+ # File.whereis('foo') # => nil
159
+ #
160
+ def self.whereis(program, path=ENV['PATH'])
161
+ if path.nil? || path.empty?
162
+ raise ArgumentError, "path cannot be empty"
163
+ end
164
+
165
+ paths = []
166
+
167
+ # Bail out early if an absolute path is provided.
168
+ if program =~ /^\/|^[a-z]:[\\\/]/i
169
+ program += WIN32EXTS if MSWINDOWS && File.extname(program).empty?
170
+ program = program.tr("\\", '/') if MSWINDOWS
171
+ found = Dir[program]
172
+ if found[0] && File.executable?(found[0]) && !File.directory?(found[0])
173
+ if File::ALT_SEPARATOR
174
+ return found.map{ |f| f.tr('/', "\\") }
175
+ else
176
+ return found
177
+ end
178
+ else
179
+ return nil
180
+ end
181
+ end
182
+
183
+ # Iterate over each path glob the dir + program.
184
+ path.split(File::PATH_SEPARATOR).each{ |dir|
185
+ next unless File.exists?(dir) # In case of bogus second argument
186
+ file = File.join(dir, program)
187
+
188
+ # Dir[] doesn't handle backslashes properly, so convert them. Also, if
189
+ # the program name doesn't have an extension, try them all.
190
+ if MSWINDOWS
191
+ file = file.tr("\\", "/")
192
+ file += WIN32EXTS if File.extname(program).empty?
193
+ end
194
+
195
+ found = Dir[file].first
196
+
197
+ # Convert all forward slashes to backslashes if supported
198
+ if found && File.executable?(found) && !File.directory?(found)
199
+ found.tr!(File::SEPARATOR, File::ALT_SEPARATOR) if File::ALT_SEPARATOR
200
+ paths << found
201
+ end
202
+ }
203
+
204
+ paths.empty? ? nil : paths.uniq
205
+ end
206
+
207
+ # In block form, yields the first +num_lines+ from +filename+. In non-block
208
+ # form, returns an Array of +num_lines+
209
+ #
210
+ # Examples:
211
+ #
212
+ # # Return an array
213
+ # File.head('somefile.txt') # => ['This is line1', 'This is line2', ...]
214
+ #
215
+ # # Use a block
216
+ # File.head('somefile.txt'){ |line| puts line }
217
+ #
218
+ def self.head(filename, num_lines=10)
219
+ a = []
220
+
221
+ IO.foreach(filename){ |line|
222
+ break if num_lines <= 0
223
+ num_lines -= 1
224
+ if block_given?
225
+ yield line
226
+ else
227
+ a << line
228
+ end
229
+ }
230
+
231
+ return a.empty? ? nil : a # Return nil in block form
232
+ end
233
+
234
+ # In block form, yields line +from+ up to line +to+. In non-block form
235
+ # returns an Array of lines from +from+ to +to+.
236
+ #
237
+ def self.middle(filename, from=10, to=20)
238
+ if block_given?
239
+ IO.readlines(filename)[from-1..to-1].each{ |line| yield line }
240
+ else
241
+ IO.readlines(filename)[from-1..to-1]
242
+ end
243
+ end
244
+
245
+ # In block form, yields the last +num_lines+ of file +filename+.
246
+ # In non-block form, it returns the lines as an array.
247
+ #
248
+ # Note that this method slurps the entire file, so I don't recommend it
249
+ # for very large files. Also note that 'tail -f' functionality is not
250
+ # present. See the 'file-tail' library for that.
251
+ #
252
+ # Example:
253
+ #
254
+ # File.tail('somefile.txt') # => ['This is line7', 'This is line8', ...]
255
+ #
256
+ def self.tail(filename, num_lines=10)
257
+ if block_given?
258
+ IO.readlines(filename).reverse[0..num_lines-1].reverse.each{ |line|
259
+ yield line
260
+ }
261
+ else
262
+ IO.readlines(filename).reverse[0..num_lines-1].reverse
263
+ end
264
+ end
265
+
266
+ # Converts a text file from one OS platform format to another, ala
267
+ # 'dos2unix'. The possible values for +platform+ include:
268
+ #
269
+ # * MS Windows -> dos, windows, win32, mswin
270
+ # * Unix/BSD -> unix, linux, bsd
271
+ # * Mac -> mac, macintosh, apple, osx
272
+ #
273
+ # Note that this method is only valid for an ftype of "file". Otherwise a
274
+ # TypeError will be raised. If an invalid format value is received, an
275
+ # ArgumentError is raised.
276
+ #
277
+ def self.nl_convert(old_file, new_file = old_file, platform = 'dos')
278
+ unless File::Stat.new(old_file).file?
279
+ raise ArgumentError, 'Only valid for plain text files'
280
+ end
281
+
282
+ case platform
283
+ when /dos|windows|win32|mswin|cygwin|mingw/i
284
+ format = "\cM\cJ"
285
+ when /unix|linux|bsd/i
286
+ format = "\cJ"
287
+ when /mac|apple|macintosh|osx/i
288
+ format = "\cM"
289
+ else
290
+ raise ArgumentError, "Invalid platform string"
291
+ end
292
+
293
+ orig = $\ # AKA $OUTPUT_RECORD_SEPARATOR
294
+ $\ = format
295
+
296
+ if old_file == new_file
297
+ require 'fileutils'
298
+ require 'tempfile'
299
+
300
+ begin
301
+ temp_name = Time.new.strftime("%Y%m%d%H%M%S")
302
+ tf = Tempfile.new('ruby_temp_' + temp_name)
303
+ tf.open
304
+
305
+ IO.foreach(old_file){ |line|
306
+ line.chomp!
307
+ tf.print line
308
+ }
309
+ ensure
310
+ tf.close if tf && !tf.closed?
311
+ end
312
+
313
+ File.delete(old_file)
314
+ FileUtils.cp(tf.path, old_file)
315
+ else
316
+ begin
317
+ nf = File.new(new_file, 'w')
318
+ IO.foreach(old_file){ |line|
319
+ line.chomp!
320
+ nf.print line
321
+ }
322
+ ensure
323
+ nf.close if nf && !nf.closed?
324
+ end
325
+ end
326
+
327
+ $\ = orig
328
+ self
329
+ end
330
+
331
+ # Changes the access and modification time if present, or creates a 0
332
+ # byte file +filename+ if it doesn't already exist.
333
+ #
334
+ def self.touch(filename)
335
+ if File.exists?(filename)
336
+ time = Time.now
337
+ File.utime(time, time, filename)
338
+ else
339
+ File.open(filename, 'w'){}
340
+ end
341
+ self
342
+ end
343
+
344
+ # With no arguments, returns a four element array consisting of the number
345
+ # of bytes, characters, words and lines in filename, respectively.
346
+ #
347
+ # Valid options are 'bytes', 'characters' (or just 'chars'), 'words' and
348
+ # 'lines'.
349
+ #
350
+ def self.wc(filename, option='all')
351
+ option.downcase!
352
+ valid = %w/all bytes characters chars lines words/
353
+
354
+ unless valid.include?(option)
355
+ raise ArgumentError, "Invalid option: '#{option}'"
356
+ end
357
+
358
+ n = 0
359
+
360
+ if option == 'lines'
361
+ IO.foreach(filename){ n += 1 }
362
+ return n
363
+ elsif option == 'bytes'
364
+ File.open(filename){ |f|
365
+ f.each_byte{ n += 1 }
366
+ }
367
+ return n
368
+ elsif option == 'characters' || option == 'chars'
369
+ File.open(filename){ |f|
370
+ while f.getc
371
+ n += 1
372
+ end
373
+ }
374
+ return n
375
+ elsif option == 'words'
376
+ IO.foreach(filename){ |line|
377
+ n += line.split.length
378
+ }
379
+ return n
380
+ else
381
+ bytes,chars,lines,words = 0,0,0,0
382
+ IO.foreach(filename){ |line|
383
+ lines += 1
384
+ words += line.split.length
385
+ chars += line.split('').length
386
+ }
387
+ File.open(filename){ |f|
388
+ while f.getc
389
+ bytes += 1
390
+ end
391
+ }
392
+ return [bytes,chars,words,lines]
393
+ end
394
+ end
395
+
396
+ # Already provided by win32-file on MS Windows
397
+ unless respond_to?(:sparse?)
398
+ # Returns whether or not +file+ is a sparse file.
399
+ #
400
+ # A sparse file is a any file where its size is greater than the number
401
+ # of 512k blocks it consumes, i.e. its apparent and actual file size is
402
+ # not the same.
403
+ #
404
+ # See http://en.wikipedia.org/wiki/Sparse_file for more information.
405
+ #
406
+ def self.sparse?(file)
407
+ stats = File.stat(file)
408
+ stats.size > stats.blocks * 512
409
+ end
410
+ end
411
+
412
+ private
413
+
414
+ def self.bmp?(file)
415
+ IO.read(file, 3) == "BM6"
416
+ end
417
+
418
+ def self.jpg?(file)
419
+ IO.read(file, 10) == "\377\330\377\340\000\020JFIF"
420
+ end
421
+
422
+ def self.png?(file)
423
+ IO.read(file, 4) == "\211PNG"
424
+ end
425
+
426
+ def self.gif?(file)
427
+ ['GIF89a', 'GIF97a'].include?(IO.read(file, 6))
428
+ end
429
+ end