From 66a71da85e99ebd13a167b457b102379e57c283d Mon Sep 17 00:00:00 2001 From: Joel Ambass Date: Thu, 18 Feb 2021 21:34:00 +0100 Subject: [PATCH] Support Whitespace tags for Objects This patch is the first step towards supporting the Whitespace Control tags. It only implements support for the whitespace object tags `{{-` and `-}}`. Support for the tags is more complicated since the `Tag.eval/3` function needs to support the `trim_previous` and `trim_next` arguments. In some cases there are multiple of them and we need to forward them to further calls of `Solid.render/3`. I'm very new to NimbleParsec and generally parsers so I'm posting this to get verification on the current path I have taken. Some guidance would be appreciated if possible. Needed by https://github.com/edgurgel/solid/issues/10 --- lib/solid.ex | 55 +++++++++++++++++++++++++++++++++---- lib/solid/context.ex | 5 ++-- lib/solid/parser.ex | 55 +++++++++++++++++++++---------------- test/cases/112/input.json | 1 + test/cases/112/input.liquid | 3 ++ 5 files changed, 88 insertions(+), 31 deletions(-) create mode 100644 test/cases/112/input.json create mode 100644 test/cases/112/input.liquid diff --git a/lib/solid.ex b/lib/solid.ex index ab84b11..26be110 100644 --- a/lib/solid.ex +++ b/lib/solid.ex @@ -74,6 +74,7 @@ defmodule Solid do Enum.reduce(text, {[], context}, fn entry, {acc, context} -> try do {result, context} = do_render(entry, context, options) + {result, acc, context} = maybe_trim(entry, result, acc, context) {[result | acc], context} catch {:break_exp, partial_result, context} -> @@ -87,6 +88,55 @@ defmodule Solid do {Enum.reverse(result), context} end + defp maybe_trim({:text, _data}, result, acc, context) do + result = maybe_trim_current(result, context) + context = Map.put(context, :trim_next, false) + {result, acc, context} + end + + defp maybe_trim({:object, data}, result, acc, context) do + [trim_previous] = Keyword.get(data, :trim_previous) + acc = maybe_trim_previous(acc, trim_previous) + + result = maybe_trim_current(result, context) + + [trim_next] = Keyword.get(data, :trim_next) + context = Map.put(context, :trim_next, trim_next) + + {result, acc, context} + end + + defp maybe_trim({:tag, _data}, result, acc, context) do + context = Map.put(context, :trim_next, false) + {result, acc, context} + end + + defp maybe_trim_current(result, context) do + if context.trim_next do + trim_leading(result) + else + result + end + end + + defp maybe_trim_previous(acc, flag) do + if flag && !Enum.empty?(acc) do + [prev | tail] = acc + trimmed_prev = trim_trailing(prev) + [trimmed_prev | tail] + else + acc + end + end + + defp trim_trailing([value]) do + [String.trim_trailing(value)] + end + + defp trim_leading([value]) do + [String.trim_leading(value)] + end + defp do_render({:text, string}, context, _options), do: {string, context} defp do_render({:object, object}, context, options) do @@ -95,11 +145,6 @@ defmodule Solid do end defp do_render({:tag, tag}, context, options) do - {tag_text, context} = render_tag(tag, context, options) - {tag_text, context} - end - - defp render_tag(tag, context, options) do {result, context} = Tag.eval(tag, context, options) if result do diff --git a/lib/solid/context.ex b/lib/solid/context.ex index 8bc20f8..dc11ee4 100644 --- a/lib/solid/context.ex +++ b/lib/solid/context.ex @@ -1,11 +1,12 @@ defmodule Solid.Context do - defstruct vars: %{}, counter_vars: %{}, iteration_vars: %{}, cycle_state: %{} + defstruct vars: %{}, counter_vars: %{}, iteration_vars: %{}, cycle_state: %{}, trim_next: false @type t :: %__MODULE__{ vars: Map.t(), counter_vars: Map.t(), iteration_vars: %{optional(String.t()) => term}, - cycle_state: Map.t() + cycle_state: Map.t(), + trim_next: boolean } @type scope :: :counter_vars | :vars | :iteration_vars diff --git a/lib/solid/parser.ex b/lib/solid/parser.ex index df5c61a..6c73d4d 100644 --- a/lib/solid/parser.ex +++ b/lib/solid/parser.ex @@ -96,18 +96,25 @@ defmodule Solid.Parser.Base do argument = choice([value, field]) - opening_object = string("{{") - closing_object = string("}}") + opening_tag = ignore(string("{%")) + closing_tag = ignore(string("%}")) - opening_tag = string("{%") - closing_tag = string("%}") + opening_objects = choice([ + string("{{-") |> replace(true) |> tag(:trim_previous), + string("{{") |> replace(false) |> tag(:trim_previous) + ]) + + closing_objects = choice([ + string("-}}") |> replace(true) |> tag(:trim_next), + string("}}") |> replace(false) |> tag(:trim_next) + ]) space = string(" ") |> times(min: 0) text = - lookahead_not(choice([opening_object, opening_tag])) + lookahead_not(choice([opening_objects, opening_tag])) |> utf8_string([], 1) |> times(min: 1) |> reduce({Enum, :join, []}) @@ -136,13 +143,13 @@ defmodule Solid.Parser.Base do |> tag(:filter) object = - ignore(opening_object) + opening_objects |> ignore(space) - |> lookahead_not(closing_object) + |> lookahead_not(closing_objects) |> tag(argument, :argument) |> optional(tag(repeat(filter), :filters)) |> ignore(space) - |> ignore(closing_object) + |> concat(closing_objects) |> tag(:object) comment = string("comment") @@ -150,7 +157,7 @@ defmodule Solid.Parser.Base do end_comment = string("endcomment") comment_tag = - ignore(opening_tag) + opening_tag |> ignore(space) |> ignore(comment) |> ignore(space) @@ -171,7 +178,7 @@ defmodule Solid.Parser.Base do |> replace({-1, -1}) counter_tag = - ignore(opening_tag) + opening_tag |> ignore(space) |> concat(choice([increment, decrement])) |> ignore(space) @@ -181,7 +188,7 @@ defmodule Solid.Parser.Base do |> tag(:counter_exp) case_tag = - ignore(opening_tag) + opening_tag |> ignore(space) |> ignore(string("case")) |> ignore(space) @@ -190,7 +197,7 @@ defmodule Solid.Parser.Base do |> ignore(closing_tag) when_tag = - ignore(opening_tag) + opening_tag |> ignore(space) |> ignore(string("when")) |> ignore(space) @@ -201,7 +208,7 @@ defmodule Solid.Parser.Base do |> tag(:when) else_tag = - ignore(opening_tag) + opening_tag |> ignore(space) |> ignore(string("else")) |> ignore(space) @@ -258,7 +265,7 @@ defmodule Solid.Parser.Base do |> repeat(choice([bool_and, bool_or]) |> concat(expression)) if_tag = - ignore(opening_tag) + opening_tag |> ignore(space) |> ignore(string("if")) |> tag(boolean_expression, :expression) @@ -266,7 +273,7 @@ defmodule Solid.Parser.Base do |> tag(parsec(:liquid_entry), :result) elsif_tag = - ignore(opening_tag) + opening_tag |> ignore(space) |> ignore(string("elsif")) |> tag(boolean_expression, :expression) @@ -275,7 +282,7 @@ defmodule Solid.Parser.Base do |> tag(:elsif_exp) unless_tag = - ignore(opening_tag) + opening_tag |> ignore(space) |> ignore(string("unless")) |> tag(boolean_expression, :expression) @@ -304,7 +311,7 @@ defmodule Solid.Parser.Base do |> ignore(closing_tag) assign_tag = - ignore(opening_tag) + opening_tag |> ignore(space) |> ignore(string("assign")) |> ignore(space) @@ -352,7 +359,7 @@ defmodule Solid.Parser.Base do |> reduce({Enum, :into, [%{}]}) for_tag = - ignore(opening_tag) + opening_tag |> ignore(space) |> ignore(string("for")) |> ignore(space) @@ -375,7 +382,7 @@ defmodule Solid.Parser.Base do |> tag(:for_exp) capture_tag = - ignore(opening_tag) + opening_tag |> ignore(space) |> ignore(string("capture")) |> ignore(space) @@ -392,7 +399,7 @@ defmodule Solid.Parser.Base do |> tag(:capture_exp) break_tag = - ignore(opening_tag) + opening_tag |> ignore(space) |> ignore(string("break")) |> ignore(space) @@ -400,7 +407,7 @@ defmodule Solid.Parser.Base do |> tag(:break_exp) continue_tag = - ignore(opening_tag) + opening_tag |> ignore(space) |> ignore(string("continue")) |> ignore(space) @@ -415,7 +422,7 @@ defmodule Solid.Parser.Base do |> ignore(closing_tag) raw_tag = - ignore(opening_tag) + opening_tag |> ignore(space) |> ignore(string("raw")) |> ignore(space) @@ -425,7 +432,7 @@ defmodule Solid.Parser.Base do |> tag(:raw_exp) cycle_tag = - ignore(opening_tag) + opening_tag |> ignore(space) |> ignore(string("cycle")) |> ignore(space) @@ -475,7 +482,7 @@ defmodule Solid.Parser.Base do all_tags = if custom_tags != [] do custom_tag = - ignore(opening_tag) + opening_tag |> ignore(space) |> concat(choice(custom_tags)) |> ignore(space) diff --git a/test/cases/112/input.json b/test/cases/112/input.json new file mode 100644 index 0000000..0967ef4 --- /dev/null +++ b/test/cases/112/input.json @@ -0,0 +1 @@ +{} diff --git a/test/cases/112/input.liquid b/test/cases/112/input.liquid new file mode 100644 index 0000000..a25db9f --- /dev/null +++ b/test/cases/112/input.liquid @@ -0,0 +1,3 @@ +{% assign my_variable = "tomato" %} +{% assign your_variable = "your" %} +! {{ your_variable }} ! {{- my_variable -}} !