babl-json 0.4.0 → 0.5.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 03dff31c23ccad4de2970e0e9f1e8c0ac2df3257
4
- data.tar.gz: 88bfe61b0d2fcfc20ff71a4e009d6ddf15416e00
3
+ metadata.gz: 1bad3eaac421de5e967efa6a17096a9a95529174
4
+ data.tar.gz: e722e283eb9a3004a935a28bac8b09f8f94a10e6
5
5
  SHA512:
6
- metadata.gz: c9c04e5150dd7aeda65aefcb989f4a72eb5aed5b5afd0f18edae0495818e45b6fad658c1d693375ff711329c0c036e0c333d0d5ce99349f100c8a2da0b98f07c
7
- data.tar.gz: 720f05f879639374ab4a26bc7be49b4d32c6fb7c8534621643d09bb2d3a4f402cedc8af994682268760fcdc2418ac72e6cf7e9ffd9bda8dbf14d4d914683cec7
6
+ metadata.gz: fa8c6c330b936f4c1b4a3a455bba8491a43911c7c644d161b233215e8a436e2091722e6d85c0005d2b6aa89f5641d69dddb8d50ebd1cc49010368caa434633fb
7
+ data.tar.gz: 9a6a76cc67edcf4f7f2b952b645d5fa974f492294dde354925c9eb3aa794dd3c210689c55b082923ee4682eaaa960cc6db540dd78776cc10ac7fc6cb83758bc6
@@ -6,32 +6,84 @@ require 'babl/rendering'
6
6
  require 'babl/operators'
7
7
 
8
8
  module Babl
9
+ # There is basically two ways to use BABL: the Rails way, and the clean way.
10
+ #
11
+ # Rails way
12
+ # - BABL detects Rails and integrates with it automatically (only if BABL is required AFTER Rails).
13
+ # - BABL configuration is global (defined via Babl.configure).
14
+ # - Global configuration is also used by:
15
+ # Babl.template
16
+ # Babl.compile
17
+ # Babl.source
18
+ #
19
+ # Clean way
20
+ # - You can decide not to use global configuration. It will result in more verbosity
21
+ # but it protects you from global settings.
22
+ # - Equivalences:
23
+ # Babl.template ==> Babl::Template.new
24
+ # Babl.compile ==> Babl::Template#compile
25
+ # Babl.source ==> Babl::Template#source
26
+ #
9
27
  class Config
10
- attr_accessor :search_path, :preloader, :pretty, :cache_templates
28
+ attr_accessor :preloader, # No practical use outside Bannerman today.
29
+ :pretty, # Pretty format JSON output (boolean).
30
+ :cache_templates, # Enable or disable caching of compiled templates (Rails only, boolean).
31
+ :lookup_context, # Specify how to find templates.
32
+ :using # List of user-defined modules containing custom operators.
11
33
 
12
34
  def initialize
13
- @search_path = nil
14
35
  @preloader = Rendering::NoopPreloader
15
36
  @pretty = true
16
37
  @cache_templates = false
38
+ @lookup_context = nil
39
+ @using = []
40
+ end
41
+ end
42
+
43
+ class AbsoluteLookupContext
44
+ attr_reader :search_path
45
+
46
+ def initialize(search_path)
47
+ @search_path = search_path
48
+ raise Errors::InvalidTemplate, 'Missing search path' unless search_path
49
+ end
50
+
51
+ def find(current_template, partial_name)
52
+ query = File.join(search_path, "{#{partial_name}}{.babl,}")
53
+ path = Dir[query].first
54
+ return unless path
55
+ source = File.read(path)
56
+ [current_template.source(source, path, 0), self]
17
57
  end
18
58
  end
19
59
 
20
60
  class << self
21
- def compile(&block)
22
- source(&block).compile(
61
+ def compile(*args, &block)
62
+ raise ArgumentError, 'Wrong number of arguments' if args.size > 1
63
+ raise ArgumentError, 'Template or block expected' unless args.empty? ^ block.nil?
64
+
65
+ (args.empty? ? source(&block) : template.call(args.first)).compile(
23
66
  pretty: config.pretty,
24
- preloader: config.preloader
67
+ preloader: config.preloader,
68
+ lookup_context: config.lookup_context
25
69
  )
26
70
  end
27
71
 
28
- def source(&block)
29
- template = Template.new
30
- if config.search_path
31
- ctx = Operators::Partial::AbsoluteLookupContext.new(config.search_path)
32
- template = template.with_lookup_context(ctx)
33
- end
34
- template.source(&block)
72
+ def source(*args, &block)
73
+ template.source(*args, &block)
74
+ end
75
+
76
+ def template
77
+ cached = @cached_template
78
+ return cached.last if cached && config.using == cached.first
79
+ # Calling 'using' is a very inefficient operation, because
80
+ # it creates a new class. We can avoid that cost most of the
81
+ # time, assuming 'config.using' does not change often (typically
82
+ # it should only change once at startup)
83
+ modules = config.using.dup
84
+ template = Template.new.using(*modules)
85
+ @cached_template = [modules, template]
86
+ template
35
87
  end
36
88
 
37
89
  def configure
@@ -57,11 +57,7 @@ module Babl
57
57
  end
58
58
 
59
59
  def wrap
60
- rescope { |bound| yield bind(bound) }
61
- end
62
-
63
- def rescope(&block)
64
- dup.tap { |tb| tb.instance_variable_set(:@scope, block) }
60
+ self.class.new { |bound| yield bind(bound) }
65
61
  end
66
62
  end
67
63
 
@@ -8,17 +8,20 @@ module Babl
8
8
  module Builder
9
9
  # TemplateBase is a thin wrapper around Builder.
10
10
  #
11
- # Since the BABL code is run via #instance_eval within an instance of this class, we want to
11
+ # Since the BABL code is run via #instance_exec within an instance of this class, we want to
12
12
  # define as few methods as possible here.
13
- class TemplateBase < Utils::Value.new(:builder)
13
+ class TemplateBase
14
+ attr_reader :builder
15
+
14
16
  def initialize(builder = ChainBuilder.new(&:itself))
15
- super(builder)
17
+ @builder = builder
18
+ freeze
16
19
  end
17
20
 
18
- def compile(preloader: Rendering::NoopPreloader, pretty: true, optimize: true)
21
+ def compile(preloader: Rendering::NoopPreloader, pretty: true, optimize: true, lookup_context: nil)
19
22
  # Compute dependencies & schema on the non-simplified node tree in order
20
23
  # to catch all errors.
21
- tree = precompile
24
+ tree = precompile(lookup_context: lookup_context)
22
25
  dependencies = tree.dependencies
23
26
  schema = tree.schema
24
27
 
@@ -40,13 +43,13 @@ module Babl
40
43
  end
41
44
 
42
45
  def unscoped
43
- self.class.new builder.rescope(&:itself)
46
+ self.class.new
44
47
  end
45
48
 
46
49
  protected
47
50
 
48
- def precompile
49
- builder.precompile(Nodes::TerminalValue.instance)
51
+ def precompile(node = Nodes::TerminalValue.instance, **context)
52
+ builder.precompile(node, **context)
50
53
  end
51
54
 
52
55
  def construct_node(**new_context, &block)
@@ -21,4 +21,5 @@ require 'babl/operators/source'
21
21
  require 'babl/operators/static'
22
22
  require 'babl/operators/switch'
23
23
  require 'babl/operators/typed'
24
+ require 'babl/operators/using'
24
25
  require 'babl/operators/with'
@@ -13,7 +13,7 @@ module Babl
13
13
  protected
14
14
 
15
15
  # Override TemplateBase#precompile to add parent dependencies verification
16
- def precompile
16
+ def precompile(*)
17
17
  Nodes::Parent::Verifier.new(super)
18
18
  end
19
19
  end
@@ -8,40 +8,14 @@ module Babl
8
8
  # Load a partial template given its name
9
9
  # A 'lookup_context' must be defined
10
10
  def partial(partial_name)
11
- raise Errors::InvalidTemplate, 'Cannot use partial without lookup context' unless lookup_context
12
-
13
- path, source, partial_lookup_context = lookup_context.find(partial_name)
14
- raise Errors::InvalidTemplate, "Cannot find partial '#{partial_name}'" unless path
15
-
16
- with_lookup_context(partial_lookup_context)
17
- .source(source, path, 0)
18
- .with_lookup_context(lookup_context)
19
- end
20
-
21
- def with_lookup_context(lookup_context)
22
- self.class.new(builder.dup.tap { |inst| inst.instance_variable_set(:@lookup_context, lookup_context) })
23
- end
24
-
25
- def lookup_context
26
- builder.instance_variable_get(:@lookup_context)
27
- end
28
- end
29
-
30
- class AbsoluteLookupContext
31
- attr_reader :search_path
32
-
33
- def initialize(search_path)
34
- @search_path = search_path
35
- raise Errors::InvalidTemplate, 'Invalid search path' unless search_path
36
- end
37
-
38
- def find(partial_name)
39
- query = File.join(search_path, "{#{partial_name}}{.babl,}")
40
- path = Dir[query].first
41
- return unless path
42
-
43
- source = File.read(path)
44
- [path, source, self]
11
+ current_template = unscoped
12
+ construct_terminal { |ctx|
13
+ lookup_context = ctx[:lookup_context]
14
+ raise Errors::InvalidTemplate, 'Cannot use partial without lookup context' unless lookup_context
15
+ template, new_lookup_context = lookup_context.find(current_template, partial_name)
16
+ raise Errors::InvalidTemplate, "Cannot find partial '#{partial_name}'" unless template
17
+ template.precompile(Nodes::TerminalValue.instance, lookup_context: new_lookup_context)
18
+ }
45
19
  end
46
20
  end
47
21
  end
@@ -23,7 +23,7 @@ module Babl
23
23
  protected
24
24
 
25
25
  # Override TemplateBase#precompile to ensure that all pin dependencies are satisfied.
26
- def precompile
26
+ def precompile(*)
27
27
  super.tap do |node|
28
28
  raise Errors::InvalidTemplate, 'Unresolved pin' unless node.pinned_dependencies.empty?
29
29
  end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+ module Babl
3
+ module Operators
4
+ module Using
5
+ module DSL
6
+ def using(*mods, &block)
7
+ extended_self =
8
+ if mods.empty?
9
+ self
10
+ else
11
+ ::Class.new(self.class) { mods.each { |mod| include mod } }.new(builder)
12
+ end
13
+
14
+ extended_self.source(&(block || -> { self }))
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
@@ -51,7 +51,7 @@ module Babl
51
51
 
52
52
  # Return an array containing the navigation history
53
53
  def stack
54
- (parent ? parent.stack : []) + [key].compact
54
+ (parent ? parent.stack : Utils::Array::EMPTY) + [key].compact
55
55
  end
56
56
  end
57
57
  end
@@ -4,11 +4,11 @@ require 'babl/utils'
4
4
  module Babl
5
5
  module Schema
6
6
  class FixedArray < Utils::Value.new(:items)
7
- EMPTY = new([])
7
+ EMPTY = new(Utils::Array::EMPTY)
8
8
 
9
9
  def json
10
10
  if items.empty?
11
- { enum: [[]] }
11
+ { enum: [Utils::Array::EMPTY] }
12
12
  else
13
13
  { type: 'array', items: items.map(&:json), additionalItems: false }
14
14
  end
@@ -9,8 +9,8 @@ module Babl
9
9
  super(properties.to_set.freeze, additional)
10
10
  end
11
11
 
12
- EMPTY = new([], false)
13
- EMPTY_WITH_ADDITIONAL = new([], true)
12
+ EMPTY = new(Utils::Array::EMPTY, false)
13
+ EMPTY_WITH_ADDITIONAL = new(Utils::Array::EMPTY, true)
14
14
 
15
15
  class Property < Utils::Value.new(:name, :value, :required)
16
16
  def initialize(name, value, required)
@@ -24,6 +24,7 @@ module Babl
24
24
  include Operators::Pin::DSL
25
25
  include Operators::Source::DSL
26
26
  include Operators::Static::DSL
27
+ include Operators::Using::DSL
27
28
  include Operators::Switch::DSL
28
29
  include Operators::Typed::DSL
29
30
  include Operators::With::DSL
@@ -33,7 +33,7 @@ module Babl
33
33
  end
34
34
 
35
35
  def self.eval(dsl, &block)
36
- new(dsl, block.binding.receiver).instance_eval(&block)
36
+ new(dsl, block.binding.receiver).instance_exec(&block)
37
37
  end
38
38
 
39
39
  def initialize(receiver, fallback)
@@ -5,35 +5,44 @@ module Babl
5
5
  module Utils
6
6
  # Construct deeply immutable value objects
7
7
  # Similar to Struct, but:
8
- # - Properties are assumed deeply immutable (#hash is assumed constant)
8
+ # - Properties are assumed deeply immutable (#hash is assumed constant & store permanently)
9
9
  # - Constructor requires all arguments
10
10
  # - #== has the same meaning as #eql?
11
- class Value
11
+ # - The object is frozen
12
+ #
13
+ # Goals :
14
+ # - Create completely immutable value objects
15
+ # - Fast comparison between instances (using precomputed hash values)
16
+ # - Low overhead (relies on native Ruby Struct)
17
+ class Value < Struct
12
18
  def self.new(*fields)
13
- ::Class.new(::Struct.new(:_cached_hash, *fields)) do
14
- field_aliases = ::Array.new(fields.size) { |i| "v#{i}" }
15
- const_set(:FIELDS, fields.map(&:to_sym))
16
- class_eval <<-RUBY
17
- def initialize(#{field_aliases.join(',')})
18
- super(#{['nil', field_aliases].join(',')})
19
- hash
20
- freeze
21
- end
19
+ fields = fields.map(&:to_sym)
20
+ field_aliases = ::Array.new(fields.size) { |i| "v#{i}" }
22
21
 
23
- def hash
24
- self._cached_hash ||= super
25
- end
22
+ clazz = super(:_cached_hash, *fields)
23
+ clazz.const_set(:FIELDS, fields)
24
+ clazz.class_eval <<-RUBY
25
+ def initialize(#{field_aliases.join(',')})
26
+ super(#{['nil', field_aliases].join(',')})
27
+ hash
28
+ freeze
29
+ end
30
+ RUBY
26
31
 
27
- def ==(other)
28
- eql?(other)
29
- end
32
+ clazz
33
+ end
34
+
35
+ def hash
36
+ self._cached_hash ||= super
37
+ end
38
+
39
+ def ==(other)
40
+ eql?(other)
41
+ end
30
42
 
31
- def self.with(hash = Utils::Hash::EMPTY)
32
- raise ::ArgumentError unless ::Hash === hash && (hash.keys - FIELDS).empty?
33
- new(*FIELDS.map { |f| hash.fetch(f) })
34
- end
35
- RUBY
36
- end
43
+ def self.with(hash = Utils::Hash::EMPTY)
44
+ raise ::ArgumentError unless ::Hash === hash && (hash.keys - self::FIELDS).empty?
45
+ new(*self::FIELDS.map { |f| hash.fetch(f) })
37
46
  end
38
47
  end
39
48
  end
@@ -1,4 +1,4 @@
1
1
  # frozen_string_literal: true
2
2
  module Babl
3
- VERSION = '0.4.0'
3
+ VERSION = '0.5.0'
4
4
  end
metadata CHANGED
@@ -1,113 +1,113 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: babl-json
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.0
4
+ version: 0.5.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Frederic Terrazzoni
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2017-10-03 00:00:00.000000000 Z
11
+ date: 2017-10-27 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
- name: pry
14
+ name: coveralls
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
17
  - - "~>"
18
18
  - !ruby/object:Gem::Version
19
- version: '0'
19
+ version: '0.8'
20
20
  type: :development
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
24
  - - "~>"
25
25
  - !ruby/object:Gem::Version
26
- version: '0'
26
+ version: '0.8'
27
27
  - !ruby/object:Gem::Dependency
28
- name: rspec
28
+ name: jbuilder
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
31
  - - "~>"
32
32
  - !ruby/object:Gem::Version
33
- version: '3'
33
+ version: '2'
34
34
  type: :development
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
38
  - - "~>"
39
39
  - !ruby/object:Gem::Version
40
- version: '3'
40
+ version: '2'
41
41
  - !ruby/object:Gem::Dependency
42
- name: rubocop
42
+ name: json-schema
43
43
  requirement: !ruby/object:Gem::Requirement
44
44
  requirements:
45
45
  - - "~>"
46
46
  - !ruby/object:Gem::Version
47
- version: '0.50'
47
+ version: '2.8'
48
48
  type: :development
49
49
  prerelease: false
50
50
  version_requirements: !ruby/object:Gem::Requirement
51
51
  requirements:
52
52
  - - "~>"
53
53
  - !ruby/object:Gem::Version
54
- version: '0.50'
54
+ version: '2.8'
55
55
  - !ruby/object:Gem::Dependency
56
- name: json-schema
56
+ name: pry
57
57
  requirement: !ruby/object:Gem::Requirement
58
58
  requirements:
59
59
  - - "~>"
60
60
  - !ruby/object:Gem::Version
61
- version: '2.8'
61
+ version: '0'
62
62
  type: :development
63
63
  prerelease: false
64
64
  version_requirements: !ruby/object:Gem::Requirement
65
65
  requirements:
66
66
  - - "~>"
67
67
  - !ruby/object:Gem::Version
68
- version: '2.8'
68
+ version: '0'
69
69
  - !ruby/object:Gem::Dependency
70
- name: coveralls
70
+ name: rabl
71
71
  requirement: !ruby/object:Gem::Requirement
72
72
  requirements:
73
73
  - - "~>"
74
74
  - !ruby/object:Gem::Version
75
- version: '0.8'
75
+ version: '0.13'
76
76
  type: :development
77
77
  prerelease: false
78
78
  version_requirements: !ruby/object:Gem::Requirement
79
79
  requirements:
80
80
  - - "~>"
81
81
  - !ruby/object:Gem::Version
82
- version: '0.8'
82
+ version: '0.13'
83
83
  - !ruby/object:Gem::Dependency
84
- name: jbuilder
84
+ name: rspec
85
85
  requirement: !ruby/object:Gem::Requirement
86
86
  requirements:
87
87
  - - "~>"
88
88
  - !ruby/object:Gem::Version
89
- version: '2'
89
+ version: '3'
90
90
  type: :development
91
91
  prerelease: false
92
92
  version_requirements: !ruby/object:Gem::Requirement
93
93
  requirements:
94
94
  - - "~>"
95
95
  - !ruby/object:Gem::Version
96
- version: '2'
96
+ version: '3'
97
97
  - !ruby/object:Gem::Dependency
98
- name: rabl
98
+ name: rubocop
99
99
  requirement: !ruby/object:Gem::Requirement
100
100
  requirements:
101
- - - "~>"
101
+ - - '='
102
102
  - !ruby/object:Gem::Version
103
- version: '0.13'
103
+ version: '0.51'
104
104
  type: :development
105
105
  prerelease: false
106
106
  version_requirements: !ruby/object:Gem::Requirement
107
107
  requirements:
108
- - - "~>"
108
+ - - '='
109
109
  - !ruby/object:Gem::Version
110
- version: '0.13'
110
+ version: '0.51'
111
111
  - !ruby/object:Gem::Dependency
112
112
  name: multi_json
113
113
  requirement: !ruby/object:Gem::Requirement
@@ -175,6 +175,7 @@ files:
175
175
  - lib/babl/operators/static.rb
176
176
  - lib/babl/operators/switch.rb
177
177
  - lib/babl/operators/typed.rb
178
+ - lib/babl/operators/using.rb
178
179
  - lib/babl/operators/with.rb
179
180
  - lib/babl/railtie.rb
180
181
  - lib/babl/rendering.rb