From 74d649a989c8384c27f917e30a176ca51cccf131 Mon Sep 17 00:00:00 2001 From: Jeremy Jackson Date: Tue, 11 Nov 2025 11:16:53 -0700 Subject: [PATCH 01/13] Working specs for each_helper_spec.rb --- Gemfile | 3 - Gemfile.lock | 95 +------------------ lib/ruby-handlebars/helper.rb | 17 +++- .../helpers/each_helper_spec.rb | 19 ++-- spec/ruby-handlebars/helpers/shared.rb | 5 +- spec/spec_helper.rb | 3 +- 6 files changed, 29 insertions(+), 113 deletions(-) diff --git a/Gemfile b/Gemfile index 95ec193..95b3819 100644 --- a/Gemfile +++ b/Gemfile @@ -3,11 +3,8 @@ source 'https://rubygems.org' gem 'parslet', '~> 2.0', '>= 2.0.0' group :development do - gem 'pry', '~> 0.10', '>= 0.10.1' - gem 'pry-stack_explorer', '~>0.4', '>= 0.4.9.1' gem 'rspec', '~> 3.1', '>= 3.1.0' gem 'rspec-mocks', '~> 3.1', '>= 3.1.3' - gem 'juwelier', '~> 2.4' end group :test do diff --git a/Gemfile.lock b/Gemfile.lock index 01cb961..4d2696f 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,95 +1,9 @@ GEM remote: https://rubygems.org/ specs: - addressable (2.8.0) - public_suffix (>= 2.0.2, < 5.0) - binding_of_caller (1.0.0) - debug_inspector (>= 0.0.1) - builder (3.2.4) - coderay (1.1.3) - debug_inspector (1.1.0) - descendants_tracker (0.0.4) - thread_safe (~> 0.3, >= 0.3.1) diff-lcs (1.5.0) docile (1.4.0) - faraday (1.10.0) - faraday-em_http (~> 1.0) - faraday-em_synchrony (~> 1.0) - faraday-excon (~> 1.1) - faraday-httpclient (~> 1.0) - faraday-multipart (~> 1.0) - faraday-net_http (~> 1.0) - faraday-net_http_persistent (~> 1.0) - faraday-patron (~> 1.0) - faraday-rack (~> 1.0) - faraday-retry (~> 1.0) - ruby2_keywords (>= 0.0.4) - faraday-em_http (1.0.0) - faraday-em_synchrony (1.0.0) - faraday-excon (1.1.0) - faraday-httpclient (1.0.1) - faraday-multipart (1.0.3) - multipart-post (>= 1.2, < 3) - faraday-net_http (1.0.1) - faraday-net_http_persistent (1.2.0) - faraday-patron (1.0.0) - faraday-rack (1.0.0) - faraday-retry (1.0.3) - git (1.11.0) - rchardet (~> 1.8) - github_api (0.19.0) - addressable (~> 2.4) - descendants_tracker (~> 0.0.4) - faraday (>= 0.8, < 2) - hashie (~> 3.5, >= 3.5.2) - oauth2 (~> 1.0) - hashie (3.6.0) - highline (2.0.3) - juwelier (2.4.9) - builder - bundler - git - github_api - highline - kamelcase (~> 0) - nokogiri - psych - rake - rdoc - semver2 - jwt (2.3.0) - kamelcase (0.0.2) - semver2 (~> 3) - method_source (1.0.0) - mini_portile2 (2.8.0) - multi_json (1.15.0) - multi_xml (0.6.0) - multipart-post (2.1.1) - nokogiri (1.13.6) - mini_portile2 (~> 2.8.0) - racc (~> 1.4) - oauth2 (1.4.9) - faraday (>= 0.17.3, < 3.0) - jwt (>= 1.0, < 3.0) - multi_json (~> 1.3) - multi_xml (~> 0.5) - rack (>= 1.2, < 3) parslet (2.0.0) - pry (0.14.1) - coderay (~> 1.1) - method_source (~> 1.0) - pry-stack_explorer (0.6.1) - binding_of_caller (~> 1.0) - pry (~> 0.13) - psych (4.0.4) - stringio - public_suffix (4.0.7) - racc (1.6.0) - rack (2.2.3.1) - rake (13.0.6) - rchardet (1.8.0) - rdoc (6.4.0) - psych (>= 4.0.0) rspec (3.11.0) rspec-core (~> 3.11.0) rspec-expectations (~> 3.11.0) @@ -103,28 +17,21 @@ GEM diff-lcs (>= 1.2.0, < 2.0) rspec-support (~> 3.11.0) rspec-support (3.11.0) - ruby2_keywords (0.0.5) - semver2 (3.4.2) simplecov (0.21.2) docile (~> 1.1) simplecov-html (~> 0.11) simplecov_json_formatter (~> 0.1) simplecov-html (0.12.3) simplecov_json_formatter (0.1.4) - stringio (3.0.2) - thread_safe (0.3.6) PLATFORMS ruby DEPENDENCIES - juwelier (~> 2.4) parslet (~> 2.0, >= 2.0.0) - pry (~> 0.10, >= 0.10.1) - pry-stack_explorer (~> 0.4, >= 0.4.9.1) rspec (~> 3.1, >= 3.1.0) rspec-mocks (~> 3.1, >= 3.1.3) simplecov BUNDLED WITH - 2.3.12 + 2.7.2 diff --git a/lib/ruby-handlebars/helper.rb b/lib/ruby-handlebars/helper.rb index 6a59205..e64cc7c 100644 --- a/lib/ruby-handlebars/helper.rb +++ b/lib/ruby-handlebars/helper.rb @@ -26,10 +26,23 @@ def apply(context, arguments = [], block = [], else_block = []) def apply_as(context, arguments = [], as_arguments = [], block = [], else_block = []) arguments = [arguments] unless arguments.is_a? Array + args = [context] + hash = {} + arguments.each do |arg| + if arg.is_a?(Hash) && arg.has_key?(:named_parameter) + named = arg[:named_parameter] + hash[named[:key].to_s] = named[:value].eval(context) + else + args << arg.eval(context) + end + end + as_arguments = [as_arguments] unless as_arguments.is_a? Array - args = [context] + arguments.map {|arg| arg.eval(context)} + as_arguments.map(&:name) + split_block(block, else_block) + args += as_arguments.map(&:name) - @fn.call(*args) + blocks = split_block(block, else_block) + + @fn.call(*args, hash: hash, block: blocks[0], else_block: blocks[1]) end private diff --git a/spec/ruby-handlebars/helpers/each_helper_spec.rb b/spec/ruby-handlebars/helpers/each_helper_spec.rb index 807819c..324a0cd 100644 --- a/spec/ruby-handlebars/helpers/each_helper_spec.rb +++ b/spec/ruby-handlebars/helpers/each_helper_spec.rb @@ -1,11 +1,6 @@ -require_relative '../../spec_helper' +require 'spec_helper' require_relative './shared' -require_relative '../../../lib/ruby-handlebars' -require_relative '../../../lib/ruby-handlebars/tree' -require_relative '../../../lib/ruby-handlebars/helpers/each_helper' - - describe Handlebars::Helpers::EachHelper do let(:subject) { Handlebars::Helpers::EachHelper } let(:hbs) {Handlebars::Handlebars.new} @@ -19,7 +14,7 @@ let(:values) { [Handlebars::Tree::String.new('a'), Handlebars::Tree::String.new('b'), Handlebars::Tree::String.new('c') ]} it 'applies the block on all values' do - subject.apply(ctx, values, block, else_block) + subject.apply(ctx, values, block: block, else_block: else_block, hash: {}) expect(block).to have_received(:fn).exactly(3).times expect(else_block).not_to have_received(:fn) @@ -29,14 +24,14 @@ let(:values) { nil } it 'uses the else_block if provided' do - subject.apply(ctx, values, block, else_block) + subject.apply(ctx, values, block: block, else_block: else_block, hash: {}) expect(block).not_to have_received(:fn) expect(else_block).to have_received(:fn).once end it 'returns nil if no else_block is provided' do - expect(subject.apply(ctx, values, block, nil)).to be nil + expect(subject.apply(ctx, values, block: block, else_block: nil, hash: {})).to be nil end end @@ -44,14 +39,14 @@ let(:values) { [] } it 'uses the else_block if provided' do - subject.apply(ctx, values, block, else_block) + subject.apply(ctx, values, block: block, else_block: else_block, hash: {}) expect(block).not_to have_received(:fn) expect(else_block).to have_received(:fn).once end it 'returns nil if no else_block is provided' do - expect(subject.apply(ctx, values, block, nil)).to be nil + expect(subject.apply(ctx, values, block: block, else_block: nil, hash: {})).to be nil end end end @@ -119,7 +114,7 @@ "{{/each}}" ].join("\n") - data = double(items: ducks) + data = {items: ducks} expect(evaluate(template, data)).to eq([ "'}, + ], + close_options: collapse_options ) ] ) @@ -479,7 +480,7 @@ expect(parser.parse('Hi }{{ hey }}')).to eq( block_items: [ {template_content: 'Hi }'}, - base.merge(replaced_unsafe_item: 'hey') + collapse_options.merge(replaced_unsafe_item: 'hey') ] ) end @@ -488,7 +489,7 @@ expect(parser.parse('}{{ hey }}')).to eq( block_items: [ {template_content: '}'}, - base.merge(replaced_unsafe_item: 'hey') + collapse_options.merge(replaced_unsafe_item: 'hey') ] ) end diff --git a/spec/ruby-handlebars/helpers/each_helper_spec.rb b/spec/ruby-handlebars/helpers/each_helper_spec.rb index 33fee72..9b3f86a 100644 --- a/spec/ruby-handlebars/helpers/each_helper_spec.rb +++ b/spec/ruby-handlebars/helpers/each_helper_spec.rb @@ -14,7 +14,7 @@ let(:values) { [Handlebars::Tree::String.new('a'), Handlebars::Tree::String.new('b'), Handlebars::Tree::String.new('c') ]} it 'applies the block on all values' do - subject.apply(ctx, values, block: block, else_block: else_block, hash: {}) + subject.apply(ctx, values, hash: {}, block: block, else_block: else_block, collapse: {}) expect(block).to have_received(:fn).exactly(3).times expect(else_block).not_to have_received(:fn) @@ -24,14 +24,14 @@ let(:values) { nil } it 'uses the else_block if provided' do - subject.apply(ctx, values, block: block, else_block: else_block, hash: {}) + subject.apply(ctx, values, hash: {}, block: block, else_block: else_block, collapse: {}) expect(block).not_to have_received(:fn) expect(else_block).to have_received(:fn).once end it 'returns nil if no else_block is provided' do - expect(subject.apply(ctx, values, block: block, else_block: nil, hash: {})).to be nil + expect(subject.apply(ctx, values, hash: {}, block: block, else_block: nil, collapse: {})).to be nil end end @@ -39,14 +39,14 @@ let(:values) { [] } it 'uses the else_block if provided' do - subject.apply(ctx, values, block: block, else_block: else_block, hash: {}) + subject.apply(ctx, values, hash: {}, block: block, else_block: else_block, collapse: {}) expect(block).not_to have_received(:fn) expect(else_block).to have_received(:fn).once end it 'returns nil if no else_block is provided' do - expect(subject.apply(ctx, values, block: block, else_block: nil, hash: {})).to be nil + expect(subject.apply(ctx, values, hash: {}, block: block, else_block: nil, collapse: {})).to be nil end end end @@ -210,6 +210,38 @@ ].join("\n")) end + context "white space" do + let(:data) { {items: ['a', 'b', 'c']} } + + it "can be stripped in simple cases" do + result = evaluate("[ {{~#each items}} {{this}} {{/each~}} ]", data) + expect(result).to eq("[ a b c ]") + + result = evaluate("[ {{~#each items}} {{~this~}} {{/each~}} ]", data) + expect(result).to eq("[abc]") + + result = evaluate("[ {{~#each items~}} {{this}} {{~/each~}} ]", data) + expect(result).to eq("[abc]") + end + + it "can be stripped in cases with else" do + result = evaluate("[ {{~#each items~}} {{this}} {{else}} otherwise {{/each~}} ]", data) + expect(result).to eq("[a b c ]") + + result = evaluate("[ {{~#each items~}} {{this}} {{~else}} otherwise {{/each~}} ]", data) + expect(result).to eq("[abc]") + + result = evaluate("[ {{~#each nothing}} x {{else}} otherwise {{/each~}} ]") + expect(result).to eq("[ otherwise ]") + + result = evaluate("[ {{~#each nothing}} x {{else~}} otherwise {{~/each~}} ]") + expect(result).to eq("[otherwise]") + + result = evaluate("[ {{~#each nothing}} x {{~/each~}} ]") + expect(result).to eq("[]") + end + end + context 'special variables' do it '@first' do template = [ diff --git a/spec/ruby-handlebars/helpers/if_helper_spec.rb b/spec/ruby-handlebars/helpers/if_helper_spec.rb index dd6ebe8..d8cf594 100644 --- a/spec/ruby-handlebars/helpers/if_helper_spec.rb +++ b/spec/ruby-handlebars/helpers/if_helper_spec.rb @@ -25,7 +25,7 @@ let(:else_block) { nil } it 'returns an empty-string' do - expect(subject.apply(ctx, params, block: block, else_block: else_block, hash: {})).to eq("") + expect(subject.apply(ctx, params, hash: {}, block: block, else_block: else_block, collapse: {})).to eq("") expect(block).not_to have_received(:fn) expect(else_block).not_to have_received(:fn) @@ -82,5 +82,38 @@ expect(evaluate(template, {first_condition: false, second_condition: false}).strip).to eq("Case 4") end end + + context "white space" do + it "can be stripped in simple cases" do + result = evaluate("foo {{#if true}} bar {{/if}} baz") + expect(result).to eq("foo bar baz") + + result = evaluate("foo {{~#if true}} bar {{/if~}} baz") + expect(result).to eq("foo bar baz") + + result = evaluate("foo {{~#if true~}} bar {{~/if~}} baz") + expect(result).to eq("foobarbaz") + end + + it "can be stripped in complex cases with else" do + result = evaluate("foo {{#if foo}} bar {{else}} baz {{/if}} qux", foo: true) + expect(result).to eq("foo bar qux") + + result = evaluate("foo {{~#if foo}} bar {{else}} baz {{/if~}} qux", foo: true) + expect(result).to eq("foo bar qux") + + result = evaluate("foo {{~#if foo~}} bar {{~else}} baz {{/if~}} qux", foo: true) + expect(result).to eq("foobarqux") + + result = evaluate("foo {{~#if foo}} bar {{else}} baz {{/if~}} qux", foo: false) + expect(result).to eq("foo baz qux") + + result = evaluate("foo {{~#if foo}} bar {{else~}} baz {{/if~}} qux", foo: false) + expect(result).to eq("foobaz qux") + + result = evaluate("foo {{~#if foo}} bar {{else~}} baz {{~/if~}} qux", foo: false) + expect(result).to eq("foobazqux") + end + end end end diff --git a/spec/ruby-handlebars/helpers/shared.rb b/spec/ruby-handlebars/helpers/shared.rb index 57f4d41..ef19d13 100644 --- a/spec/ruby-handlebars/helpers/shared.rb +++ b/spec/ruby-handlebars/helpers/shared.rb @@ -36,7 +36,7 @@ def evaluate(template, args = {}) include_context "shared apply helper" it "when condition is #{title}" do - subject.apply(ctx, params, block: block, else_block: else_block, hash: {}) + subject.apply(ctx, params, hash: {}, block: block, else_block: else_block, collapse: {}) expect(block).to have_received(:fn).once expect(else_block).not_to have_received(:fn) @@ -47,7 +47,7 @@ def evaluate(template, args = {}) include_context "shared apply helper" it "when condition is #{title}" do - subject.apply(ctx, params, block: block, else_block: else_block, hash: {}) + subject.apply(ctx, params, hash: {}, block: block, else_block: else_block, collapse: {}) expect(block).not_to have_received(:fn) expect(else_block).to have_received(:fn).once diff --git a/spec/ruby-handlebars/helpers/unless_helper_spec.rb b/spec/ruby-handlebars/helpers/unless_helper_spec.rb index 13f5024..cd93645 100644 --- a/spec/ruby-handlebars/helpers/unless_helper_spec.rb +++ b/spec/ruby-handlebars/helpers/unless_helper_spec.rb @@ -1,9 +1,6 @@ -require_relative '../../spec_helper' +require 'spec_helper' require_relative './shared' -require_relative '../../../lib/ruby-handlebars' -require_relative '../../../lib/ruby-handlebars/helpers/unless_helper' - describe Handlebars::Helpers::UnlessHelper do let(:subject) { Handlebars::Helpers::UnlessHelper } @@ -29,7 +26,7 @@ let(:else_block) { nil } it 'returns an empty-string' do - expect(subject.apply(ctx, params, block: block, else_block: else_block, hash: {})).to eq("") + expect(subject.apply(ctx, params, hash: {}, block: block, else_block: else_block, collapse: {})).to eq("") expect(block).not_to have_received(:fn) expect(else_block).not_to have_received(:fn) @@ -61,5 +58,39 @@ expect(evaluate(template, {condition: false})).to eq("\n Show something\n") expect(evaluate(template, {condition: true})).to eq("\n Do not show something\n") end + + + context "white space" do + it "can be stripped in simple cases" do + result = evaluate("foo {{#unless false}} bar {{/unless}} baz") + expect(result).to eq("foo bar baz") + + result = evaluate("foo {{~#unless false}} bar {{/unless~}} baz") + expect(result).to eq("foo bar baz") + + result = evaluate("foo {{~#unless false~}} bar {{~/unless~}} baz") + expect(result).to eq("foobarbaz") + end + + it "can be stripped in complex cases with else" do + result = evaluate("foo {{#unless foo}} bar {{else}} baz {{/unless}} qux", foo: false) + expect(result).to eq("foo bar qux") + + result = evaluate("foo {{~#unless foo}} bar {{else}} baz {{/unless~}} qux", foo: false) + expect(result).to eq("foo bar qux") + + result = evaluate("foo {{~#unless foo~}} bar {{~else}} baz {{/unless~}} qux", foo: false) + expect(result).to eq("foobarqux") + + result = evaluate("foo {{~#unless foo}} bar {{else}} baz {{/unless~}} qux", foo: true) + expect(result).to eq("foo baz qux") + + result = evaluate("foo {{~#unless foo}} bar {{else~}} baz {{/unless~}} qux", foo: true) + expect(result).to eq("foobaz qux") + + result = evaluate("foo {{~#unless foo}} bar {{else~}} baz {{~/unless~}} qux", foo: true) + expect(result).to eq("foobazqux") + end + end end end diff --git a/spec/ruby-handlebars/helpers/with_helper_spec.rb b/spec/ruby-handlebars/helpers/with_helper_spec.rb index a875c0d..314f54c 100644 --- a/spec/ruby-handlebars/helpers/with_helper_spec.rb +++ b/spec/ruby-handlebars/helpers/with_helper_spec.rb @@ -1,10 +1,6 @@ -require_relative '../../spec_helper' +require 'spec_helper' require_relative './shared' -require_relative '../../../lib/ruby-handlebars' -require_relative '../../../lib/ruby-handlebars/tree' -require_relative '../../../lib/ruby-handlebars/helpers/with_helper' - describe Handlebars::Helpers::WithHelper do let(:subject) { Handlebars::Helpers::WithHelper } let(:hbs) { Handlebars::Handlebars.new } @@ -76,5 +72,32 @@ expect(evaluate(template, city_data).strip).to eq("San Francisco: 883305") end + + context "white space" do + it "can be stripped in simple cases" do + result = evaluate("[ {{~#with city}} {{city.name}} {{/with~}} ]", city_data) + expect(result).to eq("[ San Francisco ]") + + result = evaluate("[ {{~#with city}} {{~city.name~}} {{/with~}} ]", city_data) + expect(result).to eq("[San Francisco]") + + result = evaluate("[ {{~#with city~}} {{city.name}} {{~/with~}} ]", city_data) + expect(result).to eq("[San Francisco]") + end + + it "can be stripped in complex cases with else" do + result = evaluate("[ {{~#with city~}} {{city.name}} {{else}} otherwise {{/with~}} ]", city_data) + expect(result).to eq("[San Francisco ]") + + result = evaluate("[ {{~#with city~}} {{city.name}} {{~else}} otherwise {{/with~}} ]", city_data) + expect(result).to eq("[San Francisco]") + + result = evaluate("[ {{~#with city~}} {{city.name}} {{else}} otherwise {{/with~}} ]", person_data) + expect(result).to eq("[ otherwise ]") + + result = evaluate("[ {{~#with city~}} {{city.name}} {{else~}} otherwise {{~/with~}} ]", person_data) + expect(result).to eq("[otherwise]") + end + end end end From 98da142091f5eae7d1ee1a0176afb76383442a58 Mon Sep 17 00:00:00 2001 From: Jeremy Jackson Date: Fri, 14 Nov 2025 16:03:47 -0700 Subject: [PATCH 09/13] Adds lookup helper (default helper) --- lib/ruby-handlebars/helpers/default_helper.rb | 8 +- lib/ruby-handlebars/helpers/lookup_helper.rb | 20 +++ .../helpers/register_default_helpers.rb | 2 + .../helpers/lookup_helper_spec.rb | 130 ++++++++++++++++++ .../helpers/unless_helper_spec.rb | 1 - 5 files changed, 156 insertions(+), 5 deletions(-) create mode 100644 lib/ruby-handlebars/helpers/lookup_helper.rb create mode 100644 spec/ruby-handlebars/helpers/lookup_helper_spec.rb diff --git a/lib/ruby-handlebars/helpers/default_helper.rb b/lib/ruby-handlebars/helpers/default_helper.rb index c843a26..6db2ca2 100644 --- a/lib/ruby-handlebars/helpers/default_helper.rb +++ b/lib/ruby-handlebars/helpers/default_helper.rb @@ -2,12 +2,12 @@ module Handlebars module Helpers class DefaultHelper def self.register(hbs) - hbs.register_helper(self.registry_name) do |context, parameters, **opts| - self.apply(context, parameters, **opts) + hbs.register_helper(self.registry_name) do |context, *parameters, **opts| + self.apply(context, *parameters, **opts) end if self.respond_to?(:apply) - hbs.register_as_helper(self.registry_name) do |context, parameters, as_names, **opts| - self.apply_as(context, parameters, as_names, **opts) + hbs.register_as_helper(self.registry_name) do |context, *parameters, as_names, **opts| + self.apply_as(context, *parameters, as_names, **opts) end if self.respond_to?(:apply_as) end diff --git a/lib/ruby-handlebars/helpers/lookup_helper.rb b/lib/ruby-handlebars/helpers/lookup_helper.rb new file mode 100644 index 0000000..c9c23a0 --- /dev/null +++ b/lib/ruby-handlebars/helpers/lookup_helper.rb @@ -0,0 +1,20 @@ +require_relative 'default_helper' + +module Handlebars + module Helpers + class LookupHelper < DefaultHelper + def self.registry_name + 'lookup' + end + + def self.apply(context, lookup, key, collapse:, **_opts) + result = lookup[key] + return result unless result.is_a?(String) + + result.lstrip! if collapse[:helper]&.collapse_after + result.rstrip! if collapse[:close]&.collapse_before + result + end + end + end +end diff --git a/lib/ruby-handlebars/helpers/register_default_helpers.rb b/lib/ruby-handlebars/helpers/register_default_helpers.rb index 3924806..1abdfa1 100644 --- a/lib/ruby-handlebars/helpers/register_default_helpers.rb +++ b/lib/ruby-handlebars/helpers/register_default_helpers.rb @@ -1,6 +1,7 @@ require_relative 'each_helper' require_relative 'helper_missing_helper' require_relative 'if_helper' +require_relative 'lookup_helper' require_relative 'unless_helper' require_relative 'with_helper' @@ -10,6 +11,7 @@ def self.register_default_helpers(hbs) EachHelper.register(hbs) HelperMissingHelper.register(hbs) IfHelper.register(hbs) + LookupHelper.register(hbs) UnlessHelper.register(hbs) WithHelper.register(hbs) end diff --git a/spec/ruby-handlebars/helpers/lookup_helper_spec.rb b/spec/ruby-handlebars/helpers/lookup_helper_spec.rb new file mode 100644 index 0000000..e428568 --- /dev/null +++ b/spec/ruby-handlebars/helpers/lookup_helper_spec.rb @@ -0,0 +1,130 @@ +# {{#with (lookup additional_evidence.disputer_evidence.dispute_rebuttal 0)}} +# {{#if this.has_attachment}} +#
+# Fig 9. Policy Disclosure +#
+# {{/if}} +# {{/with}} + +require 'spec_helper' +require_relative './shared' + +describe Handlebars::Helpers::LookupHelper do + let(:subject) { described_class } + let(:hbs) { Handlebars::Handlebars.new } + + it_behaves_like "a registerable helper", "lookup" + + context '.apply' do + include_context "shared apply helper" + end + + context "integration" do + include_context "shared helpers integration tests" + + let(:data) do + { + people: ["Nils", "Yehuda"], + cities: [ + "Darmstadt", + "San Francisco", + ], + } + end + + it "can lookup details" do + expect(evaluate(<<~TEMPLATE, data).strip).to eq("Nils lives in Darmstadt\nYehuda lives in San Francisco") + {{#each people}} + {{~this}} lives in {{lookup ../cities @index}} + {{/each}} + TEMPLATE + end + + end + # + # it "changes the evaluation context" do + # template = <<~HANDLEBARS + # {{#with person}} + # {{firstname}} {{lastname}} + # {{/with}} + # HANDLEBARS + # + # expect(evaluate(template, person_data).strip).to eq("Yehuda Katz") + # end + # + # it "supports block parameters" do + # template = <<~HANDLEBARS + # {{#with city as | city |}} + # {{#with city.location as | loc |}} + # {{city.name}}: {{loc.north}} {{loc.east}} + # {{/with}} + # {{/with}} + # HANDLEBARS + # + # expect(evaluate(template, city_data).strip).to eq("San Francisco: 37.73, -122.44") + # end + # + # it "supports else blocks" do + # template = <<~HANDLEBARS + # {{#with city}} + # {{city.name}} (not shown because there is no city) + # {{else}} + # No city found + # {{/with}} + # HANDLEBARS + # + # expect(evaluate(template, person_data).strip).to eq("No city found") + # end + # + # it "supports simple relative paths" do + # template = <<~HANDLEBARS + # {{#with city}} + # {{#with location}} + # {{../name}}: {{../population}} -- {{north}} + # {{/with}} + # {{/with}} + # HANDLEBARS + # + # expect(evaluate(template, city_data).strip).to eq("San Francisco: 883305 -- 37.73,") + # end + # + # it "supports complex relative paths", skip: "Relative paths are not yet supported" do + # template = <<~HANDLEBARS + # {{#with city as | city |}} + # {{#with city.location as | loc |}} + # {{city.name}}: {{../population}} + # {{/with}} + # {{/with}} + # HANDLEBARS + # + # expect(evaluate(template, city_data).strip).to eq("San Francisco: 883305") + # end + # + # context "white space" do + # it "can be stripped in simple cases" do + # result = evaluate("[ {{~#with city}} {{city.name}} {{/with~}} ]", city_data) + # expect(result).to eq("[ San Francisco ]") + # + # result = evaluate("[ {{~#with city}} {{~city.name~}} {{/with~}} ]", city_data) + # expect(result).to eq("[San Francisco]") + # + # result = evaluate("[ {{~#with city~}} {{city.name}} {{~/with~}} ]", city_data) + # expect(result).to eq("[San Francisco]") + # end + # + # it "can be stripped in complex cases with else" do + # result = evaluate("[ {{~#with city~}} {{city.name}} {{else}} otherwise {{/with~}} ]", city_data) + # expect(result).to eq("[San Francisco ]") + # + # result = evaluate("[ {{~#with city~}} {{city.name}} {{~else}} otherwise {{/with~}} ]", city_data) + # expect(result).to eq("[San Francisco]") + # + # result = evaluate("[ {{~#with city~}} {{city.name}} {{else}} otherwise {{/with~}} ]", person_data) + # expect(result).to eq("[ otherwise ]") + # + # result = evaluate("[ {{~#with city~}} {{city.name}} {{else~}} otherwise {{~/with~}} ]", person_data) + # expect(result).to eq("[otherwise]") + # end + # end + # end +end diff --git a/spec/ruby-handlebars/helpers/unless_helper_spec.rb b/spec/ruby-handlebars/helpers/unless_helper_spec.rb index cd93645..ca77c51 100644 --- a/spec/ruby-handlebars/helpers/unless_helper_spec.rb +++ b/spec/ruby-handlebars/helpers/unless_helper_spec.rb @@ -1,7 +1,6 @@ require 'spec_helper' require_relative './shared' - describe Handlebars::Helpers::UnlessHelper do let(:subject) { Handlebars::Helpers::UnlessHelper } let(:hbs) {Handlebars::Handlebars.new} From 6f09300f6ea0d89f7cea6ef8764a702e4f5e3c85 Mon Sep 17 00:00:00 2001 From: Jeremy Jackson Date: Fri, 14 Nov 2025 16:05:05 -0700 Subject: [PATCH 10/13] Allows nesting by simply using `{{#path.key}}block content{{/path.key}}` structure --- lib/ruby-handlebars/context.rb | 4 + lib/ruby-handlebars/helpers/with_helper.rb | 7 +- lib/ruby-handlebars/parser.rb | 2 +- lib/ruby-handlebars/tree.rb | 36 +++++---- spec/handlebars_spec.rb | 81 +++++++++++++------ .../helpers/with_helper_spec.rb | 14 +++- 6 files changed, 102 insertions(+), 42 deletions(-) diff --git a/lib/ruby-handlebars/context.rb b/lib/ruby-handlebars/context.rb index 983e9a5..3bd4c11 100644 --- a/lib/ruby-handlebars/context.rb +++ b/lib/ruby-handlebars/context.rb @@ -120,6 +120,10 @@ def with_nested_context block_result end + def with_nested_temporary_context(args) + with_nested_context { with_temporary_context(args) { yield } } + end + def with_temporary_context(args = {}) saved = args.keys.collect { |key| [key, get(key.to_s)] }.to_h diff --git a/lib/ruby-handlebars/helpers/with_helper.rb b/lib/ruby-handlebars/helpers/with_helper.rb index d0c8589..88715eb 100644 --- a/lib/ruby-handlebars/helpers/with_helper.rb +++ b/lib/ruby-handlebars/helpers/with_helper.rb @@ -9,7 +9,12 @@ def self.registry_name def self.apply(context, data, block:, else_block:, collapse:, **_opts) if data - result = context.with_temporary_context(data) do + # TODO: helpers need a bit of a rework to handle properly + # nested cases with top.second being able to create + # two ../../ traversal levels. It has to happen above + # this helper, or we need to change how this helper gets + # its data. + result = context.with_nested_temporary_context(data) do block.fn(context) end result.lstrip! if collapse[:helper]&.collapse_after diff --git a/lib/ruby-handlebars/parser.rb b/lib/ruby-handlebars/parser.rb index e8df45e..c54b8c8 100644 --- a/lib/ruby-handlebars/parser.rb +++ b/lib/ruby-handlebars/parser.rb @@ -24,7 +24,7 @@ class Parser < Parslet::Parser rule(:else_kw) { str('else') } rule(:as_kw) { str('as') } - rule(:identifier) { (else_kw >> space? >> dccurly).absent? >> at.maybe >> str("../").repeat.maybe >> match['@\-a-zA-Z0-9_\?'].repeat(1) } + rule(:identifier) { (else_kw >> space? >> dccurly).absent? >> at.maybe >> str("../").repeat.maybe >> match['@\-a-zA-Z0-9_\.\?'].repeat(1) } rule(:directory) { (else_kw >> space? >> dccurly).absent? >> match['@\-a-zA-Z0-9_\/\?'].repeat(1) } rule(:path) { identifier >> (dot >> (identifier | else_kw)).repeat } diff --git a/lib/ruby-handlebars/tree.rb b/lib/ruby-handlebars/tree.rb index b75d004..b296254 100644 --- a/lib/ruby-handlebars/tree.rb +++ b/lib/ruby-handlebars/tree.rb @@ -53,19 +53,27 @@ class Helper < TreeItem.new(:name, :parameters, :as_parameters, :collapse_before def _eval(context) helper = as_parameters ? context.get_as_helper(name.to_s) : context.get_helper(name.to_s) if helper.nil? - context.get_helper('helperMissing').apply(context, String.new(name.to_s)) - else - collapse = { - helper: CollapseOptions.new(collapse_before, collapse_after), - else: else_options, - close: close_options - } - if as_parameters - helper.apply_as(context, parameters, as_parameters, block, else_block, collapse) + # check the context for a matching key. + if context.get(name.to_s) + # swap the helper to "with" + helper = context.get_helper('with') + self.parameters = Parameter.new(Parslet::Slice.new(0, name.to_s)) else - helper.apply(context, parameters, block, else_block, collapse) + # fall back to the missing helper. + return context.get_helper('helperMissing').apply(context, String.new(name.to_s)) end end + + collapse = { + helper: CollapseOptions.new(collapse_before, collapse_after), + else: else_options, + close: close_options + } + if as_parameters + helper.apply_as(context, parameters, as_parameters, block, else_block, collapse) + else + helper.apply(context, parameters, block, else_block, collapse) + end end end @@ -92,7 +100,6 @@ def _eval(context) class Comment < TreeItem.new(:comment, :collapse_before, :collapse_after) def _eval(context) - "" end end @@ -149,18 +156,15 @@ class Transform < Parslet::Transform parameter_name: simple(:name) ) { Tree::Parameter.new(name) } - # TODO: Is this still used -- does it need collapse behavior? - rule( + rule(COLLAPSABLE.merge( comment: simple(:content) - ) { Tree::Comment.new(content) } + )) { Tree::Comment.new(content, collapse_before, collapse_after) } - # TODO: Is this still used? rule( unsafe_helper_name: simple(:name), parameters: subtree(:parameters) ) { Tree::EscapedHelper.new(name, parameters) } - # TODO: Is this still used? rule( safe_helper_name: simple(:name), parameters: subtree(:parameters) diff --git a/spec/handlebars_spec.rb b/spec/handlebars_spec.rb index c6ddd0f..df31cbd 100644 --- a/spec/handlebars_spec.rb +++ b/spec/handlebars_spec.rb @@ -2,9 +2,11 @@ require 'ruby-handlebars/escapers/dummy_escaper' describe Handlebars::Handlebars do - let(:hbs) {Handlebars::Handlebars.new} + let(:hbs) { Handlebars::Handlebars.new } def evaluate(template, args = {}) + hbs.register_helper(:ifCond) { } + hbs.register_helper(:lookup) { } hbs.compile(template).call(args) end @@ -49,6 +51,16 @@ def evaluate(template, args = {}) expect(evaluate('Hello {{first-name}}', {"first-name": 'world'})).to eq('Hello world') end + context 'with comments' do + it 'can remove comments' do + expect(evaluate('Hello {{! comment content}} world')).to eq('Hello world') + end + + it 'can remove comments with whitespace' do + expect(evaluate('Hello {{~! comment content~}} world')).to eq('Hello world') + end + end + context 'partials' do it 'simple' do hbs.register_partial('plic', "Plic") @@ -69,7 +81,7 @@ def evaluate(template, args = {}) hbs.register_partial('brackets', "[{{name}}]") expect(evaluate("Hello {{> brackets}}", {name: 'world'})).to eq("Hello [world]") end - + it 'with a string argument' do hbs.register_partial('with_args', "[{{name}}]") expect(evaluate("Hello {{> with_args name='jon'}}")).to eq("Hello [jon]") @@ -81,12 +93,12 @@ def evaluate(template, args = {}) end it 'with variables in arguments' do - hbs.register_partial('with_args', "[{{fname}} {{lname}}]") + hbs.register_partial('with_args', "[{{fname}} {{lname}}]") expect(evaluate("Hello {{> with_args fname='jon' lname=last_name}}", {last_name: 'doe'})).to eq("Hello [jon doe]") end it 'with a helper as an argument' do - hbs.register_helper('wrap_parens') {|context, value| "(#{value})"} + hbs.register_helper('wrap_parens') { |context, value| "(#{value})" } hbs.register_partial('with_args', "[{{fname}} {{lname}}]") expect(evaluate("Hello {{> with_args fname='jon' lname=(wrap_parens 'doe')}}")).to eq("Hello [jon (doe)]") end @@ -101,32 +113,32 @@ def evaluate(template, args = {}) context 'helpers' do it 'without any argument' do - hbs.register_helper('rainbow') {|context| "-"} + hbs.register_helper('rainbow') { |context| "-" } expect(evaluate("{{rainbow}}")).to eq("-") end it 'with a single argument' do - hbs.register_helper('noah') {|context, value| value.gsub(/a/, '')} + hbs.register_helper('noah') { |context, value| value.gsub(/a/, '') } expect(evaluate("{{noah country}}", {country: 'Canada'})).to eq("Cnd") end it 'with multiple arguments, including strings' do - hbs.register_helper('add') {|context, left, op, right| "#{left} #{op} #{right}"} + hbs.register_helper('add') { |context, left, op, right| "#{left} #{op} #{right}" } expect(evaluate("{{add left '&' right}}", {left: 'Law', right: 'Order'})).to eq("Law & Order") expect(evaluate("{{{add left '&' right}}}", {left: 'Law', right: 'Order'})).to eq("Law & Order") end it 'with an empty string argument' do - hbs.register_helper('noah') {|context, value| value.to_s.gsub(/a/, '')} + hbs.register_helper('noah') { |context, value| value.to_s.gsub(/a/, '') } expect(evaluate("hey{{noah ''}}there", {})).to eq("heythere") end it 'with helpers as arguments' do - hbs.register_helper('wrap_parens') {|context, value| "(#{value})"} - hbs.register_helper('wrap_dashes') {|context, value| "-#{value}-"} + hbs.register_helper('wrap_parens') { |context, value| "(#{value})" } + hbs.register_helper('wrap_dashes') { |context, value| "-#{value}-" } expect(evaluate('{{wrap_dashes (wrap_parens "hello")}}', {})).to eq("-(hello)-") expect(evaluate('{{wrap_dashes (wrap_parens world)}}', {world: "world"})).to eq("-(world)-") @@ -136,17 +148,17 @@ def evaluate(template, args = {}) hbs.register_helper('comment') do |context, commenter, block| block.fn(context).split("\n").map do |line| "#{commenter} #{line}" - end.join("\n") - - expect(evaluate([ - "{{comment '//'}}", - "Author: {{author.name}}, {{author.company}}", - "Date: {{commit_date}}", - "{{/comment}}" - ].join("\n"), {author: {name: 'Vincent', company: 'Hiptest'}, commit_date: 'today'})).to eq([ - "// Author: Vincent, Hiptest", - "// Date: today" - ].join("\n")) + end.join("\n") + + expect(evaluate([ + "{{comment '//'}}", + "Author: {{author.name}}, {{author.company}}", + "Date: {{commit_date}}", + "{{/comment}}" + ].join("\n"), {author: {name: 'Vincent', company: 'Hiptest'}, commit_date: 'today'})).to eq([ + "// Author: Vincent, Hiptest", + "// Date: today" + ].join("\n")) end end @@ -184,7 +196,7 @@ def evaluate(template, args = {}) end it '"else" can be part of a path' do - expect(evaluate('My {{ something.else }} template', { something: { else: 'awesome' }})).to eq('My awesome template') + expect(evaluate('My {{ something.else }} template', {something: {else: 'awesome'}})).to eq('My awesome template') end end @@ -254,7 +266,7 @@ def evaluate(template, args = {}) let(:name) { '<"\'>&' } let(:replacement_escaped) { evaluate('Hello {{ name }}', {name: name}) } let(:helper_replacement_escaped) { - hbs.register_helper('wrap_parens') {|context, value| "(#{value})"} + hbs.register_helper('wrap_parens') { |context, value| "(#{value})" } evaluate('Hello {{wrap_parens name}}', {name: name}) } @@ -303,4 +315,27 @@ def self.escape(value) end end end + + describe "nesting" do + let(:template) { <<~TEMPLATE.strip } + TEMPLATE + + it "allows nesting properties with path notation" do + result = evaluate(<<~TEMPLATE.strip, {top: {second: {third: "_value_"}}}) + {{#top.second}}{{third}}{{/top.second}} + TEMPLATE + expect(result).to eq("_value_") + end + + it "allows nesting properties" do + result = evaluate(<<~TEMPLATE.strip, {top: {second: {third: "_value_"}}}) + {{#top~}} + {{#second}} + {{~third~}} + {{/second}} + {{~/top}} + TEMPLATE + expect(result).to eq("_value_") + end + end end diff --git a/spec/ruby-handlebars/helpers/with_helper_spec.rb b/spec/ruby-handlebars/helpers/with_helper_spec.rb index 314f54c..31994a5 100644 --- a/spec/ruby-handlebars/helpers/with_helper_spec.rb +++ b/spec/ruby-handlebars/helpers/with_helper_spec.rb @@ -61,7 +61,19 @@ expect(evaluate(template, person_data).strip).to eq("No city found") end - it "supports relative paths", skip: "Relative paths are not yet supported" do + it "supports simple relative paths" do + template = <<~HANDLEBARS + {{#with city}} + {{#with location}} + {{../name}}: {{../population}} -- {{north}} + {{/with}} + {{/with}} + HANDLEBARS + + expect(evaluate(template, city_data).strip).to eq("San Francisco: 883305 -- 37.73,") + end + + it "supports complex relative paths", skip: "Relative paths are not yet supported" do template = <<~HANDLEBARS {{#with city as | city |}} {{#with city.location as | loc |}} From 121f5acf28e73ffb9cfd18698d351009bc406ab4 Mon Sep 17 00:00:00 2001 From: Jeremy Jackson Date: Sat, 15 Nov 2025 10:54:30 -0700 Subject: [PATCH 11/13] Fixed some cases for lookup helper. --- lib/ruby-handlebars/helpers/lookup_helper.rb | 2 +- .../helpers/lookup_helper_spec.rb | 94 ------------------- 2 files changed, 1 insertion(+), 95 deletions(-) diff --git a/lib/ruby-handlebars/helpers/lookup_helper.rb b/lib/ruby-handlebars/helpers/lookup_helper.rb index c9c23a0..bb59eff 100644 --- a/lib/ruby-handlebars/helpers/lookup_helper.rb +++ b/lib/ruby-handlebars/helpers/lookup_helper.rb @@ -8,7 +8,7 @@ def self.registry_name end def self.apply(context, lookup, key, collapse:, **_opts) - result = lookup[key] + result = lookup.respond_to?(:[]) ? lookup[key] : '' return result unless result.is_a?(String) result.lstrip! if collapse[:helper]&.collapse_after diff --git a/spec/ruby-handlebars/helpers/lookup_helper_spec.rb b/spec/ruby-handlebars/helpers/lookup_helper_spec.rb index e428568..4012a9b 100644 --- a/spec/ruby-handlebars/helpers/lookup_helper_spec.rb +++ b/spec/ruby-handlebars/helpers/lookup_helper_spec.rb @@ -1,11 +1,3 @@ -# {{#with (lookup additional_evidence.disputer_evidence.dispute_rebuttal 0)}} -# {{#if this.has_attachment}} -#
-# Fig 9. Policy Disclosure -#
-# {{/if}} -# {{/with}} - require 'spec_helper' require_relative './shared' @@ -41,90 +33,4 @@ end end - # - # it "changes the evaluation context" do - # template = <<~HANDLEBARS - # {{#with person}} - # {{firstname}} {{lastname}} - # {{/with}} - # HANDLEBARS - # - # expect(evaluate(template, person_data).strip).to eq("Yehuda Katz") - # end - # - # it "supports block parameters" do - # template = <<~HANDLEBARS - # {{#with city as | city |}} - # {{#with city.location as | loc |}} - # {{city.name}}: {{loc.north}} {{loc.east}} - # {{/with}} - # {{/with}} - # HANDLEBARS - # - # expect(evaluate(template, city_data).strip).to eq("San Francisco: 37.73, -122.44") - # end - # - # it "supports else blocks" do - # template = <<~HANDLEBARS - # {{#with city}} - # {{city.name}} (not shown because there is no city) - # {{else}} - # No city found - # {{/with}} - # HANDLEBARS - # - # expect(evaluate(template, person_data).strip).to eq("No city found") - # end - # - # it "supports simple relative paths" do - # template = <<~HANDLEBARS - # {{#with city}} - # {{#with location}} - # {{../name}}: {{../population}} -- {{north}} - # {{/with}} - # {{/with}} - # HANDLEBARS - # - # expect(evaluate(template, city_data).strip).to eq("San Francisco: 883305 -- 37.73,") - # end - # - # it "supports complex relative paths", skip: "Relative paths are not yet supported" do - # template = <<~HANDLEBARS - # {{#with city as | city |}} - # {{#with city.location as | loc |}} - # {{city.name}}: {{../population}} - # {{/with}} - # {{/with}} - # HANDLEBARS - # - # expect(evaluate(template, city_data).strip).to eq("San Francisco: 883305") - # end - # - # context "white space" do - # it "can be stripped in simple cases" do - # result = evaluate("[ {{~#with city}} {{city.name}} {{/with~}} ]", city_data) - # expect(result).to eq("[ San Francisco ]") - # - # result = evaluate("[ {{~#with city}} {{~city.name~}} {{/with~}} ]", city_data) - # expect(result).to eq("[San Francisco]") - # - # result = evaluate("[ {{~#with city~}} {{city.name}} {{~/with~}} ]", city_data) - # expect(result).to eq("[San Francisco]") - # end - # - # it "can be stripped in complex cases with else" do - # result = evaluate("[ {{~#with city~}} {{city.name}} {{else}} otherwise {{/with~}} ]", city_data) - # expect(result).to eq("[San Francisco ]") - # - # result = evaluate("[ {{~#with city~}} {{city.name}} {{~else}} otherwise {{/with~}} ]", city_data) - # expect(result).to eq("[San Francisco]") - # - # result = evaluate("[ {{~#with city~}} {{city.name}} {{else}} otherwise {{/with~}} ]", person_data) - # expect(result).to eq("[ otherwise ]") - # - # result = evaluate("[ {{~#with city~}} {{city.name}} {{else~}} otherwise {{~/with~}} ]", person_data) - # expect(result).to eq("[otherwise]") - # end - # end - # end end From aaa239b211c543d0dc62cd69e215638b2fdf6f7e Mon Sep 17 00:00:00 2001 From: Jeremy Jackson Date: Mon, 17 Nov 2025 10:48:30 -0700 Subject: [PATCH 12/13] Refactors duplicated code. --- lib/ruby-handlebars/helpers/default_helper.rb | 6 ++++++ lib/ruby-handlebars/helpers/each_helper.rb | 13 ++---------- lib/ruby-handlebars/helpers/if_helper.rb | 19 ++++++----------- lib/ruby-handlebars/helpers/lookup_helper.rb | 4 +--- lib/ruby-handlebars/helpers/unless_helper.rb | 21 ++----------------- lib/ruby-handlebars/helpers/with_helper.rb | 18 ++++------------ 6 files changed, 21 insertions(+), 60 deletions(-) diff --git a/lib/ruby-handlebars/helpers/default_helper.rb b/lib/ruby-handlebars/helpers/default_helper.rb index 6db2ca2..31c202c 100644 --- a/lib/ruby-handlebars/helpers/default_helper.rb +++ b/lib/ruby-handlebars/helpers/default_helper.rb @@ -11,6 +11,12 @@ def self.register(hbs) end if self.respond_to?(:apply_as) end + def self.stripped_result(result, start_collapse, end_collapse) + result = result.lstrip if start_collapse&.collapse_after + result = result.rstrip if end_collapse&.collapse_before + result + end + # Should be implemented by sub-classes # def self.registry_name # 'myHelperName' diff --git a/lib/ruby-handlebars/helpers/each_helper.rb b/lib/ruby-handlebars/helpers/each_helper.rb index 4ac3c5d..69cb5f9 100644 --- a/lib/ruby-handlebars/helpers/each_helper.rb +++ b/lib/ruby-handlebars/helpers/each_helper.rb @@ -43,19 +43,10 @@ def self.add_and_execute(block, context, items, item, index, else_block, collaps :@last => index == items.length - 1 } - result = context.with_temporary_context(locals.merge(extra.to_h)) do + context.with_temporary_context(locals.merge(extra.to_h)) do context.add_items(item) if item.respond_to?(:map) - block.fn(context) + stripped_result(block.fn(context), collapse[:helper], else_block.nil? ? collapse[:close] : collapse[:else]) end - - result.lstrip! if collapse[:helper]&.collapse_after - if else_block - result.rstrip! if collapse[:else]&.collapse_before - else - result.rstrip! if collapse[:close]&.collapse_before - end - - result end end end diff --git a/lib/ruby-handlebars/helpers/if_helper.rb b/lib/ruby-handlebars/helpers/if_helper.rb index e301c79..f312429 100644 --- a/lib/ruby-handlebars/helpers/if_helper.rb +++ b/lib/ruby-handlebars/helpers/if_helper.rb @@ -9,24 +9,17 @@ def self.registry_name def self.apply(context, condition, block:, else_block:, collapse:, **_opts) condition = !condition.empty? if condition.respond_to?(:empty?) + branch(condition, context, block, else_block, collapse) + end + def self.branch(condition, context, block, else_block, collapse) if condition - result = block.fn(context) - result.lstrip! if collapse[:helper]&.collapse_after - if else_block - result.rstrip! if collapse[:else]&.collapse_before - else - result.rstrip! if collapse[:close]&.collapse_before - end + stripped_result(block.fn(context), collapse[:helper], else_block.nil? ? collapse[:close] : collapse[:else]) elsif else_block - result = else_block.fn(context) - result.lstrip! if collapse[:else]&.collapse_after - result.rstrip! if collapse[:close]&.collapse_before + stripped_result(else_block.fn(context), collapse[:else], collapse[:close]) else - return "" + "" end - - result end end end diff --git a/lib/ruby-handlebars/helpers/lookup_helper.rb b/lib/ruby-handlebars/helpers/lookup_helper.rb index bb59eff..d044918 100644 --- a/lib/ruby-handlebars/helpers/lookup_helper.rb +++ b/lib/ruby-handlebars/helpers/lookup_helper.rb @@ -11,9 +11,7 @@ def self.apply(context, lookup, key, collapse:, **_opts) result = lookup.respond_to?(:[]) ? lookup[key] : '' return result unless result.is_a?(String) - result.lstrip! if collapse[:helper]&.collapse_after - result.rstrip! if collapse[:close]&.collapse_before - result + stripped_result(result, collapse[:helper], collapse[:close]) end end end diff --git a/lib/ruby-handlebars/helpers/unless_helper.rb b/lib/ruby-handlebars/helpers/unless_helper.rb index 36f4991..a079cb8 100644 --- a/lib/ruby-handlebars/helpers/unless_helper.rb +++ b/lib/ruby-handlebars/helpers/unless_helper.rb @@ -2,31 +2,14 @@ module Handlebars module Helpers - class UnlessHelper < DefaultHelper + class UnlessHelper < IfHelper def self.registry_name 'unless' end def self.apply(context, condition, block:, else_block:, collapse:, **_opts) condition = !condition.empty? if condition.respond_to?(:empty?) - - if !condition - result = block.fn(context) - result.lstrip! if collapse[:helper]&.collapse_after - if else_block - result.rstrip! if collapse[:else]&.collapse_before - else - result.rstrip! if collapse[:close]&.collapse_before - end - elsif else_block - result = else_block.fn(context) - result.lstrip! if collapse[:else]&.collapse_after - result.rstrip! if collapse[:close]&.collapse_before - else - return "" - end - - result + branch(!condition, context, block, else_block, collapse) end end end diff --git a/lib/ruby-handlebars/helpers/with_helper.rb b/lib/ruby-handlebars/helpers/with_helper.rb index 88715eb..83def09 100644 --- a/lib/ruby-handlebars/helpers/with_helper.rb +++ b/lib/ruby-handlebars/helpers/with_helper.rb @@ -14,24 +14,14 @@ def self.apply(context, data, block:, else_block:, collapse:, **_opts) # two ../../ traversal levels. It has to happen above # this helper, or we need to change how this helper gets # its data. - result = context.with_nested_temporary_context(data) do - block.fn(context) - end - result.lstrip! if collapse[:helper]&.collapse_after - if else_block - result.rstrip! if collapse[:else]&.collapse_before - else - result.rstrip! if collapse[:close]&.collapse_before + context.with_nested_temporary_context(data) do + stripped_result(block.fn(context), collapse[:helper], else_block.nil? ? collapse[:close] : collapse[:else]) end elsif else_block - result = else_block.fn(context) - result.lstrip! if collapse[:else]&.collapse_after - result.rstrip! if collapse[:close]&.collapse_before + stripped_result(else_block.fn(context), collapse[:else], collapse[:close]) else - return "" + "" end - - result end def self.apply_as(context, data, name, **opts) From 0db4e9699b1ae16b08fc3e292abbfc28a87333cf Mon Sep 17 00:00:00 2001 From: Jeremy Jackson Date: Mon, 17 Nov 2025 11:11:47 -0700 Subject: [PATCH 13/13] Uses forwardable for delegation. --- lib/ruby-handlebars/context.rb | 30 ++++++------------------------ 1 file changed, 6 insertions(+), 24 deletions(-) diff --git a/lib/ruby-handlebars/context.rb b/lib/ruby-handlebars/context.rb index 3bd4c11..ce0d797 100644 --- a/lib/ruby-handlebars/context.rb +++ b/lib/ruby-handlebars/context.rb @@ -1,8 +1,14 @@ +require "forwardable" + module Handlebars class Context PATH_REGEX = /\.\.\/|[^.\/]+/ class Data + extend Forwardable + + def_delegators :@hash, :[]=, :keys, :key?, :empty?, :merge!, :map + def initialize(hash) @hash = hash end @@ -18,10 +24,6 @@ def [](k) to_number(k.to_s) || nil end - def []=(k, v) - @hash[k] = v - end - def dup self.class.new(@hash.dup) # shallow copy. end @@ -34,26 +36,6 @@ def respond_to?(val, _ = false) %w[[] has_key?].include?(val.to_s) ? true : false end - def keys - @hash.keys - end - - def key?(...) - @hash.key?(...) - end - - def empty? - @hash.empty? - end - - def merge!(...) - @hash.merge!(...) - end - - def map(...) - @hash.map(...) - end - private def to_number(val)