Skip to content

Loading…

Improve name resolution documentation #46

Open
cjerdonek opened this Issue · 9 comments

4 participants

@cjerdonek

The name resolution descriptions in the "interpolation," "sections," and "inverted" parts of the documentation are very similar. Indeed, it looks like the descriptions in "sections" and "inverted" are identical. It would help to combine these descriptions into a single section so that it is easier to see how name resolution differs or is the same in these three contexts.

In the meantime, can someone clarify the following?

Should name resolution for sections and inverted sections obey the same rules? Also, is it intentional that a single period cannot be used for sections and inverted sections?

Finally, the spec says for sections, "if the context is an object and the method with the given name has an arity of 1, the method SHOULD be called with a String containing the unprocessed contents of the sections; the data is the value returned." Can someone clarify whether this should be done even for the non-final parts of the name split on periods?

Thanks.

@pvande

Name resolution should behave identically for sections and inverted sections.

The omission of the implicit iterator as a valid name is unintentional; it should always be permitted as a name resolving to the topmost element on the stack.

Re-reading the spec, the method / function resolution step occurs before iteration, and methods with an arity of 1 are simply a special case of methods. Since subsequent dotted names would need to be resolved either against the return result or the "method object", and since the latter is a non-portable concept, I vote yes.

(Both of these things should be codified and tested by the spec.)

@cjerdonek

Thanks a lot for the clarification, @pvande.

One more question though. I'm having trouble understanding what is meant by the following part of the spec regarding name resolution in sections. Or else I understand it just fine but don't see the intended use case:

This tag's content names the data to replace the tag.  Name resolution
is as follows:
  ...
  4) If the context is an object and the method with the given name has an
  arity of 1, the method SHOULD be called with a String containing the
  unprocessed contents of the sections; the data is the value returned.
  ...
For each element in the data list, the element MUST be pushed onto the
context stack, the section MUST be rendered, and the element MUST be
popped off the context stack.

Can you provide an example use case of this part of the spec in action? To provide a starting point, here's a deliberately simple example that we could use to see what issue I'm having. Say you have the following set up (in Python)--

class Foo(object):
    def bar(self, contents):
        # Capitalize the section contents.
        return contents.upper()

foo = Foo()
template = '{{#foo.bar}}Hello: {{person}}{{/foo.bar}}'
context = {'foo': foo, 'person': 'Joe'}

In the example above, in the final step of the rendering process per the spec above, it seems like the section contents Hello: {{person}} would be rendered with the following (last in, first out) context stack:

[{'foo': foo, 'person': 'Joe'}, 'HELLO: {{PERSON}}']

which would yield:

'Hello: Joe'

So the upper-case method return value never comes into play (since it's on the top of the context stack as a string value and there is no implicit iterator). If instead, however, we constructed a simple example using an implicit iterator to gain access to the top of the context stack, we would get something like the following, which doesn't seem useful either--

template = '{{#foo.bar}}Hello: {{.}}{{/foo.bar}}'
context = {'foo': foo}

which, using similar reasoning, would yield--

'Hello: HELLO: {{.}}'

Obviously, either I'm interpreting the spec wrong, or neither of the above is an intended use case. Can you provide your thoughts? Thanks a lot.

@cjerdonek

Any thoughts on the question above? Thanks a lot.

@pvande

Sorry for the delay. I'm not sure why I didn't see your follow-up questions.

This tag's content names the data to replace the tag.  Name resolution
is as follows:
  ...
  4) If the context is an object and the method with the given name has an
  arity of 1, the method SHOULD be called with a String containing the
  unprocessed contents of the sections; the data is the value returned.
  ...
For each element in the data list, the element MUST be pushed onto the
context stack, the section MUST be rendered, and the element MUST be
popped off the context stack.

This is a clear issue with the text of the spec. Lambdas (and methods) are intended to have "special" behavior that is not adequately captured by this. Pointing a section at an arity 1 function/method should cause the method to "pre-process" the content of the section, but should (probably?) avoid any direct manipulation of the context stack. So in your example, the transformed contents of the section would be HELLO: {{PERSON}}, which would then be resolved as HELLO:.

I'll make sure that the text is cleaned up soon. Thanks!

@cjerdonek cjerdonek added a commit to cjerdonek/mustache-spec that referenced this issue
@cjerdonek cjerdonek Clarify and simplify language around object methods in sections.yml.
See issue #46 for related discussion:

  mustache#46
3de4731
@cjerdonek

Okay, great. Thanks for the clarification.

I think the crux of the issue here was the phrase "the data is the value returned," because the return value in this case (the pre-processed, unrendered value of the section) does not parallel the use of the word "data" in the previous bullet (i.e. in "If the context is a hash, the data is the value associated with the name."). You also noted this here in the issue #47 discussion.

I just uploaded a commit that I think simplifies and clarifies the language in a few ways: 3de4731

First, it explicitly ties the "object method" case back to the language specified in the lambda section, instead of duplicating the lambda language for object methods. It wasn't clear to me before that object methods should be treated the same as lambdas.

Second, it removes the phrase "the data is the value returned" that we discussed above.

And third, it reduces the amount of special casing in the language. With this change, it is clearer that object methods of arity zero and one should be treated the same as arity zero and one lambdas (respectively), because lambdas can occur as hash values in the previous bullet anyways.

Does that seem okay to you?

@cjerdonek

@pvande, any reaction to this -- does this seem okay? Thanks.

@samwyse

@cjerdonek, I disagree with your commit for one main reason: it refers to lambdas, which are an optional portion of the spec. The specification unfortunately requires the reader to assign meanings to several terms, when they should have been defined somewhere within the spec itself. My interpretation of a 'method' is an engine-provided function that is attached to an object in the context stack. (Note that while most, if not all, programming languages pass that object as an implicit parameter to the function, I'm not willing to say that Mustache requires that.) A 'lambda', OTOH, is a user-provided function, etc., and due to uneven support within programming languages, the spec does not require any way to define them.

I propose the following:

    4) If the context is an object, the data is the method associated with
    the name.  That method must have an arity of 1.
    5) If any name parts were retained in step 1, each should be resolved
    against a context stack containing only the result from the former
    resolution.  If any part fails resolution, the result should be considered
    falsey, and should interpolate as the empty string.
  If the data is not of a list type, it is coerced into a list as follows: if
  the data is truthy (e.g. `!!data == true`), use a single-element list
  containing the data, otherwise use an empty list.

  For each element in the data list, the element MUST be pushed onto the
  context stack, the section MUST be rendered, and the element MUST be popped
  off the context stack.  If the element on the top of the context stack
  is a method, the method SHOULD be called with a String containing the  
  unprocessed contents of the sections; the data is the value returned.

I still see one problem, that the intermediate form of the rendered data is 'HELLO: {{PERSON}}'. The Mustache spec does not specify that names are case insensitive, so it is implementation dependent if PERSON is recognized or not. Either foo.bar should be re-written to not alter tags, or the context object should be {'foo': foo, 'person': 'Joe', PERSON: 'JOE'}.

Perhaps @bobthecow or @groue could comment?

@groue

Lambdas - or any way to let the user inject his own code in the template rendering - are a much needed feature. Without code injection, Mustache is so minimalist it turns into a painful trap as soon as your application starts to leave the trivial zone. So even if the spec does not require implementation of such feature, it's indeed mandatory for any implementation that pretends to be slightly useful.

Now, unfortunately, the lambdas described by the spec are quite weak, and do not provide any help for frequently asked features. My own implementation lets, of course, the end-user implement spec-compliant lambda, but not only. You'll see a list of examples on this page, under § Examples. And for a list of much requested feature that I personally consider mandatory to implement, check the GRMustache FAQ.

@groue

@samwyse: You'll see that I don't consider arity being a relevant concept: your modifications still go in a direction I left a long time ago (while keeping users able to implement spec-like lambdas if they need to, of course).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Something went wrong with that request. Please try again.