├── .github ├── dependabot.yaml └── workflows │ └── tests.yaml ├── .gitignore ├── .travis.yml ├── CONTRIBUTING.md ├── LICENSE ├── MANIFEST.in ├── README.md ├── pandoc_mustache ├── __init__.py ├── pandoc_mustache.py └── version.py ├── requirements-dev.txt ├── requirements.txt ├── setup.cfg ├── setup.py └── tests ├── test_html_escaping.py ├── test_metadata_list_emptyval.py ├── test_metadata_yaml_format.py ├── test_mustache_in_abstract.py ├── test_mustache_variable_miss.py ├── test_no_mustache_metadata.py └── test_path_w_space.py /.github/dependabot.yaml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: "github-actions" 4 | directory: "/" 5 | schedule: 6 | interval: "weekly" 7 | day: "sunday" 8 | time: "16:00" 9 | groups: 10 | github-actions: 11 | patterns: [ "*" ] 12 | - package-ecosystem: "pip" 13 | directory: "/" 14 | schedule: 15 | interval: "weekly" 16 | day: "sunday" 17 | time: "16:00" 18 | groups: 19 | python-requirements: 20 | patterns: [ "*" ] 21 | -------------------------------------------------------------------------------- /.github/workflows/tests.yaml: -------------------------------------------------------------------------------- 1 | name: Tests 2 | 3 | on: 4 | workflow_dispatch: 5 | push: 6 | branches: 7 | - main 8 | 9 | jobs: 10 | tests: 11 | runs-on: ubuntu-latest 12 | timeout-minutes: 10 13 | strategy: 14 | matrix: 15 | python: ['3.12', '3.11', '3.10', '3.9', '3.8'] 16 | pandoc: ['3.4', '3.3', '3.2.1', '3.1.13', '3.0.1', '2.19.2'] 17 | 18 | steps: 19 | - name: Check out repository 20 | uses: actions/checkout@v4 21 | 22 | - name: Set up Pandoc 23 | run: | 24 | # See latest versions here: https://github.com/jgm/pandoc/releases/ 25 | wget --progress=dot:mega -O pandoc.deb "https://github.com/jgm/pandoc/releases/download/${{ matrix.pandoc }}/pandoc-${{ matrix.pandoc }}-1-amd64.deb" 26 | sudo dpkg --install pandoc.deb 27 | rm pandoc.deb 28 | 29 | - name: Set up Python 30 | uses: actions/setup-python@v5 31 | with: 32 | python-version: '${{ matrix.python }}' 33 | cache: 'pip' 34 | 35 | - name: Install Python dependencies 36 | run: | 37 | python3 -m pip install -r requirements-dev.txt 38 | pip install . 39 | 40 | - name: Run tests 41 | run: pytest 42 | 43 | - name: Output success message 44 | run: echo "✅ All tests passed with Python ${{ matrix.python }} and pandoc ${{ matrix.pandoc }}." >> $GITHUB_STEP_SUMMARY 45 | if: success() 46 | 47 | - name: Output failure message 48 | run: echo "❌ Tests failed with Python ${{ matrix.python }} and pandoc ${{ matrix.pandoc }}." >> $GITHUB_STEP_SUMMARY 49 | if: failure() 50 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | README.rst 2 | README.html 3 | debug.txt 4 | 5 | ################################################################################ 6 | ### Python (https://github.com/github/gitignore/blob/main/Python.gitignore) 7 | ################################################################################ 8 | 9 | # Byte-compiled / optimized / DLL files 10 | __pycache__/ 11 | *.py[cod] 12 | *$py.class 13 | 14 | # C extensions 15 | *.so 16 | 17 | # Distribution / packaging 18 | .Python 19 | build/ 20 | develop-eggs/ 21 | dist/ 22 | downloads/ 23 | eggs/ 24 | .eggs/ 25 | lib/ 26 | lib64/ 27 | parts/ 28 | sdist/ 29 | var/ 30 | wheels/ 31 | share/python-wheels/ 32 | *.egg-info/ 33 | .installed.cfg 34 | *.egg 35 | MANIFEST 36 | 37 | # PyInstaller 38 | # Usually these files are written by a python script from a template 39 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 40 | *.manifest 41 | *.spec 42 | 43 | # Installer logs 44 | pip-log.txt 45 | pip-delete-this-directory.txt 46 | 47 | # Unit test / coverage reports 48 | htmlcov/ 49 | .tox/ 50 | .nox/ 51 | .coverage 52 | .coverage.* 53 | .cache 54 | nosetests.xml 55 | coverage.xml 56 | *.cover 57 | *.py,cover 58 | .hypothesis/ 59 | .pytest_cache/ 60 | cover/ 61 | 62 | # Translations 63 | *.mo 64 | *.pot 65 | 66 | # Django stuff: 67 | *.log 68 | local_settings.py 69 | db.sqlite3 70 | db.sqlite3-journal 71 | 72 | # Flask stuff: 73 | instance/ 74 | .webassets-cache 75 | 76 | # Scrapy stuff: 77 | .scrapy 78 | 79 | # Sphinx documentation 80 | docs/_build/ 81 | 82 | # PyBuilder 83 | .pybuilder/ 84 | target/ 85 | 86 | # Jupyter Notebook 87 | .ipynb_checkpoints 88 | 89 | # IPython 90 | profile_default/ 91 | ipython_config.py 92 | 93 | # pyenv 94 | # For a library or package, you might want to ignore these files since the code is 95 | # intended to run in multiple environments; otherwise, check them in: 96 | # .python-version 97 | 98 | # pipenv 99 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 100 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 101 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 102 | # install all needed dependencies. 103 | #Pipfile.lock 104 | 105 | # poetry 106 | # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. 107 | # This is especially recommended for binary packages to ensure reproducibility, and is more 108 | # commonly ignored for libraries. 109 | # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control 110 | #poetry.lock 111 | 112 | # pdm 113 | # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. 114 | #pdm.lock 115 | # pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it 116 | # in version control. 117 | # https://pdm.fming.dev/latest/usage/project/#working-with-version-control 118 | .pdm.toml 119 | .pdm-python 120 | .pdm-build/ 121 | 122 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm 123 | __pypackages__/ 124 | 125 | # Celery stuff 126 | celerybeat-schedule 127 | celerybeat.pid 128 | 129 | # SageMath parsed files 130 | *.sage.py 131 | 132 | # Environments 133 | .env 134 | .venv 135 | env/ 136 | venv/ 137 | ENV/ 138 | env.bak/ 139 | venv.bak/ 140 | 141 | # Spyder project settings 142 | .spyderproject 143 | .spyproject 144 | 145 | # Rope project settings 146 | .ropeproject 147 | 148 | # mkdocs documentation 149 | /site 150 | 151 | # mypy 152 | .mypy_cache/ 153 | .dmypy.json 154 | dmypy.json 155 | 156 | # Pyre type checker 157 | .pyre/ 158 | 159 | # pytype static type analyzer 160 | .pytype/ 161 | 162 | # Cython debug symbols 163 | cython_debug/ 164 | 165 | # PyCharm 166 | # JetBrains specific template is maintained in a separate JetBrains.gitignore that can 167 | # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore 168 | # and can be added to the global gitignore or merged into this file. For a more nuclear 169 | # option (not recommended) you can uncomment the following to ignore the entire idea folder. 170 | #.idea/ 171 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | # os: linux and sudo: false is assumed, which means it is using container-based Ubuntu 2 | language: python 3 | cache: pip 4 | dist: xenial # required for Python >= 3.7 5 | # build matrix: different python and pandoc versions 6 | python: 7 | - "2.7" 8 | - "3.4" 9 | - "3.5" 10 | - "3.6" 11 | - "3.7" 12 | - "3.7-dev" # 3.7 development branch 13 | - "3.8-dev" # 3.8 development branch 14 | - "nightly" # Travis CI supports a special version name nightly, which points to a recent development version of CPython build 15 | # pypy (version info from [Changelogs — PyPy documentation](http://doc.pypy.org/en/latest/index-of-whatsnew.html)) 16 | - "pypy" 17 | - "pypy3" 18 | # https://groups.google.com/forum/?fromgroups#!topic/pandoc-discuss/uGASAhRydfI 19 | env: 20 | - pandocVersion=1.19.2.1 21 | - pandocVersion=latest 22 | matrix: 23 | allow_failures: 24 | # - python: "3.6-dev" 25 | # - python: "3.7-dev" 26 | # - python: "nightly" 27 | # - python: "pypy3" 28 | fast_finish: true 29 | # download pandoc 30 | before_install: 31 | - | 32 | if [[ $pandocVersion == "latest" ]]; then 33 | url="https://github.com/jgm/pandoc/releases/latest" 34 | else 35 | url="https://github.com/jgm/pandoc/releases/tag/$pandocVersion" 36 | fi 37 | path=$(curl -L $url | grep -o '/jgm/pandoc/releases/download/.*-amd64\.deb') 38 | downloadUrl="https://github.com$path" 39 | file=${path##*/} 40 | # install dependencies 41 | install: 42 | # pandoc 43 | - wget $downloadUrl && 44 | sudo dpkg -i $file 45 | - pip install -e . 46 | before_script: 47 | # pasteurize for py2 only (disabled because Python code is currently universal as-written) 48 | # - | 49 | # if [[ "$TRAVIS_PYTHON_VERSION" == "2.7" || "$TRAVIS_PYTHON_VERSION" == "pypy" ]]; then 50 | # pasteurize -wn ./pandoc_mustache/pandoc_mustache.py 51 | # fi 52 | # commands to run tests 53 | script: 54 | - pytest 55 | before_deploy: 56 | # create README.rst for PyPI README 57 | - pandoc -f markdown+autolink_bare_uris-fancy_lists-implicit_header_references -M date="`date "+%B %e, %Y"`" --toc -s -o README.rst README.md 58 | # debugging rst output 59 | ## install dependencies for rst to html 60 | - pip install -e .[pypi] 61 | ## test if the rst output is valid 62 | - rst2html.py README.rst > README.html 63 | deploy: 64 | provider: pypi 65 | user: michaelstepner 66 | password: 67 | secure: HI/2sdV0lcuZ9llZr5TUfxVASz8hYjYdehvxRKKKrVBeBIfaIOus6Cysh5hRorqy/wssXy4tA3Q+SzqElhbv1NECjfzWyGqwSiZWwvz5wiAFQlHVVvG98kcCJDXqI3ez8BHJgCz8WSPu5jeBHGhwweJk3DIGw4vhZRcsN+tzWSlPkyZ4TizGoSEF2P4P7k+ostryPID75lQ71LgAyE/vzbdkqmJvZ/UY9OHZU17n1xYTxpKV+bKAQoHh+TWulb7fiGP0QiZdi96meDU9huJIsjBDSJj71WGAir3lh/96itM+kHNgPn5A7Q9LwH207tVtMQOD3cxWvaDnpFmXJH8e0xWsyQv3zS1sH10X+3PoM6xAOh42eCOKYjrloF/6C52TOlieBVzT1l3siCk/fg72x/aqffwkluz9VMUDQC05abbyewTBFPLOdn40vhy6nw8tuXlR5ahBj8YUVEdU5dMMLR3tDtFTMBAnAALi2vTtSvZL2uYooFjUQ0e7csiSl7dVJvnkHNNTGw08q0u1MZK1D1tpgZk/bnFzrBHwEQVt18/GLIbompam9OWFTnGsAfDngBxiNW51HkztTuieVPyCKyWJTvg8xTynqqNj2Juva/DvkzNPH6LBj5WCM61wbR9LO6EMUvK5ihUu2YTw3enwGHD77d08G5eyWkPpYFWcBMA= 68 | distributions: sdist bdist_wheel 69 | skip_cleanup: true 70 | on: 71 | tags: true 72 | python: '3.6' 73 | condition: "$pandocVersion = latest" 74 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to pandoc-mustache 2 | 3 | [![Project Status: Inactive – The project has reached a stable, usable state but is no longer being actively developed; support/maintenance will be provided as time allows.](http://www.repostatus.org/badges/latest/inactive.svg)](http://www.repostatus.org/#inactive) 4 | 5 | This code is not being actively developed. It was created to fulfill my pandoc writing needs, and the current feature set is adequate for me. 6 | 7 | If you have a **bug report**, you can create an issue or file a pull request. I'll look into it, time permitting. 8 | 9 | If you have a **feature request**, it is unlikely that I will be able to implement it for you. You can create an issue to generate discussion. If you implement a feature, you can file pull request and I will review it eventually, as time permits. If you're interested in making major additions to the code, I'd be happy to welcome a new maintainer to the project. -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | CC0 1.0 Universal 2 | 3 | Statement of Purpose 4 | 5 | The laws of most jurisdictions throughout the world automatically confer 6 | exclusive Copyright and Related Rights (defined below) upon the creator and 7 | subsequent owner(s) (each and all, an "owner") of an original work of 8 | authorship and/or a database (each, a "Work"). 9 | 10 | Certain owners wish to permanently relinquish those rights to a Work for the 11 | purpose of contributing to a commons of creative, cultural and scientific 12 | works ("Commons") that the public can reliably and without fear of later 13 | claims of infringement build upon, modify, incorporate in other works, reuse 14 | and redistribute as freely as possible in any form whatsoever and for any 15 | purposes, including without limitation commercial purposes. These owners may 16 | contribute to the Commons to promote the ideal of a free culture and the 17 | further production of creative, cultural and scientific works, or to gain 18 | reputation or greater distribution for their Work in part through the use and 19 | efforts of others. 20 | 21 | For these and/or other purposes and motivations, and without any expectation 22 | of additional consideration or compensation, the person associating CC0 with a 23 | Work (the "Affirmer"), to the extent that he or she is an owner of Copyright 24 | and Related Rights in the Work, voluntarily elects to apply CC0 to the Work 25 | and publicly distribute the Work under its terms, with knowledge of his or her 26 | Copyright and Related Rights in the Work and the meaning and intended legal 27 | effect of CC0 on those rights. 28 | 29 | 1. Copyright and Related Rights. A Work made available under CC0 may be 30 | protected by copyright and related or neighboring rights ("Copyright and 31 | Related Rights"). Copyright and Related Rights include, but are not limited 32 | to, the following: 33 | 34 | i. the right to reproduce, adapt, distribute, perform, display, communicate, 35 | and translate a Work; 36 | 37 | ii. moral rights retained by the original author(s) and/or performer(s); 38 | 39 | iii. publicity and privacy rights pertaining to a person's image or likeness 40 | depicted in a Work; 41 | 42 | iv. rights protecting against unfair competition in regards to a Work, 43 | subject to the limitations in paragraph 4(a), below; 44 | 45 | v. rights protecting the extraction, dissemination, use and reuse of data in 46 | a Work; 47 | 48 | vi. database rights (such as those arising under Directive 96/9/EC of the 49 | European Parliament and of the Council of 11 March 1996 on the legal 50 | protection of databases, and under any national implementation thereof, 51 | including any amended or successor version of such directive); and 52 | 53 | vii. other similar, equivalent or corresponding rights throughout the world 54 | based on applicable law or treaty, and any national implementations thereof. 55 | 56 | 2. Waiver. To the greatest extent permitted by, but not in contravention of, 57 | applicable law, Affirmer hereby overtly, fully, permanently, irrevocably and 58 | unconditionally waives, abandons, and surrenders all of Affirmer's Copyright 59 | and Related Rights and associated claims and causes of action, whether now 60 | known or unknown (including existing as well as future claims and causes of 61 | action), in the Work (i) in all territories worldwide, (ii) for the maximum 62 | duration provided by applicable law or treaty (including future time 63 | extensions), (iii) in any current or future medium and for any number of 64 | copies, and (iv) for any purpose whatsoever, including without limitation 65 | commercial, advertising or promotional purposes (the "Waiver"). Affirmer makes 66 | the Waiver for the benefit of each member of the public at large and to the 67 | detriment of Affirmer's heirs and successors, fully intending that such Waiver 68 | shall not be subject to revocation, rescission, cancellation, termination, or 69 | any other legal or equitable action to disrupt the quiet enjoyment of the Work 70 | by the public as contemplated by Affirmer's express Statement of Purpose. 71 | 72 | 3. Public License Fallback. Should any part of the Waiver for any reason be 73 | judged legally invalid or ineffective under applicable law, then the Waiver 74 | shall be preserved to the maximum extent permitted taking into account 75 | Affirmer's express Statement of Purpose. In addition, to the extent the Waiver 76 | is so judged Affirmer hereby grants to each affected person a royalty-free, 77 | non transferable, non sublicensable, non exclusive, irrevocable and 78 | unconditional license to exercise Affirmer's Copyright and Related Rights in 79 | the Work (i) in all territories worldwide, (ii) for the maximum duration 80 | provided by applicable law or treaty (including future time extensions), (iii) 81 | in any current or future medium and for any number of copies, and (iv) for any 82 | purpose whatsoever, including without limitation commercial, advertising or 83 | promotional purposes (the "License"). The License shall be deemed effective as 84 | of the date CC0 was applied by Affirmer to the Work. Should any part of the 85 | License for any reason be judged legally invalid or ineffective under 86 | applicable law, such partial invalidity or ineffectiveness shall not 87 | invalidate the remainder of the License, and in such case Affirmer hereby 88 | affirms that he or she will not (i) exercise any of his or her remaining 89 | Copyright and Related Rights in the Work or (ii) assert any associated claims 90 | and causes of action with respect to the Work, in either case contrary to 91 | Affirmer's express Statement of Purpose. 92 | 93 | 4. Limitations and Disclaimers. 94 | 95 | a. No trademark or patent rights held by Affirmer are waived, abandoned, 96 | surrendered, licensed or otherwise affected by this document. 97 | 98 | b. Affirmer offers the Work as-is and makes no representations or warranties 99 | of any kind concerning the Work, express, implied, statutory or otherwise, 100 | including without limitation warranties of title, merchantability, fitness 101 | for a particular purpose, non infringement, or the absence of latent or 102 | other defects, accuracy, or the present or absence of errors, whether or not 103 | discoverable, all to the greatest extent permissible under applicable law. 104 | 105 | c. Affirmer disclaims responsibility for clearing rights of other persons 106 | that may apply to the Work or any use thereof, including without limitation 107 | any person's Copyright and Related Rights in the Work. Further, Affirmer 108 | disclaims responsibility for obtaining any necessary consents, permissions 109 | or other rights required for any use of the Work. 110 | 111 | d. Affirmer understands and acknowledges that Creative Commons is not a 112 | party to this document and has no duty or obligation with respect to this 113 | CC0 or use of the Work. 114 | 115 | For more information, please see 116 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include README.md 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # pandoc-mustache: Variable Substitution in Pandoc 2 | 3 | [![Development Status](https://img.shields.io/pypi/status/pandoc-mustache.svg)](https://pypi.python.org/pypi/pandoc-mustache/) 4 | [![PyPI version](https://img.shields.io/pypi/v/pandoc-mustache.svg)](https://pypi.python.org/pypi/pandoc-mustache/) 5 | [![Python version](https://img.shields.io/pypi/pyversions/pandoc-mustache.svg)](https://pypi.python.org/pypi/pandoc-mustache/) 6 | [![Tests](https://github.com/michaelstepner/pandoc-mustache/actions/workflows/tests.yaml/badge.svg)](https://github.com/michaelstepner/pandoc-mustache/actions/workflows/tests.yaml) 7 | 8 | The **pandoc-mustache** filter allows you to put variables into your pandoc document text, with their values stored in a separate file. When you run `pandoc` the variables are replaced with their values. 9 | 10 | *Technical note:* This pandoc filter is not a complete implementation of the [Mustache template spec](https://mustache.github.io/). Only variable replacement is supported: other [tag types](https://mustache.github.io/mustache.5.html#TAG-TYPES) are not currently supported. 11 | 12 | ## Example 13 | 14 | This document, in `document.md`: 15 | 16 | ``` 17 | --- 18 | mustache: ./le_gaps.yaml 19 | --- 20 | The richest American men live {{diff_le_richpoor_men}} years longer than the poorest men, 21 | while the richest American women live {{diff_le_richpoor_women}} years longer than the poorest women. 22 | ``` 23 | 24 | Combined with these variable definitions, in `le_gaps.yaml`: 25 | 26 | ```yaml 27 | diff_le_richpoor_men: "14.6" 28 | diff_le_richpoor_women: "10.1" 29 | ``` 30 | 31 | Will be converted by `pandoc document.md --filter pandoc-mustache` to: 32 | 33 | > The richest American men live 14.6 years longer than the poorest men, while the richest American women live 10.1 years longer than the poorest women. 34 | 35 | ## Installation 36 | 37 | Install by opening a terminal and running: 38 | 39 | ``` 40 | pip install -U pandoc-mustache 41 | ``` 42 | 43 | Python 2.7, 3.4+, pypy, and pypy3 are supported. 44 | 45 | ## Usage 46 | 47 | 1. Within a pandoc document, variables are referenced by enclosing the variable name in double "mustaches", i.e. curly brackets, like `{{this}}`. 48 | 49 | 2. The variables are defined in one or more separate files, using YAML formatted key-value pairs. For example: 50 | 51 | ```yaml 52 | place: Montreal 53 | temperature: '7' 54 | ``` 55 | 56 | 3. The pandoc document containing the mustache variables points to the YAML file (or files) which contain the variable definitions. These files are indicated using the mustache field in a [YAML metadata block](https://pandoc.org/MANUAL.html#metadata-blocks), typically placed at the top of the pandoc document. Absolute paths and relative paths are supported: relative paths are evaluated relative to the working directory where `pandoc` is being run. 57 | 58 | An example: 59 | 60 | ```yaml 61 | --- 62 | title: My Report 63 | author: Jane Smith 64 | mustache: ./vars.yaml 65 | --- 66 | The temperature in {{place}} was {{temperature}} degrees. 67 | ``` 68 | 69 | Or, with more than one file: 70 | 71 | ```yaml 72 | --- 73 | title: My Report 74 | author: Jane Smith 75 | mustache: 76 | - ./vars.yaml 77 | - ./additional_vars.yaml 78 | --- 79 | The temperature in {{place}} was {{temperature}} degrees. 80 | The humidity was {{humidity}}%. 81 | ``` 82 | 83 | 4. Run pandoc and replace all variables in the document with their values by adding `--filter pandoc-mustache` to the pandoc command. 84 | 85 | ### Tips and Tricks 86 | 87 | * When defining variables in YAML, there is no need to enclose strings in quotes. But you should enclose numbers in quotes if you want them to appear in the document using the exact same formatting. Some examples: 88 | 89 | ```yaml 90 | unquoted_string: Montreal # becomes: Montreal 91 | quoted_string: 'Montreal' # becomes: Montreal 92 | trailingzero_num: 7.40 # becomes: 7.4 93 | trailingzero_string: '7.40' # becomes: 7.40 94 | ``` 95 | 96 | * If you're writing a document that reports computed numerical results, you can program your code (in R, Python, Stata, etc.) to write those numbers to a YAML file automatically each time they are generated. By referencing your numerical results using variables instead of hard-coding them into the text, the document can be updated instantly if the results change. And you can be certain that all the numbers in the output document reflect the latest results of your analysis. 97 | 98 | ## Contributing 99 | 100 | [![Project Status: Inactive – The project has reached a stable, usable state but is no longer being actively developed; support/maintenance will be provided as time allows.](http://www.repostatus.org/badges/latest/inactive.svg)](http://www.repostatus.org/#inactive) 101 | 102 | This code is not being actively developed. It was created to fulfill my pandoc writing needs, and the current feature set is adequate for me. 103 | 104 | If you have a **bug report**, you can create an issue or file a pull request. I'll look into it, time permitting. 105 | 106 | If you have a **feature request**, it is unlikely that I will be able to implement it for you. You can create an issue to generate discussion. If you implement a feature, you can file pull request and I will review it eventually, as time permits. If you're interested in making major additions to the code, I'd be happy to welcome a new maintainer to the project. 107 | 108 | ## License 109 | 110 | All of the files in this repository are released to the public domain under a [CC0 license](https://creativecommons.org/publicdomain/zero/1.0/) to permit the widest possible reuse. 111 | 112 | ## Acknowledgements 113 | 114 | This pandoc filter was created using Sergio Correia's [panflute](https://github.com/sergiocorreia/panflute) package. The [panflute](https://github.com/sergiocorreia/panflute) repository also served as an inspiration for the organization of this repository. 115 | 116 | ### Related Filters 117 | 118 | Scott Koga-Browes' [pandoc-abbreviations](https://github.com/scokobro/pandoc-abbreviations) filter also performs variable replacement in pandoc documents, using a different syntax. 119 | -------------------------------------------------------------------------------- /pandoc_mustache/__init__.py: -------------------------------------------------------------------------------- 1 | from .version import __version__ 2 | import pandoc_mustache 3 | -------------------------------------------------------------------------------- /pandoc_mustache/pandoc_mustache.py: -------------------------------------------------------------------------------- 1 | """ 2 | Pandoc filter to apply mustache templates on regular text. 3 | """ 4 | from past.builtins import basestring 5 | from panflute import * 6 | import pystache, yaml 7 | 8 | def prepare(doc): 9 | """ Parse metadata to obtain list of mustache templates, 10 | then load those templates. 11 | """ 12 | doc.mustache_files = doc.get_metadata('mustache') 13 | if isinstance(doc.mustache_files, basestring): # process single YAML value stored as string 14 | if not doc.mustache_files: 15 | doc.mustache_files = None # switch empty string back to None 16 | else: 17 | doc.mustache_files = [ doc.mustache_files ] # put non-empty string in list 18 | # with open('debug.txt', 'a') as the_file: 19 | # the_file.write(str(doc.mustache_files)) 20 | # the_file.write('\n') 21 | if doc.mustache_files is not None: 22 | doc.mustache_hashes = [yaml.load(open(file, 'r').read(), Loader=yaml.SafeLoader) for file in doc.mustache_files] 23 | doc.mhash = { k: v for mdict in doc.mustache_hashes for k, v in mdict.items() } # combine list of dicts into a single dict 24 | doc.mrenderer = pystache.Renderer(escape=lambda u: u, missing_tags='strict') 25 | else: 26 | doc.mhash = None 27 | 28 | def action(elem, doc): 29 | """ Apply combined mustache template to all strings in document. 30 | """ 31 | if type(elem) == Str and doc.mhash is not None: 32 | elem.text = doc.mrenderer.render(elem.text, doc.mhash) 33 | return elem 34 | 35 | def main(doc=None): 36 | return run_filter(action, prepare=prepare, doc=doc) 37 | 38 | if __name__ == '__main__': 39 | main() 40 | -------------------------------------------------------------------------------- /pandoc_mustache/version.py: -------------------------------------------------------------------------------- 1 | __version__ = '0.1.0' 2 | -------------------------------------------------------------------------------- /requirements-dev.txt: -------------------------------------------------------------------------------- 1 | -r requirements.txt 2 | 3 | pytest==8.3.3 4 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | panflute==2.3.1 2 | pystache==0.6.5 3 | PyYAML==6.0.2 4 | future==1.0.0 5 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [metadata] 2 | description-file = README.md 3 | [bdist_wheel] 4 | universal=1 5 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | """Pandoc filter for variable substitution using Mustache syntax 2 | 3 | See: 4 | https://github.com/michaelstepner/pandoc-mustache/ 5 | """ 6 | 7 | from setuptools import setup, find_packages 8 | # To use a consistent encoding 9 | from codecs import open 10 | from os import path 11 | 12 | here = path.abspath(path.dirname(__file__)) 13 | 14 | # Get the long description from the README file 15 | try: 16 | with open(path.join(here, 'README.rst'), encoding='utf-8') as f: 17 | long_description = f.read() 18 | except (IOError): 19 | with open(path.join(here, 'README.md'), encoding='utf-8') as f: 20 | long_description = f.read() 21 | 22 | # Import version number 23 | version = {} 24 | with open("pandoc_mustache/version.py") as fp: 25 | exec(fp.read(), version) 26 | version = version['__version__'] 27 | 28 | setup( 29 | name='pandoc-mustache', 30 | 31 | # Versions should comply with PEP440. For a discussion on single-sourcing 32 | # the version across setup.py and the project code, see 33 | # https://packaging.python.org/en/latest/single_source_version.html 34 | version=version, 35 | 36 | description='Pandoc filter for variable substitution using Mustache syntax', 37 | long_description=long_description, 38 | 39 | # The project's main homepage. 40 | url='https://github.com/michaelstepner/pandoc-mustache/', 41 | 42 | # Author details 43 | author="Michael Stepner", 44 | author_email='stepner@mit.edu', 45 | 46 | # Choose your license 47 | license='CC0-1.0', 48 | 49 | # See https://pypi.python.org/pypi?%3Aaction=list_classifiers 50 | classifiers=[ 51 | # How mature is this project? Common values are 52 | # 3 - Alpha 53 | # 4 - Beta 54 | # 5 - Production/Stable 55 | 'Development Status :: 4 - Beta', 56 | 57 | # Indicate who your project is intended for 58 | 'Environment :: Console', 59 | 'Intended Audience :: End Users/Desktop', 60 | 'Intended Audience :: Developers', 61 | 'Topic :: Software Development :: Build Tools', 62 | 'Topic :: Text Processing :: Filters', 63 | 'Operating System :: OS Independent', 64 | 65 | # Pick your license as you wish (should match "license" above) 66 | 'License :: CC0 1.0 Universal (CC0 1.0) Public Domain Dedication', 67 | 68 | # Specify the Python versions you support here. In particular, ensure 69 | # that you indicate whether you support Python 2, Python 3 or both. 70 | # https://pypi.python.org/pypi?%3Aaction=list_classifiers 71 | 'Programming Language :: Python :: 2', 72 | 'Programming Language :: Python :: 2.7', 73 | 'Programming Language :: Python :: 3', 74 | 'Programming Language :: Python :: 3.4', 75 | 'Programming Language :: Python :: 3.5', 76 | 'Programming Language :: Python :: 3.6', 77 | 'Programming Language :: Python :: 3.7', 78 | 'Programming Language :: Python :: Implementation :: CPython', 79 | 'Programming Language :: Python :: Implementation :: PyPy' 80 | ], 81 | 82 | # What does your project relate to? 83 | keywords='pandoc pandocfilters panflute markdown latex mustache', 84 | 85 | # You can just specify the packages manually here if your project is 86 | # simple. Or you can use find_packages(). 87 | packages=find_packages(exclude=['contrib', 'docs', 'tests', 'examples']), 88 | 89 | # Alternatively, if you want to distribute just a my_module.py, uncomment 90 | # this: 91 | # py_modules=["pandoc_mustache"], 92 | 93 | # List run-time dependencies here. These will be installed by pip when 94 | # your project is installed. For an analysis of "install_requires" vs pip's 95 | # requirements files see: 96 | # https://packaging.python.org/en/latest/requirements.html 97 | install_requires=[ 98 | 'panflute', 99 | 'pystache', 100 | 'pyyaml', 101 | 'future' 102 | ], 103 | 104 | # List additional groups of dependencies here (e.g. development 105 | # dependencies). You can install these using the following syntax, 106 | # for example: 107 | # $ pip install -e .[dev,test] 108 | extras_require={ 109 | # 'dev': ['check-manifest'], 110 | # 'test': ['pandocfilters', 'configparser', 'pytest-cov', 'future'], 111 | 'pypi': ['docutils'] 112 | }, 113 | 114 | # If there are data files included in your packages that need to be 115 | # installed, specify them here. If using Python 2.6 or less, then these 116 | # have to be included in MANIFEST.in as well. 117 | #package_data={ 118 | # 'sample': ['package_data.dat'], 119 | #}, 120 | 121 | # Although 'package_data' is the preferred approach, in some case you may 122 | # need to place data files outside of your packages. See: 123 | # http://docs.python.org/3.4/distutils/setupscript.html#installing-additional-files # noqa 124 | # In this case, 'data_file' will be installed into '/my_data' 125 | #data_files=[('my_data', ['data/data_file'])], 126 | 127 | # To provide executable scripts, use entry points in preference to the 128 | # "scripts" keyword. Entry points provide cross-platform support and allow 129 | # pip to create the appropriate form of executable for the target platform. 130 | entry_points={ 131 | 'console_scripts': [ 132 | 'pandoc-mustache=pandoc_mustache.pandoc_mustache:main', 133 | ], 134 | }, 135 | ) 136 | -------------------------------------------------------------------------------- /tests/test_html_escaping.py: -------------------------------------------------------------------------------- 1 | """ 2 | Test that escaping characters for HTML is disabled. 3 | """ 4 | import os, subprocess 5 | 6 | def test_escape_singlequote(tmpdir): 7 | 8 | # Define empty dictionaries 9 | doc = {} 10 | template = {} 11 | 12 | # Prepare file names 13 | doc['path'] = tmpdir.join("document.md") 14 | template['path'] = tmpdir.join("template.yaml") 15 | 16 | # Prepare file contents 17 | doc['metadata'] = '''--- 18 | mustache: {mustachefile} 19 | --- 20 | ''' 21 | doc['mfiles'] = { "mustachefile": template['path'] } 22 | doc['text'] = 'Hello {{place}}' 23 | template['content'] = "place: world ' universe" 24 | 25 | # Write contents to files 26 | with open(doc['path'].strpath, "a") as myfile: 27 | myfile.write(doc['metadata'].format(**doc['mfiles'])) 28 | myfile.write(doc['text']) 29 | template['path'].write(template['content']) 30 | 31 | # Run pandoc 32 | output = subprocess.check_output(["pandoc", doc['path'].strpath, "--filter", "pandoc-mustache", "--to=plain"], universal_newlines=True) 33 | 34 | # Test output 35 | assert output == "Hello world ' universe\n" 36 | 37 | def test_escape_gt(tmpdir): 38 | 39 | # Define empty dictionaries 40 | doc = {} 41 | template = {} 42 | 43 | # Prepare file names 44 | doc['path'] = tmpdir.join("document.md") 45 | template['path'] = tmpdir.join("template.yaml") 46 | 47 | # Prepare file contents 48 | doc['metadata'] = '''--- 49 | mustache: {mustachefile} 50 | --- 51 | ''' 52 | doc['mfiles'] = { "mustachefile": template['path'] } 53 | doc['text'] = 'Hello {{place}}' 54 | template['content'] = "place: world > universe" 55 | 56 | # Write contents to files 57 | with open(doc['path'].strpath, "a") as myfile: 58 | myfile.write(doc['metadata'].format(**doc['mfiles'])) 59 | myfile.write(doc['text']) 60 | template['path'].write(template['content']) 61 | 62 | # Run pandoc 63 | output = subprocess.check_output(["pandoc", doc['path'].strpath, "--filter", "pandoc-mustache", "--to=plain"], universal_newlines=True) 64 | 65 | # Test output 66 | assert output == "Hello world > universe\n" 67 | 68 | def test_escape_ampersand(tmpdir): 69 | 70 | # Define empty dictionaries 71 | doc = {} 72 | template = {} 73 | 74 | # Prepare file names 75 | doc['path'] = tmpdir.join("document.md") 76 | template['path'] = tmpdir.join("template.yaml") 77 | 78 | # Prepare file contents 79 | doc['metadata'] = '''--- 80 | mustache: {mustachefile} 81 | --- 82 | ''' 83 | doc['mfiles'] = { "mustachefile": template['path'] } 84 | doc['text'] = 'Hello {{place}}' 85 | template['content'] = "place: world & universe" 86 | 87 | # Write contents to files 88 | with open(doc['path'].strpath, "a") as myfile: 89 | myfile.write(doc['metadata'].format(**doc['mfiles'])) 90 | myfile.write(doc['text']) 91 | template['path'].write(template['content']) 92 | 93 | # Run pandoc 94 | output = subprocess.check_output(["pandoc", doc['path'].strpath, "--filter", "pandoc-mustache", "--to=plain"], universal_newlines=True) 95 | 96 | # Test output 97 | assert output == "Hello world & universe\n" 98 | -------------------------------------------------------------------------------- /tests/test_metadata_list_emptyval.py: -------------------------------------------------------------------------------- 1 | """ 2 | Test that if a list with an empty value for a mustache file is specified in pandoc YAML metadata, 3 | an error is thrown. 4 | """ 5 | import os, subprocess, sys 6 | 7 | def test_blank_mustache_list(tmpdir): 8 | 9 | # Define empty dictionaries 10 | doc = {} 11 | 12 | # Prepare file names 13 | doc['path'] = tmpdir.join("document.md") 14 | 15 | # Prepare file contents 16 | doc['metadata'] = '''--- 17 | mustache: 18 | - 19 | --- 20 | ''' 21 | doc['text'] = 'Hello {{place}}' 22 | 23 | # Write contents to files 24 | with open(doc['path'].strpath, "a") as myfile: 25 | myfile.write(doc['metadata']) 26 | myfile.write(doc['text']) 27 | 28 | # Run pandoc, assert error 29 | try: 30 | output = subprocess.check_output(["pandoc", doc['path'].strpath, "--filter", "pandoc-mustache"], universal_newlines=True, stderr=subprocess.STDOUT) 31 | assert 0 # expecting an exception when calling pandoc 32 | except subprocess.CalledProcessError as e: 33 | assert e.returncode == 83 34 | if (sys.version_info > (3, 0)): # Python 3 35 | assert "FileNotFoundError" in e.output 36 | else: 37 | assert "IOError" in e.output 38 | assert "No such file or directory:" in e.output 39 | 40 | def test_2el_mustache_list_wblank(tmpdir): 41 | 42 | # Define empty dictionaries 43 | doc = {} 44 | template = {} 45 | 46 | # Prepare file names 47 | doc['path'] = tmpdir.join("document.md") 48 | template['path'] = tmpdir.join("template.yaml") 49 | 50 | # Prepare file contents 51 | doc['metadata'] = '''--- 52 | mustache: 53 | - {mustachefile} 54 | - 55 | --- 56 | ''' 57 | doc['mfiles'] = { "mustachefile": template['path'] } 58 | doc['text'] = 'Hello {{place}}' 59 | template['content'] = "place: 'world'" 60 | 61 | # Write contents to files 62 | with open(doc['path'].strpath, "a") as myfile: 63 | myfile.write(doc['metadata'].format(**doc['mfiles'])) 64 | myfile.write(doc['text']) 65 | template['path'].write(template['content']) 66 | 67 | # Run pandoc, assert error 68 | try: 69 | output = subprocess.check_output(["pandoc", doc['path'].strpath, "--filter", "pandoc-mustache"], universal_newlines=True, stderr=subprocess.STDOUT) 70 | assert 0 # expecting an exception when calling pandoc 71 | except subprocess.CalledProcessError as e: 72 | assert e.returncode == 83 73 | if (sys.version_info > (3, 0)): # Python 3 74 | assert "FileNotFoundError" in e.output 75 | else: 76 | assert "IOError" in e.output 77 | assert "No such file or directory:" in e.output 78 | -------------------------------------------------------------------------------- /tests/test_metadata_yaml_format.py: -------------------------------------------------------------------------------- 1 | """ 2 | Test that the pandoc metadata block containing mustache templates can be in various formats: 3 | 4 | --- 5 | mustache: /path/to/file 6 | --- 7 | 8 | --- 9 | mustache: 10 | - /path/to/file 11 | --- 12 | 13 | --- 14 | mustache: 15 | - /path/to/file1 16 | - /path/to/file2 17 | --- 18 | 19 | """ 20 | import os, subprocess 21 | 22 | def test_yaml_mapping(tmpdir): 23 | 24 | # Define empty dictionaries 25 | doc = {} 26 | template = {} 27 | 28 | # Prepare file names 29 | doc['path'] = tmpdir.join("document.md") 30 | template['path'] = tmpdir.join("template.yaml") 31 | 32 | # Prepare file contents 33 | doc['metadata'] = '''--- 34 | mustache: {mustachefile} 35 | --- 36 | ''' 37 | doc['mfiles'] = { "mustachefile": template['path'] } 38 | doc['text'] = 'Hello {{place}}' 39 | template['content'] = "place: 'world'" 40 | 41 | # Write contents to files 42 | with open(doc['path'].strpath, "a") as myfile: 43 | myfile.write(doc['metadata'].format(**doc['mfiles'])) 44 | myfile.write(doc['text']) 45 | template['path'].write(template['content']) 46 | 47 | # Run pandoc 48 | output = subprocess.check_output(["pandoc", doc['path'].strpath, "--filter", "pandoc-mustache"], universal_newlines=True) 49 | 50 | # Test output 51 | assert output == "

Hello world

\n" 52 | 53 | def test_yaml_list_1el(tmpdir): 54 | 55 | # Define empty dictionaries 56 | doc = {} 57 | template = {} 58 | 59 | # Prepare file names 60 | doc['path'] = tmpdir.join("document.md") 61 | template['path'] = tmpdir.join("template.yaml") 62 | 63 | # Prepare file contents 64 | doc['metadata'] = '''--- 65 | mustache: 66 | - {mustachefile} 67 | --- 68 | ''' 69 | doc['mfiles'] = { "mustachefile": template['path'] } 70 | doc['text'] = 'Hello {{place}}' 71 | template['content'] = "place: 'world'" 72 | 73 | # Write contents to files 74 | with open(doc['path'].strpath, "a") as myfile: 75 | myfile.write(doc['metadata'].format(**doc['mfiles'])) 76 | myfile.write(doc['text']) 77 | template['path'].write(template['content']) 78 | 79 | # Run pandoc 80 | output = subprocess.check_output(["pandoc", doc['path'].strpath, "--filter", "pandoc-mustache"], universal_newlines=True) 81 | 82 | # Test output 83 | assert output == "

Hello world

\n" 84 | 85 | def test_yaml_list_2el(tmpdir): 86 | 87 | # Define empty dictionaries 88 | doc = {} 89 | template = {} 90 | template2 = {} 91 | 92 | # Prepare file names 93 | doc['path'] = tmpdir.join("document.md") 94 | template['path'] = tmpdir.join("template.yaml") 95 | template2['path'] = tmpdir.join("template2.yaml") 96 | 97 | # Prepare file contents 98 | doc['metadata'] = '''--- 99 | mustache: 100 | - {mustachefile} 101 | - {mustachefile2} 102 | --- 103 | ''' 104 | doc['mfiles'] = { "mustachefile": template['path'], "mustachefile2": template2['path'] } 105 | doc['text'] = 'Hello {{adj}} {{place}}' 106 | template['content'] = "place: 'world'" 107 | template2['content'] = "adj: 'dark'" 108 | 109 | # Write contents to files 110 | with open(doc['path'].strpath, "a") as myfile: 111 | myfile.write(doc['metadata'].format(**doc['mfiles'])) 112 | myfile.write(doc['text']) 113 | template['path'].write(template['content']) 114 | template2['path'].write(template2['content']) 115 | 116 | # Run pandoc 117 | output = subprocess.check_output(["pandoc", doc['path'].strpath, "--filter", "pandoc-mustache"], universal_newlines=True) 118 | 119 | # Test output 120 | assert output == "

Hello dark world

\n" 121 | -------------------------------------------------------------------------------- /tests/test_mustache_in_abstract.py: -------------------------------------------------------------------------------- 1 | """ 2 | Test that a mustache variable in the abstract will be replaced. 3 | """ 4 | import os, subprocess 5 | 6 | def test_mustache_abstract(tmpdir): 7 | 8 | # Define empty dictionaries 9 | doc = {} 10 | template = {} 11 | 12 | # Prepare file names 13 | doc['path'] = tmpdir.join("document.md") 14 | template['path'] = tmpdir.join("template.yaml") 15 | 16 | # Prepare file contents 17 | doc['metadata'] = '''--- 18 | abstract: Hello {{{{place}}}}. 19 | mustache: {mustachefile} 20 | --- 21 | ''' # quadruple curly brace will be printed as double curly brace, doubling is escaping from .format 22 | doc['mfiles'] = { "mustachefile": template['path'] } 23 | doc['text'] = "It is {{who}}." 24 | template['content'] = """place: 'world' 25 | who: 'me' 26 | """ 27 | 28 | # Write contents to files 29 | with open(doc['path'].strpath, "a") as myfile: 30 | myfile.write(doc['metadata'].format(**doc['mfiles'])) 31 | myfile.write(doc['text']) 32 | template['path'].write(template['content']) 33 | 34 | # Run pandoc 35 | output = subprocess.check_output(["pandoc", doc['path'].strpath, "--filter", "pandoc-mustache", "--to=asciidoc", "--standalone"], universal_newlines=True) 36 | 37 | # Test output 38 | print (output) 39 | assert output == """[abstract] 40 | == Abstract 41 | Hello world. 42 | 43 | It is me. 44 | """ 45 | -------------------------------------------------------------------------------- /tests/test_mustache_variable_miss.py: -------------------------------------------------------------------------------- 1 | """ 2 | Test that error is thrown if the document contains a mustache {{variable}} that does not exist 3 | in the template. 4 | """ 5 | import os, subprocess, sys, platform 6 | 7 | def test_yaml_mapping(tmpdir): 8 | 9 | # Define empty dictionaries 10 | doc = {} 11 | template = {} 12 | 13 | # Prepare file names 14 | doc['path'] = tmpdir.join("document.md") 15 | template['path'] = tmpdir.join("template.yaml") 16 | 17 | # Prepare file contents 18 | doc['metadata'] = '''--- 19 | mustache: {mustachefile} 20 | --- 21 | ''' 22 | doc['mfiles'] = { "mustachefile": template['path'] } 23 | doc['text'] = 'Hello {{place}}' 24 | template['content'] = "planet: 'world'" 25 | 26 | # Write contents to files 27 | with open(doc['path'].strpath, "a") as myfile: 28 | myfile.write(doc['metadata'].format(**doc['mfiles'])) 29 | myfile.write(doc['text']) 30 | template['path'].write(template['content']) 31 | 32 | # Run pandoc, assert error 33 | try: 34 | output = subprocess.check_output(["pandoc", doc['path'].strpath, "--filter", "pandoc-mustache"], universal_newlines=True, stderr=subprocess.STDOUT) 35 | assert 0 # expecting an exception when calling pandoc 36 | except subprocess.CalledProcessError as e: 37 | assert e.returncode == 83 38 | if platform.python_implementation()=='CPython': 39 | assert "pystache.context.KeyNotFoundError" in e.output 40 | if (sys.version_info > (3, 0)): # Python 3 41 | assert "Key 'place' not found" in e.output 42 | else: 43 | assert "Key u'place' not found" in e.output 44 | -------------------------------------------------------------------------------- /tests/test_no_mustache_metadata.py: -------------------------------------------------------------------------------- 1 | """ 2 | Test that if no mustache file is specified in pandoc YAML metadata, the doc is returned unaltered. 3 | If there are mustache variables in the document, no error gets thrown: they appear in the output. 4 | """ 5 | import os, subprocess 6 | 7 | def test_no_mustache_file(tmpdir): 8 | 9 | # Define empty dictionaries 10 | doc = {} 11 | 12 | # Prepare file names 13 | doc['path'] = tmpdir.join("document.md") 14 | 15 | # Prepare file contents 16 | doc['metadata'] = '' 17 | doc['text'] = 'Hello {{place}}' 18 | 19 | # Write contents to files 20 | with open(doc['path'].strpath, "a") as myfile: 21 | myfile.write(doc['metadata']) 22 | myfile.write(doc['text']) 23 | 24 | # Run pandoc 25 | output = subprocess.check_output(["pandoc", doc['path'].strpath, "--filter", "pandoc-mustache"], universal_newlines=True) 26 | 27 | # Test output 28 | assert output == "

Hello {{place}}

\n" 29 | 30 | def test_blank_mustache_mapping(tmpdir): 31 | 32 | # Define empty dictionaries 33 | doc = {} 34 | 35 | # Prepare file names 36 | doc['path'] = tmpdir.join("document.md") 37 | 38 | # Prepare file contents 39 | doc['metadata'] = '''--- 40 | mustache: 41 | --- 42 | ''' 43 | doc['text'] = 'Hello {{place}}' 44 | 45 | # Write contents to files 46 | with open(doc['path'].strpath, "a") as myfile: 47 | myfile.write(doc['metadata']) 48 | myfile.write(doc['text']) 49 | 50 | # Run pandoc 51 | output = subprocess.check_output(["pandoc", doc['path'].strpath, "--filter", "pandoc-mustache"], universal_newlines=True) 52 | 53 | # Test output 54 | assert output == "

Hello {{place}}

\n" 55 | -------------------------------------------------------------------------------- /tests/test_path_w_space.py: -------------------------------------------------------------------------------- 1 | """ 2 | Test that mustache template can be in folder that contains spaces. 3 | """ 4 | import os, subprocess 5 | 6 | def test_yaml_header_styles(tmpdir): 7 | 8 | # Prepare file names 9 | doc = tmpdir.join("document.md") 10 | template = tmpdir.mkdir("template folder").join("template.yaml") 11 | 12 | # Prepare file contents 13 | doc_metadata = '''--- 14 | mustache: {mustachefile} 15 | --- 16 | ''' 17 | doc_text = 'Hello {{place}}' 18 | template_content = "place: 'world'" 19 | mfiles = { "mustachefile": template } 20 | 21 | # Write contents to files 22 | with open(doc.strpath, "a") as myfile: 23 | myfile.write(doc_metadata.format(**mfiles)) 24 | myfile.write(doc_text) 25 | template.write(template_content) 26 | 27 | # Run pandoc 28 | output = subprocess.check_output(["pandoc", doc.strpath, "--filter", "pandoc-mustache"], universal_newlines=True) 29 | 30 | # Test output 31 | assert output == "

Hello world

\n" 32 | --------------------------------------------------------------------------------