diff --git a/lib/liquid.rb b/lib/liquid.rb index e10cdf6..8fd3788 100644 --- a/lib/liquid.rb +++ b/lib/liquid.rb @@ -84,6 +84,7 @@ require 'liquid/usage' require 'liquid/register' require 'liquid/static_registers' require 'liquid/template_factory' +require 'liquid/optimizer' # Load all the tags of the standard library # diff --git a/lib/liquid/optimizer.rb b/lib/liquid/optimizer.rb new file mode 100644 index 0000000..97bc44e --- /dev/null +++ b/lib/liquid/optimizer.rb @@ -0,0 +1,55 @@ +# frozen_string_literal: true + +module Liquid + class Optimizer + def optimize(node) + case node + when Liquid::Template then optimize(node.root.body) + when Liquid::Document then optimize(node.body) + when Liquid::BlockBody then optimize_block(node) + when Liquid::Variable then optimize_variable(node) + when Liquid::Assign then optimize_assign(node) + when Liquid::For then optimize_for(node) + when Liquid::If then optimize_if(node) + end + node + end + + def optimize_block(block) + block.nodelist.each { |node| optimize(node) } + end + + def optimize_variable(node) + # Turn chained `| append: "..."| append: "..."`, into a single `append_all: [...]` + if node.filters.size > 1 && node.filters.all? { |f, _| f == "append" } + node.filters = [["append_all", node.filters.map { |f, (arg)| arg }]] + end + end + + def optimize_assign(node) + optimize(node.from) + end + + def optimize_for(node) + optimize(node.collection_name) + optimize_block(node) + end + + def optimize_if(node) + node.blocks.each do |block| + optimize_condition(block) + optimize(block.attachment) + end + end + + def optimize_condition(node) + case node + when Liquid::ElseCondition + # noop + when Liquid::Condition + optimize(node.left) + optimize(node.right) if node.right + end + end + end +end \ No newline at end of file diff --git a/lib/liquid/standardfilters.rb b/lib/liquid/standardfilters.rb index 0f4c914..b4e6768 100644 --- a/lib/liquid/standardfilters.rb +++ b/lib/liquid/standardfilters.rb @@ -281,6 +281,10 @@ module Liquid input.to_s + string.to_s end + def append_all(input, *items) + input.to_s + items.join + end + def concat(input, array) unless array.respond_to?(:to_ary) raise ArgumentError, "concat filter requires an array argument" diff --git a/test/unit/optimizer_test.rb b/test/unit/optimizer_test.rb new file mode 100644 index 0000000..f3014da --- /dev/null +++ b/test/unit/optimizer_test.rb @@ -0,0 +1,16 @@ +# frozen_string_literal: true + +require 'test_helper' + +class OptimizerUnitTest < Minitest::Test + include Liquid + + def test_combines_append_filters + optimizer = Optimizer.new + var = Variable.new('hello | append: "a" | append: b', ParseContext.new) + var = optimizer.optimize(var) + assert_equal([ + ['append_all', ["a", VariableLookup.new("b")]] + ], var.filters) + end +end