├── .github ├── FUNDING.yml └── workflows │ ├── moban-update.yml │ └── pythonpublish.yml ├── .gitignore ├── .isort.cfg ├── .moban.d ├── CUSTOM_README.rst.jj2 ├── additional_badges.rst.jj2 ├── custom_setup.py.jj2 ├── docs │ └── source │ │ ├── conf.py.jj2 │ │ └── index.rst.jj2 ├── header.rst ├── requirements.txt.jj2 ├── usage.rst.jj2 ├── yehua.background.rst ├── yehua.feature.rst └── yehua_travis.yml.jj2 ├── .moban.yml ├── .travis.yml ├── CHANGELOG.rst ├── CONTRIBUTORS.rst ├── LICENSE ├── MANIFEST.in ├── Makefile ├── README.rst ├── changelog.yml ├── docs ├── requirements.txt └── source │ ├── _static │ ├── github.png │ ├── push2github.png │ ├── yehua-0.png │ ├── yehua-1.png │ ├── yehua-2.png │ ├── yehua-3.png │ ├── yehua-4.png │ ├── yehua-5.png │ ├── yehua-6.png │ ├── yehua-7.png │ ├── yehua-cookiecutter.gif │ ├── yehua-hello.gif │ └── yehua-story.png │ ├── conf.py │ ├── enterprise_usage.rst │ ├── extended_usage.rst │ ├── filespec.rst │ ├── hello_cookie_cutter │ ├── cookiecutter.json │ ├── yehua.yml │ └── {{cookiecutter.directory_name}} │ │ └── {{cookiecutter.file_name}}.py │ ├── index.rst │ ├── spelling_wordlist.txt │ ├── usage.rst │ └── yehua-story.uml ├── examples └── npm-init │ ├── index.js │ ├── package.json.jj2 │ └── yehua.yml ├── format.sh ├── lint.sh ├── requirements.txt ├── setup.cfg ├── setup.py ├── test.sh ├── tests ├── cutie_tests │ ├── __init__.py │ ├── test_get_number.py │ ├── test_prompt_yes_or_no.py │ ├── test_secure_input.py │ ├── test_select.py │ └── test_select_multiple.py ├── fixtures │ ├── project_s │ │ ├── .editorconfig │ │ ├── .github │ │ │ └── ISSUE_TEMPLATE.md │ │ ├── .gitignore │ │ ├── .moban.yml │ │ ├── .travis.yml │ │ ├── AUTHORS.rst │ │ ├── CONTRIBUTING.rst │ │ ├── HISTORY.rst │ │ ├── LICENSE │ │ ├── MANIFEST.in │ │ ├── Makefile │ │ ├── README.rst │ │ ├── docs │ │ │ ├── Makefile │ │ │ ├── authors.rst │ │ │ ├── conf.py │ │ │ ├── contributing.rst │ │ │ ├── history.rst │ │ │ ├── index.rst │ │ │ ├── installation.rst │ │ │ ├── make.bat │ │ │ ├── readme.rst │ │ │ └── usage.rst │ │ ├── project_s.yml │ │ ├── project_s │ │ │ ├── __init__.py │ │ │ ├── cli.py │ │ │ └── project_s.py │ │ ├── requirements_dev.txt │ │ ├── setup.cfg │ │ ├── setup.py │ │ ├── tests │ │ │ ├── __init__.py │ │ │ └── test_project_s.py │ │ └── tox.ini │ ├── project_templating │ │ └── test-me │ │ │ ├── Makefile │ │ │ ├── changelog.yml │ │ │ └── test-me.yml │ └── project_yehua │ │ ├── .azure-pipelines-steps-macos.yml │ │ ├── .azure-pipelines-steps.yml │ │ ├── .github │ │ └── workflows │ │ │ ├── moban-update.yml │ │ │ └── pythonpublish.yml │ │ ├── .gitignore │ │ ├── .isort.cfg │ │ ├── .moban.d │ │ ├── CUSTOM_README.rst.jj2 │ │ ├── custom_setup.py.jj2 │ │ └── tests │ │ │ └── custom_requirements.txt.jj2 │ │ ├── .moban.yml │ │ ├── .travis.yml │ │ ├── CHANGELOG.rst │ │ ├── CONTRIBUTORS.rst │ │ ├── LICENSE │ │ ├── MANIFEST.in │ │ ├── Makefile │ │ ├── Pipfile │ │ ├── README.rst │ │ ├── azure-pipelines.yml │ │ ├── changelog.yml │ │ ├── docs │ │ └── source │ │ │ └── conf.py │ │ ├── format.sh │ │ ├── lint.sh │ │ ├── project_yehua.yml │ │ ├── project_yehua │ │ ├── __init__.py │ │ └── _version.py │ │ ├── requirements.txt │ │ ├── setup.cfg │ │ ├── setup.py │ │ ├── test.bat │ │ ├── test.sh │ │ └── tests │ │ └── requirements.txt ├── requirements.txt ├── test_cookie_cutter.py ├── test_main.py ├── test_project.py └── test_yehua.py ├── yehua-npm-usage.gif ├── yehua-usage.gif ├── yehua.yaml └── yehua ├── __init__.py ├── __main__.py ├── _version.py ├── cookiecutter.py ├── cookiecutter_to_yehua.py ├── main.py ├── project.py ├── theme.py ├── thirdparty ├── __init__.py └── cutie.py └── utils.py /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: chfw 4 | patreon: chfw 5 | -------------------------------------------------------------------------------- /.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 | - name: Set up Python 12 | uses: actions/setup-python@v1 13 | with: 14 | python-version: '3.7' 15 | - name: check changes 16 | run: | 17 | pip install moban gitfs2 pypifs moban-jinja2-github moban-ansible 18 | moban 19 | git status 20 | git diff --exit-code 21 | - name: Auto-commit 22 | if: failure() 23 | uses: docker://cdssnc/auto-commit-github-action 24 | env: 25 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 26 | with: 27 | args: >- 28 | This is an auto-commit, updating project meta data, 29 | such as changelog.rst, contributors.rst 30 | -------------------------------------------------------------------------------- /.github/workflows/pythonpublish.yml: -------------------------------------------------------------------------------- 1 | name: Upload Yehua Package 2 | 3 | on: 4 | release: 5 | types: [created] 6 | 7 | jobs: 8 | deploy: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - uses: actions/checkout@v1 12 | - name: Set up Python 13 | uses: actions/setup-python@v1 14 | with: 15 | python-version: '3.x' 16 | - name: Install dependencies 17 | run: | 18 | python -m pip install --upgrade pip 19 | pip install setuptools wheel twine 20 | - name: Build and publish 21 | env: 22 | TWINE_USERNAME: ${{ secrets.PYPI_USERNAME }} 23 | TWINE_PASSWORD: ${{ secrets.PYPI_PASSWORD }} 24 | run: | 25 | python setup.py sdist bdist_wheel 26 | twine upload dist/* 27 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # April 2016 2 | # reference: https://github.com/github/gitignore/blob/master/Python.gitignore 3 | # Byte-compiled / optimized / DLL files 4 | __pycache__/ 5 | *.py[cod] 6 | *$py.class 7 | 8 | # C extensions 9 | *.so 10 | 11 | # Distribution / packaging 12 | .Python 13 | env/ 14 | build/ 15 | develop-eggs/ 16 | dist/ 17 | downloads/ 18 | eggs/ 19 | .eggs/ 20 | lib/ 21 | lib64/ 22 | parts/ 23 | sdist/ 24 | var/ 25 | *.egg-info/ 26 | .installed.cfg 27 | *.egg 28 | 29 | # PyInstaller 30 | # Usually these files are written by a python script from a template 31 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 32 | *.manifest 33 | *.spec 34 | 35 | # Installer logs 36 | pip-log.txt 37 | pip-delete-this-directory.txt 38 | 39 | # Unit test / coverage reports 40 | htmlcov/ 41 | .tox/ 42 | .coverage 43 | .coverage.* 44 | .cache 45 | nosetests.xml 46 | coverage.xml 47 | *,cover 48 | .hypothesis/ 49 | 50 | # Translations 51 | *.mo 52 | *.pot 53 | 54 | # Django stuff: 55 | *.log 56 | local_settings.py 57 | 58 | # Flask stuff: 59 | instance/ 60 | .webassets-cache 61 | 62 | # Scrapy stuff: 63 | .scrapy 64 | 65 | # Sphinx documentation 66 | docs/_build/ 67 | 68 | # PyBuilder 69 | target/ 70 | 71 | # IPython Notebook 72 | .ipynb_checkpoints 73 | 74 | # pyenv 75 | .python-version 76 | 77 | # celery beat schedule file 78 | celerybeat-schedule 79 | 80 | # dotenv 81 | .env 82 | 83 | # virtualenv 84 | venv/ 85 | ENV/ 86 | 87 | # Spyder project settings 88 | .spyderproject 89 | 90 | # Rope project settings 91 | .ropeproject 92 | *~ 93 | commons/ 94 | commons 95 | .moban.hashes 96 | -------------------------------------------------------------------------------- /.isort.cfg: -------------------------------------------------------------------------------- 1 | [settings] 2 | line_length=79 3 | # Ignore generated files 4 | skip=setup.py, yehua/__init__.py 5 | known_third_party=ruamel.yaml,jinja2,moban,colorful,rich,readchar,colorama,mock, nose 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.d/CUSTOM_README.rst.jj2: -------------------------------------------------------------------------------- 1 | {% extends "README.rst.jj2" %} 2 | 3 | {% from "usage.rst.jj2" import usage %} 4 | 5 | {% block features %} 6 | {% include 'additional_badges.rst.jj2' %} 7 | 8 | Introduction 9 | ================================================================================ 10 | 11 | {%include "yehua.feature.rst"%} 12 | 13 | {% endblock %} 14 | 15 | {% block bottom_block %} 16 | 17 | For offline usage, you need to get `pypi-mobans-pkg` installed:: 18 | 19 | $ pip install yehua[pypi-mobans] 20 | 21 | 22 | or:: 23 | 24 | $ pip install pypi-mobans-pkg 25 | 26 | 27 | Usage 28 | ================================================================================ 29 | 30 | {{ usage('readme') }} 31 | 32 | Background 33 | ================================================================================ 34 | 35 | {%include "yehua.background.rst" %} 36 | 37 | 38 | License 39 | ================================================================================ 40 | 41 | NEW BSD License 42 | 43 | 44 | It embeds MIT licensed `cutie `_ from 45 | Hans Schülein. Please refer to LICENSE file for more details 46 | {% endblock %} -------------------------------------------------------------------------------- /.moban.d/additional_badges.rst.jj2: -------------------------------------------------------------------------------- 1 | .. image:: https://raw.githubusercontent.com/pyexcel/pyexcel.github.io/master/images/patreon.png 2 | :target: https://www.patreon.com/chfw 3 | 4 | .. image:: https://badges.gitter.im/chfw_yehua/Lobby.svg 5 | :alt: Join the chat at https://gitter.im/chfw_yehua/Lobby 6 | :target: https://gitter.im/chfw_yehua/Lobby?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge 7 | 8 | -------------------------------------------------------------------------------- /.moban.d/custom_setup.py.jj2: -------------------------------------------------------------------------------- 1 | {% extends "setup.py.jj2" %} 2 | 3 | {%block additional_classifiers%} 4 | 'Topic :: Utilities', 5 | {%endblock %} 6 | 7 | {%block platform_block %} 8 | {%endblock%} 9 | 10 | 11 | {% block morefiles %}"CONTRIBUTORS.rst",{% endblock %} -------------------------------------------------------------------------------- /.moban.d/docs/source/conf.py.jj2: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | {%block additional_imports%} 3 | {%endblock%} 4 | DESCRIPTION = ( 5 | {% for i in range(0, description|length, 70) %} 6 | '{{ description[i:(70+i)] }}' + 7 | {% endfor %} 8 | '' 9 | ) 10 | extensions = [ 11 | 'sphinx.ext.autodoc', 12 | 'sphinx.ext.doctest', 13 | 'sphinx.ext.intersphinx', 14 | 'sphinx.ext.viewcode', 15 | {%block SPHINX_EXTENSIONS%} 16 | {%endblock%} 17 | ] 18 | 19 | templates_path = ['_templates'] 20 | source_suffix = '.rst' 21 | master_doc = 'index' 22 | spelling_word_list_filename = 'spelling_wordlist.txt' 23 | project = u'{{name}}' 24 | copyright = u'{{copyright_year}} {{company}}' 25 | version = '{{release}}' 26 | release = '{{version}}' 27 | exclude_patterns = [] 28 | pygments_style = 'sphinx' 29 | {%block custom_doc_theme%} 30 | html_theme = 'default' 31 | {%endblock%} 32 | html_static_path = ['_static'] 33 | htmlhelp_basename = '{{name}}doc' 34 | latex_elements = {} 35 | latex_documents = [ 36 | ('index', '{{name}}.tex', 37 | '{{name}} Documentation', 38 | '{{company}}', 'manual'), 39 | ] 40 | man_pages = [ 41 | ('index', '{{name}}', 42 | '{{name}} Documentation', 43 | [u'{{company}}'], 1) 44 | ] 45 | texinfo_documents = [ 46 | ('index', '{{name}}', 47 | '{{name}} Documentation', 48 | '{{company}}', '{{name}}', 49 | DESCRIPTION, 50 | 'Miscellaneous'), 51 | ] 52 | -------------------------------------------------------------------------------- /.moban.d/docs/source/index.rst.jj2: -------------------------------------------------------------------------------- 1 | {% from "usage.rst.jj2" import usage %} 2 | {%include "header.rst" %} 3 | 4 | {% include 'badges.rst.jj2' %} 5 | {% include 'additional_badges.rst.jj2' %} 6 | 7 | Introduction 8 | -------------------------------------------------------------------------------- 9 | 10 | {%include "yehua.feature.rst"%} 11 | 12 | 13 | Installation 14 | -------------------------------------------------------------------------------- 15 | 16 | {%include "installation.rst.jj2" %} 17 | 18 | Usage 19 | -------------------------------------------------------------------------------- 20 | 21 | {{ usage('sphinx') }} 22 | 23 | Background 24 | -------------------------------------------------------------------------------- 25 | 26 | {%include "yehua.background.rst" %} 27 | 28 | Documentation 29 | -------------------------------------------------------------------------------- 30 | 31 | .. toctree:: 32 | 33 | usage 34 | extended_usage 35 | enterprise_usage 36 | 37 | License 38 | ================================================================================ 39 | 40 | NEW BSD License 41 | 42 | -------------------------------------------------------------------------------- /.moban.d/header.rst: -------------------------------------------------------------------------------- 1 | `{{name}}` - Let you focus on code, instead of setup scaffolding 2 | ================================================================================ 3 | 4 | :Author: {{author}} 5 | :Source code: http://github.com/{{organisation}}/{{name}}.git 6 | :Issues: http://github.com/{{organisation}}/{{name}}/issues 7 | :License: {{license}} License 8 | {% if version == release%} 9 | :Released: |version| 10 | {%else%} 11 | :Development: |release| 12 | :Released: |version| 13 | {%endif%} 14 | :Generated: |today| 15 | -------------------------------------------------------------------------------- /.moban.d/requirements.txt.jj2: -------------------------------------------------------------------------------- 1 | {% for dependency in dependencies: %} 2 | {{dependency}} 3 | {% endfor %} 4 | -------------------------------------------------------------------------------- /.moban.d/usage.rst.jj2: -------------------------------------------------------------------------------- 1 | {%macro usage(page) %} 2 | 3 | {% if page == 'readme' %} 4 | {%set image_path = 'docs/source/_static'%} 5 | {% else %} 6 | {%set image_path = '_static'%} 7 | {% endif %} 8 | 9 | Simply type in and you are taken care of:: 10 | 11 | $ yh 12 | 13 | It will use pypi-mobans-pkg by default and if it is not installed, it will 14 | install latest one. Then pypi-mobans-pkg takes over and will do 15 | [these](https://github.com/moremoban/pypi-mobans/blob/dev/yehua.yml) 16 | for you: 17 | 18 | #. Consult you on your project static information which can update as 19 | many as you want to. 20 | #. Create the Python package folder structure 21 | #. Initialize the package as git project 22 | 23 | You will simply need to commit it after you will have reviewed the 24 | generated files. 25 | 26 | Tutorial 27 | ----------------- 28 | 29 | Let's make a python command line utility using `yehua`. The command 30 | will be `hello` and it prints `world`. 31 | 32 | Step 1 Let's launch yehua 33 | ****************************** 34 | |slide1| 35 | 36 | Step 2 Fill-in the meta data for your project 37 | *********************************************** 38 | |slide2| 39 | 40 | At the end, yehua generates a folder named 'hello', which contains [all necessary 41 | files](https://github.com/moremoban/pypi-mobans). 42 | 43 | Step 3 Start coding 44 | ************************* 45 | 46 | .. image:: https://github.com/moremoban/yehua/raw/dev/docs/source/_static/yehua-hello.gif 47 | :width: 600px 48 | 49 | 50 | In above animation, we write up the actual code in hello/main.py 51 | 52 | .. code:: python 53 | 54 | def main(): 55 | print('world') 56 | 57 | Why is it enough? yehua generates a command utility python and 58 | it has pre-wired to invoke hello.main.main() function. You 59 | can find it out in setup.py. 60 | 61 | Step 4 Install it 62 | ********************* 63 | Now all is done. Let's install it 64 | 65 | |slide7| 66 | 67 | You can now run `hello` at your command line. 68 | 69 | Step 5 push to github 70 | *************************** 71 | 72 | Suppose you are happy with everything. Please do the following to 73 | push it to your github:: 74 | 75 | $ git commit -am ":sparkle: initial commit" 76 | 77 | Then create your project repository in github and do these to push it out:: 78 | 79 | $ git remote add origin https://github.com/moremoban/hello.git 80 | $ git push origin master 81 | 82 | You can find the `hello project`_ on github. 83 | 84 | Step 7 enable travis 85 | *************************** 86 | 87 | The generated project already has `.travis.yml` file. What you 88 | will need to do is to register with travis.org if you have not 89 | done so. And then go to travis and activate your project. 90 | 91 | 92 | .. |slide1| image:: {{image_path}}/yehua-0.png 93 | :scale: 100% 94 | .. |slide2| image:: {{image_path}}/yehua-1.png 95 | :scale: 100% 96 | .. |slide3| image:: {{image_path}}/yehua-2.png 97 | :scale: 100% 98 | .. |slide4| image:: {{image_path}}/yehua-3.png 99 | :scale: 100% 100 | .. |slide5| image:: {{image_path}}/yehua-4.png 101 | :scale: 100% 102 | .. |slide6| image:: {{image_path}}/yehua-5.png 103 | :scale: 100% 104 | .. |slide7| image:: {{image_path}}/yehua-7.png 105 | :scale: 100% 106 | .. |slide9| image:: {{image_path}}/github.png 107 | :scale: 60% 108 | .. |slide10| image:: {{image_path}}/push2github.png 109 | :scale: 60% 110 | 111 | .. _hello project: https://github.com/moremoban/hello 112 | .. _pyexcel commons: https://github.com/pyexcel/pyexcel-commons 113 | .. _pyexcel: https://github.com/pyexcel 114 | .. _moban: https://github.com/moremoban/moban 115 | .. _setupmobans: https://github.com/moremoban/setupmobans 116 | {% endmacro %} -------------------------------------------------------------------------------- /.moban.d/yehua.background.rst: -------------------------------------------------------------------------------- 1 | The original problem I was trying to solve is: I would like to place 2 | common paragraphs in the documentation of my projects in a central 3 | place (pyexcel-mobans), and all projects could reference it dynamically 4 | so that when those common paragraphs get updated, the updates can be 5 | easily propagated to all relevant projects. The derived problem is: 6 | what can I do to a new project? I found myself doing a lot of 7 | copy-and-paste a lot, which lead to the creation of "yehua". Later, 8 | John Vandenberg, an active member of coala, suggested extracting the 9 | generic sets of pyexcel-mobans to form pypi-mobans, so that 10 | a vanilla python package can be created. Why not cookiecutter? 11 | Well, I have not heard of it at the time of creation. But it turns out 12 | that this project started to pave the way to be the cookiecutter 13 | for organisations. 14 | 15 | Why to choose "yehua"? Here is `the little story`_ behind the 16 | choice of name. And this `music video`_ would help bridge the 17 | cultural gap between you and me. 18 | 19 | .. _the little story: https://github.com/moremoban/yehua/issues/5#issuecomment-317218010 20 | .. _music video: https://www.youtube.com/watch?v=_JFTOQ6F1-M&frags=pl%2Cwn 21 | 22 | 23 | -------------------------------------------------------------------------------- /.moban.d/yehua.feature.rst: -------------------------------------------------------------------------------- 1 | 2 | .. image:: https://github.com/moremoban/yehua/raw/dev/yehua-usage.gif 3 | :width: 600px 4 | 5 | **yehua** /'jɛhwa/ is {{description|lower}} It creates a project skeleton, S 6 | from the default project template, T, of an organisation. `moban`_, the other 7 | tool of moremoban organisation, keeps S in synchronisation with T forever. This 8 | use case is what we called: **continuous templating**. 9 | 10 | .. image:: https://github.com/moremoban/yehua/raw/dev/docs/source/_static/yehua-story.png 11 | :width: 600px 12 | 13 | Cookiecutter users 14 | -------------------------- 15 | 16 | Yes, we now support cookiecutter templates. It has been requested since 2018 17 | Europython. Simply there is tons of cookiecutter templates out there. 18 | 19 | 20 | .. image:: https://github.com/moremoban/yehua/raw/dev/docs/source/_static/yehua-cookiecutter.gif 21 | :width: 600px 22 | 23 | What you do is to replace 'cookiecutter' with 'yh':: 24 | 25 | $ pip install yehua[cookiecutter] 26 | $ yh gh:audreyr/cookiecutter-pypackage 27 | 28 | And what moremoban promise is, whenever your source template changes, you 29 | can `synchronize` them any time with another moremoban's command 'moban':: 30 | 31 | $ moban 32 | 33 | Yes, you need a separate command, which replaces your effort to synchronize 34 | the upstream templates all the time. 35 | 36 | What's different with Yehua 37 | ------------------------------------ 38 | 39 | When the scope is a single project, **yehua** is no different to `cookiecutter`_ and 40 | `PyScaffold`_. It will create a project skeleton from `pypi-mobans`_, other templates such 41 | as cookiecutter templates, yehua mobans. 42 | 43 | When the scope is all projects within an organisation, **yehua** helps tackle 44 | information fragmentation problem, because all new projects after its creation, 45 | are still in synchronisation with T. For example, removing python 2.7 test 46 | in your travis file, can be done either manually by hand or automatically via 47 | `moban`_. What's the difference? The latter is faster and typo-free option. Here is 48 | `an example`_. 49 | 50 | `PyScaffold`_ version 3 has rolled out '--update' option, recognizing the organisational 51 | need of continous templating. Why do not **yehua** join `PyScaffold`_? Well, 52 | moremoban organisation started with '--update' at the start so our architecture 53 | and vision are closer to that of `cookiecutter`_: 54 | 55 | 1. we do not want to limit ourselves in pythonsphere. We wanted to serve all 56 | IT projects. In our mind, they are all about text templating. 57 | 58 | 2. we split the tool and the templates, serving the previous statement. 59 | People can create npm package template and use yehua+moban for continuous templating. 60 | Here are a list of examples: 61 | 62 | * `pypkg-mobans in pyecharts project `_ 63 | * `echarts-js-mobans in echarts-map project `_ 64 | 65 | .. _moban: https://github.com/moremoban/moban 66 | .. _cookiecutter: https://github.com/cookiecutter/cookiecutter 67 | .. _PyScaffold: https://github.com/pyscaffold/pyscaffold 68 | .. _pypi-mobans: https://github.com/moremobans/pypi-mobans 69 | .. _an example: https://github.com/moremoban/yehua/blob/dev/.github/workflows/moban-update.yml 70 | 71 | -------------------------------------------------------------------------------- /.moban.d/yehua_travis.yml.jj2: -------------------------------------------------------------------------------- 1 | {% extends 'travis.yml.jj2' %} 2 | 3 | {%block custom_python_versions%} 4 | python: 5 | - 3.7 6 | - 3.6 7 | - 3.8 8 | {%endblock%} 9 | -------------------------------------------------------------------------------- /.moban.yml: -------------------------------------------------------------------------------- 1 | configuration: 2 | configuration_dir: "git://github.com/pyexcel/pyexcel-mobans.git!/config" 3 | template_dir: 4 | - "git://github.com/moremoban/pypi-mobans.git?submodule=true&brach=dev!/statics" 5 | - "git://github.com/moremoban/pypi-mobans.git?submodule=true&branch=dev!/templates" 6 | - ".moban.d" 7 | configuration: yehua.yaml 8 | targets: 9 | - README.rst: CUSTOM_README.rst.jj2 10 | - setup.py: custom_setup.py.jj2 11 | - requirements.txt: requirements.txt.jj2 12 | - "tests/requirements.txt": "tests/requirements.txt.jj2" 13 | - "docs/source/conf.py": "docs/source/conf.py.jj2" 14 | - "docs/source/index.rst": "docs/source/index.rst.jj2" 15 | - 'yehua/_version.py': '_version.py.jj2' 16 | - '.travis.yml': 'yehua_travis.yml.jj2' 17 | - output: CHANGELOG.rst 18 | configuration: changelog.yml 19 | template: CHANGELOG.rst.jj2 20 | - ".github/workflows/moban-update.yml": "moban-update.yml" 21 | - Makefile: Makefile.jj2 22 | - CONTRIBUTORS.rst: CONTRIBUTORS.rst.jj2 23 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | dist: xenial 3 | language: python 4 | notifications: 5 | email: false 6 | python: 7 | - 3.7 8 | - 3.6 9 | - 3.8 10 | 11 | stages: 12 | - lint 13 | - moban 14 | - test 15 | 16 | 17 | .lint: &lint 18 | git: 19 | submodules: false 20 | python: 3.6 21 | env: 22 | - MINREQ=0 23 | stage: lint 24 | script: bash lint.sh 25 | 26 | .moban: &moban 27 | python: 3.6 28 | env: 29 | - MINREQ=0 30 | stage: moban 31 | install: pip install moban gitfs2 pypifs moban-jinja2-github moban-ansible 32 | script: 33 | - moban 34 | - git diff --exit-code 35 | 36 | jobs: 37 | include: 38 | - *moban 39 | - *lint 40 | 41 | stage: test 42 | 43 | before_install: 44 | - if [[ -f min_requirements.txt && "$MINREQ" -eq 1 ]]; then 45 | mv min_requirements.txt requirements.txt ; 46 | fi 47 | - test ! -f rnd_requirements.txt || 48 | pip install --no-deps -r rnd_requirements.txt 49 | - test ! -f rnd_requirements.txt || pip install -r rnd_requirements.txt ; 50 | - pip install -r tests/requirements.txt 51 | script: 52 | - make test 53 | after_success: 54 | codecov 55 | -------------------------------------------------------------------------------- /CHANGELOG.rst: -------------------------------------------------------------------------------- 1 | Change log 2 | ================================================================================ 3 | 4 | 0.1.4 - 14.09.2020 5 | -------------------------------------------------------------------------------- 6 | 7 | **Updated** 8 | 9 | #. `#53 `_: parity with 10 | cookiecutter: support gh:audreyr/cookiecutter-pypackage 11 | 12 | 0.1.3 - 13.09.2020 13 | -------------------------------------------------------------------------------- 14 | 15 | **Updated** 16 | 17 | #. use pypi-mobans v0.1.0 18 | #. no longer, yehua has any template in itself. 19 | 20 | 0.1.2 - 26.08.2020 21 | -------------------------------------------------------------------------------- 22 | 23 | **Fixed** 24 | 25 | #. support pypi-mobans v0.0.15. 26 | #. auto-generation of contributors 27 | #. github action for moban update and commits 28 | #. better isort action 29 | 30 | 0.1.1 - 29.05.2020 31 | -------------------------------------------------------------------------------- 32 | 33 | **Fixed** 34 | 35 | #. `#62 `_: fix unwanted 36 | dependency for yh standalone use 37 | 38 | 0.1.0 - 19.05.2020 39 | -------------------------------------------------------------------------------- 40 | 41 | **Removed** 42 | 43 | #. python 2 support has been dropped 44 | 45 | **Updated** 46 | 47 | #. -v is changed to do moban style verbose(-v, -vv, -vvv), for version, please 48 | use -V 49 | 50 | **Added** 51 | 52 | #. `#37 `_: cookiecutter support 53 | #. `#38 `_: non-verbose mode 54 | #. added moban update work flow 55 | #. python filesystem 2 support. yehua templates and cookiecutter templates can 56 | be in git, zip, s3, etc. 57 | 58 | 0.0.8 - 10-01-2020 59 | -------------------------------------------------------------------------------- 60 | 61 | **Added** 62 | 63 | #. `#30 `_: 'yh -h' or 'yh --help' 64 | triggers help text 65 | #. `#32 `_: pypi-moban-pkg as 66 | installation extras 67 | #. enable auto github auto publishing 68 | #. generate mit license 69 | 70 | **Updated** 71 | 72 | #. `#35 `_: better error message 73 | when the project name has been made a directory already 74 | #. updated moban dependency to v0.6.0 75 | #. updated yaml library to ruamel.yaml. PyYAML is out because only one yaml 76 | library is wanted in the organisation. 77 | 78 | 0.0.7 - 6/10/2019 79 | -------------------------------------------------------------------------------- 80 | 81 | **Updated** 82 | 83 | #. upgrade yehua to use pypi-mobans-pkg version 0.0.7 84 | #. generated project will have azure build pipeline, moban command stage in 85 | travis and local flake8 check 86 | 87 | 0.0.6 - 04/15/2019 88 | -------------------------------------------------------------------------------- 89 | 90 | **Updated** 91 | 92 | #. upgrade yehua to use pypi-mobans-pkg version 0.0.5 93 | #. generated project will have four new files: pipfile, lint.sh, changelog.yml 94 | and Makefile 95 | 96 | 0.0.5 - 08/11/2018 97 | -------------------------------------------------------------------------------- 98 | 99 | **added** 100 | 101 | #. `#6 `_: provide Pipfile for 102 | pipenv 103 | 104 | 0.0.4 - 06/07/2018 105 | -------------------------------------------------------------------------------- 106 | 107 | **Updated** 108 | 109 | #. `#11 `_: keep up-to-date with 110 | pypi-mobans 111 | 112 | 0.0.3 - 24/02/2018 113 | -------------------------------------------------------------------------------- 114 | 115 | **Added** 116 | 117 | #. To add all files to a git repo is being made optional. The action is 118 | specified in `git-repo-files` under `post-moban` section. This particular 119 | need arises when it is used to scaffold other type of projects such as npm. 120 | 121 | 0.0.2 - 15/10/2017 122 | -------------------------------------------------------------------------------- 123 | 124 | **Added** 125 | 126 | #. Automatically inflate project meta data. One yehua command and typing a few 127 | questions are required before a complete project scaffolding 128 | #. Automatically obtain setupmobans repo for previous task. 129 | #. Automatically initialize package as git project and add all project files for 130 | the user to commit 131 | 132 | **Removed** 133 | 134 | #. Built-in template files are off-loaded to setupmobans, which are more 135 | frequently updated. 136 | 137 | 0.0.1 - 02/07/2017 138 | -------------------------------------------------------------------------------- 139 | -------------------------------------------------------------------------------- /CONTRIBUTORS.rst: -------------------------------------------------------------------------------- 1 | 2 | 3 | 2 contributors 4 | ================================================================================ 5 | 6 | In alphabetical order: 7 | 8 | * `Ayan Banerjee `_ 9 | * `John Vandenberg `_ 10 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2015-2020 by Onni Software Ltd. and its contributors 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms of the software as well 5 | as documentation, with or without modification, are permitted provided 6 | that the following conditions are met: 7 | 8 | * Redistributions of source code must retain the above copyright notice, this 9 | list of conditions and the following disclaimer. 10 | 11 | * Redistributions in binary form must reproduce the above copyright notice, 12 | this list of conditions and the following disclaimer in the documentation 13 | and/or other materials provided with the distribution. 14 | 15 | * Neither the name of 'yehua' nor the names of the contributors 16 | may not be used to endorse or promote products derived from this software 17 | without specific prior written permission. 18 | 19 | THIS SOFTWARE AND DOCUMENTATION IS PROVIDED BY THE COPYRIGHT HOLDERS AND 20 | CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT 21 | NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 22 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER 23 | OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 24 | EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 25 | PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 26 | PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 27 | LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 28 | NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 29 | SOFTWARE AND DOCUMENTATION, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH 30 | DAMAGE. 31 | 32 | 33 | Please note 'cutie' package under under the following license: 34 | 35 | The MIT License (MIT) 36 | 37 | Copyright © 2018 Hans Schülein 38 | 39 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 40 | 41 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 42 | 43 | THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include LICENSE 2 | include README.rst 3 | include CONTRIBUTORS.rst 4 | include CHANGELOG.rst 5 | recursive-include yehua/resources * 6 | recursive-include tests *.py 7 | recursive-include tests *.txt 8 | recursive-include tests *.yml 9 | recursive-include tests Makefile 10 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | all: test 2 | 3 | test: lint 4 | bash test.sh 5 | 6 | install_test: 7 | pip install -r tests/requirements.txt 8 | 9 | lint: 10 | bash lint.sh 11 | 12 | format: 13 | bash format.sh 14 | 15 | git-diff-check: 16 | git diff --exit-code 17 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | ================================================================================ 2 | yehua - Project template tool for an organisation 3 | ================================================================================ 4 | 5 | .. image:: https://api.travis-ci.org/moremoban/yehua.svg 6 | :target: http://travis-ci.org/moremoban/yehua 7 | 8 | .. image:: https://codecov.io/github/moremoban/yehua/coverage.png 9 | :target: https://codecov.io/github/moremoban/yehua 10 | .. image:: https://badge.fury.io/py/yehua.svg 11 | :target: https://pypi.org/project/yehua 12 | 13 | .. image:: https://pepy.tech/badge/yehua/month 14 | :target: https://pepy.tech/project/yehua/month 15 | 16 | .. image:: https://img.shields.io/github/stars/moremoban/yehua.svg?style=social&maxAge=3600&label=Star 17 | :target: https://github.com/moremoban/yehua/stargazers 18 | 19 | .. image:: https://img.shields.io/static/v1?label=continuous%20templating&message=%E6%A8%A1%E7%89%88%E6%9B%B4%E6%96%B0&color=blue&style=flat-square 20 | :target: https://moban.readthedocs.io/en/latest/#at-scale-continous-templating-for-open-source-projects 21 | 22 | .. image:: https://img.shields.io/static/v1?label=coding%20style&message=black&color=black&style=flat-square 23 | :target: https://github.com/psf/black 24 | 25 | .. image:: https://readthedocs.org/projects/yehua/badge/?version=latest 26 | :target: http://yehua.readthedocs.org/en/latest/ 27 | 28 | .. image:: https://raw.githubusercontent.com/pyexcel/pyexcel.github.io/master/images/patreon.png 29 | :target: https://www.patreon.com/chfw 30 | 31 | .. image:: https://badges.gitter.im/chfw_yehua/Lobby.svg 32 | :alt: Join the chat at https://gitter.im/chfw_yehua/Lobby 33 | :target: https://gitter.im/chfw_yehua/Lobby?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge 34 | 35 | 36 | Introduction 37 | ================================================================================ 38 | 39 | 40 | .. image:: https://github.com/moremoban/yehua/raw/dev/yehua-usage.gif 41 | :width: 600px 42 | 43 | **yehua** /'jɛhwa/ is yet another a project template tool for an organisation. It creates a project skeleton, S 44 | from the default project template, T, of an organisation. `moban`_, the other 45 | tool of moremoban organisation, keeps S in synchronisation with T forever. This 46 | use case is what we called: **continuous templating**. 47 | 48 | .. image:: https://github.com/moremoban/yehua/raw/dev/docs/source/_static/yehua-story.png 49 | :width: 600px 50 | 51 | Cookiecutter users 52 | -------------------------- 53 | 54 | Yes, we now support cookiecutter templates. It has been requested since 2018 55 | Europython. Simply there is tons of cookiecutter templates out there. 56 | 57 | 58 | .. image:: https://github.com/moremoban/yehua/raw/dev/docs/source/_static/yehua-cookiecutter.gif 59 | :width: 600px 60 | 61 | What you do is to replace 'cookiecutter' with 'yh':: 62 | 63 | $ pip install yehua[cookiecutter] 64 | $ yh gh:audreyr/cookiecutter-pypackage 65 | 66 | And what moremoban promise is, whenever your source template changes, you 67 | can `synchronize` them any time with another moremoban's command 'moban':: 68 | 69 | $ moban 70 | 71 | Yes, you need a separate command, which replaces your effort to synchronize 72 | the upstream templates all the time. 73 | 74 | What's different with Yehua 75 | ------------------------------------ 76 | 77 | When the scope is a single project, **yehua** is no different to `cookiecutter`_ and 78 | `PyScaffold`_. It will create a project skeleton from `pypi-mobans`_, other templates such 79 | as cookiecutter templates, yehua mobans. 80 | 81 | When the scope is all projects within an organisation, **yehua** helps tackle 82 | information fragmentation problem, because all new projects after its creation, 83 | are still in synchronisation with T. For example, removing python 2.7 test 84 | in your travis file, can be done either manually by hand or automatically via 85 | `moban`_. What's the difference? The latter is faster and typo-free option. Here is 86 | `an example`_. 87 | 88 | `PyScaffold`_ version 3 has rolled out '--update' option, recognizing the organisational 89 | need of continous templating. Why do not **yehua** join `PyScaffold`_? Well, 90 | moremoban organisation started with '--update' at the start so our architecture 91 | and vision are closer to that of `cookiecutter`_: 92 | 93 | 1. we do not want to limit ourselves in pythonsphere. We wanted to serve all 94 | IT projects. In our mind, they are all about text templating. 95 | 96 | 2. we split the tool and the templates, serving the previous statement. 97 | People can create npm package template and use yehua+moban for continuous templating. 98 | Here are a list of examples: 99 | 100 | * `pypkg-mobans in pyecharts project `_ 101 | * `echarts-js-mobans in echarts-map project `_ 102 | 103 | .. _moban: https://github.com/moremoban/moban 104 | .. _cookiecutter: https://github.com/cookiecutter/cookiecutter 105 | .. _PyScaffold: https://github.com/pyscaffold/pyscaffold 106 | .. _pypi-mobans: https://github.com/moremobans/pypi-mobans 107 | .. _an example: https://github.com/moremoban/yehua/blob/dev/.github/workflows/moban-update.yml 108 | 109 | 110 | 111 | Installation 112 | ================================================================================ 113 | 114 | 115 | You can install yehua via pip: 116 | 117 | .. code-block:: bash 118 | 119 | $ pip install yehua 120 | 121 | 122 | or clone it and install it: 123 | 124 | .. code-block:: bash 125 | 126 | $ git clone https://github.com/moremoban/yehua.git 127 | $ cd yehua 128 | $ python setup.py install 129 | 130 | 131 | For offline usage, you need to get `pypi-mobans-pkg` installed:: 132 | 133 | $ pip install yehua[pypi-mobans] 134 | 135 | 136 | or:: 137 | 138 | $ pip install pypi-mobans-pkg 139 | 140 | 141 | Usage 142 | ================================================================================ 143 | 144 | 145 | 146 | Simply type in and you are taken care of:: 147 | 148 | $ yh 149 | 150 | It will use pypi-mobans-pkg by default and if it is not installed, it will 151 | install latest one. Then pypi-mobans-pkg takes over and will do 152 | [these](https://github.com/moremoban/pypi-mobans/blob/dev/yehua.yml) 153 | for you: 154 | 155 | #. Consult you on your project static information which can update as 156 | many as you want to. 157 | #. Create the Python package folder structure 158 | #. Initialize the package as git project 159 | 160 | You will simply need to commit it after you will have reviewed the 161 | generated files. 162 | 163 | Tutorial 164 | ----------------- 165 | 166 | Let's make a python command line utility using `yehua`. The command 167 | will be `hello` and it prints `world`. 168 | 169 | Step 1 Let's launch yehua 170 | ****************************** 171 | |slide1| 172 | 173 | Step 2 Fill-in the meta data for your project 174 | *********************************************** 175 | |slide2| 176 | 177 | At the end, yehua generates a folder named 'hello', which contains [all necessary 178 | files](https://github.com/moremoban/pypi-mobans). 179 | 180 | Step 3 Start coding 181 | ************************* 182 | 183 | .. image:: https://github.com/moremoban/yehua/raw/dev/docs/source/_static/yehua-hello.gif 184 | :width: 600px 185 | 186 | 187 | In above animation, we write up the actual code in hello/main.py 188 | 189 | .. code:: python 190 | 191 | def main(): 192 | print('world') 193 | 194 | Why is it enough? yehua generates a command utility python and 195 | it has pre-wired to invoke hello.main.main() function. You 196 | can find it out in setup.py. 197 | 198 | Step 4 Install it 199 | ********************* 200 | Now all is done. Let's install it 201 | 202 | |slide7| 203 | 204 | You can now run `hello` at your command line. 205 | 206 | Step 5 push to github 207 | *************************** 208 | 209 | Suppose you are happy with everything. Please do the following to 210 | push it to your github:: 211 | 212 | $ git commit -am ":sparkle: initial commit" 213 | 214 | Then create your project repository in github and do these to push it out:: 215 | 216 | $ git remote add origin https://github.com/moremoban/hello.git 217 | $ git push origin master 218 | 219 | You can find the `hello project`_ on github. 220 | 221 | Step 7 enable travis 222 | *************************** 223 | 224 | The generated project already has `.travis.yml` file. What you 225 | will need to do is to register with travis.org if you have not 226 | done so. And then go to travis and activate your project. 227 | 228 | 229 | .. |slide1| image:: docs/source/_static/yehua-0.png 230 | :scale: 100% 231 | .. |slide2| image:: docs/source/_static/yehua-1.png 232 | :scale: 100% 233 | .. |slide3| image:: docs/source/_static/yehua-2.png 234 | :scale: 100% 235 | .. |slide4| image:: docs/source/_static/yehua-3.png 236 | :scale: 100% 237 | .. |slide5| image:: docs/source/_static/yehua-4.png 238 | :scale: 100% 239 | .. |slide6| image:: docs/source/_static/yehua-5.png 240 | :scale: 100% 241 | .. |slide7| image:: docs/source/_static/yehua-7.png 242 | :scale: 100% 243 | .. |slide9| image:: docs/source/_static/github.png 244 | :scale: 60% 245 | .. |slide10| image:: docs/source/_static/push2github.png 246 | :scale: 60% 247 | 248 | .. _hello project: https://github.com/moremoban/hello 249 | .. _pyexcel commons: https://github.com/pyexcel/pyexcel-commons 250 | .. _pyexcel: https://github.com/pyexcel 251 | .. _moban: https://github.com/moremoban/moban 252 | .. _setupmobans: https://github.com/moremoban/setupmobans 253 | 254 | 255 | Background 256 | ================================================================================ 257 | 258 | The original problem I was trying to solve is: I would like to place 259 | common paragraphs in the documentation of my projects in a central 260 | place (pyexcel-mobans), and all projects could reference it dynamically 261 | so that when those common paragraphs get updated, the updates can be 262 | easily propagated to all relevant projects. The derived problem is: 263 | what can I do to a new project? I found myself doing a lot of 264 | copy-and-paste a lot, which lead to the creation of "yehua". Later, 265 | John Vandenberg, an active member of coala, suggested extracting the 266 | generic sets of pyexcel-mobans to form pypi-mobans, so that 267 | a vanilla python package can be created. Why not cookiecutter? 268 | Well, I have not heard of it at the time of creation. But it turns out 269 | that this project started to pave the way to be the cookiecutter 270 | for organisations. 271 | 272 | Why to choose "yehua"? Here is `the little story`_ behind the 273 | choice of name. And this `music video`_ would help bridge the 274 | cultural gap between you and me. 275 | 276 | .. _the little story: https://github.com/moremoban/yehua/issues/5#issuecomment-317218010 277 | .. _music video: https://www.youtube.com/watch?v=_JFTOQ6F1-M&frags=pl%2Cwn 278 | 279 | 280 | 281 | 282 | License 283 | ================================================================================ 284 | 285 | NEW BSD License 286 | 287 | 288 | It embeds MIT licensed `cutie `_ from 289 | Hans Schülein. Please refer to LICENSE file for more details 290 | -------------------------------------------------------------------------------- /changelog.yml: -------------------------------------------------------------------------------- 1 | name: yehua 2 | organisation: moremoban 3 | releases: 4 | - changes: 5 | - action: Updated 6 | details: 7 | - "`#53`: parity with cookiecutter: support gh:audreyr/cookiecutter-pypackage" 8 | date: 14.09.2020 9 | version: 0.1.4 10 | - changes: 11 | - action: Updated 12 | details: 13 | - "use pypi-mobans v0.1.0" 14 | - no longer, yehua has any template in itself. 15 | date: 13.09.2020 16 | version: 0.1.3 17 | - changes: 18 | - action: Fixed 19 | details: 20 | - "support pypi-mobans v0.0.15." 21 | - auto-generation of contributors 22 | - github action for moban update and commits 23 | - better isort action 24 | date: 26.08.2020 25 | version: 0.1.2 26 | - changes: 27 | - action: Fixed 28 | details: 29 | - "`#62`: fix unwanted dependency for yh standalone use" 30 | date: 29.05.2020 31 | version: 0.1.1 32 | - changes: 33 | - action: Removed 34 | details: 35 | - "python 2 support has been dropped" 36 | - action: Updated 37 | details: 38 | - "-v is changed to do moban style verbose(-v, -vv, -vvv), for version, please use -V" 39 | - action: Added 40 | details: 41 | - "`#37`: cookiecutter support" 42 | - "`#38`: non-verbose mode" 43 | - "added moban update work flow" 44 | - "python filesystem 2 support. yehua templates and cookiecutter templates can be in git, zip, s3, etc." 45 | date: 19.05.2020 46 | version: 0.1.0 47 | - changes: 48 | - action: Added 49 | details: 50 | - "`#30`: 'yh -h' or 'yh --help' triggers help text" 51 | - "`#32`: pypi-moban-pkg as installation extras" 52 | - "enable auto github auto publishing" 53 | - "generate mit license" 54 | - action: Updated 55 | details: 56 | - "`#35`: better error message when the project name has been made a directory already" 57 | - "updated moban dependency to v0.6.0" 58 | - "updated yaml library to ruamel.yaml. PyYAML is out because only one yaml library is wanted in the organisation." 59 | date: 10-01-2020 60 | version: 0.0.8 61 | - changes: 62 | - action: Updated 63 | details: 64 | - "upgrade yehua to use pypi-mobans-pkg version 0.0.7" 65 | - "generated project will have azure build pipeline, moban command stage in travis and local flake8 check" 66 | date: 6/10/2019 67 | version: 0.0.7 68 | - changes: 69 | - action: Updated 70 | details: 71 | - "upgrade yehua to use pypi-mobans-pkg version 0.0.5" 72 | - "generated project will have four new files: pipfile, lint.sh, changelog.yml and Makefile " 73 | date: 04/15/2019 74 | version: 0.0.6 75 | - changes: 76 | - action: added 77 | details: 78 | - "`#6`: provide Pipfile for pipenv" 79 | date: 08/11/2018 80 | version: 0.0.5 81 | - changes: 82 | - action: Updated 83 | details: 84 | - "`#11`: keep up-to-date with pypi-mobans" 85 | date: 06/07/2018 86 | version: 0.0.4 87 | - changes: 88 | - action: Added 89 | details: 90 | - "To add all files to a git repo is being made optional. The action is specified in `git-repo-files` under `post-moban` section. This particular need arises when it is used to scaffold other type of projects such as npm." 91 | date: 24/02/2018 92 | version: 0.0.3 93 | - changes: 94 | - action: Added 95 | details: 96 | - Automatically inflate project meta data. One yehua command and typing a few questions 97 | are required before a complete project scaffolding 98 | - Automatically obtain setupmobans repo for previous task. 99 | - Automatically initialize package as git project and add all project files for 100 | the user to commit 101 | - action: Removed 102 | details: 103 | - Built-in template files are off-loaded to setupmobans, which are more frequently updated. 104 | date: 15/10/2017 105 | version: 0.0.2 106 | - changes: [] 107 | date: 02/07/2017 108 | version: 0.0.1 109 | -------------------------------------------------------------------------------- /docs/requirements.txt: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /docs/source/_static/github.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/moremoban/yehua/e90ac103ec28e1101fd845796c02083d52ddf43e/docs/source/_static/github.png -------------------------------------------------------------------------------- /docs/source/_static/push2github.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/moremoban/yehua/e90ac103ec28e1101fd845796c02083d52ddf43e/docs/source/_static/push2github.png -------------------------------------------------------------------------------- /docs/source/_static/yehua-0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/moremoban/yehua/e90ac103ec28e1101fd845796c02083d52ddf43e/docs/source/_static/yehua-0.png -------------------------------------------------------------------------------- /docs/source/_static/yehua-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/moremoban/yehua/e90ac103ec28e1101fd845796c02083d52ddf43e/docs/source/_static/yehua-1.png -------------------------------------------------------------------------------- /docs/source/_static/yehua-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/moremoban/yehua/e90ac103ec28e1101fd845796c02083d52ddf43e/docs/source/_static/yehua-2.png -------------------------------------------------------------------------------- /docs/source/_static/yehua-3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/moremoban/yehua/e90ac103ec28e1101fd845796c02083d52ddf43e/docs/source/_static/yehua-3.png -------------------------------------------------------------------------------- /docs/source/_static/yehua-4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/moremoban/yehua/e90ac103ec28e1101fd845796c02083d52ddf43e/docs/source/_static/yehua-4.png -------------------------------------------------------------------------------- /docs/source/_static/yehua-5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/moremoban/yehua/e90ac103ec28e1101fd845796c02083d52ddf43e/docs/source/_static/yehua-5.png -------------------------------------------------------------------------------- /docs/source/_static/yehua-6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/moremoban/yehua/e90ac103ec28e1101fd845796c02083d52ddf43e/docs/source/_static/yehua-6.png -------------------------------------------------------------------------------- /docs/source/_static/yehua-7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/moremoban/yehua/e90ac103ec28e1101fd845796c02083d52ddf43e/docs/source/_static/yehua-7.png -------------------------------------------------------------------------------- /docs/source/_static/yehua-cookiecutter.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/moremoban/yehua/e90ac103ec28e1101fd845796c02083d52ddf43e/docs/source/_static/yehua-cookiecutter.gif -------------------------------------------------------------------------------- /docs/source/_static/yehua-hello.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/moremoban/yehua/e90ac103ec28e1101fd845796c02083d52ddf43e/docs/source/_static/yehua-hello.gif -------------------------------------------------------------------------------- /docs/source/_static/yehua-story.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/moremoban/yehua/e90ac103ec28e1101fd845796c02083d52ddf43e/docs/source/_static/yehua-story.png -------------------------------------------------------------------------------- /docs/source/conf.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | DESCRIPTION = ( 3 | 'Yet another a project template tool for an organisation.' + 4 | '' 5 | ) 6 | extensions = [ 7 | 'sphinx.ext.autodoc', 8 | 'sphinx.ext.doctest', 9 | 'sphinx.ext.intersphinx', 10 | 'sphinx.ext.viewcode', 11 | ] 12 | 13 | templates_path = ['_templates'] 14 | source_suffix = '.rst' 15 | master_doc = 'index' 16 | spelling_word_list_filename = 'spelling_wordlist.txt' 17 | project = u'yehua' 18 | copyright = u'2017-2020 Onni Software Ltd.' 19 | version = '0.1.4' 20 | release = '0.1.4' 21 | exclude_patterns = [] 22 | pygments_style = 'sphinx' 23 | html_theme = 'default' 24 | html_static_path = ['_static'] 25 | htmlhelp_basename = 'yehuadoc' 26 | latex_elements = {} 27 | latex_documents = [ 28 | ('index', 'yehua.tex', 29 | 'yehua Documentation', 30 | 'Onni Software Ltd.', 'manual'), 31 | ] 32 | man_pages = [ 33 | ('index', 'yehua', 34 | 'yehua Documentation', 35 | [u'Onni Software Ltd.'], 1) 36 | ] 37 | texinfo_documents = [ 38 | ('index', 'yehua', 39 | 'yehua Documentation', 40 | 'Onni Software Ltd.', 'yehua', 41 | DESCRIPTION, 42 | 'Miscellaneous'), 43 | ] 44 | -------------------------------------------------------------------------------- /docs/source/enterprise_usage.rst: -------------------------------------------------------------------------------- 1 | Enterprise use case: scaffolding all projects in your organisation 2 | ================================================================================ 3 | 4 | Previous example shows that you can write up your own yehua.yml and supporting 5 | templates and static files and **yehua** creates the scaffolding for you. 6 | 7 | Now in an enterprise context, you as the team lead or the architect would like 8 | to propagate the best practices to all new projects. What you can do is to 9 | make the yehua.yml and its files into repository. And then you could set:: 10 | 11 | $ export YEHUA_FILE=/location/to/the/cloned/yehua/repo/yehua.yml 12 | 13 | Since then, yehua would always use that yehua file for scaffolding. 14 | 15 | if you want to try it now, you could do this:: 16 | 17 | $ export YEHUA_FILE=/location/to/yehua/repo/examples/npm-init/yehua.yml 18 | $ yehua 19 | -------------------------------------------------------------------------------- /docs/source/extended_usage.rst: -------------------------------------------------------------------------------- 1 | Custom use case: scaffolding a npm package 2 | ================================================================================ 3 | 4 | As mentioned in previous section, **yehua** would take a yehua.yml from command 5 | line options. This section walk you through creating a npm package, **non-python 6 | package**. 7 | 8 | If you are familiar with npm, you can try and compare with yehua:: 9 | 10 | $ npm init 11 | 12 | Evaluation 13 | -------------------------------------------------------------------------------- 14 | 15 | Please first checkout yehua repository so that you will have the access to 16 | example directory. Then make sure you have **yehua** installed. 17 | 18 | .. image:: https://github.com/chfw/yehua/raw/master/yehua-npm-usage.gif 19 | :width: 600px 20 | 21 | 22 | yehua.yml for npm package 23 | -------------------------------------------------------------------------------- 24 | 25 | Let us go through the variant yehua file. 26 | 27 | configuration section 28 | ******************************************************************************** 29 | 30 | .. literalinclude:: ../../examples/npm-init/yehua.yml 31 | :lines: 12-15 32 | 33 | the current directory where yehua.yml file is, is configured as the folder for 34 | templates and static files. 35 | 36 | user questions section 37 | ******************************************************************************** 38 | 39 | .. literalinclude:: ../../examples/npm-init/yehua.yml 40 | :lines: 15-24 41 | 42 | the questions here are prompted to collect user inputs. 43 | 44 | templates section 45 | ******************************************************************************** 46 | 47 | .. literalinclude:: ../../examples/npm-init/yehua.yml 48 | :lines: 25-26 49 | 50 | Now the variables are used in template package.json.jj2 into package.json. 51 | 52 | static section 53 | ******************************************************************************** 54 | 55 | .. literalinclude:: ../../examples/npm-init/yehua.yml 56 | :lines: 27-28 57 | 58 | Now, **yehua** simply copies index.js to target package folder. 59 | -------------------------------------------------------------------------------- /docs/source/filespec.rst: -------------------------------------------------------------------------------- 1 | Example yehua file 2 | ================================================================================ 3 | 4 | Here is the sample startup file:: 5 | 6 | configuration: 7 | - template_path: ./relative_path_to_this_file 8 | - static_path: ./relative_path_to_this_file 9 | questions: 10 | - simple_keyword: "Simple Question?" 11 | - complex_question: 12 | - question: "The actual question?" 13 | "1. Answser": 14 | - follow_up_keyword: "Follow up question?" 15 | "2. Answer": "N/A" 16 | mobans: 17 | - 'mobans': 'this is optional' 18 | layout: 19 | - tests 20 | - docs: 21 | - source 22 | - .moban.d: 23 | - tests 24 | - docs: 25 | - source 26 | templates: 27 | - "{{project_name}}.yml": project.yml 28 | - .moban.yml: .moban.yml 29 | static: 30 | - ".moban.d/README.rst": "README.rst" 31 | - "{{project_src}}/__init__.py": __init__.py.jj2 32 | post-moban: 33 | git-repo-files: 34 | - "this_is_optional" 35 | -------------------------------------------------------------------------------- /docs/source/hello_cookie_cutter/cookiecutter.json: -------------------------------------------------------------------------------- 1 | { 2 | "directory_name": "Hello", 3 | "file_name": "Howdy", 4 | "greeting_recipient": "Julie" 5 | } 6 | -------------------------------------------------------------------------------- /docs/source/hello_cookie_cutter/yehua.yml: -------------------------------------------------------------------------------- 1 | introduction: "\nYehua will walk you through creating a blank python package.\nPress\ 2 | \ ^C to quit at any time.\n" 3 | configuration: 4 | template_path: '{{cookiecutter.directory_name}}' 5 | static_path: '{{cookiecutter.directory_name}}' 6 | questions: 7 | - directory_name: 'directory_name:' 8 | - file_name: 'file_name:' 9 | - greeting_recipient: 'greeting_recipient:' 10 | templates: 11 | - cookiecutter>.py: '{{cookiecutter.file_name}}.py' 12 | layout: [] 13 | -------------------------------------------------------------------------------- /docs/source/hello_cookie_cutter/{{cookiecutter.directory_name}}/{{cookiecutter.file_name}}.py: -------------------------------------------------------------------------------- 1 | print("Hello, {{cookiecutter.greeting_recipient}}!") 2 | -------------------------------------------------------------------------------- /docs/source/index.rst: -------------------------------------------------------------------------------- 1 | `yehua` - Let you focus on code, instead of setup scaffolding 2 | ================================================================================ 3 | 4 | :Author: C. W. 5 | :Source code: http://github.com/moremoban/yehua.git 6 | :Issues: http://github.com/moremoban/yehua/issues 7 | :License: New BSD License 8 | :Released: |version| 9 | :Generated: |today| 10 | 11 | .. image:: https://api.travis-ci.org/moremoban/yehua.svg 12 | :target: http://travis-ci.org/moremoban/yehua 13 | 14 | .. image:: https://codecov.io/github/moremoban/yehua/coverage.png 15 | :target: https://codecov.io/github/moremoban/yehua 16 | .. image:: https://badge.fury.io/py/yehua.svg 17 | :target: https://pypi.org/project/yehua 18 | 19 | .. image:: https://pepy.tech/badge/yehua/month 20 | :target: https://pepy.tech/project/yehua/month 21 | 22 | .. image:: https://img.shields.io/github/stars/moremoban/yehua.svg?style=social&maxAge=3600&label=Star 23 | :target: https://github.com/moremoban/yehua/stargazers 24 | 25 | .. image:: https://img.shields.io/static/v1?label=continuous%20templating&message=%E6%A8%A1%E7%89%88%E6%9B%B4%E6%96%B0&color=blue&style=flat-square 26 | :target: https://moban.readthedocs.io/en/latest/#at-scale-continous-templating-for-open-source-projects 27 | 28 | .. image:: https://img.shields.io/static/v1?label=coding%20style&message=black&color=black&style=flat-square 29 | :target: https://github.com/psf/black 30 | .. image:: https://raw.githubusercontent.com/pyexcel/pyexcel.github.io/master/images/patreon.png 31 | :target: https://www.patreon.com/chfw 32 | 33 | .. image:: https://badges.gitter.im/chfw_yehua/Lobby.svg 34 | :alt: Join the chat at https://gitter.im/chfw_yehua/Lobby 35 | :target: https://gitter.im/chfw_yehua/Lobby?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge 36 | 37 | 38 | Introduction 39 | -------------------------------------------------------------------------------- 40 | 41 | 42 | .. image:: https://github.com/moremoban/yehua/raw/dev/yehua-usage.gif 43 | :width: 600px 44 | 45 | **yehua** /'jɛhwa/ is yet another a project template tool for an organisation. It creates a project skeleton, S 46 | from the default project template, T, of an organisation. `moban`_, the other 47 | tool of moremoban organisation, keeps S in synchronisation with T forever. This 48 | use case is what we called: **continuous templating**. 49 | 50 | .. image:: https://github.com/moremoban/yehua/raw/dev/docs/source/_static/yehua-story.png 51 | :width: 600px 52 | 53 | Cookiecutter users 54 | -------------------------- 55 | 56 | Yes, we now support cookiecutter templates. It has been requested since 2018 57 | Europython. Simply there is tons of cookiecutter templates out there. 58 | 59 | 60 | .. image:: https://github.com/moremoban/yehua/raw/dev/docs/source/_static/yehua-cookiecutter.gif 61 | :width: 600px 62 | 63 | What you do is to replace 'cookiecutter' with 'yh':: 64 | 65 | $ pip install yehua[cookiecutter] 66 | $ yh gh:audreyr/cookiecutter-pypackage 67 | 68 | And what moremoban promise is, whenever your source template changes, you 69 | can `synchronize` them any time with another moremoban's command 'moban':: 70 | 71 | $ moban 72 | 73 | Yes, you need a separate command, which replaces your effort to synchronize 74 | the upstream templates all the time. 75 | 76 | What's different with Yehua 77 | ------------------------------------ 78 | 79 | When the scope is a single project, **yehua** is no different to `cookiecutter`_ and 80 | `PyScaffold`_. It will create a project skeleton from `pypi-mobans`_, other templates such 81 | as cookiecutter templates, yehua mobans. 82 | 83 | When the scope is all projects within an organisation, **yehua** helps tackle 84 | information fragmentation problem, because all new projects after its creation, 85 | are still in synchronisation with T. For example, removing python 2.7 test 86 | in your travis file, can be done either manually by hand or automatically via 87 | `moban`_. What's the difference? The latter is faster and typo-free option. Here is 88 | `an example`_. 89 | 90 | `PyScaffold`_ version 3 has rolled out '--update' option, recognizing the organisational 91 | need of continous templating. Why do not **yehua** join `PyScaffold`_? Well, 92 | moremoban organisation started with '--update' at the start so our architecture 93 | and vision are closer to that of `cookiecutter`_: 94 | 95 | 1. we do not want to limit ourselves in pythonsphere. We wanted to serve all 96 | IT projects. In our mind, they are all about text templating. 97 | 98 | 2. we split the tool and the templates, serving the previous statement. 99 | People can create npm package template and use yehua+moban for continuous templating. 100 | Here are a list of examples: 101 | 102 | * `pypkg-mobans in pyecharts project `_ 103 | * `echarts-js-mobans in echarts-map project `_ 104 | 105 | .. _moban: https://github.com/moremoban/moban 106 | .. _cookiecutter: https://github.com/cookiecutter/cookiecutter 107 | .. _PyScaffold: https://github.com/pyscaffold/pyscaffold 108 | .. _pypi-mobans: https://github.com/moremobans/pypi-mobans 109 | .. _an example: https://github.com/moremoban/yehua/blob/dev/.github/workflows/moban-update.yml 110 | 111 | 112 | 113 | Installation 114 | -------------------------------------------------------------------------------- 115 | 116 | 117 | You can install yehua via pip: 118 | 119 | .. code-block:: bash 120 | 121 | $ pip install yehua 122 | 123 | 124 | or clone it and install it: 125 | 126 | .. code-block:: bash 127 | 128 | $ git clone https://github.com/moremoban/yehua.git 129 | $ cd yehua 130 | $ python setup.py install 131 | 132 | Usage 133 | -------------------------------------------------------------------------------- 134 | 135 | 136 | 137 | Simply type in and you are taken care of:: 138 | 139 | $ yh 140 | 141 | It will use pypi-mobans-pkg by default and if it is not installed, it will 142 | install latest one. Then pypi-mobans-pkg takes over and will do 143 | [these](https://github.com/moremoban/pypi-mobans/blob/dev/yehua.yml) 144 | for you: 145 | 146 | #. Consult you on your project static information which can update as 147 | many as you want to. 148 | #. Create the Python package folder structure 149 | #. Initialize the package as git project 150 | 151 | You will simply need to commit it after you will have reviewed the 152 | generated files. 153 | 154 | Tutorial 155 | ----------------- 156 | 157 | Let's make a python command line utility using `yehua`. The command 158 | will be `hello` and it prints `world`. 159 | 160 | Step 1 Let's launch yehua 161 | ****************************** 162 | |slide1| 163 | 164 | Step 2 Fill-in the meta data for your project 165 | *********************************************** 166 | |slide2| 167 | 168 | At the end, yehua generates a folder named 'hello', which contains [all necessary 169 | files](https://github.com/moremoban/pypi-mobans). 170 | 171 | Step 3 Start coding 172 | ************************* 173 | 174 | .. image:: https://github.com/moremoban/yehua/raw/dev/docs/source/_static/yehua-hello.gif 175 | :width: 600px 176 | 177 | 178 | In above animation, we write up the actual code in hello/main.py 179 | 180 | .. code:: python 181 | 182 | def main(): 183 | print('world') 184 | 185 | Why is it enough? yehua generates a command utility python and 186 | it has pre-wired to invoke hello.main.main() function. You 187 | can find it out in setup.py. 188 | 189 | Step 4 Install it 190 | ********************* 191 | Now all is done. Let's install it 192 | 193 | |slide7| 194 | 195 | You can now run `hello` at your command line. 196 | 197 | Step 5 push to github 198 | *************************** 199 | 200 | Suppose you are happy with everything. Please do the following to 201 | push it to your github:: 202 | 203 | $ git commit -am ":sparkle: initial commit" 204 | 205 | Then create your project repository in github and do these to push it out:: 206 | 207 | $ git remote add origin https://github.com/moremoban/hello.git 208 | $ git push origin master 209 | 210 | You can find the `hello project`_ on github. 211 | 212 | Step 7 enable travis 213 | *************************** 214 | 215 | The generated project already has `.travis.yml` file. What you 216 | will need to do is to register with travis.org if you have not 217 | done so. And then go to travis and activate your project. 218 | 219 | 220 | .. |slide1| image:: _static/yehua-0.png 221 | :scale: 100% 222 | .. |slide2| image:: _static/yehua-1.png 223 | :scale: 100% 224 | .. |slide3| image:: _static/yehua-2.png 225 | :scale: 100% 226 | .. |slide4| image:: _static/yehua-3.png 227 | :scale: 100% 228 | .. |slide5| image:: _static/yehua-4.png 229 | :scale: 100% 230 | .. |slide6| image:: _static/yehua-5.png 231 | :scale: 100% 232 | .. |slide7| image:: _static/yehua-7.png 233 | :scale: 100% 234 | .. |slide9| image:: _static/github.png 235 | :scale: 60% 236 | .. |slide10| image:: _static/push2github.png 237 | :scale: 60% 238 | 239 | .. _hello project: https://github.com/moremoban/hello 240 | .. _pyexcel commons: https://github.com/pyexcel/pyexcel-commons 241 | .. _pyexcel: https://github.com/pyexcel 242 | .. _moban: https://github.com/moremoban/moban 243 | .. _setupmobans: https://github.com/moremoban/setupmobans 244 | 245 | 246 | Background 247 | -------------------------------------------------------------------------------- 248 | 249 | The original problem I was trying to solve is: I would like to place 250 | common paragraphs in the documentation of my projects in a central 251 | place (pyexcel-mobans), and all projects could reference it dynamically 252 | so that when those common paragraphs get updated, the updates can be 253 | easily propagated to all relevant projects. The derived problem is: 254 | what can I do to a new project? I found myself doing a lot of 255 | copy-and-paste a lot, which lead to the creation of "yehua". Later, 256 | John Vandenberg, an active member of coala, suggested extracting the 257 | generic sets of pyexcel-mobans to form pypi-mobans, so that 258 | a vanilla python package can be created. Why not cookiecutter? 259 | Well, I have not heard of it at the time of creation. But it turns out 260 | that this project started to pave the way to be the cookiecutter 261 | for organisations. 262 | 263 | Why to choose "yehua"? Here is `the little story`_ behind the 264 | choice of name. And this `music video`_ would help bridge the 265 | cultural gap between you and me. 266 | 267 | .. _the little story: https://github.com/moremoban/yehua/issues/5#issuecomment-317218010 268 | .. _music video: https://www.youtube.com/watch?v=_JFTOQ6F1-M&frags=pl%2Cwn 269 | 270 | 271 | 272 | Documentation 273 | -------------------------------------------------------------------------------- 274 | 275 | .. toctree:: 276 | 277 | usage 278 | extended_usage 279 | enterprise_usage 280 | 281 | License 282 | ================================================================================ 283 | 284 | NEW BSD License 285 | -------------------------------------------------------------------------------- /docs/source/spelling_wordlist.txt: -------------------------------------------------------------------------------- 1 | yml 2 | yehua 3 | json 4 | jj 5 | js 6 | npm 7 | pypi 8 | rst 9 | github 10 | travis 11 | moban 12 | organisation 13 | ci 14 | -------------------------------------------------------------------------------- /docs/source/usage.rst: -------------------------------------------------------------------------------- 1 | Default use case: scaffolding a python package 2 | ================================================================================ 3 | 4 | **yehua** reads a configuration file named yehua.yml which then instruct **yehua 5 | to ask questions to collect project specific variables, and create the file 6 | structures. 7 | 8 | It takes a yehua file from command line option, or YEHUA_FILE in the environment 9 | variable. If none is found, it resorts to the default yehua.yml file included 10 | in the package. And the default yehua.yml create a blank python package. 11 | 12 | 13 | Default yehua.yml file 14 | -------------------------------------------------------------------------------- 15 | 16 | Let us go through the default yehua file. And then talk about the customization 17 | in the next chapter 18 | 19 | configuration section 20 | ******************************************************************************** 21 | 22 | **yehua** expects two set of files: templates and static files. configuration 23 | section tells where they are relative to yehua.yml file. 24 | 25 | .. literalinclude:: ../../yehua/resources/yehua.yml 26 | :lines: 4-6 27 | 28 | In the default use case, the templates are located in yehua/resources/templates 29 | and the static files are located in yehua/resources/static. 30 | 31 | The files in the static folder will not be templated and will be copied across. 32 | 33 | As a rule of thumb, if your file needs user input, please place it in templates 34 | folder. Otherwise, please place your files in static folder, which is copied 35 | across. 36 | 37 | user question section 38 | ******************************************************************************** 39 | 40 | **yehua** will then ask a list of questions. The quoted lines are prompted to 41 | the end user and the answers are given to the variable names on the left hand 42 | side of semi-colon. Here is a simple format:: 43 | 44 | - holding_variable_name_for_the_answer: "What is most intuitive question for the variable?" 45 | 46 | Then "holding_variable_name_for_the_answer" can be used in your template files. 47 | 48 | .. literalinclude:: ../../yehua/resources/yehua.yml 49 | :lines: 7-13 50 | 51 | layout section 52 | ******************************************************************************** 53 | 54 | layout section outlines the folder structure of the resulting project folder. 55 | **yehua** will create the folder layout before generating any outputs. 56 | 57 | .. literalinclude:: ../../yehua/resources/yehua.yml 58 | :lines: 22-30 59 | 60 | In this case, a default python package will have tests, docs and .moban.d at 61 | its first level. **yehua** would always create a sub folder named after 62 | project_name. 63 | 64 | templates section 65 | ******************************************************************************** 66 | 67 | The files in this section follows this spec: 68 | 69 | - templated_target_file: template.file.in.templates.folder 70 | 71 | .. literalinclude:: ../../yehua/resources/yehua.yml 72 | :lines: 33-36 73 | 74 | As you can see, **yehua** would expand `project_name` for you. 75 | 76 | 77 | static section 78 | ******************************************************************************** 79 | 80 | File in this section is simply copied over. Here is the spec:: 81 | 82 | - relative_file_path: relative_static_file_path_in_static_folder 83 | 84 | .. literalinclude:: ../../yehua/resources/yehua.yml 85 | :lines: 37-44 86 | 87 | 88 | Template files 89 | -------------------------------------------------------------------------------- 90 | 91 | Let's examine the beginning of project.yml in templates folder. 92 | 93 | .. literalinclude:: ../../yehua/resources/templates/project.yml 94 | :lines: 1-5 95 | 96 | #. project_name 97 | #. organisation 98 | #. author 99 | #. contact 100 | #. company 101 | 102 | are unknown to yehua and only the user knows the answer. Hence, it brings 103 | us to the next section: user questions 104 | 105 | Static files 106 | -------------------------------------------------------------------------------- 107 | 108 | The static files in yehua/resources/static are copied over by yehua according to 109 | the instruction file. 110 | 111 | 112 | After all actions 113 | -------------------------------------------------------------------------------- 114 | 115 | The possible extra actions is `git-repo-files` where you can name the files 116 | to be added to a git repo. Here is a list of default files to be added to 117 | the file repo: 118 | 119 | .. literalinclude:: ../../yehua/resources/yehua.yml 120 | :lines: 46-62 121 | -------------------------------------------------------------------------------- /docs/source/yehua-story.uml: -------------------------------------------------------------------------------- 1 | @startuml 2 | 3 | Participant "Project Template" as T1 4 | Participant "Awesome Template" as T2 5 | Participant "Nth Generated Project" as G 6 | 7 | T1 --> G : yehua generate 8 | G --> G: development starts 9 | group continuous templating 10 | T1 --> T1 : new features added 11 | T1 --> G : moban synchronize 12 | G --> G : development continues 13 | end 14 | group no limit on the numer of source templates 15 | G --> G : add another awesome template 16 | T2 --> G: moban synchronize 17 | T1 --> T1: bug fix 18 | T1 --> G: moban synchronize 19 | T2 --> T2: a typo correction 20 | T2 --> G: moban synchornize 21 | end 22 | 23 | @enduml -------------------------------------------------------------------------------- /examples/npm-init/index.js: -------------------------------------------------------------------------------- 1 | console.log("hello world"); 2 | -------------------------------------------------------------------------------- /examples/npm-init/package.json.jj2: -------------------------------------------------------------------------------- 1 | { 2 | "name": "{{project_name}}", 3 | "version": "{{version}}", 4 | "description": "{{description}}", 5 | "main": "{{entry_point}}", 6 | "scripts": { 7 | "test": "{{test_command}}", 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "{{git_repo}}" 12 | }, 13 | "keywords": [ 14 | "{{keywords}}" 15 | ], 16 | "author": "{{author}}", 17 | "license": "{{license}}" 18 | } -------------------------------------------------------------------------------- /examples/npm-init/yehua.yml: -------------------------------------------------------------------------------- 1 | introduction: | 2 | This utility will walk you through creating a package.json file. 3 | It only covers the most common items, and tries to guess sensible defaults. 4 | 5 | See `npm help json` for definitive documentation on these fields 6 | and exactly what they do. 7 | 8 | Use `npm install --save` afterwards to install a package and 9 | save it as a dependency in the package.json file. 10 | 11 | Press ^C at any time to quit. 12 | configuration: 13 | template_path: . 14 | static_path: . 15 | questions: 16 | - project_name: "name:" 17 | - version: "version:" 18 | - description: "description:" 19 | - entry_point: "entry point:" 20 | - test_command: "test command:" 21 | - git_repo: "git repository:" 22 | - keywords: "keywords:" 23 | - author: "author:" 24 | - license: "license:" 25 | templates: 26 | - package.json: package.json.jj2 27 | static: 28 | - index.js: index.js -------------------------------------------------------------------------------- /format.sh: -------------------------------------------------------------------------------- 1 | isort $(find yehua -name "*.py"|xargs echo) $(find tests -name "*.py"|grep -v 'project_'| xargs echo) 2 | black -l 79 yehua 3 | black --exclude "/tests\/fixtures\/project_.*/" -l 79 tests 4 | -------------------------------------------------------------------------------- /lint.sh: -------------------------------------------------------------------------------- 1 | pip install flake8 2 | flake8 . --exclude=.moban.d,docs,setup.py,tests/fixtures --builtins=unicode,xrange,long 3 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | ruamel.yaml>=0.15.42;python_version == '3.7' 2 | ruamel.yaml>=0.15.5;python_version != '3.4' and python_version < '3.7' 3 | ruamel.yaml>=0.15.98;python_version == '3.8' 4 | Jinja2 5 | moban>=0.6.0 6 | colorful 7 | rich 8 | readchar 9 | colorama 10 | moban-jinja2-github>=0.0.2 11 | moban-ansible 12 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [metadata] 2 | description-file = README.rst 3 | [bdist_wheel] 4 | universal = 1 5 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | """ 4 | Template by pypi-mobans 5 | """ 6 | 7 | import os 8 | import sys 9 | import codecs 10 | import locale 11 | import platform 12 | from shutil import rmtree 13 | 14 | from setuptools import Command, setup, find_packages 15 | 16 | PY2 = sys.version_info[0] == 2 17 | PY26 = PY2 and sys.version_info[1] < 7 18 | PY33 = sys.version_info < (3, 4) 19 | 20 | # Work around mbcs bug in distutils. 21 | # http://bugs.python.org/issue10945 22 | # This work around is only if a project supports Python < 3.4 23 | 24 | # Work around for locale not being set 25 | try: 26 | lc = locale.getlocale() 27 | pf = platform.system() 28 | if pf != "Windows" and lc == (None, None): 29 | locale.setlocale(locale.LC_ALL, "C.UTF-8") 30 | except (ValueError, UnicodeError, locale.Error): 31 | locale.setlocale(locale.LC_ALL, "en_US.UTF-8") 32 | 33 | NAME = "yehua" 34 | AUTHOR = "C. W." 35 | VERSION = "0.1.4" 36 | EMAIL = "wangc_2011@hotmail.com" 37 | LICENSE = "New BSD" 38 | ENTRY_POINTS = { 39 | "console_scripts": [ 40 | "yh = yehua.main:main" 41 | ], 42 | } 43 | DESCRIPTION = ( 44 | "Yet another a project template tool for an organisation." 45 | ) 46 | URL = "https://github.com/moremoban/yehua" 47 | DOWNLOAD_URL = "%s/archive/0.1.4.tar.gz" % URL 48 | FILES = ["README.rst","CONTRIBUTORS.rst", "CHANGELOG.rst"] 49 | KEYWORDS = [ 50 | "python", 51 | ] 52 | 53 | CLASSIFIERS = [ 54 | "Topic :: Software Development :: Libraries", 55 | "Programming Language :: Python", 56 | "Intended Audience :: Developers", 57 | 58 | "Programming Language :: Python :: 3 :: Only", 59 | 60 | 61 | 62 | "Programming Language :: Python :: 3.6", 63 | "Programming Language :: Python :: 3.7", 64 | "Programming Language :: Python :: 3.8", 65 | 66 | 'Topic :: Utilities', 67 | ] 68 | 69 | PYTHON_REQUIRES = ">=3.6" 70 | 71 | INSTALL_REQUIRES = [ 72 | "Jinja2", 73 | "moban>=0.6.0", 74 | "colorful", 75 | "rich", 76 | "readchar", 77 | "colorama", 78 | "moban-jinja2-github>=0.0.2", 79 | "moban-ansible", 80 | ] 81 | SETUP_COMMANDS = {} 82 | 83 | PACKAGES = find_packages(exclude=["ez_setup", "examples", "tests", "tests.*"]) 84 | EXTRAS_REQUIRE = { 85 | "pypi-mobans": ['pypi-mobans-pkg>=0.1.4'], 86 | "cookiecutter": ['cookiecutter==1.7.0'], 87 | ":python_version == '3.7'": ["ruamel.yaml>=0.15.42"], 88 | ":python_version != '3.4' and python_version < '3.7'": ["ruamel.yaml>=0.15.5"], 89 | ":python_version == '3.8'": ["ruamel.yaml>=0.15.98"], 90 | } 91 | # You do not need to read beyond this line 92 | PUBLISH_COMMAND = "{0} setup.py sdist bdist_wheel upload -r pypi".format(sys.executable) 93 | HERE = os.path.abspath(os.path.dirname(__file__)) 94 | 95 | GS_COMMAND = ("gs yehua v0.1.4 " + 96 | "Find 0.1.4 in changelog for more details") 97 | NO_GS_MESSAGE = ("Automatic github release is disabled. " + 98 | "Please install gease to enable it.") 99 | UPLOAD_FAILED_MSG = ( 100 | 'Upload failed. please run "%s" yourself.' % PUBLISH_COMMAND) 101 | 102 | 103 | class PublishCommand(Command): 104 | """Support setup.py upload.""" 105 | 106 | description = "Build and publish the package on github and pypi" 107 | user_options = [] 108 | 109 | @staticmethod 110 | def status(s): 111 | """Prints things in bold.""" 112 | print("\033[1m{0}\033[0m".format(s)) 113 | 114 | def initialize_options(self): 115 | pass 116 | 117 | def finalize_options(self): 118 | pass 119 | 120 | def run(self): 121 | try: 122 | self.status("Removing previous builds...") 123 | rmtree(os.path.join(HERE, "dist")) 124 | rmtree(os.path.join(HERE, "build")) 125 | rmtree(os.path.join(HERE, "yehua.egg-info")) 126 | except OSError: 127 | pass 128 | 129 | self.status("Building Source and Wheel (universal) distribution...") 130 | run_status = True 131 | if has_gease(): 132 | run_status = os.system(GS_COMMAND) == 0 133 | else: 134 | self.status(NO_GS_MESSAGE) 135 | if run_status: 136 | if os.system(PUBLISH_COMMAND) != 0: 137 | self.status(UPLOAD_FAILED_MSG) 138 | 139 | sys.exit() 140 | 141 | 142 | SETUP_COMMANDS.update({ 143 | "publish": PublishCommand 144 | }) 145 | 146 | def has_gease(): 147 | """ 148 | test if github release command is installed 149 | 150 | visit http://github.com/moremoban/gease for more info 151 | """ 152 | try: 153 | import gease # noqa 154 | return True 155 | except ImportError: 156 | return False 157 | 158 | 159 | def read_files(*files): 160 | """Read files into setup""" 161 | text = "" 162 | for single_file in files: 163 | content = read(single_file) 164 | text = text + content + "\n" 165 | return text 166 | 167 | 168 | def read(afile): 169 | """Read a file into setup""" 170 | the_relative_file = os.path.join(HERE, afile) 171 | with codecs.open(the_relative_file, "r", "utf-8") as opened_file: 172 | content = filter_out_test_code(opened_file) 173 | content = "".join(list(content)) 174 | return content 175 | 176 | 177 | def filter_out_test_code(file_handle): 178 | found_test_code = False 179 | for line in file_handle.readlines(): 180 | if line.startswith(".. testcode:"): 181 | found_test_code = True 182 | continue 183 | if found_test_code is True: 184 | if line.startswith(" "): 185 | continue 186 | else: 187 | empty_line = line.strip() 188 | if len(empty_line) == 0: 189 | continue 190 | else: 191 | found_test_code = False 192 | yield line 193 | else: 194 | for keyword in ["|version|", "|today|"]: 195 | if keyword in line: 196 | break 197 | else: 198 | yield line 199 | 200 | 201 | if __name__ == "__main__": 202 | setup( 203 | test_suite="tests", 204 | name=NAME, 205 | author=AUTHOR, 206 | version=VERSION, 207 | author_email=EMAIL, 208 | description=DESCRIPTION, 209 | url=URL, 210 | download_url=DOWNLOAD_URL, 211 | long_description=read_files(*FILES), 212 | license=LICENSE, 213 | keywords=KEYWORDS, 214 | python_requires=PYTHON_REQUIRES, 215 | extras_require=EXTRAS_REQUIRE, 216 | tests_require=["nose"], 217 | install_requires=INSTALL_REQUIRES, 218 | packages=PACKAGES, 219 | include_package_data=True, 220 | zip_safe=False, 221 | entry_points=ENTRY_POINTS, 222 | classifiers=CLASSIFIERS, 223 | cmdclass=SETUP_COMMANDS 224 | ) 225 | -------------------------------------------------------------------------------- /test.sh: -------------------------------------------------------------------------------- 1 | pip freeze 2 | pip install -e . 3 | pytest --cov=yehua --cov=tests tests/ --ignore=tests/fixtures/project_s 4 | -------------------------------------------------------------------------------- /tests/cutie_tests/__init__.py: -------------------------------------------------------------------------------- 1 | import readchar 2 | 3 | from yehua.thirdparty import cutie 4 | 5 | 6 | def PrintCall(states): 7 | def func(msg=None, state="selectable"): 8 | if msg: 9 | return ((states[state] + msg,),) 10 | else: 11 | return ((states[state],),) 12 | 13 | return func 14 | 15 | 16 | def yield_input(*data, raise_on_empty=False): 17 | """ 18 | Closure that returns predefined data. 19 | 20 | If the data is exhausted raise a MockException or reraise the IndexError 21 | """ 22 | data = list(data) 23 | 24 | def func(*a, **kw): 25 | try: 26 | return data.pop(0) 27 | except IndexError as e: 28 | if raise_on_empty: 29 | raise MockException() 30 | else: 31 | raise e 32 | 33 | return func 34 | 35 | 36 | class InputContext: 37 | """ 38 | Context manager to simulate keyboard input returned by `readchar.readkey`, 39 | by replacing it in `cutie` with `yield_input` 40 | 41 | When the supplied keystrokes are exhausted a `MockException` will be 42 | raised. This can be used to terminate the execution at any desired point, 43 | rather than relying on internal control mechanisms. 44 | 45 | Usage: 46 | with InputContext(" ", "\r"): 47 | cutie.select(["foo", "bar"]) 48 | This will issue a space and enter keypress, selecting the first item and 49 | confirming. 50 | """ 51 | 52 | def __init__(self, *data, raise_on_empty=True): 53 | cutie.readchar.readkey = yield_input( 54 | *data, raise_on_empty=raise_on_empty 55 | ) 56 | 57 | def __enter__(self): 58 | pass 59 | 60 | def __exit__(self, *a): 61 | cutie.readchar.readkey = readchar.readkey 62 | 63 | 64 | class MockException(Exception): 65 | pass 66 | -------------------------------------------------------------------------------- /tests/cutie_tests/test_get_number.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | from unittest import mock 3 | 4 | from yehua.thirdparty import cutie 5 | from . import MockException 6 | 7 | 8 | class TestCutieGetNumber(unittest.TestCase): 9 | @mock.patch("yehua.thirdparty.cutie.print", side_effect=MockException) 10 | def test_invalid_number(self, mock_print): 11 | with mock.patch("yehua.thirdparty.cutie.input", return_value="foo"): 12 | with self.assertRaises(MockException): 13 | cutie.get_number("bar") 14 | mock_print.assert_called_once_with( 15 | "Not a valid number.\033[K\033[1A\r\033[K", end="" 16 | ) 17 | 18 | @mock.patch("yehua.thirdparty.cutie.print", side_effect=MockException) 19 | def test_not_allow_float(self, mock_print): 20 | with mock.patch("yehua.thirdparty.cutie.input", return_value="1.2"): 21 | with self.assertRaises(MockException): 22 | cutie.get_number("foo", allow_float=False) 23 | mock_print.assert_called_once_with( 24 | "Has to be an integer.\033[K\033[1A\r\033[K", end="" 25 | ) 26 | 27 | def test_allow_float_returns_float(self): 28 | with mock.patch("yehua.thirdparty.cutie.input", return_value="1.2"): 29 | val = cutie.get_number("foo") 30 | self.assertIsInstance(val, float) 31 | self.assertEqual(val, 1.2) 32 | 33 | def test_not_allow_float_returns_int(self): 34 | with mock.patch("yehua.thirdparty.cutie.input", return_value="1"): 35 | val = cutie.get_number("foo", allow_float=False) 36 | self.assertIsInstance(val, int) 37 | self.assertEqual(val, 1) 38 | 39 | @mock.patch("yehua.thirdparty.cutie.print", side_effect=MockException) 40 | def test_min_value_float_too_low(self, mock_print): 41 | with mock.patch("yehua.thirdparty.cutie.input", return_value="1.2"): 42 | with self.assertRaises(MockException): 43 | cutie.get_number("foo", min_value=1.3) 44 | mock_print.assert_called_once_with( 45 | "Has to be at least 1.3.\033[K\033[1A\r\033[K", end="" 46 | ) 47 | 48 | def test_min_value_float_equal(self): 49 | with mock.patch("yehua.thirdparty.cutie.input", return_value="1.2"): 50 | self.assertEqual(cutie.get_number("foo", min_value=1.2), 1.2) 51 | 52 | def test_min_value_float_greater(self): 53 | with mock.patch("yehua.thirdparty.cutie.input", return_value="1.3"): 54 | self.assertEqual(cutie.get_number("foo", min_value=1.2), 1.3) 55 | 56 | @mock.patch("yehua.thirdparty.cutie.print", side_effect=MockException) 57 | def test_min_value_int_too_low(self, mock_print): 58 | with mock.patch("yehua.thirdparty.cutie.input", return_value="1"): 59 | with self.assertRaises(MockException): 60 | cutie.get_number("foo", min_value=2) 61 | mock_print.assert_called_once_with( 62 | "Has to be at least 2.\033[K\033[1A\r\033[K", end="" 63 | ) 64 | 65 | def test_min_value_int_equal(self): 66 | with mock.patch("yehua.thirdparty.cutie.input", return_value="1"): 67 | self.assertEqual(cutie.get_number("foo", min_value=1), 1) 68 | 69 | def test_min_value_int_greater(self): 70 | with mock.patch("yehua.thirdparty.cutie.input", return_value="2"): 71 | self.assertEqual(cutie.get_number("foo", min_value=1), 2) 72 | 73 | @mock.patch("yehua.thirdparty.cutie.print", side_effect=MockException) 74 | def test_max_value_float_too_high(self, mock_print): 75 | with mock.patch("yehua.thirdparty.cutie.input", return_value="1.2"): 76 | with self.assertRaises(MockException): 77 | cutie.get_number("foo", max_value=1.1) 78 | mock_print.assert_called_once_with( 79 | "Has to be at most 1.1.\033[1A\r\033[K", end="" 80 | ) 81 | 82 | def test_max_value_float_equal(self): 83 | with mock.patch("yehua.thirdparty.cutie.input", return_value="1.1"): 84 | self.assertEqual(cutie.get_number("foo", max_value=1.1), 1.1) 85 | 86 | def test_max_value_float_smaller(self): 87 | with mock.patch("yehua.thirdparty.cutie.input", return_value="1.1"): 88 | self.assertEqual(cutie.get_number("foo", max_value=1.2), 1.1) 89 | 90 | @mock.patch("yehua.thirdparty.cutie.print", side_effect=MockException) 91 | def test_max_value_int_too_high(self, mock_print): 92 | with mock.patch("yehua.thirdparty.cutie.input", return_value="2"): 93 | with self.assertRaises(MockException): 94 | cutie.get_number("foo", max_value=1) 95 | mock_print.assert_called_once_with( 96 | "Has to be at most 1.\033[1A\r\033[K", end="" 97 | ) 98 | 99 | def test_max_value_int_equal(self): 100 | with mock.patch("yehua.thirdparty.cutie.input", return_value="1"): 101 | self.assertEqual(cutie.get_number("foo", max_value=1), 1) 102 | 103 | def test_max_value_int_smaller(self): 104 | with mock.patch("yehua.thirdparty.cutie.input", return_value="1"): 105 | self.assertEqual(cutie.get_number("foo", max_value=2), 1) 106 | 107 | @mock.patch("yehua.thirdparty.cutie.print") 108 | def test_print_finalize(self, mock_print): 109 | with mock.patch("yehua.thirdparty.cutie.input", return_value="1"): 110 | cutie.get_number("foo") 111 | mock_print.assert_called_once_with("\033[K", end="") 112 | -------------------------------------------------------------------------------- /tests/cutie_tests/test_prompt_yes_or_no.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | from unittest import mock 3 | 4 | import readchar 5 | 6 | from . import PrintCall, InputContext, cutie 7 | 8 | print_call = PrintCall( 9 | {"selected": "\x1b[K\x1b[31m>\x1b[0m ", "selectable": "\x1b[K "} 10 | ) 11 | 12 | 13 | class TestPromtYesOrNo(unittest.TestCase): 14 | 15 | default_yes_print_calls = [ 16 | (tuple(),), 17 | (("\x1b[K\x1b[31m>\x1b[0m Yes",),), 18 | (("\x1b[K No",),), 19 | (("\x1b[3A\r\x1b[Kfoo (Y/N) Yes",), {"end": "", "flush": True}), 20 | (("\x1b[K\n\x1b[K\n\x1b[K\n\x1b[3A",),), 21 | ] 22 | 23 | default_no_print_calls = [ 24 | (tuple(),), 25 | (("\x1b[K Yes",),), 26 | (("\x1b[K\x1b[31m>\x1b[0m No",),), 27 | (("\x1b[3A\r\x1b[Kfoo (Y/N) No",), {"end": "", "flush": True}), 28 | (("\x1b[K\n\x1b[K\n\x1b[K\n\x1b[3A",),), 29 | ] 30 | 31 | @mock.patch("yehua.thirdparty.cutie.print") 32 | def test_print_message(self, mock_print): 33 | expected_calls = [ 34 | (tuple(),), 35 | (("\x1b[K Yes",),), 36 | (("\x1b[K\x1b[31m>\x1b[0m No",),), 37 | (("\x1b[3A\r\x1b[Kfoo (Y/N) ",), {"end": "", "flush": True}), 38 | (("\x1b[K\n\x1b[K\n\x1b[K\n\x1b[3A",),), 39 | ] 40 | with InputContext("\r"): 41 | cutie.prompt_yes_or_no("foo") 42 | self.assertEqual(mock_print.call_args_list, expected_calls) 43 | 44 | @mock.patch("yehua.thirdparty.cutie.print") 45 | def test_print_message_custom_prefixes(self, mock_print): 46 | expected_calls = [(("\x1b[K+Yes",),), (("\x1b[K*No",),)] 47 | with InputContext("\r"): 48 | cutie.prompt_yes_or_no( 49 | "foo", selected_prefix="*", deselected_prefix="+" 50 | ) 51 | self.assertEqual(mock_print.call_args_list[1:3], expected_calls) 52 | 53 | @mock.patch("yehua.thirdparty.cutie.print") 54 | def test_print_message_custom_yes_no_text(self, mock_print): 55 | expected_calls = [ 56 | (("\x1b[K bar",),), 57 | (("\x1b[K\x1b[31m>\x1b[0m baz",),), 58 | ] 59 | with InputContext("\r"): 60 | cutie.prompt_yes_or_no("foo", yes_text="bar", no_text="baz") 61 | self.assertEqual(mock_print.call_args_list[1:3], expected_calls) 62 | 63 | @mock.patch("yehua.thirdparty.cutie.print") 64 | def test_print_message_default_is_yes(self, mock_print): 65 | expected_calls = [ 66 | (("\x1b[K\x1b[31m>\x1b[0m Yes",),), 67 | (("\x1b[K No",),), 68 | ] 69 | with InputContext("\r"): 70 | cutie.prompt_yes_or_no("foo", default_is_yes=True) 71 | self.assertEqual(mock_print.call_args_list[1:3], expected_calls) 72 | 73 | @mock.patch("yehua.thirdparty.cutie.print") 74 | def test_move_up(self, mock_print): 75 | with InputContext(readchar.key.UP, "\r"): 76 | self.assertTrue(cutie.prompt_yes_or_no("foo")) 77 | self.assertEqual( 78 | mock_print.call_args_list[-5:], self.default_yes_print_calls 79 | ) 80 | 81 | @mock.patch("yehua.thirdparty.cutie.print") 82 | def test_move_up_over_boundary(self, mock_print): 83 | with InputContext(readchar.key.UP, readchar.key.UP, "\r"): 84 | self.assertFalse(cutie.prompt_yes_or_no("foo")) 85 | self.assertEqual( 86 | mock_print.call_args_list[-5:], self.default_no_print_calls 87 | ) 88 | 89 | @mock.patch("yehua.thirdparty.cutie.print") 90 | def test_move_down(self, mock_print): 91 | with InputContext(readchar.key.DOWN, "\r"): 92 | self.assertFalse( 93 | cutie.prompt_yes_or_no("foo", default_is_yes=True) 94 | ) 95 | self.assertEqual( 96 | mock_print.call_args_list[-5:], self.default_no_print_calls 97 | ) 98 | 99 | @mock.patch("yehua.thirdparty.cutie.print") 100 | def test_move_down_over_boundary(self, mock_print): 101 | with InputContext(readchar.key.DOWN, "\r"): 102 | self.assertTrue(cutie.prompt_yes_or_no("foo")) 103 | self.assertEqual( 104 | mock_print.call_args_list[-5:], self.default_yes_print_calls 105 | ) 106 | 107 | @mock.patch("yehua.thirdparty.cutie.print") 108 | def test_backspace_delete_char(self, mock_print): 109 | expected_calls = [ 110 | (tuple(),), 111 | print_call("Yes", "selected"), 112 | print_call("No"), 113 | (("\x1b[3A\r\x1b[Kfoo (Y/N) Ye",), {"end": "", "flush": True}), 114 | (("\x1b[K\n\x1b[K\n\x1b[K\n\x1b[3A",),), 115 | ] 116 | with InputContext(readchar.key.UP, readchar.key.BACKSPACE, "\r"): 117 | cutie.prompt_yes_or_no("foo") 118 | self.assertEqual(mock_print.call_args_list[-5:], expected_calls) 119 | 120 | @mock.patch("yehua.thirdparty.cutie.print") 121 | def test_ctrl_c_abort(self, *m): 122 | with InputContext(readchar.key.CTRL_C): 123 | with self.assertRaises(KeyboardInterrupt): 124 | cutie.prompt_yes_or_no("") 125 | 126 | @mock.patch("yehua.thirdparty.cutie.print") 127 | def test_ctrl_c_abort_with_input(self, *m): 128 | with InputContext(readchar.key.UP, readchar.key.CTRL_D): 129 | with self.assertRaises(KeyboardInterrupt): 130 | cutie.prompt_yes_or_no("") 131 | 132 | @mock.patch("yehua.thirdparty.cutie.print") 133 | def test_ctrl_d_abort(self, *m): 134 | with InputContext(readchar.key.CTRL_D): 135 | with self.assertRaises(KeyboardInterrupt): 136 | cutie.prompt_yes_or_no("") 137 | 138 | @mock.patch("yehua.thirdparty.cutie.print") 139 | def test_ctrl_d_abort_with_input(self, *m): 140 | with InputContext(readchar.key.UP, readchar.key.CTRL_D): 141 | with self.assertRaises(KeyboardInterrupt): 142 | cutie.prompt_yes_or_no("") 143 | 144 | @mock.patch("yehua.thirdparty.cutie.print") 145 | def test_enter_confirm_default(self, *m): 146 | with InputContext(readchar.key.ENTER): 147 | self.assertFalse(cutie.prompt_yes_or_no("")) 148 | 149 | @mock.patch("yehua.thirdparty.cutie.print") 150 | def test_enter_confirm_selection(self, *m): 151 | with InputContext(readchar.key.UP, readchar.key.ENTER): 152 | self.assertTrue(cutie.prompt_yes_or_no("")) 153 | 154 | @mock.patch("yehua.thirdparty.cutie.print") 155 | def test_tab_select(self, mock_print): 156 | expected_calls = [ 157 | (tuple(),), 158 | print_call("Yes"), 159 | print_call("No", "selected"), 160 | (("\x1b[3A\r\x1b[Kfoo (Y/N) No",), {"end": "", "flush": True}), 161 | (("\x1b[K\n\x1b[K\n\x1b[K\n\x1b[3A",),), 162 | ] 163 | with InputContext("\t", "\r"): 164 | cutie.prompt_yes_or_no("foo") 165 | self.assertEqual(mock_print.call_args_list[-5:], expected_calls) 166 | 167 | @mock.patch("yehua.thirdparty.cutie.print") 168 | def test_write_keypress_to_terminal(self, mock_print): 169 | expected_calls = [ 170 | (tuple(),), 171 | print_call("Yes"), 172 | print_call("No", "selected"), 173 | (("\x1b[3A\r\x1b[Kfoo (Y/N) ",), {"end": "", "flush": True}), 174 | (tuple(),), 175 | print_call("Yes"), 176 | print_call("No"), 177 | (("\x1b[3A\r\x1b[Kfoo (Y/N) f",), {"end": "", "flush": True}), 178 | (tuple(),), 179 | print_call("Yes"), 180 | print_call("No"), 181 | (("\x1b[3A\r\x1b[Kfoo (Y/N) fo",), {"end": "", "flush": True}), 182 | (tuple(),), 183 | print_call("Yes"), 184 | print_call("No"), 185 | (("\x1b[3A\r\x1b[Kfoo (Y/N) foo",), {"end": "", "flush": True}), 186 | ] 187 | with InputContext("f", "o", "o", readchar.key.CTRL_C): 188 | with self.assertRaises(KeyboardInterrupt): 189 | cutie.prompt_yes_or_no("foo") 190 | self.assertEqual(mock_print.call_args_list, expected_calls) 191 | 192 | @mock.patch("yehua.thirdparty.cutie.print") 193 | def test_write_keypress_to_terminal_resume_selection(self, mock_print): 194 | expected_calls = [ 195 | (tuple(),), 196 | print_call("Yes", "selected"), 197 | print_call("No"), 198 | (("\x1b[3A\r\x1b[Kfoo (Y/N) Yes",), {"end": "", "flush": True}), 199 | (("\x1b[K\n\x1b[K\n\x1b[K\n\x1b[3A",),), 200 | ] 201 | with InputContext("f", readchar.key.DOWN, "\r"): 202 | self.assertTrue(cutie.prompt_yes_or_no("foo")) 203 | self.assertEqual(mock_print.call_args_list[-5:], expected_calls) 204 | 205 | @mock.patch("yehua.thirdparty.cutie.print") 206 | def test_evaluate_written_input_yes_ignorecase(self, mock_print): 207 | expected_calls = [ 208 | (tuple(),), 209 | print_call("Yes", "selected"), 210 | print_call("No"), 211 | (("\x1b[3A\r\x1b[Kfoo (Y/N) yes",), {"end": "", "flush": True}), 212 | (("\x1b[K\n\x1b[K\n\x1b[K\n\x1b[3A",),), 213 | ] 214 | with InputContext("y", "e", "s", "\r"): 215 | self.assertTrue(cutie.prompt_yes_or_no("foo")) 216 | self.assertEqual(mock_print.call_args_list[-5:], expected_calls) 217 | 218 | @mock.patch("yehua.thirdparty.cutie.print") 219 | def test_evaluate_written_input_yes_case_sensitive(self, mock_print): 220 | expected_calls = ( 221 | ("\x1b[3A\r\x1b[Kfoo (Y/N) yes",), 222 | {"end": "", "flush": True}, 223 | ) 224 | 225 | with InputContext("y", "e", "s", readchar.key.CTRL_C): 226 | res = None 227 | with self.assertRaises(KeyboardInterrupt): 228 | res = cutie.prompt_yes_or_no("foo", has_to_match_case=True) 229 | self.assertIsNone(res) 230 | self.assertEqual(mock_print.call_args_list[-1], expected_calls) 231 | 232 | @mock.patch("yehua.thirdparty.cutie.print") 233 | def test_evaluate_written_input_no_ignorecase(self, mock_print): 234 | expected_calls = [ 235 | (tuple(),), 236 | print_call("Yes"), 237 | print_call("No", "selected"), 238 | (("\x1b[3A\r\x1b[Kfoo (Y/N) no",), {"end": "", "flush": True}), 239 | (("\x1b[K\n\x1b[K\n\x1b[K\n\x1b[3A",),), 240 | ] 241 | with InputContext("n", "o", "\r"): 242 | self.assertFalse(cutie.prompt_yes_or_no("foo")) 243 | self.assertEqual(mock_print.call_args_list[-5:], expected_calls) 244 | 245 | @mock.patch("yehua.thirdparty.cutie.print") 246 | def test_evaluate_written_input_no_case_sensitive(self, mock_print): 247 | expected_calls = ( 248 | ("\x1b[3A\r\x1b[Kfoo (Y/N) no",), 249 | {"end": "", "flush": True}, 250 | ) 251 | 252 | with InputContext("n", "o", readchar.key.CTRL_C): 253 | res = None 254 | with self.assertRaises(KeyboardInterrupt): 255 | res = cutie.prompt_yes_or_no("foo", has_to_match_case=True) 256 | self.assertIsNone(res) 257 | self.assertEqual(mock_print.call_args_list[-1], expected_calls) 258 | -------------------------------------------------------------------------------- /tests/cutie_tests/test_secure_input.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | from unittest import mock 3 | 4 | from yehua.thirdparty import cutie 5 | 6 | 7 | class TestSecureInput(unittest.TestCase): 8 | def test_secure_input(self): 9 | with mock.patch( 10 | "yehua.thirdparty.cutie.getpass.getpass", return_value="foo" 11 | ) as mock_getpass: 12 | self.assertEqual(cutie.secure_input("foo"), "foo") 13 | mock_getpass.assert_called_once_with("foo ") 14 | -------------------------------------------------------------------------------- /tests/cutie_tests/test_select.py: -------------------------------------------------------------------------------- 1 | import string 2 | import unittest 3 | from unittest import mock 4 | 5 | import readchar 6 | 7 | from . import PrintCall, InputContext, MockException, cutie 8 | 9 | print_call = PrintCall( 10 | { 11 | "selectable": "\x1b[K\x1b[1m[ ]\x1b[0m ", 12 | "selected": "\x1b[K\x1b[1m[\x1b[32;1mx\x1b[0;1m]\x1b[0m ", 13 | "caption": "\x1b[K", 14 | } 15 | ) 16 | 17 | 18 | class TestSelect(unittest.TestCase): 19 | @mock.patch("yehua.thirdparty.cutie.print", side_effect=MockException) 20 | def test_print_list_newlines(self, mock_print): 21 | args_list = ["foo", "bar"] 22 | with self.assertRaises(MockException): 23 | cutie.select(args_list) 24 | mock_print.assert_called_once_with("\n" * (len(args_list) - 1)) 25 | 26 | @mock.patch( 27 | "yehua.thirdparty.cutie.readchar.readkey", side_effect=MockException 28 | ) 29 | @mock.patch("yehua.thirdparty.cutie.print") 30 | def test_print_move_to_first_item(self, mock_print, *m): 31 | args_list = ["foo", "bar"] 32 | with self.assertRaises(MockException): 33 | cutie.select(args_list) 34 | self.assertEqual( 35 | mock_print.call_args_list[1], ((f"\033[{len(args_list) + 1}A",),) 36 | ) 37 | 38 | @mock.patch( 39 | "yehua.thirdparty.cutie.readchar.readkey", side_effect=MockException 40 | ) 41 | @mock.patch("yehua.thirdparty.cutie.print") 42 | def test_print_options(self, mock_print, *m): 43 | args_list = ["foo", "bar"] 44 | expected_calls = [print_call("foo", "selected"), print_call("bar")] 45 | with self.assertRaises(MockException): 46 | cutie.select(args_list) 47 | self.assertEqual(mock_print.call_args_list[2:], expected_calls) 48 | 49 | @mock.patch( 50 | "yehua.thirdparty.cutie.readchar.readkey", side_effect=MockException 51 | ) 52 | @mock.patch("yehua.thirdparty.cutie.print") 53 | def test_print_options_selected_index_set(self, mock_print, *m): 54 | args_list = ["foo", "bar"] 55 | expected_calls = [print_call("foo"), print_call("bar", "selected")] 56 | with self.assertRaises(MockException): 57 | cutie.select(args_list, selected_index=1) 58 | self.assertEqual(mock_print.call_args_list[2:], expected_calls) 59 | 60 | @mock.patch( 61 | "yehua.thirdparty.cutie.readchar.readkey", side_effect=MockException 62 | ) 63 | @mock.patch("yehua.thirdparty.cutie.print") 64 | def test_print_non_selectable(self, mock_print, *m): 65 | args_list = ["foo", "bar"] 66 | expected_calls = [ 67 | print_call("foo", "selected"), 68 | print_call("bar", "caption"), 69 | ] 70 | with self.assertRaises(MockException): 71 | cutie.select(args_list, caption_indices=[1]) 72 | self.assertEqual(mock_print.call_args_list[2:], expected_calls) 73 | 74 | @mock.patch( 75 | "yehua.thirdparty.cutie.readchar.readkey", side_effect=MockException 76 | ) 77 | @mock.patch("yehua.thirdparty.cutie.print") 78 | def test_print_options_custom_prefixes(self, mock_print, *m): 79 | args_list = ["foo", "bar", "baz"] 80 | expected_calls = [ 81 | (("\x1b[K*foo",),), 82 | (("\x1b[K+bar",),), 83 | (("\x1b[K$baz",),), 84 | ] 85 | with self.assertRaises(MockException): 86 | cutie.select( 87 | args_list, 88 | caption_indices=[2], 89 | selected_prefix="*", 90 | deselected_prefix="+", 91 | caption_prefix="$", 92 | ) 93 | self.assertEqual(mock_print.call_args_list[2:], expected_calls) 94 | 95 | @mock.patch("yehua.thirdparty.cutie.print") 96 | def test_ignore_unrecognized_key(self, mock_print): 97 | exclude = [ 98 | "__builtins__", 99 | "__cached__", 100 | "__doc__", 101 | "__file__", 102 | "__loader__", 103 | "__name__", 104 | "__package__", 105 | "__spec__", 106 | "UP", 107 | "DOWN", 108 | "ENTER", 109 | "CTRL_C", 110 | "CTRL_D", 111 | ] 112 | all_keys = [ 113 | getattr(readchar.key, k) 114 | for k in dir(readchar.key) 115 | if k not in exclude 116 | ] 117 | all_keys.extend(string.printable) 118 | expected_calls = [ 119 | (("",),), 120 | (("\x1b[2A",),), 121 | (("\x1b[K\x1b[1m[\x1b[32;1mx\x1b[0;1m]\x1b[0m foo",),), 122 | ] 123 | 124 | for key in all_keys: 125 | with InputContext(readchar.key.DOWN, key, readchar.key.ENTER): 126 | selindex = cutie.select(["foo"]) 127 | self.assertEqual(selindex, 0) 128 | self.assertEqual(mock_print.call_args_list[:3], expected_calls) 129 | mock_print.reset_mock() 130 | 131 | @mock.patch("yehua.thirdparty.cutie.print") 132 | def test_move_up(self, *m): 133 | with InputContext(readchar.key.UP, "\r"): 134 | args_list = ["foo", "bar"] 135 | selindex = cutie.select(args_list, selected_index=1) 136 | self.assertEqual(selindex, 0) 137 | 138 | @mock.patch("yehua.thirdparty.cutie.print") 139 | def test_move_up_skip_caption(self, *m): 140 | with InputContext(readchar.key.UP, "\r"): 141 | args_list = ["foo", "bar", "baz"] 142 | selindex = cutie.select( 143 | args_list, selected_index=2, caption_indices=[1] 144 | ) 145 | self.assertEqual(selindex, 0) 146 | 147 | @mock.patch("yehua.thirdparty.cutie.print") 148 | def test_move_down(self, *m): 149 | with InputContext(readchar.key.DOWN, "\r"): 150 | args_list = ["foo", "bar"] 151 | selindex = cutie.select(args_list) 152 | self.assertEqual(selindex, 1) 153 | 154 | @mock.patch("yehua.thirdparty.cutie.print") 155 | def test_move_down_skip_caption(self, *m): 156 | with InputContext(readchar.key.DOWN, "\r"): 157 | args_list = ["foo", "bar", "baz"] 158 | selindex = cutie.select(args_list, caption_indices=[1]) 159 | self.assertEqual(selindex, 2) 160 | 161 | @mock.patch("yehua.thirdparty.cutie.print") 162 | def test_keyboard_interrupt_ctrl_c_no_input(self, *m): 163 | with InputContext(readchar.key.CTRL_C): 164 | with self.assertRaises(KeyboardInterrupt): 165 | cutie.select(["foo"]) 166 | 167 | @mock.patch("yehua.thirdparty.cutie.print") 168 | def test_keyboard_interrupt_ctrl_c_selected(self, *m): 169 | with InputContext(readchar.key.DOWN, readchar.key.CTRL_C): 170 | with self.assertRaises(KeyboardInterrupt): 171 | cutie.select(["foo"], selected_index=0) 172 | 173 | @mock.patch("yehua.thirdparty.cutie.print") 174 | def test_keyboard_interrupt_ctrl_d_no_input(self, *m): 175 | with InputContext(readchar.key.CTRL_D): 176 | with self.assertRaises(KeyboardInterrupt): 177 | cutie.select(["foo"]) 178 | 179 | @mock.patch("yehua.thirdparty.cutie.print") 180 | def test_keyboard_interrupt_ctrl_d_selected(self, *m): 181 | with InputContext(readchar.key.DOWN, readchar.key.CTRL_D): 182 | with self.assertRaises(KeyboardInterrupt): 183 | cutie.select(["foo"], selected_index=0) 184 | -------------------------------------------------------------------------------- /tests/fixtures/project_s/.editorconfig: -------------------------------------------------------------------------------- 1 | # http://editorconfig.org 2 | 3 | root = true 4 | 5 | [*] 6 | indent_style = space 7 | indent_size = 4 8 | trim_trailing_whitespace = true 9 | insert_final_newline = true 10 | charset = utf-8 11 | end_of_line = lf 12 | 13 | [*.bat] 14 | indent_style = tab 15 | end_of_line = crlf 16 | 17 | [LICENSE] 18 | insert_final_newline = false 19 | 20 | [Makefile] 21 | indent_style = tab 22 | -------------------------------------------------------------------------------- /tests/fixtures/project_s/.github/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | * project_n version: 2 | * Python version: 3 | * Operating System: 4 | 5 | ### Description 6 | 7 | Describe what you were trying to get done. 8 | Tell us what happened, what went wrong, and what you expected to happen. 9 | 10 | ### What I Did 11 | 12 | ``` 13 | Paste the command(s) you ran and the output. 14 | If there was a crash, please include the traceback here. 15 | ``` 16 | -------------------------------------------------------------------------------- /tests/fixtures/project_s/.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | env/ 12 | build/ 13 | develop-eggs/ 14 | dist/ 15 | downloads/ 16 | eggs/ 17 | .eggs/ 18 | lib/ 19 | lib64/ 20 | parts/ 21 | sdist/ 22 | var/ 23 | wheels/ 24 | *.egg-info/ 25 | .installed.cfg 26 | *.egg 27 | 28 | # PyInstaller 29 | # Usually these files are written by a python script from a template 30 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 31 | *.manifest 32 | *.spec 33 | 34 | # Installer logs 35 | pip-log.txt 36 | pip-delete-this-directory.txt 37 | 38 | # Unit test / coverage reports 39 | htmlcov/ 40 | .tox/ 41 | .coverage 42 | .coverage.* 43 | .cache 44 | nosetests.xml 45 | coverage.xml 46 | *.cover 47 | .hypothesis/ 48 | .pytest_cache/ 49 | 50 | # Translations 51 | *.mo 52 | *.pot 53 | 54 | # Django stuff: 55 | *.log 56 | local_settings.py 57 | 58 | # Flask stuff: 59 | instance/ 60 | .webassets-cache 61 | 62 | # Scrapy stuff: 63 | .scrapy 64 | 65 | # Sphinx documentation 66 | docs/_build/ 67 | 68 | # PyBuilder 69 | target/ 70 | 71 | # Jupyter Notebook 72 | .ipynb_checkpoints 73 | 74 | # pyenv 75 | .python-version 76 | 77 | # celery beat schedule file 78 | celerybeat-schedule 79 | 80 | # SageMath parsed files 81 | *.sage.py 82 | 83 | # dotenv 84 | .env 85 | 86 | # virtualenv 87 | .venv 88 | venv/ 89 | ENV/ 90 | 91 | # Spyder project settings 92 | .spyderproject 93 | .spyproject 94 | 95 | # Rope project settings 96 | .ropeproject 97 | 98 | # mkdocs documentation 99 | /site 100 | 101 | # mypy 102 | .mypy_cache/ 103 | 104 | # IDE settings 105 | .vscode/ -------------------------------------------------------------------------------- /tests/fixtures/project_s/.moban.yml: -------------------------------------------------------------------------------- 1 | configuration: 2 | template_dir: 3 | - git://github.com/moremoban/cookiecutter-pypackage.git!/{{cookiecutter.project_slug}} 4 | configuration: project_s.yml 5 | force_template_type: cookiecutter 6 | template_types: 7 | cookiecutter: 8 | base_type: jinja2 9 | file_extensions: 10 | - cookiecutter 11 | options: 12 | trim_blocks: false 13 | lstrip_blocks: false 14 | extensions: 15 | - cookiecutter.extensions.JsonifyExtension 16 | - jinja2_time.TimeExtension 17 | targets: 18 | - LICENSE: LICENSE 19 | - CONTRIBUTING.rst: CONTRIBUTING.rst 20 | - Makefile: Makefile 21 | - tests/test_project_s.py: tests/test_{{cookiecutter.project_slug}}.py 22 | - tests/__init__.py: tests/__init__.py 23 | - MANIFEST.in: MANIFEST.in 24 | - docs/index.rst: docs/index.rst 25 | - docs/contributing.rst: docs/contributing.rst 26 | - docs/Makefile: docs/Makefile 27 | - docs/conf.py: docs/conf.py 28 | - docs/usage.rst: docs/usage.rst 29 | - docs/make.bat: docs/make.bat 30 | - docs/history.rst: docs/history.rst 31 | - docs/installation.rst: docs/installation.rst 32 | - docs/authors.rst: docs/authors.rst 33 | - docs/readme.rst: docs/readme.rst 34 | - .editorconfig: .editorconfig 35 | - setup.py: setup.py 36 | - .gitignore: .gitignore 37 | - HISTORY.rst: HISTORY.rst 38 | - project_s/__init__.py: '{{cookiecutter.project_slug}}/__init__.py' 39 | - project_s/cli.py: '{{cookiecutter.project_slug}}/cli.py' 40 | - project_s/project_s.py: '{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}.py' 41 | - .github/ISSUE_TEMPLATE.md: .github/ISSUE_TEMPLATE.md 42 | - tox.ini: tox.ini 43 | - AUTHORS.rst: AUTHORS.rst 44 | - setup.cfg: setup.cfg 45 | - README.rst: README.rst 46 | - .travis.yml: .travis.yml 47 | - requirements_dev.txt: requirements_dev.txt 48 | -------------------------------------------------------------------------------- /tests/fixtures/project_s/.travis.yml: -------------------------------------------------------------------------------- 1 | # Config file for automatic testing at travis-ci.com 2 | 3 | language: python 4 | python: 5 | - 3.8 6 | - 3.7 7 | - 3.6 8 | - 3.5 9 | 10 | # Command to install dependencies, e.g. pip install -r requirements.txt --use-mirrors 11 | install: pip install -U tox-travis 12 | 13 | # Command to run tests, e.g. python setup.py test 14 | script: tox 15 | 16 | # Assuming you have installed the travis-ci CLI tool, after you 17 | # create the Github repo and add it to Travis, run the 18 | # following command to finish PyPI deployment setup: 19 | # $ travis encrypt --add deploy.password 20 | deploy: 21 | provider: pypi 22 | distributions: sdist bdist_wheel 23 | user: pypi_u 24 | password: 25 | secure: PLEASE_REPLACE_ME 26 | on: 27 | tags: true 28 | repo: github_u/project_s 29 | python: 3.8 30 | -------------------------------------------------------------------------------- /tests/fixtures/project_s/AUTHORS.rst: -------------------------------------------------------------------------------- 1 | ======= 2 | Credits 3 | ======= 4 | 5 | Development Lead 6 | ---------------- 7 | 8 | * full_n 9 | 10 | Contributors 11 | ------------ 12 | 13 | None yet. Why not be the first? 14 | -------------------------------------------------------------------------------- /tests/fixtures/project_s/CONTRIBUTING.rst: -------------------------------------------------------------------------------- 1 | .. highlight:: shell 2 | 3 | ============ 4 | Contributing 5 | ============ 6 | 7 | Contributions are welcome, and they are greatly appreciated! Every little bit 8 | helps, and credit will always be given. 9 | 10 | You can contribute in many ways: 11 | 12 | Types of Contributions 13 | ---------------------- 14 | 15 | Report Bugs 16 | ~~~~~~~~~~~ 17 | 18 | Report bugs at https://github.com/github_u/project_s/issues. 19 | 20 | If you are reporting a bug, please include: 21 | 22 | * Your operating system name and version. 23 | * Any details about your local setup that might be helpful in troubleshooting. 24 | * Detailed steps to reproduce the bug. 25 | 26 | Fix Bugs 27 | ~~~~~~~~ 28 | 29 | Look through the GitHub issues for bugs. Anything tagged with "bug" and "help 30 | wanted" is open to whoever wants to implement it. 31 | 32 | Implement Features 33 | ~~~~~~~~~~~~~~~~~~ 34 | 35 | Look through the GitHub issues for features. Anything tagged with "enhancement" 36 | and "help wanted" is open to whoever wants to implement it. 37 | 38 | Write Documentation 39 | ~~~~~~~~~~~~~~~~~~~ 40 | 41 | project_n could always use more documentation, whether as part of the 42 | official project_n docs, in docstrings, or even on the web in blog posts, 43 | articles, and such. 44 | 45 | Submit Feedback 46 | ~~~~~~~~~~~~~~~ 47 | 48 | The best way to send feedback is to file an issue at https://github.com/github_u/project_s/issues. 49 | 50 | If you are proposing a feature: 51 | 52 | * Explain in detail how it would work. 53 | * Keep the scope as narrow as possible, to make it easier to implement. 54 | * Remember that this is a volunteer-driven project, and that contributions 55 | are welcome :) 56 | 57 | Get Started! 58 | ------------ 59 | 60 | Ready to contribute? Here's how to set up `project_s` for local development. 61 | 62 | 1. Fork the `project_s` repo on GitHub. 63 | 2. Clone your fork locally:: 64 | 65 | $ git clone git@github.com:your_name_here/project_s.git 66 | 67 | 3. Install your local copy into a virtualenv. Assuming you have virtualenvwrapper installed, this is how you set up your fork for local development:: 68 | 69 | $ mkvirtualenv project_s 70 | $ cd project_s/ 71 | $ python setup.py develop 72 | 73 | 4. Create a branch for local development:: 74 | 75 | $ git checkout -b name-of-your-bugfix-or-feature 76 | 77 | Now you can make your changes locally. 78 | 79 | 5. When you're done making changes, check that your changes pass flake8 and the 80 | tests, including testing other Python versions with tox:: 81 | 82 | $ flake8 project_s tests 83 | $ python setup.py test or pytest 84 | $ tox 85 | 86 | To get flake8 and tox, just pip install them into your virtualenv. 87 | 88 | 6. Commit your changes and push your branch to GitHub:: 89 | 90 | $ git add . 91 | $ git commit -m "Your detailed description of your changes." 92 | $ git push origin name-of-your-bugfix-or-feature 93 | 94 | 7. Submit a pull request through the GitHub website. 95 | 96 | Pull Request Guidelines 97 | ----------------------- 98 | 99 | Before you submit a pull request, check that it meets these guidelines: 100 | 101 | 1. The pull request should include tests. 102 | 2. If the pull request adds functionality, the docs should be updated. Put 103 | your new functionality into a function with a docstring, and add the 104 | feature to the list in README.rst. 105 | 3. The pull request should work for Python 3.5, 3.6, 3.7 and 3.8, and for PyPy. Check 106 | https://travis-ci.com/github_u/project_s/pull_requests 107 | and make sure that the tests pass for all supported Python versions. 108 | 109 | Tips 110 | ---- 111 | 112 | To run a subset of tests:: 113 | 114 | $ pytest tests.test_project_s 115 | 116 | 117 | Deploying 118 | --------- 119 | 120 | A reminder for the maintainers on how to deploy. 121 | Make sure all your changes are committed (including an entry in HISTORY.rst). 122 | Then run:: 123 | 124 | $ bump2version patch # possible: major / minor / patch 125 | $ git push 126 | $ git push --tags 127 | 128 | Travis will then deploy to PyPI if tests pass. 129 | -------------------------------------------------------------------------------- /tests/fixtures/project_s/HISTORY.rst: -------------------------------------------------------------------------------- 1 | ======= 2 | History 3 | ======= 4 | 5 | 1.0 (2020-05-10) 6 | ------------------ 7 | 8 | * First release on PyPI. 9 | -------------------------------------------------------------------------------- /tests/fixtures/project_s/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020, full_n 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 | -------------------------------------------------------------------------------- /tests/fixtures/project_s/MANIFEST.in: -------------------------------------------------------------------------------- 1 | include AUTHORS.rst 2 | include CONTRIBUTING.rst 3 | include HISTORY.rst 4 | include LICENSE 5 | include README.rst 6 | 7 | recursive-include tests * 8 | recursive-exclude * __pycache__ 9 | recursive-exclude * *.py[co] 10 | 11 | recursive-include docs *.rst conf.py Makefile make.bat *.jpg *.png *.gif 12 | -------------------------------------------------------------------------------- /tests/fixtures/project_s/Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: clean clean-test clean-pyc clean-build docs help 2 | .DEFAULT_GOAL := help 3 | 4 | define BROWSER_PYSCRIPT 5 | import os, webbrowser, sys 6 | 7 | from urllib.request import pathname2url 8 | 9 | webbrowser.open("file://" + pathname2url(os.path.abspath(sys.argv[1]))) 10 | endef 11 | export BROWSER_PYSCRIPT 12 | 13 | define PRINT_HELP_PYSCRIPT 14 | import re, sys 15 | 16 | for line in sys.stdin: 17 | match = re.match(r'^([a-zA-Z_-]+):.*?## (.*)$$', line) 18 | if match: 19 | target, help = match.groups() 20 | print("%-20s %s" % (target, help)) 21 | endef 22 | export PRINT_HELP_PYSCRIPT 23 | 24 | BROWSER := python -c "$$BROWSER_PYSCRIPT" 25 | 26 | help: 27 | @python -c "$$PRINT_HELP_PYSCRIPT" < $(MAKEFILE_LIST) 28 | 29 | clean: clean-build clean-pyc clean-test ## remove all build, test, coverage and Python artifacts 30 | 31 | clean-build: ## remove build artifacts 32 | rm -fr build/ 33 | rm -fr dist/ 34 | rm -fr .eggs/ 35 | find . -name '*.egg-info' -exec rm -fr {} + 36 | find . -name '*.egg' -exec rm -f {} + 37 | 38 | clean-pyc: ## remove Python file artifacts 39 | find . -name '*.pyc' -exec rm -f {} + 40 | find . -name '*.pyo' -exec rm -f {} + 41 | find . -name '*~' -exec rm -f {} + 42 | find . -name '__pycache__' -exec rm -fr {} + 43 | 44 | clean-test: ## remove test and coverage artifacts 45 | rm -fr .tox/ 46 | rm -f .coverage 47 | rm -fr htmlcov/ 48 | rm -fr .pytest_cache 49 | 50 | lint: ## check style with flake8 51 | flake8 project_s tests 52 | 53 | test: ## run tests quickly with the default Python 54 | pytest 55 | 56 | test-all: ## run tests on every Python version with tox 57 | tox 58 | 59 | coverage: ## check code coverage quickly with the default Python 60 | coverage run --source project_s -m pytest 61 | coverage report -m 62 | coverage html 63 | $(BROWSER) htmlcov/index.html 64 | 65 | docs: ## generate Sphinx HTML documentation, including API docs 66 | rm -f docs/project_s.rst 67 | rm -f docs/modules.rst 68 | sphinx-apidoc -o docs/ project_s 69 | $(MAKE) -C docs clean 70 | $(MAKE) -C docs html 71 | $(BROWSER) docs/_build/html/index.html 72 | 73 | servedocs: docs ## compile the docs watching for changes 74 | watchmedo shell-command -p '*.rst' -c '$(MAKE) -C docs html' -R -D . 75 | 76 | release: dist ## package and upload a release 77 | twine upload dist/* 78 | 79 | dist: clean ## builds source and wheel package 80 | python setup.py sdist 81 | python setup.py bdist_wheel 82 | ls -l dist 83 | 84 | install: clean ## install the package to the active Python's site-packages 85 | python setup.py install 86 | -------------------------------------------------------------------------------- /tests/fixtures/project_s/README.rst: -------------------------------------------------------------------------------- 1 | ========= 2 | project_n 3 | ========= 4 | 5 | 6 | .. image:: https://img.shields.io/pypi/v/project_s.svg 7 | :target: https://pypi.python.org/pypi/project_s 8 | 9 | .. image:: https://img.shields.io/travis/github_u/project_s.svg 10 | :target: https://travis-ci.com/github_u/project_s 11 | 12 | .. image:: https://readthedocs.org/projects/project-s/badge/?version=latest 13 | :target: https://project-s.readthedocs.io/en/latest/?badge=latest 14 | :alt: Documentation Status 15 | 16 | 17 | .. image:: https://pyup.io/repos/github/github_u/project_s/shield.svg 18 | :target: https://pyup.io/repos/github/github_u/project_s/ 19 | :alt: Updates 20 | 21 | 22 | 23 | project_sd 24 | 25 | 26 | * Free software: MIT license 27 | * Documentation: https://project-s.readthedocs.io. 28 | 29 | 30 | Features 31 | -------- 32 | 33 | * TODO 34 | 35 | Credits 36 | ------- 37 | 38 | This package was created with Cookiecutter_ and the `audreyr/cookiecutter-pypackage`_ project template. 39 | 40 | .. _Cookiecutter: https://github.com/audreyr/cookiecutter 41 | .. _`audreyr/cookiecutter-pypackage`: https://github.com/audreyr/cookiecutter-pypackage 42 | -------------------------------------------------------------------------------- /tests/fixtures/project_s/docs/Makefile: -------------------------------------------------------------------------------- 1 | # Minimal makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line. 5 | SPHINXOPTS = 6 | SPHINXBUILD = python -msphinx 7 | SPHINXPROJ = project_s 8 | SOURCEDIR = . 9 | BUILDDIR = _build 10 | 11 | # Put it first so that "make" without argument is like "make help". 12 | help: 13 | @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 14 | 15 | .PHONY: help Makefile 16 | 17 | # Catch-all target: route all unknown targets to Sphinx using the new 18 | # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). 19 | %: Makefile 20 | @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 21 | -------------------------------------------------------------------------------- /tests/fixtures/project_s/docs/authors.rst: -------------------------------------------------------------------------------- 1 | .. include:: ../AUTHORS.rst 2 | -------------------------------------------------------------------------------- /tests/fixtures/project_s/docs/conf.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # 3 | # project_s documentation build configuration file, created by 4 | # sphinx-quickstart on Fri Jun 9 13:47:02 2017. 5 | # 6 | # This file is execfile()d with the current directory set to its 7 | # containing dir. 8 | # 9 | # Note that not all possible configuration values are present in this 10 | # autogenerated file. 11 | # 12 | # All configuration values have a default; values that are commented out 13 | # serve to show the default. 14 | 15 | # If extensions (or modules to document with autodoc) are in another 16 | # directory, add these directories to sys.path here. If the directory is 17 | # relative to the documentation root, use os.path.abspath to make it 18 | # absolute, like shown here. 19 | # 20 | import os 21 | import sys 22 | sys.path.insert(0, os.path.abspath('..')) 23 | 24 | import project_s 25 | 26 | # -- General configuration --------------------------------------------- 27 | 28 | # If your documentation needs a minimal Sphinx version, state it here. 29 | # 30 | # needs_sphinx = '1.0' 31 | 32 | # Add any Sphinx extension module names here, as strings. They can be 33 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom ones. 34 | extensions = ['sphinx.ext.autodoc', 'sphinx.ext.viewcode'] 35 | 36 | # Add any paths that contain templates here, relative to this directory. 37 | templates_path = ['_templates'] 38 | 39 | # The suffix(es) of source filenames. 40 | # You can specify multiple suffix as a list of string: 41 | # 42 | # source_suffix = ['.rst', '.md'] 43 | source_suffix = '.rst' 44 | 45 | # The master toctree document. 46 | master_doc = 'index' 47 | 48 | # General information about the project. 49 | project = 'project_n' 50 | copyright = "2020, full_n" 51 | author = "full_n" 52 | 53 | # The version info for the project you're documenting, acts as replacement 54 | # for |version| and |release|, also used in various other places throughout 55 | # the built documents. 56 | # 57 | # The short X.Y version. 58 | version = project_s.__version__ 59 | # The full version, including alpha/beta/rc tags. 60 | release = project_s.__version__ 61 | 62 | # The language for content autogenerated by Sphinx. Refer to documentation 63 | # for a list of supported languages. 64 | # 65 | # This is also used if you do content translation via gettext catalogs. 66 | # Usually you set "language" from the command line for these cases. 67 | language = None 68 | 69 | # List of patterns, relative to source directory, that match files and 70 | # directories to ignore when looking for source files. 71 | # This patterns also effect to html_static_path and html_extra_path 72 | exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store'] 73 | 74 | # The name of the Pygments (syntax highlighting) style to use. 75 | pygments_style = 'sphinx' 76 | 77 | # If true, `todo` and `todoList` produce output, else they produce nothing. 78 | todo_include_todos = False 79 | 80 | 81 | # -- Options for HTML output ------------------------------------------- 82 | 83 | # The theme to use for HTML and HTML Help pages. See the documentation for 84 | # a list of builtin themes. 85 | # 86 | html_theme = 'alabaster' 87 | 88 | # Theme options are theme-specific and customize the look and feel of a 89 | # theme further. For a list of options available for each theme, see the 90 | # documentation. 91 | # 92 | # html_theme_options = {} 93 | 94 | # Add any paths that contain custom static files (such as style sheets) here, 95 | # relative to this directory. They are copied after the builtin static files, 96 | # so a file named "default.css" will overwrite the builtin "default.css". 97 | html_static_path = ['_static'] 98 | 99 | 100 | # -- Options for HTMLHelp output --------------------------------------- 101 | 102 | # Output file base name for HTML help builder. 103 | htmlhelp_basename = 'project_sdoc' 104 | 105 | 106 | # -- Options for LaTeX output ------------------------------------------ 107 | 108 | latex_elements = { 109 | # The paper size ('letterpaper' or 'a4paper'). 110 | # 111 | # 'papersize': 'letterpaper', 112 | 113 | # The font size ('10pt', '11pt' or '12pt'). 114 | # 115 | # 'pointsize': '10pt', 116 | 117 | # Additional stuff for the LaTeX preamble. 118 | # 119 | # 'preamble': '', 120 | 121 | # Latex figure (float) alignment 122 | # 123 | # 'figure_align': 'htbp', 124 | } 125 | 126 | # Grouping the document tree into LaTeX files. List of tuples 127 | # (source start file, target name, title, author, documentclass 128 | # [howto, manual, or own class]). 129 | latex_documents = [ 130 | (master_doc, 'project_s.tex', 131 | 'project_n Documentation', 132 | 'full_n', 'manual'), 133 | ] 134 | 135 | 136 | # -- Options for manual page output ------------------------------------ 137 | 138 | # One entry per manual page. List of tuples 139 | # (source start file, name, description, authors, manual section). 140 | man_pages = [ 141 | (master_doc, 'project_s', 142 | 'project_n Documentation', 143 | [author], 1) 144 | ] 145 | 146 | 147 | # -- Options for Texinfo output ---------------------------------------- 148 | 149 | # Grouping the document tree into Texinfo files. List of tuples 150 | # (source start file, target name, title, author, 151 | # dir menu entry, description, category) 152 | texinfo_documents = [ 153 | (master_doc, 'project_s', 154 | 'project_n Documentation', 155 | author, 156 | 'project_s', 157 | 'One line description of project.', 158 | 'Miscellaneous'), 159 | ] 160 | -------------------------------------------------------------------------------- /tests/fixtures/project_s/docs/contributing.rst: -------------------------------------------------------------------------------- 1 | .. include:: ../CONTRIBUTING.rst 2 | -------------------------------------------------------------------------------- /tests/fixtures/project_s/docs/history.rst: -------------------------------------------------------------------------------- 1 | .. include:: ../HISTORY.rst 2 | -------------------------------------------------------------------------------- /tests/fixtures/project_s/docs/index.rst: -------------------------------------------------------------------------------- 1 | Welcome to project_n's documentation! 2 | ====================================== 3 | 4 | .. toctree:: 5 | :maxdepth: 2 6 | :caption: Contents: 7 | 8 | readme 9 | installation 10 | usage 11 | modules 12 | contributing 13 | authors 14 | history 15 | 16 | Indices and tables 17 | ================== 18 | * :ref:`genindex` 19 | * :ref:`modindex` 20 | * :ref:`search` 21 | -------------------------------------------------------------------------------- /tests/fixtures/project_s/docs/installation.rst: -------------------------------------------------------------------------------- 1 | .. highlight:: shell 2 | 3 | ============ 4 | Installation 5 | ============ 6 | 7 | 8 | Stable release 9 | -------------- 10 | 11 | To install project_n, run this command in your terminal: 12 | 13 | .. code-block:: console 14 | 15 | $ pip install project_s 16 | 17 | This is the preferred method to install project_n, as it will always install the most recent stable release. 18 | 19 | If you don't have `pip`_ installed, this `Python installation guide`_ can guide 20 | you through the process. 21 | 22 | .. _pip: https://pip.pypa.io 23 | .. _Python installation guide: http://docs.python-guide.org/en/latest/starting/installation/ 24 | 25 | 26 | From sources 27 | ------------ 28 | 29 | The sources for project_n can be downloaded from the `Github repo`_. 30 | 31 | You can either clone the public repository: 32 | 33 | .. code-block:: console 34 | 35 | $ git clone git://github.com/github_u/project_s 36 | 37 | Or download the `tarball`_: 38 | 39 | .. code-block:: console 40 | 41 | $ curl -OJL https://github.com/github_u/project_s/tarball/master 42 | 43 | Once you have a copy of the source, you can install it with: 44 | 45 | .. code-block:: console 46 | 47 | $ python setup.py install 48 | 49 | 50 | .. _Github repo: https://github.com/github_u/project_s 51 | .. _tarball: https://github.com/github_u/project_s/tarball/master 52 | -------------------------------------------------------------------------------- /tests/fixtures/project_s/docs/make.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | 3 | pushd %~dp0 4 | 5 | REM Command file for Sphinx documentation 6 | 7 | if "%SPHINXBUILD%" == "" ( 8 | set SPHINXBUILD=python -msphinx 9 | ) 10 | set SOURCEDIR=. 11 | set BUILDDIR=_build 12 | set SPHINXPROJ=project_s 13 | 14 | if "%1" == "" goto help 15 | 16 | %SPHINXBUILD% >NUL 2>NUL 17 | if errorlevel 9009 ( 18 | echo. 19 | echo.The Sphinx module was not found. Make sure you have Sphinx installed, 20 | echo.then set the SPHINXBUILD environment variable to point to the full 21 | echo.path of the 'sphinx-build' executable. Alternatively you may add the 22 | echo.Sphinx directory to PATH. 23 | echo. 24 | echo.If you don't have Sphinx installed, grab it from 25 | echo.http://sphinx-doc.org/ 26 | exit /b 1 27 | ) 28 | 29 | %SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% 30 | goto end 31 | 32 | :help 33 | %SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% 34 | 35 | :end 36 | popd 37 | -------------------------------------------------------------------------------- /tests/fixtures/project_s/docs/readme.rst: -------------------------------------------------------------------------------- 1 | .. include:: ../README.rst 2 | -------------------------------------------------------------------------------- /tests/fixtures/project_s/docs/usage.rst: -------------------------------------------------------------------------------- 1 | ===== 2 | Usage 3 | ===== 4 | 5 | To use project_n in a project:: 6 | 7 | import project_s 8 | -------------------------------------------------------------------------------- /tests/fixtures/project_s/project_s.yml: -------------------------------------------------------------------------------- 1 | cookiecutter: 2 | full_name: full_n 3 | email: email_ 4 | github_username: github_u 5 | project_name: project_n 6 | project_slug: project_s 7 | project_short_description: project_sd 8 | pypi_username: pypi_u 9 | version: '1.0' 10 | use_pytest: y 11 | use_pypi_deployment_with_travis: y 12 | add_pyup_badge: y 13 | command_line_interface: Click 14 | create_author_file: y 15 | open_source_license: MIT license 16 | project_name: project_s 17 | -------------------------------------------------------------------------------- /tests/fixtures/project_s/project_s/__init__.py: -------------------------------------------------------------------------------- 1 | """Top-level package for project_n.""" 2 | 3 | __author__ = """full_n""" 4 | __email__ = 'email_' 5 | __version__ = '1.0' 6 | -------------------------------------------------------------------------------- /tests/fixtures/project_s/project_s/cli.py: -------------------------------------------------------------------------------- 1 | """Console script for project_s.""" 2 | import sys 3 | import click 4 | 5 | 6 | @click.command() 7 | def main(args=None): 8 | """Console script for project_s.""" 9 | click.echo("Replace this message by putting your code into " 10 | "project_s.cli.main") 11 | click.echo("See click documentation at https://click.palletsprojects.com/") 12 | return 0 13 | 14 | 15 | if __name__ == "__main__": 16 | sys.exit(main()) # pragma: no cover 17 | -------------------------------------------------------------------------------- /tests/fixtures/project_s/project_s/project_s.py: -------------------------------------------------------------------------------- 1 | """Main module.""" 2 | -------------------------------------------------------------------------------- /tests/fixtures/project_s/requirements_dev.txt: -------------------------------------------------------------------------------- 1 | pip==19.2.3 2 | bump2version==0.5.11 3 | wheel==0.33.6 4 | watchdog==0.9.0 5 | flake8==3.7.8 6 | tox==3.14.0 7 | coverage==4.5.4 8 | Sphinx==1.8.5 9 | twine==1.14.0 10 | Click==7.0 11 | pytest==4.6.5 12 | pytest-runner==5.1 -------------------------------------------------------------------------------- /tests/fixtures/project_s/setup.cfg: -------------------------------------------------------------------------------- 1 | [bumpversion] 2 | current_version = 1.0 3 | commit = True 4 | tag = True 5 | 6 | [bumpversion:file:setup.py] 7 | search = version='{current_version}' 8 | replace = version='{new_version}' 9 | 10 | [bumpversion:file:project_s/__init__.py] 11 | search = __version__ = '{current_version}' 12 | replace = __version__ = '{new_version}' 13 | 14 | [bdist_wheel] 15 | universal = 1 16 | 17 | [flake8] 18 | exclude = docs 19 | 20 | [aliases] 21 | # Define setup.py command aliases here 22 | test = pytest 23 | 24 | [tool:pytest] 25 | collect_ignore = ['setup.py'] 26 | -------------------------------------------------------------------------------- /tests/fixtures/project_s/setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | """The setup script.""" 4 | 5 | from setuptools import setup, find_packages 6 | 7 | with open('README.rst') as readme_file: 8 | readme = readme_file.read() 9 | 10 | with open('HISTORY.rst') as history_file: 11 | history = history_file.read() 12 | 13 | requirements = ['Click>=7.0', ] 14 | 15 | setup_requirements = ['pytest-runner', ] 16 | 17 | test_requirements = ['pytest>=3', ] 18 | 19 | setup( 20 | author="full_n", 21 | author_email='email_', 22 | python_requires='>=3.5', 23 | classifiers=[ 24 | 'Development Status :: 2 - Pre-Alpha', 25 | 'Intended Audience :: Developers', 26 | 'License :: OSI Approved :: MIT License', 27 | 'Natural Language :: English', 28 | 'Programming Language :: Python :: 3', 29 | 'Programming Language :: Python :: 3.5', 30 | 'Programming Language :: Python :: 3.6', 31 | 'Programming Language :: Python :: 3.7', 32 | 'Programming Language :: Python :: 3.8', 33 | ], 34 | description="project_sd", 35 | entry_points={ 36 | 'console_scripts': [ 37 | 'project_s=project_s.cli:main', 38 | ], 39 | }, 40 | install_requires=requirements, 41 | license="MIT license", 42 | long_description=readme + '\n\n' + history, 43 | include_package_data=True, 44 | keywords='project_s', 45 | name='project_s', 46 | packages=find_packages(include=['project_s', 'project_s.*']), 47 | setup_requires=setup_requirements, 48 | test_suite='tests', 49 | tests_require=test_requirements, 50 | url='https://github.com/github_u/project_s', 51 | version='1.0', 52 | zip_safe=False, 53 | ) 54 | -------------------------------------------------------------------------------- /tests/fixtures/project_s/tests/__init__.py: -------------------------------------------------------------------------------- 1 | """Unit test package for project_s.""" 2 | -------------------------------------------------------------------------------- /tests/fixtures/project_s/tests/test_project_s.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | """Tests for `project_s` package.""" 4 | 5 | import pytest 6 | 7 | from click.testing import CliRunner 8 | 9 | from project_s import project_s 10 | from project_s import cli 11 | 12 | 13 | @pytest.fixture 14 | def response(): 15 | """Sample pytest fixture. 16 | 17 | See more at: http://doc.pytest.org/en/latest/fixture.html 18 | """ 19 | # import requests 20 | # return requests.get('https://github.com/audreyr/cookiecutter-pypackage') 21 | 22 | 23 | def test_content(response): 24 | """Sample pytest test function with the pytest fixture as an argument.""" 25 | # from bs4 import BeautifulSoup 26 | # assert 'GitHub' in BeautifulSoup(response.content).title.string 27 | 28 | 29 | def test_command_line_interface(): 30 | """Test the CLI.""" 31 | runner = CliRunner() 32 | result = runner.invoke(cli.main) 33 | assert result.exit_code == 0 34 | assert 'project_s.cli.main' in result.output 35 | help_result = runner.invoke(cli.main, ['--help']) 36 | assert help_result.exit_code == 0 37 | assert '--help Show this message and exit.' in help_result.output 38 | -------------------------------------------------------------------------------- /tests/fixtures/project_s/tox.ini: -------------------------------------------------------------------------------- 1 | [tox] 2 | envlist = py35, py36, py37, py38, flake8 3 | 4 | [travis] 5 | python = 6 | 3.8: py38 7 | 3.7: py37 8 | 3.6: py36 9 | 3.5: py35 10 | 11 | [testenv:flake8] 12 | basepython = python 13 | deps = flake8 14 | commands = flake8 project_s tests 15 | 16 | [testenv] 17 | setenv = 18 | PYTHONPATH = {toxinidir} 19 | deps = 20 | -r{toxinidir}/requirements_dev.txt 21 | ; If you want to make tox run the tests with the same versions, create a 22 | ; requirements.txt with the pinned versions and uncomment the following line: 23 | ; -r{toxinidir}/requirements.txt 24 | commands = 25 | pip install -U pip 26 | pytest --basetemp={envtmpdir} 27 | -------------------------------------------------------------------------------- /tests/fixtures/project_templating/test-me/Makefile: -------------------------------------------------------------------------------- 1 | all: test 2 | 3 | test: lint 4 | bash test.sh 5 | 6 | install_test: 7 | pip install -r tests/requirements.txt 8 | 9 | git-diff-check: 10 | git diff --exit-code 11 | 12 | lint: 13 | bash lint.sh 14 | 15 | format: 16 | isort -y $(find test_me -name "*.py"|xargs echo) $(find tests -name "*.py"|xargs echo) 17 | black -l 79 test_me 18 | black -l 79 tests 19 | 20 | git-diff-check: 21 | git diff --exit-code 22 | -------------------------------------------------------------------------------- /tests/fixtures/project_templating/test-me/changelog.yml: -------------------------------------------------------------------------------- 1 | name: test-me 2 | organisation: 3 | releases: 4 | - changes: 5 | - action: first release 6 | details: 7 | - what a feat! 8 | version: 0.0.1 9 | date: 10 | -------------------------------------------------------------------------------- /tests/fixtures/project_templating/test-me/test-me.yml: -------------------------------------------------------------------------------- 1 | name: "test-me" 2 | organisation: "" 3 | author: "" 4 | contact: "" 5 | company: "" 6 | version: "0.0.1" 7 | current_version: "0.0.1" 8 | release: "0.0.0" 9 | copyright_year: 2020 10 | license: 11 | dependencies: [] 12 | description: "" 13 | lint_command: make install_test lint format git-diff-check 14 | -------------------------------------------------------------------------------- /tests/fixtures/project_yehua/.azure-pipelines-steps-macos.yml: -------------------------------------------------------------------------------- 1 | steps: 2 | - task: UsePythonVersion@0 3 | displayName: 'Use Python 3.x' 4 | - script: | 5 | python -m pip install --upgrade pip setuptools wheel 6 | test ! -f rnd_requirements.txt || pip install -r rnd_requirements.txt 7 | pip install -r requirements.txt 8 | pip install -r tests/requirements.txt 9 | displayName: 'Setup dependencies' 10 | - script: | 11 | make 12 | displayName: 'Run tests' 13 | -------------------------------------------------------------------------------- /tests/fixtures/project_yehua/.azure-pipelines-steps.yml: -------------------------------------------------------------------------------- 1 | steps: 2 | - task: UsePythonVersion@0 3 | displayName: 'Use Python 3.x' 4 | - script: | 5 | python -m pip install --upgrade pip setuptools wheel 6 | if exist rnd_requirements.txt pip install -r rnd_requirements.txt 7 | pip install -r requirements.txt 8 | pip install -r tests\requirements.txt 9 | displayName: 'Setup dependencies' 10 | - script: | 11 | test.bat 12 | displayName: 'Run tests' 13 | -------------------------------------------------------------------------------- /tests/fixtures/project_yehua/.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 | - name: Set up Python 12 | uses: actions/setup-python@v1 13 | with: 14 | python-version: '3.7' 15 | - name: check changes 16 | run: | 17 | pip install moban gitfs2 pypifs moban-jinja2-github moban-ansible 18 | moban 19 | git status 20 | git diff --exit-code 21 | - name: Auto-commit 22 | if: failure() 23 | uses: docker://cdssnc/auto-commit-github-action 24 | env: 25 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 26 | with: 27 | args: >- 28 | This is an auto-commit, updating project meta data, 29 | such as changelog.rst, contributors.rst 30 | -------------------------------------------------------------------------------- /tests/fixtures/project_yehua/.github/workflows/pythonpublish.yml: -------------------------------------------------------------------------------- 1 | name: Upload Python Package 2 | 3 | on: 4 | release: 5 | types: [created] 6 | 7 | jobs: 8 | deploy: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - uses: actions/checkout@v1 12 | - name: Set up Python 13 | uses: actions/setup-python@v1 14 | with: 15 | python-version: '3.x' 16 | - name: Install dependencies 17 | run: | 18 | python -m pip install --upgrade pip 19 | pip install setuptools wheel twine 20 | - name: Build and publish 21 | env: 22 | TWINE_USERNAME: ${{ secrets.PYPI_USERNAME }} 23 | TWINE_PASSWORD: ${{ secrets.PYPI_PASSWORD }} 24 | run: | 25 | python setup.py sdist bdist_wheel 26 | twine upload dist/* 27 | -------------------------------------------------------------------------------- /tests/fixtures/project_yehua/.gitignore: -------------------------------------------------------------------------------- 1 | # moban hashes 2 | .moban.hashes 3 | 4 | # Extra rules from https://github.com/github/gitignore/ 5 | # Python rules 6 | # Byte-compiled / optimized / DLL files 7 | __pycache__/ 8 | *.py[cod] 9 | *$py.class 10 | 11 | # C extensions 12 | *.so 13 | 14 | # Distribution / packaging 15 | .Python 16 | build/ 17 | develop-eggs/ 18 | dist/ 19 | downloads/ 20 | eggs/ 21 | .eggs/ 22 | lib/ 23 | lib64/ 24 | parts/ 25 | sdist/ 26 | var/ 27 | wheels/ 28 | share/python-wheels/ 29 | *.egg-info/ 30 | .installed.cfg 31 | *.egg 32 | MANIFEST 33 | 34 | # PyInstaller 35 | # Usually these files are written by a python script from a template 36 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 37 | *.manifest 38 | *.spec 39 | 40 | # Installer logs 41 | pip-log.txt 42 | pip-delete-this-directory.txt 43 | 44 | # Unit test / coverage reports 45 | htmlcov/ 46 | .tox/ 47 | .nox/ 48 | .coverage 49 | .coverage.* 50 | .cache 51 | nosetests.xml 52 | coverage.xml 53 | *.cover 54 | *.py,cover 55 | .hypothesis/ 56 | .pytest_cache/ 57 | cover/ 58 | 59 | # Translations 60 | *.mo 61 | *.pot 62 | 63 | # Django stuff: 64 | *.log 65 | local_settings.py 66 | db.sqlite3 67 | db.sqlite3-journal 68 | 69 | # Flask stuff: 70 | instance/ 71 | .webassets-cache 72 | 73 | # Scrapy stuff: 74 | .scrapy 75 | 76 | # Sphinx documentation 77 | docs/_build/ 78 | 79 | # PyBuilder 80 | .pybuilder/ 81 | target/ 82 | 83 | # Jupyter Notebook 84 | .ipynb_checkpoints 85 | 86 | # IPython 87 | profile_default/ 88 | ipython_config.py 89 | 90 | # pyenv 91 | # For a library or package, you might want to ignore these files since the code is 92 | # intended to run in multiple environments; otherwise, check them in: 93 | # .python-version 94 | 95 | # pipenv 96 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 97 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 98 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 99 | # install all needed dependencies. 100 | #Pipfile.lock 101 | 102 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 103 | __pypackages__/ 104 | 105 | # Celery stuff 106 | celerybeat-schedule 107 | celerybeat.pid 108 | 109 | # SageMath parsed files 110 | *.sage.py 111 | 112 | # Environments 113 | .env 114 | .venv 115 | env/ 116 | venv/ 117 | ENV/ 118 | env.bak/ 119 | venv.bak/ 120 | 121 | # Spyder project settings 122 | .spyderproject 123 | .spyproject 124 | 125 | # Rope project settings 126 | .ropeproject 127 | 128 | # mkdocs documentation 129 | /site 130 | 131 | # mypy 132 | .mypy_cache/ 133 | .dmypy.json 134 | dmypy.json 135 | 136 | # Pyre type checker 137 | .pyre/ 138 | 139 | # pytype static type analyzer 140 | .pytype/ 141 | 142 | # Cython debug symbols 143 | cython_debug/ 144 | 145 | # VirtualEnv rules 146 | # Virtualenv 147 | # http://iamzed.com/2009/05/07/a-primer-on-virtualenv/ 148 | .Python 149 | [Bb]in 150 | [Ii]nclude 151 | [Ll]ib 152 | [Ll]ib64 153 | [Ll]ocal 154 | [Ss]cripts 155 | pyvenv.cfg 156 | .venv 157 | pip-selfcheck.json 158 | 159 | # Linux rules 160 | *~ 161 | 162 | # temporary files which can be created if a process still has a handle open of a deleted file 163 | .fuse_hidden* 164 | 165 | # KDE directory preferences 166 | .directory 167 | 168 | # Linux trash folder which might appear on any partition or disk 169 | .Trash-* 170 | 171 | # .nfs files are created when an open file is removed but is still being accessed 172 | .nfs* 173 | 174 | # Windows rules 175 | # Windows thumbnail cache files 176 | Thumbs.db 177 | Thumbs.db:encryptable 178 | ehthumbs.db 179 | ehthumbs_vista.db 180 | 181 | # Dump file 182 | *.stackdump 183 | 184 | # Folder config file 185 | [Dd]esktop.ini 186 | 187 | # Recycle Bin used on file shares 188 | $RECYCLE.BIN/ 189 | 190 | # Windows Installer files 191 | *.cab 192 | *.msi 193 | *.msix 194 | *.msm 195 | *.msp 196 | 197 | # Windows shortcuts 198 | *.lnk 199 | 200 | # macOS rules 201 | # General 202 | .DS_Store 203 | .AppleDouble 204 | .LSOverride 205 | 206 | # Icon must end with two \r 207 | Icon 208 | 209 | 210 | # Thumbnails 211 | ._* 212 | 213 | # Files that might appear in the root of a volume 214 | .DocumentRevisions-V100 215 | .fseventsd 216 | .Spotlight-V100 217 | .TemporaryItems 218 | .Trashes 219 | .VolumeIcon.icns 220 | .com.apple.timemachine.donotpresent 221 | 222 | # Directories potentially created on remote AFP share 223 | .AppleDB 224 | .AppleDesktop 225 | Network Trash Folder 226 | Temporary Items 227 | .apdisk 228 | 229 | # Emacs rules 230 | # -*- mode: gitignore; -*- 231 | *~ 232 | \#*\# 233 | /.emacs.desktop 234 | /.emacs.desktop.lock 235 | *.elc 236 | auto-save-list 237 | tramp 238 | .\#* 239 | 240 | # Org-mode 241 | .org-id-locations 242 | *_archive 243 | 244 | # flymake-mode 245 | *_flymake.* 246 | 247 | # eshell files 248 | /eshell/history 249 | /eshell/lastdir 250 | 251 | # elpa packages 252 | /elpa/ 253 | 254 | # reftex files 255 | *.rel 256 | 257 | # AUCTeX auto folder 258 | /auto/ 259 | 260 | # cask packages 261 | .cask/ 262 | dist/ 263 | 264 | # Flycheck 265 | flycheck_*.el 266 | 267 | # server auth directory 268 | /server/ 269 | 270 | # projectiles files 271 | .projectile 272 | 273 | # directory configuration 274 | .dir-locals.el 275 | 276 | # network security 277 | /network-security.data 278 | 279 | 280 | # Vim rules 281 | # Swap 282 | [._]*.s[a-v][a-z] 283 | !*.svg # comment out if you don't need vector files 284 | [._]*.sw[a-p] 285 | [._]s[a-rt-v][a-z] 286 | [._]ss[a-gi-z] 287 | [._]sw[a-p] 288 | 289 | # Session 290 | Session.vim 291 | Sessionx.vim 292 | 293 | # Temporary 294 | .netrwhist 295 | *~ 296 | # Auto-generated tag files 297 | tags 298 | # Persistent undo 299 | [._]*.un~ 300 | 301 | # JetBrains rules 302 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider 303 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 304 | 305 | # User-specific stuff 306 | .idea/**/workspace.xml 307 | .idea/**/tasks.xml 308 | .idea/**/usage.statistics.xml 309 | .idea/**/dictionaries 310 | .idea/**/shelf 311 | 312 | # Generated files 313 | .idea/**/contentModel.xml 314 | 315 | # Sensitive or high-churn files 316 | .idea/**/dataSources/ 317 | .idea/**/dataSources.ids 318 | .idea/**/dataSources.local.xml 319 | .idea/**/sqlDataSources.xml 320 | .idea/**/dynamic.xml 321 | .idea/**/uiDesigner.xml 322 | .idea/**/dbnavigator.xml 323 | 324 | # Gradle 325 | .idea/**/gradle.xml 326 | .idea/**/libraries 327 | 328 | # Gradle and Maven with auto-import 329 | # When using Gradle or Maven with auto-import, you should exclude module files, 330 | # since they will be recreated, and may cause churn. Uncomment if using 331 | # auto-import. 332 | # .idea/artifacts 333 | # .idea/compiler.xml 334 | # .idea/jarRepositories.xml 335 | # .idea/modules.xml 336 | # .idea/*.iml 337 | # .idea/modules 338 | # *.iml 339 | # *.ipr 340 | 341 | # CMake 342 | cmake-build-*/ 343 | 344 | # Mongo Explorer plugin 345 | .idea/**/mongoSettings.xml 346 | 347 | # File-based project format 348 | *.iws 349 | 350 | # IntelliJ 351 | out/ 352 | 353 | # mpeltonen/sbt-idea plugin 354 | .idea_modules/ 355 | 356 | # JIRA plugin 357 | atlassian-ide-plugin.xml 358 | 359 | # Cursive Clojure plugin 360 | .idea/replstate.xml 361 | 362 | # Crashlytics plugin (for Android Studio and IntelliJ) 363 | com_crashlytics_export_strings.xml 364 | crashlytics.properties 365 | crashlytics-build.properties 366 | fabric.properties 367 | 368 | # Editor-based Rest Client 369 | .idea/httpRequests 370 | 371 | # Android studio 3.1+ serialized cache file 372 | .idea/caches/build_file_checksums.ser 373 | 374 | # SublimeText rules 375 | # Cache files for Sublime Text 376 | *.tmlanguage.cache 377 | *.tmPreferences.cache 378 | *.stTheme.cache 379 | 380 | # Workspace files are user-specific 381 | *.sublime-workspace 382 | 383 | # Project files should be checked into the repository, unless a significant 384 | # proportion of contributors will probably not be using Sublime Text 385 | # *.sublime-project 386 | 387 | # SFTP configuration file 388 | sftp-config.json 389 | sftp-config-alt*.json 390 | 391 | # Package control specific files 392 | Package Control.last-run 393 | Package Control.ca-list 394 | Package Control.ca-bundle 395 | Package Control.system-ca-bundle 396 | Package Control.cache/ 397 | Package Control.ca-certs/ 398 | Package Control.merged-ca-bundle 399 | Package Control.user-ca-bundle 400 | oscrypto-ca-bundle.crt 401 | bh_unicode_properties.cache 402 | 403 | # Sublime-github package stores a github token in this file 404 | # https://packagecontrol.io/packages/sublime-github 405 | GitHub.sublime-settings 406 | 407 | # KDevelop4 rules 408 | *.kdev4 409 | .kdev4/ 410 | 411 | # Kate rules 412 | # Swap Files # 413 | .*.kate-swp 414 | .swp.* 415 | 416 | # TextMate rules 417 | *.tmproj 418 | *.tmproject 419 | tmtags 420 | 421 | # VisualStudioCode rules 422 | .vscode/* 423 | !.vscode/settings.json 424 | !.vscode/tasks.json 425 | !.vscode/launch.json 426 | !.vscode/extensions.json 427 | *.code-workspace 428 | 429 | # Local History for Visual Studio Code 430 | .history/ 431 | 432 | # Xcode rules 433 | # Xcode 434 | # 435 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 436 | 437 | ## User settings 438 | xcuserdata/ 439 | 440 | ## compatibility with Xcode 8 and earlier (ignoring not required starting Xcode 9) 441 | *.xcscmblueprint 442 | *.xccheckout 443 | 444 | ## compatibility with Xcode 3 and earlier (ignoring not required starting Xcode 4) 445 | build/ 446 | DerivedData/ 447 | *.moved-aside 448 | *.pbxuser 449 | !default.pbxuser 450 | *.mode1v3 451 | !default.mode1v3 452 | *.mode2v3 453 | !default.mode2v3 454 | *.perspectivev3 455 | !default.perspectivev3 456 | 457 | ## Gcc Patch 458 | /*.gcno 459 | 460 | # Eclipse rules 461 | .metadata 462 | bin/ 463 | tmp/ 464 | *.tmp 465 | *.bak 466 | *.swp 467 | *~.nib 468 | local.properties 469 | .settings/ 470 | .loadpath 471 | .recommenders 472 | 473 | # External tool builders 474 | .externalToolBuilders/ 475 | 476 | # Locally stored "Eclipse launch configurations" 477 | *.launch 478 | 479 | # PyDev specific (Python IDE for Eclipse) 480 | *.pydevproject 481 | 482 | # CDT-specific (C/C++ Development Tooling) 483 | .cproject 484 | 485 | # CDT- autotools 486 | .autotools 487 | 488 | # Java annotation processor (APT) 489 | .factorypath 490 | 491 | # PDT-specific (PHP Development Tools) 492 | .buildpath 493 | 494 | # sbteclipse plugin 495 | .target 496 | 497 | # Tern plugin 498 | .tern-project 499 | 500 | # TeXlipse plugin 501 | .texlipse 502 | 503 | # STS (Spring Tool Suite) 504 | .springBeans 505 | 506 | # Code Recommenders 507 | .recommenders/ 508 | 509 | # Annotation Processing 510 | .apt_generated/ 511 | .apt_generated_test/ 512 | 513 | # Scala IDE specific (Scala & Java development for Eclipse) 514 | .cache-main 515 | .scala_dependencies 516 | .worksheet 517 | 518 | # Uncomment this line if you wish to ignore the project description file. 519 | # Typically, this file would be tracked if it contains build/dependency configurations: 520 | #.project 521 | 522 | # TortoiseGit rules 523 | # Project-level settings 524 | /.tgitconfig 525 | 526 | # Tags rules 527 | # Ignore tags created by etags, ctags, gtags (GNU global) and cscope 528 | TAGS 529 | .TAGS 530 | !TAGS/ 531 | tags 532 | .tags 533 | !tags/ 534 | gtags.files 535 | GTAGS 536 | GRTAGS 537 | GPATH 538 | GSYMS 539 | cscope.files 540 | cscope.out 541 | cscope.in.out 542 | cscope.po.out 543 | -------------------------------------------------------------------------------- /tests/fixtures/project_yehua/.isort.cfg: -------------------------------------------------------------------------------- 1 | [settings] 2 | line_length=79 3 | # Ignore generated files 4 | skip=setup.py, /__init__.py 5 | known_third_party=mock, nose 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 -------------------------------------------------------------------------------- /tests/fixtures/project_yehua/.moban.d/CUSTOM_README.rst.jj2: -------------------------------------------------------------------------------- 1 | {% extends 'README.rst.jj2' %} 2 | 3 | {%block documentation_link%} 4 | .. image:: https://dev.azure.com/{{organisation}}/{{name}}/_apis/build/status/{{organisation}}.{{name}}?branchName=master 5 | :target: https://dev.azure.com/{{organisation}}/{{name}}/_build/latest?definitionId=2&branchName=master 6 | {%endblock%} 7 | -------------------------------------------------------------------------------- /tests/fixtures/project_yehua/.moban.d/custom_setup.py.jj2: -------------------------------------------------------------------------------- 1 | {% extends "setup.py.jj2" %} 2 | -------------------------------------------------------------------------------- /tests/fixtures/project_yehua/.moban.d/tests/custom_requirements.txt.jj2: -------------------------------------------------------------------------------- 1 | {% extends "tests/requirements.txt.jj2" %} 2 | 3 | {%block extras %} 4 | moban 5 | black;python_version>="3.6" 6 | isort;python_version>="3.6" 7 | {%endblock%} 8 | -------------------------------------------------------------------------------- /tests/fixtures/project_yehua/.moban.yml: -------------------------------------------------------------------------------- 1 | configuration: 2 | template_dir: 3 | - "pypi://pypi-mobans-pkg/resources/templates" 4 | - "pypi://pypi-mobans-pkg/resources/statics" 5 | - ".moban.d" 6 | configuration: project_yehua.yml 7 | targets: 8 | - README.rst: CUSTOM_README.rst.jj2 9 | - setup.py: custom_setup.py.jj2 10 | - requirements.txt: requirements.txt.jj2 11 | - "tests/requirements.txt": "tests/custom_requirements.txt.jj2" 12 | - "docs/source/conf.py": "docs/conf.py_t" 13 | - test.sh: test.script.jj2 14 | - test.bat: test.script.jj2 15 | - "project_yehua/_version.py": "_version.py.jj2" 16 | - .gitignore: gitignore.jj2 17 | - .travis.yml: travis.yml.jj2 18 | - Pipfile: Pipfile.jj2 19 | - output: CHANGELOG.rst 20 | configuration: changelog.yml 21 | template: CHANGELOG.rst.jj2 22 | - lint.sh: lint.script.jj2 23 | - LICENSE: "mit_license.jj2" 24 | - MANIFEST.in: MANIFEST.in.jj2 25 | - ".github/workflows/pythonpublish.yml": "pythonpublish.yml" 26 | - ".github/workflows/moban-update.yml": "moban-update.yml" 27 | - "azure-pipelines.yml": "azure/azure-pipelines.yml" 28 | - ".azure-pipelines-steps-macos.yml": "azure/pipelines-steps-macos.yml" 29 | - ".azure-pipelines-steps.yml": "azure/pipelines-steps.yml" 30 | - Makefile: Makefile.jj2 31 | - format.sh: format.sh.jj2 32 | - .isort.cfg: isort.cfg.jj2 33 | - CONTRIBUTORS.rst: CONTRIBUTORS.rst.jj2 34 | -------------------------------------------------------------------------------- /tests/fixtures/project_yehua/.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | dist: xenial 3 | language: python 4 | notifications: 5 | email: false 6 | python: 7 | - &pypy2 pypy2.7-6.0 8 | - &pypy3 pypy3.5-6.0 9 | - 3.8 10 | - 3.7 11 | - 3.6 12 | - 3.5 13 | - 2.7 14 | 15 | stages: 16 | - lint 17 | - moban 18 | - test 19 | 20 | 21 | .lint: &lint 22 | git: 23 | submodules: false 24 | python: 3.6 25 | env: 26 | - MINREQ=0 27 | stage: lint 28 | script: make install_test lint format git-diff-check 29 | 30 | .moban: &moban 31 | python: 3.6 32 | env: 33 | - MINREQ=0 34 | stage: moban 35 | install: pip install moban gitfs2 pypifs moban-jinja2-github moban-ansible 36 | script: 37 | - moban 38 | - git diff --exit-code 39 | 40 | jobs: 41 | include: 42 | - *moban 43 | - *lint 44 | 45 | stage: test 46 | 47 | before_install: 48 | - if [[ -f min_requirements.txt && "$MINREQ" -eq 1 ]]; then 49 | mv min_requirements.txt requirements.txt ; 50 | fi 51 | - test ! -f rnd_requirements.txt || 52 | pip install --no-deps -r rnd_requirements.txt 53 | - test ! -f rnd_requirements.txt || pip install -r rnd_requirements.txt ; 54 | - pip install -r tests/requirements.txt 55 | script: 56 | - make test 57 | after_success: 58 | codecov 59 | -------------------------------------------------------------------------------- /tests/fixtures/project_yehua/CHANGELOG.rst: -------------------------------------------------------------------------------- 1 | Change log 2 | ================================================================================ 3 | 4 | 0.0.1 - None 5 | -------------------------------------------------------------------------------- 6 | 7 | **first release** 8 | 9 | #. what a feat! 10 | -------------------------------------------------------------------------------- /tests/fixtures/project_yehua/CONTRIBUTORS.rst: -------------------------------------------------------------------------------- 1 | 2 | 0 contributors 3 | ================================================================================ 4 | 5 | In alphabetical order: 6 | -------------------------------------------------------------------------------- /tests/fixtures/project_yehua/LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2020 copyright 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 | -------------------------------------------------------------------------------- /tests/fixtures/project_yehua/MANIFEST.in: -------------------------------------------------------------------------------- 1 | include README.rst 2 | include LICENSE 3 | include CHANGELOG.rst 4 | recursive-include tests * 5 | recursive-include docs * 6 | -------------------------------------------------------------------------------- /tests/fixtures/project_yehua/Makefile: -------------------------------------------------------------------------------- 1 | all: test 2 | 3 | test: lint 4 | bash test.sh 5 | 6 | install_test: 7 | pip install -r tests/requirements.txt 8 | 9 | git-diff-check: 10 | git diff --exit-code 11 | 12 | lint: 13 | bash lint.sh 14 | 15 | format: 16 | bash format.sh 17 | 18 | git-diff-check: 19 | git diff --exit-code 20 | -------------------------------------------------------------------------------- /tests/fixtures/project_yehua/Pipfile: -------------------------------------------------------------------------------- 1 | [[source]] 2 | url = 'https://pypi.python.org/simple' 3 | verify_ssl = true 4 | name = 'pypi' 5 | 6 | [requires] 7 | python_version= '3.6' 8 | 9 | [packages] 10 | 11 | [dev-packages] 12 | nose = "*" 13 | mock = "*" 14 | codecov = "*" 15 | coverage = "*" 16 | flake8 = "*" 17 | -------------------------------------------------------------------------------- /tests/fixtures/project_yehua/README.rst: -------------------------------------------------------------------------------- 1 | ================================================================================ 2 | project_yehua 3 | ================================================================================ 4 | 5 | .. image:: https://api.travis-ci.org/github/project_yehua.svg 6 | :target: http://travis-ci.org/github/project_yehua 7 | 8 | .. image:: https://codecov.io/github/github/project_yehua/coverage.png 9 | :target: https://codecov.io/github/github/project_yehua 10 | .. image:: https://img.shields.io/github/stars/github/project_yehua.svg?style=social&maxAge=3600&label=Star 11 | :target: https://github.com/github/project_yehua/stargazers 12 | 13 | .. image:: https://dev.azure.com/github/project_yehua/_apis/build/status/github.project_yehua?branchName=master 14 | :target: https://dev.azure.com/github/project_yehua/_build/latest?definitionId=2&branchName=master 15 | 16 | 17 | Installation 18 | ================================================================================ 19 | 20 | You can get it: 21 | 22 | .. code-block:: bash 23 | 24 | $ git clone https://github.com/github/project_yehua.git 25 | $ cd project_yehua 26 | $ python setup.py install 27 | -------------------------------------------------------------------------------- /tests/fixtures/project_yehua/azure-pipelines.yml: -------------------------------------------------------------------------------- 1 | # Starter pipeline 2 | # Add steps that build, run tests, deploy, and more: 3 | # https://aka.ms/yaml 4 | jobs: 5 | - job: 'Windows' 6 | pool: 7 | vmImage: vs2017-win2016 8 | steps: 9 | - template: '.azure-pipelines-steps.yml' 10 | - job: 'MacOS' 11 | pool: 12 | vmImage: macOS-10.13 13 | steps: 14 | - template: '.azure-pipelines-steps-macos.yml' 15 | -------------------------------------------------------------------------------- /tests/fixtures/project_yehua/changelog.yml: -------------------------------------------------------------------------------- 1 | name: project_yehua 2 | organisation: github 3 | releases: 4 | - changes: 5 | - action: first release 6 | details: 7 | - what a feat! 8 | version: 0.0.1 9 | date: 10 | -------------------------------------------------------------------------------- /tests/fixtures/project_yehua/docs/source/conf.py: -------------------------------------------------------------------------------- 1 | # Configuration file for the Sphinx documentation builder. 2 | # 3 | # This file only contains a selection of the most common options. For a full 4 | # list see the documentation: 5 | # https://www.sphinx-doc.org/en/master/usage/configuration.html 6 | 7 | # -- Path setup -------------------------------------------------------------- 8 | 9 | # If extensions (or modules to document with autodoc) are in another directory, 10 | # add these directories to sys.path here. If the directory is relative to the 11 | # documentation root, use os.path.abspath to make it absolute, like shown here. 12 | # 13 | # import os 14 | # import sys 15 | # sys.path.insert(0, os.path.abspath('.')) 16 | 17 | # -- Project information ----------------------------------------------------- 18 | 19 | project = '' 20 | copyright = '' 21 | author = 'author' 22 | # The short X.Y version 23 | version = '0.0.1' 24 | # The full version, including alpha/beta/rc tags 25 | release = '0.0.0' 26 | 27 | # -- General configuration --------------------------------------------------- 28 | 29 | # Add any Sphinx extension module names here, as strings. They can be 30 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom 31 | # ones. 32 | extensions = [] 33 | 34 | # Add any paths that contain templates here, relative to this directory. 35 | templates_path = ['templates'] 36 | 37 | # The suffix(es) of source filenames. 38 | # You can specify multiple suffix as a list of string: 39 | # 40 | # source_suffix = ['.rst', '.md'] 41 | source_suffix = '' 42 | 43 | # The master toctree document. 44 | master_doc = '' 45 | 46 | # List of patterns, relative to source directory, that match files and 47 | # directories to ignore when looking for source files. 48 | # This pattern also affects html_static_path and html_extra_path. 49 | exclude_patterns = [] 50 | 51 | 52 | # -- Options for HTML output ------------------------------------------------- 53 | 54 | # The theme to use for HTML and HTML Help pages. See the documentation for 55 | # a list of builtin themes. 56 | # 57 | html_theme = 'alabaster' 58 | 59 | # Add any paths that contain custom static files (such as style sheets) here, 60 | # relative to this directory. They are copied after the builtin static files, 61 | # so a file named "default.css" will overwrite the builtin "default.css". 62 | html_static_path = ['static'] 63 | -------------------------------------------------------------------------------- /tests/fixtures/project_yehua/format.sh: -------------------------------------------------------------------------------- 1 | isort $(find project_yehua -name "*.py"|xargs echo) $(find tests -name "*.py"|xargs echo) 2 | black -l 79 project_yehua 3 | black -l 79 tests 4 | -------------------------------------------------------------------------------- /tests/fixtures/project_yehua/lint.sh: -------------------------------------------------------------------------------- 1 | pip install flake8 2 | flake8 . --exclude=.moban.d,docs,setup.py --builtins=unicode,xrange,long 3 | python setup.py checkdocs 4 | -------------------------------------------------------------------------------- /tests/fixtures/project_yehua/project_yehua.yml: -------------------------------------------------------------------------------- 1 | name: "project_yehua" 2 | organisation: "github" 3 | author: "author" 4 | contact: "contact" 5 | company: "copyright" 6 | version: "0.0.1" 7 | current_version: "0.0.1" 8 | release: "0.0.0" 9 | copyright_year: 2020 10 | command_line_interface: "cli" 11 | entry_point: "project_yehua.main:main" 12 | license: mit 13 | dependencies: [] 14 | description: "description" 15 | lint_command: make install_test lint format git-diff-check 16 | -------------------------------------------------------------------------------- /tests/fixtures/project_yehua/project_yehua/__init__.py: -------------------------------------------------------------------------------- 1 | # flake8: noqa 2 | from project_yehua._version import __author__, __version__ 3 | -------------------------------------------------------------------------------- /tests/fixtures/project_yehua/project_yehua/_version.py: -------------------------------------------------------------------------------- 1 | __version__ = "0.0.1" 2 | __author__ = "author" 3 | -------------------------------------------------------------------------------- /tests/fixtures/project_yehua/requirements.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/moremoban/yehua/e90ac103ec28e1101fd845796c02083d52ddf43e/tests/fixtures/project_yehua/requirements.txt -------------------------------------------------------------------------------- /tests/fixtures/project_yehua/setup.cfg: -------------------------------------------------------------------------------- 1 | [bdist_wheel] 2 | universal = 1 3 | -------------------------------------------------------------------------------- /tests/fixtures/project_yehua/setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | """ 4 | Template by pypi-mobans 5 | """ 6 | 7 | import os 8 | import sys 9 | import codecs 10 | import locale 11 | import platform 12 | from shutil import rmtree 13 | 14 | from setuptools import Command, setup, find_packages 15 | 16 | PY2 = sys.version_info[0] == 2 17 | PY26 = PY2 and sys.version_info[1] < 7 18 | PY33 = sys.version_info < (3, 4) 19 | 20 | # Work around mbcs bug in distutils. 21 | # http://bugs.python.org/issue10945 22 | # This work around is only if a project supports Python < 3.4 23 | 24 | # Work around for locale not being set 25 | try: 26 | lc = locale.getlocale() 27 | pf = platform.system() 28 | if pf != "Windows" and lc == (None, None): 29 | locale.setlocale(locale.LC_ALL, "C.UTF-8") 30 | except (ValueError, UnicodeError, locale.Error): 31 | locale.setlocale(locale.LC_ALL, "en_US.UTF-8") 32 | 33 | NAME = "project_yehua" 34 | AUTHOR = "author" 35 | VERSION = "0.0.1" 36 | EMAIL = "contact" 37 | LICENSE = "mit" 38 | ENTRY_POINTS = { 39 | "console_scripts": [ 40 | "cli = project_yehua.main:main" 41 | ], 42 | } 43 | DESCRIPTION = ( 44 | "description" 45 | ) 46 | URL = "https://github.com/github/project_yehua" 47 | DOWNLOAD_URL = "%s/archive/0.0.0.tar.gz" % URL 48 | FILES = ["README.rst", "CHANGELOG.rst"] 49 | KEYWORDS = [ 50 | "python", 51 | ] 52 | 53 | CLASSIFIERS = [ 54 | "Topic :: Software Development :: Libraries", 55 | "Programming Language :: Python", 56 | "Intended Audience :: Developers", 57 | "Programming Language :: Python :: 2.6", 58 | "Programming Language :: Python :: 2.7", 59 | "Programming Language :: Python :: 3.3", 60 | "Programming Language :: Python :: 3.4", 61 | "Programming Language :: Python :: 3.5", 62 | "Programming Language :: Python :: 3.6", 63 | "Programming Language :: Python :: 3.7", 64 | "Programming Language :: Python :: 3.8", 65 | 66 | ] 67 | 68 | 69 | INSTALL_REQUIRES = [ 70 | ] 71 | SETUP_COMMANDS = {} 72 | 73 | PACKAGES = find_packages(exclude=["ez_setup", "examples", "tests", "tests.*"]) 74 | EXTRAS_REQUIRE = {} 75 | # You do not need to read beyond this line 76 | PUBLISH_COMMAND = "{0} setup.py sdist bdist_wheel upload -r pypi".format(sys.executable) 77 | GS_COMMAND = ("gs project_yehua v0.0.0 " + 78 | "Find 0.0.0 in changelog for more details") 79 | NO_GS_MESSAGE = ("Automatic github release is disabled. " + 80 | "Please install gease to enable it.") 81 | UPLOAD_FAILED_MSG = ( 82 | 'Upload failed. please run "%s" yourself.' % PUBLISH_COMMAND) 83 | HERE = os.path.abspath(os.path.dirname(__file__)) 84 | 85 | 86 | class PublishCommand(Command): 87 | """Support setup.py upload.""" 88 | 89 | description = "Build and publish the package on github and pypi" 90 | user_options = [] 91 | 92 | @staticmethod 93 | def status(s): 94 | """Prints things in bold.""" 95 | print("\033[1m{0}\033[0m".format(s)) 96 | 97 | def initialize_options(self): 98 | pass 99 | 100 | def finalize_options(self): 101 | pass 102 | 103 | def run(self): 104 | try: 105 | self.status("Removing previous builds...") 106 | rmtree(os.path.join(HERE, "dist")) 107 | rmtree(os.path.join(HERE, "build")) 108 | rmtree(os.path.join(HERE, "project_yehua.egg-info")) 109 | except OSError: 110 | pass 111 | 112 | self.status("Building Source and Wheel (universal) distribution...") 113 | run_status = True 114 | if has_gease(): 115 | run_status = os.system(GS_COMMAND) == 0 116 | else: 117 | self.status(NO_GS_MESSAGE) 118 | if run_status: 119 | if os.system(PUBLISH_COMMAND) != 0: 120 | self.status(UPLOAD_FAILED_MSG) 121 | 122 | sys.exit() 123 | 124 | 125 | SETUP_COMMANDS.update({ 126 | "publish": PublishCommand 127 | }) 128 | 129 | 130 | def has_gease(): 131 | """ 132 | test if github release command is installed 133 | 134 | visit http://github.com/moremoban/gease for more info 135 | """ 136 | try: 137 | import gease # noqa 138 | return True 139 | except ImportError: 140 | return False 141 | 142 | 143 | def read_files(*files): 144 | """Read files into setup""" 145 | text = "" 146 | for single_file in files: 147 | content = read(single_file) 148 | text = text + content + "\n" 149 | return text 150 | 151 | 152 | def read(afile): 153 | """Read a file into setup""" 154 | the_relative_file = os.path.join(HERE, afile) 155 | with codecs.open(the_relative_file, "r", "utf-8") as opened_file: 156 | content = filter_out_test_code(opened_file) 157 | content = "".join(list(content)) 158 | return content 159 | 160 | 161 | def filter_out_test_code(file_handle): 162 | found_test_code = False 163 | for line in file_handle.readlines(): 164 | if line.startswith(".. testcode:"): 165 | found_test_code = True 166 | continue 167 | if found_test_code is True: 168 | if line.startswith(" "): 169 | continue 170 | else: 171 | empty_line = line.strip() 172 | if len(empty_line) == 0: 173 | continue 174 | else: 175 | found_test_code = False 176 | yield line 177 | else: 178 | for keyword in ["|version|", "|today|"]: 179 | if keyword in line: 180 | break 181 | else: 182 | yield line 183 | 184 | 185 | if __name__ == "__main__": 186 | setup( 187 | test_suite="tests", 188 | name=NAME, 189 | author=AUTHOR, 190 | version=VERSION, 191 | author_email=EMAIL, 192 | description=DESCRIPTION, 193 | url=URL, 194 | download_url=DOWNLOAD_URL, 195 | long_description=read_files(*FILES), 196 | license=LICENSE, 197 | keywords=KEYWORDS, 198 | extras_require=EXTRAS_REQUIRE, 199 | tests_require=["nose"], 200 | install_requires=INSTALL_REQUIRES, 201 | packages=PACKAGES, 202 | include_package_data=True, 203 | zip_safe=False, 204 | entry_points=ENTRY_POINTS, 205 | classifiers=CLASSIFIERS, 206 | cmdclass=SETUP_COMMANDS 207 | ) 208 | -------------------------------------------------------------------------------- /tests/fixtures/project_yehua/test.bat: -------------------------------------------------------------------------------- 1 | pip freeze 2 | nosetests --with-coverage --cover-package project_yehua --cover-package tests tests docs/source project_yehua 3 | -------------------------------------------------------------------------------- /tests/fixtures/project_yehua/test.sh: -------------------------------------------------------------------------------- 1 | pip freeze 2 | nosetests --with-coverage --cover-package project_yehua --cover-package tests tests docs/source project_yehua 3 | -------------------------------------------------------------------------------- /tests/fixtures/project_yehua/tests/requirements.txt: -------------------------------------------------------------------------------- 1 | nose 2 | mock;python_version<"3" 3 | codecov 4 | coverage 5 | flake8 6 | black 7 | isort 8 | collective.checkdocs 9 | pygments 10 | moban 11 | moban_jinja2_github 12 | moban 13 | black;python_version>="3.6" 14 | isort;python_version>="3.6" 15 | -------------------------------------------------------------------------------- /tests/requirements.txt: -------------------------------------------------------------------------------- 1 | nose 2 | mock;python_version<"3" 3 | codecov 4 | coverage 5 | flake8 6 | black 7 | isort 8 | collective.checkdocs 9 | pygments 10 | moban 11 | moban_jinja2_github 12 | pytest 13 | pytest-cov 14 | cookiecutter 15 | gitfs2 16 | pypifs 17 | pypi-mobans-pkg>=0.1.4 18 | -------------------------------------------------------------------------------- /tests/test_cookie_cutter.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | import shutil 4 | 5 | from mock import patch 6 | from nose.tools import eq_ 7 | from moban.externals.file_system import is_dir, url_join, read_unicode 8 | 9 | import fs 10 | from yehua.main import main 11 | 12 | 13 | @patch("yehua.cookiecutter.get_user_inputs") 14 | def test_local_cookie_cutter_package(fake_inputs): 15 | project_name = "my_test" 16 | file_name = "yehua_test" 17 | fake_inputs.return_value = { 18 | "directory_name": project_name, 19 | "file_name": file_name, 20 | "greeting_recipient": "yehua", 21 | } 22 | 23 | path = os.path.join("docs", "source", "hello_cookie_cutter") 24 | with patch.object(sys, "argv", ["yh", path]): 25 | main() 26 | 27 | with open(os.path.join(project_name, f"{file_name}.py")) as f: 28 | content = f.read() 29 | 30 | assert content == 'print("Hello, yehua!")\n' 31 | os.unlink(os.path.join(project_name, f"{file_name}.py")) 32 | os.unlink(os.path.join(project_name, f"{project_name}.yml")) 33 | os.unlink(os.path.join(project_name, ".moban.yml")) 34 | os.unlink(os.path.join(project_name, ".moban.hashes")) 35 | shutil.rmtree(project_name) 36 | 37 | 38 | @patch("yehua.cookiecutter.get_user_inputs") 39 | def test_github_package(fake_inputs): 40 | project_name = "git_package_test" 41 | file_name = "yehua_test" 42 | fake_inputs.return_value = { 43 | "directory_name": project_name, 44 | "file_name": file_name, 45 | "greeting_recipient": "yehua", 46 | } 47 | 48 | path = "git://github.com/moremoban/cookiecutter-helloworld.git" 49 | with patch.object(sys, "argv", ["yh", path]): 50 | main() 51 | 52 | with open(os.path.join(project_name, f"{file_name}.py")) as f: 53 | content = f.read() 54 | 55 | assert content == 'print("Hello, yehua!")\n' 56 | os.unlink(os.path.join(project_name, f"{file_name}.py")) 57 | os.unlink(os.path.join(project_name, f"{project_name}.yml")) 58 | os.unlink(os.path.join(project_name, ".moban.yml")) 59 | os.unlink(os.path.join(project_name, ".moban.hashes")) 60 | shutil.rmtree(project_name) 61 | 62 | 63 | @patch("yehua.cookiecutter.get_user_inputs") 64 | def test_reference_pypi_package(fake_inputs): 65 | project_name = "project_s" 66 | fake_inputs.return_value = { 67 | "full_name": "full_n", 68 | "email": "email_", 69 | "github_username": "github_u", 70 | "project_name": "project_n", 71 | "project_slug": "project_s", 72 | "project_short_description": "project_sd", 73 | "pypi_username": "pypi_u", 74 | "version": "1.0", 75 | "use_pytest": "y", 76 | "use_pypi_deployment_with_travis": "y", 77 | "add_pyup_badge": "y", 78 | "command_line_interface": "Click", 79 | "create_author_file": "y", 80 | "open_source_license": "MIT license", 81 | } 82 | path = "gh:moremoban/cookiecutter-pypackage" 83 | with patch.object(sys, "argv", ["yh", path]): 84 | main() 85 | 86 | assert os.path.exists(os.path.join("project_s", ".git")) 87 | 88 | for a_file in find_files("project_s"): 89 | reference = url_join("tests/fixtures", a_file) 90 | if ".git" in a_file: 91 | continue 92 | if fs.path.basename(a_file) in [ 93 | ".moban.yml", 94 | "HISTORY.rst", 95 | ".moban.hashes", 96 | ]: 97 | # no way to compare them 98 | continue 99 | r = read_unicode(reference) 100 | a = read_unicode(a_file) 101 | eq_(r, a, f"{a_file} differs") 102 | os.unlink(a_file) 103 | 104 | shutil.rmtree(project_name) 105 | 106 | 107 | def find_files(dir): 108 | with fs.open_fs(dir) as the_fs: 109 | for a_file in the_fs.listdir("."): 110 | relative_path = url_join(dir, a_file) 111 | if is_dir(relative_path): 112 | a_list = find_files(relative_path) 113 | yield from a_list 114 | else: 115 | yield relative_path 116 | -------------------------------------------------------------------------------- /tests/test_main.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | 4 | from mock import patch 5 | from nose.tools import eq_, raises 6 | 7 | from six import StringIO 8 | from yehua.main import HELP_TEXT, main, control_c_quit, get_yehua_file 9 | 10 | 11 | @patch("yehua.project.os.chdir") 12 | @patch("yehua.project._run_command") 13 | @patch("yehua.project.get_user_inputs") 14 | @patch("yehua.utils.mkdir") 15 | @patch("yehua.utils.save_file") 16 | @patch("yehua.utils.copy_file") 17 | def test_main(copy, save, mkdir, inputs, *_): 18 | copy.return_value = 0 19 | save.return_value = 0 20 | mkdir.return_value = 0 21 | inputs.return_value = dict(project_name="test-me") 22 | with patch.object(sys, "argv", ["yh"]): 23 | main() 24 | calls = mkdir.call_args_list 25 | calls = [str(call) for call in calls] 26 | expected = [ 27 | "call('test-me')", 28 | "call('test-me/test_me')", 29 | "call('test-me/tests')", 30 | "call('test-me/docs')", 31 | "call('test-me/docs/source')", 32 | "call('test-me/.moban.d')", 33 | "call('test-me/.moban.d/tests')", 34 | "call('test-me/.moban.d/docs')", 35 | "call('test-me/.moban.d/docs/source')", 36 | ] 37 | eq_(calls, expected) 38 | 39 | 40 | @raises(SystemExit) 41 | def test_main_dash_dash_help(): 42 | args = ["yehua", "--help"] 43 | with patch("sys.stdout", new_callable=StringIO) as out: 44 | with patch.object(sys, "argv", args): 45 | main() 46 | eq_(out.getvalue(), HELP_TEXT) 47 | 48 | 49 | @raises(SystemExit) 50 | def test_main_dash_h(): 51 | args = ["yehua", "-h"] 52 | with patch("sys.stdout", new_callable=StringIO) as out: 53 | with patch.object(sys, "argv", args): 54 | main() 55 | eq_(out.getvalue(), HELP_TEXT) 56 | 57 | 58 | def test_yehua_file_passed_in_command_line(): 59 | args = ["yehua", "/tmp/yehua.yml"] 60 | with patch("yehua.main.Project") as mocked_project: 61 | with patch.object(sys, "argv", args): 62 | main() 63 | mocked_project.assert_called() 64 | 65 | 66 | @patch("yehua.main.Project") 67 | @patch("yehua.main.cookiecutter_json_to_yehua_file") 68 | def test_gh_url(cookie_cutter_handler, mocked_project): 69 | from moban.exceptions import FileNotFound 70 | 71 | args = ["yehua", "gh:org/repo"] 72 | cookie_cutter_handler.side_effect = FileNotFound 73 | with patch.object(sys, "argv", args): 74 | main() 75 | mocked_project.assert_called_with("git://github.com/org/repo.git") 76 | 77 | 78 | @raises(Exception) 79 | def test_a_directory_is_passed_in_command_line(): 80 | args = ["yehua", "/"] 81 | with patch.object(sys, "argv", args): 82 | main() 83 | 84 | 85 | def test_get_yehua_file_1(): 86 | file_name = "testme" 87 | os.environ["YEHUA_FILE"] = file_name 88 | yehua_file = get_yehua_file() 89 | eq_(file_name, yehua_file) 90 | os.environ.pop("YEHUA_FILE") 91 | 92 | 93 | def test_get_yehua_file_2(): 94 | with open("yehua.yml", "w") as f: 95 | f.write("test") 96 | yehua_file = get_yehua_file() 97 | eq_(os.path.abspath("yehua.yml"), yehua_file) 98 | os.unlink("yehua.yml") 99 | 100 | 101 | def test_get_yehua_file_3(): 102 | default_yehua_file = "pypi://pypi-mobans-pkg/resources/yehua.yml" 103 | yehua_file = get_yehua_file() 104 | eq_(default_yehua_file, yehua_file) 105 | 106 | 107 | @raises(SystemExit) 108 | def test_contrl_c_quit(): 109 | control_c_quit("not", "used") 110 | -------------------------------------------------------------------------------- /tests/test_project.py: -------------------------------------------------------------------------------- 1 | import os 2 | import re 3 | import codecs 4 | import unittest 5 | 6 | from mock import patch 7 | from nose.tools import eq_, raises 8 | 9 | from yehua.main import get_yehua_file 10 | from yehua.project import Project 11 | 12 | 13 | @patch("yehua.utils.mkdir") 14 | @patch("yehua.project.get_user_inputs") 15 | def test_project(inputs, mkdir): 16 | mkdir.return_value = 0 17 | inputs.return_value = dict(project_name="test-me") 18 | yehua_file = get_yehua_file() 19 | project = Project(yehua_file) 20 | project.create_all_directories() 21 | calls = mkdir.call_args_list 22 | calls = [str(call) for call in calls] 23 | expected = [ 24 | "call('test-me')", 25 | "call('test-me/test_me')", 26 | "call('test-me/tests')", 27 | "call('test-me/docs')", 28 | "call('test-me/docs/source')", 29 | "call('test-me/.moban.d')", 30 | "call('test-me/.moban.d/tests')", 31 | "call('test-me/.moban.d/docs')", 32 | "call('test-me/.moban.d/docs/source')", 33 | ] 34 | eq_(calls, expected) 35 | 36 | 37 | @raises(Exception) 38 | @patch("yehua.utils.mkdir") 39 | @patch("yehua.project.get_user_inputs") 40 | def test_existing_directory(inputs, mkdir): 41 | mkdir.return_value = 0 42 | inputs.return_value = dict(project_name="yehua") 43 | yehua_file = get_yehua_file() 44 | project = Project(yehua_file) 45 | project.create_all_directories() 46 | 47 | 48 | class TestProject(unittest.TestCase): 49 | def setUp(self): 50 | self.patcher1 = patch("yehua.utils.copy_file") 51 | self.copy_file = self.patcher1.start() 52 | 53 | self.patcher2 = patch("yehua.project.get_user_inputs") 54 | self.inputs = self.patcher2.start() 55 | 56 | self.patcher3 = patch("yehua.utils.save_file") 57 | self.save_file = self.patcher3.start() 58 | 59 | def tearDown(self): 60 | self.patcher3.stop() 61 | self.patcher2.stop() 62 | self.patcher1.stop() 63 | 64 | def test_project_copy_static(self): 65 | self.copy_file.return_value = 0 66 | self.inputs.return_value = dict(project_name="test-me") 67 | project = Project(get_yehua_file()) 68 | project.copy_static_files() 69 | calls = self.copy_file.call_args_list 70 | calls = [split_call_arguments(call) for call in calls] 71 | expected = [ 72 | ["CUSTOM_README.rst", "test-me/.moban.d/CUSTOM_README.rst.jj2"], 73 | ["custom_setup.py.jj2", "test-me/.moban.d/custom_setup.py.jj2"], 74 | [ 75 | "requirements.txt.jj2", 76 | "test-me/.moban.d/tests/custom_requirements.txt.jj2", 77 | ], 78 | ["CHANGELOG.rst", "test-me/CHANGELOG.rst"], 79 | ["setup.cfg", "test-me/setup.cfg"], 80 | ] 81 | updated_calls = [ 82 | [os.path.basename(call[0]), call[1]] for call in calls 83 | ] 84 | for call, expectee in zip(updated_calls, expected): 85 | eq_(call, expectee) 86 | 87 | def test_project_templating(self): 88 | def mock_save_file(filename, filecontent): 89 | file_to_write = os.path.join( 90 | "tests", "fixtures", "project_templating", filename 91 | ) 92 | path = os.path.dirname(file_to_write) 93 | if not os.path.exists(path): 94 | print(path) 95 | os.mkdir(path) 96 | with open(file_to_write, "w") as f: 97 | f.write(filecontent) 98 | file_to_read = os.path.join( 99 | "tests", "fixtures", "project_templating", filename 100 | ) 101 | with codecs.open(file_to_read, "r", encoding="utf-8") as f: 102 | expected = f.read() 103 | self.assertMultiLineEqual(filecontent, expected) 104 | 105 | self.inputs.return_value = dict(project_name="test-me") 106 | self.save_file.side_effect = mock_save_file 107 | project = Project(get_yehua_file()) 108 | project.templating() 109 | 110 | 111 | def split_call_arguments(mock_call): 112 | pattern = r"call\('(.*)', '(.*)'\)" 113 | result = re.match(pattern, str(mock_call)) 114 | return [result.group(1), result.group(2)] 115 | 116 | 117 | def test_get_simple_user_inputs(): 118 | from yehua.utils import get_user_inputs 119 | 120 | simple_questions = [{"hello": "world?"}] 121 | 122 | with patch("yehua.utils.yehua_input") as yehua_input: 123 | yehua_input.return_value = "hello" 124 | answers = get_user_inputs(simple_questions) 125 | assert answers["hello"] == "hello" 126 | 127 | 128 | def test_template(): 129 | from yehua.utils import get_user_inputs 130 | 131 | questions_with_template = [ 132 | {"hello": "hello?"}, 133 | {"foo": "foo [{{hello}}]"}, # note yehua does not require a prefix 134 | {"bar": "bar [{{cookiecutter.hello}}]"}, 135 | ] 136 | 137 | with patch("yehua.utils.yehua_input") as yehua_input: 138 | yehua_input.side_effect = ["hello", None, None] 139 | answers = get_user_inputs(questions_with_template) 140 | assert answers["hello"] == "hello" 141 | assert answers["foo"] == "hello" 142 | assert answers["bar"] == "hello" 143 | 144 | 145 | @patch("yehua.utils.cutie.select") 146 | @patch("yehua.utils.yehua_input") 147 | def test_get_complex_user_inputs(fake_input, fake_select): 148 | from yehua.utils import get_user_inputs 149 | 150 | simple_questions = [ 151 | { 152 | "hello": [ 153 | { 154 | "question": "Multiple choice question?", 155 | "1. option 1": "N/A", 156 | "2. option 2": [{"option 2": "What is your answer?"}], 157 | } 158 | ] 159 | } 160 | ] 161 | 162 | fake_select.return_value = 2 163 | fake_input.return_value = "hello" 164 | answers = get_user_inputs(simple_questions) 165 | eq_(answers["hello"], "option 2") 166 | eq_(answers["option 2"], "hello") 167 | -------------------------------------------------------------------------------- /tests/test_yehua.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | import shutil 4 | from filecmp import dircmp 5 | 6 | from mock import patch 7 | 8 | from yehua.main import main 9 | 10 | 11 | @patch("yehua.project.get_user_inputs") 12 | def test_reference_pypi_package(fake_inputs): 13 | project_name = "project_yehua" 14 | fake_inputs.return_value = { 15 | "project_name": project_name, 16 | "description": "description", 17 | "license": "mit", 18 | "author": "author", 19 | "contact": "contact", 20 | "organisation": "github", 21 | "company": "copyright", 22 | "project_type": "command line interface", 23 | "cli": "cli", 24 | } 25 | with patch.object(sys, "argv", ["yh"]): 26 | main() 27 | 28 | assert os.path.exists(os.path.join("project_yehua", ".git")) 29 | shutil.rmtree(os.path.join("project_yehua", ".git")) 30 | 31 | c = dircmp("project_yehua", "tests/fixtures/project_yehua") 32 | assert len(c.diff_files) == 0, "\n".join(c.diff_files) 33 | shutil.rmtree("project_yehua") 34 | -------------------------------------------------------------------------------- /yehua-npm-usage.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/moremoban/yehua/e90ac103ec28e1101fd845796c02083d52ddf43e/yehua-npm-usage.gif -------------------------------------------------------------------------------- /yehua-usage.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/moremoban/yehua/e90ac103ec28e1101fd845796c02083d52ddf43e/yehua-usage.gif -------------------------------------------------------------------------------- /yehua.yaml: -------------------------------------------------------------------------------- 1 | name: "yehua" 2 | full_name: "Project template tool for an organisation" 3 | organisation: "moremoban" 4 | author: C. W. 5 | contact: "wangc_2011@hotmail.com" 6 | company: "Onni Software Ltd." 7 | version: "0.1.4" 8 | current_version: "0.1.4" 9 | release: "0.1.4" 10 | copyright_year: 2017-2020 11 | command_line_interface: "yh" 12 | entry_point: "yehua.main:main" 13 | license: New BSD 14 | branch: master 15 | lint_command: bash lint.sh 16 | setup_use_markers: true 17 | dependencies: 18 | - ruamel.yaml>=0.15.42;python_version == '3.7' 19 | - ruamel.yaml>=0.15.5;python_version != '3.4' and python_version < '3.7' 20 | - ruamel.yaml>=0.15.98;python_version == '3.8' 21 | - Jinja2 22 | - moban>=0.6.0 23 | - colorful 24 | - rich 25 | - readchar 26 | - colorama 27 | - moban-jinja2-github>=0.0.2 28 | - moban-ansible 29 | extra_dependencies: 30 | - pypi-mobans: 31 | - pypi-mobans-pkg>=0.1.4 32 | - cookiecutter: 33 | - cookiecutter==1.7.0 34 | test_dependencies: 35 | - pytest 36 | - pytest-cov 37 | - cookiecutter 38 | - gitfs2 39 | - pypifs 40 | - pypi-mobans-pkg>=0.1.4 41 | description: Yet another a project template tool for an organisation. 42 | python_requires: ">=3.6" 43 | min_python_version: "3.6" 44 | excluded_github_users: 45 | - chfw 46 | - gitter-badger 47 | -------------------------------------------------------------------------------- /yehua/__init__.py: -------------------------------------------------------------------------------- 1 | from yehua._version import __author__, __version__ # noqa: F401 2 | -------------------------------------------------------------------------------- /yehua/__main__.py: -------------------------------------------------------------------------------- 1 | from yehua.main import main 2 | 3 | if __name__ == "__main__": 4 | main() 5 | -------------------------------------------------------------------------------- /yehua/_version.py: -------------------------------------------------------------------------------- 1 | __version__ = "0.1.4" 2 | __author__ = "C. W." 3 | -------------------------------------------------------------------------------- /yehua/cookiecutter.py: -------------------------------------------------------------------------------- 1 | import os 2 | import logging 3 | from copy import deepcopy 4 | 5 | from jinja2 import Environment 6 | 7 | import yehua.utils as utils 8 | from yehua.utils import dump_yaml, get_user_inputs 9 | from yehua.project import Project 10 | 11 | MOBAN_FILE_FOR_COOKIE_CUTTER = """ 12 | configuration: 13 | template_dir: 14 | - "" 15 | configuration: project.json 16 | force_template_type: cookiecutter 17 | template_types: 18 | cookiecutter: 19 | base_type: jinja2 20 | file_extensions: 21 | - cookiecutter 22 | options: 23 | trim_blocks: false 24 | lstrip_blocks: false 25 | extensions: 26 | - cookiecutter.extensions.JsonifyExtension 27 | - jinja2_time.TimeExtension 28 | """ 29 | LOG = logging.getLogger(__name__) 30 | 31 | 32 | class CookieCutter(Project): 33 | def __init__(self, project_content, base_dir): 34 | self.source_dir = base_dir 35 | self.template_dir = base_dir 36 | self.project_content = project_content 37 | self.project_name = None 38 | self.answers = None 39 | self.name = None 40 | self.directives = None 41 | self.cookie_cutter_dir = utils.find_project_name(self.source_dir) 42 | self._ask_questions() 43 | self._append_magic_variables() 44 | self._template_yehua_file() 45 | 46 | def _create_jj2_environment(self, _): 47 | return Environment( 48 | keep_trailing_newline=True, 49 | trim_blocks=True, 50 | lstrip_blocks=True, 51 | block_start_string="cookiecutter>" 100 | ) 101 | else: 102 | return item 103 | -------------------------------------------------------------------------------- /yehua/main.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import signal 3 | import logging 4 | import argparse 5 | 6 | from moban.exceptions import FileNotFound 7 | 8 | from yehua.utils import get_yehua_file 9 | from yehua.project import Project 10 | from yehua._version import __version__ 11 | from yehua.cookiecutter import CookieCutter 12 | from yehua.cookiecutter_to_yehua import cookiecutter_json_to_yehua_file 13 | 14 | HELP_TEXT = ( 15 | """ 16 | Usage: %s 17 | 18 | yehua [yehua_file/help] 19 | 20 | where: 21 | 22 | - yehua_file: is an instruction file for yehua to act. See documentation for 23 | its file specification. 24 | - help: print this text and exit 25 | 26 | If no argument is given, the command looks for the instruction file in the 27 | shell environment variable "YEHUA_FILE" first, then for "yehua.yml" at 28 | current working directory, and at last use the default "yehua.yml" in its 29 | own package. 30 | """ 31 | % __version__ 32 | ) 33 | DESCRIPTION = ( 34 | "Make an installable, github ready, travis-ci capable " 35 | + "python package in 1 minute" 36 | ) 37 | LOG = logging.getLogger(__name__) 38 | LOG_LEVEL = [logging.ERROR, logging.WARNING, logging.INFO, logging.DEBUG] 39 | 40 | 41 | def main(): 42 | signal.signal(signal.SIGINT, control_c_quit) 43 | parser = create_parser() 44 | options = vars(parser.parse_args()) 45 | handle_verbose(options["verbose"]) 46 | 47 | yehua_file = options.get("url") 48 | if yehua_file is None: 49 | yehua_file = get_yehua_file() 50 | try: 51 | if yehua_file.endswith("git") and yehua_file.startswith("https"): 52 | yehua_file = yehua_file.replace("https://", "git://") 53 | 54 | if yehua_file.startswith("gh:"): 55 | yehua_file = yehua_file.replace("gh:", "git://github.com/") 56 | if not yehua_file.endswith(".git"): 57 | yehua_file = yehua_file + ".git" 58 | 59 | yehua = cookiecutter_json_to_yehua_file(yehua_file) 60 | project = CookieCutter(yehua, yehua_file) 61 | except FileNotFound: 62 | # then it is yehua file 63 | project = Project(yehua_file) 64 | project.create_all_directories() 65 | project.templating() 66 | project.copy_static_files() 67 | project.inflate_all_by_moban() 68 | project.post_moban() 69 | project.end() 70 | 71 | 72 | def usage(): 73 | print(HELP_TEXT) 74 | sys.exit(0) 75 | 76 | 77 | def control_c_quit(_, __): 78 | print("\n") 79 | sys.exit(0) 80 | 81 | 82 | def create_parser(): 83 | parser = argparse.ArgumentParser(prog="yh", description=DESCRIPTION) 84 | parser.add_argument( 85 | "url", 86 | metavar="url", 87 | type=str, 88 | nargs="?", 89 | help="a url to yehua file or cookie_cutter", 90 | ) 91 | parser.add_argument( 92 | "-V", "--version", action="version", version=f"yehua {__version__}" 93 | ) 94 | parser.add_argument( 95 | "-v", 96 | action="count", 97 | dest="verbose", 98 | default=0, 99 | help="show verbose, try -v, -vv, -vvv", 100 | ) 101 | return parser 102 | 103 | 104 | def handle_verbose(verbose_level): 105 | if verbose_level > len(LOG_LEVEL): 106 | verbose_level = len(LOG_LEVEL) - 1 107 | level = LOG_LEVEL[verbose_level] 108 | logging.basicConfig( 109 | format="%(asctime)s - %(name)s - %(levelname)s - %(message)s", 110 | level=level, 111 | ) 112 | -------------------------------------------------------------------------------- /yehua/project.py: -------------------------------------------------------------------------------- 1 | import os 2 | import subprocess 3 | from datetime import datetime 4 | 5 | from jinja2 import Environment 6 | from moban.externals.file_system import exists, is_dir, read_unicode 7 | 8 | import fs 9 | import yehua.utils as utils 10 | from yehua.utils import get_user_inputs 11 | from jinja2_fsloader import FSLoader 12 | 13 | 14 | class Project: 15 | def __init__(self, yehua_file): 16 | if not exists(yehua_file): 17 | raise Exception("%s does not exist" % yehua_file) 18 | if is_dir(yehua_file): 19 | raise Exception("A yehua file is expected. Not a directory") 20 | 21 | self.project_file = yehua_file 22 | self.project_name = None 23 | self.answers = None 24 | self.name = None 25 | self.directives = None 26 | self._ask_questions() 27 | self._append_magic_variables() 28 | self._template_yehua_file() 29 | 30 | def create_all_directories(self): 31 | folder_tree = { 32 | self.answers["project_name"]: self.directives.get("layout", None) 33 | } 34 | utils.make_directories(None, folder_tree) 35 | 36 | def templating(self): 37 | for template in self.directives["templates"]: 38 | for output, template_file in template.items(): 39 | template = self.jj2_environment.get_template(template_file) 40 | rendered_content = template.render(**self.answers) 41 | target = os.path.join(self.project_name, output) 42 | utils.save_file(target, rendered_content) 43 | 44 | def copy_static_files(self): 45 | if "static" not in self.directives: 46 | return 47 | for static in self.directives["static"]: 48 | for output, source in static.items(): 49 | source = os.path.abspath(os.path.join(self.static_dir, source)) 50 | dest = os.path.join(self.project_name, output) 51 | utils.copy_file(source, dest) 52 | 53 | def inflate_all_by_moban(self): 54 | current = os.getcwd() 55 | project_name = self.answers["project_name"] 56 | os.chdir(project_name) 57 | cmd = "moban" 58 | _run_command(cmd) 59 | os.chdir(current) 60 | utils.color_print( 61 | f"\u2713 Files are generated under [info]{project_name}[/info]" 62 | ) 63 | 64 | def post_moban(self): 65 | if "post-moban" not in self.directives: 66 | return 67 | for key, value in self.directives["post-moban"].items(): 68 | if key == "git-repo-files": 69 | self.initialize_git_and_add_all(value) 70 | 71 | def initialize_git_and_add_all(self, project_files): 72 | project_name = self.answers["project_name"] 73 | current = os.getcwd() 74 | os.chdir(project_name) 75 | cmd = "git init" 76 | _run_command(cmd) 77 | for file_name in project_files: 78 | _run_command(f"git add {file_name}") 79 | os.chdir(current) 80 | utils.color_print( 81 | f"\u2713 Git repo initialized under [info]{project_name}[/info]" 82 | + " and is ready to commit" 83 | ) 84 | 85 | def end(self): 86 | utils.color_print( 87 | "All done!! project [info]%s[/info] is created." 88 | % self.project_name 89 | ) 90 | utils.color_print( 91 | "In the future, " 92 | + "run [info]moban[/info] to synchronize with the project template" 93 | ) 94 | 95 | def _ask_questions(self): 96 | content = read_unicode(self.project_file) 97 | first_stage = utils.load_yaml(content) 98 | utils.color_print(first_stage["introduction"]) 99 | base_path = fs.path.dirname(self.project_file) 100 | with fs.open_fs(base_path) as the_fs: 101 | self.template_dir = os.path.join( 102 | the_fs._root_path, 103 | first_stage["configuration"]["template_path"], 104 | ) 105 | self.static_dir = os.path.join( 106 | the_fs._root_path, first_stage["configuration"]["static_path"] 107 | ) 108 | self.answers = get_user_inputs(first_stage["questions"]) 109 | 110 | def _append_magic_variables(self): 111 | self.project_name = self.answers["project_name"] 112 | self.answers["now"] = datetime.utcnow() 113 | 114 | self.jj2_environment = self._create_jj2_environment(self.template_dir) 115 | 116 | def _template_yehua_file(self): 117 | base_path = fs.path.dirname(self.project_file) 118 | with fs.open_fs(base_path) as the_fs: 119 | base_path = the_fs._root_path 120 | tmp_env = self._create_jj2_environment(base_path) 121 | template = tmp_env.get_template( 122 | os.path.basename(self.project_file) 123 | ) 124 | renderred_content = template.render(**self.answers) 125 | self.directives = utils.load_yaml(renderred_content) 126 | 127 | def _create_jj2_environment(self, path): 128 | template_loader = FSLoader(path) 129 | environment = Environment( 130 | loader=template_loader, 131 | keep_trailing_newline=True, 132 | trim_blocks=True, 133 | lstrip_blocks=True, 134 | ) 135 | return environment 136 | 137 | 138 | def _run_command(command): 139 | subprocess.check_call( 140 | command.split(" "), 141 | stdout=subprocess.DEVNULL, 142 | stderr=subprocess.DEVNULL, 143 | ) 144 | -------------------------------------------------------------------------------- /yehua/theme.py: -------------------------------------------------------------------------------- 1 | THEME = {"info": "#F47983"} 2 | -------------------------------------------------------------------------------- /yehua/thirdparty/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/moremoban/yehua/e90ac103ec28e1101fd845796c02083d52ddf43e/yehua/thirdparty/__init__.py -------------------------------------------------------------------------------- /yehua/utils.py: -------------------------------------------------------------------------------- 1 | import os 2 | import re 3 | import codecs 4 | import shutil 5 | import logging 6 | 7 | import colorful 8 | from jinja2 import Environment 9 | from ruamel.yaml import YAML 10 | 11 | import fs 12 | from yehua.theme import THEME 13 | from yehua.thirdparty import cutie 14 | 15 | DEFAULT_FILE = "yehua.yml" 16 | DEFAULT_PYPI_YEHUA_FILE = "pypi://pypi-mobans-pkg/resources/yehua.yml" 17 | ENVIRONMENT_KEY = "YEHUA_FILE" 18 | LOG = logging.getLogger(__name__) 19 | 20 | yehua_input = input 21 | 22 | 23 | def get_yehua_file(): 24 | yehua_file = os.environ.get(ENVIRONMENT_KEY, None) 25 | if yehua_file is None: 26 | if os.path.exists(DEFAULT_FILE): 27 | yehua_file = os.path.abspath(DEFAULT_FILE) 28 | else: 29 | yehua_file = DEFAULT_PYPI_YEHUA_FILE 30 | return yehua_file 31 | 32 | 33 | def get_resource_dir(folder): 34 | current_path = os.path.dirname(__file__) 35 | resource_path = os.path.join(current_path, folder) 36 | return resource_path 37 | 38 | 39 | def make_directories(parent, node_dictionary): 40 | for key, value in node_dictionary.items(): 41 | if parent: 42 | the_parent = os.path.join(parent, key) 43 | else: 44 | the_parent = key 45 | if os.path.exists(the_parent): 46 | raise Exception("%s exists. Please remove it." % the_parent) 47 | mkdir(the_parent) 48 | if value is None: 49 | continue 50 | for item in value: 51 | if isinstance(item, dict): 52 | make_directories(the_parent, item) 53 | else: 54 | mkdir(os.path.join(the_parent, item)) 55 | 56 | 57 | def make_project_src(project_name): 58 | return project_name.lower().replace("-", "_") 59 | 60 | 61 | # The following Python depdencies are trusted to work 62 | def copy_file(source, dest): 63 | shutil.copy(source, dest) 64 | 65 | 66 | def mkdir(path): 67 | os.mkdir(path) 68 | 69 | 70 | def save_file(filename, filecontent): 71 | with codecs.open(os.path.join(filename), "w", encoding="utf-8") as f: 72 | f.write(filecontent) 73 | 74 | 75 | def load_yaml(content): 76 | yaml = YAML(typ="rt") 77 | data = yaml.load(content) 78 | return data 79 | 80 | 81 | def dump_yaml(content, file_handle): 82 | yaml = YAML(typ="rt") 83 | yaml.dump(content, file_handle) 84 | 85 | 86 | def find_project_name(parent_directory): 87 | with fs.open_fs(parent_directory) as the_fs: 88 | for a_file in the_fs.listdir("."): 89 | project_name_condition = a_file.startswith("{{") 90 | if project_name_condition: 91 | return a_file 92 | 93 | 94 | def get_user_inputs(questions): # refactor this later 95 | LOG.debug(questions) 96 | answers = {} 97 | env = Environment() 98 | colorful.update_palette({"peach": "#f47983"}) 99 | for q in questions: 100 | for key, question in q.items(): 101 | if isinstance(question, list): 102 | q, additional = raise_complex_question(question) 103 | answers[key] = q 104 | if additional: 105 | answers.update(additional) 106 | else: 107 | if "{{" in question: 108 | # {"foo": "foo [{{yehua.hello}}]"}, 109 | # {"bar": "bar [{{cookiecutter.hello}}]"} 110 | template = env.from_string(question) 111 | question = template.render(cookiecutter=answers, **answers) 112 | 113 | match = re.match(r"(.*)\[(.*)\].*", question) 114 | if match: 115 | q, default_answer = match.group(1), match.group(2) 116 | decorated_question = ( 117 | f"{q}[{colorful.peach(default_answer)}]: " 118 | ) 119 | if default_answer in ["y", "n"]: 120 | decorated_question = ( 121 | q + f"[{colorful.peach(default_answer)}]" 122 | ) 123 | a = cutie.prompt_yes_or_no( 124 | decorated_question, 125 | default_is_yes=default_answer == "y", 126 | deselected_prefix=" ", 127 | selected_prefix=colorful.bold_peach("\u27a4 "), 128 | char_prompt=False, 129 | ) 130 | if a is None: 131 | raise Exception() 132 | 133 | else: 134 | a = yehua_input(decorated_question) 135 | else: 136 | decorated_question = question 137 | a = yehua_input(decorated_question) 138 | if not a: 139 | match = re.match(r".*\[(.*)\].*", question) 140 | if match: 141 | a = match.group(1) 142 | answers[key] = a 143 | LOG.debug(answers) 144 | return answers 145 | 146 | 147 | def raise_complex_question(question): 148 | additional_answers = None 149 | for subq in question: 150 | question = subq.pop("question") 151 | suggested_answers = sorted(subq.keys()) 152 | full_question = [question] + suggested_answers 153 | a = cutie.select( 154 | full_question, 155 | caption_indices=[0], 156 | selected_index=1, 157 | deselected_prefix="[ ] ", 158 | selected_prefix=( 159 | colorful.bold_white("[") 160 | + colorful.bold_peach("\u2713") 161 | + colorful.bold_white("] ") 162 | ), 163 | ) 164 | if a is None: 165 | raise Exception() 166 | for key in suggested_answers: 167 | if key.startswith(str(a)): 168 | string_answer = key.split(".")[1].strip() 169 | if subq[key] != "N/A": 170 | additional_answers = get_user_inputs(subq[key]) 171 | break 172 | return string_answer, additional_answers 173 | 174 | 175 | def color_print(rich_text): 176 | from rich.theme import Theme 177 | from rich.console import Console 178 | 179 | theme = Theme(THEME) 180 | console = Console(theme=theme) 181 | console.print(rich_text) 182 | --------------------------------------------------------------------------------