babl-json 0.1.4 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|