├── .github
├── FUNDING.yml
├── PULL_REQUEST_TEMPLATE.md
└── workflows
│ ├── moban-update.yml
│ ├── pythonpackage.yml
│ └── pythonpublish.yml
├── .gitignore
├── .isort.cfg
├── .moban.cd
├── changelog.yml
└── moban.yml
├── .moban.d
├── custom_conf.py.jj2
├── moban_gitignore.jj2
├── moban_readme.jj2
├── moban_setup.py.jj2
└── moban_travis.yml.jj2
├── CHANGELOG.rst
├── CONTRIBUTING.md
├── CONTRIBUTORS.rst
├── LICENSE
├── MANIFEST.in
├── Makefile
├── README.rst
├── docs
├── README.rst
├── conf.py
├── deprecated-level-10-moban-dependency-as-git-repo
│ ├── .moban.yml
│ ├── README.rst
│ ├── config.yml
│ └── local
│ │ ├── demo.txt.jj2
│ │ └── mytravis.yml
├── deprecated-level-9-moban-dependency-as-pypi-package
│ ├── .moban.yml
│ ├── README.rst
│ ├── config.yml
│ └── local
│ │ └── demo.txt.jj2
├── engine.uml
├── extension.rst
├── images
│ ├── engine.svg
│ ├── moban-in-intro.gif
│ └── moban-in-pyexcel-demo.gif
├── index.rst
├── level-1-jinja2-cli
│ ├── README.rst
│ ├── a.template
│ └── data.yml
├── level-10-moban-dependency-as-git-repo
│ ├── .moban.yml
│ ├── README.rst
│ ├── config.yml
│ └── local
│ │ ├── demo.txt.jj2
│ │ └── mytravis.yml
├── level-11-use-handlebars
│ ├── .moban.cd
│ │ └── data.base.yaml
│ ├── .moban.td
│ │ └── base.hbs
│ ├── .moban.yml
│ ├── README.rst
│ ├── a.template.handlebars
│ └── data.yml
├── level-12-use-template-engine-extensions
│ ├── .moban.yml
│ ├── README.rst
│ ├── a.template
│ ├── b.template
│ └── data.yml
├── level-13-any-data-override-any-data
│ ├── .moban.cd
│ │ ├── parent.json
│ │ └── parent.yaml
│ ├── .moban.td
│ │ └── base.jj2
│ ├── README.rst
│ ├── a.template
│ ├── child.json
│ └── child.yaml
├── level-14-custom-data-loader
│ ├── .moban.cd
│ │ ├── parent.custom
│ │ └── parent.json
│ ├── .moban.td
│ │ └── base.jj2
│ ├── .moban.yml
│ ├── README.rst
│ ├── a.template
│ ├── child.custom
│ ├── custom-data-loader
│ │ ├── __init__.py
│ │ └── custom.py
│ └── override_custom.yaml
├── level-15-copy-templates-as-target
│ ├── .moban.yml
│ ├── README.rst
│ ├── misc-1-copying
│ │ └── can-create-folder
│ │ │ └── if-not-exists.txt
│ └── template-sources
│ │ ├── as_long_as_this_one_has.copy
│ │ ├── dir-for-copying
│ │ ├── afile.txt
│ │ └── sub_directory_is_not_copied
│ │ │ └── becuase_star_star_is_needed.txt
│ │ ├── dir-for-recusive-copying
│ │ ├── fileb.txt
│ │ └── sub_directory_is_copied
│ │ │ └── because_star_star_is_specified.txt
│ │ ├── file-in-template-sources-folder.txt
│ │ ├── file_extension_will_trigger.copy
│ │ └── when_source_have.same_file_extension
├── level-16-group-targets-using-template-type
│ ├── .moban.yml
│ ├── README.rst
│ ├── misc-1-copying
│ │ └── can-create-folder
│ │ │ └── if-not-exists.txt
│ └── template-sources
│ │ ├── dir-for-copying
│ │ ├── afile.txt
│ │ └── sub_directory_is_not_copied
│ │ │ └── becuase_star_star_is_needed.txt
│ │ ├── dir-for-recusive-copying
│ │ ├── fileb.txt
│ │ └── sub_directory_is_copied
│ │ │ └── because_star_star_is_specified.txt
│ │ └── file-in-template-sources-folder.txt
├── level-17-force-template-type-from-moban-file
│ ├── .moban.yml
│ ├── README.rst
│ ├── misc-1-copying
│ │ └── can-create-folder
│ │ │ ├── if-not-exists
│ │ │ └── if-not-exists.txt
│ └── template-sources
│ │ ├── dir-for-copying
│ │ ├── afile.txt
│ │ └── sub_directory_is_not_copied
│ │ │ └── becuase_star_star_is_needed.txt
│ │ ├── dir-for-recusive-copying
│ │ ├── fileb.txt
│ │ └── sub_directory_is_copied
│ │ │ └── because_star_star_is_specified.txt
│ │ └── file-in-template-sources-folder.txt
├── level-18-user-defined-template-types
│ ├── .moban.cd
│ │ └── data.base.yaml
│ ├── .moban.yml
│ ├── README.rst
│ ├── a.template.file_type_of_my_choice
│ ├── a.template.jj2
│ └── data.yml
├── level-19-moban-a-sub-group-in-targets
│ ├── .moban.yml
│ ├── README.rst
│ ├── a.template.jj2
│ ├── misc-1-copying
│ │ └── can-create-folder
│ │ │ └── if-not-exists.txt
│ └── template-sources
│ │ ├── dir-for-copying
│ │ ├── afile.txt
│ │ └── sub_directory_is_not_copied
│ │ │ └── becuase_star_star_is_needed.txt
│ │ ├── dir-for-recusive-copying
│ │ ├── fileb.txt
│ │ └── sub_directory_is_copied
│ │ │ └── because_star_star_is_specified.txt
│ │ └── file-in-template-sources-folder.txt
├── level-2-template-inheritance
│ ├── .moban.td
│ │ └── base.jj2
│ ├── README.rst
│ ├── a.template
│ └── data.yml
├── level-20-templates-configs-in-zip-or-tar
│ ├── .moban.yml
│ ├── README.rst
│ ├── cool-templates
│ │ ├── base.jj2
│ │ └── cool.template.jj2
│ ├── custom-config.tar
│ ├── custom-templates
│ │ ├── subfolder
│ │ │ └── template.in.zip.jj2
│ │ └── template.in.zip.jj2
│ ├── data.yml
│ ├── data2.yml
│ └── templates.zip
├── level-21-copy-templates-into-an-alien-file-system
│ ├── .moban.yml
│ ├── README.rst
│ └── template-sources.zip
├── level-22-intermediate-targets
│ ├── .moban.yaml
│ ├── README.rst
│ ├── data.yml
│ └── original.jj2
├── level-23-inherit-organisational-moban-file
│ ├── .moban.yml
│ ├── README.rst
│ ├── data.yaml
│ ├── parent.moban.yaml
│ ├── template_a.jj2
│ └── template_b.jj2
├── level-24-files-over-http
│ ├── .moban.yml
│ ├── README.rst
│ ├── config.yml
│ └── local
│ │ └── demo.txt.jj2
├── level-25-delete-intermediate-targets
│ ├── .moban.yaml
│ ├── README.rst
│ ├── data.yml
│ └── original.jj2
├── level-26-strip-rendered-content
│ ├── .moban.yaml
│ ├── README.rst
│ ├── content_with_lots_of_white_spaces.jj2
│ └── data.yml
├── level-3-data-override
│ ├── .moban.cd
│ │ └── data.base.yaml
│ ├── .moban.td
│ │ └── base.jj2
│ ├── README.rst
│ ├── a.template
│ └── data.yml
├── level-4-single-command
│ ├── .moban.cd
│ │ └── data.base.yaml
│ ├── .moban.td
│ │ └── base.jj2
│ ├── .moban.yml
│ ├── README.rst
│ ├── a.template.jj2
│ └── data.yml
├── level-5-custom-configuration
│ ├── .moban.yml
│ ├── README.rst
│ ├── cool-templates
│ │ ├── base.jj2
│ │ └── cool.template
│ ├── custom-config
│ │ └── data.base.yaml
│ ├── custom-templates
│ │ └── a.template.jj2
│ └── data.yml
├── level-6-complex-configuration
│ ├── .moban.yml
│ ├── README.rst
│ ├── cool-templates
│ │ ├── base.jj2
│ │ └── cool.template.jj2
│ ├── custom-config
│ │ └── data.base.yaml
│ ├── custom-templates
│ │ └── a.template.jj2
│ ├── data.yml
│ └── data2.yml
├── level-7-use-custom-jinja2-filter-test-n-global
│ ├── .moban.yml
│ ├── README.rst
│ ├── custom-jj2-plugin
│ │ ├── __init__.py
│ │ ├── filter.py
│ │ ├── global.py
│ │ └── test.py
│ ├── data.yml
│ ├── global.output
│ └── my-templates
│ │ ├── filter.jj2
│ │ ├── global.jj2
│ │ └── test.jj2
├── level-8-pass-a-folder-full-of-templates
│ ├── .moban.yml
│ ├── README.rst
│ ├── config
│ │ └── level8.yml
│ └── template-folder
│ │ ├── show_sub_folder_on_windows.txt
│ │ └── templates
│ │ └── my.jj2
├── level-9-moban-dependency-as-pypi-package
│ ├── .moban.yml
│ ├── README.rst
│ ├── config.yml
│ └── local
│ │ └── demo.txt.jj2
├── migration-notes.rst
├── misc-1-copying-templates
│ ├── .moban.yml
│ ├── README.rst
│ └── template-sources
│ │ ├── dir-for-copying
│ │ ├── afile.txt
│ │ └── sub_directory_is_not_copied
│ │ │ └── becuase_star_star_is_needed.txt
│ │ ├── dir-for-recusive-copying
│ │ ├── fileb.txt
│ │ └── sub_directory_is_copied
│ │ │ └── because_star_star_is_specified.txt
│ │ └── file-in-template-sources-folder.txt
└── trouble-shooting-guide.rst
├── format.sh
├── lint.sh
├── min_requirements.txt
├── moban
├── __init__.py
├── __main__.py
├── _version.py
├── constants.py
├── core
│ ├── __init__.py
│ ├── content_processor.py
│ ├── context.py
│ ├── data_loader.py
│ ├── definitions.py
│ ├── hashstore.py
│ ├── moban_factory.py
│ ├── mobanfile
│ │ ├── __init__.py
│ │ ├── store.py
│ │ ├── targets.py
│ │ └── templates.py
│ ├── plugins.py
│ ├── strategy.py
│ └── utils.py
├── deprecated
│ ├── __init__.py
│ ├── library.py
│ └── repo.py
├── exceptions.py
├── externals
│ ├── __init__.py
│ ├── buffered_writer.py
│ ├── file_system.py
│ └── reporter.py
├── main.py
├── plugins
│ ├── __init__.py
│ ├── copy.py
│ ├── delete.py
│ ├── jinja2
│ │ ├── __init__.py
│ │ ├── engine.py
│ │ ├── extensions.py
│ │ └── filters
│ │ │ ├── __init__.py
│ │ │ ├── repr.py
│ │ │ └── text.py
│ ├── json_loader.py
│ ├── strip.py
│ └── yaml_loader.py
└── program_options.py
├── mobanfile
├── requirements.txt
├── rnd_requirements.txt
├── setup.cfg
├── setup.py
├── test.bat
├── test.sh
└── tests
├── __init__.py
├── core
├── __init__.py
├── test_context.py
├── test_engine.py
└── test_moban_factory.py
├── data_loaders
├── __init__.py
├── test_json_loader.py
├── test_merge_dict.py
├── test_overrides.py
└── test_yaml_loader.py
├── deprecated
├── test_handle_requires.py
└── test_repo.py
├── fixtures
├── .moban-2.yml
├── .moban-version-1.0.yml
├── .moban-version-1234.yml
├── .moban.yml
├── a.handlebars
├── a.jj2
├── child.json
├── child.yaml
├── coala_color.svg
├── config
│ └── base.yaml
├── copier-directory
│ ├── copier-sample-dir
│ │ └── file1
│ └── level1-file1
├── copier-test01.csv
├── copier-test02.csv
├── copier-test03.csv
├── copier-test04.csv
├── copier-test05.csv
├── duplicated.moban.yml
├── environ_vars_as_data
│ └── test.template
├── file_system
│ ├── template-sources.tar
│ └── template-sources.zip
├── globals
│ ├── basic.template
│ ├── basic.yml
│ ├── nested.template
│ ├── variables.template
│ └── variables.yml
├── issue_126
│ ├── config
│ │ ├── A.yaml
│ │ ├── B.yaml
│ │ ├── multi-key-A.yaml
│ │ ├── multi-key-B.yaml
│ │ ├── multi-key-config.yaml
│ │ ├── nested-A.yaml
│ │ └── nested-B.yaml
│ ├── config_BA.yaml
│ ├── multi-key-config-override.yaml
│ ├── multi-key-config.yaml
│ ├── raspberry.yaml
│ └── the_config.yaml
├── jinja_tests
│ ├── file_tests.template
│ └── file_tests.yml
├── mobanengine
│ └── sample_template_type.yml
├── mobanfile
│ ├── a.template.jj2
│ └── filterme.handlebars
├── non-unicode.char
├── orphan.yaml
├── override_fs_url.yaml
├── simple.yaml
├── template
└── template-tests
│ └── a.jj2
├── integration_tests
├── __init__.py
└── test_command_line_options.py
├── jinja2
├── __init__.py
├── test_engine.py
├── test_extensions.py
├── test_repr.py
└── test_text.py
├── mobanfile
├── __init__.py
├── test_mobanfile.py
├── test_targets.py
└── test_templates.py
├── regression_tests
├── level-21-b-copy-templates-into-a-tar
│ ├── .moban.yml
│ ├── README.rst
│ └── template-sources.tar
├── level-21-c-copy-templates-from-a-tar
│ ├── .moban.yml
│ ├── README.rst
│ └── template-sources.tar
├── level-7-b-template-engine-plugin
│ ├── README.rst
│ ├── custom-plugin
│ │ └── deduplicate.py
│ └── duplicated_content.txt
├── level-7-plugin-dir-cli
│ ├── README.rst
│ ├── custom-jj2-plugin
│ │ ├── __init__.py
│ │ ├── filter.py
│ │ ├── global.py
│ │ └── test.py
│ ├── data.yml
│ └── my-templates
│ │ ├── filter.jj2
│ │ ├── global.jj2
│ │ └── test.jj2
├── regr-01-copy-binary-file
│ ├── .moban.yml
│ └── copy-source
│ │ └── image.png
└── regr-02-templating-failure-results-in-copy-action
│ ├── .moban.yml
│ └── copy-source
│ └── image.png
├── requirements.txt
├── test_buffered_writer.py
├── test_copy_engine.py
├── test_definitions.py
├── test_docs.py
├── test_file_system.py
├── test_hash_store.py
├── test_main.py
├── test_regression.py
├── test_reporter.py
├── test_store.py
└── utils.py
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | # These are supported funding model platforms
2 |
3 | github: chfw
4 | patreon: chfw
5 |
--------------------------------------------------------------------------------
/.github/PULL_REQUEST_TEMPLATE.md:
--------------------------------------------------------------------------------
1 | Before raising the PR, here is a check list:
2 |
3 | - [ ] have you written unit tests for your code changes?
4 | - [ ] have you updated the change log?
5 | - [ ] can someone else understand your changes without your explanation?
6 | - [ ] are you proud of your code changes?
7 |
8 |
--------------------------------------------------------------------------------
/.github/workflows/moban-update.yml:
--------------------------------------------------------------------------------
1 | on: [push]
2 |
3 | jobs:
4 | run_moban:
5 | runs-on: ubuntu-latest
6 | name: synchronize templates via moban
7 | steps:
8 | - uses: actions/checkout@v2
9 | with:
10 | ref: ${{ github.head_ref }}
11 | token: ${{ secrets.PAT }}
12 | - name: Set up Python
13 | uses: actions/setup-python@v5
14 | with:
15 | python-version: '3.11'
16 | - name: check changes
17 | run: |
18 | pip install markupsafe==2.0.1
19 | pip install ruamel.yaml moban gitfs2 pypifs moban-jinja2-github moban-ansible
20 | make update
21 | git status
22 | git diff --exit-code
23 | - name: Auto-commit
24 | if: failure()
25 | uses: stefanzweifel/git-auto-commit-action@v4
26 | with:
27 | commit_message: >-
28 | This is an auto-commit, updating project meta data,
29 | such as changelog.rst, contributors.rst
30 |
31 |
--------------------------------------------------------------------------------
/.github/workflows/pythonpackage.yml:
--------------------------------------------------------------------------------
1 | name: Unit tests
2 |
3 | on: [push]
4 |
5 | jobs:
6 | build:
7 | name: ${{ matrix.os }} / ${{ matrix.python_version }}
8 | runs-on: ${{matrix.os}}-latest
9 | strategy:
10 | matrix:
11 | os: [Ubuntu]
12 | python-version: ["3.9.16"]
13 |
14 | steps:
15 | - uses: actions/checkout@v2
16 | - uses: actions/setup-python@v5
17 | with:
18 | python-version: ${{ matrix.python-version }}
19 | - name: Install dependencies
20 | run: |
21 | pip install markupsafe==2.0.1
22 | python -m pip install --upgrade pip setuptools
23 | pip install -r requirements.txt
24 | - name: Lint with flake8
25 | run: |
26 | make install_test format git-diff-check
27 | - name: Test with pytest
28 | run: |
29 | pip install -r tests/requirements.txt
30 | make
31 |
--------------------------------------------------------------------------------
/.github/workflows/pythonpublish.yml:
--------------------------------------------------------------------------------
1 | name: Upload Python Package
2 |
3 | on:
4 | release:
5 | types: [created]
6 |
7 | jobs:
8 | pypi-publish:
9 | name: upload release to PyPI
10 | runs-on: ubuntu-latest
11 | # Specifying a GitHub environment is optional, but strongly encouraged
12 | environment: pypi
13 | permissions:
14 | # IMPORTANT: this permission is mandatory for trusted publishing
15 | id-token: write
16 | steps:
17 | # retrieve your distributions here
18 | - uses: actions/checkout@v1
19 | - name: Set up Python
20 | uses: actions/setup-python@v1
21 | with:
22 | python-version: '3.x'
23 | - name: Install dependencies
24 | run: |
25 | python -m pip install --upgrade pip
26 | pip install setuptools wheel
27 | - name: Build
28 | run: |
29 | python setup.py sdist bdist_wheel
30 | - name: Publish package distributions to PyPI
31 | uses: pypa/gh-action-pypi-publish@release/v1
32 |
--------------------------------------------------------------------------------
/.isort.cfg:
--------------------------------------------------------------------------------
1 | [settings]
2 | line_length=79
3 | # Ignore generated files
4 | skip=setup.py, moban/__init__.py
5 | known_third_party=fs, lml, crayons, jinja2, ruamel.yaml, pytest, jinja2_fsloader
6 | indent=' '
7 | multi_line_output=3
8 | length_sort=1
9 | include_trailing_comma=true
10 | default_section=FIRSTPARTY
11 | no_lines_before=LOCALFOLDER
12 | sections=FUTURE,STDLIB,THIRDPARTY,FIRSTPARTY,LOCALFOLDER
13 |
--------------------------------------------------------------------------------
/.moban.cd/moban.yml:
--------------------------------------------------------------------------------
1 | name: moban
2 | project: moban
3 | organisation: moremoban
4 | author: C. W.
5 | contact: wangc_2011@hotmail.com
6 | license: MIT
7 | version: 0.8.2
8 | current_version: 0.8.3
9 | release: 0.8.3
10 | branch: master
11 | master: index
12 | command_line_interface: "moban"
13 | entry_point: "moban.main:main"
14 | company: Onni Software Ltd.
15 | copyright_year: 2016-2025
16 | copyright: 2016-2025 Onni Software Ltd. and its contributors
17 | keywords:
18 | - jinja2
19 | - moban
20 | dependencies:
21 | - ruamel.yaml>=0.15.5;python_version == '3.6'
22 | - ruamel.yaml>=0.15.42;python_version == '3.7'
23 | - ruamel.yaml>=0.15.98;python_version == '3.8'
24 | - ruamel.yaml>=0.15.98;python_version == '3.9'
25 | - jinja2>=2.7.1
26 | - lml>=0.0.9
27 | - appdirs>=1.4.3
28 | - crayons>= 0.1.0
29 | - fs>=2.4.11
30 | - jinja2-fsloader>=0.2.0
31 | - moban-jinja2-github
32 | description: General purpose static text generator
33 | scm_host: github.com
34 | lint_command: make install_test format git-diff-check lint
35 | moban_command: make update git-diff-check
36 | setup_use_markers: true
37 | setup_use_markers_fix: true
38 | python_requires: ">=3.6"
39 | min_python_version: "3.6"
40 | excluded_github_users:
41 | - chfw
42 |
--------------------------------------------------------------------------------
/.moban.d/custom_conf.py.jj2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/moremoban/moban/11306525140a16f8b3231e41c73c48b3cfa4b333/.moban.d/custom_conf.py.jj2
--------------------------------------------------------------------------------
/.moban.d/moban_gitignore.jj2:
--------------------------------------------------------------------------------
1 | {% extends "gitignore.jj2" %}
2 |
3 | {% block extra %}
4 | docs/deprecated-level-9-moban-dependency-as-pypi-package/mytravis.yml
5 | docs/deprecated-level-10-moban-dependency-as-git-repo/mytravis.yml
6 | docs/level-1-jinja2-cli/testout
7 | docs/level-10-moban-dependency-as-git-repo/mytravis.yml
8 | docs/level-18-user-defined-template-types/b.output
9 | docs/level-9-moban-dependency-as-pypi-package/mytravis.yml
10 | docs/misc-1-copying-templates/misc-1-copying/
11 | docs/misc-1-copying-templates/test-dir/
12 | docs/misc-1-copying-templates/test-recursive-dir/
13 | docs/level-12-use-template-engine-extensions/a.output
14 | docs/level-13-any-data-override-any-data/a.output
15 | docs/level-13-any-data-override-any-data/b.output
16 | docs/level-15-copy-templates-as-target/target_in_short_form
17 | docs/level-15-copy-templates-as-target/target_without_template_type
18 | docs/level-15-copy-templates-as-target/test-dir/
19 | docs/level-15-copy-templates-as-target/test-recursive-dir/
20 | docs/level-16-group-targets-using-template-type/test-dir/
21 | docs/level-16-group-targets-using-template-type/test-recursive-dir/
22 | docs/level-17-force-template-type-from-moban-file/test-dir/
23 | docs/level-17-force-template-type-from-moban-file/test-recursive-dir/
24 | docs/level-11-use-handlebars/b.output
25 | docs/level-14-custom-data-loader/b.output
26 | docs/level-19-moban-a-sub-group-in-targets/test-dir/
27 | docs/level-19-moban-a-sub-group-in-targets/test-recursive-dir/
28 | docs/level-6-complex-configuration/a.output
29 | docs/level-7-use-custom-jinja2-filter-test-n-global/filter.output
30 | docs/level-8-pass-a-folder-full-of-templates/templated-folder
31 | {% endblock %}
--------------------------------------------------------------------------------
/.moban.d/moban_setup.py.jj2:
--------------------------------------------------------------------------------
1 | {%extends 'setup.py.jj2'%}
2 |
3 | {%block platform_block%}
4 | {%endblock%}
5 |
6 | {%block morefiles%} "CONTRIBUTORS.rst",{%endblock%}
7 |
--------------------------------------------------------------------------------
/.moban.d/moban_travis.yml.jj2:
--------------------------------------------------------------------------------
1 | {% extends 'travis.yml.jj2' %}
2 |
3 | {%block extra_matrix %}
4 | env:
5 | - MINREQ=1
6 | {%endblock%}
7 |
8 | {%block custom_python_versions%}
9 | python:
10 | - 3.7
11 | - 3.6
12 | - 3.8
13 | - 3.9-dev
14 | {%endblock%}
15 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | Fork [moban](https://github.com/moremoban/moban) by clicking "Fork".
2 |
3 | ## Using virtualenv and Remote Configuration
4 | 1. `pip3 install virtualenv`
5 | 2. Move to the location where you want to setup moban and type `mkdir moban` in the terminal
6 | 3. `cd moban`
7 | 3. Type `virtualenv venv`
8 | 4. `source venv/bin/activate`
9 | 5. `git clone https://github.com/YOUR_USERNAME/moban.git`
10 | 6. `cd moban`
11 | 7. `pip install -r requirements.txt`
12 | 8. `git remote add upstream https://github.com/moremoban/moban.git`
13 | 9. Type ` git remote -v ` and you should see
14 | ```
15 | origin https://github.com/YOUR_USERNAME/moban.git (fetch)
16 | origin https://github.com/YOUR_USERNAME/moban.git (push)
17 | upstream https://github.com/moremoban/moban.git (fetch)
18 | upstream https://github.com/moremoban/moban.git (push)
19 | ```
20 |
21 | When you want to update your local copy type
`git fetch upstream`
`git merge upstream/master`
`git push`
22 |
23 | ## Run unit tests
24 |
25 | 1. please install unit test requirements:
26 |
27 | ```
28 | $ pip install tests/requirements.txt
29 | ```
30 |
31 | 2. Then run
32 |
33 | ```
34 | $ make
35 | ```
36 |
37 | ## In order to format the code automatically
38 |
39 | you will need python 3.6 to run "make format"
40 |
41 | When you enable travis-ci on your own account, you shall see travis-ci running a build on each of your pushed commit to your own fork.
42 |
43 | ## Steps for creating a Pull Request
44 | 1. Checkout to the master branch `git checkout master`
45 | 3. Start a new branch with a suitable name `git checkout -b branch_name`
46 | 4. Develop a new feature or solve an existing issue
47 | 5. Add the changed files `git add file_name`
48 | 6. Commit with a suitable message `git commit -m " Changes made "`
49 | 7. Push `git push origin branch_name`
50 | 8. Go to the Github Repository and create a pull request to the dev branch
51 |
52 | ## Commit messages
53 |
54 | We use git emojis for commit messages. Please read [the guide](https://github.com/slashsBin/styleguide-git-commit-message).
55 |
--------------------------------------------------------------------------------
/CONTRIBUTORS.rst:
--------------------------------------------------------------------------------
1 |
2 |
3 | 10 contributors
4 | ================================================================================
5 |
6 | In alphabetical order:
7 |
8 | * `Andrew Scheller `_
9 | * `Ayan Banerjee `_
10 | * `CLiu13 `_
11 | * `dependabot[bot] `_
12 | * `John Vandenberg `_
13 | * `Joshua Chung `_
14 | * `Kacper Potyrała `_
15 | * `pgajdos `_
16 | * `PRAJWAL M `_
17 | * `salotz `_
18 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2016-2017 Onni Software Ltd. and its contributors.
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/MANIFEST.in:
--------------------------------------------------------------------------------
1 | include README.rst
2 | include LICENSE
3 | include CHANGELOG.rst
4 | include CONTRIBUTORS.rst
5 | recursive-include tests *
6 | recursive-include docs *
7 |
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 | all: test
2 |
3 | install:
4 | pip install .
5 |
6 | install_test:
7 | pip install -r tests/requirements.txt
8 |
9 | update:
10 | moban -m mobanfile
11 |
12 | git-diff-check:
13 | git diff --exit-code
14 |
15 | test:
16 | bash test.sh
17 |
18 | lint:
19 | bash lint.sh
20 | yamllint -d "{extends: default, rules: {line-length: {max: 120}}}" .moban.cd/changelog.yml
21 | yamllint -d "{extends: default, ignore: .moban.cd/changelog.yml}" .
22 |
23 | format:
24 | bash format.sh
25 |
26 | uml:
27 | plantuml -tsvg -o ./images/ docs/*.uml
28 |
29 |
30 | doc: uml
31 | sphinx-build -b html docs build
32 |
--------------------------------------------------------------------------------
/docs/README.rst:
--------------------------------------------------------------------------------
1 | Tutorial
2 | ================================================================================
3 |
4 | This section covers the use cases for moban. Please check them out individually.
5 |
6 | #. `Jinja2 command line`_
7 | #. `Template inheritance`_
8 | #. `Data override`_
9 | #. `Single command`_
10 | #. `Custom configuration`_
11 | #. `Complex configuration`_
12 | #. `Use custom jinja2 filter test n global`_
13 | #. `Pass a folder full of templates`_
14 | #. `Use pypi package as a moban dependency`_
15 | #. `Use git repository as a moban dependency`_
16 | #. `Use handlebars template with moban`_
17 | #. `Use template engine extensions`_
18 | #. `Any data overrides any data`_
19 | #. `Custom data loader`_
20 | #. `Copy templates as target`_
21 | #. `Group targets by template type`_
22 | #. `Force template type from moban file`_
23 | #. `User defined template types`_
24 | #. `Select a group target to run`_
25 | #. `Template files in a zip or tar`_
26 | #. `Template copying from a zip to a zip`_
27 | #. `Intermediate targets`_
28 | #. `Mobanfile inheritance`_
29 | #. `Files over http(s)`_
30 | #. `Remove intermediate targets`_
31 | #. `Striping the rendered content`_
32 |
33 | .. _Jinja2 command line: level-1-jinja2-cli
34 | .. _Template inheritance: level-2-template-inheritance
35 | .. _Data override: level-3-data-override
36 | .. _Single command: level-4-single-command
37 | .. _Custom configuration: level-5-custom-configuration
38 | .. _Complex configuration: level-6-complex-configuration
39 | .. _Use custom jinja2 filter test n global: level-7-use-custom-jinja2-filter-test-n-global
40 | .. _Pass a folder full of templates: level-8-pass-a-folder-full-of-templates
41 | .. _Use pypi package as a moban dependency: level-9-moban-dependency-as-pypi-package
42 | .. _Use git repository as a moban dependency: level-10-moban-dependency-as-git-repo
43 | .. _Use handlebars template with moban: level-11-use-handlebars
44 | .. _Use template engine extensions: level-12-use-template-engine-extensions
45 | .. _Any data overrides any data: level-13-any-data-override-any-data
46 | .. _Custom data loader: level-14-custom-data-loader
47 | .. _Copy templates as target: level-15-copy-templates-as-target
48 | .. _Group targets by template type: level-16-group-targets-using-template-type
49 | .. _Force template type from moban file: level-17-force-template-type-from-moban-file
50 | .. _User defined template types: level-18-user-defined-template-types
51 | .. _Select a group target to run: level-19-moban-a-sub-group-in-targets
52 | .. _Template files in a zip or tar: level-20-templates-configs-in-zip-or-tar
53 | .. _Template copying from a zip to a zip: level-21-copy-templates-into-an-alien-file-system
54 | .. _Intermediate targets: level-22-intermediate-targets
55 | .. _Mobanfile inheritance: level-23-inherit-organisational-moban-file
56 | .. _Files over http(s): level-24-files-over-http
57 | .. _Remove intermediate targets: level-25-delete-intermediate-targets
58 | .. _Striping the rendered content: level-26-strip-rendered-content
59 |
--------------------------------------------------------------------------------
/docs/conf.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | DESCRIPTION = (
3 | 'General purpose static text generator' +
4 | ''
5 | )
6 | # Configuration file for the Sphinx documentation builder.
7 | #
8 | # This file only contains a selection of the most common options. For a full
9 | # list see the documentation:
10 | # https://www.sphinx-doc.org/en/master/usage/configuration.html
11 |
12 | # -- Path setup --------------------------------------------------------------
13 |
14 | # If extensions (or modules to document with autodoc) are in another directory,
15 | # add these directories to sys.path here. If the directory is relative to the
16 | # documentation root, use os.path.abspath to make it absolute, like shown here.
17 | #
18 | # import os
19 | # import sys
20 | # sys.path.insert(0, os.path.abspath('.'))
21 |
22 | # -- Project information -----------------------------------------------------
23 |
24 | project = 'moban'
25 | copyright = '2016-2025 Onni Software Ltd.'
26 | author = 'C. W.'
27 | # The short X.Y version
28 | version = '0.8.2'
29 | # The full version, including alpha/beta/rc tags
30 | release = '0.8.3'
31 |
32 | # -- General configuration ---------------------------------------------------
33 |
34 | # Add any Sphinx extension module names here, as strings. They can be
35 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
36 | # ones.
37 | extensions = [ 'sphinx.ext.autodoc', 'sphinx.ext.doctest', 'sphinx.ext.intersphinx', 'sphinx.ext.viewcode',]
38 |
39 | # Add any paths that contain templates here, relative to this directory.
40 | templates_path = ['_templates']
41 |
42 | # The language for content autogenerated by Sphinx. Refer to documentation
43 | # for a list of supported languages.
44 | #
45 | # This is also used if you do content translation via gettext catalogs.
46 | # Usually you set "language" from the command line for these cases.
47 | language = 'en'
48 |
49 | # List of patterns, relative to source directory, that match files and
50 | # directories to ignore when looking for source files.
51 | # This pattern also affects html_static_path and html_extra_path.
52 | exclude_patterns = []
53 |
54 |
55 | # -- Options for HTML output -------------------------------------------------
56 |
57 | # The theme to use for HTML and HTML Help pages. See the documentation for
58 | # a list of builtin themes.
59 | #
60 | html_theme = 'sphinx_rtd_theme'
61 |
62 | # Add any paths that contain custom static files (such as style sheets) here,
63 | # relative to this directory. They are copied after the builtin static files,
64 | # so a file named "default.css" will overwrite the builtin "default.css".
65 | html_static_path = ['_static']
66 |
67 | # -- Extension configuration -------------------------------------------------
68 | # -- Options for intersphinx extension ---------------------------------------
69 |
70 | # Example configuration for intersphinx: refer to the Python standard library.
71 | intersphinx_mapping = {'python': ('https://docs.python.org/3',
72 | 'python-inv.txt')}
73 | # TODO: html_theme not configurable upstream
74 | html_theme = 'default'
75 |
76 | # TODO: DESCRIPTION not configurable upstream
77 | texinfo_documents = [
78 | ('index', 'moban',
79 | 'moban Documentation',
80 | 'Onni Software Ltd.', 'moban',
81 | DESCRIPTION,
82 | 'Miscellaneous'),
83 | ]
84 | intersphinx_mapping.update({
85 | })
86 | master_doc = "index"
87 |
--------------------------------------------------------------------------------
/docs/deprecated-level-10-moban-dependency-as-git-repo/.moban.yml:
--------------------------------------------------------------------------------
1 | requires:
2 | - https://github.com/moremoban/pypi-mobans
3 | configuration:
4 | template_dir:
5 | - "pypi-mobans:templates"
6 | - local
7 | configuration: config.yml
8 | configuration_dir: "pypi-mobans:config"
9 | targets:
10 | - mytravis.yml: travis.yml.jj2
11 | - test.txt: demo.txt.jj2
12 |
--------------------------------------------------------------------------------
/docs/deprecated-level-10-moban-dependency-as-git-repo/README.rst:
--------------------------------------------------------------------------------
1 | level 10: moban dependency as git repo
2 | ================================================================================
3 |
4 | Since the support to have a pypi package as dependency, the moban pro user will
5 | find it more useful to have git repo so that the changes to static content
6 | could get propagate as it happens using git push and git pull.
7 |
8 | For now, github.com, gitlab.com and bitbucket.com are supported. Pull request
9 | is welcome to add or improve this feature.
10 |
11 |
12 | Here are the sample file::
13 |
14 | requires:
15 | - https://github.com/moremoban/pypi-mobans
16 | configuration:
17 | template_dir:
18 | - "pypi-mobans:templates"
19 | - local
20 | configuration: config.yml
21 | targets:
22 | - mytravis.yml: travis.yml.jj2
23 | - test.txt: demo.txt.jj2
24 |
25 | where `requires` lead to a list of pypi packages. And when you refer to it,
26 | as in level-9 section, please use "pypi-mobans:"
27 |
28 |
29 | Alternative syntax when submodule exists
30 | --------------------------------------------------------------------------------
31 |
32 | The alternative syntax is::
33 |
34 | requires:
35 | - type: git
36 | url: https://github.com/your-git-url
37 | submodule: true
38 | branch: your_choice_or_default_branch_if_not_specified
39 | reference: your_alternative_reference_but_not_used_together_with_branch
40 | ...
41 |
42 |
--------------------------------------------------------------------------------
/docs/deprecated-level-10-moban-dependency-as-git-repo/config.yml:
--------------------------------------------------------------------------------
1 | overrides: data.yml
2 | level10: "moban dependency as git repo"
3 |
--------------------------------------------------------------------------------
/docs/deprecated-level-10-moban-dependency-as-git-repo/local/demo.txt.jj2:
--------------------------------------------------------------------------------
1 | {{name}}: {{level10}}
--------------------------------------------------------------------------------
/docs/deprecated-level-10-moban-dependency-as-git-repo/local/mytravis.yml:
--------------------------------------------------------------------------------
1 | sudo: false
2 | language: python
3 | notifications:
4 | email: false
5 | python:
6 | - pypy-5.3.1
7 | - 3.7-dev
8 | - 3.6
9 | - 3.5
10 | - 3.4
11 | - 2.7
12 | before_install:
13 | - if [[ $TRAVIS_PYTHON_VERSION == "2.6" ]]; then pip install flake8==2.6.2; fi
14 | - if [[ -f min_requirements.txt && "$MINREQ" -eq 1 ]]; then
15 | mv min_requirements.txt requirements.txt ;
16 | fi
17 | - test ! -f rnd_requirements.txt ||
18 | pip install --no-deps -r rnd_requirements.txt
19 | - test ! -f rnd_requirements.txt || pip install -r rnd_requirements.txt ;
20 | - pip install -r tests/requirements.txt
21 | script:
22 | - make test
23 | after_success:
24 | codecov
25 |
--------------------------------------------------------------------------------
/docs/deprecated-level-9-moban-dependency-as-pypi-package/.moban.yml:
--------------------------------------------------------------------------------
1 | requires:
2 | - pypi-mobans-pkg
3 | configuration:
4 | template_dir:
5 | - "pypi-mobans-pkg:templates"
6 | - local
7 | configuration: config.yml
8 | configuration_dir: "pypi-mobans-pkg:config"
9 | targets:
10 | - mytravis.yml: travis.yml.jj2
11 | - test.txt: demo.txt.jj2
12 |
--------------------------------------------------------------------------------
/docs/deprecated-level-9-moban-dependency-as-pypi-package/README.rst:
--------------------------------------------------------------------------------
1 | level 9: moban dependency as pypi package
2 | ================================================================================
3 |
4 | Why not enable template reuse? Once a template is written somewhere by somebody,
5 | as long as it is good and useful, it is always to reuse it, isn't it? DRY
6 | principle kicks in.
7 |
8 | Now with moban, it is possible to package up your mobans/templates
9 | into a pypi package and distribute it to the world of moban.
10 |
11 |
12 | Here are the sample file::
13 |
14 | requires:
15 | - pypi-mobans
16 | configuration:
17 | template_dir:
18 | - "pypi-mobans:templates"
19 | configuration: config.yml
20 | targets:
21 | - mytravis.yml: travis.yml.jj2
22 | - test.txt: demo.txt.jj2
23 |
24 | where `requires` lead to a list of pypi packages. The short syntax is::
25 |
26 | requires:
27 | - python-package-name
28 |
29 | When you refer to it in configuration section, here is the syntax::
30 |
31 | configuration:
32 | - template_dir:
33 | - "python-package-name:relative-folder-inside-the-package"
34 |
35 | Note: when you do not have relative directory, please keep semi-colon::
36 |
37 | configuration:
38 | template_dir:
39 | - "python-package-name:"
40 |
41 | Alternative syntax
42 | --------------------------------------------------------------------------------
43 |
44 | The alternative syntax is::
45 |
46 | requires:
47 | - type: pypi
48 | name: pypi-mobans
49 | ...
50 |
--------------------------------------------------------------------------------
/docs/deprecated-level-9-moban-dependency-as-pypi-package/config.yml:
--------------------------------------------------------------------------------
1 | overrides: data.yml
2 | level9: "moban dependency as pypi package"
3 |
--------------------------------------------------------------------------------
/docs/deprecated-level-9-moban-dependency-as-pypi-package/local/demo.txt.jj2:
--------------------------------------------------------------------------------
1 | {{name}}: {{level9}}
--------------------------------------------------------------------------------
/docs/engine.uml:
--------------------------------------------------------------------------------
1 | @startuml
2 |
3 | package "moban" {
4 | [engine factory]
5 | [jinja2 engine]
6 | [data loader]
7 | [yaml loader]
8 | [json loader]
9 | [file system layer]
10 | }
11 |
12 | [lml]
13 |
14 | package "pyfilesystem2" {
15 | [fs]
16 | [tar]
17 | [zip]
18 | [file]
19 | [s3]
20 | }
21 |
22 | package "moban-mako" {
23 | [mako engine]
24 | }
25 |
26 | package "moban-haml" {
27 | [haml engine]
28 | }
29 |
30 | package "moremobans/gitfs2" {
31 | [git repo]
32 | }
33 |
34 | package "moremobans/pypifs" {
35 | [python package]
36 | }
37 |
38 | [fs.dropbox]
39 |
40 |
41 | [engine factory] -> [lml] : get all engines
42 | [lml] <-- [jinja2 engine] : register
43 | [lml] <.. [mako engine] : register
44 | [lml] <.. [haml engine] : register
45 | [lml] <.. [yaml loader] : register
46 | [lml] <.. [json loader] : register
47 | [data loader] -> [lml] : get all loaders
48 | [file system layer] -> [fs] : access templates,config,output
49 | [fs] <.. [git repo]
50 | [fs] <.. [python package]
51 | [fs] <.. [tar]
52 | [fs] <.. [zip]
53 | [fs] <.. [file]
54 | [fs] <.. [s3]
55 | [fs] <.. [fs.dropbox]
56 | @enduml
57 |
--------------------------------------------------------------------------------
/docs/images/moban-in-intro.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/moremoban/moban/11306525140a16f8b3231e41c73c48b3cfa4b333/docs/images/moban-in-intro.gif
--------------------------------------------------------------------------------
/docs/images/moban-in-pyexcel-demo.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/moremoban/moban/11306525140a16f8b3231e41c73c48b3cfa4b333/docs/images/moban-in-pyexcel-demo.gif
--------------------------------------------------------------------------------
/docs/index.rst:
--------------------------------------------------------------------------------
1 | .. moban documentation master file, created by
2 | sphinx-quickstart on Wed Mar 23 18:14:56 2016.
3 | You can adapt this file completely to your liking, but it should at least
4 | contain the root `toctree` directive.
5 |
6 | .. include:: ../README.rst
7 |
8 |
9 | Tutorial
10 | ==========================================================================================
11 |
12 | Please clone the moban repository as the data mentioned in the tutorial are stored in
13 | examples folder.
14 |
15 | .. toctree::
16 | :maxdepth: 1
17 |
18 | level-1-jinja2-cli/README.rst
19 | level-2-template-inheritance/README.rst
20 | level-3-data-override/README.rst
21 | level-4-single-command/README.rst
22 | level-5-custom-configuration/README.rst
23 | level-6-complex-configuration/README.rst
24 | level-7-use-custom-jinja2-filter-test-n-global/README.rst
25 | level-8-pass-a-folder-full-of-templates/README.rst
26 | level-9-moban-dependency-as-pypi-package/README.rst
27 | level-10-moban-dependency-as-git-repo/README.rst
28 | level-11-use-handlebars/README.rst
29 | level-12-use-template-engine-extensions/README.rst
30 | level-13-any-data-override-any-data/README.rst
31 | level-14-custom-data-loader/README.rst
32 | level-15-copy-templates-as-target/README.rst
33 | level-16-group-targets-using-template-type/README.rst
34 | level-17-force-template-type-from-moban-file/README.rst
35 | level-18-user-defined-template-types/README.rst
36 | level-19-moban-a-sub-group-in-targets/README.rst
37 | level-20-templates-configs-in-zip-or-tar/README.rst
38 | level-21-copy-templates-into-an-alien-file-system/README.rst
39 | level-22-intermediate-targets/README.rst
40 | level-23-inherit-organisational-moban-file/README.rst
41 | level-24-files-over-http/README.rst
42 | level-25-delete-intermediate-targets/README.rst
43 | level-26-strip-rendered-content/README.rst
44 |
45 |
46 | For more complex use case, please look at `its usage in pyexcel project `_
47 |
48 | Developer Guide
49 | ================================================================================
50 |
51 | .. toctree::
52 |
53 | extension
54 |
55 | .. include:: ../CHANGELOG.rst
56 |
57 | Migration Notes
58 | ================================================================================
59 |
60 | .. toctree::
61 |
62 | trouble-shooting-guide
63 | migration-note
64 |
65 |
66 | Indices and tables
67 | ==================
68 |
69 | * :ref:`genindex`
70 | * :ref:`modindex`
71 | * :ref:`search`
72 |
--------------------------------------------------------------------------------
/docs/level-1-jinja2-cli/README.rst:
--------------------------------------------------------------------------------
1 | Level 1 Jinja2 on command line
2 | ================================================================================
3 |
4 | `moban` reads data in yaml format, renders a template file in jinja2 format and
5 | outputs it to `moban.output`. By default, it looks for `data.yml` as its data file,
6 | but it will fallback to environment variables if a data file cannot be found
7 |
8 | Evaluation
9 | --------------------------------------------------------------------------------
10 |
11 | Please clone the moban project and install moban::
12 |
13 |
14 | $ git clone https://github.com/chfw/moban.git
15 | $ cd moban
16 | $ python setup.py install
17 |
18 |
19 | Then go to `docs/level-1-jinja2-cli`. here are different commands to evaluate it:
20 |
21 |
22 | .. code-block:: bash
23 |
24 | moban -c data.yml -t a.template
25 |
26 | 'moban.output' is the generated file.
27 |
28 | .. code-block:: bash
29 |
30 | moban -c data.yml -t a.template -o my.output
31 |
32 | `-o my.output` will override the default name
33 |
34 |
35 | .. note::
36 | You may simply type the short form:
37 |
38 | .. code-block:: bash
39 |
40 | moban -t a.template
41 |
42 | because moban looks for `data.yml` by default
43 |
44 | As well, you can define your own variable:
45 |
46 | .. code-block:: bash
47 |
48 | moban -D hello=maailman -t a.template
49 |
50 | And when you check 'moban.output', you will find you have overwritten data.yaml.
51 |
--------------------------------------------------------------------------------
/docs/level-1-jinja2-cli/a.template:
--------------------------------------------------------------------------------
1 | {{hello}}
--------------------------------------------------------------------------------
/docs/level-1-jinja2-cli/data.yml:
--------------------------------------------------------------------------------
1 | hello: world
2 |
--------------------------------------------------------------------------------
/docs/level-10-moban-dependency-as-git-repo/.moban.yml:
--------------------------------------------------------------------------------
1 | configuration:
2 | template_dir:
3 | - "git://github.com/moremoban/pypi-mobans.git!/templates"
4 | - local
5 | configuration: config.yml
6 | configuration_dir: "git://github.com/moremoban/pypi-mobans.git!/config"
7 | targets:
8 | - mytravis.yml: travis.yml.jj2
9 | - test.txt: demo.txt.jj2
10 |
--------------------------------------------------------------------------------
/docs/level-10-moban-dependency-as-git-repo/README.rst:
--------------------------------------------------------------------------------
1 | level 10: moban dependency as git repo
2 | ================================================================================
3 |
4 | .. note::
5 |
6 | You will need to install gitfs2
7 |
8 | Since the support to have a pypi package as dependency, the moban pro user will
9 | find it more useful to have git repo so that the changes to static content
10 | could get propagate as it happens using git push and git pull.
11 |
12 | For now, github.com, gitlab.com and bitbucket.com are supported. Pull request
13 | is welcome to add or improve this feature.
14 |
15 |
16 | Here are the sample file::
17 |
18 | configuration:
19 | template_dir:
20 | - "git://github.com/moremoban/pypi-mobans.git!/templates"
21 | - local
22 | configuration: config.yml
23 | configuration_dir: "git://github.com/moremoban/pypi-mobans.git!/config"
24 | targets:
25 | - mytravis.yml: travis.yml.jj2
26 | - test.txt: demo.txt.jj2
27 |
28 | where `requires` lead to a list of pypi packages. And when you refer to it,
29 | as in level-9 section, please use "pypi-mobans:"
30 |
31 |
32 | The syntax when submodule exists
33 | --------------------------------------------------------------------------------
34 |
35 | The sumodule syntax is::
36 |
37 | configuration:
38 | template_dir:
39 | - "git://github.com/moremoban/pypi-mobans.git?submodule=true&branch=your_choice_or_default_branch_if_not_specified!/templates"
40 | - local
41 |
42 |
43 | If you have reference instead of branch::
44 |
45 | configuration:
46 | template_dir:
47 | - "git://github.com/moremoban/pypi-mobans.git?submodule=true&reference=your_alternative_reference_but_not_used_together_with_branch!/templates"
48 | - local
49 |
50 |
--------------------------------------------------------------------------------
/docs/level-10-moban-dependency-as-git-repo/config.yml:
--------------------------------------------------------------------------------
1 | overrides: data.yml
2 | level10: "moban dependency as git repo"
3 |
--------------------------------------------------------------------------------
/docs/level-10-moban-dependency-as-git-repo/local/demo.txt.jj2:
--------------------------------------------------------------------------------
1 | {{name}}: {{level10}}
--------------------------------------------------------------------------------
/docs/level-10-moban-dependency-as-git-repo/local/mytravis.yml:
--------------------------------------------------------------------------------
1 | sudo: false
2 | language: python
3 | notifications:
4 | email: false
5 | python:
6 | - pypy-5.3.1
7 | - 3.7-dev
8 | - 3.6
9 | - 3.5
10 | - 3.4
11 | - 2.7
12 | before_install:
13 | - if [[ $TRAVIS_PYTHON_VERSION == "2.6" ]]; then pip install flake8==2.6.2; fi
14 | - if [[ -f min_requirements.txt && "$MINREQ" -eq 1 ]]; then
15 | mv min_requirements.txt requirements.txt ;
16 | fi
17 | - test ! -f rnd_requirements.txt ||
18 | pip install --no-deps -r rnd_requirements.txt
19 | - test ! -f rnd_requirements.txt || pip install -r rnd_requirements.txt ;
20 | - pip install -r tests/requirements.txt
21 | script:
22 | - make test
23 | after_success:
24 | codecov
25 |
--------------------------------------------------------------------------------
/docs/level-11-use-handlebars/.moban.cd/data.base.yaml:
--------------------------------------------------------------------------------
1 | nihao: shijie
2 | hello: shijie
3 |
--------------------------------------------------------------------------------
/docs/level-11-use-handlebars/.moban.td/base.hbs:
--------------------------------------------------------------------------------
1 |
2 | {{hello}}
3 |
4 | {{nihao}}
5 |
6 |
--------------------------------------------------------------------------------
/docs/level-11-use-handlebars/.moban.yml:
--------------------------------------------------------------------------------
1 | targets:
2 | - a.output: a.template.handlebars
3 | - b.output: base.hbs
4 |
--------------------------------------------------------------------------------
/docs/level-11-use-handlebars/README.rst:
--------------------------------------------------------------------------------
1 | Level 11: use handlebars
2 | ================================================================================
3 |
4 | moban is extensible via lml. Charlie Liu through Google Code-in 2018 has
5 | kindly contributed moban-handlebars plugin.
6 |
7 |
8 | Evaluation
9 | --------------------------------------------------------------------------------
10 |
11 | Please go to `docs/level-11-use-handlebars` directory. You will have to::
12 |
13 | $ pip install moban-handlebars
14 |
15 |
16 | Here is the `.moban.yml`, which replaces `jj2` with handlebars files in level 4::
17 |
18 | targets:
19 | - a.output: a.template.handlebars
20 | - b.output: base.hbs
21 |
22 |
23 | where `targets` should lead an array of dictionaries, `requires` installs
24 | moban-handlebars extension. You can provide file suffixes: ".handlebars"
25 | or ".hbs" to your handlebars template.
26 |
27 | Here is how to launch it
28 | .. code-block:: bash
29 |
30 | moban
31 |
--------------------------------------------------------------------------------
/docs/level-11-use-handlebars/a.template.handlebars:
--------------------------------------------------------------------------------
1 | {{no-inheritance}}
2 |
--------------------------------------------------------------------------------
/docs/level-11-use-handlebars/data.yml:
--------------------------------------------------------------------------------
1 | overrides: data.base.yaml
2 | hello: world
3 | no-inheritance: handlebars does not support inheritance
4 |
--------------------------------------------------------------------------------
/docs/level-12-use-template-engine-extensions/.moban.yml:
--------------------------------------------------------------------------------
1 | targets:
2 | - a.output: a.template
3 | - b.output: b.template
4 | extensions:
5 | jinja2:
6 | - jinja2.ext.with_
7 | - filter:moban.externals.file_system.url_join
8 | - test:moban.externals.file_system.exists
9 | - global:description=moban.constants.PROGRAM_DESCRIPTION
10 |
--------------------------------------------------------------------------------
/docs/level-12-use-template-engine-extensions/a.template:
--------------------------------------------------------------------------------
1 | {% for _ in range(1,5) %}
2 | {{ hello }}
3 | {% endfor %}
4 | {% if 'b.template' is exists %}
5 | b.template exists
6 | {% endif %}
7 | {% if 'alien' is exists %}
8 | we are not the only intelligent life in the universe
9 | {% endif %}
10 | {{ 'a' | url_join('b')}}
11 | {{ description }}
12 |
--------------------------------------------------------------------------------
/docs/level-12-use-template-engine-extensions/b.template:
--------------------------------------------------------------------------------
1 | {% with %}
2 | {% set foo = 142 %}
3 | {{ foo }}
4 | {% with %}
5 | {% set foo = 42 %}
6 | {{ foo }}
7 | {% endwith %}
8 | {{ foo }}
9 | {% endwith %}
10 |
--------------------------------------------------------------------------------
/docs/level-12-use-template-engine-extensions/data.yml:
--------------------------------------------------------------------------------
1 | hello: world
2 |
--------------------------------------------------------------------------------
/docs/level-13-any-data-override-any-data/.moban.cd/parent.json:
--------------------------------------------------------------------------------
1 | {
2 | "nihao": "shijie from parent.json",
3 | "hello": "shijie from parent.json"
4 | }
5 |
--------------------------------------------------------------------------------
/docs/level-13-any-data-override-any-data/.moban.cd/parent.yaml:
--------------------------------------------------------------------------------
1 | nihao: shijie from parent.yaml
2 | hello: shijie
3 |
--------------------------------------------------------------------------------
/docs/level-13-any-data-override-any-data/.moban.td/base.jj2:
--------------------------------------------------------------------------------
1 | {%block header %}
2 | {%endblock%}
3 |
4 | {{hello}}
5 |
6 | {{nihao}}
7 |
8 | {%block footer %}
9 | {%endblock%}
--------------------------------------------------------------------------------
/docs/level-13-any-data-override-any-data/README.rst:
--------------------------------------------------------------------------------
1 | Level 13: any data override any data
2 | ================================================================================
3 |
4 | It's thought that why shall we constrain ourselves on yaml file format. Along
5 | the development path, json file format was added. What about other file formats?
6 |
7 | By default yaml, json is supported. Due to the new capability `overrides` key
8 | word can override any supported data format::
9 |
10 | overrides: data.base.json
11 | ....
12 |
13 | or simple use `.json` data instead of `.yaml` data.
14 |
15 | Evaluation
16 | --------------------------------------------------------------------------------
17 |
18 | Please change directory to `docs/level-13-any-data-override-any-data` directory.
19 |
20 | In this example, `child.yaml` overrides `.moban.cd/parent.json`, here is the
21 | command to launch it:
22 |
23 | .. code-block:: bash
24 |
25 | moban -c child.yaml -t a.template
26 |
27 | 'moban.output' is the generated file::
28 |
29 | ========header============
30 |
31 | world from child.yaml
32 |
33 | shijie from parent.json
34 |
35 | ========footer============
36 |
37 |
38 | And we can try `child.json`, which you can guess, overrides `.moban.cd/parent.yaml`
39 |
40 | .. code-block:: bash
41 |
42 | moban -c child.json -t a.template
43 |
44 | 'moban.output' is the generated file::
45 |
46 | ========header============
47 |
48 | world from child.json
49 |
50 | shijie from parent.yml
51 |
52 | ========footer============
53 |
--------------------------------------------------------------------------------
/docs/level-13-any-data-override-any-data/a.template:
--------------------------------------------------------------------------------
1 | {%extends 'base.jj2' %}
2 |
3 | {%block header %}
4 | ========header============
5 | {%endblock%}
6 |
7 | {%block footer %}
8 | ========footer============
9 | {%endblock%}
10 |
--------------------------------------------------------------------------------
/docs/level-13-any-data-override-any-data/child.json:
--------------------------------------------------------------------------------
1 | {
2 | "overrides": "parent.yaml",
3 | "hello": "world from child.json"
4 | }
5 |
--------------------------------------------------------------------------------
/docs/level-13-any-data-override-any-data/child.yaml:
--------------------------------------------------------------------------------
1 | overrides: parent.json
2 | hello: world from child.yaml
3 |
--------------------------------------------------------------------------------
/docs/level-14-custom-data-loader/.moban.cd/parent.custom:
--------------------------------------------------------------------------------
1 | hello,nihao
2 | world from parent.cusom,shijie from parent.custom
--------------------------------------------------------------------------------
/docs/level-14-custom-data-loader/.moban.cd/parent.json:
--------------------------------------------------------------------------------
1 | {
2 | "nihao": "shijie from parent.json",
3 | "hello": "shijie from parent.json"
4 | }
5 |
--------------------------------------------------------------------------------
/docs/level-14-custom-data-loader/.moban.td/base.jj2:
--------------------------------------------------------------------------------
1 | {%block header %}
2 | {%endblock%}
3 |
4 | {{hello}}
5 |
6 | {{nihao}}
7 |
8 | {%block footer %}
9 | {%endblock%}
--------------------------------------------------------------------------------
/docs/level-14-custom-data-loader/.moban.yml:
--------------------------------------------------------------------------------
1 | configuration:
2 | plugin_dir:
3 | - custom-data-loader
4 | template: a.template
5 | targets:
6 | - output: a.output
7 | configuration: child.custom
8 | - output: b.output
9 | configuration: override_custom.yaml
10 |
--------------------------------------------------------------------------------
/docs/level-14-custom-data-loader/README.rst:
--------------------------------------------------------------------------------
1 | Level 14: custom data loader
2 | ================================================================================
3 |
4 | Continuing from level 13, `moban` since v0.4.0 allows data loader extension.
5 | Due to the new capability `overrides` key word can override any
6 | data format::
7 |
8 | overrides: yours.custom
9 | ....
10 |
11 | or simple use `.custom` data instead of `.yaml` data.
12 |
13 | However, you will need to provide a data loader for `.custom` yourselves.
14 |
15 | Evaluation
16 | --------------------------------------------------------------------------------
17 |
18 | Please change directory to `docs/level-14-custom-data-loader` directory.
19 |
20 |
21 | In this tutorial, a custom data loader was provided to show case its dataloader
22 | extension. Here is the mobanfile::
23 |
24 | configuration:
25 | plugin_dir:
26 | - custom-data-loader
27 | template: a.template
28 | targets:
29 | - output: a.output
30 | configuration: child.custom
31 | - output: b.output
32 | configuration: override_custom.yaml
33 |
34 | `custom-data-loader` is a directory where custom.py lives. The protocol is
35 | that the custom loader register itself to a file extension and return
36 | a data dictionary confirming mobanfile schema. On call, `moban` will provide
37 | an absolute file name for your loader to work on.
38 |
39 |
40 | Here is the code to do the registration:
41 |
42 | .. code-block:: python
43 |
44 | @PluginInfo(constants.DATA_LOADER_EXTENSION, tags=["custom"])
45 |
46 |
47 | In order to evaluate, you can simply type::
48 |
49 | $ moban
50 | $ cat a.output
51 | ========header============
52 |
53 | world from child.cusom
54 |
55 | shijie from parent.json
56 |
57 | ========footer============
58 | $ cat b.output
59 | ========header============
60 |
61 | world from override_custom.yaml
62 |
63 | shijie from parent.custom
64 |
65 | ========footer============
66 |
67 |
68 | .. warning::
69 |
70 | Python 2 dictates the existence of __init__.py in the plugin directory. Otheriwse
71 | your plugin won't load
72 |
--------------------------------------------------------------------------------
/docs/level-14-custom-data-loader/a.template:
--------------------------------------------------------------------------------
1 | {%extends 'base.jj2' %}
2 |
3 | {%block header %}
4 | ========header============
5 | {%endblock%}
6 |
7 | {%block footer %}
8 | ========footer============
9 | {%endblock%}
10 |
--------------------------------------------------------------------------------
/docs/level-14-custom-data-loader/child.custom:
--------------------------------------------------------------------------------
1 | hello,overrides
2 | world from child.cusom,parent.json
--------------------------------------------------------------------------------
/docs/level-14-custom-data-loader/custom-data-loader/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/moremoban/moban/11306525140a16f8b3231e41c73c48b3cfa4b333/docs/level-14-custom-data-loader/custom-data-loader/__init__.py
--------------------------------------------------------------------------------
/docs/level-14-custom-data-loader/custom-data-loader/custom.py:
--------------------------------------------------------------------------------
1 | import csv
2 |
3 | from lml.plugin import PluginInfo
4 |
5 | from moban import constants
6 |
7 |
8 | @PluginInfo(constants.DATA_LOADER_EXTENSION, tags=["custom"])
9 | def open_custom(file_name):
10 | with open(file_name, "r") as data_csv:
11 | csvreader = csv.reader(data_csv)
12 | rows = []
13 | for row in csvreader:
14 | rows.append(row)
15 |
16 | data = dict(zip(rows[0], rows[1]))
17 | return data
18 |
--------------------------------------------------------------------------------
/docs/level-14-custom-data-loader/override_custom.yaml:
--------------------------------------------------------------------------------
1 | overrides: parent.custom
2 | hello: world from override_custom.yaml
3 |
--------------------------------------------------------------------------------
/docs/level-15-copy-templates-as-target/.moban.yml:
--------------------------------------------------------------------------------
1 | configuration:
2 | template_dir:
3 | - template-sources
4 | targets:
5 | - output: simple.file.copy
6 | template: file-in-template-sources-folder.txt
7 | template_type: copy
8 | - output: target_without_template_type
9 | template: file_extension_will_trigger.copy
10 | - target_in_short_form: as_long_as_this_one_has.copy
11 | - output_is_copied.same_file_extension: when_source_have.same_file_extension
12 | - output: "misc-1-copying/can-create-folder/if-not-exists.txt"
13 | template: file-in-template-sources-folder.txt
14 | template_type: copy
15 | - output: "test-dir"
16 | template: dir-for-copying
17 | template_type: copy
18 | - output: "test-recursive-dir"
19 | template: dir-for-recusive-copying/**
20 | template_type: copy
21 |
--------------------------------------------------------------------------------
/docs/level-15-copy-templates-as-target/README.rst:
--------------------------------------------------------------------------------
1 | Level 15: template copying becomes an action plugin in targets
2 | ================================================================================
3 |
4 | With `.moban.yml`, you can copy templates to your destination. More information
5 | is documented in `misc-1-copying-template`.
6 |
7 | Explicit syntax::
8 |
9 | targets:
10 | - output: explicit
11 | template: template_file
12 | template_type: copy
13 |
14 |
15 | Implicit syntax::
16 |
17 | targets:
18 | - output: explicit
19 | template: template_file.copy
20 |
21 |
22 | Shorthand syntax::
23 |
24 | targets:
25 | - explicit: template_file.copy
26 | - output_is_copied.same_file_extension: when_source_have.same_file_extension
27 |
28 | No implicit nor short hand syntax for the following directory copying unless
29 | you take a look at `force-template-type`. When you read
30 | `level-17-force-template-type-from-moban-file/README.rst`, you will find
31 | out more.
32 |
33 |
34 | Directory copying syntax::
35 |
36 |
37 | targets:
38 | - output: dest-dir
39 | template: source-dir
40 | template_type: copy
41 |
42 |
43 | Recursive directory copying syntax::
44 |
45 |
46 | targets:
47 | - output: dest-dir
48 | template: source-dir/**
49 | template_type: copy
50 |
51 |
52 | Evaluation
53 | --------------------------------------------------------------------------------
54 |
55 | Here is example moban file for copying::
56 |
57 | configuration:
58 | template_dir:
59 | - template-sources
60 | targets:
61 | - output: simple.file.copy
62 | template: file-in-template-sources-folder.txt
63 | template_type: copy
64 | - output: target_without_template_type
65 | template: file_extension_will_trigger.copy
66 | - target_in_short_form: as_long_as_this_one_has.copy
67 | - output_is_copied.same_file_extension: when_source_have.same_file_extension
68 | - output: "misc-1-copying/can-create-folder/if-not-exists.txt"
69 | template: file-in-template-sources-folder.txt
70 | template_type: copy
71 | - output: "test-dir"
72 | template: dir-for-copying
73 | template_type: copy
74 | - output: "test-recursive-dir"
75 | template: dir-for-recusive-copying/**
76 | template_type: copy
77 |
78 |
79 | template copy does:
80 |
81 |
82 | #. copies any template inside pre-declared template directory to anywhere. moban will create directory if needed.
83 | #. copies any directory to anywhere. If "**" is followed, moban attempts to do recursive copying.
84 |
--------------------------------------------------------------------------------
/docs/level-15-copy-templates-as-target/misc-1-copying/can-create-folder/if-not-exists.txt:
--------------------------------------------------------------------------------
1 | test file
2 |
--------------------------------------------------------------------------------
/docs/level-15-copy-templates-as-target/template-sources/as_long_as_this_one_has.copy:
--------------------------------------------------------------------------------
1 | it is OK to have a short form, but the file to be 'copied' shall have 'copy' extension, so as to trigger ContentForwardEngine, 'copy' engine.
2 |
--------------------------------------------------------------------------------
/docs/level-15-copy-templates-as-target/template-sources/dir-for-copying/afile.txt:
--------------------------------------------------------------------------------
1 | dir for copying
2 |
--------------------------------------------------------------------------------
/docs/level-15-copy-templates-as-target/template-sources/dir-for-copying/sub_directory_is_not_copied/becuase_star_star_is_needed.txt:
--------------------------------------------------------------------------------
1 | Please look at .moban.yml
2 |
--------------------------------------------------------------------------------
/docs/level-15-copy-templates-as-target/template-sources/dir-for-recusive-copying/fileb.txt:
--------------------------------------------------------------------------------
1 | everything is copied
2 |
--------------------------------------------------------------------------------
/docs/level-15-copy-templates-as-target/template-sources/dir-for-recusive-copying/sub_directory_is_copied/because_star_star_is_specified.txt:
--------------------------------------------------------------------------------
1 | dest_directory: source_directory/**
2 |
--------------------------------------------------------------------------------
/docs/level-15-copy-templates-as-target/template-sources/file-in-template-sources-folder.txt:
--------------------------------------------------------------------------------
1 | test file
2 |
--------------------------------------------------------------------------------
/docs/level-15-copy-templates-as-target/template-sources/file_extension_will_trigger.copy:
--------------------------------------------------------------------------------
1 | file extension will trigger copy engine
2 |
--------------------------------------------------------------------------------
/docs/level-15-copy-templates-as-target/template-sources/when_source_have.same_file_extension:
--------------------------------------------------------------------------------
1 | it is implicit copy as well
--------------------------------------------------------------------------------
/docs/level-16-group-targets-using-template-type/.moban.yml:
--------------------------------------------------------------------------------
1 | configuration:
2 | template_dir:
3 | - template-sources
4 | targets:
5 | - copy:
6 | - simple.file.copy: file-in-template-sources-folder.txt
7 | - "misc-1-copying/can-create-folder/if-not-exists.txt":
8 | file-in-template-sources-folder.txt
9 | - "test-dir": dir-for-copying
10 | - "test-recursive-dir": dir-for-recusive-copying/**
11 |
--------------------------------------------------------------------------------
/docs/level-16-group-targets-using-template-type/README.rst:
--------------------------------------------------------------------------------
1 | Level 16: group targets by their template type
2 | ================================================================================
3 |
4 | Since moban version 0.4.0, you can group your targets with their template type.
5 | For example, with `copy` target, you can do the following things:
6 |
7 |
8 | Here is example moban file for copying::
9 |
10 | configuration:
11 | template_dir:
12 | - template-sources
13 | targets:
14 | - copy:
15 | - simple.file.copy: file-in-template-sources-folder.txt
16 | - "misc-1-copying/can-create-folder/if-not-exists.txt": file-in-template-sources-folder.txt
17 | - "test-dir": dir-for-copying
18 | - "test-recursive-dir": dir-for-recusive-copying/**
19 |
20 | More information is documented in `misc-1-copying-template`.
21 |
22 |
23 | template copy does:
24 |
25 | #. copies any template inside pre-declared template directory to anywhere. moban will create directory if needed.
26 | #. copies any directory to anywhere. If "**" is followed, moban attempts to do recursive copying.
27 |
28 |
29 | .. note::
30 |
31 | The suffix `.copy` of `simple.file.copy` will be removed.
32 |
--------------------------------------------------------------------------------
/docs/level-16-group-targets-using-template-type/misc-1-copying/can-create-folder/if-not-exists.txt:
--------------------------------------------------------------------------------
1 | test file
2 |
--------------------------------------------------------------------------------
/docs/level-16-group-targets-using-template-type/template-sources/dir-for-copying/afile.txt:
--------------------------------------------------------------------------------
1 | dir for copying
2 |
--------------------------------------------------------------------------------
/docs/level-16-group-targets-using-template-type/template-sources/dir-for-copying/sub_directory_is_not_copied/becuase_star_star_is_needed.txt:
--------------------------------------------------------------------------------
1 | Please look at .moban.yml
2 |
--------------------------------------------------------------------------------
/docs/level-16-group-targets-using-template-type/template-sources/dir-for-recusive-copying/fileb.txt:
--------------------------------------------------------------------------------
1 | everything is copied
2 |
--------------------------------------------------------------------------------
/docs/level-16-group-targets-using-template-type/template-sources/dir-for-recusive-copying/sub_directory_is_copied/because_star_star_is_specified.txt:
--------------------------------------------------------------------------------
1 | dest_directory: source_directory/**
2 |
--------------------------------------------------------------------------------
/docs/level-16-group-targets-using-template-type/template-sources/file-in-template-sources-folder.txt:
--------------------------------------------------------------------------------
1 | test file
2 |
--------------------------------------------------------------------------------
/docs/level-17-force-template-type-from-moban-file/.moban.yml:
--------------------------------------------------------------------------------
1 | configuration:
2 | template_dir:
3 | - template-sources
4 | force_template_type: copy
5 | targets:
6 | - simple.file.copy: file-in-template-sources-folder.txt
7 | - "misc-1-copying/can-create-folder/if-not-exists.txt":
8 | file-in-template-sources-folder.txt
9 | - "test-dir": dir-for-copying
10 | - "test-recursive-dir": dir-for-recusive-copying/**
11 |
--------------------------------------------------------------------------------
/docs/level-17-force-template-type-from-moban-file/README.rst:
--------------------------------------------------------------------------------
1 | Level 17: force template type
2 | ================================================================================
3 |
4 | Since moban version 0.4.0, you can enforce all targets to use one and only one
5 | template type, regardless of their individual template types.
6 |
7 |
8 | Here is example moban file for copying::
9 |
10 | configuration:
11 | template_dir:
12 | - template-sources
13 | force_template_type: copy
14 | targets:
15 | - simple.file.copy: file-in-template-sources-folder.txt
16 | - "misc-1-copying/can-create-folder/if-not-exists.txt": file-in-template-sources-folder.txt
17 | - "test-dir": dir-for-copying
18 | - "test-recursive-dir": dir-for-recusive-copying/**
19 |
20 | More information is documented in `misc-1-copying-template`.
21 |
22 |
23 | template copy does:
24 |
25 | #. copies any template inside pre-declared template directory to anywhere. moban will create directory if needed.
26 | #. copies any directory to anywhere. If "**" is followed, moban attempts to do recursive copying.
27 |
--------------------------------------------------------------------------------
/docs/level-17-force-template-type-from-moban-file/misc-1-copying/can-create-folder/if-not-exists:
--------------------------------------------------------------------------------
1 | test file
2 |
--------------------------------------------------------------------------------
/docs/level-17-force-template-type-from-moban-file/misc-1-copying/can-create-folder/if-not-exists.txt:
--------------------------------------------------------------------------------
1 | test file
2 |
--------------------------------------------------------------------------------
/docs/level-17-force-template-type-from-moban-file/template-sources/dir-for-copying/afile.txt:
--------------------------------------------------------------------------------
1 | dir for copying
2 |
--------------------------------------------------------------------------------
/docs/level-17-force-template-type-from-moban-file/template-sources/dir-for-copying/sub_directory_is_not_copied/becuase_star_star_is_needed.txt:
--------------------------------------------------------------------------------
1 | Please look at .moban.yml
2 |
--------------------------------------------------------------------------------
/docs/level-17-force-template-type-from-moban-file/template-sources/dir-for-recusive-copying/fileb.txt:
--------------------------------------------------------------------------------
1 | everything is copied
2 |
--------------------------------------------------------------------------------
/docs/level-17-force-template-type-from-moban-file/template-sources/dir-for-recusive-copying/sub_directory_is_copied/because_star_star_is_specified.txt:
--------------------------------------------------------------------------------
1 | dest_directory: source_directory/**
2 |
--------------------------------------------------------------------------------
/docs/level-17-force-template-type-from-moban-file/template-sources/file-in-template-sources-folder.txt:
--------------------------------------------------------------------------------
1 | test file
2 |
--------------------------------------------------------------------------------
/docs/level-18-user-defined-template-types/.moban.cd/data.base.yaml:
--------------------------------------------------------------------------------
1 | nihao: shijie
2 | hello: shijie
3 |
--------------------------------------------------------------------------------
/docs/level-18-user-defined-template-types/.moban.yml:
--------------------------------------------------------------------------------
1 | configuration:
2 | template_types:
3 | my_own_type:
4 | base_type: jinja2
5 | file_extensions:
6 | - file_type_of_my_choice
7 | options:
8 | extensions:
9 | - jinja2_time.TimeExtension
10 | targets:
11 | - a.output: a.template.file_type_of_my_choice
12 | - output: b.output
13 | template: a.template.jj2
14 | template_type:
15 | base_type: jinja2
16 | options:
17 | variable_start_string: '((('
18 | variable_end_string: ')))'
19 |
--------------------------------------------------------------------------------
/docs/level-18-user-defined-template-types/README.rst:
--------------------------------------------------------------------------------
1 | Level 18: User defined template types
2 | ================================================================================
3 |
4 | Since moban version 4.1, custom template types can be defined to deviate from
5 | default configurations of the template engines. In addition, the configuration
6 | possibilities are:
7 |
8 | #. associate your own file extensions
9 | #. choose your own template engine extensions
10 |
11 |
12 | Evaluation
13 | --------------------------------------------------------------------------------
14 |
15 | Please go to `docs/level-4-single-command` directory.
16 |
17 |
18 | Here is the `.moban.yml`, which inserts `template_types` on top of the moban
19 | file found in level 4::
20 |
21 | configuration:
22 | template_types:
23 | my_own_type:
24 | base_type: jinja2
25 | file_extensions:
26 | - file_type_of_my_choice
27 | options:
28 | extensions:
29 | - jinja2_time.TimeExtension
30 | targets:
31 | - a.output: a.template.file_type_of_my_choice
32 |
33 |
34 | where `template_types` is a dictionary of different custom types.
35 |
36 | Also, you can define your `template` on the fly by putting the template
37 | parameters inside targets. One such example is::
38 |
39 | targets:
40 | - output: b.output
41 | template: a.template.jj2
42 | template_type:
43 | base_type: jinja2
44 | options:
45 | block_end_string: '*))'
46 | block_start_string: '((*'
47 | variable_start_string: '((('
48 | variable_end_string: ')))'
49 |
--------------------------------------------------------------------------------
/docs/level-18-user-defined-template-types/a.template.file_type_of_my_choice:
--------------------------------------------------------------------------------
1 | {% now 'utc' %}
2 |
3 |
--------------------------------------------------------------------------------
/docs/level-18-user-defined-template-types/a.template.jj2:
--------------------------------------------------------------------------------
1 | ((( nihao )))
2 |
--------------------------------------------------------------------------------
/docs/level-18-user-defined-template-types/data.yml:
--------------------------------------------------------------------------------
1 | overrides: data.base.yaml
2 | hello: world
3 |
--------------------------------------------------------------------------------
/docs/level-19-moban-a-sub-group-in-targets/.moban.yml:
--------------------------------------------------------------------------------
1 | configuration:
2 | template_dir:
3 | - template-sources
4 | - .
5 | targets:
6 | - a.output: a.template.jj2
7 | - copy:
8 | - simple.file.copy: file-in-template-sources-folder.txt
9 | - "misc-1-copying/can-create-folder/if-not-exists.txt":
10 | file-in-template-sources-folder.txt
11 | - "test-dir": dir-for-copying
12 | - "test-recursive-dir": dir-for-recusive-copying/**
13 |
--------------------------------------------------------------------------------
/docs/level-19-moban-a-sub-group-in-targets/README.rst:
--------------------------------------------------------------------------------
1 | Level 19: select a group target to run
2 | ================================================================================
3 |
4 | Since moban version 0.4.2, you can select a group target to run.
5 | For example, with `copy` target mixed with normal file list:
6 |
7 |
8 | configuration:
9 | template_dir:
10 | - template-sources
11 | targets:
12 | - a.output: a.template.jj2
13 | - copy:
14 | - simple.file.copy: file-in-template-sources-folder.txt
15 | - "misc-1-copying/can-create-folder/if-not-exists.txt": file-in-template-sources-folder.txt
16 | - "test-dir": dir-for-copying
17 | - "test-recursive-dir": dir-for-recusive-copying/**
18 |
19 | you can do the following things::
20 |
21 | $ moban -g copy
22 |
23 |
--------------------------------------------------------------------------------
/docs/level-19-moban-a-sub-group-in-targets/a.template.jj2:
--------------------------------------------------------------------------------
1 | I will not be selected in level 19
2 |
--------------------------------------------------------------------------------
/docs/level-19-moban-a-sub-group-in-targets/misc-1-copying/can-create-folder/if-not-exists.txt:
--------------------------------------------------------------------------------
1 | test file
2 |
--------------------------------------------------------------------------------
/docs/level-19-moban-a-sub-group-in-targets/template-sources/dir-for-copying/afile.txt:
--------------------------------------------------------------------------------
1 | dir for copying
2 |
--------------------------------------------------------------------------------
/docs/level-19-moban-a-sub-group-in-targets/template-sources/dir-for-copying/sub_directory_is_not_copied/becuase_star_star_is_needed.txt:
--------------------------------------------------------------------------------
1 | Please look at .moban.yml
2 |
--------------------------------------------------------------------------------
/docs/level-19-moban-a-sub-group-in-targets/template-sources/dir-for-recusive-copying/fileb.txt:
--------------------------------------------------------------------------------
1 | everything is copied
2 |
--------------------------------------------------------------------------------
/docs/level-19-moban-a-sub-group-in-targets/template-sources/dir-for-recusive-copying/sub_directory_is_copied/because_star_star_is_specified.txt:
--------------------------------------------------------------------------------
1 | dest_directory: source_directory/**
2 |
--------------------------------------------------------------------------------
/docs/level-19-moban-a-sub-group-in-targets/template-sources/file-in-template-sources-folder.txt:
--------------------------------------------------------------------------------
1 | test file
2 |
--------------------------------------------------------------------------------
/docs/level-2-template-inheritance/.moban.td/base.jj2:
--------------------------------------------------------------------------------
1 | {%block header %}
2 | {%endblock%}
3 |
4 | {{hello}}
5 |
6 | {%block footer %}
7 | {%endblock%}
--------------------------------------------------------------------------------
/docs/level-2-template-inheritance/README.rst:
--------------------------------------------------------------------------------
1 | Level 2: template inheritance
2 | ================================================================================
3 |
4 | Template inheritance is a feature in Jinja2. This example show how it was done.
5 | `a.template` inherits `base.jj2`, which is located in `.moban.td`, the default
6 | template directory.
7 |
8 |
9 | .. warning::
10 |
11 | `a.template` could be a symbolic link on Unix/Linux. It will not work if you
12 | template
13 | `a symbolic link on Windows `_.
14 | Use symbolic link at your own calculated risk.
15 |
16 |
17 | Evaluation
18 | --------------------------------------------------------------------------------
19 |
20 | Please go to `docs/level-2-template-inheritance`, here is the command to launch it:
21 |
22 | .. code-block:: bash
23 |
24 | moban -c data.yaml -t a.template
25 |
26 | `a.template` inherits `.moban.td/base.jj2`.
27 |
--------------------------------------------------------------------------------
/docs/level-2-template-inheritance/a.template:
--------------------------------------------------------------------------------
1 | {%extends 'base.jj2' %}
2 |
3 | {%block header %}
4 | ========header============
5 | {%endblock%}
6 |
7 | {%block footer %}
8 | ========footer============
9 | {%endblock%}
10 |
--------------------------------------------------------------------------------
/docs/level-2-template-inheritance/data.yml:
--------------------------------------------------------------------------------
1 | hello: world
2 |
--------------------------------------------------------------------------------
/docs/level-20-templates-configs-in-zip-or-tar/.moban.yml:
--------------------------------------------------------------------------------
1 | configuration:
2 | configuration_dir: 'tar://custom-config.tar'
3 | template_dir:
4 | - zip://templates.zip
5 | - cool-templates
6 | - '.'
7 | targets:
8 | - output: 'tar://a.tar!/a.output'
9 | configuration: data.yml
10 | template: template.in.zip.jj2
11 | - output: 'zip://a.zip!/a.output2'
12 | configuration: data2.yml
13 | template: subfolder/template.in.zip.jj2
14 |
--------------------------------------------------------------------------------
/docs/level-20-templates-configs-in-zip-or-tar/README.rst:
--------------------------------------------------------------------------------
1 | Level 20: templates, files in a zip or tar
2 | ================================================================================
3 |
4 | On top of level 6, you could have files in a zip or tar.
5 | In the following example::
6 |
7 | configuration:
8 | configuration_dir: 'tar://custom-config.tar'
9 | template_dir:
10 | - zip://templates.zip
11 | - cool-templates
12 | - '.'
13 | targets:
14 | - output: 'tar://a.tar/a.output'
15 | configuration: data.yml
16 | template: template.in.zip.jj2
17 | - output: 'zip://a.zip/a.output2'
18 | configuration: data2.yml
19 | template: subfolder/template.in.zip.jj2
20 |
21 | where `template.in.zip.jj2` were loaded from a zip file
22 |
23 |
24 | Evaluation
25 | --------------------------------------------------------------------------------
26 |
27 | Please go to `docs/level-20-templates-configs-in-zip-or-tar` directory.
28 |
29 | Here is the command to launch it:
30 |
31 | .. code-block:: bash
32 |
33 | moban
34 |
35 | 'a.output' is the generated file in a.tar::
36 |
37 | ========header============
38 |
39 | world
40 |
41 | shijie
42 |
43 | this demonstrations jinja2's include statement
44 |
45 | ========footer============
46 |
47 | `a.output2` is in a.zip::
48 |
49 | ========header============
50 |
51 | world2
52 |
53 | shijie
54 |
55 | this demonstrations jinja2's include statement
56 |
57 | ========footer============
58 |
--------------------------------------------------------------------------------
/docs/level-20-templates-configs-in-zip-or-tar/cool-templates/base.jj2:
--------------------------------------------------------------------------------
1 | {%block header %}
2 | {%endblock%}
3 |
4 | {{hello}}
5 |
6 | {{nihao}}
7 |
8 | {%include 'cool.template.jj2'%}
9 |
10 | {%block footer %}
11 | {%endblock%}
12 |
--------------------------------------------------------------------------------
/docs/level-20-templates-configs-in-zip-or-tar/cool-templates/cool.template.jj2:
--------------------------------------------------------------------------------
1 | this demonstrates jinja2's include statement
2 |
--------------------------------------------------------------------------------
/docs/level-20-templates-configs-in-zip-or-tar/custom-config.tar:
--------------------------------------------------------------------------------
1 | data.in.tar.yaml 000644 000765 000024 00000000034 13517160574 014307 0 ustar 00jaska staff 000000 000000 nihao: shijie
2 | hello: shijie
3 |
--------------------------------------------------------------------------------
/docs/level-20-templates-configs-in-zip-or-tar/custom-templates/subfolder/template.in.zip.jj2:
--------------------------------------------------------------------------------
1 | {%extends 'base.jj2' %}
2 |
3 | {%block header %}
4 | ========header============
5 | {%endblock%}
6 |
7 | {%block footer %}
8 | ========footer============
9 | {%endblock%}
10 |
--------------------------------------------------------------------------------
/docs/level-20-templates-configs-in-zip-or-tar/custom-templates/template.in.zip.jj2:
--------------------------------------------------------------------------------
1 | {%extends 'base.jj2' %}
2 |
3 | {%block header %}
4 | ========header============
5 | {%endblock%}
6 |
7 | {%block footer %}
8 | ========footer============
9 | {%endblock%}
10 |
--------------------------------------------------------------------------------
/docs/level-20-templates-configs-in-zip-or-tar/data.yml:
--------------------------------------------------------------------------------
1 | overrides: data.in.tar.yaml
2 | hello: world
3 |
--------------------------------------------------------------------------------
/docs/level-20-templates-configs-in-zip-or-tar/data2.yml:
--------------------------------------------------------------------------------
1 | overrides: data.in.tar.yaml
2 | hello: world2
3 |
--------------------------------------------------------------------------------
/docs/level-20-templates-configs-in-zip-or-tar/templates.zip:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/moremoban/moban/11306525140a16f8b3231e41c73c48b3cfa4b333/docs/level-20-templates-configs-in-zip-or-tar/templates.zip
--------------------------------------------------------------------------------
/docs/level-21-copy-templates-into-an-alien-file-system/.moban.yml:
--------------------------------------------------------------------------------
1 | configuration:
2 | template_dir:
3 | - "zip://template-sources.zip"
4 | targets:
5 | - output: "zip://my.zip!/simple.file.copy"
6 | template: file-in-template-sources-folder.txt
7 | template_type: copy
8 | - output: "zip://my.zip!/target_without_template_type"
9 | template: file_extension_will_trigger.copy
10 | - "zip://my.zip!/target_in_short_form": as_long_as_this_one_has.copy
11 | - output: "zip://my.zip!/misc-1-copying/can-create-folder/if-not-exists.txt"
12 | template: file-in-template-sources-folder.txt
13 | template_type: copy
14 | - output: "zip://my.zip!/test-dir"
15 | template: dir-for-copying
16 | template_type: copy
17 | - output: "zip://my.zip!/test-recursive-dir"
18 | template: dir-for-recusive-copying/**
19 | template_type: copy
20 |
--------------------------------------------------------------------------------
/docs/level-21-copy-templates-into-an-alien-file-system/README.rst:
--------------------------------------------------------------------------------
1 | Level 21: template copying from a zip to a zip
2 | ================================================================================
3 |
4 | In level 15, with `.moban.yml`, you can copy templates to your destination. Now
5 | with similiar moban syntax, let me show how to create a new zip file where
6 | all templates are copied to.
7 |
8 | Explicit syntax::
9 |
10 | targets:
11 | - output: "zip://your.zip/explicit"
12 | template: template_file
13 | template_type: copy
14 |
15 |
16 | Implicit syntax::
17 |
18 | targets:
19 | - output: "zip://your.zip/implicit"
20 | template: template_file.copy
21 |
22 |
23 | Shorthand syntax::
24 |
25 | targets:
26 | - "zip://your.zip/shorthand": template_file.copy
27 |
28 |
29 | No implicit nor short hand syntax for the following directory copying unless
30 | you take a look at `force-template-type`. When you read
31 | `level-17-force-template-type-from-moban-file/README.rst`, you will find
32 | out more.
33 |
34 |
35 | Directory copying syntax::
36 |
37 |
38 | targets:
39 | - output: "zip://your.zip/dest-dir"
40 | template: source-dir
41 | template_type: copy
42 |
43 |
44 | Recursive directory copying syntax::
45 |
46 |
47 | targets:
48 | - output: "zip://your.zip/dest-dir"
49 | template: source-dir/**
50 | template_type: copy
51 |
52 |
53 | Evaluation
54 | --------------------------------------------------------------------------------
55 |
56 | Here is example moban file for copying::
57 |
58 | configuration:
59 | template_dir:
60 | - "zip://template-sources.zip"
61 | targets:
62 | - output: "zip://my.zip/simple.file.copy"
63 | template: file-in-template-sources-folder.txt
64 | template_type: copy
65 | - output: "zip://my.zip/target_without_template_type"
66 | template: file_extension_will_trigger.copy
67 | - "zip://my.zip/target_in_short_form": as_long_as_this_one_has.copy
68 | - output: "zip://my.zip/misc-1-copying/can-create-folder/if-not-exists.txt"
69 | template: file-in-template-sources-folder.txt
70 | template_type: copy
71 | - output: "zip://my.zip/test-dir"
72 | template: dir-for-copying
73 | template_type: copy
74 | - output: "zip://my.zip/test-recursive-dir"
75 | template: dir-for-recusive-copying/**
76 | template_type: copy
77 |
78 |
79 | template copy does:
80 |
81 |
82 | #. copies any template inside pre-declared template directory to anywhere. moban will create directory if needed.
83 | #. copies any directory to anywhere. If "**" is followed, moban attempts to do recursive copying.
84 |
--------------------------------------------------------------------------------
/docs/level-21-copy-templates-into-an-alien-file-system/template-sources.zip:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/moremoban/moban/11306525140a16f8b3231e41c73c48b3cfa4b333/docs/level-21-copy-templates-into-an-alien-file-system/template-sources.zip
--------------------------------------------------------------------------------
/docs/level-22-intermediate-targets/.moban.yaml:
--------------------------------------------------------------------------------
1 | targets:
2 | - intermediate.jj2: original.jj2
3 | - final: intermediate.jj2
4 |
--------------------------------------------------------------------------------
/docs/level-22-intermediate-targets/README.rst:
--------------------------------------------------------------------------------
1 | Level 22: intermediate targets
2 | ================================================================================
3 |
4 | It is natural to allow intermediate target to be source so that different
5 | moban plugins can interact with each other. The good news is since moban verion
6 | 0.6.5, it is support.
7 |
8 | .. note::
9 | The bad news is, folder as imtermediate target is not supported yet and will be
10 | considered in next incremental build. For now, the date cannot be confirmed.
11 |
12 | Here are the syntax::
13 |
14 | targets:
15 | - intermediate.jj2: original.jj2
16 | - final: intermediate.jj2
17 |
18 | With moban 0.6.4-, above syntax cannot result in `final` file to be generated
19 | because `intermediate.jj2` does not exist until moban is run.
20 |
--------------------------------------------------------------------------------
/docs/level-22-intermediate-targets/data.yml:
--------------------------------------------------------------------------------
1 | hello: world
2 |
--------------------------------------------------------------------------------
/docs/level-22-intermediate-targets/original.jj2:
--------------------------------------------------------------------------------
1 | a {{hello}}
2 |
--------------------------------------------------------------------------------
/docs/level-23-inherit-organisational-moban-file/.moban.yml:
--------------------------------------------------------------------------------
1 | overrides: parent.moban.yaml
2 | targets:
3 | - output_a: template_a.jj2
4 |
--------------------------------------------------------------------------------
/docs/level-23-inherit-organisational-moban-file/README.rst:
--------------------------------------------------------------------------------
1 | Level 23: moban file inheritance
2 | ================================================================================
3 |
4 | It is a bit tedious to repeat a few common configuration in moban file. Why not
5 | create a parent moban file? Then allow child project to deviate from.
6 |
7 | The answer is to use 'overrides' in `.moban.yaml`, so called moban file.
8 |
9 | `overrides` could over ride any data file format in any location in theory. And
10 | it support override a specific key set.
11 |
--------------------------------------------------------------------------------
/docs/level-23-inherit-organisational-moban-file/data.yaml:
--------------------------------------------------------------------------------
1 | template_a: I am template a
2 | template_b: I am template b
3 |
--------------------------------------------------------------------------------
/docs/level-23-inherit-organisational-moban-file/parent.moban.yaml:
--------------------------------------------------------------------------------
1 | configuration:
2 | configuration: data.yaml
3 | targets:
4 | - output_b: template_b.jj2
5 |
--------------------------------------------------------------------------------
/docs/level-23-inherit-organisational-moban-file/template_a.jj2:
--------------------------------------------------------------------------------
1 | {{template_a}}
--------------------------------------------------------------------------------
/docs/level-23-inherit-organisational-moban-file/template_b.jj2:
--------------------------------------------------------------------------------
1 | {{template_b}}
--------------------------------------------------------------------------------
/docs/level-24-files-over-http/.moban.yml:
--------------------------------------------------------------------------------
1 | configuration:
2 | template_dir:
3 | - "https://raw.githubusercontent.com/moremoban/pypi-mobans/dev/templates/"
4 | - local
5 | configuration: config.yml
6 | configuration_dir: >-
7 | https://raw.githubusercontent.com/moremoban/pypi-mobans/dev/config/
8 | targets:
9 | - mytravis.yml: travis.yml.jj2
10 | - test.txt: demo.txt.jj2
11 |
--------------------------------------------------------------------------------
/docs/level-24-files-over-http/README.rst:
--------------------------------------------------------------------------------
1 | level 24: templates and configuration files over http(s)
2 | ================================================================================
3 |
4 | .. note::
5 |
6 | You will need to install httpfs
7 |
8 | Why not to take a template off the web? Once a template is written somewhere
9 | by somebody, as long as it is good and useful, it is always to reuse it,
10 | isn't it? DRY principle kicks in.
11 |
12 | Now with mobanfile, it is possible to package up your mobans/templates and
13 | configuration files from a HTTP(S) protocol.
14 |
15 |
16 | Here are the sample file::
17 |
18 | configuration:
19 | template_dir:
20 | - "https://raw.githubusercontent.com/moremoban/pypi-mobans/dev/templates/"
21 | - local
22 | configuration: config.yml
23 | configuration_dir: "https://raw.githubusercontent.com/moremoban/pypi-mobans/dev/config/"
24 | targets:
25 | - mytravis.yml: travis.yml.jj2
26 | - test.txt: demo.txt.jj2
27 |
28 | When you refer to it in configuration section, here is the syntax::
29 |
30 | configuration:
31 | template_dir:
32 | - "https://raw.githubusercontent.com/moremoban/pypi-mobans/dev/templates/"
33 |
34 | .. warn::
35 |
36 | The trailing '/' must be there.
37 |
38 |
39 | Maintenance note
40 | --------------------------------------------------------------------------------
41 |
42 | To the maintainer, in order to eat the dog food. Please checkout pypi-mobans
43 | and run a http server inside local pypi-mobans folder.
44 |
45 | Then update moban's mobanfile to::
46 |
47 | configuration:
48 | template_dir:
49 | - "http://localhost:8000/templates/"
50 | - "http://localhost:8000/statics/"
51 | - ".moban.d"
52 |
53 | Then run `make update`
54 |
55 |
--------------------------------------------------------------------------------
/docs/level-24-files-over-http/config.yml:
--------------------------------------------------------------------------------
1 | overrides: data.yml
2 | level24: "files over http protocol"
3 |
--------------------------------------------------------------------------------
/docs/level-24-files-over-http/local/demo.txt.jj2:
--------------------------------------------------------------------------------
1 | {{name}}: {{level24}}
--------------------------------------------------------------------------------
/docs/level-25-delete-intermediate-targets/.moban.yaml:
--------------------------------------------------------------------------------
1 | targets:
2 | - intermediate.jj2: original.jj2
3 | - intermediate2.jj2: original.jj2
4 | - intermediate3.jj2: original.jj2
5 | - final: intermediate.jj2
6 | - output: what_ever_here_will_be_ignored
7 | template: intermediate.jj2
8 | template_type: delete
9 | - output: ''
10 | template: intermediate2.jj2
11 | - delete!: intermediate3.jj2
12 |
--------------------------------------------------------------------------------
/docs/level-25-delete-intermediate-targets/README.rst:
--------------------------------------------------------------------------------
1 | Level 25: delete intermediate targets
2 | ================================================================================
3 |
4 | Continue with level 22, we would like to delete intermediate files.
5 |
6 | .. note::
7 |
8 | What is intermediate targets? Simply they are the files moban generates
9 | but in the end those files are not really used.
10 |
11 |
12 | For safety reasons, we only delete intermediate targets. We are not allowing
13 | moban to delete any files in template folders and staic folder.
14 |
15 | Here is the short syntax::
16 |
17 | targets:
18 | - delete!: intermediate_file.jj2
19 |
20 | Here are the full syntax::
21 |
22 | targets:
23 | - output: what_ever_here_will_be_ignored
24 | template: intermediate.jj2
25 | template_type: delete
26 | - output: ''
27 | template: intermediate2.jj2
28 |
29 |
30 | Example mobanfile::
31 |
32 | targets:
33 | - intermediate.jj2: original.jj2
34 | - intermediate2.jj2: original.jj2
35 | - intermediate3.jj2: original.jj2
36 | - output: x
37 | template: intermediate.jj2
38 | template_type: delete
39 | - output: ''
40 | template: intermediate2.jj2
41 | - delete!: intermediate3.jj2
42 |
43 |
--------------------------------------------------------------------------------
/docs/level-25-delete-intermediate-targets/data.yml:
--------------------------------------------------------------------------------
1 | hello: world
2 |
--------------------------------------------------------------------------------
/docs/level-25-delete-intermediate-targets/original.jj2:
--------------------------------------------------------------------------------
1 | a {{hello}}
2 |
--------------------------------------------------------------------------------
/docs/level-26-strip-rendered-content/.moban.yaml:
--------------------------------------------------------------------------------
1 | targets:
2 | - intermediate.strip: content_with_lots_of_white_spaces.jj2
3 | - final: intermediate.strip
4 | - delete!: intermediate.strip
5 |
--------------------------------------------------------------------------------
/docs/level-26-strip-rendered-content/README.rst:
--------------------------------------------------------------------------------
1 | Level 26: Strip the white spaces
2 | ================================================================================
3 |
4 | It was requested, a long time ago, to be able to strip the white spaces
5 | before and after the rendered content. Due to these factors:
6 |
7 | 1. templating order needs to be respected first
8 | 2. intermediate targets(moban generated files) can be allowed as template
9 | 3. and delete the intermediate file
10 |
11 | Now, all three factors are now supported. Hence, 'strip' feature can be
12 | rolled out.
13 |
14 | Here is the short syntax::
15 |
16 | targets:
17 | - final: intermediate_file.strip
18 |
19 | Here are the full syntax::
20 |
21 | targets:
22 | - output: final
23 | template: intermediate_file.what_ever
24 | template_type: strip
25 |
26 |
27 | Example mobanfile::
28 |
29 | targets:
30 | - intermediate.strip: content_with_lots_of_white_spaces.jj2
31 | - final: intermediate.strip
32 | - delete!: intermediate.strip
33 |
34 |
--------------------------------------------------------------------------------
/docs/level-26-strip-rendered-content/content_with_lots_of_white_spaces.jj2:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | a {{hello}}
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/docs/level-26-strip-rendered-content/data.yml:
--------------------------------------------------------------------------------
1 | hello: world
2 |
--------------------------------------------------------------------------------
/docs/level-3-data-override/.moban.cd/data.base.yaml:
--------------------------------------------------------------------------------
1 | nihao: shijie
2 | hello: shijie
3 |
--------------------------------------------------------------------------------
/docs/level-3-data-override/.moban.td/base.jj2:
--------------------------------------------------------------------------------
1 | {%block header %}
2 | {%endblock%}
3 |
4 | {{hello}}
5 |
6 | {{nihao}}
7 |
8 | {%block footer %}
9 | {%endblock%}
--------------------------------------------------------------------------------
/docs/level-3-data-override/README.rst:
--------------------------------------------------------------------------------
1 | Level 3: data override
2 | ================================================================================
3 |
4 | What `moban` brings on the table is data inheritance by introducing `overrides`
5 | key word in the yaml file::
6 |
7 | overrides: data.base.yaml
8 | ....
9 |
10 | And `.moban.cd` is the default directory where the base data file can be placed.
11 |
12 |
13 | Evaluation
14 | --------------------------------------------------------------------------------
15 |
16 | Please change directory to `docs/level-3-data-override` directory.
17 |
18 | In this example, `data.yaml` overrides `.moban.cd/data.base.yaml`, here is the
19 | command to launch it:
20 |
21 | .. code-block:: bash
22 |
23 | moban -c data.yaml -t a.template
24 |
25 | 'moban.output' is the generated file::
26 |
27 | ========header============
28 |
29 | world
30 |
31 | shijie
32 |
33 | ========footer============
34 |
35 |
36 | New development
37 | --------------------------------------------------------------------------------
38 |
39 | Since verison 0.6.0, `overrides` syntax support two more use cases:
40 |
41 | 1 override more than one configuration file
42 | *********************************************
43 |
44 | For example::
45 |
46 | overrides:
47 | - config-file-a.yaml
48 | - config-file-b.yaml
49 |
50 | 2 override more than one configuration file
51 | ********************************************
52 |
53 | For example::
54 |
55 | overrides:
56 | - config-file-a.yaml: keya
57 | - config-file-b.yaml: keyb
58 |
--------------------------------------------------------------------------------
/docs/level-3-data-override/a.template:
--------------------------------------------------------------------------------
1 | {%extends 'base.jj2' %}
2 |
3 | {%block header %}
4 | ========header============
5 | {%endblock%}
6 |
7 | {%block footer %}
8 | ========footer============
9 | {%endblock%}
10 |
--------------------------------------------------------------------------------
/docs/level-3-data-override/data.yml:
--------------------------------------------------------------------------------
1 | overrides: data.base.yaml
2 | hello: world
3 |
--------------------------------------------------------------------------------
/docs/level-4-single-command/.moban.cd/data.base.yaml:
--------------------------------------------------------------------------------
1 | nihao: shijie
2 | hello: shijie
3 |
--------------------------------------------------------------------------------
/docs/level-4-single-command/.moban.td/base.jj2:
--------------------------------------------------------------------------------
1 | {%block header %}
2 | {%endblock%}
3 |
4 | {{hello}}
5 |
6 | {{nihao}}
7 |
8 | {%block footer %}
9 | {%endblock%}
--------------------------------------------------------------------------------
/docs/level-4-single-command/.moban.yml:
--------------------------------------------------------------------------------
1 | targets:
2 | - a.output: a.template.jj2
3 |
--------------------------------------------------------------------------------
/docs/level-4-single-command/README.rst:
--------------------------------------------------------------------------------
1 | Level 4: single command
2 | ================================================================================
3 |
4 | If you use moban regularly and operates over a number of files, you may consider
5 | write a `.moban.yml`, which is a mini script file that commands `moban` to
6 | iterate through a number of files
7 |
8 |
9 | Evaluation
10 | --------------------------------------------------------------------------------
11 |
12 | Please go to `docs/level-4-single-command` directory.
13 |
14 |
15 | Here is the `.moban.yml`, which replaces the command in level 3::
16 |
17 | targets:
18 | - a.output: a.template
19 |
20 |
21 | where `targets` should lead an array of dictionaries.
22 |
23 | Here is how to launch it
24 | .. code-block:: bash
25 |
26 | moban
27 |
28 | 'a.output' is the generated file::
29 |
30 | ========header============
31 |
32 | world
33 |
34 | shijie
35 |
36 | ========footer============
37 |
--------------------------------------------------------------------------------
/docs/level-4-single-command/a.template.jj2:
--------------------------------------------------------------------------------
1 | {%extends 'base.jj2' %}
2 |
3 | {%block header %}
4 | ========header============
5 | {%endblock%}
6 |
7 | {%block footer %}
8 | ========footer============
9 | {%endblock%}
10 |
--------------------------------------------------------------------------------
/docs/level-4-single-command/data.yml:
--------------------------------------------------------------------------------
1 | overrides: data.base.yaml
2 | hello: world
3 |
--------------------------------------------------------------------------------
/docs/level-5-custom-configuration/.moban.yml:
--------------------------------------------------------------------------------
1 | configuration:
2 | configuration_dir: 'custom-config'
3 | template_dir:
4 | - custom-templates
5 | - cool-templates
6 | - '.'
7 | targets:
8 | - a.output: a.template.jj2
9 |
--------------------------------------------------------------------------------
/docs/level-5-custom-configuration/README.rst:
--------------------------------------------------------------------------------
1 | Level 5: custom configuration
2 | ================================================================================
3 |
4 | With `.moban.yml`, you can even change default data directory `.moban.cd` and
5 | default template directory `.moan.td`. Read this example::
6 |
7 | configuration:
8 | configuration_dir: 'custom-config'
9 | template_dir:
10 | - custom-templates
11 | - cool-templates
12 | - '.'
13 | targets:
14 | - a.output: a.template
15 |
16 |
17 | where `configuration` lead a dictionary of key words:
18 |
19 | #. `configuration_dir` - the new configuration directory
20 | #. `template_dir` - an array of template directories
21 |
22 |
23 | Evaluation
24 | --------------------------------------------------------------------------------
25 |
26 | Please go to `docs/level-5-custom-configuration` directory.
27 |
28 |
29 | Here is the command to launch it:
30 |
31 | .. code-block:: bash
32 |
33 | moban
34 |
35 | 'a.output' is the generated file::
36 |
37 | ========header============
38 |
39 | world
40 |
41 | shijie
42 |
43 | this demonstrations jinja2's include statement
44 |
45 | ========footer============
46 |
--------------------------------------------------------------------------------
/docs/level-5-custom-configuration/cool-templates/base.jj2:
--------------------------------------------------------------------------------
1 | {%block header %}
2 | {%endblock%}
3 |
4 | {{hello}}
5 |
6 | {{nihao}}
7 |
8 | {%include 'cool.template'%}
9 |
10 | {%block footer %}
11 | {%endblock%}
--------------------------------------------------------------------------------
/docs/level-5-custom-configuration/cool-templates/cool.template:
--------------------------------------------------------------------------------
1 | this demonstrates jinja2's include statement
2 |
--------------------------------------------------------------------------------
/docs/level-5-custom-configuration/custom-config/data.base.yaml:
--------------------------------------------------------------------------------
1 | nihao: shijie
2 | hello: shijie
3 |
--------------------------------------------------------------------------------
/docs/level-5-custom-configuration/custom-templates/a.template.jj2:
--------------------------------------------------------------------------------
1 | {%extends 'base.jj2' %}
2 |
3 | {%block header %}
4 | ========header============
5 | {%endblock%}
6 |
7 | {%block footer %}
8 | ========footer============
9 | {%endblock%}
10 |
--------------------------------------------------------------------------------
/docs/level-5-custom-configuration/data.yml:
--------------------------------------------------------------------------------
1 | overrides: data.base.yaml
2 | hello: world
3 |
--------------------------------------------------------------------------------
/docs/level-6-complex-configuration/.moban.yml:
--------------------------------------------------------------------------------
1 | configuration:
2 | configuration_dir: 'custom-config'
3 | template_dir:
4 | - custom-templates
5 | - cool-templates
6 | - '.'
7 | template: a.template.jj2
8 | targets:
9 | - output: a.output
10 | configuration: data.yml
11 | - output: a.output2
12 | configuration: data2.yml
13 |
--------------------------------------------------------------------------------
/docs/level-6-complex-configuration/README.rst:
--------------------------------------------------------------------------------
1 | Level 6: Complex Configuration
2 | ================================================================================
3 |
4 | On top of level 5, you could have a common template, where data and output change.
5 | In the following example::
6 |
7 | configuration:
8 | configuration_dir: 'custom-config'
9 | template_dir:
10 | - custom-templates
11 | - cool-templates
12 | - '.'
13 | template: a.template
14 | targets:
15 | - output: a.output
16 | configuration: data.yml
17 | - output: a.output2
18 | configuration: data2.yml
19 |
20 | where `template` under `confiugration` needs a template file, which will be a
21 | default template across `targets`. And in this example, the expand form of
22 | `targets` is illustrated:
23 |
24 | {
25 | "output": 'an output file',
26 | "configuration": 'data file',
27 | "template": "the template file"
28 | }
29 |
30 | .. warning::
31 |
32 | `a.template` could be a symbolic link on Unix/Linux. It will not work if you
33 | template
34 | `a symbolic link on Windows `_.
35 | Use symbolic link at your own calculated risk.
36 |
37 |
38 | Evaluation
39 | --------------------------------------------------------------------------------
40 |
41 | Please go to `docs/level-6-complex-configuration` directory.
42 |
43 | Here is the command to launch it:
44 |
45 | .. code-block:: bash
46 |
47 | moban
48 |
49 | 'a.output' is the generated file::
50 |
51 | ========header============
52 |
53 | world
54 |
55 | shijie
56 |
57 | this demonstrations jinja2's include statement
58 |
59 | ========footer============
60 |
61 | `a.output2` is::
62 |
63 | ========header============
64 |
65 | world2
66 |
67 | shijie
68 |
69 | this demonstrations jinja2's include statement
70 |
71 | ========footer============
72 |
--------------------------------------------------------------------------------
/docs/level-6-complex-configuration/cool-templates/base.jj2:
--------------------------------------------------------------------------------
1 | {%block header %}
2 | {%endblock%}
3 |
4 | {{hello}}
5 |
6 | {{nihao}}
7 |
8 | {%include 'cool.template.jj2'%}
9 |
10 | {%block footer %}
11 | {%endblock%}
12 |
--------------------------------------------------------------------------------
/docs/level-6-complex-configuration/cool-templates/cool.template.jj2:
--------------------------------------------------------------------------------
1 | this demonstrates jinja2's include statement
2 |
--------------------------------------------------------------------------------
/docs/level-6-complex-configuration/custom-config/data.base.yaml:
--------------------------------------------------------------------------------
1 | nihao: shijie
2 | hello: shijie
3 |
--------------------------------------------------------------------------------
/docs/level-6-complex-configuration/custom-templates/a.template.jj2:
--------------------------------------------------------------------------------
1 | {%extends 'base.jj2' %}
2 |
3 | {%block header %}
4 | ========header============
5 | {%endblock%}
6 |
7 | {%block footer %}
8 | ========footer============
9 | {%endblock%}
10 |
--------------------------------------------------------------------------------
/docs/level-6-complex-configuration/data.yml:
--------------------------------------------------------------------------------
1 | overrides: data.base.yaml
2 | hello: world
3 |
--------------------------------------------------------------------------------
/docs/level-6-complex-configuration/data2.yml:
--------------------------------------------------------------------------------
1 | overrides: data.base.yaml
2 | hello: world2
3 |
--------------------------------------------------------------------------------
/docs/level-7-use-custom-jinja2-filter-test-n-global/.moban.yml:
--------------------------------------------------------------------------------
1 | configuration:
2 | template_dir:
3 | - my-templates
4 | plugin_dir:
5 | - custom-jj2-plugin
6 | configuration: data.yml
7 | targets:
8 | - filter.output: filter.jj2
9 | - test.output: test.jj2
10 | - global.output: global.jj2
11 |
--------------------------------------------------------------------------------
/docs/level-7-use-custom-jinja2-filter-test-n-global/README.rst:
--------------------------------------------------------------------------------
1 | Level 7: Custom jinja filters, tests and globals
2 | ================================================================================
3 |
4 | Level 7 example demonstrates advanced plugin capabilities of moban. The following
5 | moban file had `plugin_dir` specified::
6 |
7 | configuration:
8 | template_dir:
9 | - my-templates
10 | plugin_dir:
11 | - custom-jj2-plugin
12 | configuration: data.yml
13 | targets:
14 | - filter.output: filter.jj2
15 | - test.output: test.jj2
16 |
17 | Where `custom-jj2-plugin` is a directory holding all jinja2 filters, tests
18 | and globals. Under it, there are 4 files::
19 |
20 | __init__.py filter.py test.py global.py
21 |
22 | It is very important to have `__init__.py`, otherwise, it will NOT work. Other three
23 | files are named to show case the feature. You can choose whichever name you prefer,
24 | as long as you and your team could make sense of the names.
25 |
26 | .. note::
27 |
28 | if you intend to use extensions for one off usage, please use '-pd' cli option.
29 | i.e. `moban -td my-templates/ -t filter.jj2 -pd custom-jj2-plugin`
30 |
31 | Evaluation
32 | --------------------------------------------------------------------------------
33 |
34 | Please go to `docs/level-7-use-custom-jinja2-filter-test-n-global` directory,
35 |
36 | Here is the command to launch it:
37 |
38 | .. code-block:: bash
39 |
40 | $ moban
41 | Templating filter.jj2 to filter.output
42 | Templating test.jj2 to test.output
43 | Templating global.jj2 to global.output
44 | Templated 3 files.
45 | Everything is up to date!
46 |
47 | Please examine individual template and its associated plugin for more details.
48 |
--------------------------------------------------------------------------------
/docs/level-7-use-custom-jinja2-filter-test-n-global/custom-jj2-plugin/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/moremoban/moban/11306525140a16f8b3231e41c73c48b3cfa4b333/docs/level-7-use-custom-jinja2-filter-test-n-global/custom-jj2-plugin/__init__.py
--------------------------------------------------------------------------------
/docs/level-7-use-custom-jinja2-filter-test-n-global/custom-jj2-plugin/filter.py:
--------------------------------------------------------------------------------
1 | import sys
2 | import base64
3 |
4 | from moban.plugins.jinja2.extensions import JinjaFilter
5 |
6 |
7 | @JinjaFilter()
8 | def base64encode(string):
9 | if sys.version_info[0] > 2:
10 | content = base64.b64encode(string.encode('utf-8'))
11 | content = content.decode('utf-8')
12 | else:
13 | content = base64.b64encode(string)
14 | return content
15 |
--------------------------------------------------------------------------------
/docs/level-7-use-custom-jinja2-filter-test-n-global/custom-jj2-plugin/global.py:
--------------------------------------------------------------------------------
1 | from moban.plugins.jinja2.extensions import jinja_global
2 |
3 | jinja_global('global', dict(hello='world'))
4 |
--------------------------------------------------------------------------------
/docs/level-7-use-custom-jinja2-filter-test-n-global/custom-jj2-plugin/test.py:
--------------------------------------------------------------------------------
1 | from moban.plugins.jinja2.extensions import JinjaTest
2 |
3 |
4 | @JinjaTest()
5 | def level7(value):
6 | return value == 'level7'
7 |
--------------------------------------------------------------------------------
/docs/level-7-use-custom-jinja2-filter-test-n-global/data.yml:
--------------------------------------------------------------------------------
1 | level: level7
2 | level8: level8
3 |
--------------------------------------------------------------------------------
/docs/level-7-use-custom-jinja2-filter-test-n-global/global.output:
--------------------------------------------------------------------------------
1 | world
--------------------------------------------------------------------------------
/docs/level-7-use-custom-jinja2-filter-test-n-global/my-templates/filter.jj2:
--------------------------------------------------------------------------------
1 | {{ 'abc' | base64encode }}
2 |
--------------------------------------------------------------------------------
/docs/level-7-use-custom-jinja2-filter-test-n-global/my-templates/global.jj2:
--------------------------------------------------------------------------------
1 | {{ global.hello }}
--------------------------------------------------------------------------------
/docs/level-7-use-custom-jinja2-filter-test-n-global/my-templates/test.jj2:
--------------------------------------------------------------------------------
1 | {% if level is level7%}
2 | Hello, you are in level 7 example
3 | {% else %}
4 | Hello, you are not in {{level}}
5 | {% endif %}
6 |
7 | {% if level8 is level7%}
8 | Hello, you are in level 7 example
9 | {% else %}
10 | Hello, you are not in level 7
11 | {% endif %}
12 |
--------------------------------------------------------------------------------
/docs/level-8-pass-a-folder-full-of-templates/.moban.yml:
--------------------------------------------------------------------------------
1 | configuration:
2 | configuration_dir: 'config'
3 | configuration: level8.yml
4 | template_dir:
5 | - template-folder
6 | targets:
7 | - templated-folder: templates
8 |
--------------------------------------------------------------------------------
/docs/level-8-pass-a-folder-full-of-templates/README.rst:
--------------------------------------------------------------------------------
1 | Level 8: Pass a folder full of templates
2 | ================================================================================
3 |
4 | We already know that in moban file, you can pass
5 | on a dictionary in targets section, and it apply the template. The assumption
6 | was that the template parameter is a file. Now, what if the parameter is a
7 | directory?
8 |
9 | When you pass a directory with full of templates, moban will also assume the
10 | target is a directory and will generate the output there. When saving the
11 | files, it will remove its file suffices automatically.
12 |
--------------------------------------------------------------------------------
/docs/level-8-pass-a-folder-full-of-templates/config/level8.yml:
--------------------------------------------------------------------------------
1 | a: "test"
2 |
--------------------------------------------------------------------------------
/docs/level-8-pass-a-folder-full-of-templates/template-folder/show_sub_folder_on_windows.txt:
--------------------------------------------------------------------------------
1 | please
--------------------------------------------------------------------------------
/docs/level-8-pass-a-folder-full-of-templates/template-folder/templates/my.jj2:
--------------------------------------------------------------------------------
1 | it is a {{a}}
2 |
--------------------------------------------------------------------------------
/docs/level-9-moban-dependency-as-pypi-package/.moban.yml:
--------------------------------------------------------------------------------
1 | configuration:
2 | template_dir:
3 | - "pypi://pypi-mobans-pkg/resources/templates"
4 | - local
5 | configuration: config.yml
6 | configuration_dir: "pypi://pypi-mobans-pkg/resources/config"
7 | targets:
8 | - mytravis.yml: travis.yml.jj2
9 | - test.txt: demo.txt.jj2
10 |
--------------------------------------------------------------------------------
/docs/level-9-moban-dependency-as-pypi-package/README.rst:
--------------------------------------------------------------------------------
1 | level 9: moban dependency as pypi package
2 | ================================================================================
3 |
4 | .. note::
5 |
6 | You will need to install pypifs
7 |
8 | Why not enable template reuse? Once a template is written somewhere by somebody,
9 | as long as it is good and useful, it is always to reuse it, isn't it? DRY
10 | principle kicks in.
11 |
12 | Now with moban, it is possible to package up your mobans/templates
13 | into a pypi package and distribute it to the world of moban.
14 |
15 |
16 | Here are the sample file::
17 |
18 | configuration:
19 | template_dir:
20 | - "pypi://pypi-mobans-pkg/resources/templates"
21 | configuration: config.yml
22 | configuration_dir: "pypi://pypi-mobans-pkg/config"
23 | targets:
24 | - mytravis.yml: travis.yml.jj2
25 | - test.txt: demo.txt.jj2
26 |
27 | When you refer to it in configuration section, here is the syntax::
28 |
29 | configuration:
30 | - template_dir:
31 | - "pypi://python-package-name/relative-folder-inside-the-package"
32 |
33 | Note: when you do not have relative directory::
34 |
35 | configuration:
36 | template_dir:
37 | - "pypi://python-package-name"
38 |
--------------------------------------------------------------------------------
/docs/level-9-moban-dependency-as-pypi-package/config.yml:
--------------------------------------------------------------------------------
1 | overrides: data.yml
2 | level9: "moban dependency as pypi package"
3 |
--------------------------------------------------------------------------------
/docs/level-9-moban-dependency-as-pypi-package/local/demo.txt.jj2:
--------------------------------------------------------------------------------
1 | {{name}}: {{level9}}
--------------------------------------------------------------------------------
/docs/migration-notes.rst:
--------------------------------------------------------------------------------
1 | Migrate to 0.8.x
2 | ================================================================================
3 |
4 | In version 0.8.0, `moban.plugins.jinja2.tests.files` is moved to moban-ansible
5 | package. `moban.plugins.jinja2.filters.github` is moved to moban-jinja2-github
6 | package Please install them for backward compatibility.
7 |
8 |
9 | Migrate to 0.7.x
10 | ================================================================================
11 |
12 | From 2020 onwards, minimum requirement is Python 3.6
13 |
14 |
15 | For existing moban users, python 2 support has been dropped. Please stay with
16 | versions lower than 0.7.0 if you are still using python 2.
17 |
18 | Migrate to 0.6.x
19 | ================================================================================
20 |
21 | It has been noticed that, this version will fail to template but do a copy, in
22 | the following situation::
23 |
24 | targets:
25 | index.rst: index.rst
26 |
27 | Please note that 0.6.x changed its behavior to do a copy instead of templating.
28 |
--------------------------------------------------------------------------------
/docs/misc-1-copying-templates/.moban.yml:
--------------------------------------------------------------------------------
1 | configuration:
2 | template_dir:
3 | - template-sources
4 | copy:
5 | - simple.file.copy: file-in-template-sources-folder.txt
6 | - "misc-1-copying/can-create-folder/if-not-exists.txt":
7 | file-in-template-sources-folder.txt
8 | - "test-dir": dir-for-copying
9 | - "test-recursive-dir": dir-for-recusive-copying/**
10 |
--------------------------------------------------------------------------------
/docs/misc-1-copying-templates/README.rst:
--------------------------------------------------------------------------------
1 | Misc 1: copying templates - Deprecated since version 0.4.0
2 | ================================================================================
3 |
4 | .. warning::
5 | This chapter is kept for regression testing. If you have moban v0.4.0 or
6 | above, please do not use the syntax here
7 |
8 | With `.moban.yml`, you can copy templates to your destination. Please be
9 | aware that it is not the same as 'cp', 'copy' commands you have experienced.
10 |
11 |
12 | Please be aware that, your templates and template folder have to be inside
13 | declared template folders. It does not copy any file or folder.
14 |
15 |
16 | Here is example moban file for copying::
17 |
18 | configuration:
19 | template_dir:
20 | - template-sources
21 | copy:
22 | - simple.file.copy: file-in-template-sources-folder.txt
23 | - "misc-1-copying/can-create-folder/if-not-exists.txt": file-in-template-sources-folder.txt
24 | - "test-dir": dir-for-copying
25 | - "test-recursive-dir": dir-for-recusive-copying/**
26 |
27 |
28 | template copy does:
29 |
30 |
31 | #. copies any template inside pre-declared template directory to anywhere. moban will create directory if needed.
32 | #. copies any directory to anywhere. If "**" is followed, moban attempts to do recursive copying.
33 |
--------------------------------------------------------------------------------
/docs/misc-1-copying-templates/template-sources/dir-for-copying/afile.txt:
--------------------------------------------------------------------------------
1 | dir for copying
2 |
--------------------------------------------------------------------------------
/docs/misc-1-copying-templates/template-sources/dir-for-copying/sub_directory_is_not_copied/becuase_star_star_is_needed.txt:
--------------------------------------------------------------------------------
1 | Please look at .moban.yml
2 |
--------------------------------------------------------------------------------
/docs/misc-1-copying-templates/template-sources/dir-for-recusive-copying/fileb.txt:
--------------------------------------------------------------------------------
1 | everything is copied
2 |
--------------------------------------------------------------------------------
/docs/misc-1-copying-templates/template-sources/dir-for-recusive-copying/sub_directory_is_copied/because_star_star_is_specified.txt:
--------------------------------------------------------------------------------
1 | dest_directory: source_directory/**
2 |
--------------------------------------------------------------------------------
/docs/misc-1-copying-templates/template-sources/file-in-template-sources-folder.txt:
--------------------------------------------------------------------------------
1 | test file
2 |
--------------------------------------------------------------------------------
/docs/trouble-shooting-guide.rst:
--------------------------------------------------------------------------------
1 | Trouble shooting guide
2 | ==========================
3 |
4 | 1. Why a file was not templated but copied instead?
5 |
6 | It has been coded so that template engine can choose to pass on the template if it failed to handle. Moban will take over
7 | and use default 'copy' action.
8 |
9 | In order to find out what went wrong, you can use '-vvv' to enable all logs to assist you.
10 |
--------------------------------------------------------------------------------
/format.sh:
--------------------------------------------------------------------------------
1 | isort $(find moban -name "*.py"|xargs echo) $(find tests -name "*.py"|xargs echo)
2 | black -l 79 moban
3 | black -l 79 tests
4 |
--------------------------------------------------------------------------------
/lint.sh:
--------------------------------------------------------------------------------
1 | flake8 --max-line-length=88 --exclude=.moban.d,docs --ignore=W503,W504,E302,E712,F401
2 | python setup.py checkdocs
3 |
--------------------------------------------------------------------------------
/min_requirements.txt:
--------------------------------------------------------------------------------
1 | ruamel.yaml==0.15.5;python_version == '3.6'
2 | ruamel.yaml==0.15.42;python_version == '3.7'
3 | ruamel.yaml==0.15.98;python_version == '3.8'
4 | ruamel.yaml==0.15.98;python_version == '3.9'
5 | jinja2==2.7.1
6 | lml==0.0.9
7 | appdirs==1.4.3
8 | crayons== 0.1.0
9 | fs==2.4.11
10 | jinja2-fsloader==0.2.0
11 | moban-jinja2-github
12 |
--------------------------------------------------------------------------------
/moban/__init__.py:
--------------------------------------------------------------------------------
1 | from moban._version import __author__, __version__
2 |
3 | __all__ = ("__author__", "__version__")
4 |
--------------------------------------------------------------------------------
/moban/__main__.py:
--------------------------------------------------------------------------------
1 | from moban.main import main
2 |
3 | if __name__ == "__main__":
4 | main()
5 |
--------------------------------------------------------------------------------
/moban/_version.py:
--------------------------------------------------------------------------------
1 | __version__ = "0.8.2"
2 | __author__ = "C. W."
3 |
--------------------------------------------------------------------------------
/moban/core/__init__.py:
--------------------------------------------------------------------------------
1 | from moban.core.moban_factory import MobanFactory
2 |
3 | ENGINES = MobanFactory()
4 |
--------------------------------------------------------------------------------
/moban/core/content_processor.py:
--------------------------------------------------------------------------------
1 | import logging
2 |
3 | from lml.plugin import PluginInfo
4 |
5 | from moban import constants
6 |
7 | LOG = logging.getLogger(__name__)
8 |
9 |
10 | class ContentProcessor(PluginInfo):
11 | """
12 | @ContentProcessor('strip', 'Stripping', 'Stripped'):
13 | def strip(template_file: str) -> str:
14 | ret = template_file.strip()
15 | return ret
16 | """
17 |
18 | def __init__(self, action, action_continuing_tense, action_past_tense):
19 | super(ContentProcessor, self).__init__(
20 | constants.TEMPLATE_ENGINE_EXTENSION, tags=[action]
21 | )
22 | self.action = action
23 | self.action_continuing_tense = action_continuing_tense
24 | self.action_past_tense = action_continuing_tense
25 |
26 | def __call__(self, a_content_processor_function):
27 | continuing_tense = self.action_continuing_tense
28 | past_tense = self.action_past_tense
29 |
30 | class CustomEngine(object):
31 | ACTION_IN_PRESENT_CONTINUOUS_TENSE = continuing_tense
32 | ACTION_IN_PAST_TENSE = past_tense
33 |
34 | def __init__(self, template_fs, options=None):
35 | self.template_fs = template_fs
36 | self.options = options
37 |
38 | def get_template(self, template_file):
39 | content = self.template_fs.readbytes(template_file)
40 | return content
41 |
42 | def get_template_from_string(self, a_string):
43 | return a_string
44 |
45 | def apply_template(self, template, *_):
46 | ret = a_content_processor_function(template, self.options)
47 | return ret
48 |
49 | super(ContentProcessor, self).__call__(CustomEngine)
50 | return a_content_processor_function
51 |
--------------------------------------------------------------------------------
/moban/core/context.py:
--------------------------------------------------------------------------------
1 | import os
2 | import copy
3 | import logging
4 |
5 | from moban import constants, exceptions
6 | from moban.core import utils
7 | from moban.program_options import OPTIONS
8 | from moban.core.data_loader import merge, load_data
9 |
10 | LOG = logging.getLogger(__name__)
11 | MESSAGE_USING_ENV_VARS = "Attempting to use environment vars as data..."
12 |
13 |
14 | class Context(object):
15 | def __init__(self, context_dirs):
16 | utils.verify_the_existence_of_directories(context_dirs)
17 | self.context_dirs = context_dirs
18 | self.__cached_environ_variables = dict(
19 | (key, os.environ[key]) for key in os.environ
20 | )
21 |
22 | def get_data(self, file_name):
23 | custom_data = copy.deepcopy(OPTIONS[constants.CLI_DICT])
24 | try:
25 | data = load_data(self.context_dirs, file_name)
26 | merge(custom_data, data)
27 | merge(custom_data, self.__cached_environ_variables)
28 | return custom_data
29 | except (IOError, exceptions.IncorrectDataInput) as exception:
30 | # If data file doesn't exist:
31 | # 1. Alert the user of their (potential) mistake
32 | # 2. Attempt to use environment vars as data
33 | LOG.warn(str(exception))
34 | LOG.info(MESSAGE_USING_ENV_VARS)
35 | merge(custom_data, self.__cached_environ_variables)
36 | return custom_data
37 |
--------------------------------------------------------------------------------
/moban/core/data_loader.py:
--------------------------------------------------------------------------------
1 | import logging
2 | from collections import OrderedDict
3 |
4 | from lml.plugin import PluginManager
5 | from ruamel.yaml.comments import CommentedSeq
6 |
7 | from moban import constants
8 | from moban.externals import file_system
9 |
10 | LOG = logging.getLogger()
11 |
12 |
13 | class AnyDataLoader(PluginManager):
14 | def __init__(self):
15 | super(AnyDataLoader, self).__init__(constants.DATA_LOADER_EXTENSION)
16 |
17 | def get_data(self, file_name):
18 | file_extension = file_system.path_splitext(file_name)[1]
19 | file_type = file_extension
20 | if file_extension.startswith("."):
21 | file_type = file_type[1:]
22 |
23 | try:
24 | loader_function = self.load_me_now(file_type)
25 | except Exception:
26 | loader_function = self.load_me_now(constants.DEFAULT_DATA_TYPE)
27 | return loader_function(file_name)
28 |
29 |
30 | LOADER = AnyDataLoader()
31 |
32 |
33 | def load_data(base_dir, file_name):
34 | abs_file_path = search_file(base_dir, file_name)
35 | data = LOADER.get_data(abs_file_path)
36 | if data is not None:
37 | parent_data = OrderedDict()
38 | if constants.LABEL_OVERRIDES in data:
39 | overrides = data.pop(constants.LABEL_OVERRIDES)
40 | if not isinstance(overrides, list):
41 | overrides = [overrides]
42 | for parent_file in overrides:
43 | file_name, key = parent_file, None
44 | results = match_fs_url(parent_file)
45 | if results:
46 | file_name, key = results
47 | elif ":" in parent_file and "://" not in parent_file:
48 | file_name, key = parent_file.split(":")
49 | try:
50 | child_data = load_data(base_dir, file_name)
51 | except IOError:
52 | LOG.warn(f"failed to load {file_name} in {base_dir}")
53 | child_data = {}
54 | if data:
55 | if key:
56 | child_data = OrderedDict({key: child_data[key]})
57 | parent_data = merge(parent_data, child_data)
58 | if parent_data:
59 | return merge(data, parent_data)
60 | else:
61 | return data
62 | else:
63 | return None
64 |
65 |
66 | def merge(left, right):
67 | """
68 | deep merge dictionary on the left with the one
69 | on the right.
70 |
71 | Fill in left dictionary with right one where
72 | the value of the key from the right one in
73 | the left one is missing or None.
74 | """
75 | if isinstance(left, dict) and isinstance(right, dict):
76 | for key, value in right.items():
77 | if key not in left:
78 | left[key] = value
79 | elif left[key] is None:
80 | left[key] = value
81 | else:
82 | left[key] = merge(left[key], value)
83 | else:
84 | both_list_alike = (
85 | isinstance(left, CommentedSeq) and isinstance(right, CommentedSeq)
86 | ) or (isinstance(left, list) and isinstance(right, list))
87 | if both_list_alike:
88 | left.extend(right)
89 | return left
90 |
91 |
92 | def search_file(base_dir, file_name):
93 | the_file = file_name
94 | if not file_system.exists(the_file):
95 | if base_dir:
96 | file_under_base_dir = file_system.url_join(base_dir, the_file)
97 | if file_system.exists(file_under_base_dir):
98 | the_file = file_system.fs_url(file_under_base_dir)
99 | else:
100 | raise IOError(
101 | constants.ERROR_DATA_FILE_NOT_FOUND % (file_name, the_file)
102 | )
103 | else:
104 | raise IOError(constants.ERROR_DATA_FILE_ABSENT % the_file)
105 | return the_file
106 |
107 |
108 | def match_fs_url(file_name):
109 | import re
110 |
111 | results = re.match("(.*://.*):(.*)", file_name)
112 | if results:
113 | return (results.group(1), results.group(2))
114 |
--------------------------------------------------------------------------------
/moban/core/definitions.py:
--------------------------------------------------------------------------------
1 | import logging
2 |
3 | from moban import constants
4 |
5 | LOG = logging.getLogger(__name__)
6 |
7 |
8 | class TemplateTarget(object):
9 | def __init__(
10 | self,
11 | template_file,
12 | data_file,
13 | output,
14 | template_type=constants.DEFAULT_TEMPLATE_TYPE,
15 | ):
16 | self.template_file = template_file
17 | self.data_file = data_file
18 | self.original_output = output
19 | self.template_type = template_type
20 | self.output = self.original_output
21 |
22 | self.set_template_type(template_type)
23 | LOG.info("create a target {}".format(self))
24 |
25 | def set_template_type(self, new_template_type):
26 | self.template_type = new_template_type
27 | if self.original_output.endswith(self.template_type):
28 | self.output = self.original_output.replace(
29 | "." + self.template_type, ""
30 | )
31 | else:
32 | self.output = self.original_output
33 |
34 | def __eq__(self, other):
35 | return (
36 | self.template_file == other.template_file
37 | and self.data_file == other.data_file
38 | and self.output == other.output
39 | and self.template_type == self.template_type
40 | )
41 |
42 | def __repr__(self):
43 | return "%s,%s,%s,%s" % (
44 | self.template_file,
45 | self.data_file,
46 | self.output,
47 | self.template_type,
48 | )
49 |
--------------------------------------------------------------------------------
/moban/core/hashstore.py:
--------------------------------------------------------------------------------
1 | import json
2 | import hashlib
3 |
4 | from moban import constants, exceptions
5 | from moban.externals import file_system
6 |
7 |
8 | class HashStore:
9 | IGNORE_CACHE_FILE = False
10 |
11 | def __init__(self):
12 | self.cache_file = constants.DEFAULT_MOBAN_CACHE_FILE
13 | if (
14 | file_system.exists(self.cache_file)
15 | and self.IGNORE_CACHE_FILE is False
16 | ):
17 | with file_system.open_file(self.cache_file) as f:
18 | self.hashes = json.load(f)
19 | else:
20 | self.hashes = {}
21 |
22 | def is_file_changed(self, file_name, file_content, source_template):
23 | changed, with_permission = self._is_source_updated(
24 | file_name, file_content, source_template
25 | )
26 |
27 | if changed is False:
28 | target_hash = get_file_hash(
29 | file_name, with_permission=with_permission
30 | )
31 | if target_hash != self.hashes[file_name]:
32 | changed = True
33 | return changed
34 |
35 | def _is_source_updated(self, file_name, file_content, source_template):
36 | changed = True
37 | content = file_content
38 | with_permission = True
39 | try:
40 | content = _mix(
41 | file_content,
42 | oct(file_system.file_permissions(source_template)),
43 | )
44 | except exceptions.NoPermissionsNeeded:
45 | # HttpFs does not have getsyspath
46 | # zip, tar have no permission
47 | # win32 does not work
48 | with_permission = False
49 | pass
50 | content_hash = get_hash(content)
51 | if file_system.exists(file_name):
52 | if file_name in self.hashes:
53 | if content_hash == self.hashes[file_name]:
54 | changed = False
55 | # else the dest file has not been created yet
56 | # so no need to get content hash at all
57 | if changed:
58 | self.hashes[file_name] = content_hash
59 |
60 | return changed, with_permission
61 |
62 | def save_hashes(self):
63 | with open(self.cache_file, "w") as f:
64 | json.dump(self.hashes, f)
65 |
66 |
67 | HASH_STORE = HashStore()
68 |
69 |
70 | def get_file_hash(afile, with_permission=True):
71 | content = file_system.read_bytes(afile)
72 | try:
73 | if with_permission:
74 | content = _mix(content, oct(file_system.file_permissions(afile)))
75 | except exceptions.NoPermissionsNeeded:
76 | # HttpFs does not have getsyspath
77 | # zip, tar have no permission
78 | # win32 does not work
79 | pass
80 | return get_hash(content)
81 |
82 |
83 | def get_hash(content):
84 | md5 = hashlib.md5()
85 | md5.update(content)
86 | return md5.digest().decode("latin1")
87 |
88 |
89 | def _mix(content, file_permissions_copy):
90 | file_permissions_copy = file_permissions_copy.encode("utf-8")
91 | return content + file_permissions_copy
92 |
--------------------------------------------------------------------------------
/moban/core/mobanfile/store.py:
--------------------------------------------------------------------------------
1 | class Store:
2 | def __init__(self):
3 | self.init()
4 |
5 | def add(self, target):
6 | self.targets.append(target)
7 | self.look_up_by_output[target.output] = target
8 |
9 | def init(self):
10 | self.targets = []
11 | self.look_up_by_output = {}
12 | self.intermediate_targets = []
13 |
14 |
15 | STORE = Store()
16 |
--------------------------------------------------------------------------------
/moban/core/mobanfile/templates.py:
--------------------------------------------------------------------------------
1 | import logging
2 |
3 | from moban import constants
4 | from moban.externals import reporter, file_system
5 | from .store import STORE
6 |
7 | LOG = logging.getLogger(__name__)
8 |
9 |
10 | def handle_template(template_file, output, template_dirs):
11 | LOG.info(f"handling {template_file}")
12 |
13 | template_file = template_file
14 | multi_fs = file_system.get_multi_fs(template_dirs)
15 | if template_file.endswith("**"):
16 | source_dir = template_file[:-3]
17 | _, fs = multi_fs.which(source_dir)
18 | if fs:
19 | yield from _listing_directory_files_recusively(
20 | fs, source_dir, output
21 | )
22 | else:
23 | reporter.report_error_message(f"{template_file} cannot be found")
24 | else:
25 | if STORE.look_up_by_output.get(template_file) is None:
26 | _, fs = multi_fs.which(template_file)
27 | if fs is None:
28 | reporter.report_error_message(
29 | f"{template_file} cannot be found"
30 | )
31 | elif fs.isdir(template_file):
32 | yield from _list_dir_files(fs, template_file, output)
33 | else:
34 | yield _create_a_single_target(template_file, output)
35 | else:
36 | # when template_file is not found, it means
37 | it_is_generated_by_moban = template_file
38 | STORE.intermediate_targets.append(it_is_generated_by_moban)
39 | yield _create_a_single_target(template_file, output)
40 |
41 |
42 | def _list_dir_files(fs, source, dest):
43 | for file_name in fs.listdir(source):
44 | # please note jinja2 does NOT like windows path
45 | # hence the following statement looks like cross platform
46 | # src_file_under_dir = fs.path.join(source, file_name)
47 | # but actually it breaks windows instead.
48 | src_file_under_dir = f"{source}/{file_name}"
49 | if fs.isfile(src_file_under_dir):
50 | dest_file_under_dir = f"{dest}/{file_name}"
51 | template_type = _get_template_type(src_file_under_dir)
52 | yield (src_file_under_dir, dest_file_under_dir, template_type)
53 |
54 |
55 | def _listing_directory_files_recusively(fs, source, dest):
56 | for file_name in fs.listdir(source):
57 | src_file_under_dir = f"{source}/{file_name}"
58 | dest_file_under_dir = f"{dest}/{file_name}"
59 | if fs.isfile(src_file_under_dir):
60 | template_type = _get_template_type(src_file_under_dir)
61 | yield (src_file_under_dir, dest_file_under_dir, template_type)
62 | elif fs.isdir(src_file_under_dir):
63 | yield from _listing_directory_files_recusively(
64 | fs, src_file_under_dir, dest_file_under_dir
65 | )
66 |
67 |
68 | def _create_a_single_target(template_file, output):
69 | if output == constants.TEMPLATE_DELETE + "!":
70 | template_type = constants.TEMPLATE_DELETE
71 | else:
72 | template_type = _get_template_type(template_file)
73 | # output.jj2: source.jj2 means 'copy'
74 | if template_type and output.endswith("." + template_type):
75 | LOG.info(
76 | f"template type switched to from {template_type} to "
77 | + constants.TEMPLATE_COPY
78 | )
79 | template_type = constants.TEMPLATE_COPY
80 | return (template_file, output, template_type)
81 |
82 |
83 | def _get_template_type(template_file):
84 | _, extension = file_system.path_splitext(template_file)
85 | if extension:
86 | template_type = extension[1:]
87 | else:
88 | template_type = None
89 | return template_type
90 |
--------------------------------------------------------------------------------
/moban/core/plugins.py:
--------------------------------------------------------------------------------
1 | from lml.loader import scan_plugins_regex
2 |
3 | from moban import constants
4 |
5 | BUILTIN_EXENSIONS = [
6 | "moban.plugins.jinja2.engine",
7 | "moban.plugins.yaml_loader",
8 | "moban.plugins.json_loader",
9 | "moban.plugins.copy",
10 | "moban.plugins.delete",
11 | "moban.plugins.strip",
12 | ]
13 |
14 |
15 | def make_sure_all_pkg_are_loaded():
16 | scan_plugins_regex(constants.MOBAN_ALL, "moban", None, BUILTIN_EXENSIONS)
17 |
--------------------------------------------------------------------------------
/moban/core/strategy.py:
--------------------------------------------------------------------------------
1 | from collections import defaultdict
2 |
3 | import moban.constants as constants
4 | import moban.exceptions as exceptions
5 |
6 |
7 | class Strategy(object):
8 | DATA_FIRST = 1
9 | TEMPLATE_FIRST = 2
10 |
11 | def __init__(self, array_of_param_tuple):
12 | self.data_file_index = defaultdict(list)
13 | self.template_file_index = defaultdict(list)
14 | self.tuples = array_of_param_tuple
15 |
16 | def process(self):
17 | for target in self.tuples:
18 | _append_to_array_item_to_dictionary_key(
19 | self.data_file_index,
20 | target.data_file,
21 | (target.template_file, target.output),
22 | )
23 | _append_to_array_item_to_dictionary_key(
24 | self.template_file_index,
25 | target.template_file,
26 | (target.data_file, target.output),
27 | )
28 |
29 | def what_to_do(self):
30 | choice = Strategy.DATA_FIRST
31 | if self.data_file_index == {}:
32 | choice = Strategy.TEMPLATE_FIRST
33 | elif self.template_file_index != {}:
34 | data_files = len(self.data_file_index)
35 | template_files = len(self.template_file_index)
36 | if data_files > template_files:
37 | choice = Strategy.TEMPLATE_FIRST
38 | return choice
39 |
40 |
41 | def _append_to_array_item_to_dictionary_key(adict, key, array_item):
42 | if array_item in adict[key]:
43 | raise exceptions.MobanfileGrammarException(
44 | constants.MESSAGE_SYNTAX_ERROR % (array_item, key)
45 | )
46 | else:
47 | adict[key].append(array_item)
48 |
--------------------------------------------------------------------------------
/moban/core/utils.py:
--------------------------------------------------------------------------------
1 | import os
2 | import re
3 | import sys
4 | import logging
5 |
6 | from lml.utils import do_import
7 |
8 | from moban import constants
9 | from moban.externals import file_system
10 |
11 | LOG = logging.getLogger(__name__)
12 |
13 |
14 | def verify_the_existence_of_directories(dirs):
15 | if not isinstance(dirs, list):
16 | dirs = [dirs]
17 |
18 | dirs = [
19 | directory
20 | for directory in dirs
21 | if not (
22 | constants.DEFAULT_CONFIGURATION_DIRNAME in directory
23 | or constants.DEFAULT_TEMPLATE_DIRNAME in directory
24 | )
25 | ]
26 | if file_system.exists(constants.DEFAULT_CONFIGURATION_DIRNAME):
27 | dirs.append(constants.DEFAULT_CONFIGURATION_DIRNAME)
28 |
29 | if file_system.exists(constants.DEFAULT_TEMPLATE_DIRNAME):
30 | dirs.append(constants.DEFAULT_TEMPLATE_DIRNAME)
31 | return dirs
32 |
33 |
34 | def handle_plugin_dirs(plugin_dirs):
35 | if plugin_dirs is None:
36 | return
37 | LOG.info("handling plugin dirs {}".format(",".join(plugin_dirs)))
38 | for plugin_dir in plugin_dirs:
39 | plugin_path = os.path.normcase(
40 | os.path.dirname(os.path.abspath(plugin_dir))
41 | )
42 | if plugin_path not in sys.path:
43 | sys.path.append(plugin_path)
44 | pysearchre = re.compile(".py$", re.IGNORECASE)
45 | pluginfiles = filter(pysearchre.search, os.listdir(plugin_dir))
46 | plugins = list(map(lambda fp: os.path.splitext(fp)[0], pluginfiles))
47 | for plugin in plugins:
48 | plugin_module = os.path.basename(plugin_dir) + "." + plugin
49 | do_import(plugin_module)
50 |
--------------------------------------------------------------------------------
/moban/deprecated/library.py:
--------------------------------------------------------------------------------
1 | from lml.plugin import PluginManager
2 |
3 | from moban import constants
4 |
5 |
6 | class LibraryManager(PluginManager):
7 | def __init__(self):
8 | super(LibraryManager, self).__init__(constants.LIBRARY_EXTENSION)
9 |
10 | def resource_path_of(self, library_name):
11 | library = self.get_a_plugin(library_name)
12 | return library.resources_path
13 |
14 |
15 | LIBRARIES = LibraryManager()
16 |
--------------------------------------------------------------------------------
/moban/deprecated/repo.py:
--------------------------------------------------------------------------------
1 | import sys
2 | import subprocess
3 |
4 | from moban import constants, exceptions
5 | from moban.externals import reporter, file_system
6 |
7 |
8 | def git_clone(requires):
9 | from git import Repo
10 |
11 | if sys.platform != "win32":
12 | # Unfortunately for windows user, the following function
13 | # needs shell=True, which expose security risk. I would
14 | # rather not to trade it with its marginal benefit
15 | make_sure_git_is_available()
16 |
17 | moban_home = get_moban_home()
18 | file_system.mkdir_p(moban_home)
19 |
20 | for require in requires:
21 | repo_name = get_repo_name(require.git_url)
22 | local_repo_folder = file_system.path_join(moban_home, repo_name)
23 | if file_system.exists(local_repo_folder):
24 | reporter.report_git_pull(repo_name)
25 | repo = Repo(local_repo_folder)
26 | repo.git.pull()
27 | if require.reference:
28 | repo.git.checkout(require.reference)
29 | elif require.branch:
30 | repo.git.checkout(require.branch)
31 | if require.submodule:
32 | reporter.report_info_message("updating submodule")
33 | repo.git.submodule("update")
34 | else:
35 | reporter.report_git_clone(require.git_url)
36 | repo = Repo.clone_from(
37 | require.git_url, local_repo_folder, **require.clone_params()
38 | )
39 | if require.submodule:
40 | reporter.report_info_message("checking out submodule")
41 | repo.git.submodule("update", "--init")
42 |
43 |
44 | def get_repo_name(repo_url):
45 | import giturlparse
46 | from giturlparse.parser import ParserError
47 |
48 | try:
49 | repo = giturlparse.parse(repo_url.rstrip("/"))
50 | return repo.name
51 | except ParserError:
52 | reporter.report_error_message(
53 | constants.MESSAGE_INVALID_GIT_URL % repo_url
54 | )
55 | raise
56 |
57 |
58 | def get_moban_home():
59 | from appdirs import user_cache_dir
60 |
61 | home_dir = user_cache_dir(appname=constants.PROGRAM_NAME)
62 | return file_system.path_join(home_dir, constants.MOBAN_REPOS_DIR_NAME)
63 |
64 |
65 | def make_sure_git_is_available():
66 | try:
67 | subprocess.check_output(["git", "--help"])
68 | except Exception:
69 | raise exceptions.NoGitCommand("Please install git command")
70 |
--------------------------------------------------------------------------------
/moban/exceptions.py:
--------------------------------------------------------------------------------
1 | class DirectoryNotFound(Exception):
2 | pass
3 |
4 |
5 | class FileNotFound(Exception):
6 | pass
7 |
8 |
9 | class NoThirdPartyEngine(Exception):
10 | pass
11 |
12 |
13 | class MobanfileGrammarException(Exception):
14 | pass
15 |
16 |
17 | class NoTemplate(Exception):
18 | pass
19 |
20 |
21 | class IncorrectDataInput(Exception):
22 | pass
23 |
24 |
25 | class GroupTargetNotFound(Exception):
26 | pass
27 |
28 |
29 | class NoGitCommand(Exception):
30 | pass
31 |
32 |
33 | class UnsupportedPyFS2Protocol(Exception):
34 | pass
35 |
36 |
37 | class NoPermissionsNeeded(Exception):
38 | pass
39 |
40 |
41 | class SingleHTTPURLConstraint(Exception):
42 | pass
43 |
44 |
45 | class PassOn(Exception):
46 | """
47 | Raised when template engine cannot do anything with the given template.
48 |
49 | i.e. given a png image :/
50 | """
51 |
52 | pass
53 |
--------------------------------------------------------------------------------
/moban/externals/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/moremoban/moban/11306525140a16f8b3231e41c73c48b3cfa4b333/moban/externals/__init__.py
--------------------------------------------------------------------------------
/moban/externals/buffered_writer.py:
--------------------------------------------------------------------------------
1 | import os
2 |
3 | import fs
4 | import fs.path
5 |
6 | from moban.externals import file_system
7 |
8 |
9 | class BufferedWriter(object):
10 | def __init__(self):
11 | self.fs_list = {}
12 |
13 | def write_file_out(self, filename, content):
14 | if filename == "-":
15 | print(content.decode())
16 | elif file_system.is_zip_alike_url(filename):
17 | self.write_file_out_to_zip(filename, content)
18 | else:
19 | write_file_out(filename, content)
20 |
21 | def write_file_out_to_zip(self, filename, content):
22 | zip_file, file_name = file_system.url_split(filename)
23 | if zip_file not in self.fs_list:
24 | self.fs_list[zip_file] = fs.open_fs(zip_file, create=True)
25 | base_dirs = fs.path.dirname(file_name)
26 | if not self.fs_list[zip_file].exists(base_dirs):
27 | self.fs_list[zip_file].makedirs(base_dirs)
28 | self.fs_list[zip_file].writebytes(file_name, content)
29 |
30 | def close(self):
31 | for fsx in self.fs_list.values():
32 | fsx.close()
33 |
34 |
35 | def write_file_out(filename, content):
36 | if not file_system.is_zip_alike_url(filename):
37 | dest_folder = os.path.dirname(filename)
38 | if dest_folder:
39 | file_system.mkdir_p(dest_folder)
40 |
41 | file_system.write_bytes(filename, content)
42 |
--------------------------------------------------------------------------------
/moban/externals/reporter.py:
--------------------------------------------------------------------------------
1 | import sys
2 |
3 | import crayons
4 |
5 | import moban.constants as constants
6 |
7 | MESSAGE_TEMPLATING = "{0} {1} to {2}"
8 | MESSAGE_UP_TO_DATE = "Everything is up to date!"
9 | MESSAGE_NO_TEMPLATING = "No actions performed"
10 | MESSAGE_REPORT = "{0} {1} out of {2} files."
11 | MESSAGE_TEMPLATED_ALL = "{0} {1} files."
12 | MESSAGE_PULLING_REPO = "Updating {0}..."
13 | MESSAGE_CLONING_REPO = "Cloning {0}..."
14 | MESSAGE_TEMPLATE_NOT_IN_MOBAN_FILE = "{0} is not defined in your moban file!"
15 | MESSAGE_FILE_EXTENSION_NOT_NEEDED = "File extension is not required for ad-hoc\
16 | type"
17 |
18 | GLOBAL = {"PRINT": True}
19 |
20 |
21 | def report_templating(
22 | action_in_present_continuous_tense, source_file, destination_file
23 | ):
24 | if destination_file:
25 | do_print(
26 | MESSAGE_TEMPLATING.format(
27 | action_in_present_continuous_tense,
28 | crayons.yellow(source_file),
29 | crayons.green(destination_file),
30 | )
31 | )
32 | else:
33 | do_print(
34 | f"{action_in_present_continuous_tense} {crayons.yellow(source_file)}"
35 | )
36 |
37 |
38 | def report_no_action():
39 | do_print(crayons.yellow(MESSAGE_NO_TEMPLATING, bold=True))
40 |
41 |
42 | def report_full_run(action_in_past_tense, file_count):
43 | figure = crayons.green(str(file_count), bold=True)
44 | message = MESSAGE_TEMPLATED_ALL.format(action_in_past_tense, figure)
45 | do_print(_format_single(message, file_count))
46 |
47 |
48 | def report_partial_run(action_in_past_tense, file_count, total):
49 | figure = crayons.green(str(file_count), bold=True)
50 | total_figure = crayons.yellow(str(total), bold=True)
51 | message = MESSAGE_REPORT.format(action_in_past_tense, figure, total_figure)
52 | do_print(_format_single(message, total))
53 |
54 |
55 | def report_error_message(message):
56 | error_print(crayons.white("Error: ", bold=True) + crayons.red(message))
57 |
58 |
59 | def report_warning_message(message):
60 | error_print(
61 | crayons.white("Warning: ", bold=True) + crayons.yellow(message)
62 | )
63 |
64 |
65 | def report_info_message(message):
66 | do_print(crayons.white("Info: ") + crayons.green(message))
67 |
68 |
69 | def report_up_to_date():
70 | do_print(crayons.green(MESSAGE_UP_TO_DATE, bold=True))
71 |
72 |
73 | def convert_to_shell_exit_code(number_of_templated_files):
74 | return (
75 | constants.HAS_CHANGES
76 | if number_of_templated_files > 0
77 | else constants.NO_CHANGES
78 | )
79 |
80 |
81 | def report_git_pull(repo):
82 | colored_repo = crayons.green(repo, bold=True)
83 | do_print(MESSAGE_PULLING_REPO.format(colored_repo))
84 |
85 |
86 | def report_git_clone(repo):
87 | colored_repo = crayons.green(repo, bold=True)
88 | do_print(MESSAGE_CLONING_REPO.format(colored_repo))
89 |
90 |
91 | def report_template_not_in_moban_file(template):
92 | message = MESSAGE_TEMPLATE_NOT_IN_MOBAN_FILE.format(template)
93 | report_warning_message(message)
94 |
95 |
96 | def _format_single(message, count):
97 | if count == 1:
98 | return message.replace("files", "file")
99 | return message
100 |
101 |
102 | def report_file_extension_not_needed():
103 | report_info_message(MESSAGE_FILE_EXTENSION_NOT_NEEDED)
104 |
105 |
106 | def do_print(message):
107 | if GLOBAL["PRINT"]:
108 | print(message)
109 |
110 |
111 | def error_print(message):
112 | sys.stderr.write(message + "\n")
113 |
--------------------------------------------------------------------------------
/moban/plugins/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/moremoban/moban/11306525140a16f8b3231e41c73c48b3cfa4b333/moban/plugins/__init__.py
--------------------------------------------------------------------------------
/moban/plugins/copy.py:
--------------------------------------------------------------------------------
1 | from moban.core.content_processor import ContentProcessor
2 |
3 |
4 | @ContentProcessor("copy", "Copying", "Copied")
5 | def copy(content: str, _: dict) -> str:
6 | """
7 | Does no templating, works like 'copy'.
8 |
9 | Respects templating directories, for example: naughty.template
10 | could exist in any of template directires: dir1,
11 | dir2, dir3, and this engine will find it for you. With conventional
12 | copy command, the source file path must be known.
13 |
14 | And this engine does not really touch the dest file but only read
15 | the source file. Everything else is taken care of by moban
16 | templating mechanism.
17 | """
18 | return content
19 |
--------------------------------------------------------------------------------
/moban/plugins/delete.py:
--------------------------------------------------------------------------------
1 | import fs
2 | from lml.plugin import PluginInfo
3 |
4 | from moban import constants
5 | from moban.core.mobanfile.store import STORE
6 |
7 |
8 | @PluginInfo(
9 | constants.TEMPLATE_ENGINE_EXTENSION, tags=[constants.TEMPLATE_DELETE]
10 | )
11 | class DeleteEngine(object):
12 | """
13 | Does no templating but delete generated intermediate targets
14 |
15 | """
16 |
17 | ACTION_IN_PRESENT_CONTINUOUS_TENSE = "Deleting"
18 | ACTION_IN_PAST_TENSE = "Deleted"
19 |
20 | def __init__(self, template_fs, options=None):
21 | self.template_fs = template_fs
22 |
23 | def get_template(self, template_file):
24 | if template_file in STORE.intermediate_targets:
25 | with fs.open_fs(".") as the_fs:
26 | if the_fs.exists(template_file):
27 | the_fs.remove(template_file)
28 | return True
29 | else:
30 | return False
31 | else:
32 | raise Exception(f"Cannot remove {template_file}")
33 |
34 | def get_template_from_string(self, string):
35 | raise NotImplementedError("Not sure what to do")
36 |
37 | def apply_template(self, template, *_):
38 | raise NotImplementedError("Not sure what to do")
39 |
--------------------------------------------------------------------------------
/moban/plugins/jinja2/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/moremoban/moban/11306525140a16f8b3231e41c73c48b3cfa4b333/moban/plugins/jinja2/__init__.py
--------------------------------------------------------------------------------
/moban/plugins/jinja2/extensions.py:
--------------------------------------------------------------------------------
1 | from lml.plugin import PluginInfo
2 |
3 | from moban import constants
4 |
5 |
6 | class JinjaFilter(PluginInfo):
7 | def __init__(self):
8 | super(JinjaFilter, self).__init__(constants.JINJA_FILTER_EXTENSION)
9 |
10 | def tags(self):
11 | yield self.cls.__name__
12 |
13 |
14 | class JinjaTest(PluginInfo):
15 | def __init__(self, test_name=None):
16 | super(JinjaTest, self).__init__(constants.JINJA_TEST_EXTENSION)
17 | self.test_name = test_name
18 |
19 | def tags(self):
20 | if self.test_name:
21 | yield self.test_name
22 | else:
23 | yield self.cls.__name__
24 |
25 |
26 | def jinja_tests(**keywords):
27 | for key, value in keywords.items():
28 | JinjaTest(key)(value)
29 |
30 |
31 | def jinja_global(identifier, dict_obj):
32 | plugin = PluginInfo(constants.JINJA_GLOBALS_EXTENSION, tags=[identifier])
33 | plugin(dict_obj)
34 |
--------------------------------------------------------------------------------
/moban/plugins/jinja2/filters/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/moremoban/moban/11306525140a16f8b3231e41c73c48b3cfa4b333/moban/plugins/jinja2/filters/__init__.py
--------------------------------------------------------------------------------
/moban/plugins/jinja2/filters/repr.py:
--------------------------------------------------------------------------------
1 | from moban.plugins.jinja2.extensions import JinjaFilter
2 |
3 |
4 | @JinjaFilter()
5 | def repr(string):
6 | if isinstance(string, list):
7 | return ["'{0}'".format(str(element)) for element in string]
8 | else:
9 | return "'{0}'".format(str(string))
10 |
--------------------------------------------------------------------------------
/moban/plugins/jinja2/filters/text.py:
--------------------------------------------------------------------------------
1 | import re
2 |
3 | from moban.plugins.jinja2.extensions import JinjaFilter
4 |
5 |
6 | @JinjaFilter()
7 | def split_length(input_line, length):
8 | start = 0
9 | limit = length
10 | line = re.sub(r"\s+", " ", input_line)
11 | line_length = len(line)
12 | if line_length <= length:
13 | yield line
14 | else:
15 | while True:
16 | if " " in line[start : start + limit]: # noqa
17 | # go back and find a space
18 | while limit > 0 and line[start + limit] != " ":
19 | limit -= 1
20 | else:
21 | # full whole line is single unit
22 | # so go forward find a space
23 | while (start + limit) < len(line) and line[
24 | start + limit
25 | ] != " ":
26 | limit += 1
27 |
28 | yield line[start : start + limit] # noqa
29 | start = start + limit + 1
30 | limit = length
31 | if len(line[start:]) < length or start + limit >= len(line):
32 | break
33 |
34 | yield line[start:]
35 |
--------------------------------------------------------------------------------
/moban/plugins/json_loader.py:
--------------------------------------------------------------------------------
1 | import json
2 |
3 | from lml.plugin import PluginInfo
4 |
5 | from moban import constants
6 | from moban.externals.file_system import open_file
7 |
8 |
9 | @PluginInfo(constants.DATA_LOADER_EXTENSION, tags=["json"])
10 | def open_json(file_name):
11 | """
12 | returns json contents as string
13 | """
14 | with open_file(file_name) as json_file:
15 | data = json.load(json_file)
16 | return data
17 |
--------------------------------------------------------------------------------
/moban/plugins/strip.py:
--------------------------------------------------------------------------------
1 | from moban.core.content_processor import ContentProcessor
2 |
3 |
4 | @ContentProcessor("strip", "Stripping", "Stripped")
5 | def strip(content: str, _: dict) -> str:
6 | """Works like 'copy', but strip empty spaces before and after"""
7 | return content.strip()
8 |
--------------------------------------------------------------------------------
/moban/plugins/yaml_loader.py:
--------------------------------------------------------------------------------
1 | from lml.plugin import PluginInfo
2 | from ruamel.yaml import YAML
3 |
4 | from moban import constants
5 | from moban.externals.file_system import open_file
6 |
7 |
8 | @PluginInfo(constants.DATA_LOADER_EXTENSION, tags=["yaml", "yml"])
9 | def open_yaml(file_name):
10 | with open_file(file_name) as data_yaml:
11 | yaml = YAML(typ="rt")
12 | data = yaml.load(data_yaml)
13 | return data
14 |
--------------------------------------------------------------------------------
/moban/program_options.py:
--------------------------------------------------------------------------------
1 | from moban import constants
2 |
3 | OPTIONS = {constants.CLI_DICT: {}}
4 |
--------------------------------------------------------------------------------
/mobanfile:
--------------------------------------------------------------------------------
1 | configuration:
2 | template_dir:
3 | - "git://github.com/moremoban/pypi-mobans.git?submodule=true&brach=dev!/templates"
4 | - "git://github.com/moremoban/pypi-mobans.git?submodule=true&brach=dev!/statics"
5 | - "git://github.com/moremoban/moban-anyconfig.git?branch=master!/.moban.d/"
6 | - "git://github.com/moremoban/moban-handlebars.git?branch=dev!/.moban.d/"
7 | - "git://github.com/moremoban/moban-velocity.git?branch=dev!/.moban.d/"
8 | - "git://github.com/moremoban/moban-slim.git?branch=dev!/.moban.d/"
9 | - "git://github.com/moremoban/httpfs.git?branch=master!/.moban.d/"
10 | - "git://github.com/moremoban/gitfs2.git?branch=dev!/.moban.d/"
11 | - "git://github.com/moremoban/pypifs.git?branch=master!/.moban.d/"
12 | - ".moban.d"
13 | configuration: moban.yml
14 | targets:
15 | - README.rst: moban_readme.jj2
16 | - setup.py: moban_setup.py.jj2
17 | - moban/_version.py: _version.py.jj2
18 | - docs/conf.py: conf.py.jj2
19 | - .travis.yml: moban_travis.yml.jj2
20 | - requirements.txt: requirements.txt.jj2
21 | - .gitignore: moban_gitignore.jj2
22 | - output: CHANGELOG.rst
23 | configuration: changelog.yml
24 | template: CHANGELOG.rst.jj2
25 | - min_requirements.txt: min_requirements.txt.jj2
26 | - ".github/workflows/pythonpublish.yml": "pythonpublish.yml"
27 | - "CONTRIBUTORS.rst": "CONTRIBUTORS.rst.jj2"
28 | - format.sh: format.sh.jj2
29 |
--------------------------------------------------------------------------------
/requirements.txt:
--------------------------------------------------------------------------------
1 | ruamel.yaml>=0.15.5;python_version == '3.6'
2 | ruamel.yaml>=0.15.42;python_version == '3.7'
3 | ruamel.yaml>=0.15.98;python_version == '3.8'
4 | ruamel.yaml>=0.15.98;python_version == '3.9'
5 | jinja2>=2.7.1
6 | lml>=0.0.9
7 | appdirs>=1.4.3
8 | crayons>= 0.1.0
9 | fs>=2.4.11
10 | jinja2-fsloader>=0.2.0
11 | moban-jinja2-github
12 |
--------------------------------------------------------------------------------
/rnd_requirements.txt:
--------------------------------------------------------------------------------
1 | https://github.com/moremoban/moban-handlebars/archive/dev.zip
2 |
--------------------------------------------------------------------------------
/setup.cfg:
--------------------------------------------------------------------------------
1 | [metadata]
2 | description_file = README.rst
3 | [bdist_wheel]
4 | universal = 1
5 |
--------------------------------------------------------------------------------
/test.bat:
--------------------------------------------------------------------------------
1 | pip freeze
2 |
3 | pytest --verbosity=3 --cov=moban --doctest-glob=*.rst
4 |
--------------------------------------------------------------------------------
/test.sh:
--------------------------------------------------------------------------------
1 | pip freeze
2 |
3 | pytest --verbosity=3 --cov=moban --doctest-glob=*.rst
4 |
--------------------------------------------------------------------------------
/tests/__init__.py:
--------------------------------------------------------------------------------
1 | from moban.main import load_engine_factory_and_engines
2 |
3 |
4 | def setUpModule():
5 | load_engine_factory_and_engines()
6 |
--------------------------------------------------------------------------------
/tests/core/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/moremoban/moban/11306525140a16f8b3231e41c73c48b3cfa4b333/tests/core/__init__.py
--------------------------------------------------------------------------------
/tests/core/test_context.py:
--------------------------------------------------------------------------------
1 | import os
2 |
3 | import fs.path
4 |
5 | from moban.core.context import Context
6 |
7 |
8 | def test_context():
9 | context = Context(fs.path.join("tests", "fixtures"))
10 | data = context.get_data("simple.yaml")
11 | assert data["simple"] == "yaml"
12 |
13 |
14 | def test_environ_variables():
15 | test_var = "TEST_ENVIRONMENT_VARIABLE"
16 | test_value = "am I found"
17 | os.environ[test_var] = test_value
18 | context = Context(fs.path.join("tests", "fixtures"))
19 | data = context.get_data("simple.yaml")
20 | assert data[test_var] == test_value
21 |
22 |
23 | def test_json_data_overrides_environ_variables():
24 | test_var = "TEST_ENVIRONMENT_VARIABLE"
25 | test_value = "am I found"
26 | os.environ[test_var] = test_value
27 | context = Context(fs.path.join("tests", "fixtures"))
28 | data = context.get_data("simple.json")
29 | assert data[test_var] == test_value
30 |
31 |
32 | def test_unknown_data_file():
33 | test_var = "TEST_ENVIRONMENT_VARIABLE"
34 | test_value = "am I found"
35 | os.environ[test_var] = test_value
36 | context = Context(fs.path.join("tests", "fixtures"))
37 | data = context.get_data("unknown.data")
38 | assert data[test_var] == test_value
39 |
--------------------------------------------------------------------------------
/tests/data_loaders/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/moremoban/moban/11306525140a16f8b3231e41c73c48b3cfa4b333/tests/data_loaders/__init__.py
--------------------------------------------------------------------------------
/tests/data_loaders/test_json_loader.py:
--------------------------------------------------------------------------------
1 | import fs.path
2 |
3 | from moban.plugins.json_loader import open_json
4 |
5 |
6 | def test_open_json():
7 | content = open_json(fs.path.join("tests", "fixtures", "child.json"))
8 | expected = {"key": "hello world", "pass": "ox"}
9 | assert expected == content
10 |
--------------------------------------------------------------------------------
/tests/data_loaders/test_merge_dict.py:
--------------------------------------------------------------------------------
1 | from ruamel.yaml import YAML
2 |
3 | from moban.core.data_loader import merge
4 |
5 |
6 | def test_simple_union():
7 | user = {"hi": "world"}
8 | default = {"world": "hi"}
9 | merged = merge(user, default)
10 | assert merged == {"hi": "world", "world": "hi"}
11 |
12 |
13 | def test_simple_overlapping():
14 | user = {"hi": "world", "world": "hei"}
15 | default = {"world": "hi"}
16 | merged = merge(user, default)
17 | assert merged == {"hi": "world", "world": "hei"}
18 |
19 |
20 | def test_two_level_merge():
21 | user = {"L1": {"L2": "World"}}
22 | default = {"L1": {"L2.1": "Hi"}}
23 | merged = merge(user, default)
24 | assert merged == {"L1": {"L2": "World", "L2.1": "Hi"}}
25 |
26 |
27 | def test_two_level_conflict():
28 | user = {"L1": {"L2": "World"}}
29 | default = {"L1": {"L2": "Hi"}}
30 | merged = merge(user, default)
31 | assert merged == {"L1": {"L2": "World"}}
32 |
33 |
34 | def test_three_level_conflict():
35 | user = {"L1": {"L2": {"L3": "World"}}}
36 | default = {"L1": {"L2": {"L3": "Hi"}}}
37 | merged = merge(user, default)
38 | assert merged == {"L1": {"L2": {"L3": "World"}}}
39 |
40 |
41 | def test_merge_value_as_list():
42 | user = {"L1": ["a", "b"]}
43 | default = {"L1": ["c", "d"]}
44 | merged = merge(user, default)
45 | assert merged == {"L1": ["a", "b", "c", "d"]}
46 |
47 |
48 | def test_merge_value_as_list_in_yaml():
49 | yaml = YAML(typ="rt")
50 | user = yaml.load(
51 | """
52 | L1:
53 | - a
54 | - b
55 | """
56 | )
57 | default = yaml.load(
58 | """
59 | L1:
60 | - c
61 | - d
62 | """
63 | )
64 | merged = merge(user, default)
65 | assert merged == {"L1": ["a", "b", "c", "d"]}
66 |
--------------------------------------------------------------------------------
/tests/data_loaders/test_overrides.py:
--------------------------------------------------------------------------------
1 | import os
2 |
3 | import fs
4 |
5 | from moban.main import load_engine_factory_and_engines
6 | from moban.core.data_loader import load_data
7 |
8 |
9 | def test_overrides_a_list_of_config_files():
10 | base_dir = fs.path.join("tests", "fixtures", "issue_126")
11 | config_dir = fs.path.join(base_dir, "config")
12 | actual = load_data(config_dir, fs.path.join(base_dir, "the_config.yaml"))
13 | expected = [
14 | ("key", "value"),
15 | ("key_from_a", "apple"),
16 | ("key_from_b", "bee"),
17 | ]
18 | for item, expected_item in zip(actual.items(), expected):
19 | assert item == expected_item
20 |
21 | assert len(actual) == len(expected)
22 |
23 |
24 | def test_overrides_a_list_of_config_files_but_cannot_find_them():
25 | base_dir = fs.path.join("tests", "fixtures", "issue_126")
26 | actual = load_data(None, fs.path.join(base_dir, "the_config.yaml"))
27 |
28 | expected = [("key", "value")]
29 | for item, expected_item in zip(actual.items(), expected):
30 | assert item == expected_item
31 |
32 | assert len(actual) == len(expected)
33 |
34 |
35 | def test_overrides_ignores_override_sequence():
36 | base_dir = fs.path.join("tests", "fixtures", "issue_126")
37 | config_dir = fs.path.join(base_dir, "config")
38 | actual = load_data(config_dir, fs.path.join(base_dir, "the_config.yaml"))
39 | expected = [
40 | ("key", "value"),
41 | ("key_from_a", "apple"),
42 | ("key_from_b", "bee"),
43 | ]
44 | for item, expected_item in zip(actual.items(), expected):
45 | assert item == expected_item
46 |
47 |
48 | def test_overrides_select_keys_from_parent_files():
49 | base_dir = fs.path.join("tests", "fixtures", "issue_126")
50 | config_dir = fs.path.join(base_dir, "config")
51 | actual = load_data(
52 | config_dir, fs.path.join(base_dir, "multi-key-config.yaml")
53 | )
54 | expected = [
55 | ("cat", "from config"),
56 | ("alpha", "from a"),
57 | ("beta", "from b"),
58 | ]
59 | for item, expected_item in zip(actual.items(), expected):
60 | assert item == expected_item
61 |
62 |
63 | def test_overrides_select_keys():
64 | base_dir = fs.path.join("tests", "fixtures", "issue_126")
65 | config_dir = fs.path.join(base_dir, "config")
66 | actual = load_data(
67 | config_dir, fs.path.join(base_dir, "multi-key-config-override.yaml")
68 | )
69 | expected = [
70 | ("alpha", "from config"),
71 | ("cat", "from config"),
72 | ("beta", "from b"),
73 | ]
74 | for item, expected_item in zip(actual.items(), expected):
75 | assert item == expected_item
76 |
77 |
78 | def test_overrides_nested_keys():
79 | base_dir = fs.path.join("tests", "fixtures", "issue_126")
80 | config_dir = fs.path.join(base_dir, "config")
81 | actual = load_data(config_dir, fs.path.join(base_dir, "raspberry.yaml"))
82 | expected = {
83 | "raspberry": {
84 | "other": "OpenGL 3.0",
85 | "version": 4,
86 | "memory": "4GB",
87 | "core": "quad",
88 | "wifi": "2.5 & 5.0 GHz",
89 | "USB": 3.0,
90 | "Bluetooth": 5.0,
91 | },
92 | "tessel": {"version": 2, "USB": "micro", "wifi": "802.11gn"},
93 | }
94 |
95 | assert dict(actual) == expected
96 |
97 |
98 | def test_overrides_fs_url():
99 | load_engine_factory_and_engines()
100 | base_dir = fs.path.join("tests", "fixtures")
101 | actual = load_data(None, fs.path.join(base_dir, "override_fs_url.yaml"))
102 | assert "requires" in actual
103 |
--------------------------------------------------------------------------------
/tests/data_loaders/test_yaml_loader.py:
--------------------------------------------------------------------------------
1 | import pytest
2 | import fs.path
3 |
4 | from moban.core.data_loader import load_data
5 | from moban.plugins.yaml_loader import open_yaml
6 |
7 |
8 | def test_simple_yaml():
9 | test_file = fs.path.join("tests", "fixtures", "simple.yaml")
10 | data = open_yaml(test_file)
11 | assert data == {"simple": "yaml"}
12 |
13 |
14 | def test_inheritance_yaml():
15 | test_file = fs.path.join("tests", "fixtures", "child.yaml")
16 | data = load_data(fs.path.join("tests", "fixtures", "config"), test_file)
17 | assert data == {"key": "hello world", "pass": "ox"}
18 |
19 |
20 | def test_exception():
21 | test_file = fs.path.join("tests", "fixtures", "orphan.yaml")
22 | data = load_data(fs.path.join("tests", "fixtures", "config"), test_file)
23 | assert len(data) == 0
24 |
25 |
26 | def test_exception_2():
27 | test_file = fs.path.join("tests", "fixtures", "dragon.yaml")
28 | with pytest.raises(IOError):
29 | load_data(fs.path.join("tests", "fixtures", "config"), test_file)
30 |
31 |
32 | def test_exception_3():
33 | test_file = fs.path.join("tests", "fixtures", "dragon.yaml")
34 | with pytest.raises(IOError):
35 | load_data(None, test_file)
36 |
--------------------------------------------------------------------------------
/tests/deprecated/test_handle_requires.py:
--------------------------------------------------------------------------------
1 | from unittest.mock import patch
2 |
3 | from moban.deprecated import GitRequire
4 |
5 |
6 | @patch("moban.deprecated.pip_install")
7 | def test_handle_requires_pypkg(fake_pip_install):
8 | modules = ["package1", "package2"]
9 | from moban.deprecated import handle_requires
10 |
11 | handle_requires(modules)
12 | fake_pip_install.assert_called_with(modules)
13 |
14 |
15 | @patch("moban.deprecated.pip_install")
16 | def test_handle_requires_pypkg_with_alternative_syntax(fake_pip_install):
17 | modules = [{"type": "pypi", "name": "pypi-mobans"}]
18 | from moban.deprecated import handle_requires
19 |
20 | handle_requires(modules)
21 | fake_pip_install.assert_called_with(["pypi-mobans"])
22 |
23 |
24 | @patch("moban.deprecated.git_clone")
25 | def test_handle_requires_repos(fake_git_clone):
26 | repos = ["https://github.com/my/repo", "https://gitlab.com/my/repo"]
27 | from moban.deprecated import handle_requires
28 |
29 | expected = []
30 | for repo in repos:
31 | expected.append(GitRequire(git_url=repo, submodule=False))
32 |
33 | handle_requires(repos)
34 | fake_git_clone.assert_called_with(expected)
35 |
36 |
37 | @patch("moban.deprecated.git_clone")
38 | def test_handle_requires_repos_with_alternative_syntax(fake_git_clone):
39 | repos = [{"type": "git", "url": "https://github.com/my/repo"}]
40 | from moban.deprecated import handle_requires
41 |
42 | handle_requires(repos)
43 | fake_git_clone.assert_called_with(
44 | [GitRequire(git_url="https://github.com/my/repo")]
45 | )
46 |
47 |
48 | @patch("moban.deprecated.pip_install")
49 | @patch("moban.deprecated.git_clone")
50 | def test_handle_requires_repos_with_submodule(
51 | fake_git_clone, fake_pip_install
52 | ):
53 | repos = [
54 | {"type": "git", "url": "https://github.com/my/repo", "submodule": True}
55 | ]
56 | from moban.deprecated import handle_requires
57 |
58 | handle_requires(repos)
59 | fake_git_clone.assert_called_with(
60 | [GitRequire(git_url="https://github.com/my/repo", submodule=True)]
61 | )
62 | assert fake_pip_install.called is False
63 |
64 |
65 | def test_is_repo():
66 | repos = [
67 | "https://github.com/my/repo",
68 | "https://gitlab.com/my/repo",
69 | "https://bitbucket.com/my/repo",
70 | "https://unsupported.com/my/repo",
71 | "invalid/repo/url",
72 | ]
73 | from moban.deprecated import is_repo
74 |
75 | actual = [is_repo(repo) for repo in repos]
76 | expected = [True, True, True, False, False]
77 | assert expected == actual
78 |
--------------------------------------------------------------------------------
/tests/fixtures/.moban-2.yml:
--------------------------------------------------------------------------------
1 | requires:
2 | - pypi-mobans-pkg==0.0.12
3 | configuration:
4 | configuration_dir: "setupmobans:config"
5 | template_dir:
6 | - "setupmobans:templates"
7 | - ".moban.d"
8 | targets:
9 | - output: README.rst
10 | configuration: custom-data.yaml
11 | template: README.rst.jj2
12 | - setup.py: setup.py.jj2
13 |
--------------------------------------------------------------------------------
/tests/fixtures/.moban-version-1.0.yml:
--------------------------------------------------------------------------------
1 | version: 1.0
2 | configuration:
3 | configuration_dir: "commons/config"
4 | template_dir:
5 | - ".moban.d"
6 |
--------------------------------------------------------------------------------
/tests/fixtures/.moban-version-1234.yml:
--------------------------------------------------------------------------------
1 | moban_file_spec_version: 1234
2 | configuration:
3 | configuration_dir: "."
4 | template_dir:
5 | - ".moban.d"
6 | configuration: data.yaml
7 | targets:
8 | - README.rst: README.rst
9 | - setup.py: setup.py
10 |
--------------------------------------------------------------------------------
/tests/fixtures/.moban.yml:
--------------------------------------------------------------------------------
1 | requires:
2 | - pypi-mobans-pkg
3 | configuration:
4 | template_dir:
5 | - "setupmobans:templates"
6 | - ".moban.d"
7 | configuration_dir: "setupmobans:config"
8 | configuration: data.yaml
9 | targets:
10 | - README.rst: README.rst.jj2
11 | - setup.py: setup.py.jj2
12 |
--------------------------------------------------------------------------------
/tests/fixtures/a.handlebars:
--------------------------------------------------------------------------------
1 | {{key}} {{pass}}
--------------------------------------------------------------------------------
/tests/fixtures/a.jj2:
--------------------------------------------------------------------------------
1 | {{key}} {{pass}}
--------------------------------------------------------------------------------
/tests/fixtures/child.json:
--------------------------------------------------------------------------------
1 | {
2 | "key": "hello world",
3 | "pass": "ox"
4 | }
--------------------------------------------------------------------------------
/tests/fixtures/child.yaml:
--------------------------------------------------------------------------------
1 | key: 'hello world'
2 | overrides: base.yaml
3 |
--------------------------------------------------------------------------------
/tests/fixtures/config/base.yaml:
--------------------------------------------------------------------------------
1 | key: 'to be overriden'
2 | pass: ox
3 |
--------------------------------------------------------------------------------
/tests/fixtures/copier-directory/copier-sample-dir/file1:
--------------------------------------------------------------------------------
1 | sample-dir-file-1
2 |
--------------------------------------------------------------------------------
/tests/fixtures/copier-directory/level1-file1:
--------------------------------------------------------------------------------
1 | dir-level-1-file-1
2 |
--------------------------------------------------------------------------------
/tests/fixtures/copier-test01.csv:
--------------------------------------------------------------------------------
1 | test 01
2 |
--------------------------------------------------------------------------------
/tests/fixtures/copier-test02.csv:
--------------------------------------------------------------------------------
1 | test 02
2 |
--------------------------------------------------------------------------------
/tests/fixtures/copier-test03.csv:
--------------------------------------------------------------------------------
1 | test 3
2 |
--------------------------------------------------------------------------------
/tests/fixtures/copier-test04.csv:
--------------------------------------------------------------------------------
1 | test 4
2 |
--------------------------------------------------------------------------------
/tests/fixtures/copier-test05.csv:
--------------------------------------------------------------------------------
1 | test 05
2 |
--------------------------------------------------------------------------------
/tests/fixtures/duplicated.moban.yml:
--------------------------------------------------------------------------------
1 | configuration:
2 | template_dir:
3 | - "."
4 | targets:
5 | - setup.py: setup.py
6 | - setup.py: setup.py
7 |
--------------------------------------------------------------------------------
/tests/fixtures/environ_vars_as_data/test.template:
--------------------------------------------------------------------------------
1 | {{ TEST_ENVIRONMENT_VARIABLE }}
--------------------------------------------------------------------------------
/tests/fixtures/file_system/template-sources.zip:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/moremoban/moban/11306525140a16f8b3231e41c73c48b3cfa4b333/tests/fixtures/file_system/template-sources.zip
--------------------------------------------------------------------------------
/tests/fixtures/globals/basic.template:
--------------------------------------------------------------------------------
1 | {{ test.hello }}
2 |
3 | {{ globals }}
--------------------------------------------------------------------------------
/tests/fixtures/globals/basic.yml:
--------------------------------------------------------------------------------
1 | globals: test
2 |
--------------------------------------------------------------------------------
/tests/fixtures/globals/nested.template:
--------------------------------------------------------------------------------
1 | {% include 'variables.template' %}
--------------------------------------------------------------------------------
/tests/fixtures/globals/variables.template:
--------------------------------------------------------------------------------
1 | template: {{ __template__ }}
2 | target: {{ __target__ }}
3 | {{ test }}
--------------------------------------------------------------------------------
/tests/fixtures/globals/variables.yml:
--------------------------------------------------------------------------------
1 | test: here
2 |
--------------------------------------------------------------------------------
/tests/fixtures/issue_126/config/A.yaml:
--------------------------------------------------------------------------------
1 | key_from_a: apple
2 |
--------------------------------------------------------------------------------
/tests/fixtures/issue_126/config/B.yaml:
--------------------------------------------------------------------------------
1 | key_from_b: bee
2 |
--------------------------------------------------------------------------------
/tests/fixtures/issue_126/config/multi-key-A.yaml:
--------------------------------------------------------------------------------
1 | alpha: 'from a'
2 | beta: 'from a'
3 |
--------------------------------------------------------------------------------
/tests/fixtures/issue_126/config/multi-key-B.yaml:
--------------------------------------------------------------------------------
1 | alpha: 'from b'
2 | beta: 'from b'
3 |
--------------------------------------------------------------------------------
/tests/fixtures/issue_126/config/multi-key-config.yaml:
--------------------------------------------------------------------------------
1 | cat: 'from config'
2 | overrides:
3 | - multi-key-A.yaml:alpha
4 | - multi-key-B.yaml:beta
5 |
--------------------------------------------------------------------------------
/tests/fixtures/issue_126/config/nested-A.yaml:
--------------------------------------------------------------------------------
1 | raspberry:
2 | core: quad
3 | memory: 4GB
4 | version: 4
5 | tessel:
6 | version: 2
7 |
--------------------------------------------------------------------------------
/tests/fixtures/issue_126/config/nested-B.yaml:
--------------------------------------------------------------------------------
1 | raspberry:
2 | Bluetooth: 5.0
3 | USB: 3.0
4 | wifi: '2.5 & 5.0 GHz'
5 | tessel:
6 | USB: micro
7 | wifi: 802.11gn
8 |
--------------------------------------------------------------------------------
/tests/fixtures/issue_126/config_BA.yaml:
--------------------------------------------------------------------------------
1 | key: value
2 | overrides:
3 | - B.yaml
4 | - A.yaml
5 |
--------------------------------------------------------------------------------
/tests/fixtures/issue_126/multi-key-config-override.yaml:
--------------------------------------------------------------------------------
1 | alpha: 'from config'
2 | cat: 'from config'
3 | overrides:
4 | - multi-key-A.yaml:alpha
5 | - multi-key-B.yaml:beta
6 |
--------------------------------------------------------------------------------
/tests/fixtures/issue_126/multi-key-config.yaml:
--------------------------------------------------------------------------------
1 | cat: 'from config'
2 | overrides:
3 | - multi-key-A.yaml:alpha
4 | - multi-key-B.yaml:beta
5 |
--------------------------------------------------------------------------------
/tests/fixtures/issue_126/raspberry.yaml:
--------------------------------------------------------------------------------
1 | overrides:
2 | - nested-A.yaml
3 | - nested-B.yaml
4 | raspberry:
5 | other: 'OpenGL 3.0'
6 |
--------------------------------------------------------------------------------
/tests/fixtures/issue_126/the_config.yaml:
--------------------------------------------------------------------------------
1 | key: value
2 | overrides:
3 | - A.yaml
4 | - B.yaml
5 |
--------------------------------------------------------------------------------
/tests/fixtures/jinja_tests/file_tests.template:
--------------------------------------------------------------------------------
1 | {% if 'tests/fixtures/jinja_tests/file_tests.template' is exists %}
2 | yes
3 | {%else%}
4 | no
5 | {% endif %}
6 | {{ test }}
--------------------------------------------------------------------------------
/tests/fixtures/jinja_tests/file_tests.yml:
--------------------------------------------------------------------------------
1 | test: here
2 |
--------------------------------------------------------------------------------
/tests/fixtures/mobanengine/sample_template_type.yml:
--------------------------------------------------------------------------------
1 | template_types:
2 | custom_jinja:
3 | base_type: jinja2 # use base_type, instead of overrides
4 | file_extensions:
5 | - moban
6 | - new
7 | - demo_file_suffix
8 | options:
9 | extensions:
10 | - jinja2.ext.do
11 |
--------------------------------------------------------------------------------
/tests/fixtures/mobanfile/a.template.jj2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/moremoban/moban/11306525140a16f8b3231e41c73c48b3cfa4b333/tests/fixtures/mobanfile/a.template.jj2
--------------------------------------------------------------------------------
/tests/fixtures/mobanfile/filterme.handlebars:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/moremoban/moban/11306525140a16f8b3231e41c73c48b3cfa4b333/tests/fixtures/mobanfile/filterme.handlebars
--------------------------------------------------------------------------------
/tests/fixtures/non-unicode.char:
--------------------------------------------------------------------------------
1 | �
2 |
--------------------------------------------------------------------------------
/tests/fixtures/orphan.yaml:
--------------------------------------------------------------------------------
1 | overrides: darkmatter.yaml
2 |
--------------------------------------------------------------------------------
/tests/fixtures/override_fs_url.yaml:
--------------------------------------------------------------------------------
1 | overrides: "git://github.com/moremoban/moban!/tests/fixtures/.moban.yml"
2 | targets:
3 | - my: test
4 |
--------------------------------------------------------------------------------
/tests/fixtures/simple.yaml:
--------------------------------------------------------------------------------
1 | simple: yaml
2 |
--------------------------------------------------------------------------------
/tests/fixtures/template:
--------------------------------------------------------------------------------
1 | I pretend to be a template file without suffix so that moban cannot figure out
2 |
--------------------------------------------------------------------------------
/tests/fixtures/template-tests/a.jj2:
--------------------------------------------------------------------------------
1 | a test template
2 |
--------------------------------------------------------------------------------
/tests/integration_tests/__init__.py:
--------------------------------------------------------------------------------
1 | from moban.main import load_engine_factory_and_engines
2 |
3 |
4 | def setUpModule():
5 | load_engine_factory_and_engines()
6 |
--------------------------------------------------------------------------------
/tests/jinja2/__init__.py:
--------------------------------------------------------------------------------
1 | from moban.main import load_engine_factory_and_engines
2 |
3 |
4 | def setUpModule():
5 | load_engine_factory_and_engines()
6 |
--------------------------------------------------------------------------------
/tests/jinja2/test_engine.py:
--------------------------------------------------------------------------------
1 | import fs
2 |
3 | from moban.externals import file_system
4 | from moban.plugins.jinja2.engine import Engine
5 |
6 |
7 | def test_jinja2_template():
8 | path = fs.path.join("tests", "fixtures", "jinja_tests")
9 | fsys = file_system.get_multi_fs([path])
10 | options = {"extensions": ["test:moban.externals.file_system.exists"]}
11 | engine = Engine(fsys, options)
12 | template = engine.get_template("file_tests.template")
13 | data = dict(test="here")
14 | result = engine.apply_template(template, data, None)
15 | expected = "yes\nhere"
16 | assert expected == result
17 |
18 |
19 | def test_jinja2_template_string():
20 | path = fs.path.join("tests", "fixtures", "jinja_tests")
21 | fsys = file_system.get_multi_fs([path])
22 | engine = Engine(fsys)
23 | template = engine.get_template_from_string("{{test}}")
24 | data = dict(test="here")
25 | result = engine.apply_template(template, data, None)
26 | expected = "here"
27 | assert expected == result
28 |
--------------------------------------------------------------------------------
/tests/jinja2/test_extensions.py:
--------------------------------------------------------------------------------
1 | import os
2 |
3 | import fs
4 |
5 | from moban.externals import file_system
6 | from moban.core.moban_factory import MobanEngine
7 | from moban.plugins.jinja2.engine import Engine
8 | from moban.plugins.jinja2.extensions import jinja_global
9 |
10 |
11 | def test_globals():
12 | output = "globals.txt"
13 | test_dict = dict(hello="world")
14 | jinja_global("test", test_dict)
15 | path = fs.path.join("tests", "fixtures", "globals")
16 | template_fs = file_system.get_multi_fs([path])
17 | engine = MobanEngine(template_fs, path, Engine(template_fs))
18 | engine.render_to_file("basic.template", "basic.yml", output)
19 | with open(output, "r") as output_file:
20 | content = output_file.read()
21 | assert content == "world\n\ntest"
22 | os.unlink(output)
23 |
--------------------------------------------------------------------------------
/tests/jinja2/test_repr.py:
--------------------------------------------------------------------------------
1 | from moban.plugins.jinja2.filters.repr import repr as repr_function
2 |
3 |
4 | def test_string():
5 | me = "abc"
6 | expected = repr_function(me)
7 | assert expected == "'abc'"
8 |
9 |
10 | def test_list():
11 | me = [1, 2, 3]
12 | expected = repr_function(me)
13 | assert expected == ["'1'", "'2'", "'3'"]
14 |
--------------------------------------------------------------------------------
/tests/jinja2/test_text.py:
--------------------------------------------------------------------------------
1 | from moban.plugins.jinja2.filters.text import split_length
2 |
3 |
4 | def test_split_length():
5 | inputs = [
6 | ["some good issues are helping the developer for the", 12],
7 | ["http://github.com/chfw/abc is cool", 12],
8 | ["http://github.com/chfw/abc is cool", 100],
9 | ["some extra space will be removed", 10],
10 | ]
11 | expectations = [
12 | ["some good", "issues are", "helping the", "developer", "for the"],
13 | ["http://github.com/chfw/abc", "is cool"],
14 | ["http://github.com/chfw/abc is cool"],
15 | ["some extra", "space will", "be removed"],
16 | ]
17 | for test, expect in zip(inputs, expectations):
18 | actual = split_length(*test)
19 | assert list(actual) == expect
20 |
--------------------------------------------------------------------------------
/tests/mobanfile/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/moremoban/moban/11306525140a16f8b3231e41c73c48b3cfa4b333/tests/mobanfile/__init__.py
--------------------------------------------------------------------------------
/tests/mobanfile/test_mobanfile.py:
--------------------------------------------------------------------------------
1 | from unittest.mock import patch
2 |
3 | import fs.path
4 |
5 | from moban.core.definitions import TemplateTarget
6 |
7 |
8 | @patch("moban.core.moban_factory.MobanEngine.render_to_files")
9 | def test_handle_targets(fake_renderer):
10 | from moban.core.mobanfile import handle_targets
11 |
12 | TEMPLATE = "copier-test01.csv"
13 | OUTPUT = "output.csv"
14 | CONFIGURATION = "child.yaml"
15 | TEMPLATE_DIRS = [fs.path.join("tests", "fixtures")]
16 | DEFAULT_TEMPLATE_TYPE = "jinja2"
17 |
18 | options = dict(
19 | configuration=CONFIGURATION,
20 | template_type=DEFAULT_TEMPLATE_TYPE,
21 | template_dir=TEMPLATE_DIRS,
22 | configuration_dir=fs.path.join("tests", "fixtures"),
23 | )
24 | short_hand_targets = [{OUTPUT: TEMPLATE}]
25 | handle_targets(options, short_hand_targets)
26 |
27 | call_args = list(fake_renderer.call_args[0][0])
28 | assert call_args == [
29 | TemplateTarget(
30 | "copier-test01.csv",
31 | "child.yaml",
32 | "output.csv",
33 | template_type="jinja2",
34 | )
35 | ]
36 |
37 |
38 | @patch("moban.core.moban_factory.MobanEngine.render_to_files")
39 | def test_handle_targets_sequence(fake_renderer):
40 | from moban.core.mobanfile import handle_targets
41 | from moban.core.mobanfile.store import STORE
42 |
43 | STORE.init() # required to reset the store
44 |
45 | TEMPLATE1 = "a.template.jj2"
46 | OUTPUT1 = "filterme.handlebars" # in the future, this could dynamic output
47 | OUTPUT2 = "filtered_output.txt"
48 | CONFIGURATION = "child.yaml"
49 | TEMPLATE_DIRS = [fs.path.join("tests", "fixtures", "mobanfile")]
50 | DEFAULT_TEMPLATE_TYPE = "jinja2"
51 |
52 | options = dict(
53 | configuration=CONFIGURATION,
54 | template_type=DEFAULT_TEMPLATE_TYPE,
55 | template_dir=TEMPLATE_DIRS,
56 | configuration_dir=fs.path.join("tests", "fixtures"),
57 | )
58 | short_hand_targets = [{OUTPUT1: TEMPLATE1}, {OUTPUT2: OUTPUT1}]
59 | handle_targets(options, short_hand_targets)
60 |
61 | call_args = list(fake_renderer.call_args_list)
62 |
63 | assert call_args[0][0][0][0] == TemplateTarget(
64 | "a.template.jj2",
65 | "child.yaml",
66 | "filterme.handlebars",
67 | template_type="jj2",
68 | )
69 | assert call_args[1][0][0][0] == TemplateTarget(
70 | "filterme.handlebars",
71 | "child.yaml",
72 | "filtered_output.txt",
73 | template_type="handlebars",
74 | )
75 |
--------------------------------------------------------------------------------
/tests/mobanfile/test_templates.py:
--------------------------------------------------------------------------------
1 | import unittest
2 | from unittest.mock import patch
3 |
4 | import fs.path
5 |
6 | from moban.core.mobanfile.templates import handle_template
7 |
8 |
9 | class TestHandleTemplateFunction(unittest.TestCase):
10 | def setUp(self):
11 | self.base_dir = [fs.path.join("tests", "fixtures")]
12 |
13 | def test_copy_files(self):
14 | results = list(
15 | handle_template("copier-test01.csv", "/tmp/test", self.base_dir)
16 | )
17 | expected = [("copier-test01.csv", "/tmp/test", "csv")]
18 | assert expected == results
19 |
20 | @patch("moban.externals.reporter.report_error_message")
21 | def test_file_not_found(self, reporter):
22 | list(
23 | handle_template(
24 | "copier-test-not-found.csv", "/tmp/test", self.base_dir
25 | )
26 | )
27 | reporter.assert_called_with(
28 | "copier-test-not-found.csv cannot be found"
29 | )
30 |
31 | def test_listing_dir(self):
32 | test_dir = "/tmp/copy-a-directory"
33 | results = list(
34 | handle_template("copier-directory", test_dir, self.base_dir)
35 | )
36 | expected = [
37 | (
38 | "copier-directory/level1-file1",
39 | fs.path.join("/tmp/copy-a-directory", "level1-file1"),
40 | None,
41 | )
42 | ]
43 | assert expected == results
44 |
45 | def test_listing_dir_recusively(self):
46 | test_dir = "/tmp/copy-a-directory"
47 | results = list(
48 | handle_template("copier-directory/**", test_dir, self.base_dir)
49 | )
50 | expected = [
51 | (
52 | fs.path.join("copier-directory", "copier-sample-dir", "file1"),
53 | fs.path.join(
54 | "/tmp/copy-a-directory", "copier-sample-dir", "file1"
55 | ),
56 | None,
57 | ),
58 | (
59 | fs.path.join("copier-directory", "level1-file1"),
60 | fs.path.join("/tmp/copy-a-directory", "level1-file1"),
61 | None,
62 | ),
63 | ]
64 | assert sorted(results, key=lambda x: x[0]) == sorted(
65 | expected, key=lambda x: x[0]
66 | )
67 |
68 | @patch("moban.externals.reporter.report_error_message")
69 | def test_listing_dir_recusively_with_error(self, reporter):
70 | test_dir = "/tmp/copy-a-directory"
71 | list(
72 | handle_template(
73 | "copier-directory-does-not-exist/**", test_dir, self.base_dir
74 | )
75 | )
76 | assert reporter.call_count == 1
77 |
--------------------------------------------------------------------------------
/tests/regression_tests/level-21-b-copy-templates-into-a-tar/.moban.yml:
--------------------------------------------------------------------------------
1 | configuration:
2 | template_dir:
3 | - "tar://template-sources.tar"
4 | targets:
5 | - output: "tar://my.tar!/simple.file.copy"
6 | template: file-in-template-sources-folder.txt
7 | template_type: copy
8 | - output: "tar://my.tar!/target_without_template_type"
9 | template: file_extension_will_trigger.copy
10 | - "tar://my.tar!/target_in_short_form": as_long_as_this_one_has.copy
11 | - output: "tar://my.tar!/misc-1-copying/can-create-folder/if-not-exists.txt"
12 | template: file-in-template-sources-folder.txt
13 | template_type: copy
14 | - output: "tar://my.tar!/test-dir"
15 | template: dir-for-copying
16 | template_type: copy
17 | - output: "tar://my.tar!/test-recursive-dir"
18 | template: dir-for-recusive-copying/**
19 | template_type: copy
20 |
--------------------------------------------------------------------------------
/tests/regression_tests/level-21-b-copy-templates-into-a-tar/README.rst:
--------------------------------------------------------------------------------
1 | Level 21-b: template copying from a tar to a tar
2 | ================================================================================
3 |
4 | In level 15, with `.moban.yml`, you can copy templates to your destination. Now
5 | with similiar moban syntax, let me show how to create a new zip file where
6 | all templates are copied to.
7 |
8 | Explicit syntax::
9 |
10 | targets:
11 | - output: "tar://your.zip/explicit"
12 | template: template_file
13 | template_type: copy
14 |
15 |
16 | Implicit syntax::
17 |
18 | targets:
19 | - output: "tar://your.zip/implicit"
20 | template: template_file.copy
21 |
22 |
23 | Shorthand syntax::
24 |
25 | targets:
26 | - "tar://your.zip/shorthand": template_file.copy
27 |
28 |
29 | No implicit nor short hand syntax for the following directory copying unless
30 | you take a look at `force-template-type`. When you read
31 | `level-17-force-template-type-from-moban-file/README.rst`, you will find
32 | out more.
33 |
34 |
35 | Directory copying syntax::
36 |
37 |
38 | targets:
39 | - output: "tar://your.zip/dest-dir"
40 | template: source-dir
41 | template_type: copy
42 |
43 |
44 | Recursive directory copying syntax::
45 |
46 |
47 | targets:
48 | - output: "tar://your.zip/dest-dir"
49 | template: source-dir/**
50 | template_type: copy
51 |
52 |
53 | Evaluation
54 | --------------------------------------------------------------------------------
55 |
56 | Here is example moban file for copying::
57 |
58 | configuration:
59 | template_dir:
60 | - "tar://template-sources.tar"
61 | targets:
62 | - output: "tar://my.tar/simple.file.copy"
63 | template: file-in-template-sources-folder.txt
64 | template_type: copy
65 | - output: "tar://my.tar/target_without_template_type"
66 | template: file_extension_will_trigger.copy
67 | - "tar://my.tar/target_in_short_form": as_long_as_this_one_has.copy
68 | - output: "tar://my.tar/misc-1-copying/can-create-folder/if-not-exists.txt"
69 | template: file-in-template-sources-folder.txt
70 | template_type: copy
71 | - output: "tar://my.tar/test-dir"
72 | template: dir-for-copying
73 | template_type: copy
74 | - output: "tar://my.tar/test-recursive-dir"
75 | template: dir-for-recusive-copying/**
76 | template_type: copy
77 |
78 |
79 | template copy does:
80 |
81 |
82 | #. copies any template inside pre-declared template directory to anywhere. moban will create directory if needed.
83 | #. copies any directory to anywhere. If "**" is followed, moban attempts to do recursive copying.
84 |
--------------------------------------------------------------------------------
/tests/regression_tests/level-21-c-copy-templates-from-a-tar/.moban.yml:
--------------------------------------------------------------------------------
1 | configuration:
2 | template_dir:
3 | - "tar://template-sources.tar"
4 | targets:
5 | - output: "zip://my.zip!/simple.file.copy"
6 | template: file-in-template-sources-folder.txt
7 | template_type: copy
8 | - output: "zip://my.zip!/target_without_template_type"
9 | template: file_extension_will_trigger.copy
10 | - "zip://my.zip!/target_in_short_form": as_long_as_this_one_has.copy
11 | - output: "zip://my.zip!/misc-1-copying/can-create-folder/if-not-exists.txt"
12 | template: file-in-template-sources-folder.txt
13 | template_type: copy
14 | - output: "zip://my.zip!/test-dir"
15 | template: dir-for-copying
16 | template_type: copy
17 | - output: "zip://my.zip!/test-recursive-dir"
18 | template: dir-for-recusive-copying/**
19 | template_type: copy
20 |
--------------------------------------------------------------------------------
/tests/regression_tests/level-21-c-copy-templates-from-a-tar/README.rst:
--------------------------------------------------------------------------------
1 | Level 21-c: template copying from a tar to a zip
2 | ================================================================================
3 |
4 | Here is another set of regression tests file for ::
5 |
6 | configuration:
7 | template_dir:
8 | - "tar://template-sources.tar"
9 | targets:
10 | - output: "zip://my.zip!/simple.file.copy"
11 | template: file-in-template-sources-folder.txt
12 | template_type: copy
13 | - output: "zip://my.zip!/target_without_template_type"
14 | template: file_extension_will_trigger.copy
15 | - "zip://my.zip!/target_in_short_form": as_long_as_this_one_has.copy
16 | - output: "zip://my.zip!/misc-1-copying/can-create-folder/if-not-exists.txt"
17 | template: file-in-template-sources-folder.txt
18 | template_type: copy
19 | - output: "zip://my.zip!/test-dir"
20 | template: dir-for-copying
21 | template_type: copy
22 | - output: "zip://my.zip!/test-recursive-dir"
23 | template: dir-for-recusive-copying/**
24 | template_type: copy
25 |
--------------------------------------------------------------------------------
/tests/regression_tests/level-7-b-template-engine-plugin/README.rst:
--------------------------------------------------------------------------------
1 | Level 7 b: Custom template engine
2 | ================================================================================
3 |
4 | We will test this on '-pd' cli option with custom template engine.
5 |
6 | .. code-block:: bash
7 |
8 | $ moban --template-type de-duplicate -pd custom-plugin -t duplicated_content.txt
9 |
--------------------------------------------------------------------------------
/tests/regression_tests/level-7-b-template-engine-plugin/custom-plugin/deduplicate.py:
--------------------------------------------------------------------------------
1 | from moban.core.content_processor import ContentProcessor
2 |
3 |
4 | @ContentProcessor("de-duplicate", "De-duplicating", "De-duplicated")
5 | def de_duplicate(content: str, _: dict) -> str:
6 | """
7 | Does no templating, works like 'copy'.
8 |
9 | """
10 | lines = content.split(b"\n")
11 | new_lines = []
12 | for line in lines:
13 | if line not in new_lines:
14 | new_lines.append(line)
15 | return b"\n".join(new_lines)
16 |
--------------------------------------------------------------------------------
/tests/regression_tests/level-7-b-template-engine-plugin/duplicated_content.txt:
--------------------------------------------------------------------------------
1 | 1
2 | 2
3 | 1
4 | 2
5 | 2
6 | 1
7 | 2
8 | 2
9 | 3
10 | 4
11 | 5
12 | 6
13 | 8
14 |
--------------------------------------------------------------------------------
/tests/regression_tests/level-7-plugin-dir-cli/README.rst:
--------------------------------------------------------------------------------
1 | Level 7: Custom jinja filters, tests and globals on cli
2 | ================================================================================
3 |
4 | We will test this on '-pd' cli option
5 |
6 | .. code-block:: bash
7 |
8 | $ moban -td my-templates/ -t filter.jj2 -pd custom-jj2-plugin
9 |
10 |
--------------------------------------------------------------------------------
/tests/regression_tests/level-7-plugin-dir-cli/custom-jj2-plugin/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/moremoban/moban/11306525140a16f8b3231e41c73c48b3cfa4b333/tests/regression_tests/level-7-plugin-dir-cli/custom-jj2-plugin/__init__.py
--------------------------------------------------------------------------------
/tests/regression_tests/level-7-plugin-dir-cli/custom-jj2-plugin/filter.py:
--------------------------------------------------------------------------------
1 | import sys
2 | import base64
3 |
4 | from moban.plugins.jinja2.extensions import JinjaFilter
5 |
6 |
7 | @JinjaFilter()
8 | def base64encode(string):
9 | if sys.version_info[0] > 2:
10 | content = base64.b64encode(string.encode("utf-8"))
11 | content = content.decode("utf-8")
12 | else:
13 | content = base64.b64encode(string)
14 | return content
15 |
--------------------------------------------------------------------------------
/tests/regression_tests/level-7-plugin-dir-cli/custom-jj2-plugin/global.py:
--------------------------------------------------------------------------------
1 | from moban.plugins.jinja2.extensions import jinja_global
2 |
3 | jinja_global("global", dict(hello="world"))
4 |
--------------------------------------------------------------------------------
/tests/regression_tests/level-7-plugin-dir-cli/custom-jj2-plugin/test.py:
--------------------------------------------------------------------------------
1 | from moban.plugins.jinja2.extensions import JinjaTest
2 |
3 |
4 | @JinjaTest()
5 | def level7(value):
6 | return value == "level7"
7 |
--------------------------------------------------------------------------------
/tests/regression_tests/level-7-plugin-dir-cli/data.yml:
--------------------------------------------------------------------------------
1 | level: level7
2 | level8: level8
3 |
--------------------------------------------------------------------------------
/tests/regression_tests/level-7-plugin-dir-cli/my-templates/filter.jj2:
--------------------------------------------------------------------------------
1 | {{ 'abc' | base64encode }}
2 |
--------------------------------------------------------------------------------
/tests/regression_tests/level-7-plugin-dir-cli/my-templates/global.jj2:
--------------------------------------------------------------------------------
1 | {{ global.hello }}
--------------------------------------------------------------------------------
/tests/regression_tests/level-7-plugin-dir-cli/my-templates/test.jj2:
--------------------------------------------------------------------------------
1 | {% if level is level7%}
2 | Hello, you are in level 7 example
3 | {% else %}
4 | Hello, you are not in {{level}}
5 | {% endif %}
6 |
7 | {% if level8 is level7%}
8 | Hello, you are in level 7 example
9 | {% else %}
10 | Hello, you are not in level 7
11 | {% endif %}
12 |
--------------------------------------------------------------------------------
/tests/regression_tests/regr-01-copy-binary-file/.moban.yml:
--------------------------------------------------------------------------------
1 | configuration:
2 | template_dir:
3 | - copy-source
4 | targets:
5 | - output: regression-test.png
6 | template: image.png
7 | template_type: copy
8 |
--------------------------------------------------------------------------------
/tests/regression_tests/regr-01-copy-binary-file/copy-source/image.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/moremoban/moban/11306525140a16f8b3231e41c73c48b3cfa4b333/tests/regression_tests/regr-01-copy-binary-file/copy-source/image.png
--------------------------------------------------------------------------------
/tests/regression_tests/regr-02-templating-failure-results-in-copy-action/.moban.yml:
--------------------------------------------------------------------------------
1 | configuration:
2 | template_dir:
3 | - copy-source
4 | targets:
5 | - output: regression-test.png
6 | template: image.png
7 | template_type: jj2
8 |
--------------------------------------------------------------------------------
/tests/regression_tests/regr-02-templating-failure-results-in-copy-action/copy-source/image.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/moremoban/moban/11306525140a16f8b3231e41c73c48b3cfa4b333/tests/regression_tests/regr-02-templating-failure-results-in-copy-action/copy-source/image.png
--------------------------------------------------------------------------------
/tests/requirements.txt:
--------------------------------------------------------------------------------
1 | pytest
2 | pytest-cov
3 | codecov
4 | coverage
5 | yamllint
6 | flake8
7 | black
8 | isort
9 | moban-handlebars
10 | pypi-mobans-pkg==0.0.12
11 | arrow
12 | jinja2_time
13 | pypifs
14 | gitfs2
15 | jinja2-python-version>=1.1.2
16 | httpfs
17 | collective.checkdocs
18 | Pygments
19 | moban-ansible
20 |
--------------------------------------------------------------------------------
/tests/test_buffered_writer.py:
--------------------------------------------------------------------------------
1 | import os
2 | import tempfile
3 | import unittest
4 |
5 | import fs
6 |
7 | from moban.externals import file_system
8 | from moban.externals.buffered_writer import BufferedWriter, write_file_out
9 |
10 | CONTENT = b"""
11 | helloworld
12 |
13 |
14 |
15 |
16 | """
17 | EXPECTED = "\n helloworld\n\n\n\n\n "
18 |
19 |
20 | class TestBufferedWriter(unittest.TestCase):
21 | def setUp(self):
22 | self.writer = BufferedWriter()
23 |
24 | def test_write_text(self):
25 | test_file = "testout"
26 | self.writer.write_file_out(test_file, CONTENT)
27 | self.writer.close()
28 | content = file_system.read_text(test_file)
29 | assert content == EXPECTED
30 | os.unlink(test_file)
31 |
32 | def test_write_a_zip(self):
33 | tmp_dir = os.path.normcase(tempfile.gettempdir())
34 | test_file = "zip://" + tmp_dir + "/testout.zip!/testout"
35 | self.writer.write_file_out(test_file, CONTENT)
36 | self.writer.close()
37 | content = file_system.read_text(test_file)
38 | assert content == EXPECTED
39 | os.unlink(fs.path.join(tmp_dir, "testout.zip"))
40 |
41 |
42 | def test_write_file_out():
43 | test_file = "testout"
44 | write_file_out(test_file, CONTENT)
45 | with open(test_file, "r") as f:
46 | content = f.read()
47 | assert content == EXPECTED
48 |
--------------------------------------------------------------------------------
/tests/test_copy_engine.py:
--------------------------------------------------------------------------------
1 | import os
2 | import unittest
3 |
4 | import fs
5 |
6 | from moban.core import ENGINES
7 | from moban.externals import file_system
8 |
9 |
10 | class TestContentForwardEngine(unittest.TestCase):
11 | def setUp(self):
12 | template_path = fs.path.join("tests", "fixtures")
13 | fsys = file_system.get_multi_fs([template_path])
14 | ContentForwardEngine = ENGINES.load_me_now("copy")
15 | self.engine = ContentForwardEngine(fsys)
16 |
17 | def test_get_template(self):
18 | template_content = self.engine.get_template("copier-test01.csv")
19 | # remove '\r' for windows
20 | assert "test 01\n", template_content.decode("utf-8").replace(
21 | "\r" == ""
22 | )
23 |
24 | def test_encoding_of_template(self):
25 | template_content_ = self.engine.get_template("coala_color.svg")
26 | with open("tests/fixtures/coala_color.svg", "r") as expected:
27 | expected = expected.read()
28 | assert expected, template_content_.decode("utf-8").replace("\r" == "")
29 |
30 | def test_get_template_from_string(self):
31 | test_content = "simply forwarded"
32 | template_content = self.engine.get_template_from_string(test_content)
33 | assert test_content == template_content
34 |
35 | def test_apply_template(self):
36 | test_content = "simply forwarded"
37 | template_content = self.engine.apply_template(test_content, "not used")
38 | assert test_content == template_content
39 |
40 |
41 | class TestCopyEncoding(unittest.TestCase):
42 | def setUp(self):
43 | template_path = fs.path.join("tests", "fixtures")
44 | template_fs = file_system.get_multi_fs([template_path])
45 | ContentForwardEngine = ENGINES.load_me_now("copy")
46 | self.engine = ContentForwardEngine(template_fs)
47 |
48 | def test_encoding_of_template(self):
49 | template_content = self.engine.get_template("coala_color.svg")
50 | with open("tests/fixtures/coala_color.svg", "rb") as expected:
51 | expected = expected.read()
52 | assert expected == template_content
53 | template_content = self.engine.get_template("non-unicode.char")
54 | with open("tests/fixtures/non-unicode.char", "rb") as expected:
55 | expected = expected.read()
56 | assert expected == template_content
57 |
--------------------------------------------------------------------------------
/tests/test_definitions.py:
--------------------------------------------------------------------------------
1 | from moban.deprecated import GitRequire
2 | from moban.core.definitions import TemplateTarget
3 |
4 |
5 | def test_git_require_repr():
6 | require = GitRequire(git_url="http://github.com/some/repo")
7 | assert "http://github.com/some/repo,None,False" == repr(require)
8 |
9 |
10 | def test_template_target_repr():
11 | require = TemplateTarget("template_file", "dat_file", "output")
12 | assert "template_file,dat_file,output,jinja2" == repr(require)
13 |
14 |
15 | def test_template_target_output_suffix_change():
16 | require = TemplateTarget(
17 | "template_file", "dat_file", "output.copy", template_type="copy"
18 | )
19 | assert "template_file,dat_file,output,copy" == repr(require)
20 |
21 |
22 | def test_template_target_output_suffix_updates_after_set():
23 | require = TemplateTarget(
24 | "template_file", "dat_file", "output.copy", template_type="copy"
25 | )
26 | require.set_template_type("jinja2")
27 | assert "template_file,dat_file,output.copy,jinja2" == repr(require)
28 |
29 |
30 | def test_clone_params():
31 | require = GitRequire(git_url="http://github.com/some/repo")
32 | actual = require.clone_params()
33 | expected = {"single_branch": True, "depth": 2}
34 | assert expected == actual
35 |
36 |
37 | def test_branch_params():
38 | require = GitRequire(
39 | git_url="http://github.com/some/repo", branch="ghpages"
40 | )
41 | actual = require.clone_params()
42 | expected = {"single_branch": True, "branch": "ghpages", "depth": 2}
43 | assert expected == actual
44 |
--------------------------------------------------------------------------------
/tests/test_hash_store.py:
--------------------------------------------------------------------------------
1 | import os
2 | import sys
3 | import unittest
4 | from unittest.mock import patch
5 |
6 | import pytest
7 |
8 | from moban.externals import file_system
9 | from moban.exceptions import NoPermissionsNeeded
10 | from moban.core.hashstore import HashStore, get_file_hash
11 |
12 |
13 | class TestHashStore(unittest.TestCase):
14 | def setUp(self):
15 | self.source_template = file_system.path_join(
16 | "tests", "fixtures", "a.jj2"
17 | )
18 | self.fixture = (
19 | "test.out",
20 | "test content".encode("utf-8"),
21 | self.source_template,
22 | )
23 | self.file_hash = get_file_hash(self.source_template)
24 |
25 | def tearDown(self):
26 | if os.path.exists(".moban.hashes"):
27 | os.unlink(".moban.hashes")
28 |
29 | def test_simple_use_case(self):
30 | hs = HashStore()
31 | flag = hs.is_file_changed(*self.fixture)
32 |
33 | hs.save_hashes()
34 | assert flag is True
35 |
36 | @patch("moban.core.hashstore.file_system.file_permissions")
37 | def test_permission_check_failed(self, fake):
38 | """
39 | when system permission fails, both source hash and
40 | target hash shall not use permission.
41 | """
42 | fake.side_effect = [NoPermissionsNeeded()]
43 | hs = HashStore()
44 | flag = hs.is_file_changed(*self.fixture)
45 |
46 | assert hs.hashes["test.out"] != self.file_hash
47 | hs.save_hashes()
48 | assert flag is True
49 |
50 | def test_dest_file_does_not_exist(self):
51 | hs = HashStore()
52 | flag = hs.is_file_changed(*self.fixture)
53 | hs.save_hashes()
54 | hs2 = HashStore()
55 | flag = hs2.is_file_changed(*self.fixture)
56 | assert flag is True
57 |
58 | def test_dest_file_exist(self):
59 | hs = HashStore()
60 | flag = hs.is_file_changed(*self.fixture)
61 | if flag:
62 | with open(self.fixture[0], "wb") as f:
63 | f.write(self.fixture[1])
64 | hs.save_hashes()
65 | hs2 = HashStore()
66 | flag = hs2.is_file_changed(*self.fixture)
67 | assert flag is False
68 | hs2.save_hashes()
69 | os.unlink(self.fixture[0])
70 |
71 | def test_dest_file_changed(self):
72 | """
73 | The situation is:
74 |
75 | moban once
76 | then update the generated file
77 | moban again, and the generated file should be detected
78 | and get templated
79 | """
80 | hs = HashStore()
81 | flag = hs.is_file_changed(*self.fixture)
82 | if flag:
83 | with open(self.fixture[0], "wb") as f:
84 | f.write(self.fixture[1])
85 | hs.save_hashes()
86 | # no change
87 | hs2 = HashStore()
88 | flag = hs2.is_file_changed(*self.fixture)
89 | assert flag is False
90 | hs2.save_hashes()
91 | # now let update the generated file
92 | hs3 = HashStore()
93 | with open(self.fixture[0], "w") as f:
94 | f.write("hey changed")
95 | flag = hs3.is_file_changed(*self.fixture)
96 | assert flag is True
97 | hs3.save_hashes()
98 | os.unlink(self.fixture[0])
99 |
100 | def test_dest_file_file_permision_changed(self):
101 | """
102 | Save as above, but this time,
103 | the generated file had file permision change
104 | """
105 | if sys.platform == "win32":
106 | return pytest.skip("No file permission check on windows")
107 | hs = HashStore()
108 | flag = hs.is_file_changed(*self.fixture)
109 | if flag:
110 | with open(self.fixture[0], "wb") as f:
111 | f.write(self.fixture[1])
112 | hs.save_hashes()
113 | # no change
114 | hs2 = HashStore()
115 | flag = hs2.is_file_changed(*self.fixture)
116 | assert flag is False
117 | hs2.save_hashes()
118 | # now let change file permision of generated file
119 | hs3 = HashStore()
120 | os.chmod(self.fixture[0], 0o766)
121 | flag = hs3.is_file_changed(*self.fixture)
122 | assert flag is True
123 | hs3.save_hashes()
124 | os.unlink(self.fixture[0])
125 |
--------------------------------------------------------------------------------
/tests/test_reporter.py:
--------------------------------------------------------------------------------
1 | import sys
2 | import unittest
3 | from unittest.mock import patch
4 |
5 | from moban.externals import reporter
6 |
7 | PY2 = sys.version_info[0] == 2
8 | if PY2:
9 | from StringIO import StringIO
10 | else:
11 | from io import StringIO
12 |
13 |
14 | class TestReporter(unittest.TestCase):
15 | def setUp(self):
16 | reporter.GLOBAL["PRINT"] = True
17 |
18 | def test_partial_run(self):
19 | patcher = patch("sys.stdout", new_callable=StringIO)
20 | fake_stdout = patcher.start()
21 | reporter.report_partial_run("Actioned", 1, 20)
22 | patcher.stop()
23 | assert fake_stdout.getvalue() == "Actioned 1 out of 20 files.\n"
24 |
25 | def test_full_run(self):
26 | patcher = patch("sys.stdout", new_callable=StringIO)
27 | fake_stdout = patcher.start()
28 | reporter.report_full_run("Worked on", 20)
29 | patcher.stop()
30 | assert fake_stdout.getvalue() == "Worked on 20 files.\n"
31 |
32 | def test_error_message(self):
33 | patcher = patch("sys.stderr", new_callable=StringIO)
34 | fake_stdout = patcher.start()
35 | reporter.report_error_message("something wrong")
36 | patcher.stop()
37 | assert fake_stdout.getvalue() == "Error: something wrong\n"
38 |
39 | def test_info_message(self):
40 | patcher = patch("sys.stdout", new_callable=StringIO)
41 | fake_stdout = patcher.start()
42 | reporter.report_info_message("for your information")
43 | patcher.stop()
44 | assert fake_stdout.getvalue() == "Info: for your information\n"
45 |
46 | def test_warning_message(self):
47 | patcher = patch("sys.stderr", new_callable=StringIO)
48 | fake_stdout = patcher.start()
49 | reporter.report_warning_message("Maybe you wanna know")
50 | patcher.stop()
51 | assert fake_stdout.getvalue() == "Warning: Maybe you wanna know\n"
52 |
53 | def test_report_templating(self):
54 | patcher = patch("sys.stdout", new_callable=StringIO)
55 | fake_stdout = patcher.start()
56 | reporter.report_templating("Transforming", "a", "b")
57 | patcher.stop()
58 | assert fake_stdout.getvalue() == "Transforming a to b\n"
59 |
60 | def test_no_action(self):
61 | patcher = patch("sys.stdout", new_callable=StringIO)
62 | fake_stdout = patcher.start()
63 | reporter.report_no_action()
64 | patcher.stop()
65 | assert fake_stdout.getvalue() == "No actions performed\n"
66 |
67 | def test_format_single(self):
68 | message = "1 files"
69 | ret = reporter._format_single(message, 1)
70 | assert ret == "1 file"
71 |
72 | def test_report_template_not_in_moban_file(self):
73 | patcher = patch("sys.stderr", new_callable=StringIO)
74 | fake_stdout = patcher.start()
75 | reporter.report_template_not_in_moban_file("test.jj2")
76 | patcher.stop()
77 | assert (
78 | fake_stdout.getvalue()
79 | == "Warning: test.jj2 is not defined in your moban file!\n"
80 | )
81 |
82 | def test_report_file_extension_not_needed(self):
83 | patcher = patch("sys.stdout", new_callable=StringIO)
84 | fake_stdout = patcher.start()
85 | reporter.report_file_extension_not_needed()
86 | patcher.stop()
87 | assert (
88 | fake_stdout.getvalue()
89 | == "Info: File extension is not required for ad-hoc type\n"
90 | )
91 |
--------------------------------------------------------------------------------
/tests/test_store.py:
--------------------------------------------------------------------------------
1 | from moban.core.definitions import TemplateTarget
2 | from moban.core.mobanfile.store import Store
3 |
4 |
5 | def test_store():
6 | store = Store()
7 | output = "output"
8 | target = TemplateTarget("template_file", "data_file", output)
9 | store.add(target)
10 | assert target == store.look_up_by_output.get(output)
11 |
--------------------------------------------------------------------------------
/tests/utils.py:
--------------------------------------------------------------------------------
1 | import os
2 | import sys
3 | import unittest
4 | from textwrap import dedent
5 | from unittest.mock import patch
6 |
7 | import fs
8 | from fs.opener.parse import parse_fs_url
9 |
10 | from moban.main import main
11 | from moban.externals import file_system
12 |
13 |
14 | def verify_content(file_name, expected):
15 | with open(file_name, "r") as f:
16 | content = f.read()
17 | assert content == expected
18 |
19 |
20 | def verify_content_with_fs(file_name, expected):
21 | content = file_system.read_unicode(file_name)
22 | assert content == expected
23 |
24 |
25 | def run_moban(args, folder, criterias):
26 | with patch.object(sys, "argv", args):
27 | main()
28 | for output, expected in criterias:
29 | verify_content(output, expected)
30 | os.unlink(output)
31 |
32 |
33 | def run_moban_with_fs(args, folder, criterias):
34 | with patch.object(sys, "argv", args):
35 | main()
36 |
37 | for output, expected in criterias:
38 | verify_content_with_fs(output, expected)
39 | result = parse_fs_url(output)
40 | os.unlink(result.resource) # delete the zip file
41 |
42 |
43 | class Docs(unittest.TestCase):
44 | def setUp(self):
45 | self.current = os.getcwd()
46 | self.base_folder = "docs"
47 |
48 | def tearDown(self):
49 | if os.path.exists(".moban.hashes"):
50 | os.unlink(".moban.hashes")
51 | os.chdir(self.current)
52 |
53 | def run_moban(self, moban_cli, working_directory, assertions):
54 | os.chdir(fs.path.join(self.base_folder, working_directory))
55 | run_moban(moban_cli, None, assertions)
56 |
57 | def run_moban_with_fs(self, moban_cli, working_directory, assertions):
58 | os.chdir(fs.path.join(self.base_folder, working_directory))
59 | run_moban_with_fs(moban_cli, None, assertions)
60 |
61 |
62 | def custom_dedent(long_texts):
63 | refined = dedent(long_texts)
64 | if refined.startswith("\n"):
65 | refined = refined[1:]
66 | return refined
67 |
--------------------------------------------------------------------------------