handlebars-engine 0.1.0 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
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: []