handlebars-engine 0.1.0 → 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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 5687452a43187e2eb02f0f14b8cdfbb9326f7a45324aff57036ca6f40f1e3f1b
4
- data.tar.gz: f6f11a588f3db269fd34beb13e5c74ed0c8d15e8533e6dca1f99d6fa6737ae8f
3
+ metadata.gz: 0b6bc490b95cc268a0cc7ee638660caecbfe9d978a13335b8c3588f89e16c3db
4
+ data.tar.gz: 014eb30c05c4edba8b9f9a340e3539a8542edabd94954efa189b2611456e10d8
5
5
  SHA512:
6
- metadata.gz: 19f3131b68bb2b4062e30c730becb77efdf9d62b057863d2b2e2c97369ddab4216c1d08eb370070b1f9e5950ff75015414a72c6b04d8c0df277954ee2beedc9a
7
- data.tar.gz: b28fbad17ce848de5b10a6a023a6ee227c0c2175d996587338f726f320b9e9f75995b3927662005c35d3460b8c5fc2d9f27f40c962e6b4fd628f8355035b457d
6
+ metadata.gz: a092e8fdae258e997d6afb3505b20a67898caf7d08bfd37efee1a50f663b0e084648902b6d396fe392acb46a8e5587deee277e2ec750f31ce103ad8dd42d1d6b
7
+ data.tar.gz: 99c271de356730e76c660fb806d8dd619edb0f522951a5e822f6460e0e415c02e20311aab2e5be55e89f9b2ffd161e87b75784726512df26d5c59d002ec06f9a
data/CHANGELOG.md CHANGED
@@ -6,3 +6,28 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
8
  ## [Unreleased]
9
+
10
+ ## [0.2.0] - 2022-01-27
11
+
12
+ This is the initial implementation, wrapping the JavaScript Handlebars.
13
+
14
+ ### Added
15
+ - `Handlebars::Engine#compile`
16
+ - `Handlebars::Engine#precompile`
17
+ - `Handlebars::Engine#template`
18
+ - `Handlebars::Engine#register_helper`
19
+ - `Handlebars::Engine#unregister_helper`
20
+ - `Handlebars::Engine#register_partial`
21
+ - `Handlebars::Engine#unregister_partial`
22
+ - `Handlebars::Engine#register_helper_missing`
23
+ - `Handlebars::Engine#unregister_helper_missing`
24
+ - `Handlebars::Engine#register_partial_missing`
25
+ - `Handlebars::Engine#unregister_partial_missing`
26
+ - `Handlebars::Engine#version`
27
+
28
+ ## [0.1.0] - 2022-01-13
29
+
30
+ This is the initial package.
31
+
32
+ ### Added
33
+ - gem init
data/README.md CHANGED
@@ -26,7 +26,144 @@ Or install it yourself as:
26
26
 
27
27
  ## Usage
28
28
 
29
- TODO: Write usage instructions here
29
+ ### Quick Start
30
+
31
+ ```ruby
32
+ handlebars = Handlebars::Engine.new
33
+ template = handlebars.compile("{{firstname}} {{lastname}}")
34
+ template.call({ firstname: "Yehuda", lastname: "Katz" })
35
+ # => "Yehuda Katz"
36
+ ```
37
+
38
+ ### Custom Helpers
39
+
40
+ Handlebars helpers can be accessed from any context in a template. You can
41
+ register a helper with the `register_helper` method:
42
+
43
+ ```ruby
44
+ handlebars = Handlebars::Engine.new
45
+ handlebars.register_helper(:loud) do |ctx, arg, opts|
46
+ arg.upcase
47
+ end
48
+ template = handlebars.compile("{{firstname}} {{loud lastname}}")
49
+ template.call({ firstname: "Yehuda", lastname: "Katz" })
50
+ # => "Yehuda KATZ"
51
+ ```
52
+
53
+ #### Helper Arguments
54
+
55
+ Helpers receive the current context as the first argument of the block.
56
+
57
+ ```ruby
58
+ handlebars = Handlebars::Engine.new
59
+ handlebars.register_helper(:full_name) do |ctx, opts|
60
+ "#{ctx["firstname"]} #{ctx["lastname"]}"
61
+ end
62
+ template = handlebars.compile("{{full_name}}")
63
+ template.call({ firstname: "Yehuda", lastname: "Katz" })
64
+ # => "Yehuda Katz"
65
+ ```
66
+
67
+ Any arguments to the helper are included as individual positional arguments.
68
+
69
+ ```ruby
70
+ handlebars = Handlebars::Engine.new
71
+ handlebars.register_helper(:join) do |ctx, *args, opts|
72
+ args.join(" ")
73
+ end
74
+ template = handlebars.compile("{{join firstname lastname}}")
75
+ template.call({ firstname: "Yehuda", lastname: "Katz" })
76
+ # => "Yehuda Katz"
77
+ ```
78
+
79
+ The last argument is a hash of options.
80
+
81
+ See https://handlebarsjs.com/guide/#custom-helpers.
82
+
83
+ ### Block Helpers
84
+
85
+ See https://handlebarsjs.com/guide/#block-helpers.
86
+
87
+ ### Partials
88
+
89
+ Handlebars partials allow for code reuse by creating shared templates.
90
+
91
+ You can register a partial using the `register_partial` method:
92
+
93
+ ```ruby
94
+ handlebars = Handlebars::Engine.new
95
+ handlebars.register_partial(:person, "{{person.name}} is {{person.age}}.")
96
+ template = handlebars.compile("{{> person person=.}}")
97
+ template.call({ name: "Yehuda Katz", age: 20 })
98
+ # => "Yehuda Katz is 20."
99
+ ```
100
+
101
+ See https://handlebarsjs.com/guide/#partials.
102
+ See https://handlebarsjs.com/guide/partials.html.
103
+
104
+ ### Hooks
105
+
106
+ #### Helper Missing
107
+
108
+ This hook is called for a mustache or a block-statement when
109
+ * a simple mustache-expression is not a registered helper, *and*
110
+ * it is not a property of the current evaluation context.
111
+
112
+ You can add custom handling for those situations by registering a helper with
113
+ the `register_helper_missing` method:
114
+
115
+ ```ruby
116
+ handlebars = Handlebars::Engine.new
117
+ handlebars.register_helper_missing do |ctx, *args, opts|
118
+ "Missing: #{opts["name"]}(#{args.join(", ")})"
119
+ end
120
+
121
+ template = handlebars.compile("{{foo 2 true}}")
122
+ template.call
123
+ # => "Missing: foo(2, true)"
124
+
125
+ template = handlebars.compile("{{#foo true}}{{/foo}}")
126
+ template.call
127
+ # => "Missing: foo(true)"
128
+ ```
129
+
130
+ See https://handlebarsjs.com/guide/hooks.html#helpermissing.
131
+
132
+ ##### Blocks
133
+
134
+ This hook is called for a block-statement when
135
+ * a block-expression calls a helper that is not registered, *and*
136
+ * the name is a property of the current evaluation context.
137
+
138
+ You can add custom handling for those situations by registering a helper with
139
+ the `register_helper_missing` method (with a `:block` argument):
140
+
141
+ ```ruby
142
+ handlebars = Handlebars::Engine.new
143
+ handlebars.register_helper_missing(:block) do |ctx, *args, opts|
144
+ "Missing: #{opts["name"]}(#{args.join(", ")})"
145
+ end
146
+
147
+ template = handlebars.compile("{{#person}}{{name}}{{/person}}")
148
+ template.call({ person: { name: "Yehuda Katz" } })
149
+ # => "Missing: person"
150
+ ```
151
+
152
+ See https://handlebarsjs.com/guide/hooks.html#blockhelpermissing.
153
+
154
+ #### Partial Missing
155
+
156
+ This hook is called for a partial that is not registered.
157
+
158
+ ```ruby
159
+ handlebars = Handlebars::Engine.new
160
+ handlebars.register_partial_missing do |name|
161
+ "partial: #{name}"
162
+ end
163
+ ```
164
+
165
+ Note: This is not a part of the offical Handlebars API. It is provided for
166
+ convenience.
30
167
 
31
168
  ## Changelog
32
169
 
@@ -0,0 +1,43 @@
1
+ var {
2
+ compile,
3
+ precompile,
4
+ registerPartial,
5
+ unregisterPartial,
6
+ registerHelper,
7
+ unregisterHelper,
8
+ VERSION,
9
+ } = Handlebars;
10
+
11
+ var template = (spec) => {
12
+ eval(`spec = ${spec}`);
13
+ return Handlebars.template(spec);
14
+ };
15
+
16
+ var registerPartial = Handlebars.registerPartial.bind(Handlebars);
17
+ var unregisterPartial = Handlebars.unregisterPartial.bind(Handlebars);
18
+
19
+ var registerHelper = (...args) => {
20
+ const fn = args[args.length - 1];
21
+ function wrapper(...args) {
22
+ args.unshift(this);
23
+ return fn(...args);
24
+ }
25
+ args[args.length - 1] = wrapper;
26
+ return Handlebars.registerHelper(...args);
27
+ };
28
+
29
+ var unregisterHelper = Handlebars.unregisterHelper.bind(Handlebars);
30
+
31
+ var partialMissing;
32
+
33
+ const partialsHandler = {
34
+ get(partials, name) {
35
+ const partial = partials[name] ?? partialMissing?.(name);
36
+ if (partial) {
37
+ partials[name] = partial;
38
+ }
39
+ return partial;
40
+ },
41
+ };
42
+
43
+ Handlebars.partials = new Proxy(Handlebars.partials, partialsHandler);
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Handlebars
4
- module Engine
5
- VERSION = "0.1.0"
4
+ class Engine
5
+ VERSION = "0.2.0"
6
6
  end
7
7
  end
@@ -1,9 +1,224 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "handlebars/source"
4
+ require "json"
5
+ require "mini_racer"
6
+ require "securerandom"
3
7
  require_relative "engine/version"
4
8
 
5
9
  module Handlebars
6
10
  # The Handlebars engine.
7
- module Engine
11
+ #
12
+ # This API follows the JavaScript API as closely as possible:
13
+ # https://handlebarsjs.com/api-reference/.
14
+ class Engine
15
+ # Creates a new instance.
16
+ #
17
+ # @param lazy [true, false] immediately loads and initializes the JavaScript
18
+ # environment.
19
+ def initialize(lazy: false)
20
+ init! unless lazy
21
+ end
22
+
23
+ ###################################
24
+ # Compilation
25
+ ###################################
26
+
27
+ # Compiles a template so it can be executed immediately.
28
+ #
29
+ # @param template [String] the template string to compile
30
+ # @param options [Hash] the options
31
+ # @return [Proc] the template function to call
32
+ # @see https://handlebarsjs.com/api-reference/compilation.html#handlebars-compile-template-options
33
+ def compile(*args)
34
+ call(__method__, args, assign: true)
35
+ end
36
+
37
+ # Precompiles a given template so it can be executed without compilation.
38
+ #
39
+ # @param template [String] the template string to precompiled
40
+ # @param options [Hash] the options
41
+ # @return [String] the precompiled template spec
42
+ # @see https://handlebarsjs.com/api-reference/compilation.html#handlebars-precompile-template-options
43
+ def precompile(*args)
44
+ call(__method__, args)
45
+ end
46
+
47
+ # Sets up a template that was precompiled with `precompile`.
48
+ #
49
+ # @param spec [String] the precompiled template spec
50
+ # @return [Proc] the template function to call
51
+ # @see #precompile
52
+ # @see https://handlebarsjs.com/api-reference/compilation.html#handlebars-template-templatespec
53
+ def template(*args)
54
+ call(__method__, args, assign: true)
55
+ end
56
+
57
+ ###################################
58
+ # Runtime
59
+ ###################################
60
+
61
+ # Registers helpers accessible by any template in the environment.
62
+ #
63
+ # @param name [String, Symbol] the name of the helper
64
+ # @yieldparam context [Hash] the current context
65
+ # @yieldparam arguments [Object] the arguments (optional)
66
+ # @yieldparam options [Hash] the options hash (optional)
67
+ # @see https://handlebarsjs.com/api-reference/runtime.html#handlebars-registerhelper-name-helper
68
+ def register_helper(name, &block)
69
+ attach(name, &block)
70
+ call(:registerHelper, [name.to_s, name.to_sym], eval: true)
71
+ end
72
+
73
+ # Unregisters a previously registered helper.
74
+ #
75
+ # @param name [String, Symbol] the name of the helper
76
+ # @see https://handlebarsjs.com/api-reference/runtime.html#handlebars-unregisterhelper-name
77
+ def unregister_helper(name)
78
+ call(:unregisterHelper, [name])
79
+ end
80
+
81
+ # Registers partials accessible by any template in the environment.
82
+ #
83
+ # @param name [String, Symbol] the name of the partial
84
+ # @param partial [String] the partial template
85
+ # @see https://handlebarsjs.com/api-reference/runtime.html#handlebars-registerpartial-name-partial
86
+ def register_partial(name = nil, partial = nil, **partials)
87
+ partials[name] = partial if name
88
+ call(:registerPartial, [partials])
89
+ end
90
+
91
+ # Unregisters a previously registered partial.
92
+ #
93
+ # @param name [String, Symbol] the name of the partial
94
+ # @see https://handlebarsjs.com/api-reference/runtime.html#handlebars-unregisterpartial-name
95
+ def unregister_partial(name)
96
+ call(:unregisterPartial, [name])
97
+ end
98
+
99
+ ###################################
100
+ # Hooks
101
+ ###################################
102
+
103
+ # Registers the hook called when a mustache or a block-statement is missing.
104
+ #
105
+ # @param type [Symbol] the type of hook to register (`:basic` or `:block`)
106
+ # @yieldparam arguments [Object] the arguments (optional)
107
+ # @yieldparam options [Hash] the options hash (optional)
108
+ # @see https://handlebarsjs.com/guide/hooks.html#helpermissing
109
+ def register_helper_missing(type = :basic, &block)
110
+ name = helper_missing_name(type)
111
+ register_helper(name, &block)
112
+ end
113
+
114
+ # Unregisters the previously registered hook.
115
+ #
116
+ # @param type [Symbol] the type of hook to register (`:basic` or `:block`)
117
+ # @see https://handlebarsjs.com/guide/hooks.html#helpermissing
118
+ def unregister_helper_missing(type = :basic)
119
+ name = helper_missing_name(type)
120
+ unregister_helper(name)
121
+ end
122
+
123
+ # Registers the hook called when a partial is missing.
124
+ #
125
+ # Note: This is not a part of the offical Handlebars API. It is provided for
126
+ # convenience.
127
+ #
128
+ # @yieldparam name [String] the name of the undefined partial
129
+ def register_partial_missing(&block)
130
+ attach(:partialMissing, &block)
131
+ end
132
+
133
+ # Unregisters the previously registered hook.
134
+ def unregister_partial_missing
135
+ evaluate("delete partialMissing")
136
+ end
137
+
138
+ ###################################
139
+ # Miscellaneous
140
+ ###################################
141
+
142
+ # Returns the version of Handlebars.
143
+ #
144
+ # @return [String] the Handlebars version.
145
+ def version
146
+ evaluate("VERSION")
147
+ end
148
+
149
+ ###################################
150
+ # Private
151
+ ###################################
152
+
153
+ private
154
+
155
+ def attach(name, &block)
156
+ init!
157
+ @context.attach(name.to_s, block)
158
+ end
159
+
160
+ def call(name, args, assign: false, eval: false)
161
+ init!
162
+ name = name.to_s
163
+
164
+ if assign || eval
165
+ call_via_eval(name, args, assign: assign)
166
+ else
167
+ @context.call(name, *args)
168
+ end
169
+ end
170
+
171
+ def call_via_eval(name, args, assign: false)
172
+ args = js_args(args)
173
+
174
+ var = assign ? "v#{SecureRandom.alphanumeric}" : nil
175
+
176
+ code = "#{name}(#{args.join(", ")})"
177
+ code = "#{var} = #{code}" if var
178
+
179
+ result = evaluate(code)
180
+
181
+ if var && result.is_a?(MiniRacer::JavaScriptFunction)
182
+ result = ->(*a) { @context.call(var, *a) }
183
+ finalizer = ->(*) { evaluate("delete #{var}") }
184
+ ObjectSpace.define_finalizer(result, finalizer)
185
+ end
186
+
187
+ result
188
+ end
189
+
190
+ def evaluate(code)
191
+ @context.eval(code)
192
+ end
193
+
194
+ def helper_missing_name(type)
195
+ case type
196
+ when :basic
197
+ :helperMissing
198
+ when :block
199
+ :blockHelperMissing
200
+ end
201
+ end
202
+
203
+ def init!
204
+ return if @init
205
+
206
+ @context = MiniRacer::Context.new
207
+ @context.load(::Handlebars::Source.bundled_path)
208
+ @context.load(File.absolute_path("engine/init.js", __dir__))
209
+
210
+ @init = true
211
+ end
212
+
213
+ def js_args(args)
214
+ args.map { |arg|
215
+ case arg
216
+ when Symbol
217
+ arg
218
+ else
219
+ JSON.generate(arg)
220
+ end
221
+ }
222
+ end
8
223
  end
9
224
  end
metadata CHANGED
@@ -1,18 +1,46 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: handlebars-engine
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Zach Gianos
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2022-01-13 00:00:00.000000000 Z
12
- dependencies: []
11
+ date: 2022-01-27 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: handlebars-source
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: mini_racer
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
13
41
  description: " A simple interface to Handlebars.js for Ruby.\n"
14
42
  email:
15
- - zach.gianos@gmail.com
43
+ - zach.gianos+git@gmail.com
16
44
  executables:
17
45
  - handlebars
18
46
  extensions: []
@@ -23,14 +51,15 @@ files:
23
51
  - README.md
24
52
  - exe/handlebars
25
53
  - lib/handlebars/engine.rb
54
+ - lib/handlebars/engine/init.js
26
55
  - lib/handlebars/engine/version.rb
27
56
  homepage: https://github.com/gi/handlebars-ruby
28
57
  licenses:
29
58
  - MIT
30
59
  metadata:
31
60
  changelog_uri: https://github.com/gi/handlebars-ruby/CHANGELOG.md
61
+ github_repo: https://github.com/gi/handlebars-ruby
32
62
  homepage_uri: https://github.com/gi/handlebars-ruby
33
- rubygems_mfa_required: 'true'
34
63
  source_code_uri: https://github.com/gi/handlebars-ruby
35
64
  post_install_message:
36
65
  rdoc_options: []