qrpm 0.0.3 → 0.3.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.
- checksums.yaml +4 -4
- data/.ruby-version +1 -1
- data/NOTES +7 -0
- data/TODO +30 -0
- data/doc/pg.yml +37 -0
- data/doc/qrpm2.rb +69 -0
- data/doc/qrpm2.yml +55 -0
- data/example/configure +0 -0
- data/example/make +0 -0
- data/example/my_package_name-1.2.3-4.x86_64.rpm +0 -0
- data/example/my_package_name.spec +49 -0
- data/example.yml +84 -0
- data/exe/qrpm +134 -42
- data/lib/qrpm/compiler.rb +238 -0
- data/lib/qrpm/fragment.rb +177 -0
- data/lib/qrpm/lexer.rb +41 -0
- data/lib/qrpm/node.rb +372 -31
- data/lib/qrpm/qrpm.rb +182 -53
- data/lib/qrpm/rpm.rb +107 -60
- data/lib/qrpm/template.erb +28 -29
- data/lib/qrpm/template.yml +15 -12
- data/lib/qrpm/utils.rb +10 -0
- data/lib/qrpm/version.rb +1 -1
- data/lib/qrpm.rb +107 -1
- data/qrpm.gemspec +3 -3
- metadata +51 -10
- data/lib/qrpm/parser.rb +0 -183
data/lib/qrpm/node.rb
CHANGED
@@ -1,53 +1,394 @@
|
|
1
1
|
|
2
|
-
module Qrpm
|
2
|
+
module Qrpm
|
3
3
|
class Node
|
4
|
-
|
5
|
-
attr_reader :
|
6
|
-
def path() "#{directory}/#{name}" end
|
4
|
+
# Parent node
|
5
|
+
attr_reader :parent
|
7
6
|
|
8
|
-
|
9
|
-
|
7
|
+
# Path to node. This is a String expression that leads from the
|
8
|
+
# root node down to the current node. The expression may only contain
|
9
|
+
# variables if the node is a DirectoryNode but it can include integer
|
10
|
+
# indexes
|
11
|
+
attr_reader :path
|
12
|
+
|
13
|
+
# Name of node within its parent. ArrayNode element objects (this include
|
14
|
+
# files) has integer "names" and only DirectoryNode keys may contain
|
15
|
+
# references. It is initialized with a String or an Integer except for
|
16
|
+
# DirectoryNode objects that are initialized with a Fragment::Expression
|
17
|
+
# (directory nodes is not part of the dictionary). When computing directory
|
18
|
+
# paths, the source of the Fragment::Expression is used
|
19
|
+
attr_reader :name
|
20
|
+
|
21
|
+
# The value of this node. This can be a Fragment::Expression (ValueNode),
|
22
|
+
# Hash, or Array object
|
23
|
+
attr_reader :expr
|
24
|
+
|
25
|
+
# Interpolated #name. Initialized by Compile#analyze. Default an alias for #name
|
26
|
+
alias_method :key, :name
|
27
|
+
|
28
|
+
# Interpolated #expr. Initialized by Compile#analyze. Default an alias for #expr
|
29
|
+
alias_method :value, :expr
|
30
|
+
|
31
|
+
def initialize(parent, name, expr)
|
32
|
+
constrain parent, HashNode, ArrayNode, nil
|
33
|
+
constrain name, Fragment::Fragment, String, Integer, nil
|
34
|
+
constrain expr, Fragment::Fragment, Hash, Array
|
35
|
+
@parent = parent
|
36
|
+
@name = name
|
37
|
+
@expr = expr
|
38
|
+
@interpolated = false
|
39
|
+
@parent&.send(:add_node, self)
|
40
|
+
@path = Node.ref(@parent&.path, self.is_a?(DirectoryNode) ? @name.source : name) # FIXME
|
10
41
|
end
|
11
42
|
|
12
|
-
|
13
|
-
|
43
|
+
# Return list of variable names in #expr. Directory nodes may include
|
44
|
+
# variables in the key so they include key variables too
|
45
|
+
def variables = abstract_method
|
14
46
|
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
}
|
47
|
+
# Interpolate variables in Node. Note that interpolation is not recursive
|
48
|
+
# except for DirectoryNode objects that interpolates both key and elements.
|
49
|
+
# #interpolate sets the interpolated flag and returns self
|
50
|
+
def interpolate(dict)
|
51
|
+
@interpolated = true
|
52
|
+
self
|
22
53
|
end
|
54
|
+
|
55
|
+
# True if object has been interpolated
|
56
|
+
def interpolated? = @interpolated
|
57
|
+
|
58
|
+
# Name of class
|
59
|
+
def class_name = self.class.to_s.sub(/.*::/, "")
|
60
|
+
|
61
|
+
# Signature. Used in tests
|
62
|
+
def signature(klass: self.class_name, key: self.name) = abstract_method
|
63
|
+
|
64
|
+
def inspect = "#<#{self.class} #{path.inspect}>"
|
65
|
+
def dump = abstract_method
|
66
|
+
|
67
|
+
# Traverse node and its children recursively and execute block on each
|
68
|
+
# node. The nodes are traversed in depth-first order. The optional
|
69
|
+
# +klasses+ argument is a list of Class objects and the block is only
|
70
|
+
# executed on matching nodes. The default is to execute the block on all
|
71
|
+
# nodes. Returns an array of traversed nodes if no block is given
|
72
|
+
def traverse(*klasses, &block)
|
73
|
+
klasses = klasses.flatten
|
74
|
+
if block_given?
|
75
|
+
yield self if klasses.empty? || klasses.include?(self.class)
|
76
|
+
traverse_recursively(&block)
|
77
|
+
else
|
78
|
+
l = klasses.empty? || klasses.include?(self.class) ? [self] : []
|
79
|
+
traverse_recursively { |n| l << n }
|
80
|
+
l
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
def dot(expr)
|
85
|
+
curr = self
|
86
|
+
src = ".#{expr}" if expr[0] != "["
|
87
|
+
while src =~ /^\.(#{IDENT_RE})|\[(\d+)\]/
|
88
|
+
member, index = $1, $2
|
89
|
+
src = $'
|
90
|
+
if member
|
91
|
+
curr.is_a?(HashNode) or raise ArgumentError, "#{member} is not a hash in '#{expr}'"
|
92
|
+
curr.key?(member) or raise ArgumentError, "Unknown member '#{member}' in '#{expr}'"
|
93
|
+
curr = curr[member]
|
94
|
+
else
|
95
|
+
curr.is_a?(ArrayNode) or raise ArgumentError, "#{curr.key_source} is not an array in '#{expr}'"
|
96
|
+
curr.size > index.to_i or raise "Out of range index '#{index}' in '#{expr}'"
|
97
|
+
curr = curr[index]
|
98
|
+
end
|
99
|
+
end
|
100
|
+
src.empty? or raise ArgumentError, "Illegal expression: #{expr}"
|
101
|
+
curr
|
102
|
+
end
|
103
|
+
|
104
|
+
protected
|
105
|
+
def self.ref(parent_ref, element)
|
106
|
+
if parent_ref
|
107
|
+
parent_ref + (element.is_a?(Integer) ? "[#{element}]" : ".#{element}")
|
108
|
+
else
|
109
|
+
element
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
def traverse_recursively(&block) = abstract_method
|
114
|
+
def signature_content = abstract_method
|
23
115
|
end
|
24
116
|
|
25
|
-
class
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
117
|
+
class ValueNode < Node
|
118
|
+
# Source code of expression
|
119
|
+
def source() @expr.source end
|
120
|
+
|
121
|
+
# Override Qrpm#value. Initially nil, initialized by #interpolate
|
122
|
+
attr_reader :value
|
123
|
+
|
124
|
+
def initialize(parent, name, expr)
|
125
|
+
expr ||= Fragment::NilFragment.new
|
126
|
+
constrain expr, Fragment::Fragment
|
127
|
+
super
|
128
|
+
end
|
129
|
+
|
130
|
+
# Override Qrpm methods
|
131
|
+
def variables() @variables ||= expr.variables end
|
132
|
+
|
133
|
+
def interpolate(dict)
|
134
|
+
@value ||= expr.interpolate(dict) # Allows StandardDirNode to do its own assignment
|
135
|
+
super
|
136
|
+
end
|
137
|
+
|
138
|
+
def signature() "#{class_name}(#{name},#{expr.source})" end
|
139
|
+
def dump() puts value ? value : source end
|
140
|
+
|
141
|
+
protected
|
142
|
+
def traverse_recursively(&block) [] end
|
143
|
+
end
|
144
|
+
|
145
|
+
# Pre-defined standard directory node. #setsys and #setpck is used to point
|
146
|
+
# the value at either the corresponding system or package directory depending
|
147
|
+
# on the number of files in that directory
|
148
|
+
class StandardDirNode < ValueNode
|
149
|
+
def initialize(parent, name)
|
150
|
+
super parent, name, nil
|
151
|
+
end
|
152
|
+
|
153
|
+
def signature() "#{class_name}(#{name},#{value.inspect})" end
|
154
|
+
|
155
|
+
def setsys() @expr = Fragment::Fragment.parse("$sys#{name}") end
|
156
|
+
def setpck() @expr = Fragment::Fragment.parse("$pck#{name}") end
|
157
|
+
end
|
158
|
+
|
159
|
+
class ContainerNode < Node
|
160
|
+
# Return list of Node objects in the container. Hash keys are not included
|
161
|
+
def exprs = abstract_method
|
162
|
+
|
163
|
+
forward_to :expr, :empty?, :size, :[], :[]=
|
164
|
+
|
165
|
+
def variables() @variables ||= exprs.map(&:variables).flatten.uniq end
|
166
|
+
|
167
|
+
# Can't be defined as an alias because #exprs is redefined in derived
|
168
|
+
# classes, otherwise #values would refer to the derived version of #exprs
|
169
|
+
def values() exprs end
|
170
|
+
|
171
|
+
def signature = "#{self.class_name}(#{name},#{exprs.map { |v| v.signature }.join(",")})"
|
172
|
+
|
173
|
+
protected
|
174
|
+
def traverse_recursively(&block)
|
175
|
+
values.map { |value| value.traverse(&block) }.flatten
|
176
|
+
end
|
177
|
+
|
178
|
+
def add_node(node) = abstract_method
|
179
|
+
end
|
180
|
+
|
181
|
+
# A HashNode has a hash as expr. It doesn't forward #interpolate to its
|
182
|
+
# members (FileNode overrides that)
|
183
|
+
#
|
184
|
+
class HashNode < ContainerNode
|
185
|
+
# Override ContainerNode#exprs
|
186
|
+
def exprs() expr.values end
|
187
|
+
|
188
|
+
def initialize(parent, name, hash = {})
|
189
|
+
constrain hash, Hash
|
190
|
+
super(parent, name, hash.dup)
|
31
191
|
end
|
192
|
+
|
193
|
+
forward_to :expr, :key?, :keys
|
194
|
+
|
32
195
|
def dump
|
33
|
-
|
34
|
-
|
35
|
-
|
196
|
+
puts "{"
|
197
|
+
indent {
|
198
|
+
expr.each { |k,v|
|
199
|
+
print "#{k}: "
|
200
|
+
v.dump
|
201
|
+
}
|
36
202
|
}
|
203
|
+
puts "}"
|
37
204
|
end
|
205
|
+
|
206
|
+
protected
|
207
|
+
# Override ContainerNode#add_node
|
208
|
+
def add_node(node) self[node.name] = node end
|
38
209
|
end
|
39
210
|
|
40
|
-
class
|
41
|
-
|
211
|
+
class RootNode < HashNode
|
212
|
+
def initialize() super(nil, nil, {}) end
|
42
213
|
|
43
|
-
|
44
|
-
|
45
|
-
|
214
|
+
# Override Node#interpolate. Only interpolates contained DirectoryNode
|
215
|
+
# objects (TODO doubtfull - this is a Qrpm-level problem not a Node problem)
|
216
|
+
def interpolate(dict)
|
217
|
+
exprs.each { |e| e.is_a?(DirectoryNode) and e.interpolate(dict) }
|
218
|
+
super
|
46
219
|
end
|
220
|
+
|
221
|
+
def signature = "#{self.class_name}(#{values.map { |v| v.signature }.join(",")})"
|
222
|
+
end
|
223
|
+
|
224
|
+
# A file. Always an element of a DirectoryNode object
|
225
|
+
#
|
226
|
+
# A file is a hash with an integer key and with the following expressions as members:
|
227
|
+
#
|
228
|
+
# file Source file. The source file path is prefixed with $srcdir if
|
229
|
+
# defined. May be nil
|
230
|
+
# name Basename of the destination file. This defaults to the
|
231
|
+
# basename of the source file/symlink/reflink. The full path of
|
232
|
+
# the destination file is computed by prefixing the path of the
|
233
|
+
# parent directory
|
234
|
+
# reflink Path on the target filesystem to the source of the reflink
|
235
|
+
# (hard-link). May be nil
|
236
|
+
# symlink Path on the target filesystem to the source of the symlink.
|
237
|
+
# May be nil
|
238
|
+
# perm Permissions of the target file in chmod(1) octal or rwx
|
239
|
+
# notation. May be nil
|
240
|
+
#
|
241
|
+
# Exactly one of 'file', 'symlink', and 'reflink' must be defined. 'perm'
|
242
|
+
# can't be used together with 'symlink' or 'reflink'
|
243
|
+
#
|
244
|
+
# When interpolated the following methods are defined on a FileNode:
|
245
|
+
#
|
246
|
+
# srcpath Path to source file
|
247
|
+
# dstpath Path to destination file
|
248
|
+
# dstname Basename of destination file
|
249
|
+
# reflink Path to source link
|
250
|
+
# symlink Path to source link
|
251
|
+
# perm Permissions
|
252
|
+
#
|
253
|
+
class FileNode < HashNode
|
254
|
+
# Source file. This is the relative path to the file in the build directory
|
255
|
+
# except for link files. Link files have a path on the target filesystem as
|
256
|
+
# path. It is the interpolated value of #expr["file/reflink/symlink"]
|
257
|
+
attr_reader :srcpath
|
258
|
+
|
259
|
+
# Destination file path
|
260
|
+
attr_reader :dstpath
|
261
|
+
|
262
|
+
# Destination file name
|
263
|
+
attr_reader :dstname
|
264
|
+
|
265
|
+
# Hard-link file
|
266
|
+
attr_reader :reflink
|
267
|
+
|
268
|
+
# Symbolic link file
|
269
|
+
attr_reader :symlink
|
270
|
+
|
271
|
+
# Permissions of destination file. Perm is always a string
|
272
|
+
attr_reader :perm
|
273
|
+
|
274
|
+
# Directory
|
275
|
+
def directory = parent.directory
|
276
|
+
|
277
|
+
# Query methods
|
278
|
+
def file? = !link?
|
279
|
+
def link? = symlink? || reflink?
|
280
|
+
def reflink? = @expr.key?("reflink")
|
281
|
+
def symlink? = @expr.key?("symlink")
|
282
|
+
|
283
|
+
def initialize(parent, name)
|
284
|
+
constrain parent, DirectoryNode
|
285
|
+
constrain name, Integer
|
286
|
+
super
|
287
|
+
end
|
288
|
+
|
289
|
+
def interpolate(dict)
|
290
|
+
super
|
291
|
+
exprs.each { |e| e.interpolate(dict) }
|
292
|
+
@srcpath = value[%w(file symlink reflink).find { |k| expr.key?(k) }].value
|
293
|
+
@dstname = value["name"]&.value || File.basename(srcpath)
|
294
|
+
@dstpath = "#{parent.directory}/#{@dstname}"
|
295
|
+
@reflink = value["reflink"]&.value
|
296
|
+
@symlink = value["symlink"]&.value
|
297
|
+
@perm = value["perm"]&.value
|
298
|
+
self
|
299
|
+
end
|
300
|
+
|
301
|
+
# :call-seq:
|
302
|
+
# FileNode.make(directory_node, filename)
|
303
|
+
# FileNode.make(directory_node, hash)
|
304
|
+
#
|
305
|
+
# Shorthand for creating file object
|
306
|
+
def self.make(parent, arg)
|
307
|
+
file = FileNode.new(parent, parent.size)
|
308
|
+
hash = arg.is_a?(String) ? { "file" => arg } : arg
|
309
|
+
hash.each { |k,v| ValueNode.new(file, k.to_s, Fragment::Fragment.parse(v)) }
|
310
|
+
file
|
311
|
+
end
|
312
|
+
|
313
|
+
# Signature. Used in tests
|
314
|
+
def signature = "FileNode(#{name},#{expr["file"].source})"
|
315
|
+
|
316
|
+
# Path to source file. Returns the QRPM source expression or the
|
317
|
+
# interpolated result if the FileNode object has been interpolated. Used by
|
318
|
+
# Qrpm#dump
|
319
|
+
def src
|
320
|
+
e = expr["file"] || expr["reflink"] || expr["symlink"]
|
321
|
+
interpolated? ? e.value : e.source
|
322
|
+
end
|
323
|
+
|
324
|
+
# Name of destination. Returns the QRPM source expression or the
|
325
|
+
# interpolated result if the FileNode object has been interpolated. Used by
|
326
|
+
# Qrpm#dump
|
327
|
+
def dst
|
328
|
+
if expr["name"]
|
329
|
+
interpolated? ? expr["name"].value : expr["name"].expr.source
|
330
|
+
else
|
331
|
+
File.basename(src)
|
332
|
+
end
|
333
|
+
end
|
334
|
+
end
|
335
|
+
|
336
|
+
class ArrayNode < ContainerNode
|
337
|
+
# Override ContainerNode#exprs
|
338
|
+
def exprs() expr end
|
339
|
+
|
340
|
+
def initialize(parent, name, array = [])
|
341
|
+
constrain array, Array
|
342
|
+
super(parent, name, array.dup)
|
343
|
+
end
|
344
|
+
|
345
|
+
forward_to :expr, :first, :last
|
346
|
+
|
347
|
+
# Array forwards #interpolate to its children
|
348
|
+
def interpolate(dict)
|
349
|
+
exprs.each { |e| e.interpolate(dict) }
|
350
|
+
super
|
351
|
+
end
|
352
|
+
|
47
353
|
def dump
|
48
|
-
|
49
|
-
|
354
|
+
puts "["
|
355
|
+
indent {
|
356
|
+
expr.each { |n|
|
357
|
+
print "- "
|
358
|
+
n.dump
|
359
|
+
}
|
50
360
|
}
|
361
|
+
puts "]"
|
362
|
+
end
|
363
|
+
|
364
|
+
protected
|
365
|
+
def add_node(node)
|
366
|
+
node.instance_variable_set(:"@name", expr.size)
|
367
|
+
expr << node
|
368
|
+
end
|
369
|
+
end
|
370
|
+
|
371
|
+
class DirectoryNode < ArrayNode
|
372
|
+
# Override Qrpm#key
|
373
|
+
attr_reader :key
|
374
|
+
|
375
|
+
# File system path to the directory. An alias for uuid/key
|
376
|
+
def directory() key end
|
377
|
+
|
378
|
+
def initialize(parent, name, array = [])
|
379
|
+
constrain name, Fragment::Fragment
|
380
|
+
super
|
381
|
+
end
|
382
|
+
|
383
|
+
def variables() (super + key_variables).uniq end
|
384
|
+
def key_variables() @key_variables ||= name.variables end
|
385
|
+
|
386
|
+
# A directory node also interpolates its key
|
387
|
+
def interpolate(dict)
|
388
|
+
# #key is used by the embedded files to compute their paths so it has be
|
389
|
+
# interpolated before we interpolate the files through the +super+ method
|
390
|
+
@key = name.interpolate(dict)
|
391
|
+
super
|
51
392
|
end
|
52
393
|
end
|
53
394
|
end
|
data/lib/qrpm/qrpm.rb
CHANGED
@@ -1,63 +1,192 @@
|
|
1
|
+
module Qrpm
|
2
|
+
class Qrpm
|
3
|
+
# Definitions. Maps from path to Node object
|
4
|
+
attr_reader :defs
|
1
5
|
|
2
|
-
|
3
|
-
|
4
|
-
# '${name}'. The variables are returned in left-to-right order
|
5
|
-
#
|
6
|
-
def collect_exprs(expr)
|
7
|
-
expr.scan(/\$([\w_]+)|\$\{([\w_]+)\}/).flatten.compact
|
8
|
-
end
|
6
|
+
# Dependencies. Maps from variable name to list of variables it depends on
|
7
|
+
attr_reader :deps
|
9
8
|
|
10
|
-
#
|
11
|
-
|
12
|
-
|
13
|
-
#
|
14
|
-
|
15
|
-
|
16
|
-
#
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
9
|
+
# Variable definitions. This is ValueNode, HashNode, and ArrayNode objects in #defs
|
10
|
+
attr_reader :vars
|
11
|
+
|
12
|
+
# Directory definitions. This is the Directory nodes in #defs
|
13
|
+
attr_reader :dirs
|
14
|
+
|
15
|
+
# File definitions. This is the File nodes in #defs
|
16
|
+
attr_reader :files
|
17
|
+
|
18
|
+
# Dictionary. Maps from path to interpolated value. Only evaluated nodes
|
19
|
+
# have entries in #dict
|
20
|
+
attr_reader :dict
|
21
|
+
|
22
|
+
# Source directory
|
23
|
+
def srcdir() @dict["srcdir"] end
|
24
|
+
|
25
|
+
def initialize(defs, deps)
|
26
|
+
constrain defs, String => Node
|
27
|
+
@defs, @deps = defs, deps
|
28
|
+
@vars, @dirs, @files = {}, {}, {}
|
29
|
+
@defs.values.map(&:traverse).flatten.each { |object|
|
30
|
+
case object
|
31
|
+
when FileNode; @files[object.path] = object
|
32
|
+
when DirectoryNode; @dirs[object.path] = object
|
33
|
+
else @vars[object.path] = object
|
34
|
+
end
|
35
|
+
}
|
36
|
+
@dict = {}
|
37
|
+
@evaluated = false
|
38
|
+
end
|
39
|
+
|
40
|
+
# True if object has been evaluated
|
41
|
+
def evaluated? = @evaluated
|
22
42
|
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
43
|
+
# Evaluate object. Returns self
|
44
|
+
def evaluate
|
45
|
+
@evaluated ||= begin
|
46
|
+
unresolved = @defs.dup # Queue of unresolved definitions
|
27
47
|
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
48
|
+
# Find objects. Built-in RPM fields and directories are evaluated recursively
|
49
|
+
paths = FIELDS.keys.select { |k| @defs.key? k } + dirs.keys #+ DEFAULTS.keys
|
50
|
+
|
51
|
+
# Find dependency order of objects
|
52
|
+
ordered_deps = find_evaluation_order(paths)
|
53
|
+
|
54
|
+
# Evaluate objects and remove them from the @unresolved queue
|
55
|
+
ordered_deps.each { |path|
|
56
|
+
node = @defs[path]
|
57
|
+
node.interpolate(dict) if !node.interpolated? && !dict.key?(path)
|
58
|
+
unresolved.delete(path)
|
59
|
+
@dict[path] = node.value if !node.is_a?(DirectoryNode) && !node.is_a?(FileNode)
|
60
|
+
}
|
61
|
+
self
|
35
62
|
end
|
36
|
-
|
37
|
-
end
|
38
|
-
unresolved.empty? or raise "Unresolved variables: #{unresolved.join(", ")}"
|
63
|
+
end
|
39
64
|
|
40
|
-
|
41
|
-
|
65
|
+
# Evaluate and return Rpm object
|
66
|
+
def rpm(**rpm_options)
|
67
|
+
evaluate
|
68
|
+
used_vars = dict.keys.map { |k| [k, @defs[k]] }.to_h
|
69
|
+
Rpm.new(dict["srcdir"], used_vars, files.values, **rpm_options)
|
70
|
+
end
|
71
|
+
|
72
|
+
def [](name) @dict[name] end
|
73
|
+
def key?(name) @dict.key? name end
|
42
74
|
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
75
|
+
def inspect
|
76
|
+
"#<#{self.class}>"
|
77
|
+
end
|
78
|
+
|
79
|
+
def dump
|
80
|
+
FIELDS.keys.each { |f|
|
81
|
+
puts if f == "make"
|
82
|
+
obj = self[f]
|
83
|
+
if obj.is_a?(Array)
|
84
|
+
puts "#{f.capitalize}:"
|
85
|
+
self[f].each { |e| indent.puts "- #{e.value}" }
|
86
|
+
else
|
87
|
+
puts "#{f.capitalize}: #{self[f]}" if key? f
|
88
|
+
end
|
89
|
+
}
|
90
|
+
puts
|
91
|
+
puts "Directories:"
|
92
|
+
indent {
|
93
|
+
dirs.values.each { |d|
|
94
|
+
puts d.key
|
95
|
+
indent {
|
96
|
+
d.values.each { |f|
|
97
|
+
if f.file? && File.basename(f.src) == f.dst
|
98
|
+
print f.src
|
99
|
+
else
|
100
|
+
joiner = f.file? ? "->" : (f.reflink? ? "~>" : "~~>")
|
101
|
+
print "#{f.src} #{joiner} #{f.dst}"
|
102
|
+
end
|
103
|
+
print ", perm: #{f.perm}" if f.perm
|
104
|
+
puts
|
105
|
+
}
|
106
|
+
}
|
107
|
+
}
|
108
|
+
}
|
109
|
+
end
|
59
110
|
|
60
|
-
def
|
61
|
-
|
111
|
+
def dump_parts(parts = [:defs, :deps, :vars, :dirs, :files, :dict])
|
112
|
+
parts = Array(parts)
|
113
|
+
if parts.include? :defs
|
114
|
+
puts "Defs"
|
115
|
+
indent { defs.each { |k,v| puts "#{k}: #{v.value.inspect}" if v.value } }
|
116
|
+
end
|
117
|
+
if parts.include? :deps
|
118
|
+
puts "Deps"
|
119
|
+
indent { deps.each { |k,v| puts "#{k}: #{v.join(", ")}" if !v.empty? } }
|
120
|
+
end
|
121
|
+
if parts.include? :vars
|
122
|
+
puts "Vars"
|
123
|
+
indent { vars.each { |k,v| puts "#{k}: #{v.inspect}" if !v.value.nil? } }
|
124
|
+
end
|
125
|
+
if parts.include? :dict
|
126
|
+
puts "Dict"
|
127
|
+
indent { dict.each { |k,v| puts "#{k}: #{v.inspect}" } }
|
128
|
+
end
|
129
|
+
if parts.include? :dirs
|
130
|
+
puts "Dirs"
|
131
|
+
indent { dirs.each { |k,v| puts "#{k}: #{v.directory}" } }
|
132
|
+
end
|
133
|
+
if parts.include? :files
|
134
|
+
puts "Files"
|
135
|
+
indent { files.each { |k,v| puts "#{k}: #{v.srcpath}" } }
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
private
|
140
|
+
# Assumes all required variables have been defined
|
141
|
+
def interpolate(node)
|
142
|
+
constrain node, Node
|
143
|
+
node.interpolate(defs)
|
144
|
+
end
|
145
|
+
|
146
|
+
def find_evaluation_order(paths)
|
147
|
+
paths.map { |path|
|
148
|
+
@deps[path].empty? ? [path] : find_evaluation_order_recusively([], path).flatten.reverse + [path]
|
149
|
+
}.flatten.uniq
|
150
|
+
end
|
151
|
+
|
152
|
+
def find_evaluation_order_recusively(stack, object)
|
153
|
+
if stack.include? object
|
154
|
+
cycle = stack.drop_while { |e| e != object } + [object]
|
155
|
+
raise "Cyclic definition: #{cycle.join(' -> ')}"
|
156
|
+
end
|
157
|
+
@deps[object].map { |e|
|
158
|
+
if @deps.key?(e)
|
159
|
+
[e] + find_evaluation_order_recusively(stack + [object], e)
|
160
|
+
else
|
161
|
+
[]
|
162
|
+
end
|
163
|
+
}
|
164
|
+
end
|
165
|
+
|
166
|
+
def find_dependencies_recusively(stack, object)
|
167
|
+
if stack.include? object
|
168
|
+
cycle = stack.drop_while { |e| e != object } + [object]
|
169
|
+
raise "Cyclic definition: #{cycle.join(' -> ')}"
|
170
|
+
end
|
171
|
+
@deps[object].map { |e|
|
172
|
+
[e] + find_dependencies_recusively(stack + [object], e)
|
173
|
+
}
|
174
|
+
end
|
175
|
+
|
176
|
+
def to_val(obj)
|
177
|
+
case obj
|
178
|
+
when HashNode
|
179
|
+
obj.exprs.map { |node| [node.key, to_val(node)] }.to_h
|
180
|
+
when ArrayNode
|
181
|
+
obj.exprs.map { |node| to_val(node.value) }
|
182
|
+
when ValueNode
|
183
|
+
obj.value
|
184
|
+
when String
|
185
|
+
obj
|
186
|
+
else
|
187
|
+
raise StandardError.new "Unexpected object class: #{obj.class}"
|
188
|
+
end
|
189
|
+
end
|
190
|
+
end
|
62
191
|
end
|
63
192
|
|