├── literate ├── core │ ├── intercepts │ │ └── matplotlib.py │ ├── __init__.py │ ├── package.py │ ├── utils.py │ ├── templates.py │ ├── interceptor.py │ ├── loader.py │ └── parser.py ├── __init__.py ├── templates │ ├── scripts │ │ └── literate.coffee │ ├── document.html │ ├── content.html │ └── styles │ │ └── literate.styl └── commands │ ├── commands.py │ └── __init__.py ├── .gitignore ├── vendor ├── fonts │ ├── aller-bold.eot │ ├── aller-bold.ttf │ ├── aller-bold.woff │ ├── aller-light.eot │ ├── aller-light.ttf │ ├── aller-light.woff │ ├── novecento-bold.eot │ ├── novecento-bold.ttf │ └── novecento-bold.woff └── highlight.js │ ├── LICENSE │ ├── styles │ ├── github.css │ └── default.css │ └── highlight.pack.js ├── Makefile ├── examples └── basic │ └── example.pylit ├── LICENSE ├── setup.py ├── thoughts.txt └── README.md /literate/core/intercepts/matplotlib.py: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 -------------------------------------------------------------------------------- /literate/core/__init__.py: -------------------------------------------------------------------------------- 1 | import interceptor, loader, parser, templates, utils, package -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | *.pyc 3 | *.egg-info 4 | build 5 | other 6 | examples/*/build 7 | -------------------------------------------------------------------------------- /vendor/fonts/aller-bold.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/debrouwere/python-literate/HEAD/vendor/fonts/aller-bold.eot -------------------------------------------------------------------------------- /vendor/fonts/aller-bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/debrouwere/python-literate/HEAD/vendor/fonts/aller-bold.ttf -------------------------------------------------------------------------------- /vendor/fonts/aller-bold.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/debrouwere/python-literate/HEAD/vendor/fonts/aller-bold.woff -------------------------------------------------------------------------------- /vendor/fonts/aller-light.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/debrouwere/python-literate/HEAD/vendor/fonts/aller-light.eot -------------------------------------------------------------------------------- /vendor/fonts/aller-light.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/debrouwere/python-literate/HEAD/vendor/fonts/aller-light.ttf -------------------------------------------------------------------------------- /vendor/fonts/aller-light.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/debrouwere/python-literate/HEAD/vendor/fonts/aller-light.woff -------------------------------------------------------------------------------- /vendor/fonts/novecento-bold.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/debrouwere/python-literate/HEAD/vendor/fonts/novecento-bold.eot -------------------------------------------------------------------------------- /vendor/fonts/novecento-bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/debrouwere/python-literate/HEAD/vendor/fonts/novecento-bold.ttf -------------------------------------------------------------------------------- /vendor/fonts/novecento-bold.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/debrouwere/python-literate/HEAD/vendor/fonts/novecento-bold.woff -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | all: 2 | rm -rf ./build 3 | mkdir -p ./build/resources/{scripts,styles} 4 | coffee -cbo ./build/resources/scripts ./literate/templates/scripts 5 | stylus -o ./build/resources/styles ./literate/templates/styles -------------------------------------------------------------------------------- /literate/__init__.py: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | VERSION = (0, 0, 1) 4 | 5 | from core import loader, parser, templates, package, utils 6 | import commands 7 | 8 | # patch the `import` statement so it can handle literate python 9 | loader.patch() 10 | 11 | if __name__ == '__main__': 12 | commands.initialize() -------------------------------------------------------------------------------- /literate/templates/scripts/literate.coffee: -------------------------------------------------------------------------------- 1 | hljs.initHighlightingOnLoad() 2 | 3 | console.log 'jQ' 4 | console.log $ 5 | 6 | $(document).ready -> 7 | # code here will mainly be for switching between one-column and two-column layouts 8 | $('h1').click -> 9 | console.log 'clicked h1' 10 | $('body').toggleClass 'parallel' -------------------------------------------------------------------------------- /literate/core/package.py: -------------------------------------------------------------------------------- 1 | import templates, utils, parser 2 | import fs 3 | 4 | def create(dest): 5 | if isinstance(dest, basestring): 6 | dest = fs.Directory(dest) 7 | 8 | # create or clear destination 9 | if dest.exists: 10 | dest.remove(recursive=True) 11 | dest.create() 12 | 13 | # copy over assets 14 | assets = fs.Directory(utils.here('../build/resources')) 15 | assets.copy(dest, root=True) 16 | vendor = fs.Directory(utils.here('../vendor')) 17 | vendor.copy(fs.Directory(dest.path, 'resources'), root=True) -------------------------------------------------------------------------------- /literate/templates/document.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 |

{{ title }}

11 |
12 | {% include 'content.html' %} 13 |
14 | 15 | -------------------------------------------------------------------------------- /literate/templates/content.html: -------------------------------------------------------------------------------- 1 | {% for block in blocks %} 2 |
3 |
4 | {% for chunk in block.prose %} 5 | {{ chunk.html }} 6 | {% endfor %} 7 |
8 |
9 | {% for chunk in block.code %} 10 |
11 |
{{ chunk.text }}
12 |
13 | {% if chunk.output %} 14 |
15 |
{{ chunk.output }}
16 |
17 | {% endif %} 18 | {% endfor %} 19 |
20 |
21 | {% endfor %} -------------------------------------------------------------------------------- /examples/basic/example.pylit: -------------------------------------------------------------------------------- 1 | # Literate Python example 2 | 3 | This is a Literate Python document. 4 | 5 | 6 | Non-indented text is regular **Markdown**, 7 | but indented text is Python. For example: 8 | 9 | l = [1, 2, 3] 10 | print [float(el*3) for el in l] 11 | x = l[0] 12 | 13 | And then we can write more. 14 | 15 | And more still. 16 | 17 | print "But continue with some Python" 18 | 19 | v = " ".join(['If we', 'care to']) 20 | print v 21 | 22 | 23 | print "And two lines if we want to split up contiguous Python samples" 24 | 25 | We can also include variables, like `x` (valued **{x}**) from a couple of blocks back. 26 | 27 | If it can be stringified, we'll print it: {l} 28 | 29 | And _that's_ what Literate Python is all about. It's a really nice tool for 30 | data analysis, scientific notebooks, documentation and what-not. -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (C) 2013 by Stijn Debrouwere 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /literate/core/utils.py: -------------------------------------------------------------------------------- 1 | import os 2 | import shutil 3 | import re 4 | from copy import copy 5 | 6 | def line(string, **kwargs): 7 | string = string.format(**kwargs) 8 | string = re.sub(r'\n+', ' ', string) 9 | return re.sub(r'\s{2,}', ' ', string) 10 | 11 | def here(*segments): 12 | current = os.path.dirname(__file__) 13 | return os.path.abspath(os.path.join(current, '..', *segments)) 14 | 15 | def find(location): 16 | if os.path.isfile(location): 17 | return [location] 18 | elif os.path.isdir(location): 19 | ls = [] 20 | for root, dirs, files in os.walk('./'): 21 | ls.extend([os.path.join(root, file) for file in files]) 22 | return [file for file in ls if os.path.splitext(file)[1] in loader.EXTENSIONS] 23 | else: 24 | raise ValueError("Need a directory or a file.") 25 | 26 | def out(output, dest, stdout): 27 | if stdout: 28 | print output 29 | else: 30 | try: 31 | os.makedirs(os.path.dirname(dest)) 32 | except OSError: 33 | pass 34 | 35 | with open(dest, 'w') as f: 36 | f.write(output) -------------------------------------------------------------------------------- /literate/core/templates.py: -------------------------------------------------------------------------------- 1 | import jinja2 2 | import utils 3 | import parser 4 | 5 | loader = jinja2.FileSystemLoader(utils.here('templates')) 6 | environment = jinja2.Environment(loader=loader) 7 | 8 | def content(blocks): 9 | template = environment.get_template('content.html') 10 | return template.render(blocks=blocks) 11 | 12 | def document(title, blocks, **options): 13 | template = environment.get_template('document.html') 14 | return template.render(title=title, blocks=blocks, options=options) 15 | 16 | def pipe(): 17 | raise NotImplementedError() 18 | 19 | # REFACTOR: old code, but I want to offer similar functionality 20 | # to provide a very easy way to allow for custom templating 21 | def replace(src, body): 22 | assets = None 23 | 24 | if os.path.isfile(src): 25 | template = open(src).read() 26 | else: 27 | template = open(os.path.join(src, 'template.html')).read() 28 | assets_dir = os.path.join(src, 'assets') 29 | 30 | if os.path.exists(assets_dir): 31 | assets = assets_dir 32 | 33 | output = template.replace('
', body) 34 | 35 | return (output, assets) -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | import os 2 | from setuptools import setup, find_packages 3 | from literate import VERSION 4 | 5 | 6 | f = open(os.path.join(os.path.dirname(__file__), 'README.md')) 7 | readme = f.read() 8 | f.close() 9 | 10 | setup( 11 | name='python-literate', 12 | version=".".join(map(str, VERSION)), 13 | description='A wedding of literate programming and IPython notebooks. Create Markdown/HTML notebooks that include documentation, code and the output of that code.', 14 | long_description=readme, 15 | author='Stijn Debrouwere', 16 | author_email='stijn@stdout.be', 17 | url='http://github.com/stdbrouw/python-literate/tree/master', 18 | packages=find_packages(), 19 | zip_safe=False, 20 | classifiers=[ 21 | 'Development Status :: 4 - Beta', 22 | 'Intended Audience :: Developers', 23 | 'License :: OSI Approved :: MIT License', 24 | 'Operating System :: OS Independent', 25 | 'Programming Language :: Python', 26 | ], 27 | entry_points = { 28 | 'console_scripts': [ 29 | 'literate = literate.commands:initialize', 30 | ], 31 | }, 32 | ) 33 | 34 | -------------------------------------------------------------------------------- /literate/core/interceptor.py: -------------------------------------------------------------------------------- 1 | """ 2 | The way in which a hijack / hook / override / patch 3 | works will be different for every class or library: 4 | sometimes it's a function, sometimes a class method, 5 | sometimes an instance method, but the mechanism is 6 | identical in all cases: take an object, return a 7 | modified or different one. 8 | 9 | This way we can hook into the output of all sorts of 10 | different libraries and finetune the representation 11 | for our python-literate reports, without having 12 | to change anything to those libraries themselves. 13 | Add in images, HTML visualizations, tables and what-not. 14 | 15 | See `load.py` for proof of concept. 16 | """ 17 | 18 | import pandas as pd 19 | 20 | def hijack(cls): 21 | class Sub(cls): 22 | def plot(self, *vargs, **kwargs): 23 | return 'hijack!' 24 | return Sub 25 | 26 | hijacks = [] 27 | 28 | def clshijack(cls): 29 | _plot = cls.plot 30 | def plot(*vargs, **kwargs): 31 | res = _plot(*vargs, **kwargs) 32 | hijacks.append(res) 33 | return res 34 | 35 | cls.plot = plot 36 | return cls 37 | 38 | pd.Series = clshijack(pd.Series) -------------------------------------------------------------------------------- /literate/commands/commands.py: -------------------------------------------------------------------------------- 1 | import fs 2 | from literate import parser, templates, package 3 | 4 | def run(options): 5 | for path in options.src: 6 | source = fs.File(path).read() 7 | parser.run(source) 8 | 9 | def untangle(options): 10 | raise NotImplementedError() 11 | 12 | def weave(options): 13 | src = fs.File(options.src) 14 | dest = fs.Directory(options.dest) 15 | 16 | # find literate python files 17 | if src.is_file: 18 | documents = [src] 19 | else: 20 | # TODO: specify whether we should search recursively or not 21 | # recursive=options.recursive 22 | # TODO: support for Literate Python with docstrings 23 | documents = fs.Directory(src.path).find('*.pylit') # '*.py.md', '.md.py' 24 | 25 | package.create(dest) 26 | 27 | # weave literate python 28 | for doc in documents: 29 | raw = doc.read() 30 | blocks = parser.weave(raw, evaluate=options.evaluate) 31 | # template=None 32 | # TODO: template should be handled differently depending on whether 33 | # it's a Jinja template (.html) or an executable that we should 34 | # pass the context (w/ simplify=True above) 35 | html = templates.document(doc.name, blocks, prose=options.prose, capture=options.capture) 36 | filename = doc.name + '.html' 37 | f = fs.File(dest.path, filename) 38 | f.write(html) 39 | 40 | # documents are groups of blocks 41 | #literate.package(documents, options.dest) -------------------------------------------------------------------------------- /vendor/highlight.js/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2006, Ivan Sagalaev 2 | All rights reserved. 3 | Redistribution and use in source and binary forms, with or without 4 | modification, are permitted provided that the following conditions are met: 5 | 6 | * Redistributions of source code must retain the above copyright 7 | notice, this list of conditions and the following disclaimer. 8 | * Redistributions in binary form must reproduce the above copyright 9 | notice, this list of conditions and the following disclaimer in the 10 | documentation and/or other materials provided with the distribution. 11 | * Neither the name of highlight.js nor the names of its contributors 12 | may be used to endorse or promote products derived from this software 13 | without specific prior written permission. 14 | 15 | THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND ANY 16 | EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 17 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 18 | DISCLAIMED. IN NO EVENT SHALL THE REGENTS AND CONTRIBUTORS BE LIABLE FOR ANY 19 | DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 20 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 21 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 22 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 23 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 24 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 25 | -------------------------------------------------------------------------------- /literate/templates/styles/literate.styl: -------------------------------------------------------------------------------- 1 | @font-face 2 | font-family: 'novecento-bold' 3 | src: url('../vendor/fonts/novecento-bold.eot') 4 | src: url('../vendor/fonts/novecento-bold.eot?#iefix') format('embedded-opentype') 5 | src: url('../vendor/fonts/novecento-bold.woff') format('woff') 6 | src: url('../vendor/fonts/novecento-bold.ttf') format('truetype') 7 | font-weight: normal 8 | font-style: normal 9 | 10 | h1, h2, h3 11 | font-family: 'novecento-bold' 12 | font-weight: normal 13 | 14 | body 15 | background: white 16 | width: 720px 17 | margin: 20px auto 18 | font-family: palatino, helvetica, arial, serif 19 | 20 | p 21 | font-size: 18px 22 | line-height: 1.4 23 | 24 | blockquote, pre 25 | margin: 0 0 0 0 26 | 27 | blockquote.source, blockquote.output 28 | padding: 0.6em 29 | 30 | div.code 31 | box-shadow: 0 0 10px #ddd inset 32 | 33 | pre, pre code 34 | font-family: monaco 35 | font-size: 12px 36 | 37 | blockquote.source 38 | &, code 39 | background: rgba(255, 255, 255, 0.2) 40 | 41 | border-left: 2px solid #ccc 42 | 43 | pre code 44 | padding: 0 45 | 46 | blockquote p 47 | margin: 0 48 | 49 | div.code blockquote.output 50 | font-size: 14px 51 | background: rgba(0, 0, 0, 0.075) 52 | border-left: 2px solid #999 53 | 54 | body.parallel 55 | background: #fff 56 | width: 960px 57 | 58 | div.block 59 | float: left 60 | margin-bottom: 20px 61 | 62 | div 63 | float: left 64 | 65 | div.prose 66 | width: 400px 67 | margin-right: 20px 68 | 69 | div.code 70 | width: 540px -------------------------------------------------------------------------------- /literate/core/loader.py: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | import __builtin__ 4 | import os 5 | from tempfile import NamedTemporaryFile 6 | import py_compile 7 | import parser 8 | from contextlib import contextmanager 9 | 10 | builtin_import = __builtin__.__import__ 11 | EXTENSIONS = ('.py.md', '.pylit', ) 12 | 13 | 14 | @contextmanager 15 | def tempfile(data): 16 | temp = NamedTemporaryFile(delete=False) 17 | temp.write(data) 18 | temp.close() 19 | yield temp.name 20 | os.unlink(temp.name) 21 | 22 | 23 | def exists(name): 24 | for extension in EXTENSIONS: 25 | path = name.replace('.', '/') + extension 26 | if os.path.exists(path) is True: 27 | return (name, path) 28 | return False 29 | 30 | 31 | def importer(name, globals=globals(), locals=locals(), fromlist=[], level=-1): 32 | try: 33 | return builtin_import(name, globals, locals, fromlist, level) 34 | except ImportError as error: 35 | location = exists(name) 36 | if location: 37 | name, path = location 38 | source = open(path).read() 39 | code = parser.python(source) 40 | dest = name + '.py' 41 | cache_dest = name + '.pyc' 42 | 43 | # the importer doesn't create plain `.py` files by itself, but 44 | # the pylit executable can (if asked) and if they're there, we 45 | # keep them up to date 46 | if os.path.exists(dest): 47 | with open(dest, 'w'): 48 | dest.write(code) 49 | else: 50 | with tempfile(code) as buff: 51 | py_compile.compile(buff, cache_dest, path, doraise=True) 52 | 53 | return builtin_import(name) 54 | else: 55 | raise error 56 | 57 | 58 | def patch(): 59 | __builtin__.__import__ = importer -------------------------------------------------------------------------------- /vendor/highlight.js/styles/github.css: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | github.com style (c) Vasily Polovnyov 4 | 5 | */ 6 | 7 | pre code { 8 | display: block; padding: 0.5em; 9 | color: #333; 10 | background: #f8f8ff 11 | } 12 | 13 | pre .comment, 14 | pre .template_comment, 15 | pre .diff .header, 16 | pre .javadoc { 17 | color: #998; 18 | font-style: italic 19 | } 20 | 21 | pre .keyword, 22 | pre .css .rule .keyword, 23 | pre .winutils, 24 | pre .javascript .title, 25 | pre .nginx .title, 26 | pre .subst, 27 | pre .request, 28 | pre .status { 29 | color: #333; 30 | font-weight: bold 31 | } 32 | 33 | pre .number, 34 | pre .hexcolor, 35 | pre .ruby .constant { 36 | color: #099; 37 | } 38 | 39 | pre .string, 40 | pre .tag .value, 41 | pre .phpdoc, 42 | pre .tex .formula { 43 | color: #d14 44 | } 45 | 46 | pre .title, 47 | pre .id { 48 | color: #900; 49 | font-weight: bold 50 | } 51 | 52 | pre .javascript .title, 53 | pre .lisp .title, 54 | pre .clojure .title, 55 | pre .subst { 56 | font-weight: normal 57 | } 58 | 59 | pre .class .title, 60 | pre .haskell .type, 61 | pre .vhdl .literal, 62 | pre .tex .command { 63 | color: #458; 64 | font-weight: bold 65 | } 66 | 67 | pre .tag, 68 | pre .tag .title, 69 | pre .rules .property, 70 | pre .django .tag .keyword { 71 | color: #000080; 72 | font-weight: normal 73 | } 74 | 75 | pre .attribute, 76 | pre .variable, 77 | pre .lisp .body { 78 | color: #008080 79 | } 80 | 81 | pre .regexp { 82 | color: #009926 83 | } 84 | 85 | pre .class { 86 | color: #458; 87 | font-weight: bold 88 | } 89 | 90 | pre .symbol, 91 | pre .ruby .symbol .string, 92 | pre .lisp .keyword, 93 | pre .tex .special, 94 | pre .prompt { 95 | color: #990073 96 | } 97 | 98 | pre .built_in, 99 | pre .lisp .title, 100 | pre .clojure .built_in { 101 | color: #0086b3 102 | } 103 | 104 | pre .preprocessor, 105 | pre .pi, 106 | pre .doctype, 107 | pre .shebang, 108 | pre .cdata { 109 | color: #999; 110 | font-weight: bold 111 | } 112 | 113 | pre .deletion { 114 | background: #fdd 115 | } 116 | 117 | pre .addition { 118 | background: #dfd 119 | } 120 | 121 | pre .diff .change { 122 | background: #0086b3 123 | } 124 | 125 | pre .chunk { 126 | color: #aaa 127 | } 128 | -------------------------------------------------------------------------------- /thoughts.txt: -------------------------------------------------------------------------------- 1 | TODO: figure out a better name than "untangle" 2 | while it describes the process, it doesn't describe 3 | the fact that the Markdown and HTML files are rendered 4 | results, not just a part of the original .pylit we 5 | extracted. `render` comes closer, but also not quite. 6 | `run` doesn't have the connotation of generating output 7 | 8 | noweb has `notangle` (produce code) and `noweave` (produce documentation) 9 | (so you can do notangle myliterate.py | xargs python) 10 | Sweave similarly has tangle and weave commands. 11 | 12 | `unweave` perhaps has a more physical connotation than `untangle`. 13 | Or perhaps it's really "untangling" (the code) but "weaving together" (a report). 14 | 15 | If `untangle` produced any Markdown, to be consistent with the metaphor 16 | it should give you the raw thing, either without any code (.md.py) or with the code unrun (.pylit) 17 | 18 | And if we want to give `literate` the option to run your code through Python, 19 | maybe don't have a subcommand but just 20 | 21 | literate code.pylit 22 | 23 | or perhaps (if having it all as subcommands is more elegant) 24 | 25 | literate python code.pylit 26 | 27 | and 28 | 29 | literate weave code.pylit --formats md html 30 | 31 | and I don't think we really need a command that does both at the same time. 32 | 33 | --- 34 | 35 | literate weave -o reports myreport.pylit -i $input 36 | 37 | # this is kind of problematic, isn't it? 38 | # do parameters go to `weave` (as usual) 39 | # or to `.pylit` and what if they clash? 40 | # --- 41 | # should such a file specify `#!bin/env literate weave` 42 | # at the top? 43 | # --- 44 | # interestingly enough, the behavior of the `python` 45 | # executable is to take anything before a file name 46 | # as python parameters, everything else as script parameters 47 | # 48 | # I suppose that could work 49 | 50 | --- 51 | 52 | Docco CLI options are well thought-out: 53 | 54 | Usage: docco [options] FILES 55 | 56 | Options: 57 | 58 | -h, --help output usage information 59 | -V, --version output the version number 60 | -l, --layout [layout] choose a built-in layouts (parallel, linear) 61 | -c, --css [file] use a custom css file 62 | -o, --output [path] use a custom output path 63 | -t, --template [file] use a custom .jst template 64 | -e, --extension [ext] use the given file extension for all inputs -------------------------------------------------------------------------------- /literate/commands/__init__.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | import commands 3 | 4 | def initialize(): 5 | arguments = argparse.ArgumentParser() 6 | subparsers = arguments.add_subparsers() 7 | 8 | run = subparsers.add_parser('run') 9 | run.set_defaults(func=commands.run, help="run a Literate Python script") 10 | run.add_argument('src', nargs=1) 11 | 12 | weave = subparsers.add_parser('weave', 13 | help="create HTML notebooks or an intermediate Markdown representation \ 14 | out of Literate Python scripts") 15 | weave.set_defaults(func=commands.weave) 16 | weave.add_argument('src', 17 | default='./', nargs='?', 18 | help="source file or directory") 19 | weave.add_argument('dest', 20 | default='./notebook', nargs='?', 21 | help="destination file or directory") 22 | weave.add_argument('-t', '--template', 23 | help="pass woven files to an executable instead of the default templating engine") 24 | weave.add_argument('-r', '--recursive', 25 | default=False, action='store_true', 26 | help="look for .pylit files in subdirectories") 27 | weave.add_argument('-e', '--evaluate', 28 | default=False, action='store_true', 29 | help="evaluate the code and make global variables available to the prose") 30 | weave.add_argument('-c', '--capture', 31 | default=False, action='store_true', 32 | help="capture and display the code's output") 33 | weave.add_argument('-p', '--prose', 34 | default=False, action='store_true', 35 | help="display only prose, not code") 36 | 37 | untangle = subparsers.add_parser('untangle', 38 | help="untangle code from documentation and write away both to separate files") 39 | untangle.set_defaults(func=commands.untangle) 40 | untangle.add_argument('-f', '--formats', 41 | default=['md', 'py'], nargs='*', 42 | help="what formats you'd like to output") 43 | untangle.add_argument('-p', '--print', 44 | dest='stdout', default=False, action='store_true', 45 | help="print to stdout (only useful when you've selected \ 46 | a single output format)") 47 | untangle.add_argument('-r', '--recursive', 48 | default=False, action='store_true', 49 | help="look for .pylit files in subdirectories") 50 | 51 | args = arguments.parse_args() 52 | args.func(args) -------------------------------------------------------------------------------- /vendor/highlight.js/styles/default.css: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Original style from softwaremaniacs.org (c) Ivan Sagalaev 4 | 5 | */ 6 | 7 | pre code { 8 | display: block; padding: 0.5em; 9 | background: #F0F0F0; 10 | } 11 | 12 | pre code, 13 | pre .subst, 14 | pre .tag .title, 15 | pre .lisp .title, 16 | pre .clojure .built_in, 17 | pre .nginx .title { 18 | color: black; 19 | } 20 | 21 | pre .string, 22 | pre .title, 23 | pre .constant, 24 | pre .parent, 25 | pre .tag .value, 26 | pre .rules .value, 27 | pre .rules .value .number, 28 | pre .preprocessor, 29 | pre .ruby .symbol, 30 | pre .ruby .symbol .string, 31 | pre .aggregate, 32 | pre .template_tag, 33 | pre .django .variable, 34 | pre .smalltalk .class, 35 | pre .addition, 36 | pre .flow, 37 | pre .stream, 38 | pre .bash .variable, 39 | pre .apache .tag, 40 | pre .apache .cbracket, 41 | pre .tex .command, 42 | pre .tex .special, 43 | pre .erlang_repl .function_or_atom, 44 | pre .markdown .header { 45 | color: #800; 46 | } 47 | 48 | pre .comment, 49 | pre .annotation, 50 | pre .template_comment, 51 | pre .diff .header, 52 | pre .chunk, 53 | pre .markdown .blockquote { 54 | color: #888; 55 | } 56 | 57 | pre .number, 58 | pre .date, 59 | pre .regexp, 60 | pre .literal, 61 | pre .smalltalk .symbol, 62 | pre .smalltalk .char, 63 | pre .go .constant, 64 | pre .change, 65 | pre .markdown .bullet, 66 | pre .markdown .link_url { 67 | color: #080; 68 | } 69 | 70 | pre .label, 71 | pre .javadoc, 72 | pre .ruby .string, 73 | pre .decorator, 74 | pre .filter .argument, 75 | pre .localvars, 76 | pre .array, 77 | pre .attr_selector, 78 | pre .important, 79 | pre .pseudo, 80 | pre .pi, 81 | pre .doctype, 82 | pre .deletion, 83 | pre .envvar, 84 | pre .shebang, 85 | pre .apache .sqbracket, 86 | pre .nginx .built_in, 87 | pre .tex .formula, 88 | pre .erlang_repl .reserved, 89 | pre .prompt, 90 | pre .markdown .link_label, 91 | pre .vhdl .attribute, 92 | pre .clojure .attribute, 93 | pre .coffeescript .property { 94 | color: #88F 95 | } 96 | 97 | pre .keyword, 98 | pre .id, 99 | pre .phpdoc, 100 | pre .title, 101 | pre .built_in, 102 | pre .aggregate, 103 | pre .css .tag, 104 | pre .javadoctag, 105 | pre .phpdoc, 106 | pre .yardoctag, 107 | pre .smalltalk .class, 108 | pre .winutils, 109 | pre .bash .variable, 110 | pre .apache .tag, 111 | pre .go .typename, 112 | pre .tex .command, 113 | pre .markdown .strong, 114 | pre .request, 115 | pre .status { 116 | font-weight: bold; 117 | } 118 | 119 | pre .markdown .emphasis { 120 | font-style: italic; 121 | } 122 | 123 | pre .nginx .built_in { 124 | font-weight: normal; 125 | } 126 | 127 | pre .coffeescript .javascript, 128 | pre .javascript .xml, 129 | pre .tex .formula, 130 | pre .xml .javascript, 131 | pre .xml .vbscript, 132 | pre .xml .css, 133 | pre .xml .cdata { 134 | opacity: 0.5; 135 | } 136 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Literate helps you author reproducible HTML reports from Python. Integrate Python code and Markdown documentation in `.pylit` files (or `.py.md` if you prefer.) Literate will capture the output of your code and weave it in with your thoughts and the code itself. 2 | 3 | Here's what a `.pylit` document can look like: 4 | 5 | numbers = [1, 2, 3] 6 | print [float(number * 3) for number in numbers] 7 | first, middle, last = numbers 8 | 9 | And then we can write more, and talk about our calculations 10 | like {first} and {last}. 11 | 12 | Indented text is Python. Non-indented text is Markdown. You can use any 13 | variables from Python in Markdown, and they will be interpolated like 14 | you're used to from regular Python string formatting. 15 | 16 | We can run that through Literate with the command: 17 | 18 | ./literate untangle myreport.pylit --capture --print 19 | 20 | Or save it to somewhere: 21 | 22 | ./literate untangle ./myreport.pylit ./build --capture 23 | 24 | The output will look like: 25 | 26 | > Here's what a `.pylit` document can look like: 27 | > 28 | > > numbers = [1, 2, 3] 29 | > > print [float(number * 3) for number in numbers] 30 | > > first, middle, last = numbers 31 | > > `[3.0, 6.0, 9.0]` 32 | > 33 | > And then we can write more, and talk about our calculations 34 | > like 1 and 3. 35 | 36 | If you want to capture output (like in the example above), pass the `--capture` flag 37 | to the command line interface. It's great for research notebooks, and akin to 38 | IPython notebooks, R Markdown, Sweave and Pweave. 39 | 40 | For regular literate programming (just code and your explanations of that code) 41 | simply leave off the `--capture` flag: it's what Literate does by default. 42 | 43 | ## The command-line interface 44 | 45 | To find out more about how to use the command-line interface, try: 46 | 47 | ./literate untangle --help 48 | ./literate run --help 49 | 50 | ## Working with Literate Python files 51 | 52 | The Python interpreter doesn't understand Literate Python by default, but that's easily fixed: 53 | 54 | # add support for importing Literate Python 55 | # and then import our `myreport.pylit` file 56 | # like a regular Python source file 57 | import literate 58 | import myreport 59 | 60 | Just like `.py` files generate `.pyc` bytecode, `.pylit` files sometimes generate 61 | a `.py` representation. Running `literate untangle myreport.pylit` or doing 62 | `import myreport` produces a `myreport.py` file. 63 | 64 | **Never put source code in, or edit, a `.py` file if there's a `.pylit` file 65 | with the same basename. It may be overwritten and you will lose whatever code 66 | you had in your `.py` file.** 67 | 68 | ## Output formats 69 | 70 | Literate can output to `.py`, `.md` and `.html`. HTML output is very useful for simple reports. 71 | 72 | ./literate untangle examples/basic examples/basic/build \ 73 | --template examples/basic/template.html \ 74 | --formats html \ 75 | --capture 76 | 77 | There's a very basic HTML template built into Literate, but you can also use your own. 78 | Use the `--template` flag, e.g. `--template mytemplate.html`. Your template file should 79 | contain a `
` tag, which is where Literate will put your generated report. 80 | 81 | For finer control over layout and report rendering, use the Markdown output (`--formats md`) which 82 | will contain code, documentation and captured output. With those Markdown files, build your own 83 | reports using the site generator of your choice. Jekyll's a good one. 84 | 85 | ## Does it do... 86 | 87 | Literate Python is inspired on Literate CoffeeScript rather than the traditional Sweave syntax. 88 | You won't get fine-grained control over which individual chunk to run or display because I 89 | don't believe that's how literate reports should work. 90 | 91 | Don't include code in a `.pylit` file you don't want in the generated output but instead 92 | put it in library files, it's as simple as that. 93 | 94 | ## Roadmap 95 | 96 | ### 1.0rc 97 | 98 | * fix './' (which should be the CWD, not the dir of the literate script) 99 | * tests 100 | * Also, when using the Literate style, add Markdown to the Python output 101 | in docstrings. 102 | * Python output hooks 103 | - initial support for `matplotlib` plots 104 | * different weaving and untangling modes 105 | weave 106 | - literate (code + interpolated documentation) 107 | - --prose (interpolated documentation) 108 | - --notebook (code + output + interpolated documentation) 109 | untangle (--formats) 110 | - md 111 | - py 112 | * Make it possible to pass on woven files to a templating language executable 113 | Ideally: `literate weave . --template jade --object {context} --output build` 114 | (with {context} and {contextFile} variables) 115 | * A good default template 116 | - dynamic one-column / two-column 117 | - works on directories (includes a TOC) 118 | * Review code and put it up on PyPI 119 | 120 | ### Other 121 | 122 | * A Sublime Text syntax highlighter 123 | 124 | ### Future 125 | 126 | * Support for plain text (may make it easier for people who 127 | want to use formats other than those we support to DIY) 128 | * Support for D3 plots, tables and other visualizations 129 | * Support for `.md.py` (Python with Markdown docstrings) 130 | (I suppose the catch is that the one works with Python out of the box, 131 | the other works with Markdown out of the box...) -------------------------------------------------------------------------------- /vendor/highlight.js/highlight.pack.js: -------------------------------------------------------------------------------- 1 | var hljs=new function(){function l(o){return o.replace(/&/gm,"&").replace(//gm,">")}function b(p){for(var o=p.firstChild;o;o=o.nextSibling){if(o.nodeName=="CODE"){return o}if(!(o.nodeType==3&&o.nodeValue.match(/\s+/))){break}}}function h(p,o){return Array.prototype.map.call(p.childNodes,function(q){if(q.nodeType==3){return o?q.nodeValue.replace(/\n/g,""):q.nodeValue}if(q.nodeName=="BR"){return"\n"}return h(q,o)}).join("")}function a(q){var p=(q.className+" "+q.parentNode.className).split(/\s+/);p=p.map(function(r){return r.replace(/^language-/,"")});for(var o=0;o"}while(x.length||v.length){var u=t().splice(0,1)[0];y+=l(w.substr(p,u.offset-p));p=u.offset;if(u.event=="start"){y+=s(u.node);r.push(u.node)}else{if(u.event=="stop"){var o,q=r.length;do{q--;o=r[q];y+=("")}while(o!=u.node);r.splice(q,1);while(q'+L[0]+""}else{r+=L[0]}N=A.lR.lastIndex;L=A.lR.exec(K)}return r+K.substr(N)}function z(){if(A.sL&&!e[A.sL]){return l(w)}var r=A.sL?d(A.sL,w):g(w);if(A.r>0){v+=r.keyword_count;B+=r.r}return''+r.value+""}function J(){return A.sL!==undefined?z():G()}function I(L,r){var K=L.cN?'':"";if(L.rB){x+=K;w=""}else{if(L.eB){x+=l(r)+K;w=""}else{x+=K;w=r}}A=Object.create(L,{parent:{value:A}});B+=L.r}function C(K,r){w+=K;if(r===undefined){x+=J();return 0}var L=o(r,A);if(L){x+=J();I(L,r);return L.rB?0:r.length}var M=s(A,r);if(M){if(!(M.rE||M.eE)){w+=r}x+=J();do{if(A.cN){x+=""}A=A.parent}while(A!=M.parent);if(M.eE){x+=l(r)}w="";if(M.starts){I(M.starts,"")}return M.rE?0:r.length}if(t(r,A)){throw"Illegal"}w+=r;return r.length||1}var F=e[D];f(F);var A=F;var w="";var B=0;var v=0;var x="";try{var u,q,p=0;while(true){A.t.lastIndex=p;u=A.t.exec(E);if(!u){break}q=C(E.substr(p,u.index-p),u[0]);p=u.index+q}C(E.substr(p));return{r:B,keyword_count:v,value:x,language:D}}catch(H){if(H=="Illegal"){return{r:0,keyword_count:0,value:l(E)}}else{throw H}}}function g(s){var o={keyword_count:0,r:0,value:l(s)};var q=o;for(var p in e){if(!e.hasOwnProperty(p)){continue}var r=d(p,s);r.language=p;if(r.keyword_count+r.r>q.keyword_count+q.r){q=r}if(r.keyword_count+r.r>o.keyword_count+o.r){q=o;o=r}}if(q.language){o.second_best=q}return o}function i(q,p,o){if(p){q=q.replace(/^((<[^>]+>|\t)+)/gm,function(r,v,u,t){return v.replace(/\t/g,p)})}if(o){q=q.replace(/\n/g,"
")}return q}function m(r,u,p){var v=h(r,p);var t=a(r);if(t=="no-highlight"){return}var w=t?d(t,v):g(v);t=w.language;var o=c(r);if(o.length){var q=document.createElement("pre");q.innerHTML=w.value;w.value=j(o,c(q),v)}w.value=i(w.value,u,p);var s=r.className;if(!s.match("(\\s|^)(language-)?"+t+"(\\s|$)")){s=s?(s+" "+t):t}r.innerHTML=w.value;r.className=s;r.result={language:t,kw:w.keyword_count,re:w.r};if(w.second_best){r.second_best={language:w.second_best.language,kw:w.second_best.keyword_count,re:w.second_best.r}}}function n(){if(n.called){return}n.called=true;Array.prototype.map.call(document.getElementsByTagName("pre"),b).filter(Boolean).forEach(function(o){m(o,hljs.tabReplace)})}function k(){window.addEventListener("DOMContentLoaded",n,false);window.addEventListener("load",n,false)}var e={};this.LANGUAGES=e;this.highlight=d;this.highlightAuto=g;this.fixMarkup=i;this.highlightBlock=m;this.initHighlighting=n;this.initHighlightingOnLoad=k;this.IR="[a-zA-Z][a-zA-Z0-9_]*";this.UIR="[a-zA-Z_][a-zA-Z0-9_]*";this.NR="\\b\\d+(\\.\\d+)?";this.CNR="(\\b0[xX][a-fA-F0-9]+|(\\b\\d+(\\.\\d*)?|\\.\\d+)([eE][-+]?\\d+)?)";this.BNR="\\b(0b[01]+)";this.RSR="!|!=|!==|%|%=|&|&&|&=|\\*|\\*=|\\+|\\+=|,|\\.|-|-=|/|/=|:|;|<|<<|<<=|<=|=|==|===|>|>=|>>|>>=|>>>|>>>=|\\?|\\[|\\{|\\(|\\^|\\^=|\\||\\|=|\\|\\||~";this.BE={b:"\\\\[\\s\\S]",r:0};this.ASM={cN:"string",b:"'",e:"'",i:"\\n",c:[this.BE],r:0};this.QSM={cN:"string",b:'"',e:'"',i:"\\n",c:[this.BE],r:0};this.CLCM={cN:"comment",b:"//",e:"$"};this.CBLCLM={cN:"comment",b:"/\\*",e:"\\*/"};this.HCM={cN:"comment",b:"#",e:"$"};this.NM={cN:"number",b:this.NR,r:0};this.CNM={cN:"number",b:this.CNR,r:0};this.BNM={cN:"number",b:this.BNR,r:0};this.inherit=function(q,r){var o={};for(var p in q){o[p]=q[p]}if(r){for(var p in r){o[p]=r[p]}}return o}}();hljs.LANGUAGES.python=function(a){var f={cN:"prompt",b:"^(>>>|\\.\\.\\.) "};var c=[{cN:"string",b:"(u|b)?r?'''",e:"'''",c:[f],r:10},{cN:"string",b:'(u|b)?r?"""',e:'"""',c:[f],r:10},{cN:"string",b:"(u|r|ur)'",e:"'",c:[a.BE],r:10},{cN:"string",b:'(u|r|ur)"',e:'"',c:[a.BE],r:10},{cN:"string",b:"(b|br)'",e:"'",c:[a.BE]},{cN:"string",b:'(b|br)"',e:'"',c:[a.BE]}].concat([a.ASM,a.QSM]);var e={cN:"title",b:a.UIR};var d={cN:"params",b:"\\(",e:"\\)",c:["self",a.CNM,f].concat(c)};var b={bWK:true,e:":",i:"[${=;\\n]",c:[e,d],r:10};return{k:{keyword:"and elif is global as in if from raise for except finally print import pass return exec else break not with class assert yield try while continue del or def lambda nonlocal|10",built_in:"None True False Ellipsis NotImplemented"},i:"(|\\?)",c:c.concat([f,a.HCM,a.inherit(b,{cN:"function",k:"def"}),a.inherit(b,{cN:"class",k:"class"}),a.CNM,{cN:"decorator",b:"@",e:"$"},{b:"\\b(print|exec)\\("}])}}(hljs); -------------------------------------------------------------------------------- /literate/core/parser.py: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | import sys 4 | import utils 5 | from cStringIO import StringIO 6 | import markdown as md 7 | import re 8 | 9 | 10 | def br(string): 11 | # in Markdown, ending a line with more than one space 12 | # signifies a (single) line break 13 | return re.sub('\n{1}', ' \n', string) 14 | 15 | def pad(string, padding): 16 | pattern = re.compile(r'^', re.MULTILINE) 17 | return re.sub(pattern, padding, string) 18 | 19 | def strip(l): 20 | while len(l) and not len(l[0]): 21 | l = l[1:] 22 | while len(l) and not len(l[-1]): 23 | l = l[:-1] 24 | return l 25 | 26 | def issibling(a, b): 27 | return a.__class__ == b.__class__ 28 | 29 | class Chunk(object): 30 | def __init__(self, raw, variables={}): 31 | self.raw = [raw] 32 | self.variables = variables 33 | self.output = False 34 | 35 | def append(self, line): 36 | self.raw.append(line) 37 | 38 | @property 39 | def text(self): 40 | return "\n".join(self.lines) 41 | 42 | def html(self, run): 43 | return md.markdown(self.markdown(run)) 44 | 45 | @property 46 | def name(self): 47 | return self.__class__.__name__.lower().replace('chunk', '') 48 | 49 | def __repr__(self): 50 | return "<{kind} [{lines}]>".format(kind=self.name, lines=len(self.lines)) 51 | 52 | @property 53 | def is_empty(self): 54 | return not len(self.lines) 55 | 56 | 57 | class PythonChunk(Chunk): 58 | def parse(self): 59 | if self.output is not False: 60 | return self.output 61 | 62 | stdout = sys.stdout 63 | sys.stdout = StringIO() 64 | exec self.text 65 | self.output = sys.stdout.getvalue() 66 | sys.stdout = stdout 67 | self.variables.update(locals()) 68 | 69 | return self.output 70 | 71 | @property 72 | def lines(self): 73 | _lines = [re.sub('^( {4}|\t)', '', line) for line in self.raw] 74 | return strip(_lines) 75 | 76 | # THOUGHT: I think I want to leave it up to the templating layer 77 | # to decide how to display the output. So we'll pass on chunks 78 | # w/ {type, raw, html, output} 79 | def markdown(self, run): 80 | code = pad(self.text, ' ') 81 | if run: 82 | output = pad(br(self.parse()), '> ') 83 | return "\n\n".join([code, output]) 84 | else: 85 | return code 86 | 87 | # See note above. 88 | def serialize(self): 89 | return { 90 | 'type': self.name, 91 | 'text': self.text, 92 | 'html': False, 93 | 'output': self.output, 94 | } 95 | 96 | class MarkdownChunk(Chunk): 97 | def parse(self): 98 | if hasattr(self, '_parsed'): 99 | return self._parsed 100 | 101 | self._parsed = md.markdown(self.text) 102 | return self._parsed 103 | 104 | @property 105 | def lines(self): 106 | return strip(self.raw) 107 | 108 | # THOUGHT: I think I want to leave it up to the templating layer 109 | # to decide how to display the output. So we'll pass on chunks 110 | # w/ {type, raw, html, output} 111 | def markdown(self, run=True): 112 | if run: 113 | try: 114 | return self.text.format(**self.variables) 115 | except KeyError, name: 116 | raise KeyError(utils.line(""" 117 | You referenced {name} in your literate document, 118 | but no such variable was found. Did you enable 119 | code evaluation?".format(name=name)) 120 | """, name=name)) 121 | else: 122 | return self.text 123 | 124 | # See note above. 125 | def serialize(self): 126 | return { 127 | 'type': self.name, 128 | 'raw': self.text, 129 | 'html': self.html(True), 130 | 'output': False, 131 | } 132 | 133 | class Document(object): 134 | def weave(self, evaluate=True, simplify=False): 135 | blocks = [] 136 | variables = {} 137 | for chunk in self.chunks: 138 | if evaluate: 139 | # pre-parse our chunks, and give them the combined 140 | # state of all previously parsed chunks too 141 | chunk.variables.update(variables) 142 | chunk.parse() 143 | variables.update(chunk.variables) 144 | 145 | pairs = [] 146 | pair = {} 147 | for chunk in self.chunks: 148 | if simplify: 149 | data = chunk.serialize() 150 | else: 151 | data = chunk 152 | 153 | if isinstance(chunk, MarkdownChunk) and len(pair): 154 | pairs.append(pair) 155 | pair = {} 156 | 157 | if isinstance(chunk, MarkdownChunk): 158 | pair.setdefault('prose', []).append(data) 159 | else: 160 | pair.setdefault('code', []).append(data) 161 | 162 | prev = chunk 163 | 164 | pairs.append(pair) 165 | return pairs 166 | 167 | def untangle(self): 168 | types = {'python': '', 'markdown': ''} 169 | 170 | for chunk in self.chunks: 171 | types[chunk.name] += chunk.text + '\n' 172 | 173 | return types 174 | 175 | def run(self): 176 | code = self.untangle()['python'] 177 | exec code 178 | 179 | def __init__(self, raw): 180 | self.raw = raw 181 | self.tokenize() 182 | 183 | 184 | class LiterateDocument(Document): 185 | def categorize(self, line): 186 | if line.startswith('\t') or line.startswith(' '): 187 | return PythonChunk 188 | else: 189 | return MarkdownChunk 190 | 191 | def tokenize(self): 192 | lines = self.raw.strip().split('\n') 193 | chunks = [] 194 | 195 | for i, line in enumerate(lines): 196 | Chunk = self.categorize(line) 197 | 198 | if len(chunks): 199 | prev = lines[i-1].strip() 200 | cur = lines[i].strip() 201 | same_kind = isinstance(chunks[-1], Chunk) 202 | whitespace = len(cur) is 0 and not len(prev) is 0 203 | double_space = len(cur) is 0 and len(prev) is 0 204 | same_kind = same_kind or whitespace 205 | continuation = same_kind and not double_space 206 | else: 207 | continuation = False 208 | 209 | if continuation: 210 | chunks[-1].append(line) 211 | else: 212 | chunks.append(Chunk(line)) 213 | 214 | self.chunks = filter(lambda chunk: not chunk.is_empty, chunks) 215 | 216 | def untangle(self, pure=False): 217 | types = super(LiterateDocument, self).untangle() 218 | # pure means what we remove any python code from our markdown 219 | # document (this is usually not what you want) 220 | if not pure: 221 | types['markdown'] = self.raw 222 | 223 | return types 224 | 225 | class DocStringDocument(Document): 226 | def untangle(self, pure=False): 227 | types = super(DocStringDocument, self).untangle() 228 | # pure means what we remove any docstrings from our python 229 | # code (this is usually not what you want) 230 | if not pure: 231 | types['python'] = self.raw 232 | 233 | return types 234 | 235 | def weave(string, evaluate=True, simplify=True): 236 | document = LiterateDocument(string) 237 | return document.weave(evaluate=evaluate, simplify=simplify) 238 | 239 | def untangle(string): 240 | document = LiterateDocument(string) 241 | return document.untangle() 242 | 243 | def run(string): 244 | document = LiterateDocument(string) 245 | document.run() --------------------------------------------------------------------------------