├── .gitignore ├── .nojekyll ├── LICENSE ├── MANIFEST ├── MANIFEST.in ├── Makefile ├── README.rst ├── bootstrap └── pyweb.py ├── clippings ├── chunk.w ├── command.w ├── emitter.w └── web.w ├── dist ├── py-web-lp-3.2.tar.gz └── py_web_lp-3.2-py3-none-any.whl ├── docutils.conf ├── examples ├── ackermanns.html ├── ackermanns.py ├── ackermanns.rst ├── ackermanns.w ├── hello_world_latex.tex ├── hello_world_latex.w ├── hello_world_rst.html ├── hello_world_rst.rst ├── hello_world_rst.w ├── hw.html ├── hw.py ├── hw.rst ├── hw.w ├── hw_latex_1.py ├── hw_latex_2.py ├── hw_rst_1.py └── hw_rst_2.py ├── index.html ├── jedit └── pyweb.xml ├── page-layout.css ├── pyproject.toml ├── pyweb.css ├── pyweb.toml ├── requirements.txt ├── spike ├── example.html ├── example.rst ├── jdemo.py ├── test.png └── test.uml ├── src ├── Makefile ├── _build │ └── html │ │ ├── .buildinfo │ │ ├── _images │ │ ├── plantuml-0bff076f813d017a60bee35cd131d50c7c41d245.png │ │ ├── plantuml-4c8ed7c9fa80955420fb7e1d1a646a6dca37f7fd.png │ │ ├── plantuml-5c4f75552d0f17db0e1a31509dcbf1aba1fcdae0.png │ │ ├── plantuml-5e56d809cce3e55d45373ec11d41a2b777f82db2.png │ │ ├── plantuml-8c7ddae0c5921097f120117e800425881eb8020d.png │ │ ├── plantuml-95ad8fb8a1f49dffd970672ef62b015c72f8d9eb.png │ │ ├── plantuml-9709a6999a63105f75b7067a5431c035dec921ba.png │ │ ├── plantuml-9c211de7ab70c13e3003fe0a3f4287c4870ae68b.png │ │ ├── plantuml-a057f978b90f99a509529f42568e557951021d28.png │ │ ├── plantuml-ad2b1999b26d857e32915ff7a3e02e112a1944a8.png │ │ ├── plantuml-af6c5e3c3b745092f6451b2df5c1ecb6ef57c635.png │ │ ├── plantuml-bbfb0883085a6dd7ae0b50ad41ca1da1f40771c0.png │ │ ├── plantuml-bfb4d2a68a2eaa4252def69a662d98e73fdece3e.png │ │ ├── plantuml-de8f7453a793112cdb80266d3c7b23a65cbbf218.png │ │ └── plantuml-fae6df9bf8cb355e39c19c6987c8cc7be8d16732.png │ │ ├── _sources │ │ └── pyweb.rst.txt │ │ ├── _static │ │ ├── _sphinx_javascript_frameworks_compat.js │ │ ├── alabaster.css │ │ ├── basic.css │ │ ├── custom.css │ │ ├── doctools.js │ │ ├── documentation_options.js │ │ ├── file.png │ │ ├── jquery-3.6.0.js │ │ ├── jquery.js │ │ ├── language_data.js │ │ ├── minus.png │ │ ├── plus.png │ │ ├── pygments.css │ │ ├── searchtools.js │ │ ├── underscore-1.13.1.js │ │ └── underscore.js │ │ ├── genindex.html │ │ ├── objects.inv │ │ ├── pyweb.html │ │ ├── search.html │ │ └── searchindex.js ├── c4_diagrams.uml ├── conf.py ├── done.w ├── impl.w ├── intro.w ├── language.w ├── logging.toml ├── make.bat ├── overview.w ├── pyweb.py ├── pyweb.rst ├── pyweb.toml ├── pyweb.w ├── scripts.w ├── tangle.py ├── tests.w ├── todo.w ├── usage.w └── weave.py ├── tests ├── docutils.conf ├── func.w ├── intro.w ├── page-layout.css ├── pyweb.css ├── pyweb_test.html ├── pyweb_test.rst ├── pyweb_test.w ├── runner.py ├── runner.w ├── scripts.w ├── test_loader.py ├── test_scripts.py ├── test_tangler.py ├── test_unit.py ├── test_weaver.py └── unit.w └── tox.ini /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | dev.sh 3 | .idea 4 | pyweb-3.0.py 5 | __pycache__ 6 | test/__pycache__/*.pyc 7 | tests/.svn/* 8 | py_web_tool.egg-info/* 9 | *.pyc 10 | *.aux 11 | *.out 12 | *.toc 13 | v2_test 14 | .tox 15 | pyweb-3.0.py 16 | /clippings/ 17 | plantuml/plantuml*.jar 18 | src/_build/doctrees/environment.pickle 19 | src/_build/doctrees/*.doctree 20 | src/_build/html/_plantuml/* 21 | *.egg-info/* 22 | src/*.egg-info/* 23 | tox_env.sh 24 | tmp* 25 | -------------------------------------------------------------------------------- /.nojekyll: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 3-Clause License 2 | 3 | Copyright (c) 2019, S.Lott 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | 1. Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | 2. Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | 3. Neither the name of the copyright holder nor the names of its 17 | contributors may be used to endorse or promote products derived from 18 | this software without specific prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 21 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 23 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 24 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 26 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 27 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 28 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | -------------------------------------------------------------------------------- /MANIFEST: -------------------------------------------------------------------------------- 1 | # file GENERATED by distutils, do NOT edit 2 | README 3 | additional.w 4 | docutils.conf 5 | done.w 6 | impl.w 7 | intro.w 8 | jedit.rst 9 | jedit.w 10 | overview.w 11 | page-layout.css 12 | pyweb.html 13 | pyweb.py 14 | pyweb.rst 15 | pyweb.w 16 | setup.py 17 | tests.w 18 | testweaver.rst 19 | todo.w 20 | jedit/pyweb.xml 21 | test/combined.w 22 | test/docutils.conf 23 | test/func.w 24 | test/intro.w 25 | test/page-layout.css 26 | test/pyweb.css 27 | test/pyweb_test.html 28 | test/pyweb_test.w 29 | test/test.py 30 | test/test_latex.w 31 | test/test_loader.py 32 | test/test_rst.html 33 | test/test_rst.w 34 | test/test_tangler.py 35 | test/test_unit.py 36 | test/test_weaver.py 37 | test/unit.w 38 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include src/*.w src/*.css src/*.html src/*.conf src/*.rst src/*.py 2 | include bootstrap/*.py 3 | include tests/*.w tests/*.css tests/*.html tests/*.conf tests/*.py 4 | include jedit/*.xml 5 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # Makefile for py-web-tool. 2 | # Requires a pyweb-3.0.py (untouched) to bootstrap the current version. 3 | 4 | SOURCE_PYLPWEB = src/pyweb.w src/intro.w src/overview.w src/impl.w src/tests.w src/todo.w src/done.w src/language.w src/usage.w 5 | TEST_PYLPWEB = tests/pyweb_test.w tests/intro.w tests/unit.w tests/func.w tests/scripts.w 6 | EXAMPLES_PYLPWEB = examples/hello_world_latex.w examples/hello_world_rst.w ackermanns.w 7 | DOCUTILS_PYLPWEB = docutils.conf pyweb.css page-layout.css 8 | 9 | .PHONY : test doc build examples tox 10 | 11 | # Note the bootstrapping new version from version 3.1 as baseline. 12 | PYLPWEB_BOOTSTRAP=${PWD}/bootstrap/pyweb.py 13 | 14 | test : $(SOURCE_PYLPWEB) $(TEST_PYLPWEB) 15 | python3 $(PYLPWEB_BOOTSTRAP) -xw -v -o src src/pyweb.w 16 | # cp src/pyweb.toml pyweb.toml # Can obliterate test setup... 17 | python3 src/pyweb.py tests/pyweb_test.w -o tests 18 | PYTHONPATH=${PWD}/src PYTHONHASHSEED=0 pytest -vv 19 | python3 src/pyweb.py tests/pyweb_test.w -xt -o tests 20 | rst2html.py tests/pyweb_test.rst tests/pyweb_test.html 21 | mypy --strict --show-error-codes --exclude 'conf\.py' src 22 | 23 | doc : src/pyweb.html 24 | 25 | build : src/pyweb.py src/tangle.py src/weave.py src/pyweb.html tests/pyweb_test.rst 26 | 27 | examples : examples/hello_world_latex.tex examples/hello_world_rst.html examples/ackermanns.html examples/hw.html 28 | 29 | src/pyweb.py src/pyweb.rst : $(SOURCE_PYLPWEB) 30 | cd src && python3 pyweb.py pyweb.w 31 | 32 | src/pyweb.html : src/pyweb.rst $(DOCUTILS_PYLPWEB) 33 | # rst2html.py $< $@ 34 | cd src && make html 35 | 36 | tests/pyweb_test.rst : src/pyweb.py $(TEST_PYLPWEB) 37 | python3 src/pyweb.py tests/pyweb_test.w -o tests 38 | 39 | tests/pyweb_test.html : tests/pyweb_test.rst $(DOCUTILS_PYLPWEB) 40 | rst2html.py $< $@ 41 | 42 | examples/hello_world_rst.rst : examples/hello_world_rst.w 43 | python3 src/pyweb.py -w rst $< -o examples 44 | 45 | examples/hello_world_rst.html : examples/hello_world_rst.rst $(DOCUTILS_PYLPWEB) 46 | rst2html.py $< $@ 47 | 48 | examples/hello_world_latex.tex : examples/hello_world_latex.w 49 | python3 src/pyweb.py -w latex $< -o examples 50 | 51 | examples/ackermanns.rst : examples/ackermanns.w 52 | python3 src/pyweb.py -w rst $< -o examples 53 | python -m doctest examples/ackermanns.py 54 | 55 | examples/ackermanns.html : examples/ackermanns.rst $(DOCUTILS_PYLPWEB) 56 | rst2html.py $< $@ 57 | 58 | examples/hw.rst : examples/hw.w 59 | python3 src/pyweb.py -pi -w rst $< -o examples 60 | python3 examples/hw.py >examples/hw_output.log 61 | 62 | examples/hw.html : examples/hw.rst $(DOCUTILS_PYLPWEB) 63 | rst2html.py $< $@ 64 | 65 | tox: 66 | tox 67 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | pyWebLP: In Python, Yet Another Literate Programming Tool 2 | 3 | Literate programming is an attempt to reconcile the opposing needs 4 | of clear presentation to people with the technical issues of 5 | creating code that will work with our current set of tools. 6 | 7 | Presentation to people requires extensive and sophisticated typesetting 8 | techniques. Further, the "narrative arc" of a presentation may not 9 | follow the source code as layed out for the compiler. 10 | 11 | **py-web-tool** is a literate programming tool based on Knuth's Web to combine the actions 12 | of weaving a document with tangling source files. 13 | It is independent of any particular document markup or source language. 14 | Is uses a simple set of markup tags to define chunks of code and 15 | documentation. 16 | 17 | The ``pyweb.w`` file is the source for the various ``pyweb`` module and script files. 18 | The various source code files are created by applying a 19 | tangle operation to the ``.w`` file. The final documentation is created by 20 | applying a weave operation to the ``.w`` file. 21 | 22 | Installation 23 | ------------- 24 | 25 | This requires Python 3.10. 26 | 27 | :: 28 | 29 | python -m pip install py-web-lp 30 | 31 | This will install the ``pyweb`` module and all of its dependencies. Alternatively, 32 | you may better want to install as ``pipx install py-web-lp``. 33 | 34 | Produce Documentation 35 | --------------------- 36 | 37 | The supplied documentation uses RST markup; it requires docutils. 38 | 39 | :: 40 | 41 | python3 -m pip install docutils 42 | 43 | :: 44 | 45 | python3 -m pyweb src/pyweb.w -o src 46 | rst2html.py src/pyweb.rst src/pyweb.html 47 | 48 | Authoring 49 | --------- 50 | 51 | The ``pyweb.html`` document describes the markup used to define code chunks 52 | and assemble those code chunks into a coherent document as well as working code. 53 | You'll create a ``.w`` file with documentation and code. 54 | 55 | If you're a JEdit user, the ``jedit`` directory can be used 56 | to configure syntax highlighting that includes **py-web-lp** and RST. 57 | 58 | Operation 59 | --------- 60 | 61 | After installation and authoring, you can then run **py-web-lp** with the following 62 | command 63 | 64 | :: 65 | 66 | python3 -m pyweb src/pyweb.w -o src 67 | 68 | This will create the various output files from the source ``.w`` file. 69 | 70 | - ``pyweb.rst`` is the final woven document. This can be run through docutils for publication. 71 | 72 | - ``pyweb.py``, ``tangle.py``, ``weave.py`` are the tangled code files. 73 | 74 | All of the files are produced from a single source. 75 | 76 | Testing 77 | ------- 78 | 79 | The ``tests`` directory includes ``pyweb_test.w``, which will create a 80 | complete test suite. 81 | You can create this with the following command 82 | 83 | :: 84 | 85 | python3 -m pyweb tests/pyweb_test.w -o tests 86 | 87 | This weaves a ``tests/pyweb_test.rst`` file. This can be run through docutils for publication. 88 | 89 | This tangles several test modules: ``test.py``, ``test_tangler.py``, ``test_weaver.py``, 90 | ``test_loader.py``, ``test_unit.py``, and ``test_scripts.py``. 91 | 92 | Use **pytest** to run all the tests. 93 | 94 | Here's a typical sequence, used during development: 95 | 96 | :: 97 | 98 | python3 bootstrap/pyweb.py -xw src/pyweb.w -o src 99 | python3 src/pyweb.py tests/pyweb_test.w -o tests 100 | PYTHONPATH=${PWD}/src pytest 101 | rst2html.py tests/pyweb_test.rst tests/pyweb_test.html 102 | mypy --strict src 103 | 104 | Note that a previous release, untouched, is saved in the project's ``bootstrap`` directory. 105 | This is **not** changed during development, since **py-web-lp** is written with **py-web-lp**. 106 | -------------------------------------------------------------------------------- /dist/py-web-lp-3.2.tar.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slott56/py-web-tool/8bbc603068ba3f589f92e9366bdd92a17591720a/dist/py-web-lp-3.2.tar.gz -------------------------------------------------------------------------------- /dist/py_web_lp-3.2-py3-none-any.whl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slott56/py-web-tool/8bbc603068ba3f589f92e9366bdd92a17591720a/dist/py_web_lp-3.2-py3-none-any.whl -------------------------------------------------------------------------------- /docutils.conf: -------------------------------------------------------------------------------- 1 | # docutils.conf 2 | 3 | [html4css1 writer] 4 | stylesheet-path: /Users/slott/miniconda3/envs/pywebtool/lib/python3.10/site-packages/docutils/writers/html4css1/html4css1.css, 5 | page-layout.css 6 | syntax-highlight: long 7 | -------------------------------------------------------------------------------- /examples/ackermanns.py: -------------------------------------------------------------------------------- 1 | 2 | 3 | phi_1 = ( 4 | lambda m, n, p: ( 5 | { 6 | m+n if p == 0 else None, 7 | 0 if n == 0 and p == 1 else None, 8 | 1 if n == 0 and p == 2 else None, 9 | m if n == 0 and p > 2 else None, 10 | phi_1(m, phi_1(m, n-1, p), p-1) if n > 0 and p > 0 else None, 11 | } - {None} 12 | ).pop() 13 | ) 14 | 15 | REPL_set_reduction = """ 16 | 17 | >>> phi_1(3, 5, 0) 18 | 8 19 | >>> phi_1(3, 5, 1) 20 | 15 21 | >>> phi_1(3, 5, 2) 22 | 243 23 | >>> 3**5 24 | 243 25 | 26 | """ 27 | 28 | 29 | phi = ( 30 | lambda m, n, p: ( 31 | { 32 | p == 0: lambda m, n, p: m+n, 33 | n == 0 and p == 1: lambda m, n, p: 0, 34 | n == 0 and p == 2: lambda m, n, p: 1, 35 | n == 0 and p > 2: lambda m, n, p: m, 36 | n > 0 and p > 0: lambda m, n, p: phi(m, phi(m, n-1, p), p-1) 37 | }[True](m, n, p) 38 | ) 39 | ) 40 | 41 | REPL_dict_reduction = """ 42 | 43 | >>> phi(3, 5, 0) == 3+5 44 | True 45 | >>> phi(3, 5, 1) == 3*5 46 | True 47 | >>> phi(3, 5, 2) == 3**5 48 | True 49 | 50 | """ 51 | 52 | 53 | def phi_m(m, n, p): 54 | match (m, n, p): 55 | case (_, _, 0): 56 | return m + n 57 | case (_, 0, 1): 58 | return 0 59 | case (_, 0, 2): 60 | return 1 61 | case (_, 0, _) if p > 2: 62 | return m 63 | case (_, _, _) if n > 0 and p > 0: 64 | return phi_m(m, phi_m(m, n-1, p), p-1) 65 | 66 | REPL_match_statement = """ 67 | 68 | >>> phi_m(3, 5, 0) == 3+5 69 | True 70 | >>> phi_m(3, 5, 1) == 3*5 71 | True 72 | >>> phi_m(3, 5, 2) == 3**5 73 | True 74 | 75 | """ 76 | 77 | __test__ = {n: v for n, v in globals().items() if n.startswith('REPL')} 78 | -------------------------------------------------------------------------------- /examples/ackermanns.rst: -------------------------------------------------------------------------------- 1 | #################### 2 | Ackermann’s Function 3 | #################### 4 | 5 | ============== 6 | Steven F. Lott 7 | ============== 8 | 9 | .. contents:: 10 | 11 | Definitions 12 | =========== 13 | 14 | Here are the definitions for Ackermann's full :math:`\varphi (m,n,p)` function: 15 | 16 | .. math:: 17 | 18 | 19 | \varphi (m,n,p) = \begin{cases} 20 | \varphi (m,n,0)&=m+n\\ 21 | \varphi (m,0,1)&=0\\ 22 | \varphi (m,0,2)&=1\\ 23 | \varphi (m,0,p)&=m {\textbf{ for }}p>2\\ 24 | \varphi (m,n,p)&=\varphi (m,\varphi (m,n-1,p),p-1) {\textbf{ for }}n,p>0 25 | \end{cases} 26 | 27 | These definitions have the following consequences: 28 | 29 | .. math:: 30 | 31 | 32 | \begin{align} 33 | \varphi (m,n,0)&=m+n\\ 34 | \varphi (m,n,1)&=m\times n\\ 35 | \varphi (m,n,2)&=m^{n} 36 | \end{align} 37 | 38 | Implementations 39 | =============== 40 | 41 | We'll look at a number of ways 42 | to implement this. 43 | 44 | 45 | .. _`ackermanns.py (1)`: 46 | .. rubric:: ackermanns.py (1) = 47 | .. parsed-literal:: 48 | :class: code 49 | 50 | 51 | →\ `set reduction (2)`_ 52 | REPL\_set\_reduction = """ 53 | →\ `test set reduction (3)`_ 54 | """ 55 | 56 | →\ `dictionary reduction (4)`_ 57 | REPL\_dict\_reduction = """ 58 | →\ `test dict reduction (5)`_ 59 | """ 60 | 61 | →\ `match statement (6)`_ 62 | REPL\_match\_statement = """ 63 | →\ `test match statemnent (7)`_ 64 | """ 65 | 66 | \_\_test\_\_ = {n: v for n, v in globals().items() if n.startswith('REPL')} 67 | 68 | .. 69 | 70 | .. class:: small 71 | 72 | ∎ *ackermanns.py (1)* 73 | 74 | 75 | 76 | Set Reduction 77 | ------------- 78 | 79 | First, let's consider evaluating the conditionals into "value or None" conditions. 80 | We can then create a set and keep the non-None items. 81 | 82 | The set reduction involves computing a non-\ ``None`` result for the case that is true 83 | and ``None`` result for all remaining cases. The resulting set of values will be reduced to 84 | two items. Subtracting the ``None`` item from the set leaves the result value. 85 | 86 | 87 | .. _`set reduction (2)`: 88 | .. rubric:: set reduction (2) = 89 | .. parsed-literal:: 90 | :class: code 91 | 92 | 93 | phi\_1 = ( 94 | lambda m, n, p: ( 95 | { 96 | m+n if p == 0 else None, 97 | 0 if n == 0 and p == 1 else None, 98 | 1 if n == 0 and p == 2 else None, 99 | m if n == 0 and p > 2 else None, 100 | phi\_1(m, phi\_1(m, n-1, p), p-1) if n > 0 and p > 0 else None, 101 | } - {None} 102 | ).pop() 103 | ) 104 | 105 | .. 106 | 107 | .. class:: small 108 | 109 | ∎ *set reduction (2)* 110 | 111 | 112 | 113 | 114 | .. _`test set reduction (3)`: 115 | .. rubric:: test set reduction (3) = 116 | .. parsed-literal:: 117 | :class: code 118 | 119 | 120 | >>> phi\_1(3, 5, 0) 121 | 8 122 | >>> phi\_1(3, 5, 1) 123 | 15 124 | >>> phi\_1(3, 5, 2) 125 | 243 126 | >>> 3\*\*5 127 | 243 128 | 129 | .. 130 | 131 | .. class:: small 132 | 133 | ∎ *test set reduction (3)* 134 | 135 | 136 | 137 | 138 | Dictionary Reduction 139 | -------------------- 140 | 141 | We can use the condition value (``True`` or ``False``) as a dictionary 142 | key. The value for each key is the lambda to evaluate when the key is ``True``. 143 | Picking the ``True`` key from the dictionary maps to the applicable lambda. 144 | The other lambda, mapped to ``False`` can be ignored. 145 | 146 | 147 | .. _`dictionary reduction (4)`: 148 | .. rubric:: dictionary reduction (4) = 149 | .. parsed-literal:: 150 | :class: code 151 | 152 | 153 | phi = ( 154 | lambda m, n, p: ( 155 | { 156 | p == 0: lambda m, n, p: m+n, 157 | n == 0 and p == 1: lambda m, n, p: 0, 158 | n == 0 and p == 2: lambda m, n, p: 1, 159 | n == 0 and p > 2: lambda m, n, p: m, 160 | n > 0 and p > 0: lambda m, n, p: phi(m, phi(m, n-1, p), p-1) 161 | }[True](m, n, p) 162 | ) 163 | ) 164 | 165 | .. 166 | 167 | .. class:: small 168 | 169 | ∎ *dictionary reduction (4)* 170 | 171 | 172 | 173 | 174 | .. _`test dict reduction (5)`: 175 | .. rubric:: test dict reduction (5) = 176 | .. parsed-literal:: 177 | :class: code 178 | 179 | 180 | >>> phi(3, 5, 0) == 3+5 181 | True 182 | >>> phi(3, 5, 1) == 3\*5 183 | True 184 | >>> phi(3, 5, 2) == 3\*\*5 185 | True 186 | 187 | .. 188 | 189 | .. class:: small 190 | 191 | ∎ *test dict reduction (5)* 192 | 193 | 194 | 195 | 196 | Match/Case 197 | ---------- 198 | 199 | We can use Python 3.10's ``match`` statement, also. 200 | This is generally what folks expect to see. 201 | 202 | 203 | .. _`match statement (6)`: 204 | .. rubric:: match statement (6) = 205 | .. parsed-literal:: 206 | :class: code 207 | 208 | 209 | def phi\_m(m, n, p): 210 | match (m, n, p): 211 | case (\_, \_, 0): 212 | return m + n 213 | case (\_, 0, 1): 214 | return 0 215 | case (\_, 0, 2): 216 | return 1 217 | case (\_, 0, \_) if p > 2: 218 | return m 219 | case (\_, \_, \_) if n > 0 and p > 0: 220 | return phi\_m(m, phi\_m(m, n-1, p), p-1) 221 | 222 | .. 223 | 224 | .. class:: small 225 | 226 | ∎ *match statement (6)* 227 | 228 | 229 | 230 | 231 | .. _`test match statemnent (7)`: 232 | .. rubric:: test match statemnent (7) = 233 | .. parsed-literal:: 234 | :class: code 235 | 236 | 237 | >>> phi\_m(3, 5, 0) == 3+5 238 | True 239 | >>> phi\_m(3, 5, 1) == 3\*5 240 | True 241 | >>> phi\_m(3, 5, 2) == 3\*\*5 242 | True 243 | 244 | .. 245 | 246 | .. class:: small 247 | 248 | ∎ *test match statemnent (7)* 249 | 250 | 251 | 252 | Conclusion 253 | ========== 254 | 255 | We've looked at three ways to define a fairly complex function with a lot of complex-looking 256 | special cases. 257 | 258 | The ``match`` statement seems to fit most people's expectations of the complex-looking math. 259 | -------------------------------------------------------------------------------- /examples/ackermanns.w: -------------------------------------------------------------------------------- 1 | #################### 2 | Ackermann’s Function 3 | #################### 4 | 5 | ============== 6 | Steven F. Lott 7 | ============== 8 | 9 | .. contents:: 10 | 11 | Definitions 12 | =========== 13 | 14 | Here are the definitions for Ackermann's full :math:`\varphi (m,n,p)` function: 15 | 16 | .. math:: 17 | 18 | 19 | \varphi (m,n,p) = \begin{cases} 20 | \varphi (m,n,0)&=m+n\\ 21 | \varphi (m,0,1)&=0\\ 22 | \varphi (m,0,2)&=1\\ 23 | \varphi (m,0,p)&=m {\textbf{ for }}p>2\\ 24 | \varphi (m,n,p)&=\varphi (m,\varphi (m,n-1,p),p-1) {\textbf{ for }}n,p>0 25 | \end{cases} 26 | 27 | These definitions have the following consequences: 28 | 29 | .. math:: 30 | 31 | 32 | \begin{align} 33 | \varphi (m,n,0)&=m+n\\ 34 | \varphi (m,n,1)&=m\times n\\ 35 | \varphi (m,n,2)&=m^{n} 36 | \end{align} 37 | 38 | Implementations 39 | =============== 40 | 41 | We'll look at a number of ways 42 | to implement this. 43 | 44 | @o ackermanns.py 45 | @{ 46 | @ 47 | REPL_set_reduction = """ 48 | @ 49 | """ 50 | 51 | @ 52 | REPL_dict_reduction = """ 53 | @ 54 | """ 55 | 56 | @ 57 | REPL_match_statement = """ 58 | @ 59 | """ 60 | 61 | __test__ = {n: v for n, v in globals().items() if n.startswith('REPL')} 62 | @} 63 | 64 | Set Reduction 65 | ------------- 66 | 67 | First, let's consider evaluating the conditionals into "value or None" conditions. 68 | We can then create a set and keep the non-None items. 69 | 70 | The set reduction involves computing a non-\ ``None`` result for the case that is true 71 | and ``None`` result for all remaining cases. The resulting set of values will be reduced to 72 | two items. Subtracting the ``None`` item from the set leaves the result value. 73 | 74 | @d set reduction 75 | @{ 76 | phi_1 = ( 77 | lambda m, n, p: ( 78 | { 79 | m+n if p == 0 else None, 80 | 0 if n == 0 and p == 1 else None, 81 | 1 if n == 0 and p == 2 else None, 82 | m if n == 0 and p > 2 else None, 83 | phi_1(m, phi_1(m, n-1, p), p-1) if n > 0 and p > 0 else None, 84 | } - {None} 85 | ).pop() 86 | ) 87 | @} 88 | 89 | @d test set reduction 90 | @{ 91 | >>> phi_1(3, 5, 0) 92 | 8 93 | >>> phi_1(3, 5, 1) 94 | 15 95 | >>> phi_1(3, 5, 2) 96 | 243 97 | >>> 3**5 98 | 243 99 | @} 100 | 101 | 102 | Dictionary Reduction 103 | -------------------- 104 | 105 | We can use the condition value (``True`` or ``False``) as a dictionary 106 | key. The value for each key is the lambda to evaluate when the key is ``True``. 107 | Picking the ``True`` key from the dictionary maps to the applicable lambda. 108 | The other lambda, mapped to ``False`` can be ignored. 109 | 110 | @d dictionary reduction 111 | @{ 112 | phi = ( 113 | lambda m, n, p: ( 114 | { 115 | p == 0: lambda m, n, p: m+n, 116 | n == 0 and p == 1: lambda m, n, p: 0, 117 | n == 0 and p == 2: lambda m, n, p: 1, 118 | n == 0 and p > 2: lambda m, n, p: m, 119 | n > 0 and p > 0: lambda m, n, p: phi(m, phi(m, n-1, p), p-1) 120 | }[True](m, n, p) 121 | ) 122 | ) 123 | @} 124 | 125 | @d test dict reduction 126 | @{ 127 | >>> phi(3, 5, 0) == 3+5 128 | True 129 | >>> phi(3, 5, 1) == 3*5 130 | True 131 | >>> phi(3, 5, 2) == 3**5 132 | True 133 | @} 134 | 135 | 136 | Match/Case 137 | ---------- 138 | 139 | We can use Python 3.10's ``match`` statement, also. 140 | This is generally what folks expect to see. 141 | 142 | @d match statement 143 | @{ 144 | def phi_m(m, n, p): 145 | match (m, n, p): 146 | case (_, _, 0): 147 | return m + n 148 | case (_, 0, 1): 149 | return 0 150 | case (_, 0, 2): 151 | return 1 152 | case (_, 0, _) if p > 2: 153 | return m 154 | case (_, _, _) if n > 0 and p > 0: 155 | return phi_m(m, phi_m(m, n-1, p), p-1) 156 | @} 157 | 158 | @d test match statemnent 159 | @{ 160 | >>> phi_m(3, 5, 0) == 3+5 161 | True 162 | >>> phi_m(3, 5, 1) == 3*5 163 | True 164 | >>> phi_m(3, 5, 2) == 3**5 165 | True 166 | @} 167 | 168 | Conclusion 169 | ========== 170 | 171 | We've looked at three ways to define a fairly complex function with a lot of complex-looking 172 | special cases. 173 | 174 | The ``match`` statement seems to fit most people's expectations of the complex-looking math. 175 | -------------------------------------------------------------------------------- /examples/hello_world_latex.tex: -------------------------------------------------------------------------------- 1 | \documentclass{article} 2 | \usepackage{fancyvrb} 3 | \title{Test Program} 4 | \author{Jason R. Fruit} 5 | 6 | \begin{document} 7 | 8 | \maketitle 9 | \tableofcontents 10 | 11 | \section{Introduction} 12 | 13 | This test program prints the word ``hello'', followed by the name of 14 | the operating system as understood by Python. It is implemented in 15 | Python and uses the \texttt{os} module. It builds the message string 16 | in two different ways, and writes separate versions of the program to 17 | two different files. 18 | 19 | \section{Implementation} 20 | 21 | \subsection{Output files} 22 | 23 | This document contains the makings of two files; the first, 24 | \texttt{test.py}, uses simple string concatenation to build its output 25 | message: 26 | 27 | \label{pyweb1} 28 | \begin{flushleft} 29 | \textit{Code example hw_latex_1.py (1)} 30 | \begin{Verbatim}[commandchars=\\\{\},codes={\catcode`$=3\catcode`^=7},frame=single] 31 | 32 | $\triangleright$ Code Example Import the os module (3) 33 | $\triangleright$ Code Example Get the OS description (4) 34 | $\triangleright$ Code Example Construct the message with Concatenation (5) 35 | $\triangleright$ Code Example Print the message (7) 36 | 37 | \end{Verbatim} 38 | [] 39 | \end{flushleft} 40 | 41 | 42 | The second uses string substitution: 43 | 44 | \label{pyweb2} 45 | \begin{flushleft} 46 | \textit{Code example hw_latex_2.py (2)} 47 | \begin{Verbatim}[commandchars=\\\{\},codes={\catcode`$=3\catcode`^=7},frame=single] 48 | 49 | $\triangleright$ Code Example Import the os module (3) 50 | $\triangleright$ Code Example Get the OS description (4) 51 | $\triangleright$ Code Example Construct the message with Substitution (6) 52 | $\triangleright$ Code Example Print the message (7) 53 | 54 | \end{Verbatim} 55 | [] 56 | \end{flushleft} 57 | 58 | 59 | \subsection{Retrieving the OS description} 60 | 61 | First we must import the os module so we can learn about the OS: 62 | 63 | \label{pyweb3} 64 | \begin{flushleft} 65 | \textit{Code example Import the os module (3)} 66 | \begin{Verbatim}[commandchars=\\\{\},codes={\catcode`$=3\catcode`^=7},frame=single] 67 | 68 | import os 69 | 70 | \end{Verbatim} 71 | 72 | \footnotesize 73 | Used by: 74 | \begin{list}{}{} 75 | 76 | \item Code example hw_latex_1.py (1) (Sect. \ref{pyweb1}, p. \pageref{pyweb1}) 77 | ; 78 | \item Code example hw_latex_2.py (2) (Sect. \ref{pyweb2}, p. \pageref{pyweb2}) 79 | 80 | \end{list} 81 | \normalsize 82 | \end{flushleft} 83 | 84 | 85 | That having been done, we can retrieve Python's name for the OS type: 86 | 87 | \label{pyweb4} 88 | \begin{flushleft} 89 | \textit{Code example Get the OS description (4)} 90 | \begin{Verbatim}[commandchars=\\\{\},codes={\catcode`$=3\catcode`^=7},frame=single] 91 | 92 | os_name = os.name 93 | 94 | \end{Verbatim} 95 | 96 | \footnotesize 97 | Used by: 98 | \begin{list}{}{} 99 | 100 | \item Code example hw_latex_1.py (1) (Sect. \ref{pyweb1}, p. \pageref{pyweb1}) 101 | ; 102 | \item Code example hw_latex_2.py (2) (Sect. \ref{pyweb2}, p. \pageref{pyweb2}) 103 | 104 | \end{list} 105 | \normalsize 106 | \end{flushleft} 107 | 108 | 109 | \subsection{Building the message} 110 | 111 | Now, we're ready for the meat of the application: concatenating two strings: 112 | 113 | \label{pyweb5} 114 | \begin{flushleft} 115 | \textit{Code example Construct the message with Concatenation (5)} 116 | \begin{Verbatim}[commandchars=\\\{\},codes={\catcode`$=3\catcode`^=7},frame=single] 117 | 118 | msg = "Hello, " + os_name + "!" 119 | 120 | \end{Verbatim} 121 | 122 | \footnotesize 123 | Used by: 124 | \begin{list}{}{} 125 | 126 | \item Code example hw_latex_1.py (1) (Sect. \ref{pyweb1}, p. \pageref{pyweb1}) 127 | 128 | \end{list} 129 | \normalsize 130 | \end{flushleft} 131 | 132 | 133 | But wait! Is there a better way? Using string substitution might be 134 | better: 135 | 136 | \label{pyweb6} 137 | \begin{flushleft} 138 | \textit{Code example Construct the message with Substitution (6)} 139 | \begin{Verbatim}[commandchars=\\\{\},codes={\catcode`$=3\catcode`^=7},frame=single] 140 | 141 | msg = f"Hello, {os_name}!" 142 | 143 | \end{Verbatim} 144 | 145 | \footnotesize 146 | Used by: 147 | \begin{list}{}{} 148 | 149 | \item Code example hw_latex_2.py (2) (Sect. \ref{pyweb2}, p. \pageref{pyweb2}) 150 | 151 | \end{list} 152 | \normalsize 153 | \end{flushleft} 154 | 155 | 156 | We'll use the first of these methods in \texttt{latex_test.py}, and the 157 | other in \texttt{latex_test_2.py}. 158 | 159 | \subsection{Printing the message} 160 | 161 | Finally, we print the message out for the user to see. Hopefully, a 162 | cheery greeting will make them happy to know what operating system 163 | they have: 164 | 165 | \label{pyweb7} 166 | \begin{flushleft} 167 | \textit{Code example Print the message (7)} 168 | \begin{Verbatim}[commandchars=\\\{\},codes={\catcode`$=3\catcode`^=7},frame=single] 169 | 170 | print(msg) 171 | 172 | \end{Verbatim} 173 | 174 | \footnotesize 175 | Used by: 176 | \begin{list}{}{} 177 | 178 | \item Code example hw_latex_1.py (1) (Sect. \ref{pyweb1}, p. \pageref{pyweb1}) 179 | ; 180 | \item Code example hw_latex_2.py (2) (Sect. \ref{pyweb2}, p. \pageref{pyweb2}) 181 | 182 | \end{list} 183 | \normalsize 184 | \end{flushleft} 185 | 186 | 187 | \end{document} 188 | -------------------------------------------------------------------------------- /examples/hello_world_latex.w: -------------------------------------------------------------------------------- 1 | \documentclass{article} 2 | \usepackage{fancyvrb} 3 | \title{Test Program} 4 | \author{Jason R. Fruit} 5 | 6 | \begin{document} 7 | 8 | \maketitle 9 | \tableofcontents 10 | 11 | \section{Introduction} 12 | 13 | This test program prints the word ``hello'', followed by the name of 14 | the operating system as understood by Python. It is implemented in 15 | Python and uses the \texttt{os} module. It builds the message string 16 | in two different ways, and writes separate versions of the program to 17 | two different files. 18 | 19 | \section{Implementation} 20 | 21 | \subsection{Output files} 22 | 23 | This document contains the makings of two files; the first, 24 | \texttt{test.py}, uses simple string concatenation to build its output 25 | message: 26 | 27 | @o hw_latex_1.py 28 | @{ 29 | @< Import the os module @> 30 | @< Get the OS description @> 31 | @< Construct the message with Concatenation @> 32 | @< Print the message @> 33 | @} 34 | 35 | The second uses string substitution: 36 | 37 | @o hw_latex_2.py 38 | @{ 39 | @< Import the os module @> 40 | @< Get the OS description @> 41 | @< Construct the message with Substitution @> 42 | @< Print the message @> 43 | @} 44 | 45 | \subsection{Retrieving the OS description} 46 | 47 | First we must import the os module so we can learn about the OS: 48 | 49 | @d Import the os module 50 | @{ 51 | import os 52 | @} 53 | 54 | That having been done, we can retrieve Python's name for the OS type: 55 | 56 | @d Get the OS description 57 | @{ 58 | os_name = os.name 59 | @} 60 | 61 | \subsection{Building the message} 62 | 63 | Now, we're ready for the meat of the application: concatenating two strings: 64 | 65 | @d Construct the message with Concatenation 66 | @{ 67 | msg = "Hello, " + os_name + "!" 68 | @} 69 | 70 | But wait! Is there a better way? Using string substitution might be 71 | better: 72 | 73 | @d Construct the message with Substitution 74 | @{ 75 | msg = f"Hello, {os_name}!" 76 | @} 77 | 78 | We'll use the first of these methods in \texttt{latex_test.py}, and the 79 | other in \texttt{latex_test_2.py}. 80 | 81 | \subsection{Printing the message} 82 | 83 | Finally, we print the message out for the user to see. Hopefully, a 84 | cheery greeting will make them happy to know what operating system 85 | they have: 86 | 87 | @d Print the message 88 | @{ 89 | print(msg) 90 | @} 91 | 92 | \end{document} 93 | -------------------------------------------------------------------------------- /examples/hello_world_rst.rst: -------------------------------------------------------------------------------- 1 | #################### 2 | Test Program 3 | #################### 4 | 5 | =============== 6 | Jason R. Fruit 7 | =============== 8 | 9 | .. contents:: 10 | 11 | 12 | Introduction 13 | ============ 14 | 15 | This test program prints the word "hello", followed by the name of 16 | the operating system as understood by Python. It is implemented in 17 | Python and uses the ``os`` module. It builds the message string 18 | in two different ways, and writes separate versions of the program to 19 | two different files. 20 | 21 | Implementation 22 | ============== 23 | 24 | Output files 25 | ------------ 26 | 27 | This document contains the makings of two files; the first, 28 | ``test.py``, uses simple string concatenation to build its output 29 | message: 30 | 31 | 32 | .. _`hw_rst_1.py (1)`: 33 | .. rubric:: hw_rst_1.py (1) = 34 | .. parsed-literal:: 35 | :class: code 36 | 37 | 38 | →\ `Import the os module (3)`_ 39 | →\ `Get the OS description (4)`_ 40 | →\ `Construct the message with Concatenation (5)`_ 41 | →\ `Print the message (7)`_ 42 | 43 | .. 44 | 45 | .. class:: small 46 | 47 | ∎ *hw_rst_1.py (1)* 48 | 49 | 50 | 51 | The second uses string substitution: 52 | 53 | 54 | .. _`hw_rst_2.py (2)`: 55 | .. rubric:: hw_rst_2.py (2) = 56 | .. parsed-literal:: 57 | :class: code 58 | 59 | 60 | →\ `Import the os module (3)`_ 61 | →\ `Get the OS description (4)`_ 62 | →\ `Construct the message with Substitution (6)`_ 63 | →\ `Print the message (7)`_ 64 | 65 | .. 66 | 67 | .. class:: small 68 | 69 | ∎ *hw_rst_2.py (2)* 70 | 71 | 72 | 73 | Retrieving the OS description 74 | ------------------------------- 75 | 76 | First we must import the os module so we can learn about the OS: 77 | 78 | 79 | .. _`Import the os module (3)`: 80 | .. rubric:: Import the os module (3) = 81 | .. parsed-literal:: 82 | :class: code 83 | 84 | 85 | import os 86 | 87 | .. 88 | 89 | .. class:: small 90 | 91 | ∎ *Import the os module (3)* 92 | 93 | 94 | 95 | That having been done, we can retrieve Python's name for the OS type: 96 | 97 | 98 | .. _`Get the OS description (4)`: 99 | .. rubric:: Get the OS description (4) = 100 | .. parsed-literal:: 101 | :class: code 102 | 103 | 104 | os\_name = os.name 105 | 106 | .. 107 | 108 | .. class:: small 109 | 110 | ∎ *Get the OS description (4)* 111 | 112 | 113 | 114 | Building the message 115 | --------------------- 116 | 117 | Now, we're ready for the meat of the application: concatenating two strings: 118 | 119 | 120 | .. _`Construct the message with Concatenation (5)`: 121 | .. rubric:: Construct the message with Concatenation (5) = 122 | .. parsed-literal:: 123 | :class: code 124 | 125 | 126 | msg = "Hello, " + os\_name + "!" 127 | 128 | .. 129 | 130 | .. class:: small 131 | 132 | ∎ *Construct the message with Concatenation (5)* 133 | 134 | 135 | 136 | But wait! Is there a better way? Using string substitution might be 137 | better: 138 | 139 | 140 | .. _`Construct the message with Substitution (6)`: 141 | .. rubric:: Construct the message with Substitution (6) = 142 | .. parsed-literal:: 143 | :class: code 144 | 145 | 146 | msg = f"Hello, {os\_name}!" 147 | 148 | .. 149 | 150 | .. class:: small 151 | 152 | ∎ *Construct the message with Substitution (6)* 153 | 154 | 155 | 156 | We'll use the first of these methods in ``rst_test_1.py``, and the 157 | other in ``rst_test_2.py``. 158 | 159 | Printing the message 160 | ---------------------- 161 | 162 | Finally, we print the message out for the user to see. Hopefully, a 163 | cheery greeting will make them happy to know what operating system 164 | they have: 165 | 166 | 167 | .. _`Print the message (7)`: 168 | .. rubric:: Print the message (7) = 169 | .. parsed-literal:: 170 | :class: code 171 | 172 | 173 | print(msg) 174 | 175 | .. 176 | 177 | .. class:: small 178 | 179 | ∎ *Print the message (7)* 180 | 181 | 182 | -------------------------------------------------------------------------------- /examples/hello_world_rst.w: -------------------------------------------------------------------------------- 1 | #################### 2 | Test Program 3 | #################### 4 | 5 | =============== 6 | Jason R. Fruit 7 | =============== 8 | 9 | .. contents:: 10 | 11 | 12 | Introduction 13 | ============ 14 | 15 | This test program prints the word "hello", followed by the name of 16 | the operating system as understood by Python. It is implemented in 17 | Python and uses the ``os`` module. It builds the message string 18 | in two different ways, and writes separate versions of the program to 19 | two different files. 20 | 21 | Implementation 22 | ============== 23 | 24 | Output files 25 | ------------ 26 | 27 | This document contains the makings of two files; the first, 28 | ``test.py``, uses simple string concatenation to build its output 29 | message: 30 | 31 | @o hw_rst_1.py 32 | @{ 33 | @< Import the os module @> 34 | @< Get the OS description @> 35 | @< Construct the message with Concatenation @> 36 | @< Print the message @> 37 | @} 38 | 39 | The second uses string substitution: 40 | 41 | @o hw_rst_2.py 42 | @{ 43 | @< Import the os module @> 44 | @< Get the OS description @> 45 | @< Construct the message with Substitution @> 46 | @< Print the message @> 47 | @} 48 | 49 | Retrieving the OS description 50 | ------------------------------- 51 | 52 | First we must import the os module so we can learn about the OS: 53 | 54 | @d Import the os module 55 | @{ 56 | import os 57 | @} 58 | 59 | That having been done, we can retrieve Python's name for the OS type: 60 | 61 | @d Get the OS description 62 | @{ 63 | os_name = os.name 64 | @} 65 | 66 | Building the message 67 | --------------------- 68 | 69 | Now, we're ready for the meat of the application: concatenating two strings: 70 | 71 | @d Construct the message with Concatenation 72 | @{ 73 | msg = "Hello, " + os_name + "!" 74 | @} 75 | 76 | But wait! Is there a better way? Using string substitution might be 77 | better: 78 | 79 | @d Construct the message with Substitution 80 | @{ 81 | msg = f"Hello, {os_name}!" 82 | @} 83 | 84 | We'll use the first of these methods in ``rst_test_1.py``, and the 85 | other in ``rst_test_2.py``. 86 | 87 | Printing the message 88 | ---------------------- 89 | 90 | Finally, we print the message out for the user to see. Hopefully, a 91 | cheery greeting will make them happy to know what operating system 92 | they have: 93 | 94 | @d Print the message 95 | @{ 96 | print(msg) 97 | @} 98 | -------------------------------------------------------------------------------- /examples/hw.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Hello World 8 | 361 | 382 | 383 | 384 |
385 |

Hello World

386 | 387 |

This file has a small example.

388 |

The Body Of The Script (1) =

389 |
390 | print("Hello, World!")
391 | 
392 | 393 |

The Body Of The Script (1)

394 |

The Python module includes a small script.

395 |

hw.py (2) =

396 |
397 | →The Body Of The Script (1)
398 | 
399 | 400 |

hw.py (2)

401 |
402 | 403 | 404 | -------------------------------------------------------------------------------- /examples/hw.py: -------------------------------------------------------------------------------- 1 | 2 | 3 | print("Hello, World!") 4 | 5 | -------------------------------------------------------------------------------- /examples/hw.rst: -------------------------------------------------------------------------------- 1 | ########### 2 | Hello World 3 | ########### 4 | 5 | This file has a *small* example. 6 | 7 | 8 | .. _`The Body Of The Script (1)`: 9 | .. rubric:: The Body Of The Script (1) = 10 | .. parsed-literal:: 11 | :class: code 12 | 13 | 14 | print("Hello, World!") 15 | 16 | .. 17 | 18 | .. class:: small 19 | 20 | ∎ *The Body Of The Script (1)* 21 | 22 | 23 | 24 | The Python module includes a small script. 25 | 26 | 27 | .. _`hw.py (2)`: 28 | .. rubric:: hw.py (2) = 29 | .. parsed-literal:: 30 | :class: code 31 | 32 | 33 | →\ `The Body Of The Script (1)`_ 34 | 35 | .. 36 | 37 | .. class:: small 38 | 39 | ∎ *hw.py (2)* 40 | 41 | 42 | -------------------------------------------------------------------------------- /examples/hw.w: -------------------------------------------------------------------------------- 1 | ########### 2 | Hello World 3 | ########### 4 | 5 | This file has a *small* example. 6 | 7 | @d The Body Of The Script @{ 8 | print("Hello, World!") 9 | @} 10 | 11 | The Python module includes a small script. 12 | 13 | @o hw.py @{ 14 | @ 15 | @} 16 | -------------------------------------------------------------------------------- /examples/hw_latex_1.py: -------------------------------------------------------------------------------- 1 | 2 | 3 | import os 4 | 5 | 6 | os_name = os.name 7 | 8 | 9 | msg = "Hello, " + os_name + "!" 10 | 11 | 12 | print(msg) 13 | 14 | -------------------------------------------------------------------------------- /examples/hw_latex_2.py: -------------------------------------------------------------------------------- 1 | 2 | 3 | import os 4 | 5 | 6 | os_name = os.name 7 | 8 | 9 | msg = f"Hello, {os_name}!" 10 | 11 | 12 | print(msg) 13 | 14 | -------------------------------------------------------------------------------- /examples/hw_rst_1.py: -------------------------------------------------------------------------------- 1 | 2 | 3 | import os 4 | 5 | 6 | os_name = os.name 7 | 8 | 9 | msg = "Hello, " + os_name + "!" 10 | 11 | 12 | print(msg) 13 | 14 | -------------------------------------------------------------------------------- /examples/hw_rst_2.py: -------------------------------------------------------------------------------- 1 | 2 | 3 | import os 4 | 5 | 6 | os_name = os.name 7 | 8 | 9 | msg = f"Hello, {os_name}!" 10 | 11 | 12 | print(msg) 13 | 14 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | py-web-tool 8 | 9 | 10 |

Sorry, you should have been redirected src/_build/html/pyweb.html.

11 | 12 | 13 | -------------------------------------------------------------------------------- /jedit/pyweb.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | __ 21 | .. _ 22 | 23 | 24 | ={3,} 25 | -{3,} 26 | ~{3,} 27 | #{3,} 28 | "{3,} 29 | \^{3,} 30 | \+{3,} 31 | \*{3,} 32 | 33 | 34 | \.\.\s\|[^|]+\| 39 | 40 | 41 | \|[^|]+\| 46 | 47 | 48 | \.\.\s[A-z][A-z0-9-_]+:: 53 | 54 | 55 | \*\*[^*]+\*\* 60 | 61 | 62 | \*[^\s*][^*]*\* 67 | 68 | 69 | .. 70 | 71 | 72 | `[A-z0-9]+[^`]+`_{1,2} 76 | 77 | 78 | \[[0-9]+\]_ 82 | 83 | 84 | \[#[A-z0-9_]*\]_ 88 | 89 | 90 | [*]_ 91 | 92 | 93 | \[[A-z][A-z0-9_-]*\]_ 97 | 98 | 99 | 103 | 104 | `` 105 | `` 106 | 107 | 108 | 109 | 116 | @d 117 | @o 118 | 119 | 120 | @{ 121 | @} 122 | 123 | 124 | 125 | ` 126 | ` 127 | 128 | 129 | `{3,} 130 | 131 | 132 | :[A-z][A-z0-9 =\s\t_]*: 136 | 137 | 138 | \+-[+-]+ 142 | \+=[+=]+ 146 | 147 | 148 | 149 | 150 | 151 | 152 | @< 153 | @> 154 | 155 | 156 | 157 | 158 | -------------------------------------------------------------------------------- /page-layout.css: -------------------------------------------------------------------------------- 1 | /* Page layout tweaks */ 2 | div.document { width: 7in; } 3 | .small { font-size: smaller; } 4 | .code 5 | { 6 | color: #101080; 7 | display: block; 8 | border-color: black; 9 | border-width: thin; 10 | border-style: solid; 11 | background-color: #E0FFFF; 12 | /*#99FFFF*/ 13 | padding: 0 0 0 1%; 14 | margin: 0 6% 0 6%; 15 | text-align: left; 16 | font-size: smaller; 17 | } 18 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = ["setuptools >= 61.2.0", "wheel >= 0.37.1"] 3 | build-backend = "setuptools.build_meta" 4 | 5 | [project] 6 | # https://peps.python.org/pep-0621/#readme 7 | version = "3.2" 8 | requires-python = ">=3.10" 9 | name = "py-web-lp" 10 | description="py-web-lp: Yet Another Literate Programming Tool" 11 | readme = "README.rst" 12 | authors = [{"name" = "S.Lott", "email" = "slott56@gmail.com"}] 13 | license = {text = "BSD"} 14 | classifiers = [ 15 | "Development Status :: 5 - Production/Stable", 16 | "Environment :: Console", 17 | "Intended Audience :: Developers", 18 | "License :: OSI Approved :: BSD License", 19 | "Operating System :: OS Independent", 20 | "Programming Language :: Python :: 3 :: Only", 21 | "Programming Language :: Python :: 3.10", 22 | "Programming Language :: Python :: 3.11", 23 | "Programming Language :: Python", 24 | "Topic :: Documentation", 25 | "Topic :: Software Development :: Documentation", 26 | "Topic :: System :: Systems Administration", 27 | "Topic :: Text Processing :: Markup", 28 | "Typing :: Typed", 29 | ] 30 | dependencies = [ 31 | # Direct 32 | "tomli; python_version < '3.11'", 33 | "jinja2==3.1.4", 34 | # Indirect components of build system 35 | "setuptools", 36 | "wheel", 37 | ] 38 | 39 | [project.urls] 40 | repository = "https://github.com/slott56/py-web-tool" 41 | documentation = "https://slott56.github.io/py-web-tool/src/pyweb.html" 42 | 43 | [project.optional-dependencies] 44 | dev = [ 45 | "sphinx==7.0.1", 46 | "sphinxcontrib-plantuml==1.0.0", 47 | "docutils==0.20.1", 48 | "build", 49 | "twine", 50 | ] 51 | test = [ 52 | "tox == 4.6.4", 53 | "pytest == 7.4.0", 54 | "mypy == 1.4.1" 55 | ] 56 | 57 | [project.scripts] 58 | pyweb = "pyweb:config" 59 | -------------------------------------------------------------------------------- /pyweb.css: -------------------------------------------------------------------------------- 1 | .document { width: 720px; } 2 | .chapter { margin-left: 12px; } 3 | .code { font-family: courier,monospace; } 4 | 5 | .quote 6 | { 7 | text-align: justify; 8 | margin-left: 8%; 9 | margin-right: 8%; 10 | /*display: block;*/ 11 | /*width: 4.5in;*/ 12 | font-size: smaller; 13 | } 14 | 15 | .title 16 | { 17 | font-size: x-large; 18 | text-align: center; 19 | font-weight: bold; 20 | } 21 | 22 | .subtitle { text-align: center; } 23 | .macro { margin: 0 0 0 0; } 24 | 25 | code 26 | { 27 | color: #101080; 28 | /*black*/ 29 | /*width: 6in;*/ 30 | display: block; 31 | border-color: black; 32 | border-width: thin; 33 | border-style: solid; 34 | background-color: #E0FFFF; 35 | /*#99FFFF*/ 36 | padding: 0 0 0 1%; 37 | margin: 0 6% 0 6%; 38 | text-align: left; 39 | font-size: smaller; 40 | } 41 | 42 | CODE SPAN.KEYWORD 43 | { 44 | color: #000000; 45 | font-weight: bold; 46 | } 47 | 48 | CODE SPAN.NAME { color: #000000; } 49 | CODE SPAN.STRING { color: #004000; } 50 | CODE SPAN.NUMBER { color: #002020; } 51 | CODE SPAN.BRACKET { color: #800000; } 52 | CODE SPAN.PUNCT { color: #802000; } 53 | CODE SPAN.OP { color: #400000; } 54 | 55 | CODE SPAN.COMMENT 56 | { 57 | color: #000000; 58 | background-color: #FFF0FF; 59 | font-size: 80%; 60 | font-family sans-serif: ; 61 | } 62 | 63 | CODE PRE { margin: 0 0 0 0; } 64 | P,PRE,BLOCKQUOTE PRE { margin: 0 0 6pt 6%; } 65 | UL,OL,DL { margin-left: 6% margin-right: 6%; } 66 | 67 | /* want only level 1 UL's, OL's etc. to have a generous left margin */ 68 | OL OL, OL UL, UL OL, UL UL { margin-left: 4% margin-right: 0; } 69 | LI { margin-left: 6%; } 70 | LI LI { margin-left: 0; } -------------------------------------------------------------------------------- /pyweb.toml: -------------------------------------------------------------------------------- 1 | 2 | [pyweb] 3 | # PyWeb options go here. 4 | 5 | [logging] 6 | version = 1 7 | disable_existing_loggers = false 8 | 9 | [logging.root] 10 | handlers = [ "console",] 11 | level = "INFO" 12 | 13 | [logging.handlers.console] 14 | class = "logging.StreamHandler" 15 | stream = "ext://sys.stderr" 16 | formatter = "basic" 17 | 18 | [logging.formatters.basic] 19 | format = "{levelname}:{name}:{message}" 20 | style = "{" 21 | 22 | [logging.loggers.Weaver] 23 | level = "INFO" 24 | 25 | [logging.loggers.WebReader] 26 | level = "INFO" 27 | 28 | [logging.loggers.Tangler] 29 | level = "INFO" 30 | 31 | [logging.loggers.TanglerMake] 32 | level = "INFO" 33 | 34 | [logging.loggers.indent.TanglerMake] 35 | level = "INFO" 36 | 37 | [logging.loggers.ReferenceCommand] 38 | # Unit test requires this... 39 | level = "INFO" 40 | 41 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | # 2 | # This file is autogenerated by pip-compile with Python 3.10 3 | # by the following command: 4 | # 5 | # pip-compile --extra=test 6 | # 7 | cachetools==5.3.1 8 | # via tox 9 | chardet==5.1.0 10 | # via tox 11 | colorama==0.4.6 12 | # via tox 13 | distlib==0.3.6 14 | # via virtualenv 15 | exceptiongroup==1.1.2 16 | # via pytest 17 | filelock==3.12.2 18 | # via 19 | # tox 20 | # virtualenv 21 | iniconfig==2.0.0 22 | # via pytest 23 | jinja2==3.1.4 24 | # via pip-tools (pyproject.toml) 25 | markupsafe==2.1.3 26 | # via jinja2 27 | mypy==1.4.1 28 | # via pip-tools (pyproject.toml) 29 | mypy-extensions==1.0.0 30 | # via mypy 31 | packaging==23.1 32 | # via 33 | # pyproject-api 34 | # pytest 35 | # tox 36 | platformdirs==3.8.1 37 | # via 38 | # tox 39 | # virtualenv 40 | pluggy==1.2.0 41 | # via 42 | # pytest 43 | # tox 44 | pyproject-api==1.5.3 45 | # via tox 46 | pytest==7.4.0 47 | # via pip-tools (pyproject.toml) 48 | tomli==2.0.1 ; python_version < "3.11" 49 | # via 50 | # mypy 51 | # pip-tools (pyproject.toml) 52 | # pyproject-api 53 | # pytest 54 | # tox 55 | tox==4.6.4 56 | # via pip-tools (pyproject.toml) 57 | typing-extensions==4.7.1 58 | # via mypy 59 | virtualenv==20.23.1 60 | # via tox 61 | wheel==0.40.0 62 | # via pip-tools (pyproject.toml) 63 | 64 | # The following packages are considered to be unsafe in a requirements file: 65 | # setuptools 66 | -------------------------------------------------------------------------------- /spike/example.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Fast Exponentiation 8 | 361 | 362 | 363 |
364 |

Fast Exponentiation

365 | 366 |

A classic divide-and-conquer algorithm.

367 |

fast exp (1) =

368 |
369 | def fast_exp(n: int, p: int) -> int:
370 |     match p:
371 |         case 0:
372 |             return 1
373 |         case _ if p % 2 == 0:
374 |             t = fast_exp(n, p // 2)
375 |             return t * t
376 |         case _ if p % 1 == 0:
377 |             return n * fast_exp(n, p - 1)
378 | 
379 | 380 |

fast exp (1)

381 |

With a test case.

382 |

test case (2) =

383 |
384 | >>> fast_exp(2, 30)
385 | 1073741824
386 | 
387 | 388 |

test case (2)

389 |

example.py (3) =

390 |
391 | →fast exp (1)
392 | 
393 | __test__ = {
394 |     "test 1": '''
395 | →test case (2)
396 | 
397 |     '''
398 | }
399 | 
400 | 401 |

example.py (3)

402 |

Use python -m doctest to test.

403 |
404 | 405 | 406 | -------------------------------------------------------------------------------- /spike/example.rst: -------------------------------------------------------------------------------- 1 | Fast Exponentiation 2 | =================== 3 | 4 | A classic divide-and-conquer algorithm. 5 | 6 | 7 | .. _`fast exp (1)`: 8 | .. rubric:: fast exp (1) = 9 | .. parsed-literal:: 10 | :class: code 11 | 12 | 13 | def fast_exp(n: int, p: int) -> int: 14 | match p: 15 | case 0: 16 | return 1 17 | case _ if p % 2 == 0: 18 | t = fast_exp(n, p // 2) 19 | return t * t 20 | case _ if p % 1 == 0: 21 | return n * fast_exp(n, p - 1) 22 | 23 | .. 24 | 25 | .. class:: small 26 | 27 | ∎ *fast exp (1)* 28 | 29 | 30 | 31 | With a test case. 32 | 33 | 34 | .. _`test case (2)`: 35 | .. rubric:: test case (2) = 36 | .. parsed-literal:: 37 | :class: code 38 | 39 | 40 | >>> fast_exp(2, 30) 41 | 1073741824 42 | 43 | .. 44 | 45 | .. class:: small 46 | 47 | ∎ *test case (2)* 48 | 49 | 50 | 51 | 52 | .. _`example.py (3)`: 53 | .. rubric:: example.py (3) = 54 | .. parsed-literal:: 55 | :class: code 56 | 57 | 58 | →\ `fast exp (1)`_ 59 | 60 | __test__ = { 61 | "test 1": ''' 62 | →\ `test case (2)`_ 63 | 64 | ''' 65 | } 66 | 67 | .. 68 | 69 | .. class:: small 70 | 71 | ∎ *example.py (3)* 72 | 73 | 74 | 75 | Use ``python -m doctest`` to test. 76 | 77 | -------------------------------------------------------------------------------- /spike/test.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slott56/py-web-tool/8bbc603068ba3f589f92e9366bdd92a17591720a/spike/test.png -------------------------------------------------------------------------------- /spike/test.uml: -------------------------------------------------------------------------------- 1 | @startuml 2 | class Chunk { 3 | name: str 4 | seq: int 5 | commands: list[Command] 6 | options: list[str] 7 | def_names: list[str] 8 | } 9 | 10 | class OutputChunk 11 | Chunk <|-- OutputChunk 12 | 13 | class NamedChunk 14 | Chunk <|-- NamedChunk 15 | @enduml 16 | -------------------------------------------------------------------------------- /src/Makefile: -------------------------------------------------------------------------------- 1 | # Minimal makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line, and also 5 | # from the environment for the first two. 6 | SPHINXOPTS ?= 7 | SPHINXBUILD ?= sphinx-build 8 | SOURCEDIR = . 9 | BUILDDIR = _build 10 | 11 | # Put it first so that "make" without argument is like "make help". 12 | help: 13 | @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 14 | 15 | .PHONY: help Makefile 16 | 17 | # Catch-all target: route all unknown targets to Sphinx using the new 18 | # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). 19 | %: Makefile 20 | @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 21 | -------------------------------------------------------------------------------- /src/_build/html/.buildinfo: -------------------------------------------------------------------------------- 1 | # Sphinx build info version 1 2 | # This file hashes the configuration used when building these files. When it is not found, a full rebuild will be done. 3 | config: c4693860855b08c28e8a2e0b0d4e7e37 4 | tags: 645f666f9bcd5a90fca523b33c5a78b7 5 | -------------------------------------------------------------------------------- /src/_build/html/_images/plantuml-0bff076f813d017a60bee35cd131d50c7c41d245.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slott56/py-web-tool/8bbc603068ba3f589f92e9366bdd92a17591720a/src/_build/html/_images/plantuml-0bff076f813d017a60bee35cd131d50c7c41d245.png -------------------------------------------------------------------------------- /src/_build/html/_images/plantuml-4c8ed7c9fa80955420fb7e1d1a646a6dca37f7fd.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slott56/py-web-tool/8bbc603068ba3f589f92e9366bdd92a17591720a/src/_build/html/_images/plantuml-4c8ed7c9fa80955420fb7e1d1a646a6dca37f7fd.png -------------------------------------------------------------------------------- /src/_build/html/_images/plantuml-5c4f75552d0f17db0e1a31509dcbf1aba1fcdae0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slott56/py-web-tool/8bbc603068ba3f589f92e9366bdd92a17591720a/src/_build/html/_images/plantuml-5c4f75552d0f17db0e1a31509dcbf1aba1fcdae0.png -------------------------------------------------------------------------------- /src/_build/html/_images/plantuml-5e56d809cce3e55d45373ec11d41a2b777f82db2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slott56/py-web-tool/8bbc603068ba3f589f92e9366bdd92a17591720a/src/_build/html/_images/plantuml-5e56d809cce3e55d45373ec11d41a2b777f82db2.png -------------------------------------------------------------------------------- /src/_build/html/_images/plantuml-8c7ddae0c5921097f120117e800425881eb8020d.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slott56/py-web-tool/8bbc603068ba3f589f92e9366bdd92a17591720a/src/_build/html/_images/plantuml-8c7ddae0c5921097f120117e800425881eb8020d.png -------------------------------------------------------------------------------- /src/_build/html/_images/plantuml-95ad8fb8a1f49dffd970672ef62b015c72f8d9eb.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slott56/py-web-tool/8bbc603068ba3f589f92e9366bdd92a17591720a/src/_build/html/_images/plantuml-95ad8fb8a1f49dffd970672ef62b015c72f8d9eb.png -------------------------------------------------------------------------------- /src/_build/html/_images/plantuml-9709a6999a63105f75b7067a5431c035dec921ba.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slott56/py-web-tool/8bbc603068ba3f589f92e9366bdd92a17591720a/src/_build/html/_images/plantuml-9709a6999a63105f75b7067a5431c035dec921ba.png -------------------------------------------------------------------------------- /src/_build/html/_images/plantuml-9c211de7ab70c13e3003fe0a3f4287c4870ae68b.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slott56/py-web-tool/8bbc603068ba3f589f92e9366bdd92a17591720a/src/_build/html/_images/plantuml-9c211de7ab70c13e3003fe0a3f4287c4870ae68b.png -------------------------------------------------------------------------------- /src/_build/html/_images/plantuml-a057f978b90f99a509529f42568e557951021d28.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slott56/py-web-tool/8bbc603068ba3f589f92e9366bdd92a17591720a/src/_build/html/_images/plantuml-a057f978b90f99a509529f42568e557951021d28.png -------------------------------------------------------------------------------- /src/_build/html/_images/plantuml-ad2b1999b26d857e32915ff7a3e02e112a1944a8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slott56/py-web-tool/8bbc603068ba3f589f92e9366bdd92a17591720a/src/_build/html/_images/plantuml-ad2b1999b26d857e32915ff7a3e02e112a1944a8.png -------------------------------------------------------------------------------- /src/_build/html/_images/plantuml-af6c5e3c3b745092f6451b2df5c1ecb6ef57c635.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slott56/py-web-tool/8bbc603068ba3f589f92e9366bdd92a17591720a/src/_build/html/_images/plantuml-af6c5e3c3b745092f6451b2df5c1ecb6ef57c635.png -------------------------------------------------------------------------------- /src/_build/html/_images/plantuml-bbfb0883085a6dd7ae0b50ad41ca1da1f40771c0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slott56/py-web-tool/8bbc603068ba3f589f92e9366bdd92a17591720a/src/_build/html/_images/plantuml-bbfb0883085a6dd7ae0b50ad41ca1da1f40771c0.png -------------------------------------------------------------------------------- /src/_build/html/_images/plantuml-bfb4d2a68a2eaa4252def69a662d98e73fdece3e.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slott56/py-web-tool/8bbc603068ba3f589f92e9366bdd92a17591720a/src/_build/html/_images/plantuml-bfb4d2a68a2eaa4252def69a662d98e73fdece3e.png -------------------------------------------------------------------------------- /src/_build/html/_images/plantuml-de8f7453a793112cdb80266d3c7b23a65cbbf218.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slott56/py-web-tool/8bbc603068ba3f589f92e9366bdd92a17591720a/src/_build/html/_images/plantuml-de8f7453a793112cdb80266d3c7b23a65cbbf218.png -------------------------------------------------------------------------------- /src/_build/html/_images/plantuml-fae6df9bf8cb355e39c19c6987c8cc7be8d16732.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slott56/py-web-tool/8bbc603068ba3f589f92e9366bdd92a17591720a/src/_build/html/_images/plantuml-fae6df9bf8cb355e39c19c6987c8cc7be8d16732.png -------------------------------------------------------------------------------- /src/_build/html/_static/_sphinx_javascript_frameworks_compat.js: -------------------------------------------------------------------------------- 1 | /* 2 | * _sphinx_javascript_frameworks_compat.js 3 | * ~~~~~~~~~~ 4 | * 5 | * Compatability shim for jQuery and underscores.js. 6 | * 7 | * WILL BE REMOVED IN Sphinx 6.0 8 | * xref RemovedInSphinx60Warning 9 | * 10 | */ 11 | 12 | /** 13 | * select a different prefix for underscore 14 | */ 15 | $u = _.noConflict(); 16 | 17 | 18 | /** 19 | * small helper function to urldecode strings 20 | * 21 | * See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/decodeURIComponent#Decoding_query_parameters_from_a_URL 22 | */ 23 | jQuery.urldecode = function(x) { 24 | if (!x) { 25 | return x 26 | } 27 | return decodeURIComponent(x.replace(/\+/g, ' ')); 28 | }; 29 | 30 | /** 31 | * small helper function to urlencode strings 32 | */ 33 | jQuery.urlencode = encodeURIComponent; 34 | 35 | /** 36 | * This function returns the parsed url parameters of the 37 | * current request. Multiple values per key are supported, 38 | * it will always return arrays of strings for the value parts. 39 | */ 40 | jQuery.getQueryParameters = function(s) { 41 | if (typeof s === 'undefined') 42 | s = document.location.search; 43 | var parts = s.substr(s.indexOf('?') + 1).split('&'); 44 | var result = {}; 45 | for (var i = 0; i < parts.length; i++) { 46 | var tmp = parts[i].split('=', 2); 47 | var key = jQuery.urldecode(tmp[0]); 48 | var value = jQuery.urldecode(tmp[1]); 49 | if (key in result) 50 | result[key].push(value); 51 | else 52 | result[key] = [value]; 53 | } 54 | return result; 55 | }; 56 | 57 | /** 58 | * highlight a given string on a jquery object by wrapping it in 59 | * span elements with the given class name. 60 | */ 61 | jQuery.fn.highlightText = function(text, className) { 62 | function highlight(node, addItems) { 63 | if (node.nodeType === 3) { 64 | var val = node.nodeValue; 65 | var pos = val.toLowerCase().indexOf(text); 66 | if (pos >= 0 && 67 | !jQuery(node.parentNode).hasClass(className) && 68 | !jQuery(node.parentNode).hasClass("nohighlight")) { 69 | var span; 70 | var isInSVG = jQuery(node).closest("body, svg, foreignObject").is("svg"); 71 | if (isInSVG) { 72 | span = document.createElementNS("http://www.w3.org/2000/svg", "tspan"); 73 | } else { 74 | span = document.createElement("span"); 75 | span.className = className; 76 | } 77 | span.appendChild(document.createTextNode(val.substr(pos, text.length))); 78 | node.parentNode.insertBefore(span, node.parentNode.insertBefore( 79 | document.createTextNode(val.substr(pos + text.length)), 80 | node.nextSibling)); 81 | node.nodeValue = val.substr(0, pos); 82 | if (isInSVG) { 83 | var rect = document.createElementNS("http://www.w3.org/2000/svg", "rect"); 84 | var bbox = node.parentElement.getBBox(); 85 | rect.x.baseVal.value = bbox.x; 86 | rect.y.baseVal.value = bbox.y; 87 | rect.width.baseVal.value = bbox.width; 88 | rect.height.baseVal.value = bbox.height; 89 | rect.setAttribute('class', className); 90 | addItems.push({ 91 | "parent": node.parentNode, 92 | "target": rect}); 93 | } 94 | } 95 | } 96 | else if (!jQuery(node).is("button, select, textarea")) { 97 | jQuery.each(node.childNodes, function() { 98 | highlight(this, addItems); 99 | }); 100 | } 101 | } 102 | var addItems = []; 103 | var result = this.each(function() { 104 | highlight(this, addItems); 105 | }); 106 | for (var i = 0; i < addItems.length; ++i) { 107 | jQuery(addItems[i].parent).before(addItems[i].target); 108 | } 109 | return result; 110 | }; 111 | 112 | /* 113 | * backward compatibility for jQuery.browser 114 | * This will be supported until firefox bug is fixed. 115 | */ 116 | if (!jQuery.browser) { 117 | jQuery.uaMatch = function(ua) { 118 | ua = ua.toLowerCase(); 119 | 120 | var match = /(chrome)[ \/]([\w.]+)/.exec(ua) || 121 | /(webkit)[ \/]([\w.]+)/.exec(ua) || 122 | /(opera)(?:.*version|)[ \/]([\w.]+)/.exec(ua) || 123 | /(msie) ([\w.]+)/.exec(ua) || 124 | ua.indexOf("compatible") < 0 && /(mozilla)(?:.*? rv:([\w.]+)|)/.exec(ua) || 125 | []; 126 | 127 | return { 128 | browser: match[ 1 ] || "", 129 | version: match[ 2 ] || "0" 130 | }; 131 | }; 132 | jQuery.browser = {}; 133 | jQuery.browser[jQuery.uaMatch(navigator.userAgent).browser] = true; 134 | } 135 | -------------------------------------------------------------------------------- /src/_build/html/_static/alabaster.css: -------------------------------------------------------------------------------- 1 | @import url("basic.css"); 2 | 3 | /* -- page layout ----------------------------------------------------------- */ 4 | 5 | body { 6 | font-family: Georgia, serif; 7 | font-size: 17px; 8 | background-color: #fff; 9 | color: #000; 10 | margin: 0; 11 | padding: 0; 12 | } 13 | 14 | 15 | div.document { 16 | width: 940px; 17 | margin: 30px auto 0 auto; 18 | } 19 | 20 | div.documentwrapper { 21 | float: left; 22 | width: 100%; 23 | } 24 | 25 | div.bodywrapper { 26 | margin: 0 0 0 220px; 27 | } 28 | 29 | div.sphinxsidebar { 30 | width: 220px; 31 | font-size: 14px; 32 | line-height: 1.5; 33 | } 34 | 35 | hr { 36 | border: 1px solid #B1B4B6; 37 | } 38 | 39 | div.body { 40 | background-color: #fff; 41 | color: #3E4349; 42 | padding: 0 30px 0 30px; 43 | } 44 | 45 | div.body > .section { 46 | text-align: left; 47 | } 48 | 49 | div.footer { 50 | width: 940px; 51 | margin: 20px auto 30px auto; 52 | font-size: 14px; 53 | color: #888; 54 | text-align: right; 55 | } 56 | 57 | div.footer a { 58 | color: #888; 59 | } 60 | 61 | p.caption { 62 | font-family: inherit; 63 | font-size: inherit; 64 | } 65 | 66 | 67 | div.relations { 68 | display: none; 69 | } 70 | 71 | 72 | div.sphinxsidebar a { 73 | color: #444; 74 | text-decoration: none; 75 | border-bottom: 1px dotted #999; 76 | } 77 | 78 | div.sphinxsidebar a:hover { 79 | border-bottom: 1px solid #999; 80 | } 81 | 82 | div.sphinxsidebarwrapper { 83 | padding: 18px 10px; 84 | } 85 | 86 | div.sphinxsidebarwrapper p.logo { 87 | padding: 0; 88 | margin: -10px 0 0 0px; 89 | text-align: center; 90 | } 91 | 92 | div.sphinxsidebarwrapper h1.logo { 93 | margin-top: -10px; 94 | text-align: center; 95 | margin-bottom: 5px; 96 | text-align: left; 97 | } 98 | 99 | div.sphinxsidebarwrapper h1.logo-name { 100 | margin-top: 0px; 101 | } 102 | 103 | div.sphinxsidebarwrapper p.blurb { 104 | margin-top: 0; 105 | font-style: normal; 106 | } 107 | 108 | div.sphinxsidebar h3, 109 | div.sphinxsidebar h4 { 110 | font-family: Georgia, serif; 111 | color: #444; 112 | font-size: 24px; 113 | font-weight: normal; 114 | margin: 0 0 5px 0; 115 | padding: 0; 116 | } 117 | 118 | div.sphinxsidebar h4 { 119 | font-size: 20px; 120 | } 121 | 122 | div.sphinxsidebar h3 a { 123 | color: #444; 124 | } 125 | 126 | div.sphinxsidebar p.logo a, 127 | div.sphinxsidebar h3 a, 128 | div.sphinxsidebar p.logo a:hover, 129 | div.sphinxsidebar h3 a:hover { 130 | border: none; 131 | } 132 | 133 | div.sphinxsidebar p { 134 | color: #555; 135 | margin: 10px 0; 136 | } 137 | 138 | div.sphinxsidebar ul { 139 | margin: 10px 0; 140 | padding: 0; 141 | color: #000; 142 | } 143 | 144 | div.sphinxsidebar ul li.toctree-l1 > a { 145 | font-size: 120%; 146 | } 147 | 148 | div.sphinxsidebar ul li.toctree-l2 > a { 149 | font-size: 110%; 150 | } 151 | 152 | div.sphinxsidebar input { 153 | border: 1px solid #CCC; 154 | font-family: Georgia, serif; 155 | font-size: 1em; 156 | } 157 | 158 | div.sphinxsidebar hr { 159 | border: none; 160 | height: 1px; 161 | color: #AAA; 162 | background: #AAA; 163 | 164 | text-align: left; 165 | margin-left: 0; 166 | width: 50%; 167 | } 168 | 169 | div.sphinxsidebar .badge { 170 | border-bottom: none; 171 | } 172 | 173 | div.sphinxsidebar .badge:hover { 174 | border-bottom: none; 175 | } 176 | 177 | /* To address an issue with donation coming after search */ 178 | div.sphinxsidebar h3.donation { 179 | margin-top: 10px; 180 | } 181 | 182 | /* -- body styles ----------------------------------------------------------- */ 183 | 184 | a { 185 | color: #004B6B; 186 | text-decoration: underline; 187 | } 188 | 189 | a:hover { 190 | color: #6D4100; 191 | text-decoration: underline; 192 | } 193 | 194 | div.body h1, 195 | div.body h2, 196 | div.body h3, 197 | div.body h4, 198 | div.body h5, 199 | div.body h6 { 200 | font-family: Georgia, serif; 201 | font-weight: normal; 202 | margin: 30px 0px 10px 0px; 203 | padding: 0; 204 | } 205 | 206 | div.body h1 { margin-top: 0; padding-top: 0; font-size: 240%; } 207 | div.body h2 { font-size: 180%; } 208 | div.body h3 { font-size: 150%; } 209 | div.body h4 { font-size: 130%; } 210 | div.body h5 { font-size: 100%; } 211 | div.body h6 { font-size: 100%; } 212 | 213 | a.headerlink { 214 | color: #DDD; 215 | padding: 0 4px; 216 | text-decoration: none; 217 | } 218 | 219 | a.headerlink:hover { 220 | color: #444; 221 | background: #EAEAEA; 222 | } 223 | 224 | div.body p, div.body dd, div.body li { 225 | line-height: 1.4em; 226 | } 227 | 228 | div.admonition { 229 | margin: 20px 0px; 230 | padding: 10px 30px; 231 | background-color: #EEE; 232 | border: 1px solid #CCC; 233 | } 234 | 235 | div.admonition tt.xref, div.admonition code.xref, div.admonition a tt { 236 | background-color: #FBFBFB; 237 | border-bottom: 1px solid #fafafa; 238 | } 239 | 240 | div.admonition p.admonition-title { 241 | font-family: Georgia, serif; 242 | font-weight: normal; 243 | font-size: 24px; 244 | margin: 0 0 10px 0; 245 | padding: 0; 246 | line-height: 1; 247 | } 248 | 249 | div.admonition p.last { 250 | margin-bottom: 0; 251 | } 252 | 253 | div.highlight { 254 | background-color: #fff; 255 | } 256 | 257 | dt:target, .highlight { 258 | background: #FAF3E8; 259 | } 260 | 261 | div.warning { 262 | background-color: #FCC; 263 | border: 1px solid #FAA; 264 | } 265 | 266 | div.danger { 267 | background-color: #FCC; 268 | border: 1px solid #FAA; 269 | -moz-box-shadow: 2px 2px 4px #D52C2C; 270 | -webkit-box-shadow: 2px 2px 4px #D52C2C; 271 | box-shadow: 2px 2px 4px #D52C2C; 272 | } 273 | 274 | div.error { 275 | background-color: #FCC; 276 | border: 1px solid #FAA; 277 | -moz-box-shadow: 2px 2px 4px #D52C2C; 278 | -webkit-box-shadow: 2px 2px 4px #D52C2C; 279 | box-shadow: 2px 2px 4px #D52C2C; 280 | } 281 | 282 | div.caution { 283 | background-color: #FCC; 284 | border: 1px solid #FAA; 285 | } 286 | 287 | div.attention { 288 | background-color: #FCC; 289 | border: 1px solid #FAA; 290 | } 291 | 292 | div.important { 293 | background-color: #EEE; 294 | border: 1px solid #CCC; 295 | } 296 | 297 | div.note { 298 | background-color: #EEE; 299 | border: 1px solid #CCC; 300 | } 301 | 302 | div.tip { 303 | background-color: #EEE; 304 | border: 1px solid #CCC; 305 | } 306 | 307 | div.hint { 308 | background-color: #EEE; 309 | border: 1px solid #CCC; 310 | } 311 | 312 | div.seealso { 313 | background-color: #EEE; 314 | border: 1px solid #CCC; 315 | } 316 | 317 | div.topic { 318 | background-color: #EEE; 319 | } 320 | 321 | p.admonition-title { 322 | display: inline; 323 | } 324 | 325 | p.admonition-title:after { 326 | content: ":"; 327 | } 328 | 329 | pre, tt, code { 330 | font-family: 'Consolas', 'Menlo', 'DejaVu Sans Mono', 'Bitstream Vera Sans Mono', monospace; 331 | font-size: 0.9em; 332 | } 333 | 334 | .hll { 335 | background-color: #FFC; 336 | margin: 0 -12px; 337 | padding: 0 12px; 338 | display: block; 339 | } 340 | 341 | img.screenshot { 342 | } 343 | 344 | tt.descname, tt.descclassname, code.descname, code.descclassname { 345 | font-size: 0.95em; 346 | } 347 | 348 | tt.descname, code.descname { 349 | padding-right: 0.08em; 350 | } 351 | 352 | img.screenshot { 353 | -moz-box-shadow: 2px 2px 4px #EEE; 354 | -webkit-box-shadow: 2px 2px 4px #EEE; 355 | box-shadow: 2px 2px 4px #EEE; 356 | } 357 | 358 | table.docutils { 359 | border: 1px solid #888; 360 | -moz-box-shadow: 2px 2px 4px #EEE; 361 | -webkit-box-shadow: 2px 2px 4px #EEE; 362 | box-shadow: 2px 2px 4px #EEE; 363 | } 364 | 365 | table.docutils td, table.docutils th { 366 | border: 1px solid #888; 367 | padding: 0.25em 0.7em; 368 | } 369 | 370 | table.field-list, table.footnote { 371 | border: none; 372 | -moz-box-shadow: none; 373 | -webkit-box-shadow: none; 374 | box-shadow: none; 375 | } 376 | 377 | table.footnote { 378 | margin: 15px 0; 379 | width: 100%; 380 | border: 1px solid #EEE; 381 | background: #FDFDFD; 382 | font-size: 0.9em; 383 | } 384 | 385 | table.footnote + table.footnote { 386 | margin-top: -15px; 387 | border-top: none; 388 | } 389 | 390 | table.field-list th { 391 | padding: 0 0.8em 0 0; 392 | } 393 | 394 | table.field-list td { 395 | padding: 0; 396 | } 397 | 398 | table.field-list p { 399 | margin-bottom: 0.8em; 400 | } 401 | 402 | /* Cloned from 403 | * https://github.com/sphinx-doc/sphinx/commit/ef60dbfce09286b20b7385333d63a60321784e68 404 | */ 405 | .field-name { 406 | -moz-hyphens: manual; 407 | -ms-hyphens: manual; 408 | -webkit-hyphens: manual; 409 | hyphens: manual; 410 | } 411 | 412 | table.footnote td.label { 413 | width: .1px; 414 | padding: 0.3em 0 0.3em 0.5em; 415 | } 416 | 417 | table.footnote td { 418 | padding: 0.3em 0.5em; 419 | } 420 | 421 | dl { 422 | margin: 0; 423 | padding: 0; 424 | } 425 | 426 | dl dd { 427 | margin-left: 30px; 428 | } 429 | 430 | blockquote { 431 | margin: 0 0 0 30px; 432 | padding: 0; 433 | } 434 | 435 | ul, ol { 436 | /* Matches the 30px from the narrow-screen "li > ul" selector below */ 437 | margin: 10px 0 10px 30px; 438 | padding: 0; 439 | } 440 | 441 | pre { 442 | background: #EEE; 443 | padding: 7px 30px; 444 | margin: 15px 0px; 445 | line-height: 1.3em; 446 | } 447 | 448 | div.viewcode-block:target { 449 | background: #ffd; 450 | } 451 | 452 | dl pre, blockquote pre, li pre { 453 | margin-left: 0; 454 | padding-left: 30px; 455 | } 456 | 457 | tt, code { 458 | background-color: #ecf0f3; 459 | color: #222; 460 | /* padding: 1px 2px; */ 461 | } 462 | 463 | tt.xref, code.xref, a tt { 464 | background-color: #FBFBFB; 465 | border-bottom: 1px solid #fff; 466 | } 467 | 468 | a.reference { 469 | text-decoration: none; 470 | border-bottom: 1px dotted #004B6B; 471 | } 472 | 473 | /* Don't put an underline on images */ 474 | a.image-reference, a.image-reference:hover { 475 | border-bottom: none; 476 | } 477 | 478 | a.reference:hover { 479 | border-bottom: 1px solid #6D4100; 480 | } 481 | 482 | a.footnote-reference { 483 | text-decoration: none; 484 | font-size: 0.7em; 485 | vertical-align: top; 486 | border-bottom: 1px dotted #004B6B; 487 | } 488 | 489 | a.footnote-reference:hover { 490 | border-bottom: 1px solid #6D4100; 491 | } 492 | 493 | a:hover tt, a:hover code { 494 | background: #EEE; 495 | } 496 | 497 | 498 | @media screen and (max-width: 870px) { 499 | 500 | div.sphinxsidebar { 501 | display: none; 502 | } 503 | 504 | div.document { 505 | width: 100%; 506 | 507 | } 508 | 509 | div.documentwrapper { 510 | margin-left: 0; 511 | margin-top: 0; 512 | margin-right: 0; 513 | margin-bottom: 0; 514 | } 515 | 516 | div.bodywrapper { 517 | margin-top: 0; 518 | margin-right: 0; 519 | margin-bottom: 0; 520 | margin-left: 0; 521 | } 522 | 523 | ul { 524 | margin-left: 0; 525 | } 526 | 527 | li > ul { 528 | /* Matches the 30px from the "ul, ol" selector above */ 529 | margin-left: 30px; 530 | } 531 | 532 | .document { 533 | width: auto; 534 | } 535 | 536 | .footer { 537 | width: auto; 538 | } 539 | 540 | .bodywrapper { 541 | margin: 0; 542 | } 543 | 544 | .footer { 545 | width: auto; 546 | } 547 | 548 | .github { 549 | display: none; 550 | } 551 | 552 | 553 | 554 | } 555 | 556 | 557 | 558 | @media screen and (max-width: 875px) { 559 | 560 | body { 561 | margin: 0; 562 | padding: 20px 30px; 563 | } 564 | 565 | div.documentwrapper { 566 | float: none; 567 | background: #fff; 568 | } 569 | 570 | div.sphinxsidebar { 571 | display: block; 572 | float: none; 573 | width: 102.5%; 574 | margin: 50px -30px -20px -30px; 575 | padding: 10px 20px; 576 | background: #333; 577 | color: #FFF; 578 | } 579 | 580 | div.sphinxsidebar h3, div.sphinxsidebar h4, div.sphinxsidebar p, 581 | div.sphinxsidebar h3 a { 582 | color: #fff; 583 | } 584 | 585 | div.sphinxsidebar a { 586 | color: #AAA; 587 | } 588 | 589 | div.sphinxsidebar p.logo { 590 | display: none; 591 | } 592 | 593 | div.document { 594 | width: 100%; 595 | margin: 0; 596 | } 597 | 598 | div.footer { 599 | display: none; 600 | } 601 | 602 | div.bodywrapper { 603 | margin: 0; 604 | } 605 | 606 | div.body { 607 | min-height: 0; 608 | padding: 0; 609 | } 610 | 611 | .rtd_doc_footer { 612 | display: none; 613 | } 614 | 615 | .document { 616 | width: auto; 617 | } 618 | 619 | .footer { 620 | width: auto; 621 | } 622 | 623 | .footer { 624 | width: auto; 625 | } 626 | 627 | .github { 628 | display: none; 629 | } 630 | } 631 | 632 | 633 | /* misc. */ 634 | 635 | .revsys-inline { 636 | display: none!important; 637 | } 638 | 639 | /* Make nested-list/multi-paragraph items look better in Releases changelog 640 | * pages. Without this, docutils' magical list fuckery causes inconsistent 641 | * formatting between different release sub-lists. 642 | */ 643 | div#changelog > div.section > ul > li > p:only-child { 644 | margin-bottom: 0; 645 | } 646 | 647 | /* Hide fugly table cell borders in ..bibliography:: directive output */ 648 | table.docutils.citation, table.docutils.citation td, table.docutils.citation th { 649 | border: none; 650 | /* Below needed in some edge cases; if not applied, bottom shadows appear */ 651 | -moz-box-shadow: none; 652 | -webkit-box-shadow: none; 653 | box-shadow: none; 654 | } 655 | 656 | 657 | /* relbar */ 658 | 659 | .related { 660 | line-height: 30px; 661 | width: 100%; 662 | font-size: 0.9rem; 663 | } 664 | 665 | .related.top { 666 | border-bottom: 1px solid #EEE; 667 | margin-bottom: 20px; 668 | } 669 | 670 | .related.bottom { 671 | border-top: 1px solid #EEE; 672 | } 673 | 674 | .related ul { 675 | padding: 0; 676 | margin: 0; 677 | list-style: none; 678 | } 679 | 680 | .related li { 681 | display: inline; 682 | } 683 | 684 | nav#rellinks { 685 | float: right; 686 | } 687 | 688 | nav#rellinks li+li:before { 689 | content: "|"; 690 | } 691 | 692 | nav#breadcrumbs li+li:before { 693 | content: "\00BB"; 694 | } 695 | 696 | /* Hide certain items when printing */ 697 | @media print { 698 | div.related { 699 | display: none; 700 | } 701 | } -------------------------------------------------------------------------------- /src/_build/html/_static/custom.css: -------------------------------------------------------------------------------- 1 | /* This file intentionally left blank. */ 2 | -------------------------------------------------------------------------------- /src/_build/html/_static/doctools.js: -------------------------------------------------------------------------------- 1 | /* 2 | * doctools.js 3 | * ~~~~~~~~~~~ 4 | * 5 | * Base JavaScript utilities for all Sphinx HTML documentation. 6 | * 7 | * :copyright: Copyright 2007-2022 by the Sphinx team, see AUTHORS. 8 | * :license: BSD, see LICENSE for details. 9 | * 10 | */ 11 | "use strict"; 12 | 13 | const _ready = (callback) => { 14 | if (document.readyState !== "loading") { 15 | callback(); 16 | } else { 17 | document.addEventListener("DOMContentLoaded", callback); 18 | } 19 | }; 20 | 21 | /** 22 | * highlight a given string on a node by wrapping it in 23 | * span elements with the given class name. 24 | */ 25 | const _highlight = (node, addItems, text, className) => { 26 | if (node.nodeType === Node.TEXT_NODE) { 27 | const val = node.nodeValue; 28 | const parent = node.parentNode; 29 | const pos = val.toLowerCase().indexOf(text); 30 | if ( 31 | pos >= 0 && 32 | !parent.classList.contains(className) && 33 | !parent.classList.contains("nohighlight") 34 | ) { 35 | let span; 36 | 37 | const closestNode = parent.closest("body, svg, foreignObject"); 38 | const isInSVG = closestNode && closestNode.matches("svg"); 39 | if (isInSVG) { 40 | span = document.createElementNS("http://www.w3.org/2000/svg", "tspan"); 41 | } else { 42 | span = document.createElement("span"); 43 | span.classList.add(className); 44 | } 45 | 46 | span.appendChild(document.createTextNode(val.substr(pos, text.length))); 47 | parent.insertBefore( 48 | span, 49 | parent.insertBefore( 50 | document.createTextNode(val.substr(pos + text.length)), 51 | node.nextSibling 52 | ) 53 | ); 54 | node.nodeValue = val.substr(0, pos); 55 | 56 | if (isInSVG) { 57 | const rect = document.createElementNS( 58 | "http://www.w3.org/2000/svg", 59 | "rect" 60 | ); 61 | const bbox = parent.getBBox(); 62 | rect.x.baseVal.value = bbox.x; 63 | rect.y.baseVal.value = bbox.y; 64 | rect.width.baseVal.value = bbox.width; 65 | rect.height.baseVal.value = bbox.height; 66 | rect.setAttribute("class", className); 67 | addItems.push({ parent: parent, target: rect }); 68 | } 69 | } 70 | } else if (node.matches && !node.matches("button, select, textarea")) { 71 | node.childNodes.forEach((el) => _highlight(el, addItems, text, className)); 72 | } 73 | }; 74 | const _highlightText = (thisNode, text, className) => { 75 | let addItems = []; 76 | _highlight(thisNode, addItems, text, className); 77 | addItems.forEach((obj) => 78 | obj.parent.insertAdjacentElement("beforebegin", obj.target) 79 | ); 80 | }; 81 | 82 | /** 83 | * Small JavaScript module for the documentation. 84 | */ 85 | const Documentation = { 86 | init: () => { 87 | Documentation.highlightSearchWords(); 88 | Documentation.initDomainIndexTable(); 89 | Documentation.initOnKeyListeners(); 90 | }, 91 | 92 | /** 93 | * i18n support 94 | */ 95 | TRANSLATIONS: {}, 96 | PLURAL_EXPR: (n) => (n === 1 ? 0 : 1), 97 | LOCALE: "unknown", 98 | 99 | // gettext and ngettext don't access this so that the functions 100 | // can safely bound to a different name (_ = Documentation.gettext) 101 | gettext: (string) => { 102 | const translated = Documentation.TRANSLATIONS[string]; 103 | switch (typeof translated) { 104 | case "undefined": 105 | return string; // no translation 106 | case "string": 107 | return translated; // translation exists 108 | default: 109 | return translated[0]; // (singular, plural) translation tuple exists 110 | } 111 | }, 112 | 113 | ngettext: (singular, plural, n) => { 114 | const translated = Documentation.TRANSLATIONS[singular]; 115 | if (typeof translated !== "undefined") 116 | return translated[Documentation.PLURAL_EXPR(n)]; 117 | return n === 1 ? singular : plural; 118 | }, 119 | 120 | addTranslations: (catalog) => { 121 | Object.assign(Documentation.TRANSLATIONS, catalog.messages); 122 | Documentation.PLURAL_EXPR = new Function( 123 | "n", 124 | `return (${catalog.plural_expr})` 125 | ); 126 | Documentation.LOCALE = catalog.locale; 127 | }, 128 | 129 | /** 130 | * highlight the search words provided in the url in the text 131 | */ 132 | highlightSearchWords: () => { 133 | const highlight = 134 | new URLSearchParams(window.location.search).get("highlight") || ""; 135 | const terms = highlight.toLowerCase().split(/\s+/).filter(x => x); 136 | if (terms.length === 0) return; // nothing to do 137 | 138 | // There should never be more than one element matching "div.body" 139 | const divBody = document.querySelectorAll("div.body"); 140 | const body = divBody.length ? divBody[0] : document.querySelector("body"); 141 | window.setTimeout(() => { 142 | terms.forEach((term) => _highlightText(body, term, "highlighted")); 143 | }, 10); 144 | 145 | const searchBox = document.getElementById("searchbox"); 146 | if (searchBox === null) return; 147 | searchBox.appendChild( 148 | document 149 | .createRange() 150 | .createContextualFragment( 151 | '" 155 | ) 156 | ); 157 | }, 158 | 159 | /** 160 | * helper function to hide the search marks again 161 | */ 162 | hideSearchWords: () => { 163 | document 164 | .querySelectorAll("#searchbox .highlight-link") 165 | .forEach((el) => el.remove()); 166 | document 167 | .querySelectorAll("span.highlighted") 168 | .forEach((el) => el.classList.remove("highlighted")); 169 | const url = new URL(window.location); 170 | url.searchParams.delete("highlight"); 171 | window.history.replaceState({}, "", url); 172 | }, 173 | 174 | /** 175 | * helper function to focus on search bar 176 | */ 177 | focusSearchBar: () => { 178 | document.querySelectorAll("input[name=q]")[0]?.focus(); 179 | }, 180 | 181 | /** 182 | * Initialise the domain index toggle buttons 183 | */ 184 | initDomainIndexTable: () => { 185 | const toggler = (el) => { 186 | const idNumber = el.id.substr(7); 187 | const toggledRows = document.querySelectorAll(`tr.cg-${idNumber}`); 188 | if (el.src.substr(-9) === "minus.png") { 189 | el.src = `${el.src.substr(0, el.src.length - 9)}plus.png`; 190 | toggledRows.forEach((el) => (el.style.display = "none")); 191 | } else { 192 | el.src = `${el.src.substr(0, el.src.length - 8)}minus.png`; 193 | toggledRows.forEach((el) => (el.style.display = "")); 194 | } 195 | }; 196 | 197 | const togglerElements = document.querySelectorAll("img.toggler"); 198 | togglerElements.forEach((el) => 199 | el.addEventListener("click", (event) => toggler(event.currentTarget)) 200 | ); 201 | togglerElements.forEach((el) => (el.style.display = "")); 202 | if (DOCUMENTATION_OPTIONS.COLLAPSE_INDEX) togglerElements.forEach(toggler); 203 | }, 204 | 205 | initOnKeyListeners: () => { 206 | // only install a listener if it is really needed 207 | if ( 208 | !DOCUMENTATION_OPTIONS.NAVIGATION_WITH_KEYS && 209 | !DOCUMENTATION_OPTIONS.ENABLE_SEARCH_SHORTCUTS 210 | ) 211 | return; 212 | 213 | const blacklistedElements = new Set([ 214 | "TEXTAREA", 215 | "INPUT", 216 | "SELECT", 217 | "BUTTON", 218 | ]); 219 | document.addEventListener("keydown", (event) => { 220 | if (blacklistedElements.has(document.activeElement.tagName)) return; // bail for input elements 221 | if (event.altKey || event.ctrlKey || event.metaKey) return; // bail with special keys 222 | 223 | if (!event.shiftKey) { 224 | switch (event.key) { 225 | case "ArrowLeft": 226 | if (!DOCUMENTATION_OPTIONS.NAVIGATION_WITH_KEYS) break; 227 | 228 | const prevLink = document.querySelector('link[rel="prev"]'); 229 | if (prevLink && prevLink.href) { 230 | window.location.href = prevLink.href; 231 | event.preventDefault(); 232 | } 233 | break; 234 | case "ArrowRight": 235 | if (!DOCUMENTATION_OPTIONS.NAVIGATION_WITH_KEYS) break; 236 | 237 | const nextLink = document.querySelector('link[rel="next"]'); 238 | if (nextLink && nextLink.href) { 239 | window.location.href = nextLink.href; 240 | event.preventDefault(); 241 | } 242 | break; 243 | case "Escape": 244 | if (!DOCUMENTATION_OPTIONS.ENABLE_SEARCH_SHORTCUTS) break; 245 | Documentation.hideSearchWords(); 246 | event.preventDefault(); 247 | } 248 | } 249 | 250 | // some keyboard layouts may need Shift to get / 251 | switch (event.key) { 252 | case "/": 253 | if (!DOCUMENTATION_OPTIONS.ENABLE_SEARCH_SHORTCUTS) break; 254 | Documentation.focusSearchBar(); 255 | event.preventDefault(); 256 | } 257 | }); 258 | }, 259 | }; 260 | 261 | // quick alias for translations 262 | const _ = Documentation.gettext; 263 | 264 | _ready(Documentation.init); 265 | -------------------------------------------------------------------------------- /src/_build/html/_static/documentation_options.js: -------------------------------------------------------------------------------- 1 | var DOCUMENTATION_OPTIONS = { 2 | URL_ROOT: document.getElementById("documentation_options").getAttribute('data-url_root'), 3 | VERSION: '3.2', 4 | LANGUAGE: 'en', 5 | COLLAPSE_INDEX: false, 6 | BUILDER: 'html', 7 | FILE_SUFFIX: '.html', 8 | LINK_SUFFIX: '.html', 9 | HAS_SOURCE: true, 10 | SOURCELINK_SUFFIX: '.txt', 11 | NAVIGATION_WITH_KEYS: false, 12 | SHOW_SEARCH_SUMMARY: true, 13 | ENABLE_SEARCH_SHORTCUTS: false, 14 | }; -------------------------------------------------------------------------------- /src/_build/html/_static/file.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slott56/py-web-tool/8bbc603068ba3f589f92e9366bdd92a17591720a/src/_build/html/_static/file.png -------------------------------------------------------------------------------- /src/_build/html/_static/language_data.js: -------------------------------------------------------------------------------- 1 | /* 2 | * language_data.js 3 | * ~~~~~~~~~~~~~~~~ 4 | * 5 | * This script contains the language-specific data used by searchtools.js, 6 | * namely the list of stopwords, stemmer, scorer and splitter. 7 | * 8 | * :copyright: Copyright 2007-2022 by the Sphinx team, see AUTHORS. 9 | * :license: BSD, see LICENSE for details. 10 | * 11 | */ 12 | 13 | var stopwords = ["a", "and", "are", "as", "at", "be", "but", "by", "for", "if", "in", "into", "is", "it", "near", "no", "not", "of", "on", "or", "such", "that", "the", "their", "then", "there", "these", "they", "this", "to", "was", "will", "with"]; 14 | 15 | 16 | /* Non-minified version is copied as a separate JS file, is available */ 17 | 18 | /** 19 | * Porter Stemmer 20 | */ 21 | var Stemmer = function() { 22 | 23 | var step2list = { 24 | ational: 'ate', 25 | tional: 'tion', 26 | enci: 'ence', 27 | anci: 'ance', 28 | izer: 'ize', 29 | bli: 'ble', 30 | alli: 'al', 31 | entli: 'ent', 32 | eli: 'e', 33 | ousli: 'ous', 34 | ization: 'ize', 35 | ation: 'ate', 36 | ator: 'ate', 37 | alism: 'al', 38 | iveness: 'ive', 39 | fulness: 'ful', 40 | ousness: 'ous', 41 | aliti: 'al', 42 | iviti: 'ive', 43 | biliti: 'ble', 44 | logi: 'log' 45 | }; 46 | 47 | var step3list = { 48 | icate: 'ic', 49 | ative: '', 50 | alize: 'al', 51 | iciti: 'ic', 52 | ical: 'ic', 53 | ful: '', 54 | ness: '' 55 | }; 56 | 57 | var c = "[^aeiou]"; // consonant 58 | var v = "[aeiouy]"; // vowel 59 | var C = c + "[^aeiouy]*"; // consonant sequence 60 | var V = v + "[aeiou]*"; // vowel sequence 61 | 62 | var mgr0 = "^(" + C + ")?" + V + C; // [C]VC... is m>0 63 | var meq1 = "^(" + C + ")?" + V + C + "(" + V + ")?$"; // [C]VC[V] is m=1 64 | var mgr1 = "^(" + C + ")?" + V + C + V + C; // [C]VCVC... is m>1 65 | var s_v = "^(" + C + ")?" + v; // vowel in stem 66 | 67 | this.stemWord = function (w) { 68 | var stem; 69 | var suffix; 70 | var firstch; 71 | var origword = w; 72 | 73 | if (w.length < 3) 74 | return w; 75 | 76 | var re; 77 | var re2; 78 | var re3; 79 | var re4; 80 | 81 | firstch = w.substr(0,1); 82 | if (firstch == "y") 83 | w = firstch.toUpperCase() + w.substr(1); 84 | 85 | // Step 1a 86 | re = /^(.+?)(ss|i)es$/; 87 | re2 = /^(.+?)([^s])s$/; 88 | 89 | if (re.test(w)) 90 | w = w.replace(re,"$1$2"); 91 | else if (re2.test(w)) 92 | w = w.replace(re2,"$1$2"); 93 | 94 | // Step 1b 95 | re = /^(.+?)eed$/; 96 | re2 = /^(.+?)(ed|ing)$/; 97 | if (re.test(w)) { 98 | var fp = re.exec(w); 99 | re = new RegExp(mgr0); 100 | if (re.test(fp[1])) { 101 | re = /.$/; 102 | w = w.replace(re,""); 103 | } 104 | } 105 | else if (re2.test(w)) { 106 | var fp = re2.exec(w); 107 | stem = fp[1]; 108 | re2 = new RegExp(s_v); 109 | if (re2.test(stem)) { 110 | w = stem; 111 | re2 = /(at|bl|iz)$/; 112 | re3 = new RegExp("([^aeiouylsz])\\1$"); 113 | re4 = new RegExp("^" + C + v + "[^aeiouwxy]$"); 114 | if (re2.test(w)) 115 | w = w + "e"; 116 | else if (re3.test(w)) { 117 | re = /.$/; 118 | w = w.replace(re,""); 119 | } 120 | else if (re4.test(w)) 121 | w = w + "e"; 122 | } 123 | } 124 | 125 | // Step 1c 126 | re = /^(.+?)y$/; 127 | if (re.test(w)) { 128 | var fp = re.exec(w); 129 | stem = fp[1]; 130 | re = new RegExp(s_v); 131 | if (re.test(stem)) 132 | w = stem + "i"; 133 | } 134 | 135 | // Step 2 136 | re = /^(.+?)(ational|tional|enci|anci|izer|bli|alli|entli|eli|ousli|ization|ation|ator|alism|iveness|fulness|ousness|aliti|iviti|biliti|logi)$/; 137 | if (re.test(w)) { 138 | var fp = re.exec(w); 139 | stem = fp[1]; 140 | suffix = fp[2]; 141 | re = new RegExp(mgr0); 142 | if (re.test(stem)) 143 | w = stem + step2list[suffix]; 144 | } 145 | 146 | // Step 3 147 | re = /^(.+?)(icate|ative|alize|iciti|ical|ful|ness)$/; 148 | if (re.test(w)) { 149 | var fp = re.exec(w); 150 | stem = fp[1]; 151 | suffix = fp[2]; 152 | re = new RegExp(mgr0); 153 | if (re.test(stem)) 154 | w = stem + step3list[suffix]; 155 | } 156 | 157 | // Step 4 158 | re = /^(.+?)(al|ance|ence|er|ic|able|ible|ant|ement|ment|ent|ou|ism|ate|iti|ous|ive|ize)$/; 159 | re2 = /^(.+?)(s|t)(ion)$/; 160 | if (re.test(w)) { 161 | var fp = re.exec(w); 162 | stem = fp[1]; 163 | re = new RegExp(mgr1); 164 | if (re.test(stem)) 165 | w = stem; 166 | } 167 | else if (re2.test(w)) { 168 | var fp = re2.exec(w); 169 | stem = fp[1] + fp[2]; 170 | re2 = new RegExp(mgr1); 171 | if (re2.test(stem)) 172 | w = stem; 173 | } 174 | 175 | // Step 5 176 | re = /^(.+?)e$/; 177 | if (re.test(w)) { 178 | var fp = re.exec(w); 179 | stem = fp[1]; 180 | re = new RegExp(mgr1); 181 | re2 = new RegExp(meq1); 182 | re3 = new RegExp("^" + C + v + "[^aeiouwxy]$"); 183 | if (re.test(stem) || (re2.test(stem) && !(re3.test(stem)))) 184 | w = stem; 185 | } 186 | re = /ll$/; 187 | re2 = new RegExp(mgr1); 188 | if (re.test(w) && re2.test(w)) { 189 | re = /.$/; 190 | w = w.replace(re,""); 191 | } 192 | 193 | // and turn initial Y back to y 194 | if (firstch == "y") 195 | w = firstch.toLowerCase() + w.substr(1); 196 | return w; 197 | } 198 | } 199 | 200 | -------------------------------------------------------------------------------- /src/_build/html/_static/minus.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slott56/py-web-tool/8bbc603068ba3f589f92e9366bdd92a17591720a/src/_build/html/_static/minus.png -------------------------------------------------------------------------------- /src/_build/html/_static/plus.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slott56/py-web-tool/8bbc603068ba3f589f92e9366bdd92a17591720a/src/_build/html/_static/plus.png -------------------------------------------------------------------------------- /src/_build/html/_static/pygments.css: -------------------------------------------------------------------------------- 1 | pre { line-height: 125%; } 2 | td.linenos .normal { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; } 3 | span.linenos { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; } 4 | td.linenos .special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; } 5 | span.linenos.special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; } 6 | .highlight .hll { background-color: #ffffcc } 7 | .highlight { background: #f8f8f8; } 8 | .highlight .c { color: #8f5902; font-style: italic } /* Comment */ 9 | .highlight .err { color: #a40000; border: 1px solid #ef2929 } /* Error */ 10 | .highlight .g { color: #000000 } /* Generic */ 11 | .highlight .k { color: #004461; font-weight: bold } /* Keyword */ 12 | .highlight .l { color: #000000 } /* Literal */ 13 | .highlight .n { color: #000000 } /* Name */ 14 | .highlight .o { color: #582800 } /* Operator */ 15 | .highlight .x { color: #000000 } /* Other */ 16 | .highlight .p { color: #000000; font-weight: bold } /* Punctuation */ 17 | .highlight .ch { color: #8f5902; font-style: italic } /* Comment.Hashbang */ 18 | .highlight .cm { color: #8f5902; font-style: italic } /* Comment.Multiline */ 19 | .highlight .cp { color: #8f5902 } /* Comment.Preproc */ 20 | .highlight .cpf { color: #8f5902; font-style: italic } /* Comment.PreprocFile */ 21 | .highlight .c1 { color: #8f5902; font-style: italic } /* Comment.Single */ 22 | .highlight .cs { color: #8f5902; font-style: italic } /* Comment.Special */ 23 | .highlight .gd { color: #a40000 } /* Generic.Deleted */ 24 | .highlight .ge { color: #000000; font-style: italic } /* Generic.Emph */ 25 | .highlight .gr { color: #ef2929 } /* Generic.Error */ 26 | .highlight .gh { color: #000080; font-weight: bold } /* Generic.Heading */ 27 | .highlight .gi { color: #00A000 } /* Generic.Inserted */ 28 | .highlight .go { color: #888888 } /* Generic.Output */ 29 | .highlight .gp { color: #745334 } /* Generic.Prompt */ 30 | .highlight .gs { color: #000000; font-weight: bold } /* Generic.Strong */ 31 | .highlight .gu { color: #800080; font-weight: bold } /* Generic.Subheading */ 32 | .highlight .gt { color: #a40000; font-weight: bold } /* Generic.Traceback */ 33 | .highlight .kc { color: #004461; font-weight: bold } /* Keyword.Constant */ 34 | .highlight .kd { color: #004461; font-weight: bold } /* Keyword.Declaration */ 35 | .highlight .kn { color: #004461; font-weight: bold } /* Keyword.Namespace */ 36 | .highlight .kp { color: #004461; font-weight: bold } /* Keyword.Pseudo */ 37 | .highlight .kr { color: #004461; font-weight: bold } /* Keyword.Reserved */ 38 | .highlight .kt { color: #004461; font-weight: bold } /* Keyword.Type */ 39 | .highlight .ld { color: #000000 } /* Literal.Date */ 40 | .highlight .m { color: #990000 } /* Literal.Number */ 41 | .highlight .s { color: #4e9a06 } /* Literal.String */ 42 | .highlight .na { color: #c4a000 } /* Name.Attribute */ 43 | .highlight .nb { color: #004461 } /* Name.Builtin */ 44 | .highlight .nc { color: #000000 } /* Name.Class */ 45 | .highlight .no { color: #000000 } /* Name.Constant */ 46 | .highlight .nd { color: #888888 } /* Name.Decorator */ 47 | .highlight .ni { color: #ce5c00 } /* Name.Entity */ 48 | .highlight .ne { color: #cc0000; font-weight: bold } /* Name.Exception */ 49 | .highlight .nf { color: #000000 } /* Name.Function */ 50 | .highlight .nl { color: #f57900 } /* Name.Label */ 51 | .highlight .nn { color: #000000 } /* Name.Namespace */ 52 | .highlight .nx { color: #000000 } /* Name.Other */ 53 | .highlight .py { color: #000000 } /* Name.Property */ 54 | .highlight .nt { color: #004461; font-weight: bold } /* Name.Tag */ 55 | .highlight .nv { color: #000000 } /* Name.Variable */ 56 | .highlight .ow { color: #004461; font-weight: bold } /* Operator.Word */ 57 | .highlight .w { color: #f8f8f8; text-decoration: underline } /* Text.Whitespace */ 58 | .highlight .mb { color: #990000 } /* Literal.Number.Bin */ 59 | .highlight .mf { color: #990000 } /* Literal.Number.Float */ 60 | .highlight .mh { color: #990000 } /* Literal.Number.Hex */ 61 | .highlight .mi { color: #990000 } /* Literal.Number.Integer */ 62 | .highlight .mo { color: #990000 } /* Literal.Number.Oct */ 63 | .highlight .sa { color: #4e9a06 } /* Literal.String.Affix */ 64 | .highlight .sb { color: #4e9a06 } /* Literal.String.Backtick */ 65 | .highlight .sc { color: #4e9a06 } /* Literal.String.Char */ 66 | .highlight .dl { color: #4e9a06 } /* Literal.String.Delimiter */ 67 | .highlight .sd { color: #8f5902; font-style: italic } /* Literal.String.Doc */ 68 | .highlight .s2 { color: #4e9a06 } /* Literal.String.Double */ 69 | .highlight .se { color: #4e9a06 } /* Literal.String.Escape */ 70 | .highlight .sh { color: #4e9a06 } /* Literal.String.Heredoc */ 71 | .highlight .si { color: #4e9a06 } /* Literal.String.Interpol */ 72 | .highlight .sx { color: #4e9a06 } /* Literal.String.Other */ 73 | .highlight .sr { color: #4e9a06 } /* Literal.String.Regex */ 74 | .highlight .s1 { color: #4e9a06 } /* Literal.String.Single */ 75 | .highlight .ss { color: #4e9a06 } /* Literal.String.Symbol */ 76 | .highlight .bp { color: #3465a4 } /* Name.Builtin.Pseudo */ 77 | .highlight .fm { color: #000000 } /* Name.Function.Magic */ 78 | .highlight .vc { color: #000000 } /* Name.Variable.Class */ 79 | .highlight .vg { color: #000000 } /* Name.Variable.Global */ 80 | .highlight .vi { color: #000000 } /* Name.Variable.Instance */ 81 | .highlight .vm { color: #000000 } /* Name.Variable.Magic */ 82 | .highlight .il { color: #990000 } /* Literal.Number.Integer.Long */ -------------------------------------------------------------------------------- /src/_build/html/genindex.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Index — PyWebTool 3.2 documentation 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 |
28 |
29 |
30 | 31 | 32 |
33 | 34 | 35 |

Index

36 | 37 |
38 | S 39 | 40 |
41 |

S

42 | 43 | 47 |
48 | 49 | 50 | 51 |
52 | 53 |
54 |
55 | 95 |
96 |
97 | 105 | 106 | 107 | 108 | 109 | 110 | -------------------------------------------------------------------------------- /src/_build/html/objects.inv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slott56/py-web-tool/8bbc603068ba3f589f92e9366bdd92a17591720a/src/_build/html/objects.inv -------------------------------------------------------------------------------- /src/_build/html/search.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Search — PyWebTool 3.2 documentation 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 |
34 |
35 |
36 | 37 | 38 |
39 | 40 |

Search

41 | 42 | 50 | 51 | 52 |

53 | Searching for multiple words only shows matches that contain 54 | all words. 55 |

56 | 57 | 58 |
59 | 60 | 61 | 62 |
63 | 64 | 65 | 66 |
67 | 68 |
69 | 70 | 71 |
72 | 73 |
74 |
75 | 105 |
106 |
107 | 115 | 116 | 117 | 118 | 119 | 120 | -------------------------------------------------------------------------------- /src/c4_diagrams.uml: -------------------------------------------------------------------------------- 1 | @startuml context.png 2 | left to right direction 3 | skinparam actorStyle awesome 4 | 5 | actor "Developer" as Dev 6 | rectangle PyWeb { 7 | usecase "Tangle Source" as UC_Tangle 8 | usecase "Weave Document" as UC_Weave 9 | } 10 | rectangle IDE { 11 | usecase "Create WEB" as UC_Create 12 | usecase "Run Tests" as UC_Test 13 | } 14 | Dev --> UC_Tangle 15 | Dev --> UC_Weave 16 | Dev --> UC_Create 17 | Dev --> UC_Test 18 | 19 | UC_Test --> UC_Tangle 20 | @enduml 21 | 22 | @startuml components.png 23 | component pyweb 24 | component jinja 25 | pyweb ..> jinja 26 | 27 | component weave 28 | weave ..> pyweb 29 | 30 | component tangle 31 | tangle ..> pyweb 32 | @enduml 33 | 34 | @startuml code_model.png 35 | class Web 36 | class Chunk 37 | abstract class Command 38 | 39 | Web *-- "1..*" Chunk 40 | Chunk *-- "1..*" Command 41 | 42 | class CodeChunk 43 | Chunk <|-- CodeChunk 44 | 45 | class NamedChunk 46 | Chunk <|-- NamedChunk 47 | 48 | class OutputChunk 49 | Chunk <|-- OutputChunk 50 | 51 | class NamedCodeChunk 52 | Chunk <|-- NamedCodeChunk 53 | 54 | class TextCommand 55 | Command <|-- TextCommand 56 | 57 | class CodeCommand 58 | Command <|-- CodeCommand 59 | 60 | class ReferenceCommand 61 | Command <|-- ReferenceCommand 62 | 63 | class XRefCommand 64 | Command <|-- XRefCommand 65 | 66 | class FileXRefCommand 67 | XRefCommand <|-- FileXRefCommand 68 | 69 | class MacroXRefCommand 70 | XRefCommand <|-- MacroXRefCommand 71 | 72 | class UseridXRefCommand 73 | XRefCommand <|-- UseridXRefCommand 74 | @enduml 75 | 76 | @startuml code_emitter.png 77 | class Web 78 | 79 | abstract class Emitter { 80 | emit(web) 81 | } 82 | 83 | Emitter ..> Web 84 | 85 | class Weaver 86 | Emitter <|-- Weaver 87 | 88 | class Tangler 89 | Emitter <|-- Tangler 90 | 91 | class TanglerMake 92 | Tangler <|-- TanglerMake 93 | 94 | abstract class ReferenceStyle 95 | Weaver --> ReferenceStyle 96 | 97 | class Simple 98 | ReferenceStyle <|-- Simple 99 | 100 | class Transitive 101 | ReferenceStyle <|-- Transitive 102 | @enduml 103 | 104 | @startuml code_parser.png 105 | class Web 106 | class WebReader { 107 | parse : Web 108 | } 109 | WebReader ..> Web 110 | class Tokenizer 111 | WebReader ..> Tokenizer 112 | 113 | class OptionParser 114 | 115 | class OptionDef 116 | 117 | OptionParser *-- OptionDef 118 | 119 | WebReader ..> OptionParser 120 | @enduml 121 | 122 | @startuml code_application.png 123 | abstract class Action 124 | 125 | class ActionSequence 126 | Action <|-- ActionSequence 127 | ActionSequence *-- "2..m" Action 128 | 129 | class LoadAction 130 | Action <|-- LoadAction 131 | 132 | class WeaveAction 133 | Action <|-- WeaveAction 134 | 135 | class TangleAction 136 | Action <|-- TangleAction 137 | 138 | class Application 139 | 140 | Application *-- Action 141 | @enduml 142 | -------------------------------------------------------------------------------- /src/conf.py: -------------------------------------------------------------------------------- 1 | # Configuration file for the Sphinx documentation builder. 2 | # 3 | # This file only contains a selection of the most common options. For a full 4 | # list see the documentation: 5 | # https://www.sphinx-doc.org/en/master/usage/configuration.html 6 | 7 | # -- Path setup -------------------------------------------------------------- 8 | 9 | # If extensions (or modules to document with autodoc) are in another directory, 10 | # add these directories to sys.path here. If the directory is relative to the 11 | # documentation root, use os.path.abspath to make it absolute, like shown here. 12 | # 13 | # import os 14 | # import sys 15 | # sys.path.insert(0, os.path.abspath('.')) 16 | from pathlib import Path 17 | project_base = Path.cwd().parent 18 | 19 | 20 | # -- Project information ----------------------------------------------------- 21 | 22 | project = 'PyWebTool' 23 | copyright = '2022, S.Lott' 24 | author = 'S.Lott' 25 | 26 | # The full version, including alpha/beta/rc tags 27 | release = '3.2' 28 | 29 | 30 | # -- General configuration --------------------------------------------------- 31 | 32 | # Add any Sphinx extension module names here, as strings. They can be 33 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom 34 | # ones. 35 | extensions = [ 36 | 'sphinxcontrib.plantuml', 37 | ] 38 | 39 | # Add any paths that contain templates here, relative to this directory. 40 | templates_path = ['_templates'] 41 | 42 | # List of patterns, relative to source directory, that match files and 43 | # directories to ignore when looking for source files. 44 | # This pattern also affects html_static_path and html_extra_path. 45 | exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store'] 46 | 47 | 48 | root_doc = "pyweb" 49 | 50 | # -- Options for HTML output ------------------------------------------------- 51 | 52 | # The theme to use for HTML and HTML Help pages. See the documentation for 53 | # a list of builtin themes. 54 | # 55 | html_theme = 'alabaster' 56 | 57 | # Add any paths that contain custom static files (such as style sheets) here, 58 | # relative to this directory. They are copied after the builtin static files, 59 | # so a file named "default.css" will overwrite the builtin "default.css". 60 | html_static_path = ['_static'] 61 | 62 | 63 | # -- Options for PlantUML ----------------------------------------------------- 64 | 65 | plantuml = f'java -jar {project_base}/plantuml/plantuml-1.2022.6.jar' 66 | -------------------------------------------------------------------------------- /src/done.w: -------------------------------------------------------------------------------- 1 | .. py-web-tool/src/done.w 2 | 3 | Change Log 4 | =========== 5 | 6 | 7 | Changes for 3.2 8 | 9 | - Rename to ``py-web-lp`` to limit collisions in PyPI. 10 | 11 | - Replaced ``toml`` with ``tomli`` or built-in ``tomllib``. 12 | 13 | - Fiddle with ``pyproject.toml`` and ``tox.ini`` to eliminate ``setup.py``. 14 | 15 | - Replaced home-brewed ``OptionParser`` with ``argparse.ArgumentParser``. 16 | Now the options for ``@@d`` and ``@@o`` can be *much* more flexible. 17 | 18 | - Added the transitive path of references as a Chunk property. 19 | Removes ``SimpleReference`` and ``TransitiveReference`` classes, 20 | and the associated "reference_style" attribute of a ``Weaver``. 21 | To show transitive references, a revised ``code_end`` template is required. 22 | 23 | - Added a TOML-based configuration file with a ``[logging]`` section. 24 | 25 | - Incorporated Sphinx and PlantUML into the documentation. 26 | Support continues for ```rst2html.py``. It's used for test documentaion. 27 | See https://github.com/slott56/py-web-tool/wiki/PlantUML-support-for-RST-%5BCompleted%5D 28 | 29 | - Replaced weaving process with Jinja templates. 30 | See https://github.com/slott56/py-web-tool/wiki/Jinja-Template-for-Weaving-%5BCompleted%5D 31 | 32 | - Dramatic redesign to Class, Chunk, and Command class hierarchies. 33 | 34 | - Dramatic redesign to Emitters to switch to using Jinja templates. 35 | By stepping away from the ``string.Template``, 36 | we can incorporate list-processing ``{% for %}...{% endfor %}`` construct that 37 | pushes some processing into the template. 38 | 39 | - Removed ``'\N{LOZENGE}'`` (borrowed from Interscript) and use the ``'\N{END OF PROOF}'`` symbol instead. 40 | 41 | - Created a better ``weave.py`` example that shows how to incorporate bootstrap CSS into HTML overrides. 42 | This also requires designing a more easily extended ``Weaver`` class. 43 | 44 | Changes for 3.1 45 | 46 | - Change to Python 3.10 as the supported version. 47 | 48 | - Add type hints, f-strings, ``pathlib``, ``abc.ABC``. 49 | 50 | - Replace some complex ``elif`` blocks with ``match`` statements. 51 | 52 | - Use **pytest** as a test runner. 53 | 54 | - Add a ``Makefile``, ``pyproject.toml``, ``requirements.txt`` and ``requirements-dev.txt``. 55 | 56 | - Implement ``-o dir`` option to write output to a directory of choice, simplifying **tox** setup. 57 | 58 | - Add ``bootstrap`` directory with a snapshot of a previous working release to simplify development. 59 | 60 | - Add Test cases for ``weave.py`` and ``tangle.py`` 61 | 62 | - Replace hand-build mock classes with ``unittest.mock.Mock`` objects 63 | 64 | - Separate the projec into ``src``, ``tests``, ``examples``. Cleanup ``Makefile``, ``pyproject.toml``, etc. 65 | 66 | - Silence the ERROR-level logging during testing. 67 | 68 | - Clean up the examples 69 | 70 | Changes for 3.0 71 | 72 | - Move to GitHub 73 | 74 | Changes for 2.3.2. 75 | 76 | - Fix all ``{:s}`` format strings to be ``{!s}``. 77 | 78 | Changes for 2.3.1. 79 | 80 | - Cleanup some stray comment errors. 81 | 82 | - Revise the documentation structure and organization. 83 | 84 | - Tweak the error messages. 85 | 86 | Changes for 2.3. 87 | 88 | - Changed to Python 3.3 -- Fixed ``except``, ``raise`` and ``%``. 89 | 90 | - Removed ``doWrite()`` and simplified ``doOpen()`` and ``doClose()``. 91 | 92 | - Cleaned up RST output to be much nicer. 93 | 94 | - Change the baseline ``pyweb.w`` file to be RST instead of HTML. 95 | docutils required to produce HTML from the woven output. 96 | 97 | - Removed the unconstrained ``eval()`` function. Provided a slim set of globals. 98 | ``os`` is really just ``os.path``. 99 | Any ``os.getcwd()`` can be changed to ``os.path.realpath('.')``. 100 | ``time`` was removed and replaced with ``datetime``. 101 | Any ``time.asctime()`` must be ``datetime.datetime.now().ctime()``. 102 | 103 | - Resolved a small dispute between ``weaveReferenceTo()`` (wrong) and ``tangle()`` (right). 104 | for NamedChunks. The issue was one of failure to understand the differences 105 | between weaving -- where indentation is localized -- and tangling -- where indentation 106 | must be tracked globally. Root cause was a huge problem in ``codeBlock()`` which didn't 107 | really weave properly at all. 108 | 109 | - Fix the tokenizer and parsing. Stop using a complex tokenizer and use a simpler 110 | iterator over the tokens with ``StopIteration`` exception handling. 111 | 112 | - Replace ``optparse`` with ``argparse``. 113 | 114 | - Get rid of the global ``logger`` variable. 115 | 116 | - Remove the filename as part of ``Web()`` initial creation. 117 | A basename comes from the initial ``.w`` file loaded by the ``WebReader``. 118 | 119 | - Fix the Action class hierarchy so that composite actions are simpler. 120 | 121 | - Change references to return ``Chunk`` objects, not ``(name,sequence)`` pairs. 122 | 123 | - Make the ref list separator in ``Weaver reference summary...`` a proper template 124 | feature, not a hidden punctuation mark in the code. 125 | 126 | - Configure ``Web.reference_style`` properly so that simple or transitive references 127 | can be included as a command-line option. The default is Simple. 128 | Add the ``-r`` option so that ``-rt`` includes transitive references. 129 | 130 | - Reduce the "hard-coded" punctuation. For example, the ``", "`` in 131 | ``@@d Web weave...`` ``weaveChunk()``. This was moved into a template. 132 | 133 | - Add an ``__enter__()`` and ``__exit__()`` to make an ``Emitter`` 134 | into a proper Context Manager that can be used with a ``with`` statement. 135 | 136 | - Add the ``-n`` option to include tangler line numbers if the ``@@o`` includes 137 | the comment characters. 138 | 139 | - Cleanup the ``TanglerMake`` unit tests to remove the ``sleep()`` 140 | used to assure that the timestamps really are different. 141 | 142 | - Cleanup the syntax for adding a comment template to ``@@o``. Use ``-start`` and ``-end`` 143 | before the filename. 144 | 145 | - Cleanup the syntax for noindent named chunks. Use ``-noindent`` before the chunk name. 146 | This creates a distinct ``NamedChunk_Noindent`` instance that handles indentation 147 | differently from other ``Chunk`` subclasses. 148 | 149 | - Cleanup the ``TangleAction`` summary. 150 | 151 | - Clean up the error messages. Raising an exception seems 152 | heavy-handed and confusing. Count errors instead. 153 | 154 | 155 | Changes since version 1.4 156 | 157 | - Removed home-brewed logger. 158 | 159 | - Replaced ``getopt`` with ``optparse``. 160 | 161 | - Replaced LaTeX markup. 162 | 163 | - Corrected significant problems in cross-reference resolution. 164 | 165 | - Replaced all HTML and LaTeX-specific features with a much simpler template 166 | engine which applies a template to a Chunk. The Templates are separate 167 | configuration items. The big issue with templates are conditional processing 168 | and the use of loops to handle multiple references in a transitive closure. 169 | While it's nice to depend on Jinja2, it's also nice to be totally stand-alone. 170 | Sigh. Choices include the no-logic ``string.Template`` in the standard library 171 | or the ``Templite+`` Recipe 576663. 172 | 173 | - Looked at SCons API. Renamed "Operation" to "Action"; renamed "perform" to "__call__". 174 | Consider having "__call__" which does logging, then call "execute". 175 | 176 | - Eliminated the EmitterFactory; replace this with simple injection of 177 | the proper template configuration. 178 | 179 | - Removed the ``@@O`` command; it was essentially a variant template for LaTeX. 180 | 181 | - Disentangled indentation and quoting in the codeBlock. 182 | Indentation rules vary between Tangling and Weaving. 183 | Quoting is unique to a woven codeBlock. Fix ``referenceTo()`` to write 184 | indented without code quoting. 185 | 186 | - Offer a basic RST template. 187 | Note that colorizing may be easier to handle with an RST template. 188 | The weaving markup template degenerates 189 | to ``.. parsed-literal::`` and indent. By doing this, 190 | the RST output from *pyWeb* can be run through DocUtils ``rst2html.py`` 191 | or perhaps *Sphix* to create final HTML. The hard part is the indent. 192 | 193 | - Tweaked (but didn't fix) ReferenceCommand tangle and all setIndent/clrIndent operations. 194 | Only a ReferenceCommand actually cares about indentation. And that indentation 195 | is totally based on the "context" plus the text in the Command immediate in front 196 | of the ReferenceCommand. 197 | -------------------------------------------------------------------------------- /src/intro.w: -------------------------------------------------------------------------------- 1 | .. py-web-tool/src/intro.w 2 | 3 | Introduction 4 | ============ 5 | 6 | Literate programming was pioneered by Knuth as a method for 7 | developing readable, understandable presentations of programs. 8 | These would present a program in a literate fashion for people 9 | to read and understand; this would be in parallel with presentation as source text 10 | for a compiler to process and both would be generated from a common source file. 11 | 12 | One intent is to synchronize the program source with the 13 | documentation about that source. If the program and the documentation 14 | have a common origin, then the traditional gaps between intent 15 | (expressed in the documentation) and action (expressed in the 16 | working program) are significantly reduced. 17 | 18 | **py-web-lp** is a literate programming tool that combines the actions 19 | of *weaving* a document with *tangling* source files. 20 | It is independent of any source language. 21 | While is designed to work with RST document markup, it should be amenable to any other 22 | flavor of markup. 23 | It uses a small set of markup tags to define chunks of code and 24 | documentation. 25 | 26 | Background 27 | ----------- 28 | 29 | The following is an almost verbatim quote from Briggs' *nuweb* documentation, 30 | and provides an apt summary of Literate Programming. 31 | 32 | In 1984, Knuth introduced the idea of *literate programming* and 33 | described a pair of tools to support the practise (Donald E. Knuth, 34 | "Literate Programming", *The Computer Journal* 27 (1984), no. 2, 97-111.) 35 | His approach was to combine Pascal code with T\ :sub:`e`\ X documentation to 36 | produce a new language, ``WEB``, that offered programmers a superior 37 | approach to programming. He wrote several programs in ``WEB``, 38 | including ``weave`` and ``tangle``, the programs used to support 39 | literate programming. 40 | The idea was that a programmer wrote one document, the web file, that 41 | combined documentation written in T\ :sub:`e`\ X (Donald E. Knuth, 42 | T\ :sub:`e`\ X book, Computers and Typesetting, 1986) with code (written in Pascal). 43 | 44 | Running ``tangle`` on the web file would produce a complete 45 | Pascal program, ready for compilation by an ordinary Pascal compiler. 46 | The primary function of ``tangle`` is to allow the programmer to 47 | present elements of the program in any desired order, regardless of 48 | the restrictions imposed by the programming language. Thus, the 49 | programmer is free to present his program in a top-down fashion, 50 | bottom-up fashion, or whatever seems best in terms of promoting 51 | understanding and maintenance. 52 | 53 | Running ``weave`` on the web file would produce a T\ :sub:`e`\ X file, ready 54 | to be processed by T\ :sub:`e`\ X. The resulting document included a variety of 55 | automatically generated indices and cross-references that made it much 56 | easier to navigate the code. Additionally, all of the code sections 57 | were automatically prettyprinted, resulting in a quite impressive 58 | document. 59 | 60 | Knuth also wrote the programs for T\ :sub:`e`\ X and ``METAFONT`` 61 | entirely in ``WEB``, eventually publishing them in book 62 | form. These are probably the 63 | largest programs ever published in a readable form. 64 | 65 | 66 | Other Tools 67 | ------------ 68 | 69 | Numerous tools have been developed based on Knuth's initial 70 | work. A relatively complete survey is available at sites 71 | like `Literate Programming `_, 72 | and the OASIS 73 | `XML Cover Pages: Literate Programming with SGML and XML `_. 74 | 75 | The immediate predecessors to this **py-web-lp** tool are 76 | `FunnelWeb `_, 77 | `noweb `_ and 78 | `nuweb `_. The ideas lifted from these other 79 | tools created the foundation for **py-web-lp**. 80 | 81 | There are several Python-oriented literate programming tools. 82 | These include 83 | `LEO `_, 84 | `interscript `_, 85 | `lpy `_, 86 | `py2html `_, 87 | `PyLit-3 `_ 88 | 89 | The *FunnelWeb* tool is independent of any programming language 90 | and only mildly dependent on T\ :sub:`e`\ X. 91 | It has 19 commands, many of which duplicate features of HTML or 92 | L\ :sub:`a`\ T\ :sub:`e`\ X. 93 | 94 | The *noweb* tool was written by Norman Ramsey. 95 | This tool uses a sophisticated multi-processing framework, via Unix 96 | pipes, to permit flexible manipulation of the source file to tangle 97 | and weave the programming language and documentation markup files. 98 | 99 | The *nuweb* Simple Literate Programming Tool was developed by 100 | Preston Briggs (preston@@tera.com). His work was supported by ARPA, 101 | through ONR grant N00014-91-J-1989. It is written 102 | in C, and very focused on producing L\ :sub:`a`\ T\ :sub:`e`\ X documents. It can 103 | produce HTML, but this is clearly added after the fact. It cannot be 104 | easily extended, and is not object-oriented. 105 | 106 | The *LEO* tool is a structured GUI editor for creating 107 | source. It uses XML and *noweb*\ -style chunk management. It is more 108 | than a simple weave and tangle tool. 109 | 110 | The *interscript* tool is very large and sophisticated, but doesn't gracefully 111 | tolerate HTML markup in the document. It can create a variety of 112 | markup languages from the interscript source, making it suitable for 113 | creating HTML as well as L\ :sub:`a`\ T\ :sub:`e`\ X. 114 | 115 | The *lpy* tool can produce very complex HTML representations of 116 | a Python program. It works by locating documentation markup embedded 117 | in Python comments and docstrings. This is called "inverted literate 118 | programming". 119 | 120 | The *py2html* tool does very sophisticated syntax coloring. 121 | 122 | The *PyLit-3* tool is perhaps the very best approach to Literate 123 | programming, since it leverages an existing lightweight markup language 124 | and it's output formatting. However, it's limited in the presentation order, 125 | making it difficult to present a complex Python module out of the proper 126 | Python required presentation. 127 | 128 | **py-web-lp** 129 | --------------- 130 | 131 | **py-web-lp** works with any 132 | programming language. It can work with any markup language, but is currently 133 | configured to work with RST. This philosophy 134 | comes from *FunnelWeb* 135 | *noweb*, *nuweb* and *interscript*. The primary differences 136 | between **py-web-lp** and other tools are the following. 137 | 138 | - **py-web-lp** is object-oriented, permitting easy extension. 139 | *noweb* extensions 140 | are separate processes that communicate through a sophisticated protocol. 141 | *nuweb* is not easily extended without rewriting and recompiling 142 | the C programs. 143 | 144 | - **py-web-lp** is built in the very portable Python programming 145 | language. This allows it to run anywhere that Python 3.3 runs, with 146 | only the addition of docutils. This makes it a useful 147 | tool for programmers in any language. 148 | 149 | - **py-web-lp** is much simpler than *FunnelWeb*, *LEO* or *Interscript*. It has 150 | a very limited selection of commands, but can still produce 151 | complex programs and HTML documents. 152 | 153 | - **py-web-lp** does not invent a complex markup language like *Interscript*. 154 | Because *Iterscript* has its own markup, it can generate L\ :sub:`a`\ T\ :sub:`e`\ X or HTML or other 155 | output formats from a unique input format. While powerful, it seems simpler to 156 | avoid inventing yet another sophisticated markup language. The language **py-web-lp** 157 | uses is very simple, and the author's use their preferred markup language almost 158 | exclusively. 159 | 160 | - **py-web-lp** supports the forward literate programming philosophy, 161 | where a source document creates programming language and markup language. 162 | The alternative, deriving the document from markup embedded in 163 | program comments ("inverted literate programming"), seems less appealing. 164 | The disadvantage of inverted literate programming is that the final document 165 | can't reflect the original author's preferred order of exposition, 166 | since that informtion generally isn't part of the source code. 167 | 168 | - **py-web-lp** also specifically rejects some features of *nuweb* 169 | and *FunnelWeb*. These include the macro capability with parameter 170 | substitution, and multiple references to a chunk. These two capabilities 171 | can be used to grow object-like applications from non-object programming 172 | languages (*e.g.* C or Pascal). Since most modern languages (Python, 173 | Java, C++) are object-oriented, this macro capability is more of a problem 174 | than a help. 175 | 176 | - Since **py-web-lp** is built in the Python interpreter, a source document 177 | can include Python expressions that are evaluated during weave operation to 178 | produce time stamps, source file descriptions or other information in the woven 179 | or tangled output. 180 | 181 | 182 | **py-web-lp** works with any programming language; it can work with any markup language. 183 | The initial release supports RST via simple templates. 184 | 185 | The following is extensively quoted from Briggs' *nuweb* documentation, 186 | and provides an excellent background in the advantages of the very 187 | simple approach started by *nuweb* and adopted by **py-web-lp**. 188 | 189 | The need to support arbitrary 190 | programming languages has many consequences: 191 | 192 | :No prettyprinting: 193 | Both ``WEB`` and ``CWEB`` are able to 194 | prettyprint the code sections of their documents because they 195 | understand the language well enough to parse it. Since we want to use 196 | *any* language, we've got to abandon this feature. 197 | However, we do allow particular individual formulas or fragments 198 | of L\ :sub:`a`\ T\ :sub:`e`\ X 199 | or HTML code to be formatted and still be part of the output files. 200 | 201 | :Limited index of identifiers: 202 | Because ``WEB`` knows about Pascal, 203 | it is able to construct an index of all the identifiers occurring in 204 | the code sections (filtering out keywords and the standard type 205 | identifiers). Unfortunately, this isn't as easy in our case. We don't 206 | know what an identifier looks like in each language and we certainly 207 | don't know all the keywords. We provide a mechanism to mark 208 | identifiers, and we use a pretty standard pattern for recognizing 209 | identifiers almost most programming languages. 210 | 211 | 212 | Of course, we've got to have some compensation for our losses or the 213 | whole idea would be a waste. Here are the advantages I [Briggs] can see: 214 | 215 | :Simplicity: 216 | The majority of the commands in ``WEB`` are concerned with control of the 217 | automatic prettyprinting. Since we don't prettyprint, many commands are 218 | eliminated. A further set of commands is subsumed by L\ :sub:`a`\ T\ :sub:`e`\ X 219 | and may also be eliminated. As a result, our set of commands is reduced to 220 | only about seven members (explained in the next section). 221 | This simplicity is also reflected in the size of this tool, 222 | which is quite a bit smaller than the tools used with other approaches. 223 | 224 | :No prettyprinting: 225 | Everyone disagrees about how their code should look, so automatic 226 | formatting annoys many people. One approach is to provide ways to 227 | control the formatting. Our approach is simpler -- we perform no 228 | automatic formatting and therefore allow the programmer complete 229 | control of code layout. 230 | 231 | :Control: 232 | We also offer the programmer reasonably complete control of the 233 | layout of his output files (the files generated during tangling). 234 | Of course, this is essential for languages that are sensitive to layout; 235 | but it is also important in many practical situations, *e.g.*, debugging. 236 | 237 | :Speed: 238 | Since [**py-web-lp**] doesn't do too much, it runs very quickly. 239 | It combines the functions of ``tangle`` and ``weave`` into a single 240 | program that performs both functions at once. 241 | 242 | :Chunk numbers: 243 | Inspired by the example of **noweb**, [**py-web-lp**] refers to all program code 244 | chunks by a simple, ascending sequence number through the file. 245 | This becomes the HTML anchor name, also. 246 | 247 | :Multiple file output: 248 | The programmer may specify more than one output file in a single [**py-web-lp**] 249 | source file. This is required when constructing programs in a combination of 250 | languages (say, Fortran and C). It's also an advantage when constructing 251 | very large programs. 252 | 253 | Acknowledgements 254 | ---------------- 255 | 256 | This application is very directly based on (derived from?) work that 257 | preceded this, particularly the following: 258 | 259 | - Ross N. Williams' *FunnelWeb* http://www.ross.net/funnelweb/ 260 | 261 | - Norman Ramsey's *noweb* http://www.eecs.harvard.edu/~nr/noweb/ 262 | 263 | - Preston Briggs' *nuweb* http://sourceforge.net/projects/nuweb/ 264 | Currently supported by Charles Martin and Marc W. Mengel 265 | 266 | Also, after using John Skaller's *interscript* http://interscript.sourceforge.net/ 267 | for two large development efforts, I finally understood the feature set I really wanted. 268 | 269 | Jason Fruit and others contributed to the previous version. 270 | -------------------------------------------------------------------------------- /src/logging.toml: -------------------------------------------------------------------------------- 1 | 2 | version = 1 3 | disable_existing_loggers = false 4 | 5 | [root] 6 | handlers = [ "console",] 7 | level = "INFO" 8 | 9 | [handlers.console] 10 | class = "logging.StreamHandler" 11 | stream = "ext://sys.stderr" 12 | formatter = "basic" 13 | 14 | [formatters.basic] 15 | format = "{levelname}:{name}:{message}" 16 | style = "{" 17 | 18 | [loggers.Weaver] 19 | level = "INFO" 20 | 21 | [loggers.WebReader] 22 | level = "INFO" 23 | 24 | [loggers.Tangler] 25 | level = "INFO" 26 | 27 | [loggers.TanglerMake] 28 | level = "INFO" 29 | 30 | [loggers.indent.TanglerMake] 31 | level = "INFO" 32 | 33 | [loggers.WebReader] 34 | level = "INFO" 35 | 36 | [loggers.ReferenceCommand] 37 | # Unit test requires this... 38 | level = "INFO" 39 | 40 | -------------------------------------------------------------------------------- /src/make.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | 3 | pushd %~dp0 4 | 5 | REM Command file for Sphinx documentation 6 | 7 | if "%SPHINXBUILD%" == "" ( 8 | set SPHINXBUILD=sphinx-build 9 | ) 10 | set SOURCEDIR=. 11 | set BUILDDIR=_build 12 | 13 | %SPHINXBUILD% >NUL 2>NUL 14 | if errorlevel 9009 ( 15 | echo. 16 | echo.The 'sphinx-build' command was not found. Make sure you have Sphinx 17 | echo.installed, then set the SPHINXBUILD environment variable to point 18 | echo.to the full path of the 'sphinx-build' executable. Alternatively you 19 | echo.may add the Sphinx directory to PATH. 20 | echo. 21 | echo.If you don't have Sphinx installed, grab it from 22 | echo.https://www.sphinx-doc.org/ 23 | exit /b 1 24 | ) 25 | 26 | if "%1" == "" goto help 27 | 28 | %SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% 29 | goto end 30 | 31 | :help 32 | %SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% 33 | 34 | :end 35 | popd 36 | -------------------------------------------------------------------------------- /src/pyweb.toml: -------------------------------------------------------------------------------- 1 | 2 | [pyweb] 3 | # PyWeb options go here. 4 | 5 | [logging] 6 | version = 1 7 | disable_existing_loggers = false 8 | 9 | [logging.root] 10 | handlers = [ "console",] 11 | level = "INFO" 12 | 13 | [logging.handlers.console] 14 | class = "logging.StreamHandler" 15 | stream = "ext://sys.stderr" 16 | formatter = "basic" 17 | 18 | [logging.formatters.basic] 19 | format = "{levelname}:{name}:{message}" 20 | style = "{" 21 | 22 | [logging.loggers.Weaver] 23 | level = "INFO" 24 | 25 | [logging.loggers.WebReader] 26 | level = "INFO" 27 | 28 | [logging.loggers.Tangler] 29 | level = "INFO" 30 | 31 | [logging.loggers.TanglerMake] 32 | level = "INFO" 33 | 34 | [logging.loggers.indent.TanglerMake] 35 | level = "INFO" 36 | 37 | [logging.loggers.ReferenceCommand] 38 | # Unit test requires this... 39 | level = "INFO" 40 | 41 | -------------------------------------------------------------------------------- /src/pyweb.w: -------------------------------------------------------------------------------- 1 | ############################## 2 | pyWeb Literate Programming 3.2 3 | ############################## 4 | 5 | ================================================= 6 | Yet Another Literate Programming Tool 7 | ================================================= 8 | 9 | .. contents:: 10 | 11 | @i intro.w 12 | 13 | @i usage.w 14 | 15 | @i language.w 16 | 17 | @i overview.w 18 | 19 | @i impl.w 20 | 21 | @i tests.w 22 | 23 | @i scripts.w 24 | 25 | @i todo.w 26 | 27 | @i done.w 28 | 29 | 30 | Indices 31 | ======= 32 | 33 | Files 34 | ------ 35 | 36 | @f 37 | 38 | Macros 39 | ------ 40 | 41 | @m 42 | 43 | User Identifiers 44 | ---------------- 45 | 46 | @u 47 | 48 | 49 | --------- 50 | 51 | .. class:: small 52 | 53 | Created by @(thisApplication@) at @(datetime.datetime.now().ctime()@). 54 | 55 | Source @(theFile@) modified @(datetime.datetime.fromtimestamp(os.path.getmtime(theFile)).ctime()@). 56 | 57 | pyweb.__version__ '@(__version__@)'. 58 | 59 | Working directory '@(os.path.realpath('.')@)'. 60 | -------------------------------------------------------------------------------- /src/scripts.w: -------------------------------------------------------------------------------- 1 | .. py-web-tool/src/scripts.w 2 | 3 | Handy Scripts and Other Files 4 | ================================================= 5 | 6 | Two aditional scripts, ``tangle.py`` and ``weave.py``, are provided as examples 7 | which can be customized and extended. 8 | 9 | ``tangle.py`` Script 10 | --------------------- 11 | 12 | This script shows a simple version of Tangling. This has a permitted 13 | error for '@@i' commands to allow an include file (for example test results) 14 | to be omitted from the tangle operation. 15 | 16 | Note the general flow of this top-level script. 17 | 18 | 1. Create the logging context. 19 | 20 | 2. Create the options. This hard-coded object is a stand-in for 21 | parsing command-line options. 22 | 23 | 3. Create the web object. 24 | 25 | 4. For each action (``LoadAction`` and ``TangleAction`` in this example) 26 | Set the web, set the options, execute the callable action, and write 27 | a summary. 28 | 29 | @o tangle.py 30 | @{#!/usr/bin/env python3 31 | """Sample tangle.py script.""" 32 | import argparse 33 | import logging 34 | from pathlib import Path 35 | import pyweb 36 | 37 | def main(source: Path) -> None: 38 | with pyweb.Logger(pyweb.default_logging_config): 39 | logger = logging.getLogger(__file__) 40 | 41 | options = argparse.Namespace( 42 | source_path=source, 43 | output=source.parent, 44 | verbosity=logging.INFO, 45 | command='@@', 46 | permitList=['@@i'], 47 | tangler_line_numbers=False, 48 | webReader=pyweb.WebReader(), 49 | theTangler=pyweb.TanglerMake(), 50 | ) 51 | 52 | for action in pyweb.LoadAction(), pyweb.TangleAction(): 53 | action(options) 54 | logger.info(action.summary()) 55 | 56 | if __name__ == "__main__": 57 | main(Path("examples/test_rst.w")) 58 | @} 59 | 60 | ``weave.py`` Script 61 | --------------------- 62 | 63 | This script shows a simple version of Weaving. This shows how 64 | to define a customized set of templates for a different markup language. 65 | 66 | 67 | A customized weaver generally has three parts. 68 | 69 | @o weave.py 70 | @{@ 71 | 72 | @ 73 | 74 | @ 75 | @} 76 | 77 | @d weave.py overheads... 78 | @{#!/usr/bin/env python3 79 | """Sample weave.py script.""" 80 | import argparse 81 | import logging 82 | import string 83 | from pathlib import Path 84 | from textwrap import dedent 85 | 86 | import pyweb 87 | @} 88 | 89 | To override templates, a class 90 | needs to provide a text definition of 91 | the Jinja ``{% macro %}`` definitions. 92 | This is used to update the superclass 93 | ``template_map``. 94 | 95 | Something like the following sets the macros in use. 96 | 97 | .. parsed-literal:: 98 | 99 | self.template_map['html_macros'] = my_templates 100 | 101 | Any macro **not** defined gets a default implementation. 102 | 103 | @d weave.py custom weaver definition... 104 | @{ 105 | class MyHTML(pyweb.Weaver): 106 | bootstrap_html = dedent(""" 107 | {%- macro begin_code(chunk) %} 108 |
109 |
110 | 111 | 112 |

{{chunk.full_name or chunk.name}} ({{chunk.seq}}) {% if chunk.initial %}={% else %}+={% endif %}

113 |
114 |
115 |

116 |     {%- endmacro -%}
117 | 
118 |     {%- macro end_code(chunk) %}
119 |         
120 |
121 | 125 |
126 | {% endmacro -%} 127 | """) 128 | 129 | def __init__(self, output: Path = Path.cwd()) -> None: 130 | super().__init__(output) 131 | self.template_map = pyweb.Weaver.template_map | {"html_macros": self.bootstrap_html} 132 | @} 133 | 134 | @d weaver.py processing... 135 | @{ 136 | def main(source: Path) -> None: 137 | with pyweb.Logger(pyweb.default_logging_config): 138 | logger = logging.getLogger(__file__) 139 | 140 | options = argparse.Namespace( 141 | source_path=source, 142 | output=source.parent, 143 | verbosity=logging.INFO, 144 | weaver="html", 145 | command='@@', 146 | permitList=[], 147 | tangler_line_numbers=False, 148 | webReader=pyweb.WebReader(), 149 | 150 | theWeaver=MyHTML(), # Customize with a specific Weaver subclass 151 | ) 152 | 153 | for action in pyweb.LoadAction(), pyweb.WeaveAction(): 154 | action(options) 155 | logger.info(action.summary()) 156 | 157 | if __name__ == "__main__": 158 | main(Path("examples/test_rst.w")) 159 | @} 160 | -------------------------------------------------------------------------------- /src/tangle.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """Sample tangle.py script.""" 3 | import argparse 4 | import logging 5 | from pathlib import Path 6 | import pyweb 7 | 8 | def main(source: Path) -> None: 9 | with pyweb.Logger(pyweb.default_logging_config): 10 | logger = logging.getLogger(__file__) 11 | 12 | options = argparse.Namespace( 13 | source_path=source, 14 | output=source.parent, 15 | verbosity=logging.INFO, 16 | command='@', 17 | permitList=['@i'], 18 | tangler_line_numbers=False, 19 | webReader=pyweb.WebReader(), 20 | theTangler=pyweb.TanglerMake(), 21 | ) 22 | 23 | for action in pyweb.LoadAction(), pyweb.TangleAction(): 24 | action(options) 25 | logger.info(action.summary()) 26 | 27 | if __name__ == "__main__": 28 | main(Path("examples/test_rst.w")) 29 | -------------------------------------------------------------------------------- /src/tests.w: -------------------------------------------------------------------------------- 1 | .. py-web-tool/src/test.w 2 | 3 | Unit Tests 4 | =========== 5 | 6 | The ``tests`` directory includes ``pyweb_test.w``, which will create a 7 | complete test suite. 8 | 9 | This source will weaves a ``pyweb_test.html`` file. See `tests/pyweb_test.html `_. 10 | 11 | This source will tangle several test modules: ``test.py``, ``test_tangler.py``, ``test_weaver.py``, 12 | ``test_loader.py``, ``test_unit.py``, and ``test_scripts.py``. 13 | 14 | Use **pytest** to discover and run all 80+ test cases. 15 | 16 | Here's a script that works out well for running this without disturbing the development 17 | environment. The ``PYTHONPATH`` setting is essential to support importing ``pyweb``. 18 | 19 | .. parsed-literal:: 20 | 21 | python pyweb.py -o tests tests/pyweb_test.w 22 | PYTHONPATH=$(PWD) pytest 23 | 24 | Note that the last line really does set an environment variable and run 25 | the ``pytest`` tool on a single line. 26 | -------------------------------------------------------------------------------- /src/todo.w: -------------------------------------------------------------------------------- 1 | .. py-web-tool/src/todo.w 2 | 3 | 4 | To Do 5 | ======= 6 | 7 | 1. Change the comment start and comment end options 8 | to use Jinja template fragments. 9 | There needs to be an ``-add '# {{chunk.position}}'`` 10 | which overrides the default of ``''`` and injects 11 | this into each Tangled chunk. Indented appropriately. 12 | 13 | 1. Implement all four alternative references in the ``end_code()`` macro. 14 | 15 | - Nothing. 16 | 17 | - The immediate reference. 18 | 19 | - The two variants on full paths: 20 | 21 | - top-down ``→ Named (1) / → Sub-Named (2) / → Sub-Sub-Named (3)`` 22 | 23 | - bottom-up ``→ Sub-Sub-Named (3) ∈ → Sub-Named (2) ∈ → Named (1)`` 24 | 25 | #. Implement ``@@h`` command and ``HiddenOutputChunk``. This may be sufficient. 26 | Tangling can then include non-woven output files. 27 | Viewed the other way, Weaving can exclude the non-woven file. 28 | The use case is a book chapter with test cases that are **not** woven into the text. 29 | See https://github.com/slott56/py-web-tool/wiki/Tangle-only-Output. 30 | 31 | #. Permit selecting a specific ``begin_code()`` template for a named block. 32 | A book may have distinct formatting for REPL examples and code examples. 33 | It may be as small as CSS classes for RST or HTML. It may be a more elaborate 34 | set of differences in LaTeX. 35 | 36 | #. Update the ``-indent`` option on ``@@d`` chunks to accept a numeric argument with the 37 | specific indentation value. This becomes a kind of "noindent" with a given 38 | value. The ``-noindent`` would then be the same as ``-indent 0``. 39 | Currently, `-indent` and `-noindent` are true/false flags. 40 | 41 | #. We might want to decompose the ``impl.w`` file: it's huge. 42 | The four major sections -- base model, output, input parsing, other components -- make sense. 43 | However, since the output is a *single* .rst file, it doesn't change much to do this. 44 | 45 | #. We might want to interleave code and test into a document that presents both 46 | side-by-side. We can route the tangled code to multiple files. 47 | It can be awkward to create tangled files in multiple directories, however. 48 | We'd have to use ``../tests/whatever.py``, **assuming** we were always using ``-o src``. 49 | 50 | #. Rename the module from ``pyweb`` to ``pylpweb`` to avoid name squatting issues. 51 | Rename the project from ``py-web-lp`` to ``py-lpweb``. 52 | 53 | #. Offer a basic XHTML template that uses ``CDATA`` sections instead of quoting. 54 | Does require the standard quoting for the ``CDATA`` end tag. 55 | 56 | #. Note that the overall ``Web`` is a bit like a ``NamedChunk`` that contains ``Chunks``. 57 | This similarity could be factored out. 58 | While this will create a more proper **Composition** pattern implementation, it 59 | leads to the question of why nest ``@@d`` or ``@@o`` chunks in the first place? 60 | -------------------------------------------------------------------------------- /src/usage.w: -------------------------------------------------------------------------------- 1 | .. py-web-tool/src/usage.w 2 | 3 | Installing 4 | ========== 5 | 6 | This requires Python 3.10. 7 | 8 | This is not (currently) hosted in PyPI. Instead of installing it with PIP, 9 | clone the GitHub repository or download the distribution kit. 10 | 11 | After downloading, install pyweb "manually" using the provided ``setup.py``. 12 | 13 | :: 14 | 15 | python setup.py install 16 | 17 | This will install the ``pyweb`` module. 18 | 19 | This depends on Jinja2 templates. The Jinja components should be installed 20 | when ``setup.py`` uses ``requirements.txt`` to install the required components. 21 | 22 | Using 23 | ===== 24 | 25 | **py-web-lp** supports two use cases, `Tangle Source Files`_ and `Weave Documentation`_. 26 | These are often combined to both tangle and weave an application and it's documentation. 27 | The work starts with creating a WEB file with documentation and code. 28 | 29 | Create WEB File 30 | ---------------- 31 | 32 | See `The py-web-lp Markup Language`_ for more details on the language. 33 | For a simple example, we'll use the following WEB file: ``examples/hw.w``. 34 | 35 | .. parsed-literal:: 36 | 37 | ########### 38 | Hello World 39 | ########### 40 | 41 | This file has a *small* example. 42 | 43 | @@d The Body Of The Script @@{ 44 | print("Hello, World!") 45 | @@} 46 | 47 | The Python module includes a small script. 48 | 49 | @@o hw.py @@{ 50 | @@ 51 | @@} 52 | 53 | This example has RST markup document, that includes some ``@@d`` and ``@@o`` chunks 54 | to define code blocks. The ``@@d`` is the definition of a named chunk, ``The Body Of The Script``. 55 | The ``@@o`` defines an output file to be tangled. This file has a reference to 56 | the ``The Body Of The Script`` chunk. 57 | 58 | When tangling, the code will be used to build the file(s) in the ``@@o`` chunk(s). 59 | In this example, it will write the ``hw.py`` file by tangling the referenced chunk. 60 | 61 | When weaving, the ``@@d`` and ``@@o`` chunks will have some additional RST markup inserted 62 | into the document. The output file will have a name based on the source WEB document. 63 | In this case it will be ``hw.rst``. 64 | 65 | 66 | Tangle Source Files 67 | ------------------- 68 | 69 | A user initiates this process when they have a complete ``.w`` file that contains 70 | a description of source files. These source files are described with ``@@o`` commands 71 | in the WEB file. 72 | 73 | The use case is successful when the source files are produced. 74 | 75 | The use case is a failure when the source files cannot be produced, due to 76 | errors in the ``.w`` file. These must be corrected based on information in log messages. 77 | 78 | A typical command to tangle (without weaving) is: 79 | 80 | .. parsed-literal:: 81 | 82 | pyweb -xw examples/hw.w -o examples 83 | 84 | The outputs will be defined by the ``@@o`` commands in the source. 85 | The ``-o`` option writes the resulting tangled files to the named directory. 86 | 87 | Weave Documentation 88 | ------------------- 89 | 90 | A user initiates this process when they have a ``.w`` file that contains 91 | a description of a document to produce. The document is described by the entire 92 | WEB file. The default is to use ReSTructured Text (RST) markup. 93 | The output file will have the ``.rst`` suffix. 94 | 95 | The use case is successful when the documentation file is produced. 96 | 97 | The use case is a failure when the documentation file cannot be produced, due to 98 | errors in the ``.w`` file. These must be corrected based on information in log messages. 99 | 100 | A typical command to weave (without tangling) is: 101 | 102 | .. parsed-literal:: 103 | 104 | pyweb -xt examples/hw.w -o examples 105 | 106 | The output will be named ``examples/hw.rst``. The ``-o`` option made sure the file 107 | was written to the ``examples`` directory. 108 | 109 | Running **py-web-lp** to Tangle and Weave 110 | ------------------------------------------- 111 | 112 | Assuming that you have marked ``pyweb.py`` as executable, 113 | you do the following: 114 | 115 | .. code:: bash 116 | 117 | pyweb examples/hw.w -o examples 118 | 119 | This will tangle the ``@@o`` commands in ``examples/hw.w`` 120 | It will also weave the output, and create ``examples/hw.rst``. 121 | This can be processed by docutils to create an HTML file. 122 | 123 | Command Line Options 124 | ~~~~~~~~~~~~~~~~~~~~~ 125 | 126 | Currently, the following command line options are accepted. 127 | 128 | 129 | :-v: 130 | Verbose logging. 131 | 132 | :-s: 133 | Silent operation. 134 | 135 | :-c *x*: 136 | Change the command character from ``@@`` to ``*x*``. 137 | 138 | :-w *weaver*: 139 | Choose a particular documentation weaver template. Currently the choices 140 | are ``rst``, ``tex``, and ``html``. 141 | 142 | :-xw: 143 | Exclude weaving. This does tangling of source program files only. 144 | 145 | :-xt: 146 | Exclude tangling. This does weaving of the document file only. 147 | 148 | :-p *command*: 149 | Permit errors in the given list of commands. The most common 150 | version is ``-pi`` to permit errors in locating an include file. 151 | This is done in the following scenario: pass 1 uses ``-xw -pi`` to exclude 152 | weaving and permit include-file errors; 153 | the tangled program is run to create test results; pass 2 uses 154 | ``-xt`` to exclude tangling and include the test results. 155 | 156 | :-o *directory*: 157 | The directory to which to write output files. 158 | 159 | Bootstrapping 160 | -------------- 161 | 162 | **py-web-lp** is written using **py-web-lp**. The distribution includes the original ``.w`` 163 | files as well as a ``.py`` module. 164 | 165 | The bootstrap procedure is to run a "known good" ``pyweb`` to transform 166 | a working copy into a new version of ``pyweb``. We provide the previous release in the ``bootstrap`` 167 | directory. 168 | 169 | .. parsed-literal:: 170 | 171 | python bootstrap/pyweb.py pyweb.w 172 | rst2html.py pyweb.rst pyweb.html 173 | 174 | The resulting ``pyweb.html`` file is the updated documentation. 175 | The ``pyweb.py`` is the updated candidate release of **py-web-lp**. 176 | 177 | Similarly, the tests built from a ``.w`` files. 178 | 179 | .. code:: bash 180 | 181 | python pyweb.py tests/pyweb_test.w -o tests 182 | PYTHONPATH=.. pytest 183 | rst2html.py tests/pyweb_test.rst tests/pyweb_test.html 184 | 185 | Dependencies 186 | ------------- 187 | 188 | **py-web-lp** requires Python 3.10 or newer. 189 | 190 | The following components are listed in the ``requirements.txt`` 191 | file. These can be loaded via 192 | 193 | .. code:: bash 194 | 195 | python -m pip install -r requirements.txt 196 | 197 | This lp uses `Jinja `_ for template processing. 198 | 199 | The `tomli `_ library is used to parse configuration files 200 | for older Python that lack a ``tomllib`` in the standard library. 201 | 202 | If you create RST output, you'll want to use either `docutils `_ or `Sphinx `_ to translate 203 | the RST to HTML or LaTeX or any of the other formats supported by docutils or Sphinx. 204 | This is not a proper requirement to run the tool. It's a common 205 | part of an overall document production tool chain. 206 | 207 | The overview contains PlantUML diagrams. 208 | See https://plantuml.com/ for more information. 209 | The `PlantUML for Sphinx `_ plug-in 210 | can be used to render the diagrams automatically. 211 | 212 | For development, additional components 213 | like ``pytest``, ``tox``, and ``mypy`` are also used for development. 214 | 215 | More Advanced Usage 216 | =================== 217 | 218 | Here are two more advanced use cases. 219 | 220 | Tangle, Test, and Weave with Test Results 221 | ----------------------------------------- 222 | 223 | A user initiates this process when the final document should include test output 224 | from the source files created by the tangle operation. This is an extension to 225 | the example shown earlier. 226 | 227 | .. parsed-literal:: 228 | 229 | ########### 230 | Hello World 231 | ########### 232 | 233 | This file has a *small* example. 234 | 235 | @@d The Body Of The Script @@{ 236 | print("Hello, World!") 237 | @@} 238 | 239 | The Python module includes a small script. 240 | 241 | @@o hw.py @@{ 242 | @@ 243 | @@} 244 | 245 | Example Output 246 | ============== 247 | 248 | @@i examples/hw_output.log 249 | 250 | 251 | The use case is successful when the documentation file is produced, including 252 | current test output. 253 | 254 | The use case is a failure when the documentation file cannot be produced, due to 255 | errors in the ``.w`` file. These must be corrected based on information in log messages. 256 | 257 | The use case is a failure when the documentation file does not include current 258 | test output. 259 | 260 | The sequence is as follows: 261 | 262 | .. parsed-literal:: 263 | 264 | pyweb -xw -pi examples/hw.w -o examples 265 | python examples/hw.py >examples/hw_output.log 266 | pyweb -xt examples/hw.w -o examples 267 | 268 | The first step uses ``-xw`` to excludes document weaving. 269 | The ``-pi`` option will permits errors on the ``@@i`` command. 270 | This is necessary in the event that the log file does not yet exist. 271 | 272 | The second step runs the test, creating a log file. 273 | 274 | The third step weaves the final document, including the test output file. 275 | The ``-xt`` option excludes tangling, since output file had already been produced. 276 | 277 | 278 | Template Changes 279 | ---------------- 280 | 281 | The woven document is based -- primarily -- on the text in the source WEB file. 282 | This is processed using a small set of Jinja2 macros to modify behavior. 283 | To fine-tune the results, we can adjust the templates used by this application. 284 | 285 | The easiest way to do this is to work with the ``weave.py`` script which shows 286 | how to create a customized subclass of ``Weaver``. 287 | The `Handy Scripts and Other Files`_ section shows this script and how it's build 288 | from a few ``pyweb`` components. 289 | -------------------------------------------------------------------------------- /src/weave.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """Sample weave.py script.""" 3 | import argparse 4 | import logging 5 | import string 6 | from pathlib import Path 7 | from textwrap import dedent 8 | 9 | import pyweb 10 | 11 | 12 | 13 | class MyHTML(pyweb.Weaver): 14 | bootstrap_html = dedent(""" 15 | {%- macro begin_code(chunk) %} 16 |
17 |
18 | 19 | 20 |

{{chunk.full_name or chunk.name}} ({{chunk.seq}}) {% if chunk.initial %}={% else %}+={% endif %}

21 |
22 |
23 |

24 |     {%- endmacro -%}
25 | 
26 |     {%- macro end_code(chunk) %}
27 |         
28 |
29 | 33 |
34 | {% endmacro -%} 35 | """) 36 | 37 | def __init__(self, output: Path = Path.cwd()) -> None: 38 | super().__init__(output) 39 | self.template_map = pyweb.Weaver.template_map | {"html_macros": self.bootstrap_html} 40 | 41 | 42 | 43 | def main(source: Path) -> None: 44 | with pyweb.Logger(pyweb.default_logging_config): 45 | logger = logging.getLogger(__file__) 46 | 47 | options = argparse.Namespace( 48 | source_path=source, 49 | output=source.parent, 50 | verbosity=logging.INFO, 51 | weaver="html", 52 | command='@', 53 | permitList=[], 54 | tangler_line_numbers=False, 55 | webReader=pyweb.WebReader(), 56 | 57 | theWeaver=MyHTML(), # Customize with a specific Weaver subclass 58 | ) 59 | 60 | for action in pyweb.LoadAction(), pyweb.WeaveAction(): 61 | action(options) 62 | logger.info(action.summary()) 63 | 64 | if __name__ == "__main__": 65 | main(Path("examples/test_rst.w")) 66 | 67 | -------------------------------------------------------------------------------- /tests/docutils.conf: -------------------------------------------------------------------------------- 1 | # docutils.conf 2 | 3 | [html4css1 writer] 4 | stylesheet-path: /Users/slott/miniconda3/envs/pywebtool/lib/python3.10/site-packages/docutils/writers/html4css1/html4css1.css, 5 | page-layout.css 6 | syntax-highlight: long 7 | -------------------------------------------------------------------------------- /tests/intro.w: -------------------------------------------------------------------------------- 1 | Introduction 2 | ============ 3 | 4 | .. test/intro.w 5 | 6 | There are two levels of testing in this document. 7 | 8 | - `Unit Testing`_ 9 | 10 | - `Functional Testing`_ 11 | 12 | Other testing, like performance or security, is possible. 13 | But for this application, not very interesting. 14 | 15 | This doument builds a complete test suite, ``test.py``. 16 | 17 | .. parsed-literal:: 18 | 19 | MacBookPro-SLott:test slott$ python3.3 ../pyweb.py pyweb_test.w 20 | INFO:Application:Setting root log level to 'INFO' 21 | INFO:Application:Setting command character to '@@' 22 | INFO:Application:Weaver RST 23 | INFO:Application:load, tangle and weave 'pyweb_test.w' 24 | INFO:LoadAction:Starting Load 25 | INFO:WebReader:Including 'intro.w' 26 | WARNING:WebReader:Unknown @@-command in input: "@@'" 27 | INFO:WebReader:Including 'unit.w' 28 | INFO:WebReader:Including 'func.w' 29 | INFO:WebReader:Including 'combined.w' 30 | INFO:TangleAction:Starting Tangle 31 | INFO:TanglerMake:Tangling 'test_unit.py' 32 | INFO:TanglerMake:No change to 'test_unit.py' 33 | INFO:TanglerMake:Tangling 'test_loader.py' 34 | INFO:TanglerMake:No change to 'test_loader.py' 35 | INFO:TanglerMake:Tangling 'test.py' 36 | INFO:TanglerMake:No change to 'test.py' 37 | INFO:TanglerMake:Tangling 'page-layout.css' 38 | INFO:TanglerMake:No change to 'page-layout.css' 39 | INFO:TanglerMake:Tangling 'docutils.conf' 40 | INFO:TanglerMake:No change to 'docutils.conf' 41 | INFO:TanglerMake:Tangling 'test_tangler.py' 42 | INFO:TanglerMake:No change to 'test_tangler.py' 43 | INFO:TanglerMake:Tangling 'test_weaver.py' 44 | INFO:TanglerMake:No change to 'test_weaver.py' 45 | INFO:WeaveAction:Starting Weave 46 | INFO:RST:Weaving 'pyweb_test.rst' 47 | INFO:RST:Wrote 3173 lines to 'pyweb_test.rst' 48 | INFO:WeaveAction:Finished Normally 49 | INFO:Application:Load 1911 lines from 5 files in 0.05 sec., Tangle 138 lines in 0.03 sec., Weave 3173 lines in 0.02 sec. 50 | MacBookPro-SLott:test slott$ PYTHONPATH=.. python3.3 test.py 51 | ERROR:WebReader:At ('test8_inc.tmp', 4): end of input, ('@@{', '@@[') not found 52 | ERROR:WebReader:Errors in included file test8_inc.tmp, output is incomplete. 53 | .ERROR:WebReader:At ('test1.w', 8): expected ('@@{',), found '@@o' 54 | ERROR:WebReader:Extra '@@{' (possibly missing chunk name) near ('test1.w', 9) 55 | ERROR:WebReader:Extra '@@{' (possibly missing chunk name) near ('test1.w', 9) 56 | ............................................................................. 57 | ---------------------------------------------------------------------- 58 | Ran 78 tests in 0.025s 59 | 60 | OK 61 | MacBookPro-SLott:test slott$ rst2html.py pyweb_test.rst pyweb_test.html 62 | -------------------------------------------------------------------------------- /tests/page-layout.css: -------------------------------------------------------------------------------- 1 | /* Page layout tweaks */ 2 | div.document { width: 7in; } 3 | .small { font-size: smaller; } 4 | .code 5 | { 6 | color: #101080; 7 | display: block; 8 | border-color: black; 9 | border-width: thin; 10 | border-style: solid; 11 | background-color: #E0FFFF; 12 | /*#99FFFF*/ 13 | padding: 0 0 0 1%; 14 | margin: 0 6% 0 6%; 15 | text-align: left; 16 | font-size: smaller; 17 | } 18 | -------------------------------------------------------------------------------- /tests/pyweb.css: -------------------------------------------------------------------------------- 1 | .document { width: 720px; } 2 | .chapter { margin-left: 12px; } 3 | .code { font-family: courier,monospace; } 4 | 5 | .quote 6 | { 7 | text-align: justify; 8 | margin-left: 8%; 9 | margin-right: 8%; 10 | /*display: block;*/ 11 | /*width: 4.5in;*/ 12 | font-size: smaller; 13 | } 14 | 15 | .title 16 | { 17 | font-size: x-large; 18 | text-align: center; 19 | font-weight: bold; 20 | } 21 | 22 | .subtitle { text-align: center; } 23 | .macro { margin: 0 0 0 0; } 24 | 25 | code 26 | { 27 | color: #101080; 28 | /*black*/ 29 | /*width: 6in;*/ 30 | display: block; 31 | border-color: black; 32 | border-width: thin; 33 | border-style: solid; 34 | background-color: #E0FFFF; 35 | /*#99FFFF*/ 36 | padding: 0 0 0 1%; 37 | margin: 0 6% 0 6%; 38 | text-align: left; 39 | font-size: smaller; 40 | } 41 | 42 | CODE SPAN.KEYWORD 43 | { 44 | color: #000000; 45 | font-weight: bold; 46 | } 47 | 48 | CODE SPAN.NAME { color: #000000; } 49 | CODE SPAN.STRING { color: #004000; } 50 | CODE SPAN.NUMBER { color: #002020; } 51 | CODE SPAN.BRACKET { color: #800000; } 52 | CODE SPAN.PUNCT { color: #802000; } 53 | CODE SPAN.OP { color: #400000; } 54 | 55 | CODE SPAN.COMMENT 56 | { 57 | color: #000000; 58 | background-color: #FFF0FF; 59 | font-size: 80%; 60 | font-family sans-serif: ; 61 | } 62 | 63 | CODE PRE { margin: 0 0 0 0; } 64 | P,PRE,BLOCKQUOTE PRE { margin: 0 0 6pt 6%; } 65 | UL,OL,DL { margin-left: 6% margin-right: 6%; } 66 | 67 | /* want only level 1 UL's, OL's etc. to have a generous left margin */ 68 | OL OL, OL UL, UL OL, UL UL { margin-left: 4% margin-right: 0; } 69 | LI { margin-left: 6%; } 70 | LI LI { margin-left: 0; } -------------------------------------------------------------------------------- /tests/pyweb_test.w: -------------------------------------------------------------------------------- 1 | ############################################ 2 | pyWeb Literate Programming 3.2 - Test Suite 3 | ############################################ 4 | 5 | 6 | ================================================= 7 | Yet Another Literate Programming Tool 8 | ================================================= 9 | 10 | .. contents:: 11 | 12 | 13 | @i intro.w 14 | 15 | @i unit.w 16 | 17 | @i func.w 18 | 19 | @i scripts.w 20 | 21 | No Longer supported: @@i runner.w, using **pytest** seems better. 22 | 23 | Additional Files 24 | ================= 25 | 26 | To get the RST to look good, there are two additional files. 27 | These are clones of what's in the ``src`` directory. 28 | 29 | ``docutils.conf`` defines two CSS files to use. 30 | The default CSS file may need to be customized. 31 | 32 | @o docutils.conf 33 | @{# docutils.conf 34 | 35 | [html4css1 writer] 36 | stylesheet-path: /Users/slott/miniconda3/envs/pywebtool/lib/python3.10/site-packages/docutils/writers/html4css1/html4css1.css, 37 | page-layout.css 38 | syntax-highlight: long 39 | @} 40 | 41 | ``page-layout.css`` This tweaks one CSS to be sure that 42 | the resulting HTML pages are easier to read. These are minor 43 | tweaks to the default CSS. 44 | 45 | @o page-layout.css 46 | @{/* Page layout tweaks */ 47 | div.document { width: 7in; } 48 | .small { font-size: smaller; } 49 | .code 50 | { 51 | color: #101080; 52 | display: block; 53 | border-color: black; 54 | border-width: thin; 55 | border-style: solid; 56 | background-color: #E0FFFF; 57 | /*#99FFFF*/ 58 | padding: 0 0 0 1%; 59 | margin: 0 6% 0 6%; 60 | text-align: left; 61 | font-size: smaller; 62 | } 63 | @} 64 | 65 | 66 | Indices 67 | ======= 68 | 69 | Files 70 | ----- 71 | 72 | @f 73 | 74 | Macros 75 | ------ 76 | 77 | @m 78 | 79 | 80 | ---------- 81 | 82 | .. class:: small 83 | 84 | Created by @(thisApplication@) at @(datetime.datetime.now().ctime()@). 85 | 86 | Source @(theFile@) modified @(datetime.datetime.fromtimestamp(os.path.getmtime(theFile)).ctime()@). 87 | 88 | pyweb.__version__ '@(__version__@)'. 89 | 90 | Working directory '@(os.path.realpath('.')@)'. 91 | -------------------------------------------------------------------------------- /tests/runner.py: -------------------------------------------------------------------------------- 1 | """Combined tests.""" 2 | import argparse 3 | import unittest 4 | import test_loader 5 | import test_tangler 6 | import test_weaver 7 | import test_unit 8 | import logging 9 | import sys 10 | 11 | 12 | 13 | def suite(): 14 | s = unittest.TestSuite() 15 | for m in (test_loader, test_tangler, test_weaver, test_unit): 16 | s.addTests(unittest.defaultTestLoader.loadTestsFromModule(m)) 17 | return s 18 | 19 | 20 | def get_options(argv: list[str] = sys.argv[1:]) -> argparse.Namespace: 21 | parser = argparse.ArgumentParser() 22 | parser.add_argument("-v", "--verbose", dest="verbosity", action="store_const", const=logging.INFO) 23 | parser.add_argument("-d", "--debug", dest="verbosity", action="store_const", const=logging.DEBUG) 24 | parser.add_argument("-l", "--logger", dest="logger", action="store", help="comma-separated list") 25 | defaults = argparse.Namespace( 26 | verbosity=logging.CRITICAL, 27 | logger="" 28 | ) 29 | config = parser.parse_args(argv, namespace=defaults) 30 | return config 31 | 32 | 33 | if __name__ == "__main__": 34 | options = get_options() 35 | logging.basicConfig(stream=sys.stderr, level=options.verbosity) 36 | logger = logging.getLogger("test") 37 | for logger_name in (n.strip() for n in options.logger.split(',')): 38 | l = logging.getLogger(logger_name) 39 | l.setLevel(options.verbosity) 40 | logger.info(f"Setting {l}") 41 | 42 | tr = unittest.TextTestRunner() 43 | result = tr.run(suite()) 44 | logging.shutdown() 45 | sys.exit(len(result.failures) + len(result.errors)) 46 | 47 | -------------------------------------------------------------------------------- /tests/runner.w: -------------------------------------------------------------------------------- 1 | Combined Test Runner 2 | ===================== 3 | 4 | .. test/runner.w 5 | 6 | This is a small runner that executes all tests in all test modules. 7 | Instead of test discovery as done by **pytest** and others, 8 | this defines a test suite "the hard way" with an explicit list of modules. 9 | 10 | @o runner.py 11 | @{@ 12 | @ 13 | @ 14 | @ 15 | @} 16 | 17 | The overheads import unittest and logging, because those are essential 18 | infrastructure. Additionally, each of the test modules is also imported. 19 | 20 | @d Combined Test overheads... 21 | @{"""Combined tests.""" 22 | import argparse 23 | import unittest 24 | import test_loader 25 | import test_tangler 26 | import test_weaver 27 | import test_unit 28 | import logging 29 | import sys 30 | 31 | @} 32 | 33 | The test suite is built from each of the individual test modules. 34 | 35 | @d Combined Test suite... 36 | @{ 37 | def suite(): 38 | s = unittest.TestSuite() 39 | for m in (test_loader, test_tangler, test_weaver, test_unit): 40 | s.addTests(unittest.defaultTestLoader.loadTestsFromModule(m)) 41 | return s 42 | @} 43 | 44 | In order to debug failing tests, we accept some command-line 45 | parameters to the combined testing script. 46 | 47 | @d Combined Test command line options... 48 | @{ 49 | def get_options(argv: list[str] = sys.argv[1:]) -> argparse.Namespace: 50 | parser = argparse.ArgumentParser() 51 | parser.add_argument("-v", "--verbose", dest="verbosity", action="store_const", const=logging.INFO) 52 | parser.add_argument("-d", "--debug", dest="verbosity", action="store_const", const=logging.DEBUG) 53 | parser.add_argument("-l", "--logger", dest="logger", action="store", help="comma-separated list") 54 | defaults = argparse.Namespace( 55 | verbosity=logging.CRITICAL, 56 | logger="" 57 | ) 58 | config = parser.parse_args(argv, namespace=defaults) 59 | return config 60 | @} 61 | 62 | This means we can use ``-dlWebReader`` to debug the Web Reader. 63 | We can use ``-d -lWebReader,TanglerMake`` to debug both 64 | the WebReader class and the TanglerMake class. Not all classes have named loggers. 65 | Logger names include ``Emitter``, 66 | ``indent.Emitter``, 67 | ``Chunk``, 68 | ``Command``, 69 | ``Reference``, 70 | ``Web``, 71 | ``WebReader``, 72 | ``Action``, and 73 | ``Application``. 74 | As well as subclasses of Emitter, Chunk, Command, and Action. 75 | 76 | The main script initializes logging. Note that the typical setup 77 | uses ``logging.CRITICAL`` to silence some expected warning messages. 78 | For debugging, ``logging.WARN`` provides more information. 79 | 80 | Once logging is running, it executes the ``unittest.TextTestRunner`` on the test suite. 81 | 82 | 83 | @d Combined Test main... 84 | @{ 85 | if __name__ == "__main__": 86 | options = get_options() 87 | logging.basicConfig(stream=sys.stderr, level=options.verbosity) 88 | logger = logging.getLogger("test") 89 | for logger_name in (n.strip() for n in options.logger.split(',')): 90 | l = logging.getLogger(logger_name) 91 | l.setLevel(options.verbosity) 92 | logger.info(f"Setting {l}") 93 | 94 | tr = unittest.TextTestRunner() 95 | result = tr.run(suite()) 96 | logging.shutdown() 97 | sys.exit(len(result.failures) + len(result.errors)) 98 | @} 99 | -------------------------------------------------------------------------------- /tests/scripts.w: -------------------------------------------------------------------------------- 1 | Additional Scripts Testing 2 | ========================== 3 | 4 | .. test/scripts.w 5 | 6 | We provide these two additional scripts; effectively command-line short-cuts: 7 | 8 | - ``tangle.py`` 9 | 10 | - ``weave.py`` 11 | 12 | These need their own test cases. 13 | 14 | 15 | This gives us the following outline for the script testing. 16 | 17 | @o test_scripts.py 18 | @{@