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
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 20dbd1b42e36b6fa9d7f7744b2cae7caba856a05
|
4
|
+
data.tar.gz: 25e21d06230f8694532b962ab9062a648682cde9
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 3b133517888b63bef55810cd3815fc4fc5378a0b5e200e519aefebed0f3bf50d9d64141c72f9f508b181b153c3c2f6348f4126b5eba8a00a73a6908341bd8ac4
|
7
|
+
data.tar.gz: e762bbdc629534d829cc56d13fe40617111754b15ca64a13e0865b3f55d60ca13dd226ad8584c06be492069285b295e09b9baaa38c2bafc61dfce1cdc95e670a
|
data/.travis.yml
CHANGED
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,11 @@
|
|
1
1
|
# BABL Changelog
|
2
2
|
|
3
|
+
## 0.2.0 (July 11, 2017)
|
4
|
+
|
5
|
+
- Added support for producing documentation using [JSON-Schema](http://json-schema.org/).
|
6
|
+
- Improved some error messages occurring during rendering phase.
|
7
|
+
- Stricter validation of templates passed to the `merge` operator. Compilation will fail if at least one of them is not producing object.
|
8
|
+
|
3
9
|
## 0.1.4 (July 6, 2017)
|
4
10
|
|
5
11
|
- Added operator `extends`, to simplify the common usage pattern `merge(partial(...), ...)`.
|
data/README.md
CHANGED
@@ -66,9 +66,7 @@ As of today, the only compatible preloader implementation *has not been released
|
|
66
66
|
|
67
67
|
### Automatic documentation
|
68
68
|
|
69
|
-
|
70
|
-
|
71
|
-
Mid-term goal is to generate a [JSON schema](http://json-schema.org/).
|
69
|
+
The structure of the JSON produced by a BABL template can be documentated using [JSON-Schema](http://json-schema.org/).
|
72
70
|
|
73
71
|
### Rails integration
|
74
72
|
|
data/babl.gemspec
CHANGED
@@ -2,7 +2,7 @@ require File.join(File.dirname(__FILE__), 'lib/babl/version')
|
|
2
2
|
|
3
3
|
Gem::Specification.new do |gem|
|
4
4
|
gem.name = "babl-json"
|
5
|
-
gem.version =
|
5
|
+
gem.version = Babl::VERSION
|
6
6
|
gem.licenses = ['MIT']
|
7
7
|
gem.authors = ['Frederic Terrazzoni']
|
8
8
|
gem.email = ['frederic.terrazzoni@gmail.com']
|
@@ -18,6 +18,8 @@ Gem::Specification.new do |gem|
|
|
18
18
|
gem.add_development_dependency 'pry', '~> 0'
|
19
19
|
gem.add_development_dependency 'rspec', '~> 3'
|
20
20
|
gem.add_development_dependency 'rubocop', '~> 0.48'
|
21
|
+
gem.add_development_dependency 'json-schema', '~> 2.8'
|
21
22
|
|
22
23
|
gem.add_dependency 'oj', '~> 3.0'
|
24
|
+
gem.add_dependency 'values', '~> 1.8'
|
23
25
|
end
|
data/lib/babl.rb
CHANGED
@@ -1,12 +1,10 @@
|
|
1
1
|
require 'babl/railtie' if defined?(Rails)
|
2
2
|
require 'babl/template'
|
3
3
|
require 'babl/version'
|
4
|
+
require 'babl/rendering/noop_preloader'
|
5
|
+
require 'babl/operators/partial'
|
4
6
|
|
5
7
|
module Babl
|
6
|
-
class BablError < StandardError; end
|
7
|
-
class InvalidTemplateError < BablError; end
|
8
|
-
class RenderingError < BablError; end
|
9
|
-
|
10
8
|
class Config
|
11
9
|
attr_accessor :search_path, :preloader, :pretty
|
12
10
|
|
@@ -18,9 +16,9 @@ module Babl
|
|
18
16
|
end
|
19
17
|
|
20
18
|
class << self
|
21
|
-
def compile(template:
|
19
|
+
def compile(template: Babl::Template.new, &source)
|
22
20
|
if config.search_path
|
23
|
-
ctx =
|
21
|
+
ctx = Babl::Operators::Partial::AbsoluteLookupContext.new(config.search_path)
|
24
22
|
template = template.with_lookup_context(ctx)
|
25
23
|
end
|
26
24
|
|
@@ -1,3 +1,7 @@
|
|
1
|
+
require 'babl/nodes/internal_value'
|
2
|
+
require 'babl/nodes/terminal_value'
|
3
|
+
require 'babl/errors'
|
4
|
+
|
1
5
|
module Babl
|
2
6
|
module Builder
|
3
7
|
# Builder provides a simple framework for defining & chaining BABL's operators easily.
|
@@ -36,8 +40,8 @@ module Babl
|
|
36
40
|
# Append a terminal operator, and return a new Builder object
|
37
41
|
def construct_terminal
|
38
42
|
construct_node do |node, context|
|
39
|
-
unless [
|
40
|
-
raise ::
|
43
|
+
unless [Nodes::InternalValue.instance, Nodes::TerminalValue.instance].include?(node)
|
44
|
+
raise Errors::InvalidTemplateError, 'Chaining is not allowed after a terminal operator'
|
41
45
|
end
|
42
46
|
yield context
|
43
47
|
end
|
@@ -1,3 +1,8 @@
|
|
1
|
+
require 'babl/nodes/terminal_value'
|
2
|
+
require 'babl/builder/chain_builder'
|
3
|
+
require 'babl/rendering/compiled_template'
|
4
|
+
require 'babl/rendering/noop_preloader'
|
5
|
+
|
1
6
|
module Babl
|
2
7
|
module Builder
|
3
8
|
# TemplateBase is a thin wrapper around Builder.
|
@@ -9,8 +14,16 @@ module Babl
|
|
9
14
|
@builder = builder
|
10
15
|
end
|
11
16
|
|
12
|
-
def compile(
|
13
|
-
|
17
|
+
def compile(preloader: Rendering::NoopPreloader, pretty: true)
|
18
|
+
node = precompile
|
19
|
+
|
20
|
+
Rendering::CompiledTemplate.with(
|
21
|
+
preloader: preloader,
|
22
|
+
pretty: pretty,
|
23
|
+
node: node,
|
24
|
+
dependencies: node.dependencies,
|
25
|
+
json_schema: node.schema.json
|
26
|
+
)
|
14
27
|
end
|
15
28
|
|
16
29
|
protected
|
@@ -20,7 +33,7 @@ module Babl
|
|
20
33
|
end
|
21
34
|
|
22
35
|
def precompile
|
23
|
-
builder.precompile(
|
36
|
+
builder.precompile(Nodes::TerminalValue.instance)
|
24
37
|
end
|
25
38
|
|
26
39
|
def construct_node(**new_context, &block)
|
data/lib/babl/errors.rb
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
require 'babl/utils/hash'
|
2
|
+
require 'values'
|
3
|
+
|
4
|
+
module Babl
|
5
|
+
module Nodes
|
6
|
+
class CreatePin < Value.new(:node, :ref)
|
7
|
+
def render(ctx)
|
8
|
+
node.render(ctx.create_pin(ref))
|
9
|
+
end
|
10
|
+
|
11
|
+
def schema
|
12
|
+
node.schema
|
13
|
+
end
|
14
|
+
|
15
|
+
def dependencies
|
16
|
+
Babl::Utils::Hash.deep_merge(node.dependencies, node.pinned_dependencies[ref] || {})
|
17
|
+
end
|
18
|
+
|
19
|
+
def pinned_dependencies
|
20
|
+
node.pinned_dependencies.reject { |k, _v| k == ref }
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
require 'babl/utils/hash'
|
2
|
+
require 'values'
|
3
|
+
|
4
|
+
module Babl
|
5
|
+
module Nodes
|
6
|
+
class Dep < Value.new(:node, :path)
|
7
|
+
def initialize(node, path)
|
8
|
+
super(node, canonicalize(path))
|
9
|
+
end
|
10
|
+
|
11
|
+
def render(ctx)
|
12
|
+
node.render(ctx)
|
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 dependencies
|
24
|
+
Babl::Utils::Hash.deep_merge(node.dependencies, path)
|
25
|
+
end
|
26
|
+
|
27
|
+
private
|
28
|
+
|
29
|
+
def canonicalize(path)
|
30
|
+
case path
|
31
|
+
when ::Array then path.reduce({}) { |a, p| a.merge(canonicalize(p)) }
|
32
|
+
when ::Hash then path.map { |k, v| [k.to_sym, canonicalize(v)] }.to_h
|
33
|
+
else { path.to_sym => {} }
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
require 'babl/schema/dyn_array'
|
2
|
+
require 'babl/errors'
|
3
|
+
require 'values'
|
4
|
+
|
5
|
+
module Babl
|
6
|
+
module Nodes
|
7
|
+
class Each < Value.new(:node)
|
8
|
+
def dependencies
|
9
|
+
{ __each__: node.dependencies }
|
10
|
+
end
|
11
|
+
|
12
|
+
def schema
|
13
|
+
Schema::DynArray.new(node.schema, false)
|
14
|
+
end
|
15
|
+
|
16
|
+
def pinned_dependencies
|
17
|
+
node.pinned_dependencies
|
18
|
+
end
|
19
|
+
|
20
|
+
def render(ctx)
|
21
|
+
collection = ctx.object
|
22
|
+
unless Enumerable === collection
|
23
|
+
raise Errors::RenderingError, "Not enumerable : #{collection}\n#{ctx.formatted_stack}"
|
24
|
+
end
|
25
|
+
collection.each_with_index.map { |value, idx| node.render(ctx.move_forward(value, idx)) }
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
require 'babl/utils/hash'
|
2
|
+
require 'babl/schema/fixed_array'
|
3
|
+
require 'values'
|
4
|
+
|
5
|
+
module Babl
|
6
|
+
module Nodes
|
7
|
+
class FixedArray < Value.new(:nodes)
|
8
|
+
def schema
|
9
|
+
Schema::FixedArray.new(nodes.map(&:schema), false)
|
10
|
+
end
|
11
|
+
|
12
|
+
def dependencies
|
13
|
+
nodes.map(&:dependencies).reduce({}) { |a, b| Babl::Utils::Hash.deep_merge(a, b) }
|
14
|
+
end
|
15
|
+
|
16
|
+
def pinned_dependencies
|
17
|
+
nodes.map(&:pinned_dependencies).reduce({}) { |a, b| Babl::Utils::Hash.deep_merge(a, b) }
|
18
|
+
end
|
19
|
+
|
20
|
+
def render(ctx)
|
21
|
+
nodes.map { |node| node.render(ctx) }
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
require 'babl/utils/hash'
|
2
|
+
require 'values'
|
3
|
+
|
4
|
+
module Babl
|
5
|
+
module Nodes
|
6
|
+
class GotoPin < Value.new(:node, :ref)
|
7
|
+
def dependencies
|
8
|
+
{}
|
9
|
+
end
|
10
|
+
|
11
|
+
def pinned_dependencies
|
12
|
+
Babl::Utils::Hash.deep_merge(node.pinned_dependencies, ref => node.dependencies)
|
13
|
+
end
|
14
|
+
|
15
|
+
def schema
|
16
|
+
node.schema
|
17
|
+
end
|
18
|
+
|
19
|
+
def render(ctx)
|
20
|
+
node.render(ctx.goto_pin(ref))
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -1,17 +1,18 @@
|
|
1
1
|
require 'singleton'
|
2
|
+
require 'babl/errors'
|
2
3
|
|
3
4
|
module Babl
|
4
|
-
module
|
5
|
-
# This Node plays a role similar to
|
5
|
+
module Nodes
|
6
|
+
# This Node plays a role similar to TerminalValue, but it does not perform any
|
6
7
|
# type checking on the produced object, which is allowed to be any Ruby object,
|
7
8
|
# including non-serializable objects.
|
8
9
|
#
|
9
10
|
# It is used when the output is not rendered (conditions in #switch, values passed to block in #with, ...)
|
10
|
-
class
|
11
|
+
class InternalValue
|
11
12
|
include Singleton
|
12
13
|
|
13
|
-
def
|
14
|
-
|
14
|
+
def schema
|
15
|
+
raise Errors::InvalidTemplateError, 'Internal nodes cannot be documented'
|
15
16
|
end
|
16
17
|
|
17
18
|
def dependencies
|
@@ -0,0 +1,102 @@
|
|
1
|
+
require 'babl/utils/hash'
|
2
|
+
require 'babl/errors'
|
3
|
+
require 'values'
|
4
|
+
|
5
|
+
module Babl
|
6
|
+
module Nodes
|
7
|
+
class Merge < Value.new(:nodes)
|
8
|
+
def initialize(nodes)
|
9
|
+
super
|
10
|
+
end
|
11
|
+
|
12
|
+
def dependencies
|
13
|
+
nodes.map(&:dependencies).reduce({}) { |a, b| Babl::Utils::Hash.deep_merge(a, b) }
|
14
|
+
end
|
15
|
+
|
16
|
+
def pinned_dependencies
|
17
|
+
nodes.map(&:pinned_dependencies).reduce({}) { |a, b| Babl::Utils::Hash.deep_merge(a, b) }
|
18
|
+
end
|
19
|
+
|
20
|
+
def schema
|
21
|
+
nodes.map(&:schema).reduce(Schema::Object::EMPTY) { |a, b| merge_doc(a, b) }
|
22
|
+
end
|
23
|
+
|
24
|
+
def render(ctx)
|
25
|
+
nodes.map { |node| node.render(ctx) }.compact.reduce({}) { |acc, val|
|
26
|
+
raise Errors::RenderingError, "Only objects can be merged\n" + ctx.formatted_stack unless ::Hash === val
|
27
|
+
acc.merge!(val)
|
28
|
+
}
|
29
|
+
end
|
30
|
+
|
31
|
+
private
|
32
|
+
|
33
|
+
# Merge two documentations together
|
34
|
+
def merge_doc(doc1, doc2)
|
35
|
+
doc1 = Schema::Object::EMPTY if Schema::Static::NULL == doc1
|
36
|
+
doc2 = Schema::Object::EMPTY if Schema::Static::NULL == doc2
|
37
|
+
|
38
|
+
case
|
39
|
+
when Schema::AnyOf === doc1 || Schema::AnyOf === doc2
|
40
|
+
merge_extended(doc1, doc2)
|
41
|
+
when Schema::Object === doc1 && Schema::Object === doc2
|
42
|
+
merge_object(doc1, doc2)
|
43
|
+
when Schema::Object === doc1 && Schema::Anything === doc2
|
44
|
+
merge_object(doc1, Schema::Object::EMPTY_WITH_ADDITIONAL)
|
45
|
+
when Schema::Anything === doc1 && Schema::Object === doc2
|
46
|
+
merge_object(Schema::Object::EMPTY_WITH_ADDITIONAL, doc2)
|
47
|
+
else
|
48
|
+
raise Errors::InvalidTemplateError, 'Only objects can be merged'
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
# Merge two documention when Schema::AnyOf is involved either
|
53
|
+
# on left, right or both sides.
|
54
|
+
def merge_extended(doc1, doc2)
|
55
|
+
# Ensure doc1 & doc2 are both Schema::AnyOf
|
56
|
+
doc1ext = Schema::AnyOf.new([doc1])
|
57
|
+
doc2ext = Schema::AnyOf.new([doc2])
|
58
|
+
|
59
|
+
# Generate all possible combinations
|
60
|
+
all_docs = doc1ext.choices.product(doc2ext.choices)
|
61
|
+
.map { |choice1, choice2| merge_doc(choice1, choice2) }
|
62
|
+
|
63
|
+
# Analyze each property accross all combination to
|
64
|
+
# generate a Schema::Object::Property filled with
|
65
|
+
# accurate information.
|
66
|
+
final_properties = all_docs.flat_map(&:properties)
|
67
|
+
.group_by(&:name)
|
68
|
+
.map do |name, properties|
|
69
|
+
Schema::Object::Property.new(
|
70
|
+
name,
|
71
|
+
Schema::AnyOf.new(properties.map(&:value)).simplify,
|
72
|
+
properties.size == all_docs.size && properties.all?(&:required)
|
73
|
+
)
|
74
|
+
end
|
75
|
+
|
76
|
+
# Generate the final Schema::Object
|
77
|
+
Schema::Object.new(final_properties, all_docs.any?(&:additional), false)
|
78
|
+
end
|
79
|
+
|
80
|
+
# Merge two Schema::Object
|
81
|
+
def merge_object(doc1, doc2)
|
82
|
+
additional = doc1.additional || doc2.additional
|
83
|
+
|
84
|
+
properties = (
|
85
|
+
doc1.properties.map { |property| doc2.additional ? allow_anything(property) : property } +
|
86
|
+
doc2.properties
|
87
|
+
).each_with_object({}) { |property, acc| acc[property.name] = property }.values
|
88
|
+
|
89
|
+
Schema::Object.new properties, additional, false
|
90
|
+
end
|
91
|
+
|
92
|
+
# Rewrite a property to allow Schema::Anything as value
|
93
|
+
def allow_anything(property)
|
94
|
+
Schema::Object::Property.new(
|
95
|
+
property.name,
|
96
|
+
Schema::AnyOf.new([property.value, Schema::Anything.instance]).simplify,
|
97
|
+
property.required
|
98
|
+
)
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
require 'values'
|
2
|
+
|
3
|
+
module Babl
|
4
|
+
module Nodes
|
5
|
+
class Nav < Value.new(:through, :node)
|
6
|
+
def dependencies
|
7
|
+
{ through => node.dependencies }
|
8
|
+
end
|
9
|
+
|
10
|
+
def schema
|
11
|
+
node.schema
|
12
|
+
end
|
13
|
+
|
14
|
+
def pinned_dependencies
|
15
|
+
node.pinned_dependencies
|
16
|
+
end
|
17
|
+
|
18
|
+
def render(ctx)
|
19
|
+
node.render(ctx.move_forward_block(through) { navigate(ctx.object) })
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
|
24
|
+
def navigate(object)
|
25
|
+
if ::Hash === object
|
26
|
+
object.fetch(through)
|
27
|
+
else
|
28
|
+
object.send(through)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|