3

Say I want to write a function that format floats with 2 digits in pystache.

I want both the float number and the function in the context (I believe this is the correct philosophy of Mustache).

What should I do?

In order to make the problem clear, I will show a pair of code snippets I have written. They do not work.

Snippet (a):

import pystache

tpl = "{{#fmt}}{{q}}{{/fmt}}"

ctx = {
    'q' : 1234.5678,
    'fmt' : lambda x: "%.2f" % float(x)
}

print pystache.render(tpl, ctx)

This fails, with error "could not convert string to float: {{q}}". I can understand this error: {{fmt}} is evaluated before {{q}}.

Snippet (b):

import pystache

tpl = "{{#q}}{{fmt}}{{/q}}"

ctx = {
    'q' : 1234.5678,
    'fmt' : lambda x: "%.2f" % float(x)
}

print pystache.render(tpl, ctx)

This fails with error: "lambda() takes exactly 1 argument (0 given)". I can't understand this error: shouldn't the context be passed as argument?

luca.vercelli
  • 898
  • 7
  • 24
  • What tutorial did you use? https://github.com/defunkt/pystache shows how it's supposed to be used. – ivan_pozdeev Nov 03 '17 at 15:36
  • I have seen that tutorial, however the examples are very naive, I am not able applying it to a real use case. Most tutorials are intended for JavaScript. – luca.vercelli Nov 03 '17 at 16:01
  • Your problem statement is incomplete. Please upgrade the code to a [mcve] and include the desired result and full traceback of the error. Otherwise, I cannot know what you're really doing and what you're trying to achieve. – ivan_pozdeev Nov 03 '17 at 16:09
  • Please, you can ignore my code. I thought it was useful, probably it is not. All I want is to define a function that formats numbers. There may be a lot of different numbers in my contest (say {{p}}, {{q}}, {{r}},..) and when I print them I want to format all of them with 2 decimal digits. – luca.vercelli Nov 03 '17 at 16:11
  • There is an issue on this: https://github.com/defunkt/pystache/issues/157 – ivan_pozdeev Nov 04 '17 at 19:27

3 Answers3

2

Short answer: mustache doesn't support this. It expects all data values to come preprocessed.

In 2012, at formating of dates, numbers and more · Issue #41 · mustache/spec, ppl suggessted various implementations, incl. from other template engines, and couldn't reach any conclusion.

As per mustache.js date formatting , ppl have constructed several extensions and/or workarounds (To me, the most promising one looks to be the {{value | format}} syntax extension), or suggested moving to other markup engines.


Additional info:

The spec at http://mustache.github.io/mustache.5.html (linked from http://mustache.github.io front page no less) is obsolete, dated 2009. The latest spec that pystache follows resides at https://github.com/mustache/spec , and looks abandoned, too: the latest commit is dated 02.2015 and the latest spec update is from 2011. Nor does it have a successor.

So, by this point, the standard is dead, and the markup is thus free-for-all to augment.

I'd still suggest to consider other formats linked to in the aforementioned discussions before reinventing the wheel.

ivan_pozdeev
  • 33,874
  • 19
  • 107
  • 152
0

According to ivan_pozdeev's comments, I will post my own answer. I will not accept it, because I think it's a too ugly solution.

import pystache
import copy

tpl = "{{#fmt}}{{q}}{{/fmt}}"

ctx = {
    'q' : 1234.5678,
}

class OtherContext(object):
    def __init__(self, renderer):
        self.renderer = renderer

    def fmt(self):
        return lambda s: "%.2f" % float(copy.deepcopy(self.renderer).render(s, self.renderer.context))


renderer = pystache.Renderer(missing_tags='strict')

ctx2 = OtherContext(renderer)

print renderer.render(tpl, ctx, ctx2)
luca.vercelli
  • 898
  • 7
  • 24
0

I also had this problem. After setting a breakpoint on a defined method that I called with my lambda, I found that the data Pystache passes is only the lambda subsection of the template. Not very helpful.

Then, after a lot of digging around, I found that on Nov 5, 2013, cjerdonek mentions accessing the current context via the renderer, and ultimately this code comment from the pystache.renderer module:

# This is an experimental way of giving views access to the current context.
# [...]
@property
def context(self):
    """
    Return the current rendering context [experimental].
    """

    return self._context

Thankfully, this experiment works Ultimately:

  • Your lambda must call a defined method.
    • A strict inline lambda doesn't work in my experience.
  • Your defined method must be able to access an instance of the Renderer class.
    • In the method, you'll get the context, process the value, and print it.
  • Your Renderer instance must be used to render your template and context, so that it then has access to the context.

I built the following to meet your requirements. This is in Python 3.6.5, and I expanded the names for readability.

import pystache

RENDERER = None


def format_float(template):
    '''
    @param template: not actually used in your use case
        (returns '{{value}}' in this case, which you could render if needed)
    '''
    context_stack = RENDERER.context._stack
    # the data has been last in the stack in my experience
    context_data = context_stack[len(context_stack) - 1]
    x = float(context_data['value'])

    print("%.2f" % x)


if __name__ == '__main__':
    RENDERER = pystache.Renderer()

    template = '{{#formatter}}{{value}}{{/formatter}}'
    context = {
        'value' : 1234.5678,
        'formatter' : lambda template: format_float(template)
    }

    RENDERER.render(template, context)

This prints 1234.57 to the console.

nikodaemus
  • 1,918
  • 3
  • 21
  • 32