Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 5 additions & 3 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -60,13 +60,15 @@ jobs:
strategy:
matrix:
os:
- macos-14
- macos-latest
- ubuntu-22.04
- ubuntu-latest
ruby:
- ruby-2.6
- ruby-2.7
- ruby-3.0
- ruby-3.1
- ruby-3.2
- ruby-3.3
- ruby-3.4
# - jruby
# - truffleruby
runs-on: ${{ matrix.os }}
Expand Down
7 changes: 5 additions & 2 deletions .rubocop.yml
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
require:
plugins:
- rubocop-performance
- rubocop-rake
- rubocop-rspec

AllCops:
TargetRubyVersion: 2.6
TargetRubyVersion: 3.0
Exclude:
- bin/**/*
- vendor/**/*
Expand Down Expand Up @@ -148,6 +148,9 @@ RSpec/ExampleLength:
- hash
- heredoc

RSpec/IncludeExamples:
Enabled: false

RSpec/MultipleMemoizedHelpers:
Max: 20

Expand Down
2 changes: 1 addition & 1 deletion handlebars-engine.gemspec
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ Gem::Specification.new do |spec|

spec.homepage = "https://github.com/gi/handlebars-ruby"
spec.license = "MIT"
spec.required_ruby_version = ">= 2.6.0"
spec.required_ruby_version = ">= 3.0"

spec.metadata["changelog_uri"] = "#{spec.homepage}/CHANGELOG.md"
spec.metadata["github_repo"] = spec.homepage
Expand Down
19 changes: 16 additions & 3 deletions lib/handlebars/engine.rb
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,8 @@ class Engine
# environment.
# @param path [String, nil] the path to the version of Handlebars to load.
# If `nil`, the contents of `Handlebars::Source.bundled_path` is loaded.
def initialize(lazy: false, path: nil)
def initialize(lazy: false, logger: nil, path: nil)
@logger = logger
@path = path
init! unless lazy
end
Expand Down Expand Up @@ -84,9 +85,9 @@ def register_helper(name = nil, function = nil, **helpers, &block)
case f
when Proc
attach(n, &f)
evaluate("registerHelper('#{n}', #{n})")
evaluate("registerRbHelper('#{n}', #{n})")
when String, Symbol
evaluate("Handlebars.registerHelper('#{n}', #{f})")
evaluate("registerJsHelper('#{n}', #{f})")
end
end
end
Expand Down Expand Up @@ -175,13 +176,16 @@ def version

def attach(name, &block)
init!
@logger&.debug { "[handlebars] attaching #{name}" }
@context.attach(name.to_s, block)
end

def call(name, args, assign: false, eval: false)
init!
name = name.to_s

@logger&.debug { "[handlebars] calling #{name} with args #{args}" }

if assign || eval
call_via_eval(name, args, assign: assign)
else
Expand All @@ -207,6 +211,7 @@ def call_via_eval(name, args, assign: false)
end

def evaluate(code)
@logger&.debug { "[handlebars] evaluating #{code}" }
@context.eval(code)
end

Expand All @@ -222,10 +227,18 @@ def helper_missing_name(type)
def init!
return if @init

@logger&.debug { "[handlebars] initializing" }

@context = MiniRacer::Context.new
@context.attach(
"console.log",
->(*args) { @logger&.debug { "[handlebars] #{args.join(" ")}" } },
)
@context.load(@path || ::Handlebars::Source.bundled_path)
@context.load(File.absolute_path("engine/init.js", __dir__))

@logger&.debug { "[handlebars] initialized" }

@init = true
end

Expand Down
4 changes: 3 additions & 1 deletion lib/handlebars/engine/function.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,14 @@ module Handlebars
class Engine
# A proxy for a JavaScript function defined in the context.
class Function
def initialize(context, name)
def initialize(context, name, logger: nil)
@context = context
@logger = logger
@name = name
end

def call(*args)
@logger&.debug { "[handlebars] calling #{@name} with args #{args}" }
@context.call(@name, *args)
end
end
Expand Down
17 changes: 13 additions & 4 deletions lib/handlebars/engine/init.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,23 @@ var template = (spec) => {
var registerPartial = Handlebars.registerPartial.bind(Handlebars);
var unregisterPartial = Handlebars.unregisterPartial.bind(Handlebars);

var registerHelper = (...args) => {
const fn = args[args.length - 1];
var registerJsHelper = Handlebars.registerHelper.bind(Handlebars);

var registerRbHelper = (name, fn) => {
function wrapper(...args) {
// Ruby cannot access the `this` context, so pass it as the first argument.
args.unshift(this);
const { ...options } = args[args.length-1];
Object.entries(options).forEach(([key, value]) => {
if (typeof value === "function") {
// functions are cannot be passed back to Ruby
options[key] = "function";
}
});
args[args.length-1] = options
return fn(...args);
}
args[args.length - 1] = wrapper;
return Handlebars.registerHelper(...args);
return registerJsHelper(name, wrapper);
};

var unregisterHelper = Handlebars.unregisterHelper.bind(Handlebars);
Expand Down
36 changes: 33 additions & 3 deletions spec/handlebars/engine_spec.rb
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
# frozen_string_literal: true

require "logger"
require "tempfile"

RSpec.describe Handlebars::Engine do
let(:engine) { described_class.new(**engine_options) }
let(:engine_context) { engine.instance_variable_get(:@context) }
let(:engine_options) { {} }
let(:log) { Tempfile.new }
let(:logger) { Logger.new(log, level: Logger::FATAL) }
let(:render) { renderer.call(render_context, render_options) }
let(:render_context) { { name: "Zach", age: 30 } }
let(:render_options) { {} }
Expand Down Expand Up @@ -40,7 +43,26 @@
end

it "does not create the context" do
expect(engine_context).to be nil
expect(engine_context).to be_nil
end
end

context "when `logger` is defined" do
before do
engine_options[:logger] = logger
logger.debug!
end

it "logs initialization" do
engine
log.rewind
expect(log.read).to include("[handlebars] initializing")
end

it "logs javascript" do
engine.send(:evaluate, "console.log('js', 'log')")
log.rewind
expect(log.read).to include("[handlebars] js log")
end
end

Expand Down Expand Up @@ -255,7 +277,7 @@
describe "the options" do
it "includes the main block function" do
opts = include(
"fn" => kind_of(MiniRacer::JavaScriptFunction),
"fn" => "function", # kind_of(MiniRacer::JavaScriptFunction),
)
args = [anything, any_args, opts]
render
Expand All @@ -264,7 +286,7 @@

it "includes the else block function" do
opts = include(
"inverse" => kind_of(MiniRacer::JavaScriptFunction),
"inverse" => "function", # kind_of(MiniRacer::JavaScriptFunction),
)
args = [anything, any_args, opts]
render
Expand All @@ -279,6 +301,14 @@
<<~JS
function (...args) {
args.unshift(this);
const { ...options } = args[args.length-1];
Object.entries(options).forEach(([key, value]) => {
if (typeof value === "function") {
// functions are cannot be passed back to Ruby
options[key] = "function";
}
});
args[args.length-1] = options
return tester(...args);
}
JS
Expand Down
Loading