├── .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 | [](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 | [](https://pypi.python.org/pypi/pandoc-mustache/)
4 | [](https://pypi.python.org/pypi/pandoc-mustache/)
5 | [](https://pypi.python.org/pypi/pandoc-mustache/)
6 | [](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 | [](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 |
--------------------------------------------------------------------------------