babl-json 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (43) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +3 -0
  3. data/.rubocop.yml +228 -0
  4. data/.ruby-version +1 -0
  5. data/.travis.yml +4 -0
  6. data/CHANGELOG.md +5 -0
  7. data/Gemfile +5 -0
  8. data/LICENSE +7 -0
  9. data/README.md +87 -0
  10. data/babl.gemspec +23 -0
  11. data/lib/babl.rb +41 -0
  12. data/lib/babl/builder/chain_builder.rb +85 -0
  13. data/lib/babl/builder/template_base.rb +37 -0
  14. data/lib/babl/operators/array.rb +42 -0
  15. data/lib/babl/operators/call.rb +25 -0
  16. data/lib/babl/operators/dep.rb +48 -0
  17. data/lib/babl/operators/each.rb +45 -0
  18. data/lib/babl/operators/enter.rb +22 -0
  19. data/lib/babl/operators/merge.rb +49 -0
  20. data/lib/babl/operators/nav.rb +55 -0
  21. data/lib/babl/operators/nullable.rb +16 -0
  22. data/lib/babl/operators/object.rb +55 -0
  23. data/lib/babl/operators/parent.rb +90 -0
  24. data/lib/babl/operators/partial.rb +46 -0
  25. data/lib/babl/operators/pin.rb +78 -0
  26. data/lib/babl/operators/source.rb +12 -0
  27. data/lib/babl/operators/static.rb +40 -0
  28. data/lib/babl/operators/switch.rb +71 -0
  29. data/lib/babl/operators/with.rb +51 -0
  30. data/lib/babl/railtie.rb +29 -0
  31. data/lib/babl/rendering/compiled_template.rb +28 -0
  32. data/lib/babl/rendering/context.rb +60 -0
  33. data/lib/babl/rendering/internal_value_node.rb +30 -0
  34. data/lib/babl/rendering/noop_preloader.rb +10 -0
  35. data/lib/babl/rendering/terminal_value_node.rb +54 -0
  36. data/lib/babl/template.rb +48 -0
  37. data/lib/babl/utils/hash.rb +11 -0
  38. data/lib/babl/version.rb +3 -0
  39. data/spec/construction_spec.rb +246 -0
  40. data/spec/navigation_spec.rb +133 -0
  41. data/spec/partial_spec.rb +53 -0
  42. data/spec/pinning_spec.rb +137 -0
  43. metadata +145 -0
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 656d75b72add7bd391267cf72e6e45959a5aaf7d
4
+ data.tar.gz: 34c2a76f614a357da5ac05a18c37f7bd585c8e5c
5
+ SHA512:
6
+ metadata.gz: 8c651ce6396f0c2ea87fe1d1be350074c3425ff6c2718940e0ff404a889e805c483fa88e4442d7dc5d9e1b4457af62bec0650d301f7aa1882d5034895e84c383
7
+ data.tar.gz: '08b57f384167c02bf9268bab025261394726d285051e8a583f76bf0f8a47264c12af1dcc7427ff8bd1e560fc081a11abe7bb9289cf8e4bcb9855adfb410695ed'
@@ -0,0 +1,3 @@
1
+ .DS_Store
2
+ Gemfile.lock
3
+ *.gem
@@ -0,0 +1,228 @@
1
+ AllCops:
2
+ Include:
3
+ - Gemfile
4
+ - '**/*.babl'
5
+ TargetRubyVersion: 2.3
6
+
7
+ # Begin -- Keep the following
8
+ CaseIndentation:
9
+ EnforcedStyle: end
10
+
11
+ Metrics/LineLength:
12
+ Max: 130
13
+
14
+ Metrics/PerceivedComplexity:
15
+ Max: 12
16
+
17
+ ParameterLists:
18
+ Max: 5
19
+ CountKeywordArgs: false
20
+
21
+ Rails:
22
+ Enabled: False
23
+
24
+ Style/AlignParameters:
25
+ EnforcedStyle: with_fixed_indentation
26
+
27
+ Style/AndOr:
28
+ EnforcedStyle: conditionals
29
+
30
+ Style/BlockDelimiters:
31
+ Enabled: False
32
+
33
+ Style/CollectionMethods:
34
+ Enabled: True
35
+
36
+ Style/Documentation:
37
+ Enabled: False
38
+
39
+ Style/ExtraSpacing:
40
+ Enabled: True
41
+
42
+ Style/IndentationWidth:
43
+ Width: 4
44
+
45
+ Style/IndentHash:
46
+ EnforcedStyle: consistent
47
+
48
+ Style/Lambda:
49
+ Enabled: False
50
+
51
+ Style/MultilineIfModifier:
52
+ Enabled: False
53
+
54
+ Style/MultilineOperationIndentation:
55
+ EnforcedStyle: indented
56
+
57
+ Style/NumericLiterals:
58
+ Enabled: False
59
+
60
+ Style/SignalException:
61
+ EnforcedStyle: only_raise
62
+ # End -- Keep the following
63
+
64
+ Lint/AmbiguousBlockAssociation:
65
+ Enabled: False
66
+
67
+ Lint/EmptyWhen:
68
+ Enabled: False
69
+
70
+ Lint/EndAlignment:
71
+ Enabled: False
72
+
73
+ Lint/InheritException:
74
+ Enabled: False
75
+
76
+ Lint/NestedMethodDefinition:
77
+ Enabled: False
78
+
79
+ Lint/SafeNavigationChain:
80
+ Enabled: True
81
+
82
+ Lint/UnneededSplatExpansion:
83
+ Enabled: False
84
+
85
+ Lint/UselessAccessModifier:
86
+ Enabled: False
87
+
88
+
89
+ Metrics/AbcSize:
90
+ Enabled: False
91
+ Max: 30
92
+
93
+ Metrics/BlockLength:
94
+ Enabled: False
95
+ Max: 25
96
+
97
+ Metrics/ClassLength:
98
+ Enabled: False
99
+ Max: 200
100
+
101
+ Metrics/CyclomaticComplexity:
102
+ Enabled: False
103
+ Max: 10
104
+
105
+ Metrics/MethodLength:
106
+ Enabled: False
107
+ Max: 20
108
+
109
+ Metrics/ModuleLength:
110
+ Enabled: False
111
+
112
+
113
+ Performance/Casecmp:
114
+ Enabled: False
115
+
116
+
117
+ Rails/Blank:
118
+ Enabled: False
119
+
120
+ Rails/Date:
121
+ Enabled: False
122
+
123
+ Rails/DynamicFindBy:
124
+ Enabled: False
125
+
126
+ Rails/FilePath:
127
+ Enabled: False
128
+
129
+ Rails/PluralizationGrammar:
130
+ Enabled: False
131
+
132
+ Rails/RelativeDateConstant:
133
+ Enabled: False
134
+
135
+ Rails/SkipsModelValidations:
136
+ Enabled: False
137
+
138
+ Rails/TimeZone:
139
+ Enabled: False
140
+
141
+
142
+ Security/YAMLLoad:
143
+ Enabled: False
144
+
145
+
146
+ Style/Alias:
147
+ Enabled: False
148
+
149
+ Style/ConditionalAssignment:
150
+ Enabled: False
151
+
152
+ Style/ClassVars:
153
+ Enabled: False
154
+
155
+ Style/EmptyCaseCondition:
156
+ Enabled: False
157
+
158
+ Style/EmptyLineAfterMagicComment:
159
+ Enabled: False
160
+
161
+ Style/EmptyMethod:
162
+ Enabled: False
163
+
164
+ Style/FileName:
165
+ Enabled: False
166
+
167
+ Style/FrozenStringLiteralComment:
168
+ Enabled: False
169
+
170
+ Style/GuardClause:
171
+ Enabled: False
172
+
173
+ Style/IndentArray:
174
+ Enabled: False
175
+
176
+ Style/IndentHeredoc:
177
+ Enabled: False
178
+
179
+ Style/MixinGrouping:
180
+ Enabled: False
181
+
182
+ Style/MultilineBlockChain:
183
+ Enabled: False
184
+
185
+ Style/MultilineMemoization:
186
+ Enabled: False
187
+
188
+ Style/MultilineMethodCallIndentation:
189
+ Enabled: False
190
+
191
+ Style/MutableConstant:
192
+ Enabled: False
193
+
194
+ Style/NestedParenthesizedCalls:
195
+ Enabled: False
196
+
197
+ Style/Next:
198
+ Enabled: False
199
+
200
+ Style/NumericPredicate:
201
+ Enabled: False
202
+
203
+ Style/ParallelAssignment:
204
+ Enabled: False
205
+
206
+ Style/RedundantParentheses:
207
+ Enabled: False
208
+
209
+ Style/RedundantSelf:
210
+ Enabled: False
211
+
212
+ Style/SafeNavigation:
213
+ Enabled: False
214
+
215
+ Style/SingleLineBlockParams:
216
+ Enabled: False
217
+
218
+ Style/CaseEquality:
219
+ Enabled: False
220
+
221
+ Style/StringLiterals:
222
+ Enabled: False
223
+
224
+ Style/SymbolArray:
225
+ Enabled: False
226
+
227
+ Style/VariableNumber:
228
+ Enabled: False
@@ -0,0 +1 @@
1
+ 2.3.1
@@ -0,0 +1,4 @@
1
+ language: ruby
2
+ install:
3
+ - bundle install
4
+ script: bundle exec rspec spec
@@ -0,0 +1,5 @@
1
+ # BABL Changelog
2
+
3
+ ## 0.1 (May 16, 2017)
4
+
5
+ - First released version
data/Gemfile ADDED
@@ -0,0 +1,5 @@
1
+ ruby File.open('./.ruby-version').readline.split("\n").first
2
+
3
+ source 'https://rubygems.org' do
4
+ gemspec
5
+ end
data/LICENSE ADDED
@@ -0,0 +1,7 @@
1
+ Copyright 2017 Frederic Terrazzoni
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
4
+
5
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
6
+
7
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,87 @@
1
+ # BABL #
2
+
3
+ [![Build Status](https://travis-ci.org/getbannerman/babl.svg?branch=master)](https://travis-ci.org/getbannerman/babl)
4
+
5
+ BABL (Bannerman API Builder Language) is a templating langage for generating JSON in APIs.
6
+
7
+ It plays a role similar to [RABL](https://github.com/nesquena/rabl), [JBuilder](https://github.com/rails/jbuilder), [Grape Entity](https://github.com/ruby-grape/grape-entity), and many others. However, unlike existing tools, BABL has several advantages :
8
+
9
+
10
+ ### Static compilation
11
+
12
+ BABL is a simple Ruby DSL. Unlike RABL, the template code is fully parsed and executed before any data is available. The approach makes it possible to detect errors earlier and document the output schema automatically, without data.
13
+
14
+ ### Automatic preloading
15
+
16
+ BABL is also able to infer the "input schema". It can loosely be seen as the list of properties we need to read from the models to construct the JSON output. These *dependencies* can be used to retrieve data more efficiently. For instance,all ActiveRecord associations can be preloaded automatically using this mechanism.
17
+
18
+ ### Simple syntax
19
+
20
+ JSON is simple, and generating JSON should be as simple as possible.
21
+
22
+ BABL template:
23
+
24
+ ```ruby
25
+ object(
26
+ document: object(
27
+ :id, :title
28
+
29
+ owner: _.nullable.object(:id, :name),
30
+ authors: _.each.object(:id, :name),
31
+ category: 'Not implemented'
32
+ )
33
+ )
34
+ ```
35
+
36
+ Output:
37
+
38
+ ```json
39
+ {
40
+ "document": {
41
+ "id": 1,
42
+ "title": "Hello BABL",
43
+ "owner": null,
44
+ "authors": [
45
+ { "id": 4, "name": "Fred" },
46
+ { "id": 5, "name": "Vivien" }
47
+ ],
48
+ "category": "not implemented"
49
+ }
50
+ }
51
+ ```
52
+
53
+ ## Documentation
54
+
55
+ TODO
56
+
57
+ ## Current limitations
58
+
59
+ ### Automatic preloading
60
+
61
+ This feature only works if BABL is configured to use an external preloader.
62
+
63
+ As of today, the only compatible preloader implementation *has not been released* yet. Hopefully, it will be available soon :-)
64
+
65
+ ### Automatic documentation
66
+
67
+ Support for automatic documentation is very limited, and the output is indecently ugly. It is more a proof-of-concept that a useful feature.
68
+
69
+ The mid-term goal is to generate a [JSON schema](http://json-schema.org/) documentation.
70
+
71
+ ### Rails integration
72
+
73
+ This gem implements support of `*.babl` views in [Rails](https://github.com/rails/rails/).
74
+
75
+ In theory, the template could be compliled once for all and re-used for subsequent requests. In practice, today's implementation will re-compile the template at every request, because Rails templating mechanism doesn't make our life easy.
76
+
77
+ If it turns out to be a performance bottleneck, we will try to work around this issue.
78
+
79
+ ### Recursion
80
+
81
+ BABL does not support recursive templates. The first reason is that it makes dependency tracking more complicated (especially on preloader side). The other reason is that it is not as useful as it might seem.
82
+
83
+ ## License
84
+
85
+ Copyright (c) 2017 [Bannerman](https://www.bannerman.com/), [Frederic Terrazzoni](https://github.com/fterrazzoni)
86
+
87
+ Licensed under the [MIT license](https://opensource.org/licenses/MIT).
@@ -0,0 +1,23 @@
1
+ require File.join(File.dirname(__FILE__), 'lib/babl/version')
2
+
3
+ Gem::Specification.new do |gem|
4
+ gem.name = "babl-json"
5
+ gem.version = ::Babl::VERSION
6
+ gem.licenses = ['MIT']
7
+ gem.authors = ['Frederic Terrazzoni']
8
+ gem.email = ['frederic.terrazzoni@gmail.com']
9
+ gem.description = "JSON templating on steroids"
10
+ gem.summary = gem.description
11
+ gem.homepage = 'https://github.com/getbannerman/babl'
12
+
13
+ gem.files = `git ls-files`.split($INPUT_RECORD_SEPARATOR)
14
+ gem.executables = gem.files.grep(%r{^bin/}).map { |f| File.basename(f) }
15
+ gem.test_files = gem.files.grep(%r{^spec/})
16
+ gem.require_paths = ['lib']
17
+
18
+ gem.add_development_dependency 'pry', '~> 0'
19
+ gem.add_development_dependency 'rspec', '~> 3'
20
+ gem.add_development_dependency 'rubocop', '~> 0.48'
21
+
22
+ gem.add_dependency 'oj', '~> 3.0'
23
+ end
@@ -0,0 +1,41 @@
1
+ require 'babl/railtie' if defined?(Rails)
2
+ require 'babl/template'
3
+ require 'babl/version'
4
+
5
+ module Babl
6
+ class BablError < StandardError; end
7
+ class InvalidTemplateError < BablError; end
8
+ class RenderingError < BablError; end
9
+
10
+ class Config
11
+ attr_accessor :search_path, :preloader, :pretty
12
+
13
+ def initialize
14
+ @search_path = nil
15
+ @preloader = Babl::Rendering::NoopPreloader
16
+ @pretty = true
17
+ end
18
+ end
19
+
20
+ class << self
21
+ def compile(template: ::Babl::Template.new, &source)
22
+ if config.search_path
23
+ ctx = ::Babl::Operators::Partial::AbsoluteLookupContext.new(config.search_path)
24
+ template = template.with_lookup_context(ctx)
25
+ end
26
+
27
+ template.source(&source).compile(
28
+ pretty: config.pretty,
29
+ preloader: config.preloader
30
+ )
31
+ end
32
+
33
+ def configure
34
+ yield(config)
35
+ end
36
+
37
+ def config
38
+ @config ||= Config.new
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,85 @@
1
+ module Babl
2
+ module Builder
3
+ # Builder provides a simple framework for defining & chaining BABL's operators easily.
4
+ #
5
+ # Compiling a template is a multi-phase process:
6
+ #
7
+ # 1- [BABL => ChainBuilder] Template definition (via Builder#construct_node & Builder#construct_terminal) :
8
+ # The operator chain is created by wrapping blocks (current block stored in 'scope')
9
+ #
10
+ # 2- [Builder => BoundOperator] Template binding (via Builder#bind) :
11
+ # A BoundOperator is created for each operator and passed to the next, in left-to-right
12
+ # order. This step is necessary to propagate context from root to leaves. A typical
13
+ # use-case is the 'enter' operator, which requires the parent context in which it is called.
14
+ #
15
+ # 3- [BoundOperator => Node] Node precompilation (via Builder#precompile):
16
+ # BoundOperators are transformed into a Node tree, in right-to-left order. Each node
17
+ # contains its own rendering logic, dependency tracking & documentation generator.
18
+ #
19
+ # 4- [Node => CompiledTemplate] Compilation output: (via Template#compile):
20
+ # The resulting Node is used to compute the dependencies & generate the documentation.
21
+ # Finally, we pack everything is a CompiledTemplate which is exposed to the user.
22
+ #
23
+ class ChainBuilder
24
+ def initialize(&block)
25
+ @scope = block
26
+ end
27
+
28
+ def precompile(node, **context)
29
+ bind(BoundOperator.new(context)).precompile(node)
30
+ end
31
+
32
+ def bind(bound)
33
+ @scope[bound]
34
+ end
35
+
36
+ # Append a terminal operator, and return a new Builder object
37
+ def construct_terminal
38
+ construct_node do |node, context|
39
+ unless [Rendering::InternalValueNode.instance, Rendering::TerminalValueNode.instance].include?(node)
40
+ raise ::Babl::InvalidTemplateError, 'Chaining is not allowed after a terminal operator'
41
+ end
42
+ yield context
43
+ end
44
+ end
45
+
46
+ # Append an operator to the chain, and return a new Builder object
47
+ def construct_node(**new_context)
48
+ wrap { |bound|
49
+ bound.nest(bound.context.merge(new_context)) { |node|
50
+ yield(node, bound.context)
51
+ }
52
+ }
53
+ end
54
+
55
+ def wrap
56
+ rescope { |bound| yield bind(bound) }
57
+ end
58
+
59
+ def rescope(&block)
60
+ dup.tap { |tb| tb.instance_variable_set(:@scope, block) }
61
+ end
62
+ end
63
+
64
+ class BoundOperator
65
+ attr_reader :context
66
+
67
+ def initialize(context, &scope)
68
+ @context = context
69
+ @scope = scope || :itself.to_proc
70
+ end
71
+
72
+ def precompile(node)
73
+ scope[node]
74
+ end
75
+
76
+ def nest(context)
77
+ self.class.new(context) { |node| scope[yield node] }
78
+ end
79
+
80
+ private
81
+
82
+ attr_reader :scope
83
+ end
84
+ end
85
+ end