Git Product home page Git Product logo

solid's People

Contributors

barkerja avatar bceskavich avatar bluzky avatar christopherlai avatar cjbell avatar davidelias avatar dependabot-preview[bot] avatar edgurgel avatar esse avatar fcapovilla avatar iautom8things avatar jalcine avatar jcambass avatar jeroenvisser101 avatar jmks avatar karlseguin avatar kianmeng avatar lostkobrakai avatar markglenn avatar mayankgureja avatar sb8244 avatar shahnerodgers avatar sirfitz avatar wmnnd avatar wtcross avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar

solid's Issues

Context Value Keys

@edgurgel How do you feel about the following:

Instead of the parser returning {:field, String.t()} it returns {:field [atom()]}. This would remove the need to split the string during render/runtime and also play nicely with structs without the need for any coercion.

I feel enforcing keys as atoms is more beneficial than strings, due to the fact you gain the ability to use structs for your render values.

The parser would become:

field    <- [0-9a-zA-Z\._]* access* `
 case Node of
   [FieldName | [[]]] -> {field, lists:map(fun(Field) -> binary_to_existing_atom(Field, utf8) end, string:split(iolist_to_binary(FieldName), "."))};
   [FieldName | [Accesses]] ->
     {field, lists:map(fun(Field) -> binary_to_existing_atom(Field, utf8) end, string:split(iolist_to_binary(FieldName), ".")), Accesses}
 end
`;

And the argument:

@spec get({:field, [atom()]} | {:field, [atom()], [{:access, non_neg_integer}]} | {:value, term}, Context.t) :: term
def get({:value, val}, _hash), do: val
def get({:field, key}, %Context{vars: vars}) do
  get_in(vars, key)
end

break and continue with whitespace control issue

     Render result was different!
     Input:
     ########################

     this should print

     {%- break -%}

     this should not print

     ########################


     code:  liquid_output == solid_output
     left:  "########################\n\nthis should print\n"
     right: "########################\n\nthis should print"
     Render result was different!
     Input:
     ########################

     this should print

     {%- continue %}

     this should not print

     ########################


     code:  liquid_output == solid_output
     left:  "########################\n\nthis should print\n"
     right: "########################\n\nthis should print"

https://github.com/edgurgel/solid/blob/master/test/integration/whitespace_control_cases/break_tag_test.exs#L3-L15
& https://github.com/edgurgel/solid/blob/master/test/integration/whitespace_control_cases/continue_tag_test.exs#L3-L15

increment bug

input.liquid

{% increment my_number %}
{% increment my_number %}
{% increment my_number %}

{{ my_number }}

input.json

{
  "my_number": 4
}
Render result was different!
     Input:
     {% increment my_number %}
     {% increment my_number %}
     {% increment my_number %}

     {{ my_number }}


     code:  liquid_output == solid_output
     left:  "4\n5\n6\n\n7\n"
     right: "0\n1\n2\n\n4\n"

ArgumentError when using `contains` if `:contains` hasn't been used.

There's an issue if you use the contains operator due to the use of :erlang.binary_to_existing_atom.

Screen Shot 2021-05-28 at 12 02 31 PM

I'm opening this issue instead of a PR because I'm not sure what the best route would be to solve this. There's an easy workaround that makes this a non-blocking issue, for me:

Simply defining :contains somewhere "fixes" this for me, but I think ideally this module should somehow ensure that :contains already exists.

I've seen @before_compile used for this or just as a no-op line proceeding the usage of :erlang.binary_to_existing_atom

Custom block tags

{% navigation %}
  {% link About %}
  {% link Services %}
  {% link Contact %}
{% endnavigation %}

Allow Filesystems to serve pre-parsed templates

Hey there, I was building a Liquid template that required a couple of render statements which would include template files from the local file system. I noticed that this had quite an impact on performance, and my suspicion is that it might be because Solid keeps re-reading and re-parsing each included template file from the filesystem (https://github.com/edgurgel/solid/blob/main/lib/solid/tag/render.ex#L54).

Would you be open to a PR that changes the signature of the FileSystem.read_template_file/2 callback from read_template_file(binary(), options :: any()) :: String.t() to read_template_file(binary(), options :: any()) :: String.t() | Template.t() ? Iโ€™d also adjust the render tag so that it handles this correctly.

Custom tag with arguments

Is it possible to pass arguments into a custom tag? For example: Shopify's render tag allows for this syntax:

{% render 'name', my_variable: my_variable, my_other_variable: 'oranges' %}

I've tried this out and it doesn't seem possible today, although I could be missing something.

render for

{% assign variants = product.variants %}
{% render "product_variant" for variants as variant %}

Issue with filters not applied

This is a super strange one that gets into some stuff I'm not sure about. I am seeing filters not being applied, sometimes, which made me look into root cause.

Symptoms

I see the input to my filter appear when I restart the server. The filter is not applied.

Running MyCustomFilters.the_filter("x") properly returns the filter result. The solid filter is now applied every time.

Root Cause

I tracked it down to String.to_existing_atom raising an error for my function:

iex(9)> String.to_existing_atom("asset_url")
** (ArgumentError) argument error
    :erlang.binary_to_existing_atom("asset_url", :utf8)

After I manually run the function, it becomes an existing atom. TBH, I'm not entirely sure what causes a function to be registered as an atom, and I'm not sure why it's not an existing atom when the app first boots.

basic Filesystem usage syntax

question about basic Filesystem usage

what am I missing for filesystem usage in a simple Phoenix index action below?

def index(conn, _params) do
    template_path = "/lib/app_web/templates/liquid/"
    file_system = Solid.LocalFileSystem.new(template_path)
    text = Solid.LocalFileSystem.full_path(file_system, "hello")
    render(conn, text)
end
/lib/app_web/templates/liquid/_hello.liquid
hello

gives

    ** (FunctionClauseError) no function clause matching in String.match?/2
        (elixir 1.12.2) lib/string.ex:2259: String.match?(%Solid.LocalFileSystem{pattern: "_%s.liquid", root: "/lib/app_web/templates/liquid/"}, ~r/^[^.\/][a-zA-Z0-9_\/]+$/)
        (solid 0.10.0) lib/solid/file_system.ex:84: Solid.LocalFileSystem.full_path/2

no doubt missing some basic config

Use filter in boolean expression

Issue
Use filter in if condition raise exception

{% for item in user.hobbies %}

{% if item | upcase == "COOKING" %}
<b>{{item}}</b>
{% else %}
{{ item }}
{% endif %}

{% endfor %}

Error when less than 2 tags are enabled

defmodule Parser do
    use Solid.Parser.Base, excluded_tags: [
      Solid.Tag.Break,
      Solid.Tag.Continue,
      Solid.Tag.Counter,
      Solid.Tag.Comment,
      Solid.Tag.Assign,
      Solid.Tag.Capture,
      #Solid.Tag.If,
      Solid.Tag.Case,
      Solid.Tag.For,
      Solid.Tag.Raw,
      Solid.Tag.Cycle,
      Solid.Tag.Render
    ]
  end

gives the following error:
** (FunctionClauseError) no function clause matching in NimbleParsec.choice/3
If I comment out (enable) a second tag, the error disappears.

Rendering options are not documented fully in the code

In the README you mention

Solid.render/3 doesn't raise or return errors unless strict_variables: true or strict_filters: true are passed as options.

But the function doc doesn't mention it. IMO, README should not be mandatory read, code docs always take precedence as the source of truth

Pass options to custom tag

I read code which render custom_tag and I see that the options is not passed to custom tag render function.
Is there any reason not to do that?

In my case I want to add custom tag render to render another template, and I think it would be better to pass via options instead of variables.

For example:

template
|> Solid.parse!()
|> Solid.render(my_variables, tags: %{"include" => MyIncludeTemplate}, lookup_dir: "templates/")

or we can support tag options tuple

template
|> Solid.parse!()
|> Solid.render(my_variables, tags: %{"include" => {MyIncludeTemplate, [lookup_dir: "templates/"]})

Passing render options to Custom Filters

Hi @edgurgel - thanks for creating this library for the community! What are your thoughts on passing the options passed to render down to the custom filters.

Use case:
In a multi-tenant environment, if I create a custom filter like asset_url (similar to the one Shopify offers), I need to know the tenant in order to return the correct URL. Additionally, filename passed to asset_url might have a hash appended to the name.

{{ "logo.png" | asset_url }} returns https://cdn.com/logo-123abc-hash.png.

Thanks!

Custom tags in for loops not rendering

I'm seeing something really odd and wanted to raise it up as a potential bug. I haven't ruled out me doing something stupid, but everything double-checks out.

I have a {% for %} tag which has a custom parsed tag inside of it. In this case, it's something like:

{% for foo in bars %}
  {% render "a_template" %}
{% endfor %}

If I put the render tag outside of the loop, it executes. If I put it in the loop, it seems to never get called.

Include tag?

I see some reference to include tags however, are they actually implemented?
Also not sure if this is actually feasible using a custom tag because ideally we'd want to do the template inclusion in parse time and not in runtime?

date filter is incomplete

The date filter should accept parameters from the Unix strftime, which includes %s. %s outputs the number of seconds since Epoch (Jan 1, 1970_. This does not appear to be supported in this library. Are there plans to support this?

Error when define custom parser with only one tag

I define a new custom parser with only 1 custom tag

defmodule CustomPaddingParser do
  use Solid.Parser.Base, custom_tags: ["padding"]
end

And compiler throw this message. If number of custom tag >= 2, it works.

** (FunctionClauseError) no function clause matching in NimbleParsec.choice/2

    The following arguments were given to NimbleParsec.choice/2:

        # 1
        []

        # 2
        [[string: "padding"]]

    Attempted function clauses (showing 1 out of 1):

        def choice(combinator, [_, _ | _] = choices) when is_list(combinator)

    (nimble_parsec 1.1.0) lib/nimble_parsec.ex:1437: NimbleParsec.choice/2
    test/support/custom_parsers.ex:10: (module)
    (stdlib 3.14.1) erl_eval.erl:680: :erl_eval.do_apply/6

Publish v0.13

Hello,

Could you please publish v0.13? Has some nice improvements that I'd like to use.

Thanks.

Improve error message

Currently if parsing failed, solid the return message

Reason: expected end of string, line: xxx

It does not provide much detail so you don't know where to fix or what to fix.
It will be better if the error message can provide more details:

  • Which file ( in case using render tag )
  • Which line ( currently it return only the line of opening tags)
    For example
{% if 1 == 1 %}
Hi
how are you {{name
{% endif %}

and the line which error return is line 1, not line 3

Add strict_filters option

Problem

We have added support to strict_variables (#105). We should also support to the strict_filters option that Liquid supports:

template = Liquid::Template.parse("{{x | filter1 | upcase}}") 
template.render({ 'x' => 'foo' }, { strict_filters: true }) 
#=> '' # when at least one filter in the filter chain is undefined, a whole expression is rendered as nil 
template.errors 
#=> [#<Liquid::UndefinedFilter: Liquid error: undefined filter filter1>]

https://www.rubydoc.info/gems/liquid/4.0.1#undefined-variables-and-filters

Iterator variables not available to custom tag

I have a simple template that uses a custom tag inside of a for loop:

  {% for file in files.items %}
  	{% render_file file %}
  {% endfor %}

If I inspect the vars present in the custom tag, I have files available, but file is not defined. Is it expected that the iteration variable is available to the custom tag?

I see that iteration_vars exists, so I'm wondering if the order for grabbing a variable should be: context.iteration_vars fallback to context.vars

Custom tag support

Hey there! I've been working to move https://koype.net/ to use this library over the unmaintained https://github.com/bettyblocks/liquid-elixir. It did work well but I had support for providing custom tags. It looks like this uses nimble-parsec so I'm not sure what would be required to extend this so custom tag support could be a tag. My use case was allowing templates to look up routes in my Phoenix application.

Solid.Tag.Render is parser-unaware

When rendering using {% render arg %}, Solid.Tag.Render is unaware of what parser the parent template used to render it. This causes an issue where, if the child template uses a custom tag, rendering fails.

I've created a test repo to demonstrate this.

I think the solution would be to add a :parser field to Solid.Template, but wanted your take on this.

Compilation Performance when adding Custom Tags

Thank you for this awesome library, we love it ๐Ÿ˜„

We've noticed that when we add custom tags (of which we've added 8) it slows down our compiles tremendously when processing those tags. So just the tags take about 20 seconds to compile.

Do you know if this is this something that's just a limitation of nimble_parsec and some kind of Big(O) issue with the underlying algorithms or potentially how we're constructing our tags that's resulting in way more parser combinations than needed?

Here's an example of a custom tag:

In Liquid it is:

{% image file_id %}

The elixir:

  @impl true
  def spec(_parser) do
    space = Literal.whitespace(min: 0)

    ignore(BaseTag.opening_tag())
    |> ignore(space)
    |> ignore(string("image"))
    |> ignore(space)
    |> tag(Argument.argument(), :file_id)
    |> ignore(space)
    |> ignore(BaseTag.closing_tag())
  end

Possible to tell if a tag doesn't get a value?

Apologies for using the issue tracker for help.

I am wondering if you see any way to detect whether a tag results in no value? I have a use case where I want to display an error (or even the {{ name }} text) if a value isn't available.

I attempted to look down the path of providing my own Solid.Context struct, but I don't think there is a place that I could hook in (even hacky) to detect an empty value.

render with

render with

{% assign featured_product = all_products["product_handle"] %}
{% render "product" with featured_product as product %}

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.