././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1609625345.5792255 chevron-0.14.0/0000755000175000017310000000000000000000000012110 5ustar00noahusers././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1609619590.0 chevron-0.14.0/MANIFEST.in0000644000175000017310000000002200000000000013640 0ustar00noahusersinclude README.md ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1609625345.5792255 chevron-0.14.0/PKG-INFO0000644000175000017310000001426600000000000013216 0ustar00noahusersMetadata-Version: 2.1 Name: chevron Version: 0.14.0 Summary: Mustache templating language renderer Home-page: https://github.com/noahmorrison/chevron Author: noah morrison Author-email: noah@morrison.ph License: MIT Description: [![PyPI version](https://badge.fury.io/py/chevron.svg)](https://badge.fury.io/py/chevron) [![Build Status](https://travis-ci.org/noahmorrison/chevron.svg?branch=master)](https://travis-ci.org/noahmorrison/chevron) [![Coverage Status](https://coveralls.io/repos/github/noahmorrison/chevron/badge.svg?branch=master)](https://coveralls.io/github/noahmorrison/chevron?branch=master) A python implementation of the [mustache templating language](http://mustache.github.io). Why chevron? ------------ I'm glad you asked! ### chevron is fast ### Chevron runs in less than half the time of [pystache](http://github.com/defunkt/pystache) (Which is not even up to date on the spec). And in about 70% the time of [Stache](https://github.com/hyperturtle/Stache) (A 'trimmed' version of mustache, also not spec compliant). ### chevron is pep8 ### The flake8 command is run by [travis](https://travis-ci.org/noahmorrison/chevron) to ensure consistency. ### chevron is spec compliant ### Chevron passes all the unittests provided by the [spec](https://github.com/mustache/spec) (in every version listed below). If you find a test that chevron does not pass, please [report it.](https://github.com/noahmorrison/chevron/issues/new) ### chevron is Python 2 and 3 compatible ### Python 2.6, 2.7, 3.2, 3.3, 3.4, 3.5, and 3.6 are all tested by travis. USAGE ----- Commandline usage: (if installed via pypi) ``` usage: chevron [-h] [-v] [-d DATA] [-p PARTIALS_PATH] [-e PARTIALS_EXT] [-l DEF_LDEL] [-r DEF_RDEL] template positional arguments: template The mustache file optional arguments: -h, --help show this help message and exit -v, --version show program's version number and exit -d DATA, --data DATA The json data file -p PARTIALS_PATH, --path PARTIALS_PATH The directory where your partials reside -e PARTIALS_EXT, --ext PARTIALS_EXT The extension for your mustache partials, 'mustache' by default -l DEF_LDEL, --left-delimiter DEF_LDEL The default left delimiter, "{{" by default. -r DEF_RDEL, --right-delimiter DEF_RDEL The default right delimiter, "}}" by default. ``` Python usage with strings ```python import chevron chevron.render('Hello, {{ mustache }}!', {'mustache': 'World'}) ``` Python usage with file ```python import chevron with open('file.mustache', 'r') as f: chevron.render(f, {'mustache': 'World'}) ``` Python usage with unpacking ```python import chevron args = { 'template': 'Hello, {{ mustache }}!', 'data': { 'mustache': 'World' } } chevron.render(**args) ``` chevron supports partials (via dictionaries) ```python import chevron args = { 'template': 'Hello, {{> thing }}!', 'partials_dict': { 'thing': 'World' } } chevron.render(**args) ``` chevron supports partials (via the filesystem) ```python import chevron args = { 'template': 'Hello, {{> thing }}!', # defaults to . 'partials_path': 'partials/', # defaults to mustache 'partials_ext': 'ms', } # ./partials/thing.ms will be read and rendered chevron.render(**args) ``` chevron supports lambdas ```python import chevron def first(text, render): # return only first occurance of items result = render(text) return [ x.strip() for x in result.split(" || ") if x.strip() ][0] def inject_x(text, render): # inject data into scope return render(text, {'x': 'data'}) args = { 'template': 'Hello, {{# first}} {{x}} || {{y}} || {{z}} {{/ first}}! {{# inject_x}} {{x}} {{/ inject_x}}', 'data': { 'y': 'foo', 'z': 'bar', 'first': first, 'inject_x': inject_x } } chevron.render(**args) ``` INSTALL ------- - with git ``` $ git clone https://github.com/noahmorrison/chevron.git ``` or using submodules ``` $ git submodules add https://github.com/noahmorrison/chevron.git ``` Also available on pypi! - with pip ``` $ pip install chevron ``` TODO --- * get popular * have people complain * fix those complaints Platform: UNKNOWN Classifier: Development Status :: 4 - Beta Classifier: License :: OSI Approved :: MIT License Classifier: Programming Language :: Python :: 2.6 Classifier: Programming Language :: Python :: 2.7 Classifier: Programming Language :: Python :: 3.2 Classifier: Programming Language :: Python :: 3.3 Classifier: Programming Language :: Python :: 3.4 Classifier: Programming Language :: Python :: 3.5 Classifier: Programming Language :: Python :: 3.6 Classifier: Topic :: Text Processing :: Markup Description-Content-Type: text/markdown ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1609619590.0 chevron-0.14.0/README.md0000644000175000017310000001004300000000000013365 0ustar00noahusers[![PyPI version](https://badge.fury.io/py/chevron.svg)](https://badge.fury.io/py/chevron) [![Build Status](https://travis-ci.org/noahmorrison/chevron.svg?branch=master)](https://travis-ci.org/noahmorrison/chevron) [![Coverage Status](https://coveralls.io/repos/github/noahmorrison/chevron/badge.svg?branch=master)](https://coveralls.io/github/noahmorrison/chevron?branch=master) A python implementation of the [mustache templating language](http://mustache.github.io). Why chevron? ------------ I'm glad you asked! ### chevron is fast ### Chevron runs in less than half the time of [pystache](http://github.com/defunkt/pystache) (Which is not even up to date on the spec). And in about 70% the time of [Stache](https://github.com/hyperturtle/Stache) (A 'trimmed' version of mustache, also not spec compliant). ### chevron is pep8 ### The flake8 command is run by [travis](https://travis-ci.org/noahmorrison/chevron) to ensure consistency. ### chevron is spec compliant ### Chevron passes all the unittests provided by the [spec](https://github.com/mustache/spec) (in every version listed below). If you find a test that chevron does not pass, please [report it.](https://github.com/noahmorrison/chevron/issues/new) ### chevron is Python 2 and 3 compatible ### Python 2.6, 2.7, 3.2, 3.3, 3.4, 3.5, and 3.6 are all tested by travis. USAGE ----- Commandline usage: (if installed via pypi) ``` usage: chevron [-h] [-v] [-d DATA] [-p PARTIALS_PATH] [-e PARTIALS_EXT] [-l DEF_LDEL] [-r DEF_RDEL] template positional arguments: template The mustache file optional arguments: -h, --help show this help message and exit -v, --version show program's version number and exit -d DATA, --data DATA The json data file -p PARTIALS_PATH, --path PARTIALS_PATH The directory where your partials reside -e PARTIALS_EXT, --ext PARTIALS_EXT The extension for your mustache partials, 'mustache' by default -l DEF_LDEL, --left-delimiter DEF_LDEL The default left delimiter, "{{" by default. -r DEF_RDEL, --right-delimiter DEF_RDEL The default right delimiter, "}}" by default. ``` Python usage with strings ```python import chevron chevron.render('Hello, {{ mustache }}!', {'mustache': 'World'}) ``` Python usage with file ```python import chevron with open('file.mustache', 'r') as f: chevron.render(f, {'mustache': 'World'}) ``` Python usage with unpacking ```python import chevron args = { 'template': 'Hello, {{ mustache }}!', 'data': { 'mustache': 'World' } } chevron.render(**args) ``` chevron supports partials (via dictionaries) ```python import chevron args = { 'template': 'Hello, {{> thing }}!', 'partials_dict': { 'thing': 'World' } } chevron.render(**args) ``` chevron supports partials (via the filesystem) ```python import chevron args = { 'template': 'Hello, {{> thing }}!', # defaults to . 'partials_path': 'partials/', # defaults to mustache 'partials_ext': 'ms', } # ./partials/thing.ms will be read and rendered chevron.render(**args) ``` chevron supports lambdas ```python import chevron def first(text, render): # return only first occurance of items result = render(text) return [ x.strip() for x in result.split(" || ") if x.strip() ][0] def inject_x(text, render): # inject data into scope return render(text, {'x': 'data'}) args = { 'template': 'Hello, {{# first}} {{x}} || {{y}} || {{z}} {{/ first}}! {{# inject_x}} {{x}} {{/ inject_x}}', 'data': { 'y': 'foo', 'z': 'bar', 'first': first, 'inject_x': inject_x } } chevron.render(**args) ``` INSTALL ------- - with git ``` $ git clone https://github.com/noahmorrison/chevron.git ``` or using submodules ``` $ git submodules add https://github.com/noahmorrison/chevron.git ``` Also available on pypi! - with pip ``` $ pip install chevron ``` TODO --- * get popular * have people complain * fix those complaints ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1609625345.5792255 chevron-0.14.0/chevron/0000755000175000017310000000000000000000000013554 5ustar00noahusers././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1609619590.0 chevron-0.14.0/chevron/__init__.py0000644000175000017310000000023400000000000015664 0ustar00noahusersfrom .main import main, cli_main from .renderer import render from .tokenizer import ChevronError __all__ = ['main', 'render', 'cli_main', 'ChevronError'] ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1609622315.0 chevron-0.14.0/chevron/main.py0000755000175000017310000000635100000000000015062 0ustar00noahusers#!/usr/bin/python import io import sys try: from .renderer import render from .metadata import version except (ValueError, SystemError): # python 2 from renderer import render from metadata import version def main(template, data=None, **kwargs): with io.open(template, 'r', encoding='utf-8') as template_file: yaml_loader = kwargs.pop('yaml_loader', None) or 'SafeLoader' if data is not None: with io.open(data, 'r', encoding='utf-8') as data_file: data = _load_data(data_file, yaml_loader) else: data = {} args = { 'template': template_file, 'data': data } args.update(kwargs) return render(**args) def _load_data(file, yaml_loader): try: import yaml loader = getattr(yaml, yaml_loader) # not tested return yaml.load(file, Loader=loader) # not tested except ImportError: import json return json.load(file) def cli_main(): """Render mustache templates using json files""" import argparse import os def is_file_or_pipe(arg): if not os.path.exists(arg) or os.path.isdir(arg): parser.error('The file {0} does not exist!'.format(arg)) else: return arg def is_dir(arg): if not os.path.isdir(arg): parser.error('The directory {0} does not exist!'.format(arg)) else: return arg parser = argparse.ArgumentParser(description=__doc__) parser.add_argument('-v', '--version', action='version', version=version) parser.add_argument('template', help='The mustache file', type=is_file_or_pipe) parser.add_argument('-d', '--data', dest='data', help='The json data file', type=is_file_or_pipe, default={}) parser.add_argument('-y', '--yaml-loader', dest='yaml_loader', help=argparse.SUPPRESS) parser.add_argument('-p', '--path', dest='partials_path', help='The directory where your partials reside', type=is_dir, default='.') parser.add_argument('-e', '--ext', dest='partials_ext', help='The extension for your mustache\ partials, \'mustache\' by default', default='mustache') parser.add_argument('-l', '--left-delimiter', dest='def_ldel', help='The default left delimiter, "{{" by default.', default='{{') parser.add_argument('-r', '--right-delimiter', dest='def_rdel', help='The default right delimiter, "}}" by default.', default='}}') parser.add_argument('-w', '--warn', dest='warn', help='Print a warning to stderr for each undefined template key encountered', action='store_true') args = vars(parser.parse_args()) try: sys.stdout.write(main(**args)) sys.stdout.flush() except SyntaxError as e: print('Chevron: syntax error') sys.exit(' ' + '\n '.join(e.args[0].split('\n'))) if __name__ == '__main__': cli_main() ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1609625341.0 chevron-0.14.0/chevron/metadata.py0000644000175000017310000000002300000000000015701 0ustar00noahusersversion = '0.14.0' ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1609622764.0 chevron-0.14.0/chevron/renderer.py0000644000175000017310000003137100000000000015741 0ustar00noahusers# -*- coding: utf-8 -*- import io from os import linesep, path try: from collections.abc import Sequence, Iterator, Callable except ImportError: # python 2 from collections import Sequence, Iterator, Callable try: from .tokenizer import tokenize except (ValueError, SystemError): # python 2 from tokenizer import tokenize import sys if sys.version_info[0] == 3: python3 = True unicode_type = str string_type = str def unicode(x, y): return x else: # python 2 python3 = False unicode_type = unicode string_type = basestring # noqa: F821 (This is defined in python2) # # Helper functions # def _html_escape(string): """HTML escape all of these " & < >""" html_codes = { '"': '"', '<': '<', '>': '>', } # & must be handled first string = string.replace('&', '&') for char in html_codes: string = string.replace(char, html_codes[char]) return string def _get_key(key, scopes, warn=False): """Get a key from the current scope""" # If the key is a dot if key == '.': # Then just return the current scope return scopes[0] # Loop through the scopes for scope in scopes: try: # For every dot seperated key for child in key.split('.'): # Move into the scope try: # Try subscripting (Normal dictionaries) scope = scope[child] except (TypeError, AttributeError): try: scope = getattr(scope, child) except (TypeError, AttributeError): # Try as a list scope = scope[int(child)] # Return an empty string if falsy, with two exceptions # 0 should return 0, and False should return False if scope in (0, False): return scope try: # This allows for custom falsy data types # https://github.com/noahmorrison/chevron/issues/35 if scope._CHEVRON_return_scope_when_falsy: return scope except AttributeError: return scope or '' except (AttributeError, KeyError, IndexError, ValueError): # We couldn't find the key in the current scope # We'll try again on the next pass pass # We couldn't find the key in any of the scopes if warn: sys.stderr.write("Could not find key '%s'%s" % (key, linesep)) return '' def _get_partial(name, partials_dict, partials_path, partials_ext): """Load a partial""" try: # Maybe the partial is in the dictionary return partials_dict[name] except KeyError: # Don't try loading from the file system if the partials_path is None or empty if partials_path is None or partials_path == '': return '' # Nope... try: # Maybe it's in the file system path_ext = ('.' + partials_ext if partials_ext else '') partial_path = path.join(partials_path, name + path_ext) with io.open(partial_path, 'r', encoding='utf-8') as partial: return partial.read() except IOError: # Alright I give up on you return '' # # The main rendering function # g_token_cache = {} def render(template='', data={}, partials_path='.', partials_ext='mustache', partials_dict={}, padding='', def_ldel='{{', def_rdel='}}', scopes=None, warn=False): """Render a mustache template. Renders a mustache template with a data scope and partial capability. Given the file structure... ╷ ├─╼ main.py ├─╼ main.ms └─┮ partials └── part.ms then main.py would make the following call: render(open('main.ms', 'r'), {...}, 'partials', 'ms') Arguments: template -- A file-like object or a string containing the template data -- A python dictionary with your data scope partials_path -- The path to where your partials are stored If set to None, then partials won't be loaded from the file system (defaults to '.') partials_ext -- The extension that you want the parser to look for (defaults to 'mustache') partials_dict -- A python dictionary which will be search for partials before the filesystem is. {'include': 'foo'} is the same as a file called include.mustache (defaults to {}) padding -- This is for padding partials, and shouldn't be used (but can be if you really want to) def_ldel -- The default left delimiter ("{{" by default, as in spec compliant mustache) def_rdel -- The default right delimiter ("}}" by default, as in spec compliant mustache) scopes -- The list of scopes that get_key will look through warn -- Issue a warning to stderr when a template substitution isn't found in the data Returns: A string containing the rendered template. """ # If the template is a seqeuence but not derived from a string if isinstance(template, Sequence) and \ not isinstance(template, string_type): # Then we don't need to tokenize it # But it does need to be a generator tokens = (token for token in template) else: if template in g_token_cache: tokens = (token for token in g_token_cache[template]) else: # Otherwise make a generator tokens = tokenize(template, def_ldel, def_rdel) output = unicode('', 'utf-8') if scopes is None: scopes = [data] # Run through the tokens for tag, key in tokens: # Set the current scope current_scope = scopes[0] # If we're an end tag if tag == 'end': # Pop out of the latest scope del scopes[0] # If the current scope is falsy and not the only scope elif not current_scope and len(scopes) != 1: if tag in ['section', 'inverted section']: # Set the most recent scope to a falsy value # (I heard False is a good one) scopes.insert(0, False) # If we're a literal tag elif tag == 'literal': # Add padding to the key and add it to the output if not isinstance(key, unicode_type): # python 2 key = unicode(key, 'utf-8') output += key.replace('\n', '\n' + padding) # If we're a variable tag elif tag == 'variable': # Add the html escaped key to the output thing = _get_key(key, scopes, warn=warn) if thing is True and key == '.': # if we've coerced into a boolean by accident # (inverted tags do this) # then get the un-coerced object (next in the stack) thing = scopes[1] if not isinstance(thing, unicode_type): thing = unicode(str(thing), 'utf-8') output += _html_escape(thing) # If we're a no html escape tag elif tag == 'no escape': # Just lookup the key and add it thing = _get_key(key, scopes, warn=warn) if not isinstance(thing, unicode_type): thing = unicode(str(thing), 'utf-8') output += thing # If we're a section tag elif tag == 'section': # Get the sections scope scope = _get_key(key, scopes, warn=warn) # If the scope is a callable (as described in # https://mustache.github.io/mustache.5.html) if isinstance(scope, Callable): # Generate template text from tags text = unicode('', 'utf-8') tags = [] for tag in tokens: if tag == ('end', key): break tags.append(tag) tag_type, tag_key = tag if tag_type == 'literal': text += tag_key elif tag_type == 'no escape': text += "%s& %s %s" % (def_ldel, tag_key, def_rdel) else: text += "%s%s %s%s" % (def_ldel, { 'commment': '!', 'section': '#', 'inverted section': '^', 'end': '/', 'partial': '>', 'set delimiter': '=', 'no escape': '&', 'variable': '' }[tag_type], tag_key, def_rdel) g_token_cache[text] = tags rend = scope(text, lambda template, data=None: render(template, data={}, partials_path=partials_path, partials_ext=partials_ext, partials_dict=partials_dict, padding=padding, def_ldel=def_ldel, def_rdel=def_rdel, scopes=data and [data]+scopes or scopes, warn=warn)) if python3: output += rend else: # python 2 output += rend.decode('utf-8') # If the scope is a sequence, an iterator or generator but not # derived from a string elif isinstance(scope, (Sequence, Iterator)) and \ not isinstance(scope, string_type): # Then we need to do some looping # Gather up all the tags inside the section # (And don't be tricked by nested end tags with the same key) # TODO: This feels like it still has edge cases, no? tags = [] tags_with_same_key = 0 for tag in tokens: if tag == ('section', key): tags_with_same_key += 1 if tag == ('end', key): tags_with_same_key -= 1 if tags_with_same_key < 0: break tags.append(tag) # For every item in the scope for thing in scope: # Append it as the most recent scope and render new_scope = [thing] + scopes rend = render(template=tags, scopes=new_scope, padding=padding, partials_path=partials_path, partials_ext=partials_ext, partials_dict=partials_dict, def_ldel=def_ldel, def_rdel=def_rdel, warn=warn) if python3: output += rend else: # python 2 output += rend.decode('utf-8') else: # Otherwise we're just a scope section scopes.insert(0, scope) # If we're an inverted section elif tag == 'inverted section': # Add the flipped scope to the scopes scope = _get_key(key, scopes, warn=warn) scopes.insert(0, not scope) # If we're a partial elif tag == 'partial': # Load the partial partial = _get_partial(key, partials_dict, partials_path, partials_ext) # Find what to pad the partial with left = output.rpartition('\n')[2] part_padding = padding if left.isspace(): part_padding += left # Render the partial part_out = render(template=partial, partials_path=partials_path, partials_ext=partials_ext, partials_dict=partials_dict, def_ldel=def_ldel, def_rdel=def_rdel, padding=part_padding, scopes=scopes, warn=warn) # If the partial was indented if left.isspace(): # then remove the spaces from the end part_out = part_out.rstrip(' \t') # Add the partials output to the ouput if python3: output += part_out else: # python 2 output += part_out.decode('utf-8') if python3: return output else: # python 2 return output.encode('utf-8') ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1609619590.0 chevron-0.14.0/chevron/tokenizer.py0000644000175000017310000001661000000000000016144 0ustar00noahusers# Globals _CURRENT_LINE = 1 _LAST_TAG_LINE = None class ChevronError(SyntaxError): pass # # Helper functions # def grab_literal(template, l_del): """Parse a literal from the template""" global _CURRENT_LINE try: # Look for the next tag and move the template to it literal, template = template.split(l_del, 1) _CURRENT_LINE += literal.count('\n') return (literal, template) # There are no more tags in the template? except ValueError: # Then the rest of the template is a literal return (template, '') def l_sa_check(template, literal, is_standalone): """Do a preliminary check to see if a tag could be a standalone""" # If there is a newline, or the previous tag was a standalone if literal.find('\n') != -1 or is_standalone: padding = literal.split('\n')[-1] # If all the characters since the last newline are spaces if padding.isspace() or padding == '': # Then the next tag could be a standalone return True else: # Otherwise it can't be return False def r_sa_check(template, tag_type, is_standalone): """Do a final checkto see if a tag could be a standalone""" # Check right side if we might be a standalone if is_standalone and tag_type not in ['variable', 'no escape']: on_newline = template.split('\n', 1) # If the stuff to the right of us are spaces we're a standalone if on_newline[0].isspace() or not on_newline[0]: return True else: return False # If we're a tag can't be a standalone else: return False def parse_tag(template, l_del, r_del): """Parse a tag from a template""" global _CURRENT_LINE global _LAST_TAG_LINE tag_types = { '!': 'comment', '#': 'section', '^': 'inverted section', '/': 'end', '>': 'partial', '=': 'set delimiter?', '{': 'no escape?', '&': 'no escape' } # Get the tag try: tag, template = template.split(r_del, 1) except ValueError: raise ChevronError('unclosed tag ' 'at line {0}'.format(_CURRENT_LINE)) # Find the type meaning of the first character tag_type = tag_types.get(tag[0], 'variable') # If the type is not a variable if tag_type != 'variable': # Then that first character is not needed tag = tag[1:] # If we might be a set delimiter tag if tag_type == 'set delimiter?': # Double check to make sure we are if tag.endswith('='): tag_type = 'set delimiter' # Remove the equal sign tag = tag[:-1] # Otherwise we should complain else: raise ChevronError('unclosed set delimiter tag\n' 'at line {0}'.format(_CURRENT_LINE)) # If we might be a no html escape tag elif tag_type == 'no escape?': # And we have a third curly brace # (And are using curly braces as delimiters) if l_del == '{{' and r_del == '}}' and template.startswith('}'): # Then we are a no html escape tag template = template[1:] tag_type = 'no escape' # Strip the whitespace off the key and return return ((tag_type, tag.strip()), template) # # The main tokenizing function # def tokenize(template, def_ldel='{{', def_rdel='}}'): """Tokenize a mustache template Tokenizes a mustache template in a generator fashion, using file-like objects. It also accepts a string containing the template. Arguments: template -- a file-like object, or a string of a mustache template def_ldel -- The default left delimiter ("{{" by default, as in spec compliant mustache) def_rdel -- The default right delimiter ("}}" by default, as in spec compliant mustache) Returns: A generator of mustache tags in the form of a tuple -- (tag_type, tag_key) Where tag_type is one of: * literal * section * inverted section * end * partial * no escape And tag_key is either the key or in the case of a literal tag, the literal itself. """ global _CURRENT_LINE, _LAST_TAG_LINE _CURRENT_LINE = 1 _LAST_TAG_LINE = None # If the template is a file-like object then read it try: template = template.read() except AttributeError: pass is_standalone = True open_sections = [] l_del = def_ldel r_del = def_rdel while template: literal, template = grab_literal(template, l_del) # If the template is completed if not template: # Then yield the literal and leave yield ('literal', literal) break # Do the first check to see if we could be a standalone is_standalone = l_sa_check(template, literal, is_standalone) # Parse the tag tag, template = parse_tag(template, l_del, r_del) tag_type, tag_key = tag # Special tag logic # If we are a set delimiter tag if tag_type == 'set delimiter': # Then get and set the delimiters dels = tag_key.strip().split(' ') l_del, r_del = dels[0], dels[-1] # If we are a section tag elif tag_type in ['section', 'inverted section']: # Then open a new section open_sections.append(tag_key) _LAST_TAG_LINE = _CURRENT_LINE # If we are an end tag elif tag_type == 'end': # Then check to see if the last opened section # is the same as us try: last_section = open_sections.pop() except IndexError: raise ChevronError('Trying to close tag "{0}"\n' 'Looks like it was not opened.\n' 'line {1}' .format(tag_key, _CURRENT_LINE + 1)) if tag_key != last_section: # Otherwise we need to complain raise ChevronError('Trying to close tag "{0}"\n' 'last open tag is "{1}"\n' 'line {2}' .format(tag_key, last_section, _CURRENT_LINE + 1)) # Do the second check to see if we're a standalone is_standalone = r_sa_check(template, tag_type, is_standalone) # Which if we are if is_standalone: # Remove the stuff before the newline template = template.split('\n', 1)[-1] # Partials need to keep the spaces on their left if tag_type != 'partial': # But other tags don't literal = literal.rstrip(' ') # Start yielding # Ignore literals that are empty if literal != '': yield ('literal', literal) # Ignore comments and set delimiters if tag_type not in ['comment', 'set delimiter?']: yield (tag_type, tag_key) # If there are any open sections when we're done if open_sections: # Then we need to complain raise ChevronError('Unexpected EOF\n' 'the tag "{0}" was never closed\n' 'was opened at line {1}' .format(open_sections[-1], _LAST_TAG_LINE)) ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1609625345.5792255 chevron-0.14.0/chevron.egg-info/0000755000175000017310000000000000000000000015246 5ustar00noahusers././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1609625345.0 chevron-0.14.0/chevron.egg-info/PKG-INFO0000644000175000017310000001426600000000000016354 0ustar00noahusersMetadata-Version: 2.1 Name: chevron Version: 0.14.0 Summary: Mustache templating language renderer Home-page: https://github.com/noahmorrison/chevron Author: noah morrison Author-email: noah@morrison.ph License: MIT Description: [![PyPI version](https://badge.fury.io/py/chevron.svg)](https://badge.fury.io/py/chevron) [![Build Status](https://travis-ci.org/noahmorrison/chevron.svg?branch=master)](https://travis-ci.org/noahmorrison/chevron) [![Coverage Status](https://coveralls.io/repos/github/noahmorrison/chevron/badge.svg?branch=master)](https://coveralls.io/github/noahmorrison/chevron?branch=master) A python implementation of the [mustache templating language](http://mustache.github.io). Why chevron? ------------ I'm glad you asked! ### chevron is fast ### Chevron runs in less than half the time of [pystache](http://github.com/defunkt/pystache) (Which is not even up to date on the spec). And in about 70% the time of [Stache](https://github.com/hyperturtle/Stache) (A 'trimmed' version of mustache, also not spec compliant). ### chevron is pep8 ### The flake8 command is run by [travis](https://travis-ci.org/noahmorrison/chevron) to ensure consistency. ### chevron is spec compliant ### Chevron passes all the unittests provided by the [spec](https://github.com/mustache/spec) (in every version listed below). If you find a test that chevron does not pass, please [report it.](https://github.com/noahmorrison/chevron/issues/new) ### chevron is Python 2 and 3 compatible ### Python 2.6, 2.7, 3.2, 3.3, 3.4, 3.5, and 3.6 are all tested by travis. USAGE ----- Commandline usage: (if installed via pypi) ``` usage: chevron [-h] [-v] [-d DATA] [-p PARTIALS_PATH] [-e PARTIALS_EXT] [-l DEF_LDEL] [-r DEF_RDEL] template positional arguments: template The mustache file optional arguments: -h, --help show this help message and exit -v, --version show program's version number and exit -d DATA, --data DATA The json data file -p PARTIALS_PATH, --path PARTIALS_PATH The directory where your partials reside -e PARTIALS_EXT, --ext PARTIALS_EXT The extension for your mustache partials, 'mustache' by default -l DEF_LDEL, --left-delimiter DEF_LDEL The default left delimiter, "{{" by default. -r DEF_RDEL, --right-delimiter DEF_RDEL The default right delimiter, "}}" by default. ``` Python usage with strings ```python import chevron chevron.render('Hello, {{ mustache }}!', {'mustache': 'World'}) ``` Python usage with file ```python import chevron with open('file.mustache', 'r') as f: chevron.render(f, {'mustache': 'World'}) ``` Python usage with unpacking ```python import chevron args = { 'template': 'Hello, {{ mustache }}!', 'data': { 'mustache': 'World' } } chevron.render(**args) ``` chevron supports partials (via dictionaries) ```python import chevron args = { 'template': 'Hello, {{> thing }}!', 'partials_dict': { 'thing': 'World' } } chevron.render(**args) ``` chevron supports partials (via the filesystem) ```python import chevron args = { 'template': 'Hello, {{> thing }}!', # defaults to . 'partials_path': 'partials/', # defaults to mustache 'partials_ext': 'ms', } # ./partials/thing.ms will be read and rendered chevron.render(**args) ``` chevron supports lambdas ```python import chevron def first(text, render): # return only first occurance of items result = render(text) return [ x.strip() for x in result.split(" || ") if x.strip() ][0] def inject_x(text, render): # inject data into scope return render(text, {'x': 'data'}) args = { 'template': 'Hello, {{# first}} {{x}} || {{y}} || {{z}} {{/ first}}! {{# inject_x}} {{x}} {{/ inject_x}}', 'data': { 'y': 'foo', 'z': 'bar', 'first': first, 'inject_x': inject_x } } chevron.render(**args) ``` INSTALL ------- - with git ``` $ git clone https://github.com/noahmorrison/chevron.git ``` or using submodules ``` $ git submodules add https://github.com/noahmorrison/chevron.git ``` Also available on pypi! - with pip ``` $ pip install chevron ``` TODO --- * get popular * have people complain * fix those complaints Platform: UNKNOWN Classifier: Development Status :: 4 - Beta Classifier: License :: OSI Approved :: MIT License Classifier: Programming Language :: Python :: 2.6 Classifier: Programming Language :: Python :: 2.7 Classifier: Programming Language :: Python :: 3.2 Classifier: Programming Language :: Python :: 3.3 Classifier: Programming Language :: Python :: 3.4 Classifier: Programming Language :: Python :: 3.5 Classifier: Programming Language :: Python :: 3.6 Classifier: Topic :: Text Processing :: Markup Description-Content-Type: text/markdown ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1609625345.0 chevron-0.14.0/chevron.egg-info/SOURCES.txt0000644000175000017310000000043500000000000017134 0ustar00noahusersMANIFEST.in README.md setup.py chevron/__init__.py chevron/main.py chevron/metadata.py chevron/renderer.py chevron/tokenizer.py chevron.egg-info/PKG-INFO chevron.egg-info/SOURCES.txt chevron.egg-info/dependency_links.txt chevron.egg-info/entry_points.txt chevron.egg-info/top_level.txt././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1609625345.0 chevron-0.14.0/chevron.egg-info/dependency_links.txt0000644000175000017310000000000100000000000021314 0ustar00noahusers ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1609625345.0 chevron-0.14.0/chevron.egg-info/entry_points.txt0000644000175000017310000000005600000000000020545 0ustar00noahusers[console_scripts] chevron = chevron:cli_main ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1609625345.0 chevron-0.14.0/chevron.egg-info/top_level.txt0000644000175000017310000000001000000000000017767 0ustar00noahuserschevron ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1609625345.5792255 chevron-0.14.0/setup.cfg0000644000175000017310000000004600000000000013731 0ustar00noahusers[egg_info] tag_build = tag_date = 0 ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1609619590.0 chevron-0.14.0/setup.py0000644000175000017310000000226300000000000013625 0ustar00noahusers#!/usr/bin/env python import chevron.metadata try: from setuptools import setup except ImportError: from distutils.core import setup with open('README.md') as f: readme = f.read() setup(name='chevron', version=chevron.metadata.version, license='MIT', description='Mustache templating language renderer', long_description=readme, long_description_content_type='text/markdown', author='noah morrison', author_email='noah@morrison.ph', url='https://github.com/noahmorrison/chevron', packages=['chevron'], entry_points={ 'console_scripts': ['chevron=chevron:cli_main'] }, classifiers=[ 'Development Status :: 4 - Beta', 'License :: OSI Approved :: MIT License', 'Programming Language :: Python :: 2.6', 'Programming Language :: Python :: 2.7', 'Programming Language :: Python :: 3.2', 'Programming Language :: Python :: 3.3', 'Programming Language :: Python :: 3.4', 'Programming Language :: Python :: 3.5', 'Programming Language :: Python :: 3.6', 'Topic :: Text Processing :: Markup' ] )