├── .env ├── .gitignore ├── LICENSE ├── README.rst ├── assets └── slides.jpg ├── cookiecutter.json ├── pytest.ini ├── requirements.txt ├── tasks.py ├── tests ├── conftest.py └── test_project.py └── {{cookiecutter.repo_name}} ├── .env ├── .gitignore ├── LICENSE.rst ├── README.rst ├── css ├── colorful.css ├── custom.css ├── default.css └── normalize.css ├── img ├── hovercraft-logo.png ├── python-powered.png └── title-logo.png ├── index.rst ├── requirements.txt ├── setup.py └── tasks.py /.env: -------------------------------------------------------------------------------- 1 | # /bin/bash - help gedit syntax highlight do the right thing 2 | # autoenv script (https://github.com/kennethreitz/autoenv) 3 | # Use ". .env -h" for help. 4 | _venv_virtualenv=/usr/bin/virtualenv 5 | _venv_py3=true 6 | _venv_pip_req='pip<8' 7 | _venv_master_url="https://raw.githubusercontent.com/Springerle/py-generic-project/master/.env" 8 | 9 | _venv_temp=/usr/local/bin/virtualenv 10 | if test ! -x $_venv_virtualenv -a -x $_venv_temp; then 11 | _venv_virtualenv=$_venv_temp 12 | fi 13 | test "${_venv_py3:0:2}" != '{''{' || _venv_py3=false 14 | if $_venv_py3; then # Python 3 mode? 15 | # /opt/pyenv, see https://github.com/jhermann/priscilla/tree/master/pyenv 16 | _venv_temp=/opt/pyenv/bin/pyvenv-3 17 | if test -x $_venv_temp; then 18 | _venv_virtualenv=$_venv_temp 19 | fi 20 | # python3 venv in recent Debian / Ubuntu 21 | for _venv_temp in 3 3.6 3.5 3.4 3.3; do 22 | _venv_temp=/usr/bin/pyvenv-$_venv_temp 23 | if test -x $_venv_temp; then 24 | _venv_virtualenv=$_venv_temp 25 | break 26 | fi 27 | done 28 | fi 29 | 30 | _venv_readlink=readlink 31 | case "$(uname -s)" in 32 | # See http://en.wikipedia.org/wiki/Uname#Examples 33 | Darwin) 34 | _venv_temp="/usr/local/opt/coreutils/libexec/gnubin/readlink" 35 | if test -x $_venv_temp; then 36 | _venv_readlink=$_venv_temp 37 | else 38 | echo "*** No readlink command, do a 'brew install coreutils'..." 39 | return 1 40 | fi 41 | ;; 42 | CYGWIN*) 43 | ;; 44 | Linux|*) 45 | ;; 46 | esac 47 | 48 | test -z "$JENKINS_URL" || echo '***' "\$0=$0" "\$BASH_SOURCE=${BASH_SOURCE[0]}" 49 | if test "$0" = "-bash" -o "$(basename -- "$0")" = "bash" -o "$(basename -- "${BASH_SOURCE[0]}")" = ".env"; then 50 | _venv_script=$($_venv_readlink -f ${BASH_SOURCE[0]}) 51 | else 52 | _venv_script="/dont-know-really/.env" 53 | fi 54 | _venv_xtrace=$(set +o | grep xtrace) 55 | set +x 56 | _venv_name="$(basename $(pwd))" 57 | 58 | _venv_pip_log() { 59 | if $_venv_verbose; then 60 | cat 61 | else 62 | egrep "Found.existing.installation|Collecting|Installing.collected.packages|Searching.for|Installed|Finished" 63 | fi 64 | } 65 | 66 | # command line flags, mostly for CI server usage 67 | _venv_create=false 68 | _venv_develop=false 69 | _venv_verbose=false 70 | while test "${1:0:1}" = "-"; do 71 | case "$1" in 72 | --yes) _venv_create=true ;; 73 | --develop) _venv_develop=true ;; 74 | --verbose | -v) _venv_verbose=true; set -x ;; 75 | --virtualenv) shift; _venv_virtualenv="$1" ;; 76 | --help | -h) 77 | # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 78 | echo "usage: . $_venv_script " 79 | echo 80 | echo "Create and/or activate a Python virtual environment. Updates pip and other" 81 | echo "system packages, and recognizes 'dev-requirements.txt' and 'setup.py' or" 82 | echo "'requirements.txt'." 83 | echo 84 | echo "This is an autoenv script (see https://github.com/kennethreitz/autoenv)." 85 | echo 86 | echo "Options:" 87 | echo " --yes create missing venv without prompting" 88 | echo " --develop call 'develop -U' on activation" 89 | echo " -v | --verbose don't filter install logs on terminal" 90 | echo " (full logs in '.venv/pip-install.log')" 91 | echo " -h | --help this help message" 92 | echo " --virtualenv PATH set explicit path of virtualenv binary to use" 93 | echo " --update update to the newest master copy" 94 | return 1 95 | ;; 96 | --update) 97 | python </dev/null || return 1 106 | sed -r -e "s/^_venv_py3=.*/_venv_py3=$_venv_py3/" <"/tmp/$USER-py-env-update.py" >.env 107 | git diff .env || : 108 | echo ".env script update OK" 109 | return 0 110 | ;; 111 | *) echo "WARNING: Ignored unknown option '$1'" ;; 112 | esac 113 | shift 114 | done 115 | 116 | # Outside the tree of the .env script? 117 | if pwd | egrep -v '^'$(dirname "$_venv_script")'($|/)' >/dev/null; then 118 | : echo Outside "[$0 $1 ; $_venv_script]" 119 | 120 | # Inside the tree of the .env script, but have another local '.env'? 121 | elif test \! -f .env -o "$_venv_script" != "$(pwd)/.env"; then 122 | : echo Inside "[$0 $1 ; $_venv_script]" 123 | 124 | # Only try virtualenv creation outside of template dirs; the egrep pattern is escaped for hiding it from Jinja2 125 | elif pwd | egrep -v '/{''{''.*''}''}(/|$)' >/dev/null || $_venv_create; then 126 | test -f ".env" && _venv_ask=true || _venv_ask=false 127 | 128 | # Look for existing venv at common locations 129 | for _venv_base in .venv venv . ..; do 130 | if test -f "$_venv_base/$_venv_name/bin/activate"; then 131 | deactivate 2>/dev/null || : 132 | . "$_venv_base/$_venv_name/bin/activate" 133 | if test -f setup.py; then 134 | $_venv_develop && python setup.py -q develop -U || : 135 | python setup.py --name --version --url | tr \\n \\0 \ 136 | | xargs -0 printf "*** Activated %s %s @ %s\\n" || : 137 | else 138 | echo "*** Activated $_venv_base/$_venv_name" 139 | fi 140 | _venv_ask=false 141 | break 142 | fi 143 | done 144 | 145 | if $_venv_ask && test \! -d .venv; then 146 | $_venv_create || { read -p "No virtualenv found, shall I create one for you? [Y/n] " -n 1 -r || REPLY='n'; echo; } 147 | if $_venv_create || [[ $REPLY =~ ^[Yy]?$ ]]; then 148 | # Create, activate, and update virtualenv 149 | $_venv_virtualenv ".venv/$_venv_name" 150 | . ".venv/$_venv_name/bin/activate" 151 | ".venv/$_venv_name/bin/pip" --log ".venv/pip-install.log" install -U "$_venv_pip_req" 2>&1 | _venv_pip_log \ 152 | || echo >&2 "!!! pip failed, see .venv/pip-install.log for details" 153 | ".venv/$_venv_name/bin/pip" --log ".venv/pip-install.log" install -U "setuptools>=14.3" "wheel>=0.24.0" 2>&1 | _venv_pip_log \ 154 | || echo >&2 "!!! pip failed, see .venv/pip-install.log for details" 155 | 156 | # Get rid of cruft some older systems produce 157 | ".venv/$_venv_name/bin/pip" --log ".venv/pip-install.log" uninstall --yes distribute >/dev/null 2>&1 || : 158 | 159 | # pypandoc fails when the base package is missing, so we install it here, if possible 160 | if which pandoc >/dev/null ; then 161 | ".venv/$_venv_name/bin/pip" --log ".venv/pip-install.log" install pypandoc 2>&1 | _venv_pip_log \ 162 | || echo >&2 "!!! pip failed, see .venv/pip-install.log for details" 163 | fi 164 | 165 | # Install development + project dependencies 166 | if test -f dev-requirements.txt; then 167 | ".venv/$_venv_name/bin/pip" --log ".venv/pip-install.log" install -r dev-requirements.txt 2>&1 | _venv_pip_log \ 168 | || echo >&2 "!!! pip failed, see .venv/pip-install.log for details" 169 | fi 170 | if test -f setup.py; then 171 | ".venv/$_venv_name/bin/python" setup.py develop -U 2>&1 | _venv_pip_log 172 | echo 173 | ".venv/$_venv_name/bin/python" setup.py --name --version --author --author-email --license --description --url \ 174 | | tr \\n \\0 | xargs -0 printf "%s %s by %s <%s> [%s]\\n%s\\n%s\\n" || : 175 | else 176 | echo 177 | if test -f requirements.txt; then 178 | echo "*** No 'setup.py' found, installing requirements..." 179 | ".venv/$_venv_name/bin/pip" --log ".venv/pip-install.log" install -r requirements.txt 2>&1 | _venv_pip_log 180 | else 181 | echo "*** No 'setup.py' or 'requirements.txt' found, all done." 182 | fi 183 | fi 184 | else 185 | # prevent constant prompting 186 | mkdir -p .venv 187 | fi 188 | fi 189 | fi 190 | 191 | unset _venv_script _venv_name _venv_ask _venv_create _venv_develop _venv_pip_log _venv_base 192 | unset _venv_readlink _venv_temp _venv_virtualenv _venv_verbose _venv_py3 _venv_pip_req _venv_master_url 193 | eval $_venv_xtrace # restore xtrace state 194 | unset _venv_xtrace 195 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .venv/ 2 | __pycache__/ 3 | present-with-hovercraft-slides/ 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | CC0 1.0 Universal 2 | 3 | Statement of Purpose 4 | 5 | The laws of most jurisdictions throughout the world automatically confer 6 | exclusive Copyright and Related Rights (defined below) upon the creator and 7 | subsequent owner(s) (each and all, an "owner") of an original work of 8 | authorship and/or a database (each, a "Work"). 9 | 10 | Certain owners wish to permanently relinquish those rights to a Work for the 11 | purpose of contributing to a commons of creative, cultural and scientific 12 | works ("Commons") that the public can reliably and without fear of later 13 | claims of infringement build upon, modify, incorporate in other works, reuse 14 | and redistribute as freely as possible in any form whatsoever and for any 15 | purposes, including without limitation commercial purposes. These owners may 16 | contribute to the Commons to promote the ideal of a free culture and the 17 | further production of creative, cultural and scientific works, or to gain 18 | reputation or greater distribution for their Work in part through the use and 19 | efforts of others. 20 | 21 | For these and/or other purposes and motivations, and without any expectation 22 | of additional consideration or compensation, the person associating CC0 with a 23 | Work (the "Affirmer"), to the extent that he or she is an owner of Copyright 24 | and Related Rights in the Work, voluntarily elects to apply CC0 to the Work 25 | and publicly distribute the Work under its terms, with knowledge of his or her 26 | Copyright and Related Rights in the Work and the meaning and intended legal 27 | effect of CC0 on those rights. 28 | 29 | 1. Copyright and Related Rights. A Work made available under CC0 may be 30 | protected by copyright and related or neighboring rights ("Copyright and 31 | Related Rights"). Copyright and Related Rights include, but are not limited 32 | to, the following: 33 | 34 | i. the right to reproduce, adapt, distribute, perform, display, communicate, 35 | and translate a Work; 36 | 37 | ii. moral rights retained by the original author(s) and/or performer(s); 38 | 39 | iii. publicity and privacy rights pertaining to a person's image or likeness 40 | depicted in a Work; 41 | 42 | iv. rights protecting against unfair competition in regards to a Work, 43 | subject to the limitations in paragraph 4(a), below; 44 | 45 | v. rights protecting the extraction, dissemination, use and reuse of data in 46 | a Work; 47 | 48 | vi. database rights (such as those arising under Directive 96/9/EC of the 49 | European Parliament and of the Council of 11 March 1996 on the legal 50 | protection of databases, and under any national implementation thereof, 51 | including any amended or successor version of such directive); and 52 | 53 | vii. other similar, equivalent or corresponding rights throughout the world 54 | based on applicable law or treaty, and any national implementations thereof. 55 | 56 | 2. Waiver. To the greatest extent permitted by, but not in contravention of, 57 | applicable law, Affirmer hereby overtly, fully, permanently, irrevocably and 58 | unconditionally waives, abandons, and surrenders all of Affirmer's Copyright 59 | and Related Rights and associated claims and causes of action, whether now 60 | known or unknown (including existing as well as future claims and causes of 61 | action), in the Work (i) in all territories worldwide, (ii) for the maximum 62 | duration provided by applicable law or treaty (including future time 63 | extensions), (iii) in any current or future medium and for any number of 64 | copies, and (iv) for any purpose whatsoever, including without limitation 65 | commercial, advertising or promotional purposes (the "Waiver"). Affirmer makes 66 | the Waiver for the benefit of each member of the public at large and to the 67 | detriment of Affirmer's heirs and successors, fully intending that such Waiver 68 | shall not be subject to revocation, rescission, cancellation, termination, or 69 | any other legal or equitable action to disrupt the quiet enjoyment of the Work 70 | by the public as contemplated by Affirmer's express Statement of Purpose. 71 | 72 | 3. Public License Fallback. Should any part of the Waiver for any reason be 73 | judged legally invalid or ineffective under applicable law, then the Waiver 74 | shall be preserved to the maximum extent permitted taking into account 75 | Affirmer's express Statement of Purpose. In addition, to the extent the Waiver 76 | is so judged Affirmer hereby grants to each affected person a royalty-free, 77 | non transferable, non sublicensable, non exclusive, irrevocable and 78 | unconditional license to exercise Affirmer's Copyright and Related Rights in 79 | the Work (i) in all territories worldwide, (ii) for the maximum duration 80 | provided by applicable law or treaty (including future time extensions), (iii) 81 | in any current or future medium and for any number of copies, and (iv) for any 82 | purpose whatsoever, including without limitation commercial, advertising or 83 | promotional purposes (the "License"). The License shall be deemed effective as 84 | of the date CC0 was applied by Affirmer to the Work. Should any part of the 85 | License for any reason be judged legally invalid or ineffective under 86 | applicable law, such partial invalidity or ineffectiveness shall not 87 | invalidate the remainder of the License, and in such case Affirmer hereby 88 | affirms that he or she will not (i) exercise any of his or her remaining 89 | Copyright and Related Rights in the Work or (ii) assert any associated claims 90 | and causes of action with respect to the Work, in either case contrary to 91 | Affirmer's express Statement of Purpose. 92 | 93 | 4. Limitations and Disclaimers. 94 | 95 | a. No trademark or patent rights held by Affirmer are waived, abandoned, 96 | surrendered, licensed or otherwise affected by this document. 97 | 98 | b. Affirmer offers the Work as-is and makes no representations or warranties 99 | of any kind concerning the Work, express, implied, statutory or otherwise, 100 | including without limitation warranties of title, merchantability, fitness 101 | for a particular purpose, non infringement, or the absence of latent or 102 | other defects, accuracy, or the present or absence of errors, whether or not 103 | discoverable, all to the greatest extent permissible under applicable law. 104 | 105 | c. Affirmer disclaims responsibility for clearing rights of other persons 106 | that may apply to the Work or any use thereof, including without limitation 107 | any person's Copyright and Related Rights in the Work. Further, Affirmer 108 | disclaims responsibility for obtaining any necessary consents, permissions 109 | or other rights required for any use of the Work. 110 | 111 | d. Affirmer understands and acknowledges that Creative Commons is not a 112 | party to this document and has no duty or obligation with respect to this 113 | CC0 or use of the Work. 114 | 115 | For more information, please see 116 | 117 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | hovercraft-slides 2 | ================= 3 | 4 | A cookiecutter template that creates a `Hovercraft!`_ presentation. 5 | 6 |  |Groups|  |CC0 licensed|  |GitHub Issues| 7 | 8 | .. contents:: **Table of Contents** 9 | 10 | 11 | Why ‘Hovercraft!’? 12 | ------------------ 13 | 14 | Disclaimer: YMMV, these are my reasons… 15 | 16 | - HTML slides work everywhere (uses `impress.js`_) 17 | - But write in text markup (`reStructuredText`_) 18 | - Re-use your CSS3 know-how 19 | - Text is way more malleable than typical presentation software slides 20 | 21 | - Refactor your slides as you do with code 22 | - Text is SCM-friendly, get a proper change history 23 | - Easy to update embedded code snippets (even automatically) 24 | - Re-purpose your slides easily (blog posts, docs, …) 25 | 26 | - Embedded presenter console (2nd window with notes + timer + slide previews) 27 | - Live-reload preview during authoring 28 | - Convert to PDF to get a single-file document 29 | 30 | For authoring, you need Python3 + bash (Linux, Mac OSX, Babun, or Windows 10). 31 | 32 | 33 | Features 34 | -------- 35 | 36 | - Selection of a few standard licences (CC0, CC-BY-SA-4, …) 37 | - Default CSS for common styling needs 38 | 39 | - Floating images 40 | - Image-only slides 41 | - 2-column slide layout 42 | - Styled bullet points 43 | - An optional global notice (at the bottom of the screen) 44 | 45 | The `demo slides`_ rendered from `reStructuredText`_ look like this: 46 | 47 | .. figure:: https://raw.githubusercontent.com/Springerle/hovercraft-slides/master/assets/slides.jpg 48 | :alt: Demo slide thumbnails 49 | 50 | Demo slide thumbnails 51 | 52 | 53 | Usage 54 | ----- 55 | 56 | Quick Test of the Default Slides 57 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 58 | 59 | To directly view the demo slides that come with this template, execute 60 | these commands: 61 | 62 | .. code:: sh 63 | 64 | git clone "https://github.com/Springerle/hovercraft-slides.git" 65 | cd hovercraft-slides 66 | . .env --yes && invoke test 67 | 68 | After a while, a browser tab should open with the rendered presentation. 69 | 70 | 71 | Create Your Own Slide Set 72 | ~~~~~~~~~~~~~~~~~~~~~~~~~ 73 | 74 | To use this template, refer to the `Cookiecutter documentation`_ 75 | – it was tested with Cookiecutter 1.4.0. Here's a way to install it 76 | to its own virtualenv: 77 | 78 | .. code:: sh 79 | 80 | mkdir -p ~/bin ~/.local/venvs 81 | test -d ~/.local/venvs/cookiecutter \ 82 | || virtualenv ~/.local/venvs/cookiecutter 83 | ~/.local/venvs/cookiecutter/bin/pip install -U pip setuptools wheel 84 | ~/.local/venvs/cookiecutter/bin/pip install -U cookiecutter 85 | ln -s ~/.local/venvs/cookiecutter/bin/cookiecutter ~/bin 86 | which cookiecutter || exec $SHELL -l 87 | cookiecutter --version 88 | 89 | You also need Python3 installed 90 | on the machine you're authoring your slide set on, with a working 91 | ``pyvenv`` command. After you created your new slide project, these 92 | commands install all tools and open a browser tab with the rendered 93 | ``index.rst`` slide set: 94 | 95 | .. code:: sh 96 | 97 | . .env --yes 98 | invoke view 99 | 100 | Note that ``invoke view`` starts a watchdog process that will react to 101 | any changes in ``*.rst`` files by rendering and reloading the open 102 | browser tab (*live reload*). 103 | 104 | See the `template's README`_ for more details on available tasks. 105 | 106 | 107 | Add Your Own Logo 108 | ~~~~~~~~~~~~~~~~~ 109 | 110 | The logo that appears on the right of slide titles is in the 111 | ``img/title-logo.png`` file. Keep it roughly the same height at ``72px`` 112 | – if your logo is not square, you should increase the ``padding-right`` 113 | value of ``80px`` for ``h1`` accordingly (in the first section of 114 | ``css/default.css``). 115 | 116 | 117 | Add Your Custom CSS 118 | ~~~~~~~~~~~~~~~~~~~ 119 | 120 | If you want to add you own CSS styles, place them in the ``css/custom.css`` file, 121 | *after* the import of the defaults:: 122 | 123 | @import url('default.css'); 124 | 125 | The ``:css:`` field in ``index.rst`` points to that custom CSS file. 126 | Doing it that way ensures you can easily update the defaults later on, 127 | by just overwriting ``default.css`` with a new version from the template. 128 | 129 | And in case you're adding some CSS rules useful to a wider audience, PRs are always welcome. 130 | 131 | 132 | .. |Groups| image:: https://img.shields.io/badge/Google_groups-springerle--users-orange.svg 133 | :target: https://groups.google.com/forum/#!forum/springerle-users 134 | .. |CC0 licensed| image:: http://img.shields.io/badge/license-CC0-red.svg 135 | :target: https://raw.githubusercontent.com/Springerle/hovercraft-slides/master/LICENSE 136 | .. |GitHub Issues| image:: https://img.shields.io/github/issues/Springerle/hovercraft-slides.svg 137 | :target: https://github.com/Springerle/hovercraft-slides/issues 138 | 139 | .. _`Hovercraft!`: https://hovercraft.readthedocs.io/ 140 | .. _`impress.js`: https://github.com/impress/impress.js 141 | .. _`reStructuredText`: http://docutils.sourceforge.net/rst.html 142 | .. _`Cookiecutter documentation`: https://cookiecutter.readthedocs.io/en/latest/usage.html 143 | .. _`demo slides`: https://raw.githubusercontent.com/Springerle/hovercraft-slides/master/%7B%7Bcookiecutter.repo_name%7D%7D/index.rst 144 | .. _`template's README`: https://github.com/Springerle/hovercraft-slides/blob/master/%7B%7Bcookiecutter.repo_name%7D%7D/README.rst 145 | -------------------------------------------------------------------------------- /assets/slides.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Springerle/hovercraft-slides/b007075ead9f6b4cb451ed53422d7ec4a4529296/assets/slides.jpg -------------------------------------------------------------------------------- /cookiecutter.json: -------------------------------------------------------------------------------- 1 | { 2 | "full_name": "Joe Schmoe" 3 | , "github_username": "{{ (cookiecutter.full_name[0] + cookiecutter.full_name.split(None, 1)[1]) | lower | replace(' ', '') }}" 4 | , "email": "{{ cookiecutter.github_username }}@example.com" 5 | , "project_name": "Present with Hovercraft!" 6 | , "global_notice": "Licensed under Creative Commons \"CC0\"" 7 | , "repo_name": "{{ cookiecutter.project_name | lower | replace(' ', '-') | replace('!', '') }}-slides" 8 | , "version": "1.0" 9 | , "year": "2016" 10 | , "license": "CC0" 11 | , "github_url": "https://github.com/{{ cookiecutter.github_username }}/{{ cookiecutter.repo_name }}" 12 | , "url": "{{ cookiecutter.github_url }}" 13 | } 14 | -------------------------------------------------------------------------------- /pytest.ini: -------------------------------------------------------------------------------- 1 | # 2 | # Configuration for py.test 3 | # 4 | 5 | [pytest] 6 | norecursedirs = .* *.egg *.egg-info bin dist include lib local share static docs 7 | python_files = tests/test_*.py 8 | addopts = --spec --color=yes --ignore {{cookiecutter.repo_name}} --ignore hovercraft-presentation-slides 9 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | # 2 | # Project's virtualenv requirements 3 | # 4 | 5 | ##pip<8 6 | invoke==0.21.0 7 | #rituals==0.3.0 8 | -e git+https://github.com/jhermann/rituals.git#egg=rituals 9 | cookiecutter>=1.3 10 | pytest==3.2.3 11 | pytest-spec==1.1.0 12 | 13 | hovercraft==2.4 14 | -------------------------------------------------------------------------------- /tasks.py: -------------------------------------------------------------------------------- 1 | # *- coding: utf-8 -*- 2 | # pylint: disable=locally-disabled, bad-continuation 3 | # pylint: disable=wildcard-import, unused-import, unused-wildcard-import 4 | # pylint: disable=superfluous-parens, redefined-builtin 5 | """ Project automation for Invoke. 6 | """ 7 | 8 | import os 9 | import sys 10 | import shlex 11 | import shutil 12 | import subprocess 13 | 14 | from rituals.easy import task, Collection, pushd 15 | from rituals.util import antglob, notify 16 | from rituals.acts.documentation import namespace as _docs 17 | 18 | ROOT_FOLDER = os.path.dirname(__file__) 19 | sys.path.insert(0, os.path.join(ROOT_FOLDER, "tests")) 20 | from conftest import TEST_FOLDER 21 | 22 | 23 | @task(help=dict( 24 | venv="Include an existing virtualenv (in '.venv')", 25 | extra="Any extra patterns, space-separated and possibly quoted", 26 | )) 27 | def clean(ctx, venv=False, extra=''): 28 | """Perform house-keeping.""" 29 | notify.banner("Cleaning up project files") 30 | 31 | # Stop any left-over watchdog 32 | if os.path.exists(TEST_FOLDER): 33 | ctx.run("cd {} && . .env && inv view --kill".format(TEST_FOLDER)) 34 | 35 | # Add patterns based on given parameters 36 | venv_dirs = ['.venv'] 37 | patterns = [TEST_FOLDER + '/', 'pip-selfcheck.json', '**/*~'] 38 | if venv: 39 | patterns.extend([i + '/' for i in venv_dirs]) 40 | if extra: 41 | patterns.extend(shlex.split(extra)) 42 | 43 | # Build fileset 44 | patterns = [antglob.includes(i) for i in patterns] 45 | if not venv: 46 | # Do not scan venv dirs when not cleaning them 47 | patterns.extend([antglob.excludes(i + '/') for i in venv_dirs]) 48 | fileset = antglob.FileSet('.', patterns) 49 | 50 | # Iterate over matches and remove them 51 | for name in fileset: 52 | notify.info('rm {0}'.format(name)) 53 | if name.endswith('/'): 54 | shutil.rmtree(name) 55 | else: 56 | os.unlink(name) 57 | 58 | 59 | @task(pre=[clean]) 60 | def test(ctx): 61 | """Perform integration tests.""" 62 | ctx.run("py.test") 63 | with pushd(TEST_FOLDER): 64 | assert os.path.exists('README.rst'), "README is created" 65 | ctx.run(". .env --yes") 66 | 67 | # Start preview in a clean shell environment 68 | subprocess.call('''bash -l -c "cd '{}/{}' && . .env && invoke view"''' 69 | .format(os.getcwd(), TEST_FOLDER), shell=True) 70 | 71 | 72 | namespace = Collection.from_module(sys.modules[__name__], name='') 73 | namespace.add_collection(_docs) 74 | -------------------------------------------------------------------------------- /tests/conftest.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # pylint: disable= 3 | """ py.test dynamic configuration. 4 | 5 | For details needed to understand these tests, refer to: 6 | https://pytest.org/ 7 | http://pythontesting.net/start-here/ 8 | """ 9 | from __future__ import absolute_import, unicode_literals 10 | 11 | import os 12 | import shutil 13 | import logging 14 | import subprocess 15 | 16 | import pytest 17 | 18 | 19 | TEST_FOLDER = 'present-with-hovercraft-slides' 20 | 21 | 22 | # Globally available fixtures 23 | @pytest.fixture(scope='session') 24 | def project(): 25 | """ Materialize cookiecutter template (once). 26 | 27 | The fixture contains the abspath of the created workdir. 28 | """ 29 | if os.path.exists(TEST_FOLDER): 30 | shutil.rmtree(TEST_FOLDER) 31 | subprocess.check_call(['cookiecutter', '--no-input', '.']) 32 | 33 | return os.path.abspath(TEST_FOLDER) 34 | 35 | 36 | @pytest.fixture(scope='session') 37 | def logger(): 38 | """Test logger instance as a fixture.""" 39 | logging.basicConfig(level=logging.DEBUG) 40 | return logging.getLogger('tests') 41 | -------------------------------------------------------------------------------- /tests/test_project.py: -------------------------------------------------------------------------------- 1 | # *- coding: utf-8 -*- 2 | # pylint: disable=missing-docstring, bad-continuation 3 | """ Test the template. 4 | """ 5 | from __future__ import absolute_import, unicode_literals 6 | 7 | import io 8 | import os 9 | 10 | 11 | def test_project_basedir_was_created(project): 12 | assert os.path.exists(project), "Project was created" 13 | assert os.path.isdir(project), "Project base directory was created" 14 | 15 | 16 | def test_readme_has_github_url_and_newline_at_end(project): 17 | with io.open(project + '/README.rst', encoding='utf-8') as handle: 18 | readme = handle.read() 19 | 20 | # TODO: cookiecutter needs a --no-rc option, so we'll always get 'jschmoe' 21 | assert any("https://github.com/{}/{}".format(i, os.path.basename(project)) in readme 22 | for i in ('jschmoe', 'jhermann')), "README contains repo URL" 23 | assert readme.endswith('\n'), "README has the newline at end of file" 24 | -------------------------------------------------------------------------------- /{{cookiecutter.repo_name}}/.env: -------------------------------------------------------------------------------- 1 | # /bin/bash - help gedit syntax highlight do the right thing 2 | # autoenv script (https://github.com/kennethreitz/autoenv) 3 | # Use ". .env -h" for help. 4 | _venv_virtualenv=/usr/bin/virtualenv 5 | _venv_py3=true 6 | _venv_pip_req='pip<8' 7 | _venv_master_url="https://raw.githubusercontent.com/Springerle/py-generic-project/master/.env" 8 | 9 | _venv_temp=/usr/local/bin/virtualenv 10 | if test ! -x $_venv_virtualenv -a -x $_venv_temp; then 11 | _venv_virtualenv=$_venv_temp 12 | fi 13 | test "${_venv_py3:0:2}" != '{''{' || _venv_py3=false 14 | if $_venv_py3; then # Python 3 mode? 15 | # /opt/pyenv, see https://github.com/jhermann/priscilla/tree/master/pyenv 16 | _venv_temp=/opt/pyenv/bin/pyvenv-3 17 | if test -x $_venv_temp; then 18 | _venv_virtualenv=$_venv_temp 19 | fi 20 | # python3 venv in recent Debian / Ubuntu 21 | for _venv_temp in 3 3.6 3.5 3.4 3.3; do 22 | _venv_temp=/usr/bin/pyvenv-$_venv_temp 23 | if test -x $_venv_temp; then 24 | _venv_virtualenv=$_venv_temp 25 | break 26 | fi 27 | done 28 | fi 29 | 30 | _venv_readlink=readlink 31 | case "$(uname -s)" in 32 | # See http://en.wikipedia.org/wiki/Uname#Examples 33 | Darwin) 34 | _venv_temp="/usr/local/opt/coreutils/libexec/gnubin/readlink" 35 | if test -x $_venv_temp; then 36 | _venv_readlink=$_venv_temp 37 | else 38 | echo "*** No readlink command, do a 'brew install coreutils'..." 39 | return 1 40 | fi 41 | ;; 42 | CYGWIN*) 43 | ;; 44 | Linux|*) 45 | ;; 46 | esac 47 | 48 | test -z "$JENKINS_URL" || echo '***' "\$0=$0" "\$BASH_SOURCE=${BASH_SOURCE[0]}" 49 | if test "$0" = "-bash" -o "$(basename -- "$0")" = "bash" -o "$(basename -- "${BASH_SOURCE[0]}")" = ".env"; then 50 | _venv_script=$($_venv_readlink -f ${BASH_SOURCE[0]}) 51 | else 52 | _venv_script="/dont-know-really/.env" 53 | fi 54 | _venv_xtrace=$(set +o | grep xtrace) 55 | set +x 56 | _venv_name="$(basename $(pwd))" 57 | 58 | _venv_pip_log() { 59 | if $_venv_verbose; then 60 | cat 61 | else 62 | egrep "Found.existing.installation|Collecting|Installing.collected.packages|Searching.for|Installed|Finished" 63 | fi 64 | } 65 | 66 | # command line flags, mostly for CI server usage 67 | _venv_create=false 68 | _venv_develop=false 69 | _venv_verbose=false 70 | while test "${1:0:1}" = "-"; do 71 | case "$1" in 72 | --yes) _venv_create=true ;; 73 | --develop) _venv_develop=true ;; 74 | --verbose | -v) _venv_verbose=true ;; 75 | --virtualenv) shift; _venv_virtualenv="$1" ;; 76 | --help | -h) 77 | # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 78 | echo "usage: . $_venv_script " 79 | echo 80 | echo "Create and/or activate a Python virtual environment. Updates pip and other" 81 | echo "system packages, and recognizes 'dev-requirements.txt' and 'setup.py' or" 82 | echo "'requirements.txt'." 83 | echo 84 | echo "This is an autoenv script (see https://github.com/kennethreitz/autoenv)." 85 | echo 86 | echo "Options:" 87 | echo " --yes create missing venv without prompting" 88 | echo " --develop call 'develop -U' on activation" 89 | echo " -v | --verbose don't filter install logs on terminal" 90 | echo " (full logs in '.venv/pip-install.log')" 91 | echo " -h | --help this help message" 92 | echo " --virtualenv PATH set explicit path of virtualenv binary to use" 93 | echo " --update update to the newest master copy" 94 | return 1 95 | ;; 96 | --update) 97 | python -c "import urllib2; open('/tmp/$USER-py-env-update.py', 'wb').write(urllib2.urlopen('$_venv_master_url').read())" 98 | grep "unset _venv_xtrace" "/tmp/$USER-py-env-update.py" >/dev/null || return 1 99 | sed -r -e "s/^_venv_py3=.*/_venv_py3=$_venv_py3/" <"/tmp/$USER-py-env-update.py" >.env 100 | git diff .env || : 101 | echo ".env script update OK" 102 | return 0 103 | ;; 104 | *) echo "WARNING: Ignored unknown option '$1'" ;; 105 | esac 106 | shift 107 | done 108 | 109 | # Outside the tree of the .env script? 110 | if pwd | egrep -v '^'$(dirname "$_venv_script")'($|/)' >/dev/null; then 111 | : echo Outside "[$0 $1 ; $_venv_script]" 112 | 113 | # Inside the tree of the .env script, but have another local '.env'? 114 | elif test \! -f .env -o "$_venv_script" != "$(pwd)/.env"; then 115 | : echo Inside "[$0 $1 ; $_venv_script]" 116 | 117 | # Only try virtualenv creation outside of template dirs; the egrep pattern is escaped for hiding it from Jinja2 118 | elif pwd | egrep -v '/{''{''.*''}''}(/|$)' >/dev/null || $_venv_create; then 119 | test -f ".env" && _venv_ask=true || _venv_ask=false 120 | 121 | # Look for existing venv at common locations 122 | for _venv_base in .venv venv . ..; do 123 | if test -f "$_venv_base/$_venv_name/bin/activate"; then 124 | deactivate 2>/dev/null || : 125 | . "$_venv_base/$_venv_name/bin/activate" 126 | if test -f setup.py; then 127 | $_venv_develop && python setup.py -q develop -U || : 128 | python setup.py --name --version --url | tr \\n \\0 \ 129 | | xargs -0 printf "*** Activated %s %s @ %s\\n" || : 130 | else 131 | echo "*** Activated $_venv_base/$_venv_name" 132 | fi 133 | _venv_ask=false 134 | break 135 | fi 136 | done 137 | 138 | if $_venv_ask && test \! -d .venv; then 139 | $_venv_create || { read -p "No virtualenv found, shall I create one for you? [Y/n] " -n 1 -r || REPLY='n'; echo; } 140 | if $_venv_create || [[ $REPLY =~ ^[Yy]?$ ]]; then 141 | # Create, activate, and update virtualenv 142 | $_venv_virtualenv ".venv/$_venv_name" 143 | . ".venv/$_venv_name/bin/activate" 144 | ".venv/$_venv_name/bin/pip" --log ".venv/pip-install.log" install -U "$_venv_pip_req" 2>&1 | _venv_pip_log \ 145 | || echo >&2 "!!! pip failed, see .venv/pip-install.log for details" 146 | ".venv/$_venv_name/bin/pip" --log ".venv/pip-install.log" install -U "setuptools>=14.3" "wheel>=0.24.0" 2>&1 | _venv_pip_log \ 147 | || echo >&2 "!!! pip failed, see .venv/pip-install.log for details" 148 | 149 | # Get rid of cruft some older systems produce 150 | ".venv/$_venv_name/bin/pip" --log ".venv/pip-install.log" uninstall --yes distribute >/dev/null 2>&1 || : 151 | 152 | # pypandoc fails when the base package is missing, so we install it here, if possible 153 | if which pandoc >/dev/null ; then 154 | ".venv/$_venv_name/bin/pip" --log ".venv/pip-install.log" install pypandoc 2>&1 | _venv_pip_log \ 155 | || echo >&2 "!!! pip failed, see .venv/pip-install.log for details" 156 | fi 157 | 158 | # Install development + project dependencies 159 | if test -f dev-requirements.txt; then 160 | ".venv/$_venv_name/bin/pip" --log ".venv/pip-install.log" install -r dev-requirements.txt 2>&1 | _venv_pip_log \ 161 | || echo >&2 "!!! pip failed, see .venv/pip-install.log for details" 162 | fi 163 | if test -f setup.py; then 164 | ".venv/$_venv_name/bin/python" setup.py develop -U 2>&1 | _venv_pip_log 165 | echo 166 | ".venv/$_venv_name/bin/python" setup.py --name --version --author --author-email --license --description --url \ 167 | | tr \\n \\0 | xargs -0 printf "%s %s by %s <%s> [%s]\\n%s\\n%s\\n" || : 168 | else 169 | echo 170 | if test -f requirements.txt; then 171 | echo "*** No 'setup.py' found, installing requirements..." 172 | ".venv/$_venv_name/bin/pip" --log ".venv/pip-install.log" install -r requirements.txt 2>&1 | _venv_pip_log 173 | else 174 | echo "*** No 'setup.py' or 'requirements.txt' found, all done." 175 | fi 176 | fi 177 | else 178 | # prevent constant prompting 179 | mkdir -p .venv 180 | fi 181 | fi 182 | fi 183 | 184 | unset _venv_script _venv_name _venv_ask _venv_create _venv_develop _venv_pip_log _venv_base 185 | unset _venv_readlink _venv_temp _venv_virtualenv _venv_verbose _venv_py3 _venv_pip_req _venv_master_url 186 | eval $_venv_xtrace # restore xtrace state 187 | unset _venv_xtrace 188 | -------------------------------------------------------------------------------- /{{cookiecutter.repo_name}}/.gitignore: -------------------------------------------------------------------------------- 1 | *.html 2 | _html/ 3 | -------------------------------------------------------------------------------- /{{cookiecutter.repo_name}}/LICENSE.rst: -------------------------------------------------------------------------------- 1 | .. Check CC-BY-SA-4 {% if "cc-by-sa-4" == cookiecutter.license.lower() %} 2 | 3 | Creative Commons Attribution-ShareAlike 4.0 International Public License 4 | ======================================================================== 5 | 6 | By exercising the Licensed Rights (defined below), You accept and agree 7 | to be bound by the terms and conditions of this Creative Commons 8 | Attribution-ShareAlike 4.0 International Public License ("Public 9 | License"). To the extent this Public License may be interpreted as a 10 | contract, You are granted the Licensed Rights in consideration of Your 11 | acceptance of these terms and conditions, and the Licensor grants You 12 | such rights in consideration of benefits the Licensor receives from 13 | making the Licensed Material available under these terms and conditions. 14 | 15 | Section 1 – Definitions. 16 | ------------------------ 17 | 18 | #. **Adapted Material** means material subject to Copyright and Similar 19 | Rights that is derived from or based upon the Licensed Material and 20 | in which the Licensed Material is translated, altered, arranged, 21 | transformed, or otherwise modified in a manner requiring permission 22 | under the Copyright and Similar Rights held by the Licensor. For 23 | purposes of this Public License, where the Licensed Material is a 24 | musical work, performance, or sound recording, Adapted Material is 25 | always produced where the Licensed Material is synched in timed 26 | relation with a moving image. 27 | #. **Adapter's License** means the license You apply to Your Copyright 28 | and Similar Rights in Your contributions to Adapted Material in 29 | accordance with the terms and conditions of this Public License. 30 | #. **BY-SA Compatible License** means a license listed at 31 | `creativecommons.org/compatiblelicenses `__, 32 | approved by Creative Commons as essentially the equivalent of this 33 | Public License. 34 | #. **Copyright and Similar Rights** means copyright and/or similar 35 | rights closely related to copyright including, without limitation, 36 | performance, broadcast, sound recording, and Sui Generis Database 37 | Rights, without regard to how the rights are labeled or categorized. 38 | For purposes of this Public License, the rights specified in Section 39 | `2(b)(1)-(2) `__ 40 | are not Copyright and Similar Rights. 41 | #. **Effective Technological Measures** means those measures that, in 42 | the absence of proper authority, may not be circumvented under laws 43 | fulfilling obligations under Article 11 of the WIPO Copyright Treaty 44 | adopted on December 20, 1996, and/or similar international 45 | agreements. 46 | #. **Exceptions and Limitations** means fair use, fair dealing, and/or 47 | any other exception or limitation to Copyright and Similar Rights 48 | that applies to Your use of the Licensed Material. 49 | #. **License Elements** means the license attributes listed in the name 50 | of a Creative Commons Public License. The License Elements of this 51 | Public License are Attribution and ShareAlike. 52 | #. **Licensed Material** means the artistic or literary work, database, 53 | or other material to which the Licensor applied this Public License. 54 | #. **Licensed Rights** means the rights granted to You subject to the 55 | terms and conditions of this Public License, which are limited to all 56 | Copyright and Similar Rights that apply to Your use of the Licensed 57 | Material and that the Licensor has authority to license. 58 | #. **Licensor** means the individual(s) or entity(ies) granting rights 59 | under this Public License. 60 | #. **Share** means to provide material to the public by any means or 61 | process that requires permission under the Licensed Rights, such as 62 | reproduction, public display, public performance, distribution, 63 | dissemination, communication, or importation, and to make material 64 | available to the public including in ways that members of the public 65 | may access the material from a place and at a time individually 66 | chosen by them. 67 | #. **Sui Generis Database Rights** means rights other than copyright 68 | resulting from Directive 96/9/EC of the European Parliament and of 69 | the Council of 11 March 1996 on the legal protection of databases, as 70 | amended and/or succeeded, as well as other essentially equivalent 71 | rights anywhere in the world. 72 | #. **You** means the individual or entity exercising the Licensed Rights 73 | under this Public License. **Your** has a corresponding meaning. 74 | 75 | Section 2 – Scope. 76 | ------------------ 77 | 78 | #. **License grant**. 79 | 80 | #. Subject to the terms and conditions of this Public License, the 81 | Licensor hereby grants You a worldwide, royalty-free, 82 | non-sublicensable, non-exclusive, irrevocable license to exercise 83 | the Licensed Rights in the Licensed Material to: 84 | 85 | #. reproduce and Share the Licensed Material, in whole or in part; 86 | and 87 | #. produce, reproduce, and Share Adapted Material. 88 | 89 | #. Exceptions and Limitations. For the avoidance of doubt, where 90 | Exceptions and Limitations apply to Your use, this Public License 91 | does not apply, and You do not need to comply with its terms and 92 | conditions. 93 | #. Term. The term of this Public License is specified in Section 94 | `6(a) `__. 95 | #. Media and formats; technical modifications allowed. The Licensor 96 | authorizes You to exercise the Licensed Rights in all media and 97 | formats whether now known or hereafter created, and to make 98 | technical modifications necessary to do so. The Licensor waives 99 | and/or agrees not to assert any right or authority to forbid You 100 | from making technical modifications necessary to exercise the 101 | Licensed Rights, including technical modifications necessary to 102 | circumvent Effective Technological Measures. For purposes of this 103 | Public License, simply making modifications authorized by this 104 | Section 105 | `2(a)(4) `__ 106 | never produces Adapted Material. 107 | #. Downstream recipients. 108 | 109 | #. Offer from the Licensor – Licensed Material. Every recipient of 110 | the Licensed Material automatically receives an offer from the 111 | Licensor to exercise the Licensed Rights under the terms and 112 | conditions of this Public License. 113 | #. Additional offer from the Licensor – Adapted Material. Every 114 | recipient of Adapted Material from You automatically receives 115 | an offer from the Licensor to exercise the Licensed Rights in 116 | the Adapted Material under the conditions of the Adapter’s 117 | License You apply. 118 | #. No downstream restrictions. You may not offer or impose any 119 | additional or different terms or conditions on, or apply any 120 | Effective Technological Measures to, the Licensed Material if 121 | doing so restricts exercise of the Licensed Rights by any 122 | recipient of the Licensed Material. 123 | 124 | #. No endorsement. Nothing in this Public License constitutes or may 125 | be construed as permission to assert or imply that You are, or 126 | that Your use of the Licensed Material is, connected with, or 127 | sponsored, endorsed, or granted official status by, the Licensor 128 | or others designated to receive attribution as provided in Section 129 | `3(a)(1)(A)(i) `__. 130 | 131 | #. **Other rights**. 132 | 133 | #. Moral rights, such as the right of integrity, are not licensed 134 | under this Public License, nor are publicity, privacy, and/or 135 | other similar personality rights; however, to the extent possible, 136 | the Licensor waives and/or agrees not to assert any such rights 137 | held by the Licensor to the limited extent necessary to allow You 138 | to exercise the Licensed Rights, but not otherwise. 139 | #. Patent and trademark rights are not licensed under this Public 140 | License. 141 | #. To the extent possible, the Licensor waives any right to collect 142 | royalties from You for the exercise of the Licensed Rights, 143 | whether directly or through a collecting society under any 144 | voluntary or waivable statutory or compulsory licensing scheme. In 145 | all other cases the Licensor expressly reserves any right to 146 | collect such royalties. 147 | 148 | Section 3 – License Conditions. 149 | ------------------------------- 150 | 151 | Your exercise of the Licensed Rights is expressly made subject to the 152 | following conditions. 153 | 154 | #. **Attribution**. 155 | 156 | #. If You Share the Licensed Material (including in modified form), 157 | You must: 158 | 159 | #. retain the following if it is supplied by the Licensor with the 160 | Licensed Material: 161 | 162 | #. identification of the creator(s) of the Licensed Material 163 | and any others designated to receive attribution, in any 164 | reasonable manner requested by the Licensor (including by 165 | pseudonym if designated); 166 | #. a copyright notice; 167 | #. a notice that refers to this Public License; 168 | #. a notice that refers to the disclaimer of warranties; 169 | #. a URI or hyperlink to the Licensed Material to the extent 170 | reasonably practicable; 171 | 172 | #. indicate if You modified the Licensed Material and retain an 173 | indication of any previous modifications; and 174 | #. indicate the Licensed Material is licensed under this Public 175 | License, and include the text of, or the URI or hyperlink to, 176 | this Public License. 177 | 178 | #. You may satisfy the conditions in Section 179 | `3(a)(1) `__ 180 | in any reasonable manner based on the medium, means, and context 181 | in which You Share the Licensed Material. For example, it may be 182 | reasonable to satisfy the conditions by providing a URI or 183 | hyperlink to a resource that includes the required information. 184 | #. If requested by the Licensor, You must remove any of the 185 | information required by Section 186 | `3(a)(1)(A) `__ 187 | to the extent reasonably practicable. 188 | 189 | #. **ShareAlike**. 190 | 191 | In addition to the conditions in Section 192 | `3(a) `__, 193 | if You Share Adapted Material You produce, the following conditions 194 | also apply. 195 | 196 | #. The Adapter’s License You apply must be a Creative Commons license 197 | with the same License Elements, this version or later, or a BY-SA 198 | Compatible License. 199 | #. You must include the text of, or the URI or hyperlink to, the 200 | Adapter's License You apply. You may satisfy this condition in any 201 | reasonable manner based on the medium, means, and context in which 202 | You Share Adapted Material. 203 | #. You may not offer or impose any additional or different terms or 204 | conditions on, or apply any Effective Technological Measures to, 205 | Adapted Material that restrict exercise of the rights granted 206 | under the Adapter's License You apply. 207 | 208 | Section 4 – Sui Generis Database Rights. 209 | ---------------------------------------- 210 | 211 | Where the Licensed Rights include Sui Generis Database Rights that apply 212 | to Your use of the Licensed Material: 213 | 214 | #. for the avoidance of doubt, Section 215 | `2(a)(1) `__ 216 | grants You the right to extract, reuse, reproduce, and Share all or a 217 | substantial portion of the contents of the database; 218 | #. if You include all or a substantial portion of the database contents 219 | in a database in which You have Sui Generis Database Rights, then the 220 | database in which You have Sui Generis Database Rights (but not its 221 | individual contents) is Adapted Material, including for purposes of 222 | Section 223 | `3(b) `__; 224 | and 225 | #. You must comply with the conditions in Section 226 | `3(a) `__ 227 | if You Share all or a substantial portion of the contents of the 228 | database. 229 | 230 | For the avoidance of doubt, this Section 231 | `4 `__ 232 | supplements and does not replace Your obligations under this Public 233 | License where the Licensed Rights include other Copyright and Similar 234 | Rights. 235 | 236 | Section 5 – Disclaimer of Warranties and Limitation of Liability. 237 | ----------------------------------------------------------------- 238 | 239 | #. **Unless otherwise separately undertaken by the Licensor, to the 240 | extent possible, the Licensor offers the Licensed Material as-is and 241 | as-available, and makes no representations or warranties of any kind 242 | concerning the Licensed Material, whether express, implied, 243 | statutory, or other. This includes, without limitation, warranties of 244 | title, merchantability, fitness for a particular purpose, 245 | non-infringement, absence of latent or other defects, accuracy, or 246 | the presence or absence of errors, whether or not known or 247 | discoverable. Where disclaimers of warranties are not allowed in full 248 | or in part, this disclaimer may not apply to You.** 249 | #. **To the extent possible, in no event will the Licensor be liable to 250 | You on any legal theory (including, without limitation, negligence) 251 | or otherwise for any direct, special, indirect, incidental, 252 | consequential, punitive, exemplary, or other losses, costs, expenses, 253 | or damages arising out of this Public License or use of the Licensed 254 | Material, even if the Licensor has been advised of the possibility of 255 | such losses, costs, expenses, or damages. Where a limitation of 256 | liability is not allowed in full or in part, this limitation may not 257 | apply to You.** 258 | 259 | #. The disclaimer of warranties and limitation of liability provided 260 | above shall be interpreted in a manner that, to the extent possible, 261 | most closely approximates an absolute disclaimer and waiver of all 262 | liability. 263 | 264 | Section 6 – Term and Termination. 265 | --------------------------------- 266 | 267 | #. This Public License applies for the term of the Copyright and Similar 268 | Rights licensed here. However, if You fail to comply with this Public 269 | License, then Your rights under this Public License terminate 270 | automatically. 271 | #. Where Your right to use the Licensed Material has terminated under 272 | Section 273 | `6(a) `__, 274 | it reinstates: 275 | 276 | #. automatically as of the date the violation is cured, provided it 277 | is cured within 30 days of Your discovery of the violation; or 278 | #. upon express reinstatement by the Licensor. 279 | 280 | For the avoidance of doubt, this Section 281 | `6(b) `__ 282 | does not affect any right the Licensor may have to seek remedies for 283 | Your violations of this Public License. 284 | 285 | #. For the avoidance of doubt, the Licensor may also offer the Licensed 286 | Material under separate terms or conditions or stop distributing the 287 | Licensed Material at any time; however, doing so will not terminate 288 | this Public License. 289 | #. Sections 290 | `1 `__, 291 | `5 `__, 292 | `6 `__, 293 | `7 `__, 294 | and 295 | `8 `__ 296 | survive termination of this Public License. 297 | 298 | Section 7 – Other Terms and Conditions. 299 | --------------------------------------- 300 | 301 | #. The Licensor shall not be bound by any additional or different terms 302 | or conditions communicated by You unless expressly agreed. 303 | #. Any arrangements, understandings, or agreements regarding the 304 | Licensed Material not stated herein are separate from and independent 305 | of the terms and conditions of this Public License. 306 | 307 | Section 8 – Interpretation. 308 | --------------------------- 309 | 310 | #. For the avoidance of doubt, this Public License does not, and shall 311 | not be interpreted to, reduce, limit, restrict, or impose conditions 312 | on any use of the Licensed Material that could lawfully be made 313 | without permission under this Public License. 314 | #. To the extent possible, if any provision of this Public License is 315 | deemed unenforceable, it shall be automatically reformed to the 316 | minimum extent necessary to make it enforceable. If the provision 317 | cannot be reformed, it shall be severed from this Public License 318 | without affecting the enforceability of the remaining terms and 319 | conditions. 320 | #. No term or condition of this Public License will be waived and no 321 | failure to comply consented to unless expressly agreed to by the 322 | Licensor. 323 | #. Nothing in this Public License constitutes or may be interpreted as a 324 | limitation upon, or waiver of, any privileges and immunities that 325 | apply to the Licensor or You, including from the legal processes of 326 | any jurisdiction or authority. 327 | 328 | Creative Commons is not a party to its public licenses. Notwithstanding, Creative Commons may elect to apply one of its public licenses to material it publishes and in those instances will be considered the “Licensor.” The text of the Creative Commons public licenses is dedicated to the public domain under the `CC0 Public Domain Dedication `__. Except for the limited purpose of indicating that material is shared under a Creative Commons public license or as otherwise permitted by the Creative Commons policies published at `creativecommons.org/policies `__, Creative Commons does not authorize the use of the trademark “Creative Commons” or any other trademark or logo of Creative Commons without its prior written consent including, without limitation, in connection with any unauthorized modifications to any of its public licenses or any other arrangements, understandings, or agreements concerning use of licensed material. For the avoidance of doubt, this paragraph does not form part of the public licenses. 329 | 330 | Creative Commons may be contacted at `creativecommons.org `__. 331 | 332 | .. End CC-BY-SA-4 {% else -%} 333 | 334 | .. Default is CC0 335 | 336 | CC0 1.0 Universal 337 | ================= 338 | 339 | Statement of Purpose 340 | -------------------- 341 | 342 | The laws of most jurisdictions throughout the world automatically confer 343 | exclusive Copyright and Related Rights (defined below) upon the creator 344 | and subsequent owner(s) (each and all, an "owner") of an original work 345 | of authorship and/or a database (each, a "Work"). 346 | 347 | Certain owners wish to permanently relinquish those rights to a Work for 348 | the purpose of contributing to a commons of creative, cultural and 349 | scientific works ("Commons") that the public can reliably and without 350 | fear of later claims of infringement build upon, modify, incorporate in 351 | other works, reuse and redistribute as freely as possible in any form 352 | whatsoever and for any purposes, including without limitation commercial 353 | purposes. These owners may contribute to the Commons to promote the 354 | ideal of a free culture and the further production of creative, cultural 355 | and scientific works, or to gain reputation or greater distribution for 356 | their Work in part through the use and efforts of others. 357 | 358 | For these and/or other purposes and motivations, and without any 359 | expectation of additional consideration or compensation, the person 360 | associating CC0 with a Work (the "Affirmer"), to the extent that he or 361 | she is an owner of Copyright and Related Rights in the Work, voluntarily 362 | elects to apply CC0 to the Work and publicly distribute the Work under 363 | its terms, with knowledge of his or her Copyright and Related Rights in 364 | the Work and the meaning and intended legal effect of CC0 on those 365 | rights. 366 | 367 | **1. Copyright and Related Rights.** A Work made available under CC0 may 368 | be protected by copyright and related or neighboring rights ("Copyright 369 | and Related Rights"). Copyright and Related Rights include, but are not 370 | limited to, the following: 371 | 372 | #. the right to reproduce, adapt, distribute, perform, display, 373 | communicate, and translate a Work; 374 | #. moral rights retained by the original author(s) and/or performer(s); 375 | #. publicity and privacy rights pertaining to a person's image or 376 | likeness depicted in a Work; 377 | #. rights protecting against unfair competition in regards to a Work, 378 | subject to the limitations in paragraph 4(a), below; 379 | #. rights protecting the extraction, dissemination, use and reuse of 380 | data in a Work; 381 | #. database rights (such as those arising under Directive 96/9/EC of the 382 | European Parliament and of the Council of 11 March 1996 on the legal 383 | protection of databases, and under any national implementation 384 | thereof, including any amended or successor version of such 385 | directive); and 386 | #. other similar, equivalent or corresponding rights throughout the 387 | world based on applicable law or treaty, and any national 388 | implementations thereof. 389 | 390 | **2. Waiver.** To the greatest extent permitted by, but not in 391 | contravention of, applicable law, Affirmer hereby overtly, fully, 392 | permanently, irrevocably and unconditionally waives, abandons, and 393 | surrenders all of Affirmer's Copyright and Related Rights and associated 394 | claims and causes of action, whether now known or unknown (including 395 | existing as well as future claims and causes of action), in the Work (i) 396 | in all territories worldwide, (ii) for the maximum duration provided by 397 | applicable law or treaty (including future time extensions), (iii) in 398 | any current or future medium and for any number of copies, and (iv) for 399 | any purpose whatsoever, including without limitation commercial, 400 | advertising or promotional purposes (the "Waiver"). Affirmer makes the 401 | Waiver for the benefit of each member of the public at large and to the 402 | detriment of Affirmer's heirs and successors, fully intending that such 403 | Waiver shall not be subject to revocation, rescission, cancellation, 404 | termination, or any other legal or equitable action to disrupt the quiet 405 | enjoyment of the Work by the public as contemplated by Affirmer's 406 | express Statement of Purpose. 407 | 408 | **3. Public License Fallback.** Should any part of the Waiver for any 409 | reason be judged legally invalid or ineffective under applicable law, 410 | then the Waiver shall be preserved to the maximum extent permitted 411 | taking into account Affirmer's express Statement of Purpose. In 412 | addition, to the extent the Waiver is so judged Affirmer hereby grants 413 | to each affected person a royalty-free, non transferable, non 414 | sublicensable, non exclusive, irrevocable and unconditional license to 415 | exercise Affirmer's Copyright and Related Rights in the Work (i) in all 416 | territories worldwide, (ii) for the maximum duration provided by 417 | applicable law or treaty (including future time extensions), (iii) in 418 | any current or future medium and for any number of copies, and (iv) for 419 | any purpose whatsoever, including without limitation commercial, 420 | advertising or promotional purposes (the "License"). The License shall 421 | be deemed effective as of the date CC0 was applied by Affirmer to the 422 | Work. Should any part of the License for any reason be judged legally 423 | invalid or ineffective under applicable law, such partial invalidity or 424 | ineffectiveness shall not invalidate the remainder of the License, and 425 | in such case Affirmer hereby affirms that he or she will not (i) 426 | exercise any of his or her remaining Copyright and Related Rights in the 427 | Work or (ii) assert any associated claims and causes of action with 428 | respect to the Work, in either case contrary to Affirmer's express 429 | Statement of Purpose. 430 | 431 | **4. Limitations and Disclaimers.** 432 | 433 | #. No trademark or patent rights held by Affirmer are waived, abandoned, 434 | surrendered, licensed or otherwise affected by this document. 435 | #. Affirmer offers the Work as-is and makes no representations or 436 | warranties of any kind concerning the Work, express, implied, 437 | statutory or otherwise, including without limitation warranties of 438 | title, merchantability, fitness for a particular purpose, non 439 | infringement, or the absence of latent or other defects, accuracy, or 440 | the present or absence of errors, whether or not discoverable, all to 441 | the greatest extent permissible under applicable law. 442 | #. Affirmer disclaims responsibility for clearing rights of other 443 | persons that may apply to the Work or any use thereof, including 444 | without limitation any person's Copyright and Related Rights in the 445 | Work. Further, Affirmer disclaims responsibility for obtaining any 446 | necessary consents, permissions or other rights required for any use 447 | of the Work. 448 | #. Affirmer understands and acknowledges that Creative Commons is not a 449 | party to this document and has no duty or obligation with respect to 450 | this CC0 or use of the Work. 451 | 452 | For more information, please see 453 | `Creative Commons CC0 `__. 454 | 455 | .. End CC0 {% endif %} 456 | -------------------------------------------------------------------------------- /{{cookiecutter.repo_name}}/README.rst: -------------------------------------------------------------------------------- 1 | {% macro section(title, level='=') -%} 2 | {{ title }} 3 | {% for _ in title %}{{ level }}{% endfor %} 4 | {%- endmacro -%} 5 | {{ section(cookiecutter.project_name) }} 6 | 7 | .. contents:: **Table of Contents** 8 | 9 | 10 | Introduction 11 | ------------ 12 | 13 | This is a project for a `slide deck`_, which uses `reStructuredText`_ 14 | markup to create an `impress.js`_ presentation via the Python3 tool `Hovercraft!`_. 15 | 16 | .. _`slide deck`: index.rst 17 | 18 | 19 | Installation 20 | ------------ 21 | 22 | To create a working directory for this project, call these commands:: 23 | 24 | git clone "{{ cookiecutter.github_url }}.git" 25 | cd "{{ cookiecutter.repo_name }}" 26 | . .env --yes 27 | invoke view 28 | 29 | Python3 and ``pyvenv`` must be available for this to work. 30 | 31 | 32 | Usage 33 | ----- 34 | 35 | You can call the following tasks via ``invoke``. 36 | 37 | ======= ===================================================================== 38 | Task Description 39 | ======= ===================================================================== 40 | clean Perform house-keeping. 41 | html Build HTML tree. 42 | pdf Build PDF slide show. 43 | thumbs Create slide show thumbnails. 44 | upload Upload a ZIP of the slide deck (to a WebDAV URL, e.g. `Artifactory`_). 45 | view Start live-reload watchdog. 46 | ======= ===================================================================== 47 | 48 | Note that ``invoke view`` starts a watchdog process that will react to any 49 | changes in ``*.rst`` files by rendering and reloading the open browser tab (*live reload*). 50 | Use ``invoke view --kill`` to stop a running watchdog. 51 | 52 | The ``pdf`` task needs `deck2pdf`_ installed, and ``thumbs`` additionally requires *ImageMagick* 53 | (usually available via your operating system's packages). 54 | 55 | When using `Artifactory`_ to publish your rendered slides, note that appending 56 | ``!/index.html`` to the slide deck archive's URL makes the deck directly available 57 | in any browser. To provide the upload URL, put it into your shell environment like this:: 58 | 59 | export INVOKE_RITUALS_DOCS_UPLOAD_TARGETS_WEBDAV_URL=\ 60 | "https://repo.example.com/artifactory/wwwdata-local/slides/{name}/{version}/{name}-{version}.zip" 61 | 62 | The version will be ``latest`` by default, but you can provide an explicit one by passing the 63 | ``--release`` option to the ``upload`` task. 64 | 65 | Usually you also need to provide upload credentials, put them into the file ``~/.netrc`` similar to this:: 66 | 67 | machine repo.example.com 68 | user «your username» 69 | password «your personal API key» 70 | 71 | Call ``chmod 600 ~/.netrc`` afterwards, to protect your sensitive data. 72 | 73 | 74 | .. _`deck2pdf`: https://github.com/melix/deck2pdf 75 | .. _`impress.js`: https://github.com/impress/impress.js 76 | .. _`reStructuredText`: http://docutils.sourceforge.net/rst.html 77 | .. _`Hovercraft!`: https://hovercraft.readthedocs.io/ 78 | .. _`Artifactory`: https://www.jfrog.com/artifactory/ 79 | -------------------------------------------------------------------------------- /{{cookiecutter.repo_name}}/css/colorful.css: -------------------------------------------------------------------------------- 1 | /* https://raw.githubusercontent.com/richleland/pygments-css/master/colorful.css 2 | */ 3 | 4 | .highlight .bp { color: #007020 } /* Name.Builtin.Pseudo */ 5 | .highlight .c1 { color: #808080 } /* Comment.Single */ 6 | .highlight .c { color: #808080 } /* Comment */ 7 | .highlight .cm { color: #808080 } /* Comment.Multiline */ 8 | .highlight .cp { color: #507090 } /* Comment.Preproc */ 9 | .highlight .cs { color: #cc0000; font-weight: bold } /* Comment.Special */ 10 | .highlight .err { color: #F00000; background-color: #F0A0A0 } /* Error */ 11 | .highlight .gd { color: #A00000 } /* Generic.Deleted */ 12 | .highlight .ge { font-style: italic } /* Generic.Emph */ 13 | .highlight .gh { color: #000080; font-weight: bold } /* Generic.Heading */ 14 | .highlight .gi { color: #00A000 } /* Generic.Inserted */ 15 | .highlight .go { color: #808080 } /* Generic.Output */ 16 | .highlight .gp { color: #c65d09; font-weight: bold } /* Generic.Prompt */ 17 | .highlight .gr { color: #FF0000 } /* Generic.Error */ 18 | .highlight .gs { font-weight: bold } /* Generic.Strong */ 19 | .highlight .gt { color: #0040D0 } /* Generic.Traceback */ 20 | .highlight .gu { color: #800080; font-weight: bold } /* Generic.Subheading */ 21 | .highlight .hll { background-color: #ffffcc } 22 | .highlight .il { color: #0000D0; font-weight: bold } /* Literal.Number.Integer.Long */ 23 | .highlight .kc { color: #008000; font-weight: bold } /* Keyword.Constant */ 24 | .highlight .k { color: #008000; font-weight: bold } /* Keyword */ 25 | .highlight .kd { color: #008000; font-weight: bold } /* Keyword.Declaration */ 26 | .highlight .kn { color: #008000; font-weight: bold } /* Keyword.Namespace */ 27 | .highlight .kp { color: #003080; font-weight: bold } /* Keyword.Pseudo */ 28 | .highlight .kr { color: #008000; font-weight: bold } /* Keyword.Reserved */ 29 | .highlight .kt { color: #303090; font-weight: bold } /* Keyword.Type */ 30 | .highlight .m { color: #6000E0; font-weight: bold } /* Literal.Number */ 31 | .highlight .mf { color: #6000E0; font-weight: bold } /* Literal.Number.Float */ 32 | .highlight .mh { color: #005080; font-weight: bold } /* Literal.Number.Hex */ 33 | .highlight .mi { color: #0000D0; font-weight: bold } /* Literal.Number.Integer */ 34 | .highlight .mo { color: #4000E0; font-weight: bold } /* Literal.Number.Oct */ 35 | .highlight .na { color: #0000C0 } /* Name.Attribute */ 36 | .highlight .nb { color: #007020 } /* Name.Builtin */ 37 | .highlight .nc { color: #B00060; font-weight: bold } /* Name.Class */ 38 | .highlight .nd { color: #505050; font-weight: bold } /* Name.Decorator */ 39 | .highlight .ne { color: #F00000; font-weight: bold } /* Name.Exception */ 40 | .highlight .nf { color: #0060B0; font-weight: bold } /* Name.Function */ 41 | .highlight .ni { color: #800000; font-weight: bold } /* Name.Entity */ 42 | .highlight .nl { color: #907000; font-weight: bold } /* Name.Label */ 43 | .highlight .nn { color: #0e84b5; font-weight: bold } /* Name.Namespace */ 44 | .highlight .no { color: #003060; font-weight: bold } /* Name.Constant */ 45 | .highlight .nt { color: #007000 } /* Name.Tag */ 46 | .highlight .nv { color: #906030 } /* Name.Variable */ 47 | .highlight .o { color: #303030 } /* Operator */ 48 | .highlight .ow { color: #000000; font-weight: bold } /* Operator.Word */ 49 | .highlight .s1 { background-color: #fff0f0 } /* Literal.String.Single */ 50 | .highlight .s2 { background-color: #fff0f0 } /* Literal.String.Double */ 51 | .highlight .s { background-color: #fff0f0 } /* Literal.String */ 52 | .highlight .sb { background-color: #fff0f0 } /* Literal.String.Backtick */ 53 | .highlight .sc { color: #0040D0 } /* Literal.String.Char */ 54 | .highlight .sd { color: #D04020 } /* Literal.String.Doc */ 55 | .highlight .se { color: #606060; font-weight: bold; background-color: #fff0f0 } /* Literal.String.Escape */ 56 | .highlight .sh { background-color: #fff0f0 } /* Literal.String.Heredoc */ 57 | .highlight .si { background-color: #e0e0e0 } /* Literal.String.Interpol */ 58 | .highlight .sr { color: #000000; background-color: #fff0ff } /* Literal.String.Regex */ 59 | .highlight .ss { color: #A06000 } /* Literal.String.Symbol */ 60 | .highlight .sx { color: #D02000; background-color: #fff0f0 } /* Literal.String.Other */ 61 | .highlight .vc { color: #306090 } /* Name.Variable.Class */ 62 | .highlight .vg { color: #d07000; font-weight: bold } /* Name.Variable.Global */ 63 | .highlight .vi { color: #3030B0 } /* Name.Variable.Instance */ 64 | .highlight .w { color: #bbbbbb } /* Text.Whitespace */ 65 | -------------------------------------------------------------------------------- /{{cookiecutter.repo_name}}/css/custom.css: -------------------------------------------------------------------------------- 1 | /**************************************************************************** 2 | *** Your Own Presentation Styles 3 | ****************************************************************************/ 4 | 5 | @import url('default.css'); 6 | 7 | /* Add your custom rules here… */ 8 | -------------------------------------------------------------------------------- /{{cookiecutter.repo_name}}/css/default.css: -------------------------------------------------------------------------------- 1 | /**************************************************************************** 2 | *** Default Presentation Styles 3 | ****************************************************************************/ 4 | 5 | @import url('normalize.css'); 6 | @import url('colorful.css'); 7 | 8 | body, div.step { 9 | color: white; 10 | font-family: Verdana, Geneva, sans-serif; 11 | font-size: 20pt; 12 | width: 1100px; 13 | 14 | background: #111; 15 | background: radial-gradient(#225, #111); 16 | } 17 | 18 | {%- if cookiecutter.global_notice %} 19 | body:before { 20 | content: "{{ cookiecutter.global_notice | replace('"', '\\"') }}"; 21 | color: white; 22 | background-color: #911; 23 | font-family: Verdana, Sans; 24 | font-size: 30%; 25 | font-weight: 600; 26 | padding: 5px 10px; 27 | border-radius: 5px; 28 | text-align: center; 29 | display: block; 30 | position: absolute; 31 | bottom: .6em; 32 | left: 50%; 33 | text-align: middle; 34 | min-width: 60em; 35 | transform: translateX(-30em); 36 | } 37 | 38 | {%- endif %} 39 | h1, h2, h3, h4, h5 { 40 | font-family: "Times New Roman", Times, serif; 41 | background-color: #149; 42 | padding: 5px; 43 | padding-left: 10px; 44 | border-radius: 4px; 45 | box-shadow: 1px 1px #777; 46 | } 47 | h1 { 48 | background: #149 no-repeat right; 49 | background-image: url('../img/title-logo.png'); 50 | padding: 10px; 51 | padding-right: 80px; 52 | font-size: 170%; 53 | } 54 | h2 { 55 | font-size: 120%; 56 | } 57 | h3 { 58 | font-size: 110%; 59 | } 60 | h4 { 61 | font-size: 95%; 62 | } 63 | h5 { 64 | font-size: 90%; 65 | } 66 | 67 | 68 | /**************************************************************************** 69 | *** Slide Styles 70 | ****************************************************************************/ 71 | 72 | div.step { 73 | min-width: 1024px; 74 | min-height: 720px; 75 | } 76 | 77 | img.float-right { 78 | border: none; 79 | padding-left: 1rem; 80 | float: right; 81 | } 82 | 83 | 84 | /**************************************************************************** 85 | *** List / Bullet Styles 86 | ****************************************************************************/ 87 | 88 | li { 89 | margin-top: .2em; 90 | } 91 | div.step > ul > li { 92 | margin-top: .4em; 93 | } 94 | 95 | div.step > ul > li > ul { 96 | font-size: 85%; 97 | } 98 | 99 | div.step.small-items > ul > li, div.step.compact-items > ul > li, div.step.tiny-items > ul > li { 100 | margin-top: .1em; 101 | } 102 | 103 | div.step.small-items ul { 104 | font-size: 70%; 105 | } 106 | 107 | div.step.compact-items ul { 108 | font-size: 60%; 109 | } 110 | 111 | div.step.tiny-items ul { 112 | font-size: 50%; 113 | } 114 | 115 | div.step img.centered { 116 | position: absolute; 117 | margin: auto; 118 | top: 0; 119 | left: 0; 120 | right: 0; 121 | bottom: 0; 122 | } 123 | 124 | div.step img.background { 125 | z-index: -1; 126 | } 127 | 128 | div.step.bold { 129 | font-weight: 700; 130 | } 131 | 132 | div.step.big { 133 | font-size: 120%; 134 | } 135 | 136 | div.step.bigger { 137 | font-size: 150%; 138 | } 139 | 140 | div.step.large { 141 | font-size: 180%; 142 | } 143 | 144 | div.step.huge { 145 | font-size: 250%; 146 | } 147 | 148 | div.step.red { 149 | color: #f00; 150 | } 151 | 152 | div.step.green { 153 | color: #0f0; 154 | } 155 | 156 | div.step.blue { 157 | color: #00f; 158 | } 159 | 160 | div.step.black { 161 | color: #000; 162 | } 163 | 164 | div.step.white { 165 | color: #fff; 166 | } 167 | 168 | div.step.black-outline { 169 | text-shadow: black 0px 0px 1px, black 0px 0px 1px, black 0px 0px 1px, black 0px 0px 1px, black 0px 0px 1px; 170 | 171 | } 172 | 173 | div.step.big.black-outline, div.step.bigger.black-outline, div.step.large.black-outline, div.step.huge.black-outline { 174 | text-shadow: black 0px 0px 1px, black 0px 0px 1px, black 0px 0px 1px, black 0px 0px 1px, black 0px 0px 1px, 175 | black 0px 0px 1px, black 0px 0px 1px, black 0px 0px 1px, black 0px 0px 1px, black 0px 0px 1px, 176 | black 0px 0px 1px, black 0px 0px 1px, black 0px 0px 1px, black 0px 0px 1px, black 0px 0px 1px, 177 | black 0px 0px 1px, black 0px 0px 1px, black 0px 0px 1px, black 0px 0px 1px, black 0px 0px 1px, 178 | black 0px 0px 1px, black 0px 0px 1px, black 0px 0px 1px, black 0px 0px 1px, black 0px 0px 1px, 179 | black 0px 0px 1px, black 0px 0px 1px, black 0px 0px 1px, black 0px 0px 1px, black 0px 0px 1px; 180 | -webkit-font-smoothing: antialiased; 181 | } 182 | 183 | div.step.grey-outline { 184 | text-shadow: #999 0px 0px 1px, #999 0px 0px 1px, #999 0px 0px 1px, #999 0px 0px 1px, #999 0px 0px 1px; 185 | 186 | } 187 | 188 | div.step.big.grey-outline, div.step.bigger.grey-outline, div.step.large.grey-outline, div.step.huge.grey-outline { 189 | text-shadow: #999 0px 0px 1px, #999 0px 0px 1px, #999 0px 0px 1px, #999 0px 0px 1px, #999 0px 0px 1px, 190 | #999 0px 0px 1px, #999 0px 0px 1px, #999 0px 0px 1px, #999 0px 0px 1px, #999 0px 0px 1px, 191 | #999 0px 0px 1px, #999 0px 0px 1px, #999 0px 0px 1px, #999 0px 0px 1px, #999 0px 0px 1px, 192 | #999 0px 0px 1px, #999 0px 0px 1px, #999 0px 0px 1px, #999 0px 0px 1px, #999 0px 0px 1px, 193 | #999 0px 0px 1px, #999 0px 0px 1px, #999 0px 0px 1px, #999 0px 0px 1px, #999 0px 0px 1px, 194 | #999 0px 0px 1px, #999 0px 0px 1px, #999 0px 0px 1px, #999 0px 0px 1px, #999 0px 0px 1px; 195 | -webkit-font-smoothing: antialiased; 196 | } 197 | 198 | div.step.white-outline { 199 | text-shadow: white 0px 0px 1px, #000 0px 0px 1px, white 0px 0px 1px, white 0px 0px 1px, white 0px 0px 1px; 200 | 201 | } 202 | 203 | div.step.big.white-outline, div.step.bigger.white-outline, div.step.large.white-outline, div.step.huge.white-outline { 204 | text-shadow: white 0px 0px 1px, white 0px 0px 1px, white 0px 0px 1px, white 0px 0px 1px, white 0px 0px 1px, 205 | white 0px 0px 1px, white 0px 0px 1px, white 0px 0px 1px, white 0px 0px 1px, white 0px 0px 1px, 206 | white 0px 0px 1px, white 0px 0px 1px, white 0px 0px 1px, white 0px 0px 1px, white 0px 0px 1px, 207 | white 0px 0px 1px, white 0px 0px 1px, white 0px 0px 1px, white 0px 0px 1px, white 0px 0px 1px, 208 | white 0px 0px 1px, white 0px 0px 1px, white 0px 0px 1px, white 0px 0px 1px, white 0px 0px 1px, 209 | white 0px 0px 1px, white 0px 0px 1px, white 0px 0px 1px, white 0px 0px 1px, white 0px 0px 1px; 210 | -webkit-font-smoothing: antialiased; 211 | } 212 | 213 | div.step.centered { 214 | display: flex !important; 215 | align-items: center; 216 | justify-content: center; 217 | } 218 | 219 | div.step.centered > div.line-block { 220 | text-align: center; 221 | margin: auto; 222 | width: 780px; 223 | max-width: 50%; 224 | } 225 | 226 | div.column-2 > ul { 227 | column-count: 2; 228 | -moz-column-count: 2; 229 | -webkit-column-count: 2; 230 | column-width: 520p; 231 | -moz-column-width: 520p; 232 | -webkit-column-width: 520p; 233 | } 234 | 235 | div.bullet-checkmark > ul > li { 236 | list-style: none; 237 | } 238 | div.bullet-checkmark > ul > li:before { 239 | content: "\2714"; 240 | color: #9e9; 241 | margin-right: 1.1rem; 242 | } 243 | 244 | div.bullet-arrow > ul > li { 245 | list-style: none; 246 | } 247 | div.bullet-arrow > ul > li:before { 248 | content: "\27a2"; 249 | color: #e99; 250 | margin-right: 1.1rem; 251 | } 252 | 253 | div.bullet-hand > ul > li { 254 | list-style: none; 255 | } 256 | div.bullet-hand > ul > li:before { 257 | content: "\261b"; 258 | color: #99e; 259 | margin-right: 1.1rem; 260 | font-size: 175%; 261 | } 262 | 263 | div.bullet-star > ul > li { 264 | list-style: none; 265 | } 266 | div.bullet-star > ul > li:before { 267 | content: "\2605"; 268 | color: #88f; 269 | margin-right: 1.1rem; 270 | } 271 | 272 | 273 | /**************************************************************************** 274 | *** Code Blocks 275 | ****************************************************************************/ 276 | 277 | pre.highlight { 278 | color: #111; 279 | background-color: #eee; 280 | box-shadow: .1rem .1rem .5rem .1rem #99f; 281 | padding: .5rem; 282 | border: 1px solid #99f; 283 | } 284 | 285 | pre.small { 286 | font-size: 70%; 287 | } 288 | 289 | .highlight .c { color: #090 } /* Comment */ 290 | .highlight .c1 { color: #090 } /* Comment.Single */ 291 | .highlight .s1 { color: #009 } /* Literal.String.Single */ 292 | .highlight .s2 { color: #009 } /* Literal.String.Double */ 293 | .highlight .sd { color: #900 } /* Literal.String.Doc */ 294 | 295 | 296 | /**************************************************************************** 297 | *** Text Styles 298 | ****************************************************************************/ 299 | 300 | a { 301 | color: #aaf; 302 | text-decoration: none; 303 | } 304 | a:visited { 305 | color: #98f; 306 | } 307 | a:hover { 308 | color: #bbf; 309 | } 310 | 311 | tt { 312 | background: linear-gradient(to top, #336, #669, #336); 313 | padding-left: .3rem; 314 | padding-right: .3rem; 315 | } 316 | -------------------------------------------------------------------------------- /{{cookiecutter.repo_name}}/css/normalize.css: -------------------------------------------------------------------------------- 1 | /*! normalize.css v4.0.0 | MIT License | github.com/necolas/normalize.css */ 2 | 3 | /** 4 | * 1. Change the default font family in all browsers (opinionated). 5 | * 2. Prevent adjustments of font size after orientation changes in IE and iOS. 6 | */ 7 | 8 | html { 9 | font-family: sans-serif; /* 1 */ 10 | -ms-text-size-adjust: 100%; /* 2 */ 11 | -webkit-text-size-adjust: 100%; /* 2 */ 12 | } 13 | 14 | /** 15 | * Remove the margin in all browsers (opinionated). 16 | */ 17 | 18 | body { 19 | margin: 0; 20 | } 21 | 22 | /* HTML5 display definitions 23 | ========================================================================== */ 24 | 25 | /** 26 | * Add the correct display in IE 9-. 27 | * 1. Add the correct display in Edge, IE, and Firefox. 28 | * 2. Add the correct display in IE. 29 | */ 30 | 31 | article, 32 | aside, 33 | details, /* 1 */ 34 | figcaption, 35 | figure, 36 | footer, 37 | header, 38 | main, /* 2 */ 39 | menu, 40 | nav, 41 | section, 42 | summary { /* 1 */ 43 | display: block; 44 | } 45 | 46 | /** 47 | * Add the correct display in IE 9-. 48 | */ 49 | 50 | audio, 51 | canvas, 52 | progress, 53 | video { 54 | display: inline-block; 55 | } 56 | 57 | /** 58 | * Add the correct display in iOS 4-7. 59 | */ 60 | 61 | audio:not([controls]) { 62 | display: none; 63 | height: 0; 64 | } 65 | 66 | /** 67 | * Add the correct vertical alignment in Chrome, Firefox, and Opera. 68 | */ 69 | 70 | progress { 71 | vertical-align: baseline; 72 | } 73 | 74 | /** 75 | * Add the correct display in IE 10-. 76 | * 1. Add the correct display in IE. 77 | */ 78 | 79 | template, /* 1 */ 80 | [hidden] { 81 | display: none; 82 | } 83 | 84 | /* Links 85 | ========================================================================== */ 86 | 87 | /** 88 | * Remove the gray background on active links in IE 10. 89 | */ 90 | 91 | a { 92 | background-color: transparent; 93 | } 94 | 95 | /** 96 | * Remove the outline on focused links when they are also active or hovered 97 | * in all browsers (opinionated). 98 | */ 99 | 100 | a:active, 101 | a:hover { 102 | outline-width: 0; 103 | } 104 | 105 | /* Text-level semantics 106 | ========================================================================== */ 107 | 108 | /** 109 | * 1. Remove the bottom border in Firefox 39-. 110 | * 2. Add the correct text decoration in Chrome, Edge, IE, Opera, and Safari. 111 | */ 112 | 113 | abbr[title] { 114 | border-bottom: none; /* 1 */ 115 | text-decoration: underline; /* 2 */ 116 | text-decoration: underline dotted; /* 2 */ 117 | } 118 | 119 | /** 120 | * Prevent the duplicate application of `bolder` by the next rule in Safari 6. 121 | */ 122 | 123 | b, 124 | strong { 125 | font-weight: inherit; 126 | } 127 | 128 | /** 129 | * Add the correct font weight in Chrome, Edge, and Safari. 130 | */ 131 | 132 | b, 133 | strong { 134 | font-weight: bolder; 135 | } 136 | 137 | /** 138 | * Add the correct font style in Android 4.3-. 139 | */ 140 | 141 | dfn { 142 | font-style: italic; 143 | } 144 | 145 | /** 146 | * Correct the font size and margin on `h1` elements within `section` and 147 | * `article` contexts in Chrome, Firefox, and Safari. 148 | */ 149 | 150 | h1 { 151 | font-size: 2em; 152 | margin: 0.67em 0; 153 | } 154 | 155 | /** 156 | * Add the correct background and color in IE 9-. 157 | */ 158 | 159 | mark { 160 | background-color: #ff0; 161 | color: #000; 162 | } 163 | 164 | /** 165 | * Add the correct font size in all browsers. 166 | */ 167 | 168 | small { 169 | font-size: 80%; 170 | } 171 | 172 | /** 173 | * Prevent `sub` and `sup` elements from affecting the line height in 174 | * all browsers. 175 | */ 176 | 177 | sub, 178 | sup { 179 | font-size: 75%; 180 | line-height: 0; 181 | position: relative; 182 | vertical-align: baseline; 183 | } 184 | 185 | sub { 186 | bottom: -0.25em; 187 | } 188 | 189 | sup { 190 | top: -0.5em; 191 | } 192 | 193 | /* Embedded content 194 | ========================================================================== */ 195 | 196 | /** 197 | * Remove the border on images inside links in IE 10-. 198 | */ 199 | 200 | img { 201 | border-style: none; 202 | } 203 | 204 | /** 205 | * Hide the overflow in IE. 206 | */ 207 | 208 | svg:not(:root) { 209 | overflow: hidden; 210 | } 211 | 212 | /* Grouping content 213 | ========================================================================== */ 214 | 215 | /** 216 | * 1. Correct the inheritance and scaling of font size in all browsers. 217 | * 2. Correct the odd `em` font sizing in all browsers. 218 | */ 219 | 220 | code, 221 | kbd, 222 | pre, 223 | samp { 224 | font-family: monospace, monospace; /* 1 */ 225 | font-size: 1em; /* 2 */ 226 | } 227 | 228 | /** 229 | * Add the correct margin in IE 8. 230 | */ 231 | 232 | figure { 233 | margin: 1em 40px; 234 | } 235 | 236 | /** 237 | * 1. Add the correct box sizing in Firefox. 238 | * 2. Show the overflow in Edge and IE. 239 | */ 240 | 241 | hr { 242 | box-sizing: content-box; /* 1 */ 243 | height: 0; /* 1 */ 244 | overflow: visible; /* 2 */ 245 | } 246 | 247 | /* Forms 248 | ========================================================================== */ 249 | 250 | /** 251 | * Change font properties to `inherit` in all browsers (opinionated). 252 | */ 253 | 254 | button, 255 | input, 256 | select, 257 | textarea { 258 | font: inherit; 259 | } 260 | 261 | /** 262 | * Restore the font weight unset by the previous rule. 263 | */ 264 | 265 | optgroup { 266 | font-weight: bold; 267 | } 268 | 269 | /** 270 | * Show the overflow in IE. 271 | * 1. Show the overflow in Edge. 272 | * 2. Show the overflow in Edge, Firefox, and IE. 273 | */ 274 | 275 | button, 276 | input, /* 1 */ 277 | select { /* 2 */ 278 | overflow: visible; 279 | } 280 | 281 | /** 282 | * Remove the margin in Safari. 283 | * 1. Remove the margin in Firefox and Safari. 284 | */ 285 | 286 | button, 287 | input, 288 | select, 289 | textarea { /* 1 */ 290 | margin: 0; 291 | } 292 | 293 | /** 294 | * Remove the inheritence of text transform in Edge, Firefox, and IE. 295 | * 1. Remove the inheritence of text transform in Firefox. 296 | */ 297 | 298 | button, 299 | select { /* 1 */ 300 | text-transform: none; 301 | } 302 | 303 | /** 304 | * Change the cursor in all browsers (opinionated). 305 | */ 306 | 307 | button, 308 | [type="button"], 309 | [type="reset"], 310 | [type="submit"] { 311 | cursor: pointer; 312 | } 313 | 314 | /** 315 | * Restore the default cursor to disabled elements unset by the previous rule. 316 | */ 317 | 318 | [disabled] { 319 | cursor: default; 320 | } 321 | 322 | /** 323 | * 1. Prevent a WebKit bug where (2) destroys native `audio` and `video` 324 | * controls in Android 4. 325 | * 2. Correct the inability to style clickable types in iOS. 326 | */ 327 | 328 | button, 329 | html [type="button"], /* 1 */ 330 | [type="reset"], 331 | [type="submit"] { 332 | -webkit-appearance: button; /* 2 */ 333 | } 334 | 335 | /** 336 | * Remove the inner border and padding in Firefox. 337 | */ 338 | 339 | button::-moz-focus-inner, 340 | input::-moz-focus-inner { 341 | border: 0; 342 | padding: 0; 343 | } 344 | 345 | /** 346 | * Restore the focus styles unset by the previous rule. 347 | */ 348 | 349 | button:-moz-focusring, 350 | input:-moz-focusring { 351 | outline: 1px dotted ButtonText; 352 | } 353 | 354 | /** 355 | * Change the border, margin, and padding in all browsers (opinionated). 356 | */ 357 | 358 | fieldset { 359 | border: 1px solid #c0c0c0; 360 | margin: 0 2px; 361 | padding: 0.35em 0.625em 0.75em; 362 | } 363 | 364 | /** 365 | * 1. Correct the text wrapping in Edge and IE. 366 | * 2. Correct the color inheritance from `fieldset` elements in IE. 367 | * 3. Remove the padding so developers are not caught out when they zero out 368 | * `fieldset` elements in all browsers. 369 | */ 370 | 371 | legend { 372 | box-sizing: border-box; /* 1 */ 373 | color: inherit; /* 2 */ 374 | display: table; /* 1 */ 375 | max-width: 100%; /* 1 */ 376 | padding: 0; /* 3 */ 377 | white-space: normal; /* 1 */ 378 | } 379 | 380 | /** 381 | * Remove the default vertical scrollbar in IE. 382 | */ 383 | 384 | textarea { 385 | overflow: auto; 386 | } 387 | 388 | /** 389 | * 1. Add the correct box sizing in IE 10-. 390 | * 2. Remove the padding in IE 10-. 391 | */ 392 | 393 | [type="checkbox"], 394 | [type="radio"] { 395 | box-sizing: border-box; /* 1 */ 396 | padding: 0; /* 2 */ 397 | } 398 | 399 | /** 400 | * Correct the cursor style of increment and decrement buttons in Chrome. 401 | */ 402 | 403 | [type="number"]::-webkit-inner-spin-button, 404 | [type="number"]::-webkit-outer-spin-button { 405 | height: auto; 406 | } 407 | 408 | /** 409 | * Correct the odd appearance of search inputs in Chrome and Safari. 410 | */ 411 | 412 | [type="search"] { 413 | -webkit-appearance: textfield; 414 | } 415 | 416 | /** 417 | * Remove the inner padding and cancel buttons in Chrome on OS X and 418 | * Safari on OS X. 419 | */ 420 | 421 | [type="search"]::-webkit-search-cancel-button, 422 | [type="search"]::-webkit-search-decoration { 423 | -webkit-appearance: none; 424 | } 425 | -------------------------------------------------------------------------------- /{{cookiecutter.repo_name}}/img/hovercraft-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Springerle/hovercraft-slides/b007075ead9f6b4cb451ed53422d7ec4a4529296/{{cookiecutter.repo_name}}/img/hovercraft-logo.png -------------------------------------------------------------------------------- /{{cookiecutter.repo_name}}/img/python-powered.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Springerle/hovercraft-slides/b007075ead9f6b4cb451ed53422d7ec4a4529296/{{cookiecutter.repo_name}}/img/python-powered.png -------------------------------------------------------------------------------- /{{cookiecutter.repo_name}}/img/title-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Springerle/hovercraft-slides/b007075ead9f6b4cb451ed53422d7ec4a4529296/{{cookiecutter.repo_name}}/img/title-logo.png -------------------------------------------------------------------------------- /{{cookiecutter.repo_name}}/index.rst: -------------------------------------------------------------------------------- 1 | {% macro section(title, level='=') -%} 2 | {{ title }} 3 | {% for _ in title %}{{ level }}{% endfor %} 4 | {%- endmacro -%} 5 | :title: {{ cookiecutter.project_name }} 6 | :author: {{ cookiecutter.full_name }} <{{ cookiecutter.email }}> 7 | :description: Copyright (c) {{ cookiecutter.year }} 8 | :keywords: slides, impress.js 9 | :auto-console: false 10 | :skip-help: true 11 | :css: css/custom.css 12 | 13 | ---- 14 | 15 | :class: bullet-hand 16 | 17 | {{ section(cookiecutter.project_name) }} 18 | 19 | **{{ cookiecutter.full_name }} <{{ cookiecutter.email }}>** 20 | 21 | Agenda 22 | ------ 23 | 24 | - What is `Hovercraft!`_? 25 | - Why use `Hovercraft!`_? 26 | - Show example `Hovercraft!`_ slides 27 | - Present built-in styling options 28 | 29 | ---- 30 | 31 | :class: bullet-checkmark 32 | 33 | What is Hovercraft? 34 | =================== 35 | 36 | - Write your slides in *re*\ Structured\ *Text* 37 | - Get a HTML5 / CSS3 presentation created from markup 38 | - Prezi-style effects by using ``impress.js`` (including 3D effects) 39 | 40 | .. break 41 | 42 | - Convert to PDF to get a single-file document 43 | - Easy to create using a template for new presentation projects 44 | - You need Python3, a modern browser, and Linux / Mac OSX 45 | - Not so Windows-friendly, but might work 46 | 47 | - some amount of manual setup 48 | - less convenience (e.g. no live reload watchdog) 49 | 50 | 51 | ---- 52 | 53 | :class: bullet-checkmark 54 | 55 | Why Use Hovercraft? 56 | =================== 57 | 58 | Disclaimer: YMMV 59 | 60 | - HTML slides work everywhere, re-use CSS know-how 61 | - But write in text markup (*re*\ Structured\ *Text*) 62 | - Text is way more malleable than typical presentation software slides 63 | 64 | - Refactor your slides as you do with code 65 | - Text is SCM-friendly, get a proper change history 66 | - Easy to update embedded code snippets (even automatically) 67 | - Re-purpose your slides easily (blog posts, docs, …) 68 | 69 | - Embedded presenter console (notes + timer + slide previews) 70 | - Live-reload preview during authoring 71 | 72 | 73 | ---- 74 | 75 | :class: bullet-star 76 | 77 | Bullet Styles 78 | ============= 79 | 80 | - Bullet points cause *PowerPoint Poisoning* 81 | - … but we use them anyway 82 | 83 | .. break 84 | 85 | - Styling top-level bullets 86 | 87 | - use ``:class: bullet-STYLE`` on the slide 88 | - ``bullet-checkmark``, ``bullet-arrow``, ``bullet-hand``, or ``bullet-star`` 89 | 90 | .. break 91 | 92 | - To add spacing between bullets, add a *re*\ Structured\ *Text* comment: 93 | 94 | :: 95 | 96 | .. break 97 | 98 | ---- 99 | 100 | :class: centered huge bold red white-outline 101 | 102 | .. image:: img/python-powered.png 103 | :width: 720px 104 | :class: centered background 105 | 106 | | Text Style: centered huge bold red white-outline 107 | 108 | | Use them in combination with background images 109 | 110 | 111 | ---- 112 | 113 | Text Styles 114 | =========== 115 | 116 | - Emphasis: ``bold`` 117 | - Size: ``big``, ``bigger``, ``large``, ``huge`` 118 | - Color: ``red``, ``green``, ``blue``, ``black``, ``white`` 119 | - Outline: ``black-outline``, ``grey-outline``, ``white-outline`` 120 | 121 | 122 | ---- 123 | 124 | Centered Images 125 | =============== 126 | 127 | - Leave out the title for image-only slides 128 | - Add ``:class: centered`` for centering 129 | - Add ``:class: background`` to put it below other content 130 | 131 | .. image:: img/python-powered.png 132 | :width: 720px 133 | :class: centered background 134 | 135 | 136 | ---- 137 | 138 | Floating Images 139 | =============== 140 | 141 | .. image:: img/python-powered.png 142 | :width: 480px 143 | :class: float-right 144 | 145 | - Eye-candy can sweeten bullet points 146 | - Use ``:class: float-right`` on an image 147 | - The image floats to the right of any content following it 148 | 149 | 150 | ---- 151 | 152 | Code Blocks 153 | =========== 154 | 155 | .. code-block:: python 156 | :class: small 157 | 158 | @task(help={ 159 | 'browse': "Open slides in a new browser tab", 160 | }) 161 | def html(ctx, browse=False): 162 | """Build HTML tree.""" 163 | index_file = '_html/index.html' 164 | ctx.run("hovercraft -t simple --skip-notes index.rst {}" 165 | .format(os.path.dirname(index_file))) 166 | 167 | # Open in browser? 168 | if browse: 169 | webbrowser.open_new_tab(index_file) 170 | 171 | ---- 172 | 173 | :class: bullet-checkmark compact-items column-2 174 | 175 | \>\>\> import this # 2-column layout 176 | ===================================== 177 | 178 | **The Zen of Python** (`PEP-20`_) 179 | 180 | * Beautiful is better than ugly. 181 | * Explicit is better than implicit. 182 | * Simple is better than complex. 183 | * Complex is better than complicated. 184 | * Flat is better than nested. 185 | * Sparse is better than dense. 186 | * Readability counts. 187 | * Special cases aren't special enough to break the rules. 188 | * Although practicality beats purity. 189 | * Errors should never pass silently. 190 | * Unless explicitly silenced. 191 | * Now is better than never. 192 | * Although never is often better than *right* now. 193 | 194 | .. break 195 | 196 | * In the face of ambiguity, refuse the temptation to guess. 197 | * There should be one – and preferably only one – obvious way to do it. 198 | * Although that way may not be obvious at first unless you're Dutch. 199 | * If the implementation is hard to explain, it's a bad idea. 200 | * If the implementation is easy to explain, it may be a good idea. 201 | * Namespaces are one honking great idea – let's do more of those! 202 | 203 | .. _`PEP-20`: https://www.python.org/dev/peps/pep-0020/ 204 | 205 | 206 | ---- 207 | 208 | :class: bullet-arrow 209 | 210 | Credits 211 | ======= 212 | 213 | .. image:: img/python-powered.png 214 | :width: 240px 215 | :class: float-right 216 | 217 | - Powered by `Hovercraft!`_ 218 | - Powered by `Cookiecutter`_ 219 | 220 | .. break 221 | 222 | - Hovercraft! logo – https://github.com/regebro/hovercraft/ 223 | - Python logo – https://www.python.org/community/logos/ 224 | 225 | *[Licensing conditions of the original projects apply]* 226 | 227 | .. _`Hovercraft!`: https://hovercraft.readthedocs.io/ 228 | .. _`Cookiecutter`: https://github.com/audreyr/cookiecutter 229 | -------------------------------------------------------------------------------- /{{cookiecutter.repo_name}}/requirements.txt: -------------------------------------------------------------------------------- 1 | # 2 | # Project's virtualenv requirements 3 | # 4 | 5 | ##cython 6 | invoke==0.13.0 7 | #rituals==0.3.0 8 | -e git+https://github.com/jhermann/rituals.git#egg=rituals 9 | 10 | hovercraft==2.1 11 | -------------------------------------------------------------------------------- /{{cookiecutter.repo_name}}/setup.py: -------------------------------------------------------------------------------- 1 | """A setup shim for 'rituals'""" 2 | import os 3 | import re 4 | import sys 5 | import subprocess 6 | from datetime import datetime 7 | 8 | try: 9 | url = subprocess.check_output('git remote get-url origin', 10 | stderr=subprocess.STDOUT, shell=True) 11 | except subprocess.CalledProcessError: 12 | url = '{{ cookiecutter.url }}' 13 | else: 14 | url = url.decode('utf-8').strip() 15 | if url.endswith('.git'): 16 | url = url[:-4] 17 | if url.startswith('ssh://'): 18 | url = url[6:] 19 | url = re.sub(r'git@([^:/]+)[:/]', r'https://\1/', url) 20 | 21 | try: 22 | now = '{:%Y%m%d-%H%M}'.format(datetime.now()) 23 | version = subprocess.check_output("git describe --long --dirty='-{}' --all --always".format(now), 24 | stderr=subprocess.STDOUT, shell=True) 25 | version = version.decode('utf-8').strip().replace('/', '-') 26 | except subprocess.CalledProcessError: 27 | filedate = os.path.getmtime(os.path.join(os.path.dirname(__file__), 'index.rst')) 28 | version = datetime.fromtimestamp(filedate).isoformat('-')[:16].replace(':', '').replace('-', '.') 29 | 30 | project = dict( 31 | name=os.path.basename(os.path.dirname(os.path.abspath(__file__))), 32 | version=version, 33 | url=url, 34 | author='{{ cookiecutter.full_name }}', 35 | author_email='{{ cookiecutter.email }}', 36 | license='{{ cookiecutter.license }}', 37 | ) 38 | 39 | if __name__ == "__main__": 40 | install = True 41 | for arg in sys.argv[1:]: 42 | if arg.startswith('--') and arg.lstrip('-') in project: 43 | print(project.get(arg.lstrip('-'))) 44 | install = False 45 | 46 | if install: 47 | subprocess.call("pip install -r requirements.txt", shell=True) 48 | -------------------------------------------------------------------------------- /{{cookiecutter.repo_name}}/tasks.py: -------------------------------------------------------------------------------- 1 | # *- coding: utf-8 -*- 2 | # pylint: disable=locally-disabled, bad-continuation 3 | # pylint: disable=wildcard-import, unused-import, unused-wildcard-import 4 | # pylint: disable=superfluous-parens, redefined-builtin 5 | """ Project automation for Invoke. 6 | """ 7 | 8 | import os 9 | import sys 10 | import time 11 | import shlex 12 | import shutil 13 | import signal 14 | import subprocess 15 | import webbrowser 16 | 17 | from rituals.easy import task, Collection, pushd 18 | from rituals.util import antglob, notify 19 | from rituals.util.which import which, WhichError 20 | from rituals.acts.basic import help 21 | from rituals.acts.documentation import upload 22 | 23 | BASEDIR = os.path.dirname(__file__) 24 | UPLOAD_URL = os.environ.get('INVOKE_RITUALS_DOCS_UPLOAD_TARGETS_WEBDAV_URL') 25 | if UPLOAD_URL: 26 | # re-use a typical configuration for Python docs, but store slide uploads separate 27 | UPLOAD_URL = UPLOAD_URL.replace('/python/', '/slides/') 28 | os.environ['INVOKE_RITUALS_DOCS_UPLOAD_TARGETS_WEBDAV_URL'] = UPLOAD_URL 29 | 30 | 31 | @task(help=dict( 32 | venv="Include an existing virtualenv (in '.' or in '.venv')", 33 | extra="Any extra patterns, space-separated and possibly quoted", 34 | )) 35 | def clean(ctx, venv=False, extra=''): 36 | """Perform house-keeping.""" 37 | notify.banner("Cleaning up project files") 38 | 39 | ctx.run("invoke view --kill") 40 | 41 | # Add patterns based on given parameters 42 | venv_dirs = ['bin', 'include', 'lib', 'share', 'local', '.venv'] 43 | patterns = ['_html/', 'pip-selfcheck.json'] 44 | excludes = ['.git/', '.hg/', '.svn/'] 45 | if venv: 46 | patterns.extend([i + '/' for i in venv_dirs]) 47 | if extra: 48 | patterns.extend(shlex.split(extra)) 49 | 50 | # Build fileset 51 | patterns = [antglob.includes(i) for i in patterns] + [antglob.excludes(i) for i in excludes] 52 | if not venv: 53 | # Do not scan venv dirs when not cleaning them 54 | patterns.extend([antglob.excludes(i + '/') for i in venv_dirs]) 55 | fileset = antglob.FileSet(BASEDIR, patterns) 56 | 57 | # Iterate over matches and remove them 58 | for name in fileset: 59 | notify.info('rm {0}'.format(name)) 60 | if name.endswith('/'): 61 | shutil.rmtree(os.path.join(BASEDIR, name)) 62 | else: 63 | os.unlink(os.path.join(BASEDIR, name)) 64 | 65 | 66 | @task(help={ 67 | 'clean': "Start with a clean build area", 68 | 'kill': "Kill a running watchdog process and exit", 69 | 'opts': "Extra flags for Hovercraft", 70 | }) 71 | def view(ctx, browse=False, clean=False, kill=False, opts=''): 72 | """Start live-reload watchdog.""" 73 | def activity(what=None, i=None): 74 | "Helper" 75 | if i is None: 76 | sys.stdout.write(what + '\n') 77 | else: 78 | sys.stdout.write(' {} Waiting for {}\r'.format(r'\|/-'[i % 4], what or 'something')) 79 | sys.stdout.flush() 80 | 81 | def pidof(cmd_fragment): 82 | """Find a process by its cmd string.""" 83 | find_pid = "ps auxww | grep -i '{}' | grep -v grep".format(cmd_fragment.replace("'", r"'\''")) 84 | try: 85 | pid = subprocess.check_output(find_pid, shell=True).strip() 86 | return int(pid.split(None, 2)[1]) if pid else 0 87 | except subprocess.CalledProcessError: 88 | return 0 89 | 90 | # Kill old processes 91 | core_cmd = '''watchmedo shell-command''' 92 | for i in range(60): 93 | pid = pidof(core_cmd + '.*' + BASEDIR) 94 | if pid: 95 | print("Killing old watchdog process #{}...".format(pid)) 96 | os.kill(pid, signal.SIGTERM) 97 | time.sleep(.3) 98 | else: 99 | if kill: 100 | print("No (more) watchdog processes for this directory found.") 101 | break 102 | if kill: 103 | return 0 104 | 105 | # Clean result and start watchdog 106 | index_file = '_html/index.html' 107 | cmd = core_cmd + ( 108 | ''' --command="hovercraft{opts} index.rst {htmldir}" ''' 109 | ''' --patterns="*.rst"''' 110 | ''' {basedir} &''' 111 | .format(opts=opts, htmldir=os.path.dirname(index_file), basedir=BASEDIR)) 112 | if clean: 113 | ctx.run("invoke clean") 114 | elif os.path.exists(index_file): 115 | os.remove(index_file) 116 | ctx.run("invoke html") # run a plain HTML build 1st 117 | subprocess.call(cmd, shell=True) 118 | 119 | # Wait for watchdog 120 | for i in range(60): 121 | activity('watchdog process', i) 122 | if pidof(core_cmd + '.*' + BASEDIR): 123 | activity('OK') 124 | break 125 | time.sleep(1) 126 | else: 127 | activity('ERR') 128 | return 1 129 | 130 | # Wait for HTML tree 131 | for i in range(60): 132 | activity('HTML index file', i) 133 | if os.path.exists(index_file): 134 | activity('OK') 135 | break 136 | time.sleep(1) 137 | # trigger first build 138 | if i == 0: 139 | os.utime(os.path.join(BASEDIR, 'README.rst'), None) 140 | else: 141 | activity('ERR') 142 | return 1 143 | 144 | # Open in browser 145 | webbrowser.open_new_tab(index_file) 146 | 147 | 148 | @task(help={ 149 | 'browse': "Open slides in a new browser tab", 150 | 'presentation': "Generate slides for presentation (with console and notes)", 151 | }) 152 | def html(ctx, browse=False, presentation=False): 153 | """Build HTML tree.""" 154 | index_file = '_html/index.html' 155 | 156 | for subdir in ('img', 'css'): 157 | dest = os.path.join(os.path.dirname(index_file), subdir) 158 | if not os.path.exists(dest): 159 | shutil.copytree(os.path.join(BASEDIR, subdir), dest) 160 | 161 | opts = "--auto-console" if presentation else "-t simple --skip-notes" 162 | ctx.run("hovercraft {} index.rst {}".format(opts, os.path.dirname(index_file))) 163 | 164 | # Open in browser? 165 | if browse: 166 | webbrowser.open_new_tab(index_file) 167 | 168 | 169 | @task(help={ 170 | 'open': "Open the generated PDF", 171 | }) 172 | def pdf(ctx, open=False): 173 | """Build PDF slide show.""" 174 | try: 175 | which("deck2pdf") 176 | except WhichError: 177 | notify.failure("You need to install 'deck2pdf' from https://github.com/melix/deck2pdf") 178 | ctx.run("invoke html") 179 | ctx.run("deck2pdf --profile=impressjs _html/index.html slides.pdf") 180 | if open: 181 | ctx.run("xdg-open slides.pdf &", pty=False) 182 | 183 | 184 | @task(help={ 185 | 'open': "Open the generated image", 186 | 'width': "Width of a single slide in pixels", 187 | }) 188 | def thumbs(ctx, open=False, width=480): 189 | """Create slide show thumbnails.""" 190 | ctx.run("invoke pdf") 191 | ctx.run("montage -density 300 slides.pdf -mode Concatenate -tile 2x999 -quality 80 -resize {} slides.jpg".format(width)) 192 | if open: 193 | ctx.run("xdg-open slides.jpg &", pty=False) 194 | 195 | 196 | namespace = Collection.from_module(sys.modules[__name__], name='', config={'rituals': dict( 197 | docs = dict( 198 | sources = '.', 199 | build = '_html', 200 | upload = dict( 201 | method = 'webdav', 202 | targets = dict( 203 | webdav = dict(url=None), # must be set in the environment 204 | ), 205 | ), 206 | ), 207 | )}) 208 | --------------------------------------------------------------------------------