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.
Files changed (78) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +1 -1
  3. data/CHANGELOG.md +6 -0
  4. data/README.md +1 -3
  5. data/babl.gemspec +3 -1
  6. data/lib/babl.rb +4 -6
  7. data/lib/babl/builder/chain_builder.rb +6 -2
  8. data/lib/babl/builder/template_base.rb +16 -3
  9. data/lib/babl/errors.rb +7 -0
  10. data/lib/babl/nodes/create_pin.rb +24 -0
  11. data/lib/babl/nodes/dep.rb +38 -0
  12. data/lib/babl/nodes/each.rb +29 -0
  13. data/lib/babl/nodes/fixed_array.rb +25 -0
  14. data/lib/babl/nodes/goto_pin.rb +24 -0
  15. data/lib/babl/{rendering/internal_value_node.rb → nodes/internal_value.rb} +6 -5
  16. data/lib/babl/nodes/merge.rb +102 -0
  17. data/lib/babl/nodes/nav.rb +33 -0
  18. data/lib/babl/nodes/object.rb +26 -0
  19. data/lib/babl/nodes/parent.rb +64 -0
  20. data/lib/babl/nodes/static.rb +34 -0
  21. data/lib/babl/nodes/switch.rb +29 -0
  22. data/lib/babl/nodes/terminal_value.rb +76 -0
  23. data/lib/babl/nodes/with.rb +28 -0
  24. data/lib/babl/operators/array.rb +5 -28
  25. data/lib/babl/operators/call.rb +4 -2
  26. data/lib/babl/operators/continue.rb +19 -0
  27. data/lib/babl/operators/default.rb +13 -0
  28. data/lib/babl/operators/dep.rb +3 -36
  29. data/lib/babl/operators/each.rb +3 -33
  30. data/lib/babl/operators/enter.rb +4 -2
  31. data/lib/babl/operators/extends.rb +4 -1
  32. data/lib/babl/operators/merge.rb +7 -30
  33. data/lib/babl/operators/nav.rb +4 -36
  34. data/lib/babl/operators/object.rb +7 -29
  35. data/lib/babl/operators/parent.rb +4 -73
  36. data/lib/babl/operators/partial.rb +4 -2
  37. data/lib/babl/operators/pin.rb +14 -58
  38. data/lib/babl/operators/static.rb +11 -30
  39. data/lib/babl/operators/switch.rb +8 -51
  40. data/lib/babl/operators/with.rb +5 -34
  41. data/lib/babl/railtie.rb +2 -2
  42. data/lib/babl/rendering/compiled_template.rb +5 -13
  43. data/lib/babl/rendering/context.rb +13 -7
  44. data/lib/babl/schema/any_of.rb +137 -0
  45. data/lib/babl/schema/anything.rb +13 -0
  46. data/lib/babl/schema/dyn_array.rb +11 -0
  47. data/lib/babl/schema/fixed_array.rb +13 -0
  48. data/lib/babl/schema/object.rb +35 -0
  49. data/lib/babl/schema/static.rb +14 -0
  50. data/lib/babl/schema/typed.rb +0 -0
  51. data/lib/babl/template.rb +4 -9
  52. data/lib/babl/utils/ref.rb +6 -0
  53. data/lib/babl/version.rb +1 -1
  54. data/spec/operators/array_spec.rb +31 -7
  55. data/spec/operators/call_spec.rb +16 -14
  56. data/spec/operators/continue_spec.rb +25 -0
  57. data/spec/operators/default_spec.rb +15 -0
  58. data/spec/operators/dep_spec.rb +4 -8
  59. data/spec/operators/each_spec.rb +24 -5
  60. data/spec/operators/enter_spec.rb +9 -7
  61. data/spec/operators/extends_spec.rb +19 -5
  62. data/spec/operators/merge_spec.rb +105 -12
  63. data/spec/operators/nav_spec.rb +22 -10
  64. data/spec/operators/null_spec.rb +5 -4
  65. data/spec/operators/nullable_spec.rb +13 -13
  66. data/spec/operators/object_spec.rb +17 -6
  67. data/spec/operators/parent_spec.rb +18 -22
  68. data/spec/operators/partial_spec.rb +8 -6
  69. data/spec/operators/pin_spec.rb +100 -61
  70. data/spec/operators/source_spec.rb +10 -6
  71. data/spec/operators/static_spec.rb +17 -9
  72. data/spec/operators/switch_spec.rb +85 -45
  73. data/spec/operators/with_spec.rb +13 -15
  74. data/spec/spec_helper.rb +2 -31
  75. data/spec/spec_helper/operator_testing.rb +46 -0
  76. data/spec/spec_helper/schema_utils.rb +33 -0
  77. metadata +63 -4
  78. 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
@@ -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
- FixedArrayNode.new(templates.map { |t|
9
- unscoped.call(t).builder.precompile(Rendering::TerminalValueNode.instance, ctx.merge(continue: nil))
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
@@ -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 ::Babl::InvalidTemplateError, 'call() expects exactly 1 argument (unless block)' unless args.size == 1
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 ::Babl::InvalidTemplateError, "call() received invalid argument: #{arg}"
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
@@ -0,0 +1,13 @@
1
+ module Babl
2
+ module Operators
3
+ module Default
4
+ module DSL
5
+ # To be used as a switch(...) condition. It is strictly equivalent to write 'true' instead,
6
+ # but it conveys more meaning.
7
+ def default
8
+ static(true)
9
+ end
10
+ end
11
+ end
12
+ end
13
+ end
@@ -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| DepNode.new(node, path) }
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
@@ -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| EachNode.new(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