ptools 1.3.1-universal-mingw32 → 1.3.2-universal-mingw32

Sign up to get free protection for your applications and to get access to all the features.
data/Rakefile CHANGED
@@ -1,142 +1,139 @@
1
- require 'rake'
2
- require 'rake/clean'
3
- require 'rake/testtask'
4
- require 'rbconfig'
5
- include RbConfig
6
-
7
- CLEAN.include("**/*.gem", "**/*.rbc", "**/*coverage*")
8
-
9
- desc 'Install the ptools package (non-gem)'
10
- task :install do
11
- sitelibdir = CONFIG["sitelibdir"]
12
- file = "lib/ptools.rb"
13
- FileUtils.cp(file, sitelibdir, :verbose => true)
14
- end
15
-
16
- namespace 'gem' do
17
- desc 'Create the ptools gem'
18
- task :create => [:clean] do
19
- Dir["*.gem"].each{ |f| File.delete(f) } # Clean first
20
- spec = eval(IO.read('ptools.gemspec'))
21
- if Gem::VERSION < "2.0.0"
22
- Gem::Builder.new(spec).build
23
- else
24
- require 'rubygems/package'
25
- Gem::Package.build(spec)
26
- end
27
- end
28
-
29
- desc 'Install the ptools gem'
30
- task :install => [:create] do
31
- file = Dir["*.gem"].first
32
- sh "gem install -l #{file}"
33
- end
34
- end
35
-
36
- Rake::TestTask.new do |t|
37
- task :test => :clean
38
- t.verbose = true
39
- t.warning = true
40
- end
41
-
42
- namespace 'test' do
43
- desc "Check test coverage using rcov"
44
- task :coverage => [:clean] do
45
- require 'rcov'
46
- rm_rf 'coverage'
47
- sh "rcov -Ilib test/test*.rb"
48
- end
49
-
50
- Rake::TestTask.new('binary') do |t|
51
- t.libs << 'test'
52
- t.verbose = true
53
- t.warning = true
54
- t.test_files = FileList['test/test_binary.rb']
55
- end
56
-
57
- Rake::TestTask.new('constants') do |t|
58
- t.libs << 'test'
59
- t.verbose = true
60
- t.warning = true
61
- t.test_files = FileList['test/test_constants.rb']
62
- end
63
-
64
- Rake::TestTask.new('head') do |t|
65
- t.libs << 'test'
66
- t.verbose = true
67
- t.warning = true
68
- t.test_files = FileList['test/test_head.rb']
69
- end
70
-
71
- Rake::TestTask.new('image') do |t|
72
- t.libs << 'test'
73
- t.verbose = true
74
- t.warning = true
75
- t.test_files = FileList['test/test_image.rb']
76
- end
77
-
78
- Rake::TestTask.new('middle') do |t|
79
- t.libs << 'test'
80
- t.verbose = true
81
- t.warning = true
82
- t.test_files = FileList['test/test_middle.rb']
83
- end
84
-
85
- Rake::TestTask.new('nlconvert') do |t|
86
- t.libs << 'test'
87
- t.verbose = true
88
- t.warning = true
89
- t.test_files = FileList['test/test_nlconvert.rb']
90
- end
91
-
92
- Rake::TestTask.new('null') do |t|
93
- t.libs << 'test'
94
- t.verbose = true
95
- t.warning = true
96
- t.test_files = FileList['test/test_null.rb']
97
- end
98
-
99
- Rake::TestTask.new('sparse') do |t|
100
- t.libs << 'test'
101
- t.verbose = true
102
- t.warning = true
103
- t.test_files = FileList['test/test_is_sparse.rb']
104
- end
105
-
106
- Rake::TestTask.new('tail') do |t|
107
- t.libs << 'test'
108
- t.verbose = true
109
- t.warning = true
110
- t.test_files = FileList['test/test_tail.rb']
111
- end
112
-
113
- Rake::TestTask.new('touch') do |t|
114
- t.libs << 'test'
115
- t.verbose = true
116
- t.warning = true
117
- t.test_files = FileList['test/test_touch.rb']
118
- end
119
-
120
- Rake::TestTask.new('wc') do |t|
121
- t.libs << 'test'
122
- t.verbose = true
123
- t.warning = true
124
- t.test_files = FileList['test/test_wc.rb']
125
- end
126
-
127
- Rake::TestTask.new('whereis') do |t|
128
- t.libs << 'test'
129
- t.verbose = true
130
- t.warning = true
131
- t.test_files = FileList['test/test_whereis.rb']
132
- end
133
-
134
- Rake::TestTask.new('which') do |t|
135
- t.libs << 'test'
136
- t.verbose = true
137
- t.warning = true
138
- t.test_files = FileList['test/test_which.rb']
139
- end
140
- end
141
-
142
- task :default => :test
1
+ require 'rake'
2
+ require 'rake/clean'
3
+ require 'rake/testtask'
4
+ require 'rbconfig'
5
+ include RbConfig
6
+
7
+ CLEAN.include("**/*.gem", "**/*.rbc", "**/*coverage*")
8
+
9
+ desc 'Install the ptools package (non-gem)'
10
+ task :install do
11
+ sitelibdir = CONFIG["sitelibdir"]
12
+ file = "lib/ptools.rb"
13
+ FileUtils.cp(file, sitelibdir, :verbose => true)
14
+ end
15
+
16
+ namespace 'gem' do
17
+ desc 'Create the ptools gem'
18
+ task :create => [:clean] do
19
+ Dir["*.gem"].each{ |f| File.delete(f) } # Clean first
20
+ spec = eval(IO.read('ptools.gemspec'))
21
+ if Gem::VERSION < "2.0.0"
22
+ Gem::Builder.new(spec).build
23
+ else
24
+ require 'rubygems/package'
25
+ Gem::Package.build(spec)
26
+ end
27
+ end
28
+
29
+ desc 'Install the ptools gem'
30
+ task :install => [:create] do
31
+ file = Dir["*.gem"].first
32
+ if RUBY_PLATFORM == 'java'
33
+ sh "jruby -S gem install -l #{file}"
34
+ else
35
+ sh "gem install -l #{file}"
36
+ end
37
+ end
38
+ end
39
+
40
+ Rake::TestTask.new do |t|
41
+ task :test => :clean
42
+ t.verbose = true
43
+ t.warning = true
44
+ end
45
+
46
+ namespace 'test' do
47
+ desc "Check test coverage using rcov"
48
+ task :coverage => [:clean] do
49
+ require 'rcov'
50
+ rm_rf 'coverage'
51
+ sh "rcov -Ilib test/test*.rb"
52
+ end
53
+
54
+ Rake::TestTask.new('binary') do |t|
55
+ t.libs << 'test'
56
+ t.verbose = true
57
+ t.warning = true
58
+ t.test_files = FileList['test/test_binary.rb']
59
+ end
60
+
61
+ Rake::TestTask.new('constants') do |t|
62
+ t.libs << 'test'
63
+ t.verbose = true
64
+ t.warning = true
65
+ t.test_files = FileList['test/test_constants.rb']
66
+ end
67
+
68
+ Rake::TestTask.new('head') do |t|
69
+ t.libs << 'test'
70
+ t.verbose = true
71
+ t.warning = true
72
+ t.test_files = FileList['test/test_head.rb']
73
+ end
74
+
75
+ Rake::TestTask.new('image') do |t|
76
+ t.libs << 'test'
77
+ t.verbose = true
78
+ t.warning = true
79
+ t.test_files = FileList['test/test_image.rb']
80
+ end
81
+
82
+ Rake::TestTask.new('nlconvert') do |t|
83
+ t.libs << 'test'
84
+ t.verbose = true
85
+ t.warning = true
86
+ t.test_files = FileList['test/test_nlconvert.rb']
87
+ end
88
+
89
+ Rake::TestTask.new('null') do |t|
90
+ t.libs << 'test'
91
+ t.verbose = true
92
+ t.warning = true
93
+ t.test_files = FileList['test/test_null.rb']
94
+ end
95
+
96
+ Rake::TestTask.new('sparse') do |t|
97
+ t.libs << 'test'
98
+ t.verbose = true
99
+ t.warning = true
100
+ t.test_files = FileList['test/test_is_sparse.rb']
101
+ end
102
+
103
+ Rake::TestTask.new('tail') do |t|
104
+ t.libs << 'test'
105
+ t.verbose = true
106
+ t.warning = true
107
+ t.test_files = FileList['test/test_tail.rb']
108
+ end
109
+
110
+ Rake::TestTask.new('touch') do |t|
111
+ t.libs << 'test'
112
+ t.verbose = true
113
+ t.warning = true
114
+ t.test_files = FileList['test/test_touch.rb']
115
+ end
116
+
117
+ Rake::TestTask.new('wc') do |t|
118
+ t.libs << 'test'
119
+ t.verbose = true
120
+ t.warning = true
121
+ t.test_files = FileList['test/test_wc.rb']
122
+ end
123
+
124
+ Rake::TestTask.new('whereis') do |t|
125
+ t.libs << 'test'
126
+ t.verbose = true
127
+ t.warning = true
128
+ t.test_files = FileList['test/test_whereis.rb']
129
+ end
130
+
131
+ Rake::TestTask.new('which') do |t|
132
+ t.libs << 'test'
133
+ t.verbose = true
134
+ t.warning = true
135
+ t.test_files = FileList['test/test_which.rb']
136
+ end
137
+ end
138
+
139
+ task :default => :test
data/lib/ptools.rb CHANGED
@@ -1,476 +1,482 @@
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.3.1'
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.
32
- #
33
- # Examples:
34
- #
35
- # File.image?('somefile.jpg') # => true
36
- # File.image?('somefile.txt') # => false
37
- #--
38
- # The approach I used here is based on information found at
39
- # http://en.wikipedia.org/wiki/Magic_number_(programming)
40
- #
41
- def self.image?(file)
42
- bool = IMAGE_EXT.include?(File.extname(file).downcase) # Match ext
43
- bool = bmp?(file) || jpg?(file) || png?(file) || gif?(file) || tiff?(file) # Check data
44
- bool
45
- end
46
-
47
- # Returns the name of the null device (aka bitbucket) on your platform.
48
- #
49
- # Examples:
50
- #
51
- # # On Linux
52
- # File.null # => '/dev/null'
53
- #
54
- # # On MS Windows
55
- # File.null # => 'NUL'
56
- #--
57
- # The values I used here are based on information from
58
- # http://en.wikipedia.org/wiki//dev/null
59
- #
60
- def self.null
61
- case RbConfig::CONFIG['host_os']
62
- when /mswin|win32|msdos|mingw|windows/i
63
- 'NUL'
64
- when /amiga/i
65
- 'NIL:'
66
- when /openvms/i
67
- 'NL:'
68
- else
69
- '/dev/null'
70
- end
71
- end
72
-
73
- class << self
74
- alias null_device null
75
- end
76
-
77
- # Returns whether or not +file+ is a binary non-image file, i.e. executable,
78
- # shared object, ect. Note that this is NOT guaranteed to be 100% accurate.
79
- # It performs a "best guess" based on a simple test of the first
80
- # +File.blksize+ characters, or 4096, whichever is smaller.
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
- return false if image?(file)
92
- bytes = File.stat(file).blksize
93
- bytes = 4096 if bytes > 4096
94
- s = (File.read(file, bytes) || "")
95
- s = s.encode('US-ASCII', :undef => :replace).split(//)
96
- ((s.size - s.grep(" ".."~").size) / s.size.to_f) > 0.30
97
- end
98
-
99
- # Looks for the first occurrence of +program+ within +path+.
100
- #
101
- # On Windows, it looks for executables ending with the suffixes defined
102
- # in your PATHEXT environment variable, or '.exe', '.bat' and '.com' if
103
- # that isn't defined, which you may optionally include in +program+.
104
- #
105
- # Returns nil if not found.
106
- #
107
- # Examples:
108
- #
109
- # File.which('ruby') # => '/usr/local/bin/ruby'
110
- # File.which('foo') # => nil
111
- #
112
- def self.which(program, path=ENV['PATH'])
113
- if path.nil? || path.empty?
114
- raise ArgumentError, "path cannot be empty"
115
- end
116
-
117
- # Bail out early if an absolute path is provided.
118
- if program =~ /^\/|^[a-z]:[\\\/]/i
119
- program += WIN32EXTS if MSWINDOWS && File.extname(program).empty?
120
- found = Dir[program].first
121
- if found && File.executable?(found) && !File.directory?(found)
122
- return found
123
- else
124
- return nil
125
- end
126
- end
127
-
128
- # Iterate over each path glob the dir + program.
129
- path.split(File::PATH_SEPARATOR).each{ |dir|
130
- dir = File.expand_path(dir)
131
-
132
- next unless File.exist?(dir) # In case of bogus second argument
133
- file = File.join(dir, program)
134
-
135
- # Dir[] doesn't handle backslashes properly, so convert them. Also, if
136
- # the program name doesn't have an extension, try them all.
137
- if MSWINDOWS
138
- file = file.tr("\\", "/")
139
- file += WIN32EXTS if File.extname(program).empty?
140
- end
141
-
142
- found = Dir[file].first
143
-
144
- # Convert all forward slashes to backslashes if supported
145
- if found && File.executable?(found) && !File.directory?(found)
146
- found.tr!(File::SEPARATOR, File::ALT_SEPARATOR) if File::ALT_SEPARATOR
147
- return found
148
- end
149
- }
150
-
151
- nil
152
- end
153
-
154
- # Returns an array of each +program+ within +path+, or nil if it cannot
155
- # be found.
156
- #
157
- # On Windows, it looks for executables ending with the suffixes defined
158
- # in your PATHEXT environment variable, or '.exe', '.bat' and '.com' if
159
- # that isn't defined, which you may optionally include in +program+.
160
- #
161
- # Examples:
162
- #
163
- # File.whereis('ruby') # => ['/usr/bin/ruby', '/usr/local/bin/ruby']
164
- # File.whereis('foo') # => nil
165
- #
166
- def self.whereis(program, path=ENV['PATH'])
167
- if path.nil? || path.empty?
168
- raise ArgumentError, "path cannot be empty"
169
- end
170
-
171
- paths = []
172
-
173
- # Bail out early if an absolute path is provided.
174
- if program =~ /^\/|^[a-z]:[\\\/]/i
175
- program += WIN32EXTS if MSWINDOWS && File.extname(program).empty?
176
- program = program.tr("\\", '/') if MSWINDOWS
177
- found = Dir[program]
178
- if found[0] && File.executable?(found[0]) && !File.directory?(found[0])
179
- if File::ALT_SEPARATOR
180
- return found.map{ |f| f.tr('/', "\\") }
181
- else
182
- return found
183
- end
184
- else
185
- return nil
186
- end
187
- end
188
-
189
- # Iterate over each path glob the dir + program.
190
- path.split(File::PATH_SEPARATOR).each{ |dir|
191
- next unless File.exist?(dir) # In case of bogus second argument
192
- file = File.join(dir, program)
193
-
194
- # Dir[] doesn't handle backslashes properly, so convert them. Also, if
195
- # the program name doesn't have an extension, try them all.
196
- if MSWINDOWS
197
- file = file.tr("\\", "/")
198
- file += WIN32EXTS if File.extname(program).empty?
199
- end
200
-
201
- found = Dir[file].first
202
-
203
- # Convert all forward slashes to backslashes if supported
204
- if found && File.executable?(found) && !File.directory?(found)
205
- found.tr!(File::SEPARATOR, File::ALT_SEPARATOR) if File::ALT_SEPARATOR
206
- paths << found
207
- end
208
- }
209
-
210
- paths.empty? ? nil : paths.uniq
211
- end
212
-
213
- # In block form, yields the first +num_lines+ from +filename+. In non-block
214
- # form, returns an Array of +num_lines+
215
- #
216
- # Examples:
217
- #
218
- # # Return an array
219
- # File.head('somefile.txt') # => ['This is line1', 'This is line2', ...]
220
- #
221
- # # Use a block
222
- # File.head('somefile.txt'){ |line| puts line }
223
- #
224
- def self.head(filename, num_lines=10)
225
- a = []
226
-
227
- IO.foreach(filename){ |line|
228
- break if num_lines <= 0
229
- num_lines -= 1
230
- if block_given?
231
- yield line
232
- else
233
- a << line
234
- end
235
- }
236
-
237
- return a.empty? ? nil : a # Return nil in block form
238
- end
239
-
240
- # In block form, yields the last +num_lines+ of file +filename+.
241
- # In non-block form, it returns the lines as an array.
242
- #
243
- # Example:
244
- #
245
- # File.tail('somefile.txt') # => ['This is line7', 'This is line8', ...]
246
- #
247
- # If you're looking for tail -f functionality, please use the file-tail
248
- # gem instead.
249
- #
250
- def self.tail(filename, num_lines=10)
251
- tail_size = 2**16 # 64k chunks
252
-
253
- # MS Windows gets unhappy if you try to seek backwards past the
254
- # end of the file, so we have some extra checks here and later.
255
- file_size = File.size(filename)
256
- read_bytes = file_size % tail_size
257
- read_bytes = tail_size if read_bytes == 0
258
- line_sep = File::ALT_SEPARATOR ? "\r\n" : "\n"
259
-
260
- buf = ''
261
-
262
- File.open(filename){ |fh|
263
- # Set the starting read position
264
- position = file_size - read_bytes
265
-
266
- # Loop until we have the lines or run out of file
267
- while buf.scan(line_sep).size <= num_lines and position >= 0
268
- fh.seek(position, IO::SEEK_SET)
269
- buf = fh.read(read_bytes) + buf
270
- read_bytes = tail_size
271
- position -= read_bytes
272
- end
273
- }
274
-
275
- lines = buf.split(line_sep).pop(num_lines)
276
- if block_given?
277
- lines.each{ |line| yield line }
278
- else
279
- lines
280
- end
281
- end
282
-
283
- # Converts a text file from one OS platform format to another, ala
284
- # 'dos2unix'. The possible values for +platform+ include:
285
- #
286
- # * MS Windows -> dos, windows, win32, mswin
287
- # * Unix/BSD -> unix, linux, bsd, osx, darwin, sunos, solaris
288
- # * Mac -> mac, macintosh, apple
289
- #
290
- # You may also specify 'local', in which case your CONFIG['host_os'] value
291
- # will be used. This is the default.
292
- #
293
- # Note that this method is only valid for an ftype of "file". Otherwise a
294
- # TypeError will be raised. If an invalid format value is received, an
295
- # ArgumentError is raised.
296
- #
297
- def self.nl_convert(old_file, new_file = old_file, platform = 'local')
298
- unless File::Stat.new(old_file).file?
299
- raise ArgumentError, 'Only valid for plain text files'
300
- end
301
-
302
- format = nl_for_platform(platform)
303
-
304
- orig = $\ # $OUTPUT_RECORD_SEPARATOR
305
- $\ = format
306
-
307
- if old_file == new_file
308
- require 'fileutils'
309
- require 'tempfile'
310
-
311
- begin
312
- temp_name = Time.new.strftime("%Y%m%d%H%M%S")
313
- tf = Tempfile.new('ruby_temp_' + temp_name)
314
- tf.open
315
-
316
- IO.foreach(old_file){ |line|
317
- line.chomp!
318
- tf.print line
319
- }
320
- ensure
321
- tf.close if tf && !tf.closed?
322
- end
323
-
324
- File.delete(old_file)
325
- FileUtils.mv(tf.path, old_file)
326
- else
327
- begin
328
- nf = File.new(new_file, 'w')
329
- IO.foreach(old_file){ |line|
330
- line.chomp!
331
- nf.print line
332
- }
333
- ensure
334
- nf.close if nf && !nf.closed?
335
- end
336
- end
337
-
338
- $\ = orig
339
- self
340
- end
341
-
342
- # Changes the access and modification time if present, or creates a 0
343
- # byte file +filename+ if it doesn't already exist.
344
- #
345
- def self.touch(filename)
346
- if File.exist?(filename)
347
- time = Time.now
348
- File.utime(time, time, filename)
349
- else
350
- File.open(filename, 'w'){}
351
- end
352
- self
353
- end
354
-
355
- # With no arguments, returns a four element array consisting of the number
356
- # of bytes, characters, words and lines in filename, respectively.
357
- #
358
- # Valid options are 'bytes', 'characters' (or just 'chars'), 'words' and
359
- # 'lines'.
360
- #
361
- def self.wc(filename, option='all')
362
- option.downcase!
363
- valid = %w/all bytes characters chars lines words/
364
-
365
- unless valid.include?(option)
366
- raise ArgumentError, "Invalid option: '#{option}'"
367
- end
368
-
369
- n = 0
370
-
371
- if option == 'lines'
372
- IO.foreach(filename){ n += 1 }
373
- return n
374
- elsif option == 'bytes'
375
- File.open(filename){ |f|
376
- f.each_byte{ n += 1 }
377
- }
378
- return n
379
- elsif option == 'characters' || option == 'chars'
380
- File.open(filename){ |f|
381
- while f.getc
382
- n += 1
383
- end
384
- }
385
- return n
386
- elsif option == 'words'
387
- IO.foreach(filename){ |line|
388
- n += line.split.length
389
- }
390
- return n
391
- else
392
- bytes,chars,lines,words = 0,0,0,0
393
- IO.foreach(filename){ |line|
394
- lines += 1
395
- words += line.split.length
396
- chars += line.split('').length
397
- }
398
- File.open(filename){ |f|
399
- while f.getc
400
- bytes += 1
401
- end
402
- }
403
- return [bytes,chars,words,lines]
404
- end
405
- end
406
-
407
- # Already provided by win32-file on MS Windows
408
- unless respond_to?(:sparse?)
409
- # Returns whether or not +file+ is a sparse file.
410
- #
411
- # A sparse file is a any file where its size is greater than the number
412
- # of 512k blocks it consumes, i.e. its apparent and actual file size is
413
- # not the same.
414
- #
415
- # See http://en.wikipedia.org/wiki/Sparse_file for more information.
416
- #
417
- def self.sparse?(file)
418
- stats = File.stat(file)
419
- stats.size > stats.blocks * 512
420
- end
421
- end
422
-
423
- private
424
-
425
- def self.nl_for_platform(platform)
426
- platform = RbConfig::CONFIG["host_os"] if platform == 'local'
427
-
428
- case platform
429
- when /dos|windows|win32|mswin|mingw/i
430
- return "\cM\cJ"
431
- when /unix|linux|bsd|cygwin|osx|darwin|solaris|sunos/i
432
- return "\cJ"
433
- when /mac|apple|macintosh/i
434
- return "\cM"
435
- else
436
- raise ArgumentError, "Invalid platform string"
437
- end
438
- end
439
-
440
- def self.bmp?(file)
441
- IO.read(file, 3) == "BM6"
442
- end
443
-
444
- def self.jpg?(file)
445
- IO.read(file, 10, nil, :encoding => 'binary') == "\377\330\377\340\000\020JFIF".force_encoding(Encoding::BINARY)
446
- end
447
-
448
- def self.png?(file)
449
- IO.read(file, 4, nil, :encoding => 'binary') == "\211PNG".force_encoding(Encoding::BINARY)
450
- end
451
-
452
- def self.gif?(file)
453
- ['GIF89a', 'GIF97a'].include?(IO.read(file, 6))
454
- end
455
-
456
- def self.tiff?(file)
457
- return false if File.size(file) < 12
458
-
459
- bytes = IO.read(file, 4)
460
-
461
- # II is Intel, MM is Motorola
462
- if bytes[0..1] != 'II'&& bytes[0..1] != 'MM'
463
- return false
464
- end
465
-
466
- if bytes[0..1] == 'II' && bytes[2..3].ord != 42
467
- return false
468
- end
469
-
470
- if bytes[0..1] == 'MM' && bytes[2..3].reverse.ord != 42
471
- return false
472
- end
473
-
474
- true
475
- end
476
- 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.3.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.
32
+ #
33
+ # Examples:
34
+ #
35
+ # File.image?('somefile.jpg') # => true
36
+ # File.image?('somefile.txt') # => false
37
+ #--
38
+ # The approach I used here is based on information found at
39
+ # http://en.wikipedia.org/wiki/Magic_number_(programming)
40
+ #
41
+ def self.image?(file)
42
+ bool = IMAGE_EXT.include?(File.extname(file).downcase) # Match ext
43
+ bool = bmp?(file) || jpg?(file) || png?(file) || gif?(file) || tiff?(file) # Check data
44
+ bool
45
+ end
46
+
47
+ # Returns the name of the null device (aka bitbucket) on your platform.
48
+ #
49
+ # Examples:
50
+ #
51
+ # # On Linux
52
+ # File.null # => '/dev/null'
53
+ #
54
+ # # On MS Windows
55
+ # File.null # => 'NUL'
56
+ #--
57
+ # The values I used here are based on information from
58
+ # http://en.wikipedia.org/wiki//dev/null
59
+ #
60
+ def self.null
61
+ case RbConfig::CONFIG['host_os']
62
+ when /mswin|win32|msdos|mingw|windows/i
63
+ 'NUL'
64
+ when /amiga/i
65
+ 'NIL:'
66
+ when /openvms/i
67
+ 'NL:'
68
+ else
69
+ '/dev/null'
70
+ end
71
+ end
72
+
73
+ class << self
74
+ alias null_device null
75
+ end
76
+
77
+ # Returns whether or not +file+ is a binary non-image file, i.e. executable,
78
+ # shared object, ect. Note that this is NOT guaranteed to be 100% accurate.
79
+ # It performs a "best guess" based on a simple test of the first
80
+ # +File.blksize+ characters, or 4096, whichever is smaller.
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
+ return false if image?(file)
92
+ bytes = File.stat(file).blksize
93
+ bytes = 4096 if bytes > 4096
94
+ s = (File.read(file, bytes) || "")
95
+ s = s.encode('US-ASCII', :undef => :replace).split(//)
96
+ ((s.size - s.grep(" ".."~").size) / s.size.to_f) > 0.30
97
+ end
98
+
99
+ # Looks for the first occurrence of +program+ within +path+.
100
+ #
101
+ # On Windows, it looks for executables ending with the suffixes defined
102
+ # in your PATHEXT environment variable, or '.exe', '.bat' and '.com' if
103
+ # that isn't defined, which you may optionally include in +program+.
104
+ #
105
+ # Returns nil if not found.
106
+ #
107
+ # Examples:
108
+ #
109
+ # File.which('ruby') # => '/usr/local/bin/ruby'
110
+ # File.which('foo') # => nil
111
+ #
112
+ def self.which(program, path=ENV['PATH'])
113
+ if path.nil? || path.empty?
114
+ raise ArgumentError, "path cannot be empty"
115
+ end
116
+
117
+ # Bail out early if an absolute path is provided.
118
+ if program =~ /^\/|^[a-z]:[\\\/]/i
119
+ program += WIN32EXTS if MSWINDOWS && File.extname(program).empty?
120
+ found = Dir[program].first
121
+ if found && File.executable?(found) && !File.directory?(found)
122
+ return found
123
+ else
124
+ return nil
125
+ end
126
+ end
127
+
128
+ # Iterate over each path glob the dir + program.
129
+ path.split(File::PATH_SEPARATOR).each{ |dir|
130
+ dir = File.expand_path(dir)
131
+
132
+ next unless File.exist?(dir) # In case of bogus second argument
133
+ file = File.join(dir, program)
134
+
135
+ # Dir[] doesn't handle backslashes properly, so convert them. Also, if
136
+ # the program name doesn't have an extension, try them all.
137
+ if MSWINDOWS
138
+ file = file.tr("\\", "/")
139
+ file += WIN32EXTS if File.extname(program).empty?
140
+ end
141
+
142
+ found = Dir[file].first
143
+
144
+ # Convert all forward slashes to backslashes if supported
145
+ if found && File.executable?(found) && !File.directory?(found)
146
+ found.tr!(File::SEPARATOR, File::ALT_SEPARATOR) if File::ALT_SEPARATOR
147
+ return found
148
+ end
149
+ }
150
+
151
+ nil
152
+ end
153
+
154
+ # Returns an array of each +program+ within +path+, or nil if it cannot
155
+ # be found.
156
+ #
157
+ # On Windows, it looks for executables ending with the suffixes defined
158
+ # in your PATHEXT environment variable, or '.exe', '.bat' and '.com' if
159
+ # that isn't defined, which you may optionally include in +program+.
160
+ #
161
+ # Examples:
162
+ #
163
+ # File.whereis('ruby') # => ['/usr/bin/ruby', '/usr/local/bin/ruby']
164
+ # File.whereis('foo') # => nil
165
+ #
166
+ def self.whereis(program, path=ENV['PATH'])
167
+ if path.nil? || path.empty?
168
+ raise ArgumentError, "path cannot be empty"
169
+ end
170
+
171
+ paths = []
172
+
173
+ # Bail out early if an absolute path is provided.
174
+ if program =~ /^\/|^[a-z]:[\\\/]/i
175
+ program += WIN32EXTS if MSWINDOWS && File.extname(program).empty?
176
+ program = program.tr("\\", '/') if MSWINDOWS
177
+ found = Dir[program]
178
+ if found[0] && File.executable?(found[0]) && !File.directory?(found[0])
179
+ if File::ALT_SEPARATOR
180
+ return found.map{ |f| f.tr('/', "\\") }
181
+ else
182
+ return found
183
+ end
184
+ else
185
+ return nil
186
+ end
187
+ end
188
+
189
+ # Iterate over each path glob the dir + program.
190
+ path.split(File::PATH_SEPARATOR).each{ |dir|
191
+ next unless File.exist?(dir) # In case of bogus second argument
192
+ file = File.join(dir, program)
193
+
194
+ # Dir[] doesn't handle backslashes properly, so convert them. Also, if
195
+ # the program name doesn't have an extension, try them all.
196
+ if MSWINDOWS
197
+ file = file.tr("\\", "/")
198
+ file += WIN32EXTS if File.extname(program).empty?
199
+ end
200
+
201
+ found = Dir[file].first
202
+
203
+ # Convert all forward slashes to backslashes if supported
204
+ if found && File.executable?(found) && !File.directory?(found)
205
+ found.tr!(File::SEPARATOR, File::ALT_SEPARATOR) if File::ALT_SEPARATOR
206
+ paths << found
207
+ end
208
+ }
209
+
210
+ paths.empty? ? nil : paths.uniq
211
+ end
212
+
213
+ # In block form, yields the first +num_lines+ from +filename+. In non-block
214
+ # form, returns an Array of +num_lines+
215
+ #
216
+ # Examples:
217
+ #
218
+ # # Return an array
219
+ # File.head('somefile.txt') # => ['This is line1', 'This is line2', ...]
220
+ #
221
+ # # Use a block
222
+ # File.head('somefile.txt'){ |line| puts line }
223
+ #
224
+ def self.head(filename, num_lines=10)
225
+ a = []
226
+
227
+ IO.foreach(filename){ |line|
228
+ break if num_lines <= 0
229
+ num_lines -= 1
230
+ if block_given?
231
+ yield line
232
+ else
233
+ a << line
234
+ end
235
+ }
236
+
237
+ return a.empty? ? nil : a # Return nil in block form
238
+ end
239
+
240
+ # In block form, yields the last +num_lines+ of file +filename+.
241
+ # In non-block form, it returns the lines as an array.
242
+ #
243
+ # Example:
244
+ #
245
+ # File.tail('somefile.txt') # => ['This is line7', 'This is line8', ...]
246
+ #
247
+ # If you're looking for tail -f functionality, please use the file-tail
248
+ # gem instead.
249
+ #
250
+ #--
251
+ # Internally I'm using a 64 chunk of memory at a time. I may allow the size
252
+ # to be configured in the future as an optional 3rd argument.
253
+ #
254
+ def self.tail(filename, num_lines=10)
255
+ tail_size = 2**16 # 64k chunks
256
+
257
+ # MS Windows gets unhappy if you try to seek backwards past the
258
+ # end of the file, so we have some extra checks here and later.
259
+ file_size = File.size(filename)
260
+ read_bytes = file_size % tail_size
261
+ read_bytes = tail_size if read_bytes == 0
262
+
263
+ line_sep = File::ALT_SEPARATOR ? "\r\n" : "\n"
264
+
265
+ buf = ''
266
+
267
+ # Open in binary mode to ensure line endings aren't converted.
268
+ File.open(filename, 'rb'){ |fh|
269
+ position = file_size - read_bytes # Set the starting read position
270
+
271
+ # Loop until we have the lines or run out of file
272
+ while buf.scan(line_sep).size <= num_lines and position >= 0
273
+ fh.seek(position, IO::SEEK_SET)
274
+ buf = fh.read(read_bytes) + buf
275
+ read_bytes = tail_size
276
+ position -= read_bytes
277
+ end
278
+ }
279
+
280
+ lines = buf.split(line_sep).pop(num_lines)
281
+
282
+ if block_given?
283
+ lines.each{ |line| yield line }
284
+ else
285
+ lines
286
+ end
287
+ end
288
+
289
+ # Converts a text file from one OS platform format to another, ala
290
+ # 'dos2unix'. The possible values for +platform+ include:
291
+ #
292
+ # * MS Windows -> dos, windows, win32, mswin
293
+ # * Unix/BSD -> unix, linux, bsd, osx, darwin, sunos, solaris
294
+ # * Mac -> mac, macintosh, apple
295
+ #
296
+ # You may also specify 'local', in which case your CONFIG['host_os'] value
297
+ # will be used. This is the default.
298
+ #
299
+ # Note that this method is only valid for an ftype of "file". Otherwise a
300
+ # TypeError will be raised. If an invalid format value is received, an
301
+ # ArgumentError is raised.
302
+ #
303
+ def self.nl_convert(old_file, new_file = old_file, platform = 'local')
304
+ unless File::Stat.new(old_file).file?
305
+ raise ArgumentError, 'Only valid for plain text files'
306
+ end
307
+
308
+ format = nl_for_platform(platform)
309
+
310
+ orig = $\ # $OUTPUT_RECORD_SEPARATOR
311
+ $\ = format
312
+
313
+ if old_file == new_file
314
+ require 'fileutils'
315
+ require 'tempfile'
316
+
317
+ begin
318
+ temp_name = Time.new.strftime("%Y%m%d%H%M%S")
319
+ tf = Tempfile.new('ruby_temp_' + temp_name)
320
+ tf.open
321
+
322
+ IO.foreach(old_file){ |line|
323
+ line.chomp!
324
+ tf.print line
325
+ }
326
+ ensure
327
+ tf.close if tf && !tf.closed?
328
+ end
329
+
330
+ File.delete(old_file)
331
+ FileUtils.mv(tf.path, old_file)
332
+ else
333
+ begin
334
+ nf = File.new(new_file, 'w')
335
+ IO.foreach(old_file){ |line|
336
+ line.chomp!
337
+ nf.print line
338
+ }
339
+ ensure
340
+ nf.close if nf && !nf.closed?
341
+ end
342
+ end
343
+
344
+ $\ = orig
345
+ self
346
+ end
347
+
348
+ # Changes the access and modification time if present, or creates a 0
349
+ # byte file +filename+ if it doesn't already exist.
350
+ #
351
+ def self.touch(filename)
352
+ if File.exist?(filename)
353
+ time = Time.now
354
+ File.utime(time, time, filename)
355
+ else
356
+ File.open(filename, 'w'){}
357
+ end
358
+ self
359
+ end
360
+
361
+ # With no arguments, returns a four element array consisting of the number
362
+ # of bytes, characters, words and lines in filename, respectively.
363
+ #
364
+ # Valid options are 'bytes', 'characters' (or just 'chars'), 'words' and
365
+ # 'lines'.
366
+ #
367
+ def self.wc(filename, option='all')
368
+ option.downcase!
369
+ valid = %w/all bytes characters chars lines words/
370
+
371
+ unless valid.include?(option)
372
+ raise ArgumentError, "Invalid option: '#{option}'"
373
+ end
374
+
375
+ n = 0
376
+
377
+ if option == 'lines'
378
+ IO.foreach(filename){ n += 1 }
379
+ return n
380
+ elsif option == 'bytes'
381
+ File.open(filename){ |f|
382
+ f.each_byte{ n += 1 }
383
+ }
384
+ return n
385
+ elsif option == 'characters' || option == 'chars'
386
+ File.open(filename){ |f|
387
+ while f.getc
388
+ n += 1
389
+ end
390
+ }
391
+ return n
392
+ elsif option == 'words'
393
+ IO.foreach(filename){ |line|
394
+ n += line.split.length
395
+ }
396
+ return n
397
+ else
398
+ bytes,chars,lines,words = 0,0,0,0
399
+ IO.foreach(filename){ |line|
400
+ lines += 1
401
+ words += line.split.length
402
+ chars += line.split('').length
403
+ }
404
+ File.open(filename){ |f|
405
+ while f.getc
406
+ bytes += 1
407
+ end
408
+ }
409
+ return [bytes,chars,words,lines]
410
+ end
411
+ end
412
+
413
+ # Already provided by win32-file on MS Windows
414
+ unless respond_to?(:sparse?)
415
+ # Returns whether or not +file+ is a sparse file.
416
+ #
417
+ # A sparse file is a any file where its size is greater than the number
418
+ # of 512k blocks it consumes, i.e. its apparent and actual file size is
419
+ # not the same.
420
+ #
421
+ # See http://en.wikipedia.org/wiki/Sparse_file for more information.
422
+ #
423
+ def self.sparse?(file)
424
+ stats = File.stat(file)
425
+ stats.size > stats.blocks * 512
426
+ end
427
+ end
428
+
429
+ private
430
+
431
+ def self.nl_for_platform(platform)
432
+ platform = RbConfig::CONFIG["host_os"] if platform == 'local'
433
+
434
+ case platform
435
+ when /dos|windows|win32|mswin|mingw/i
436
+ return "\cM\cJ"
437
+ when /unix|linux|bsd|cygwin|osx|darwin|solaris|sunos/i
438
+ return "\cJ"
439
+ when /mac|apple|macintosh/i
440
+ return "\cM"
441
+ else
442
+ raise ArgumentError, "Invalid platform string"
443
+ end
444
+ end
445
+
446
+ def self.bmp?(file)
447
+ IO.read(file, 3) == "BM6"
448
+ end
449
+
450
+ def self.jpg?(file)
451
+ IO.read(file, 10, nil, :encoding => 'binary') == "\377\330\377\340\000\020JFIF".force_encoding(Encoding::BINARY)
452
+ end
453
+
454
+ def self.png?(file)
455
+ IO.read(file, 4, nil, :encoding => 'binary') == "\211PNG".force_encoding(Encoding::BINARY)
456
+ end
457
+
458
+ def self.gif?(file)
459
+ ['GIF89a', 'GIF97a'].include?(IO.read(file, 6))
460
+ end
461
+
462
+ def self.tiff?(file)
463
+ return false if File.size(file) < 12
464
+
465
+ bytes = IO.read(file, 4)
466
+
467
+ # II is Intel, MM is Motorola
468
+ if bytes[0..1] != 'II'&& bytes[0..1] != 'MM'
469
+ return false
470
+ end
471
+
472
+ if bytes[0..1] == 'II' && bytes[2..3].ord != 42
473
+ return false
474
+ end
475
+
476
+ if bytes[0..1] == 'MM' && bytes[2..3].reverse.ord != 42
477
+ return false
478
+ end
479
+
480
+ true
481
+ end
482
+ end