yogthos / Selmer

A fast, Django inspired template system in Clojure.
Eclipse Public License 1.0
982 stars 114 forks source link

Quoted strings as tag arguments have their quotes escaped and preserved #235

Open unitof opened 4 years ago

unitof commented 4 years ago

Is it possible to have a quoted string as an argument to a tag, without the surrounding quotes being preserved in the output?

Selmer, as expected, does treat the quoted string as a single argument, but passes the string to the tag function with its quotes, escaped, as part of the string.

Or perhaps there's another notation which allows a string with spaces to be treated as a single argument?

(use 'selmer.parser)

(add-tag! :foo
  (fn [args context-map]
    (str "foo " (first args))))

(render "{% foo \"quux quuz\" %}" {})
=>"foo \"quux quuz\""

Using render-file removes the need to escape the quotes in the template file, but still introduces them in the output:

file.txt:

{% foo "quux quuz" %}
(render-file "file.txt" {})
=>"foo \"quux quuz\""

The {% safe %} tag has no discernible effect:

file.txt:

{% safe %}{% foo "quux quuz" %}{% endsafe %}
(render-file "file.txt" {})
=>"foo \"quux quuz\""
unitof commented 4 years ago

I took a look at the built-in cycle tag to see if it handles quotes strings any differently, but it has the same behavior.

In other words, I don't think it's currently possible to write a {% cycle %} tag that cycles between strings that include spaces, without literal "s being included in the output.

(render "{% for i in items %}This is {% cycle \"an apple\" \"a banana\" %}. {% endfor %}" {:items (range 5)})

=>

"This is \"an apple\". This is \"a banana\". This is \"an apple\". This is \"a banana\". This is \"an apple\". "

Removing the literal quotes formats the output as desired, but—of course—treats the strings as four separate arguments:

(render "{% for i in items %}This is {% cycle an apple a banana %}. {% endfor %}" {:items (range 5)})

=>

"This is an. This is apple. This is a. This is banana. This is an. "

My desired output (in this specific example) is:

"This is an apple. This is a banana. This is an apple. This is a banana. This is an apple. "

Is there any tag syntax workaround to achieve that?

Note that this behavior also applies to tag-content within block-style tags, which is actually where it's most relevant to my current work.

I definitely could add a (clojure.string/replace tag-content #"\"" "") to my custom tag function, but that feels a bit icky.

yogthos commented 4 years ago

Yeah, the best option at the moment is to do replace in the custom tag. I'd be open to adding support for handling quoted strings, but I probably won't be able to get around to it in the near future. If you'd be ok doing a PR for this, I could certainly help with that.

unitof commented 4 years ago

Alright, I'll see if I have time to learn how to approach this! But for now will do the replace workaround.

Would you prefer that this change the default behavior (quotes always get removed, but that technically would break backward compatibility), or be a new setting on the parser, like (parser strip-quotes-on!)?

Also would need to figure out how it could support quotes if they are desired in output strings. Probably via a double-escaping like \\\".

yogthos commented 4 years ago

I think it would be best to avoid breaking changes to make upgrading smoother, so escaping would be preferable.

boxxxie commented 2 years ago

the render function could preserve the old behavior via and options field, or add the behavior via options.

yogthos commented 2 years ago

Yeah, adding flags to hint behavior would be a reasonable approach here.

unitof commented 2 years ago

Obviously I didn't get to this—sorry! Still eager to try, but I'm very very new to Clojure so would take me a while and I may need help. Feel free to take it on someone else before I do!

yogthos commented 2 years ago

Definitely no rush, and if you do get around to it then I'm happy to help with a review.

seancorfield commented 2 years ago

We rely on this behavior explicitly at work, with a custom tag we've added that takes arguments generated by a (JS) preprocessor. We check in the tag if the argument starts and ends with " and if so, we attempt to read it as EDN to convert it to a plain string (and if that fails, we leave it as-is). We also use that as a hint that the argument string should then be render'd -- and we wouldn't want to call render on all (string) arguments.

Which is a long-winded way of saying "please don't change the default behavior but make this possible via some option to the render functions".

didibus commented 1 year ago

PR: https://github.com/yogthos/Selmer/pull/304 adds resolve-arg which will return the literal without the double quotes. If you don't want to also render the arg if a template, you can just use (parse-literal arg) from selmer.filter-parser. parse-literal will remove the double quotes if any.

yogthos commented 1 year ago

And new release 1.12.59 is up on Clojars with the feature.