filepath 0.1
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/.gitignore +6 -0
- data/.yardopts +3 -0
- data/README.md +135 -0
- data/Rakefile +26 -0
- data/UNLICENSE +24 -0
- data/lib/filepath.rb +551 -0
- data/lib/filepathlist.rb +74 -0
- data/spec/filepath_spec.rb +440 -0
- data/spec/filepathlist_spec.rb +65 -0
- data/spec/spec_helper.rb +10 -0
- data/spec/tasks.rb +40 -0
- metadata +72 -0
data/lib/filepathlist.rb
ADDED
@@ -0,0 +1,74 @@
|
|
1
|
+
# This is free and unencumbered software released into the public domain.
|
2
|
+
# See the `UNLICENSE` file or <http://unlicense.org/> for more details.
|
3
|
+
|
4
|
+
class FilePathList
|
5
|
+
include Enumerable
|
6
|
+
|
7
|
+
def initialize(raw_entries = nil)
|
8
|
+
raw_entries ||= []
|
9
|
+
@entries = raw_entries.map { |e| FilePath.new(e) }
|
10
|
+
end
|
11
|
+
|
12
|
+
def select_entries(type)
|
13
|
+
raw_entries = @entries.delete_if { |e| !e.send(type.to_s + '?') }
|
14
|
+
return FilePathList.new(raw_entries)
|
15
|
+
end
|
16
|
+
|
17
|
+
def files
|
18
|
+
return select_entries(:file)
|
19
|
+
end
|
20
|
+
|
21
|
+
def links
|
22
|
+
return select_entries(:link)
|
23
|
+
end
|
24
|
+
|
25
|
+
def directories
|
26
|
+
return select_entries(:directory)
|
27
|
+
end
|
28
|
+
|
29
|
+
def exclude(pattern) # FIXME: block
|
30
|
+
raw_entries = @entries.delete_if { |e| e =~ pattern }
|
31
|
+
return FilePathList.new(raw_entries)
|
32
|
+
end
|
33
|
+
|
34
|
+
def /(extra_path)
|
35
|
+
return self.map { |path| path / extra_path }
|
36
|
+
end
|
37
|
+
|
38
|
+
def +(extra_entries)
|
39
|
+
return FilePathList.new(@entries + extra_entries.to_a)
|
40
|
+
end
|
41
|
+
|
42
|
+
def <<(extra_path) # TODO: implement
|
43
|
+
end
|
44
|
+
|
45
|
+
def *(other_list)
|
46
|
+
if !other_list.is_a? FilePathList
|
47
|
+
other_list = FilePathList.new(Array(other_list))
|
48
|
+
end
|
49
|
+
other_entries = other_list.entries
|
50
|
+
paths = @entries.product(other_entries).map { |p1, p2| p1 / p2 }
|
51
|
+
return FilePathList.new(paths)
|
52
|
+
end
|
53
|
+
|
54
|
+
# FIXME: delegate :to => @entries
|
55
|
+
def [](index)
|
56
|
+
@entries[index]
|
57
|
+
end
|
58
|
+
|
59
|
+
def include?(*others)
|
60
|
+
@entries.include?(*others)
|
61
|
+
end
|
62
|
+
|
63
|
+
def each(&block)
|
64
|
+
@entries.each(&block)
|
65
|
+
end
|
66
|
+
|
67
|
+
def map(&block)
|
68
|
+
@entries.map(&block)
|
69
|
+
end
|
70
|
+
|
71
|
+
def inspect
|
72
|
+
@entries.inspect
|
73
|
+
end
|
74
|
+
end
|
@@ -0,0 +1,440 @@
|
|
1
|
+
# This is free and unencumbered software released into the public domain.
|
2
|
+
# See the `UNLICENSE` file or <http://unlicense.org/> for more details.
|
3
|
+
|
4
|
+
require File.join(File.dirname(__FILE__), 'spec_helper')
|
5
|
+
|
6
|
+
describe FilePath do
|
7
|
+
before(:all) do
|
8
|
+
@root = FilePath.new(FIXTURES_DIR)
|
9
|
+
end
|
10
|
+
|
11
|
+
it "can be created from a string" do
|
12
|
+
FilePath.new("foo").should be_a FilePath
|
13
|
+
end
|
14
|
+
|
15
|
+
it "can be created from another FilePath" do
|
16
|
+
orig = FilePath.new("foo")
|
17
|
+
FilePath.new(orig).should be_a FilePath
|
18
|
+
end
|
19
|
+
|
20
|
+
describe "#/" do
|
21
|
+
test_data = [
|
22
|
+
['foo', 'bar', 'foo/bar'],
|
23
|
+
['foo', '.', 'foo'],
|
24
|
+
['foo', '..', '.'],
|
25
|
+
['foo/bar', 'baz', 'foo/bar/baz'],
|
26
|
+
]
|
27
|
+
test_data.each do |base, extra, result|
|
28
|
+
it "concatenates `#{base}` and `#{extra}` (as String) into `#{result}`" do
|
29
|
+
p = FilePath.new(base) / extra
|
30
|
+
p.should == result
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
test_data.each do |base, extra, result|
|
35
|
+
it "concatenates `#{base}` and `#{extra}` (as FilePath) into `#{result}`" do
|
36
|
+
p = FilePath.new(base) / FilePath.new(extra)
|
37
|
+
p.should == result
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
describe "#+" do
|
43
|
+
it "is deprecated but performs as FilePath#/" do
|
44
|
+
p1 = FilePath.new("a")
|
45
|
+
p2 = FilePath.new("b")
|
46
|
+
|
47
|
+
p1.should_receive(:warn).with(/is deprecated/)
|
48
|
+
(p1 + p2).should == (p1 / p2)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
describe "filename" do
|
53
|
+
test_data = [
|
54
|
+
['/foo/bar', 'bar'],
|
55
|
+
['foo', 'foo'],
|
56
|
+
['/', ''],
|
57
|
+
['/foo/bar/.', 'bar'],
|
58
|
+
['a/b/../c', 'c'],
|
59
|
+
]
|
60
|
+
test_data.each do |path, result|
|
61
|
+
it "says that `#{result}` is the filename of `#{path}`" do
|
62
|
+
p = FilePath.new(path)
|
63
|
+
p.filename.should == result
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
describe "parent_dir" do
|
69
|
+
test_data = [
|
70
|
+
['/foo/bar', '/foo'],
|
71
|
+
['foo', '.'],
|
72
|
+
['/', '/'],
|
73
|
+
['/foo/bar/.', '/foo'],
|
74
|
+
['a/b/../c', 'a'],
|
75
|
+
]
|
76
|
+
test_data.each do |path, result|
|
77
|
+
it "says that `#{result}` is the parent dir of `#{path}`" do
|
78
|
+
p = FilePath.new(path)
|
79
|
+
p.parent_dir.should == result
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
describe "#relative_to(FilePath)" do
|
85
|
+
test_data = [
|
86
|
+
['/a/b/c', '/a/b', 'c'],
|
87
|
+
['/a/b/c', '/a/d', '../b/c'],
|
88
|
+
['/a/b/c', '/a/b/c/d', '..'],
|
89
|
+
['/a/b/c', '/a/b/c', '.'],
|
90
|
+
]
|
91
|
+
test_data.each do |path, base, result|
|
92
|
+
it "says that `#{path}` relative to `#{base}` is `#{result}`" do
|
93
|
+
p = FilePath.new(path)
|
94
|
+
p.relative_to(base).should == result
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
test_data2 = [
|
99
|
+
# FIXME: testare /a/b/c con ../d (bisogna prima rendere assoluto quel path)
|
100
|
+
['../e', '/a/b/c'],
|
101
|
+
['g', '/a/b/c'],
|
102
|
+
['/a/b/c', 'm'],
|
103
|
+
]
|
104
|
+
test_data2.each do |path, base|
|
105
|
+
it "raise an exception because `#{path}` and `#{base}` have different prefixes" do
|
106
|
+
p = FilePath.new(path)
|
107
|
+
expect { p.relative_to(base) }.to raise_error
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
describe "#replace_filename" do
|
113
|
+
test_data = [
|
114
|
+
['foo/bar', 'quux', 'foo/quux'],
|
115
|
+
['foo/baz/..', 'quux', 'quux'],
|
116
|
+
['/', 'foo', '/foo'],
|
117
|
+
]
|
118
|
+
test_data.each do |base, new, result|
|
119
|
+
it "changes `#{base}` + `#{new}` into `#{result}`" do
|
120
|
+
p = FilePath.new(base)
|
121
|
+
p.replace_filename(new).should == result
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
describe "#extension" do
|
127
|
+
test_data = [
|
128
|
+
['foo.bar', 'bar'],
|
129
|
+
['foo.', ''],
|
130
|
+
['foo', nil],
|
131
|
+
['foo.bar/baz.buz', 'buz'],
|
132
|
+
['foo.bar/baz', nil],
|
133
|
+
['.foo', nil],
|
134
|
+
['.foo.conf', 'conf'],
|
135
|
+
]
|
136
|
+
test_data.each do |path, ext|
|
137
|
+
it "says that `#{path}` has extension `#{ext}`" do
|
138
|
+
FilePath.new(path).extension.should == ext
|
139
|
+
end
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
describe "#extension?" do
|
144
|
+
it "says that `foo.bar` has an extension" do
|
145
|
+
FilePath.new('foo.bar').extension?.should be_true
|
146
|
+
end
|
147
|
+
|
148
|
+
it "says that `foo.` has an extension" do
|
149
|
+
FilePath.new('foo.').extension?.should be_true
|
150
|
+
end
|
151
|
+
|
152
|
+
it "says that `foo` has no extension" do
|
153
|
+
FilePath.new('foo').extension?.should be_false
|
154
|
+
end
|
155
|
+
|
156
|
+
it "says that `foo.bar/baz` has no extension" do
|
157
|
+
FilePath.new('foo.bar/baz').extension?.should be_false
|
158
|
+
end
|
159
|
+
|
160
|
+
it "says that `.foo` has no extension" do
|
161
|
+
FilePath.new('.foo').extension?.should be_false
|
162
|
+
end
|
163
|
+
|
164
|
+
it "says that `.foo.conf` has no extension" do
|
165
|
+
FilePath.new('.foo.conf').extension?.should be_true
|
166
|
+
end
|
167
|
+
end
|
168
|
+
|
169
|
+
describe "#extension?(String)" do
|
170
|
+
it "says that `foo.bar` extesions is `bar`" do
|
171
|
+
FilePath.new('foo.bar').extension?('bar').should be_true
|
172
|
+
end
|
173
|
+
|
174
|
+
it "says that `foo.bar` extension is not `baz`" do
|
175
|
+
FilePath.new('foo.bar').extension?('baz').should be_false
|
176
|
+
end
|
177
|
+
end
|
178
|
+
|
179
|
+
describe "#replace_extension(String)" do
|
180
|
+
test_data = [
|
181
|
+
['foo.bar', 'foo.baz'],
|
182
|
+
['foo.', 'foo.baz'],
|
183
|
+
['foo', 'foo.baz'],
|
184
|
+
['foo.bar/baz.buz', 'baz.baz'],
|
185
|
+
['foo.bar/baz', 'baz.baz'],
|
186
|
+
]
|
187
|
+
test_data.each do |path, result|
|
188
|
+
it "replaces `#{path}` with `baz` into `#{result}`" do
|
189
|
+
new = FilePath.new(path).replace_extension('baz')
|
190
|
+
new.basename.to_s.should == result
|
191
|
+
end
|
192
|
+
end
|
193
|
+
end
|
194
|
+
|
195
|
+
describe "#remove_extension" do
|
196
|
+
test_data = [
|
197
|
+
['foo.bar', 'foo'],
|
198
|
+
['foo.', 'foo'],
|
199
|
+
['foo', 'foo'],
|
200
|
+
['foo.bar/baz.buz', 'baz'],
|
201
|
+
['foo.bar/baz', 'baz'],
|
202
|
+
]
|
203
|
+
test_data.each do |path, result|
|
204
|
+
it "turns `#{path}` into `#{result}`" do
|
205
|
+
new = FilePath.new(path).remove_extension
|
206
|
+
new.basename.to_s.should == result
|
207
|
+
end
|
208
|
+
end
|
209
|
+
end
|
210
|
+
|
211
|
+
describe "=~" do
|
212
|
+
it "matches `/foo/bar` with /foo/" do
|
213
|
+
FilePath.new('/foo/bar').should =~ /foo/
|
214
|
+
end
|
215
|
+
|
216
|
+
it "does not match `/foo/bar` with /baz/" do
|
217
|
+
FilePath.new('/foo/bar').should_not =~ /baz/
|
218
|
+
end
|
219
|
+
|
220
|
+
it "matches `/foo/bar` with /o\\/ba" do
|
221
|
+
FilePath.new('/foo/bar').should =~ /o\/b/
|
222
|
+
end
|
223
|
+
end
|
224
|
+
|
225
|
+
describe "#absolute?" do
|
226
|
+
it "says that `/foo/bar` is absolute" do
|
227
|
+
FilePath.new('/foo/bar').should be_absolute
|
228
|
+
end
|
229
|
+
|
230
|
+
it "sasys that `foo/bar` is not absolute" do
|
231
|
+
FilePath.new('foo/bar').should_not be_absolute
|
232
|
+
end
|
233
|
+
end
|
234
|
+
|
235
|
+
describe "#ascend" do
|
236
|
+
it "goes through all the fragments of an absolute path" do
|
237
|
+
steps = []
|
238
|
+
FilePath.new("/a/b/c").ascend do |p|
|
239
|
+
steps << p
|
240
|
+
end
|
241
|
+
|
242
|
+
steps.should have(4).items
|
243
|
+
steps[0].should eq("/a/b/c")
|
244
|
+
steps[1].should eq("/a/b")
|
245
|
+
steps[2].should eq("/a")
|
246
|
+
steps[3].should eq("/")
|
247
|
+
end
|
248
|
+
|
249
|
+
it "goes through all the fragments of a relative path" do
|
250
|
+
steps = []
|
251
|
+
FilePath.new("a/b/c").ascend do |p|
|
252
|
+
steps << p
|
253
|
+
end
|
254
|
+
|
255
|
+
steps.should have(3).items
|
256
|
+
steps[0].should eq("a/b/c")
|
257
|
+
steps[1].should eq("a/b")
|
258
|
+
steps[2].should eq("a")
|
259
|
+
end
|
260
|
+
end
|
261
|
+
|
262
|
+
describe "#descend" do
|
263
|
+
it "goes through all the fragments of an absolute path" do
|
264
|
+
steps = []
|
265
|
+
FilePath.new("/a/b/c").descend do |p|
|
266
|
+
steps << p
|
267
|
+
end
|
268
|
+
|
269
|
+
steps.should have(4).items
|
270
|
+
steps[0].should eq("/")
|
271
|
+
steps[1].should eq("/a")
|
272
|
+
steps[2].should eq("/a/b")
|
273
|
+
steps[3].should eq("/a/b/c")
|
274
|
+
end
|
275
|
+
|
276
|
+
it "goes through all the fragments of a relative path" do
|
277
|
+
steps = []
|
278
|
+
FilePath.new("a/b/c").descend do |p|
|
279
|
+
steps << p
|
280
|
+
end
|
281
|
+
|
282
|
+
steps.should have(3).items
|
283
|
+
steps[0].should eq("a")
|
284
|
+
steps[1].should eq("a/b")
|
285
|
+
steps[2].should eq("a/b/c")
|
286
|
+
end
|
287
|
+
end
|
288
|
+
|
289
|
+
describe "#to_s" do
|
290
|
+
it "works on computed absolute paths" do
|
291
|
+
(FilePath.new('/') / 'a' / 'b').to_s.should eql('/a/b')
|
292
|
+
end
|
293
|
+
|
294
|
+
it "works on computed relative paths" do
|
295
|
+
(FilePath.new('a') / 'b').to_s.should eql('a/b')
|
296
|
+
end
|
297
|
+
|
298
|
+
it "returns normalized paths" do
|
299
|
+
FilePath.new("/foo/bar/..").to_s.should eql('/foo')
|
300
|
+
end
|
301
|
+
end
|
302
|
+
|
303
|
+
describe "#==(String)" do
|
304
|
+
test_data = [
|
305
|
+
['./', '.'],
|
306
|
+
['a/../b', 'b'],
|
307
|
+
['a/.././b', 'b'],
|
308
|
+
['a/./../b', 'b'],
|
309
|
+
['./foo', 'foo'],
|
310
|
+
['a/./b/c', 'a/b/c'],
|
311
|
+
['a/b/.', 'a/b'],
|
312
|
+
['a/b/', 'a/b'],
|
313
|
+
['../a/../b/c/d/../../e', '../b/e'],
|
314
|
+
]
|
315
|
+
test_data.each do |ver1, ver2|
|
316
|
+
it "says that `#{ver1}` is equivalent to `#{ver2}`" do
|
317
|
+
p = FilePath.new(ver1)
|
318
|
+
p.should == ver2
|
319
|
+
end
|
320
|
+
end
|
321
|
+
end
|
322
|
+
|
323
|
+
describe FilePath::PathResolution do
|
324
|
+
describe "#absolute_path" do
|
325
|
+
it "resolves `d1/l11` to `/dev/null`" do
|
326
|
+
(@root / 'd1' / 'l11').absolute_path.should == '/dev/null'
|
327
|
+
end
|
328
|
+
end
|
329
|
+
end
|
330
|
+
|
331
|
+
describe FilePath::FileInfo do
|
332
|
+
describe "#file?" do
|
333
|
+
it "says that `f1` is a file" do
|
334
|
+
(@root / 'f1').should be_file
|
335
|
+
end
|
336
|
+
|
337
|
+
it "says that `d1/l11` is not a file" do
|
338
|
+
(@root / 'd1' / 'l11').should_not be_file
|
339
|
+
end
|
340
|
+
|
341
|
+
it "says that the root directory is not a file" do
|
342
|
+
@root.should_not be_file
|
343
|
+
end
|
344
|
+
end
|
345
|
+
|
346
|
+
describe "#link?" do
|
347
|
+
it "says that `f1` is not a link" do
|
348
|
+
(@root / 'f1').should_not be_link
|
349
|
+
end
|
350
|
+
|
351
|
+
it "says that `d1/l11` is a link" do
|
352
|
+
(@root / 'd1' / 'l11').should be_link
|
353
|
+
end
|
354
|
+
|
355
|
+
it "says that the root directory is not a link" do
|
356
|
+
@root.should_not be_link
|
357
|
+
end
|
358
|
+
end
|
359
|
+
|
360
|
+
describe "#directory?" do
|
361
|
+
it "says that `f1` is not a directory" do
|
362
|
+
(@root / 'f1').should_not be_directory
|
363
|
+
end
|
364
|
+
|
365
|
+
it "says that `d1/l11` is not a directory" do
|
366
|
+
(@root / 'd1' / 'l11').should_not be_directory
|
367
|
+
end
|
368
|
+
|
369
|
+
it "says that the root directory is file" do
|
370
|
+
@root.should be_directory
|
371
|
+
end
|
372
|
+
end
|
373
|
+
end
|
374
|
+
|
375
|
+
describe "methods that operate on directories" do
|
376
|
+
describe "#select_entries" do
|
377
|
+
it "raises when path is not a directory" do
|
378
|
+
expect { (@root / 'f1').entries(:files) }.to raise_error(Errno::ENOTDIR)
|
379
|
+
end
|
380
|
+
end
|
381
|
+
|
382
|
+
describe "#files" do
|
383
|
+
it "finds 1 file in the root directory" do
|
384
|
+
@root.files.should have(1).item
|
385
|
+
end
|
386
|
+
|
387
|
+
it "finds 2 files in directory `d1`" do
|
388
|
+
(@root / 'd1').files.should have(2).items
|
389
|
+
end
|
390
|
+
|
391
|
+
it "finds no files in directory `d1/d12`" do
|
392
|
+
(@root / 'd1' / 'd12').files.should have(0).items
|
393
|
+
end
|
394
|
+
end
|
395
|
+
|
396
|
+
describe "#directories" do
|
397
|
+
it "finds 4 directories in the root directory" do
|
398
|
+
@root.directories.should have(4).items
|
399
|
+
end
|
400
|
+
|
401
|
+
it "finds 2 directories in directory `d2`" do
|
402
|
+
(@root / 'd2').directories.should have(2).items
|
403
|
+
end
|
404
|
+
|
405
|
+
it "finds no directories in directory `d1/d13" do
|
406
|
+
(@root / 'd1' / 'd13').directories.should have(0).items
|
407
|
+
end
|
408
|
+
end
|
409
|
+
|
410
|
+
describe "#links" do
|
411
|
+
it "finds no links in the root directory" do
|
412
|
+
@root.links.should have(0).items
|
413
|
+
end
|
414
|
+
|
415
|
+
it "finds 1 link in directory `d1`" do
|
416
|
+
(@root / 'd1').links.should have(1).item
|
417
|
+
end
|
418
|
+
end
|
419
|
+
end
|
420
|
+
end
|
421
|
+
|
422
|
+
describe String do
|
423
|
+
describe "#as_path" do
|
424
|
+
it "generates a FilePath from a String" do
|
425
|
+
path = "/a/b/c".as_path
|
426
|
+
path.should be_a(FilePath)
|
427
|
+
path.should eq("/a/b/c")
|
428
|
+
end
|
429
|
+
end
|
430
|
+
end
|
431
|
+
|
432
|
+
describe Array do
|
433
|
+
describe "#as_path" do
|
434
|
+
it "generates a FilePath from a String" do
|
435
|
+
path = ['/', 'a', 'b', 'c'].as_path
|
436
|
+
path.should be_a(FilePath)
|
437
|
+
path.should eq("/a/b/c")
|
438
|
+
end
|
439
|
+
end
|
440
|
+
end
|