├── .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.yaml000644 000765 000024 00000000034 13517160574 014307 0ustar00jaskastaff000000 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 | --------------------------------------------------------------------------------