-
Notifications
You must be signed in to change notification settings - Fork 2
/
depresso.coffee
105 lines (96 loc) · 3.07 KB
/
depresso.coffee
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
_ = require 'underscore'
_s = require 'underscore.string'
spahql = require 'spahql'
DepGraph = require 'dep-graph'
# Concepts:
# - path: A unique string
# - expression: A string that can be resolved to one or more paths
# - path resolution: Generation of paths from an expression
# - dependency: A description how a path can be given a value
# (resolution), and what paths it needs as a prerequisite.
notImplemented = -> throw new Error 'Not implemented'
pr = (x...) -> console.log x[0], JSON.stringify(x[1], null, 2)
_root = (node) ->
root = node
while root.parent()
root = root.parent()
root
# Supported expressions:
# - '/foo' starts at the root
# - '../foo' start from parent
# - './foo' and 'foo' start from current
glob = (expression, contextNode) ->
result = null
if _.isString expression
if _s.startsWith expression, '/'
result = _root(contextNode).select(expression).paths()
else if _s.startsWith expression, '../'
result = glob expression[3...expression.length], contextNode.parent()
else if _s.startsWith expression, './'
result = glob expression[2...expression.length], contextNode
else
result = contextNode.select('/' + expression).paths()
else if _.isFunction expression
result = expression(contextNode)
else
throw new Error "What #{expression}"
result
isAtomic = (expression) ->
not (_s.include expression, '//' or _s.include expression, '*')
# Get a value or values on path
value = (db, paths, atomic) ->
values = []
for p in paths
values = values.concat db.select(p).values()
if atomic
if values.length > 1
throw new Error "Expected atomic for #{ paths }"
values[0]
else
values
@resolve = (data, dependencies, wantedExpressions...) ->
db = spahql.db data
# glob expressions
nodeDeps = {}
for dep in dependencies
for nodePath in glob(dep.target, db)
dependNodes = {}
for name,path of dep.depends
dependNodes[name] =
atom: isAtomic path
paths: glob(path, db.select nodePath)
nodeDeps[nodePath] =
depends: dependNodes
calculate: dep.calculate
wanted = []
for exp in wantedExpressions
wanted = wanted.concat glob(exp, db)
# get dependency resolution order
graph = new DepGraph
for path,dep of nodeDeps
for depends in _.values dep.depends
for depend in depends.paths
graph.add path, depend
shouldCalculate = []
for w in wanted
for required in graph.getChain w
unless required in shouldCalculate
shouldCalculate.push required
unless w in shouldCalculate
shouldCalculate.push w
# perform calculation
for path in shouldCalculate
dep = nodeDeps[path]
if dep
calculation = {}
calculation.calculate = dep.calculate
for name,depends of dep.depends
calculation[name] = value db, depends.paths, depends.atom
result = calculation.calculate()
db.select(path).replace result
else
if _.isUndefined db.select(path).value()
throw new Error "Expected dependency #{ path } not met"
db.value()
@testing =
glob: glob