Skip to content

Commit

Permalink
Merge pull request #47 from wbond/raw_blocks
Browse files Browse the repository at this point in the history
Implement raw blocks
  • Loading branch information
isaacdd authored Oct 5, 2018
2 parents 38dc8b7 + 3a20049 commit bfae5b8
Show file tree
Hide file tree
Showing 6 changed files with 175 additions and 56 deletions.
75 changes: 40 additions & 35 deletions changelog.md
Original file line number Diff line number Diff line change
@@ -1,65 +1,70 @@
# changelog

## 0.9.4

- Add support for raw blocks

## 0.9.3
- Add support for dynamic partials
- Add travis integration (flake8 and coverage)

- Add support for dynamic partials
- Add travis integration (flake8 and coverage)

## 0.9.2

- Make dictionary calls take precedence over attributes and .get (issue #16)
- Add else support in inverted if blocks (issue #17)
- Improve error parsing and reporting (issue #23)
- Port over a few more tests from handlebars.js
- Make dictionary calls take precedence over attributes and .get (issue #16)
- Add else support in inverted if blocks (issue #17)
- Improve error parsing and reporting (issue #23)
- Port over a few more tests from handlebars.js

## 0.9.1

- Changed `pybars.__version__` tuple to `pybars.__version_info__`,
`pybars.__version__` is now a string
- Added `Compiler().precompile(source)` that will return Python source code
to allow for caching of compiled templates and easier debugging
- Template code now checks to ensure it is being run with the same version of
pybars that is was generated with
- Changed `pybars.__version__` tuple to `pybars.__version_info__`,
`pybars.__version__` is now a string
- Added `Compiler().precompile(source)` that will return Python source code
to allow for caching of compiled templates and easier debugging
- Template code now checks to ensure it is being run with the same version of
pybars that is was generated with

## 0.8.0

- The library now always throws `pybars.PybarsError` on errors
- Added support for nested subexpressions
- Block helpers by themselves on lines no longer introduce any whitespace
- Added `tests.py` test runner and `--debug` flag
- Moved tests out of `pybars` namespace
- The library now always throws `pybars.PybarsError` on errors
- Added support for nested subexpressions
- Block helpers by themselves on lines no longer introduce any whitespace
- Added `tests.py` test runner and `--debug` flag
- Moved tests out of `pybars` namespace

## 0.7.2

- Fixed a bug with nested scopes and parent paths (`../`)
- Fixed a bug with nested scopes and parent paths (`../`)

## 0.7.1

- Expose `pybars.Scope()` so helpers can properly scope data so `../` will work
- Expose `pybars.Scope()` so helpers can properly scope data so `../` will work

## 0.7.0

- Added subexpression support
- Added subexpression support

## 0.6.0

- Changed `False` to print `false` and `True` to print `true`
- Added support for `null` and `undefined` literals
- Changed `False` to print `false` and `True` to print `true`
- Added support for `null` and `undefined` literals

## 0.5.1

- Added support for segment literals (`[foo bar]`)
- Fixed a bug related to newlines in the middle of tags
- Added support for single-quoted string literals
- Added the `lookup` helper
- Added support for quoted partial names
- Added support for segment literals (`[foo bar]`)
- Fixed a bug related to newlines in the middle of tags
- Added support for single-quoted string literals
- Added the `lookup` helper
- Added support for quoted partial names

## 0.5.0

- Added support for negative integers
- Added support for parent acccess to data elements (`@../`)
- Added support for keyword args being passed to partials
- Added explicit `@_parent` data access
- Added `@root` data support
- Added `@index`, `@key`, `@first` and `@last` data access
- Added support for object attributes within the `#each` helper
- Added Python 3 support
- Added support for negative integers
- Added support for parent acccess to data elements (`@../`)
- Added support for keyword args being passed to partials
- Added explicit `@_parent` data access
- Added `@root` data support
- Added `@index`, `@key`, `@first` and `@last` data access
- Added support for object attributes within the `#each` helper
- Added Python 3 support
4 changes: 2 additions & 2 deletions pybars/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,8 @@
PybarsError
)

__version__ = '0.9.3'
__version_info__ = (0, 9, 3, 'final', 0)
__version__ = '0.9.4'
__version_info__ = (0, 9, 4, 'final', 0)

__all__ = [
'Compiler',
Expand Down
27 changes: 24 additions & 3 deletions pybars/_compiler.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@
| <escapedexpression>
| <expression>
| <partial>
| <rawblock>
start ::= '{' '{'
finish ::= '}' '}'
comment ::= <start> '!' (~(<finish>) <anything>)* <finish> => ('comment', )
Expand Down Expand Up @@ -121,8 +122,12 @@
blockrule ::= <start> '#' <block_inner>:i
<template>:t <alttemplate>:alt_t <symbolfinish i[0]> => ('block',) + i + (t, alt_t)
| <start> '^' <block_inner>:i
<template>:t <alttemplate>:alt_t <symbolfinish i[0]> => ('invertedblock',) + i + (t,alt_t)
<template>:t <alttemplate>:alt_t <symbolfinish i[0]> => ('invertedblock',) + i + (t, alt_t)
alttemplate ::= (<start> <alt_inner> <template>)?:alt_t => alt_t or []
rawblockstart ::= <start> <start> <block_inner>:i <finish> => i
rawblockfinish :expected ::= <start> <symbolfinish expected> <finish>
rawblock ::= <rawblockstart>:i (~(<rawblockfinish i[0]>) <anything>)*:r <rawblockfinish i[0]>
=> ('rawblock',) + i + (''.join(r),)
"""

# this grammar compiles the template to python
Expand All @@ -135,13 +140,15 @@
| <comment>
| <block>
| <invertedblock>
| <rawblock>
| <partial>
block ::= [ "block" <anything>:symbol [<arg>*:arguments] [<compile>:t] [<compile>?:alt_t] ] => builder.add_block(symbol, arguments, t, alt_t)
comment ::= [ "comment" ]
literal ::= [ ( "literal" | "newline" | "whitespace" ) :value ] => builder.add_literal(value)
expand ::= [ "expand" <path>:value [<arg>*:arguments]] => builder.add_expand(value, arguments)
escapedexpand ::= [ "escapedexpand" <path>:value [<arg>*:arguments]] => builder.add_escaped_expand(value, arguments)
invertedblock ::= [ "invertedblock" <anything>:symbol [<arg>*:arguments] [<compile>:t] [<compile>?:alt_t] ] => builder.add_invertedblock(symbol, arguments, t, alt_t)
rawblock ::= [ "rawblock" <anything>:symbol [<arg>*:arguments] <anything>:raw ] => builder.add_rawblock(symbol, arguments, raw)
partial ::= ["partial" <complexarg>:symbol [<arg>*:arguments]] => builder.add_partial(symbol, arguments)
path ::= [ "path" [<pathseg>:segment]] => ("simple", segment)
| [ "path" [<pathseg>+:segments] ] => ("complex", u"resolve(context, '" + u"', '".join(segments) + u"')" )
Expand Down Expand Up @@ -646,8 +653,6 @@ def _debug(self):
self._result.grow(u" import pdb;pdb.set_trace()\n")

def add_invertedblock(self, symbol, arguments, nested, alt_nested):
# This may need to be a blockHelperMissing clal as well.

name = nested.name
self._locals[name] = nested

Expand Down Expand Up @@ -683,6 +688,22 @@ def add_invertedblock(self, symbol, arguments, nested, alt_nested):
u" result.grow(value or '')\n"
])

def add_rawblock(self, symbol, arguments, raw):
call = self.arguments_to_call(arguments)
self._result.grow([
u" options = {'fn': lambda this: %s}\n" % repr(raw),
u" options['helpers'] = helpers\n"
u" options['partials'] = partials\n"
u" options['root'] = root\n"
u" options['inverse'] = lambda this: None\n"
u" helper = helpers.get('%s')\n" % symbol,
u" if helper and hasattr(helper, '__call__'):\n"
u" value = helper(context, options%s\n" % call,
u" else:\n"
u" value = %s\n" % repr(raw),
u" result.grow(value or '')\n"
])

def _invoke_template(self, fn_name, this_name):
self._result.grow([
u" result.grow(",
Expand Down
31 changes: 16 additions & 15 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,24 +26,25 @@ currently implemented.

Here is a partial list of features that are supported:

- `@root` root data accesor (Handlebars 2.0)
- `@_parent` parent scope accesor (Handlebars 2.0)
- `../` parent scope accessor
- `@index`, `@key` (Handlebars 1.0, 1.2)
- `@first` and `@last` data element in the `#each` helper (Handlebars 1.1)
- kwargs passed to partials (Handlebars 2.0)
- `@../index` syntax for accessing parent scope data items (Handlebars 2.0)
- `{{[segment literal notation]}}` for paths that contain non-word chars (Handlebars 1.1)
- `{{> "quoted partial name"}}` for partials that contain non-word chars (Handlebars 1.1)
- `lookup` helper for dynamic name access (Handlebars 2.0)
- Subexpresions (Handlebars 1.3)
- Lines containing only block statements and whitespace are removed (Handlebars 2.0)
- `pybars.Compiler().precompile()` that is equivalent to `Handlebars.precompile()`
- [Dynamic partials](http://handlebarsjs.com/partials.html#dynamic-partials) `{{> (whichPartial) }}` (Handlebars ???)
- `@root` root data accesor (Handlebars 2.0)
- `@_parent` parent scope accesor (Handlebars 2.0)
- `../` parent scope accessor
- `@index`, `@key` (Handlebars 1.0, 1.2)
- `@first` and `@last` data element in the `#each` helper (Handlebars 1.1)
- kwargs passed to partials (Handlebars 2.0)
- `@../index` syntax for accessing parent scope data items (Handlebars 2.0)
- `{{[segment literal notation]}}` for paths that contain non-word chars (Handlebars 1.1)
- `{{> "quoted partial name"}}` for partials that contain non-word chars (Handlebars 1.1)
- `lookup` helper for dynamic name access (Handlebars 2.0)
- Subexpresions (Handlebars 1.3)
- Lines containing only block statements and whitespace are removed (Handlebars 2.0)
- `pybars.Compiler().precompile()` that is equivalent to `Handlebars.precompile()`
- `{{> (whichPartial) }}` dynamic partials (Handlebars 3.0)
- `{{{{raw}}}}{{escaped}}{{{{/raw}}}}` raw blocks (Handlebars 2.0)

Features not currently implemented:

- Whitespace control, `{{var~}}` (Handlebars 1.1)
- Whitespace control, `{{var~}}` (Handlebars 1.1)

Feel free to jump in with issues or pull requests.

Expand Down
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@


setup(name='pybars3',
version='0.9.3',
version='0.9.4',
description='Handlebars.js templating for Python 3 and 2',
long_description='Documentation is maintained at https://github.com/wbond/pybars3#readme',
author='wbond, mjumbewu',
Expand Down
92 changes: 92 additions & 0 deletions tests/test_acceptance.py
Original file line number Diff line number Diff line change
Expand Up @@ -2403,3 +2403,95 @@ class MyDict(dict):

self.assertRender(template, context, result)
self.assertRender(template, context2, result2)

def test_raw_block(self):
template = u"{{{{raw}}}}{{escaped}}{{{{/raw}}}}"
context = {}
result = u"{{escaped}}"
self.assertRender(template, context, result)

def test_raw_block_in_block(self):
template = u"{{#valid}}{{{{raw}}}}{{escaped}}{{{{/raw}}}}{{/valid}}"
context = {
"valid": True
}
result = u"{{escaped}}"
self.assertRender(template, context, result)

def test_raw_block_in_inverted_block(self):
template = u"{{^valid}}{{{{raw}}}}{{escaped}}{{{{/raw}}}}{{/valid}}"
context = {
"valid": False
}
result = u"{{escaped}}"
self.assertRender(template, context, result)

def test_raw_block_with_spaces(self):
template = u"this is a{{{{raw}}}} {{ .raw.block }}! {{{{/raw}}}}"
context = {
"valid": True
}
result = u"this is a {{ .raw.block }}! "
self.assertRender(template, context, result)

def test_raw_block_with_helper_that_gets_raw_content(self):
template = u"{{{{raw}}}} {{test}} {{{{/raw}}}}"
context = {
"test": "hello"
}
helpers = {
"raw": lambda this, options: options['fn'](this)
}
result = u" {{test}} "
self.assertRender(template, context, result, helpers)

def test_raw_block_with_helper_that_takes_args(self):
template = u"{{{{raw \"sky\" \"is\" \"blue\"}}}}the {{{{/raw}}}}"
context = {
"test": u"hello"
}
helpers = {
"raw": lambda this, options, a, b, c: options['fn'](this) + ' '.join([a, b, c])
}
result = u"the sky is blue"
self.assertRender(template, context, result, helpers)

def test_raw_block_with_helper_that_takes_kwargs(self):
template1 = u"{{{{underline style=\"dotted\" width=18}}}} {{ Book Title }} {{{{/underline}}}}"
template2 = u"{{{{underline}}}} {{ Book Title }} {{{{/underline}}}}"
context = {}

def underline(this, options, style=None, width=10):
pattern = u'.' if style == 'dotted' else u'-'
return options['fn'](this) + u"\n" + pattern * width

helpers = {
"underline": underline
}
result1 = u"\n".join([u" {{ Book Title }} ",
u".................."])
result2 = u"\n".join([u" {{ Book Title }} ",
u"----------"])
self.assertRender(template1, context, result1, helpers)
self.assertRender(template2, context, result2, helpers)

def test_nested_raw_block_with_helper_that_gets_raw_content(self):
template = u"{{{{a}}}} {{{{b}}}} {{{{/b}}}} {{{{/a}}}}"
context = {}
helpers = {
"a": lambda this, options: options['fn'](this)
}
result = u" {{{{b}}}} {{{{/b}}}} "
self.assertRender(template, context, result, helpers)

def test_nested_raw_blocks(self):
template = u"{{{{a}}}} {{{{b}}}} {{{{/b}}}} {{{{/a}}}}"
context = {}
result = u" {{{{b}}}} {{{{/b}}}} "
self.assertRender(template, context, result)

def test_markdow_and_html_in_raw_block(self):
template = u"{{{{markdown}}}}**HTML** <a href='#'>link</a>{{{{/markdown}}}}"
context = {}
result = u"**HTML** <a href='#'>link</a>"
self.assertRender(template, context, result)

0 comments on commit bfae5b8

Please sign in to comment.