From 3a705c779387b44e1ea5faeb0c4ca7bba0ac53a4 Mon Sep 17 00:00:00 2001 From: Zach Gianos Date: Wed, 23 Jul 2025 11:07:20 -0700 Subject: [PATCH 1/6] ci: test against ruby 3.4 Fixes #21 --- .github/workflows/ci.yml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 36c376e..e673622 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -63,10 +63,11 @@ jobs: - macos-latest - 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 }} From e310cc6c8d578b06695f58a9d9066af0e14f2f55 Mon Sep 17 00:00:00 2001 From: Zach Gianos Date: Mon, 11 Aug 2025 21:13:52 -0700 Subject: [PATCH 2/6] ruby: remove support for ruby < 3.0 --- .rubocop.yml | 7 +++++-- handlebars-engine.gemspec | 2 +- spec/handlebars/engine_spec.rb | 2 +- 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/.rubocop.yml b/.rubocop.yml index c6aed2b..cceaa2d 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -1,10 +1,10 @@ -require: +plugins: - rubocop-performance - rubocop-rake - rubocop-rspec AllCops: - TargetRubyVersion: 2.6 + TargetRubyVersion: 3.0 Exclude: - bin/**/* - vendor/**/* @@ -148,6 +148,9 @@ RSpec/ExampleLength: - hash - heredoc +RSpec/IncludeExamples: + Enabled: false + RSpec/MultipleMemoizedHelpers: Max: 20 diff --git a/handlebars-engine.gemspec b/handlebars-engine.gemspec index a365baa..bff422a 100644 --- a/handlebars-engine.gemspec +++ b/handlebars-engine.gemspec @@ -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 diff --git a/spec/handlebars/engine_spec.rb b/spec/handlebars/engine_spec.rb index fe0a5f0..c91fb5d 100644 --- a/spec/handlebars/engine_spec.rb +++ b/spec/handlebars/engine_spec.rb @@ -40,7 +40,7 @@ end it "does not create the context" do - expect(engine_context).to be nil + expect(engine_context).to be_nil end end From cd1218be18d8918c0262c6a8f368502fc4516f44 Mon Sep 17 00:00:00 2001 From: Zach Gianos Date: Mon, 11 Aug 2025 21:09:05 -0700 Subject: [PATCH 3/6] [#25] engine: fix serialization of helper options functions to ruby This fixes an issue where `mini_racer` does not support serializing javascript functions nested in objects, such as in the options hash of helper function. To avoid this issue, redefine any functions with a generic `"function"` string. TODO: In the future, we can build a method to call these functions. --- lib/handlebars/engine.rb | 4 ++-- lib/handlebars/engine/init.js | 17 +++++++++++++---- spec/handlebars/engine_spec.rb | 12 ++++++++++-- 3 files changed, 25 insertions(+), 8 deletions(-) diff --git a/lib/handlebars/engine.rb b/lib/handlebars/engine.rb index 62aad78..209e291 100644 --- a/lib/handlebars/engine.rb +++ b/lib/handlebars/engine.rb @@ -84,9 +84,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 diff --git a/lib/handlebars/engine/init.js b/lib/handlebars/engine/init.js index 22d1d99..0572c69 100644 --- a/lib/handlebars/engine/init.js +++ b/lib/handlebars/engine/init.js @@ -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); diff --git a/spec/handlebars/engine_spec.rb b/spec/handlebars/engine_spec.rb index c91fb5d..c0c7370 100644 --- a/spec/handlebars/engine_spec.rb +++ b/spec/handlebars/engine_spec.rb @@ -255,7 +255,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 @@ -264,7 +264,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 @@ -279,6 +279,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 From 1f4c227b808029aa6a7cb3e2da6cdbc35e9abd63 Mon Sep 17 00:00:00 2001 From: Zach Gianos Date: Wed, 23 Jul 2025 11:03:04 -0700 Subject: [PATCH 4/6] [#20] engine: include optional logging Fixes #20 --- lib/handlebars/engine.rb | 15 ++++++++++++++- lib/handlebars/engine/function.rb | 4 +++- spec/handlebars/engine_spec.rb | 22 ++++++++++++++++++++++ 3 files changed, 39 insertions(+), 2 deletions(-) diff --git a/lib/handlebars/engine.rb b/lib/handlebars/engine.rb index 209e291..6a7c487 100644 --- a/lib/handlebars/engine.rb +++ b/lib/handlebars/engine.rb @@ -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 @@ -175,6 +176,7 @@ def version def attach(name, &block) init! + @logger&.debug { "[handlebars] attaching #{name}" } @context.attach(name.to_s, block) end @@ -182,6 +184,8 @@ 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 @@ -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 @@ -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 diff --git a/lib/handlebars/engine/function.rb b/lib/handlebars/engine/function.rb index 3cd09f5..38fab03 100644 --- a/lib/handlebars/engine/function.rb +++ b/lib/handlebars/engine/function.rb @@ -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 diff --git a/spec/handlebars/engine_spec.rb b/spec/handlebars/engine_spec.rb index c0c7370..f0a76ad 100644 --- a/spec/handlebars/engine_spec.rb +++ b/spec/handlebars/engine_spec.rb @@ -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) { {} } @@ -44,6 +47,25 @@ 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 + context "when `path` is defined" do let(:file) { Tempfile.open } From b11473a19d06c075a52cc5bed3dcdfb6e46ba1df Mon Sep 17 00:00:00 2001 From: Zach Gianos Date: Tue, 12 Aug 2025 00:27:51 -0700 Subject: [PATCH 5/6] ci: update runtimes This updates the os and ruby versions on which to test: * include latest two os versions: mac + ubuntu * include latest four ruby versions --- .github/workflows/ci.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e673622..e88dc66 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -60,10 +60,11 @@ jobs: strategy: matrix: os: + - macos-14 - macos-latest + - ubuntu-22.04 - ubuntu-latest ruby: - - ruby-3.0 - ruby-3.1 - ruby-3.2 - ruby-3.3 From 05d442d32ea8b9b7a869d8ab5a6343897e3b35e6 Mon Sep 17 00:00:00 2001 From: Zach Gianos Date: Tue, 12 Aug 2025 00:42:57 -0700 Subject: [PATCH 6/6] v0.3.4 ### 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)) --- CHANGELOG.md | 7 +++++++ lib/handlebars/engine/version.rb | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6e3a427..5718891 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 diff --git a/lib/handlebars/engine/version.rb b/lib/handlebars/engine/version.rb index 1076755..76a5b4e 100644 --- a/lib/handlebars/engine/version.rb +++ b/lib/handlebars/engine/version.rb @@ -2,6 +2,6 @@ module Handlebars class Engine - VERSION = "0.3.3" + VERSION = "0.3.4" end end