├── requirements.txt ├── nbfancy ├── __init__.py ├── tools │ ├── example_gitignore │ ├── css │ │ ├── custom.css │ │ └── css │ │ │ ├── favicon.png │ │ │ ├── fontawesome-webfont.ttf │ │ │ └── w3.css │ └── redirect.html ├── nbfancylogo.png ├── nbfancylogowhite.png ├── colourscheme_keywords.cfg ├── config │ ├── keywords.cfg │ ├── footer.ipynb │ ├── box.ipynb │ └── header.ipynb ├── template │ ├── 01_untitled_episode.ipynb │ ├── 00_schedule.ipynb │ └── 99_episode_template.ipynb ├── tutorial │ ├── 10_magic.ipynb │ ├── 11_slides.ipynb │ ├── 12_contributing.ipynb │ ├── 00_schedule.ipynb │ ├── 08_keywords.ipynb │ ├── 01_motivation.ipynb │ ├── 06_git.ipynb │ ├── 09_template.ipynb │ ├── 04_files.ipynb │ ├── 07_multicell.ipynb │ ├── 02_installation.ipynb │ ├── 03_build.ipynb │ └── 05_environments.ipynb ├── colourscheme_sandbox.ipynb ├── globalconf.py ├── nbfancylogo.svg ├── nbfancylogowhite.svg ├── nbfancyfavicon.svg ├── nbfancy_tools.py └── __main__.py ├── bash2_magic ├── __init__.py └── bash2_magic.py ├── pdflatex_magic ├── __init__.py └── pdflatex_magic.py ├── .gitignore ├── MANIFEST.in ├── .travis.yml ├── setup.py ├── LICENSE └── README.md /requirements.txt: -------------------------------------------------------------------------------- 1 | ipython>=6 2 | jupyter 3 | -------------------------------------------------------------------------------- /nbfancy/__init__.py: -------------------------------------------------------------------------------- 1 | from . import nbfancy_tools 2 | 3 | name = 'nbfancy' 4 | -------------------------------------------------------------------------------- /bash2_magic/__init__.py: -------------------------------------------------------------------------------- 1 | from .bash2_magic import * 2 | 3 | name = 'bash2_magic' 4 | -------------------------------------------------------------------------------- /pdflatex_magic/__init__.py: -------------------------------------------------------------------------------- 1 | from .pdflatex_magic import * 2 | 3 | name = 'pdflatex_magic' 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | **/.ipynb_checkpoints 2 | **/__pycache__ 3 | build 4 | dist 5 | nbfancy.egg-info 6 | -------------------------------------------------------------------------------- /nbfancy/tools/example_gitignore: -------------------------------------------------------------------------------- 1 | **/.ipynb_checkpoints 2 | **/__pycache__ 3 | nbfancy 4 | html 5 | -------------------------------------------------------------------------------- /nbfancy/nbfancylogo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JDBetteridge/nbfancy/HEAD/nbfancy/nbfancylogo.png -------------------------------------------------------------------------------- /nbfancy/tools/css/custom.css: -------------------------------------------------------------------------------- 1 | @import url("./css/w3.css"); 2 | @import url("./css/font-awesome.css"); 3 | -------------------------------------------------------------------------------- /nbfancy/nbfancylogowhite.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JDBetteridge/nbfancy/HEAD/nbfancy/nbfancylogowhite.png -------------------------------------------------------------------------------- /nbfancy/tools/css/css/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JDBetteridge/nbfancy/HEAD/nbfancy/tools/css/css/favicon.png -------------------------------------------------------------------------------- /nbfancy/tools/css/css/fontawesome-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JDBetteridge/nbfancy/HEAD/nbfancy/tools/css/css/fontawesome-webfont.ttf -------------------------------------------------------------------------------- /nbfancy/tools/redirect.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | # REQUIRED CSS FILES 2 | recursive-include nbfancy/tools * 3 | 4 | # REQUIRED TEMPLATE FILES 5 | recursive-include nbfancy/config *.ipynb *.cfg 6 | recursive-include nbfancy/template *.ipynb 7 | recursive-include nbfancy/tutorial *.ipynb 8 | 9 | # REMOVE CACHE FILES 10 | # first isn't working??? 11 | global-exclude .ipynb_checkpoints 12 | global-exclude __pycache__ 13 | -------------------------------------------------------------------------------- /nbfancy/colourscheme_keywords.cfg: -------------------------------------------------------------------------------- 1 | ## Heading 2 | Keyword, Colour, Symbol, keep_keyword, hide 3 | 4 | # Coloured boxes (mainly for testing) 5 | RED, red, fa-dot-circle-o, False, True 6 | ORANGE, orange, fa-dot-circle-o, True, False 7 | YELLOW, yellow, fa-dot-circle-o, False, False 8 | GREEN, green, fa-dot-circle-o, False, False 9 | BLUE, blue, fa-dot-circle-o, False, False 10 | PURPLE, purple, fa-dot-circle-o, False, False 11 | BROWN, brown, fa-dot-circle-o, False, False 12 | BLACK, black, fa-dot-circle-o, False, False 13 | GREY, grey, fa-dot-circle-o, False, False 14 | GRAY, gray, fa-dot-circle-o, False, False 15 | WHITE, white, fa-dot-circle-o, False, False 16 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | dist: xenial 2 | language: python 3 | python: 4 | - "3.6" 5 | - "3.7" 6 | 7 | before_install: 8 | - python setup.py sdist 9 | 10 | install: 11 | - pip install -r requirements.txt 12 | - pip install -e . 13 | 14 | script: 15 | - nbfancy configure -y all_magic 16 | - nbfancy init gh-pages --include tutorial 17 | - cd gh-pages 18 | - nbfancy rerun --allow_errors 19 | - nbfancy render 20 | - nbfancy html --include_slides 21 | 22 | deploy: 23 | provider: pages 24 | repo: JDBetteridge/nbfancy 25 | target-branch: gh-pages 26 | local-dir: gh-pages/html 27 | github_token: $GITHUB_TOKEN 28 | skip-cleanup: true 29 | on: 30 | branch: master 31 | python: 3.7 32 | -------------------------------------------------------------------------------- /nbfancy/config/keywords.cfg: -------------------------------------------------------------------------------- 1 | ################################################## 2 | ## Default keywords and colour scheme 3 | ## Supported colours: 4 | ## red, orange, yellow, green, blue, purple, 5 | ## brown, black, grey, gray, white 6 | ## Supported symbols from font-awesome 7 | ################################################## 8 | 9 | ## Heading 10 | Keyword, Colour, Symbol, keep_keyword, hide 11 | 12 | ## Environments 13 | Schedule, white, fa-clock-o, True, False 14 | 15 | Prerequisites, green, fa-star, True, False 16 | Overview, green, fa-file-o, True, False 17 | Key Points, green, fa-key, True, False 18 | Setup, green, fa-gears, True, False 19 | 20 | Exercise, yellow, fa-pencil-square-o, False, False 21 | 22 | Information, blue, fa-info-circle, False, False 23 | Solution, blue, fa-eye, False, True 24 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | import setuptools 2 | 3 | # Long description comes from README file 4 | with open('README.md', 'r') as fh: 5 | long_description = fh.read() 6 | 7 | setuptools.setup( 8 | name='nbfancy', 9 | version='0.1a2', 10 | author='Jack Betteridge', 11 | packages=setuptools.find_packages(), 12 | package_data = { 13 | 'nbfancy/config' : ['*.ipynb', '*.cfg'], 14 | 'nbfancy/tools' : ['*'], 15 | }, 16 | include_package_data=True, 17 | install_requires = ['ipython>=6', 'jupyter',], 18 | entry_points={ 19 | 'console_scripts': [ 20 | 'nbfancy=nbfancy.__main__:main', 21 | ], 22 | }, 23 | author_email='J.D.Betteridge@Bath.ac.uk', 24 | description='Tools for rendering notebooks suitable for teaching', 25 | long_description=long_description, 26 | long_description_content_type='text/markdown', 27 | url='https://github.com/JDBetteridge/nbfancy', 28 | classifiers=[ 29 | 'Programming Language :: Python :: 3', 30 | 'License :: OSI Approved :: BSD License', 31 | 'Operating System :: POSIX :: Linux', 32 | ], 33 | ) 34 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2019 Jack Betteridge & James Grant 2 | 3 | In keeping with the Jupyter and iPython packages, NBfancy is released 4 | under the bsd-3-clause licence: 5 | 6 | Redistribution and use in source and binary forms,with or without 7 | modification, are permitted provided that the following conditions are 8 | met: 9 | 10 | 1. Redistributions of source code must retain the above copyright 11 | notice, this list of conditions and the following disclaimer. 12 | 13 | 2. Redistributions in binary form must reproduce the above copyright 14 | notice, this list of conditions and the following disclaimer in the 15 | documentation and/or other materials provided with the distribution. 16 | 17 | 3. Neither the name of the copyright holder nor the names of its 18 | contributors may be used to endorse or promote products derived from 19 | this software without specific prior written permission. 20 | 21 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 22 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 23 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A 24 | PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 25 | HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 26 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED 27 | TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 28 | PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 29 | LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 30 | NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 31 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 32 | -------------------------------------------------------------------------------- /nbfancy/template/01_untitled_episode.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": { 6 | "slideshow": { 7 | "slide_type": "slide" 8 | } 9 | }, 10 | "source": [ 11 | "# Introduction" 12 | ] 13 | }, 14 | { 15 | "cell_type": "markdown", 16 | "metadata": { 17 | "slideshow": { 18 | "slide_type": "subslide" 19 | } 20 | }, 21 | "source": [ 22 | "## Overview:\n", 23 | "- **Teaching:** 0 min\n", 24 | "- **Exercises:** 0 min\n", 25 | "\n", 26 | "**Questions**\n", 27 | "- Ask some questions?\n", 28 | "\n", 29 | "**Objectives**\n", 30 | "- Set some objectives" 31 | ] 32 | }, 33 | { 34 | "cell_type": "markdown", 35 | "metadata": { 36 | "slideshow": { 37 | "slide_type": "slide" 38 | } 39 | }, 40 | "source": [ 41 | "Add lesson material here" 42 | ] 43 | }, 44 | { 45 | "cell_type": "markdown", 46 | "metadata": { 47 | "slideshow": { 48 | "slide_type": "slide" 49 | } 50 | }, 51 | "source": [ 52 | "## Key Points:\n", 53 | "- Conclude with some key points" 54 | ] 55 | } 56 | ], 57 | "metadata": { 58 | "celltoolbar": "Slideshow", 59 | "kernelspec": { 60 | "display_name": "Python 3", 61 | "language": "python", 62 | "name": "python3" 63 | }, 64 | "language_info": { 65 | "codemirror_mode": { 66 | "name": "ipython", 67 | "version": 3 68 | }, 69 | "file_extension": ".py", 70 | "mimetype": "text/x-python", 71 | "name": "python", 72 | "nbconvert_exporter": "python", 73 | "pygments_lexer": "ipython3", 74 | "version": "3.7.3" 75 | } 76 | }, 77 | "nbformat": 4, 78 | "nbformat_minor": 2 79 | } 80 | -------------------------------------------------------------------------------- /nbfancy/tutorial/10_magic.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": { 6 | "slideshow": { 7 | "slide_type": "slide" 8 | } 9 | }, 10 | "source": [ 11 | "# Magic" 12 | ] 13 | }, 14 | { 15 | "cell_type": "markdown", 16 | "metadata": { 17 | "slideshow": { 18 | "slide_type": "slide" 19 | } 20 | }, 21 | "source": [ 22 | "## Overview:\n", 23 | "- **Teaching:** 0 min\n", 24 | "- **Exercises:** 0 min\n", 25 | "\n", 26 | "**Questions**\n", 27 | "- Ask some questions?\n", 28 | "\n", 29 | "**Objectives**\n", 30 | "- Set some objectives" 31 | ] 32 | }, 33 | { 34 | "cell_type": "markdown", 35 | "metadata": { 36 | "slideshow": { 37 | "slide_type": "slide" 38 | } 39 | }, 40 | "source": [ 41 | "## Information: Under Construction\n", 42 | "This section is coming soon! (As soon as we can)" 43 | ] 44 | }, 45 | { 46 | "cell_type": "markdown", 47 | "metadata": { 48 | "slideshow": { 49 | "slide_type": "slide" 50 | } 51 | }, 52 | "source": [ 53 | "## Key Points:\n", 54 | "- Conclude with some key points" 55 | ] 56 | } 57 | ], 58 | "metadata": { 59 | "celltoolbar": "Slideshow", 60 | "kernelspec": { 61 | "display_name": "Python 3", 62 | "language": "python", 63 | "name": "python3" 64 | }, 65 | "language_info": { 66 | "codemirror_mode": { 67 | "name": "ipython", 68 | "version": 3 69 | }, 70 | "file_extension": ".py", 71 | "mimetype": "text/x-python", 72 | "name": "python", 73 | "nbconvert_exporter": "python", 74 | "pygments_lexer": "ipython3", 75 | "version": "3.5.2" 76 | } 77 | }, 78 | "nbformat": 4, 79 | "nbformat_minor": 2 80 | } 81 | -------------------------------------------------------------------------------- /nbfancy/tutorial/11_slides.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": { 6 | "slideshow": { 7 | "slide_type": "slide" 8 | } 9 | }, 10 | "source": [ 11 | "# Slides" 12 | ] 13 | }, 14 | { 15 | "cell_type": "markdown", 16 | "metadata": { 17 | "slideshow": { 18 | "slide_type": "slide" 19 | } 20 | }, 21 | "source": [ 22 | "## Overview:\n", 23 | "- **Teaching:** 0 min\n", 24 | "- **Exercises:** 0 min\n", 25 | "\n", 26 | "**Questions**\n", 27 | "- Ask some questions?\n", 28 | "\n", 29 | "**Objectives**\n", 30 | "- Set some objectives" 31 | ] 32 | }, 33 | { 34 | "cell_type": "markdown", 35 | "metadata": { 36 | "slideshow": { 37 | "slide_type": "slide" 38 | } 39 | }, 40 | "source": [ 41 | "## Information: Under Construction\n", 42 | "This section is coming soon! (As soon as we can)" 43 | ] 44 | }, 45 | { 46 | "cell_type": "markdown", 47 | "metadata": { 48 | "slideshow": { 49 | "slide_type": "slide" 50 | } 51 | }, 52 | "source": [ 53 | "## Key Points:\n", 54 | "- Conclude with some key points" 55 | ] 56 | } 57 | ], 58 | "metadata": { 59 | "celltoolbar": "Slideshow", 60 | "kernelspec": { 61 | "display_name": "Python 3", 62 | "language": "python", 63 | "name": "python3" 64 | }, 65 | "language_info": { 66 | "codemirror_mode": { 67 | "name": "ipython", 68 | "version": 3 69 | }, 70 | "file_extension": ".py", 71 | "mimetype": "text/x-python", 72 | "name": "python", 73 | "nbconvert_exporter": "python", 74 | "pygments_lexer": "ipython3", 75 | "version": "3.5.2" 76 | } 77 | }, 78 | "nbformat": 4, 79 | "nbformat_minor": 2 80 | } 81 | -------------------------------------------------------------------------------- /nbfancy/tutorial/12_contributing.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": { 6 | "slideshow": { 7 | "slide_type": "slide" 8 | } 9 | }, 10 | "source": [ 11 | "# Contributing" 12 | ] 13 | }, 14 | { 15 | "cell_type": "markdown", 16 | "metadata": { 17 | "slideshow": { 18 | "slide_type": "slide" 19 | } 20 | }, 21 | "source": [ 22 | "## Overview:\n", 23 | "- **Teaching:** 0 min\n", 24 | "- **Exercises:** 0 min\n", 25 | "\n", 26 | "**Questions**\n", 27 | "- Ask some questions?\n", 28 | "\n", 29 | "**Objectives**\n", 30 | "- Set some objectives" 31 | ] 32 | }, 33 | { 34 | "cell_type": "markdown", 35 | "metadata": { 36 | "slideshow": { 37 | "slide_type": "slide" 38 | } 39 | }, 40 | "source": [ 41 | "## Information: Under Construction\n", 42 | "This section is coming soon! (As soon as we can)" 43 | ] 44 | }, 45 | { 46 | "cell_type": "markdown", 47 | "metadata": { 48 | "slideshow": { 49 | "slide_type": "slide" 50 | } 51 | }, 52 | "source": [ 53 | "## Key Points:\n", 54 | "- Conclude with some key points" 55 | ] 56 | } 57 | ], 58 | "metadata": { 59 | "celltoolbar": "Slideshow", 60 | "kernelspec": { 61 | "display_name": "Python 3", 62 | "language": "python", 63 | "name": "python3" 64 | }, 65 | "language_info": { 66 | "codemirror_mode": { 67 | "name": "ipython", 68 | "version": 3 69 | }, 70 | "file_extension": ".py", 71 | "mimetype": "text/x-python", 72 | "name": "python", 73 | "nbconvert_exporter": "python", 74 | "pygments_lexer": "ipython3", 75 | "version": "3.5.2" 76 | } 77 | }, 78 | "nbformat": 4, 79 | "nbformat_minor": 2 80 | } 81 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # NBfancy 2 | 3 | (C) 2019 Jack Betteridge (j.d.betteridge@bath.ac.uk) and James Grant (r.j.grant@bath.ac.uk) 4 | 5 | This repository contains NBfancy, 6 | a tool for adding decoration and extended features to Jupyter notebooks. 7 | The code was developed by Jack Betteridge and James Grant 8 | for the development of programming training material: 9 | [arc-bath](https://github.com/arc-bath) and [arc-lessons](https://github.com/arc-lessons). 10 | 11 | ### Documentation 12 | 13 | The install contains the tools which make up the build system for enriching notebooks, 14 | and by way of example, also contains the documentation in the form of a tutorial. 15 | This material will assist users in creating their own courses, 16 | as both a lesson and demonstration of NBfancy's capabilities. 17 | 18 | Jupyter notebooks were chosen as the medium for out training material as: 19 | 20 | * They use markdown which is simple to use even for those not familiar. 21 | * Modifying individual codeblocks is intuitive compared with Sphinx/Jekyll builds. 22 | * We like being able to mix markdown and executable codeblocks in training material. 23 | 24 | In order to retain these features we created NBfancy to process initial notebooks 25 | into enriched Jupyter notebooks for interactive use by students, 26 | html pages for lesson material 27 | and pdfs for distribution as printouts (coming in a future release). 28 | 29 | The documentation/tutorial is available online in 30 | [rendered html](https://jdbetteridge.github.io/nbfancy/00_schedule.html) 31 | 32 | The library has recently undergone a signficant refactoring so please raise an issue if you encounter any trouble using nbfancy or incosistencies in the documentation. 33 | 34 | ### Contributions to NBfancy 35 | 36 | If you would like to contribute to NBfancy please fork the repository and create a pull request against the development branch. 37 | -------------------------------------------------------------------------------- /nbfancy/config/footer.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "DO NOT REMOVE THIS CELL!\n", 8 | "\n", 9 | "You can replace the existing template by modifying the next cell. When the notebook is rendered the following will happen:\n", 10 | "\n", 11 | "1. All the cells below will be inserted at the bottom of all the rendered notebooks, except the index/schedule notebook.\n", 12 | "2. `{previous}` will be replaced with a link to the previous (ASCII alphabetically) notebook.\n", 13 | "3. `{index}` will be replaced with a link to the index/schedule notebook (usually `00_schedule.ipynb`).\n", 14 | "4. `{next}` will be replaced with a link to the next (ASCII alphabetically) notebook, or index/schedule if last." 15 | ] 16 | }, 17 | { 18 | "cell_type": "markdown", 19 | "metadata": { 20 | "slideshow": { 21 | "slide_type": "slide" 22 | } 23 | }, 24 | "source": [ 25 | "
\n", 26 | "

Previous

\n", 27 | "

Schedule

\n", 28 | "

Next

\n", 29 | "
" 30 | ] 31 | } 32 | ], 33 | "metadata": { 34 | "kernelspec": { 35 | "display_name": "Python 3", 36 | "language": "python", 37 | "name": "python3" 38 | }, 39 | "language_info": { 40 | "codemirror_mode": { 41 | "name": "ipython", 42 | "version": 3 43 | }, 44 | "file_extension": ".py", 45 | "mimetype": "text/x-python", 46 | "name": "python", 47 | "nbconvert_exporter": "python", 48 | "pygments_lexer": "ipython3", 49 | "version": "3.7.3" 50 | } 51 | }, 52 | "nbformat": 4, 53 | "nbformat_minor": 2 54 | } 55 | -------------------------------------------------------------------------------- /nbfancy/template/00_schedule.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": { 6 | "slideshow": { 7 | "slide_type": "slide" 8 | } 9 | }, 10 | "source": [ 11 | "# Lesson title\n", 12 | "\n", 13 | "This is a description of the lesson" 14 | ] 15 | }, 16 | { 17 | "cell_type": "markdown", 18 | "metadata": { 19 | "slideshow": { 20 | "slide_type": "slide" 21 | } 22 | }, 23 | "source": [ 24 | "## Prerequisites:\n", 25 | "\n", 26 | "To complete this lesson you should be able to do all the things in this list:\n", 27 | "* Item 1\n", 28 | "* Item 2\n", 29 | "* Item 3" 30 | ] 31 | }, 32 | { 33 | "cell_type": "markdown", 34 | "metadata": { 35 | "slideshow": { 36 | "slide_type": "slide" 37 | } 38 | }, 39 | "source": [ 40 | "## Schedule:\n", 41 | "\n", 42 | "| Time | Episode | Description |\n", 43 | "|---|---|---|\n", 44 | "| 0:00 | [Introduction](01_untitled_episode.ipynb) | Introduction |\n", 45 | "| 0:15 | [Next Lesson]() | Description |" 46 | ] 47 | }, 48 | { 49 | "cell_type": "markdown", 50 | "metadata": { 51 | "slideshow": { 52 | "slide_type": "subslide" 53 | } 54 | }, 55 | "source": [ 56 | "Be sure to edit the links above." 57 | ] 58 | }, 59 | { 60 | "cell_type": "markdown", 61 | "metadata": { 62 | "slideshow": { 63 | "slide_type": "slide" 64 | } 65 | }, 66 | "source": [ 67 | "## Setup:\n", 68 | "\n", 69 | "Add any setup instructions here, or if there are a lot, consider putting it in its own section." 70 | ] 71 | } 72 | ], 73 | "metadata": { 74 | "celltoolbar": "Slideshow", 75 | "kernelspec": { 76 | "display_name": "Python 3", 77 | "language": "python", 78 | "name": "python3" 79 | }, 80 | "language_info": { 81 | "codemirror_mode": { 82 | "name": "ipython", 83 | "version": 3 84 | }, 85 | "file_extension": ".py", 86 | "mimetype": "text/x-python", 87 | "name": "python", 88 | "nbconvert_exporter": "python", 89 | "pygments_lexer": "ipython3", 90 | "version": "3.7.3" 91 | } 92 | }, 93 | "nbformat": 4, 94 | "nbformat_minor": 2 95 | } 96 | -------------------------------------------------------------------------------- /nbfancy/config/box.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "DO NOT REMOVE THIS CELL!\n", 8 | "\n", 9 | "You can replace the existing template by modifying the next cell. When the notebook is rendered the following will happen:\n", 10 | "\n", 11 | "1. The keyword does not need to appear in this template and will be automatically removed, unless specified in `default.cfg`.\n", 12 | "2. The colour `w3-green` will be substitued with the colour in `default.cfg`.\n", 13 | "3. The colour `w3-pale-green` will be substituted with the corresponding pale background colour.\n", 14 | "4. `{index}` is used internally, don't remove it or you will break things.\n", 15 | "5. The symbol `fa-star` will be substitued with the symbol in `default.cfg`.\n", 16 | "6. `TITLE` will be substituted with the marked up title which follows the colon after the keyword.\n", 17 | "7. `BODY` will be substituted with the marked up content of the cell after the first line.\n", 18 | "\n", 19 | "In the plain notebook, this markdown would generate the next cell:\n", 20 | "```markdown\n", 21 | "## Keyword: TITLE\n", 22 | "BODY\n", 23 | "```\n", 24 | "\n", 25 | "Note the spacing may not be exactly the same since the `TITLE` and `BODY` will have additional HTML tags added when marked up." 26 | ] 27 | }, 28 | { 29 | "cell_type": "markdown", 30 | "metadata": {}, 31 | "source": [ 32 | "
\n", 33 | "

TITLE

\n", 34 | " BODY\n", 35 | "
" 36 | ] 37 | } 38 | ], 39 | "metadata": { 40 | "kernelspec": { 41 | "display_name": "Python 3", 42 | "language": "python", 43 | "name": "python3" 44 | }, 45 | "language_info": { 46 | "codemirror_mode": { 47 | "name": "ipython", 48 | "version": 3 49 | }, 50 | "file_extension": ".py", 51 | "mimetype": "text/x-python", 52 | "name": "python", 53 | "nbconvert_exporter": "python", 54 | "pygments_lexer": "ipython3", 55 | "version": "3.7.1" 56 | } 57 | }, 58 | "nbformat": 4, 59 | "nbformat_minor": 2 60 | } 61 | -------------------------------------------------------------------------------- /nbfancy/template/99_episode_template.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": { 6 | "slideshow": { 7 | "slide_type": "slide" 8 | } 9 | }, 10 | "source": [ 11 | "# Lesson Title" 12 | ] 13 | }, 14 | { 15 | "cell_type": "markdown", 16 | "metadata": { 17 | "slideshow": { 18 | "slide_type": "slide" 19 | } 20 | }, 21 | "source": [ 22 | "## Overview:\n", 23 | "- **Teaching:** 0 min\n", 24 | "- **Exercises:** 0 min\n", 25 | "\n", 26 | "**Questions**\n", 27 | "- Ask some questions?\n", 28 | "\n", 29 | "**Objectives**\n", 30 | "- Set some objectives" 31 | ] 32 | }, 33 | { 34 | "cell_type": "markdown", 35 | "metadata": { 36 | "slideshow": { 37 | "slide_type": "slide" 38 | } 39 | }, 40 | "source": [ 41 | "## Information: Information\n", 42 | "Did you know?" 43 | ] 44 | }, 45 | { 46 | "cell_type": "markdown", 47 | "metadata": { 48 | "slideshow": { 49 | "slide_type": "slide" 50 | } 51 | }, 52 | "source": [ 53 | "## Exercise: The first exercise\n", 54 | "This is an exercise you should complete\n", 55 | "\n", 56 | "[solution]()" 57 | ] 58 | }, 59 | { 60 | "cell_type": "markdown", 61 | "metadata": { 62 | "slideshow": { 63 | "slide_type": "slide" 64 | } 65 | }, 66 | "source": [ 67 | "## Solution: The first exercise\n", 68 | "This is the solution to the exercise" 69 | ] 70 | }, 71 | { 72 | "cell_type": "markdown", 73 | "metadata": { 74 | "slideshow": { 75 | "slide_type": "slide" 76 | } 77 | }, 78 | "source": [ 79 | "## Key Points:\n", 80 | "- Conclude with some key points" 81 | ] 82 | } 83 | ], 84 | "metadata": { 85 | "celltoolbar": "Slideshow", 86 | "kernelspec": { 87 | "display_name": "Python 3", 88 | "language": "python", 89 | "name": "python3" 90 | }, 91 | "language_info": { 92 | "codemirror_mode": { 93 | "name": "ipython", 94 | "version": 3 95 | }, 96 | "file_extension": ".py", 97 | "mimetype": "text/x-python", 98 | "name": "python", 99 | "nbconvert_exporter": "python", 100 | "pygments_lexer": "ipython3", 101 | "version": "3.7.3" 102 | } 103 | }, 104 | "nbformat": 4, 105 | "nbformat_minor": 2 106 | } 107 | -------------------------------------------------------------------------------- /bash2_magic/bash2_magic.py: -------------------------------------------------------------------------------- 1 | from IPython.core import magic_arguments 2 | from IPython.display import Image 3 | from IPython.core.magic import line_magic, cell_magic, line_cell_magic, Magics, magics_class 4 | from IPython.core.magic_arguments import argument, magic_arguments, parse_argstring 5 | from IPython.core.magics import script 6 | 7 | import tempfile 8 | import os 9 | import sys 10 | import subprocess 11 | 12 | @magics_class 13 | class bash2Magic(Magics): 14 | @magic_arguments() 15 | @argument('--dir', help='Set the working directory') 16 | @cell_magic 17 | def bash2(self, line='', cell=None): 18 | ''' 19 | Wrapper for bash magic 20 | ''' 21 | 22 | # Parse args 23 | args = parse_argstring(self.bash2, line) 24 | #print(args) 25 | 26 | # Change to new working directory 27 | try: 28 | if args.dir is not None: 29 | if args.dir[0] == '~': 30 | args.dir = args.dir.replace('~', os.path.expanduser('~')) 31 | 32 | os.chdir(args.dir) 33 | except Exception as e: 34 | print(e) 35 | else: 36 | # Instantiate original bash magic 37 | newscript = script.ScriptMagics 38 | newscript.shell = self.shell 39 | 40 | # Add oneliner to end of cell 41 | pwdcmd = '\necho :pwd:`pwd` >&2' 42 | newcell = cell + pwdcmd 43 | 44 | # Call script magic for bash 45 | newscript.shebang(newscript, line='bash --err reterr', cell=newcell) 46 | 47 | # Extract new directory 48 | err = newscript.shell.user_ns['reterr'] 49 | parts = err.split(':pwd:') 50 | newdir = parts[-1].strip() 51 | 52 | # Write the stderr as normal, without directory info 53 | sys.stderr.write(parts[0]) 54 | sys.stderr.flush() 55 | 56 | # Change into new directory 57 | try: 58 | os.chdir(newdir) 59 | except Exception as e: 60 | print(e) 61 | 62 | return 63 | 64 | # Allows loading as an extension 65 | def load_ipython_extension(ipython): 66 | ipython.register_magics(bash2Magic) 67 | 68 | # Useful if you have already loaded as an extension 69 | #ip = get_ipython() 70 | #ip.register_magics(bash2Magic) 71 | -------------------------------------------------------------------------------- /pdflatex_magic/pdflatex_magic.py: -------------------------------------------------------------------------------- 1 | from IPython.core import magic_arguments 2 | from IPython.display import Image 3 | from IPython.core.magic import line_magic, cell_magic, line_cell_magic, Magics, magics_class 4 | 5 | import tempfile 6 | import os 7 | import subprocess 8 | 9 | @magics_class 10 | class PDFLatexMagic(Magics): 11 | @cell_magic 12 | def pdflatex(self, line='', cell=None): 13 | '''Wrap cell text with latex: header, footer 14 | Run pdflatex on resultant file 15 | Display image''' 16 | 17 | # Get working directory and save for later 18 | cwd = os.getcwd() 19 | 20 | # UID 21 | self._id = str(id(cell)) 22 | 23 | # Setup temporary directory 24 | self.tempdir = tempfile.TemporaryDirectory() 25 | os.chdir(self.tempdir.name) 26 | self.texname = os.path.join(self.tempdir.name, self._id + '.tex') 27 | self.pdfname = os.path.join(self.tempdir.name, self._id + '.pdf') 28 | self.pngname = os.path.join(self.tempdir.name, self._id + '.png') 29 | 30 | self.writeLatex(cell, cwd) 31 | 32 | if self.makeImage(): 33 | ret_val = Image(filename=self.pngname) 34 | else: 35 | print(self.error) 36 | print(self.error.output.decode()) 37 | ret_val = None 38 | 39 | os.chdir(cwd) 40 | 41 | return ret_val 42 | 43 | def writeLatex(self, celltext, cwd): 44 | '''Write cell text wrapped with latex: header, footer''' 45 | 46 | template = r''' 47 | \documentclass[convert, varwidth]{standalone} 48 | \usepackage{amsmath,amssymb,amsthm,amsxtra,graphicx} 49 | \graphicspath{{%(CWD)s/}{%(CWD)s/../images/}{%(CWD)s/../figures/}} 50 | \begin{document} 51 | %(CELLTEXT)s 52 | \end{document} 53 | ''' 54 | 55 | with open(self.texname, 'w') as tex: 56 | tex.write(template%{'CELLTEXT' : celltext, 'CWD' : cwd}) 57 | 58 | def makeImage(self): 59 | '''Make pdf with pdflatex and convert to png 60 | Remove current files''' 61 | 62 | # return True, unless pdflatex or convert fail 63 | made_pdf = False 64 | made_png = False 65 | 66 | try: 67 | # Alternative (azure???) doesn't work with figures 68 | #pdflatex --output-format=dvi test.tex 69 | #dvipng -D 300 test.dvi 70 | result = subprocess.run(['pdflatex', '-halt-on-error', self.texname], 71 | check=True, 72 | timeout=5, 73 | stdout=subprocess.PIPE, 74 | stderr=subprocess.STDOUT) 75 | made_pdf = True 76 | except Exception as e: 77 | self.error = e 78 | 79 | if made_pdf: 80 | try: 81 | result2 = subprocess.run(['convert', '-density', '300', self.pdfname, '-quality', '90', self.pngname], 82 | check=True, 83 | timeout=5, 84 | stdout=subprocess.PIPE, 85 | stderr=subprocess.STDOUT) 86 | made_png = True 87 | except Exception as e: 88 | self.error = e 89 | 90 | return made_png 91 | 92 | # Allows loading as an extension 93 | def load_ipython_extension(ipython): 94 | ipython.register_magics(PDFLatexMagic) 95 | 96 | # Useful if you have already loaded as an extension 97 | #ip = get_ipython() 98 | #ip.register_magics(PDFLatexMagic) 99 | -------------------------------------------------------------------------------- /nbfancy/colourscheme_sandbox.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "Test colour schemes here" 8 | ] 9 | }, 10 | { 11 | "cell_type": "markdown", 12 | "metadata": {}, 13 | "source": [ 14 | "
\n", 15 | "

TITLE

\n", 16 | " BODY\n", 17 | "
" 18 | ] 19 | }, 20 | { 21 | "cell_type": "markdown", 22 | "metadata": {}, 23 | "source": [ 24 | "
\n", 25 | "

TITLE

\n", 26 | " BODY\n", 27 | "
" 28 | ] 29 | }, 30 | { 31 | "cell_type": "markdown", 32 | "metadata": {}, 33 | "source": [ 34 | "
\n", 35 | "

TITLE

\n", 36 | " BODY\n", 37 | "
" 38 | ] 39 | }, 40 | { 41 | "cell_type": "markdown", 42 | "metadata": {}, 43 | "source": [ 44 | "
\n", 45 | "

TITLE

\n", 46 | " BODY\n", 47 | "
" 48 | ] 49 | }, 50 | { 51 | "cell_type": "markdown", 52 | "metadata": {}, 53 | "source": [ 54 | "
\n", 55 | "

TITLE

\n", 56 | " BODY\n", 57 | "
" 58 | ] 59 | }, 60 | { 61 | "cell_type": "markdown", 62 | "metadata": {}, 63 | "source": [ 64 | "
\n", 65 | "

TITLE

\n", 66 | " BODY\n", 67 | "
" 68 | ] 69 | }, 70 | { 71 | "cell_type": "markdown", 72 | "metadata": {}, 73 | "source": [ 74 | "
\n", 75 | "

TITLE

\n", 76 | " BODY\n", 77 | "
" 78 | ] 79 | }, 80 | { 81 | "cell_type": "markdown", 82 | "metadata": {}, 83 | "source": [ 84 | "
\n", 85 | "

TITLE

\n", 86 | " BODY\n", 87 | "
" 88 | ] 89 | }, 90 | { 91 | "cell_type": "markdown", 92 | "metadata": {}, 93 | "source": [ 94 | "
\n", 95 | "

TITLE

\n", 96 | " BODY\n", 97 | "
" 98 | ] 99 | }, 100 | { 101 | "cell_type": "markdown", 102 | "metadata": {}, 103 | "source": [ 104 | "
\n", 105 | "

Title

\n", 106 | " BODY\n", 107 | "
" 108 | ] 109 | } 110 | ], 111 | "metadata": { 112 | "kernelspec": { 113 | "display_name": "Python 3", 114 | "language": "python", 115 | "name": "python3" 116 | }, 117 | "language_info": { 118 | "codemirror_mode": { 119 | "name": "ipython", 120 | "version": 3 121 | }, 122 | "file_extension": ".py", 123 | "mimetype": "text/x-python", 124 | "name": "python", 125 | "nbconvert_exporter": "python", 126 | "pygments_lexer": "ipython3", 127 | "version": "3.7.1" 128 | } 129 | }, 130 | "nbformat": 4, 131 | "nbformat_minor": 2 132 | } 133 | -------------------------------------------------------------------------------- /nbfancy/tutorial/00_schedule.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": { 6 | "slideshow": { 7 | "slide_type": "slide" 8 | } 9 | }, 10 | "source": [ 11 | "# NBfancy Tutorial\n", 12 | "\n", 13 | "This tutorial explains how to use NBfancy to generate and build your own courses[.](slides/00_schedule.html)" 14 | ] 15 | }, 16 | { 17 | "cell_type": "markdown", 18 | "metadata": { 19 | "slideshow": { 20 | "slide_type": "subslide" 21 | } 22 | }, 23 | "source": [ 24 | "The first half covers the core features:\n", 25 | "- Motivation for using NBfancy\n", 26 | "- Installation\n", 27 | "- Typical Build Process\n", 28 | "- File structure\n", 29 | "- Environments\n", 30 | "- Using git and remote builds" 31 | ] 32 | }, 33 | { 34 | "cell_type": "markdown", 35 | "metadata": { 36 | "slideshow": { 37 | "slide_type": "subslide" 38 | } 39 | }, 40 | "source": [ 41 | "The second half covers more advanced use:\n", 42 | "- Multi-cell environments\n", 43 | "- Defining your own keywords\n", 44 | "- Making your own templates\n", 45 | "- Magic\n", 46 | "- Slides\n", 47 | "- Contributing" 48 | ] 49 | }, 50 | { 51 | "cell_type": "markdown", 52 | "metadata": { 53 | "slideshow": { 54 | "slide_type": "slide" 55 | } 56 | }, 57 | "source": [ 58 | "## Prerequisites:\n", 59 | "\n", 60 | "To get the best out of NBfancy you should be comfortable using:\n", 61 | "\n", 62 | "- Jupyter notebooks\n", 63 | "- A python package manager such as `pip` or `conda`\n", 64 | "- The command line\n", 65 | "- Git\n", 66 | "\n", 67 | "\n", 68 | "The more advanced usage require slightly more in-depth knowledge of the above." 69 | ] 70 | }, 71 | { 72 | "cell_type": "markdown", 73 | "metadata": { 74 | "slideshow": { 75 | "slide_type": "slide" 76 | } 77 | }, 78 | "source": [ 79 | "## Schedule:\n", 80 | "\n", 81 | "### Core features\n", 82 | "\n", 83 | "| Time | Episode | Description |\n", 84 | "|---|---|---|\n", 85 | "| 0:00 | [Motivation](01_motivation.ipynb) | Why use NBfancy? |\n", 86 | "| 0:10 | [Installation](02_installation.ipynb) | Install NBfancy |\n", 87 | "| 0:20 | [Build](03_build.ipynb) | A typical build process |\n", 88 | "| 0:30 | [File Structure](04_files.ipynb) | File structure for lessons |\n", 89 | "| 0:40 | [Environments](05_environments.ipynb) | Additional keywords |\n", 90 | "| 0:50 | [Git and CI](06_git.ipynb) | Automate hosted lessons |\n", 91 | "\n", 92 | "\n", 93 | "### Advanced topics\n", 94 | "\n", 95 | "| Time | Episode | Description |\n", 96 | "|---|---|---|\n", 97 | "| 1:00 | [Multicell](07_multicell.ipynb) | Multiple cell environments |\n", 98 | "| 1:10 | [Keywords](08_keywords.ipynb) | Defining keywords |\n", 99 | "| 1:20 | [Template](09_template.ipynb) | Defining templates |\n", 100 | "| 1:30 | [Magic](10_magic.ipynb) | Extra magic in NBfancy |\n", 101 | "| 1:40 | [Slides](11_slides.ipynb) | Presenting with NBfancy |\n", 102 | "| 1:50 | [Contributing](12_contributing.ipynb) | Contributing to NBfancy |" 103 | ] 104 | }, 105 | { 106 | "cell_type": "markdown", 107 | "metadata": { 108 | "slideshow": { 109 | "slide_type": "slide" 110 | } 111 | }, 112 | "source": [ 113 | "## Setup:\n", 114 | "\n", 115 | "The setup for NBfancy is covered in [Installation](02_installation.ipynb)." 116 | ] 117 | } 118 | ], 119 | "metadata": { 120 | "celltoolbar": "Slideshow", 121 | "kernelspec": { 122 | "display_name": "Python 3", 123 | "language": "python", 124 | "name": "python3" 125 | }, 126 | "language_info": { 127 | "codemirror_mode": { 128 | "name": "ipython", 129 | "version": 3 130 | }, 131 | "file_extension": ".py", 132 | "mimetype": "text/x-python", 133 | "name": "python", 134 | "nbconvert_exporter": "python", 135 | "pygments_lexer": "ipython3", 136 | "version": "3.7.3" 137 | } 138 | }, 139 | "nbformat": 4, 140 | "nbformat_minor": 2 141 | } 142 | -------------------------------------------------------------------------------- /nbfancy/globalconf.py: -------------------------------------------------------------------------------- 1 | import os 2 | import pkg_resources 3 | 4 | from tempfile import mkstemp 5 | from shutil import move, copy, copytree 6 | 7 | from IPython import start_ipython 8 | from IPython.paths import locate_profile 9 | from IPython.core.profiledir import ProfileDirError 10 | 11 | from jupyter_core.paths import jupyter_config_dir 12 | 13 | def replace(filename, pattern, subst): 14 | ''' Replaces one string with another in config file 15 | ''' 16 | #Create temp file 17 | temp_file, abs_path = mkstemp() 18 | with open(temp_file, 'w') as temp: 19 | with open(filename) as fh: 20 | for line in fh: 21 | temp.write(line.replace(pattern, subst)) 22 | #Remove original file 23 | os.remove(filename) 24 | #Move new file 25 | move(abs_path, filename) 26 | 27 | def ipython(): 28 | '''Configuration for IPython 29 | ''' 30 | # Line which must be appended to IPython config 31 | config_line = '''c.InteractiveShellApp.extensions = ['pdflatex_magic', 'bash2_magic']''' 32 | 33 | # Location of IPython default profile 34 | try: 35 | profile = locate_profile() 36 | except (ProfileDirError, OSError): 37 | start_ipython(argv=['profile', 'create', 'default']) 38 | profile = locate_profile() 39 | 40 | # Append extensions line to end of profile config 41 | config_file = os.path.join(profile, 'ipython_config.py') 42 | 43 | # Open config file 44 | fh = open(config_file, 'r+') 45 | 46 | # Search for line to be configured 47 | # Ensure it isn't a comment 48 | line_list = [] 49 | for line in fh.readlines(): 50 | if 'c.InteractiveShellApp.extensions' in line: 51 | if not line.lstrip().startswith('#'): 52 | line_list.append(line) 53 | 54 | # There should be at most one, othewise config is broken 55 | # If config is broken, this won't break it more :-P 56 | if len(line_list) == 1: 57 | # Extract current extensions 58 | before = line_list[0].split('[') 59 | after = before[1].split(']') 60 | 61 | # If none, just put new ones straight it 62 | if after[0].strip() == '': 63 | after[0] = "'pdflatex_magic', 'bash2_magic'" 64 | else: 65 | if "'pdflatex_magic'" not in after[0]: 66 | after[0] += ", 'pdflatex_magic'" 67 | if "'bash2_magic'" not in after[0]: 68 | after[0] += ", 'bash2_magic'" 69 | config_line = before[0] + '[' + ']'.join(after) 70 | fh.close() 71 | 72 | # To substitute into existing file we need to create a copy 73 | replace(config_file, line_list[0], config_line) 74 | else: 75 | # Write config line to file (at the end) 76 | fh.writelines(config_line) 77 | fh.close() 78 | 79 | # fh should be closed before here 80 | 81 | def jupyter(): 82 | '''Configuration for Jupyter 83 | ''' 84 | # Get the path to Jupyter config 85 | jupyter_dir = jupyter_config_dir() 86 | 87 | # Set a target path for the CSS file 88 | custom_dir = os.path.join(jupyter_dir, 'custom') 89 | custom_css_path = os.path.join(custom_dir, 'custom.css') 90 | 91 | if os.path.isfile(custom_css_path): 92 | # Incase the user already uses a custom CSS file, back it up 93 | backup_css_path = os.path.join(custom_dir, 'custom.backup') 94 | os.rename(custom_css_path, backup_css_path) 95 | else: 96 | # Otherwise check if the custom folder is there 97 | if not os.path.isdir(custom_dir): 98 | os.makedirs(custom_dir) 99 | 100 | resource_package = 'nbfancy' 101 | config_path = '/tools/css' # Do not use os.path.join() 102 | css_dir = pkg_resources.resource_filename(resource_package, config_path) 103 | 104 | # Copy our custom CSS file to the path 105 | copy(os.path.join(css_dir, 'custom.css'), custom_css_path) 106 | try: 107 | copytree(os.path.join(css_dir, 'css'), os.path.join(custom_dir, 'css')) 108 | except FileExistsError as e: 109 | print('ERROR: You already have a directory named') 110 | print(os.path.join(custom_dir, 'css')) 111 | print('Remove or rename and try again.') 112 | print('Install will continue, on the assumption that these files are left over from a previous build.') 113 | -------------------------------------------------------------------------------- /nbfancy/nbfancylogo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 19 | 21 | 39 | 41 | 42 | 44 | image/svg+xml 45 | 47 | 48 | 49 | 50 | 51 | 56 | NB 67 | f 77 | ancy 87 | 93 | 99 | 105 | 106 | 107 | -------------------------------------------------------------------------------- /nbfancy/nbfancylogowhite.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 19 | 21 | 39 | 41 | 42 | 44 | image/svg+xml 45 | 47 | 48 | 49 | 50 | 51 | 56 | NB 69 | f 81 | ancy 93 | 101 | 109 | 117 | 118 | 119 | -------------------------------------------------------------------------------- /nbfancy/tutorial/08_keywords.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": { 6 | "slideshow": { 7 | "slide_type": "slide" 8 | } 9 | }, 10 | "source": [ 11 | "# Keywords" 12 | ] 13 | }, 14 | { 15 | "cell_type": "markdown", 16 | "metadata": { 17 | "slideshow": { 18 | "slide_type": "slide" 19 | } 20 | }, 21 | "source": [ 22 | "## Overview:\n", 23 | "- **Teaching:** 10 min\n", 24 | "- **Exercises:** 0 min\n", 25 | "\n", 26 | "**Questions**\n", 27 | "- How can I define my own keywords?\n", 28 | "\n", 29 | "**Objectives**\n", 30 | "- Learn how to make a new type of environment" 31 | ] 32 | }, 33 | { 34 | "cell_type": "markdown", 35 | "metadata": { 36 | "slideshow": { 37 | "slide_type": "slide" 38 | } 39 | }, 40 | "source": [ 41 | "If NBfancy is initialised with the flag `--extra_config` it will give the user access to two additional configuration files: `keywords.cfg` and `box.ipynb`. The first of these, `keywords.cfg`, can be used to modify the behaviour of existing keywords and allows the user to define their own keywords. The notebook file called \"box\" is discussed in the next section." 42 | ] 43 | }, 44 | { 45 | "cell_type": "code", 46 | "execution_count": 4, 47 | "metadata": { 48 | "slideshow": { 49 | "slide_type": "fragment" 50 | } 51 | }, 52 | "outputs": [ 53 | { 54 | "name": "stdout", 55 | "output_type": "stream", 56 | "text": [ 57 | "box.ipynb footer.ipynb header.ipynb keywords.cfg\r\n" 58 | ] 59 | } 60 | ], 61 | "source": [ 62 | "!nbfancy init example2 --extra_conf\n", 63 | "!ls example2/config" 64 | ] 65 | }, 66 | { 67 | "cell_type": "markdown", 68 | "metadata": { 69 | "slideshow": { 70 | "slide_type": "slide" 71 | } 72 | }, 73 | "source": [ 74 | "The configuration file itself is a CSV file, which defines the behaviour of the underlying keywords. If this file is present and different to the internal configuration, it will be used instead." 75 | ] 76 | }, 77 | { 78 | "cell_type": "code", 79 | "execution_count": 3, 80 | "metadata": { 81 | "slideshow": { 82 | "slide_type": "slide" 83 | } 84 | }, 85 | "outputs": [ 86 | { 87 | "name": "stdout", 88 | "output_type": "stream", 89 | "text": [ 90 | "##################################################\r\n", 91 | "## Default keywords and colour scheme\r\n", 92 | "## Supported colours:\r\n", 93 | "## red, orange, yellow, green, blue, purple,\r\n", 94 | "## brown, black, grey, gray, white\r\n", 95 | "## Supported symbols from font-awesome\r\n", 96 | "##################################################\r\n", 97 | "\r\n", 98 | "## Heading\r\n", 99 | "Keyword, Colour, Symbol, keep_keyword, hide\r\n", 100 | "\r\n", 101 | "## Environments\r\n", 102 | "Schedule, white, fa-clock-o, True, False\r\n", 103 | "\r\n", 104 | "Prerequisites, green, fa-star, True, False\r\n", 105 | "Overview, green, fa-file-o, True, False\r\n", 106 | "Key Points, green, fa-key, True, False\r\n", 107 | "Setup, green, fa-gears, True, False\r\n", 108 | "\r\n", 109 | "Exercise, yellow, fa-pencil-square-o, False, False\r\n", 110 | "\r\n", 111 | "Information, blue, fa-info-circle, False, False\r\n", 112 | "Solution, blue, fa-eye, False, True\r\n" 113 | ] 114 | } 115 | ], 116 | "source": [ 117 | "!cat example2/config/keywords.cfg" 118 | ] 119 | }, 120 | { 121 | "cell_type": "markdown", 122 | "metadata": { 123 | "slideshow": { 124 | "slide_type": "slide" 125 | } 126 | }, 127 | "source": [ 128 | "Comments in this file are denoted using `#` and any line starting with `#` is ignored. Blank lines are also ignored allowing for the config to contain vertical spaces. The first line defines the headings: the keyword and all the properties that are expected. After that each line defines an environement by defining a keyword, which will trigger the cell in a notebook to be processed followed by the formatting. The formatting consists of the colour of the boxm the symbol as defined by the font-awsome font, whether or not the keyword is printed in the rendered notebook and finally whether the cell get hidden (used for solutions)." 129 | ] 130 | }, 131 | { 132 | "cell_type": "markdown", 133 | "metadata": { 134 | "slideshow": { 135 | "slide_type": "slide" 136 | } 137 | }, 138 | "source": [ 139 | "## Key Points:\n", 140 | "- The `--extra_conf` flag for the `nbfancy init` command gives access to more configuration files\n", 141 | "- The file `keywords.cfg` is a CSV file used to define new keyword environments" 142 | ] 143 | } 144 | ], 145 | "metadata": { 146 | "celltoolbar": "Slideshow", 147 | "kernelspec": { 148 | "display_name": "Python 3", 149 | "language": "python", 150 | "name": "python3" 151 | }, 152 | "language_info": { 153 | "codemirror_mode": { 154 | "name": "ipython", 155 | "version": 3 156 | }, 157 | "file_extension": ".py", 158 | "mimetype": "text/x-python", 159 | "name": "python", 160 | "nbconvert_exporter": "python", 161 | "pygments_lexer": "ipython3", 162 | "version": "3.7.3" 163 | } 164 | }, 165 | "nbformat": 4, 166 | "nbformat_minor": 2 167 | } 168 | -------------------------------------------------------------------------------- /nbfancy/tutorial/01_motivation.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": { 6 | "slideshow": { 7 | "slide_type": "slide" 8 | } 9 | }, 10 | "source": [ 11 | "# Motivation" 12 | ] 13 | }, 14 | { 15 | "cell_type": "markdown", 16 | "metadata": { 17 | "slideshow": { 18 | "slide_type": "subslide" 19 | } 20 | }, 21 | "source": [ 22 | "## Overview:\n", 23 | "- **Teaching:** 10 min\n", 24 | "- **Exercises:** 0 min\n", 25 | "\n", 26 | "**Questions**\n", 27 | "- What are the challenges in developing teaching material for programming?\n", 28 | "- Why do we like Jupyter notebooks?\n", 29 | "- How can NBfancy help?\n", 30 | "\n", 31 | "**Objectives**\n", 32 | "- Understand the motivation behind the development of NBfancy\n", 33 | "- Find out what problems NBfancy solves" 34 | ] 35 | }, 36 | { 37 | "cell_type": "markdown", 38 | "metadata": { 39 | "slideshow": { 40 | "slide_type": "slide" 41 | } 42 | }, 43 | "source": [ 44 | "## The current method\n", 45 | "In our experience the development of training material, tutorials and lessons for programming can be fairly ad-hoc. The process usually goes something like this:\n", 46 | "1. Start a document in your editor of choice\n", 47 | "2. While (not finished):\n", 48 | " 1. Write some narrative\n", 49 | " 2. Write some _perfect_ example code in a separate text editor/IDE/terminal\n", 50 | " 3. Run code (and it works first time!)\n", 51 | " 4. Copy code to document\n", 52 | " 5. Copy output to document\n", 53 | "3. Write slides/handouts/exercises ..." 54 | ] 55 | }, 56 | { 57 | "cell_type": "markdown", 58 | "metadata": { 59 | "slideshow": { 60 | "slide_type": "slide" 61 | } 62 | }, 63 | "source": [ 64 | "Of course this assumes the perfect world where eveything works first time. The ususal story is by the time we start working on the second example, we notice a mistake in the first example, or something else that is needed, or a better way of writing it. At this stage we need to go back stage 2 and typically several iterations are required. This process is time consuming, reduces the time we invest in the content and means, even minor changes can take significant effort and creating new material. Likely, if you are in this position, you just find someone elses material and hope that it fits your needs." 65 | ] 66 | }, 67 | { 68 | "cell_type": "markdown", 69 | "metadata": { 70 | "slideshow": { 71 | "slide_type": "slide" 72 | } 73 | }, 74 | "source": [ 75 | "## Information: Software Carpentry\n", 76 | "One example of a source of pre-packaged training material is [Software Carpentry](https://software-carpentry.org). We think this is a great example of how lessons should be organised, but the format doesn't lend itself to being readily edited." 77 | ] 78 | }, 79 | { 80 | "cell_type": "markdown", 81 | "metadata": { 82 | "slideshow": { 83 | "slide_type": "slide" 84 | } 85 | }, 86 | "source": [ 87 | "## Jupyter Notebooks\n", 88 | "One approach to mixing code with the text of training material is a [Jupyter notebook](https://jupyter.org). This platform allows a user to write narrative and code simultaneously. The narrative is in markdown, a lightweight markup language with basic formatting. An ever increasing number of programming languagues are supported, with code executed and output viewed all in the same document. It is for this reason that we use notebooks as the basis for NBfancy." 89 | ] 90 | }, 91 | { 92 | "cell_type": "markdown", 93 | "metadata": { 94 | "slideshow": { 95 | "slide_type": "slide" 96 | } 97 | }, 98 | "source": [ 99 | "## NBfancy\n", 100 | "While markdown is lightweight, as a superset of `HTML` notebooks can be made *prettier* by including additional fotrmatting, however this requires using yet another language while introducing additional debugging layer. We solve these issues by using NBfancy, a set of tools written to augment notebooks, to allow you to develop training material more effectively. We do this by\n", 101 | "* Providing a template for all of the material\n", 102 | "* Allowing richer content in markdown cells through keywords\n", 103 | "* Using Jupyter notebooks to mix documentation and code\n", 104 | "* Creating a simple workflow taking you from creating material to publishing it\n", 105 | "* Even more tools to aid creation of new material\n", 106 | "\n", 107 | "NBfancy is also customisable and includes features that allow you to change the system to make it a tool suitable for your needs." 108 | ] 109 | }, 110 | { 111 | "cell_type": "markdown", 112 | "metadata": { 113 | "slideshow": { 114 | "slide_type": "slide" 115 | } 116 | }, 117 | "source": [ 118 | "## Key Points:\n", 119 | "- Creating new teaching material is difficult and time consuming\n", 120 | "- Notebooks can help to some degree\n", 121 | "- NBfancy is designed to enhance the features of notebooks" 122 | ] 123 | } 124 | ], 125 | "metadata": { 126 | "celltoolbar": "Slideshow", 127 | "kernelspec": { 128 | "display_name": "Python 3", 129 | "language": "python", 130 | "name": "python3" 131 | }, 132 | "language_info": { 133 | "codemirror_mode": { 134 | "name": "ipython", 135 | "version": 3 136 | }, 137 | "file_extension": ".py", 138 | "mimetype": "text/x-python", 139 | "name": "python", 140 | "nbconvert_exporter": "python", 141 | "pygments_lexer": "ipython3", 142 | "version": "3.7.3" 143 | } 144 | }, 145 | "nbformat": 4, 146 | "nbformat_minor": 2 147 | } 148 | -------------------------------------------------------------------------------- /nbfancy/tutorial/06_git.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": { 6 | "slideshow": { 7 | "slide_type": "slide" 8 | } 9 | }, 10 | "source": [ 11 | "# Git and Continuous Integration" 12 | ] 13 | }, 14 | { 15 | "cell_type": "markdown", 16 | "metadata": { 17 | "slideshow": { 18 | "slide_type": "subslide" 19 | } 20 | }, 21 | "source": [ 22 | "## Overview:\n", 23 | "- **Teaching:** 10 min\n", 24 | "- **Exercises:** 0 min\n", 25 | "\n", 26 | "**Questions**\n", 27 | "- How can I use continuous integration to deploy my lesson?\n", 28 | "\n", 29 | "**Objectives**\n", 30 | "- Learn which files exclude git repository\n", 31 | "- Manage dependencies with `requirements.txt`\n", 32 | "- Use travis to deploy a lesson as a webpage on github" 33 | ] 34 | }, 35 | { 36 | "cell_type": "markdown", 37 | "metadata": { 38 | "slideshow": { 39 | "slide_type": "slide" 40 | } 41 | }, 42 | "source": [ 43 | "## .gitignore\n", 44 | "\n", 45 | "We use `git` for version control, if you are not familiar then see our [lesson](https://arc-lessons.github.io/version-control-git/00_schedule.html) or the [Software Carpentry](http://swcarpentry.github.io/git-novice/).\n", 46 | "\n", 47 | "To keep our repository clean, we exclude certain files to accidentally adding them to our lesson repositories. Since we are using notebooks, we will probably need to exclude checkpoint files `*.ipynbc`, bytecode `__pycache__`, the rendered notebook and published directories by creating and adding a `.gitignore` in the root of our repository:\n", 48 | "```bash\n", 49 | "**/.ipynb_checkpoints\n", 50 | "**/__pycache__\n", 51 | "nbfancy\n", 52 | "html\n", 53 | "```\n", 54 | "\n", 55 | "\n" 56 | ] 57 | }, 58 | { 59 | "cell_type": "markdown", 60 | "metadata": { 61 | "slideshow": { 62 | "slide_type": "slide" 63 | } 64 | }, 65 | "source": [ 66 | "## Dependencies\n", 67 | "\n", 68 | "If your lesson has specific dependencies you should include these in a `requirements.txt`. This will ensure that they are available for the automated build, and make it clear to your users what libraries they need. Unlike using `!pip install ` in a notebook. For instance we have tried to minimise the dependencies for `nbfancy` so its requirements is:\n", 69 | "```bash\n", 70 | "ipython>=6\n", 71 | "jupyter\n", 72 | "```" 73 | ] 74 | }, 75 | { 76 | "cell_type": "markdown", 77 | "metadata": { 78 | "slideshow": { 79 | "slide_type": "slide" 80 | } 81 | }, 82 | "source": [ 83 | "## Continuous Integration\n", 84 | "\n", 85 | "Automatically building your lesson reduces administration time and also serves to check that everything is building correctly. There is a huge amount of documentation available, [travis-ci](https://docs.travis-ci.com/user/tutorial/) is comprehensive and includes links to set up each aspect of the process. You will also need to set up tokens for automatic deployment to [github pages](https://docs.travis-ci.com/user/deployment/pages/).\n", 86 | "\n", 87 | "You will need to create and add a `.travis.yml` in the root of your repository, ours looks like:" 88 | ] 89 | }, 90 | { 91 | "cell_type": "markdown", 92 | "metadata": { 93 | "slideshow": { 94 | "slide_type": "subslide" 95 | } 96 | }, 97 | "source": [ 98 | "```\n", 99 | "dist: xenial\n", 100 | "language: python\n", 101 | "python:\n", 102 | " - \"3.7\"\n", 103 | "\n", 104 | "install:\n", 105 | " - pip install -r requirements.txt\n", 106 | " - pip install nbfancy\n", 107 | "\n", 108 | "script:\n", 109 | " - nbfancy init --include none\n", 110 | " - nbfancy rerun\n", 111 | " - nbfancy render\n", 112 | " - nbfancy html\n", 113 | "\n", 114 | "deploy:\n", 115 | " provider: pages\n", 116 | " repo: USER/lesson\n", 117 | " target-branch: gh-pages\n", 118 | " local-dir: gh-pages/html\n", 119 | " github_token: $GITHUB_TOKEN\n", 120 | " skip-cleanup: true\n", 121 | " on:\n", 122 | " branch: master\n", 123 | "```" 124 | ] 125 | }, 126 | { 127 | "cell_type": "markdown", 128 | "metadata": { 129 | "slideshow": { 130 | "slide_type": "subslide" 131 | } 132 | }, 133 | "source": [ 134 | "Note that if you don't want to rerun notebooks you should omit this line in `script`. In your requirements we recommend not including `nbfancy` as we have here as it is required to build the lesson, not by your students in the lesson (that is, unless you're writing your own nbfancy lesson) and you will need to change USER to your username.\n", 135 | "\n", 136 | "For descriptions of the different componenets of the file it is best to check out the Travis CI documentation\n" 137 | ] 138 | }, 139 | { 140 | "cell_type": "markdown", 141 | "metadata": { 142 | "slideshow": { 143 | "slide_type": "slide" 144 | } 145 | }, 146 | "source": [ 147 | "## Key Points:\n", 148 | "- Use a gitignore to avoid local builds, checkpoints, bytecode files\n", 149 | "- Use requirements.txt for the dependencies for your lesson\n", 150 | "- For github with travis CI include a .travis.yml and set up your CI" 151 | ] 152 | } 153 | ], 154 | "metadata": { 155 | "celltoolbar": "Slideshow", 156 | "kernelspec": { 157 | "display_name": "Python 3", 158 | "language": "python", 159 | "name": "python3" 160 | }, 161 | "language_info": { 162 | "codemirror_mode": { 163 | "name": "ipython", 164 | "version": 3 165 | }, 166 | "file_extension": ".py", 167 | "mimetype": "text/x-python", 168 | "name": "python", 169 | "nbconvert_exporter": "python", 170 | "pygments_lexer": "ipython3", 171 | "version": "3.7.3" 172 | } 173 | }, 174 | "nbformat": 4, 175 | "nbformat_minor": 2 176 | } 177 | -------------------------------------------------------------------------------- /nbfancy/tutorial/09_template.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": { 6 | "slideshow": { 7 | "slide_type": "slide" 8 | } 9 | }, 10 | "source": [ 11 | "# Template" 12 | ] 13 | }, 14 | { 15 | "cell_type": "markdown", 16 | "metadata": { 17 | "slideshow": { 18 | "slide_type": "slide" 19 | } 20 | }, 21 | "source": [ 22 | "## Overview:\n", 23 | "- **Teaching:** 10 min\n", 24 | "- **Exercises:** 0 min\n", 25 | "\n", 26 | "**Questions**\n", 27 | "- How can I change the look of NBfancy cells?\n", 28 | "\n", 29 | "**Objectives**\n", 30 | "- Understand that a new style can be defined in a notebook" 31 | ] 32 | }, 33 | { 34 | "cell_type": "markdown", 35 | "metadata": { 36 | "slideshow": { 37 | "slide_type": "slide" 38 | } 39 | }, 40 | "source": [ 41 | "If NBfancy is initialised with the flag `--extra_config` it will give the user access to two additional configuration files: `keywords.cfg` and `box.ipynb`. The file `keywords.cfg` is discussed in the previous section. The second file, `box.ipynb`, is a notebook which contains two cells, and allows the user to change the template that NBfancy uses for keyword cells when rendering notebooks." 42 | ] 43 | }, 44 | { 45 | "cell_type": "code", 46 | "execution_count": 1, 47 | "metadata": { 48 | "slideshow": { 49 | "slide_type": "fragment" 50 | } 51 | }, 52 | "outputs": [ 53 | { 54 | "name": "stdout", 55 | "output_type": "stream", 56 | "text": [ 57 | "box.ipynb footer.ipynb header.ipynb keywords.cfg\r\n" 58 | ] 59 | } 60 | ], 61 | "source": [ 62 | "!nbfancy init example2 --extra_conf\n", 63 | "!ls example2/config" 64 | ] 65 | }, 66 | { 67 | "cell_type": "markdown", 68 | "metadata": { 69 | "slideshow": { 70 | "slide_type": "slide" 71 | } 72 | }, 73 | "source": [ 74 | "The first cell give a description of how you can modify the second and read:" 75 | ] 76 | }, 77 | { 78 | "cell_type": "markdown", 79 | "metadata": { 80 | "slideshow": { 81 | "slide_type": "subslide" 82 | } 83 | }, 84 | "source": [ 85 | "You can replace the existing template by modifying the next cell. When the notebook is rendered the following will happen:\n", 86 | "\n", 87 | "1. The keyword does not need to appear in this template and will be automatically removed, unless specified in `default.cfg`.\n", 88 | "2. The colour `w3-green` will be substitued with the colour in `default.cfg`.\n", 89 | "3. The colour `w3-pale-green` will be substituted with the corresponding pale background colour.\n", 90 | "4. `{index}` is used internally, don't remove it or you will break things.\n", 91 | "5. The symbol `fa-star` will be substitued with the symbol in `default.cfg`.\n", 92 | "6. `TITLE` will be substituted with the marked up title which follows the colon after the keyword.\n", 93 | "7. `BODY` will be substituted with the marked up content of the cell after the first line.\n", 94 | "\n", 95 | "In the plain notebook, this markdown would generate the next cell:\n", 96 | "```markdown\n", 97 | "## Keyword: TITLE\n", 98 | "BODY\n", 99 | "```\n", 100 | "\n", 101 | "Note the spacing may not be exactly the same since the `TITLE` and `BODY` will have additional HTML tags added when marked up." 102 | ] 103 | }, 104 | { 105 | "cell_type": "markdown", 106 | "metadata": { 107 | "slideshow": { 108 | "slide_type": "slide" 109 | } 110 | }, 111 | "source": [ 112 | "The second cell contains the HTML code seen below. You can modify to suit the new style you want to use." 113 | ] 114 | }, 115 | { 116 | "cell_type": "code", 117 | "execution_count": 2, 118 | "metadata": { 119 | "slideshow": { 120 | "slide_type": "subslide" 121 | } 122 | }, 123 | "outputs": [ 124 | { 125 | "data": { 126 | "text/html": [ 127 | "
\n", 128 | "

TITLE

\n", 129 | " BODY\n", 130 | "
\n" 131 | ], 132 | "text/plain": [ 133 | "" 134 | ] 135 | }, 136 | "metadata": {}, 137 | "output_type": "display_data" 138 | } 139 | ], 140 | "source": [ 141 | "%%HTML\n", 142 | "
\n", 143 | "

TITLE

\n", 144 | " BODY\n", 145 | "
" 146 | ] 147 | }, 148 | { 149 | "cell_type": "markdown", 150 | "metadata": { 151 | "slideshow": { 152 | "slide_type": "slide" 153 | } 154 | }, 155 | "source": [ 156 | "## Header\n" 157 | ] 158 | }, 159 | { 160 | "cell_type": "markdown", 161 | "metadata": { 162 | "slideshow": { 163 | "slide_type": "slide" 164 | } 165 | }, 166 | "source": [ 167 | "## Footer\n" 168 | ] 169 | }, 170 | { 171 | "cell_type": "markdown", 172 | "metadata": { 173 | "slideshow": { 174 | "slide_type": "slide" 175 | } 176 | }, 177 | "source": [ 178 | "## Key Points:\n", 179 | "- The template that NBfancy uses can be modified by the user\n", 180 | "- The header and footer for the notebooks can also be changed" 181 | ] 182 | } 183 | ], 184 | "metadata": { 185 | "celltoolbar": "Slideshow", 186 | "kernelspec": { 187 | "display_name": "Python 3", 188 | "language": "python", 189 | "name": "python3" 190 | }, 191 | "language_info": { 192 | "codemirror_mode": { 193 | "name": "ipython", 194 | "version": 3 195 | }, 196 | "file_extension": ".py", 197 | "mimetype": "text/x-python", 198 | "name": "python", 199 | "nbconvert_exporter": "python", 200 | "pygments_lexer": "ipython3", 201 | "version": "3.7.3" 202 | } 203 | }, 204 | "nbformat": 4, 205 | "nbformat_minor": 2 206 | } 207 | -------------------------------------------------------------------------------- /nbfancy/tutorial/04_files.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": { 6 | "slideshow": { 7 | "slide_type": "slide" 8 | } 9 | }, 10 | "source": [ 11 | "# Directory Structure" 12 | ] 13 | }, 14 | { 15 | "cell_type": "markdown", 16 | "metadata": { 17 | "slideshow": { 18 | "slide_type": "subslide" 19 | } 20 | }, 21 | "source": [ 22 | "## Overview:\n", 23 | "- **Teaching:** 10 min\n", 24 | "- **Exercises:** 0 min\n", 25 | "\n", 26 | "**Questions**\n", 27 | "- What are all of the files created with `nbfancy init`?\n", 28 | "- Where do I put the notebooks that make up my lesson?\n", 29 | "- Where do I put my other files?\n", 30 | "\n", 31 | "**Objectives**\n", 32 | "- Understand the file structure for a lesson\n", 33 | "- Learn which files go in which directories" 34 | ] 35 | }, 36 | { 37 | "cell_type": "markdown", 38 | "metadata": { 39 | "slideshow": { 40 | "slide_type": "slide" 41 | } 42 | }, 43 | "source": [ 44 | "## Directories\n", 45 | "\n", 46 | "`nbfancy` uses a directory structure to help organise your lessons. As we have seen, by default the *source* notebooks for the lesson are contained in the folder, `nbplain`, rendered notebooks in `nbfancy` and published pages in `html`:" 47 | ] 48 | }, 49 | { 50 | "cell_type": "code", 51 | "execution_count": 5, 52 | "metadata": { 53 | "slideshow": { 54 | "slide_type": "subslide" 55 | } 56 | }, 57 | "outputs": [ 58 | { 59 | "name": "stdout", 60 | "output_type": "stream", 61 | "text": [ 62 | "code config data html images nbfancy nbplain\r\n" 63 | ] 64 | } 65 | ], 66 | "source": [ 67 | "!ls example" 68 | ] 69 | }, 70 | { 71 | "cell_type": "markdown", 72 | "metadata": { 73 | "slideshow": { 74 | "slide_type": "slide" 75 | } 76 | }, 77 | "source": [ 78 | "Additional directories, `code`, `config`, `data` and `images` are also created. \n", 79 | "\n", 80 | "- `code` should be used for source files that are required by your lesson, e.g. bespoke libraries or test files \n", 81 | "- `config` contains files which control the decoration of your lesson\n", 82 | "- `data` any data that your students will work with\n", 83 | "- `images` and pictures or graphics that are not generated automatically\n", 84 | "\n", 85 | "By default only `config` has any contents." 86 | ] 87 | }, 88 | { 89 | "cell_type": "markdown", 90 | "metadata": { 91 | "slideshow": { 92 | "slide_type": "slide" 93 | } 94 | }, 95 | "source": [ 96 | "## `nbplain`\n", 97 | "\n", 98 | "The default template contains three notebooks:" 99 | ] 100 | }, 101 | { 102 | "cell_type": "code", 103 | "execution_count": 7, 104 | "metadata": { 105 | "slideshow": { 106 | "slide_type": "subslide" 107 | } 108 | }, 109 | "outputs": [ 110 | { 111 | "name": "stdout", 112 | "output_type": "stream", 113 | "text": [ 114 | "00_schedule.ipynb 01_untitled_episode.ipynb 99_episode_template.ipynb\r\n" 115 | ] 116 | } 117 | ], 118 | "source": [ 119 | "!ls example/nbplain" 120 | ] 121 | }, 122 | { 123 | "cell_type": "markdown", 124 | "metadata": { 125 | "slideshow": { 126 | "slide_type": "subslide" 127 | } 128 | }, 129 | "source": [ 130 | "These are essentially standard notebooks with decoration determined as described in the [next lesson](05_environments.ipynb). `00_schedule.ipynb` is used to provide a notional schedule for the course, allowing you to specify timings, epsiodes titles and a brief description. The exact format and naming is not a prescription however and could be used as an `index`. \n", 131 | "\n", 132 | "This is followed by notebooks for each of the *episodes* that make up your *lesson*. If you have existing material you can copy the notebooks into the directory add keywords to decorate your lessons, and you should be alomst ready to build. Otherwise this is where you will write all of your notebooks for the lesson.\n", 133 | "\n", 134 | "**NOTE The numbering is used to identify the order that you wish notebooks to appear in the rendered form, the next/previous links at the bottom of each rendered page use the list order, what you see with `ls`, for the navigation.**" 135 | ] 136 | }, 137 | { 138 | "cell_type": "markdown", 139 | "metadata": { 140 | "slideshow": { 141 | "slide_type": "slide" 142 | } 143 | }, 144 | "source": [ 145 | "## config\n", 146 | "\n", 147 | "The `config` folder by default contains two notebooks, a header and footer which contain cells used to *brand* the lesson. You can edit these notebooks as you would with any other as long as you preserve the number and order of the cells in each.\n", 148 | "\n", 149 | "Additional configurabilty is covered in [keywords](08_keywords.ipynb) and [template](09_template.ipynb)." 150 | ] 151 | }, 152 | { 153 | "cell_type": "code", 154 | "execution_count": 6, 155 | "metadata": { 156 | "slideshow": { 157 | "slide_type": "subslide" 158 | } 159 | }, 160 | "outputs": [ 161 | { 162 | "name": "stdout", 163 | "output_type": "stream", 164 | "text": [ 165 | "footer.ipynb header.ipynb\r\n" 166 | ] 167 | } 168 | ], 169 | "source": [ 170 | "!ls example/config" 171 | ] 172 | }, 173 | { 174 | "cell_type": "markdown", 175 | "metadata": { 176 | "slideshow": { 177 | "slide_type": "slide" 178 | } 179 | }, 180 | "source": [ 181 | "## Key Points:\n", 182 | "- `nbfancy` uses a directory structure to oranise your lessons\n", 183 | "- By default `nbplain` is used for plain notebooks, `nbfancy` for decorated notebooks and `html` for published html.\n", 184 | "- `code`, `data` and `images` are used for dependencies of your lesson\n", 185 | "- `config` contains files used to modify the decoration of your lessons" 186 | ] 187 | } 188 | ], 189 | "metadata": { 190 | "celltoolbar": "Slideshow", 191 | "kernelspec": { 192 | "display_name": "Python 3", 193 | "language": "python", 194 | "name": "python3" 195 | }, 196 | "language_info": { 197 | "codemirror_mode": { 198 | "name": "ipython", 199 | "version": 3 200 | }, 201 | "file_extension": ".py", 202 | "mimetype": "text/x-python", 203 | "name": "python", 204 | "nbconvert_exporter": "python", 205 | "pygments_lexer": "ipython3", 206 | "version": "3.7.3" 207 | } 208 | }, 209 | "nbformat": 4, 210 | "nbformat_minor": 2 211 | } 212 | -------------------------------------------------------------------------------- /nbfancy/nbfancyfavicon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 24 | 26 | 31 | 40 | 47 | 54 | 55 | 58 | 62 | 63 | 69 | 76 | 77 | 78 | 97 | 102 | 103 | 105 | 106 | 108 | image/svg+xml 109 | 111 | 112 | 113 | 114 | 115 | 120 | 131 | N 144 | 153 | 162 | 171 | B 184 | f 197 | 198 | 199 | -------------------------------------------------------------------------------- /nbfancy/config/header.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "DO NOT REMOVE THIS CELL!\n", 8 | "\n", 9 | "You can replace the existing template by modifying the next cell. When the notebook is rendered the following will happen:\n", 10 | "\n", 11 | "All the cells below will be inserted at the top of all the rendered notebooks as is." 12 | ] 13 | }, 14 | { 15 | "cell_type": "markdown", 16 | "metadata": { 17 | "slideshow": { 18 | "slide_type": "slide" 19 | } 20 | }, 21 | "source": [ 22 | "
\n", 23 | "

\n", 24 | " \n", 25 | "
" 26 | ] 27 | } 28 | ], 29 | "metadata": { 30 | "kernelspec": { 31 | "display_name": "Python 3", 32 | "language": "python", 33 | "name": "python3" 34 | }, 35 | "language_info": { 36 | "codemirror_mode": { 37 | "name": "ipython", 38 | "version": 3 39 | }, 40 | "file_extension": ".py", 41 | "mimetype": "text/x-python", 42 | "name": "python", 43 | "nbconvert_exporter": "python", 44 | "pygments_lexer": "ipython3", 45 | "version": "3.7.3" 46 | } 47 | }, 48 | "nbformat": 4, 49 | "nbformat_minor": 2 50 | } 51 | -------------------------------------------------------------------------------- /nbfancy/tutorial/07_multicell.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": { 6 | "slideshow": { 7 | "slide_type": "slide" 8 | } 9 | }, 10 | "source": [ 11 | "# Multicell" 12 | ] 13 | }, 14 | { 15 | "cell_type": "markdown", 16 | "metadata": { 17 | "slideshow": { 18 | "slide_type": "slide" 19 | } 20 | }, 21 | "source": [ 22 | "## Overview:\n", 23 | "- **Teaching:** 10 min\n", 24 | "- **Exercises:** 0 min\n", 25 | "\n", 26 | "**Questions**\n", 27 | "- What is a multicell?\n", 28 | "- How can I use multicell functionality?\n", 29 | "\n", 30 | "**Objectives**\n", 31 | "- Understand how the multicell can be used" 32 | ] 33 | }, 34 | { 35 | "cell_type": "markdown", 36 | "metadata": { 37 | "slideshow": { 38 | "slide_type": "slide" 39 | } 40 | }, 41 | "source": [ 42 | "A group of notebook cells can be grouped together into an environment using the multicell functionality. This is done using a keyword, followed by a plus (`+`). From that point on in the notebook all cells, including code and raw cells, are part of the multicell environment. The multicell is terminated using `: keyword +`, after which cells behave as normal." 43 | ] 44 | }, 45 | { 46 | "cell_type": "markdown", 47 | "metadata": { 48 | "slideshow": { 49 | "slide_type": "slide" 50 | } 51 | }, 52 | "source": [ 53 | "## Information: Experimental\n", 54 | "Multicell support is still considered experimental, so be wary that there may still be some bugs in the functionality and unexpected behaviour." 55 | ] 56 | }, 57 | { 58 | "cell_type": "markdown", 59 | "metadata": { 60 | "slideshow": { 61 | "slide_type": "slide" 62 | } 63 | }, 64 | "source": [ 65 | "To prevent markdown cells from being executed, we will show the syntax using `%%markdown` magic. After the example the rendered output is shown." 66 | ] 67 | }, 68 | { 69 | "cell_type": "code", 70 | "execution_count": 1, 71 | "metadata": { 72 | "slideshow": { 73 | "slide_type": "slide" 74 | } 75 | }, 76 | "outputs": [ 77 | { 78 | "data": { 79 | "text/markdown": [ 80 | "## Information+: Multicell information 1\n", 81 | "Here is some information about multicells\n" 82 | ], 83 | "text/plain": [ 84 | "" 85 | ] 86 | }, 87 | "metadata": {}, 88 | "output_type": "display_data" 89 | } 90 | ], 91 | "source": [ 92 | "%%markdown\n", 93 | "## Information+: Multicell information 1\n", 94 | "Here is some information about multicells" 95 | ] 96 | }, 97 | { 98 | "cell_type": "markdown", 99 | "metadata": { 100 | "slideshow": { 101 | "slide_type": "fragment" 102 | } 103 | }, 104 | "source": [ 105 | "* They group notebook cells together into one environment\n", 106 | "* They can be very useful" 107 | ] 108 | }, 109 | { 110 | "cell_type": "markdown", 111 | "metadata": { 112 | "slideshow": { 113 | "slide_type": "fragment" 114 | } 115 | }, 116 | "source": [ 117 | "The multicell is terminated using a colon and the corresponding keyword. (1)\n", 118 | "\n", 119 | ":information+" 120 | ] 121 | }, 122 | { 123 | "cell_type": "markdown", 124 | "metadata": { 125 | "slideshow": { 126 | "slide_type": "slide" 127 | } 128 | }, 129 | "source": [ 130 | "## Information+: Multicell information 1\n", 131 | "Here is some information about multicells" 132 | ] 133 | }, 134 | { 135 | "cell_type": "markdown", 136 | "metadata": { 137 | "slideshow": { 138 | "slide_type": "fragment" 139 | } 140 | }, 141 | "source": [ 142 | "* They group notebook cells together into one environment\n", 143 | "* They can be very useful\n", 144 | "* They struggle when there are **identical** cells in a notebook" 145 | ] 146 | }, 147 | { 148 | "cell_type": "markdown", 149 | "metadata": { 150 | "slideshow": { 151 | "slide_type": "fragment" 152 | } 153 | }, 154 | "source": [ 155 | "The multicell is terminated using a colon and the corresponding keyword. (2)\n", 156 | "\n", 157 | ":information+" 158 | ] 159 | }, 160 | { 161 | "cell_type": "markdown", 162 | "metadata": { 163 | "slideshow": { 164 | "slide_type": "slide" 165 | } 166 | }, 167 | "source": [ 168 | "The above doesn't really show much additional functionality, apart from adding a big indent to the additional cells. The real power of multicells is combining them with code and raw cells" 169 | ] 170 | }, 171 | { 172 | "cell_type": "code", 173 | "execution_count": 2, 174 | "metadata": { 175 | "slideshow": { 176 | "slide_type": "slide" 177 | } 178 | }, 179 | "outputs": [ 180 | { 181 | "data": { 182 | "text/markdown": [ 183 | "## Information+: Multicell information 2\n", 184 | "This is a raw cell\n" 185 | ], 186 | "text/plain": [ 187 | "" 188 | ] 189 | }, 190 | "metadata": {}, 191 | "output_type": "display_data" 192 | } 193 | ], 194 | "source": [ 195 | "%%markdown\n", 196 | "## Information+: Multicell information 2\n", 197 | "This is a raw cell" 198 | ] 199 | }, 200 | { 201 | "cell_type": "raw", 202 | "metadata": { 203 | "slideshow": { 204 | "slide_type": "fragment" 205 | } 206 | }, 207 | "source": [ 208 | "RAW CELL" 209 | ] 210 | }, 211 | { 212 | "cell_type": "markdown", 213 | "metadata": { 214 | "slideshow": { 215 | "slide_type": "fragment" 216 | } 217 | }, 218 | "source": [ 219 | "This is a simple Python3 code cell" 220 | ] 221 | }, 222 | { 223 | "cell_type": "code", 224 | "execution_count": 7, 225 | "metadata": { 226 | "slideshow": { 227 | "slide_type": "fragment" 228 | } 229 | }, 230 | "outputs": [], 231 | "source": [ 232 | "import __hello__" 233 | ] 234 | }, 235 | { 236 | "cell_type": "markdown", 237 | "metadata": { 238 | "slideshow": { 239 | "slide_type": "fragment" 240 | } 241 | }, 242 | "source": [ 243 | "This is the end of the multicell\n", 244 | "\n", 245 | ":information+" 246 | ] 247 | }, 248 | { 249 | "cell_type": "markdown", 250 | "metadata": { 251 | "slideshow": { 252 | "slide_type": "slide" 253 | } 254 | }, 255 | "source": [ 256 | "## Information+: Multicell information 2\n", 257 | "This is another raw cell" 258 | ] 259 | }, 260 | { 261 | "cell_type": "raw", 262 | "metadata": { 263 | "slideshow": { 264 | "slide_type": "fragment" 265 | } 266 | }, 267 | "source": [ 268 | "ROAR" 269 | ] 270 | }, 271 | { 272 | "cell_type": "markdown", 273 | "metadata": { 274 | "slideshow": { 275 | "slide_type": "fragment" 276 | } 277 | }, 278 | "source": [ 279 | "This is a different Python3 code cell" 280 | ] 281 | }, 282 | { 283 | "cell_type": "code", 284 | "execution_count": 8, 285 | "metadata": { 286 | "slideshow": { 287 | "slide_type": "fragment" 288 | } 289 | }, 290 | "outputs": [ 291 | { 292 | "name": "stdout", 293 | "output_type": "stream", 294 | "text": [ 295 | "3.141592653589793\n" 296 | ] 297 | } 298 | ], 299 | "source": [ 300 | "import math\n", 301 | "print(math.pi)" 302 | ] 303 | }, 304 | { 305 | "cell_type": "markdown", 306 | "metadata": { 307 | "slideshow": { 308 | "slide_type": "fragment" 309 | } 310 | }, 311 | "source": [ 312 | "This is the end of the next multicell\n", 313 | "\n", 314 | ":information+" 315 | ] 316 | }, 317 | { 318 | "cell_type": "markdown", 319 | "metadata": { 320 | "slideshow": { 321 | "slide_type": "slide" 322 | } 323 | }, 324 | "source": [ 325 | "It is even possible to nest multicells, given that they are distinct environments. However, we strongly discourage the use of the nesting functionality, and it may be removed in the future." 326 | ] 327 | }, 328 | { 329 | "cell_type": "markdown", 330 | "metadata": { 331 | "slideshow": { 332 | "slide_type": "slide" 333 | } 334 | }, 335 | "source": [ 336 | "## Information+: Forgetting the `: keyword +`\n", 337 | "If the corresponding `: keyword +` is missing from the notebook, there is no way to stop NBfancy from consuming all of the remaining cells in that notebook and including them in that multicell. This is very noticable behaviour and is easily corrected by adding the line to original plain notebook." 338 | ] 339 | }, 340 | { 341 | "cell_type": "markdown", 342 | "metadata": {}, 343 | "source": [ 344 | "## Exercise+: You were probably curious about nested multicells" 345 | ] 346 | }, 347 | { 348 | "cell_type": "markdown", 349 | "metadata": {}, 350 | "source": [ 351 | "## Setup: Don't do it!" 352 | ] 353 | }, 354 | { 355 | "cell_type": "markdown", 356 | "metadata": {}, 357 | "source": [ 358 | ":exercise+" 359 | ] 360 | }, 361 | { 362 | "cell_type": "markdown", 363 | "metadata": {}, 364 | "source": [ 365 | ":information+ 001" 366 | ] 367 | }, 368 | { 369 | "cell_type": "markdown", 370 | "metadata": { 371 | "slideshow": { 372 | "slide_type": "slide" 373 | } 374 | }, 375 | "source": [ 376 | "## Key Points:\n", 377 | "- A multicell environment uses a keyword followed by a `+`\n", 378 | "- All cells in the notebook between the first multicell and the `: keyword +` go into the multicell\n", 379 | "- Multicells are useful for highlighting code" 380 | ] 381 | } 382 | ], 383 | "metadata": { 384 | "celltoolbar": "Slideshow", 385 | "kernelspec": { 386 | "display_name": "Python 3", 387 | "language": "python", 388 | "name": "python3" 389 | }, 390 | "language_info": { 391 | "codemirror_mode": { 392 | "name": "ipython", 393 | "version": 3 394 | }, 395 | "file_extension": ".py", 396 | "mimetype": "text/x-python", 397 | "name": "python", 398 | "nbconvert_exporter": "python", 399 | "pygments_lexer": "ipython3", 400 | "version": "3.7.3" 401 | } 402 | }, 403 | "nbformat": 4, 404 | "nbformat_minor": 2 405 | } 406 | -------------------------------------------------------------------------------- /nbfancy/tutorial/02_installation.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": { 6 | "slideshow": { 7 | "slide_type": "slide" 8 | } 9 | }, 10 | "source": [ 11 | "# Installation" 12 | ] 13 | }, 14 | { 15 | "cell_type": "markdown", 16 | "metadata": { 17 | "slideshow": { 18 | "slide_type": "subslide" 19 | } 20 | }, 21 | "source": [ 22 | "## Overview:\n", 23 | "- **Teaching:** 10 min\n", 24 | "- **Exercises:** 0 min\n", 25 | "\n", 26 | "**Questions**\n", 27 | "- How do I install NBfancy?\n", 28 | "- How do I configure NBfancy for first use?\n", 29 | "\n", 30 | "**Objectives**\n", 31 | "- Know how to install NBfancy!" 32 | ] 33 | }, 34 | { 35 | "cell_type": "markdown", 36 | "metadata": { 37 | "slideshow": { 38 | "slide_type": "slide" 39 | } 40 | }, 41 | "source": [ 42 | "## Information: Virtual environments\n", 43 | "We recommend that you install NBfancy in a virtual environment.\n", 44 | "You can do this with Python [venvs](https://docs.python.org/3/library/venv.html) or [(Ana)conda](https://docs.conda.io/projects/conda/en/latest/user-guide/tasks/manage-environments.html).\n", 45 | "Be sure to activate the virtual environment before carrying out the installation." 46 | ] 47 | }, 48 | { 49 | "cell_type": "markdown", 50 | "metadata": { 51 | "slideshow": { 52 | "slide_type": "slide" 53 | } 54 | }, 55 | "source": [ 56 | "## Install\n", 57 | "\n", 58 | "NBfancy is available in the Python Package index (PyPI), and can be installed using the command `pip` as follows" 59 | ] 60 | }, 61 | { 62 | "cell_type": "code", 63 | "execution_count": 1, 64 | "metadata": { 65 | "scrolled": true, 66 | "slideshow": { 67 | "slide_type": "subslide" 68 | } 69 | }, 70 | "outputs": [ 71 | { 72 | "name": "stdout", 73 | "output_type": "stream", 74 | "text": [ 75 | "Requirement already satisfied: nbfancy in /home/jack/Documents/James_Grant/nbfancy (0.1.dev2)\n", 76 | "Requirement already satisfied: ipython>=6 in /home/jack/Documents/James_Grant/fancy_test/lib/python3.7/site-packages (from nbfancy) (7.4.0)\n", 77 | "Requirement already satisfied: jupyter in /home/jack/Documents/James_Grant/fancy_test/lib/python3.7/site-packages (from nbfancy) (1.0.0)\n", 78 | "Requirement already satisfied: pickleshare in /home/jack/Documents/James_Grant/fancy_test/lib/python3.7/site-packages (from ipython>=6->nbfancy) (0.7.5)\n", 79 | "Requirement already satisfied: pexpect; sys_platform != \"win32\" in /home/jack/Documents/James_Grant/fancy_test/lib/python3.7/site-packages (from ipython>=6->nbfancy) (4.7.0)\n", 80 | "Requirement already satisfied: backcall in /home/jack/Documents/James_Grant/fancy_test/lib/python3.7/site-packages (from ipython>=6->nbfancy) (0.1.0)\n", 81 | "Requirement already satisfied: traitlets>=4.2 in /home/jack/Documents/James_Grant/fancy_test/lib/python3.7/site-packages (from ipython>=6->nbfancy) (4.3.2)\n", 82 | "Requirement already satisfied: pygments in /home/jack/Documents/James_Grant/fancy_test/lib/python3.7/site-packages (from ipython>=6->nbfancy) (2.3.1)\n", 83 | "Requirement already satisfied: prompt-toolkit<2.1.0,>=2.0.0 in /home/jack/Documents/James_Grant/fancy_test/lib/python3.7/site-packages (from ipython>=6->nbfancy) (2.0.9)\n", 84 | "Requirement already satisfied: jedi>=0.10 in /home/jack/Documents/James_Grant/fancy_test/lib/python3.7/site-packages (from ipython>=6->nbfancy) (0.13.3)\n", 85 | "Requirement already satisfied: setuptools>=18.5 in /home/jack/Documents/James_Grant/fancy_test/lib/python3.7/site-packages (from ipython>=6->nbfancy) (41.0.0)\n", 86 | "Requirement already satisfied: decorator in /home/jack/Documents/James_Grant/fancy_test/lib/python3.7/site-packages (from ipython>=6->nbfancy) (4.4.0)\n", 87 | "Requirement already satisfied: notebook in /home/jack/Documents/James_Grant/fancy_test/lib/python3.7/site-packages (from jupyter->nbfancy) (5.7.8)\n", 88 | "Requirement already satisfied: nbconvert in /home/jack/Documents/James_Grant/fancy_test/lib/python3.7/site-packages (from jupyter->nbfancy) (5.4.1)\n", 89 | "Requirement already satisfied: jupyter-console in /home/jack/Documents/James_Grant/fancy_test/lib/python3.7/site-packages (from jupyter->nbfancy) (6.0.0)\n", 90 | "Requirement already satisfied: ipywidgets in /home/jack/Documents/James_Grant/fancy_test/lib/python3.7/site-packages (from jupyter->nbfancy) (7.4.2)\n", 91 | "Requirement already satisfied: qtconsole in /home/jack/Documents/James_Grant/fancy_test/lib/python3.7/site-packages (from jupyter->nbfancy) (4.4.3)\n", 92 | "Requirement already satisfied: ipykernel in /home/jack/Documents/James_Grant/fancy_test/lib/python3.7/site-packages (from jupyter->nbfancy) (5.1.0)\n", 93 | "Requirement already satisfied: ptyprocess>=0.5 in /home/jack/Documents/James_Grant/fancy_test/lib/python3.7/site-packages (from pexpect; sys_platform != \"win32\"->ipython>=6->nbfancy) (0.6.0)\n", 94 | "Requirement already satisfied: ipython-genutils in /home/jack/Documents/James_Grant/fancy_test/lib/python3.7/site-packages (from traitlets>=4.2->ipython>=6->nbfancy) (0.2.0)\n", 95 | "Requirement already satisfied: six in /home/jack/Documents/James_Grant/fancy_test/lib/python3.7/site-packages (from traitlets>=4.2->ipython>=6->nbfancy) (1.12.0)\n", 96 | "Requirement already satisfied: wcwidth in /home/jack/Documents/James_Grant/fancy_test/lib/python3.7/site-packages (from prompt-toolkit<2.1.0,>=2.0.0->ipython>=6->nbfancy) (0.1.7)\n", 97 | "Requirement already satisfied: parso>=0.3.0 in /home/jack/Documents/James_Grant/fancy_test/lib/python3.7/site-packages (from jedi>=0.10->ipython>=6->nbfancy) (0.4.0)\n", 98 | "Requirement already satisfied: jinja2 in /home/jack/Documents/James_Grant/fancy_test/lib/python3.7/site-packages (from notebook->jupyter->nbfancy) (2.10.1)\n", 99 | "Requirement already satisfied: nbformat in /home/jack/Documents/James_Grant/fancy_test/lib/python3.7/site-packages (from notebook->jupyter->nbfancy) (4.4.0)\n", 100 | "Requirement already satisfied: pyzmq>=17 in /home/jack/Documents/James_Grant/fancy_test/lib/python3.7/site-packages (from notebook->jupyter->nbfancy) (18.0.1)\n", 101 | "Requirement already satisfied: Send2Trash in /home/jack/Documents/James_Grant/fancy_test/lib/python3.7/site-packages (from notebook->jupyter->nbfancy) (1.5.0)\n", 102 | "Requirement already satisfied: prometheus-client in /home/jack/Documents/James_Grant/fancy_test/lib/python3.7/site-packages (from notebook->jupyter->nbfancy) (0.6.0)\n", 103 | "Requirement already satisfied: terminado>=0.8.1 in /home/jack/Documents/James_Grant/fancy_test/lib/python3.7/site-packages (from notebook->jupyter->nbfancy) (0.8.2)\n", 104 | "Requirement already satisfied: jupyter-core>=4.4.0 in /home/jack/Documents/James_Grant/fancy_test/lib/python3.7/site-packages (from notebook->jupyter->nbfancy) (4.4.0)\n", 105 | "Requirement already satisfied: tornado<7,>=4.1 in /home/jack/Documents/James_Grant/fancy_test/lib/python3.7/site-packages (from notebook->jupyter->nbfancy) (5.1.1)\n", 106 | "Requirement already satisfied: jupyter-client>=5.2.0 in /home/jack/Documents/James_Grant/fancy_test/lib/python3.7/site-packages (from notebook->jupyter->nbfancy) (5.2.4)\n", 107 | "Requirement already satisfied: pandocfilters>=1.4.1 in /home/jack/Documents/James_Grant/fancy_test/lib/python3.7/site-packages (from nbconvert->jupyter->nbfancy) (1.4.2)\n", 108 | "Requirement already satisfied: entrypoints>=0.2.2 in /home/jack/Documents/James_Grant/fancy_test/lib/python3.7/site-packages (from nbconvert->jupyter->nbfancy) (0.3)\n", 109 | "Requirement already satisfied: defusedxml in /home/jack/Documents/James_Grant/fancy_test/lib/python3.7/site-packages (from nbconvert->jupyter->nbfancy) (0.6.0)\n", 110 | "Requirement already satisfied: testpath in /home/jack/Documents/James_Grant/fancy_test/lib/python3.7/site-packages (from nbconvert->jupyter->nbfancy) (0.4.2)\n", 111 | "Requirement already satisfied: mistune>=0.8.1 in /home/jack/Documents/James_Grant/fancy_test/lib/python3.7/site-packages (from nbconvert->jupyter->nbfancy) (0.8.4)\n", 112 | "Requirement already satisfied: bleach in /home/jack/Documents/James_Grant/fancy_test/lib/python3.7/site-packages (from nbconvert->jupyter->nbfancy) (3.1.0)\n", 113 | "Requirement already satisfied: widgetsnbextension~=3.4.0 in /home/jack/Documents/James_Grant/fancy_test/lib/python3.7/site-packages (from ipywidgets->jupyter->nbfancy) (3.4.2)\n", 114 | "Requirement already satisfied: MarkupSafe>=0.23 in /home/jack/Documents/James_Grant/fancy_test/lib/python3.7/site-packages (from jinja2->notebook->jupyter->nbfancy) (1.1.1)\n", 115 | "Requirement already satisfied: jsonschema!=2.5.0,>=2.4 in /home/jack/Documents/James_Grant/fancy_test/lib/python3.7/site-packages (from nbformat->notebook->jupyter->nbfancy) (3.0.1)\n", 116 | "Requirement already satisfied: python-dateutil>=2.1 in /home/jack/Documents/James_Grant/fancy_test/lib/python3.7/site-packages (from jupyter-client>=5.2.0->notebook->jupyter->nbfancy) (2.8.0)\n", 117 | "Requirement already satisfied: webencodings in /home/jack/Documents/James_Grant/fancy_test/lib/python3.7/site-packages (from bleach->nbconvert->jupyter->nbfancy) (0.5.1)\n", 118 | "Requirement already satisfied: attrs>=17.4.0 in /home/jack/Documents/James_Grant/fancy_test/lib/python3.7/site-packages (from jsonschema!=2.5.0,>=2.4->nbformat->notebook->jupyter->nbfancy) (19.1.0)\n", 119 | "Requirement already satisfied: pyrsistent>=0.14.0 in /home/jack/Documents/James_Grant/fancy_test/lib/python3.7/site-packages (from jsonschema!=2.5.0,>=2.4->nbformat->notebook->jupyter->nbfancy) (0.14.11)\n", 120 | "\u001b[33mYou are using pip version 10.0.1, however version 19.1 is available.\n", 121 | "You should consider upgrading via the 'pip install --upgrade pip' command.\u001b[0m\n", 122 | "Note: you may need to restart the kernel to use updated packages.\n" 123 | ] 124 | } 125 | ], 126 | "source": [ 127 | "pip install nbfancy" 128 | ] 129 | }, 130 | { 131 | "cell_type": "markdown", 132 | "metadata": { 133 | "slideshow": { 134 | "slide_type": "slide" 135 | } 136 | }, 137 | "source": [ 138 | "## Configure\n", 139 | "\n", 140 | "In order to view the rendered material *in a notebook* you must add a custom CSS file to your Jupyter global config.\n", 141 | "This step is not necessary if you only want to build HTML documents.\n", 142 | "\n", 143 | "To add the custom CSS, you can use NBfancy's configure command, and it will add the file `custom.css` to the correct directory." 144 | ] 145 | }, 146 | { 147 | "cell_type": "code", 148 | "execution_count": 5, 149 | "metadata": { 150 | "slideshow": { 151 | "slide_type": "subslide" 152 | } 153 | }, 154 | "outputs": [ 155 | { 156 | "name": "stdout", 157 | "output_type": "stream", 158 | "text": [ 159 | "ERROR: You already have a directory named\r\n", 160 | "/home/jack/.jupyter/custom/css\r\n", 161 | "Remove or rename and try again.\r\n", 162 | "Install will continue, on the assumption that these files are left over from a previous build.\r\n", 163 | "Your global config has been updated\r\n" 164 | ] 165 | } 166 | ], 167 | "source": [ 168 | "!nbfancy configure -y jupyter_css" 169 | ] 170 | }, 171 | { 172 | "cell_type": "markdown", 173 | "metadata": { 174 | "slideshow": { 175 | "slide_type": "slide" 176 | } 177 | }, 178 | "source": [ 179 | "## Information: Executing at the command line in notebooks\n", 180 | "You may not have seen `!` commands in a notebook before. This allows the command that follows to be run at the command line, rather than by the notebook kernel. The `pip` command above uses notebook magic for the Python kernel to allow it to be executed without the `!`. For NBfancy commands to be executed at the command line, we need the `!`." 181 | ] 182 | }, 183 | { 184 | "cell_type": "markdown", 185 | "metadata": { 186 | "slideshow": { 187 | "slide_type": "slide" 188 | } 189 | }, 190 | "source": [ 191 | "## Key Points:\n", 192 | "- NBfancy is available using the Python package manager `pip`\n", 193 | "- To view rendered material as a notebook, an additional configuration step is required" 194 | ] 195 | } 196 | ], 197 | "metadata": { 198 | "celltoolbar": "Slideshow", 199 | "kernelspec": { 200 | "display_name": "Python 3", 201 | "language": "python", 202 | "name": "python3" 203 | }, 204 | "language_info": { 205 | "codemirror_mode": { 206 | "name": "ipython", 207 | "version": 3 208 | }, 209 | "file_extension": ".py", 210 | "mimetype": "text/x-python", 211 | "name": "python", 212 | "nbconvert_exporter": "python", 213 | "pygments_lexer": "ipython3", 214 | "version": "3.7.3" 215 | } 216 | }, 217 | "nbformat": 4, 218 | "nbformat_minor": 2 219 | } 220 | -------------------------------------------------------------------------------- /nbfancy/tutorial/03_build.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": { 6 | "slideshow": { 7 | "slide_type": "slide" 8 | } 9 | }, 10 | "source": [ 11 | "# Build" 12 | ] 13 | }, 14 | { 15 | "cell_type": "markdown", 16 | "metadata": { 17 | "slideshow": { 18 | "slide_type": "subslide" 19 | } 20 | }, 21 | "source": [ 22 | "## Overview:\n", 23 | "- **Teaching:** 10 min\n", 24 | "- **Exercises:** 0 min\n", 25 | "\n", 26 | "**Questions**\n", 27 | "- How do I build a lesson?\n", 28 | "\n", 29 | "**Objectives**\n", 30 | "- Understand the 4 key steps in a build" 31 | ] 32 | }, 33 | { 34 | "cell_type": "markdown", 35 | "metadata": { 36 | "slideshow": { 37 | "slide_type": "slide" 38 | } 39 | }, 40 | "source": [ 41 | "The main program for NBfancy is the command line program `nbfancy`, which we saw in the previous section. Typical usage is `nbfancy [options]`, where `` is some action, and `[options]` are additional options for that action.\n", 42 | "\n", 43 | "For available actions, we can use the `--help` flag." 44 | ] 45 | }, 46 | { 47 | "cell_type": "code", 48 | "execution_count": 7, 49 | "metadata": { 50 | "slideshow": { 51 | "slide_type": "subslide" 52 | } 53 | }, 54 | "outputs": [ 55 | { 56 | "name": "stdout", 57 | "output_type": "stream", 58 | "text": [ 59 | "usage: nbfancy [-h] {init,hello,configure,rerun,render,html}\r\n", 60 | "\r\n", 61 | "positional arguments:\r\n", 62 | " {init,hello,configure,rerun,render,html}\r\n", 63 | " action to perform. Try adding --help to one of these\r\n", 64 | " options for more usage information\r\n", 65 | "\r\n", 66 | "optional arguments:\r\n", 67 | " -h, --help show this help message and exit\r\n" 68 | ] 69 | } 70 | ], 71 | "source": [ 72 | "!nbfancy --help" 73 | ] 74 | }, 75 | { 76 | "cell_type": "markdown", 77 | "metadata": { 78 | "slideshow": { 79 | "slide_type": "slide" 80 | } 81 | }, 82 | "source": [ 83 | "A typical build consists of four steps:\n", 84 | "* Initialisation with `nbfancy init`\n", 85 | "* Re-execution with `nbfancy rerun`\n", 86 | "* Rendering with `nbfancy render`\n", 87 | "* Publising to website with `nbfancy html`" 88 | ] 89 | }, 90 | { 91 | "cell_type": "markdown", 92 | "metadata": { 93 | "slideshow": { 94 | "slide_type": "slide" 95 | } 96 | }, 97 | "source": [ 98 | "## Initialisation\n", 99 | "To start making training material we initialise a directory with the `nbfancy init [dir]` command:" 100 | ] 101 | }, 102 | { 103 | "cell_type": "code", 104 | "execution_count": 8, 105 | "metadata": { 106 | "slideshow": { 107 | "slide_type": "subslide" 108 | } 109 | }, 110 | "outputs": [], 111 | "source": [ 112 | "!nbfancy init example" 113 | ] 114 | }, 115 | { 116 | "cell_type": "markdown", 117 | "metadata": { 118 | "slideshow": { 119 | "slide_type": "slide" 120 | } 121 | }, 122 | "source": [ 123 | "By default `init` will copy the template lesson to the current working directory, or optional directory argument if provided, to get you started:\n", 124 | "\n", 125 | "```bash\n", 126 | ".\n", 127 | "├── code\n", 128 | "├── config\n", 129 | "│ ├── footer.ipynb\n", 130 | "│ └── header.ipynb\n", 131 | "├── data\n", 132 | "├── images\n", 133 | "└── nbplain\n", 134 | " ├── 00_schedule.ipynb\n", 135 | " ├── 01_untitled_episode.ipynb\n", 136 | " └── 99_episode_template.ipynb\n", 137 | "\n", 138 | "5 directories, 5 files\n", 139 | "```" 140 | ] 141 | }, 142 | { 143 | "cell_type": "markdown", 144 | "metadata": { 145 | "slideshow": { 146 | "slide_type": "slide" 147 | } 148 | }, 149 | "source": [ 150 | "For more information about further options for `init` you can look at the help." 151 | ] 152 | }, 153 | { 154 | "cell_type": "code", 155 | "execution_count": 9, 156 | "metadata": { 157 | "slideshow": { 158 | "slide_type": "subslide" 159 | } 160 | }, 161 | "outputs": [ 162 | { 163 | "name": "stdout", 164 | "output_type": "stream", 165 | "text": [ 166 | "usage: nbfancy init [-h] [--extra_conf] [--include {tutorial,template,none}]\r\n", 167 | " [dir]\r\n", 168 | "\r\n", 169 | "positional arguments:\r\n", 170 | " dir Directory to initialise (default:\r\n", 171 | " /home/rjg20/rse/nbfancy/nbfancy/tutorial/example)\r\n", 172 | "\r\n", 173 | "optional arguments:\r\n", 174 | " -h, --help show this help message and exit\r\n", 175 | " --extra_conf Initialise additional configuration files (default:\r\n", 176 | " False)\r\n", 177 | " --include {tutorial,template,none}\r\n", 178 | " Fill nbplain directory with examples (default:\r\n", 179 | " template)\r\n" 180 | ] 181 | } 182 | ], 183 | "source": [ 184 | "!nbfancy init --help" 185 | ] 186 | }, 187 | { 188 | "cell_type": "markdown", 189 | "metadata": { 190 | "slideshow": { 191 | "slide_type": "slide" 192 | } 193 | }, 194 | "source": [ 195 | "## Re-execution\n", 196 | "In order to reduce issues associated with out of order execution, unexecuted cells it is strongly recommended that all notebooks that form a lesson are cleared and re-executed. This can be done quickly and easily with the `nbfancy rerun` command.\n", 197 | "\n", 198 | "Re-executing all the notebooks in order can be thought of as automated testing for the code in a lesson, ensuring that someone following along in a lesson won't encounter errors. \n", 199 | "\n", 200 | "**Note that if you intend to preserve partially executed notebooks/out-of-order execution, you should omit this step.**" 201 | ] 202 | }, 203 | { 204 | "cell_type": "code", 205 | "execution_count": 10, 206 | "metadata": { 207 | "slideshow": { 208 | "slide_type": "subslide" 209 | } 210 | }, 211 | "outputs": [ 212 | { 213 | "name": "stdout", 214 | "output_type": "stream", 215 | "text": [ 216 | "Reading input file: 00_schedule.ipynb\n", 217 | "Writing output file: 00_schedule.ipynb\n", 218 | "Reading input file: 01_untitled_episode.ipynb\n", 219 | "Writing output file: 01_untitled_episode.ipynb\n", 220 | "Reading input file: 99_episode_template.ipynb\n", 221 | "Writing output file: 99_episode_template.ipynb\n" 222 | ] 223 | } 224 | ], 225 | "source": [ 226 | "%%bash2\n", 227 | "cd example\n", 228 | "nbfancy rerun" 229 | ] 230 | }, 231 | { 232 | "cell_type": "markdown", 233 | "metadata": { 234 | "slideshow": { 235 | "slide_type": "slide" 236 | } 237 | }, 238 | "source": [ 239 | "## Information: What's this magic?\n", 240 | "As part of NBfancy we have implemented a thin wrapper around the `%%bash` magic available in notebooks, which we have called `%%bash2`. This just keeps track of what directory we are in, so as to keep the content close to what you would type at the command line. `%%bash2` magic is available as part of NBfancy, to find out more, see [this lesson](10_magic.ipynb)." 241 | ] 242 | }, 243 | { 244 | "cell_type": "markdown", 245 | "metadata": { 246 | "slideshow": { 247 | "slide_type": "slide" 248 | } 249 | }, 250 | "source": [ 251 | "Further options for `rerun` are listed in the help." 252 | ] 253 | }, 254 | { 255 | "cell_type": "code", 256 | "execution_count": 11, 257 | "metadata": { 258 | "slideshow": { 259 | "slide_type": "subslide" 260 | } 261 | }, 262 | "outputs": [ 263 | { 264 | "name": "stdout", 265 | "output_type": "stream", 266 | "text": [ 267 | "usage: nbfancy rerun [-h] [--output_dir OUTPUT_DIR] [--clear_only]\r\n", 268 | " [--allow_errors] [--timeout TIMEOUT]\r\n", 269 | " [input_dir]\r\n", 270 | "\r\n", 271 | "positional arguments:\r\n", 272 | " input_dir Plain notebook directory (default: nbplain)\r\n", 273 | "\r\n", 274 | "optional arguments:\r\n", 275 | " -h, --help show this help message and exit\r\n", 276 | " --output_dir OUTPUT_DIR\r\n", 277 | " Name of directory for re-evaluated notebooks (default:\r\n", 278 | " nbplain)\r\n", 279 | " --clear_only Clear the cells, but do not re-evaluate (default:\r\n", 280 | " False)\r\n", 281 | " --allow_errors Continue running notebook even if errors occur\r\n", 282 | " (default: False)\r\n", 283 | " --timeout TIMEOUT Number of seconds to allow each cell to run for\r\n", 284 | " (default: 60)\r\n" 285 | ] 286 | } 287 | ], 288 | "source": [ 289 | "!nbfancy rerun --help" 290 | ] 291 | }, 292 | { 293 | "cell_type": "markdown", 294 | "metadata": { 295 | "slideshow": { 296 | "slide_type": "slide" 297 | } 298 | }, 299 | "source": [ 300 | "## Rendering\n", 301 | "Keywords in notebooks can be processed and marked up by running the `nbfancy render` command. By default this creates a new directory `nbfancy` containing a rendered versions of the notebooks in `nbplain`, with cells containing keywords decorated with the respective environments (see [environments](05_environments.ipynb) section)." 302 | ] 303 | }, 304 | { 305 | "cell_type": "code", 306 | "execution_count": 12, 307 | "metadata": { 308 | "scrolled": true, 309 | "slideshow": { 310 | "slide_type": "subslide" 311 | } 312 | }, 313 | "outputs": [ 314 | { 315 | "name": "stdout", 316 | "output_type": "stream", 317 | "text": [ 318 | "Reading input file: 00_schedule.ipynb\n", 319 | "Writing output file: 00_schedule.ipynb\n", 320 | "Reading input file: 01_untitled_episode.ipynb\n", 321 | "Writing output file: 01_untitled_episode.ipynb\n", 322 | "Reading input file: 99_episode_template.ipynb\n", 323 | "Writing output file: 99_episode_template.ipynb\n", 324 | "Writing output file: 99_episode_template-soln.ipynb\n" 325 | ] 326 | } 327 | ], 328 | "source": [ 329 | "%%bash2\n", 330 | "nbfancy render" 331 | ] 332 | }, 333 | { 334 | "cell_type": "markdown", 335 | "metadata": { 336 | "slideshow": { 337 | "slide_type": "slide" 338 | } 339 | }, 340 | "source": [ 341 | "As always further options for `render` are available in the help." 342 | ] 343 | }, 344 | { 345 | "cell_type": "code", 346 | "execution_count": 13, 347 | "metadata": { 348 | "slideshow": { 349 | "slide_type": "subslide" 350 | } 351 | }, 352 | "outputs": [ 353 | { 354 | "name": "stdout", 355 | "output_type": "stream", 356 | "text": [ 357 | "usage: nbfancy render [-h] [--output_dir OUTPUT_DIR] [-c CONFIG] [input_dir]\r\n", 358 | "\r\n", 359 | "positional arguments:\r\n", 360 | " input_dir Plain notebook directory (default: nbplain)\r\n", 361 | "\r\n", 362 | "optional arguments:\r\n", 363 | " -h, --help show this help message and exit\r\n", 364 | " --output_dir OUTPUT_DIR\r\n", 365 | " Directory to output rendered notebooks to (default:\r\n", 366 | " nbfancy)\r\n", 367 | " -c CONFIG, --config CONFIG\r\n", 368 | " Custom configuration directory (default: config)\r\n" 369 | ] 370 | } 371 | ], 372 | "source": [ 373 | "!nbfancy render --help" 374 | ] 375 | }, 376 | { 377 | "cell_type": "markdown", 378 | "metadata": { 379 | "slideshow": { 380 | "slide_type": "slide" 381 | } 382 | }, 383 | "source": [ 384 | "## Publishing\n", 385 | "Once you have checked the contents of the rendered notebooks, the whole lesson can be built into a website by running the `nbfancy html` command." 386 | ] 387 | }, 388 | { 389 | "cell_type": "code", 390 | "execution_count": 14, 391 | "metadata": { 392 | "slideshow": { 393 | "slide_type": "subslide" 394 | } 395 | }, 396 | "outputs": [ 397 | { 398 | "name": "stdout", 399 | "output_type": "stream", 400 | "text": [ 401 | "Reading input file: 00_schedule.ipynb\n", 402 | "Writing output file: 00_schedule.html\n", 403 | "Reading input file: 01_untitled_episode.ipynb\n", 404 | "Writing output file: 01_untitled_episode.html\n", 405 | "Reading input file: 99_episode_template.ipynb\n", 406 | "Writing output file: 99_episode_template.html\n", 407 | "Reading input file: 99_episode_template-soln.ipynb\n", 408 | "Writing output file: 99_episode_template-soln.html\n" 409 | ] 410 | } 411 | ], 412 | "source": [ 413 | "%%bash2\n", 414 | "nbfancy html" 415 | ] 416 | }, 417 | { 418 | "cell_type": "markdown", 419 | "metadata": { 420 | "slideshow": { 421 | "slide_type": "slide" 422 | } 423 | }, 424 | "source": [ 425 | "Additional options for `html` are given by help." 426 | ] 427 | }, 428 | { 429 | "cell_type": "code", 430 | "execution_count": 15, 431 | "metadata": { 432 | "scrolled": true, 433 | "slideshow": { 434 | "slide_type": "subslide" 435 | } 436 | }, 437 | "outputs": [ 438 | { 439 | "name": "stdout", 440 | "output_type": "stream", 441 | "text": [ 442 | "usage: nbfancy html [-h] [--output_dir OUTPUT_DIR] [input_dir]\r\n", 443 | "\r\n", 444 | "positional arguments:\r\n", 445 | " input_dir Fancy notebook directory (default: nbfancy)\r\n", 446 | "\r\n", 447 | "optional arguments:\r\n", 448 | " -h, --help show this help message and exit\r\n", 449 | " --output_dir OUTPUT_DIR\r\n", 450 | " Directory to output html pages to (default: html)\r\n" 451 | ] 452 | } 453 | ], 454 | "source": [ 455 | "!nbfancy html --help" 456 | ] 457 | }, 458 | { 459 | "cell_type": "markdown", 460 | "metadata": { 461 | "slideshow": { 462 | "slide_type": "slide" 463 | } 464 | }, 465 | "source": [ 466 | "## Key Points:\n", 467 | "- A directory is initialised using `init`\n", 468 | "- The contents of a notebook can be cleared and re-executed using `rerun`\n", 469 | "- `render` provides additional rich reatures to makdown cells\n", 470 | "- A website of the material is generated using `html`" 471 | ] 472 | } 473 | ], 474 | "metadata": { 475 | "celltoolbar": "Slideshow", 476 | "kernelspec": { 477 | "display_name": "Python 3", 478 | "language": "python", 479 | "name": "python3" 480 | }, 481 | "language_info": { 482 | "codemirror_mode": { 483 | "name": "ipython", 484 | "version": 3 485 | }, 486 | "file_extension": ".py", 487 | "mimetype": "text/x-python", 488 | "name": "python", 489 | "nbconvert_exporter": "python", 490 | "pygments_lexer": "ipython3", 491 | "version": "3.7.3" 492 | } 493 | }, 494 | "nbformat": 4, 495 | "nbformat_minor": 2 496 | } 497 | -------------------------------------------------------------------------------- /nbfancy/nbfancy_tools.py: -------------------------------------------------------------------------------- 1 | import os 2 | import csv 3 | import pkg_resources 4 | import re 5 | import traceback 6 | 7 | import nbformat as nf 8 | import nbconvert as nc 9 | 10 | from urllib.parse import quote as urlquote 11 | 12 | def isdir(path): 13 | ''' 14 | Checks whether given path is a directory 15 | ''' 16 | if not os.path.isdir(path): 17 | raise OSError('"' + path + '"' + ' is not a direcotry') 18 | else: 19 | return path 20 | 21 | def try_config(configdir, filename): 22 | ''' 23 | Tries to read specified config, else uses global config 24 | returns file handle to requested file 25 | ''' 26 | resource_package = 'nbfancy' 27 | config_path = '/config' # Do not use os.path.join() 28 | 29 | if not os.path.isdir(configdir): 30 | configdir = pkg_resources.resource_filename(resource_package, config_path) 31 | 32 | try: 33 | filepath = os.path.join(configdir, filename) 34 | filehandle = open(filepath, 'r') 35 | except FileNotFoundError: 36 | configdir = pkg_resources.resource_filename(resource_package, config_path) 37 | filepath = os.path.join(configdir, filename) 38 | filehandle = open(filepath, 'r') 39 | 40 | return filehandle 41 | 42 | def read_header(configdir): 43 | ''' 44 | Reads header from config directory 45 | ''' 46 | # Open file and extract text in second cell 47 | with try_config(configdir, 'header.ipynb') as fh: 48 | notebook = nf.read(fh, nf.NO_CONVERT) 49 | template = notebook['cells'][1] 50 | 51 | return template 52 | 53 | def read_footer(configdir): 54 | ''' 55 | Reads footer from config directory 56 | ''' 57 | # Open file and extract text in second cell 58 | with try_config(configdir, 'footer.ipynb') as fh: 59 | notebook = nf.read(fh, nf.NO_CONVERT) 60 | template = notebook['cells'][1] 61 | 62 | return template 63 | 64 | def read_box_template(configdir): 65 | ''' 66 | Reads box template from given file handle 67 | ''' 68 | filehandle = try_config(configdir, 'box.ipynb') 69 | 70 | # File is already open 71 | # Open file and extract text in second cell 72 | notebook = nf.read(filehandle, nf.NO_CONVERT) 73 | box = notebook['cells'][1] 74 | template = box['source'] 75 | 76 | # Replace known values with placeholders 77 | template = template.replace('pale-green', '{bg-colour}') 78 | template = template.replace('green', '{fg-colour}') 79 | template = template.replace('fa-star', '{symbol}') 80 | template = template.replace('TITLE', '{title}') 81 | template = template.replace('BODY', '{body}') 82 | 83 | return template 84 | 85 | def colour2fgbg(colour): 86 | ''' 87 | Pairs foreground colour with background colour 88 | ''' 89 | colour = colour.lower() 90 | colour_list = ['red', 'orange', 'yellow', 'green', 'blue', 'purple'] 91 | colour_list += ['brown', 'black', 'grey', 'gray', 'white'] 92 | assert colour in colour_list 93 | 94 | fg = colour 95 | if fg == 'red': 96 | bg = 'pale-red' 97 | elif fg == 'orange': 98 | bg = 'sand' 99 | elif fg == 'yellow': 100 | bg = 'pale-yellow' 101 | elif fg == 'green': 102 | bg = 'pale-green' 103 | elif fg == 'blue': 104 | bg = 'pale-blue' 105 | elif fg == 'purple': 106 | bg = 'pale-red' 107 | elif fg == 'brown': 108 | bg = 'khaki' 109 | elif fg == 'black': 110 | bg = 'gray' 111 | elif (fg == 'gray') or (fg == 'grey'): 112 | fg = 'gray' 113 | bg = 'light-gray' 114 | elif fg == 'white': 115 | bg = 'white' 116 | 117 | return fg, bg 118 | 119 | def read_box_colour_config(configdir): 120 | ''' 121 | Create a dict of configurations for each keyword in filename 122 | Lines starting with # are ignored as are blank lines 123 | ''' 124 | config = dict() 125 | 126 | def isTF(val): 127 | ''' 128 | Return true or false if val is boolean 129 | ''' 130 | true_words = ['true', 't', '1'] 131 | false_words = ['false', 'f', '0'] 132 | test_val = val.strip().lower() 133 | if test_val in true_words: 134 | test_val = True 135 | elif test_val in false_words: 136 | test_val = False 137 | return test_val 138 | 139 | with try_config(configdir, 'keywords.cfg') as fh: 140 | no_comments = filter(lambda line: len(line)>3 and line.lstrip()[0]!='#' , fh) 141 | reader = csv.DictReader(no_comments) 142 | for row in reader: 143 | key = row.pop('Keyword') 144 | row_dict = {key.strip().lower() : isTF(row[key]) for key in row} 145 | row_dict['fg-colour'], row_dict['bg-colour'] = colour2fgbg(row_dict['colour']) 146 | config[key.strip().lower()] = row_dict 147 | 148 | return config 149 | 150 | def box_title(line, config): 151 | ''' 152 | Creates title for box. 153 | Returns html formattted title, index and which keyword was found 154 | ''' 155 | keywords = config.keys() 156 | 157 | # Search for keyword (lowercase) in first line and set that as the key 158 | for word in keywords: 159 | if word in line.lower().split(':')[0]: 160 | key = word 161 | 162 | # Recover paramters from keyword 163 | keep_keyword = config[key]['keep_keyword'] 164 | hidden = config[key]['hide'] 165 | 166 | # Whether to print keyword in title 167 | if keep_keyword: 168 | title = line.lstrip('#') 169 | else: 170 | subtitle = line.split(':') 171 | title = ':'.join(subtitle[1:]) 172 | 173 | # Safe version of title for links 174 | safetitle = title.replace(' ', '-') 175 | safetitle = safetitle.replace('`', '') 176 | index = urlquote(safetitle, safe='?!$\\') + '%0A' 177 | 178 | # Mark up title, incase markdown syntax is used 179 | htmltitle = nc.filters.markdown2html(title) 180 | htmltitle = htmltitle.replace('

', '') 181 | htmltitle = htmltitle.replace('

', '') 182 | 183 | #link = './' + solnfilename.split('/')[-1] + '#' + index 184 | 185 | return htmltitle, index, key 186 | 187 | 188 | def recursion_detector(f): 189 | ''' 190 | Detects whether a given function is calling itself 191 | ''' 192 | def decorated_f(*args, **kwargs): 193 | stack = traceback.extract_stack() 194 | if len([1 for line in stack if line[2] == f.__name__]) > 0: 195 | print('Warning: Nested environments detected, this is actively discouraged!') 196 | return f(*args, **kwargs) 197 | return decorated_f 198 | 199 | @recursion_detector 200 | def box_body(body, config, template, solnfilename, link=None, multicell=None): 201 | ''' 202 | Creates body of the box 203 | ''' 204 | # If an empty link to a solution is found, populate it with link 205 | # that was generated by the title (for single cell) 206 | if len(body) > 0 and '[solution]()' in body[-1].lower(): 207 | k = body[-1].lower().find('[solution]()') 208 | solution_phrase = body[-1][k:k+13] 209 | new_solution_phrase = '\n\n' + solution_phrase.replace('()','({link})') 210 | new_solution_phrase = new_solution_phrase.format(link=link) 211 | body[-1] = body[-1].replace(solution_phrase, new_solution_phrase) 212 | 213 | body = '\n'.join(body) 214 | 215 | # Apply markup 216 | htmlbody = nc.filters.markdown2html(body) 217 | 218 | if multicell is not None: 219 | # Bit of recursion 220 | #print('Warning nested cell environments') 221 | rendered, soln = notebook2rendered(multicell, config, template, solnfilename) 222 | 223 | # Export to html to include in cell 224 | html_exp = nc.HTMLExporter() 225 | html_exp.template_file = 'basic' 226 | temphtml, resources = html_exp.from_notebook_node(rendered) 227 | # Remove multiple newlines 228 | temphtml = re.sub(r'(\n\s*)+\n', '\n', temphtml) 229 | # Add boxy thing 230 | temphtml = temphtml.replace('class="input_area"', 231 | 'class="output_area" style="background-color:#F7F7F7;border:1px solid #CFCFCF"') 232 | # If an empty link to a solution is found, populate it with link 233 | # that was generated by the title (for multicell) 234 | if 'solution' in temphtml.lower(): 235 | k = temphtml.lower().find('solution') 236 | solution_phrase = temphtml[k:k+24] 237 | new_solution_phrase = solution_phrase.replace('href=""','href="{link}"') 238 | new_solution_phrase = new_solution_phrase.format(link=link) 239 | temphtml = temphtml.replace(solution_phrase, new_solution_phrase) 240 | 241 | htmlbody += temphtml 242 | 243 | # Escape symbols 244 | htmlbody = htmlbody.replace('*', '*') 245 | #htmlbody = htmlbody.replace('_', '_') 246 | 247 | # Format tables 248 | htmlbody = htmlbody.replace('', '
') 249 | htmlbody = htmlbody.replace('', '') 250 | 251 | # Be sure to remove final newline 252 | if len(htmlbody) > 0 and htmlbody[-1] == '\n': 253 | htmlbody = htmlbody[:-1] 254 | 255 | return htmlbody 256 | 257 | def notebook2rendered(plain, config, template, solnfilename): 258 | ''' 259 | Converts notebook JSON to rendered notebook JSON for output 260 | ''' 261 | # List all the markdown cells 262 | celllist = plain['cells'] 263 | markdownlist = [c for c in celllist if c['cell_type']=='markdown'] 264 | solnb = None 265 | 266 | # For each markdown cell check for keywords and format according to 267 | # the cell template and config files 268 | end = -1 269 | 270 | for c in markdownlist: 271 | line = c['source'].split('\n') 272 | 273 | # Check for a colon in the first line 274 | if line[0].find(':') < 0: 275 | continue 276 | 277 | # Check for a keyword if a colon is found 278 | temp_line = line[0].split(':') 279 | if any(keyword in temp_line[0].lower().strip('# ') for keyword in config.keys()): 280 | htmltitle, index, key = box_title(line[0], config) 281 | # Recover paramters from keyword 282 | hidden = config[key]['hide'] 283 | 284 | # Multicell procedure 285 | if key + '+' in temp_line[0].lower().strip('# '): 286 | start = celllist.index(c) + 1 287 | end = None 288 | # Find end cell 289 | for subcell in celllist[start:]: 290 | if subcell['cell_type'] == 'markdown': 291 | lastline = subcell['source'].split('\n') 292 | temp_lastline = lastline[-1].split(':') 293 | if key in temp_lastline[-1].lower().strip(): 294 | end = celllist.index(subcell) + 1 295 | lastline[-1] = ':'.join(temp_lastline[:-1]).strip() 296 | subcell['source'] = '\n'.join(lastline) 297 | break 298 | else: 299 | # If no end cell found print warning 300 | try: 301 | print('Warning in file', infile, ':') 302 | print('\tNo end tag found for', key + '+', 'environment in cell', start) 303 | except NameError: 304 | print('Warning in temporary file:') 305 | print('\tNo end tag found for', key + '+', 'environment in cell', start) 306 | print('\tCheck you haven\'t nested environments') 307 | 308 | # Move multicells to new notebook for processing 309 | multicell = celllist[start:end] 310 | for subcell in multicell: 311 | celllist.remove(subcell) 312 | 313 | multicellnb = nf.v4.new_notebook() 314 | multicellnb['metadata'] = plain['metadata'] 315 | multicellnb['cells'] = multicell 316 | else: 317 | # If we aren't in a multicell environment 318 | # we don't need the additional notebook 319 | multicellnb = None 320 | 321 | # If hidden move cell to new notebook 322 | if hidden: 323 | # Make a new notebook if it doesn't exist already 324 | if solnb is None: 325 | solnb = nf.v4.new_notebook() 326 | solnb['metadata'] = plain['metadata'] 327 | solnb['cells'].append(nf.v4.new_markdown_cell(source='# Solutions')) 328 | 329 | solnb['cells'].append(nf.v4.new_markdown_cell(source='')) 330 | # REDEFINE c 331 | solnb['cells'][-1] = c.copy() 332 | plain['cells'].remove(c) 333 | c = solnb['cells'][-1] 334 | htmlbody = box_body(line[1:], config, template, solnfilename, multicell=multicellnb) 335 | else: 336 | link = './' + solnfilename.split('/')[-1] + '#' + index 337 | htmlbody = box_body(line[1:], config, template, solnfilename, link=link, multicell=multicellnb) 338 | 339 | values = config[key].copy() 340 | values['index'] = index 341 | values['title'] = htmltitle 342 | values['body'] = htmlbody 343 | c['source'] = template.format_map(values) 344 | 345 | return plain, solnb 346 | 347 | def notebook2HTML(filename): 348 | ''' 349 | Converts notebook file to a html string 350 | ''' 351 | html_exp = nc.HTMLExporter() 352 | html, resources = html_exp.from_filename(filename) 353 | 354 | # SED rules: 355 | # Replace '../folders' in links with './folders' 356 | # for folders images, data, code 357 | html = html.replace('../images', './images') 358 | html = html.replace('../data', './data') 359 | html = html.replace('../code', './code') 360 | 361 | # Replace '.ipynb' in links with '.html' 362 | # the '"' ensures this (hopefully) only happens in links 363 | html = html.replace('.ipynb"', '.html"') 364 | html = html.replace('.ipynb#', '.html#') 365 | 366 | # Horrible hack because environment doesn't seem to work with CSS sheet 367 | # For plaintext blocks 368 | html = html.replace('
', '
')
369 |     # For inline highlighting
370 |     html = html.replace('', '')
371 |     
372 | 	# Another hack since \n is converted to [space] in links
373 |     html = html.replace('%0A"','%20"')
374 |     
375 |     # Add the favicon
376 |     html = html.replace('',
377 |         '\n')
378 |     
379 |     return html
380 | 
381 | def notebook2slides(filename):
382 |     '''
383 |     Converts notebook file to a slide show
384 |     '''
385 |     slides_exp = nc.SlidesExporter()
386 |     slides_exp.reveal_scroll = True # Doesn't work?
387 |     slides, resources = slides_exp.from_filename(filename)
388 |     
389 |     # Custom CSS is in the directory above slides
390 |     slides = slides.replace('href="custom.css"', 'href="../custom.css"')
391 |     
392 |     # Replace '.ipynb' in links with '.html'
393 |     # the '"' ensures this (hopefully) only happens in links
394 |     slides = slides.replace('.ipynb"', '.html"')
395 |     slides = slides.replace('.ipynb#', '.html#')
396 |     
397 |     # Horrible hack because  environment doesn't seem to work with CSS sheet
398 |     # For plaintext blocks
399 |     slides = slides.replace('
', '
')
400 |     # For inline highlighting
401 |     slides = slides.replace('', '')
402 |     
403 | 	# Another hack since \n is converted to [space] in links
404 |     slides = slides.replace('%0A"','%20"')
405 |     
406 |     # Add the favicon
407 |     slides = slides.replace('',
408 |         '\n')
409 |     
410 |     return slides
411 | 
412 | def directory_contents(directory):
413 |     '''
414 |     Returns directory notebook contents
415 |     split into lessons and solutions
416 |     '''
417 |     # Store contents of directory as list
418 |     contents = os.listdir(directory)
419 |     contents.sort()
420 |     try:
421 |         # Remove checkpoints folder from list
422 |         contents.remove('.ipynb_checkpoints')
423 |     except ValueError:
424 |         pass
425 |     
426 |     # Removes everything that isn't a notebook ending with .ipynb
427 |     contents = [f for f in contents if '.ipynb' in f]
428 |     
429 |     # Remove solution files from contents and store in seperate list
430 |     soln_contents = [f for f in contents if '-soln' in f]
431 |     contents = [f for f in contents if '-soln' not in f]
432 |     
433 |     return contents, soln_contents
434 | 
435 | def navigation_triple(directory, inputfile):
436 |     '''
437 |     Given a directory and file determines which file is
438 |     - previous lesson
439 |     - schedule
440 |     - next lesson
441 |     and returns these files as a dict
442 |     '''
443 |     contents, _ = directory_contents(directory)
444 |     
445 |     contents.append(contents[0])
446 |     
447 |     current = inputfile.split('/')[-1]
448 |     # Exceptional case if you're making a new solution document
449 |     if '-soln' in current:
450 |         current = current.replace('-soln','')
451 |     
452 |     index = contents.index(current)
453 |     
454 |     outdir = './'
455 |     triple = {  'previous' : outdir+contents[index-1],
456 |                 'index'    : outdir+contents[0],
457 |                 'next'     : outdir+contents[index+1] }
458 |     return triple
459 | 
460 | 


--------------------------------------------------------------------------------
/nbfancy/__main__.py:
--------------------------------------------------------------------------------
  1 | #!/usr/bin/env python3
  2 | import os
  3 | import sys
  4 | import argparse
  5 | import pkg_resources
  6 | 
  7 | import nbformat as nf
  8 | import nbconvert as nc
  9 | 
 10 | from shutil import move, copy, copytree
 11 | from distutils.dir_util import copy_tree # Prefer copy_tree due to overwrite
 12 | from . import globalconf
 13 | from . import nbfancy_tools as nbftools
 14 | 
 15 | # Trivial function for testing functionality
 16 | hello = lambda x: print('Hello World!')
 17 | 
 18 | def init(args):
 19 |     '''
 20 |     Initialises a directory
 21 |     '''
 22 |     parser = argparse.ArgumentParser(formatter_class=argparse.ArgumentDefaultsHelpFormatter)
 23 |     parser.prog += ' ' + sys.argv[1]
 24 |     parser.add_argument('dir',
 25 |                         type=str,
 26 |                         default=os.getcwd(),
 27 |                         nargs='?',
 28 |                         help='Directory to initialise')
 29 |     parser.add_argument('--extra_conf',
 30 |                         action='store_true',
 31 |                         help='Initialise additional configuration files')
 32 |     parser.add_argument('--include',
 33 |                         choices=['tutorial', 'template', 'none'],
 34 |                         default = 'template',
 35 |                         help='Fill nbplain directory with examples')
 36 |     args, unknown = parser.parse_known_args(sys.argv[2:])
 37 |     
 38 |     # Get cwd
 39 |     cwd = args.dir
 40 |     
 41 |     if not os.path.isdir(cwd):
 42 |         try:
 43 |             os.mkdir(cwd)
 44 |         except FileExistsError:
 45 |             print('Directory', cwd, 'already exists')
 46 |     
 47 |     # Create standard directories
 48 |     # Except nbplain, which is created at the end
 49 |     dir_list = ['config', 'images', 'code', 'data']
 50 |     for idir in dir_list:
 51 |         make_dir = os.path.join(cwd, idir)
 52 |         if not os.path.isdir(make_dir):
 53 |             os.mkdir(make_dir)
 54 |     
 55 |     # Our default configuration data
 56 |     resource_package = 'nbfancy'
 57 |     config_path = '/config'  # Do not use os.path.join()
 58 |     config_source = pkg_resources.resource_filename(resource_package, config_path)
 59 |     
 60 |     # Setup config files
 61 |     config_dir = os.path.join(cwd, 'config')
 62 |     config_files = ['header.ipynb', 'footer.ipynb']
 63 |     if args.extra_conf:
 64 |         config_files += ['box.ipynb', 'keywords.cfg']
 65 |     
 66 |     for ifile in config_files:
 67 |         source = os.path.join(config_source, ifile)
 68 |         target = os.path.join(config_dir, ifile)
 69 |         copy(source, target)
 70 |     
 71 |     # Add a .gitignore to exclude nbfancy and html directories
 72 |     # as well as checkpoints
 73 |     ignore_path = pkg_resources.resource_filename(resource_package, '/tools')
 74 |     ignore_source = os.path.join(ignore_path, 'example_gitignore')
 75 |     ignore_target = os.path.join(cwd, '.gitignore')
 76 |     copy(ignore_source, ignore_target)
 77 |     
 78 |     # Copy template if specified
 79 |     if args.include != 'none': # works for template and tutorial
 80 |         template_path = '/' + args.include  # Do not use os.path.join()
 81 |         source = pkg_resources.resource_filename(resource_package, template_path)
 82 |         target = os.path.join(cwd, 'nbplain')
 83 |         copy_tree(source, target)
 84 |         
 85 |     else: # works for none
 86 |         make_dir = os.path.join(cwd, 'nbplain')
 87 |         if not os.path.isdir(make_dir):
 88 |             try:
 89 |                 os.mkdir(make_dir)
 90 |             except FileExistsError:
 91 |                 print('Directory', make_dir, 'already exists')
 92 |                 
 93 |     
 94 | def configure(args):
 95 |     '''
 96 |     Sets up global config
 97 |     '''
 98 |     parser = argparse.ArgumentParser()
 99 |     parser.prog += ' ' + sys.argv[1]
100 |     parser.add_argument('package',
101 |                         choices=['jupyter_css', 'bash2_magic', 'pdflatex_magic', 'all_magic'],
102 |                         help='additional package to configure for use with nbfancy')
103 |     parser.add_argument('-y',
104 |                         action='store_true',
105 |                         help='answer yes to config question (only for scripting)')
106 |     args, unknown = parser.parse_known_args(sys.argv[2:])
107 |     
108 |     if not args.y:
109 |         print('This step necessarily changes your user\'s global iPython or Jupyter config.')
110 |         print('This will affect the system copy of these configuration files, even if you are in a virtualenv')
111 |         user_input = input('Do you wish to proceed? [Y/N] : ')
112 |         if 'y' in user_input.lower():
113 |             confirmed = True
114 |         else:
115 |             confirmed = False
116 |     else:
117 |         confirmed = True
118 |     
119 |     if confirmed:
120 |         if args.package == 'jupyter_css':
121 |             globalconf.jupyter()
122 |         elif args.package == 'all_magic':
123 |             globalconf.ipython()
124 |         else:
125 |             print('You can\'t pick and choose, just install all_magic')
126 |         print('Your global config has been updated')
127 |     else:
128 |         print('Your global config has not been changed, however',
129 |                 args.package,
130 |                 'is not configured to work on your system')
131 | 
132 | def rerun(args):
133 |     '''
134 |     Re evaulate all cells in notebook
135 |     '''
136 |     parser = argparse.ArgumentParser(formatter_class=argparse.ArgumentDefaultsHelpFormatter)
137 |     parser.prog += ' ' + sys.argv[1]
138 |     parser.add_argument('input_dir',
139 |                         type=str,
140 |                         default='nbplain',
141 |                         nargs='?',
142 |                         help='Plain notebook directory')
143 |     parser.add_argument('--output_dir',
144 |                         type=str,
145 |                         default=None,
146 |                         help='Name of directory for re-evaluated notebooks')
147 |     parser.add_argument('--clear_only',
148 |                         action='store_true',
149 |                         help='Clear the cells, but do not re-evaluate')
150 |     parser.add_argument('--allow_errors',
151 |                         action='store_true',
152 |                         help='Continue running notebook even if errors occur')
153 |     parser.add_argument('--timeout',
154 |                         type=int,
155 |                         default=60,
156 |                         help='Number of seconds to allow each cell to run for')
157 |     args, unknown = parser.parse_known_args(sys.argv[2:])
158 |     # Make output directory same as input unless otherwise specified
159 |     if args.output_dir is None:
160 |         args.output_dir = args.input_dir
161 |     
162 |     # Get directory contents
163 |     if os.path.isdir(args.input_dir):
164 |         contents, solution_contents = nbftools.directory_contents(args.input_dir)
165 |         contents += solution_contents
166 |         
167 |         # Create output directory
168 |         if not os.path.isdir(args.output_dir):
169 |             try:
170 |                 os.mkdir(args.output_dir)
171 |             except FileExistsError:
172 |                 print('Directory', args.output_dir, 'already exists')
173 |     else:
174 |         raise FileNotFoundError(2, 'No such directory', args.input_dir)
175 |     
176 |     # Create preprocessors 
177 |     clear_pre = nc.preprocessors.ClearOutputPreprocessor()
178 |     exec_pre = nc.preprocessors.ExecutePreprocessor(timeout=args.timeout,
179 |                                                     allow_errors=args.allow_errors)
180 |     if args.allow_errors:
181 |         print('Warning: Notebooks are being run with --allow_errors flag')
182 |         print('\tYou will not be notified of any errors and it is your')
183 |         print('\tresponsibility to verify the output is correct.')
184 |     
185 |     # Loop over contents of directory
186 |     for infile in contents:
187 |         # Read in notebook
188 |         print('Reading input file:', infile)
189 |         notebook = nf.read(os.path.join(args.input_dir, infile), nf.NO_CONVERT)
190 |         
191 |         # Clear or clear and reexecute
192 |         if args.clear_only:
193 |             clear_pre.preprocess(notebook, {'metadata': {'path': args.output_dir}})
194 |         else:
195 |             try:
196 |                 # Needs to be output dir NOT input dir
197 |                 exec_pre.preprocess(notebook, {'metadata': {'path': args.output_dir}})
198 |             except nc.preprocessors.CellExecutionError as e:
199 |                 print('Error: While executing the notebook', infile)
200 |                 print(e)
201 |                 print('Warning: notebook will be written, but some cells may not have executed')
202 |                 print('\tIf you want to continue running beyond this error try the --allow_errors flag')
203 |             
204 |         # Write out notebook
205 |         print('Writing output file:', infile)
206 |         nf.write(notebook, os.path.join(args.output_dir, infile))
207 | 
208 | def render(args):
209 |     '''
210 |     Render plain notebooks as fancy notebooks
211 |     '''
212 |     from urllib.parse import quote as urlquote
213 |     
214 |     parser = argparse.ArgumentParser(formatter_class=argparse.ArgumentDefaultsHelpFormatter)
215 |     parser.prog += ' ' + sys.argv[1]
216 |     parser.add_argument('input_dir',
217 |                         type=str,
218 |                         default='nbplain',
219 |                         nargs='?',
220 |                         help='Plain notebook directory')
221 |     parser.add_argument('--output_dir',
222 |                         type=str,
223 |                         default='nbfancy',
224 |                         help='Directory to output rendered notebooks to')
225 |     parser.add_argument('-c', '--config',
226 |                         type=str,
227 |                         default='config',
228 |                         help='Custom configuration directory')
229 |     args, unknown = parser.parse_known_args(sys.argv[2:])
230 |     
231 |     if os.path.isdir(args.input_dir):
232 |         contents, solution_contents = nbftools.directory_contents(args.input_dir)
233 |         
234 |         # Use config if specified, both fallback to global if error
235 |         if args.config:
236 |             configdir = args.config
237 |         else:
238 |             configdir = os.path.join(args.inputdir, 'config')
239 |         
240 |         # Read in the header, box config, box template and footer file
241 |         header = nbftools.read_header(configdir)
242 |         template = nbftools.read_box_template(configdir)
243 |         config = nbftools.read_box_colour_config(configdir)
244 |         footer = nbftools.read_footer(configdir)
245 |         
246 |         # Create output directory
247 |         if not os.path.isdir(args.output_dir):
248 |             try:
249 |                 os.mkdir(args.output_dir)
250 |             except FileExistsError:
251 |                 print('Directory', args.output_dir, 'already exists')
252 |     else:
253 |         raise FileNotFoundError(2, 'No such directory', args.input_dir)
254 |     
255 |     # Loop over contents of directory (excluding solution files)
256 |     for infile in contents:
257 |         # Read input file
258 |         print('Reading input file:', infile)
259 |         plain = nf.read(os.path.join(args.input_dir, infile), nf.NO_CONVERT)
260 |         solnfilename = infile.replace('.ipynb', '-soln.ipynb')
261 |         
262 |         # Render
263 |         rendered, soln = nbftools.notebook2rendered(plain,
264 |                                                     config,
265 |                                                     template,
266 |                                                     solnfilename)
267 |         
268 |         # Add header
269 |         rendered['cells'].insert(0, header)
270 |         
271 |         # Add navigation to footer
272 |         triple = {'index' : './00_schedule.ipynb'} # Prevent error
273 |         triple = nbftools.navigation_triple(args.input_dir, infile)
274 |         
275 |         tmp_footer = footer.copy()
276 |         tmp_footer['source'] = footer['source'].format_map(triple)
277 |         if triple['index'] != ('./' + infile):
278 |             rendered['cells'].append(tmp_footer)
279 |             
280 |         # Remove cell toolbars and add scroll to slides
281 |         rendered['metadata']['celltoolbar'] = 'None'
282 |         rendered['metadata']['livereveal'] =  {'scroll' : True}
283 |         
284 |         # Write the new notebook
285 |         print('Writing output file:', infile)
286 |         nf.write(plain, os.path.join(args.output_dir, infile))
287 | 
288 |         # If needed also write the solutions notebook
289 |         if soln is not None:
290 |             # Add header
291 |             soln['cells'].insert(0, header)
292 |             soln['metadata']['celltoolbar'] = 'None'
293 |             
294 |             print('Writing output file:', solnfilename)
295 |             nf.write(soln, os.path.join(args.output_dir, solnfilename))
296 |     
297 | 
298 | def html(args):
299 |     '''
300 |     Publish fancy (or even plain) notebooks as html
301 |     '''
302 |     parser = argparse.ArgumentParser(formatter_class=argparse.ArgumentDefaultsHelpFormatter)
303 |     parser.prog += ' ' + sys.argv[1]
304 |     parser.add_argument('input_dir',
305 |                         type=str,
306 |                         default='nbfancy',
307 |                         nargs='?',
308 |                         help='Fancy notebook directory')
309 |     parser.add_argument('--output_dir',
310 |                         type=str,
311 |                         default='html',
312 |                         help='Directory to output html pages to')
313 |     parser.add_argument('--include_slides',
314 |                         action='store_true',
315 |                         help='Includes html slides in slides directory inside output_dir')
316 |     parser.add_argument('--redirect',
317 |                         type=str,
318 |                         default='00_schedule.html',
319 |                         help='page to automatically redirect to')
320 |     args, unknown = parser.parse_known_args(sys.argv[2:])
321 |     
322 |     if not os.path.isdir(args.output_dir):
323 |         try:
324 |             os.mkdir(args.output_dir)
325 |         except FileExistsError:
326 |             print('Directory', args.output_dir, 'already exists')
327 |     
328 |     # Collect all input files
329 |     if os.path.isdir(args.input_dir):
330 |         contents, solution_contents = nbftools.directory_contents(args.input_dir)
331 |         contents += solution_contents
332 |     else:
333 |         raise FileNotFoundError(2, 'No such directory', args.input_dir)
334 |     
335 |     # Create output directory if not already present
336 |     cwd = os.getcwd()
337 |     make_dir = os.path.join(cwd, args.output_dir)
338 |     if not os.path.isdir(make_dir):
339 |         try:
340 |             os.mkdir(make_dir)
341 |         except FileExistsError:
342 |             print('Directory', make_dir, 'already exists')
343 |     
344 |     # Copy all resources to output directory
345 |     resource_package = 'nbfancy'
346 |     config_path = '/tools/css'  # Do not use os.path.join()
347 |     css_dir = pkg_resources.resource_filename(resource_package, config_path)
348 |     
349 |     # Copy our custom CSS directory to output directory
350 |     copy_tree(css_dir, args.output_dir)
351 |     
352 |     # Copy local resource directories
353 |     dir_list = ['images', 'code', 'data']
354 |     for idir in dir_list:
355 |         try:
356 |             copy_tree(idir, os.path.join(args.output_dir, idir))
357 |         except:
358 |             print(idir, 'directory not found, not copying')
359 |     
360 |     # Make a slides directory
361 |     slides_dir = os.path.join(args.output_dir, 'slides')
362 |     if args.include_slides and not os.path.isdir(slides_dir):
363 |         try:
364 |             os.mkdir(slides_dir)
365 |         except FileExistsError:
366 |             print('Directory', slides_dir, 'already exists')
367 |     
368 |     # Convert all collected input files (makes slides too)
369 |     for infile in contents:
370 |         # Read input file
371 |         print('Reading input file:', infile)
372 |         html = nbftools.notebook2HTML(os.path.join(args.input_dir, infile))
373 |         if args.include_slides:
374 |             slides = nbftools.notebook2slides(os.path.join(args.input_dir, infile))
375 |             
376 |         # Name the output file
377 |         outfilename = infile.replace('.ipynb', '.html')
378 |         outpath = os.path.join(args.output_dir, outfilename)
379 |         
380 |         # Write webpage
381 |         print('Writing output file:', outfilename)
382 |         with open(outpath, 'w') as fh:
383 |             fh.write(html)
384 |         
385 |         # Write slideshow
386 |         if args.include_slides:
387 |             slides_path = os.path.join(slides_dir, outfilename)
388 |             print('Writing slides output file:', outfilename)
389 |             with open(slides_path, 'w') as fh:
390 |                 fh.write(slides)
391 |     
392 |     # Add a redirect
393 |     redirect_path = '/tools'  # Do not use os.path.join()
394 |     redirect_dir = pkg_resources.resource_filename(resource_package, redirect_path)
395 |     print('Reading redirect template')
396 |     with open(os.path.join(redirect_dir, 'redirect.html'), 'r') as fh:
397 |         redirect_template = fh.read()
398 |     
399 |     redirect_html = redirect_template.format_map({'page' : args.redirect})
400 |     print('Reading redirect file')
401 |     with open(os.path.join(args.output_dir, 'index.html'), 'w') as fh:
402 |         fh.write(redirect_html)
403 | 
404 | def main():
405 |     '''
406 |     Checks for the verb used with nbfancy command
407 |     '''
408 |     # Parse all of the command line options
409 |     parser = argparse.ArgumentParser()
410 |     parser.add_argument('verb',
411 |                         choices=['init', 'hello', 'configure', 'rerun', 'render', 'html'],
412 |                         help='action to perform. Try adding --help to one of these options for more usage information')
413 |     
414 |     # Check that an argument was passed, if not print a more helpful message
415 |     if len(sys.argv)==1:
416 |         parser.print_help(sys.stderr)
417 |         sys.exit(1)          
418 |     args = parser.parse_args(sys.argv[1:2])
419 |     
420 |     # Call function with given name
421 |     
422 |     call = globals()[args.verb]
423 |     call(args)
424 | 
425 | if __name__ == '__main__':
426 |     main()
427 | 


--------------------------------------------------------------------------------
/nbfancy/tools/css/css/w3.css:
--------------------------------------------------------------------------------
  1 | /* W3.CSS 4.10 February 2018 by Jan Egil and Borge Refsnes */
  2 | html{box-sizing:border-box}*,*:before,*:after{box-sizing:inherit}
  3 | /* Extract from normalize.css by Nicolas Gallagher and Jonathan Neal git.io/normalize */
  4 | html{-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%}body{margin:0}
  5 | article,aside,details,figcaption,figure,footer,header,main,menu,nav,section,summary{display:block}
  6 | audio,canvas,progress,video{display:inline-block}progress{vertical-align:baseline}
  7 | audio:not([controls]){display:none;height:0}[hidden],template{display:none}
  8 | a{background-color:transparent;-webkit-text-decoration-skip:objects}
  9 | a:active,a:hover{outline-width:0}abbr[title]{border-bottom:none;text-decoration:underline;text-decoration:underline dotted}
 10 | dfn{font-style:italic}mark{background:#ff0;color:#000}
 11 | small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}
 12 | sub{bottom:-0.25em}sup{top:-0.5em}figure{margin:1em 40px}img{border-style:none}svg:not(:root){overflow:hidden}
 13 | code,kbd,pre,samp{font-family:monospace,monospace;font-size:1em}hr{box-sizing:content-box;height:0;overflow:visible}
 14 | button,input,select,textarea{font:inherit;margin:0}optgroup{font-weight:bold}
 15 | button,input{overflow:visible}button,select{text-transform:none}
 16 | button,html [type=button],[type=reset],[type=submit]{-webkit-appearance:button}
 17 | button::-moz-focus-inner, [type=button]::-moz-focus-inner, [type=reset]::-moz-focus-inner, [type=submit]::-moz-focus-inner{border-style:none;padding:0}
 18 | button:-moz-focusring, [type=button]:-moz-focusring, [type=reset]:-moz-focusring, [type=submit]:-moz-focusring{outline:1px dotted ButtonText}
 19 | fieldset{border:1px solid #c0c0c0;margin:0 2px;padding:.35em .625em .75em}
 20 | legend{color:inherit;display:table;max-width:100%;padding:0;white-space:normal}textarea{overflow:auto}
 21 | [type=checkbox],[type=radio]{padding:0}
 22 | [type=number]::-webkit-inner-spin-button,[type=number]::-webkit-outer-spin-button{height:auto}
 23 | [type=search]{-webkit-appearance:textfield;outline-offset:-2px}
 24 | [type=search]::-webkit-search-cancel-button,[type=search]::-webkit-search-decoration{-webkit-appearance:none}
 25 | ::-webkit-input-placeholder{color:inherit;opacity:0.54}
 26 | ::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}
 27 | /* End extract */
 28 | html,body{font-family:Verdana,sans-serif;font-size:15px;line-height:1.5}html{overflow-x:hidden}
 29 | h1{font-size:36px}h2{font-size:30px}h3{font-size:24px}h4{font-size:20px}h5{font-size:18px}h6{font-size:16px}.w3-serif{font-family:serif}
 30 | h1,h2,h3,h4,h5,h6{font-family:"Segoe UI",Arial,sans-serif;font-weight:400;margin:10px 0}.w3-wide{letter-spacing:4px}
 31 | hr{border:0;border-top:1px solid #eee;margin:20px 0}
 32 | .w3-image{max-width:100%;height:auto}img{vertical-align:middle}a{color:inherit}
 33 | .w3-table,.w3-table-all{border-collapse:collapse;border-spacing:0;width:100%;display:table}.w3-table-all{border:1px solid #ccc}
 34 | .w3-bordered tr,.w3-table-all tr{border-bottom:1px solid #ddd}.w3-striped tbody tr:nth-child(even){background-color:#f1f1f1}
 35 | .w3-table-all tr:nth-child(odd){background-color:#fff}.w3-table-all tr:nth-child(even){background-color:#f1f1f1}
 36 | .w3-hoverable tbody tr:hover,.w3-ul.w3-hoverable li:hover{background-color:#ccc}.w3-centered tr th,.w3-centered tr td{text-align:center}
 37 | .w3-table td,.w3-table th,.w3-table-all td,.w3-table-all th{padding:8px 8px;display:table-cell;text-align:left;vertical-align:top}
 38 | .w3-table th:first-child,.w3-table td:first-child,.w3-table-all th:first-child,.w3-table-all td:first-child{padding-left:16px}
 39 | .w3-btn,.w3-button{border:none;display:inline-block;padding:8px 16px;vertical-align:middle;overflow:hidden;text-decoration:none;color:inherit;background-color:inherit;text-align:center;cursor:pointer;white-space:nowrap}
 40 | .w3-btn:hover{box-shadow:0 8px 16px 0 rgba(0,0,0,0.2),0 6px 20px 0 rgba(0,0,0,0.19)}
 41 | .w3-btn,.w3-button{-webkit-touch-callout:none;-webkit-user-select:none;-khtml-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}   
 42 | .w3-disabled,.w3-btn:disabled,.w3-button:disabled{cursor:not-allowed;opacity:0.3}.w3-disabled *,:disabled *{pointer-events:none}
 43 | .w3-btn.w3-disabled:hover,.w3-btn:disabled:hover{box-shadow:none}
 44 | .w3-badge,.w3-tag{background-color:#000;color:#fff;display:inline-block;padding-left:8px;padding-right:8px;text-align:center}.w3-badge{border-radius:50%}
 45 | .w3-ul{list-style-type:none;padding:0;margin:0}.w3-ul li{padding:8px 16px;border-bottom:1px solid #ddd}.w3-ul li:last-child{border-bottom:none}
 46 | .w3-tooltip,.w3-display-container{position:relative}.w3-tooltip .w3-text{display:none}.w3-tooltip:hover .w3-text{display:inline-block}
 47 | .w3-ripple:active{opacity:0.5}.w3-ripple{transition:opacity 0s}
 48 | .w3-input{padding:8px;display:block;border:none;border-bottom:1px solid #ccc;width:100%}
 49 | .w3-select{padding:9px 0;width:100%;border:none;border-bottom:1px solid #ccc}
 50 | .w3-dropdown-click,.w3-dropdown-hover{position:relative;display:inline-block;cursor:pointer}
 51 | .w3-dropdown-hover:hover .w3-dropdown-content{display:block}
 52 | .w3-dropdown-hover:first-child,.w3-dropdown-click:hover{background-color:#ccc;color:#000}
 53 | .w3-dropdown-hover:hover > .w3-button:first-child,.w3-dropdown-click:hover > .w3-button:first-child{background-color:#ccc;color:#000}
 54 | .w3-dropdown-content{cursor:auto;color:#000;background-color:#fff;display:none;position:absolute;min-width:160px;margin:0;padding:0;z-index:1}
 55 | .w3-check,.w3-radio{width:24px;height:24px;position:relative;top:6px}
 56 | .w3-sidebar{height:100%;width:200px;background-color:#fff;position:fixed!important;z-index:1;overflow:auto}
 57 | .w3-bar-block .w3-dropdown-hover,.w3-bar-block .w3-dropdown-click{width:100%}
 58 | .w3-bar-block .w3-dropdown-hover .w3-dropdown-content,.w3-bar-block .w3-dropdown-click .w3-dropdown-content{min-width:100%}
 59 | .w3-bar-block .w3-dropdown-hover .w3-button,.w3-bar-block .w3-dropdown-click .w3-button{width:100%;text-align:left;padding:8px 16px}
 60 | .w3-main,#main{transition:margin-left .4s}
 61 | .w3-modal{z-index:3;display:none;padding-top:100px;position:fixed;left:0;top:0;width:100%;height:100%;overflow:auto;background-color:rgb(0,0,0);background-color:rgba(0,0,0,0.4)}
 62 | .w3-modal-content{margin:auto;background-color:#fff;position:relative;padding:0;outline:0;width:600px}
 63 | .w3-bar{width:100%;overflow:hidden}.w3-center .w3-bar{display:inline-block;width:auto}
 64 | .w3-bar .w3-bar-item{padding:8px 16px;float:left;width:auto;border:none;display:block;outline:0}
 65 | .w3-bar .w3-dropdown-hover,.w3-bar .w3-dropdown-click{position:static;float:left}
 66 | .w3-bar .w3-button{white-space:normal}
 67 | .w3-bar-block .w3-bar-item{width:100%;display:block;padding:8px 16px;text-align:left;border:none;white-space:normal;float:none;outline:0}
 68 | .w3-bar-block.w3-center .w3-bar-item{text-align:center}.w3-block{display:block;width:100%}
 69 | .w3-responsive{display:block;overflow-x:auto}
 70 | .w3-container:after,.w3-container:before,.w3-panel:after,.w3-panel:before,.w3-row:after,.w3-row:before,.w3-row-padding:after,.w3-row-padding:before,
 71 | .w3-cell-row:before,.w3-cell-row:after,.w3-clear:after,.w3-clear:before,.w3-bar:before,.w3-bar:after{content:"";display:table;clear:both}
 72 | .w3-col,.w3-half,.w3-third,.w3-twothird,.w3-threequarter,.w3-quarter{float:left;width:100%}
 73 | .w3-col.s1{width:8.33333%}.w3-col.s2{width:16.66666%}.w3-col.s3{width:24.99999%}.w3-col.s4{width:33.33333%}
 74 | .w3-col.s5{width:41.66666%}.w3-col.s6{width:49.99999%}.w3-col.s7{width:58.33333%}.w3-col.s8{width:66.66666%}
 75 | .w3-col.s9{width:74.99999%}.w3-col.s10{width:83.33333%}.w3-col.s11{width:91.66666%}.w3-col.s12{width:99.99999%}
 76 | @media (min-width:601px){.w3-col.m1{width:8.33333%}.w3-col.m2{width:16.66666%}.w3-col.m3,.w3-quarter{width:24.99999%}.w3-col.m4,.w3-third{width:33.33333%}
 77 | .w3-col.m5{width:41.66666%}.w3-col.m6,.w3-half{width:49.99999%}.w3-col.m7{width:58.33333%}.w3-col.m8,.w3-twothird{width:66.66666%}
 78 | .w3-col.m9,.w3-threequarter{width:74.99999%}.w3-col.m10{width:83.33333%}.w3-col.m11{width:91.66666%}.w3-col.m12{width:99.99999%}}
 79 | @media (min-width:993px){.w3-col.l1{width:8.33333%}.w3-col.l2{width:16.66666%}.w3-col.l3{width:24.99999%}.w3-col.l4{width:33.33333%}
 80 | .w3-col.l5{width:41.66666%}.w3-col.l6{width:49.99999%}.w3-col.l7{width:58.33333%}.w3-col.l8{width:66.66666%}
 81 | .w3-col.l9{width:74.99999%}.w3-col.l10{width:83.33333%}.w3-col.l11{width:91.66666%}.w3-col.l12{width:99.99999%}}
 82 | .w3-content{max-width:980px;margin:auto}.w3-rest{overflow:hidden}
 83 | .w3-cell-row{display:table;width:100%}.w3-cell{display:table-cell}
 84 | .w3-cell-top{vertical-align:top}.w3-cell-middle{vertical-align:middle}.w3-cell-bottom{vertical-align:bottom}
 85 | .w3-hide{display:none!important}.w3-show-block,.w3-show{display:block!important}.w3-show-inline-block{display:inline-block!important}
 86 | @media (max-width:600px){.w3-modal-content{margin:0 10px;width:auto!important}.w3-modal{padding-top:30px}
 87 | .w3-dropdown-hover.w3-mobile .w3-dropdown-content,.w3-dropdown-click.w3-mobile .w3-dropdown-content{position:relative}	
 88 | .w3-hide-small{display:none!important}.w3-mobile{display:block;width:100%!important}.w3-bar-item.w3-mobile,.w3-dropdown-hover.w3-mobile,.w3-dropdown-click.w3-mobile{text-align:center}
 89 | .w3-dropdown-hover.w3-mobile,.w3-dropdown-hover.w3-mobile .w3-btn,.w3-dropdown-hover.w3-mobile .w3-button,.w3-dropdown-click.w3-mobile,.w3-dropdown-click.w3-mobile .w3-btn,.w3-dropdown-click.w3-mobile .w3-button{width:100%}}
 90 | @media (max-width:768px){.w3-modal-content{width:500px}.w3-modal{padding-top:50px}}
 91 | @media (min-width:993px){.w3-modal-content{width:900px}.w3-hide-large{display:none!important}.w3-sidebar.w3-collapse{display:block!important}}
 92 | @media (max-width:992px) and (min-width:601px){.w3-hide-medium{display:none!important}}
 93 | @media (max-width:992px){.w3-sidebar.w3-collapse{display:none}.w3-main{margin-left:0!important;margin-right:0!important}}
 94 | .w3-top,.w3-bottom{position:fixed;width:100%;z-index:1}.w3-top{top:0}.w3-bottom{bottom:0}
 95 | .w3-overlay{position:fixed;display:none;width:100%;height:100%;top:0;left:0;right:0;bottom:0;background-color:rgba(0,0,0,0.5);z-index:2}
 96 | .w3-display-topleft{position:absolute;left:0;top:0}.w3-display-topright{position:absolute;right:0;top:0}
 97 | .w3-display-bottomleft{position:absolute;left:0;bottom:0}.w3-display-bottomright{position:absolute;right:0;bottom:0}
 98 | .w3-display-middle{position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);-ms-transform:translate(-50%,-50%)}
 99 | .w3-display-left{position:absolute;top:50%;left:0%;transform:translate(0%,-50%);-ms-transform:translate(-0%,-50%)}
100 | .w3-display-right{position:absolute;top:50%;right:0%;transform:translate(0%,-50%);-ms-transform:translate(0%,-50%)}
101 | .w3-display-topmiddle{position:absolute;left:50%;top:0;transform:translate(-50%,0%);-ms-transform:translate(-50%,0%)}
102 | .w3-display-bottommiddle{position:absolute;left:50%;bottom:0;transform:translate(-50%,0%);-ms-transform:translate(-50%,0%)}
103 | .w3-display-container:hover .w3-display-hover{display:block}.w3-display-container:hover span.w3-display-hover{display:inline-block}.w3-display-hover{display:none}
104 | .w3-display-position{position:absolute}
105 | .w3-circle{border-radius:50%}
106 | .w3-round-small{border-radius:2px}.w3-round,.w3-round-medium{border-radius:4px}.w3-round-large{border-radius:8px}.w3-round-xlarge{border-radius:16px}.w3-round-xxlarge{border-radius:32px}
107 | .w3-row-padding,.w3-row-padding>.w3-half,.w3-row-padding>.w3-third,.w3-row-padding>.w3-twothird,.w3-row-padding>.w3-threequarter,.w3-row-padding>.w3-quarter,.w3-row-padding>.w3-col{padding:0 8px}
108 | .w3-container,.w3-panel{padding:0.01em 16px}.w3-panel{margin-top:16px;margin-bottom:16px}
109 | .w3-code,.w3-codespan{font-family:Consolas,"courier new";font-size:16px}
110 | .w3-code{width:auto;background-color:#fff;padding:8px 12px;border-left:4px solid #4CAF50;word-wrap:break-word}
111 | .w3-codespan{color:crimson;background-color:#f1f1f1;padding-left:4px;padding-right:4px;font-size:110%}
112 | .w3-card,.w3-card-2{box-shadow:0 2px 5px 0 rgba(0,0,0,0.16),0 2px 10px 0 rgba(0,0,0,0.12)}
113 | .w3-card-4,.w3-hover-shadow:hover{box-shadow:0 4px 10px 0 rgba(0,0,0,0.2),0 4px 20px 0 rgba(0,0,0,0.19)}
114 | .w3-spin{animation:w3-spin 2s infinite linear}@keyframes w3-spin{0%{transform:rotate(0deg)}100%{transform:rotate(359deg)}}
115 | .w3-animate-fading{animation:fading 10s infinite}@keyframes fading{0%{opacity:0}50%{opacity:1}100%{opacity:0}}
116 | .w3-animate-opacity{animation:opac 0.8s}@keyframes opac{from{opacity:0} to{opacity:1}}
117 | .w3-animate-top{position:relative;animation:animatetop 0.4s}@keyframes animatetop{from{top:-300px;opacity:0} to{top:0;opacity:1}}
118 | .w3-animate-left{position:relative;animation:animateleft 0.4s}@keyframes animateleft{from{left:-300px;opacity:0} to{left:0;opacity:1}}
119 | .w3-animate-right{position:relative;animation:animateright 0.4s}@keyframes animateright{from{right:-300px;opacity:0} to{right:0;opacity:1}}
120 | .w3-animate-bottom{position:relative;animation:animatebottom 0.4s}@keyframes animatebottom{from{bottom:-300px;opacity:0} to{bottom:0;opacity:1}}
121 | .w3-animate-zoom {animation:animatezoom 0.6s}@keyframes animatezoom{from{transform:scale(0)} to{transform:scale(1)}}
122 | .w3-animate-input{transition:width 0.4s ease-in-out}.w3-animate-input:focus{width:100%!important}
123 | .w3-opacity,.w3-hover-opacity:hover{opacity:0.60}.w3-opacity-off,.w3-hover-opacity-off:hover{opacity:1}
124 | .w3-opacity-max{opacity:0.25}.w3-opacity-min{opacity:0.75}
125 | .w3-greyscale-max,.w3-grayscale-max,.w3-hover-greyscale:hover,.w3-hover-grayscale:hover{filter:grayscale(100%)}
126 | .w3-greyscale,.w3-grayscale{filter:grayscale(75%)}.w3-greyscale-min,.w3-grayscale-min{filter:grayscale(50%)}
127 | .w3-sepia{filter:sepia(75%)}.w3-sepia-max,.w3-hover-sepia:hover{filter:sepia(100%)}.w3-sepia-min{filter:sepia(50%)}
128 | .w3-tiny{font-size:10px!important}.w3-small{font-size:12px!important}.w3-medium{font-size:15px!important}.w3-large{font-size:18px!important}
129 | .w3-xlarge{font-size:24px!important}.w3-xxlarge{font-size:36px!important}.w3-xxxlarge{font-size:48px!important}.w3-jumbo{font-size:64px!important}
130 | .w3-left-align{text-align:left!important}.w3-right-align{text-align:right!important}.w3-justify{text-align:justify!important}.w3-center{text-align:center!important}
131 | .w3-border-0{border:0!important}.w3-border{border:1px solid #ccc!important}
132 | .w3-border-top{border-top:1px solid #ccc!important}.w3-border-bottom{border-bottom:1px solid #ccc!important}
133 | .w3-border-left{border-left:1px solid #ccc!important}.w3-border-right{border-right:1px solid #ccc!important}
134 | .w3-topbar{border-top:6px solid #ccc!important}.w3-bottombar{border-bottom:6px solid #ccc!important}
135 | .w3-leftbar{border-left:6px solid #ccc!important}.w3-rightbar{border-right:6px solid #ccc!important}
136 | .w3-section,.w3-code{margin-top:16px!important;margin-bottom:16px!important}
137 | .w3-margin{margin:16px!important}.w3-margin-top{margin-top:16px!important}.w3-margin-bottom{margin-bottom:16px!important}
138 | .w3-margin-left{margin-left:16px!important}.w3-margin-right{margin-right:16px!important}
139 | .w3-padding-small{padding:4px 8px!important}.w3-padding{padding:8px 16px!important}.w3-padding-large{padding:12px 24px!important}
140 | .w3-padding-16{padding-top:16px!important;padding-bottom:16px!important}.w3-padding-24{padding-top:24px!important;padding-bottom:24px!important}
141 | .w3-padding-32{padding-top:32px!important;padding-bottom:32px!important}.w3-padding-48{padding-top:48px!important;padding-bottom:48px!important}
142 | .w3-padding-64{padding-top:64px!important;padding-bottom:64px!important}
143 | .w3-left{float:left!important}.w3-right{float:right!important}
144 | .w3-button:hover{color:#000!important;background-color:#ccc!important}
145 | .w3-transparent,.w3-hover-none:hover{background-color:transparent!important}
146 | .w3-hover-none:hover{box-shadow:none!important}
147 | /* Colors */
148 | .w3-amber,.w3-hover-amber:hover{color:#000!important;background-color:#ffc107!important}
149 | .w3-aqua,.w3-hover-aqua:hover{color:#000!important;background-color:#00ffff!important}
150 | .w3-blue,.w3-hover-blue:hover{color:#fff!important;background-color:#2196F3!important}
151 | .w3-light-blue,.w3-hover-light-blue:hover{color:#000!important;background-color:#87CEEB!important}
152 | .w3-brown,.w3-hover-brown:hover{color:#fff!important;background-color:#795548!important}
153 | .w3-cyan,.w3-hover-cyan:hover{color:#000!important;background-color:#00bcd4!important}
154 | .w3-blue-grey,.w3-hover-blue-grey:hover,.w3-blue-gray,.w3-hover-blue-gray:hover{color:#fff!important;background-color:#607d8b!important}
155 | .w3-green,.w3-hover-green:hover{color:#fff!important;background-color:#4CAF50!important}
156 | .w3-light-green,.w3-hover-light-green:hover{color:#000!important;background-color:#8bc34a!important}
157 | .w3-indigo,.w3-hover-indigo:hover{color:#fff!important;background-color:#3f51b5!important}
158 | .w3-khaki,.w3-hover-khaki:hover{color:#000!important;background-color:#f0e68c!important}
159 | .w3-lime,.w3-hover-lime:hover{color:#000!important;background-color:#cddc39!important}
160 | .w3-orange,.w3-hover-orange:hover{color:#000!important;background-color:#ff9800!important}
161 | .w3-deep-orange,.w3-hover-deep-orange:hover{color:#fff!important;background-color:#ff5722!important}
162 | .w3-pink,.w3-hover-pink:hover{color:#fff!important;background-color:#e91e63!important}
163 | .w3-purple,.w3-hover-purple:hover{color:#fff!important;background-color:#9c27b0!important}
164 | .w3-deep-purple,.w3-hover-deep-purple:hover{color:#fff!important;background-color:#673ab7!important}
165 | .w3-red,.w3-hover-red:hover{color:#fff!important;background-color:#f44336!important}
166 | .w3-sand,.w3-hover-sand:hover{color:#000!important;background-color:#fdf5e6!important}
167 | .w3-teal,.w3-hover-teal:hover{color:#fff!important;background-color:#009688!important}
168 | .w3-yellow,.w3-hover-yellow:hover{color:#000!important;background-color:#ffeb3b!important}
169 | .w3-white,.w3-hover-white:hover{color:#000!important;background-color:#fff!important}
170 | .w3-black,.w3-hover-black:hover{color:#fff!important;background-color:#000!important}
171 | .w3-grey,.w3-hover-grey:hover,.w3-gray,.w3-hover-gray:hover{color:#000!important;background-color:#9e9e9e!important}
172 | .w3-light-grey,.w3-hover-light-grey:hover,.w3-light-gray,.w3-hover-light-gray:hover{color:#000!important;background-color:#f1f1f1!important}
173 | .w3-dark-grey,.w3-hover-dark-grey:hover,.w3-dark-gray,.w3-hover-dark-gray:hover{color:#fff!important;background-color:#616161!important}
174 | .w3-pale-red,.w3-hover-pale-red:hover{color:#000!important;background-color:#ffdddd!important}
175 | .w3-pale-green,.w3-hover-pale-green:hover{color:#000!important;background-color:#ddffdd!important}
176 | .w3-pale-yellow,.w3-hover-pale-yellow:hover{color:#000!important;background-color:#ffffcc!important}
177 | .w3-pale-blue,.w3-hover-pale-blue:hover{color:#000!important;background-color:#ddffff!important}
178 | .w3-text-amber,.w3-hover-text-amber:hover{color:#ffc107!important}
179 | .w3-text-aqua,.w3-hover-text-aqua:hover{color:#00ffff!important}
180 | .w3-text-blue,.w3-hover-text-blue:hover{color:#2196F3!important}
181 | .w3-text-light-blue,.w3-hover-text-light-blue:hover{color:#87CEEB!important}
182 | .w3-text-brown,.w3-hover-text-brown:hover{color:#795548!important}
183 | .w3-text-cyan,.w3-hover-text-cyan:hover{color:#00bcd4!important}
184 | .w3-text-blue-grey,.w3-hover-text-blue-grey:hover,.w3-text-blue-gray,.w3-hover-text-blue-gray:hover{color:#607d8b!important}
185 | .w3-text-green,.w3-hover-text-green:hover{color:#4CAF50!important}
186 | .w3-text-light-green,.w3-hover-text-light-green:hover{color:#8bc34a!important}
187 | .w3-text-indigo,.w3-hover-text-indigo:hover{color:#3f51b5!important}
188 | .w3-text-khaki,.w3-hover-text-khaki:hover{color:#b4aa50!important}
189 | .w3-text-lime,.w3-hover-text-lime:hover{color:#cddc39!important}
190 | .w3-text-orange,.w3-hover-text-orange:hover{color:#ff9800!important}
191 | .w3-text-deep-orange,.w3-hover-text-deep-orange:hover{color:#ff5722!important}
192 | .w3-text-pink,.w3-hover-text-pink:hover{color:#e91e63!important}
193 | .w3-text-purple,.w3-hover-text-purple:hover{color:#9c27b0!important}
194 | .w3-text-deep-purple,.w3-hover-text-deep-purple:hover{color:#673ab7!important}
195 | .w3-text-red,.w3-hover-text-red:hover{color:#f44336!important}
196 | .w3-text-sand,.w3-hover-text-sand:hover{color:#fdf5e6!important}
197 | .w3-text-teal,.w3-hover-text-teal:hover{color:#009688!important}
198 | .w3-text-yellow,.w3-hover-text-yellow:hover{color:#d2be0e!important}
199 | .w3-text-white,.w3-hover-text-white:hover{color:#fff!important}
200 | .w3-text-black,.w3-hover-text-black:hover{color:#000!important}
201 | .w3-text-grey,.w3-hover-text-grey:hover,.w3-text-gray,.w3-hover-text-gray:hover{color:#757575!important}
202 | .w3-text-light-grey,.w3-hover-text-light-grey:hover,.w3-text-light-gray,.w3-hover-text-light-gray:hover{color:#f1f1f1!important}
203 | .w3-text-dark-grey,.w3-hover-text-dark-grey:hover,.w3-text-dark-gray,.w3-hover-text-dark-gray:hover{color:#3a3a3a!important}
204 | .w3-border-amber,.w3-hover-border-amber:hover{border-color:#ffc107!important}
205 | .w3-border-aqua,.w3-hover-border-aqua:hover{border-color:#00ffff!important}
206 | .w3-border-blue,.w3-hover-border-blue:hover{border-color:#2196F3!important}
207 | .w3-border-light-blue,.w3-hover-border-light-blue:hover{border-color:#87CEEB!important}
208 | .w3-border-brown,.w3-hover-border-brown:hover{border-color:#795548!important}
209 | .w3-border-cyan,.w3-hover-border-cyan:hover{border-color:#00bcd4!important}
210 | .w3-border-blue-grey,.w3-hover-border-blue-grey:hover,.w3-border-blue-gray,.w3-hover-border-blue-gray:hover{border-color:#607d8b!important}
211 | .w3-border-green,.w3-hover-border-green:hover{border-color:#4CAF50!important}
212 | .w3-border-light-green,.w3-hover-border-light-green:hover{border-color:#8bc34a!important}
213 | .w3-border-indigo,.w3-hover-border-indigo:hover{border-color:#3f51b5!important}
214 | .w3-border-khaki,.w3-hover-border-khaki:hover{border-color:#f0e68c!important}
215 | .w3-border-lime,.w3-hover-border-lime:hover{border-color:#cddc39!important}
216 | .w3-border-orange,.w3-hover-border-orange:hover{border-color:#ff9800!important}
217 | .w3-border-deep-orange,.w3-hover-border-deep-orange:hover{border-color:#ff5722!important}
218 | .w3-border-pink,.w3-hover-border-pink:hover{border-color:#e91e63!important}
219 | .w3-border-purple,.w3-hover-border-purple:hover{border-color:#9c27b0!important}
220 | .w3-border-deep-purple,.w3-hover-border-deep-purple:hover{border-color:#673ab7!important}
221 | .w3-border-red,.w3-hover-border-red:hover{border-color:#f44336!important}
222 | .w3-border-sand,.w3-hover-border-sand:hover{border-color:#fdf5e6!important}
223 | .w3-border-teal,.w3-hover-border-teal:hover{border-color:#009688!important}
224 | .w3-border-yellow,.w3-hover-border-yellow:hover{border-color:#ffeb3b!important}
225 | .w3-border-white,.w3-hover-border-white:hover{border-color:#fff!important}
226 | .w3-border-black,.w3-hover-border-black:hover{border-color:#000!important}
227 | .w3-border-grey,.w3-hover-border-grey:hover,.w3-border-gray,.w3-hover-border-gray:hover{border-color:#9e9e9e!important}
228 | .w3-border-light-grey,.w3-hover-border-light-grey:hover,.w3-border-light-gray,.w3-hover-border-light-gray:hover{border-color:#f1f1f1!important}
229 | .w3-border-dark-grey,.w3-hover-border-dark-grey:hover,.w3-border-dark-gray,.w3-hover-border-dark-gray:hover{border-color:#616161!important}
230 | .w3-border-pale-red,.w3-hover-border-pale-red:hover{border-color:#ffe7e7!important}.w3-border-pale-green,.w3-hover-border-pale-green:hover{border-color:#e7ffe7!important}
231 | .w3-border-pale-yellow,.w3-hover-border-pale-yellow:hover{border-color:#ffffcc!important}.w3-border-pale-blue,.w3-hover-border-pale-blue:hover{border-color:#e7ffff!important}


--------------------------------------------------------------------------------
/nbfancy/tutorial/05_environments.ipynb:
--------------------------------------------------------------------------------
  1 | {
  2 |  "cells": [
  3 |   {
  4 |    "cell_type": "markdown",
  5 |    "metadata": {
  6 |     "slideshow": {
  7 |      "slide_type": "slide"
  8 |     }
  9 |    },
 10 |    "source": [
 11 |     "# Environments"
 12 |    ]
 13 |   },
 14 |   {
 15 |    "cell_type": "markdown",
 16 |    "metadata": {
 17 |     "slideshow": {
 18 |      "slide_type": "subslide"
 19 |     }
 20 |    },
 21 |    "source": [
 22 |     "## Overview:\n",
 23 |     "- **Teaching:** 10 min\n",
 24 |     "- **Exercises:** 0 min\n",
 25 |     "\n",
 26 |     "**Questions**\n",
 27 |     "- What decoration does NBfancy provide?\n",
 28 |     "- How do I use NBFancy to decorate my notebooks?\n",
 29 |     "\n",
 30 |     "**Objectives**\n",
 31 |     "- Learn what NBfancy environments do and how to use them"
 32 |    ]
 33 |   },
 34 |   {
 35 |    "cell_type": "markdown",
 36 |    "metadata": {
 37 |     "slideshow": {
 38 |      "slide_type": "slide"
 39 |     }
 40 |    },
 41 |    "source": [
 42 |     "## General\n",
 43 |     "Keywords can be entered at the start of any markdown cell in a notebook to indicate that it requires additional markup. We call these specailly marked up cells environments. The general syntax is `## Keyword: Optional title`, where:\n",
 44 |     "\n",
 45 |     "* There can be any number of hashes at the start\n",
 46 |     "* The keyword that defines the environment\n",
 47 |     "* A colon is **required**, even if no optional title is provided\n",
 48 |     "* An optional title can be given, which will appear in the cell\n",
 49 |     "\n",
 50 |     "This structure for the keywords was chosen so that it still displays well in an unrendered notebook, ie: when you are creating the material.  The specific format has been chosen for ease of use and to mitigate against accidental decoration.\n",
 51 |     "\n",
 52 |     "*All headings get normalised to `##` regardless of how many hashes are used*"
 53 |    ]
 54 |   },
 55 |   {
 56 |    "cell_type": "markdown",
 57 |    "metadata": {
 58 |     "slideshow": {
 59 |      "slide_type": "slide"
 60 |     }
 61 |    },
 62 |    "source": [
 63 |     "The keywords available by default, in the order they will normally be encountered, are\n",
 64 |     "* `Prerequisites`\n",
 65 |     "* `Schedule`\n",
 66 |     "* `Setup`\n",
 67 |     "These are ususally reserved for the `00_schedule.ipynb` page, but can be used elsewhere if required and\n",
 68 |     "* `Overview`\n",
 69 |     "* `Information`\n",
 70 |     "* `Exercise`\n",
 71 |     "* `Solution`\n",
 72 |     "* `Key Points`\n",
 73 |     "These are normally found in each `XX_episode.ipynb` page.\n",
 74 |     "\n",
 75 |     "Below we outline how each environment works. In the sections on [keywords](08_keywords.ipynb) and [template](09_template.ipynb), we outline how we can change some of the default behaviour."
 76 |    ]
 77 |   },
 78 |   {
 79 |    "cell_type": "markdown",
 80 |    "metadata": {
 81 |     "slideshow": {
 82 |      "slide_type": "slide"
 83 |     }
 84 |    },
 85 |    "source": [
 86 |     "### Format\n",
 87 |     "We give a description of each environment.\n",
 88 |     "\n",
 89 |     "This will be followed by three 'cells':\n",
 90 |     "1. The first uses `%%markdown` magic to show what the plain text you enter looks like.    \n",
 91 |     "2. The output of this cell, the second 'cell' is how the environment will render in the plain notebook\n",
 92 |     "3. The third cell is what the cell will look like once `nbfancy render` has been used.\n",
 93 |     "\n",
 94 |     "You should just enter the plain text **after** `%%markdown` into a markdown cell in a notebook and once rendered it will appear as the third example."
 95 |    ]
 96 |   },
 97 |   {
 98 |    "cell_type": "markdown",
 99 |    "metadata": {
100 |     "slideshow": {
101 |      "slide_type": "slide"
102 |     }
103 |    },
104 |    "source": [
105 |     "## Prerequisites\n",
106 |     "\n",
107 |     "By default the **prerequisites** environment is coloured green, is higlighted with a star symbol and the keyword is kept in the title."
108 |    ]
109 |   },
110 |   {
111 |    "cell_type": "code",
112 |    "execution_count": 1,
113 |    "metadata": {
114 |     "slideshow": {
115 |      "slide_type": "subslide"
116 |     }
117 |    },
118 |    "outputs": [
119 |     {
120 |      "data": {
121 |       "text/markdown": [
122 |        "## Prerequisites:\n",
123 |        "* Bulleted\n",
124 |        "* List\n",
125 |        "* of\n",
126 |        "* items\n"
127 |       ],
128 |       "text/plain": [
129 |        ""
130 |       ]
131 |      },
132 |      "metadata": {},
133 |      "output_type": "display_data"
134 |     }
135 |    ],
136 |    "source": [
137 |     "%%markdown\n",
138 |     "## Prerequisites:\n",
139 |     "* Bulleted\n",
140 |     "* List\n",
141 |     "* of\n",
142 |     "* items"
143 |    ]
144 |   },
145 |   {
146 |    "cell_type": "markdown",
147 |    "metadata": {
148 |     "slideshow": {
149 |      "slide_type": "subslide"
150 |     }
151 |    },
152 |    "source": [
153 |     "## Prerequisites:\n",
154 |     "* Bulleted\n",
155 |     "* List\n",
156 |     "* of\n",
157 |     "* items"
158 |    ]
159 |   },
160 |   {
161 |    "cell_type": "markdown",
162 |    "metadata": {
163 |     "slideshow": {
164 |      "slide_type": "slide"
165 |     }
166 |    },
167 |    "source": [
168 |     "## Schedule\n",
169 |     "The **schedule** environment is white as most of the body will usually be filled with a table of links to the various parts of the lesson. It uses the clock symbol and by default the keyword is kept."
170 |    ]
171 |   },
172 |   {
173 |    "cell_type": "code",
174 |    "execution_count": 4,
175 |    "metadata": {
176 |     "slideshow": {
177 |      "slide_type": "subslide"
178 |     }
179 |    },
180 |    "outputs": [
181 |     {
182 |      "data": {
183 |       "text/markdown": [
184 |        "## Schedule:\n",
185 |        "| Time | Episode | Description |\n",
186 |        "|---|---|---|\n",
187 |        "| 0:00 | [Introdction](01_intro.ipynb) | A good start |\n",
188 |        "| 0:10 | [Part One](02_part_one.ipynb) | The first part |\n",
189 |        "| 0:20 | [Part Two](03_part_two.ipynb) | The next part |\n"
190 |       ],
191 |       "text/plain": [
192 |        ""
193 |       ]
194 |      },
195 |      "metadata": {},
196 |      "output_type": "display_data"
197 |     }
198 |    ],
199 |    "source": [
200 |     "%%markdown\n",
201 |     "## Schedule:\n",
202 |     "| Time | Episode | Description |\n",
203 |     "|---|---|---|\n",
204 |     "| 0:00 | [Introdction](01_intro.ipynb) | A good start |\n",
205 |     "| 0:10 | [Part One](02_part_one.ipynb) | The first part |\n",
206 |     "| 0:20 | [Part Two](03_part_two.ipynb) | The next part |"
207 |    ]
208 |   },
209 |   {
210 |    "cell_type": "markdown",
211 |    "metadata": {
212 |     "slideshow": {
213 |      "slide_type": "subslide"
214 |     }
215 |    },
216 |    "source": [
217 |     "## Schedule:\n",
218 |     "| Time | Episode | Description |\n",
219 |     "|---|---|---|\n",
220 |     "| 0:00 | [Introdction](01_intro.ipynb) | A good start |\n",
221 |     "| 0:10 | [Part One](02_part_one.ipynb) | The first part |\n",
222 |     "| 0:20 | [Part Two](03_part_two.ipynb) | The next part |"
223 |    ]
224 |   },
225 |   {
226 |    "cell_type": "markdown",
227 |    "metadata": {
228 |     "slideshow": {
229 |      "slide_type": "slide"
230 |     }
231 |    },
232 |    "source": [
233 |     "## Setup\n",
234 |     "The **setup** environment is also green, in general the first and last environment you see on each page will be green, but none on the intermediate environments will be. The symbol is gears and the keyword is retained.\n",
235 |     "\n",
236 |     "When the setup is lengthy we have alternatively included a specific notebook `00_setup.ipynb` and an additional link in the schedule."
237 |    ]
238 |   },
239 |   {
240 |    "cell_type": "code",
241 |    "execution_count": 5,
242 |    "metadata": {
243 |     "slideshow": {
244 |      "slide_type": "subslide"
245 |     }
246 |    },
247 |    "outputs": [
248 |     {
249 |      "data": {
250 |       "text/markdown": [
251 |        "## Setup:\n",
252 |        "Install NBfancy by executing `pip install nbfancy`\n"
253 |       ],
254 |       "text/plain": [
255 |        ""
256 |       ]
257 |      },
258 |      "metadata": {},
259 |      "output_type": "display_data"
260 |     }
261 |    ],
262 |    "source": [
263 |     "%%markdown\n",
264 |     "## Setup:\n",
265 |     "Install NBfancy by executing `pip install nbfancy`"
266 |    ]
267 |   },
268 |   {
269 |    "cell_type": "markdown",
270 |    "metadata": {
271 |     "slideshow": {
272 |      "slide_type": "subslide"
273 |     }
274 |    },
275 |    "source": [
276 |     "## Setup:\n",
277 |     "Install NBfancy by executing `pip install nbfancy`"
278 |    ]
279 |   },
280 |   {
281 |    "cell_type": "markdown",
282 |    "metadata": {
283 |     "slideshow": {
284 |      "slide_type": "slide"
285 |     }
286 |    },
287 |    "source": [
288 |     "## Overview\n",
289 |     "The **overview** environment appears at the start of each section or episode and hence is coloured green. It has uses the page icon and retains the keyword. The overview environment is useful for outlining the timings for a section, the key questions that will be answered and the learning objectives. We suggest the following layout:"
290 |    ]
291 |   },
292 |   {
293 |    "cell_type": "code",
294 |    "execution_count": 1,
295 |    "metadata": {
296 |     "slideshow": {
297 |      "slide_type": "subslide"
298 |     }
299 |    },
300 |    "outputs": [
301 |     {
302 |      "data": {
303 |       "text/markdown": [
304 |        "## Overview:\n",
305 |        "- **Teaching:** 10 min\n",
306 |        "- **Exercises:** 10 min\n",
307 |        "\n",
308 |        "**Questions**\n",
309 |        "- Is this a list of questions?\n",
310 |        "\n",
311 |        "**Objectives**\n",
312 |        "- This is a list of outcomes\n"
313 |       ],
314 |       "text/plain": [
315 |        ""
316 |       ]
317 |      },
318 |      "metadata": {},
319 |      "output_type": "display_data"
320 |     }
321 |    ],
322 |    "source": [
323 |     "%%markdown\n",
324 |     "## Overview:\n",
325 |     "- **Teaching:** 10 min\n",
326 |     "- **Exercises:** 10 min\n",
327 |     "\n",
328 |     "**Questions**\n",
329 |     "- Is this a list of questions?\n",
330 |     "\n",
331 |     "**Objectives**\n",
332 |     "- This is a list of outcomes"
333 |    ]
334 |   },
335 |   {
336 |    "cell_type": "markdown",
337 |    "metadata": {
338 |     "slideshow": {
339 |      "slide_type": "subslide"
340 |     }
341 |    },
342 |    "source": [
343 |     "## Overview:\n",
344 |     "- **Teaching:** 10 min\n",
345 |     "- **Exercises:** 10 min\n",
346 |     "\n",
347 |     "**Questions**\n",
348 |     "- Is this a list of questions?\n",
349 |     "\n",
350 |     "**Objectives**\n",
351 |     "- This is a list of outcomes"
352 |    ]
353 |   },
354 |   {
355 |    "cell_type": "markdown",
356 |    "metadata": {
357 |     "slideshow": {
358 |      "slide_type": "slide"
359 |     }
360 |    },
361 |    "source": [
362 |     "## Information\n",
363 |     "The **information** environment is coloured blue and can be used anywhere in the body of a lesson where special attention is required or additional information is available. It uses the information circle symbol and the keyword is removed on use.  This means that the optional title will be used as the title, and if no optional title is provided, only the symbol will appear."
364 |    ]
365 |   },
366 |   {
367 |    "cell_type": "code",
368 |    "execution_count": 7,
369 |    "metadata": {
370 |     "slideshow": {
371 |      "slide_type": "subslide"
372 |     }
373 |    },
374 |    "outputs": [
375 |     {
376 |      "data": {
377 |       "text/markdown": [
378 |        "## Information: A useful tip\n",
379 |        "Here is some additional information about that thing we just did\n"
380 |       ],
381 |       "text/plain": [
382 |        ""
383 |       ]
384 |      },
385 |      "metadata": {},
386 |      "output_type": "display_data"
387 |     }
388 |    ],
389 |    "source": [
390 |     "%%markdown\n",
391 |     "## Information: A useful tip\n",
392 |     "Here is some additional information about that thing we just did"
393 |    ]
394 |   },
395 |   {
396 |    "cell_type": "markdown",
397 |    "metadata": {
398 |     "slideshow": {
399 |      "slide_type": "subslide"
400 |     }
401 |    },
402 |    "source": [
403 |     "## Information: A useful tip\n",
404 |     "Here is some additional information about that thing we just did"
405 |    ]
406 |   },
407 |   {
408 |    "cell_type": "markdown",
409 |    "metadata": {
410 |     "slideshow": {
411 |      "slide_type": "slide"
412 |     }
413 |    },
414 |    "source": [
415 |     "## Exercise and Solution environments\n",
416 |     "The **exercise** environment is yellow and has a pencil and pad as a symbol. The keyword is removed on use, but you can use the optional title to give exercises titles with a number at the start if desired. There are number of ways of using the exercise environment and its optional partner, solution.  \n",
417 |     "\n",
418 |     "The **solution** environment is blue and uses the eye symbol. As with the exercise environment the keyword is removed, and a number can be used at the start. You won't see any solution environments on this page! So as not to give the answers immediately after the exercise the solutions are placed in a seperate notebook. This allows you to create an exercise and solution together, but these get seperated by the NBfancy tool. The solution link defined in the exercise will point to the solutions page and even scroll the page to the relevant solution, if it were to appear off screen.\n",
419 |     "\n",
420 |     "We will demonstrate four different ways of using the exercise/solution environments."
421 |    ]
422 |   },
423 |   {
424 |    "cell_type": "markdown",
425 |    "metadata": {
426 |     "slideshow": {
427 |      "slide_type": "slide"
428 |     }
429 |    },
430 |    "source": [
431 |     "### Exercise  only\n",
432 |     "First an exercise with no solution, for when no solutions is provided/required."
433 |    ]
434 |   },
435 |   {
436 |    "cell_type": "code",
437 |    "execution_count": 9,
438 |    "metadata": {
439 |     "slideshow": {
440 |      "slide_type": "subslide"
441 |     }
442 |    },
443 |    "outputs": [
444 |     {
445 |      "data": {
446 |       "text/markdown": [
447 |        "## Exercise: 1\n",
448 |        "Do the first task\n"
449 |       ],
450 |       "text/plain": [
451 |        ""
452 |       ]
453 |      },
454 |      "metadata": {},
455 |      "output_type": "display_data"
456 |     }
457 |    ],
458 |    "source": [
459 |     "%%markdown\n",
460 |     "## Exercise: 1\n",
461 |     "Do the first task"
462 |    ]
463 |   },
464 |   {
465 |    "cell_type": "markdown",
466 |    "metadata": {
467 |     "slideshow": {
468 |      "slide_type": "subslide"
469 |     }
470 |    },
471 |    "source": [
472 |     "## Exercise: 1\n",
473 |     "Do the first task"
474 |    ]
475 |   },
476 |   {
477 |    "cell_type": "markdown",
478 |    "metadata": {
479 |     "slideshow": {
480 |      "slide_type": "slide"
481 |     }
482 |    },
483 |    "source": [
484 |     "### Exercise and separate solution\n",
485 |     "Second an exercise with no inline solution, but which creates a link to a solution notebook. This is useful if you have already defined solutions in a separate notebook.  Currently, the notebooks must have the same root name but with the solution ending with `-soln`. That is if your notebook is called `01_lesson.ipynb` then the link will point to `01_lesson-soln.ipynb`.\n",
486 |     "\n",
487 |     "If the exercise environment ends with an empty markdown link: `[Solution]()` then a link will be created to the solution **automatically**, if it has the same title."
488 |    ]
489 |   },
490 |   {
491 |    "cell_type": "code",
492 |    "execution_count": 10,
493 |    "metadata": {
494 |     "slideshow": {
495 |      "slide_type": "subslide"
496 |     }
497 |    },
498 |    "outputs": [
499 |     {
500 |      "data": {
501 |       "text/markdown": [
502 |        "## Exercise: 2\n",
503 |        "Do the second task\n",
504 |        "\n",
505 |        "[Solution]()\n"
506 |       ],
507 |       "text/plain": [
508 |        ""
509 |       ]
510 |      },
511 |      "metadata": {},
512 |      "output_type": "display_data"
513 |     }
514 |    ],
515 |    "source": [
516 |     "%%markdown\n",
517 |     "## Exercise: 2\n",
518 |     "Do the second task\n",
519 |     "\n",
520 |     "[Solution]()"
521 |    ]
522 |   },
523 |   {
524 |    "cell_type": "markdown",
525 |    "metadata": {
526 |     "slideshow": {
527 |      "slide_type": "subslide"
528 |     }
529 |    },
530 |    "source": [
531 |     "## Exercise: 2\n",
532 |     "Do the second task\n",
533 |     "\n",
534 |     "[Solution]()"
535 |    ]
536 |   },
537 |   {
538 |    "cell_type": "markdown",
539 |    "metadata": {
540 |     "slideshow": {
541 |      "slide_type": "slide"
542 |     }
543 |    },
544 |    "source": [
545 |     "### Exercise and solution, missing link\n",
546 |     "\n",
547 |     "Third an exercise with a solution defined after it, but no link included in the exercise. This is usually an error, but included to demonstrate that the solution is not lost, just difficult to get to!"
548 |    ]
549 |   },
550 |   {
551 |    "cell_type": "code",
552 |    "execution_count": 11,
553 |    "metadata": {
554 |     "slideshow": {
555 |      "slide_type": "subslide"
556 |     }
557 |    },
558 |    "outputs": [
559 |     {
560 |      "data": {
561 |       "text/markdown": [
562 |        "## Exercise: 3\n",
563 |        "Do the third task\n"
564 |       ],
565 |       "text/plain": [
566 |        ""
567 |       ]
568 |      },
569 |      "metadata": {},
570 |      "output_type": "display_data"
571 |     }
572 |    ],
573 |    "source": [
574 |     "%%markdown\n",
575 |     "## Exercise: 3\n",
576 |     "Do the third task"
577 |    ]
578 |   },
579 |   {
580 |    "cell_type": "markdown",
581 |    "metadata": {
582 |     "slideshow": {
583 |      "slide_type": "subslide"
584 |     }
585 |    },
586 |    "source": [
587 |     "## Exercise: 3\n",
588 |     "Do the third task"
589 |    ]
590 |   },
591 |   {
592 |    "cell_type": "code",
593 |    "execution_count": 14,
594 |    "metadata": {
595 |     "slideshow": {
596 |      "slide_type": "subslide"
597 |     }
598 |    },
599 |    "outputs": [
600 |     {
601 |      "data": {
602 |       "text/markdown": [
603 |        "## Solution: 3\n",
604 |        "Everyone gets stuck on number 3\n",
605 |        "\n",
606 |        "BIG BLANK SPACE REQUIRED\n"
607 |       ],
608 |       "text/plain": [
609 |        ""
610 |       ]
611 |      },
612 |      "metadata": {},
613 |      "output_type": "display_data"
614 |     }
615 |    ],
616 |    "source": [
617 |     "%%markdown\n",
618 |     "## Solution: 3\n",
619 |     "Everyone gets stuck on number 3\n",
620 |     "\n",
621 |     "BIG BLANK SPACE REQUIRED"
622 |    ]
623 |   },
624 |   {
625 |    "cell_type": "markdown",
626 |    "metadata": {
627 |     "slideshow": {
628 |      "slide_type": "subslide"
629 |     }
630 |    },
631 |    "source": [
632 |     "## Solution: 3\n",
633 |     "Everyone gets stuck on number 3\n",
634 |     "
\n", 635 | "BIG BLANK SPACE REQUIRED" 636 | ] 637 | }, 638 | { 639 | "cell_type": "markdown", 640 | "metadata": { 641 | "slideshow": { 642 | "slide_type": "slide" 643 | } 644 | }, 645 | "source": [ 646 | "### Exercise and Solution\n", 647 | "\n", 648 | "Fourth and finally an exercise with a solution defined after it, with an empty link included in the exercise. This is the intended typical use of the exercise and solution pair." 649 | ] 650 | }, 651 | { 652 | "cell_type": "code", 653 | "execution_count": 15, 654 | "metadata": { 655 | "slideshow": { 656 | "slide_type": "subslide" 657 | } 658 | }, 659 | "outputs": [ 660 | { 661 | "data": { 662 | "text/markdown": [ 663 | "## Exercise: 4\n", 664 | "Do the fourth task\n", 665 | "\n", 666 | "[Solution]()\n" 667 | ], 668 | "text/plain": [ 669 | "" 670 | ] 671 | }, 672 | "metadata": {}, 673 | "output_type": "display_data" 674 | } 675 | ], 676 | "source": [ 677 | "%%markdown\n", 678 | "## Exercise: 4\n", 679 | "Do the fourth task\n", 680 | "\n", 681 | "[Solution]()" 682 | ] 683 | }, 684 | { 685 | "cell_type": "markdown", 686 | "metadata": { 687 | "slideshow": { 688 | "slide_type": "subslide" 689 | } 690 | }, 691 | "source": [ 692 | "## Exercise: 4\n", 693 | "Do the fourth task\n", 694 | "\n", 695 | "[Solution]()" 696 | ] 697 | }, 698 | { 699 | "cell_type": "code", 700 | "execution_count": 16, 701 | "metadata": { 702 | "slideshow": { 703 | "slide_type": "subslide" 704 | } 705 | }, 706 | "outputs": [ 707 | { 708 | "data": { 709 | "text/markdown": [ 710 | "## Solution: 4\n", 711 | "The fourth task was trivial\n", 712 | "\n", 713 | "BIG BLANK LESS NECESSARY\n" 714 | ], 715 | "text/plain": [ 716 | "" 717 | ] 718 | }, 719 | "metadata": {}, 720 | "output_type": "display_data" 721 | } 722 | ], 723 | "source": [ 724 | "%%markdown\n", 725 | "## Solution: 4\n", 726 | "The fourth task was trivial\n", 727 | "\n", 728 | "BIG BLANK LESS NECESSARY" 729 | ] 730 | }, 731 | { 732 | "cell_type": "markdown", 733 | "metadata": { 734 | "slideshow": { 735 | "slide_type": "subslide" 736 | } 737 | }, 738 | "source": [ 739 | "## Solution: 4\n", 740 | "The fourth task was trivial\n", 741 | "
\n", 742 | "BIG BLANK LESS NECESSARY" 743 | ] 744 | }, 745 | { 746 | "cell_type": "markdown", 747 | "metadata": { 748 | "slideshow": { 749 | "slide_type": "slide" 750 | } 751 | }, 752 | "source": [ 753 | "## Key Points\n", 754 | "The key points environment is typically used to close an episode and is coloured green. It uses the key as a symbol and the keyword is kept. By way of example, we will summarise the key points of this section here." 755 | ] 756 | }, 757 | { 758 | "cell_type": "code", 759 | "execution_count": 8, 760 | "metadata": { 761 | "slideshow": { 762 | "slide_type": "subslide" 763 | } 764 | }, 765 | "outputs": [ 766 | { 767 | "data": { 768 | "text/markdown": [ 769 | "## Key Points:\n", 770 | "- The general keyword syntax is `## Keyword: Optional title\n", 771 | "- `Prerequisites`, `Schedule` and `Setup` are generally used on the first page\n", 772 | "- Sections start with an `Overview` and finish with `Key Points`\n", 773 | "- `Information` highlights additional information in the lesson\n", 774 | "- `Exercise` and `Solution` can be used as a pair to set exercises\n" 775 | ], 776 | "text/plain": [ 777 | "" 778 | ] 779 | }, 780 | "metadata": {}, 781 | "output_type": "display_data" 782 | } 783 | ], 784 | "source": [ 785 | "%%markdown\n", 786 | "## Key Points:\n", 787 | "- The general keyword syntax is `## Keyword: Optional title`\n", 788 | "- `Prerequisites`, `Schedule` and `Setup` are generally used on the first page\n", 789 | "- Sections start with an `Overview` and finish with `Key Points`\n", 790 | "- `Information` highlights additional information in the lesson\n", 791 | "- `Exercise` and `Solution` can be used as a pair to set exercises" 792 | ] 793 | }, 794 | { 795 | "cell_type": "markdown", 796 | "metadata": { 797 | "slideshow": { 798 | "slide_type": "subslide" 799 | } 800 | }, 801 | "source": [ 802 | "## Key Points:\n", 803 | "- The general keyword syntax is `## Keyword: Optional title`\n", 804 | "- `Prerequisites`, `Schedule` and `Setup` are generally used on the first page\n", 805 | "- Sections start with an `Overview` and finish with `Key Points`\n", 806 | "- `Information` highlights additional information in the lesson\n", 807 | "- `Exercise` and `Solution` can be used as a pair to set exercises" 808 | ] 809 | } 810 | ], 811 | "metadata": { 812 | "celltoolbar": "Slideshow", 813 | "kernelspec": { 814 | "display_name": "Python 3", 815 | "language": "python", 816 | "name": "python3" 817 | }, 818 | "language_info": { 819 | "codemirror_mode": { 820 | "name": "ipython", 821 | "version": 3 822 | }, 823 | "file_extension": ".py", 824 | "mimetype": "text/x-python", 825 | "name": "python", 826 | "nbconvert_exporter": "python", 827 | "pygments_lexer": "ipython3", 828 | "version": "3.7.3" 829 | } 830 | }, 831 | "nbformat": 4, 832 | "nbformat_minor": 2 833 | } 834 | --------------------------------------------------------------------------------