filepath 0.3.1 → 0.4

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