diff --git a/.coveragerc b/.coveragerc new file mode 100644 index 00000000..9a336dd6 --- /dev/null +++ b/.coveragerc @@ -0,0 +1,38 @@ +# .coveragerc to control coverage.py +[run] +branch = True + +source = pystache + +omit = + .tox/* + setup.py + pystache/tests/* + +#plugins = +# coverage_python_version + +[report] +# must set this to True to see missing +#show_missing = True + +# Regexes for lines to exclude from consideration +exclude_lines = + # Have to re-enable the standard pragma + pragma: no cover + + # Don't complain about missing debug-only code: + def __repr__ + if self\.debug + + # Don't complain if tests don't hit defensive assertion code: + raise AssertionError + raise NotImplementedError + + # Don't complain if non-runnable code isn't run: + if 0: + +ignore_errors = True + +[html] +directory = cover diff --git a/.gitchangelog.rc b/.gitchangelog.rc new file mode 100644 index 00000000..5cf63a0d --- /dev/null +++ b/.gitchangelog.rc @@ -0,0 +1,295 @@ +# -*- coding: utf-8; mode: python -*- +## +## Format +## +## ACTION: [AUDIENCE:] COMMIT_MSG [!TAG ...] +## +## Description +## +## ACTION is one of 'chg', 'fix', 'new' +## +## Is WHAT the change is about. +## +## 'chg' is for refactor, small improvement, cosmetic changes... +## 'fix' is for bug fixes +## 'new' is for new features, big improvement +## +## AUDIENCE is optional and one of 'dev', 'usr', 'pkg', 'test', 'doc' +## +## Is WHO is concerned by the change. +## +## 'dev' is for developpers (API changes, refactors...) +## 'usr' is for final users (UI changes) +## 'pkg' is for packagers (packaging changes) +## 'test' is for testers (test only related changes) +## 'doc' is for doc guys (doc only changes) +## +## COMMIT_MSG is ... well ... the commit message itself. +## +## TAGs are additionnal adjective as 'refactor' 'minor' 'cosmetic' +## +## They are preceded with a '!' or a '@' (prefer the former, as the +## latter is wrongly interpreted in github.) Commonly used tags are: +## +## 'refactor' is obviously for refactoring code only +## 'minor' is for a very meaningless change (a typo, adding a comment) +## 'cosmetic' is for cosmetic driven change (re-indentation, 80-col...) +## 'wip' is for partial functionality but complete subfunctionality. +## +## Example: +## +## new: usr: support of bazaar implemented +## chg: re-indentend some lines !cosmetic +## new: dev: updated code to be compatible with last version of killer lib. +## fix: pkg: updated year of licence coverage. +## new: test: added a bunch of test around user usability of feature X. +## fix: typo in spelling my name in comment. !minor +## +## Please note that multi-line commit message are supported, and only the +## first line will be considered as the "summary" of the commit message. So +## tags, and other rules only applies to the summary. The body of the commit +## message will be displayed in the changelog without reformatting. + + +## +## ``ignore_regexps`` is a line of regexps +## +## Any commit having its full commit message matching any regexp listed here +## will be ignored and won't be reported in the changelog. +## +ignore_regexps = [ + r'@minor', r'!minor', + r'@cosmetic', r'!cosmetic', + r'@refactor', r'!refactor', + r'@wip', r'!wip', + r'^([cC]hg|[fF]ix|[nN]ew)\s*:\s*[p|P]kg:', + r'^([cC]hg|[fF]ix|[nN]ew)\s*:\s*[d|D]ev:', + r'^(.{3,3}\s*:)?\s*[fF]irst commit.?\s*$', + r'^$', ## ignore commits with empty messages +] + + +## ``section_regexps`` is a list of 2-tuples associating a string label and a +## list of regexp +## +## Commit messages will be classified in sections thanks to this. Section +## titles are the label, and a commit is classified under this section if any +## of the regexps associated is matching. +## +## Please note that ``section_regexps`` will only classify commits and won't +## make any changes to the contents. So you'll probably want to go check +## ``subject_process`` (or ``body_process``) to do some changes to the subject, +## whenever you are tweaking this variable. +## +section_regexps = [ + ('New', [ + r'^[nN]ew\s*:\s*((dev|use?r|pkg|test|doc)\s*:\s*)?([^\n]*)$', + ]), + ('Features', [ + r'^([nN]ew|[fF]eat)\s*:\s*((dev|use?r|pkg|test|doc)\s*:\s*)?([^\n]*)$', + ]), + ('Changes', [ + r'^[cC]hg\s*:\s*((dev|use?r|pkg|test|doc)\s*:\s*)?([^\n]*)$', + ]), + ('Fixes', [ + r'^[fF]ix\s*:\s*((dev|use?r|pkg|test|doc)\s*:\s*)?([^\n]*)$', + ]), + + ('Other', None ## Match all lines + ), +] + + +## ``body_process`` is a callable +## +## This callable will be given the original body and result will +## be used in the changelog. +## +## Available constructs are: +## +## - any python callable that take one txt argument and return txt argument. +## +## - ReSub(pattern, replacement): will apply regexp substitution. +## +## - Indent(chars=" "): will indent the text with the prefix +## Please remember that template engines gets also to modify the text and +## will usually indent themselves the text if needed. +## +## - Wrap(regexp=r"\n\n"): re-wrap text in separate paragraph to fill 80-Columns +## +## - noop: do nothing +## +## - ucfirst: ensure the first letter is uppercase. +## (usually used in the ``subject_process`` pipeline) +## +## - final_dot: ensure text finishes with a dot +## (usually used in the ``subject_process`` pipeline) +## +## - strip: remove any spaces before or after the content of the string +## +## - SetIfEmpty(msg="No commit message."): will set the text to +## whatever given ``msg`` if the current text is empty. +## +## Additionally, you can `pipe` the provided filters, for instance: +#body_process = Wrap(regexp=r'\n(?=\w+\s*:)') | Indent(chars=" ") +#body_process = Wrap(regexp=r'\n(?=\w+\s*:)') +#body_process = noop +body_process = ReSub(r'((^|\n)[A-Z]\w+(-\w+)*: .*(\n\s+.*)*)+$', r'') | strip + + +## ``subject_process`` is a callable +## +## This callable will be given the original subject and result will +## be used in the changelog. +## +## Available constructs are those listed in ``body_process`` doc. +subject_process = (strip | + ReSub(r'^([cC]hg|[fF]ix|[nN]ew)\s*:\s*((dev|use?r|pkg|test|doc)\s*:\s*)?([^\n@]*)(@[a-z]+\s+)*$', r'\4') | + SetIfEmpty("No commit message.") | ucfirst | final_dot) + + +## ``tag_filter_regexp`` is a regexp +## +## Tags that will be used for the changelog must match this regexp. +## +#tag_filter_regexp = r'^v?[0-9]+\.[0-9]+(\.[0-9]+)?$' +tag_filter_regexp = r'^[0-9]+\.[0-9]+(\.[0-9]+)?$' + + +## ``unreleased_version_label`` is a string or a callable that outputs a string +## +## This label will be used as the changelog Title of the last set of changes +## between last valid tag and HEAD if any. +unreleased_version_label = "(unreleased)" +#unreleased_version_label = lambda: swrap( +# ["git", "describe", "--tags"], +#shell=False) + + +## ``output_engine`` is a callable +## +## This will change the output format of the generated changelog file +## +## Available choices are: +## +## - rest_py +## +## Legacy pure python engine, outputs ReSTructured text. +## This is the default. +## +## - mustache() +## +## Template name could be any of the available templates in +## ``templates/mustache/*.tpl``. +## Requires python package ``pystache``. +## Examples: +## - mustache("markdown") +## - mustache("restructuredtext") +## +## - makotemplate() +## +## Template name could be any of the available templates in +## ``templates/mako/*.tpl``. +## Requires python package ``mako``. +## Examples: +## - makotemplate("restructuredtext") +## +#output_engine = rest_py +#output_engine = mustache("restructuredtext") +output_engine = mustache("markdown") +#output_engine = makotemplate("restructuredtext") + + +## ``include_merge`` is a boolean +## +## This option tells git-log whether to include merge commits in the log. +## The default is to include them. +include_merge = True + + +## ``log_encoding`` is a string identifier +## +## This option tells gitchangelog what encoding is outputed by ``git log``. +## The default is to be clever about it: it checks ``git config`` for +## ``i18n.logOutputEncoding``, and if not found will default to git's own +## default: ``utf-8``. +#log_encoding = 'utf-8' + + +## ``publish`` is a callable +## +## Sets what ``gitchangelog`` should do with the output generated by +## the output engine. ``publish`` is a callable taking one argument +## that is an interator on lines from the output engine. +## +## Some helper callable are provided: +## +## Available choices are: +## +## - stdout +## +## Outputs directly to standard output +## (This is the default) +## +## - FileInsertAtFirstRegexMatch(file, pattern, idx=lamda m: m.start(), flags) +## +## Creates a callable that will parse given file for the given +## regex pattern and will insert the output in the file. +## ``idx`` is a callable that receive the matching object and +## must return a integer index point where to insert the +## the output in the file. Default is to return the position of +## the start of the matched string. +## +## - FileRegexSubst(file, pattern, replace, flags) +## +## Apply a replace inplace in the given file. Your regex pattern must +## take care of everything and might be more complex. Check the README +## for a complete copy-pastable example. +## +# publish = FileInsertIntoFirstRegexMatch( +# "CHANGELOG.rst", +# r'/(?P[0-9]+\.[0-9]+(\.[0-9]+)?)\s+\([0-9]+-[0-9]{2}-[0-9]{2}\)\n--+\n/', +# idx=lambda m: m.start(1) +# ) +#publish = stdout + + +## ``revs`` is a list of callable or a list of string +## +## callable will be called to resolve as strings and allow dynamical +## computation of these. The result will be used as revisions for +## gitchangelog (as if directly stated on the command line). This allows +## to filter exaclty which commits will be read by gitchangelog. +## +## To get a full documentation on the format of these strings, please +## refer to the ``git rev-list`` arguments. There are many examples. +## +## Using callables is especially useful, for instance, if you +## are using gitchangelog to generate incrementally your changelog. +## +## Some helpers are provided, you can use them:: +## +## - FileFirstRegexMatch(file, pattern): will return a callable that will +## return the first string match for the given pattern in the given file. +## If you use named sub-patterns in your regex pattern, it'll output only +## the string matching the regex pattern named "rev". +## +## - Caret(rev): will return the rev prefixed by a "^", which is a +## way to remove the given revision and all its ancestor. +## +## Please note that if you provide a rev-list on the command line, it'll +## replace this value (which will then be ignored). +## +## If empty, then ``gitchangelog`` will act as it had to generate a full +## changelog. +## +## The default is to use all commits to make the changelog. +#revs = ["^1.0.3", ] +#revs = [ +# Caret( +# FileFirstRegexMatch( +# "CHANGELOG.rst", +# r"(?P[0-9]+\.[0-9]+(\.[0-9]+)?)\s+\([0-9]+-[0-9]{2}-[0-9]{2}\)\n--+\n")), +# "HEAD" +#] +revs = [] diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 00000000..356dd2c7 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,73 @@ +# This workflow will install Python dependencies, run tests and lint with a variety of Python versions +# For more information see: https://help.github.com/actions/language-and-framework-guides/using-python-with-github-actions + +name: ci + +on: + push: + branches: [ master ] + pull_request: + branches: [ master ] + +jobs: + build: + + runs-on: ${{ matrix.os }} + defaults: + run: + shell: bash + env: + OS: ${{ matrix.os }} + PYTHON: ${{ matrix.python-version }} + PYTHONIOENCODING: utf-8 + PIP_DOWNLOAD_CACHE: ${{ github.workspace }}/../.pip_download_cache + strategy: + fail-fast: false + matrix: + os: [ubuntu-20.04, macos-latest, windows-latest] + python-version: [3.6, 3.7, 3.8, 3.9] + steps: + - name: Set git crlf/eol + run: | + git config --global core.autocrlf false + git config --global core.eol lf + + - uses: actions/checkout@v2 + with: + submodules: True + + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v2 + with: + python-version: ${{ matrix.python-version }} + + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install tox tox-gh-actions + + - name: Run tests with coverage + run: | + tox + env: + PLATFORM: ${{ matrix.os }} + + - name: Upload coverage to Codecov + uses: codecov/codecov-action@v1 + with: + env_vars: OS,PYTHON + + - name: Test with specs and pystache-test + run: | + tox -e setup . ext/spec/specs + + - name: Check pkg builds + run: | + tox -e deploy + + - name: Check docs + if: runner.os == 'Linux' + run: | + sudo apt-get -qq update + sudo apt-get install -y pandoc + tox -e docs diff --git a/.github/workflows/conda.yml b/.github/workflows/conda.yml new file mode 100644 index 00000000..261f9ad4 --- /dev/null +++ b/.github/workflows/conda.yml @@ -0,0 +1,55 @@ +name: Conda + +on: + workflow_dispatch: + pull_request: + push: + branches: + - master + +jobs: + build: + strategy: + fail-fast: false + matrix: + platform: [ubuntu-18.04, windows-latest, macos-latest] + python-version: [3.6, 3.7, 3.8, 3.9] + + runs-on: ${{ matrix.platform }} + + # The setup-miniconda action needs this to activate miniconda + defaults: + run: + shell: "bash -l {0}" + + steps: + - uses: actions/checkout@v2 + with: + fetch-depth: 0 + + - name: Cache conda + uses: actions/cache@v1 + with: + path: ~/conda_pkgs_dir + key: ${{matrix.os}}-conda-pkgs-${{hashFiles('**/conda/meta.yaml')}} + + - name: Get conda + uses: conda-incubator/setup-miniconda@v2 + with: + python-version: ${{ matrix.python-version }} + channels: conda-forge + channel-priority: strict + use-only-tar-bz2: true + auto-activate-base: true + + - name: Prepare + run: conda install conda-build conda-verify + + - name: Build + run: conda build conda + + - name: Install + run: conda install -c ${CONDA_PREFIX}/conda-bld/ pystache + + - name: Test + run: python test_pystache.py diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 00000000..f33c4b5d --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,94 @@ +name: Release + +on: + push: + # release on tag push + tags: + - '*' + +jobs: + wheels: + + runs-on: ${{ matrix.os }} + defaults: + run: + shell: bash + env: + PYTHONIOENCODING: utf-8 + strategy: + fail-fast: false + matrix: + os: [ubuntu-18.04, macos-latest, windows-latest] + python-version: [3.6, 3.7, 3.8, 3.9] + exclude: + - os: windows-latest + python-version: 2.7 + + steps: + - name: Set git crlf/eol + run: | + git config --global core.autocrlf false + git config --global core.eol lf + + - uses: actions/checkout@v2 + with: + fetch-depth: 0 + + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v2 + with: + python-version: ${{ matrix.python-version }} + + - name: Install dependencies + run: | + python -m pip install --upgrade pip wheel + pip install tox tox-gh-actions + + - name: Build dist pkgs + run: | + tox -e deploy + + - name: Upload artifacts + if: matrix.python-version == 3.7 && runner.os == 'Linux' + uses: actions/upload-artifact@v2 + with: + name: wheels + path: ./dist/*.whl + + create_release: + name: Create Release + needs: [wheels] + runs-on: ubuntu-18.04 + + steps: + - name: Get version + id: get_version + run: | + echo "VERSION=${GITHUB_REF/refs\/tags\//}" >> $GITHUB_ENV + echo ${{ env.VERSION }} + + - uses: actions/checkout@v2 + with: + fetch-depth: 0 + + # download all artifacts to project dir + - uses: actions/download-artifact@v2 + + - name: Generate changes file + uses: sarnold/gitchangelog-action@master + with: + github_token: ${{ secrets.GITHUB_TOKEN}} + + - name: Create release + id: create_release + uses: softprops/action-gh-release@v1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + tag_name: ${{ env.VERSION }} + name: Release v${{ env.VERSION }} + body_path: CHANGES.md + draft: false + prerelease: false + files: | + wheels/pystache*.whl diff --git a/.github/workflows/wheels.yml b/.github/workflows/wheels.yml new file mode 100644 index 00000000..58f0c5ea --- /dev/null +++ b/.github/workflows/wheels.yml @@ -0,0 +1,82 @@ +name: Wheels + +on: + workflow_dispatch: + pull_request: + #push: + #branches: [ master ] + +jobs: + build: + + runs-on: ${{ matrix.os }} + defaults: + run: + shell: bash + env: + PYTHONIOENCODING: utf-8 + strategy: + fail-fast: false + matrix: + os: [ubuntu-18.04, macos-latest, windows-latest] + python-version: [3.6, 3.7, 3.8, 3.9] + + steps: + - name: Set git crlf/eol + run: | + git config --global core.autocrlf false + git config --global core.eol lf + + - uses: actions/checkout@v2 + with: + fetch-depth: 0 + + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v2 + with: + python-version: ${{ matrix.python-version }} + + - name: Install dependencies + run: | + python -m pip install --upgrade pip wheel + pip install tox tox-gh-actions + + - name: Build dist pkgs + run: | + tox -e deploy + + - name: Upload artifacts + if: matrix.python-version == 3.7 && runner.os == 'Linux' + uses: actions/upload-artifact@v2 + with: + name: wheels + path: ./dist/*.whl + + check_artifact: + name: Check wheel artifact + needs: [build] + runs-on: ${{ matrix.os }} + defaults: + run: + shell: bash + env: + PYTHONIOENCODING: utf-8 + strategy: + fail-fast: false + matrix: + os: [ubuntu-18.04, macos-latest, windows-latest] + python-version: [3.6, 3.8, 3.9] + + steps: + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v2 + with: + python-version: ${{ matrix.python-version }} + + # download all artifacts to project dir + - uses: actions/download-artifact@v2 + + - name: Check wheel install + run: | + bash -c 'export WHL=$(ls wheels/*.whl); python -m pip install $WHL' + pystache-test diff --git a/.gitignore b/.gitignore index 758d62df..e44990cc 100644 --- a/.gitignore +++ b/.gitignore @@ -15,3 +15,5 @@ build dist MANIFEST pystache.egg-info +coverage.xml +.coverage diff --git a/.gitmodules b/.gitmodules index c55c8e5e..74f48855 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,3 @@ [submodule "ext/spec"] path = ext/spec - url = http://github.com/mustache/spec.git + url = https://github.com/mustache/spec.git diff --git a/.pep8speaks.yml b/.pep8speaks.yml new file mode 100644 index 00000000..e841b66f --- /dev/null +++ b/.pep8speaks.yml @@ -0,0 +1,15 @@ +scanner: + linter: flake8 # Other option is pycodestyle + +no_blank_comment: False # If True, no comment is made on PR without any errors. +descending_issues_order: True # If True, PEP 8 issues in message will be displayed in descending order of line numbers in the file + +[flake8] +exclude = + .git, + .github, + __pycache__, + build, + dist + +max-line-length = 110 diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 00227053..00000000 --- a/.travis.yml +++ /dev/null @@ -1,14 +0,0 @@ -language: python - -# Travis CI has no plans to support Jython and no longer supports Python 2.5. -python: - - 2.6 - - 2.7 - - 3.2 - - pypy - -script: - - python setup.py install - # Include the spec tests directory for Mustache spec tests and the - # project directory for doctests. - - pystache-test . ext/spec/specs diff --git a/HISTORY.md b/HISTORY.md index e5b7638a..60b63081 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -1,7 +1,42 @@ History ======= -**Note:** Official support for Python 2.4 will end with Pystache version 0.6.0. +**Note:** Official support for Python 2.7 will end with Pystache version 0.6.0. + +0.6.0 (2021-03-04) +------------------ + +- Bump spec versions to latest => v1.1.3 +- Modernize python and CI tools, update docs/doctests +- Update unicode conversion test for py3-only +- Add pep8speaks cfg, cleanup warnings +- Remove superfluous setup test/unused imports +- Add conda recipe/CI build + +0.5.6 (2021-02-28) +------------------ + +- Use correct wheel name in release workflow, limit wheels +- Add install check/test of downloaded wheel +- Update/add ci workflows and tox cfg, bump to next dev0 version + +0.5.5 (2020-12-16) +------------------ + +- fix document processing, update pandoc args and history +- add release.yml to CI, test env settings +- fix bogus commit message, update versions and tox cf +- add post-test steps for building pkgs with/without doc updates +- add CI build check, fix MANIFEST.in pruning + +0.5.4-2 (2020-11-09) +-------------------- + +- Merge pull request #1 from sarnold/rebase-up +- Bugfix: test_specloader.py: fix test_find__with_directory on other OSs +- Bugfix: pystache/loader.py: remove stray windows line-endings +- fix crufty (and insecure) http urls +- Bugfix: modernize python versions (keep py27) and fix spec_test load cmd 0.5.4 (2014-07-11) ------------------ diff --git a/MANIFEST.in b/MANIFEST.in index bdc64bf7..1593143f 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,7 +1,4 @@ -include README.md -include HISTORY.md -include LICENSE -include TODO.md +include README.md HISTORY.md TODO.md LICENSE include setup_description.rst include tox.ini include test_pystache.py @@ -11,3 +8,6 @@ recursive-include pystache/tests *.mustache *.txt # We deliberately exclude the gh/ directory because it contains copies # of resources needed only for the web page hosted on GitHub (via the # gh-pages branch). +exclude *.ini *travis* +prune gh +prune .git* diff --git a/README.md b/README.md index 54a96088..1203b7a0 100644 --- a/README.md +++ b/README.md @@ -10,11 +10,25 @@ Pystache -![](http://defunkt.github.com/pystache/images/logo_phillips.png "mustachioed, monocled snake by David Phillips") +[![ci](https://github.com/sarnold/pystache/actions/workflows/ci.yml/badge.svg)](https://github.com/sarnold/pystache/actions/workflows/ci.yml) +[![Conda](https://github.com/sarnold/pystache/actions/workflows/conda.yml/badge.svg)](https://github.com/sarnold/pystache/actions/workflows/conda.yml) +[![Wheels](https://github.com/sarnold/pystache/actions/workflows/wheels.yml/badge.svg)](https://github.com/sarnold/pystache/actions/workflows/wheels.yml) +[![Release](https://github.com/sarnold/pystache/actions/workflows/release.yml/badge.svg)](https://github.com/sarnold/pystache/actions/workflows/release.yml) +[![Python](https://img.shields.io/badge/python-3.6+-blue.svg)](https://www.python.org/downloads/) -![](https://secure.travis-ci.org/defunkt/pystache.png "Travis CI current build status") +[![Latest release](https://img.shields.io/github/v/release/sarnold/pystache?include_prereleases)](https://github.com/sarnold/pystache/releases/latest) +[![License](https://img.shields.io/github/license/sarnold/pystache)](https://github.com/sarnold/pystache/blob/master/LICENSE) +[![Maintainability](https://api.codeclimate.com/v1/badges/a8fa1bf4638bfc6581b6/maintainability)](https://codeclimate.com/github/sarnold/pystache/maintainability) +[![codecov](https://codecov.io/gh/sarnold/pystache/branch/master/graph/badge.svg?token=5PZNMZBI6K)](https://codecov.io/gh/sarnold/pystache) -[Pystache](http://defunkt.github.com/pystache) is a Python + + +This updated fork of Pystache is currently tested on Python 3.6+ and in +Conda, on Linux, Macos, and Windows (Python 2.7 support has been removed). + +![](gh/images/logo_phillips_small.png "mustachioed, monocled snake by David Phillips") + +[Pystache](http://sarnold.github.com/pystache) is a Python implementation of [Mustache](http://mustache.github.com/). Mustache is a framework-agnostic, logic-free templating system inspired by [ctemplate](http://code.google.com/p/google-ctemplate/) and @@ -27,10 +41,10 @@ provides a good introduction to Mustache's syntax. For a more complete (and more current) description of Mustache's behavior, see the official [Mustache spec](https://github.com/mustache/spec). -Pystache is [semantically versioned](http://semver.org) and can be found -on [PyPI](http://pypi.python.org/pypi/pystache). This version of -Pystache passes all tests in [version -1.1.2](https://github.com/mustache/spec/tree/v1.1.2) of the spec. +Pystache is [semantically versioned](http://semver.org) and older versions +can still be found on [PyPI](http://pypi.python.org/pypi/pystache). This +version of Pystache now passes all tests in [version +1.1.3](https://github.com/mustache/spec/tree/v1.1.3) of the spec. Requirements @@ -38,41 +52,25 @@ Requirements Pystache is tested with-- -- Python 2.4 (requires simplejson [version - 2.0.9](http://pypi.python.org/pypi/simplejson/2.0.9) or earlier) -- Python 2.5 (requires - [simplejson](http://pypi.python.org/pypi/simplejson/)) -- Python 2.6 -- Python 2.7 -- Python 3.1 -- Python 3.2 -- Python 3.3 -- [PyPy](http://pypy.org/) +- Python 3.6 +- Python 3.7 +- Python 3.8 +- Python 3.9 +- Conda (py36-py39) [Distribute](http://packages.python.org/distribute/) (the setuptools fork) -is recommended over [setuptools](http://pypi.python.org/pypi/setuptools), -and is required in some cases (e.g. for Python 3 support). -If you use [pip](http://www.pip-installer.org/), you probably already satisfy -this requirement. +is no longer required over [setuptools](http://pypi.python.org/pypi/setuptools), +as the current packaging is now PEP517-compliant. JSON support is needed only for the command-line interface and to run -the spec tests. We require simplejson for earlier versions of Python -since Python's [json](http://docs.python.org/library/json.html) module -was added in Python 2.6. - -For Python 2.4 we require an earlier version of simplejson since -simplejson stopped officially supporting Python 2.4 in simplejson -version 2.1.0. Earlier versions of simplejson can be installed manually, -as follows: +the spec tests; PyYAML can still be used (see the Develop section). - pip install 'simplejson<2.1.0' - -Official support for Python 2.4 will end with Pystache version 0.6.0. +Official support for Python 2 will end with Pystache version 0.6.0. Install It ---------- - pip install pystache + pip install -U pystache -f https://github.com/sarnold/pystache/releases/ And test it-- @@ -85,12 +83,12 @@ Use It ------ >>> import pystache - >>> print pystache.render('Hi {{person}}!', {'person': 'Mom'}) + >>> print(pystache.render('Hi {{person}}!', {'person': 'Mom'})) Hi Mom! You can also create dedicated view classes to hold your view logic. -Here's your view class (in .../examples/readme.py): +Here's your view class (in ../pystache/tests/examples/readme.py): class SayHello(object): def to(self): @@ -109,7 +107,7 @@ directory as your class definition): Pull it together: >>> renderer = pystache.Renderer() - >>> print renderer.render(hello) + >>> print(renderer.render(hello)) Hello, Pizza! For greater control over rendering (e.g. to specify a custom template @@ -117,22 +115,22 @@ directory), use the `Renderer` class like above. One can pass attributes to the Renderer class constructor or set them on a Renderer instance. To customize template loading on a per-view basis, subclass `TemplateSpec`. See the docstrings of the -[Renderer](https://github.com/defunkt/pystache/blob/master/pystache/renderer.py) +[Renderer](https://github.com/sarnold/pystache/blob/master/pystache/renderer.py) class and -[TemplateSpec](https://github.com/defunkt/pystache/blob/master/pystache/template_spec.py) +[TemplateSpec](https://github.com/sarnold/pystache/blob/master/pystache/template_spec.py) class for more information. You can also pre-parse a template: >>> parsed = pystache.parse(u"Hey {{#who}}{{.}}!{{/who}}") - >>> print parsed - [u'Hey ', _SectionNode(key=u'who', index_begin=12, index_end=18, parsed=[_EscapeNode(key=u'.'), u'!'])] + >>> print(parsed) + ['Hey ', _SectionNode(key='who', index_begin=12, index_end=18, parsed=[_EscapeNode(key='.'), '!'])] And then: - >>> print renderer.render(parsed, {'who': 'Pops'}) + >>> print(renderer.render(parsed, {'who': 'Pops'})) Hey Pops! - >>> print renderer.render(parsed, {'who': 'you'}) + >>> print(renderer.render(parsed, {'who': 'you'})) Hey you! Python 3 @@ -194,15 +192,16 @@ To test from a source distribution (without installing)-- python test_pystache.py To test Pystache with multiple versions of Python (with a single -command!), you can use [tox](http://pypi.python.org/pypi/tox): +command!) and different platforms, you can use [tox](http://pypi.python.org/pypi/tox): + + pip install tox + tox -e setup - pip install 'virtualenv<1.8' # Version 1.8 dropped support for Python 2.4. - pip install 'tox<1.4' # Version 1.4 dropped support for Python 2.4. - tox +To run tests on multiple versions with coverage, run: -If you do not have all Python versions listed in `tox.ini`-- + tox -e py38-linux,py39-linux # for example - tox -e py26,py32 # for example +(substitute your platform above, eg, macos or windows) The source distribution tests also include doctests and tests from the Mustache spec. To include tests from the Mustache spec in your test @@ -217,57 +216,33 @@ parses the json files. To install PyYAML-- pip install pyyaml +Once the submodule is available, you can run the full test set with: + + tox -e setup . ext/spec/specs + To run a subset of the tests, you can use [nose](http://somethingaboutorange.com/mrl/projects/nose/0.11.1/testing.html): pip install nose nosetests --tests pystache/tests/test_context.py:GetValueTests.test_dictionary__key_present -### Using Python 3 with Pystache from source - -Pystache is written in Python 2 and must be converted to Python 3 prior to -using it with Python 3. The installation process (and tox) do this -automatically. -To convert the code to Python 3 manually (while using Python 3)-- +Mailing List (old) +------------------ - python setup.py build - -This writes the converted code to a subdirectory called `build`. -By design, Python 3 builds -[cannot](https://bitbucket.org/tarek/distribute/issue/292/allow-use_2to3-with-python-2) -be created from Python 2. - -To convert the code without using setup.py, you can use -[2to3](http://docs.python.org/library/2to3.html) as follows (two steps)-- - - 2to3 --write --nobackups --no-diffs --doctests_only pystache - 2to3 --write --nobackups --no-diffs pystache - -This converts the code (and doctests) in place. - -To `import pystache` from a source distribution while using Python 3, be -sure that you are importing from a directory containing a converted -version of the code (e.g. from the `build` directory after converting), -and not from the original (unconverted) source directory. Otherwise, you will -get a syntax error. You can help prevent this by not running the Python -IDE from the project directory when importing Pystache while using Python 3. - - -Mailing List ------------- - -There is a [mailing list](http://librelist.com/browser/pystache/). Note +There is(was) a [mailing list](http://librelist.com/browser/pystache/). Note that there is a bit of a delay between posting a message and seeing it appear in the mailing list archive. Credits ------- - >>> context = { 'author': 'Chris Wanstrath', 'maintainer': 'Chris Jerdonek' } - >>> print pystache.render("Author: {{author}}\nMaintainer: {{maintainer}}", context) + >>> import pystache + >>> context = { 'author': 'Chris Wanstrath', 'maintainer': 'Chris Jerdonek','refurbisher': 'Steve Arnold' } + >>> print(pystache.render("Author: {{author}}\nMaintainer: {{maintainer}}\nRefurbisher: {{refurbisher}}", context)) Author: Chris Wanstrath Maintainer: Chris Jerdonek + Refurbisher: Steve Arnold Pystache logo by [David Phillips](http://davidphillips.us/) is licensed under a [Creative Commons Attribution-ShareAlike 3.0 Unported diff --git a/TODO.md b/TODO.md index cd824176..76853a47 100644 --- a/TODO.md +++ b/TODO.md @@ -6,11 +6,10 @@ In development branch: * Figure out a way to suppress center alignment of images in reST output. * Add a unit test for the change made in 7ea8e7180c41. This is with regard to not requiring spec tests when running tests from a downloaded sdist. -* End support for Python 2.4. -* Add Python 3.3 to tox file (after deprecating 2.4). +* End support for Python 2.7 (done as of 03/03/21 - SA) +* Release 0.6.0 on github, make a pypi account (SA) * Turn the benchmarking script at pystache/tests/benchmark.py into a command in pystache/commands, or make it a subcommand of one of the existing commands (i.e. using a command argument). * Provide support for logging in at least one of the commands. -* Make sure command parsing to pystache-test doesn't break with Python 2.4 and earlier. * Combine pystache-test with the main command. diff --git a/conda/meta.yaml b/conda/meta.yaml new file mode 100644 index 00000000..e7f4fd9c --- /dev/null +++ b/conda/meta.yaml @@ -0,0 +1,50 @@ +{% set name = "pystache" %} +{% set version = "0.6.0.dev0" %} + +package: + name: {{ name|lower }} + version: {{ version }} + +source: + path: .. + +build: + number: 0 + script: {{ PYTHON }} -m pip install . --no-deps --ignore-installed -vvv + noarch: python + entry_points: + - pystache = pystache.commands.render:main + - pystache-test = pystache.commands.test:main + +requirements: + build: + - python + - setuptools + + run: + - python + +test: + imports: + - pystache + - pystache.commands + - pystache.tests + - pystache.tests.data + - pystache.tests.data.locator + - pystache.tests.examples + + commands: + - pystache --help + - pystache-test + + +about: + home: https://github.com/sarnold/pystache + license: MIT + license_family: MIT + license_file: LICENSE + summary: Mustache for Python + +extra: + recipe-maintainers: + - sarnold diff --git a/ext/spec b/ext/spec index 9b1bc7ad..83b07216 160000 --- a/ext/spec +++ b/ext/spec @@ -1 +1 @@ -Subproject commit 9b1bc7ad19247e9671304af02078f2ce30132665 +Subproject commit 83b0721610a4e11832e83df19c73ace3289972b9 diff --git a/gh/images/logo_phillips_small.png b/gh/images/logo_phillips_small.png new file mode 100644 index 00000000..5906add2 Binary files /dev/null and b/gh/images/logo_phillips_small.png differ diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 00000000..2f210119 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,3 @@ +[build-system] +requires = ["setuptools>=40.8.0", "wheel"] +build-backend = "setuptools.build_meta" diff --git a/pystache/__init__.py b/pystache/__init__.py index 4cf24344..5edc1c54 100644 --- a/pystache/__init__.py +++ b/pystache/__init__.py @@ -10,4 +10,4 @@ __all__ = ['parse', 'render', 'Renderer', 'TemplateSpec'] -__version__ = '0.5.4' # Also change in setup.py. +__version__ = '0.6.0' diff --git a/pystache/commands/render.py b/pystache/commands/render.py index 1a9c309d..9c913e71 100644 --- a/pystache/commands/render.py +++ b/pystache/commands/render.py @@ -22,7 +22,7 @@ from sys import exc_info ex_type, ex_value, tb = exc_info() new_ex = Exception("%s: %s" % (ex_type.__name__, ex_value)) - raise new_ex.__class__, new_ex, tb + raise new_ex.__class__(new_ex).with_traceback(tb) # The optparse module is deprecated in Python 2.7 in favor of argparse. # However, argparse is not available in Python 2.6 and earlier. @@ -88,7 +88,7 @@ def main(sys_argv=sys.argv): context = json.loads(context) rendered = renderer.render(template, context) - print rendered + print(rendered) if __name__=='__main__': diff --git a/pystache/common.py b/pystache/common.py index fb266dd8..0e9b0919 100644 --- a/pystache/common.py +++ b/pystache/common.py @@ -5,17 +5,12 @@ """ -from sys import version_info def _get_string_types(): - # TODO: come up with a better solution for this. One of the issues here - # is that in Python 3 there is no common base class for unicode strings - # and byte strings, and 2to3 seems to convert all of "str", "unicode", - # and "basestring" to Python 3's "str". - if version_info < (3, ): - return basestring - # The latter evaluates to "bytes" in Python 3 -- even after conversion by 2to3. - return (unicode, type(u"a".encode('utf-8'))) + """ + Return the Python3 string type (no more python2) + """ + return (str, type("a".encode('utf-8'))) _STRING_TYPES = _get_string_types() diff --git a/pystache/defaults.py b/pystache/defaults.py index bcfdf4cd..2fab0e0e 100644 --- a/pystache/defaults.py +++ b/pystache/defaults.py @@ -39,7 +39,7 @@ FILE_ENCODING = sys.getdefaultencoding() # The delimiters to start with when parsing. -DELIMITERS = (u'{{', u'}}') +DELIMITERS = ('{{', '}}') # How to handle missing tags when rendering a template. MISSING_TAGS = MissingTags.ignore diff --git a/pystache/loader.py b/pystache/loader.py index d4a7e531..ea01d17c 100644 --- a/pystache/loader.py +++ b/pystache/loader.py @@ -6,6 +6,7 @@ """ import os +import platform import sys from pystache import common @@ -24,7 +25,7 @@ def to_unicode(s, encoding=None): """ if encoding is None: encoding = defaults.STRING_ENCODING - return unicode(s, encoding, defaults.DECODE_ERRORS) + return str(s, encoding, defaults.DECODE_ERRORS) return to_unicode @@ -86,7 +87,7 @@ def __init__(self, file_encoding=None, extension=None, to_unicode=None, def _make_locator(self): return Locator(extension=self.extension) - def unicode(self, s, encoding=None): + def str(self, s, encoding=None): """ Convert a string to unicode using the given encoding, and return it. @@ -104,8 +105,8 @@ def unicode(self, s, encoding=None): Defaults to None. """ - if isinstance(s, unicode): - return unicode(s) + if isinstance(s, str): + return str(s) return self.to_unicode(s, encoding) @@ -118,8 +119,9 @@ def read(self, path, encoding=None): if encoding is None: encoding = self.file_encoding - - return self.unicode(b, encoding) + if platform.system() == "Windows": + return self.str(b, encoding).replace('\r', '') + return self.str(b, encoding) def load_file(self, file_name): """ diff --git a/pystache/parsed.py b/pystache/parsed.py index 372d96c6..75d417de 100644 --- a/pystache/parsed.py +++ b/pystache/parsed.py @@ -41,10 +41,10 @@ def render(self, engine, context): """ # We avoid use of the ternary operator for Python 2.4 support. def get_unicode(node): - if type(node) is unicode: + if type(node) is str: return node return node.render(engine, context) - parts = map(get_unicode, self._parse_tree) + parts = list(map(get_unicode, self._parse_tree)) s = ''.join(parts) - return unicode(s) + return str(s) diff --git a/pystache/parser.py b/pystache/parser.py index 9a4fba23..b5e75a6a 100644 --- a/pystache/parser.py +++ b/pystache/parser.py @@ -11,8 +11,8 @@ from pystache.parsed import ParsedTemplate -END_OF_LINE_CHARACTERS = [u'\r', u'\n'] -NON_BLANK_RE = re.compile(ur'^(.)', re.M) +END_OF_LINE_CHARACTERS = ['\r', '\n'] +NON_BLANK_RE = re.compile(r'^(.)', re.M) # TODO: add some unit tests for this. @@ -30,12 +30,12 @@ def parse(template, delimiters=None): Examples: - >>> parsed = parse(u"Hey {{#who}}{{name}}!{{/who}}") - >>> print str(parsed).replace('u', '') # This is a hack to get the test to pass both in Python 2 and 3. + >>> parsed = parse("Hey {{#who}}{{name}}!{{/who}}") + >>> print(str(parsed).replace('u', '')) # This is an old hack. ['Hey ', _SectionNode(key='who', index_begin=12, index_end=21, parsed=[_EscapeNode(key='name'), '!'])] """ - if type(template) is not unicode: + if type(template) is not str: raise Exception("Template is not unicode: %s" % type(template)) parser = _Parser(delimiters) return parser.parse(template) @@ -94,7 +94,7 @@ def __repr__(self): return _format(self) def render(self, engine, context): - return u'' + return '' class _ChangeNode(object): @@ -106,7 +106,7 @@ def __repr__(self): return _format(self) def render(self, engine, context): - return u'' + return '' class _EscapeNode(object): @@ -147,7 +147,7 @@ def __repr__(self): def render(self, engine, context): template = engine.resolve_partial(self.key) # Indent before rendering. - template = re.sub(NON_BLANK_RE, self.indent + ur'\1', template) + template = re.sub(NON_BLANK_RE, self.indent + r'\1', template) return engine.render(template, context) @@ -168,7 +168,7 @@ def render(self, engine, context): # Note that lambdas are considered truthy for inverted sections # per the spec. if data: - return u'' + return '' return self.parsed_section.render(engine, context) @@ -218,7 +218,7 @@ def render(self, engine, context): parts.append(self.parsed.render(engine, context)) context.pop() - return unicode(''.join(parts)) + return str(''.join(parts)) class _Parser(object): diff --git a/pystache/renderengine.py b/pystache/renderengine.py index c797b176..2f1e3419 100644 --- a/pystache/renderengine.py +++ b/pystache/renderengine.py @@ -160,7 +160,7 @@ def _render_value(self, val, context, delimiters=None): if not is_string(val): # In case the template is an integer, for example. val = self.to_str(val) - if type(val) is not unicode: + if type(val) is not str: val = self.literal(val) return self.render(val, context, delimiters) diff --git a/pystache/renderer.py b/pystache/renderer.py index ff6a90c6..064f040e 100644 --- a/pystache/renderer.py +++ b/pystache/renderer.py @@ -32,7 +32,7 @@ class Renderer(object): >>> partials = {'partial': 'Hello, {{thing}}!'} >>> renderer = Renderer(partials=partials) >>> # We apply print to make the test work in Python 3 after 2to3. - >>> print renderer.render('{{>partial}}', {'thing': 'world'}) + >>> print(renderer.render('{{>partial}}', {'thing': 'world'})) Hello, world! To customize string coercion (e.g. to render False values as ''), one can @@ -130,7 +130,7 @@ def __init__(self, file_encoding=None, string_encoding=None, if string_encoding is None: string_encoding = defaults.STRING_ENCODING - if isinstance(search_dirs, basestring): + if isinstance(search_dirs, str): search_dirs = [search_dirs] self._context = None @@ -177,16 +177,16 @@ def _to_unicode_soft(self, s): """ # We type-check to avoid "TypeError: decoding Unicode is not supported". # We avoid the Python ternary operator for Python 2.4 support. - if isinstance(s, unicode): + if isinstance(s, str): return s - return self.unicode(s) + return self.str(s) def _to_unicode_hard(self, s): """ Convert a basestring to a string with type unicode (not subclass). """ - return unicode(self._to_unicode_soft(s)) + return str(self._to_unicode_soft(s)) def _escape_to_unicode(self, s): """ @@ -195,9 +195,9 @@ def _escape_to_unicode(self, s): Returns a unicode string (not subclass). """ - return unicode(self.escape(self._to_unicode_soft(s))) + return str(self.escape(self._to_unicode_soft(s))) - def unicode(self, b, encoding=None): + def str(self, b, encoding=None): """ Convert a byte string to unicode, using string_encoding and decode_errors. @@ -222,7 +222,7 @@ def unicode(self, b, encoding=None): # TODO: Wrap UnicodeDecodeErrors with a message about setting # the string_encoding and decode_errors attributes. - return unicode(b, encoding, self.decode_errors) + return str(b, encoding, self.decode_errors) def _make_loader(self): """ @@ -230,7 +230,7 @@ def _make_loader(self): """ return Loader(file_encoding=self.file_encoding, extension=self.file_extension, - to_unicode=self.unicode, search_dirs=self.search_dirs) + to_unicode=self.str, search_dirs=self.search_dirs) def _make_load_template(self): """ @@ -299,7 +299,7 @@ def resolve_partial(name): try: return load_partial(name) except TemplateNotFoundError: - return u'' + return '' return resolve_partial @@ -316,7 +316,7 @@ def resolve_context(stack, name): try: return context_get(stack, name) except KeyNotFoundError: - return u'' + return '' return resolve_context diff --git a/pystache/specloader.py b/pystache/specloader.py index 3a77d4c5..a82d52a1 100644 --- a/pystache/specloader.py +++ b/pystache/specloader.py @@ -83,7 +83,7 @@ def load(self, spec): """ if spec.template is not None: - return self.loader.unicode(spec.template, spec.template_encoding) + return self.loader.str(spec.template, spec.template_encoding) path = self._find(spec) diff --git a/pystache/tests/benchmark.py b/pystache/tests/benchmark.py index d46e9733..6cb54f8d 100755 --- a/pystache/tests/benchmark.py +++ b/pystache/tests/benchmark.py @@ -13,6 +13,13 @@ import sys from timeit import Timer +try: + import chevron as pystache + print('Using module: chevron') +except (ImportError): + import pystache + print('Using module: pystache') + import pystache # TODO: make the example realistic. @@ -76,17 +83,17 @@ def main(sys_argv): args = sys_argv[1:] count = int(args[0]) - print "Benchmarking: %sx" % count - print + print("Benchmarking: %sx" % count) + print() for example in examples: test = make_test_function(example) t = Timer(test,) - print min(t.repeat(repeat=3, number=count)) + print(min(t.repeat(repeat=3, number=count))) - print "Done" + print("Done") if __name__ == '__main__': diff --git a/pystache/tests/common.py b/pystache/tests/common.py index 222e14f2..12b76b52 100644 --- a/pystache/tests/common.py +++ b/pystache/tests/common.py @@ -72,8 +72,8 @@ def _find_files(root_dir, should_include): # http://docs.python.org/library/os.html#os.walk for dir_path, dir_names, file_names in os.walk(root_dir): new_paths = [os.path.join(dir_path, file_name) for file_name in file_names] - new_paths = filter(is_module, new_paths) - new_paths = filter(should_include, new_paths) + new_paths = list(filter(is_module, new_paths)) + new_paths = list(filter(should_include, new_paths)) paths.extend(new_paths) return paths @@ -183,7 +183,7 @@ def assertException(self, exception_type, msg, callable, *args, **kwds): try: callable(*args, **kwds) raise Exception("Expected exception: %s: %s" % (exception_type, repr(msg))) - except exception_type, err: + except exception_type as err: self.assertEqual(str(err), msg) @@ -228,10 +228,10 @@ class Attachable(object): """ def __init__(self, **kwargs): self.__args__ = kwargs - for arg, value in kwargs.iteritems(): + for arg, value in kwargs.items(): setattr(self, arg, value) def __repr__(self): return "%s(%s)" % (self.__class__.__name__, ", ".join("%s=%s" % (k, repr(v)) - for k, v in self.__args__.iteritems())) + for k, v in self.__args__.items())) diff --git a/pystache/tests/examples/unicode_output.py b/pystache/tests/examples/unicode_output.py index da0e1d2e..7bdea36f 100644 --- a/pystache/tests/examples/unicode_output.py +++ b/pystache/tests/examples/unicode_output.py @@ -8,4 +8,4 @@ class UnicodeOutput(object): def name(self): - return u'Henri Poincaré' + return 'Henri Poincaré' diff --git a/pystache/tests/main.py b/pystache/tests/main.py index 8af6b2ee..17f2fb28 100644 --- a/pystache/tests/main.py +++ b/pystache/tests/main.py @@ -88,7 +88,7 @@ def main(sys_argv): """ # TODO: use logging module - print "pystache: running tests: argv: %s" % repr(sys_argv) + print("pystache: running tests: argv: %s" % repr(sys_argv)) should_source_exist = False spec_test_dir = None @@ -131,11 +131,9 @@ def main(sys_argv): module_names = _discover_test_modules(PACKAGE_DIR) sys_argv.extend(module_names) if project_dir is not None: - # Add the current module for unit tests contained here (e.g. - # to include SetupTests). + # Add the current module for unit tests contained here sys_argv.append(__name__) - SetupTests.project_dir = project_dir extra_tests = make_extra_tests(project_dir, spec_test_dir) test_program_class = make_test_program_class(extra_tests) @@ -166,25 +164,3 @@ def is_unittest_module(path): raise Exception("No unit-test modules found--\n in %s" % package_dir) return names - - -class SetupTests(TestCase): - - """Tests about setup.py.""" - - project_dir = None - - def test_version(self): - """ - Test that setup.py's version matches the package's version. - - """ - original_path = list(sys.path) - - sys.path.insert(0, self.project_dir) - - try: - from setup import VERSION - self.assertEqual(VERSION, pystache.__version__) - finally: - sys.path = original_path diff --git a/pystache/tests/spectesting.py b/pystache/tests/spectesting.py index ec8a08df..2dd57e82 100644 --- a/pystache/tests/spectesting.py +++ b/pystache/tests/spectesting.py @@ -37,7 +37,7 @@ from sys import exc_info ex_type, ex_value, tb = exc_info() new_ex = Exception("%s: %s" % (ex_type.__name__, ex_value)) - raise new_ex.__class__, new_ex, tb + raise new_ex.__class__(new_ex).with_traceback(tb) file_extension = 'json' parser = json else: @@ -62,7 +62,7 @@ def get_spec_tests(spec_test_dir): """ # TODO: use logging module instead. - print "pystache: spec tests: using %s" % _get_parser_info() + print("pystache: spec tests: using %s" % _get_parser_info()) cases = [] @@ -103,7 +103,7 @@ def _read_spec_tests(path): """ b = common.read(path) - u = unicode(b, encoding=FILE_ENCODING) + u = str(b, encoding=FILE_ENCODING) spec_data = parse(u) tests = spec_data['tests'] @@ -133,7 +133,7 @@ def _convert_children(node): return # Otherwise, node is a dict, so attempt the conversion. - for key in node.keys(): + for key in list(node.keys()): val = node[key] if not isinstance(val, dict) or val.get('__tag__') != 'code': @@ -158,9 +158,9 @@ def _deserialize_spec_test(data, file_path): context = data['data'] description = data['desc'] # PyYAML seems to leave ASCII strings as byte strings. - expected = unicode(data['expected']) + expected = str(data['expected']) # TODO: switch to using dict.get(). - partials = data.has_key('partials') and data['partials'] or {} + partials = 'partials' in data and data['partials'] or {} template = data['template'] test_name = data['name'] @@ -237,8 +237,8 @@ def code_constructor(loader, node): value = loader.construct_mapping(node) return eval(value['python'], {}) - yaml.add_constructor(u'!code', code_constructor) - return yaml.load(u) + yaml.add_constructor('!code', code_constructor) + return yaml.full_load(u) class SpecTestBase(unittest.TestCase, AssertStringMixin): diff --git a/pystache/tests/test___init__.py b/pystache/tests/test___init__.py index eae42c1a..63d2c3bf 100644 --- a/pystache/tests/test___init__.py +++ b/pystache/tests/test___init__.py @@ -6,9 +6,9 @@ """ # Calling "import *" is allowed only at the module level. -GLOBALS_INITIAL = globals().keys() +GLOBALS_INITIAL = list(globals().keys()) from pystache import * -GLOBALS_PYSTACHE_IMPORTED = globals().keys() +GLOBALS_PYSTACHE_IMPORTED = list(globals().keys()) import unittest diff --git a/pystache/tests/test_commands.py b/pystache/tests/test_commands.py index 2529d257..34fe8ba2 100644 --- a/pystache/tests/test_commands.py +++ b/pystache/tests/test_commands.py @@ -39,7 +39,7 @@ def testMainSimple(self): """ actual = self.callScript("Hi {{thing}}", '{"thing": "world"}') - self.assertEqual(actual, u"Hi world\n") + self.assertEqual(actual, "Hi world\n") def tearDown(self): sys.stdout = ORIGINAL_STDOUT diff --git a/pystache/tests/test_defaults.py b/pystache/tests/test_defaults.py index c78ea7c8..5399bb03 100644 --- a/pystache/tests/test_defaults.py +++ b/pystache/tests/test_defaults.py @@ -31,37 +31,37 @@ def setUp(self): self.saved[e] = getattr(pystache.defaults, e) def tearDown(self): - for key, value in self.saved.items(): + for key, value in list(self.saved.items()): setattr(pystache.defaults, key, value) def test_tag_escape(self): """Test that changes to defaults.TAG_ESCAPE take effect.""" - template = u"{{foo}}" + template = "{{foo}}" context = {'foo': '<'} actual = pystache.render(template, context) - self.assertString(actual, u"<") + self.assertString(actual, "<") pystache.defaults.TAG_ESCAPE = lambda u: u actual = pystache.render(template, context) - self.assertString(actual, u"<") + self.assertString(actual, "<") def test_delimiters(self): """Test that changes to defaults.DELIMITERS take effect.""" - template = u"[[foo]]{{foo}}" + template = "[[foo]]{{foo}}" context = {'foo': 'FOO'} actual = pystache.render(template, context) - self.assertString(actual, u"[[foo]]FOO") + self.assertString(actual, "[[foo]]FOO") pystache.defaults.DELIMITERS = ('[[', ']]') actual = pystache.render(template, context) - self.assertString(actual, u"FOO{{foo}}") + self.assertString(actual, "FOO{{foo}}") def test_missing_tags(self): """Test that changes to defaults.MISSING_TAGS take effect.""" - template = u"{{foo}}" + template = "{{foo}}" context = {} actual = pystache.render(template, context) - self.assertString(actual, u"") + self.assertString(actual, "") pystache.defaults.MISSING_TAGS = 'strict' self.assertRaises(pystache.context.KeyNotFoundError, diff --git a/pystache/tests/test_examples.py b/pystache/tests/test_examples.py index 5c9f74da..9f93de30 100644 --- a/pystache/tests/test_examples.py +++ b/pystache/tests/test_examples.py @@ -7,15 +7,15 @@ import unittest -from examples.comments import Comments -from examples.double_section import DoubleSection -from examples.escaped import Escaped -from examples.unescaped import Unescaped -from examples.template_partial import TemplatePartial -from examples.delimiters import Delimiters -from examples.unicode_output import UnicodeOutput -from examples.unicode_input import UnicodeInput -from examples.nested_context import NestedContext +from .examples.comments import Comments +from .examples.double_section import DoubleSection +from .examples.escaped import Escaped +from .examples.unescaped import Unescaped +from .examples.template_partial import TemplatePartial +from .examples.delimiters import Delimiters +from .examples.unicode_output import UnicodeOutput +from .examples.unicode_input import UnicodeInput +from .examples.nested_context import NestedContext from pystache import Renderer from pystache.tests.common import EXAMPLES_DIR from pystache.tests.common import AssertStringMixin @@ -29,34 +29,34 @@ def _assert(self, obj, expected): self.assertString(actual, expected) def test_comments(self): - self._assert(Comments(), u"

A Comedy of Errors

") + self._assert(Comments(), "

A Comedy of Errors

") def test_double_section(self): - self._assert(DoubleSection(), u"* first\n* second\n* third") + self._assert(DoubleSection(), "* first\n* second\n* third") def test_unicode_output(self): renderer = Renderer() actual = renderer.render(UnicodeOutput()) - self.assertString(actual, u'

Name: Henri Poincaré

') + self.assertString(actual, '

Name: Henri Poincaré

') def test_unicode_input(self): renderer = Renderer() actual = renderer.render(UnicodeInput()) - self.assertString(actual, u'abcdé') + self.assertString(actual, 'abcdé') def test_escaping(self): - self._assert(Escaped(), u"

Bear > Shark

") + self._assert(Escaped(), "

Bear > Shark

") def test_literal(self): renderer = Renderer() actual = renderer.render(Unescaped()) - self.assertString(actual, u"

Bear > Shark

") + self.assertString(actual, "

Bear > Shark

") def test_template_partial(self): renderer = Renderer(search_dirs=EXAMPLES_DIR) actual = renderer.render(TemplatePartial(renderer=renderer)) - self.assertString(actual, u"""

Welcome

+ self.assertString(actual, """

Welcome

Again, Welcome!""") def test_template_partial_extension(self): @@ -65,7 +65,7 @@ def test_template_partial_extension(self): view = TemplatePartial(renderer=renderer) actual = renderer.render(view) - self.assertString(actual, u"""Welcome + self.assertString(actual, """Welcome ------- ## Again, Welcome! ##""") @@ -73,7 +73,7 @@ def test_template_partial_extension(self): def test_delimiters(self): renderer = Renderer() actual = renderer.render(Delimiters()) - self.assertString(actual, u"""\ + self.assertString(actual, """\ * It worked the first time. * And it worked the second time. * Then, surprisingly, it worked the third time. @@ -82,7 +82,7 @@ def test_delimiters(self): def test_nested_context(self): renderer = Renderer() actual = renderer.render(NestedContext(renderer)) - self.assertString(actual, u"one and foo and two") + self.assertString(actual, "one and foo and two") def test_nested_context_is_available_in_view(self): renderer = Renderer() @@ -91,7 +91,7 @@ def test_nested_context_is_available_in_view(self): view.template = '{{#herp}}{{#derp}}{{nested_context_in_view}}{{/derp}}{{/herp}}' actual = renderer.render(view) - self.assertString(actual, u'it works!') + self.assertString(actual, 'it works!') def test_partial_in_partial_has_access_to_grand_parent_context(self): renderer = Renderer(search_dirs=EXAMPLES_DIR) diff --git a/pystache/tests/test_loader.py b/pystache/tests/test_loader.py index f2c21874..315daff2 100644 --- a/pystache/tests/test_loader.py +++ b/pystache/tests/test_loader.py @@ -55,23 +55,23 @@ def test_init__to_unicode(self): def test_init__to_unicode__default(self): loader = Loader() - self.assertRaises(TypeError, loader.to_unicode, u"abc") + self.assertRaises(TypeError, loader.to_unicode, "abc") decode_errors = defaults.DECODE_ERRORS string_encoding = defaults.STRING_ENCODING - nonascii = u'abcdé'.encode('utf-8') + nonascii = 'abcdé'.encode('utf-8') loader = Loader() self.assertRaises(UnicodeDecodeError, loader.to_unicode, nonascii) defaults.DECODE_ERRORS = 'ignore' loader = Loader() - self.assertString(loader.to_unicode(nonascii), u'abcd') + self.assertString(loader.to_unicode(nonascii), 'abcd') defaults.STRING_ENCODING = 'utf-8' loader = Loader() - self.assertString(loader.to_unicode(nonascii), u'abcdé') + self.assertString(loader.to_unicode(nonascii), 'abcdé') def _get_path(self, filename): @@ -83,9 +83,9 @@ def test_unicode__basic__input_str(self): """ loader = Loader() - actual = loader.unicode("foo") + actual = loader.str("foo") - self.assertString(actual, u"foo") + self.assertString(actual, "foo") def test_unicode__basic__input_unicode(self): """ @@ -93,24 +93,24 @@ def test_unicode__basic__input_unicode(self): """ loader = Loader() - actual = loader.unicode(u"foo") + actual = loader.str("foo") - self.assertString(actual, u"foo") + self.assertString(actual, "foo") def test_unicode__basic__input_unicode_subclass(self): """ Test unicode(): default arguments with unicode-subclass input. """ - class UnicodeSubclass(unicode): + class UnicodeSubclass(str): pass - s = UnicodeSubclass(u"foo") + s = UnicodeSubclass("foo") loader = Loader() - actual = loader.unicode(s) + actual = loader.str(s) - self.assertString(actual, u"foo") + self.assertString(actual, "foo") def test_unicode__to_unicode__attribute(self): """ @@ -119,16 +119,16 @@ def test_unicode__to_unicode__attribute(self): """ loader = Loader() - non_ascii = u'abcdé'.encode('utf-8') - self.assertRaises(UnicodeDecodeError, loader.unicode, non_ascii) + non_ascii = 'abcdé'.encode('utf-8') + self.assertRaises(UnicodeDecodeError, loader.str, non_ascii) def to_unicode(s, encoding=None): if encoding is None: encoding = 'utf-8' - return unicode(s, encoding) + return str(s, encoding) loader.to_unicode = to_unicode - self.assertString(loader.unicode(non_ascii), u"abcdé") + self.assertString(loader.str(non_ascii), "abcdé") def test_unicode__encoding_argument(self): """ @@ -137,12 +137,12 @@ def test_unicode__encoding_argument(self): """ loader = Loader() - non_ascii = u'abcdé'.encode('utf-8') + non_ascii = 'abcdé'.encode('utf-8') - self.assertRaises(UnicodeDecodeError, loader.unicode, non_ascii) + self.assertRaises(UnicodeDecodeError, loader.str, non_ascii) - actual = loader.unicode(non_ascii, encoding='utf-8') - self.assertString(actual, u'abcdé') + actual = loader.str(non_ascii, encoding='utf-8') + self.assertString(actual, 'abcdé') # TODO: check the read() unit tests. def test_read(self): @@ -153,7 +153,7 @@ def test_read(self): loader = Loader() path = self._get_path('ascii.mustache') actual = loader.read(path) - self.assertString(actual, u'ascii: abc') + self.assertString(actual, 'ascii: abc') def test_read__file_encoding__attribute(self): """ @@ -167,7 +167,7 @@ def test_read__file_encoding__attribute(self): loader.file_encoding = 'utf-8' actual = loader.read(path) - self.assertString(actual, u'non-ascii: é') + self.assertString(actual, 'non-ascii: é') def test_read__encoding__argument(self): """ @@ -180,7 +180,7 @@ def test_read__encoding__argument(self): self.assertRaises(UnicodeDecodeError, loader.read, path) actual = loader.read(path, encoding='utf-8') - self.assertString(actual, u'non-ascii: é') + self.assertString(actual, 'non-ascii: é') def test_read__to_unicode__attribute(self): """ diff --git a/pystache/tests/test_pystache.py b/pystache/tests/test_pystache.py index 5447f8da..cf5d6afd 100644 --- a/pystache/tests/test_pystache.py +++ b/pystache/tests/test_pystache.py @@ -71,14 +71,14 @@ def test_non_strings(self): template = "{{#stats}}({{key}} & {{value}}){{/stats}}" stats = [] stats.append({'key': 123, 'value': ['something']}) - stats.append({'key': u"chris", 'value': 0.900}) + stats.append({'key': "chris", 'value': 0.900}) context = { 'stats': stats } self._assert_rendered(self.non_strings_expected, template, context) def test_unicode(self): template = 'Name: {{name}}; Age: {{age}}' - context = {'name': u'Henri Poincaré', 'age': 156 } - self._assert_rendered(u'Name: Henri Poincaré; Age: 156', template, context) + context = {'name': 'Henri Poincaré', 'age': 156} + self._assert_rendered('Name: Henri Poincaré; Age: 156', template, context) def test_sections(self): template = """
    {{#users}}
  • {{name}}
  • {{/users}}
""" diff --git a/pystache/tests/test_renderengine.py b/pystache/tests/test_renderengine.py index db916f77..ed604c59 100644 --- a/pystache/tests/test_renderengine.py +++ b/pystache/tests/test_renderengine.py @@ -33,11 +33,11 @@ def mock_literal(s): s: a byte string or unicode string. """ - if isinstance(s, unicode): + if isinstance(s, str): # Strip off unicode super classes, if present. - u = unicode(s) + u = str(s) else: - u = unicode(s, encoding='ascii') + u = str(s, encoding='ascii') # We apply upper() to make sure we are actually using our custom # function in the tests @@ -94,17 +94,17 @@ def _assert_render(self, expected, template, *context, **kwargs): engine = kwargs.get('engine', self._engine()) if partials is not None: - engine.resolve_partial = lambda key: unicode(partials[key]) + engine.resolve_partial = lambda key: str(partials[key]) context = ContextStack(*context) # RenderEngine.render() only accepts unicode template strings. - actual = engine.render(unicode(template), context) + actual = engine.render(str(template), context) self.assertString(actual=actual, expected=expected) def test_render(self): - self._assert_render(u'Hi Mom', 'Hi {{person}}', {'person': 'Mom'}) + self._assert_render('Hi Mom', 'Hi {{person}}', {'person': 'Mom'}) def test__resolve_partial(self): """ @@ -112,10 +112,10 @@ def test__resolve_partial(self): """ engine = self._engine() - partials = {'partial': u"{{person}}"} + partials = {'partial': "{{person}}"} engine.resolve_partial = lambda key: partials[key] - self._assert_render(u'Hi Mom', 'Hi {{>partial}}', {'person': 'Mom'}, engine=engine) + self._assert_render('Hi Mom', 'Hi {{>partial}}', {'person': 'Mom'}, engine=engine) def test__literal(self): """ @@ -125,13 +125,13 @@ def test__literal(self): engine = self._engine() engine.literal = lambda s: s.upper() - self._assert_render(u'BAR', '{{{foo}}}', {'foo': 'bar'}, engine=engine) + self._assert_render('BAR', '{{{foo}}}', {'foo': 'bar'}, engine=engine) def test_literal__sigil(self): template = "

{{& thing}}

" context = {'thing': 'Bear > Giraffe'} - expected = u"

Bear > Giraffe

" + expected = "

Bear > Giraffe

" self._assert_render(expected, template, context) @@ -143,7 +143,7 @@ def test__escape(self): engine = self._engine() engine.escape = lambda s: "**" + s - self._assert_render(u'**bar', '{{foo}}', {'foo': 'bar'}, engine=engine) + self._assert_render('**bar', '{{foo}}', {'foo': 'bar'}, engine=engine) def test__escape_does_not_call_literal(self): """ @@ -157,7 +157,7 @@ def test__escape_does_not_call_literal(self): template = 'literal: {{{foo}}} escaped: {{foo}}' context = {'foo': 'bar'} - self._assert_render(u'literal: BAR escaped: **bar', template, context, engine=engine) + self._assert_render('literal: BAR escaped: **bar', template, context, engine=engine) def test__escape_preserves_unicode_subclasses(self): """ @@ -167,7 +167,7 @@ def test__escape_preserves_unicode_subclasses(self): variable value is markupsafe.Markup when escaping. """ - class MyUnicode(unicode): + class MyUnicode(str): pass def escape(s): @@ -182,7 +182,7 @@ def escape(s): template = '{{foo1}} {{foo2}}' context = {'foo1': MyUnicode('bar'), 'foo2': 'bar'} - self._assert_render(u'**bar bar**', template, context, engine=engine) + self._assert_render('**bar bar**', template, context, engine=engine) # Custom to_str for testing purposes. def _to_str(self, val): @@ -197,9 +197,9 @@ def test_to_str(self): template = '{{value}}' context = {'value': None} - self._assert_render(u'None', template, context, engine=engine) + self._assert_render('None', template, context, engine=engine) engine.to_str = self._to_str - self._assert_render(u'', template, context, engine=engine) + self._assert_render('', template, context, engine=engine) def test_to_str__lambda(self): """Test the to_str attribute for a lambda.""" @@ -207,9 +207,9 @@ def test_to_str__lambda(self): template = '{{value}}' context = {'value': lambda: None} - self._assert_render(u'None', template, context, engine=engine) + self._assert_render('None', template, context, engine=engine) engine.to_str = self._to_str - self._assert_render(u'', template, context, engine=engine) + self._assert_render('', template, context, engine=engine) def test_to_str__section_list(self): """Test the to_str attribute for a section list.""" @@ -217,9 +217,9 @@ def test_to_str__section_list(self): template = '{{#list}}{{.}}{{/list}}' context = {'list': [None, None]} - self._assert_render(u'NoneNone', template, context, engine=engine) + self._assert_render('NoneNone', template, context, engine=engine) engine.to_str = self._to_str - self._assert_render(u'', template, context, engine=engine) + self._assert_render('', template, context, engine=engine) def test_to_str__section_lambda(self): # TODO: add a test for a "method with an arity of 1". @@ -239,7 +239,7 @@ def test__non_basestring__literal_and_escaped(self): template = '{{text}} {{int}} {{{int}}}' context = {'int': 100, 'text': 'foo'} - self._assert_render(u'FOO 100 100', template, context, engine=engine) + self._assert_render('FOO 100 100', template, context, engine=engine) def test_tag__output_not_interpolated(self): """ @@ -248,7 +248,7 @@ def test_tag__output_not_interpolated(self): """ template = '{{template}}: {{planet}}' context = {'template': '{{planet}}', 'planet': 'Earth'} - self._assert_render(u'{{planet}}: Earth', template, context) + self._assert_render('{{planet}}: Earth', template, context) def test_tag__output_not_interpolated__section(self): """ @@ -257,7 +257,7 @@ def test_tag__output_not_interpolated__section(self): """ template = '{{test}}' context = {'test': '{{#hello}}'} - self._assert_render(u'{{#hello}}', template, context) + self._assert_render('{{#hello}}', template, context) ## Test interpolation with "falsey" values # @@ -268,17 +268,17 @@ def test_tag__output_not_interpolated__section(self): def test_interpolation__falsey__zero(self): template = '{{.}}' context = 0 - self._assert_render(u'0', template, context) + self._assert_render('0', template, context) def test_interpolation__falsey__none(self): template = '{{.}}' context = None - self._assert_render(u'None', template, context) + self._assert_render('None', template, context) def test_interpolation__falsey__zero(self): template = '{{.}}' context = False - self._assert_render(u'False', template, context) + self._assert_render('False', template, context) # Built-in types: # @@ -310,7 +310,7 @@ def test_interpolation__built_in_type__string(self): Check tag interpolation with a built-in type: string. """ - self._assert_builtin_type('abc', 'upper', 'ABC', u'xyz') + self._assert_builtin_type('abc', 'upper', 'ABC', 'xyz') def test_interpolation__built_in_type__integer(self): """ @@ -324,7 +324,7 @@ def test_interpolation__built_in_type__integer(self): # # we need to resort to built-in attributes (double-underscored) on # the integer type. - self._assert_builtin_type(15, '__neg__', -15, u'999') + self._assert_builtin_type(15, '__neg__', -15, '999') def test_interpolation__built_in_type__list(self): """ @@ -338,7 +338,7 @@ def test_interpolation__built_in_type__list(self): template = '{{#section}}{{%s}}{{/section}}' % attr_name context = {'section': item, attr_name: 7} - self._assert_render(u'7', template, context) + self._assert_render('7', template, context) # This test is also important for testing 2to3. def test_interpolation__nonascii_nonunicode(self): @@ -347,8 +347,8 @@ def test_interpolation__nonascii_nonunicode(self): """ template = '{{nonascii}}' - context = {'nonascii': u'abcdé'.encode('utf-8')} - self._assert_render(u'abcdé', template, context) + context = {'nonascii': 'abcdé'.encode('utf-8')} + self._assert_render('abcdé', template, context) def test_implicit_iterator__literal(self): """ @@ -358,7 +358,7 @@ def test_implicit_iterator__literal(self): template = """{{#test}}{{{.}}}{{/test}}""" context = {'test': ['<', '>']} - self._assert_render(u'<>', template, context) + self._assert_render('<>', template, context) def test_implicit_iterator__escaped(self): """ @@ -368,7 +368,7 @@ def test_implicit_iterator__escaped(self): template = """{{#test}}{{.}}{{/test}}""" context = {'test': ['<', '>']} - self._assert_render(u'<>', template, context) + self._assert_render('<>', template, context) def test_literal__in_section(self): """ @@ -378,7 +378,7 @@ def test_literal__in_section(self): template = '{{#test}}1 {{{less_than}}} 2{{/test}}' context = {'test': {'less_than': '<'}} - self._assert_render(u'1 < 2', template, context) + self._assert_render('1 < 2', template, context) def test_literal__in_partial(self): """ @@ -389,11 +389,11 @@ def test_literal__in_partial(self): partials = {'partial': '1 {{{less_than}}} 2'} context = {'less_than': '<'} - self._assert_render(u'1 < 2', template, context, partials=partials) + self._assert_render('1 < 2', template, context, partials=partials) def test_partial(self): partials = {'partial': "{{person}}"} - self._assert_render(u'Hi Mom', 'Hi {{>partial}}', {'person': 'Mom'}, partials=partials) + self._assert_render('Hi Mom', 'Hi {{>partial}}', {'person': 'Mom'}, partials=partials) def test_partial__context_values(self): """ @@ -406,7 +406,9 @@ def test_partial__context_values(self): partials = {'partial': 'unescaped: {{{foo}}} escaped: {{foo}}'} context = {'foo': '<'} - self._assert_render(u'unescaped: < escaped: <', template, context, engine=engine, partials=partials) + self._assert_render( + 'unescaped: < escaped: <', + template, context, engine=engine, partials=partials) ## Test cases related specifically to lambdas. @@ -417,8 +419,8 @@ def test_section__nonascii_nonunicode(self): """ template = '{{#nonascii}}{{.}}{{/nonascii}}' - context = {'nonascii': u'abcdé'.encode('utf-8')} - self._assert_render(u'abcdé', template, context) + context = {'nonascii': 'abcdé'.encode('utf-8')} + self._assert_render('abcdé', template, context) # This test is also important for testing 2to3. def test_lambda__returning_nonascii_nonunicode(self): @@ -427,8 +429,8 @@ def test_lambda__returning_nonascii_nonunicode(self): """ template = '{{lambda}}' - context = {'lambda': lambda: u'abcdé'.encode('utf-8')} - self._assert_render(u'abcdé', template, context) + context = {'lambda': lambda: 'abcdé'.encode('utf-8')} + self._assert_render('abcdé', template, context) ## Test cases related specifically to sections. @@ -440,7 +442,7 @@ def test_section__end_tag_with_no_start_tag(self): template = '{{/section}}' try: self._assert_render(None, template) - except ParsingError, err: + except ParsingError as err: self.assertEqual(str(err), "Section end tag mismatch: section != None") def test_section__end_tag_mismatch(self): @@ -451,7 +453,7 @@ def test_section__end_tag_mismatch(self): template = '{{#section_start}}{{/section_end}}' try: self._assert_render(None, template) - except ParsingError, err: + except ParsingError as err: self.assertEqual(str(err), "Section end tag mismatch: section_end != section_start") def test_section__context_values(self): @@ -464,7 +466,7 @@ def test_section__context_values(self): template = '{{#test}}unescaped: {{{foo}}} escaped: {{foo}}{{/test}}' context = {'test': {'foo': '<'}} - self._assert_render(u'unescaped: < escaped: <', template, context, engine=engine) + self._assert_render('unescaped: < escaped: <', template, context, engine=engine) def test_section__context_precedence(self): """ @@ -473,7 +475,7 @@ def test_section__context_precedence(self): """ template = '{{entree}} : {{#vegetarian}}{{entree}}{{/vegetarian}}' context = {'entree': 'chicken', 'vegetarian': {'entree': 'beans and rice'}} - self._assert_render(u'chicken : beans and rice', template, context) + self._assert_render('chicken : beans and rice', template, context) def test_section__list_referencing_outer_context(self): """ @@ -491,7 +493,7 @@ def test_section__list_referencing_outer_context(self): template = "{{#list}}{{greeting}} {{name}}, {{/list}}" - self._assert_render(u"Hi Al, Hi Bob, ", template, context) + self._assert_render("Hi Al, Hi Bob, ", template, context) def test_section__output_not_interpolated(self): """ @@ -500,7 +502,7 @@ def test_section__output_not_interpolated(self): """ template = '{{#section}}{{template}}{{/section}}: {{planet}}' context = {'section': True, 'template': '{{planet}}', 'planet': 'Earth'} - self._assert_render(u'{{planet}}: Earth', template, context) + self._assert_render('{{planet}}: Earth', template, context) # TODO: have this test case added to the spec. def test_section__string_values_not_lists(self): @@ -511,7 +513,7 @@ def test_section__string_values_not_lists(self): template = '{{#section}}foo{{/section}}' context = {'section': '123'} # If strings were interpreted as lists, this would give "foofoofoo". - self._assert_render(u'foo', template, context) + self._assert_render('foo', template, context) def test_section__nested_truthy(self): """ @@ -525,7 +527,7 @@ def test_section__nested_truthy(self): """ template = '| A {{#bool}}B {{#bool}}C{{/bool}} D{{/bool}} E |' context = {'bool': True} - self._assert_render(u'| A B C D E |', template, context) + self._assert_render('| A B C D E |', template, context) def test_section__nested_with_same_keys(self): """ @@ -537,16 +539,16 @@ def test_section__nested_with_same_keys(self): # Start with an easier, working case. template = '{{#x}}{{#z}}{{y}}{{/z}}{{/x}}' context = {'x': {'z': {'y': 1}}} - self._assert_render(u'1', template, context) + self._assert_render('1', template, context) template = '{{#x}}{{#x}}{{y}}{{/x}}{{/x}}' context = {'x': {'x': {'y': 1}}} - self._assert_render(u'1', template, context) + self._assert_render('1', template, context) def test_section__lambda(self): template = '{{#test}}Mom{{/test}}' context = {'test': (lambda text: 'Hi %s' % text)} - self._assert_render(u'Hi Mom', template, context) + self._assert_render('Hi Mom', template, context) # This test is also important for testing 2to3. def test_section__lambda__returning_nonascii_nonunicode(self): @@ -555,8 +557,8 @@ def test_section__lambda__returning_nonascii_nonunicode(self): """ template = '{{#lambda}}{{/lambda}}' - context = {'lambda': lambda text: u'abcdé'.encode('utf-8')} - self._assert_render(u'abcdé', template, context) + context = {'lambda': lambda text: 'abcdé'.encode('utf-8')} + self._assert_render('abcdé', template, context) def test_section__lambda__returning_nonstring(self): """ @@ -565,7 +567,7 @@ def test_section__lambda__returning_nonstring(self): """ template = '{{#lambda}}foo{{/lambda}}' context = {'lambda': lambda text: len(text)} - self._assert_render(u'3', template, context) + self._assert_render('3', template, context) def test_section__iterable(self): """ @@ -575,10 +577,10 @@ def test_section__iterable(self): template = '{{#iterable}}{{.}}{{/iterable}}' context = {'iterable': (i for i in range(3))} # type 'generator' - self._assert_render(u'012', template, context) + self._assert_render('012', template, context) - context = {'iterable': xrange(4)} # type 'xrange' - self._assert_render(u'0123', template, context) + context = {'iterable': range(4)} # type 'xrange' + self._assert_render('0123', template, context) d = {'foo': 0, 'bar': 0} # We don't know what order of keys we'll be given, but from the @@ -586,8 +588,8 @@ def test_section__iterable(self): # "If items(), keys(), values(), iteritems(), iterkeys(), and # itervalues() are called with no intervening modifications to # the dictionary, the lists will directly correspond." - expected = u''.join(d.keys()) - context = {'iterable': d.iterkeys()} # type 'dictionary-keyiterator' + expected = ''.join(list(d.keys())) + context = {'iterable': iter(d.keys())} # type 'dictionary-keyiterator' self._assert_render(expected, template, context) def test_section__lambda__tag_in_output(self): @@ -605,7 +607,7 @@ def test_section__lambda__tag_in_output(self): """ template = '{{#test}}Hi {{person}}{{/test}}' context = {'person': 'Mom', 'test': (lambda text: text + " :)")} - self._assert_render(u'Hi Mom :)', template, context) + self._assert_render('Hi Mom :)', template, context) def test_section__lambda__list(self): """ @@ -621,7 +623,7 @@ def test_section__lambda__list(self): 'lambdas': [lambda text: "~{{%s}}~" % text, lambda text: "#{{%s}}#" % text]} - self._assert_render(u'<~bar~#bar#>', template, context) + self._assert_render('<~bar~#bar#>', template, context) def test_section__lambda__mixed_list(self): """ @@ -636,7 +638,7 @@ def test_section__lambda__mixed_list(self): context = {'foo': 'bar', 'lambdas': [lambda text: "~{{%s}}~" % text, 1]} - self._assert_render(u'<~bar~foo>', template, context) + self._assert_render('<~bar~foo>', template, context) def test_section__lambda__not_on_context_stack(self): """ @@ -653,7 +655,7 @@ def test_section__lambda__not_on_context_stack(self): """ context = {'foo': 'bar', 'lambda': (lambda text: "{{.}}")} template = '{{#foo}}{{#lambda}}blah{{/lambda}}{{/foo}}' - self._assert_render(u'bar', template, context) + self._assert_render('bar', template, context) def test_section__lambda__no_reinterpolation(self): """ @@ -670,15 +672,15 @@ def test_section__lambda__no_reinterpolation(self): """ template = '{{#planet}}{{#lambda}}dot{{/lambda}}{{/planet}}' context = {'planet': 'Earth', 'dot': '~{{.}}~', 'lambda': (lambda text: "#{{%s}}#" % text)} - self._assert_render(u'#~{{.}}~#', template, context) + self._assert_render('#~{{.}}~#', template, context) def test_comment__multiline(self): """ Check that multiline comments are permitted. """ - self._assert_render(u'foobar', 'foo{{! baz }}bar') - self._assert_render(u'foobar', 'foo{{! \nbaz }}bar') + self._assert_render('foobar', 'foo{{! baz }}bar') + self._assert_render('foobar', 'foo{{! \nbaz }}bar') def test_custom_delimiters__sections(self): """ @@ -689,7 +691,7 @@ def test_custom_delimiters__sections(self): """ template = '{{=[[ ]]=}}[[#foo]]bar[[/foo]]' context = {'foo': True} - self._assert_render(u'bar', template, context) + self._assert_render('bar', template, context) def test_custom_delimiters__not_retroactive(self): """ @@ -698,7 +700,7 @@ def test_custom_delimiters__not_retroactive(self): Test case for issue #35: https://github.com/defunkt/pystache/issues/35 """ - expected = u' {{foo}} ' + expected = ' {{foo}} ' self._assert_render(expected, '{{=$ $=}} {{foo}} ') self._assert_render(expected, '{{=$ $=}} {{foo}} $={{ }}=$') # was yielding u' '. @@ -713,7 +715,7 @@ def test_dot_notation(self): template = 'Hello, {{person.name}}. I see you are {{person.details.age}}.' person = Attachable(name='Biggles', details={'age': 42}) context = {'person': person} - self._assert_render(u'Hello, Biggles. I see you are 42.', template, context) + self._assert_render('Hello, Biggles. I see you are 42.', template, context) def test_dot_notation__multiple_levels(self): """ @@ -722,7 +724,7 @@ def test_dot_notation__multiple_levels(self): """ template = """Hello, Mr. {{person.name.lastname}}. I see you're back from {{person.travels.last.country.city}}.""" - expected = u"""Hello, Mr. Pither. + expected = """Hello, Mr. Pither. I see you're back from Cornwall.""" context = {'person': {'name': {'firstname': 'unknown', 'lastname': 'Pither'}, 'travels': {'last': {'country': {'city': 'Cornwall'}}}, @@ -758,10 +760,10 @@ def test_dot_notation__missing_part_terminates_search(self): context = {'a': {'b': 'A.B'}, 'c': {'a': 'A'} } template = '{{a.b}}' - self._assert_render(u'A.B', template, context) + self._assert_render('A.B', template, context) template = '{{#c}}{{a}}{{/c}}' - self._assert_render(u'A', template, context) + self._assert_render('A', template, context) template = '{{#c}}{{a.b}}{{/c}}' self.assertException(KeyNotFoundError, "Key %(unicode)s'a.b' not found: missing %(unicode)s'b'" % diff --git a/pystache/tests/test_renderer.py b/pystache/tests/test_renderer.py index 0dbe0d99..e0d2448e 100644 --- a/pystache/tests/test_renderer.py +++ b/pystache/tests/test_renderer.py @@ -10,7 +10,7 @@ import sys import unittest -from examples.simple import Simple +from .examples.simple import Simple from pystache import Renderer from pystache import TemplateSpec from pystache.common import TemplateNotFoundError @@ -33,7 +33,7 @@ def _make_renderer(): def mock_unicode(b, encoding=None): if encoding is None: encoding = 'ascii' - u = unicode(b, encoding=encoding) + u = str(b, encoding=encoding) return u.upper() @@ -197,13 +197,13 @@ def test_unicode__string_encoding(self): """ renderer = self._renderer() - b = u"é".encode('utf-8') + b = "é".encode('utf-8') renderer.string_encoding = "ascii" - self.assertRaises(UnicodeDecodeError, renderer.unicode, b) + self.assertRaises(UnicodeDecodeError, renderer.str, b) renderer.string_encoding = "utf-8" - self.assertEqual(renderer.unicode(b), u"é") + self.assertEqual(renderer.str(b), "é") def test_unicode__decode_errors(self): """ @@ -212,14 +212,14 @@ def test_unicode__decode_errors(self): """ renderer = self._renderer() renderer.string_encoding = "ascii" - b = u"déf".encode('utf-8') + b = "déf".encode('utf-8') renderer.decode_errors = "ignore" - self.assertEqual(renderer.unicode(b), "df") + self.assertEqual(renderer.str(b), "df") renderer.decode_errors = "replace" # U+FFFD is the official Unicode replacement character. - self.assertEqual(renderer.unicode(b), u'd\ufffd\ufffdf') + self.assertEqual(renderer.str(b), u'd\ufffd\ufffdf') ## Test the _make_loader() method. @@ -243,7 +243,7 @@ def test__make_loader__attributes(self): renderer = self._renderer() renderer.file_encoding = 'enc' renderer.file_extension = 'ext' - renderer.unicode = unicode_ + renderer.str = unicode_ loader = renderer._make_loader() @@ -260,12 +260,12 @@ def test_render__return_type(self): """ renderer = self._renderer() rendered = renderer.render('foo') - self.assertEqual(type(rendered), unicode) + self.assertEqual(type(rendered), str) def test_render__unicode(self): renderer = self._renderer() - actual = renderer.render(u'foo') - self.assertEqual(actual, u'foo') + actual = renderer.render('foo') + self.assertEqual(actual, 'foo') def test_render__str(self): renderer = self._renderer() @@ -274,8 +274,8 @@ def test_render__str(self): def test_render__non_ascii_character(self): renderer = self._renderer() - actual = renderer.render(u'Poincaré') - self.assertEqual(actual, u'Poincaré') + actual = renderer.render('Poincaré') + self.assertEqual(actual, 'Poincaré') def test_render__context(self): """ @@ -326,7 +326,7 @@ def test_render__nonascii_template(self): """ renderer = _make_renderer() - template = u"déf".encode("utf-8") + template = "déf".encode("utf-8") # Check that decode_errors and string_encoding are both respected. renderer.decode_errors = 'ignore' @@ -334,7 +334,7 @@ def test_render__nonascii_template(self): self.assertEqual(renderer.render(template), "df") renderer.string_encoding = 'utf_8' - self.assertEqual(renderer.render(template), u"déf") + self.assertEqual(renderer.render(template), "déf") def test_make_resolve_partial(self): """ @@ -347,7 +347,7 @@ def test_make_resolve_partial(self): actual = resolve_partial('foo') self.assertEqual(actual, 'bar') - self.assertEqual(type(actual), unicode, "RenderEngine requires that " + self.assertEqual(type(actual), str, "RenderEngine requires that " "resolve_partial return unicode strings.") def test_make_resolve_partial__unicode(self): @@ -362,7 +362,7 @@ def test_make_resolve_partial__unicode(self): self.assertEqual(resolve_partial("partial"), "foo") # Now with a value that is already unicode. - renderer.partials = {'partial': u'foo'} + renderer.partials = {'partial': 'foo'} resolve_partial = renderer._make_resolve_partial() # If the next line failed, we would get the following error: # TypeError: decoding Unicode is not supported @@ -373,7 +373,7 @@ def test_render_name(self): data_dir = get_data_path() renderer = Renderer(search_dirs=data_dir) actual = renderer.render_name("say_hello", to='foo') - self.assertString(actual, u"Hello, foo") + self.assertString(actual, "Hello, foo") def test_render_path(self): """ @@ -412,7 +412,7 @@ class Spec(TemplateSpec): spec = Spec() actual = renderer.render(spec) - self.assertString(actual, u'hello, world') + self.assertString(actual, 'hello, world') def test_render__view(self): """ @@ -484,7 +484,7 @@ def test__resolve_partial__returns_unicode(self): Check that resolve_partial returns unicode (and not a subclass). """ - class MyUnicode(unicode): + class MyUnicode(str): pass renderer = Renderer() @@ -495,12 +495,12 @@ class MyUnicode(unicode): actual = engine.resolve_partial('str') self.assertEqual(actual, "foo") - self.assertEqual(type(actual), unicode) + self.assertEqual(type(actual), str) # Check that unicode subclasses are not preserved. actual = engine.resolve_partial('subclass') self.assertEqual(actual, "abc") - self.assertEqual(type(actual), unicode) + self.assertEqual(type(actual), str) def test__resolve_partial__not_found(self): """ @@ -512,7 +512,7 @@ def test__resolve_partial__not_found(self): engine = renderer._make_render_engine() resolve_partial = engine.resolve_partial - self.assertString(resolve_partial('foo'), u'') + self.assertString(resolve_partial('foo'), '') def test__resolve_partial__not_found__missing_tags_strict(self): """ @@ -539,7 +539,7 @@ def test__resolve_partial__not_found__partials_dict(self): engine = renderer._make_render_engine() resolve_partial = engine.resolve_partial - self.assertString(resolve_partial('foo'), u'') + self.assertString(resolve_partial('foo'), '') def test__resolve_partial__not_found__partials_dict__missing_tags_strict(self): """ @@ -566,12 +566,12 @@ def test__literal__uses_renderer_unicode(self): """ renderer = self._make_renderer() - renderer.unicode = mock_unicode + renderer.str = mock_unicode engine = renderer._make_render_engine() literal = engine.literal - b = u"foo".encode("ascii") + b = "foo".encode("ascii") self.assertEqual(literal(b), "FOO") def test__literal__handles_unicode(self): @@ -585,7 +585,7 @@ def test__literal__handles_unicode(self): engine = renderer._make_render_engine() literal = engine.literal - self.assertEqual(literal(u"foo"), "foo") + self.assertEqual(literal("foo"), "foo") def test__literal__returns_unicode(self): """ @@ -598,16 +598,16 @@ def test__literal__returns_unicode(self): engine = renderer._make_render_engine() literal = engine.literal - self.assertEqual(type(literal("foo")), unicode) + self.assertEqual(type(literal("foo")), str) - class MyUnicode(unicode): + class MyUnicode(str): pass s = MyUnicode("abc") self.assertEqual(type(s), MyUnicode) - self.assertTrue(isinstance(s, unicode)) - self.assertEqual(type(literal(s)), unicode) + self.assertTrue(isinstance(s, str)) + self.assertEqual(type(literal(s)), str) ## Test the engine's escape attribute. @@ -630,12 +630,12 @@ def test__escape__uses_renderer_unicode(self): """ renderer = Renderer() - renderer.unicode = mock_unicode + renderer.str = mock_unicode engine = renderer._make_render_engine() escape = engine.escape - b = u"foo".encode('ascii') + b = "foo".encode('ascii') self.assertEqual(escape(b), "FOO") def test__escape__has_access_to_original_unicode_subclass(self): @@ -644,16 +644,16 @@ def test__escape__has_access_to_original_unicode_subclass(self): """ renderer = Renderer() - renderer.escape = lambda s: unicode(type(s).__name__) + renderer.escape = lambda s: str(type(s).__name__) engine = renderer._make_render_engine() escape = engine.escape - class MyUnicode(unicode): + class MyUnicode(str): pass - self.assertEqual(escape(u"foo".encode('ascii')), unicode.__name__) - self.assertEqual(escape(u"foo"), unicode.__name__) + self.assertEqual(escape("foo".encode('ascii')), str.__name__) + self.assertEqual(escape("foo"), str.__name__) self.assertEqual(escape(MyUnicode("foo")), MyUnicode.__name__) def test__escape__returns_unicode(self): @@ -667,17 +667,17 @@ def test__escape__returns_unicode(self): engine = renderer._make_render_engine() escape = engine.escape - self.assertEqual(type(escape("foo")), unicode) + self.assertEqual(type(escape("foo")), str) # Check that literal doesn't preserve unicode subclasses. - class MyUnicode(unicode): + class MyUnicode(str): pass s = MyUnicode("abc") self.assertEqual(type(s), MyUnicode) - self.assertTrue(isinstance(s, unicode)) - self.assertEqual(type(escape(s)), unicode) + self.assertTrue(isinstance(s, str)) + self.assertEqual(type(escape(s)), str) ## Test the missing_tags attribute. @@ -706,7 +706,7 @@ def test__resolve_context(self): stack = ContextStack({'foo': 'bar'}) self.assertEqual('bar', engine.resolve_context(stack, 'foo')) - self.assertString(u'', engine.resolve_context(stack, 'missing')) + self.assertString('', engine.resolve_context(stack, 'missing')) def test__resolve_context__missing_tags_strict(self): """ diff --git a/pystache/tests/test_simple.py b/pystache/tests/test_simple.py index 07b059f5..b88bf35e 100644 --- a/pystache/tests/test_simple.py +++ b/pystache/tests/test_simple.py @@ -2,11 +2,11 @@ import pystache from pystache import Renderer -from examples.nested_context import NestedContext -from examples.complex import Complex -from examples.lambdas import Lambdas -from examples.template_partial import TemplatePartial -from examples.simple import Simple +from .examples.nested_context import NestedContext +from .examples.complex import Complex +from .examples.lambdas import Lambdas +from .examples.template_partial import TemplatePartial +from .examples.simple import Simple from pystache.tests.common import EXAMPLES_DIR from pystache.tests.common import AssertStringMixin @@ -20,7 +20,7 @@ def test_nested_context(self): view.template = '{{#foo}}{{thing1}} and {{thing2}} and {{outer_thing}}{{/foo}}{{^foo}}Not foo!{{/foo}}' actual = renderer.render(view) - self.assertString(actual, u"one and foo and two") + self.assertString(actual, "one and foo and two") def test_looping_and_negation_context(self): template = '{{#item}}{{header}}: {{name}} {{/item}}{{^item}} Shouldnt see me{{/item}}' @@ -40,7 +40,7 @@ def test_callables(self): renderer = Renderer() actual = renderer.render(view) - self.assertString(actual, u'bar != bar. oh, it does!') + self.assertString(actual, 'bar != bar. oh, it does!') def test_rendering_partial(self): renderer = Renderer(search_dirs=EXAMPLES_DIR) @@ -49,11 +49,11 @@ def test_rendering_partial(self): view.template = '{{>inner_partial}}' actual = renderer.render(view) - self.assertString(actual, u'Again, Welcome!') + self.assertString(actual, 'Again, Welcome!') view.template = '{{#looping}}{{>inner_partial}} {{/looping}}' actual = renderer.render(view) - self.assertString(actual, u"Again, Welcome! Again, Welcome! Again, Welcome! ") + self.assertString(actual, "Again, Welcome! Again, Welcome! Again, Welcome! ") def test_non_existent_value_renders_blank(self): view = Simple() @@ -77,7 +77,7 @@ def test_template_partial_extension(self): view = TemplatePartial(renderer=renderer) actual = renderer.render(view) - self.assertString(actual, u"""Welcome + self.assertString(actual, """Welcome ------- ## Again, Welcome! ##""") diff --git a/pystache/tests/test_specloader.py b/pystache/tests/test_specloader.py index cacc0fc9..dcdc55f5 100644 --- a/pystache/tests/test_specloader.py +++ b/pystache/tests/test_specloader.py @@ -9,11 +9,11 @@ import sys import unittest -import examples -from examples.simple import Simple -from examples.complex import Complex -from examples.lambdas import Lambdas -from examples.inverted import Inverted, InvertedLists +from . import examples +from .examples.simple import Simple +from .examples.complex import Complex +from .examples.lambdas import Lambdas +from .examples.inverted import Inverted, InvertedLists from pystache import Renderer from pystache import TemplateSpec from pystache.common import TemplateNotFoundError @@ -70,7 +70,7 @@ def test_template_path_for_partials(self): renderer2 = Renderer(search_dirs=EXAMPLES_DIR) actual = renderer1.render(spec) - self.assertString(actual, u"Partial: ") + self.assertString(actual, "Partial: ") actual = renderer2.render(spec) self.assertEqual(actual, "Partial: No tags...") @@ -79,7 +79,7 @@ def test_basic_method_calls(self): renderer = Renderer() actual = renderer.render(Simple()) - self.assertString(actual, u"Hi pizza!") + self.assertString(actual, "Hi pizza!") def test_non_callable_attributes(self): view = Simple() @@ -92,7 +92,7 @@ def test_non_callable_attributes(self): def test_complex(self): renderer = Renderer() actual = renderer.render(Complex()) - self.assertString(actual, u"""\ + self.assertString(actual, """\

Colors

  • red
  • @@ -111,7 +111,7 @@ def test_higher_order_rot13(self): renderer = Renderer() actual = renderer.render(view) - self.assertString(actual, u'nopqrstuvwxyz') + self.assertString(actual, 'nopqrstuvwxyz') def test_higher_order_lambda(self): view = Lambdas() @@ -119,7 +119,7 @@ def test_higher_order_lambda(self): renderer = Renderer() actual = renderer.render(view) - self.assertString(actual, u'abcdefghijklmnopqrstuvwxyz') + self.assertString(actual, 'abcdefghijklmnopqrstuvwxyz') def test_partials_with_lambda(self): view = Lambdas() @@ -127,7 +127,7 @@ def test_partials_with_lambda(self): renderer = Renderer(search_dirs=EXAMPLES_DIR) actual = renderer.render(view) - self.assertEqual(actual, u'nopqrstuvwxyz') + self.assertEqual(actual, 'nopqrstuvwxyz') def test_hierarchical_partials_with_lambdas(self): view = Lambdas() @@ -135,12 +135,12 @@ def test_hierarchical_partials_with_lambdas(self): renderer = Renderer(search_dirs=EXAMPLES_DIR) actual = renderer.render(view) - self.assertString(actual, u'nopqrstuvwxyznopqrstuvwxyz') + self.assertString(actual, 'nopqrstuvwxyznopqrstuvwxyz') def test_inverted(self): renderer = Renderer() actual = renderer.render(Inverted()) - self.assertString(actual, u"""one, two, three, empty list""") + self.assertString(actual, """one, two, three, empty list""") def test_accessing_properties_on_parent_object_from_child_objects(self): parent = Thing() @@ -152,12 +152,12 @@ def test_accessing_properties_on_parent_object_from_child_objects(self): renderer = Renderer() actual = renderer.render(view, {'parent': parent}) - self.assertString(actual, u'derp') + self.assertString(actual, 'derp') def test_inverted_lists(self): renderer = Renderer() actual = renderer.render(InvertedLists()) - self.assertString(actual, u"""one, two, three, empty list""") + self.assertString(actual, """one, two, three, empty list""") def _make_specloader(): @@ -176,7 +176,7 @@ def to_unicode(s, encoding=None): """ if encoding is None: encoding = 'ascii' - return unicode(s, encoding, 'strict') + return str(s, encoding, 'strict') loader = Loader(file_encoding='ascii', to_unicode=to_unicode) return SpecLoader(loader=loader) @@ -222,7 +222,7 @@ def test_load__template__type_str(self): custom.template = "abc" spec_loader = self._make_specloader() - self._assert_template(spec_loader, custom, u"abc") + self._assert_template(spec_loader, custom, "abc") def test_load__template__type_unicode(self): """ @@ -230,10 +230,10 @@ def test_load__template__type_unicode(self): """ custom = TemplateSpec() - custom.template = u"abc" + custom.template = "abc" spec_loader = self._make_specloader() - self._assert_template(spec_loader, custom, u"abc") + self._assert_template(spec_loader, custom, "abc") def test_load__template__unicode_non_ascii(self): """ @@ -241,10 +241,10 @@ def test_load__template__unicode_non_ascii(self): """ custom = TemplateSpec() - custom.template = u"é" + custom.template = "é" spec_loader = self._make_specloader() - self._assert_template(spec_loader, custom, u"é") + self._assert_template(spec_loader, custom, "é") def test_load__template__with_template_encoding(self): """ @@ -252,14 +252,14 @@ def test_load__template__with_template_encoding(self): """ custom = TemplateSpec() - custom.template = u'é'.encode('utf-8') + custom.template = 'é'.encode('utf-8') spec_loader = self._make_specloader() - self.assertRaises(UnicodeDecodeError, self._assert_template, spec_loader, custom, u'é') + self.assertRaises(UnicodeDecodeError, self._assert_template, spec_loader, custom, 'é') custom.template_encoding = 'utf-8' - self._assert_template(spec_loader, custom, u'é') + self._assert_template(spec_loader, custom, 'é') # TODO: make this test complete. def test_load__template__correct_loader(self): @@ -279,10 +279,10 @@ def __init__(self): self.encoding = None # Overrides the existing method. - def unicode(self, s, encoding=None): + def str(self, s, encoding=None): self.s = s self.encoding = encoding - return u"foo" + return "foo" loader = MockLoader() custom_loader = SpecLoader() @@ -293,7 +293,7 @@ def unicode(self, s, encoding=None): view.template_encoding = "encoding-foo" # Check that our unicode() above was called. - self._assert_template(custom_loader, view, u'foo') + self._assert_template(custom_loader, view, 'foo') self.assertEqual(loader.s, "template-foo") self.assertEqual(loader.encoding, "encoding-foo") @@ -410,7 +410,7 @@ def _assert_get_template(self, custom, expected): loader = self._make_loader() actual = loader.load(custom) - self.assertEqual(type(actual), unicode) + self.assertEqual(type(actual), str) self.assertEqual(actual, expected) def test_get_template(self): @@ -420,7 +420,7 @@ def test_get_template(self): """ view = SampleView() - self._assert_get_template(view, u"ascii: abc") + self._assert_get_template(view, "ascii: abc") def test_get_template__template_encoding(self): """ @@ -432,4 +432,4 @@ def test_get_template__template_encoding(self): self.assertRaises(UnicodeDecodeError, self._assert_get_template, view, 'foo') view.template_encoding = 'utf-8' - self._assert_get_template(view, u"non-ascii: é") + self._assert_get_template(view, "non-ascii: é") diff --git a/setup.cfg b/setup.cfg new file mode 100644 index 00000000..f6f1279a --- /dev/null +++ b/setup.cfg @@ -0,0 +1,71 @@ +[metadata] +name = pystache +version = attr: pystache.__version__ +author = Chris Wanstrath +author_email = chris@ozmm.org +maintainer = Steve Arnold +maintainer_email = nerdboy@gentoo.org +description = Mustache for Python +url = https://github.com/sarnold/pystache +license = MIT +license_files = LICENSE +classifiers = + Development Status :: 4 - Beta + Intended Audience :: Developers + License :: OSI Approved :: MIT License + Programming Language :: Python :: 3 + Programming Language :: Python :: 3.6 + Programming Language :: Python :: 3.7 + Programming Language :: Python :: 3.8 + Programming Language :: Python :: 3.9 + +[options] +python_requires = >=3.6 +zip_safe = True +include_package_data = True +packages = find: + +[options.package_data] +* = *.mustache, *.txt + +[options.entry_points] +console_scripts = + pystache=pystache.commands.render:main + pystache-test=pystache.commands.test:main + +[options.extras_require] +test = + nose + +cov = + coverage + +[bdist_wheel] +universal = 0 + +[check-manifest] +ignore = + .codeclimate.yml + .gitattributes + .coveragerc + .gitignore + .pep8speaks.yml + codecov.yml + +[flake8] +exclude = + .git, + __pycache__, + build, + dist + +max-line-length = 110 + +[nosetests] +traverse-namespace = 1 +verbosity = 3 +with-coverage = 1 +with-doctest = 1 +doctest-extension = rst +cover-package = pystache +cover-xml = 1 diff --git a/setup.py b/setup.py index 0d99aae8..f0b7d7fd 100644 --- a/setup.py +++ b/setup.py @@ -28,7 +28,7 @@ instead as plain-text, which we do not want. To check in advance that PyPI will accept and parse the reST file as HTML, -you can use the rst2html program installed by the docutils package +you can use the rst2html.py program installed by the docutils package (http://docutils.sourceforge.net/). To install docutils: $ pip install docutils @@ -89,30 +89,7 @@ import shutil import sys - -py_version = sys.version_info - -# distutils does not seem to support the following setup() arguments. -# It displays a UserWarning when setup() is passed those options: -# -# * entry_points -# * install_requires -# -# distribute works with Python 2.3.5 and above: -# -# http://packages.python.org/distribute/setuptools.html#building-and-distributing-packages-with-distribute -# -if py_version < (2, 3, 5): - # TODO: this might not work yet. - import distutils as dist - from distutils import core - setup = core.setup -else: - import setuptools as dist - setup = dist.setup - - -VERSION = '0.5.4' # Also change in pystache/__init__.py. +from setuptools import setup FILE_ENCODING = 'utf-8' @@ -126,22 +103,6 @@ PREP_COMMAND = 'prep' -CLASSIFIERS = ( - 'Development Status :: 4 - Beta', - 'License :: OSI Approved :: MIT License', - 'Programming Language :: Python', - 'Programming Language :: Python :: 2', - 'Programming Language :: Python :: 2.4', - 'Programming Language :: Python :: 2.5', - 'Programming Language :: Python :: 2.6', - 'Programming Language :: Python :: 2.7', - 'Programming Language :: Python :: 3', - 'Programming Language :: Python :: 3.1', - 'Programming Language :: Python :: 3.2', - 'Programming Language :: Python :: 3.3', - 'Programming Language :: Python :: Implementation :: PyPy', -) - # Comments in reST begin with two dots. RST_LONG_DESCRIPTION_INTRO = """\ .. Do not edit this file. This file is auto-generated for PyPI by setup.py @@ -221,7 +182,7 @@ def convert_md_to_rst(md_path, rst_temp_path): """ # Pandoc uses the UTF-8 character encoding for both input and output. - command = "pandoc --write=rst --output=%s %s" % (rst_temp_path, md_path) + command = "pandoc -f markdown-smart --write=rst --output=%s %s" % (rst_temp_path, md_path) print("converting with pandoc: %s to %s\n-->%s" % (md_path, rst_temp_path, command)) @@ -308,65 +269,9 @@ def publish(): os.system('python setup.py sdist upload') -# We use the package simplejson for older Python versions since Python -# does not contain the module json before 2.6: -# -# http://docs.python.org/library/json.html -# -# Moreover, simplejson stopped officially support for Python 2.4 in version 2.1.0: -# -# https://github.com/simplejson/simplejson/blob/master/CHANGES.txt -# -requires = [] -if py_version < (2, 5): - requires.append('simplejson<2.1') -elif py_version < (2, 6): - requires.append('simplejson') - -INSTALL_REQUIRES = requires - -# TODO: decide whether to use find_packages() instead. I'm not sure that -# find_packages() is available with distutils, for example. -PACKAGES = [ - 'pystache', - 'pystache.commands', - # The following packages are only for testing. - 'pystache.tests', - 'pystache.tests.data', - 'pystache.tests.data.locator', - 'pystache.tests.examples', -] - - -# The purpose of this function is to follow the guidance suggested here: -# -# http://packages.python.org/distribute/python3.html#note-on-compatibility-with-setuptools -# -# The guidance is for better compatibility when using setuptools (e.g. with -# earlier versions of Python 2) instead of Distribute, because of new -# keyword arguments to setup() that setuptools may not recognize. -def get_extra_args(): - """ - Return a dictionary of extra args to pass to setup(). - - """ - extra = {} - # TODO: it might be more correct to check whether we are using - # Distribute instead of setuptools, since use_2to3 doesn't take - # effect when using Python 2, even when using Distribute. - if py_version >= (3, ): - # Causes 2to3 to be run during the build step. - extra['use_2to3'] = True - - return extra - - def main(sys_argv): # TODO: use the logging module instead of printing. - # TODO: include the following in a verbose mode. - sys.stderr.write("pystache: using: version %s of %s\n" % (repr(dist.__version__), repr(dist))) - command = sys_argv[-1] if command == 'publish': @@ -377,35 +282,10 @@ def main(sys_argv): sys.exit() long_description = read(RST_DESCRIPTION_PATH) - template_files = ['*.mustache', '*.txt'] - extra_args = get_extra_args() - - setup(name='pystache', - version=VERSION, - license='MIT', - description='Mustache for Python', - long_description=long_description, - author='Chris Wanstrath', - author_email='chris@ozmm.org', - maintainer='Chris Jerdonek', - maintainer_email='chris.jerdonek@gmail.com', - url='http://github.com/defunkt/pystache', - install_requires=INSTALL_REQUIRES, - packages=PACKAGES, - package_data = { - # Include template files so tests can be run. - 'pystache.tests.data': template_files, - 'pystache.tests.data.locator': template_files, - 'pystache.tests.examples': template_files, - }, - entry_points = { - 'console_scripts': [ - 'pystache=pystache.commands.render:main', - 'pystache-test=pystache.commands.test:main', - ], - }, - classifiers = CLASSIFIERS, - **extra_args + + setup( + long_description=long_description, + long_description_content_type='text/x-rst', ) diff --git a/setup_description.rst b/setup_description.rst index 724c4572..d7f1bc00 100644 --- a/setup_description.rst +++ b/setup_description.rst @@ -4,13 +4,17 @@ Pystache ======== -.. figure:: http://defunkt.github.com/pystache/images/logo_phillips.png - :alt: mustachioed, monocled snake by David Phillips +|ci| |Conda| |Wheels| |Release| |Python| -.. figure:: https://secure.travis-ci.org/defunkt/pystache.png - :alt: Travis CI current build status +|Latest release| |License| |Maintainability| |codecov| -`Pystache `__ is a Python +This updated fork of Pystache is currently tested on Python 3.6+ and in +Conda, on Linux, Macos, and Windows (Python 2.7 support has been +removed). + +|image9| + +`Pystache `__ is a Python implementation of `Mustache `__. Mustache is a framework-agnostic, logic-free templating system inspired by `ctemplate `__ and @@ -23,62 +27,45 @@ page provides a good introduction to Mustache's syntax. For a more complete (and more current) description of Mustache's behavior, see the official `Mustache spec `__. -Pystache is `semantically versioned `__ and can be -found on `PyPI `__. This version -of Pystache passes all tests in `version -1.1.2 `__ of the spec. +Pystache is `semantically versioned `__ and older +versions can still be found on +`PyPI `__. This version of +Pystache now passes all tests in `version +1.1.3 `__ of the spec. Requirements ------------ Pystache is tested with-- -- Python 2.4 (requires simplejson `version - 2.0.9 `__ or earlier) -- Python 2.5 (requires - `simplejson `__) -- Python 2.6 -- Python 2.7 -- Python 3.1 -- Python 3.2 -- Python 3.3 -- `PyPy `__ +- Python 3.6 +- Python 3.7 +- Python 3.8 +- Python 3.9 +- Conda (py36-py39) `Distribute `__ (the setuptools -fork) is recommended over -`setuptools `__, and is required -in some cases (e.g. for Python 3 support). If you use -`pip `__, you probably already satisfy -this requirement. +fork) is no longer required over +`setuptools `__, as the current +packaging is now PEP517-compliant. JSON support is needed only for the command-line interface and to run -the spec tests. We require simplejson for earlier versions of Python -since Python's `json `__ -module was added in Python 2.6. - -For Python 2.4 we require an earlier version of simplejson since -simplejson stopped officially supporting Python 2.4 in simplejson -version 2.1.0. Earlier versions of simplejson can be installed manually, -as follows: - -:: - - pip install 'simplejson<2.1.0' +the spec tests; PyYAML can still be used (see the Develop section). -Official support for Python 2.4 will end with Pystache version 0.6.0. +Official support for Python 2 will end with Pystache version 0.6.0. Install It ---------- :: - pip install pystache + pip install -U pystache -f https://github.com/sarnold/pystache/releases/ And test it-- :: - pystache-test + pystache-test To install and test from source (e.g. from GitHub), see the Develop section. @@ -88,68 +75,68 @@ Use It :: - >>> import pystache - >>> print pystache.render('Hi {{person}}!', {'person': 'Mom'}) - Hi Mom! + >>> import pystache + >>> print(pystache.render('Hi {{person}}!', {'person': 'Mom'})) + Hi Mom! You can also create dedicated view classes to hold your view logic. -Here's your view class (in .../examples/readme.py): +Here's your view class (in ../pystache/tests/examples/readme.py): :: - class SayHello(object): - def to(self): - return "Pizza" + class SayHello(object): + def to(self): + return "Pizza" Instantiating like so: :: - >>> from pystache.tests.examples.readme import SayHello - >>> hello = SayHello() + >>> from pystache.tests.examples.readme import SayHello + >>> hello = SayHello() -Then your template, say\_hello.mustache (by default in the same -directory as your class definition): +Then your template, say_hello.mustache (by default in the same directory +as your class definition): :: - Hello, {{to}}! + Hello, {{to}}! Pull it together: :: - >>> renderer = pystache.Renderer() - >>> print renderer.render(hello) - Hello, Pizza! + >>> renderer = pystache.Renderer() + >>> print(renderer.render(hello)) + Hello, Pizza! For greater control over rendering (e.g. to specify a custom template directory), use the ``Renderer`` class like above. One can pass attributes to the Renderer class constructor or set them on a Renderer instance. To customize template loading on a per-view basis, subclass ``TemplateSpec``. See the docstrings of the -`Renderer `__ +`Renderer `__ class and -`TemplateSpec `__ +`TemplateSpec `__ class for more information. You can also pre-parse a template: :: - >>> parsed = pystache.parse(u"Hey {{#who}}{{.}}!{{/who}}") - >>> print parsed - [u'Hey ', _SectionNode(key=u'who', index_begin=12, index_end=18, parsed=[_EscapeNode(key=u'.'), u'!'])] + >>> parsed = pystache.parse(u"Hey {{#who}}{{.}}!{{/who}}") + >>> print(parsed) + ['Hey ', _SectionNode(key='who', index_begin=12, index_end=18, parsed=[_EscapeNode(key='.'), '!'])] And then: :: - >>> print renderer.render(parsed, {'who': 'Pops'}) - Hey Pops! - >>> print renderer.render(parsed, {'who': 'you'}) - Hey you! + >>> print(renderer.render(parsed, {'who': 'Pops'})) + Hey Pops! + >>> print(renderer.render(parsed, {'who': 'you'})) + Hey you! Python 3 -------- @@ -211,22 +198,24 @@ To test from a source distribution (without installing)-- :: - python test_pystache.py + python test_pystache.py To test Pystache with multiple versions of Python (with a single -command!), you can use `tox `__: +command!) and different platforms, you can use +`tox `__: :: - pip install 'virtualenv<1.8' # Version 1.8 dropped support for Python 2.4. - pip install 'tox<1.4' # Version 1.4 dropped support for Python 2.4. - tox + pip install tox + tox -e setup -If you do not have all Python versions listed in ``tox.ini``-- +To run tests on multiple versions with coverage, run: :: - tox -e py26,py32 # for example + tox -e py38-linux,py39-linux # for example + +(substitute your platform above, eg, macos or windows) The source distribution tests also include doctests and tests from the Mustache spec. To include tests from the Mustache spec in your test @@ -234,8 +223,8 @@ runs: :: - git submodule init - git submodule update + git submodule init + git submodule update The test harness parses the spec's (more human-readable) yaml files if `PyYAML `__ is present. Otherwise, @@ -243,94 +232,113 @@ it parses the json files. To install PyYAML-- :: - pip install pyyaml + pip install pyyaml + +Once the submodule is available, you can run the full test set with: + +:: + + tox -e setup . ext/spec/specs To run a subset of the tests, you can use `nose `__: :: - pip install nose - nosetests --tests pystache/tests/test_context.py:GetValueTests.test_dictionary__key_present + pip install nose + nosetests --tests pystache/tests/test_context.py:GetValueTests.test_dictionary__key_present -Using Python 3 with Pystache from source -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Mailing List (old) +------------------ -Pystache is written in Python 2 and must be converted to Python 3 prior -to using it with Python 3. The installation process (and tox) do this -automatically. +There is(was) a `mailing +list `__. Note that there is a +bit of a delay between posting a message and seeing it appear in the +mailing list archive. -To convert the code to Python 3 manually (while using Python 3)-- +Credits +------- :: - python setup.py build + >>> import pystache + >>> context = { 'author': 'Chris Wanstrath', 'maintainer': 'Chris Jerdonek','refurbisher': 'Steve Arnold' } + >>> print(pystache.render("Author: {{author}}\nMaintainer: {{maintainer}}\nRefurbisher: {{refurbisher}}", context)) + Author: Chris Wanstrath + Maintainer: Chris Jerdonek + Refurbisher: Steve Arnold -This writes the converted code to a subdirectory called ``build``. By -design, Python 3 builds -`cannot `__ -be created from Python 2. +Pystache logo by `David Phillips `__ is +licensed under a `Creative Commons Attribution-ShareAlike 3.0 Unported +License `__. +|image10| -To convert the code without using setup.py, you can use -`2to3 `__ as follows (two -steps)-- +History +======= -:: +**Note:** Official support for Python 2.7 will end with Pystache version +0.6.0. - 2to3 --write --nobackups --no-diffs --doctests_only pystache - 2to3 --write --nobackups --no-diffs pystache +0.6.0 (2021-03-04) +------------------ -This converts the code (and doctests) in place. +- Bump spec versions to latest => v1.1.3 +- Modernize python and CI tools, update docs/doctests +- Update unicode conversion test for py3-only +- Add pep8speaks cfg, cleanup warnings +- Remove superfluous setup test/unused imports +- Add conda recipe/CI build -To ``import pystache`` from a source distribution while using Python 3, -be sure that you are importing from a directory containing a converted -version of the code (e.g. from the ``build`` directory after -converting), and not from the original (unconverted) source directory. -Otherwise, you will get a syntax error. You can help prevent this by not -running the Python IDE from the project directory when importing -Pystache while using Python 3. +.. _section-1: -Mailing List ------------- +0.5.6 (2021-02-28) +------------------ -There is a `mailing list `__. -Note that there is a bit of a delay between posting a message and seeing -it appear in the mailing list archive. +- Use correct wheel name in release workflow, limit wheels +- Add install check/test of downloaded wheel +- Update/add ci workflows and tox cfg, bump to next dev0 version -Credits -------- +.. _section-2: -:: +0.5.5 (2020-12-16) +------------------ - >>> context = { 'author': 'Chris Wanstrath', 'maintainer': 'Chris Jerdonek' } - >>> print pystache.render("Author: {{author}}\nMaintainer: {{maintainer}}", context) - Author: Chris Wanstrath - Maintainer: Chris Jerdonek +- fix document processing, update pandoc args and history +- add release.yml to CI, test env settings +- fix bogus commit message, update versions and tox cf +- add post-test steps for building pkgs with/without doc updates +- add CI build check, fix MANIFEST.in pruning -Pystache logo by `David Phillips `__ is -licensed under a `Creative Commons Attribution-ShareAlike 3.0 Unported -License `__. -|image0| +.. _section-3: -History -======= +0.5.4-2 (2020-11-09) +-------------------- -**Note:** Official support for Python 2.4 will end with Pystache version -0.6.0. +- Merge pull request #1 from sarnold/rebase-up +- Bugfix: test_specloader.py: fix test_find__with_directory on other + OSs +- Bugfix: pystache/loader.py: remove stray windows line-endings +- fix crufty (and insecure) http urls +- Bugfix: modernize python versions (keep py27) and fix spec_test load + cmd + +.. _section-4: 0.5.4 (2014-07-11) ------------------ - Bugfix: made test with filenames OS agnostic (issue #162). +.. _section-5: + 0.5.3 (2012-11-03) ------------------ - Added ability to customize string coercion (e.g. to have None render as ``''``) (issue #130). -- Added Renderer.render\_name() to render a template by name (issue +- Added Renderer.render_name() to render a template by name (issue #122). -- Added TemplateSpec.template\_path to specify an absolute path to a +- Added TemplateSpec.template_path to specify an absolute path to a template (issue #41). - Added option of raising errors on missing tags/partials: ``Renderer(missing_tags='strict')`` (issue #110). @@ -355,6 +363,8 @@ History - More robust handling of byte strings in Python 3. - Added Creative Commons license for David Phillips's logo. +.. _section-6: + 0.5.2 (2012-05-03) ------------------ @@ -367,16 +377,20 @@ History context stack (issue #113). - Bugfix: lists of lambdas for sections were not rendered (issue #114). +.. _section-7: + 0.5.1 (2012-04-24) ------------------ - Added support for Python 3.1 and 3.2. - Added tox support to test multiple Python versions. - Added test script entry point: pystache-test. -- Added \_\_version\_\_ package attribute. +- Added \__version_\_ package attribute. - Test harness now supports both YAML and JSON forms of Mustache spec. - Test harness no longer requires nose. +.. _section-8: + 0.5.0 (2012-04-03) ------------------ @@ -435,11 +449,15 @@ Bug fixes: - Passing ``**kwargs`` to ``Template()`` with no context no longer raises an exception. +.. _section-9: + 0.4.1 (2012-03-25) ------------------ - Added support for Python 2.4. [wangtz, jvantuyl] +.. _section-10: + 0.4.0 (2011-01-12) ------------------ @@ -447,19 +465,25 @@ Bug fixes: - Add support for inverted lists - Decoupled template loading +.. _section-11: + 0.3.1 (2010-05-07) ------------------ - Fix package +.. _section-12: + 0.3.0 (2010-05-03) ------------------ -- View.template\_path can now hold a list of path +- View.template_path can now hold a list of path - Add {{& blah}} as an alias for {{{ blah }}} - Higher Order Sections - Inverted sections +.. _section-13: + 0.2.0 (2010-02-15) ------------------ @@ -473,12 +497,16 @@ Bug fixes: [enaeseth] - Template file encoding awareness. [enaeseth] +.. _section-14: + 0.1.1 (2009-11-13) ------------------ - Ensure we're dealing with strings, always - Tests can be run by executing the test file directly +.. _section-15: + 0.1.0 (2009-11-12) ------------------ @@ -510,4 +538,23 @@ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -.. |image0| image:: http://i.creativecommons.org/l/by-sa/3.0/88x31.png +.. |ci| image:: https://github.com/sarnold/pystache/actions/workflows/ci.yml/badge.svg + :target: https://github.com/sarnold/pystache/actions/workflows/ci.yml +.. |Conda| image:: https://github.com/sarnold/pystache/actions/workflows/conda.yml/badge.svg + :target: https://github.com/sarnold/pystache/actions/workflows/conda.yml +.. |Wheels| image:: https://github.com/sarnold/pystache/actions/workflows/wheels.yml/badge.svg + :target: https://github.com/sarnold/pystache/actions/workflows/wheels.yml +.. |Release| image:: https://github.com/sarnold/pystache/actions/workflows/release.yml/badge.svg + :target: https://github.com/sarnold/pystache/actions/workflows/release.yml +.. |Python| image:: https://img.shields.io/badge/python-3.6+-blue.svg + :target: https://www.python.org/downloads/ +.. |Latest release| image:: https://img.shields.io/github/v/release/sarnold/pystache?include_prereleases + :target: https://github.com/sarnold/pystache/releases/latest +.. |License| image:: https://img.shields.io/github/license/sarnold/pystache + :target: https://github.com/sarnold/pystache/blob/master/LICENSE +.. |Maintainability| image:: https://api.codeclimate.com/v1/badges/a8fa1bf4638bfc6581b6/maintainability + :target: https://codeclimate.com/github/sarnold/pystache/maintainability +.. |codecov| image:: https://codecov.io/gh/sarnold/pystache/branch/master/graph/badge.svg?token=5PZNMZBI6K + :target: https://codecov.io/gh/sarnold/pystache +.. |image9| image:: gh/images/logo_phillips_small.png +.. |image10| image:: http://i.creativecommons.org/l/by-sa/3.0/88x31.png diff --git a/tox.ini b/tox.ini index d1eaebfb..66c4515b 100644 --- a/tox.ini +++ b/tox.ini @@ -1,36 +1,110 @@ -# A tox configuration file to test across multiple Python versions. -# -# http://pypi.python.org/pypi/tox -# [tox] -# Tox 1.4 drops py24 and adds py33. In the current version, we want to -# support 2.4, so we can't simultaneously support 3.3. -envlist = py24,py25,py26,py27,py27-yaml,py27-noargs,py31,py32,pypy +envlist = py{36,37,38,39}-{linux,macos,windows} +skip_missing_interpreters = true +isolated_build = true +#skipsdist = true + +[gh-actions] +python = + 3.6: py36 + 3.7: py37 + 3.8: py38 + 3.9: py39 + +[gh-actions:env] +PLATFORM = + ubuntu-18.04: linux + macos-latest: macos + windows-latest: windows [testenv] +passenv = CI PYTHON PYTHONIOENCODING + +deps = + pip>=20.0.1 + nose + coverage + +commands = + nosetests -sx . {posargs} + +[testenv:bare] # Change the working directory so that we don't import the pystache located # in the original location. +deps = + pip>=20.0.1 + -e . + changedir = {envbindir} + commands = - pystache-test {toxinidir} + pystache-test + +[testenv:bench] +passenv = CI PYTHON PYTHONIOENCODING -# Check that the spec tests work with PyYAML. -[testenv:py27-yaml] -basepython = - python2.7 deps = - PyYAML -changedir = - {envbindir} + pip>=20.0.1 + # uncomment for comparison, posargs expects a number, eg, 10000 + #chevron + +commands_pre = + pip install . + commands = - pystache-test {toxinidir} + python pystache/tests/benchmark.py {posargs} + +[testenv:setup] +passenv = CI PYTHON PYTHONIOENCODING + +deps = + pyyaml + twine + +commands = + python setup.py install + twine check dist/* + pystache-test {posargs} + +[testenv:deploy] +passenv = CI PYTHON PYTHONIOENCODING +allowlist_externals = bash + +deps = + pip>=19.0.1 + wheel + pep517 + twine + +commands = + python -m pep517.build . + twine check dist/* + +[testenv:check] +passenv = CI PYTHON PYTHONIOENCODING +skip_install = true + +allowlist_externals = bash + +deps = + pip>=20.0.1 -# Check that pystache-test works from an install with no arguments. -[testenv:py27-noargs] -basepython = - python2.7 -changedir = - {envbindir} commands = + bash -c 'export WHL_FILE=$(ls dist/*.whl); \ + python -m pip install $WHL_FILE' pystache-test + +[testenv:docs] +passenv = CI PYTHON PYTHONIOENCODING +allowlist_externals = bash + +deps = + pip>=19.0.1 + wheel + docutils + # apt/emerge pandoc first + +commands = + python setup.py prep + bash -c 'python setup.py --long-description | rst2html.py -v --no-raw > out.html' diff --git a/travis.yml_disabled b/travis.yml_disabled new file mode 100644 index 00000000..f0b40428 --- /dev/null +++ b/travis.yml_disabled @@ -0,0 +1,52 @@ +dist: xenial +language: python + +# Travis CI has no plans to support Jython and no longer supports Python 2.5. +python: + - "2.7" + - "3.5" + - "3.6" + - "3.7" + - "3.8" + - "3.9-dev" + - "nightly" + +matrix: + fast_finish: true + include: + - os: osx + # osx is goofy, ``python`` is always py2, images mutate fast + language: shell + before_install: + - pip3 install --upgrade pip wheel + install: + - python3 setup.py install + script: + - pystache-test . ext/spec/specs + - os: windows + # windows is even goofier, install path is different for python/python3 + # but either way you get python3 and the cmd is always ``python`` o.O + # (also versions mutuate like bacteria) + language: shell + before_install: + - choco install python3 --params "/InstallDir:C:\\Python" + - python -m pip install --upgrade pip wheel + env: PATH="/c/Python:/c/Python/Scripts:$PATH" + install: + - python setup.py install + script: + - pystache-test . ext/spec/specs + allow_failures: + - python: "nightly" + +# command to install dependencies +install: + - pip install --upgrade pip + - pip install codecov + +script: + - python setup.py install + # Include the spec tests directory for Mustache spec tests and the + # project directory for doctests. + - pystache-test . ext/spec/specs + #- tox