build-files 1.4.2 → 1.7.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 227e24df878d95517873d1f07ecc789cff1320ad76090f1da90b3273f46c901e
4
- data.tar.gz: f086ae85dcabc453cb5ee6155af0d6357db47d828eb0b7733461ecd2065b97d0
3
+ metadata.gz: 266f5c2f5a839934620afe4a06289bf7c4363d39c04b39fc3fef8b87104d7ff1
4
+ data.tar.gz: b775a997766d4ff2fc751e08fc93d6512a373e944813ee09097731f432087714
5
5
  SHA512:
6
- metadata.gz: ce4cf1a75166c6cfa4acf0703b05e727c3ec2422ee1f29abfc11c3046f9ede6d6460a314ea449b9346bff2f352c137c2bb0d1f061b34ef71ecfda448020ee79e
7
- data.tar.gz: 7bcc9d126cf37ae30527b908c0f0f4f799f85eb0ea83ff4dd588393c745ea587e334a6141ceadef393e2b7ca5edcb0aa0343d5f5dda8a3c70b5e38a3eb38a101
6
+ metadata.gz: 303be1f16bcadc201db4fd2c9530250926942e1f3f63d269646d943ca439b2c2ca930c0a9fc5caeb16fc4c35362c84580699f5732917f9df51739f339aa7640c
7
+ data.tar.gz: 52f6d5d1441dcd15d5be335cab08620aa41524aef8294a13c4adaa2df5513d9e1a03b29b42336afe29b98bdd6f8f1c53c1eb90d7279d855738acfe4e0a29be3b
data/.gitignore ADDED
@@ -0,0 +1,24 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
18
+ *.bundle
19
+ *.so
20
+ *.o
21
+ *.a
22
+ mkmf.log
23
+ .rspec_status
24
+
data/build-files.gemspec CHANGED
@@ -18,11 +18,8 @@ Gem::Specification.new do |spec|
18
18
 
19
19
  spec.required_ruby_version = '>= 2.0'
20
20
 
21
- spec.add_dependency "rb-inotify"
22
- spec.add_dependency "rb-fsevent"
23
-
24
21
  spec.add_development_dependency "covered"
25
22
  spec.add_development_dependency "bundler"
26
23
  spec.add_development_dependency "rspec", "~> 3.4"
27
- spec.add_development_dependency "rake"
24
+ spec.add_development_dependency "bake-bundler"
28
25
  end
data/lib/build/files.rb CHANGED
@@ -24,7 +24,4 @@ require_relative 'files/paths'
24
24
  require_relative 'files/glob'
25
25
  require_relative 'files/directory'
26
26
 
27
- require_relative 'files/state'
28
- require_relative 'files/monitor'
29
-
30
27
  require_relative 'files/system'
@@ -44,12 +44,15 @@ module Build
44
44
  def full_pattern
45
45
  Path.join(@root, @pattern)
46
46
  end
47
-
47
+
48
48
  # Enumerate all paths matching the pattern.
49
49
  def each(&block)
50
- return to_enum(:each) unless block_given?
50
+ return to_enum unless block_given?
51
51
 
52
- Dir.glob(full_pattern) do |path|
52
+ ::Dir.glob(full_pattern, ::File::FNM_DOTMATCH) do |path|
53
+ # Ignore `.` and `..` entries.
54
+ next if path =~ /\/..?$/
55
+
53
56
  yield Path.new(path, @root)
54
57
  end
55
58
  end
@@ -57,15 +60,15 @@ module Build
57
60
  def eql?(other)
58
61
  self.class.eql?(other.class) and @root.eql?(other.root) and @pattern.eql?(other.pattern)
59
62
  end
60
-
63
+
61
64
  def hash
62
65
  [@root, @pattern].hash
63
66
  end
64
-
67
+
65
68
  def include?(path)
66
69
  File.fnmatch(full_pattern, path)
67
70
  end
68
-
71
+
69
72
  def rebase(root)
70
73
  self.class.new(root, @pattern)
71
74
  end
@@ -55,13 +55,13 @@ module Build
55
55
  other.any?{|path| include?(path)}
56
56
  end
57
57
 
58
- def with(**args)
59
- return to_enum(:with, **args) unless block_given?
58
+ def with(**options)
59
+ return to_enum(:with, **options) unless block_given?
60
60
 
61
61
  paths = []
62
62
 
63
- each do |path|
64
- updated_path = path.with(args)
63
+ self.each do |path|
64
+ updated_path = path.with(**options)
65
65
 
66
66
  yield path, updated_path
67
67
 
@@ -22,6 +22,10 @@ module Build
22
22
  module Files
23
23
  # Represents a file path with an absolute root and a relative offset:
24
24
  class Path
25
+ def self.current
26
+ self.new(::Dir.pwd)
27
+ end
28
+
25
29
  def self.split(path)
26
30
  # Effectively dirname and basename:
27
31
  dirname, separator, filename = path.rpartition(File::SEPARATOR)
@@ -44,6 +48,14 @@ module Build
44
48
  end
45
49
  end
46
50
 
51
+ def self.root(path)
52
+ if Path === path
53
+ path.root
54
+ else
55
+ File.dirname(path)
56
+ end
57
+ end
58
+
47
59
  # Return the shortest relative path to get to path from root. Root should be a directory with which you are computing the relative path.
48
60
  def self.shortest_path(path, root)
49
61
  path_components = Path.components(path)
@@ -55,7 +67,13 @@ module Build
55
67
  # The difference between the root path and the required path, taking into account the common prefix:
56
68
  up = root_components.size - i
57
69
 
58
- return File.join([".."] * up + path_components[i..-1])
70
+ components = [".."] * up + path_components[i..-1]
71
+
72
+ if components.empty?
73
+ return "."
74
+ else
75
+ return File.join(components)
76
+ end
59
77
  end
60
78
 
61
79
  def self.relative_path(root, full_path)
@@ -83,9 +101,6 @@ module Build
83
101
  # Effectively dirname and basename:
84
102
  @root, _, @relative_path = full_path.rpartition(File::SEPARATOR)
85
103
  end
86
-
87
- # This improves the cost of hash/eql? slightly but the root cannot be deconstructed if it was an instance of Path.
88
- # @root = @root.to_s
89
104
  end
90
105
 
91
106
  attr :root
@@ -103,6 +118,17 @@ module Build
103
118
  self.parts.last
104
119
  end
105
120
 
121
+ def parent
122
+ root = @root
123
+ full_path = File.dirname(@full_path)
124
+
125
+ while root.size > full_path.size
126
+ root = Path.root(root)
127
+ end
128
+
129
+ self.class.new(full_path, root)
130
+ end
131
+
106
132
  def start_with?(*args)
107
133
  @full_path.start_with?(*args)
108
134
  end
@@ -110,7 +136,7 @@ module Build
110
136
  alias parts components
111
137
 
112
138
  def relative_path
113
- @relative_path ||= Path.relative_path(@root.to_s, @full_path).freeze
139
+ @relative_path ||= Path.relative_path(@root.to_s, @full_path.to_s).freeze
114
140
  end
115
141
 
116
142
  def relative_parts
@@ -170,12 +196,12 @@ module Build
170
196
  self.new(File.join(root, relative_path), root)
171
197
  end
172
198
 
173
- # Expand a subpath within a given root, similar to `File.expand_path`
174
- def self.expand(subpath, root = Dir.getwd)
175
- if subpath.start_with? File::SEPARATOR
176
- self.new(subpath)
199
+ # Expand a path within a given root.
200
+ def self.expand(path, root = Dir.getwd)
201
+ if path.start_with?(File::SEPARATOR)
202
+ self.new(path)
177
203
  else
178
- self.join(root, subpath)
204
+ self.join(root, path)
179
205
  end
180
206
  end
181
207
 
@@ -184,7 +210,7 @@ module Build
184
210
  end
185
211
 
186
212
  def to_str
187
- @full_path
213
+ @full_path.to_str
188
214
  end
189
215
 
190
216
  def to_path
@@ -192,7 +218,8 @@ module Build
192
218
  end
193
219
 
194
220
  def to_s
195
- @full_path
221
+ # It's not guaranteed to be string.
222
+ @full_path.to_s
196
223
  end
197
224
 
198
225
  def inspect
@@ -69,5 +69,11 @@ module Build
69
69
  self.new(paths, [root])
70
70
  end
71
71
  end
72
+
73
+ class Path
74
+ def list(*relative_paths)
75
+ Paths.directory(self, relative_paths)
76
+ end
77
+ end
72
78
  end
73
79
  end
@@ -20,6 +20,6 @@
20
20
 
21
21
  module Build
22
22
  module Files
23
- VERSION = "1.4.2"
23
+ VERSION = "1.7.0"
24
24
  end
25
25
  end
@@ -20,30 +20,34 @@
20
20
 
21
21
  require 'build/files/glob'
22
22
 
23
- module Build::Files::GlobSpec
24
- include Build::Files
23
+ RSpec.describe Build::Files::Glob do
24
+ let(:path) {Build::Files::Path.new(__dir__)}
25
25
 
26
- describe Build::Files::Glob do
27
- let(:path) {Path.new(__dir__)}
26
+ it "can glob paths" do
27
+ paths = path.glob("*.rb")
28
28
 
29
- it "can glob paths" do
30
- paths = path.glob("*.rb")
31
-
32
- expect(paths.count).to be >= 1
33
- end
29
+ expect(paths.count).to be >= 1
30
+ end
31
+
32
+ it "can be used as key in hash" do
33
+ cache = {}
34
34
 
35
- it "can be used as key in hash" do
36
- cache = {}
37
-
38
- cache[path.glob("*.rb")] = true
39
-
40
- expect(cache).to be_include(path.glob("*.rb"))
41
- end
35
+ cache[path.glob("*.rb")] = true
36
+
37
+ expect(cache).to be_include(path.glob("*.rb"))
38
+ end
39
+
40
+ it "should print nice string represenation" do
41
+ glob = Build::Files::Glob.new(".", "*.rb")
42
42
 
43
- it "should print nice string represenation" do
44
- glob = Build::Files::Glob.new(".", "*.rb")
43
+ expect("#{glob}").to be == '<Glob "."/"*.rb">'
44
+ end
45
+
46
+ context 'with dotfiles' do
47
+ it "should list files starting with dot" do
48
+ paths = path.glob("glob_spec/dotfiles/**/*")
45
49
 
46
- expect("#{glob}").to be == '<Glob "."/"*.rb">'
50
+ expect(paths.count).to be == 1
47
51
  end
48
52
  end
49
53
  end
File without changes
@@ -24,10 +24,20 @@ require 'build/files/path'
24
24
  require 'pathname'
25
25
 
26
26
  RSpec.describe Build::Files::Path do
27
+ it "can get current path" do
28
+ expect(Build::Files::Path.current.full_path).to be == Dir.pwd
29
+ end
30
+
27
31
  it "should expand the path" do
28
32
  expect(Build::Files::Path.expand("foo", "/bar")).to be == "/bar/foo"
29
33
  end
30
34
 
35
+ it "should give current path" do
36
+ path = Build::Files::Path.new("/a/b/c/file.cpp")
37
+
38
+ expect(path.shortest_path(path)).to be == "."
39
+ end
40
+
31
41
  it "should give the shortest path for outer paths" do
32
42
  input = Build::Files::Path.new("/a/b/c/file.cpp")
33
43
  output = Build::Files::Path.new("/a/b/c/d/e/")
@@ -82,10 +92,24 @@ RSpec.describe Build::Files::Path.new("/foo/bar.txt") do
82
92
  end
83
93
 
84
94
  RSpec.describe Build::Files::Path.new("/foo/bar/baz", "/foo") do
95
+ it "can compute parent path" do
96
+ parent = subject.parent
97
+
98
+ expect(parent.root).to be == subject.root
99
+ expect(parent.relative_path).to be == "bar"
100
+ expect(parent.full_path).to be == "/foo/bar"
101
+ end
102
+
85
103
  it "can add nil path" do
86
104
  expect(subject + nil).to be == subject
87
105
  end
88
106
 
107
+ it "can inspect path with nil root" do
108
+ expect do
109
+ (subject / nil).inspect
110
+ end.to_not raise_error
111
+ end
112
+
89
113
  it "can add nil root" do
90
114
  expect(subject / nil).to be == subject
91
115
  end
metadata CHANGED
@@ -1,43 +1,15 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: build-files
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.4.2
4
+ version: 1.7.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Samuel Williams
8
- autorequire:
8
+ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2019-02-14 00:00:00.000000000 Z
11
+ date: 2021-05-22 00:00:00.000000000 Z
12
12
  dependencies:
13
- - !ruby/object:Gem::Dependency
14
- name: rb-inotify
15
- requirement: !ruby/object:Gem::Requirement
16
- requirements:
17
- - - ">="
18
- - !ruby/object:Gem::Version
19
- version: '0'
20
- type: :runtime
21
- prerelease: false
22
- version_requirements: !ruby/object:Gem::Requirement
23
- requirements:
24
- - - ">="
25
- - !ruby/object:Gem::Version
26
- version: '0'
27
- - !ruby/object:Gem::Dependency
28
- name: rb-fsevent
29
- requirement: !ruby/object:Gem::Requirement
30
- requirements:
31
- - - ">="
32
- - !ruby/object:Gem::Version
33
- version: '0'
34
- type: :runtime
35
- prerelease: false
36
- version_requirements: !ruby/object:Gem::Requirement
37
- requirements:
38
- - - ">="
39
- - !ruby/object:Gem::Version
40
- version: '0'
41
13
  - !ruby/object:Gem::Dependency
42
14
  name: covered
43
15
  requirement: !ruby/object:Gem::Requirement
@@ -81,7 +53,7 @@ dependencies:
81
53
  - !ruby/object:Gem::Version
82
54
  version: '3.4'
83
55
  - !ruby/object:Gem::Dependency
84
- name: rake
56
+ name: bake-bundler
85
57
  requirement: !ruby/object:Gem::Requirement
86
58
  requirements:
87
59
  - - ">="
@@ -94,50 +66,43 @@ dependencies:
94
66
  - - ">="
95
67
  - !ruby/object:Gem::Version
96
68
  version: '0'
97
- description:
69
+ description:
98
70
  email:
99
71
  - samuel.williams@oriontransfer.co.nz
100
72
  executables: []
101
73
  extensions: []
102
74
  extra_rdoc_files: []
103
75
  files:
76
+ - ".gitignore"
104
77
  - ".rspec"
105
78
  - ".travis.yml"
106
79
  - Gemfile
107
80
  - README.md
108
- - Rakefile
109
81
  - build-files.gemspec
110
82
  - lib/build/files.rb
111
83
  - lib/build/files/composite.rb
112
84
  - lib/build/files/difference.rb
113
85
  - lib/build/files/directory.rb
114
86
  - lib/build/files/glob.rb
115
- - lib/build/files/handle.rb
116
87
  - lib/build/files/list.rb
117
- - lib/build/files/monitor.rb
118
- - lib/build/files/monitor/fsevent.rb
119
- - lib/build/files/monitor/inotify.rb
120
- - lib/build/files/monitor/polling.rb
121
88
  - lib/build/files/path.rb
122
89
  - lib/build/files/paths.rb
123
- - lib/build/files/state.rb
124
90
  - lib/build/files/system.rb
125
91
  - lib/build/files/version.rb
126
92
  - spec/build/files/directory_spec.rb
127
93
  - spec/build/files/directory_spec/.dot_file.yaml
128
94
  - spec/build/files/directory_spec/normal_file.txt
129
95
  - spec/build/files/glob_spec.rb
96
+ - spec/build/files/glob_spec/dotfiles/.file
130
97
  - spec/build/files/list_spec.rb
131
- - spec/build/files/monitor_spec.rb
132
98
  - spec/build/files/path_spec.rb
133
- - spec/build/files/state_spec.rb
134
99
  - spec/build/files/system_spec.rb
135
100
  - spec/spec_helper.rb
136
101
  homepage: ''
137
102
  licenses:
138
103
  - MIT
139
104
  metadata: {}
140
- post_install_message:
105
+ post_install_message:
141
106
  rdoc_options: []
142
107
  require_paths:
143
108
  - lib
@@ -152,8 +117,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
152
117
  - !ruby/object:Gem::Version
153
118
  version: '0'
154
119
  requirements: []
155
- rubygems_version: 3.0.2
156
- signing_key:
120
+ rubygems_version: 3.2.3
121
+ signing_key:
157
122
  specification_version: 4
158
123
  summary: Build::Files is a set of idiomatic classes for dealing with paths and monitoring
159
124
  directories.
@@ -162,9 +127,8 @@ test_files:
162
127
  - spec/build/files/directory_spec/.dot_file.yaml
163
128
  - spec/build/files/directory_spec/normal_file.txt
164
129
  - spec/build/files/glob_spec.rb
130
+ - spec/build/files/glob_spec/dotfiles/.file
165
131
  - spec/build/files/list_spec.rb
166
- - spec/build/files/monitor_spec.rb
167
132
  - spec/build/files/path_spec.rb
168
- - spec/build/files/state_spec.rb
169
133
  - spec/build/files/system_spec.rb
170
134
  - spec/spec_helper.rb
data/Rakefile DELETED
@@ -1,6 +0,0 @@
1
- require "bundler/gem_tasks"
2
- require "rspec/core/rake_task"
3
-
4
- RSpec::Core::RakeTask.new(:spec)
5
-
6
- task :default => :spec
@@ -1,59 +0,0 @@
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 'state'
22
-
23
- module Build
24
- module Files
25
- class Handle
26
- def initialize(monitor, files, &block)
27
- @monitor = monitor
28
- @state = State.new(files)
29
- @block = block
30
- end
31
-
32
- attr :monitor
33
-
34
- def commit!
35
- @state.update!
36
- end
37
-
38
- def directories
39
- @state.files.roots
40
- end
41
-
42
- def remove!
43
- @monitor.delete(self)
44
- end
45
-
46
- # Inform the handle that it might have been modified.
47
- def changed!
48
- # If @state.update! did not find any changes, don't invoke the callback:
49
- if @state.update!
50
- @block.call(@state)
51
- end
52
- end
53
-
54
- def to_s
55
- "\#<#{self.class} @state=#{@state} @block=#{@block}>"
56
- end
57
- end
58
- end
59
- end
@@ -1,43 +0,0 @@
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
- module Build
22
- module Files
23
- module Monitor
24
- case RUBY_PLATFORM
25
- when /linux/i
26
- require_relative 'monitor/inotify'
27
- Native = INotify
28
- Default = Native
29
- when /darwin/i
30
- require_relative 'monitor/fsevent'
31
- Native = FSEvent
32
- Default = Native
33
- else
34
- require_relative 'monitor/polling'
35
- Default = Polling
36
- end
37
-
38
- def self.new(*args)
39
- Default.new(*args)
40
- end
41
- end
42
- end
43
- end
@@ -1,55 +0,0 @@
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
@@ -1,53 +0,0 @@
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
@@ -1,145 +0,0 @@
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
@@ -1,172 +0,0 @@
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 'list'
22
-
23
- require 'forwardable'
24
-
25
- module Build
26
- module Files
27
- # Represents a specific file on disk with a specific mtime.
28
- class FileTime
29
- include Comparable
30
-
31
- def initialize(path, time)
32
- @path = path
33
- @time = time
34
- end
35
-
36
- attr :path
37
- attr :time
38
-
39
- def <=> other
40
- @time <=> other.time
41
- end
42
-
43
- def inspect
44
- "<FileTime #{@path.inspect} #{@time.inspect}>"
45
- end
46
- end
47
-
48
- # A stateful list of files captured at a specific time, which can then be checked for changes.
49
- class State < Files::List
50
- extend Forwardable
51
-
52
- def initialize(files)
53
- raise ArgumentError.new("Invalid files list: #{files}") unless Files::List === files
54
-
55
- @files = files
56
-
57
- @times = {}
58
-
59
- update!
60
- end
61
-
62
- attr :files
63
-
64
- attr :added
65
- attr :removed
66
- attr :changed
67
- attr :missing
68
-
69
- attr :times
70
-
71
- def_delegators :@files, :each, :roots, :count
72
-
73
- def update!
74
- last_times = @times
75
- @times = {}
76
-
77
- @added = []
78
- @removed = []
79
- @changed = []
80
- @missing = []
81
-
82
- file_times = []
83
-
84
- @files.each do |path|
85
- # When processing the same path twice (perhaps by accident), we should skip it otherwise it might cause issues when being deleted from last_times multuple times.
86
- next if @times.include? path
87
-
88
- if File.exist?(path)
89
- modified_time = File.mtime(path)
90
-
91
- if last_time = last_times.delete(path)
92
- # Path was valid last update:
93
- if modified_time != last_time
94
- @changed << path
95
-
96
- # puts "Changed: #{path}"
97
- end
98
- else
99
- # Path didn't exist before:
100
- @added << path
101
-
102
- # puts "Added: #{path}"
103
- end
104
-
105
- @times[path] = modified_time
106
-
107
- unless File.directory?(path)
108
- file_times << FileTime.new(path, modified_time)
109
- end
110
- else
111
- @missing << path
112
-
113
- # puts "Missing: #{path}"
114
- end
115
- end
116
-
117
- @removed = last_times.keys
118
- # puts "Removed: #{@removed.inspect}" if @removed.size > 0
119
-
120
- @oldest_time = file_times.min
121
- @newest_time = file_times.max
122
-
123
- return @added.size > 0 || @changed.size > 0 || @removed.size > 0 || @missing.size > 0
124
- end
125
-
126
- attr :oldest_time
127
- attr :newest_time
128
-
129
- def missing?
130
- !@missing.empty?
131
- end
132
-
133
- def empty?
134
- @times.empty?
135
- end
136
-
137
- def inspect
138
- "<State Added:#{@added} Removed:#{@removed} Changed:#{@changed} Missing:#{@missing}>"
139
- end
140
-
141
- # Are these (output) files dirty with respect to the given inputs?
142
- def dirty?(inputs)
143
- if self.missing?
144
- return true
145
- end
146
-
147
- # If there are no inputs or no outputs, we are always clean:
148
- if inputs.empty? or self.empty?
149
- return false
150
- end
151
-
152
- oldest_output_time = self.oldest_time
153
- newest_input_time = inputs.newest_time
154
-
155
- if newest_input_time and oldest_output_time
156
- # We are dirty if any inputs are newer (bigger) than any outputs:
157
- if newest_input_time > oldest_output_time
158
- return true
159
- else
160
- return false
161
- end
162
- end
163
-
164
- return true
165
- end
166
-
167
- def self.dirty?(inputs, outputs)
168
- outputs.dirty?(inputs)
169
- end
170
- end
171
- end
172
- end
@@ -1,90 +0,0 @@
1
- #!/usr/bin/env rspec
2
-
3
- # Copyright, 2015, by Samuel G. D. Williams. <http://www.codeotaku.com>
4
- #
5
- # Permission is hereby granted, free of charge, to any person obtaining a copy
6
- # of this software and associated documentation files (the "Software"), to deal
7
- # in the Software without restriction, including without limitation the rights
8
- # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
- # copies of the Software, and to permit persons to whom the Software is
10
- # furnished to do so, subject to the following conditions:
11
- #
12
- # The above copyright notice and this permission notice shall be included in
13
- # all copies or substantial portions of the Software.
14
- #
15
- # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
- # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
- # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
- # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
- # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
- # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
- # THE SOFTWARE.
22
-
23
- require 'build/files/monitor'
24
- require 'build/files/path'
25
- require 'build/files/system'
26
- require 'build/files/directory'
27
-
28
- RSpec.shared_examples_for Monitor do |driver|
29
- let(:root) {Build::Files::Path.expand('tmp', __dir__)}
30
- let(:path) {root + "test.txt"}
31
-
32
- before do
33
- root.delete
34
- root.create
35
- end
36
-
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
51
-
52
- expect(state.added).to include(path)
53
- end
54
-
55
- thread = Thread.new do
56
- sleep 1
57
- path.touch
58
- end
59
-
60
- monitor.run do
61
- throw :interrupt if changed
62
- end
63
-
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.
72
- end
73
-
74
- expect(monitor.roots).to be_include root
75
-
76
- handler.remove!
77
-
78
- expect(monitor.roots).to be_empty
79
- end
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
@@ -1,90 +0,0 @@
1
- # Copyright, 2012, 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 'build/files'
22
-
23
- module Build::Files::StateSpec
24
- describe Build::Files::State do
25
- let(:files) {Build::Files::Glob.new(__dir__, "*.rb")}
26
-
27
- it "should have no changes initially" do
28
- state = Build::Files::State.new(files)
29
-
30
- expect(state.update!).to be false
31
-
32
- expect(state.changed).to be == []
33
- expect(state.added).to be == []
34
- expect(state.removed).to be == []
35
- expect(state.missing).to be == []
36
- end
37
-
38
- it "should report missing files" do
39
- rebased_files = files.to_paths.rebase(File.join(__dir__, 'foo'))
40
- state = Build::Files::State.new(rebased_files)
41
-
42
- # Some changes were detected:
43
- expect(state.update!).to be true
44
-
45
- # Some files are missing:
46
- expect(state.missing).to_not be_empty
47
- end
48
-
49
- it "should not be confused by duplicates" do
50
- state = Build::Files::State.new(files + files)
51
-
52
- expect(state.update!).to be false
53
-
54
- expect(state.changed).to be == []
55
- expect(state.added).to be == []
56
- expect(state.removed).to be == []
57
- expect(state.missing).to be == []
58
- end
59
- end
60
-
61
- describe Build::Files::State do
62
- before(:each) do
63
- @temporary_files = Build::Files::Paths.directory(__dir__, ['a'])
64
- @temporary_files.touch
65
-
66
- @new_files = Build::Files::State.new(@temporary_files)
67
- @old_files = Build::Files::State.new(Build::Files::Glob.new(__dir__, "*.rb"))
68
- end
69
-
70
- after(:each) do
71
- @temporary_files.delete
72
- end
73
-
74
- let(:empty) {Build::Files::State.new(Build::Files::List::NONE)}
75
-
76
- it "should be clean with empty inputs or outputs" do
77
- expect(Build::Files::State.dirty?(empty, @new_files)).to be false
78
- expect(Build::Files::State.dirty?(@new_files, empty)).to be false
79
- end
80
-
81
- it "should be clean if files are newer" do
82
- expect(Build::Files::State.dirty?(@old_files, @new_files)).to be false
83
- end
84
-
85
- it "should be dirty if files are modified" do
86
- # In this case, the file mtime is usually different so...
87
- expect(Build::Files::State.dirty?(@new_files, @old_files)).to be true
88
- end
89
- end
90
- end