Skip to content
Merged
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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 DownExpand Up@@ -148,6 +148,9 @@ RSpec/ExampleLength:
- hash
- heredoc

RSpec/IncludeExamples:
Enabled: false

RSpec/MultipleMemoizedHelpers:
Max: 20

Expand Down
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -7,6 +7,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

## [0.3.4] - 2025-08-12

### Changed
- `engine`: fixed issue with serialization of functions ([#26](https://github.com/gi/handlebars-ruby/pull/26))
- `engine`: include optional logging ([#24](https://github.com/gi/handlebars-ruby/pull/24))
- `ci`: test against macOS 14, Ubuntu 22.04, and ruby 3.1-3.4 ([#27](https://github.com/gi/handlebars-ruby/pull/27))

## [0.3.3] - 2022-02-17

### Changed
Expand Down
2 changes: 1 addition & 1 deletion handlebars-engine.gemspec
Original file line numberDiff line numberDiff 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 numberDiff line numberDiff 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 DownExpand 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 DownExpand 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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
2 changes: 1 addition & 1 deletion lib/handlebars/engine/version.rb
Original file line numberDiff line numberDiff line change
Expand Up@@ -2,6 +2,6 @@

module Handlebars
class Engine
VERSION = "0.3.3"
VERSION = "0.3.4"
end
end
36 changes: 33 additions & 3 deletions spec/handlebars/engine_spec.rb
Original file line numberDiff line numberDiff 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 DownExpand 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 DownExpand 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