filepath 0.3.1 → 0.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/.yardopts CHANGED
@@ -1,3 +1,4 @@
1
1
  -m markdown
2
+ --no-private
2
3
  -
3
4
  UNLICENSE
data/Rakefile CHANGED
@@ -13,7 +13,7 @@ Bones {
13
13
  email 'gioele@svario.it'
14
14
  url 'http://github.com/gioele/filepath'
15
15
 
16
- version '0.3.1'
16
+ version '0.4'
17
17
 
18
18
  ignore_file '.gitignore'
19
19
 
@@ -1,612 +1,8 @@
1
1
  # This is free and unencumbered software released into the public domain.
2
2
  # See the `UNLICENSE` file or <http://unlicense.org/> for more details.
3
3
 
4
- require 'filepathlist'
4
+ require 'filepath/filepath.rb'
5
+ require 'filepath/filepathlist.rb'
6
+ require 'filepath/core_ext/array.rb'
7
+ require 'filepath/core_ext/string.rb'
5
8
 
6
- class FilePath
7
- SEPARATOR = '/'.freeze
8
-
9
- def initialize(path)
10
- if path.is_a? FilePath
11
- @fragments = path.fragments
12
- elsif path.is_a? Array
13
- @fragments = path
14
- else
15
- @fragments = split_path_string(path.to_str)
16
- end
17
- end
18
-
19
- attr_reader :fragments
20
-
21
- # Creates a FilePath joining the given fragments.
22
- #
23
- # @return [FilePath] a FilePath created joining the given fragments
24
-
25
- def FilePath.join(*raw_paths)
26
- if (raw_paths.count == 1) && (raw_paths.first.is_a? Array)
27
- raw_paths = raw_paths.first
28
- end
29
-
30
- paths = raw_paths.map { |p| p.as_path }
31
-
32
- frags = []
33
- paths.each { |path| frags += path.fragments }
34
-
35
- return FilePath.new(frags)
36
- end
37
-
38
-
39
- # Appends another path to the current path.
40
- #
41
- # @example Append a string
42
- #
43
- # "a/b".as_path / "c" #=> <a/b/c>
44
- #
45
- # @example Append another FilePath
46
- #
47
- # home = (ENV["HOME"] || "/root").as_path
48
- # conf_dir = '.config'.as_path
49
- #
50
- # home / conf_dir #=> </home/user/.config>
51
- #
52
- # @param [FilePath, String] extra_path the path to be appended to the
53
- # current path
54
- #
55
- # @return [FilePath] a new path with the given path appended
56
-
57
- def /(extra_path)
58
- return FilePath.join(self, extra_path)
59
- end
60
-
61
-
62
- # Append multiple paths to the current path.
63
- #
64
- # @return [FilePath] a new path with all the paths appended
65
-
66
- def join(*extra_paths)
67
- return FilePath.join(self, *extra_paths)
68
- end
69
-
70
- alias :append :join
71
-
72
-
73
- # An alias for {FilePath#/}.
74
- #
75
- # @deprecated Use the {FilePath#/} (slash) method instead. This method
76
- # does not show clearly if a path is being added or if a
77
- # string should be added to the filename
78
-
79
- def +(extra_path)
80
- warn "FilePath#+ is deprecated, use FilePath#/ instead."
81
- return self / extra_path
82
- end
83
-
84
-
85
- # Calculates the relative path from a given directory.
86
- #
87
- # @param [FilePath, String] base the directory to use as base for the
88
- # relative path
89
- #
90
- # @return [FilePath] the relative path
91
- #
92
- # @note this method operates on the normalized paths
93
-
94
- def relative_to(base)
95
- base = base.as_path
96
-
97
- if self.absolute? != base.absolute?
98
- self_abs = self.absolute? ? "absolute" : "relative"
99
- base_abs = base.absolute? ? "absolute" : "relative"
100
- msg = "cannot compare: "
101
- msg += "`#{self}` is #{self_abs} while "
102
- msg += "`#{base}` is #{base_abs}"
103
- raise ArgumentError, msg
104
- end
105
-
106
- self_frags = self.normalized_fragments
107
- base_frags = base.normalized_fragments
108
-
109
- base_frags_tmp = base_frags.dup
110
- num_same = self_frags.find_index do |frag|
111
- base_frags_tmp.delete_at(0) != frag
112
- end
113
-
114
- # find_index returns nil if `self` is a subset of `base`
115
- num_same ||= self_frags.length
116
-
117
- num_parent_dirs = base_frags.length - num_same
118
- left_in_self = self_frags[num_same..-1]
119
-
120
- frags = [".."] * num_parent_dirs + left_in_self
121
- normalized_frags = normalized_relative_frags(frags)
122
-
123
- return FilePath.join(normalized_frags)
124
- end
125
-
126
- # Calculates the relative path from a given file.
127
- #
128
- # @param [FilePath, String] base the file to use as base for the
129
- # relative path
130
- #
131
- # @return [FilePath] the relative path
132
- #
133
- # @see #relative_to
134
-
135
- def relative_to_file(base_file)
136
- return relative_to(base_file.as_path.parent_dir)
137
- end
138
-
139
-
140
- # The filename component of the path.
141
- #
142
- # The filename is the component of a path that appears after the last
143
- # path separator.
144
- #
145
- # @return [FilePath] the filename
146
-
147
- def filename
148
- if self.root?
149
- return ''.as_path
150
- end
151
-
152
- filename = self.normalized_fragments.last
153
- return filename.as_path
154
- end
155
-
156
- alias :basename :filename
157
-
158
-
159
- # The dir that contains the file
160
- #
161
- # @return [FilePath] the path of the parent dir
162
-
163
- def parent_dir
164
- return self / '..'
165
- end
166
-
167
-
168
- # Replace the path filename with the supplied path.
169
- #
170
- # @param [FilePath, String] new_path the path to be put in place of
171
- # the current filename
172
- #
173
- # @return [FilePath] a path with the supplied path instead of the
174
- # current filename
175
-
176
- def replace_filename(new_path)
177
- dir = self.parent_dir
178
- return dir / new_path
179
- end
180
-
181
- alias :replace_basename :replace_filename
182
-
183
-
184
- # The extension of the file.
185
- #
186
- # The extension of a file are the characters after the last dot.
187
- #
188
- # @return [String] the extension of the file or nil if the file has no
189
- # extension
190
-
191
- def extension
192
- filename = @fragments.last
193
-
194
- num_dots = filename.count('.')
195
-
196
- if num_dots.zero?
197
- ext = nil
198
- elsif filename.start_with?('.') && num_dots == 1
199
- ext = nil
200
- elsif filename.end_with?('.')
201
- ext = ''
202
- else
203
- ext = filename.split('.').last
204
- end
205
-
206
- return ext
207
- end
208
-
209
- alias :ext :extension
210
-
211
-
212
- # @overload extension?(ext)
213
- # @param [String, Regexp] ext the extension to be matched
214
- #
215
- # @return whether the file extension matches the given extension
216
- #
217
- # @overload extension?
218
- # @return whether the file has an extension
219
-
220
- def extension?(ext = nil)
221
- cur_ext = self.extension
222
-
223
- if ext.nil?
224
- return !cur_ext.nil?
225
- else
226
- if ext.is_a? Regexp
227
- return !cur_ext.match(ext).nil?
228
- else
229
- return cur_ext == ext
230
- end
231
- end
232
- end
233
-
234
- alias :ext? :extension?
235
-
236
-
237
- # @overload replace_extension(new_ext)
238
- # Replaces the file extension with the supplied one. If the file
239
- # has no extension it is added to the file name together with a dot.
240
- #
241
- # @param [String] new_ext the new extension
242
- #
243
- # @return [FilePath] a new path with the replaced extension
244
- #
245
- # @overload replace_extension
246
- # Removes the file extension if present.
247
- #
248
- # @return [FilePath] a new path without the extension
249
-
250
- def replace_extension(new_ext) # FIXME: accept block
251
- if !self.extension?
252
- if new_ext.nil?
253
- new_filename = filename
254
- else
255
- new_filename = filename.to_s + '.' + new_ext
256
- end
257
- else
258
- if new_ext.nil?
259
- pattern = /\.[^.]*?\Z/
260
- new_filename = filename.to_s.sub(pattern, '')
261
- else
262
- pattern = Regexp.new('.' + extension + '\\Z')
263
- new_filename = filename.to_s.sub(pattern, '.' + new_ext)
264
- end
265
- end
266
-
267
- frags = @fragments[0..-2]
268
- frags << new_filename
269
-
270
- return FilePath.join(frags)
271
- end
272
-
273
- alias :replace_ext :replace_extension
274
- alias :sub_ext :replace_extension
275
-
276
-
277
- # Removes the file extension if present.
278
- #
279
- # @return [FilePath] a new path without the extension
280
-
281
- def remove_extension
282
- return replace_ext(nil)
283
- end
284
-
285
- alias :remove_ext :remove_extension
286
-
287
-
288
- # Matches a pattern against this path.
289
- #
290
- # @param [Regexp, Object] pattern the pattern to match against
291
- # this path
292
- #
293
- # @return [Fixnum, nil] the position of the pattern in the path, or
294
- # nil if there is no match
295
- #
296
- # @note this method operates on the normalized path
297
-
298
- def =~(pattern)
299
- return self.to_s =~ pattern
300
- end
301
-
302
- def root?
303
- return @fragments == [SEPARATOR] # FIXME: windows, mac
304
- end
305
-
306
-
307
- # Is this path absolute?
308
- #
309
- # FIXME: document what an absolute path is.
310
- #
311
- # @return whether the current path is absolute
312
-
313
- def absolute?
314
- return @fragments.first == SEPARATOR # FIXME: windows, mac
315
- end
316
-
317
-
318
- # Is this path relative?
319
- #
320
- # FIXME: document what a relative path is.
321
- #
322
- # @return whether the current path is relative
323
-
324
- def relative?
325
- return !self.absolute?
326
- end
327
-
328
-
329
- # Simplify paths that contain `.` and `..`.
330
- #
331
- # The resulting path will be in normal form.
332
- #
333
- # FIXME: document what normal form is.
334
- #
335
- # @return [FilePath] a new path that does not contain `.` or `..`
336
- # fragments.
337
-
338
- def normalized
339
- return FilePath.join(self.normalized_fragments)
340
- end
341
-
342
- alias :normalised :normalized
343
-
344
-
345
- # Iterates over all the path directories, from the current path to
346
- # the root.
347
- #
348
- # @param max_depth the maximum depth to ascend to, nil to ascend
349
- # without limits.
350
- #
351
- # @yield [path] TODO
352
-
353
- def ascend(max_depth = nil, &block)
354
- iterate(max_depth, :reverse_each, &block)
355
- end
356
-
357
- # Iterates over all the directory that lead to the current path.
358
- #
359
- # @param max_depth the maximum depth to descent to, nil to descend
360
- # without limits.
361
- #
362
- # @yield [path] TODO
363
-
364
- def descend(max_depth = nil, &block)
365
- iterate(max_depth, :each, &block)
366
- end
367
-
368
- # @private
369
- def iterate(max_depth, method, &block)
370
- max_depth ||= @fragments.length
371
- (1..max_depth).send(method) do |limit|
372
- frags = @fragments.take(limit)
373
- yield FilePath.join(frags)
374
- end
375
- end
376
-
377
-
378
- # This path converted to a String
379
- #
380
- # @return [String] this path converted to a String
381
-
382
- def to_raw_string
383
- @to_raw_string ||= join_fragments(@fragments)
384
- end
385
-
386
- alias :to_raw_str :to_raw_string
387
-
388
-
389
- # @return [String] this path converted to a String
390
- #
391
- # @note this method operates on the normalized path
392
-
393
- def to_s
394
- to_str
395
- end
396
-
397
-
398
- def to_str
399
- @to_str ||= join_fragments(self.normalized_fragments)
400
- end
401
-
402
-
403
- # @return [FilePath] the path itself.
404
- def as_path
405
- self
406
- end
407
-
408
-
409
- def inspect
410
- return '<' + self.to_raw_string + '>'
411
- end
412
-
413
- def ==(other)
414
- return self.normalized_fragments == other.as_path.normalized_fragments
415
- end
416
-
417
- def eql?(other)
418
- if self.equal?(other)
419
- return true
420
- elsif self.class != other.class
421
- return false
422
- end
423
-
424
- return self.fragments == other.fragments
425
- end
426
-
427
- def hash
428
- return self.fragments.hash
429
- end
430
-
431
- # @private
432
- def split_path_string(raw_path)
433
- fragments = raw_path.split(SEPARATOR) # FIXME: windows, mac
434
-
435
- if raw_path == SEPARATOR
436
- fragments << SEPARATOR
437
- end
438
-
439
- if !fragments.empty? && fragments.first.empty?
440
- fragments[0] = SEPARATOR
441
- end
442
-
443
- return fragments
444
- end
445
-
446
- # @private
447
- def normalized_fragments
448
- @normalized_fragments ||= normalized_relative_frags(self.fragments)
449
- end
450
-
451
- # @private
452
- def normalized_relative_frags(orig_frags)
453
- frags = orig_frags.dup
454
-
455
- # remove "current dir" markers
456
- frags.delete('.')
457
-
458
- i = 0
459
- while (i < frags.length)
460
- if frags[i] == '..' && frags[i-1] == SEPARATOR
461
- # remove '..' fragments following a root delimiter
462
- frags.delete_at(i)
463
- i -= 1
464
- elsif frags[i] == '..' && frags[i-1] != '..' && i >= 1
465
- # remove every fragment followed by a ".." marker
466
- frags.delete_at(i)
467
- frags.delete_at(i-1)
468
- i -= 2
469
- end
470
- i += 1
471
- end
472
-
473
- return frags
474
- end
475
-
476
- # @private
477
- def join_fragments(frags)
478
- # FIXME: windows, mac
479
- # FIXME: avoid string substitutions and regexen
480
- return frags.join(SEPARATOR).sub(%r{^//}, SEPARATOR).sub(/\A\Z/, '.')
481
- end
482
-
483
- module PathResolution
484
- def absolute_path(base_dir = Dir.pwd) # FIXME: rename to `#absolute`?
485
- if self.absolute?
486
- return self
487
- end
488
-
489
- return base_dir.as_path / self
490
- end
491
-
492
- def real_path(base_dir = Dir.pwd)
493
- path = absolute_path(base_dir)
494
-
495
- return path.resolve_link
496
- end
497
-
498
- alias :realpath :real_path
499
-
500
- def resolve_link
501
- return File.readlink(self).as_path
502
- end
503
- end
504
-
505
- module FileInfo
506
- # @private
507
- def self.define_filetest_method(filepath_method, filetest_method = nil)
508
- filetest_method ||= filepath_method
509
- define_method(filepath_method) do
510
- return FileTest.send(filetest_method, self)
511
- end
512
- end
513
-
514
- define_filetest_method :file?
515
-
516
- define_filetest_method :link?, :symlink?
517
- alias :symlink? :link?
518
-
519
- define_filetest_method :directory?
520
-
521
- define_filetest_method :exists?
522
- alias :exist? :exists?
523
-
524
- define_filetest_method :readable?
525
-
526
- define_filetest_method :writeable?
527
-
528
- define_filetest_method :executable?
529
-
530
- define_filetest_method :setgid?
531
-
532
- define_filetest_method :setuid?
533
-
534
- define_filetest_method :empty?, :zero?
535
- alias :zero? :empty?
536
-
537
- def hidden?
538
- @fragments.last.start_with?('.') # FIXME: windows, mac
539
- end
540
- end
541
-
542
- module FileManipulationMethods
543
- def open(*args, &block)
544
- File.open(self, *args, &block)
545
- end
546
-
547
- def touch
548
- self.open('a') do ; end
549
- File.utime(File.atime(self), Time.now, self)
550
- end
551
- end
552
-
553
- module DirectoryMethods
554
- def entries(pattern = '*')
555
- if !self.directory?
556
- raise Errno::ENOTDIR.new(self)
557
- end
558
-
559
- raw_entries = Dir.glob((self / pattern))
560
- entries = FilePathList.new(raw_entries)
561
-
562
- return entries
563
- end
564
- alias :glob :entries
565
-
566
- def files
567
- entries.select_entries(:file)
568
- end
569
-
570
- def links
571
- entries.select_entries(:link)
572
- end
573
-
574
- def directories
575
- entries.select_entries(:directory)
576
- end
577
- end
578
-
579
- include PathResolution
580
- include FileInfo
581
- include FileManipulationMethods
582
- include DirectoryMethods
583
- end
584
-
585
- class String
586
- # Generates a path from a String.
587
- #
588
- # `"/a/b/c".as_path` is equivalent to `FilePath.new("/a/b/c")`.
589
- #
590
- # @return [FilePath] a new path generated from the string
591
- #
592
- # @note FIXME: `#as_path` should be `#to_path` but that method name
593
- # is already used
594
- def as_path
595
- FilePath.new(self)
596
- end
597
- end
598
-
599
- class Array
600
- # Generates a path using the elements of an Array as path fragments.
601
- #
602
- # `%w{a b c}.as_path` is equivalent to `FilePath.join('a', 'b', 'c')`.
603
- #
604
- # @return [FilePath] a new path generated using the element as path
605
- # fragments
606
- #
607
- # @note FIXME: `#as_path` should be `#to_path` but that method name
608
- # is already used
609
- def as_path
610
- FilePath.join(self)
611
- end
612
- end