filepath 0.1

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