build-files 1.3.0 → 1.4.0

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.
@@ -0,0 +1,55 @@
1
+ # Copyright, 2014, by Samuel G. D. Williams. <http://www.codeotaku.com>
2
+ #
3
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ # of this software and associated documentation files (the "Software"), to deal
5
+ # in the Software without restriction, including without limitation the rights
6
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ # copies of the Software, and to permit persons to whom the Software is
8
+ # furnished to do so, subject to the following conditions:
9
+ #
10
+ # The above copyright notice and this permission notice shall be included in
11
+ # all copies or substantial portions of the Software.
12
+ #
13
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
+ # THE SOFTWARE.
20
+
21
+ require_relative 'polling'
22
+
23
+ require 'rb-fsevent'
24
+
25
+ module Build
26
+ module Files
27
+ module Monitor
28
+ class FSEvent < Polling
29
+ def run(**options, &block)
30
+ notifier = ::FSEvent.new
31
+
32
+ catch(:interrupt) do
33
+ while true
34
+ notifier.watch self.roots do |directories|
35
+ directories.collect! do |directory|
36
+ File.expand_path(directory)
37
+ end
38
+
39
+ self.update(directories)
40
+
41
+ yield
42
+
43
+ if self.updated
44
+ notifier.stop
45
+ end
46
+ end
47
+
48
+ notifier.run
49
+ end
50
+ end
51
+ end
52
+ end
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,53 @@
1
+ # Copyright, 2014, by Samuel G. D. Williams. <http://www.codeotaku.com>
2
+ #
3
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ # of this software and associated documentation files (the "Software"), to deal
5
+ # in the Software without restriction, including without limitation the rights
6
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ # copies of the Software, and to permit persons to whom the Software is
8
+ # furnished to do so, subject to the following conditions:
9
+ #
10
+ # The above copyright notice and this permission notice shall be included in
11
+ # all copies or substantial portions of the Software.
12
+ #
13
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
+ # THE SOFTWARE.
20
+
21
+ require_relative 'polling'
22
+
23
+ require 'rb-inotify'
24
+
25
+ module Build
26
+ module Files
27
+ module Monitor
28
+ class INotify < Polling
29
+ def run(**options, &block)
30
+ notifier = ::INotify::Notifier.new
31
+
32
+ catch(:interrupt) do
33
+ while true
34
+ self.roots.each do |root|
35
+ notifier.watch root, :create, :modify, :attrib, :delete do |event|
36
+ self.update([root])
37
+
38
+ yield
39
+
40
+ if self.updated
41
+ notifier.stop
42
+ end
43
+ end
44
+ end
45
+
46
+ notifier.run
47
+ end
48
+ end
49
+ end
50
+ end
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,145 @@
1
+ # Copyright, 2014, by Samuel G. D. Williams. <http://www.codeotaku.com>
2
+ #
3
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ # of this software and associated documentation files (the "Software"), to deal
5
+ # in the Software without restriction, including without limitation the rights
6
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ # copies of the Software, and to permit persons to whom the Software is
8
+ # furnished to do so, subject to the following conditions:
9
+ #
10
+ # The above copyright notice and this permission notice shall be included in
11
+ # all copies or substantial portions of the Software.
12
+ #
13
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
+ # THE SOFTWARE.
20
+
21
+ require 'set'
22
+ require 'logger'
23
+
24
+ require_relative '../handle'
25
+
26
+ module Build
27
+ module Files
28
+ module Monitor
29
+ class Polling
30
+ def initialize(logger: nil)
31
+ @directories = Hash.new do |hash, key|
32
+ hash[key] = Set.new
33
+ end
34
+
35
+ @updated = false
36
+
37
+ @deletions = nil
38
+
39
+ @logger = logger || Logger.new(nil)
40
+ end
41
+
42
+ attr :updated
43
+
44
+ # Notify the monitor that files in these directories have changed.
45
+ def update(directories, *args)
46
+ @logger.debug{"Update: #{directories} #{args.inspect}"}
47
+
48
+ delay_deletions do
49
+ directories.each do |directory|
50
+ @logger.debug{"Directory: #{directory}"}
51
+
52
+ @directories[directory].each do |handle|
53
+ @logger.debug{"Handle changed: #{handle.inspect}"}
54
+
55
+ # Changes here may not actually require an update to the handle:
56
+ handle.changed!(*args)
57
+ end
58
+ end
59
+ end
60
+ end
61
+
62
+ def roots
63
+ @directories.keys
64
+ end
65
+
66
+ def delete(handle)
67
+ if @deletions
68
+ @logger.debug{"Delayed delete handle: #{handle}"}
69
+ @deletions << handle
70
+ else
71
+ purge(handle)
72
+ end
73
+ end
74
+
75
+ def track_changes(files, &block)
76
+ handle = Handle.new(self, files, &block)
77
+
78
+ add(handle)
79
+ end
80
+
81
+ def add(handle)
82
+ @logger.debug{"Adding handle: #{handle}"}
83
+
84
+ handle.directories.each do |directory|
85
+ # We want the full path as a plain string:
86
+ directory = directory.to_s
87
+
88
+ @directories[directory] << handle
89
+
90
+ # We just added the first handle:
91
+ if @directories[directory].size == 1
92
+ # If the handle already existed, this might trigger unnecessarily.
93
+ @updated = true
94
+ end
95
+ end
96
+
97
+ handle
98
+ end
99
+
100
+ def run(**options, &block)
101
+ catch(:interrupt) do
102
+ while true
103
+ monitor.update(monitor.roots)
104
+
105
+ yield
106
+
107
+ sleep(options[:latency] || 1.0)
108
+ end
109
+ end
110
+ end
111
+
112
+ protected
113
+
114
+ def delay_deletions
115
+ @deletions = []
116
+
117
+ yield
118
+
119
+ @deletions.each do |handle|
120
+ purge(handle)
121
+ end
122
+
123
+ @deletions = nil
124
+ end
125
+
126
+ def purge(handle)
127
+ @logger.debug{"Purge handle: #{handle}"}
128
+
129
+ handle.directories.each do |directory|
130
+ directory = directory.to_s
131
+
132
+ @directories[directory].delete(handle)
133
+
134
+ # Remove the entire record if there are no handles:
135
+ if @directories[directory].size == 0
136
+ @directories.delete(directory)
137
+
138
+ @updated = true
139
+ end
140
+ end
141
+ end
142
+ end
143
+ end
144
+ end
145
+ end
@@ -123,13 +123,24 @@ module Build
123
123
  self.class.new(@full_path + extension, @root)
124
124
  end
125
125
 
126
+ # Add a path component to the current path.
127
+ # @param path [String, nil] (Optionally) the path to append.
126
128
  def +(path)
127
- self.class.new(File.join(@full_path, path), @root)
129
+ if path
130
+ self.class.new(File.join(@full_path, path), @root)
131
+ else
132
+ self
133
+ end
128
134
  end
129
135
 
130
- # Define a new root with a sub-path:
136
+ # Use the current path to define a new root, with an optional sub-path.
137
+ # @param path [String, nil] (Optionally) the path to append.
131
138
  def /(path)
132
- self.class.new(File.join(self, path), self)
139
+ if path
140
+ self.class.new(File.join(self, path), self)
141
+ else
142
+ self.class.new(self, self)
143
+ end
133
144
  end
134
145
 
135
146
  def rebase(root)
@@ -20,6 +20,6 @@
20
20
 
21
21
  module Build
22
22
  module Files
23
- VERSION = "1.3.0"
23
+ VERSION = "1.4.0"
24
24
  end
25
25
  end
@@ -25,76 +25,66 @@ require 'build/files/path'
25
25
  require 'build/files/system'
26
26
  require 'build/files/directory'
27
27
 
28
- module Build::Files::MonitorSpec
29
- include Build::Files
28
+ RSpec.shared_examples_for Monitor do |driver|
29
+ let(:root) {Build::Files::Path.expand('tmp', __dir__)}
30
+ let(:path) {root + "test.txt"}
30
31
 
31
- ROOT = File.expand_path('../tmp', __FILE__)
32
+ before do
33
+ root.delete
34
+ root.create
35
+ end
32
36
 
33
- describe Build::Files::Monitor do
34
- shared_examples_for Monitor do |driver|
35
- let(:path) {Path.new(ROOT) + "test.txt"}
36
-
37
- before(:all) do
38
- Path.new(ROOT).create
39
- end
40
-
41
- after(:all) do
42
- Path.new(ROOT).delete
43
- end
37
+ let(:directory) {Build::Files::Directory.new(root)}
38
+ let(:monitor) {Build::Files::Monitor.new}
39
+
40
+ it "should include touched path" do
41
+ path.touch
42
+
43
+ expect(directory.to_a).to include(path)
44
+ end
45
+
46
+ it 'should detect additions' do
47
+ changed = false
48
+
49
+ monitor.track_changes(directory) do |state|
50
+ changed = true
44
51
 
45
- it 'should detect additions' do
46
- directory = Build::Files::Directory.new(ROOT)
47
- monitor = Build::Files::Monitor.new
48
-
49
- changed = false
50
-
51
- monitor.track_changes(directory) do |state|
52
- changed = state.added.include? path
53
- end
54
-
55
- touched = false
56
- triggered = 0
57
-
58
- thread = Thread.new do
59
- while triggered == 0 or touched == false
60
- sleep 0.1 if touched
61
-
62
- path.touch
63
- touched = true
64
- end
65
- end
66
-
67
- monitor.run(driver: driver) do
68
- triggered += 1
69
-
70
- throw :interrupt if touched
71
- end
72
-
73
- thread.join
74
-
75
- expect(changed).to be true
76
- expect(triggered).to be >= 1
77
- end
52
+ expect(state.added).to include(path)
78
53
  end
79
54
 
80
- # Use the cross-platform driver, :polling
81
- it_behaves_like Monitor, :polling
55
+ thread = Thread.new do
56
+ sleep 1
57
+ path.touch
58
+ end
82
59
 
83
- # Use the native platform driver, e.g. fsevent or inotify.
84
- it_behaves_like Monitor
60
+ monitor.run do
61
+ throw :interrupt if changed
62
+ end
85
63
 
86
- it "should add and remove monitored paths" do
87
- directory = Build::Files::Directory.new(ROOT)
88
- monitor = Build::Files::Monitor.new
89
-
90
- handler = monitor.track_changes(directory) do |state|
91
- end
92
-
93
- expect(monitor.roots).to be_include ROOT
94
-
95
- handler.remove!
96
-
97
- expect(monitor.roots).to be_empty
64
+ thread.join
65
+
66
+ expect(changed).to be true
67
+ end
68
+
69
+ it "should add and remove monitored paths" do
70
+ handler = monitor.track_changes(directory) do |state|
71
+ # Do nothing.
98
72
  end
73
+
74
+ expect(monitor.roots).to be_include root
75
+
76
+ handler.remove!
77
+
78
+ expect(monitor.roots).to be_empty
99
79
  end
100
80
  end
81
+
82
+ RSpec.describe Build::Files::Monitor::Polling do
83
+ it_behaves_like Monitor
84
+ end
85
+
86
+ if defined? Build::Files::Monitor::Native
87
+ RSpec.describe Build::Files::Monitor::Native do
88
+ it_behaves_like Monitor
89
+ end
90
+ end
@@ -23,173 +23,177 @@ require 'build/files/path'
23
23
 
24
24
  require 'pathname'
25
25
 
26
- module Build::Files::PathSpec
27
- include Build::Files
28
-
29
- describe Build::Files::Path do
30
- it "should expand the path" do
31
- expect(Build::Files::Path.expand("foo", "/bar")).to be == "/bar/foo"
32
- end
33
- end
34
-
35
- describe Build::Files::Path.new("/test") do
36
- it "should start_with? full path" do
37
- expect(subject).to be_start_with '/test'
38
- end
39
-
40
- it "should start_with? partial pattern" do
41
- expect(subject).to be_start_with '/te'
42
- end
43
- end
44
-
45
- describe Build::Files::Path.new("/foo/bar.txt") do
46
- it "should replace existing file extension" do
47
- expect(subject.with(extension: '.jpeg', basename: true)).to be == "/foo/bar.jpeg"
48
- end
49
-
50
- it "should append file extension" do
51
- expect(subject.with(extension: '.jpeg')).to be == "/foo/bar.txt.jpeg"
52
- end
53
-
54
- it "should change basename" do
55
- expect(subject.with(basename: 'baz', extension: '.txt')).to be == "/foo/baz.txt"
56
- end
57
- end
58
-
59
- describe Build::Files::Path.new("/foo/bar/baz", "/foo") do
60
- it "should be inspectable" do
61
- expect(subject.inspect).to be_include subject.root.to_s
62
- expect(subject.inspect).to be_include subject.relative_path.to_s
63
- end
64
-
65
- it "should convert to path" do
66
- pathname = Pathname("/foo/bar/baz")
67
-
68
- expect(Path[pathname]).to be == subject
69
- expect(Path["/foo/bar/baz"]).to be == subject
70
- end
71
-
72
- it "should be equal" do
73
- expect(subject).to be_eql subject
74
- expect(subject).to be == subject
75
-
76
- different_root_path = Path.join("/foo/bar", "baz")
77
- expect(subject).to_not be_eql different_root_path
78
- expect(subject).to be == different_root_path
79
- end
80
-
81
- it "should convert to string" do
82
- expect(subject.to_s).to be == "/foo/bar/baz"
83
-
84
- # The to_str method should return the full path (i.e. the same as to_s):
85
- expect(subject.to_s).to be == subject.to_str
86
-
87
- # Check the equality operator:
88
- expect(subject).to be == subject.dup
89
-
90
- # The length should be reported correctly:
91
- expect(subject.length).to be == subject.to_s.length
92
-
93
- # Check the return types:
94
- expect(subject).to be_kind_of Path
95
- expect(subject.root).to be_kind_of String
96
- expect(subject.relative_path).to be_kind_of String
97
- end
98
-
99
- it "should consist of parts" do
100
- expect(subject.parts).to be == ["", "foo", "bar", "baz"]
101
-
102
- expect(subject.root).to be == "/foo"
103
-
104
- expect(subject.relative_path).to be == "bar/baz"
105
-
106
- expect(subject.relative_parts).to be == ["bar", "baz"]
107
- end
108
-
109
- it "should have a new extension" do
110
- renamed_path = subject.with(root: '/tmp', extension: '.txt')
111
-
112
- expect(renamed_path.root).to be == '/tmp'
113
-
114
- expect(renamed_path.relative_path).to be == 'bar/baz.txt'
115
-
116
- object_path = subject.append(".o")
117
-
118
- expect(object_path.root).to be == "/foo"
119
- expect(object_path.relative_path).to be == "bar/baz.o"
120
- end
121
-
122
- it "should give the shortest path for outer paths" do
123
- input = Path.new("/a/b/c/file.cpp")
124
- output = Path.new("/a/b/c/d/e/")
125
-
126
- expect(input.root).to be == "/a/b/c"
127
- expect(output.root).to be == "/a/b/c/d/e"
128
-
129
- short = input.shortest_path(output)
130
-
131
- expect(short).to be == "../../file.cpp"
132
-
133
- expect(File.expand_path(short, output)).to be == input
134
- end
135
-
136
- it "should give the shortest path for inner paths" do
137
- input = Path.new("/a/b/c/file.cpp")
138
- output = Path.new("/a/")
139
-
140
- expect(input.root).to be == "/a/b/c"
141
- expect(output.root).to be == "/a"
142
-
143
- short = input.shortest_path(output)
144
-
145
- expect(short).to be == "b/c/file.cpp"
146
-
147
- expect(File.expand_path(short, output)).to be == input
148
- end
149
-
150
- it "should append a path" do
151
- subject = Path.new("/a/b/c")
152
-
153
- expect(subject + "d/e/f").to be == "/a/b/c/d/e/f"
154
- end
155
-
156
- it "should give a list of components" do
157
- expect(Path.components(subject)).to be == ["", "foo", "bar", "baz"]
158
- expect(Path.components(subject.to_s)).to be == ["", "foo", "bar", "baz"]
159
- end
160
-
161
- it "should give a basename" do
162
- expect(subject.basename).to be == "baz"
163
- end
164
-
165
- it "should have a new root" do
166
- rerooted_path = subject / "cat"
167
-
168
- expect(rerooted_path.root).to be == "/foo/bar/baz"
169
- expect(rerooted_path.relative_path).to be == "cat"
170
- end
171
-
172
- it "should give correct modes for reading" do
173
- expect(subject.for_reading).to be == [subject.to_s, File::RDONLY]
174
- end
175
-
176
- it "should give correct modes for writing" do
177
- expect(subject.for_writing).to be == [subject.to_s, File::CREAT|File::TRUNC|File::WRONLY]
178
- end
179
-
180
- it "should give correct modes for appending" do
181
- expect(subject.for_appending).to be == [subject.to_s, File::CREAT|File::APPEND|File::WRONLY]
182
- end
183
-
184
- it "should match against relative path" do
185
- expect(subject.match(subject.relative_path)).to be_truthy
186
- expect(subject.match("*/baz")).to be_truthy
187
- expect(subject.match("/baz")).to be_falsey
188
- end
189
-
190
- it "should match against absolute path" do
191
- expect(subject.match(subject.to_s)).to be_truthy
192
- expect(subject.match("/foo/**")).to be_truthy
193
- end
26
+ RSpec.describe Build::Files::Path do
27
+ it "should expand the path" do
28
+ expect(Build::Files::Path.expand("foo", "/bar")).to be == "/bar/foo"
29
+ end
30
+
31
+ it "should give the shortest path for outer paths" do
32
+ input = Build::Files::Path.new("/a/b/c/file.cpp")
33
+ output = Build::Files::Path.new("/a/b/c/d/e/")
34
+
35
+ expect(input.root).to be == "/a/b/c"
36
+ expect(output.root).to be == "/a/b/c/d/e"
37
+
38
+ short = input.shortest_path(output)
39
+
40
+ expect(short).to be == "../../file.cpp"
41
+
42
+ expect(File.expand_path(short, output)).to be == input
43
+ end
44
+
45
+ it "should give the shortest path for inner paths" do
46
+ input = Build::Files::Path.new("/a/b/c/file.cpp")
47
+ output = Build::Files::Path.new("/a/")
48
+
49
+ expect(input.root).to be == "/a/b/c"
50
+ expect(output.root).to be == "/a"
51
+
52
+ short = input.shortest_path(output)
53
+
54
+ expect(short).to be == "b/c/file.cpp"
55
+
56
+ expect(File.expand_path(short, output)).to be == input
57
+ end
58
+ end
59
+
60
+ RSpec.describe Build::Files::Path.new("/test") do
61
+ it "should start_with? full path" do
62
+ expect(subject).to be_start_with '/test'
63
+ end
64
+
65
+ it "should start_with? partial pattern" do
66
+ expect(subject).to be_start_with '/te'
67
+ end
68
+ end
69
+
70
+ RSpec.describe Build::Files::Path.new("/foo/bar.txt") do
71
+ it "should replace existing file extension" do
72
+ expect(subject.with(extension: '.jpeg', basename: true)).to be == "/foo/bar.jpeg"
73
+ end
74
+
75
+ it "should append file extension" do
76
+ expect(subject.with(extension: '.jpeg')).to be == "/foo/bar.txt.jpeg"
77
+ end
78
+
79
+ it "should change basename" do
80
+ expect(subject.with(basename: 'baz', extension: '.txt')).to be == "/foo/baz.txt"
81
+ end
82
+ end
83
+
84
+ RSpec.describe Build::Files::Path.new("/foo/bar/baz", "/foo") do
85
+ it "can add nil path" do
86
+ expect(subject + nil).to be == subject
87
+ end
88
+
89
+ it "can add nil root" do
90
+ expect(subject / nil).to be == subject
91
+ end
92
+
93
+ it "should be inspectable" do
94
+ expect(subject.inspect).to be_include subject.root.to_s
95
+ expect(subject.inspect).to be_include subject.relative_path.to_s
96
+ end
97
+
98
+ it "should convert to path" do
99
+ pathname = Pathname("/foo/bar/baz")
100
+
101
+ expect(Build::Files::Path[pathname]).to be == subject
102
+ expect(Build::Files::Path["/foo/bar/baz"]).to be == subject
103
+ end
104
+
105
+ it "should be equal" do
106
+ expect(subject).to be_eql subject
107
+ expect(subject).to be == subject
108
+
109
+ different_root_path = Build::Files::Path.join("/foo/bar", "baz")
110
+ expect(subject).to_not be_eql different_root_path
111
+ expect(subject).to be == different_root_path
112
+ end
113
+
114
+ it "should convert to string" do
115
+ expect(subject.to_s).to be == "/foo/bar/baz"
116
+
117
+ # The to_str method should return the full path (i.e. the same as to_s):
118
+ expect(subject.to_s).to be == subject.to_str
119
+
120
+ # Check the equality operator:
121
+ expect(subject).to be == subject.dup
122
+
123
+ # The length should be reported correctly:
124
+ expect(subject.length).to be == subject.to_s.length
125
+
126
+ # Check the return types:
127
+ expect(subject).to be_kind_of Build::Files::Path
128
+ expect(subject.root).to be_kind_of String
129
+ expect(subject.relative_path).to be_kind_of String
130
+ end
131
+
132
+ it "should consist of parts" do
133
+ expect(subject.parts).to be == ["", "foo", "bar", "baz"]
134
+
135
+ expect(subject.root).to be == "/foo"
136
+
137
+ expect(subject.relative_path).to be == "bar/baz"
138
+
139
+ expect(subject.relative_parts).to be == ["bar", "baz"]
140
+ end
141
+
142
+ it "should have a new extension" do
143
+ renamed_path = subject.with(root: '/tmp', extension: '.txt')
144
+
145
+ expect(renamed_path.root).to be == '/tmp'
146
+
147
+ expect(renamed_path.relative_path).to be == 'bar/baz.txt'
148
+
149
+ object_path = subject.append(".o")
150
+
151
+ expect(object_path.root).to be == "/foo"
152
+ expect(object_path.relative_path).to be == "bar/baz.o"
153
+ end
154
+
155
+ it "should append a path" do
156
+ subject = Build::Files::Path.new("/a/b/c")
157
+
158
+ expect(subject + "d/e/f").to be == "/a/b/c/d/e/f"
159
+ end
160
+
161
+ it "should give a list of components" do
162
+ expect(Build::Files::Path.components(subject)).to be == ["", "foo", "bar", "baz"]
163
+ expect(Build::Files::Path.components(subject.to_s)).to be == ["", "foo", "bar", "baz"]
164
+ end
165
+
166
+ it "should give a basename" do
167
+ expect(subject.basename).to be == "baz"
168
+ end
169
+
170
+ it "should have a new root" do
171
+ rerooted_path = subject / "cat"
172
+
173
+ expect(rerooted_path.root).to be == "/foo/bar/baz"
174
+ expect(rerooted_path.relative_path).to be == "cat"
175
+ end
176
+
177
+ it "should give correct modes for reading" do
178
+ expect(subject.for_reading).to be == [subject.to_s, File::RDONLY]
179
+ end
180
+
181
+ it "should give correct modes for writing" do
182
+ expect(subject.for_writing).to be == [subject.to_s, File::CREAT|File::TRUNC|File::WRONLY]
183
+ end
184
+
185
+ it "should give correct modes for appending" do
186
+ expect(subject.for_appending).to be == [subject.to_s, File::CREAT|File::APPEND|File::WRONLY]
187
+ end
188
+
189
+ it "should match against relative path" do
190
+ expect(subject.match(subject.relative_path)).to be_truthy
191
+ expect(subject.match("*/baz")).to be_truthy
192
+ expect(subject.match("/baz")).to be_falsey
193
+ end
194
+
195
+ it "should match against absolute path" do
196
+ expect(subject.match(subject.to_s)).to be_truthy
197
+ expect(subject.match("/foo/**")).to be_truthy
194
198
  end
195
199
  end