build-files 1.3.0 → 1.4.0

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