babl-json 0.1.4 → 0.2.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/.travis.yml +1 -1
- data/CHANGELOG.md +6 -0
- data/README.md +1 -3
- data/babl.gemspec +3 -1
- data/lib/babl.rb +4 -6
- data/lib/babl/builder/chain_builder.rb +6 -2
- data/lib/babl/builder/template_base.rb +16 -3
- data/lib/babl/errors.rb +7 -0
- data/lib/babl/nodes/create_pin.rb +24 -0
- data/lib/babl/nodes/dep.rb +38 -0
- data/lib/babl/nodes/each.rb +29 -0
- data/lib/babl/nodes/fixed_array.rb +25 -0
- data/lib/babl/nodes/goto_pin.rb +24 -0
- data/lib/babl/{rendering/internal_value_node.rb → nodes/internal_value.rb} +6 -5
- data/lib/babl/nodes/merge.rb +102 -0
- data/lib/babl/nodes/nav.rb +33 -0
- data/lib/babl/nodes/object.rb +26 -0
- data/lib/babl/nodes/parent.rb +64 -0
- data/lib/babl/nodes/static.rb +34 -0
- data/lib/babl/nodes/switch.rb +29 -0
- data/lib/babl/nodes/terminal_value.rb +76 -0
- data/lib/babl/nodes/with.rb +28 -0
- data/lib/babl/operators/array.rb +5 -28
- data/lib/babl/operators/call.rb +4 -2
- data/lib/babl/operators/continue.rb +19 -0
- data/lib/babl/operators/default.rb +13 -0
- data/lib/babl/operators/dep.rb +3 -36
- data/lib/babl/operators/each.rb +3 -33
- data/lib/babl/operators/enter.rb +4 -2
- data/lib/babl/operators/extends.rb +4 -1
- data/lib/babl/operators/merge.rb +7 -30
- data/lib/babl/operators/nav.rb +4 -36
- data/lib/babl/operators/object.rb +7 -29
- data/lib/babl/operators/parent.rb +4 -73
- data/lib/babl/operators/partial.rb +4 -2
- data/lib/babl/operators/pin.rb +14 -58
- data/lib/babl/operators/static.rb +11 -30
- data/lib/babl/operators/switch.rb +8 -51
- data/lib/babl/operators/with.rb +5 -34
- data/lib/babl/railtie.rb +2 -2
- data/lib/babl/rendering/compiled_template.rb +5 -13
- data/lib/babl/rendering/context.rb +13 -7
- data/lib/babl/schema/any_of.rb +137 -0
- data/lib/babl/schema/anything.rb +13 -0
- data/lib/babl/schema/dyn_array.rb +11 -0
- data/lib/babl/schema/fixed_array.rb +13 -0
- data/lib/babl/schema/object.rb +35 -0
- data/lib/babl/schema/static.rb +14 -0
- data/lib/babl/schema/typed.rb +0 -0
- data/lib/babl/template.rb +4 -9
- data/lib/babl/utils/ref.rb +6 -0
- data/lib/babl/version.rb +1 -1
- data/spec/operators/array_spec.rb +31 -7
- data/spec/operators/call_spec.rb +16 -14
- data/spec/operators/continue_spec.rb +25 -0
- data/spec/operators/default_spec.rb +15 -0
- data/spec/operators/dep_spec.rb +4 -8
- data/spec/operators/each_spec.rb +24 -5
- data/spec/operators/enter_spec.rb +9 -7
- data/spec/operators/extends_spec.rb +19 -5
- data/spec/operators/merge_spec.rb +105 -12
- data/spec/operators/nav_spec.rb +22 -10
- data/spec/operators/null_spec.rb +5 -4
- data/spec/operators/nullable_spec.rb +13 -13
- data/spec/operators/object_spec.rb +17 -6
- data/spec/operators/parent_spec.rb +18 -22
- data/spec/operators/partial_spec.rb +8 -6
- data/spec/operators/pin_spec.rb +100 -61
- data/spec/operators/source_spec.rb +10 -6
- data/spec/operators/static_spec.rb +17 -9
- data/spec/operators/switch_spec.rb +85 -45
- data/spec/operators/with_spec.rb +13 -15
- data/spec/spec_helper.rb +2 -31
- data/spec/spec_helper/operator_testing.rb +46 -0
- data/spec/spec_helper/schema_utils.rb +33 -0
- metadata +63 -4
- data/lib/babl/rendering/terminal_value_node.rb +0 -54
@@ -0,0 +1,26 @@
|
|
1
|
+
require 'babl/utils/hash'
|
2
|
+
require 'babl/schema/object'
|
3
|
+
require 'values'
|
4
|
+
|
5
|
+
module Babl
|
6
|
+
module Nodes
|
7
|
+
class Object < Value.new(:nodes)
|
8
|
+
def dependencies
|
9
|
+
nodes.values.map(&:dependencies).reduce({}) { |a, b| Babl::Utils::Hash.deep_merge(a, b) }
|
10
|
+
end
|
11
|
+
|
12
|
+
def pinned_dependencies
|
13
|
+
nodes.values.map(&:pinned_dependencies).reduce({}) { |a, b| Babl::Utils::Hash.deep_merge(a, b) }
|
14
|
+
end
|
15
|
+
|
16
|
+
def schema
|
17
|
+
properties = nodes.map { |k, v| Schema::Object::Property.new(k, v.schema, true) }
|
18
|
+
Schema::Object.new(properties, false, false)
|
19
|
+
end
|
20
|
+
|
21
|
+
def render(ctx)
|
22
|
+
nodes.map { |k, v| [k, v.render(ctx)] }.to_h
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
require 'babl/utils/ref'
|
2
|
+
require 'babl/errors'
|
3
|
+
require 'values'
|
4
|
+
|
5
|
+
module Babl
|
6
|
+
module Nodes
|
7
|
+
class Parent < Value.new(:node)
|
8
|
+
PARENT_MARKER = Utils::Ref.new
|
9
|
+
|
10
|
+
class Resolver < Value.new(:node)
|
11
|
+
def dependencies
|
12
|
+
backpropagate_dependencies(node.dependencies)
|
13
|
+
end
|
14
|
+
|
15
|
+
def schema
|
16
|
+
node.schema
|
17
|
+
end
|
18
|
+
|
19
|
+
def pinned_dependencies
|
20
|
+
node.pinned_dependencies
|
21
|
+
end
|
22
|
+
|
23
|
+
def render(ctx)
|
24
|
+
node.render(ctx)
|
25
|
+
end
|
26
|
+
|
27
|
+
private
|
28
|
+
|
29
|
+
def backpropagate_dependencies(deps)
|
30
|
+
raise Errors::InvalidTemplateError, 'Out of context parent dependency' if deps.key? PARENT_MARKER
|
31
|
+
new_deps = backpropagate_dependencies_one_level(deps)
|
32
|
+
deps == new_deps ? new_deps : backpropagate_dependencies(new_deps)
|
33
|
+
end
|
34
|
+
|
35
|
+
def backpropagate_dependencies_one_level(deps)
|
36
|
+
deps.reduce({}) do |out, (k, v)|
|
37
|
+
next out if k == PARENT_MARKER
|
38
|
+
|
39
|
+
Babl::Utils::Hash.deep_merge(
|
40
|
+
Babl::Utils::Hash.deep_merge(out, k => backpropagate_dependencies_one_level(v)),
|
41
|
+
v[PARENT_MARKER] || {}
|
42
|
+
)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def schema
|
48
|
+
node.schema
|
49
|
+
end
|
50
|
+
|
51
|
+
def pinned_dependencies
|
52
|
+
node.pinned_dependencies
|
53
|
+
end
|
54
|
+
|
55
|
+
def dependencies
|
56
|
+
{ PARENT_MARKER => node.dependencies }
|
57
|
+
end
|
58
|
+
|
59
|
+
def render(ctx)
|
60
|
+
node.render(ctx.move_backward)
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
require 'babl/nodes/terminal_value'
|
2
|
+
require 'babl/schema/static'
|
3
|
+
require 'values'
|
4
|
+
|
5
|
+
module Babl
|
6
|
+
module Nodes
|
7
|
+
class Static < Value.new(:serialized_value)
|
8
|
+
def initialize(value)
|
9
|
+
super(value)
|
10
|
+
end
|
11
|
+
|
12
|
+
def schema
|
13
|
+
Schema::Static.new(serialized_value)
|
14
|
+
end
|
15
|
+
|
16
|
+
def render(_ctx)
|
17
|
+
serialized_value
|
18
|
+
end
|
19
|
+
|
20
|
+
def dependencies
|
21
|
+
{}
|
22
|
+
end
|
23
|
+
|
24
|
+
def pinned_dependencies
|
25
|
+
{}
|
26
|
+
end
|
27
|
+
|
28
|
+
private
|
29
|
+
|
30
|
+
def generate_doc
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
require 'babl/utils/hash'
|
2
|
+
require 'babl/schema/any_of'
|
3
|
+
require 'babl/errors'
|
4
|
+
require 'values'
|
5
|
+
|
6
|
+
module Babl
|
7
|
+
module Nodes
|
8
|
+
class Switch < Value.new(:nodes)
|
9
|
+
def dependencies
|
10
|
+
(nodes.values + nodes.keys).map(&:dependencies)
|
11
|
+
.reduce({}) { |a, b| Babl::Utils::Hash.deep_merge(a, b) }
|
12
|
+
end
|
13
|
+
|
14
|
+
def pinned_dependencies
|
15
|
+
(nodes.values + nodes.keys).map(&:pinned_dependencies)
|
16
|
+
.reduce({}) { |a, b| Babl::Utils::Hash.deep_merge(a, b) }
|
17
|
+
end
|
18
|
+
|
19
|
+
def schema
|
20
|
+
Schema::AnyOf.new(nodes.values.map(&:schema)).simplify
|
21
|
+
end
|
22
|
+
|
23
|
+
def render(ctx)
|
24
|
+
nodes.each { |cond, value| return value.render(ctx) if cond.render(ctx) }
|
25
|
+
raise Errors::RenderingError, 'A least one switch() condition must be taken'
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,76 @@
|
|
1
|
+
require 'singleton'
|
2
|
+
require 'babl/schema/anything'
|
3
|
+
require 'babl/errors'
|
4
|
+
|
5
|
+
module Babl
|
6
|
+
module Nodes
|
7
|
+
# A TerminalValue node is always implicitely added to the end of the
|
8
|
+
# chain during compilation. It basically ensures that the output contains only
|
9
|
+
# primitives, arrays and hashes.
|
10
|
+
class TerminalValue
|
11
|
+
include Singleton
|
12
|
+
|
13
|
+
def schema
|
14
|
+
Schema::Anything.instance
|
15
|
+
end
|
16
|
+
|
17
|
+
def dependencies
|
18
|
+
{}
|
19
|
+
end
|
20
|
+
|
21
|
+
def pinned_dependencies
|
22
|
+
{}
|
23
|
+
end
|
24
|
+
|
25
|
+
def render(ctx)
|
26
|
+
render_object(ctx.object)
|
27
|
+
rescue TerminalValueError => e
|
28
|
+
raise Errors::RenderingError, "#{e.message}\n" + ctx.formatted_stack(e.babl_stack), e.backtrace
|
29
|
+
end
|
30
|
+
|
31
|
+
def render_object(obj, stack = [])
|
32
|
+
case obj
|
33
|
+
when String, Numeric, NilClass, TrueClass, FalseClass then obj
|
34
|
+
when Hash then render_hash(obj, stack)
|
35
|
+
when Array then render_array(obj, stack)
|
36
|
+
else raise TerminalValueError.new("Only primitives can be serialized: #{obj}", stack)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
private
|
41
|
+
|
42
|
+
def render_array(array, stack)
|
43
|
+
array.each_with_index.map { |obj, index|
|
44
|
+
stack.push index
|
45
|
+
render_object(obj, stack)
|
46
|
+
stack.pop
|
47
|
+
}
|
48
|
+
end
|
49
|
+
|
50
|
+
def render_hash(hash, stack)
|
51
|
+
hash.map { |k, v|
|
52
|
+
key = render_key(k, stack)
|
53
|
+
stack.push key
|
54
|
+
out = [key, render_object(v, stack)]
|
55
|
+
stack.pop
|
56
|
+
out
|
57
|
+
}.to_h
|
58
|
+
end
|
59
|
+
|
60
|
+
def render_key(key, stack)
|
61
|
+
case key
|
62
|
+
when Symbol, String, Numeric, NilClass, TrueClass, FalseClass then :"#{key}"
|
63
|
+
else raise TerminalValueError.new("Invalid key for JSON object: #{key}", stack)
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
class TerminalValueError < Errors::RenderingError
|
68
|
+
attr_reader :babl_stack
|
69
|
+
def initialize(message, babl_stack)
|
70
|
+
@babl_stack = babl_stack
|
71
|
+
super(message)
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
require 'babl/utils/hash'
|
2
|
+
require 'values'
|
3
|
+
|
4
|
+
module Babl
|
5
|
+
module Nodes
|
6
|
+
class With < Value.new(:node, :nodes, :block)
|
7
|
+
def schema
|
8
|
+
node.schema
|
9
|
+
end
|
10
|
+
|
11
|
+
def dependencies
|
12
|
+
# Dependencies of 'node' are explicitely ignored
|
13
|
+
nodes.map(&:dependencies).reduce({}) { |a, b| Babl::Utils::Hash.deep_merge(a, b) }
|
14
|
+
end
|
15
|
+
|
16
|
+
def pinned_dependencies
|
17
|
+
(nodes + [node]).map(&:pinned_dependencies).reduce({}) { |a, b| Babl::Utils::Hash.deep_merge(a, b) }
|
18
|
+
end
|
19
|
+
|
20
|
+
def render(ctx)
|
21
|
+
values = nodes.map { |n| n.render(ctx) }
|
22
|
+
node.render(ctx.move_forward_block(:__block__) do
|
23
|
+
block.arity.zero? ? ctx.object.instance_exec(&block) : block.call(*values)
|
24
|
+
end)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
data/lib/babl/operators/array.rb
CHANGED
@@ -1,3 +1,6 @@
|
|
1
|
+
require 'babl/nodes/fixed_array'
|
2
|
+
require 'babl/nodes/terminal_value'
|
3
|
+
|
1
4
|
module Babl
|
2
5
|
module Operators
|
3
6
|
module Array
|
@@ -5,38 +8,12 @@ module Babl
|
|
5
8
|
# Produce an fixed-size array, using the provided templates to populate its elements.
|
6
9
|
def array(*templates)
|
7
10
|
construct_terminal { |ctx|
|
8
|
-
|
9
|
-
unscoped.call(t).builder.precompile(
|
11
|
+
Nodes::FixedArray.new(templates.map { |t|
|
12
|
+
unscoped.call(t).builder.precompile(Nodes::TerminalValue.instance, ctx.merge(continue: nil))
|
10
13
|
})
|
11
14
|
}
|
12
15
|
end
|
13
16
|
end
|
14
|
-
|
15
|
-
class FixedArrayNode
|
16
|
-
def initialize(nodes)
|
17
|
-
@nodes = nodes
|
18
|
-
end
|
19
|
-
|
20
|
-
def documentation
|
21
|
-
nodes.map(&:documentation)
|
22
|
-
end
|
23
|
-
|
24
|
-
def dependencies
|
25
|
-
nodes.map(&:dependencies).reduce({}) { |a, b| Babl::Utils::Hash.deep_merge(a, b) }
|
26
|
-
end
|
27
|
-
|
28
|
-
def pinned_dependencies
|
29
|
-
nodes.map(&:pinned_dependencies).reduce({}) { |a, b| Babl::Utils::Hash.deep_merge(a, b) }
|
30
|
-
end
|
31
|
-
|
32
|
-
def render(ctx)
|
33
|
-
nodes.map { |node| node.render(ctx) }
|
34
|
-
end
|
35
|
-
|
36
|
-
private
|
37
|
-
|
38
|
-
attr_reader :nodes
|
39
|
-
end
|
40
17
|
end
|
41
18
|
end
|
42
19
|
end
|
data/lib/babl/operators/call.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
require 'babl/errors'
|
2
|
+
|
1
3
|
module Babl
|
2
4
|
module Operators
|
3
5
|
module Call
|
@@ -5,7 +7,7 @@ module Babl
|
|
5
7
|
# Interpret whatever is passed to this method as BABL template. It is idempotent.
|
6
8
|
def call(*args, &block)
|
7
9
|
return with(*args, &block) unless block.nil?
|
8
|
-
raise ::
|
10
|
+
raise Errors::InvalidTemplateError, 'call() expects exactly 1 argument (unless block)' unless args.size == 1
|
9
11
|
|
10
12
|
arg = args.first
|
11
13
|
|
@@ -16,7 +18,7 @@ module Babl
|
|
16
18
|
when ::Hash then object(**arg.map { |k, v| [:"#{k}", v] }.to_h)
|
17
19
|
when ::Array then array(*arg)
|
18
20
|
when ::String, ::Numeric, ::NilClass, ::TrueClass, ::FalseClass then static(arg)
|
19
|
-
else raise ::
|
21
|
+
else raise Errors::InvalidTemplateError, "call() received invalid argument: #{arg}"
|
20
22
|
end
|
21
23
|
end
|
22
24
|
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
require 'babl/errors'
|
2
|
+
|
3
|
+
module Babl
|
4
|
+
module Operators
|
5
|
+
module Continue
|
6
|
+
module DSL
|
7
|
+
# Return a special placeholder that can be used as a switch(...) value. It tells BABL to continue
|
8
|
+
# the evaluation of the original chain after switch().
|
9
|
+
def continue
|
10
|
+
construct_terminal { |context|
|
11
|
+
node = context[:continue]
|
12
|
+
raise Errors::InvalidTemplateError, 'continue() cannot be used outside switch()' unless node
|
13
|
+
node
|
14
|
+
}
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
data/lib/babl/operators/dep.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
require 'babl/nodes/dep'
|
2
|
+
|
1
3
|
module Babl
|
2
4
|
module Operators
|
3
5
|
module Dep
|
@@ -5,43 +7,8 @@ module Babl
|
|
5
7
|
# Declare dependencies as if they were generated by nav()
|
6
8
|
# but without navigating.
|
7
9
|
def dep(*path)
|
8
|
-
construct_node(continue: nil) { |node|
|
9
|
-
end
|
10
|
-
end
|
11
|
-
|
12
|
-
class DepNode
|
13
|
-
def initialize(node, path)
|
14
|
-
@node = node
|
15
|
-
@path = canonicalize(path)
|
16
|
-
end
|
17
|
-
|
18
|
-
def render(ctx)
|
19
|
-
node.render(ctx)
|
20
|
-
end
|
21
|
-
|
22
|
-
def documentation
|
23
|
-
node.documentation
|
10
|
+
construct_node(continue: nil) { |node| Nodes::Dep.new(node, path) }
|
24
11
|
end
|
25
|
-
|
26
|
-
def pinned_dependencies
|
27
|
-
node.pinned_dependencies
|
28
|
-
end
|
29
|
-
|
30
|
-
def dependencies
|
31
|
-
Babl::Utils::Hash.deep_merge(node.dependencies, path)
|
32
|
-
end
|
33
|
-
|
34
|
-
private
|
35
|
-
|
36
|
-
def canonicalize(path)
|
37
|
-
case path
|
38
|
-
when ::Array then path.reduce({}) { |a, p| a.merge(canonicalize(p)) }
|
39
|
-
when ::Hash then path.map { |k, v| [k.to_sym, canonicalize(v)] }.to_h
|
40
|
-
else { path.to_sym => {} }
|
41
|
-
end
|
42
|
-
end
|
43
|
-
|
44
|
-
attr_reader :node, :path
|
45
12
|
end
|
46
13
|
end
|
47
14
|
end
|
data/lib/babl/operators/each.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
require 'babl/nodes/each'
|
2
|
+
|
1
3
|
module Babl
|
2
4
|
module Operators
|
3
5
|
module Each
|
@@ -5,40 +7,8 @@ module Babl
|
|
5
7
|
# Construct a JSON array by iterating over the current collection,
|
6
8
|
# using the chained template for rendering each element.
|
7
9
|
def each
|
8
|
-
construct_node(key: nil, continue: nil) { |node|
|
9
|
-
end
|
10
|
-
end
|
11
|
-
|
12
|
-
class EachNode
|
13
|
-
def initialize(node)
|
14
|
-
@node = node
|
15
|
-
end
|
16
|
-
|
17
|
-
def dependencies
|
18
|
-
{ __each__: node.dependencies }
|
19
|
-
end
|
20
|
-
|
21
|
-
def documentation
|
22
|
-
[node.documentation]
|
10
|
+
construct_node(key: nil, continue: nil) { |node| Nodes::Each.new(node) }
|
23
11
|
end
|
24
|
-
|
25
|
-
def pinned_dependencies
|
26
|
-
node.pinned_dependencies
|
27
|
-
end
|
28
|
-
|
29
|
-
def render(ctx)
|
30
|
-
collection = ctx.object
|
31
|
-
|
32
|
-
unless collection.is_a?(Enumerable)
|
33
|
-
raise RenderingError, "Object is not enumerable : #{collection.inspect}"
|
34
|
-
end
|
35
|
-
|
36
|
-
collection.each_with_index.map { |value, idx| node.render(ctx.move_forward(value, idx)) }
|
37
|
-
end
|
38
|
-
|
39
|
-
private
|
40
|
-
|
41
|
-
attr_reader :node
|
42
12
|
end
|
43
13
|
end
|
44
14
|
end
|