build-graph 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 93ccee936d5902138b3cd45b565d8dfa20a477d6
4
+ data.tar.gz: 221ef37fa2440af4b5b12be9cc209402c741096d
5
+ SHA512:
6
+ metadata.gz: 3bebcf37fa8a68d1f602317d39af3fbe778687baea8cd9fc1fdbe93df20ea1964402183aa052b166557293fea0eeefb183c27735e46dd7a5261a2b8fb7483b16
7
+ data.tar.gz: cb91ae793eaa9ae002060d3bc407255376a4a5a3a7592fb0912d306a4a972ef872c77b9ac9a4ca0f192503ee926e1f1c7a0e868cf3a31514ae73e39af669b6fe
data/.gitignore ADDED
@@ -0,0 +1,17 @@
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
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in build.gemspec
4
+ gemspec
data/README.md ADDED
@@ -0,0 +1,53 @@
1
+ # Build::Graph
2
+
3
+ Build::Graph is a framework for build systems, with specific functionality for dealing with file based processes.
4
+
5
+ ## Installation
6
+
7
+ Add this line to your application's Gemfile:
8
+
9
+ gem 'build-graph'
10
+
11
+ And then execute:
12
+
13
+ $ bundle
14
+
15
+ Or install it yourself as:
16
+
17
+ $ gem install build-graph
18
+
19
+ ## Usage
20
+
21
+ TODO: Write usage instructions here
22
+
23
+ ## Contributing
24
+
25
+ 1. Fork it
26
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
27
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
28
+ 4. Push to the branch (`git push origin my-new-feature`)
29
+ 5. Create new Pull Request
30
+
31
+ ## License
32
+
33
+ Released under the MIT license.
34
+
35
+ Copyright, 2012, 2014, by [Samuel G. D. Williams](http://www.codeotaku.com/samuel-williams).
36
+
37
+ Permission is hereby granted, free of charge, to any person obtaining a copy
38
+ of this software and associated documentation files (the "Software"), to deal
39
+ in the Software without restriction, including without limitation the rights
40
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
41
+ copies of the Software, and to permit persons to whom the Software is
42
+ furnished to do so, subject to the following conditions:
43
+
44
+ The above copyright notice and this permission notice shall be included in
45
+ all copies or substantial portions of the Software.
46
+
47
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
48
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
49
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
50
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
51
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
52
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
53
+ THE SOFTWARE.
data/Rakefile ADDED
@@ -0,0 +1,9 @@
1
+ require "bundler/gem_tasks"
2
+ require "rake/testtask"
3
+
4
+ Rake::TestTask.new do |t|
5
+ t.libs << 'test'
6
+ end
7
+
8
+ desc "Run tests"
9
+ task :default => :test
@@ -0,0 +1,28 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'build/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "build-graph"
8
+ spec.version = Build::VERSION
9
+ spec.authors = ["Samuel Williams"]
10
+ spec.email = ["samuel.williams@oriontransfer.co.nz"]
11
+ spec.summary = %q{Build::Graph is a framework for build systems, with specific functionality for dealing with file based processes.}
12
+ spec.description = <<-EOF
13
+ Build::Graph is a framework for managing file-system based build processes. It provides graph based build functionality which monitors the file-system for changes. Because of this, it can efficiently manage large and complex process based builds.
14
+ EOF
15
+ spec.homepage = ""
16
+ spec.license = "MIT"
17
+
18
+ spec.files = `git ls-files`.split($/)
19
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
20
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
21
+ spec.require_paths = ["lib"]
22
+
23
+ spec.add_dependency "system"
24
+ spec.add_dependency "rainbow"
25
+
26
+ spec.add_development_dependency "bundler", "~> 1.3"
27
+ spec.add_development_dependency "rake"
28
+ end
data/lib/build.rb ADDED
@@ -0,0 +1,5 @@
1
+ require "build/version"
2
+
3
+ module Build
4
+
5
+ end
data/lib/build/edge.rb ADDED
@@ -0,0 +1,49 @@
1
+
2
+ require 'build/error'
3
+
4
+ module Build
5
+ # Represents an input to a graph node, with count inputs.
6
+ class Edge
7
+ def initialize(count = 0)
8
+ @fiber = Fiber.current
9
+ @count = count
10
+
11
+ @failed = []
12
+ end
13
+
14
+ attr :failed
15
+
16
+ attr :fiber
17
+ attr :count
18
+
19
+ def wait
20
+ if @count > 0
21
+ Fiber.yield
22
+ end
23
+
24
+ failed?
25
+ end
26
+
27
+ attr :failed
28
+
29
+ def failed?
30
+ @failed.size != 0
31
+ end
32
+
33
+ def traverse(node)
34
+ @count -= 1
35
+
36
+ if node.failed?
37
+ @failed << node
38
+ end
39
+
40
+ if @count == 0
41
+ @fiber.resume
42
+ end
43
+ end
44
+
45
+ def increment!
46
+ @count += 1
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,18 @@
1
+
2
+
3
+ module Build
4
+ class TransientError < StandardError
5
+ end
6
+
7
+ class CommandFailure < TransientError
8
+ def initialize(command, status)
9
+ super "Command #{command.inspect} failed with exit status #{status}!"
10
+
11
+ @command = command
12
+ @status = status
13
+ end
14
+
15
+ attr :command
16
+ attr :status
17
+ end
18
+ end
@@ -0,0 +1,273 @@
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 'pathname'
23
+
24
+ module Build
25
+ module Files
26
+ class List
27
+ include Enumerable
28
+
29
+ def +(list)
30
+ Composite.new([self, list])
31
+ end
32
+
33
+ def intersects? other
34
+ other.any?{|path| include?(path)}
35
+ end
36
+
37
+ def rebase(root)
38
+ raise NotImplementedError
39
+ end
40
+
41
+ def to_paths
42
+ relative_paths = self.each do |path|
43
+ path.relative_path
44
+ end
45
+
46
+ return Paths.new(@root, relative_paths)
47
+ end
48
+
49
+ def match(pattern)
50
+ all? {|path| path.match(pattern)}
51
+ end
52
+ end
53
+
54
+ class RelativePath < String
55
+ # Both paths must be full absolute paths, and path must have root as an prefix.
56
+ def initialize(path, root)
57
+ raise ArgumentError.new("#{root} is not a prefix of #{path}") unless path.start_with?(root)
58
+
59
+ super path
60
+
61
+ @root = root
62
+ end
63
+
64
+ attr :root
65
+
66
+ def relative_path
67
+ self.slice(@root.length..-1)
68
+ end
69
+ end
70
+
71
+ class Directory < List
72
+ def initialize(root, path = "")
73
+ @root = root.to_s
74
+ @path = path
75
+ end
76
+
77
+ attr :root
78
+ attr :path
79
+
80
+ def full_path
81
+ File.join(@root, @path)
82
+ end
83
+
84
+ def each(&block)
85
+ Dir.glob(full_path + "**/*").each do |path|
86
+ yield RelativePath.new(path, @root)
87
+ end
88
+ end
89
+
90
+ def roots
91
+ [full_path]
92
+ end
93
+
94
+ def eql?(other)
95
+ other.kind_of?(self.class) and @root.eql?(other.root) and @path.eql?(other.path)
96
+ end
97
+
98
+ def hash
99
+ [@root, @path].hash
100
+ end
101
+
102
+ def include?(path)
103
+ # Would be true if path is a descendant of full_path.
104
+ path.start_with?(full_path)
105
+ end
106
+
107
+ def rebase(root)
108
+ self.class.new(root, @path)
109
+ end
110
+ end
111
+
112
+ class Glob < List
113
+ def initialize(root, pattern)
114
+ @root = root.to_s
115
+ @pattern = pattern
116
+ end
117
+
118
+ attr :root
119
+ attr :pattern
120
+
121
+ def full_pattern
122
+ File.join(@root, @pattern)
123
+ end
124
+
125
+ # Enumerate all paths matching the pattern.
126
+ def each(&block)
127
+ Dir.glob(full_pattern).each do |path|
128
+ yield RelativePath.new(path, @root)
129
+ end
130
+ end
131
+
132
+ def roots
133
+ [@root]
134
+ end
135
+
136
+ def eql?(other)
137
+ other.kind_of?(self.class) and @root.eql?(other.root) and @pattern.eql?(other.pattern)
138
+ end
139
+
140
+ def hash
141
+ [@root, @pattern].hash
142
+ end
143
+
144
+ def include?(path)
145
+ File.fnmatch(full_pattern, path)
146
+ end
147
+
148
+ def rebase(root)
149
+ self.class.new(root, @pattern)
150
+ end
151
+ end
152
+
153
+ class Paths < List
154
+ def initialize(root, paths)
155
+ @root = root.to_s
156
+ @paths = Array(paths)
157
+ end
158
+
159
+ attr :paths
160
+
161
+ def each(&block)
162
+ @paths.each do |path|
163
+ full_path = File.join(@root, path)
164
+ yield RelativePath.new(full_path, @root)
165
+ end
166
+ end
167
+
168
+ def roots
169
+ [@root]
170
+ end
171
+
172
+ def eql? other
173
+ other.kind_of?(self.class) and @paths.eql?(other.paths)
174
+ end
175
+
176
+ def hash
177
+ @paths.hash
178
+ end
179
+
180
+ def include?(path)
181
+ # Compute a full relative path:
182
+ full_path = File.absolute_path(path, @root)
183
+
184
+ # If the full path starts with @root, test it for inclusion:
185
+ if full_path.start_with? @root
186
+ # Compute the relative component:
187
+ relative_path = full_path[@root.length..-1]
188
+
189
+ # Does this list of paths include it?
190
+ return @paths.include?(relative_path)
191
+ else
192
+ return false
193
+ end
194
+ end
195
+
196
+ def rebase(root)
197
+ self.class.new(root, @paths)
198
+ end
199
+
200
+ def to_paths
201
+ return self
202
+ end
203
+ end
204
+
205
+ class Composite < List
206
+ def initialize(files = Set.new)
207
+ @files = files
208
+ end
209
+
210
+ attr :files
211
+
212
+ def each(&block)
213
+ @files.each do |files|
214
+ files.each &block
215
+ end
216
+ end
217
+
218
+ def roots
219
+ @files.collect(&:roots).flatten.uniq
220
+ end
221
+
222
+ def eql?(other)
223
+ other.kind_of?(self.class) and @files.eql?(other.files)
224
+ end
225
+
226
+ def hash
227
+ @files.hash
228
+ end
229
+
230
+ def merge(list)
231
+ if list.kind_of? Composite
232
+ @files += list.files
233
+ elsif list.kind_of? List
234
+ @files << list
235
+ else
236
+ raise ArgumentError.new("Cannot merge non-list of file paths.")
237
+ end
238
+ end
239
+
240
+ def +(list)
241
+ if list.kind_of? Composite
242
+ Composite.new(@files + list.files)
243
+ else
244
+ Composite.new(@files + [list])
245
+ end
246
+ end
247
+
248
+ def include?(path)
249
+ @files.any? {|list| list.include?(path)}
250
+ end
251
+
252
+ def rebase(root)
253
+ self.class.new(@files.collect{|list| list.rebase(root)})
254
+ end
255
+
256
+ def to_paths
257
+ Composite.new(@files.collect(&:to_paths))
258
+ end
259
+
260
+ def self.[](files)
261
+ if files.size == 0
262
+ return None
263
+ elsif files.size == 1
264
+ files.first
265
+ else
266
+ self.class.new(files)
267
+ end
268
+ end
269
+ end
270
+
271
+ NONE = Composite.new
272
+ end
273
+ end