blob: d268cc5cd9237d937bd6cd7884acf9e0b084ffd1 [file] [log] [blame]
Andrew Geisslereff27472021-10-29 15:35:00 -05001From 8bb1ac2d81f697598a766714f2c439d78c85d71e Mon Sep 17 00:00:00 2001
2From: Stephen L Arnold <nerdboy@gentoo.org>
3Date: Sat, 7 Nov 2020 12:38:33 -0800
4Subject: [PATCH] Modernize python versions (remove py2x) and fix tests, update
5 spec
6
7* migrate to github actions for CI, add conda recipe/workflow
8* fix document processing, update pandoc args and history
9* convert doctests and modules to py3
10* convert packaging/setup.py to pep517, keep doc processing
11* cleanup tox cfg, add coverage, readme status
12* add pep8speaks cfg, cleanup warnings, use correct env
13* update setup_description.rst for packaging
14* set version for test release => 0.6.0 and deploy
15
16Upstream-Status: Backport [https://github.com/defunkt/pystache/pull/214]
17Signed-off-by: Stephen L Arnold <nerdboy@gentoo.org>
18---
19 .coveragerc | 38 +++
20 .gitchangelog.rc | 295 +++++++++++++++++++++
21 .github/workflows/ci.yml | 73 ++++++
22 .github/workflows/conda.yml | 55 ++++
23 .github/workflows/release.yml | 94 +++++++
24 .github/workflows/wheels.yml | 82 ++++++
25 .pep8speaks.yml | 15 ++
26 HISTORY.md | 37 ++-
27 MANIFEST.in | 8 +-
28 README.md | 141 +++++-----
29 TODO.md | 5 +-
30 conda/meta.yaml | 50 ++++
31 pyproject.toml | 3 +
32 pystache/__init__.py | 2 +-
33 pystache/commands/render.py | 4 +-
34 pystache/common.py | 13 +-
35 pystache/defaults.py | 2 +-
36 pystache/loader.py | 14 +-
37 pystache/parsed.py | 6 +-
38 pystache/parser.py | 20 +-
39 pystache/renderengine.py | 2 +-
40 pystache/renderer.py | 22 +-
41 pystache/specloader.py | 2 +-
42 pystache/tests/benchmark.py | 15 +-
43 pystache/tests/common.py | 10 +-
44 pystache/tests/examples/unicode_output.py | 2 +-
45 pystache/tests/main.py | 28 +-
46 pystache/tests/spectesting.py | 16 +-
47 pystache/tests/test___init__.py | 4 +-
48 pystache/tests/test_commands.py | 2 +-
49 pystache/tests/test_defaults.py | 18 +-
50 pystache/tests/test_examples.py | 40 +--
51 pystache/tests/test_loader.py | 46 ++--
52 pystache/tests/test_pystache.py | 6 +-
53 pystache/tests/test_renderengine.py | 148 +++++------
54 pystache/tests/test_renderer.py | 86 +++----
55 pystache/tests/test_simple.py | 20 +-
56 pystache/tests/test_specloader.py | 60 ++---
57 setup.cfg | 74 +++++-
58 setup.py | 134 +---------
59 setup_description.rst | 297 +++++++++++++---------
60 tox.ini | 118 +++++++--
61 travis.yml_disabled | 52 ++++
62 43 files changed, 1487 insertions(+), 672 deletions(-)
63 create mode 100644 .coveragerc
64 create mode 100644 .gitchangelog.rc
65 create mode 100644 .github/workflows/ci.yml
66 create mode 100644 .github/workflows/conda.yml
67 create mode 100644 .github/workflows/release.yml
68 create mode 100644 .github/workflows/wheels.yml
69 create mode 100644 .pep8speaks.yml
70 create mode 100644 conda/meta.yaml
71 create mode 100644 pyproject.toml
72 create mode 100644 travis.yml_disabled
73
74diff --git a/.coveragerc b/.coveragerc
75new file mode 100644
76index 0000000..9a336dd
77--- /dev/null
78+++ b/.coveragerc
79@@ -0,0 +1,38 @@
80+# .coveragerc to control coverage.py
81+[run]
82+branch = True
83+
84+source = pystache
85+
86+omit =
87+ .tox/*
88+ setup.py
89+ pystache/tests/*
90+
91+#plugins =
92+# coverage_python_version
93+
94+[report]
95+# must set this to True to see missing
96+#show_missing = True
97+
98+# Regexes for lines to exclude from consideration
99+exclude_lines =
100+ # Have to re-enable the standard pragma
101+ pragma: no cover
102+
103+ # Don't complain about missing debug-only code:
104+ def __repr__
105+ if self\.debug
106+
107+ # Don't complain if tests don't hit defensive assertion code:
108+ raise AssertionError
109+ raise NotImplementedError
110+
111+ # Don't complain if non-runnable code isn't run:
112+ if 0:
113+
114+ignore_errors = True
115+
116+[html]
117+directory = cover
118diff --git a/.gitchangelog.rc b/.gitchangelog.rc
119new file mode 100644
120index 0000000..5cf63a0
121--- /dev/null
122+++ b/.gitchangelog.rc
123@@ -0,0 +1,295 @@
124+# -*- coding: utf-8; mode: python -*-
125+##
126+## Format
127+##
128+## ACTION: [AUDIENCE:] COMMIT_MSG [!TAG ...]
129+##
130+## Description
131+##
132+## ACTION is one of 'chg', 'fix', 'new'
133+##
134+## Is WHAT the change is about.
135+##
136+## 'chg' is for refactor, small improvement, cosmetic changes...
137+## 'fix' is for bug fixes
138+## 'new' is for new features, big improvement
139+##
140+## AUDIENCE is optional and one of 'dev', 'usr', 'pkg', 'test', 'doc'
141+##
142+## Is WHO is concerned by the change.
143+##
144+## 'dev' is for developpers (API changes, refactors...)
145+## 'usr' is for final users (UI changes)
146+## 'pkg' is for packagers (packaging changes)
147+## 'test' is for testers (test only related changes)
148+## 'doc' is for doc guys (doc only changes)
149+##
150+## COMMIT_MSG is ... well ... the commit message itself.
151+##
152+## TAGs are additionnal adjective as 'refactor' 'minor' 'cosmetic'
153+##
154+## They are preceded with a '!' or a '@' (prefer the former, as the
155+## latter is wrongly interpreted in github.) Commonly used tags are:
156+##
157+## 'refactor' is obviously for refactoring code only
158+## 'minor' is for a very meaningless change (a typo, adding a comment)
159+## 'cosmetic' is for cosmetic driven change (re-indentation, 80-col...)
160+## 'wip' is for partial functionality but complete subfunctionality.
161+##
162+## Example:
163+##
164+## new: usr: support of bazaar implemented
165+## chg: re-indentend some lines !cosmetic
166+## new: dev: updated code to be compatible with last version of killer lib.
167+## fix: pkg: updated year of licence coverage.
168+## new: test: added a bunch of test around user usability of feature X.
169+## fix: typo in spelling my name in comment. !minor
170+##
171+## Please note that multi-line commit message are supported, and only the
172+## first line will be considered as the "summary" of the commit message. So
173+## tags, and other rules only applies to the summary. The body of the commit
174+## message will be displayed in the changelog without reformatting.
175+
176+
177+##
178+## ``ignore_regexps`` is a line of regexps
179+##
180+## Any commit having its full commit message matching any regexp listed here
181+## will be ignored and won't be reported in the changelog.
182+##
183+ignore_regexps = [
184+ r'@minor', r'!minor',
185+ r'@cosmetic', r'!cosmetic',
186+ r'@refactor', r'!refactor',
187+ r'@wip', r'!wip',
188+ r'^([cC]hg|[fF]ix|[nN]ew)\s*:\s*[p|P]kg:',
189+ r'^([cC]hg|[fF]ix|[nN]ew)\s*:\s*[d|D]ev:',
190+ r'^(.{3,3}\s*:)?\s*[fF]irst commit.?\s*$',
191+ r'^$', ## ignore commits with empty messages
192+]
193+
194+
195+## ``section_regexps`` is a list of 2-tuples associating a string label and a
196+## list of regexp
197+##
198+## Commit messages will be classified in sections thanks to this. Section
199+## titles are the label, and a commit is classified under this section if any
200+## of the regexps associated is matching.
201+##
202+## Please note that ``section_regexps`` will only classify commits and won't
203+## make any changes to the contents. So you'll probably want to go check
204+## ``subject_process`` (or ``body_process``) to do some changes to the subject,
205+## whenever you are tweaking this variable.
206+##
207+section_regexps = [
208+ ('New', [
209+ r'^[nN]ew\s*:\s*((dev|use?r|pkg|test|doc)\s*:\s*)?([^\n]*)$',
210+ ]),
211+ ('Features', [
212+ r'^([nN]ew|[fF]eat)\s*:\s*((dev|use?r|pkg|test|doc)\s*:\s*)?([^\n]*)$',
213+ ]),
214+ ('Changes', [
215+ r'^[cC]hg\s*:\s*((dev|use?r|pkg|test|doc)\s*:\s*)?([^\n]*)$',
216+ ]),
217+ ('Fixes', [
218+ r'^[fF]ix\s*:\s*((dev|use?r|pkg|test|doc)\s*:\s*)?([^\n]*)$',
219+ ]),
220+
221+ ('Other', None ## Match all lines
222+ ),
223+]
224+
225+
226+## ``body_process`` is a callable
227+##
228+## This callable will be given the original body and result will
229+## be used in the changelog.
230+##
231+## Available constructs are:
232+##
233+## - any python callable that take one txt argument and return txt argument.
234+##
235+## - ReSub(pattern, replacement): will apply regexp substitution.
236+##
237+## - Indent(chars=" "): will indent the text with the prefix
238+## Please remember that template engines gets also to modify the text and
239+## will usually indent themselves the text if needed.
240+##
241+## - Wrap(regexp=r"\n\n"): re-wrap text in separate paragraph to fill 80-Columns
242+##
243+## - noop: do nothing
244+##
245+## - ucfirst: ensure the first letter is uppercase.
246+## (usually used in the ``subject_process`` pipeline)
247+##
248+## - final_dot: ensure text finishes with a dot
249+## (usually used in the ``subject_process`` pipeline)
250+##
251+## - strip: remove any spaces before or after the content of the string
252+##
253+## - SetIfEmpty(msg="No commit message."): will set the text to
254+## whatever given ``msg`` if the current text is empty.
255+##
256+## Additionally, you can `pipe` the provided filters, for instance:
257+#body_process = Wrap(regexp=r'\n(?=\w+\s*:)') | Indent(chars=" ")
258+#body_process = Wrap(regexp=r'\n(?=\w+\s*:)')
259+#body_process = noop
260+body_process = ReSub(r'((^|\n)[A-Z]\w+(-\w+)*: .*(\n\s+.*)*)+$', r'') | strip
261+
262+
263+## ``subject_process`` is a callable
264+##
265+## This callable will be given the original subject and result will
266+## be used in the changelog.
267+##
268+## Available constructs are those listed in ``body_process`` doc.
269+subject_process = (strip |
270+ ReSub(r'^([cC]hg|[fF]ix|[nN]ew)\s*:\s*((dev|use?r|pkg|test|doc)\s*:\s*)?([^\n@]*)(@[a-z]+\s+)*$', r'\4') |
271+ SetIfEmpty("No commit message.") | ucfirst | final_dot)
272+
273+
274+## ``tag_filter_regexp`` is a regexp
275+##
276+## Tags that will be used for the changelog must match this regexp.
277+##
278+#tag_filter_regexp = r'^v?[0-9]+\.[0-9]+(\.[0-9]+)?$'
279+tag_filter_regexp = r'^[0-9]+\.[0-9]+(\.[0-9]+)?$'
280+
281+
282+## ``unreleased_version_label`` is a string or a callable that outputs a string
283+##
284+## This label will be used as the changelog Title of the last set of changes
285+## between last valid tag and HEAD if any.
286+unreleased_version_label = "(unreleased)"
287+#unreleased_version_label = lambda: swrap(
288+# ["git", "describe", "--tags"],
289+#shell=False)
290+
291+
292+## ``output_engine`` is a callable
293+##
294+## This will change the output format of the generated changelog file
295+##
296+## Available choices are:
297+##
298+## - rest_py
299+##
300+## Legacy pure python engine, outputs ReSTructured text.
301+## This is the default.
302+##
303+## - mustache(<template_name>)
304+##
305+## Template name could be any of the available templates in
306+## ``templates/mustache/*.tpl``.
307+## Requires python package ``pystache``.
308+## Examples:
309+## - mustache("markdown")
310+## - mustache("restructuredtext")
311+##
312+## - makotemplate(<template_name>)
313+##
314+## Template name could be any of the available templates in
315+## ``templates/mako/*.tpl``.
316+## Requires python package ``mako``.
317+## Examples:
318+## - makotemplate("restructuredtext")
319+##
320+#output_engine = rest_py
321+#output_engine = mustache("restructuredtext")
322+output_engine = mustache("markdown")
323+#output_engine = makotemplate("restructuredtext")
324+
325+
326+## ``include_merge`` is a boolean
327+##
328+## This option tells git-log whether to include merge commits in the log.
329+## The default is to include them.
330+include_merge = True
331+
332+
333+## ``log_encoding`` is a string identifier
334+##
335+## This option tells gitchangelog what encoding is outputed by ``git log``.
336+## The default is to be clever about it: it checks ``git config`` for
337+## ``i18n.logOutputEncoding``, and if not found will default to git's own
338+## default: ``utf-8``.
339+#log_encoding = 'utf-8'
340+
341+
342+## ``publish`` is a callable
343+##
344+## Sets what ``gitchangelog`` should do with the output generated by
345+## the output engine. ``publish`` is a callable taking one argument
346+## that is an interator on lines from the output engine.
347+##
348+## Some helper callable are provided:
349+##
350+## Available choices are:
351+##
352+## - stdout
353+##
354+## Outputs directly to standard output
355+## (This is the default)
356+##
357+## - FileInsertAtFirstRegexMatch(file, pattern, idx=lamda m: m.start(), flags)
358+##
359+## Creates a callable that will parse given file for the given
360+## regex pattern and will insert the output in the file.
361+## ``idx`` is a callable that receive the matching object and
362+## must return a integer index point where to insert the
363+## the output in the file. Default is to return the position of
364+## the start of the matched string.
365+##
366+## - FileRegexSubst(file, pattern, replace, flags)
367+##
368+## Apply a replace inplace in the given file. Your regex pattern must
369+## take care of everything and might be more complex. Check the README
370+## for a complete copy-pastable example.
371+##
372+# publish = FileInsertIntoFirstRegexMatch(
373+# "CHANGELOG.rst",
374+# r'/(?P<rev>[0-9]+\.[0-9]+(\.[0-9]+)?)\s+\([0-9]+-[0-9]{2}-[0-9]{2}\)\n--+\n/',
375+# idx=lambda m: m.start(1)
376+# )
377+#publish = stdout
378+
379+
380+## ``revs`` is a list of callable or a list of string
381+##
382+## callable will be called to resolve as strings and allow dynamical
383+## computation of these. The result will be used as revisions for
384+## gitchangelog (as if directly stated on the command line). This allows
385+## to filter exaclty which commits will be read by gitchangelog.
386+##
387+## To get a full documentation on the format of these strings, please
388+## refer to the ``git rev-list`` arguments. There are many examples.
389+##
390+## Using callables is especially useful, for instance, if you
391+## are using gitchangelog to generate incrementally your changelog.
392+##
393+## Some helpers are provided, you can use them::
394+##
395+## - FileFirstRegexMatch(file, pattern): will return a callable that will
396+## return the first string match for the given pattern in the given file.
397+## If you use named sub-patterns in your regex pattern, it'll output only
398+## the string matching the regex pattern named "rev".
399+##
400+## - Caret(rev): will return the rev prefixed by a "^", which is a
401+## way to remove the given revision and all its ancestor.
402+##
403+## Please note that if you provide a rev-list on the command line, it'll
404+## replace this value (which will then be ignored).
405+##
406+## If empty, then ``gitchangelog`` will act as it had to generate a full
407+## changelog.
408+##
409+## The default is to use all commits to make the changelog.
410+#revs = ["^1.0.3", ]
411+#revs = [
412+# Caret(
413+# FileFirstRegexMatch(
414+# "CHANGELOG.rst",
415+# r"(?P<rev>[0-9]+\.[0-9]+(\.[0-9]+)?)\s+\([0-9]+-[0-9]{2}-[0-9]{2}\)\n--+\n")),
416+# "HEAD"
417+#]
418+revs = []
419diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
420new file mode 100644
421index 0000000..356dd2c
422--- /dev/null
423+++ b/.github/workflows/ci.yml
424@@ -0,0 +1,73 @@
425+# This workflow will install Python dependencies, run tests and lint with a variety of Python versions
426+# For more information see: https://help.github.com/actions/language-and-framework-guides/using-python-with-github-actions
427+
428+name: ci
429+
430+on:
431+ push:
432+ branches: [ master ]
433+ pull_request:
434+ branches: [ master ]
435+
436+jobs:
437+ build:
438+
439+ runs-on: ${{ matrix.os }}
440+ defaults:
441+ run:
442+ shell: bash
443+ env:
444+ OS: ${{ matrix.os }}
445+ PYTHON: ${{ matrix.python-version }}
446+ PYTHONIOENCODING: utf-8
447+ PIP_DOWNLOAD_CACHE: ${{ github.workspace }}/../.pip_download_cache
448+ strategy:
449+ fail-fast: false
450+ matrix:
451+ os: [ubuntu-20.04, macos-latest, windows-latest]
452+ python-version: [3.6, 3.7, 3.8, 3.9]
453+ steps:
454+ - name: Set git crlf/eol
455+ run: |
456+ git config --global core.autocrlf false
457+ git config --global core.eol lf
458+
459+ - uses: actions/checkout@v2
460+ with:
461+ submodules: True
462+
463+ - name: Set up Python ${{ matrix.python-version }}
464+ uses: actions/setup-python@v2
465+ with:
466+ python-version: ${{ matrix.python-version }}
467+
468+ - name: Install dependencies
469+ run: |
470+ python -m pip install --upgrade pip
471+ pip install tox tox-gh-actions
472+
473+ - name: Run tests with coverage
474+ run: |
475+ tox
476+ env:
477+ PLATFORM: ${{ matrix.os }}
478+
479+ - name: Upload coverage to Codecov
480+ uses: codecov/codecov-action@v1
481+ with:
482+ env_vars: OS,PYTHON
483+
484+ - name: Test with specs and pystache-test
485+ run: |
486+ tox -e setup . ext/spec/specs
487+
488+ - name: Check pkg builds
489+ run: |
490+ tox -e deploy
491+
492+ - name: Check docs
493+ if: runner.os == 'Linux'
494+ run: |
495+ sudo apt-get -qq update
496+ sudo apt-get install -y pandoc
497+ tox -e docs
498diff --git a/.github/workflows/conda.yml b/.github/workflows/conda.yml
499new file mode 100644
500index 0000000..261f9ad
501--- /dev/null
502+++ b/.github/workflows/conda.yml
503@@ -0,0 +1,55 @@
504+name: Conda
505+
506+on:
507+ workflow_dispatch:
508+ pull_request:
509+ push:
510+ branches:
511+ - master
512+
513+jobs:
514+ build:
515+ strategy:
516+ fail-fast: false
517+ matrix:
518+ platform: [ubuntu-18.04, windows-latest, macos-latest]
519+ python-version: [3.6, 3.7, 3.8, 3.9]
520+
521+ runs-on: ${{ matrix.platform }}
522+
523+ # The setup-miniconda action needs this to activate miniconda
524+ defaults:
525+ run:
526+ shell: "bash -l {0}"
527+
528+ steps:
529+ - uses: actions/checkout@v2
530+ with:
531+ fetch-depth: 0
532+
533+ - name: Cache conda
534+ uses: actions/cache@v1
535+ with:
536+ path: ~/conda_pkgs_dir
537+ key: ${{matrix.os}}-conda-pkgs-${{hashFiles('**/conda/meta.yaml')}}
538+
539+ - name: Get conda
540+ uses: conda-incubator/setup-miniconda@v2
541+ with:
542+ python-version: ${{ matrix.python-version }}
543+ channels: conda-forge
544+ channel-priority: strict
545+ use-only-tar-bz2: true
546+ auto-activate-base: true
547+
548+ - name: Prepare
549+ run: conda install conda-build conda-verify
550+
551+ - name: Build
552+ run: conda build conda
553+
554+ - name: Install
555+ run: conda install -c ${CONDA_PREFIX}/conda-bld/ pystache
556+
557+ - name: Test
558+ run: python test_pystache.py
559diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
560new file mode 100644
561index 0000000..f33c4b5
562--- /dev/null
563+++ b/.github/workflows/release.yml
564@@ -0,0 +1,94 @@
565+name: Release
566+
567+on:
568+ push:
569+ # release on tag push
570+ tags:
571+ - '*'
572+
573+jobs:
574+ wheels:
575+
576+ runs-on: ${{ matrix.os }}
577+ defaults:
578+ run:
579+ shell: bash
580+ env:
581+ PYTHONIOENCODING: utf-8
582+ strategy:
583+ fail-fast: false
584+ matrix:
585+ os: [ubuntu-18.04, macos-latest, windows-latest]
586+ python-version: [3.6, 3.7, 3.8, 3.9]
587+ exclude:
588+ - os: windows-latest
589+ python-version: 2.7
590+
591+ steps:
592+ - name: Set git crlf/eol
593+ run: |
594+ git config --global core.autocrlf false
595+ git config --global core.eol lf
596+
597+ - uses: actions/checkout@v2
598+ with:
599+ fetch-depth: 0
600+
601+ - name: Set up Python ${{ matrix.python-version }}
602+ uses: actions/setup-python@v2
603+ with:
604+ python-version: ${{ matrix.python-version }}
605+
606+ - name: Install dependencies
607+ run: |
608+ python -m pip install --upgrade pip wheel
609+ pip install tox tox-gh-actions
610+
611+ - name: Build dist pkgs
612+ run: |
613+ tox -e deploy
614+
615+ - name: Upload artifacts
616+ if: matrix.python-version == 3.7 && runner.os == 'Linux'
617+ uses: actions/upload-artifact@v2
618+ with:
619+ name: wheels
620+ path: ./dist/*.whl
621+
622+ create_release:
623+ name: Create Release
624+ needs: [wheels]
625+ runs-on: ubuntu-18.04
626+
627+ steps:
628+ - name: Get version
629+ id: get_version
630+ run: |
631+ echo "VERSION=${GITHUB_REF/refs\/tags\//}" >> $GITHUB_ENV
632+ echo ${{ env.VERSION }}
633+
634+ - uses: actions/checkout@v2
635+ with:
636+ fetch-depth: 0
637+
638+ # download all artifacts to project dir
639+ - uses: actions/download-artifact@v2
640+
641+ - name: Generate changes file
642+ uses: sarnold/gitchangelog-action@master
643+ with:
644+ github_token: ${{ secrets.GITHUB_TOKEN}}
645+
646+ - name: Create release
647+ id: create_release
648+ uses: softprops/action-gh-release@v1
649+ env:
650+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
651+ with:
652+ tag_name: ${{ env.VERSION }}
653+ name: Release v${{ env.VERSION }}
654+ body_path: CHANGES.md
655+ draft: false
656+ prerelease: false
657+ files: |
658+ wheels/pystache*.whl
659diff --git a/.github/workflows/wheels.yml b/.github/workflows/wheels.yml
660new file mode 100644
661index 0000000..58f0c5e
662--- /dev/null
663+++ b/.github/workflows/wheels.yml
664@@ -0,0 +1,82 @@
665+name: Wheels
666+
667+on:
668+ workflow_dispatch:
669+ pull_request:
670+ #push:
671+ #branches: [ master ]
672+
673+jobs:
674+ build:
675+
676+ runs-on: ${{ matrix.os }}
677+ defaults:
678+ run:
679+ shell: bash
680+ env:
681+ PYTHONIOENCODING: utf-8
682+ strategy:
683+ fail-fast: false
684+ matrix:
685+ os: [ubuntu-18.04, macos-latest, windows-latest]
686+ python-version: [3.6, 3.7, 3.8, 3.9]
687+
688+ steps:
689+ - name: Set git crlf/eol
690+ run: |
691+ git config --global core.autocrlf false
692+ git config --global core.eol lf
693+
694+ - uses: actions/checkout@v2
695+ with:
696+ fetch-depth: 0
697+
698+ - name: Set up Python ${{ matrix.python-version }}
699+ uses: actions/setup-python@v2
700+ with:
701+ python-version: ${{ matrix.python-version }}
702+
703+ - name: Install dependencies
704+ run: |
705+ python -m pip install --upgrade pip wheel
706+ pip install tox tox-gh-actions
707+
708+ - name: Build dist pkgs
709+ run: |
710+ tox -e deploy
711+
712+ - name: Upload artifacts
713+ if: matrix.python-version == 3.7 && runner.os == 'Linux'
714+ uses: actions/upload-artifact@v2
715+ with:
716+ name: wheels
717+ path: ./dist/*.whl
718+
719+ check_artifact:
720+ name: Check wheel artifact
721+ needs: [build]
722+ runs-on: ${{ matrix.os }}
723+ defaults:
724+ run:
725+ shell: bash
726+ env:
727+ PYTHONIOENCODING: utf-8
728+ strategy:
729+ fail-fast: false
730+ matrix:
731+ os: [ubuntu-18.04, macos-latest, windows-latest]
732+ python-version: [3.6, 3.8, 3.9]
733+
734+ steps:
735+ - name: Set up Python ${{ matrix.python-version }}
736+ uses: actions/setup-python@v2
737+ with:
738+ python-version: ${{ matrix.python-version }}
739+
740+ # download all artifacts to project dir
741+ - uses: actions/download-artifact@v2
742+
743+ - name: Check wheel install
744+ run: |
745+ bash -c 'export WHL=$(ls wheels/*.whl); python -m pip install $WHL'
746+ pystache-test
747diff --git a/.pep8speaks.yml b/.pep8speaks.yml
748new file mode 100644
749index 0000000..e841b66
750--- /dev/null
751+++ b/.pep8speaks.yml
752@@ -0,0 +1,15 @@
753+scanner:
754+ linter: flake8 # Other option is pycodestyle
755+
756+no_blank_comment: False # If True, no comment is made on PR without any errors.
757+descending_issues_order: True # If True, PEP 8 issues in message will be displayed in descending order of line numbers in the file
758+
759+[flake8]
760+exclude =
761+ .git,
762+ .github,
763+ __pycache__,
764+ build,
765+ dist
766+
767+max-line-length = 110
768diff --git a/HISTORY.md b/HISTORY.md
769index e5b7638..60b6308 100644
770--- a/HISTORY.md
771+++ b/HISTORY.md
772@@ -1,7 +1,42 @@
773 History
774 =======
775
776-**Note:** Official support for Python 2.4 will end with Pystache version 0.6.0.
777+**Note:** Official support for Python 2.7 will end with Pystache version 0.6.0.
778+
779+0.6.0 (2021-03-04)
780+------------------
781+
782+- Bump spec versions to latest => v1.1.3
783+- Modernize python and CI tools, update docs/doctests
784+- Update unicode conversion test for py3-only
785+- Add pep8speaks cfg, cleanup warnings
786+- Remove superfluous setup test/unused imports
787+- Add conda recipe/CI build
788+
789+0.5.6 (2021-02-28)
790+------------------
791+
792+- Use correct wheel name in release workflow, limit wheels
793+- Add install check/test of downloaded wheel
794+- Update/add ci workflows and tox cfg, bump to next dev0 version
795+
796+0.5.5 (2020-12-16)
797+------------------
798+
799+- fix document processing, update pandoc args and history
800+- add release.yml to CI, test env settings
801+- fix bogus commit message, update versions and tox cf
802+- add post-test steps for building pkgs with/without doc updates
803+- add CI build check, fix MANIFEST.in pruning
804+
805+0.5.4-2 (2020-11-09)
806+--------------------
807+
808+- Merge pull request #1 from sarnold/rebase-up
809+- Bugfix: test_specloader.py: fix test_find__with_directory on other OSs
810+- Bugfix: pystache/loader.py: remove stray windows line-endings
811+- fix crufty (and insecure) http urls
812+- Bugfix: modernize python versions (keep py27) and fix spec_test load cmd
813
814 0.5.4 (2014-07-11)
815 ------------------
816diff --git a/MANIFEST.in b/MANIFEST.in
817index bdc64bf..1593143 100644
818--- a/MANIFEST.in
819+++ b/MANIFEST.in
820@@ -1,7 +1,4 @@
821-include README.md
822-include HISTORY.md
823-include LICENSE
824-include TODO.md
825+include README.md HISTORY.md TODO.md LICENSE
826 include setup_description.rst
827 include tox.ini
828 include test_pystache.py
829@@ -11,3 +8,6 @@ recursive-include pystache/tests *.mustache *.txt
830 # We deliberately exclude the gh/ directory because it contains copies
831 # of resources needed only for the web page hosted on GitHub (via the
832 # gh-pages branch).
833+exclude *.ini *travis*
834+prune gh
835+prune .git*
836diff --git a/README.md b/README.md
837index 54a9608..1203b7a 100644
838--- a/README.md
839+++ b/README.md
840@@ -10,11 +10,25 @@ Pystache
841 <!-- -->
842 <!-- We leave the leading brackets empty here. Otherwise, unwanted -->
843 <!-- caption text shows up in the reST version converted by pandoc. -->
844-![](http://defunkt.github.com/pystache/images/logo_phillips.png "mustachioed, monocled snake by David Phillips")
845+[![ci](https://github.com/sarnold/pystache/actions/workflows/ci.yml/badge.svg)](https://github.com/sarnold/pystache/actions/workflows/ci.yml)
846+[![Conda](https://github.com/sarnold/pystache/actions/workflows/conda.yml/badge.svg)](https://github.com/sarnold/pystache/actions/workflows/conda.yml)
847+[![Wheels](https://github.com/sarnold/pystache/actions/workflows/wheels.yml/badge.svg)](https://github.com/sarnold/pystache/actions/workflows/wheels.yml)
848+[![Release](https://github.com/sarnold/pystache/actions/workflows/release.yml/badge.svg)](https://github.com/sarnold/pystache/actions/workflows/release.yml)
849+[![Python](https://img.shields.io/badge/python-3.6+-blue.svg)](https://www.python.org/downloads/)
850
851-![](https://secure.travis-ci.org/defunkt/pystache.png "Travis CI current build status")
852+[![Latest release](https://img.shields.io/github/v/release/sarnold/pystache?include_prereleases)](https://github.com/sarnold/pystache/releases/latest)
853+[![License](https://img.shields.io/github/license/sarnold/pystache)](https://github.com/sarnold/pystache/blob/master/LICENSE)
854+[![Maintainability](https://api.codeclimate.com/v1/badges/a8fa1bf4638bfc6581b6/maintainability)](https://codeclimate.com/github/sarnold/pystache/maintainability)
855+[![codecov](https://codecov.io/gh/sarnold/pystache/branch/master/graph/badge.svg?token=5PZNMZBI6K)](https://codecov.io/gh/sarnold/pystache)
856
857-[Pystache](http://defunkt.github.com/pystache) is a Python
858+
859+
860+This updated fork of Pystache is currently tested on Python 3.6+ and in
861+Conda, on Linux, Macos, and Windows (Python 2.7 support has been removed).
862+
863+![](gh/images/logo_phillips_small.png "mustachioed, monocled snake by David Phillips")
864+
865+[Pystache](http://sarnold.github.com/pystache) is a Python
866 implementation of [Mustache](http://mustache.github.com/). Mustache is a
867 framework-agnostic, logic-free templating system inspired by
868 [ctemplate](http://code.google.com/p/google-ctemplate/) and
869@@ -27,10 +41,10 @@ provides a good introduction to Mustache's syntax. For a more complete
870 (and more current) description of Mustache's behavior, see the official
871 [Mustache spec](https://github.com/mustache/spec).
872
873-Pystache is [semantically versioned](http://semver.org) and can be found
874-on [PyPI](http://pypi.python.org/pypi/pystache). This version of
875-Pystache passes all tests in [version
876-1.1.2](https://github.com/mustache/spec/tree/v1.1.2) of the spec.
877+Pystache is [semantically versioned](http://semver.org) and older versions
878+can still be found on [PyPI](http://pypi.python.org/pypi/pystache). This
879+version of Pystache now passes all tests in [version
880+1.1.3](https://github.com/mustache/spec/tree/v1.1.3) of the spec.
881
882
883 Requirements
884@@ -38,41 +52,25 @@ Requirements
885
886 Pystache is tested with--
887
888-- Python 2.4 (requires simplejson [version
889- 2.0.9](http://pypi.python.org/pypi/simplejson/2.0.9) or earlier)
890-- Python 2.5 (requires
891- [simplejson](http://pypi.python.org/pypi/simplejson/))
892-- Python 2.6
893-- Python 2.7
894-- Python 3.1
895-- Python 3.2
896-- Python 3.3
897-- [PyPy](http://pypy.org/)
898+- Python 3.6
899+- Python 3.7
900+- Python 3.8
901+- Python 3.9
902+- Conda (py36-py39)
903
904 [Distribute](http://packages.python.org/distribute/) (the setuptools fork)
905-is recommended over [setuptools](http://pypi.python.org/pypi/setuptools),
906-and is required in some cases (e.g. for Python 3 support).
907-If you use [pip](http://www.pip-installer.org/), you probably already satisfy
908-this requirement.
909+is no longer required over [setuptools](http://pypi.python.org/pypi/setuptools),
910+as the current packaging is now PEP517-compliant.
911
912 JSON support is needed only for the command-line interface and to run
913-the spec tests. We require simplejson for earlier versions of Python
914-since Python's [json](http://docs.python.org/library/json.html) module
915-was added in Python 2.6.
916-
917-For Python 2.4 we require an earlier version of simplejson since
918-simplejson stopped officially supporting Python 2.4 in simplejson
919-version 2.1.0. Earlier versions of simplejson can be installed manually,
920-as follows:
921+the spec tests; PyYAML can still be used (see the Develop section).
922
923- pip install 'simplejson<2.1.0'
924-
925-Official support for Python 2.4 will end with Pystache version 0.6.0.
926+Official support for Python 2 will end with Pystache version 0.6.0.
927
928 Install It
929 ----------
930
931- pip install pystache
932+ pip install -U pystache -f https://github.com/sarnold/pystache/releases/
933
934 And test it--
935
936@@ -85,12 +83,12 @@ Use It
937 ------
938
939 >>> import pystache
940- >>> print pystache.render('Hi {{person}}!', {'person': 'Mom'})
941+ >>> print(pystache.render('Hi {{person}}!', {'person': 'Mom'}))
942 Hi Mom!
943
944 You can also create dedicated view classes to hold your view logic.
945
946-Here's your view class (in .../examples/readme.py):
947+Here's your view class (in ../pystache/tests/examples/readme.py):
948
949 class SayHello(object):
950 def to(self):
951@@ -109,7 +107,7 @@ directory as your class definition):
952 Pull it together:
953
954 >>> renderer = pystache.Renderer()
955- >>> print renderer.render(hello)
956+ >>> print(renderer.render(hello))
957 Hello, Pizza!
958
959 For greater control over rendering (e.g. to specify a custom template
960@@ -117,22 +115,22 @@ directory), use the `Renderer` class like above. One can pass attributes
961 to the Renderer class constructor or set them on a Renderer instance. To
962 customize template loading on a per-view basis, subclass `TemplateSpec`.
963 See the docstrings of the
964-[Renderer](https://github.com/defunkt/pystache/blob/master/pystache/renderer.py)
965+[Renderer](https://github.com/sarnold/pystache/blob/master/pystache/renderer.py)
966 class and
967-[TemplateSpec](https://github.com/defunkt/pystache/blob/master/pystache/template_spec.py)
968+[TemplateSpec](https://github.com/sarnold/pystache/blob/master/pystache/template_spec.py)
969 class for more information.
970
971 You can also pre-parse a template:
972
973 >>> parsed = pystache.parse(u"Hey {{#who}}{{.}}!{{/who}}")
974- >>> print parsed
975- [u'Hey ', _SectionNode(key=u'who', index_begin=12, index_end=18, parsed=[_EscapeNode(key=u'.'), u'!'])]
976+ >>> print(parsed)
977+ ['Hey ', _SectionNode(key='who', index_begin=12, index_end=18, parsed=[_EscapeNode(key='.'), '!'])]
978
979 And then:
980
981- >>> print renderer.render(parsed, {'who': 'Pops'})
982+ >>> print(renderer.render(parsed, {'who': 'Pops'}))
983 Hey Pops!
984- >>> print renderer.render(parsed, {'who': 'you'})
985+ >>> print(renderer.render(parsed, {'who': 'you'}))
986 Hey you!
987
988 Python 3
989@@ -194,15 +192,16 @@ To test from a source distribution (without installing)--
990 python test_pystache.py
991
992 To test Pystache with multiple versions of Python (with a single
993-command!), you can use [tox](http://pypi.python.org/pypi/tox):
994+command!) and different platforms, you can use [tox](http://pypi.python.org/pypi/tox):
995+
996+ pip install tox
997+ tox -e setup
998
999- pip install 'virtualenv<1.8' # Version 1.8 dropped support for Python 2.4.
1000- pip install 'tox<1.4' # Version 1.4 dropped support for Python 2.4.
1001- tox
1002+To run tests on multiple versions with coverage, run:
1003
1004-If you do not have all Python versions listed in `tox.ini`--
1005+ tox -e py38-linux,py39-linux # for example
1006
1007- tox -e py26,py32 # for example
1008+(substitute your platform above, eg, macos or windows)
1009
1010 The source distribution tests also include doctests and tests from the
1011 Mustache spec. To include tests from the Mustache spec in your test
1012@@ -217,57 +216,33 @@ parses the json files. To install PyYAML--
1013
1014 pip install pyyaml
1015
1016+Once the submodule is available, you can run the full test set with:
1017+
1018+ tox -e setup . ext/spec/specs
1019+
1020 To run a subset of the tests, you can use
1021 [nose](http://somethingaboutorange.com/mrl/projects/nose/0.11.1/testing.html):
1022
1023 pip install nose
1024 nosetests --tests pystache/tests/test_context.py:GetValueTests.test_dictionary__key_present
1025
1026-### Using Python 3 with Pystache from source
1027-
1028-Pystache is written in Python 2 and must be converted to Python 3 prior to
1029-using it with Python 3. The installation process (and tox) do this
1030-automatically.
1031
1032-To convert the code to Python 3 manually (while using Python 3)--
1033+Mailing List (old)
1034+------------------
1035
1036- python setup.py build
1037-
1038-This writes the converted code to a subdirectory called `build`.
1039-By design, Python 3 builds
1040-[cannot](https://bitbucket.org/tarek/distribute/issue/292/allow-use_2to3-with-python-2)
1041-be created from Python 2.
1042-
1043-To convert the code without using setup.py, you can use
1044-[2to3](http://docs.python.org/library/2to3.html) as follows (two steps)--
1045-
1046- 2to3 --write --nobackups --no-diffs --doctests_only pystache
1047- 2to3 --write --nobackups --no-diffs pystache
1048-
1049-This converts the code (and doctests) in place.
1050-
1051-To `import pystache` from a source distribution while using Python 3, be
1052-sure that you are importing from a directory containing a converted
1053-version of the code (e.g. from the `build` directory after converting),
1054-and not from the original (unconverted) source directory. Otherwise, you will
1055-get a syntax error. You can help prevent this by not running the Python
1056-IDE from the project directory when importing Pystache while using Python 3.
1057-
1058-
1059-Mailing List
1060-------------
1061-
1062-There is a [mailing list](http://librelist.com/browser/pystache/). Note
1063+There is(was) a [mailing list](http://librelist.com/browser/pystache/). Note
1064 that there is a bit of a delay between posting a message and seeing it
1065 appear in the mailing list archive.
1066
1067 Credits
1068 -------
1069
1070- >>> context = { 'author': 'Chris Wanstrath', 'maintainer': 'Chris Jerdonek' }
1071- >>> print pystache.render("Author: {{author}}\nMaintainer: {{maintainer}}", context)
1072+ >>> import pystache
1073+ >>> context = { 'author': 'Chris Wanstrath', 'maintainer': 'Chris Jerdonek','refurbisher': 'Steve Arnold' }
1074+ >>> print(pystache.render("Author: {{author}}\nMaintainer: {{maintainer}}\nRefurbisher: {{refurbisher}}", context))
1075 Author: Chris Wanstrath
1076 Maintainer: Chris Jerdonek
1077+ Refurbisher: Steve Arnold
1078
1079 Pystache logo by [David Phillips](http://davidphillips.us/) is licensed
1080 under a [Creative Commons Attribution-ShareAlike 3.0 Unported
1081diff --git a/TODO.md b/TODO.md
1082index cd82417..76853a4 100644
1083--- a/TODO.md
1084+++ b/TODO.md
1085@@ -6,11 +6,10 @@ In development branch:
1086 * Figure out a way to suppress center alignment of images in reST output.
1087 * Add a unit test for the change made in 7ea8e7180c41. This is with regard
1088 to not requiring spec tests when running tests from a downloaded sdist.
1089-* End support for Python 2.4.
1090-* Add Python 3.3 to tox file (after deprecating 2.4).
1091+* End support for Python 2.7 (done as of 03/03/21 - SA)
1092+* Release 0.6.0 on github, make a pypi account (SA)
1093 * Turn the benchmarking script at pystache/tests/benchmark.py into a command
1094 in pystache/commands, or make it a subcommand of one of the existing
1095 commands (i.e. using a command argument).
1096 * Provide support for logging in at least one of the commands.
1097-* Make sure command parsing to pystache-test doesn't break with Python 2.4 and earlier.
1098 * Combine pystache-test with the main command.
1099diff --git a/conda/meta.yaml b/conda/meta.yaml
1100new file mode 100644
1101index 0000000..e7f4fd9
1102--- /dev/null
1103+++ b/conda/meta.yaml
1104@@ -0,0 +1,50 @@
1105+{% set name = "pystache" %}
1106+{% set version = "0.6.0.dev0" %}
1107+
1108+package:
1109+ name: {{ name|lower }}
1110+ version: {{ version }}
1111+
1112+source:
1113+ path: ..
1114+
1115+build:
1116+ number: 0
1117+ script: {{ PYTHON }} -m pip install . --no-deps --ignore-installed -vvv
1118+ noarch: python
1119+ entry_points:
1120+ - pystache = pystache.commands.render:main
1121+ - pystache-test = pystache.commands.test:main
1122+
1123+requirements:
1124+ build:
1125+ - python
1126+ - setuptools
1127+
1128+ run:
1129+ - python
1130+
1131+test:
1132+ imports:
1133+ - pystache
1134+ - pystache.commands
1135+ - pystache.tests
1136+ - pystache.tests.data
1137+ - pystache.tests.data.locator
1138+ - pystache.tests.examples
1139+
1140+ commands:
1141+ - pystache --help
1142+ - pystache-test
1143+
1144+
1145+about:
1146+ home: https://github.com/sarnold/pystache
1147+ license: MIT
1148+ license_family: MIT
1149+ license_file: LICENSE
1150+ summary: Mustache for Python
1151+
1152+extra:
1153+ recipe-maintainers:
1154+ - sarnold
1155diff --git a/pyproject.toml b/pyproject.toml
1156new file mode 100644
1157index 0000000..2f21011
1158--- /dev/null
1159+++ b/pyproject.toml
1160@@ -0,0 +1,3 @@
1161+[build-system]
1162+requires = ["setuptools>=40.8.0", "wheel"]
1163+build-backend = "setuptools.build_meta"
1164diff --git a/pystache/__init__.py b/pystache/__init__.py
1165index 4cf2434..5edc1c5 100644
1166--- a/pystache/__init__.py
1167+++ b/pystache/__init__.py
1168@@ -10,4 +10,4 @@ from pystache.init import parse, render, Renderer, TemplateSpec
1169
1170 __all__ = ['parse', 'render', 'Renderer', 'TemplateSpec']
1171
1172-__version__ = '0.5.4' # Also change in setup.py.
1173+__version__ = '0.6.0'
1174diff --git a/pystache/commands/render.py b/pystache/commands/render.py
1175index 1a9c309..9c913e7 100644
1176--- a/pystache/commands/render.py
1177+++ b/pystache/commands/render.py
1178@@ -22,7 +22,7 @@ except:
1179 from sys import exc_info
1180 ex_type, ex_value, tb = exc_info()
1181 new_ex = Exception("%s: %s" % (ex_type.__name__, ex_value))
1182- raise new_ex.__class__, new_ex, tb
1183+ raise new_ex.__class__(new_ex).with_traceback(tb)
1184
1185 # The optparse module is deprecated in Python 2.7 in favor of argparse.
1186 # However, argparse is not available in Python 2.6 and earlier.
1187@@ -88,7 +88,7 @@ def main(sys_argv=sys.argv):
1188 context = json.loads(context)
1189
1190 rendered = renderer.render(template, context)
1191- print rendered
1192+ print(rendered)
1193
1194
1195 if __name__=='__main__':
1196diff --git a/pystache/common.py b/pystache/common.py
1197index fb266dd..0e9b091 100644
1198--- a/pystache/common.py
1199+++ b/pystache/common.py
1200@@ -5,17 +5,12 @@ Exposes functionality needed throughout the project.
1201
1202 """
1203
1204-from sys import version_info
1205
1206 def _get_string_types():
1207- # TODO: come up with a better solution for this. One of the issues here
1208- # is that in Python 3 there is no common base class for unicode strings
1209- # and byte strings, and 2to3 seems to convert all of "str", "unicode",
1210- # and "basestring" to Python 3's "str".
1211- if version_info < (3, ):
1212- return basestring
1213- # The latter evaluates to "bytes" in Python 3 -- even after conversion by 2to3.
1214- return (unicode, type(u"a".encode('utf-8')))
1215+ """
1216+ Return the Python3 string type (no more python2)
1217+ """
1218+ return (str, type("a".encode('utf-8')))
1219
1220
1221 _STRING_TYPES = _get_string_types()
1222diff --git a/pystache/defaults.py b/pystache/defaults.py
1223index bcfdf4c..2fab0e0 100644
1224--- a/pystache/defaults.py
1225+++ b/pystache/defaults.py
1226@@ -39,7 +39,7 @@ STRING_ENCODING = sys.getdefaultencoding()
1227 FILE_ENCODING = sys.getdefaultencoding()
1228
1229 # The delimiters to start with when parsing.
1230-DELIMITERS = (u'{{', u'}}')
1231+DELIMITERS = ('{{', '}}')
1232
1233 # How to handle missing tags when rendering a template.
1234 MISSING_TAGS = MissingTags.ignore
1235diff --git a/pystache/loader.py b/pystache/loader.py
1236index d4a7e53..ea01d17 100644
1237--- a/pystache/loader.py
1238+++ b/pystache/loader.py
1239@@ -6,6 +6,7 @@ This module provides a Loader class for locating and reading templates.
1240 """
1241
1242 import os
1243+import platform
1244 import sys
1245
1246 from pystache import common
1247@@ -24,7 +25,7 @@ def _make_to_unicode():
1248 """
1249 if encoding is None:
1250 encoding = defaults.STRING_ENCODING
1251- return unicode(s, encoding, defaults.DECODE_ERRORS)
1252+ return str(s, encoding, defaults.DECODE_ERRORS)
1253 return to_unicode
1254
1255
1256@@ -86,7 +87,7 @@ class Loader(object):
1257 def _make_locator(self):
1258 return Locator(extension=self.extension)
1259
1260- def unicode(self, s, encoding=None):
1261+ def str(self, s, encoding=None):
1262 """
1263 Convert a string to unicode using the given encoding, and return it.
1264
1265@@ -104,8 +105,8 @@ class Loader(object):
1266 Defaults to None.
1267
1268 """
1269- if isinstance(s, unicode):
1270- return unicode(s)
1271+ if isinstance(s, str):
1272+ return str(s)
1273
1274 return self.to_unicode(s, encoding)
1275
1276@@ -118,8 +119,9 @@ class Loader(object):
1277
1278 if encoding is None:
1279 encoding = self.file_encoding
1280-
1281- return self.unicode(b, encoding)
1282+ if platform.system() == "Windows":
1283+ return self.str(b, encoding).replace('\r', '')
1284+ return self.str(b, encoding)
1285
1286 def load_file(self, file_name):
1287 """
1288diff --git a/pystache/parsed.py b/pystache/parsed.py
1289index 372d96c..75d417d 100644
1290--- a/pystache/parsed.py
1291+++ b/pystache/parsed.py
1292@@ -41,10 +41,10 @@ class ParsedTemplate(object):
1293 """
1294 # We avoid use of the ternary operator for Python 2.4 support.
1295 def get_unicode(node):
1296- if type(node) is unicode:
1297+ if type(node) is str:
1298 return node
1299 return node.render(engine, context)
1300- parts = map(get_unicode, self._parse_tree)
1301+ parts = list(map(get_unicode, self._parse_tree))
1302 s = ''.join(parts)
1303
1304- return unicode(s)
1305+ return str(s)
1306diff --git a/pystache/parser.py b/pystache/parser.py
1307index c6a171f..1afd50a 100644
1308--- a/pystache/parser.py
1309+++ b/pystache/parser.py
1310@@ -11,8 +11,8 @@ from pystache import defaults
1311 from pystache.parsed import ParsedTemplate
1312
1313
1314-END_OF_LINE_CHARACTERS = [u'\r', u'\n']
1315-NON_BLANK_RE = re.compile(ur'^(.)', re.M)
1316+END_OF_LINE_CHARACTERS = ['\r', '\n']
1317+NON_BLANK_RE = re.compile(r'^(.)', re.M)
1318
1319
1320 # TODO: add some unit tests for this.
1321@@ -30,12 +30,12 @@ def parse(template, delimiters=None):
1322
1323 Examples:
1324
1325- >>> parsed = parse(u"Hey {{#who}}{{name}}!{{/who}}")
1326- >>> print str(parsed).replace('u', '') # This is a hack to get the test to pass both in Python 2 and 3.
1327+ >>> parsed = parse("Hey {{#who}}{{name}}!{{/who}}")
1328+ >>> print(str(parsed).replace('u', '')) # This is an old hack.
1329 ['Hey ', _SectionNode(key='who', index_begin=12, index_end=21, parsed=[_EscapeNode(key='name'), '!'])]
1330
1331 """
1332- if type(template) is not unicode:
1333+ if type(template) is not str:
1334 raise Exception("Template is not unicode: %s" % type(template))
1335 parser = _Parser(delimiters)
1336 return parser.parse(template)
1337@@ -94,7 +94,7 @@ class _CommentNode(object):
1338 return _format(self)
1339
1340 def render(self, engine, context):
1341- return u''
1342+ return ''
1343
1344
1345 class _ChangeNode(object):
1346@@ -106,7 +106,7 @@ class _ChangeNode(object):
1347 return _format(self)
1348
1349 def render(self, engine, context):
1350- return u''
1351+ return ''
1352
1353
1354 class _EscapeNode(object):
1355@@ -147,7 +147,7 @@ class _PartialNode(object):
1356 def render(self, engine, context):
1357 template = engine.resolve_partial(self.key)
1358 # Indent before rendering.
1359- template = re.sub(NON_BLANK_RE, self.indent + ur'\1', template)
1360+ template = re.sub(NON_BLANK_RE, self.indent + r'\1', template)
1361
1362 return engine.render(template, context)
1363
1364@@ -168,7 +168,7 @@ class _InvertedNode(object):
1365 # Note that lambdas are considered truthy for inverted sections
1366 # per the spec.
1367 if data:
1368- return u''
1369+ return ''
1370 return self.parsed_section.render(engine, context)
1371
1372
1373@@ -218,7 +218,7 @@ class _SectionNode(object):
1374 parts.append(self.parsed.render(engine, context))
1375 context.pop()
1376
1377- return unicode(''.join(parts))
1378+ return str(''.join(parts))
1379
1380
1381 class _Parser(object):
1382diff --git a/pystache/renderengine.py b/pystache/renderengine.py
1383index c797b17..2f1e341 100644
1384--- a/pystache/renderengine.py
1385+++ b/pystache/renderengine.py
1386@@ -160,7 +160,7 @@ class RenderEngine(object):
1387 if not is_string(val):
1388 # In case the template is an integer, for example.
1389 val = self.to_str(val)
1390- if type(val) is not unicode:
1391+ if type(val) is not str:
1392 val = self.literal(val)
1393 return self.render(val, context, delimiters)
1394
1395diff --git a/pystache/renderer.py b/pystache/renderer.py
1396index ff6a90c..064f040 100644
1397--- a/pystache/renderer.py
1398+++ b/pystache/renderer.py
1399@@ -32,7 +32,7 @@ class Renderer(object):
1400 >>> partials = {'partial': 'Hello, {{thing}}!'}
1401 >>> renderer = Renderer(partials=partials)
1402 >>> # We apply print to make the test work in Python 3 after 2to3.
1403- >>> print renderer.render('{{>partial}}', {'thing': 'world'})
1404+ >>> print(renderer.render('{{>partial}}', {'thing': 'world'}))
1405 Hello, world!
1406
1407 To customize string coercion (e.g. to render False values as ''), one can
1408@@ -130,7 +130,7 @@ class Renderer(object):
1409 if string_encoding is None:
1410 string_encoding = defaults.STRING_ENCODING
1411
1412- if isinstance(search_dirs, basestring):
1413+ if isinstance(search_dirs, str):
1414 search_dirs = [search_dirs]
1415
1416 self._context = None
1417@@ -177,16 +177,16 @@ class Renderer(object):
1418 """
1419 # We type-check to avoid "TypeError: decoding Unicode is not supported".
1420 # We avoid the Python ternary operator for Python 2.4 support.
1421- if isinstance(s, unicode):
1422+ if isinstance(s, str):
1423 return s
1424- return self.unicode(s)
1425+ return self.str(s)
1426
1427 def _to_unicode_hard(self, s):
1428 """
1429 Convert a basestring to a string with type unicode (not subclass).
1430
1431 """
1432- return unicode(self._to_unicode_soft(s))
1433+ return str(self._to_unicode_soft(s))
1434
1435 def _escape_to_unicode(self, s):
1436 """
1437@@ -195,9 +195,9 @@ class Renderer(object):
1438 Returns a unicode string (not subclass).
1439
1440 """
1441- return unicode(self.escape(self._to_unicode_soft(s)))
1442+ return str(self.escape(self._to_unicode_soft(s)))
1443
1444- def unicode(self, b, encoding=None):
1445+ def str(self, b, encoding=None):
1446 """
1447 Convert a byte string to unicode, using string_encoding and decode_errors.
1448
1449@@ -222,7 +222,7 @@ class Renderer(object):
1450
1451 # TODO: Wrap UnicodeDecodeErrors with a message about setting
1452 # the string_encoding and decode_errors attributes.
1453- return unicode(b, encoding, self.decode_errors)
1454+ return str(b, encoding, self.decode_errors)
1455
1456 def _make_loader(self):
1457 """
1458@@ -230,7 +230,7 @@ class Renderer(object):
1459
1460 """
1461 return Loader(file_encoding=self.file_encoding, extension=self.file_extension,
1462- to_unicode=self.unicode, search_dirs=self.search_dirs)
1463+ to_unicode=self.str, search_dirs=self.search_dirs)
1464
1465 def _make_load_template(self):
1466 """
1467@@ -299,7 +299,7 @@ class Renderer(object):
1468 try:
1469 return load_partial(name)
1470 except TemplateNotFoundError:
1471- return u''
1472+ return ''
1473
1474 return resolve_partial
1475
1476@@ -316,7 +316,7 @@ class Renderer(object):
1477 try:
1478 return context_get(stack, name)
1479 except KeyNotFoundError:
1480- return u''
1481+ return ''
1482
1483 return resolve_context
1484
1485diff --git a/pystache/specloader.py b/pystache/specloader.py
1486index 3a77d4c..a82d52a 100644
1487--- a/pystache/specloader.py
1488+++ b/pystache/specloader.py
1489@@ -83,7 +83,7 @@ class SpecLoader(object):
1490
1491 """
1492 if spec.template is not None:
1493- return self.loader.unicode(spec.template, spec.template_encoding)
1494+ return self.loader.str(spec.template, spec.template_encoding)
1495
1496 path = self._find(spec)
1497
1498diff --git a/pystache/tests/benchmark.py b/pystache/tests/benchmark.py
1499index d46e973..6cb54f8 100755
1500--- a/pystache/tests/benchmark.py
1501+++ b/pystache/tests/benchmark.py
1502@@ -13,6 +13,13 @@ tests/benchmark.py 10000
1503 import sys
1504 from timeit import Timer
1505
1506+try:
1507+ import chevron as pystache
1508+ print('Using module: chevron')
1509+except (ImportError):
1510+ import pystache
1511+ print('Using module: pystache')
1512+
1513 import pystache
1514
1515 # TODO: make the example realistic.
1516@@ -76,17 +83,17 @@ def main(sys_argv):
1517 args = sys_argv[1:]
1518 count = int(args[0])
1519
1520- print "Benchmarking: %sx" % count
1521- print
1522+ print("Benchmarking: %sx" % count)
1523+ print()
1524
1525 for example in examples:
1526
1527 test = make_test_function(example)
1528
1529 t = Timer(test,)
1530- print min(t.repeat(repeat=3, number=count))
1531+ print(min(t.repeat(repeat=3, number=count)))
1532
1533- print "Done"
1534+ print("Done")
1535
1536
1537 if __name__ == '__main__':
1538diff --git a/pystache/tests/common.py b/pystache/tests/common.py
1539index 222e14f..12b76b5 100644
1540--- a/pystache/tests/common.py
1541+++ b/pystache/tests/common.py
1542@@ -72,8 +72,8 @@ def _find_files(root_dir, should_include):
1543 # http://docs.python.org/library/os.html#os.walk
1544 for dir_path, dir_names, file_names in os.walk(root_dir):
1545 new_paths = [os.path.join(dir_path, file_name) for file_name in file_names]
1546- new_paths = filter(is_module, new_paths)
1547- new_paths = filter(should_include, new_paths)
1548+ new_paths = list(filter(is_module, new_paths))
1549+ new_paths = list(filter(should_include, new_paths))
1550 paths.extend(new_paths)
1551
1552 return paths
1553@@ -183,7 +183,7 @@ class AssertExceptionMixin:
1554 try:
1555 callable(*args, **kwds)
1556 raise Exception("Expected exception: %s: %s" % (exception_type, repr(msg)))
1557- except exception_type, err:
1558+ except exception_type as err:
1559 self.assertEqual(str(err), msg)
1560
1561
1562@@ -228,10 +228,10 @@ class Attachable(object):
1563 """
1564 def __init__(self, **kwargs):
1565 self.__args__ = kwargs
1566- for arg, value in kwargs.iteritems():
1567+ for arg, value in kwargs.items():
1568 setattr(self, arg, value)
1569
1570 def __repr__(self):
1571 return "%s(%s)" % (self.__class__.__name__,
1572 ", ".join("%s=%s" % (k, repr(v))
1573- for k, v in self.__args__.iteritems()))
1574+ for k, v in self.__args__.items()))
1575diff --git a/pystache/tests/examples/unicode_output.py b/pystache/tests/examples/unicode_output.py
1576index da0e1d2..7bdea36 100644
1577--- a/pystache/tests/examples/unicode_output.py
1578+++ b/pystache/tests/examples/unicode_output.py
1579@@ -8,4 +8,4 @@ TODO: add a docstring.
1580 class UnicodeOutput(object):
1581
1582 def name(self):
1583- return u'Henri Poincaré'
1584+ return 'Henri Poincaré'
1585diff --git a/pystache/tests/main.py b/pystache/tests/main.py
1586index 8af6b2e..17f2fb2 100644
1587--- a/pystache/tests/main.py
1588+++ b/pystache/tests/main.py
1589@@ -88,7 +88,7 @@ def main(sys_argv):
1590
1591 """
1592 # TODO: use logging module
1593- print "pystache: running tests: argv: %s" % repr(sys_argv)
1594+ print("pystache: running tests: argv: %s" % repr(sys_argv))
1595
1596 should_source_exist = False
1597 spec_test_dir = None
1598@@ -131,11 +131,9 @@ def main(sys_argv):
1599 module_names = _discover_test_modules(PACKAGE_DIR)
1600 sys_argv.extend(module_names)
1601 if project_dir is not None:
1602- # Add the current module for unit tests contained here (e.g.
1603- # to include SetupTests).
1604+ # Add the current module for unit tests contained here
1605 sys_argv.append(__name__)
1606
1607- SetupTests.project_dir = project_dir
1608
1609 extra_tests = make_extra_tests(project_dir, spec_test_dir)
1610 test_program_class = make_test_program_class(extra_tests)
1611@@ -166,25 +164,3 @@ def _discover_test_modules(package_dir):
1612 raise Exception("No unit-test modules found--\n in %s" % package_dir)
1613
1614 return names
1615-
1616-
1617-class SetupTests(TestCase):
1618-
1619- """Tests about setup.py."""
1620-
1621- project_dir = None
1622-
1623- def test_version(self):
1624- """
1625- Test that setup.py's version matches the package's version.
1626-
1627- """
1628- original_path = list(sys.path)
1629-
1630- sys.path.insert(0, self.project_dir)
1631-
1632- try:
1633- from setup import VERSION
1634- self.assertEqual(VERSION, pystache.__version__)
1635- finally:
1636- sys.path = original_path
1637diff --git a/pystache/tests/spectesting.py b/pystache/tests/spectesting.py
1638index ec8a08d..2dd57e8 100644
1639--- a/pystache/tests/spectesting.py
1640+++ b/pystache/tests/spectesting.py
1641@@ -37,7 +37,7 @@ except ImportError:
1642 from sys import exc_info
1643 ex_type, ex_value, tb = exc_info()
1644 new_ex = Exception("%s: %s" % (ex_type.__name__, ex_value))
1645- raise new_ex.__class__, new_ex, tb
1646+ raise new_ex.__class__(new_ex).with_traceback(tb)
1647 file_extension = 'json'
1648 parser = json
1649 else:
1650@@ -62,7 +62,7 @@ def get_spec_tests(spec_test_dir):
1651
1652 """
1653 # TODO: use logging module instead.
1654- print "pystache: spec tests: using %s" % _get_parser_info()
1655+ print("pystache: spec tests: using %s" % _get_parser_info())
1656
1657 cases = []
1658
1659@@ -103,7 +103,7 @@ def _read_spec_tests(path):
1660
1661 """
1662 b = common.read(path)
1663- u = unicode(b, encoding=FILE_ENCODING)
1664+ u = str(b, encoding=FILE_ENCODING)
1665 spec_data = parse(u)
1666 tests = spec_data['tests']
1667
1668@@ -133,7 +133,7 @@ def _convert_children(node):
1669 return
1670 # Otherwise, node is a dict, so attempt the conversion.
1671
1672- for key in node.keys():
1673+ for key in list(node.keys()):
1674 val = node[key]
1675
1676 if not isinstance(val, dict) or val.get('__tag__') != 'code':
1677@@ -158,9 +158,9 @@ def _deserialize_spec_test(data, file_path):
1678 context = data['data']
1679 description = data['desc']
1680 # PyYAML seems to leave ASCII strings as byte strings.
1681- expected = unicode(data['expected'])
1682+ expected = str(data['expected'])
1683 # TODO: switch to using dict.get().
1684- partials = data.has_key('partials') and data['partials'] or {}
1685+ partials = 'partials' in data and data['partials'] or {}
1686 template = data['template']
1687 test_name = data['name']
1688
1689@@ -237,8 +237,8 @@ def parse(u):
1690 value = loader.construct_mapping(node)
1691 return eval(value['python'], {})
1692
1693- yaml.add_constructor(u'!code', code_constructor)
1694- return yaml.load(u)
1695+ yaml.add_constructor('!code', code_constructor)
1696+ return yaml.full_load(u)
1697
1698
1699 class SpecTestBase(unittest.TestCase, AssertStringMixin):
1700diff --git a/pystache/tests/test___init__.py b/pystache/tests/test___init__.py
1701index eae42c1..63d2c3b 100644
1702--- a/pystache/tests/test___init__.py
1703+++ b/pystache/tests/test___init__.py
1704@@ -6,9 +6,9 @@ Tests of __init__.py.
1705 """
1706
1707 # Calling "import *" is allowed only at the module level.
1708-GLOBALS_INITIAL = globals().keys()
1709+GLOBALS_INITIAL = list(globals().keys())
1710 from pystache import *
1711-GLOBALS_PYSTACHE_IMPORTED = globals().keys()
1712+GLOBALS_PYSTACHE_IMPORTED = list(globals().keys())
1713
1714 import unittest
1715
1716diff --git a/pystache/tests/test_commands.py b/pystache/tests/test_commands.py
1717index 2529d25..34fe8ba 100644
1718--- a/pystache/tests/test_commands.py
1719+++ b/pystache/tests/test_commands.py
1720@@ -39,7 +39,7 @@ class CommandsTestCase(unittest.TestCase):
1721
1722 """
1723 actual = self.callScript("Hi {{thing}}", '{"thing": "world"}')
1724- self.assertEqual(actual, u"Hi world\n")
1725+ self.assertEqual(actual, "Hi world\n")
1726
1727 def tearDown(self):
1728 sys.stdout = ORIGINAL_STDOUT
1729diff --git a/pystache/tests/test_defaults.py b/pystache/tests/test_defaults.py
1730index c78ea7c..5399bb0 100644
1731--- a/pystache/tests/test_defaults.py
1732+++ b/pystache/tests/test_defaults.py
1733@@ -31,37 +31,37 @@ class DefaultsConfigurableTestCase(unittest.TestCase, AssertStringMixin):
1734 self.saved[e] = getattr(pystache.defaults, e)
1735
1736 def tearDown(self):
1737- for key, value in self.saved.items():
1738+ for key, value in list(self.saved.items()):
1739 setattr(pystache.defaults, key, value)
1740
1741 def test_tag_escape(self):
1742 """Test that changes to defaults.TAG_ESCAPE take effect."""
1743- template = u"{{foo}}"
1744+ template = "{{foo}}"
1745 context = {'foo': '<'}
1746 actual = pystache.render(template, context)
1747- self.assertString(actual, u"&lt;")
1748+ self.assertString(actual, "&lt;")
1749
1750 pystache.defaults.TAG_ESCAPE = lambda u: u
1751 actual = pystache.render(template, context)
1752- self.assertString(actual, u"<")
1753+ self.assertString(actual, "<")
1754
1755 def test_delimiters(self):
1756 """Test that changes to defaults.DELIMITERS take effect."""
1757- template = u"[[foo]]{{foo}}"
1758+ template = "[[foo]]{{foo}}"
1759 context = {'foo': 'FOO'}
1760 actual = pystache.render(template, context)
1761- self.assertString(actual, u"[[foo]]FOO")
1762+ self.assertString(actual, "[[foo]]FOO")
1763
1764 pystache.defaults.DELIMITERS = ('[[', ']]')
1765 actual = pystache.render(template, context)
1766- self.assertString(actual, u"FOO{{foo}}")
1767+ self.assertString(actual, "FOO{{foo}}")
1768
1769 def test_missing_tags(self):
1770 """Test that changes to defaults.MISSING_TAGS take effect."""
1771- template = u"{{foo}}"
1772+ template = "{{foo}}"
1773 context = {}
1774 actual = pystache.render(template, context)
1775- self.assertString(actual, u"")
1776+ self.assertString(actual, "")
1777
1778 pystache.defaults.MISSING_TAGS = 'strict'
1779 self.assertRaises(pystache.context.KeyNotFoundError,
1780diff --git a/pystache/tests/test_examples.py b/pystache/tests/test_examples.py
1781index 5c9f74d..9f93de3 100644
1782--- a/pystache/tests/test_examples.py
1783+++ b/pystache/tests/test_examples.py
1784@@ -7,15 +7,15 @@ TODO: add a docstring.
1785
1786 import unittest
1787
1788-from examples.comments import Comments
1789-from examples.double_section import DoubleSection
1790-from examples.escaped import Escaped
1791-from examples.unescaped import Unescaped
1792-from examples.template_partial import TemplatePartial
1793-from examples.delimiters import Delimiters
1794-from examples.unicode_output import UnicodeOutput
1795-from examples.unicode_input import UnicodeInput
1796-from examples.nested_context import NestedContext
1797+from .examples.comments import Comments
1798+from .examples.double_section import DoubleSection
1799+from .examples.escaped import Escaped
1800+from .examples.unescaped import Unescaped
1801+from .examples.template_partial import TemplatePartial
1802+from .examples.delimiters import Delimiters
1803+from .examples.unicode_output import UnicodeOutput
1804+from .examples.unicode_input import UnicodeInput
1805+from .examples.nested_context import NestedContext
1806 from pystache import Renderer
1807 from pystache.tests.common import EXAMPLES_DIR
1808 from pystache.tests.common import AssertStringMixin
1809@@ -29,34 +29,34 @@ class TestView(unittest.TestCase, AssertStringMixin):
1810 self.assertString(actual, expected)
1811
1812 def test_comments(self):
1813- self._assert(Comments(), u"<h1>A Comedy of Errors</h1>")
1814+ self._assert(Comments(), "<h1>A Comedy of Errors</h1>")
1815
1816 def test_double_section(self):
1817- self._assert(DoubleSection(), u"* first\n* second\n* third")
1818+ self._assert(DoubleSection(), "* first\n* second\n* third")
1819
1820 def test_unicode_output(self):
1821 renderer = Renderer()
1822 actual = renderer.render(UnicodeOutput())
1823- self.assertString(actual, u'<p>Name: Henri Poincaré</p>')
1824+ self.assertString(actual, '<p>Name: Henri Poincaré</p>')
1825
1826 def test_unicode_input(self):
1827 renderer = Renderer()
1828 actual = renderer.render(UnicodeInput())
1829- self.assertString(actual, u'abcdé')
1830+ self.assertString(actual, 'abcdé')
1831
1832 def test_escaping(self):
1833- self._assert(Escaped(), u"<h1>Bear &gt; Shark</h1>")
1834+ self._assert(Escaped(), "<h1>Bear &gt; Shark</h1>")
1835
1836 def test_literal(self):
1837 renderer = Renderer()
1838 actual = renderer.render(Unescaped())
1839- self.assertString(actual, u"<h1>Bear > Shark</h1>")
1840+ self.assertString(actual, "<h1>Bear > Shark</h1>")
1841
1842 def test_template_partial(self):
1843 renderer = Renderer(search_dirs=EXAMPLES_DIR)
1844 actual = renderer.render(TemplatePartial(renderer=renderer))
1845
1846- self.assertString(actual, u"""<h1>Welcome</h1>
1847+ self.assertString(actual, """<h1>Welcome</h1>
1848 Again, Welcome!""")
1849
1850 def test_template_partial_extension(self):
1851@@ -65,7 +65,7 @@ Again, Welcome!""")
1852 view = TemplatePartial(renderer=renderer)
1853
1854 actual = renderer.render(view)
1855- self.assertString(actual, u"""Welcome
1856+ self.assertString(actual, """Welcome
1857 -------
1858
1859 ## Again, Welcome! ##""")
1860@@ -73,7 +73,7 @@ Again, Welcome!""")
1861 def test_delimiters(self):
1862 renderer = Renderer()
1863 actual = renderer.render(Delimiters())
1864- self.assertString(actual, u"""\
1865+ self.assertString(actual, """\
1866 * It worked the first time.
1867 * And it worked the second time.
1868 * Then, surprisingly, it worked the third time.
1869@@ -82,7 +82,7 @@ Again, Welcome!""")
1870 def test_nested_context(self):
1871 renderer = Renderer()
1872 actual = renderer.render(NestedContext(renderer))
1873- self.assertString(actual, u"one and foo and two")
1874+ self.assertString(actual, "one and foo and two")
1875
1876 def test_nested_context_is_available_in_view(self):
1877 renderer = Renderer()
1878@@ -91,7 +91,7 @@ Again, Welcome!""")
1879 view.template = '{{#herp}}{{#derp}}{{nested_context_in_view}}{{/derp}}{{/herp}}'
1880
1881 actual = renderer.render(view)
1882- self.assertString(actual, u'it works!')
1883+ self.assertString(actual, 'it works!')
1884
1885 def test_partial_in_partial_has_access_to_grand_parent_context(self):
1886 renderer = Renderer(search_dirs=EXAMPLES_DIR)
1887diff --git a/pystache/tests/test_loader.py b/pystache/tests/test_loader.py
1888index f2c2187..315daff 100644
1889--- a/pystache/tests/test_loader.py
1890+++ b/pystache/tests/test_loader.py
1891@@ -55,23 +55,23 @@ class LoaderTests(unittest.TestCase, AssertStringMixin, SetupDefaults):
1892
1893 def test_init__to_unicode__default(self):
1894 loader = Loader()
1895- self.assertRaises(TypeError, loader.to_unicode, u"abc")
1896+ self.assertRaises(TypeError, loader.to_unicode, "abc")
1897
1898 decode_errors = defaults.DECODE_ERRORS
1899 string_encoding = defaults.STRING_ENCODING
1900
1901- nonascii = u'abcdé'.encode('utf-8')
1902+ nonascii = 'abcdé'.encode('utf-8')
1903
1904 loader = Loader()
1905 self.assertRaises(UnicodeDecodeError, loader.to_unicode, nonascii)
1906
1907 defaults.DECODE_ERRORS = 'ignore'
1908 loader = Loader()
1909- self.assertString(loader.to_unicode(nonascii), u'abcd')
1910+ self.assertString(loader.to_unicode(nonascii), 'abcd')
1911
1912 defaults.STRING_ENCODING = 'utf-8'
1913 loader = Loader()
1914- self.assertString(loader.to_unicode(nonascii), u'abcdé')
1915+ self.assertString(loader.to_unicode(nonascii), 'abcdé')
1916
1917
1918 def _get_path(self, filename):
1919@@ -83,9 +83,9 @@ class LoaderTests(unittest.TestCase, AssertStringMixin, SetupDefaults):
1920
1921 """
1922 loader = Loader()
1923- actual = loader.unicode("foo")
1924+ actual = loader.str("foo")
1925
1926- self.assertString(actual, u"foo")
1927+ self.assertString(actual, "foo")
1928
1929 def test_unicode__basic__input_unicode(self):
1930 """
1931@@ -93,24 +93,24 @@ class LoaderTests(unittest.TestCase, AssertStringMixin, SetupDefaults):
1932
1933 """
1934 loader = Loader()
1935- actual = loader.unicode(u"foo")
1936+ actual = loader.str("foo")
1937
1938- self.assertString(actual, u"foo")
1939+ self.assertString(actual, "foo")
1940
1941 def test_unicode__basic__input_unicode_subclass(self):
1942 """
1943 Test unicode(): default arguments with unicode-subclass input.
1944
1945 """
1946- class UnicodeSubclass(unicode):
1947+ class UnicodeSubclass(str):
1948 pass
1949
1950- s = UnicodeSubclass(u"foo")
1951+ s = UnicodeSubclass("foo")
1952
1953 loader = Loader()
1954- actual = loader.unicode(s)
1955+ actual = loader.str(s)
1956
1957- self.assertString(actual, u"foo")
1958+ self.assertString(actual, "foo")
1959
1960 def test_unicode__to_unicode__attribute(self):
1961 """
1962@@ -119,16 +119,16 @@ class LoaderTests(unittest.TestCase, AssertStringMixin, SetupDefaults):
1963 """
1964 loader = Loader()
1965
1966- non_ascii = u'abcdé'.encode('utf-8')
1967- self.assertRaises(UnicodeDecodeError, loader.unicode, non_ascii)
1968+ non_ascii = 'abcdé'.encode('utf-8')
1969+ self.assertRaises(UnicodeDecodeError, loader.str, non_ascii)
1970
1971 def to_unicode(s, encoding=None):
1972 if encoding is None:
1973 encoding = 'utf-8'
1974- return unicode(s, encoding)
1975+ return str(s, encoding)
1976
1977 loader.to_unicode = to_unicode
1978- self.assertString(loader.unicode(non_ascii), u"abcdé")
1979+ self.assertString(loader.str(non_ascii), "abcdé")
1980
1981 def test_unicode__encoding_argument(self):
1982 """
1983@@ -137,12 +137,12 @@ class LoaderTests(unittest.TestCase, AssertStringMixin, SetupDefaults):
1984 """
1985 loader = Loader()
1986
1987- non_ascii = u'abcdé'.encode('utf-8')
1988+ non_ascii = 'abcdé'.encode('utf-8')
1989
1990- self.assertRaises(UnicodeDecodeError, loader.unicode, non_ascii)
1991+ self.assertRaises(UnicodeDecodeError, loader.str, non_ascii)
1992
1993- actual = loader.unicode(non_ascii, encoding='utf-8')
1994- self.assertString(actual, u'abcdé')
1995+ actual = loader.str(non_ascii, encoding='utf-8')
1996+ self.assertString(actual, 'abcdé')
1997
1998 # TODO: check the read() unit tests.
1999 def test_read(self):
2000@@ -153,7 +153,7 @@ class LoaderTests(unittest.TestCase, AssertStringMixin, SetupDefaults):
2001 loader = Loader()
2002 path = self._get_path('ascii.mustache')
2003 actual = loader.read(path)
2004- self.assertString(actual, u'ascii: abc')
2005+ self.assertString(actual, 'ascii: abc')
2006
2007 def test_read__file_encoding__attribute(self):
2008 """
2009@@ -167,7 +167,7 @@ class LoaderTests(unittest.TestCase, AssertStringMixin, SetupDefaults):
2010
2011 loader.file_encoding = 'utf-8'
2012 actual = loader.read(path)
2013- self.assertString(actual, u'non-ascii: é')
2014+ self.assertString(actual, 'non-ascii: é')
2015
2016 def test_read__encoding__argument(self):
2017 """
2018@@ -180,7 +180,7 @@ class LoaderTests(unittest.TestCase, AssertStringMixin, SetupDefaults):
2019 self.assertRaises(UnicodeDecodeError, loader.read, path)
2020
2021 actual = loader.read(path, encoding='utf-8')
2022- self.assertString(actual, u'non-ascii: é')
2023+ self.assertString(actual, 'non-ascii: é')
2024
2025 def test_read__to_unicode__attribute(self):
2026 """
2027diff --git a/pystache/tests/test_pystache.py b/pystache/tests/test_pystache.py
2028index 5447f8d..cf5d6af 100644
2029--- a/pystache/tests/test_pystache.py
2030+++ b/pystache/tests/test_pystache.py
2031@@ -71,14 +71,14 @@ class PystacheTests(unittest.TestCase):
2032 template = "{{#stats}}({{key}} & {{value}}){{/stats}}"
2033 stats = []
2034 stats.append({'key': 123, 'value': ['something']})
2035- stats.append({'key': u"chris", 'value': 0.900})
2036+ stats.append({'key': "chris", 'value': 0.900})
2037 context = { 'stats': stats }
2038 self._assert_rendered(self.non_strings_expected, template, context)
2039
2040 def test_unicode(self):
2041 template = 'Name: {{name}}; Age: {{age}}'
2042- context = {'name': u'Henri Poincaré', 'age': 156 }
2043- self._assert_rendered(u'Name: Henri Poincaré; Age: 156', template, context)
2044+ context = {'name': 'Henri Poincaré', 'age': 156}
2045+ self._assert_rendered('Name: Henri Poincaré; Age: 156', template, context)
2046
2047 def test_sections(self):
2048 template = """<ul>{{#users}}<li>{{name}}</li>{{/users}}</ul>"""
2049diff --git a/pystache/tests/test_renderengine.py b/pystache/tests/test_renderengine.py
2050index db916f7..ed604c5 100644
2051--- a/pystache/tests/test_renderengine.py
2052+++ b/pystache/tests/test_renderengine.py
2053@@ -33,11 +33,11 @@ def mock_literal(s):
2054 s: a byte string or unicode string.
2055
2056 """
2057- if isinstance(s, unicode):
2058+ if isinstance(s, str):
2059 # Strip off unicode super classes, if present.
2060- u = unicode(s)
2061+ u = str(s)
2062 else:
2063- u = unicode(s, encoding='ascii')
2064+ u = str(s, encoding='ascii')
2065
2066 # We apply upper() to make sure we are actually using our custom
2067 # function in the tests
2068@@ -94,17 +94,17 @@ class RenderTests(unittest.TestCase, AssertStringMixin, AssertExceptionMixin):
2069 engine = kwargs.get('engine', self._engine())
2070
2071 if partials is not None:
2072- engine.resolve_partial = lambda key: unicode(partials[key])
2073+ engine.resolve_partial = lambda key: str(partials[key])
2074
2075 context = ContextStack(*context)
2076
2077 # RenderEngine.render() only accepts unicode template strings.
2078- actual = engine.render(unicode(template), context)
2079+ actual = engine.render(str(template), context)
2080
2081 self.assertString(actual=actual, expected=expected)
2082
2083 def test_render(self):
2084- self._assert_render(u'Hi Mom', 'Hi {{person}}', {'person': 'Mom'})
2085+ self._assert_render('Hi Mom', 'Hi {{person}}', {'person': 'Mom'})
2086
2087 def test__resolve_partial(self):
2088 """
2089@@ -112,10 +112,10 @@ class RenderTests(unittest.TestCase, AssertStringMixin, AssertExceptionMixin):
2090
2091 """
2092 engine = self._engine()
2093- partials = {'partial': u"{{person}}"}
2094+ partials = {'partial': "{{person}}"}
2095 engine.resolve_partial = lambda key: partials[key]
2096
2097- self._assert_render(u'Hi Mom', 'Hi {{>partial}}', {'person': 'Mom'}, engine=engine)
2098+ self._assert_render('Hi Mom', 'Hi {{>partial}}', {'person': 'Mom'}, engine=engine)
2099
2100 def test__literal(self):
2101 """
2102@@ -125,13 +125,13 @@ class RenderTests(unittest.TestCase, AssertStringMixin, AssertExceptionMixin):
2103 engine = self._engine()
2104 engine.literal = lambda s: s.upper()
2105
2106- self._assert_render(u'BAR', '{{{foo}}}', {'foo': 'bar'}, engine=engine)
2107+ self._assert_render('BAR', '{{{foo}}}', {'foo': 'bar'}, engine=engine)
2108
2109 def test_literal__sigil(self):
2110 template = "<h1>{{& thing}}</h1>"
2111 context = {'thing': 'Bear > Giraffe'}
2112
2113- expected = u"<h1>Bear > Giraffe</h1>"
2114+ expected = "<h1>Bear > Giraffe</h1>"
2115
2116 self._assert_render(expected, template, context)
2117
2118@@ -143,7 +143,7 @@ class RenderTests(unittest.TestCase, AssertStringMixin, AssertExceptionMixin):
2119 engine = self._engine()
2120 engine.escape = lambda s: "**" + s
2121
2122- self._assert_render(u'**bar', '{{foo}}', {'foo': 'bar'}, engine=engine)
2123+ self._assert_render('**bar', '{{foo}}', {'foo': 'bar'}, engine=engine)
2124
2125 def test__escape_does_not_call_literal(self):
2126 """
2127@@ -157,7 +157,7 @@ class RenderTests(unittest.TestCase, AssertStringMixin, AssertExceptionMixin):
2128 template = 'literal: {{{foo}}} escaped: {{foo}}'
2129 context = {'foo': 'bar'}
2130
2131- self._assert_render(u'literal: BAR escaped: **bar', template, context, engine=engine)
2132+ self._assert_render('literal: BAR escaped: **bar', template, context, engine=engine)
2133
2134 def test__escape_preserves_unicode_subclasses(self):
2135 """
2136@@ -167,7 +167,7 @@ class RenderTests(unittest.TestCase, AssertStringMixin, AssertExceptionMixin):
2137 variable value is markupsafe.Markup when escaping.
2138
2139 """
2140- class MyUnicode(unicode):
2141+ class MyUnicode(str):
2142 pass
2143
2144 def escape(s):
2145@@ -182,7 +182,7 @@ class RenderTests(unittest.TestCase, AssertStringMixin, AssertExceptionMixin):
2146 template = '{{foo1}} {{foo2}}'
2147 context = {'foo1': MyUnicode('bar'), 'foo2': 'bar'}
2148
2149- self._assert_render(u'**bar bar**', template, context, engine=engine)
2150+ self._assert_render('**bar bar**', template, context, engine=engine)
2151
2152 # Custom to_str for testing purposes.
2153 def _to_str(self, val):
2154@@ -197,9 +197,9 @@ class RenderTests(unittest.TestCase, AssertStringMixin, AssertExceptionMixin):
2155 template = '{{value}}'
2156 context = {'value': None}
2157
2158- self._assert_render(u'None', template, context, engine=engine)
2159+ self._assert_render('None', template, context, engine=engine)
2160 engine.to_str = self._to_str
2161- self._assert_render(u'', template, context, engine=engine)
2162+ self._assert_render('', template, context, engine=engine)
2163
2164 def test_to_str__lambda(self):
2165 """Test the to_str attribute for a lambda."""
2166@@ -207,9 +207,9 @@ class RenderTests(unittest.TestCase, AssertStringMixin, AssertExceptionMixin):
2167 template = '{{value}}'
2168 context = {'value': lambda: None}
2169
2170- self._assert_render(u'None', template, context, engine=engine)
2171+ self._assert_render('None', template, context, engine=engine)
2172 engine.to_str = self._to_str
2173- self._assert_render(u'', template, context, engine=engine)
2174+ self._assert_render('', template, context, engine=engine)
2175
2176 def test_to_str__section_list(self):
2177 """Test the to_str attribute for a section list."""
2178@@ -217,9 +217,9 @@ class RenderTests(unittest.TestCase, AssertStringMixin, AssertExceptionMixin):
2179 template = '{{#list}}{{.}}{{/list}}'
2180 context = {'list': [None, None]}
2181
2182- self._assert_render(u'NoneNone', template, context, engine=engine)
2183+ self._assert_render('NoneNone', template, context, engine=engine)
2184 engine.to_str = self._to_str
2185- self._assert_render(u'', template, context, engine=engine)
2186+ self._assert_render('', template, context, engine=engine)
2187
2188 def test_to_str__section_lambda(self):
2189 # TODO: add a test for a "method with an arity of 1".
2190@@ -239,7 +239,7 @@ class RenderTests(unittest.TestCase, AssertStringMixin, AssertExceptionMixin):
2191 template = '{{text}} {{int}} {{{int}}}'
2192 context = {'int': 100, 'text': 'foo'}
2193
2194- self._assert_render(u'FOO 100 100', template, context, engine=engine)
2195+ self._assert_render('FOO 100 100', template, context, engine=engine)
2196
2197 def test_tag__output_not_interpolated(self):
2198 """
2199@@ -248,7 +248,7 @@ class RenderTests(unittest.TestCase, AssertStringMixin, AssertExceptionMixin):
2200 """
2201 template = '{{template}}: {{planet}}'
2202 context = {'template': '{{planet}}', 'planet': 'Earth'}
2203- self._assert_render(u'{{planet}}: Earth', template, context)
2204+ self._assert_render('{{planet}}: Earth', template, context)
2205
2206 def test_tag__output_not_interpolated__section(self):
2207 """
2208@@ -257,7 +257,7 @@ class RenderTests(unittest.TestCase, AssertStringMixin, AssertExceptionMixin):
2209 """
2210 template = '{{test}}'
2211 context = {'test': '{{#hello}}'}
2212- self._assert_render(u'{{#hello}}', template, context)
2213+ self._assert_render('{{#hello}}', template, context)
2214
2215 ## Test interpolation with "falsey" values
2216 #
2217@@ -268,17 +268,17 @@ class RenderTests(unittest.TestCase, AssertStringMixin, AssertExceptionMixin):
2218 def test_interpolation__falsey__zero(self):
2219 template = '{{.}}'
2220 context = 0
2221- self._assert_render(u'0', template, context)
2222+ self._assert_render('0', template, context)
2223
2224 def test_interpolation__falsey__none(self):
2225 template = '{{.}}'
2226 context = None
2227- self._assert_render(u'None', template, context)
2228+ self._assert_render('None', template, context)
2229
2230 def test_interpolation__falsey__zero(self):
2231 template = '{{.}}'
2232 context = False
2233- self._assert_render(u'False', template, context)
2234+ self._assert_render('False', template, context)
2235
2236 # Built-in types:
2237 #
2238@@ -310,7 +310,7 @@ class RenderTests(unittest.TestCase, AssertStringMixin, AssertExceptionMixin):
2239 Check tag interpolation with a built-in type: string.
2240
2241 """
2242- self._assert_builtin_type('abc', 'upper', 'ABC', u'xyz')
2243+ self._assert_builtin_type('abc', 'upper', 'ABC', 'xyz')
2244
2245 def test_interpolation__built_in_type__integer(self):
2246 """
2247@@ -324,7 +324,7 @@ class RenderTests(unittest.TestCase, AssertStringMixin, AssertExceptionMixin):
2248 #
2249 # we need to resort to built-in attributes (double-underscored) on
2250 # the integer type.
2251- self._assert_builtin_type(15, '__neg__', -15, u'999')
2252+ self._assert_builtin_type(15, '__neg__', -15, '999')
2253
2254 def test_interpolation__built_in_type__list(self):
2255 """
2256@@ -338,7 +338,7 @@ class RenderTests(unittest.TestCase, AssertStringMixin, AssertExceptionMixin):
2257
2258 template = '{{#section}}{{%s}}{{/section}}' % attr_name
2259 context = {'section': item, attr_name: 7}
2260- self._assert_render(u'7', template, context)
2261+ self._assert_render('7', template, context)
2262
2263 # This test is also important for testing 2to3.
2264 def test_interpolation__nonascii_nonunicode(self):
2265@@ -347,8 +347,8 @@ class RenderTests(unittest.TestCase, AssertStringMixin, AssertExceptionMixin):
2266
2267 """
2268 template = '{{nonascii}}'
2269- context = {'nonascii': u'abcdé'.encode('utf-8')}
2270- self._assert_render(u'abcdé', template, context)
2271+ context = {'nonascii': 'abcdé'.encode('utf-8')}
2272+ self._assert_render('abcdé', template, context)
2273
2274 def test_implicit_iterator__literal(self):
2275 """
2276@@ -358,7 +358,7 @@ class RenderTests(unittest.TestCase, AssertStringMixin, AssertExceptionMixin):
2277 template = """{{#test}}{{{.}}}{{/test}}"""
2278 context = {'test': ['<', '>']}
2279
2280- self._assert_render(u'<>', template, context)
2281+ self._assert_render('<>', template, context)
2282
2283 def test_implicit_iterator__escaped(self):
2284 """
2285@@ -368,7 +368,7 @@ class RenderTests(unittest.TestCase, AssertStringMixin, AssertExceptionMixin):
2286 template = """{{#test}}{{.}}{{/test}}"""
2287 context = {'test': ['<', '>']}
2288
2289- self._assert_render(u'&lt;&gt;', template, context)
2290+ self._assert_render('&lt;&gt;', template, context)
2291
2292 def test_literal__in_section(self):
2293 """
2294@@ -378,7 +378,7 @@ class RenderTests(unittest.TestCase, AssertStringMixin, AssertExceptionMixin):
2295 template = '{{#test}}1 {{{less_than}}} 2{{/test}}'
2296 context = {'test': {'less_than': '<'}}
2297
2298- self._assert_render(u'1 < 2', template, context)
2299+ self._assert_render('1 < 2', template, context)
2300
2301 def test_literal__in_partial(self):
2302 """
2303@@ -389,11 +389,11 @@ class RenderTests(unittest.TestCase, AssertStringMixin, AssertExceptionMixin):
2304 partials = {'partial': '1 {{{less_than}}} 2'}
2305 context = {'less_than': '<'}
2306
2307- self._assert_render(u'1 < 2', template, context, partials=partials)
2308+ self._assert_render('1 < 2', template, context, partials=partials)
2309
2310 def test_partial(self):
2311 partials = {'partial': "{{person}}"}
2312- self._assert_render(u'Hi Mom', 'Hi {{>partial}}', {'person': 'Mom'}, partials=partials)
2313+ self._assert_render('Hi Mom', 'Hi {{>partial}}', {'person': 'Mom'}, partials=partials)
2314
2315 def test_partial__context_values(self):
2316 """
2317@@ -406,7 +406,9 @@ class RenderTests(unittest.TestCase, AssertStringMixin, AssertExceptionMixin):
2318 partials = {'partial': 'unescaped: {{{foo}}} escaped: {{foo}}'}
2319 context = {'foo': '<'}
2320
2321- self._assert_render(u'unescaped: < escaped: &lt;', template, context, engine=engine, partials=partials)
2322+ self._assert_render(
2323+ 'unescaped: < escaped: &lt;',
2324+ template, context, engine=engine, partials=partials)
2325
2326 ## Test cases related specifically to lambdas.
2327
2328@@ -417,8 +419,8 @@ class RenderTests(unittest.TestCase, AssertStringMixin, AssertExceptionMixin):
2329
2330 """
2331 template = '{{#nonascii}}{{.}}{{/nonascii}}'
2332- context = {'nonascii': u'abcdé'.encode('utf-8')}
2333- self._assert_render(u'abcdé', template, context)
2334+ context = {'nonascii': 'abcdé'.encode('utf-8')}
2335+ self._assert_render('abcdé', template, context)
2336
2337 # This test is also important for testing 2to3.
2338 def test_lambda__returning_nonascii_nonunicode(self):
2339@@ -427,8 +429,8 @@ class RenderTests(unittest.TestCase, AssertStringMixin, AssertExceptionMixin):
2340
2341 """
2342 template = '{{lambda}}'
2343- context = {'lambda': lambda: u'abcdé'.encode('utf-8')}
2344- self._assert_render(u'abcdé', template, context)
2345+ context = {'lambda': lambda: 'abcdé'.encode('utf-8')}
2346+ self._assert_render('abcdé', template, context)
2347
2348 ## Test cases related specifically to sections.
2349
2350@@ -440,7 +442,7 @@ class RenderTests(unittest.TestCase, AssertStringMixin, AssertExceptionMixin):
2351 template = '{{/section}}'
2352 try:
2353 self._assert_render(None, template)
2354- except ParsingError, err:
2355+ except ParsingError as err:
2356 self.assertEqual(str(err), "Section end tag mismatch: section != None")
2357
2358 def test_section__end_tag_mismatch(self):
2359@@ -451,7 +453,7 @@ class RenderTests(unittest.TestCase, AssertStringMixin, AssertExceptionMixin):
2360 template = '{{#section_start}}{{/section_end}}'
2361 try:
2362 self._assert_render(None, template)
2363- except ParsingError, err:
2364+ except ParsingError as err:
2365 self.assertEqual(str(err), "Section end tag mismatch: section_end != section_start")
2366
2367 def test_section__context_values(self):
2368@@ -464,7 +466,7 @@ class RenderTests(unittest.TestCase, AssertStringMixin, AssertExceptionMixin):
2369 template = '{{#test}}unescaped: {{{foo}}} escaped: {{foo}}{{/test}}'
2370 context = {'test': {'foo': '<'}}
2371
2372- self._assert_render(u'unescaped: < escaped: &lt;', template, context, engine=engine)
2373+ self._assert_render('unescaped: < escaped: &lt;', template, context, engine=engine)
2374
2375 def test_section__context_precedence(self):
2376 """
2377@@ -473,7 +475,7 @@ class RenderTests(unittest.TestCase, AssertStringMixin, AssertExceptionMixin):
2378 """
2379 template = '{{entree}} : {{#vegetarian}}{{entree}}{{/vegetarian}}'
2380 context = {'entree': 'chicken', 'vegetarian': {'entree': 'beans and rice'}}
2381- self._assert_render(u'chicken : beans and rice', template, context)
2382+ self._assert_render('chicken : beans and rice', template, context)
2383
2384 def test_section__list_referencing_outer_context(self):
2385 """
2386@@ -491,7 +493,7 @@ class RenderTests(unittest.TestCase, AssertStringMixin, AssertExceptionMixin):
2387
2388 template = "{{#list}}{{greeting}} {{name}}, {{/list}}"
2389
2390- self._assert_render(u"Hi Al, Hi Bob, ", template, context)
2391+ self._assert_render("Hi Al, Hi Bob, ", template, context)
2392
2393 def test_section__output_not_interpolated(self):
2394 """
2395@@ -500,7 +502,7 @@ class RenderTests(unittest.TestCase, AssertStringMixin, AssertExceptionMixin):
2396 """
2397 template = '{{#section}}{{template}}{{/section}}: {{planet}}'
2398 context = {'section': True, 'template': '{{planet}}', 'planet': 'Earth'}
2399- self._assert_render(u'{{planet}}: Earth', template, context)
2400+ self._assert_render('{{planet}}: Earth', template, context)
2401
2402 # TODO: have this test case added to the spec.
2403 def test_section__string_values_not_lists(self):
2404@@ -511,7 +513,7 @@ class RenderTests(unittest.TestCase, AssertStringMixin, AssertExceptionMixin):
2405 template = '{{#section}}foo{{/section}}'
2406 context = {'section': '123'}
2407 # If strings were interpreted as lists, this would give "foofoofoo".
2408- self._assert_render(u'foo', template, context)
2409+ self._assert_render('foo', template, context)
2410
2411 def test_section__nested_truthy(self):
2412 """
2413@@ -525,7 +527,7 @@ class RenderTests(unittest.TestCase, AssertStringMixin, AssertExceptionMixin):
2414 """
2415 template = '| A {{#bool}}B {{#bool}}C{{/bool}} D{{/bool}} E |'
2416 context = {'bool': True}
2417- self._assert_render(u'| A B C D E |', template, context)
2418+ self._assert_render('| A B C D E |', template, context)
2419
2420 def test_section__nested_with_same_keys(self):
2421 """
2422@@ -537,16 +539,16 @@ class RenderTests(unittest.TestCase, AssertStringMixin, AssertExceptionMixin):
2423 # Start with an easier, working case.
2424 template = '{{#x}}{{#z}}{{y}}{{/z}}{{/x}}'
2425 context = {'x': {'z': {'y': 1}}}
2426- self._assert_render(u'1', template, context)
2427+ self._assert_render('1', template, context)
2428
2429 template = '{{#x}}{{#x}}{{y}}{{/x}}{{/x}}'
2430 context = {'x': {'x': {'y': 1}}}
2431- self._assert_render(u'1', template, context)
2432+ self._assert_render('1', template, context)
2433
2434 def test_section__lambda(self):
2435 template = '{{#test}}Mom{{/test}}'
2436 context = {'test': (lambda text: 'Hi %s' % text)}
2437- self._assert_render(u'Hi Mom', template, context)
2438+ self._assert_render('Hi Mom', template, context)
2439
2440 # This test is also important for testing 2to3.
2441 def test_section__lambda__returning_nonascii_nonunicode(self):
2442@@ -555,8 +557,8 @@ class RenderTests(unittest.TestCase, AssertStringMixin, AssertExceptionMixin):
2443
2444 """
2445 template = '{{#lambda}}{{/lambda}}'
2446- context = {'lambda': lambda text: u'abcdé'.encode('utf-8')}
2447- self._assert_render(u'abcdé', template, context)
2448+ context = {'lambda': lambda text: 'abcdé'.encode('utf-8')}
2449+ self._assert_render('abcdé', template, context)
2450
2451 def test_section__lambda__returning_nonstring(self):
2452 """
2453@@ -565,7 +567,7 @@ class RenderTests(unittest.TestCase, AssertStringMixin, AssertExceptionMixin):
2454 """
2455 template = '{{#lambda}}foo{{/lambda}}'
2456 context = {'lambda': lambda text: len(text)}
2457- self._assert_render(u'3', template, context)
2458+ self._assert_render('3', template, context)
2459
2460 def test_section__iterable(self):
2461 """
2462@@ -575,10 +577,10 @@ class RenderTests(unittest.TestCase, AssertStringMixin, AssertExceptionMixin):
2463 template = '{{#iterable}}{{.}}{{/iterable}}'
2464
2465 context = {'iterable': (i for i in range(3))} # type 'generator'
2466- self._assert_render(u'012', template, context)
2467+ self._assert_render('012', template, context)
2468
2469- context = {'iterable': xrange(4)} # type 'xrange'
2470- self._assert_render(u'0123', template, context)
2471+ context = {'iterable': range(4)} # type 'xrange'
2472+ self._assert_render('0123', template, context)
2473
2474 d = {'foo': 0, 'bar': 0}
2475 # We don't know what order of keys we'll be given, but from the
2476@@ -586,8 +588,8 @@ class RenderTests(unittest.TestCase, AssertStringMixin, AssertExceptionMixin):
2477 # "If items(), keys(), values(), iteritems(), iterkeys(), and
2478 # itervalues() are called with no intervening modifications to
2479 # the dictionary, the lists will directly correspond."
2480- expected = u''.join(d.keys())
2481- context = {'iterable': d.iterkeys()} # type 'dictionary-keyiterator'
2482+ expected = ''.join(list(d.keys()))
2483+ context = {'iterable': iter(d.keys())} # type 'dictionary-keyiterator'
2484 self._assert_render(expected, template, context)
2485
2486 def test_section__lambda__tag_in_output(self):
2487@@ -605,7 +607,7 @@ class RenderTests(unittest.TestCase, AssertStringMixin, AssertExceptionMixin):
2488 """
2489 template = '{{#test}}Hi {{person}}{{/test}}'
2490 context = {'person': 'Mom', 'test': (lambda text: text + " :)")}
2491- self._assert_render(u'Hi Mom :)', template, context)
2492+ self._assert_render('Hi Mom :)', template, context)
2493
2494 def test_section__lambda__list(self):
2495 """
2496@@ -621,7 +623,7 @@ class RenderTests(unittest.TestCase, AssertStringMixin, AssertExceptionMixin):
2497 'lambdas': [lambda text: "~{{%s}}~" % text,
2498 lambda text: "#{{%s}}#" % text]}
2499
2500- self._assert_render(u'<~bar~#bar#>', template, context)
2501+ self._assert_render('<~bar~#bar#>', template, context)
2502
2503 def test_section__lambda__mixed_list(self):
2504 """
2505@@ -636,7 +638,7 @@ class RenderTests(unittest.TestCase, AssertStringMixin, AssertExceptionMixin):
2506 context = {'foo': 'bar',
2507 'lambdas': [lambda text: "~{{%s}}~" % text, 1]}
2508
2509- self._assert_render(u'<~bar~foo>', template, context)
2510+ self._assert_render('<~bar~foo>', template, context)
2511
2512 def test_section__lambda__not_on_context_stack(self):
2513 """
2514@@ -653,7 +655,7 @@ class RenderTests(unittest.TestCase, AssertStringMixin, AssertExceptionMixin):
2515 """
2516 context = {'foo': 'bar', 'lambda': (lambda text: "{{.}}")}
2517 template = '{{#foo}}{{#lambda}}blah{{/lambda}}{{/foo}}'
2518- self._assert_render(u'bar', template, context)
2519+ self._assert_render('bar', template, context)
2520
2521 def test_section__lambda__no_reinterpolation(self):
2522 """
2523@@ -670,15 +672,15 @@ class RenderTests(unittest.TestCase, AssertStringMixin, AssertExceptionMixin):
2524 """
2525 template = '{{#planet}}{{#lambda}}dot{{/lambda}}{{/planet}}'
2526 context = {'planet': 'Earth', 'dot': '~{{.}}~', 'lambda': (lambda text: "#{{%s}}#" % text)}
2527- self._assert_render(u'#~{{.}}~#', template, context)
2528+ self._assert_render('#~{{.}}~#', template, context)
2529
2530 def test_comment__multiline(self):
2531 """
2532 Check that multiline comments are permitted.
2533
2534 """
2535- self._assert_render(u'foobar', 'foo{{! baz }}bar')
2536- self._assert_render(u'foobar', 'foo{{! \nbaz }}bar')
2537+ self._assert_render('foobar', 'foo{{! baz }}bar')
2538+ self._assert_render('foobar', 'foo{{! \nbaz }}bar')
2539
2540 def test_custom_delimiters__sections(self):
2541 """
2542@@ -689,7 +691,7 @@ class RenderTests(unittest.TestCase, AssertStringMixin, AssertExceptionMixin):
2543 """
2544 template = '{{=[[ ]]=}}[[#foo]]bar[[/foo]]'
2545 context = {'foo': True}
2546- self._assert_render(u'bar', template, context)
2547+ self._assert_render('bar', template, context)
2548
2549 def test_custom_delimiters__not_retroactive(self):
2550 """
2551@@ -698,7 +700,7 @@ class RenderTests(unittest.TestCase, AssertStringMixin, AssertExceptionMixin):
2552 Test case for issue #35: https://github.com/defunkt/pystache/issues/35
2553
2554 """
2555- expected = u' {{foo}} '
2556+ expected = ' {{foo}} '
2557 self._assert_render(expected, '{{=$ $=}} {{foo}} ')
2558 self._assert_render(expected, '{{=$ $=}} {{foo}} $={{ }}=$') # was yielding u' '.
2559
2560@@ -713,7 +715,7 @@ class RenderTests(unittest.TestCase, AssertStringMixin, AssertExceptionMixin):
2561 template = 'Hello, {{person.name}}. I see you are {{person.details.age}}.'
2562 person = Attachable(name='Biggles', details={'age': 42})
2563 context = {'person': person}
2564- self._assert_render(u'Hello, Biggles. I see you are 42.', template, context)
2565+ self._assert_render('Hello, Biggles. I see you are 42.', template, context)
2566
2567 def test_dot_notation__multiple_levels(self):
2568 """
2569@@ -722,7 +724,7 @@ class RenderTests(unittest.TestCase, AssertStringMixin, AssertExceptionMixin):
2570 """
2571 template = """Hello, Mr. {{person.name.lastname}}.
2572 I see you're back from {{person.travels.last.country.city}}."""
2573- expected = u"""Hello, Mr. Pither.
2574+ expected = """Hello, Mr. Pither.
2575 I see you're back from Cornwall."""
2576 context = {'person': {'name': {'firstname': 'unknown', 'lastname': 'Pither'},
2577 'travels': {'last': {'country': {'city': 'Cornwall'}}},
2578@@ -758,10 +760,10 @@ class RenderTests(unittest.TestCase, AssertStringMixin, AssertExceptionMixin):
2579 context = {'a': {'b': 'A.B'}, 'c': {'a': 'A'} }
2580
2581 template = '{{a.b}}'
2582- self._assert_render(u'A.B', template, context)
2583+ self._assert_render('A.B', template, context)
2584
2585 template = '{{#c}}{{a}}{{/c}}'
2586- self._assert_render(u'A', template, context)
2587+ self._assert_render('A', template, context)
2588
2589 template = '{{#c}}{{a.b}}{{/c}}'
2590 self.assertException(KeyNotFoundError, "Key %(unicode)s'a.b' not found: missing %(unicode)s'b'" %
2591diff --git a/pystache/tests/test_renderer.py b/pystache/tests/test_renderer.py
2592index 0dbe0d9..e0d2448 100644
2593--- a/pystache/tests/test_renderer.py
2594+++ b/pystache/tests/test_renderer.py
2595@@ -10,7 +10,7 @@ import os
2596 import sys
2597 import unittest
2598
2599-from examples.simple import Simple
2600+from .examples.simple import Simple
2601 from pystache import Renderer
2602 from pystache import TemplateSpec
2603 from pystache.common import TemplateNotFoundError
2604@@ -33,7 +33,7 @@ def _make_renderer():
2605 def mock_unicode(b, encoding=None):
2606 if encoding is None:
2607 encoding = 'ascii'
2608- u = unicode(b, encoding=encoding)
2609+ u = str(b, encoding=encoding)
2610 return u.upper()
2611
2612
2613@@ -197,13 +197,13 @@ class RendererTests(unittest.TestCase, AssertStringMixin):
2614
2615 """
2616 renderer = self._renderer()
2617- b = u"é".encode('utf-8')
2618+ b = "é".encode('utf-8')
2619
2620 renderer.string_encoding = "ascii"
2621- self.assertRaises(UnicodeDecodeError, renderer.unicode, b)
2622+ self.assertRaises(UnicodeDecodeError, renderer.str, b)
2623
2624 renderer.string_encoding = "utf-8"
2625- self.assertEqual(renderer.unicode(b), u"é")
2626+ self.assertEqual(renderer.str(b), "é")
2627
2628 def test_unicode__decode_errors(self):
2629 """
2630@@ -212,14 +212,14 @@ class RendererTests(unittest.TestCase, AssertStringMixin):
2631 """
2632 renderer = self._renderer()
2633 renderer.string_encoding = "ascii"
2634- b = u"déf".encode('utf-8')
2635+ b = "déf".encode('utf-8')
2636
2637 renderer.decode_errors = "ignore"
2638- self.assertEqual(renderer.unicode(b), "df")
2639+ self.assertEqual(renderer.str(b), "df")
2640
2641 renderer.decode_errors = "replace"
2642 # U+FFFD is the official Unicode replacement character.
2643- self.assertEqual(renderer.unicode(b), u'd\ufffd\ufffdf')
2644+ self.assertEqual(renderer.str(b), u'd\ufffd\ufffdf')
2645
2646 ## Test the _make_loader() method.
2647
2648@@ -243,7 +243,7 @@ class RendererTests(unittest.TestCase, AssertStringMixin):
2649 renderer = self._renderer()
2650 renderer.file_encoding = 'enc'
2651 renderer.file_extension = 'ext'
2652- renderer.unicode = unicode_
2653+ renderer.str = unicode_
2654
2655 loader = renderer._make_loader()
2656
2657@@ -260,12 +260,12 @@ class RendererTests(unittest.TestCase, AssertStringMixin):
2658 """
2659 renderer = self._renderer()
2660 rendered = renderer.render('foo')
2661- self.assertEqual(type(rendered), unicode)
2662+ self.assertEqual(type(rendered), str)
2663
2664 def test_render__unicode(self):
2665 renderer = self._renderer()
2666- actual = renderer.render(u'foo')
2667- self.assertEqual(actual, u'foo')
2668+ actual = renderer.render('foo')
2669+ self.assertEqual(actual, 'foo')
2670
2671 def test_render__str(self):
2672 renderer = self._renderer()
2673@@ -274,8 +274,8 @@ class RendererTests(unittest.TestCase, AssertStringMixin):
2674
2675 def test_render__non_ascii_character(self):
2676 renderer = self._renderer()
2677- actual = renderer.render(u'Poincaré')
2678- self.assertEqual(actual, u'Poincaré')
2679+ actual = renderer.render('Poincaré')
2680+ self.assertEqual(actual, 'Poincaré')
2681
2682 def test_render__context(self):
2683 """
2684@@ -326,7 +326,7 @@ class RendererTests(unittest.TestCase, AssertStringMixin):
2685
2686 """
2687 renderer = _make_renderer()
2688- template = u"déf".encode("utf-8")
2689+ template = "déf".encode("utf-8")
2690
2691 # Check that decode_errors and string_encoding are both respected.
2692 renderer.decode_errors = 'ignore'
2693@@ -334,7 +334,7 @@ class RendererTests(unittest.TestCase, AssertStringMixin):
2694 self.assertEqual(renderer.render(template), "df")
2695
2696 renderer.string_encoding = 'utf_8'
2697- self.assertEqual(renderer.render(template), u"déf")
2698+ self.assertEqual(renderer.render(template), "déf")
2699
2700 def test_make_resolve_partial(self):
2701 """
2702@@ -347,7 +347,7 @@ class RendererTests(unittest.TestCase, AssertStringMixin):
2703
2704 actual = resolve_partial('foo')
2705 self.assertEqual(actual, 'bar')
2706- self.assertEqual(type(actual), unicode, "RenderEngine requires that "
2707+ self.assertEqual(type(actual), str, "RenderEngine requires that "
2708 "resolve_partial return unicode strings.")
2709
2710 def test_make_resolve_partial__unicode(self):
2711@@ -362,7 +362,7 @@ class RendererTests(unittest.TestCase, AssertStringMixin):
2712 self.assertEqual(resolve_partial("partial"), "foo")
2713
2714 # Now with a value that is already unicode.
2715- renderer.partials = {'partial': u'foo'}
2716+ renderer.partials = {'partial': 'foo'}
2717 resolve_partial = renderer._make_resolve_partial()
2718 # If the next line failed, we would get the following error:
2719 # TypeError: decoding Unicode is not supported
2720@@ -373,7 +373,7 @@ class RendererTests(unittest.TestCase, AssertStringMixin):
2721 data_dir = get_data_path()
2722 renderer = Renderer(search_dirs=data_dir)
2723 actual = renderer.render_name("say_hello", to='foo')
2724- self.assertString(actual, u"Hello, foo")
2725+ self.assertString(actual, "Hello, foo")
2726
2727 def test_render_path(self):
2728 """
2729@@ -412,7 +412,7 @@ class RendererTests(unittest.TestCase, AssertStringMixin):
2730
2731 spec = Spec()
2732 actual = renderer.render(spec)
2733- self.assertString(actual, u'hello, world')
2734+ self.assertString(actual, 'hello, world')
2735
2736 def test_render__view(self):
2737 """
2738@@ -484,7 +484,7 @@ class Renderer_MakeRenderEngineTests(unittest.TestCase, AssertStringMixin, Asser
2739 Check that resolve_partial returns unicode (and not a subclass).
2740
2741 """
2742- class MyUnicode(unicode):
2743+ class MyUnicode(str):
2744 pass
2745
2746 renderer = Renderer()
2747@@ -495,12 +495,12 @@ class Renderer_MakeRenderEngineTests(unittest.TestCase, AssertStringMixin, Asser
2748
2749 actual = engine.resolve_partial('str')
2750 self.assertEqual(actual, "foo")
2751- self.assertEqual(type(actual), unicode)
2752+ self.assertEqual(type(actual), str)
2753
2754 # Check that unicode subclasses are not preserved.
2755 actual = engine.resolve_partial('subclass')
2756 self.assertEqual(actual, "abc")
2757- self.assertEqual(type(actual), unicode)
2758+ self.assertEqual(type(actual), str)
2759
2760 def test__resolve_partial__not_found(self):
2761 """
2762@@ -512,7 +512,7 @@ class Renderer_MakeRenderEngineTests(unittest.TestCase, AssertStringMixin, Asser
2763 engine = renderer._make_render_engine()
2764 resolve_partial = engine.resolve_partial
2765
2766- self.assertString(resolve_partial('foo'), u'')
2767+ self.assertString(resolve_partial('foo'), '')
2768
2769 def test__resolve_partial__not_found__missing_tags_strict(self):
2770 """
2771@@ -539,7 +539,7 @@ class Renderer_MakeRenderEngineTests(unittest.TestCase, AssertStringMixin, Asser
2772 engine = renderer._make_render_engine()
2773 resolve_partial = engine.resolve_partial
2774
2775- self.assertString(resolve_partial('foo'), u'')
2776+ self.assertString(resolve_partial('foo'), '')
2777
2778 def test__resolve_partial__not_found__partials_dict__missing_tags_strict(self):
2779 """
2780@@ -566,12 +566,12 @@ class Renderer_MakeRenderEngineTests(unittest.TestCase, AssertStringMixin, Asser
2781
2782 """
2783 renderer = self._make_renderer()
2784- renderer.unicode = mock_unicode
2785+ renderer.str = mock_unicode
2786
2787 engine = renderer._make_render_engine()
2788 literal = engine.literal
2789
2790- b = u"foo".encode("ascii")
2791+ b = "foo".encode("ascii")
2792 self.assertEqual(literal(b), "FOO")
2793
2794 def test__literal__handles_unicode(self):
2795@@ -585,7 +585,7 @@ class Renderer_MakeRenderEngineTests(unittest.TestCase, AssertStringMixin, Asser
2796 engine = renderer._make_render_engine()
2797 literal = engine.literal
2798
2799- self.assertEqual(literal(u"foo"), "foo")
2800+ self.assertEqual(literal("foo"), "foo")
2801
2802 def test__literal__returns_unicode(self):
2803 """
2804@@ -598,16 +598,16 @@ class Renderer_MakeRenderEngineTests(unittest.TestCase, AssertStringMixin, Asser
2805 engine = renderer._make_render_engine()
2806 literal = engine.literal
2807
2808- self.assertEqual(type(literal("foo")), unicode)
2809+ self.assertEqual(type(literal("foo")), str)
2810
2811- class MyUnicode(unicode):
2812+ class MyUnicode(str):
2813 pass
2814
2815 s = MyUnicode("abc")
2816
2817 self.assertEqual(type(s), MyUnicode)
2818- self.assertTrue(isinstance(s, unicode))
2819- self.assertEqual(type(literal(s)), unicode)
2820+ self.assertTrue(isinstance(s, str))
2821+ self.assertEqual(type(literal(s)), str)
2822
2823 ## Test the engine's escape attribute.
2824
2825@@ -630,12 +630,12 @@ class Renderer_MakeRenderEngineTests(unittest.TestCase, AssertStringMixin, Asser
2826
2827 """
2828 renderer = Renderer()
2829- renderer.unicode = mock_unicode
2830+ renderer.str = mock_unicode
2831
2832 engine = renderer._make_render_engine()
2833 escape = engine.escape
2834
2835- b = u"foo".encode('ascii')
2836+ b = "foo".encode('ascii')
2837 self.assertEqual(escape(b), "FOO")
2838
2839 def test__escape__has_access_to_original_unicode_subclass(self):
2840@@ -644,16 +644,16 @@ class Renderer_MakeRenderEngineTests(unittest.TestCase, AssertStringMixin, Asser
2841
2842 """
2843 renderer = Renderer()
2844- renderer.escape = lambda s: unicode(type(s).__name__)
2845+ renderer.escape = lambda s: str(type(s).__name__)
2846
2847 engine = renderer._make_render_engine()
2848 escape = engine.escape
2849
2850- class MyUnicode(unicode):
2851+ class MyUnicode(str):
2852 pass
2853
2854- self.assertEqual(escape(u"foo".encode('ascii')), unicode.__name__)
2855- self.assertEqual(escape(u"foo"), unicode.__name__)
2856+ self.assertEqual(escape("foo".encode('ascii')), str.__name__)
2857+ self.assertEqual(escape("foo"), str.__name__)
2858 self.assertEqual(escape(MyUnicode("foo")), MyUnicode.__name__)
2859
2860 def test__escape__returns_unicode(self):
2861@@ -667,17 +667,17 @@ class Renderer_MakeRenderEngineTests(unittest.TestCase, AssertStringMixin, Asser
2862 engine = renderer._make_render_engine()
2863 escape = engine.escape
2864
2865- self.assertEqual(type(escape("foo")), unicode)
2866+ self.assertEqual(type(escape("foo")), str)
2867
2868 # Check that literal doesn't preserve unicode subclasses.
2869- class MyUnicode(unicode):
2870+ class MyUnicode(str):
2871 pass
2872
2873 s = MyUnicode("abc")
2874
2875 self.assertEqual(type(s), MyUnicode)
2876- self.assertTrue(isinstance(s, unicode))
2877- self.assertEqual(type(escape(s)), unicode)
2878+ self.assertTrue(isinstance(s, str))
2879+ self.assertEqual(type(escape(s)), str)
2880
2881 ## Test the missing_tags attribute.
2882
2883@@ -706,7 +706,7 @@ class Renderer_MakeRenderEngineTests(unittest.TestCase, AssertStringMixin, Asser
2884 stack = ContextStack({'foo': 'bar'})
2885
2886 self.assertEqual('bar', engine.resolve_context(stack, 'foo'))
2887- self.assertString(u'', engine.resolve_context(stack, 'missing'))
2888+ self.assertString('', engine.resolve_context(stack, 'missing'))
2889
2890 def test__resolve_context__missing_tags_strict(self):
2891 """
2892diff --git a/pystache/tests/test_simple.py b/pystache/tests/test_simple.py
2893index 07b059f..b88bf35 100644
2894--- a/pystache/tests/test_simple.py
2895+++ b/pystache/tests/test_simple.py
2896@@ -2,11 +2,11 @@ import unittest
2897
2898 import pystache
2899 from pystache import Renderer
2900-from examples.nested_context import NestedContext
2901-from examples.complex import Complex
2902-from examples.lambdas import Lambdas
2903-from examples.template_partial import TemplatePartial
2904-from examples.simple import Simple
2905+from .examples.nested_context import NestedContext
2906+from .examples.complex import Complex
2907+from .examples.lambdas import Lambdas
2908+from .examples.template_partial import TemplatePartial
2909+from .examples.simple import Simple
2910
2911 from pystache.tests.common import EXAMPLES_DIR
2912 from pystache.tests.common import AssertStringMixin
2913@@ -20,7 +20,7 @@ class TestSimple(unittest.TestCase, AssertStringMixin):
2914 view.template = '{{#foo}}{{thing1}} and {{thing2}} and {{outer_thing}}{{/foo}}{{^foo}}Not foo!{{/foo}}'
2915
2916 actual = renderer.render(view)
2917- self.assertString(actual, u"one and foo and two")
2918+ self.assertString(actual, "one and foo and two")
2919
2920 def test_looping_and_negation_context(self):
2921 template = '{{#item}}{{header}}: {{name}} {{/item}}{{^item}} Shouldnt see me{{/item}}'
2922@@ -40,7 +40,7 @@ class TestSimple(unittest.TestCase, AssertStringMixin):
2923
2924 renderer = Renderer()
2925 actual = renderer.render(view)
2926- self.assertString(actual, u'bar != bar. oh, it does!')
2927+ self.assertString(actual, 'bar != bar. oh, it does!')
2928
2929 def test_rendering_partial(self):
2930 renderer = Renderer(search_dirs=EXAMPLES_DIR)
2931@@ -49,11 +49,11 @@ class TestSimple(unittest.TestCase, AssertStringMixin):
2932 view.template = '{{>inner_partial}}'
2933
2934 actual = renderer.render(view)
2935- self.assertString(actual, u'Again, Welcome!')
2936+ self.assertString(actual, 'Again, Welcome!')
2937
2938 view.template = '{{#looping}}{{>inner_partial}} {{/looping}}'
2939 actual = renderer.render(view)
2940- self.assertString(actual, u"Again, Welcome! Again, Welcome! Again, Welcome! ")
2941+ self.assertString(actual, "Again, Welcome! Again, Welcome! Again, Welcome! ")
2942
2943 def test_non_existent_value_renders_blank(self):
2944 view = Simple()
2945@@ -77,7 +77,7 @@ class TestSimple(unittest.TestCase, AssertStringMixin):
2946 view = TemplatePartial(renderer=renderer)
2947
2948 actual = renderer.render(view)
2949- self.assertString(actual, u"""Welcome
2950+ self.assertString(actual, """Welcome
2951 -------
2952
2953 ## Again, Welcome! ##""")
2954diff --git a/pystache/tests/test_specloader.py b/pystache/tests/test_specloader.py
2955index cacc0fc..dcdc55f 100644
2956--- a/pystache/tests/test_specloader.py
2957+++ b/pystache/tests/test_specloader.py
2958@@ -9,11 +9,11 @@ import os.path
2959 import sys
2960 import unittest
2961
2962-import examples
2963-from examples.simple import Simple
2964-from examples.complex import Complex
2965-from examples.lambdas import Lambdas
2966-from examples.inverted import Inverted, InvertedLists
2967+from . import examples
2968+from .examples.simple import Simple
2969+from .examples.complex import Complex
2970+from .examples.lambdas import Lambdas
2971+from .examples.inverted import Inverted, InvertedLists
2972 from pystache import Renderer
2973 from pystache import TemplateSpec
2974 from pystache.common import TemplateNotFoundError
2975@@ -70,7 +70,7 @@ class ViewTestCase(unittest.TestCase, AssertStringMixin):
2976 renderer2 = Renderer(search_dirs=EXAMPLES_DIR)
2977
2978 actual = renderer1.render(spec)
2979- self.assertString(actual, u"Partial: ")
2980+ self.assertString(actual, "Partial: ")
2981
2982 actual = renderer2.render(spec)
2983 self.assertEqual(actual, "Partial: No tags...")
2984@@ -79,7 +79,7 @@ class ViewTestCase(unittest.TestCase, AssertStringMixin):
2985 renderer = Renderer()
2986 actual = renderer.render(Simple())
2987
2988- self.assertString(actual, u"Hi pizza!")
2989+ self.assertString(actual, "Hi pizza!")
2990
2991 def test_non_callable_attributes(self):
2992 view = Simple()
2993@@ -92,7 +92,7 @@ class ViewTestCase(unittest.TestCase, AssertStringMixin):
2994 def test_complex(self):
2995 renderer = Renderer()
2996 actual = renderer.render(Complex())
2997- self.assertString(actual, u"""\
2998+ self.assertString(actual, """\
2999 <h1>Colors</h1>
3000 <ul>
3001 <li><strong>red</strong></li>
3002@@ -111,7 +111,7 @@ class ViewTestCase(unittest.TestCase, AssertStringMixin):
3003
3004 renderer = Renderer()
3005 actual = renderer.render(view)
3006- self.assertString(actual, u'nopqrstuvwxyz')
3007+ self.assertString(actual, 'nopqrstuvwxyz')
3008
3009 def test_higher_order_lambda(self):
3010 view = Lambdas()
3011@@ -119,7 +119,7 @@ class ViewTestCase(unittest.TestCase, AssertStringMixin):
3012
3013 renderer = Renderer()
3014 actual = renderer.render(view)
3015- self.assertString(actual, u'abcdefghijklmnopqrstuvwxyz')
3016+ self.assertString(actual, 'abcdefghijklmnopqrstuvwxyz')
3017
3018 def test_partials_with_lambda(self):
3019 view = Lambdas()
3020@@ -127,7 +127,7 @@ class ViewTestCase(unittest.TestCase, AssertStringMixin):
3021
3022 renderer = Renderer(search_dirs=EXAMPLES_DIR)
3023 actual = renderer.render(view)
3024- self.assertEqual(actual, u'nopqrstuvwxyz')
3025+ self.assertEqual(actual, 'nopqrstuvwxyz')
3026
3027 def test_hierarchical_partials_with_lambdas(self):
3028 view = Lambdas()
3029@@ -135,12 +135,12 @@ class ViewTestCase(unittest.TestCase, AssertStringMixin):
3030
3031 renderer = Renderer(search_dirs=EXAMPLES_DIR)
3032 actual = renderer.render(view)
3033- self.assertString(actual, u'nopqrstuvwxyznopqrstuvwxyz')
3034+ self.assertString(actual, 'nopqrstuvwxyznopqrstuvwxyz')
3035
3036 def test_inverted(self):
3037 renderer = Renderer()
3038 actual = renderer.render(Inverted())
3039- self.assertString(actual, u"""one, two, three, empty list""")
3040+ self.assertString(actual, """one, two, three, empty list""")
3041
3042 def test_accessing_properties_on_parent_object_from_child_objects(self):
3043 parent = Thing()
3044@@ -152,12 +152,12 @@ class ViewTestCase(unittest.TestCase, AssertStringMixin):
3045 renderer = Renderer()
3046 actual = renderer.render(view, {'parent': parent})
3047
3048- self.assertString(actual, u'derp')
3049+ self.assertString(actual, 'derp')
3050
3051 def test_inverted_lists(self):
3052 renderer = Renderer()
3053 actual = renderer.render(InvertedLists())
3054- self.assertString(actual, u"""one, two, three, empty list""")
3055+ self.assertString(actual, """one, two, three, empty list""")
3056
3057
3058 def _make_specloader():
3059@@ -176,7 +176,7 @@ def _make_specloader():
3060 """
3061 if encoding is None:
3062 encoding = 'ascii'
3063- return unicode(s, encoding, 'strict')
3064+ return str(s, encoding, 'strict')
3065
3066 loader = Loader(file_encoding='ascii', to_unicode=to_unicode)
3067 return SpecLoader(loader=loader)
3068@@ -222,7 +222,7 @@ class SpecLoaderTests(unittest.TestCase, AssertIsMixin, AssertStringMixin,
3069 custom.template = "abc"
3070
3071 spec_loader = self._make_specloader()
3072- self._assert_template(spec_loader, custom, u"abc")
3073+ self._assert_template(spec_loader, custom, "abc")
3074
3075 def test_load__template__type_unicode(self):
3076 """
3077@@ -230,10 +230,10 @@ class SpecLoaderTests(unittest.TestCase, AssertIsMixin, AssertStringMixin,
3078
3079 """
3080 custom = TemplateSpec()
3081- custom.template = u"abc"
3082+ custom.template = "abc"
3083
3084 spec_loader = self._make_specloader()
3085- self._assert_template(spec_loader, custom, u"abc")
3086+ self._assert_template(spec_loader, custom, "abc")
3087
3088 def test_load__template__unicode_non_ascii(self):
3089 """
3090@@ -241,10 +241,10 @@ class SpecLoaderTests(unittest.TestCase, AssertIsMixin, AssertStringMixin,
3091
3092 """
3093 custom = TemplateSpec()
3094- custom.template = u"é"
3095+ custom.template = "é"
3096
3097 spec_loader = self._make_specloader()
3098- self._assert_template(spec_loader, custom, u"é")
3099+ self._assert_template(spec_loader, custom, "é")
3100
3101 def test_load__template__with_template_encoding(self):
3102 """
3103@@ -252,14 +252,14 @@ class SpecLoaderTests(unittest.TestCase, AssertIsMixin, AssertStringMixin,
3104
3105 """
3106 custom = TemplateSpec()
3107- custom.template = u'é'.encode('utf-8')
3108+ custom.template = 'é'.encode('utf-8')
3109
3110 spec_loader = self._make_specloader()
3111
3112- self.assertRaises(UnicodeDecodeError, self._assert_template, spec_loader, custom, u'é')
3113+ self.assertRaises(UnicodeDecodeError, self._assert_template, spec_loader, custom, 'é')
3114
3115 custom.template_encoding = 'utf-8'
3116- self._assert_template(spec_loader, custom, u'é')
3117+ self._assert_template(spec_loader, custom, 'é')
3118
3119 # TODO: make this test complete.
3120 def test_load__template__correct_loader(self):
3121@@ -279,10 +279,10 @@ class SpecLoaderTests(unittest.TestCase, AssertIsMixin, AssertStringMixin,
3122 self.encoding = None
3123
3124 # Overrides the existing method.
3125- def unicode(self, s, encoding=None):
3126+ def str(self, s, encoding=None):
3127 self.s = s
3128 self.encoding = encoding
3129- return u"foo"
3130+ return "foo"
3131
3132 loader = MockLoader()
3133 custom_loader = SpecLoader()
3134@@ -293,7 +293,7 @@ class SpecLoaderTests(unittest.TestCase, AssertIsMixin, AssertStringMixin,
3135 view.template_encoding = "encoding-foo"
3136
3137 # Check that our unicode() above was called.
3138- self._assert_template(custom_loader, view, u'foo')
3139+ self._assert_template(custom_loader, view, 'foo')
3140 self.assertEqual(loader.s, "template-foo")
3141 self.assertEqual(loader.encoding, "encoding-foo")
3142
3143@@ -410,7 +410,7 @@ class TemplateSpecTests(unittest.TestCase, AssertPathsMixin):
3144 loader = self._make_loader()
3145 actual = loader.load(custom)
3146
3147- self.assertEqual(type(actual), unicode)
3148+ self.assertEqual(type(actual), str)
3149 self.assertEqual(actual, expected)
3150
3151 def test_get_template(self):
3152@@ -420,7 +420,7 @@ class TemplateSpecTests(unittest.TestCase, AssertPathsMixin):
3153 """
3154 view = SampleView()
3155
3156- self._assert_get_template(view, u"ascii: abc")
3157+ self._assert_get_template(view, "ascii: abc")
3158
3159 def test_get_template__template_encoding(self):
3160 """
3161@@ -432,4 +432,4 @@ class TemplateSpecTests(unittest.TestCase, AssertPathsMixin):
3162 self.assertRaises(UnicodeDecodeError, self._assert_get_template, view, 'foo')
3163
3164 view.template_encoding = 'utf-8'
3165- self._assert_get_template(view, u"non-ascii: é")
3166+ self._assert_get_template(view, "non-ascii: é")
3167diff --git a/setup.cfg b/setup.cfg
3168index 861a9f5..f6f1279 100644
3169--- a/setup.cfg
3170+++ b/setup.cfg
3171@@ -1,5 +1,71 @@
3172-[egg_info]
3173-tag_build =
3174-tag_date = 0
3175-tag_svn_revision = 0
3176+[metadata]
3177+name = pystache
3178+version = attr: pystache.__version__
3179+author = Chris Wanstrath
3180+author_email = chris@ozmm.org
3181+maintainer = Steve Arnold
3182+maintainer_email = nerdboy@gentoo.org
3183+description = Mustache for Python
3184+url = https://github.com/sarnold/pystache
3185+license = MIT
3186+license_files = LICENSE
3187+classifiers =
3188+ Development Status :: 4 - Beta
3189+ Intended Audience :: Developers
3190+ License :: OSI Approved :: MIT License
3191+ Programming Language :: Python :: 3
3192+ Programming Language :: Python :: 3.6
3193+ Programming Language :: Python :: 3.7
3194+ Programming Language :: Python :: 3.8
3195+ Programming Language :: Python :: 3.9
3196
3197+[options]
3198+python_requires = >=3.6
3199+zip_safe = True
3200+include_package_data = True
3201+packages = find:
3202+
3203+[options.package_data]
3204+* = *.mustache, *.txt
3205+
3206+[options.entry_points]
3207+console_scripts =
3208+ pystache=pystache.commands.render:main
3209+ pystache-test=pystache.commands.test:main
3210+
3211+[options.extras_require]
3212+test =
3213+ nose
3214+
3215+cov =
3216+ coverage
3217+
3218+[bdist_wheel]
3219+universal = 0
3220+
3221+[check-manifest]
3222+ignore =
3223+ .codeclimate.yml
3224+ .gitattributes
3225+ .coveragerc
3226+ .gitignore
3227+ .pep8speaks.yml
3228+ codecov.yml
3229+
3230+[flake8]
3231+exclude =
3232+ .git,
3233+ __pycache__,
3234+ build,
3235+ dist
3236+
3237+max-line-length = 110
3238+
3239+[nosetests]
3240+traverse-namespace = 1
3241+verbosity = 3
3242+with-coverage = 1
3243+with-doctest = 1
3244+doctest-extension = rst
3245+cover-package = pystache
3246+cover-xml = 1
3247diff --git a/setup.py b/setup.py
3248index 0d99aae..f0b7d7f 100644
3249--- a/setup.py
3250+++ b/setup.py
3251@@ -28,7 +28,7 @@ it on the PyPI project page. If PyPI finds any issues, it will render it
3252 instead as plain-text, which we do not want.
3253
3254 To check in advance that PyPI will accept and parse the reST file as HTML,
3255-you can use the rst2html program installed by the docutils package
3256+you can use the rst2html.py program installed by the docutils package
3257 (http://docutils.sourceforge.net/). To install docutils:
3258
3259 $ pip install docutils
3260@@ -89,30 +89,7 @@ import os
3261 import shutil
3262 import sys
3263
3264-
3265-py_version = sys.version_info
3266-
3267-# distutils does not seem to support the following setup() arguments.
3268-# It displays a UserWarning when setup() is passed those options:
3269-#
3270-# * entry_points
3271-# * install_requires
3272-#
3273-# distribute works with Python 2.3.5 and above:
3274-#
3275-# http://packages.python.org/distribute/setuptools.html#building-and-distributing-packages-with-distribute
3276-#
3277-if py_version < (2, 3, 5):
3278- # TODO: this might not work yet.
3279- import distutils as dist
3280- from distutils import core
3281- setup = core.setup
3282-else:
3283- import setuptools as dist
3284- setup = dist.setup
3285-
3286-
3287-VERSION = '0.5.4' # Also change in pystache/__init__.py.
3288+from setuptools import setup
3289
3290 FILE_ENCODING = 'utf-8'
3291
3292@@ -126,22 +103,6 @@ TEMP_EXTENSION = '.temp'
3293
3294 PREP_COMMAND = 'prep'
3295
3296-CLASSIFIERS = (
3297- 'Development Status :: 4 - Beta',
3298- 'License :: OSI Approved :: MIT License',
3299- 'Programming Language :: Python',
3300- 'Programming Language :: Python :: 2',
3301- 'Programming Language :: Python :: 2.4',
3302- 'Programming Language :: Python :: 2.5',
3303- 'Programming Language :: Python :: 2.6',
3304- 'Programming Language :: Python :: 2.7',
3305- 'Programming Language :: Python :: 3',
3306- 'Programming Language :: Python :: 3.1',
3307- 'Programming Language :: Python :: 3.2',
3308- 'Programming Language :: Python :: 3.3',
3309- 'Programming Language :: Python :: Implementation :: PyPy',
3310-)
3311-
3312 # Comments in reST begin with two dots.
3313 RST_LONG_DESCRIPTION_INTRO = """\
3314 .. Do not edit this file. This file is auto-generated for PyPI by setup.py
3315@@ -221,7 +182,7 @@ def convert_md_to_rst(md_path, rst_temp_path):
3316
3317 """
3318 # Pandoc uses the UTF-8 character encoding for both input and output.
3319- command = "pandoc --write=rst --output=%s %s" % (rst_temp_path, md_path)
3320+ command = "pandoc -f markdown-smart --write=rst --output=%s %s" % (rst_temp_path, md_path)
3321 print("converting with pandoc: %s to %s\n-->%s" % (md_path, rst_temp_path,
3322 command))
3323
3324@@ -308,65 +269,9 @@ Run the following command and commit the changes--
3325 os.system('python setup.py sdist upload')
3326
3327
3328-# We use the package simplejson for older Python versions since Python
3329-# does not contain the module json before 2.6:
3330-#
3331-# http://docs.python.org/library/json.html
3332-#
3333-# Moreover, simplejson stopped officially support for Python 2.4 in version 2.1.0:
3334-#
3335-# https://github.com/simplejson/simplejson/blob/master/CHANGES.txt
3336-#
3337-requires = []
3338-if py_version < (2, 5):
3339- requires.append('simplejson<2.1')
3340-elif py_version < (2, 6):
3341- requires.append('simplejson')
3342-
3343-INSTALL_REQUIRES = requires
3344-
3345-# TODO: decide whether to use find_packages() instead. I'm not sure that
3346-# find_packages() is available with distutils, for example.
3347-PACKAGES = [
3348- 'pystache',
3349- 'pystache.commands',
3350- # The following packages are only for testing.
3351- 'pystache.tests',
3352- 'pystache.tests.data',
3353- 'pystache.tests.data.locator',
3354- 'pystache.tests.examples',
3355-]
3356-
3357-
3358-# The purpose of this function is to follow the guidance suggested here:
3359-#
3360-# http://packages.python.org/distribute/python3.html#note-on-compatibility-with-setuptools
3361-#
3362-# The guidance is for better compatibility when using setuptools (e.g. with
3363-# earlier versions of Python 2) instead of Distribute, because of new
3364-# keyword arguments to setup() that setuptools may not recognize.
3365-def get_extra_args():
3366- """
3367- Return a dictionary of extra args to pass to setup().
3368-
3369- """
3370- extra = {}
3371- # TODO: it might be more correct to check whether we are using
3372- # Distribute instead of setuptools, since use_2to3 doesn't take
3373- # effect when using Python 2, even when using Distribute.
3374- if py_version >= (3, ):
3375- # Causes 2to3 to be run during the build step.
3376- extra['use_2to3'] = True
3377-
3378- return extra
3379-
3380-
3381 def main(sys_argv):
3382
3383 # TODO: use the logging module instead of printing.
3384- # TODO: include the following in a verbose mode.
3385- sys.stderr.write("pystache: using: version %s of %s\n" % (repr(dist.__version__), repr(dist)))
3386-
3387 command = sys_argv[-1]
3388
3389 if command == 'publish':
3390@@ -377,35 +282,10 @@ def main(sys_argv):
3391 sys.exit()
3392
3393 long_description = read(RST_DESCRIPTION_PATH)
3394- template_files = ['*.mustache', '*.txt']
3395- extra_args = get_extra_args()
3396-
3397- setup(name='pystache',
3398- version=VERSION,
3399- license='MIT',
3400- description='Mustache for Python',
3401- long_description=long_description,
3402- author='Chris Wanstrath',
3403- author_email='chris@ozmm.org',
3404- maintainer='Chris Jerdonek',
3405- maintainer_email='chris.jerdonek@gmail.com',
3406- url='http://github.com/defunkt/pystache',
3407- install_requires=INSTALL_REQUIRES,
3408- packages=PACKAGES,
3409- package_data = {
3410- # Include template files so tests can be run.
3411- 'pystache.tests.data': template_files,
3412- 'pystache.tests.data.locator': template_files,
3413- 'pystache.tests.examples': template_files,
3414- },
3415- entry_points = {
3416- 'console_scripts': [
3417- 'pystache=pystache.commands.render:main',
3418- 'pystache-test=pystache.commands.test:main',
3419- ],
3420- },
3421- classifiers = CLASSIFIERS,
3422- **extra_args
3423+
3424+ setup(
3425+ long_description=long_description,
3426+ long_description_content_type='text/x-rst',
3427 )
3428
3429
3430diff --git a/setup_description.rst b/setup_description.rst
3431index 724c457..d7f1bc0 100644
3432--- a/setup_description.rst
3433+++ b/setup_description.rst
3434@@ -4,13 +4,17 @@
3435 Pystache
3436 ========
3437
3438-.. figure:: http://defunkt.github.com/pystache/images/logo_phillips.png
3439- :alt: mustachioed, monocled snake by David Phillips
3440+|ci| |Conda| |Wheels| |Release| |Python|
3441
3442-.. figure:: https://secure.travis-ci.org/defunkt/pystache.png
3443- :alt: Travis CI current build status
3444+|Latest release| |License| |Maintainability| |codecov|
3445
3446-`Pystache <http://defunkt.github.com/pystache>`__ is a Python
3447+This updated fork of Pystache is currently tested on Python 3.6+ and in
3448+Conda, on Linux, Macos, and Windows (Python 2.7 support has been
3449+removed).
3450+
3451+|image9|
3452+
3453+`Pystache <http://sarnold.github.com/pystache>`__ is a Python
3454 implementation of `Mustache <http://mustache.github.com/>`__. Mustache
3455 is a framework-agnostic, logic-free templating system inspired by
3456 `ctemplate <http://code.google.com/p/google-ctemplate/>`__ and
3457@@ -23,62 +27,45 @@ page provides a good introduction to Mustache's syntax. For a more
3458 complete (and more current) description of Mustache's behavior, see the
3459 official `Mustache spec <https://github.com/mustache/spec>`__.
3460
3461-Pystache is `semantically versioned <http://semver.org>`__ and can be
3462-found on `PyPI <http://pypi.python.org/pypi/pystache>`__. This version
3463-of Pystache passes all tests in `version
3464-1.1.2 <https://github.com/mustache/spec/tree/v1.1.2>`__ of the spec.
3465+Pystache is `semantically versioned <http://semver.org>`__ and older
3466+versions can still be found on
3467+`PyPI <http://pypi.python.org/pypi/pystache>`__. This version of
3468+Pystache now passes all tests in `version
3469+1.1.3 <https://github.com/mustache/spec/tree/v1.1.3>`__ of the spec.
3470
3471 Requirements
3472 ------------
3473
3474 Pystache is tested with--
3475
3476-- Python 2.4 (requires simplejson `version
3477- 2.0.9 <http://pypi.python.org/pypi/simplejson/2.0.9>`__ or earlier)
3478-- Python 2.5 (requires
3479- `simplejson <http://pypi.python.org/pypi/simplejson/>`__)
3480-- Python 2.6
3481-- Python 2.7
3482-- Python 3.1
3483-- Python 3.2
3484-- Python 3.3
3485-- `PyPy <http://pypy.org/>`__
3486+- Python 3.6
3487+- Python 3.7
3488+- Python 3.8
3489+- Python 3.9
3490+- Conda (py36-py39)
3491
3492 `Distribute <http://packages.python.org/distribute/>`__ (the setuptools
3493-fork) is recommended over
3494-`setuptools <http://pypi.python.org/pypi/setuptools>`__, and is required
3495-in some cases (e.g. for Python 3 support). If you use
3496-`pip <http://www.pip-installer.org/>`__, you probably already satisfy
3497-this requirement.
3498+fork) is no longer required over
3499+`setuptools <http://pypi.python.org/pypi/setuptools>`__, as the current
3500+packaging is now PEP517-compliant.
3501
3502 JSON support is needed only for the command-line interface and to run
3503-the spec tests. We require simplejson for earlier versions of Python
3504-since Python's `json <http://docs.python.org/library/json.html>`__
3505-module was added in Python 2.6.
3506-
3507-For Python 2.4 we require an earlier version of simplejson since
3508-simplejson stopped officially supporting Python 2.4 in simplejson
3509-version 2.1.0. Earlier versions of simplejson can be installed manually,
3510-as follows:
3511-
3512-::
3513-
3514- pip install 'simplejson<2.1.0'
3515+the spec tests; PyYAML can still be used (see the Develop section).
3516
3517-Official support for Python 2.4 will end with Pystache version 0.6.0.
3518+Official support for Python 2 will end with Pystache version 0.6.0.
3519
3520 Install It
3521 ----------
3522
3523 ::
3524
3525- pip install pystache
3526+ pip install -U pystache -f https://github.com/sarnold/pystache/releases/
3527
3528 And test it--
3529
3530 ::
3531
3532- pystache-test
3533+ pystache-test
3534
3535 To install and test from source (e.g. from GitHub), see the Develop
3536 section.
3537@@ -88,68 +75,68 @@ Use It
3538
3539 ::
3540
3541- >>> import pystache
3542- >>> print pystache.render('Hi {{person}}!', {'person': 'Mom'})
3543- Hi Mom!
3544+ >>> import pystache
3545+ >>> print(pystache.render('Hi {{person}}!', {'person': 'Mom'}))
3546+ Hi Mom!
3547
3548 You can also create dedicated view classes to hold your view logic.
3549
3550-Here's your view class (in .../examples/readme.py):
3551+Here's your view class (in ../pystache/tests/examples/readme.py):
3552
3553 ::
3554
3555- class SayHello(object):
3556- def to(self):
3557- return "Pizza"
3558+ class SayHello(object):
3559+ def to(self):
3560+ return "Pizza"
3561
3562 Instantiating like so:
3563
3564 ::
3565
3566- >>> from pystache.tests.examples.readme import SayHello
3567- >>> hello = SayHello()
3568+ >>> from pystache.tests.examples.readme import SayHello
3569+ >>> hello = SayHello()
3570
3571-Then your template, say\_hello.mustache (by default in the same
3572-directory as your class definition):
3573+Then your template, say_hello.mustache (by default in the same directory
3574+as your class definition):
3575
3576 ::
3577
3578- Hello, {{to}}!
3579+ Hello, {{to}}!
3580
3581 Pull it together:
3582
3583 ::
3584
3585- >>> renderer = pystache.Renderer()
3586- >>> print renderer.render(hello)
3587- Hello, Pizza!
3588+ >>> renderer = pystache.Renderer()
3589+ >>> print(renderer.render(hello))
3590+ Hello, Pizza!
3591
3592 For greater control over rendering (e.g. to specify a custom template
3593 directory), use the ``Renderer`` class like above. One can pass
3594 attributes to the Renderer class constructor or set them on a Renderer
3595 instance. To customize template loading on a per-view basis, subclass
3596 ``TemplateSpec``. See the docstrings of the
3597-`Renderer <https://github.com/defunkt/pystache/blob/master/pystache/renderer.py>`__
3598+`Renderer <https://github.com/sarnold/pystache/blob/master/pystache/renderer.py>`__
3599 class and
3600-`TemplateSpec <https://github.com/defunkt/pystache/blob/master/pystache/template_spec.py>`__
3601+`TemplateSpec <https://github.com/sarnold/pystache/blob/master/pystache/template_spec.py>`__
3602 class for more information.
3603
3604 You can also pre-parse a template:
3605
3606 ::
3607
3608- >>> parsed = pystache.parse(u"Hey {{#who}}{{.}}!{{/who}}")
3609- >>> print parsed
3610- [u'Hey ', _SectionNode(key=u'who', index_begin=12, index_end=18, parsed=[_EscapeNode(key=u'.'), u'!'])]
3611+ >>> parsed = pystache.parse(u"Hey {{#who}}{{.}}!{{/who}}")
3612+ >>> print(parsed)
3613+ ['Hey ', _SectionNode(key='who', index_begin=12, index_end=18, parsed=[_EscapeNode(key='.'), '!'])]
3614
3615 And then:
3616
3617 ::
3618
3619- >>> print renderer.render(parsed, {'who': 'Pops'})
3620- Hey Pops!
3621- >>> print renderer.render(parsed, {'who': 'you'})
3622- Hey you!
3623+ >>> print(renderer.render(parsed, {'who': 'Pops'}))
3624+ Hey Pops!
3625+ >>> print(renderer.render(parsed, {'who': 'you'}))
3626+ Hey you!
3627
3628 Python 3
3629 --------
3630@@ -211,22 +198,24 @@ To test from a source distribution (without installing)--
3631
3632 ::
3633
3634- python test_pystache.py
3635+ python test_pystache.py
3636
3637 To test Pystache with multiple versions of Python (with a single
3638-command!), you can use `tox <http://pypi.python.org/pypi/tox>`__:
3639+command!) and different platforms, you can use
3640+`tox <http://pypi.python.org/pypi/tox>`__:
3641
3642 ::
3643
3644- pip install 'virtualenv<1.8' # Version 1.8 dropped support for Python 2.4.
3645- pip install 'tox<1.4' # Version 1.4 dropped support for Python 2.4.
3646- tox
3647+ pip install tox
3648+ tox -e setup
3649
3650-If you do not have all Python versions listed in ``tox.ini``--
3651+To run tests on multiple versions with coverage, run:
3652
3653 ::
3654
3655- tox -e py26,py32 # for example
3656+ tox -e py38-linux,py39-linux # for example
3657+
3658+(substitute your platform above, eg, macos or windows)
3659
3660 The source distribution tests also include doctests and tests from the
3661 Mustache spec. To include tests from the Mustache spec in your test
3662@@ -234,8 +223,8 @@ runs:
3663
3664 ::
3665
3666- git submodule init
3667- git submodule update
3668+ git submodule init
3669+ git submodule update
3670
3671 The test harness parses the spec's (more human-readable) yaml files if
3672 `PyYAML <http://pypi.python.org/pypi/PyYAML>`__ is present. Otherwise,
3673@@ -243,94 +232,113 @@ it parses the json files. To install PyYAML--
3674
3675 ::
3676
3677- pip install pyyaml
3678+ pip install pyyaml
3679+
3680+Once the submodule is available, you can run the full test set with:
3681+
3682+::
3683+
3684+ tox -e setup . ext/spec/specs
3685
3686 To run a subset of the tests, you can use
3687 `nose <http://somethingaboutorange.com/mrl/projects/nose/0.11.1/testing.html>`__:
3688
3689 ::
3690
3691- pip install nose
3692- nosetests --tests pystache/tests/test_context.py:GetValueTests.test_dictionary__key_present
3693+ pip install nose
3694+ nosetests --tests pystache/tests/test_context.py:GetValueTests.test_dictionary__key_present
3695
3696-Using Python 3 with Pystache from source
3697-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
3698+Mailing List (old)
3699+------------------
3700
3701-Pystache is written in Python 2 and must be converted to Python 3 prior
3702-to using it with Python 3. The installation process (and tox) do this
3703-automatically.
3704+There is(was) a `mailing
3705+list <http://librelist.com/browser/pystache/>`__. Note that there is a
3706+bit of a delay between posting a message and seeing it appear in the
3707+mailing list archive.
3708
3709-To convert the code to Python 3 manually (while using Python 3)--
3710+Credits
3711+-------
3712
3713 ::
3714
3715- python setup.py build
3716+ >>> import pystache
3717+ >>> context = { 'author': 'Chris Wanstrath', 'maintainer': 'Chris Jerdonek','refurbisher': 'Steve Arnold' }
3718+ >>> print(pystache.render("Author: {{author}}\nMaintainer: {{maintainer}}\nRefurbisher: {{refurbisher}}", context))
3719+ Author: Chris Wanstrath
3720+ Maintainer: Chris Jerdonek
3721+ Refurbisher: Steve Arnold
3722
3723-This writes the converted code to a subdirectory called ``build``. By
3724-design, Python 3 builds
3725-`cannot <https://bitbucket.org/tarek/distribute/issue/292/allow-use_2to3-with-python-2>`__
3726-be created from Python 2.
3727+Pystache logo by `David Phillips <http://davidphillips.us/>`__ is
3728+licensed under a `Creative Commons Attribution-ShareAlike 3.0 Unported
3729+License <http://creativecommons.org/licenses/by-sa/3.0/deed.en_US>`__.
3730+|image10|
3731
3732-To convert the code without using setup.py, you can use
3733-`2to3 <http://docs.python.org/library/2to3.html>`__ as follows (two
3734-steps)--
3735+History
3736+=======
3737
3738-::
3739+**Note:** Official support for Python 2.7 will end with Pystache version
3740+0.6.0.
3741
3742- 2to3 --write --nobackups --no-diffs --doctests_only pystache
3743- 2to3 --write --nobackups --no-diffs pystache
3744+0.6.0 (2021-03-04)
3745+------------------
3746
3747-This converts the code (and doctests) in place.
3748+- Bump spec versions to latest => v1.1.3
3749+- Modernize python and CI tools, update docs/doctests
3750+- Update unicode conversion test for py3-only
3751+- Add pep8speaks cfg, cleanup warnings
3752+- Remove superfluous setup test/unused imports
3753+- Add conda recipe/CI build
3754
3755-To ``import pystache`` from a source distribution while using Python 3,
3756-be sure that you are importing from a directory containing a converted
3757-version of the code (e.g. from the ``build`` directory after
3758-converting), and not from the original (unconverted) source directory.
3759-Otherwise, you will get a syntax error. You can help prevent this by not
3760-running the Python IDE from the project directory when importing
3761-Pystache while using Python 3.
3762+.. _section-1:
3763
3764-Mailing List
3765-------------
3766+0.5.6 (2021-02-28)
3767+------------------
3768
3769-There is a `mailing list <http://librelist.com/browser/pystache/>`__.
3770-Note that there is a bit of a delay between posting a message and seeing
3771-it appear in the mailing list archive.
3772+- Use correct wheel name in release workflow, limit wheels
3773+- Add install check/test of downloaded wheel
3774+- Update/add ci workflows and tox cfg, bump to next dev0 version
3775
3776-Credits
3777--------
3778+.. _section-2:
3779
3780-::
3781+0.5.5 (2020-12-16)
3782+------------------
3783
3784- >>> context = { 'author': 'Chris Wanstrath', 'maintainer': 'Chris Jerdonek' }
3785- >>> print pystache.render("Author: {{author}}\nMaintainer: {{maintainer}}", context)
3786- Author: Chris Wanstrath
3787- Maintainer: Chris Jerdonek
3788+- fix document processing, update pandoc args and history
3789+- add release.yml to CI, test env settings
3790+- fix bogus commit message, update versions and tox cf
3791+- add post-test steps for building pkgs with/without doc updates
3792+- add CI build check, fix MANIFEST.in pruning
3793
3794-Pystache logo by `David Phillips <http://davidphillips.us/>`__ is
3795-licensed under a `Creative Commons Attribution-ShareAlike 3.0 Unported
3796-License <http://creativecommons.org/licenses/by-sa/3.0/deed.en_US>`__.
3797-|image0|
3798+.. _section-3:
3799
3800-History
3801-=======
3802+0.5.4-2 (2020-11-09)
3803+--------------------
3804
3805-**Note:** Official support for Python 2.4 will end with Pystache version
3806-0.6.0.
3807+- Merge pull request #1 from sarnold/rebase-up
3808+- Bugfix: test_specloader.py: fix test_find__with_directory on other
3809+ OSs
3810+- Bugfix: pystache/loader.py: remove stray windows line-endings
3811+- fix crufty (and insecure) http urls
3812+- Bugfix: modernize python versions (keep py27) and fix spec_test load
3813+ cmd
3814+
3815+.. _section-4:
3816
3817 0.5.4 (2014-07-11)
3818 ------------------
3819
3820 - Bugfix: made test with filenames OS agnostic (issue #162).
3821
3822+.. _section-5:
3823+
3824 0.5.3 (2012-11-03)
3825 ------------------
3826
3827 - Added ability to customize string coercion (e.g. to have None render
3828 as ``''``) (issue #130).
3829-- Added Renderer.render\_name() to render a template by name (issue
3830+- Added Renderer.render_name() to render a template by name (issue
3831 #122).
3832-- Added TemplateSpec.template\_path to specify an absolute path to a
3833+- Added TemplateSpec.template_path to specify an absolute path to a
3834 template (issue #41).
3835 - Added option of raising errors on missing tags/partials:
3836 ``Renderer(missing_tags='strict')`` (issue #110).
3837@@ -355,6 +363,8 @@ History
3838 - More robust handling of byte strings in Python 3.
3839 - Added Creative Commons license for David Phillips's logo.
3840
3841+.. _section-6:
3842+
3843 0.5.2 (2012-05-03)
3844 ------------------
3845
3846@@ -367,16 +377,20 @@ History
3847 context stack (issue #113).
3848 - Bugfix: lists of lambdas for sections were not rendered (issue #114).
3849
3850+.. _section-7:
3851+
3852 0.5.1 (2012-04-24)
3853 ------------------
3854
3855 - Added support for Python 3.1 and 3.2.
3856 - Added tox support to test multiple Python versions.
3857 - Added test script entry point: pystache-test.
3858-- Added \_\_version\_\_ package attribute.
3859+- Added \__version_\_ package attribute.
3860 - Test harness now supports both YAML and JSON forms of Mustache spec.
3861 - Test harness no longer requires nose.
3862
3863+.. _section-8:
3864+
3865 0.5.0 (2012-04-03)
3866 ------------------
3867
3868@@ -435,11 +449,15 @@ Bug fixes:
3869 - Passing ``**kwargs`` to ``Template()`` with no context no longer
3870 raises an exception.
3871
3872+.. _section-9:
3873+
3874 0.4.1 (2012-03-25)
3875 ------------------
3876
3877 - Added support for Python 2.4. [wangtz, jvantuyl]
3878
3879+.. _section-10:
3880+
3881 0.4.0 (2011-01-12)
3882 ------------------
3883
3884@@ -447,19 +465,25 @@ Bug fixes:
3885 - Add support for inverted lists
3886 - Decoupled template loading
3887
3888+.. _section-11:
3889+
3890 0.3.1 (2010-05-07)
3891 ------------------
3892
3893 - Fix package
3894
3895+.. _section-12:
3896+
3897 0.3.0 (2010-05-03)
3898 ------------------
3899
3900-- View.template\_path can now hold a list of path
3901+- View.template_path can now hold a list of path
3902 - Add {{& blah}} as an alias for {{{ blah }}}
3903 - Higher Order Sections
3904 - Inverted sections
3905
3906+.. _section-13:
3907+
3908 0.2.0 (2010-02-15)
3909 ------------------
3910
3911@@ -473,12 +497,16 @@ Bug fixes:
3912 [enaeseth]
3913 - Template file encoding awareness. [enaeseth]
3914
3915+.. _section-14:
3916+
3917 0.1.1 (2009-11-13)
3918 ------------------
3919
3920 - Ensure we're dealing with strings, always
3921 - Tests can be run by executing the test file directly
3922
3923+.. _section-15:
3924+
3925 0.1.0 (2009-11-12)
3926 ------------------
3927
3928@@ -510,4 +538,23 @@ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
3929 TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
3930 SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
3931
3932-.. |image0| image:: http://i.creativecommons.org/l/by-sa/3.0/88x31.png
3933+.. |ci| image:: https://github.com/sarnold/pystache/actions/workflows/ci.yml/badge.svg
3934+ :target: https://github.com/sarnold/pystache/actions/workflows/ci.yml
3935+.. |Conda| image:: https://github.com/sarnold/pystache/actions/workflows/conda.yml/badge.svg
3936+ :target: https://github.com/sarnold/pystache/actions/workflows/conda.yml
3937+.. |Wheels| image:: https://github.com/sarnold/pystache/actions/workflows/wheels.yml/badge.svg
3938+ :target: https://github.com/sarnold/pystache/actions/workflows/wheels.yml
3939+.. |Release| image:: https://github.com/sarnold/pystache/actions/workflows/release.yml/badge.svg
3940+ :target: https://github.com/sarnold/pystache/actions/workflows/release.yml
3941+.. |Python| image:: https://img.shields.io/badge/python-3.6+-blue.svg
3942+ :target: https://www.python.org/downloads/
3943+.. |Latest release| image:: https://img.shields.io/github/v/release/sarnold/pystache?include_prereleases
3944+ :target: https://github.com/sarnold/pystache/releases/latest
3945+.. |License| image:: https://img.shields.io/github/license/sarnold/pystache
3946+ :target: https://github.com/sarnold/pystache/blob/master/LICENSE
3947+.. |Maintainability| image:: https://api.codeclimate.com/v1/badges/a8fa1bf4638bfc6581b6/maintainability
3948+ :target: https://codeclimate.com/github/sarnold/pystache/maintainability
3949+.. |codecov| image:: https://codecov.io/gh/sarnold/pystache/branch/master/graph/badge.svg?token=5PZNMZBI6K
3950+ :target: https://codecov.io/gh/sarnold/pystache
3951+.. |image9| image:: gh/images/logo_phillips_small.png
3952+.. |image10| image:: http://i.creativecommons.org/l/by-sa/3.0/88x31.png
3953diff --git a/tox.ini b/tox.ini
3954index d1eaebf..66c4515 100644
3955--- a/tox.ini
3956+++ b/tox.ini
3957@@ -1,36 +1,110 @@
3958-# A tox configuration file to test across multiple Python versions.
3959-#
3960-# http://pypi.python.org/pypi/tox
3961-#
3962 [tox]
3963-# Tox 1.4 drops py24 and adds py33. In the current version, we want to
3964-# support 2.4, so we can't simultaneously support 3.3.
3965-envlist = py24,py25,py26,py27,py27-yaml,py27-noargs,py31,py32,pypy
3966+envlist = py{36,37,38,39}-{linux,macos,windows}
3967+skip_missing_interpreters = true
3968+isolated_build = true
3969+#skipsdist = true
3970+
3971+[gh-actions]
3972+python =
3973+ 3.6: py36
3974+ 3.7: py37
3975+ 3.8: py38
3976+ 3.9: py39
3977+
3978+[gh-actions:env]
3979+PLATFORM =
3980+ ubuntu-18.04: linux
3981+ macos-latest: macos
3982+ windows-latest: windows
3983
3984 [testenv]
3985+passenv = CI PYTHON PYTHONIOENCODING
3986+
3987+deps =
3988+ pip>=20.0.1
3989+ nose
3990+ coverage
3991+
3992+commands =
3993+ nosetests -sx . {posargs}
3994+
3995+[testenv:bare]
3996 # Change the working directory so that we don't import the pystache located
3997 # in the original location.
3998+deps =
3999+ pip>=20.0.1
4000+ -e .
4001+
4002 changedir =
4003 {envbindir}
4004+
4005 commands =
4006- pystache-test {toxinidir}
4007+ pystache-test
4008+
4009+[testenv:bench]
4010+passenv = CI PYTHON PYTHONIOENCODING
4011
4012-# Check that the spec tests work with PyYAML.
4013-[testenv:py27-yaml]
4014-basepython =
4015- python2.7
4016 deps =
4017- PyYAML
4018-changedir =
4019- {envbindir}
4020+ pip>=20.0.1
4021+ # uncomment for comparison, posargs expects a number, eg, 10000
4022+ #chevron
4023+
4024+commands_pre =
4025+ pip install .
4026+
4027 commands =
4028- pystache-test {toxinidir}
4029+ python pystache/tests/benchmark.py {posargs}
4030+
4031+[testenv:setup]
4032+passenv = CI PYTHON PYTHONIOENCODING
4033+
4034+deps =
4035+ pyyaml
4036+ twine
4037+
4038+commands =
4039+ python setup.py install
4040+ twine check dist/*
4041+ pystache-test {posargs}
4042+
4043+[testenv:deploy]
4044+passenv = CI PYTHON PYTHONIOENCODING
4045+allowlist_externals = bash
4046+
4047+deps =
4048+ pip>=19.0.1
4049+ wheel
4050+ pep517
4051+ twine
4052+
4053+commands =
4054+ python -m pep517.build .
4055+ twine check dist/*
4056+
4057+[testenv:check]
4058+passenv = CI PYTHON PYTHONIOENCODING
4059+skip_install = true
4060+
4061+allowlist_externals = bash
4062+
4063+deps =
4064+ pip>=20.0.1
4065
4066-# Check that pystache-test works from an install with no arguments.
4067-[testenv:py27-noargs]
4068-basepython =
4069- python2.7
4070-changedir =
4071- {envbindir}
4072 commands =
4073+ bash -c 'export WHL_FILE=$(ls dist/*.whl); \
4074+ python -m pip install $WHL_FILE'
4075 pystache-test
4076+
4077+[testenv:docs]
4078+passenv = CI PYTHON PYTHONIOENCODING
4079+allowlist_externals = bash
4080+
4081+deps =
4082+ pip>=19.0.1
4083+ wheel
4084+ docutils
4085+ # apt/emerge pandoc first
4086+
4087+commands =
4088+ python setup.py prep
4089+ bash -c 'python setup.py --long-description | rst2html.py -v --no-raw > out.html'
4090diff --git a/travis.yml_disabled b/travis.yml_disabled
4091new file mode 100644
4092index 0000000..f0b4042
4093--- /dev/null
4094+++ b/travis.yml_disabled
4095@@ -0,0 +1,52 @@
4096+dist: xenial
4097+language: python
4098+
4099+# Travis CI has no plans to support Jython and no longer supports Python 2.5.
4100+python:
4101+ - "2.7"
4102+ - "3.5"
4103+ - "3.6"
4104+ - "3.7"
4105+ - "3.8"
4106+ - "3.9-dev"
4107+ - "nightly"
4108+
4109+matrix:
4110+ fast_finish: true
4111+ include:
4112+ - os: osx
4113+ # osx is goofy, ``python`` is always py2, images mutate fast
4114+ language: shell
4115+ before_install:
4116+ - pip3 install --upgrade pip wheel
4117+ install:
4118+ - python3 setup.py install
4119+ script:
4120+ - pystache-test . ext/spec/specs
4121+ - os: windows
4122+ # windows is even goofier, install path is different for python/python3
4123+ # but either way you get python3 and the cmd is always ``python`` o.O
4124+ # (also versions mutuate like bacteria)
4125+ language: shell
4126+ before_install:
4127+ - choco install python3 --params "/InstallDir:C:\\Python"
4128+ - python -m pip install --upgrade pip wheel
4129+ env: PATH="/c/Python:/c/Python/Scripts:$PATH"
4130+ install:
4131+ - python setup.py install
4132+ script:
4133+ - pystache-test . ext/spec/specs
4134+ allow_failures:
4135+ - python: "nightly"
4136+
4137+# command to install dependencies
4138+install:
4139+ - pip install --upgrade pip
4140+ - pip install codecov
4141+
4142+script:
4143+ - python setup.py install
4144+ # Include the spec tests directory for Mustache spec tests and the
4145+ # project directory for doctests.
4146+ - pystache-test . ext/spec/specs
4147+ #- tox
4148--
41492.33.0
4150