diff options
| author | Nick Fitzgerald <fitzgen@gmail.com> | 2010-06-29 21:18:53 -0700 | 
|---|---|---|
| committer | Nick Fitzgerald <fitzgen@gmail.com> | 2010-06-29 21:18:53 -0700 | 
| commit | 993fbde068c7cad977aa2be857237f6ab7d018f1 (patch) | |
| tree | 6162725ff340ccd8d3d0c41969545ad835305f13 /pocco | |
| parent | d22799ca1d63e55d88fe0a3adf4c71904a591012 (diff) | |
| download | pycco-993fbde068c7cad977aa2be857237f6ab7d018f1.tar.gz pycco-993fbde068c7cad977aa2be857237f6ab7d018f1.zip  | |
Fixing up pocco to work with files that dont have an extension and adding the setup.py script
Diffstat (limited to 'pocco')
| -rwxr-xr-x | pocco | 218 | 
1 files changed, 218 insertions, 0 deletions
@@ -0,0 +1,218 @@ +#!/usr/bin/env python + +# **Pocco** is a Python port of [Docco](http://jashkenas.github.com/docco/ ): +# the original quick-and-dirty, hundred-line-long, literate-programming-style +# documentation generator. It produces HTML that displays your comments +# alongside your code. Comments are passed through +# [Markdown](http://daringfireball.net/projects/markdown/syntax), and code is +# passed through [Pygments](http://pygments.org/) syntax highlighting.  This +# page is the result of running Pocco against its own source file. +# +# If you install Pocco, you can run it from the command-line: +# +#     pocco src/*.py +# +# ...will generate linked HTML documentation for the named source files, saving +# it into a `docs` folder. +# +# To install Pocco, simply +# +#     sudo setup.py install +# + +#### Main Documentation Generation Functions + +# Generate the documentation for a source file by reading it in, splitting it +# up into comment/code sections, highlighting them for the appropriate language, +# and merging them into an HTML template. +def generate_documentation(source): +    fh = open(source, "r") +    sections = parse(source, fh.read()) +    highlight(source, +              sections) +    generate_html(source, sections) + +# Given a string of source code, parse out each comment and the code that +# follows it, and create an individual **section** for it. +# Sections take the form: +# +#     { "docs_text": ..., +#       "docs_html": ..., +#       "code_text": ..., +#       "code_html": ..., +#       "num":       ... +#     } +# +def parse(source, code): +    lines = code.split("\n") +    sections = [] +    language = get_language(source) +    has_code = docs_text = code_text = "" + +    if lines[0].startswith("#!"): +        lines.pop(0) + +    def save(docs, code): +        sections.append({ +            "docs_text": docs, +            "code_text": code +        }) + +    for line in lines: +        if re.match(language["comment_matcher"], line): +            if has_code: +                save(docs_text, code_text) +                has_code = docs_text = code_text = '' +            docs_text += re.sub(language["comment_matcher"], "", line) + "\n" +        else: +            has_code = True +            code_text += line + '\n' +    save(docs_text, code_text) +    return sections + +# Highlights a single chunk of code using the **Pygments** module, and runs the +# text of its corresponding comment through **Markdown**. +# +# We process the entire file in a single call to Pygments by inserting little +# marker comments between each section and then splitting the result string +# wherever our markers occur. +def highlight(source, sections): +    language = get_language(source) + +    output = pygments.highlight(language["divider_text"].join(section["code_text"] for section in sections), +                                language["lexer"], +                                formatters.get_formatter_by_name("html")) + +    output = output.replace(highlight_start, "").replace(highlight_end, "") +    fragments = re.split(language["divider_html"], output) +    for i, section in enumerate(sections): +        section["code_html"] = highlight_start + shift(fragments, "") + highlight_end +        section["docs_html"] = markdown(section["docs_text"]) +        section["num"] = i + +# Once all of the code is finished highlighting, we can generate the HTML file +# and write out the documentation. Pass the completed sections into the template +# found in `resources/pocco.html` +def generate_html(source, sections): +    title = path.basename(source) +    dest = destination(source) +    html = pocco_template({ +        "title":       title, +        "sections":    sections, +        "sources":     sources, +        "path":        path, +        "destination": destination +    }) +    print "pocco = %s -> %s" % (source, dest) +    fh = open(dest, "w") +    fh.write(html) +    fh.close() + +#### Helpers & Setup + +# Import our external dependencies. +import pygments +import pystache +import re +import sys +from markdown import markdown +from os import path +from pygments import lexers, formatters +from subprocess import Popen, PIPE + +# A list of the languages that Pocco supports, mapping the file extension to +# the name of the Pygments lexer and the symbol that indicates a comment. To +# add another language to Pocco's repertoire, add it here. +languages = { +    ".coffee": { "name": "coffee-script", "symbol": "#" }, +    ".js":     { "name": "javascript",    "symbol": "//" }, +    ".rb":     { "name": "ruby",          "symbol": "#" }, +    ".py":     { "name": "python",        "symbol": "#" }, +    ".scm":    { "name": "scheme",        "symbol": ";;" }, +} + +# Build out the appropriate matchers and delimiters for each language. +for ext, l in languages.items(): +    # Does the line begin with a comment? +    l["comment_matcher"] = re.compile(r"^\s*" + l["symbol"] + "\s?") + +    # The dividing token we feed into Pygments, to delimit the boundaries between +    # sections. +    l["divider_text"] = "\n" + l["symbol"] + "DIVIDER\n" + +    # The mirror of `divider_text` that we expect Pygments to return. We can split +    # on this to recover the original sections. +    l["divider_html"] = re.compile(r'\n*<span class="c[1]?">' + l["symbol"] + 'DIVIDER</span>\n*') + +    # Get the Pygments Lexer for this language. +    l["lexer"] = lexers.get_lexer_by_name(l["name"]) + +# Get the current language we're documenting, based on the extension. +def get_language(source): +    try: +        return languages[ source[source.rindex("."):] ] +    except ValueError: +        source = open(source, "r") +        code = source.read() +        source.close() +        lang = lexers.guess_lexer(code).name.lower() +        for l in languages.values(): +            if l["name"] == lang: +                return l +        else: +            raise ValueError("Can't figure out the language!") + +# Compute the destination HTML path for an input source file path. If the source +# is `lib/example.py`, the HTML will be at `docs/example.html` +def destination(filepath): +    try: +        name = filepath.replace(filepath[ filepath.rindex("."): ], "") +    except ValueError: +        name = filepath +    return "docs/" + path.basename(name) + ".html" + +# Shift items off the front of the `list` until it is empty, then return +# `default`. +def shift(list, default): +    try: +        return list.pop(0) +    except IndexError: +        return default + +# Ensure that the destination directory exists. +def ensure_directory(): +    Popen(["mkdir", "-p", "docs"]).wait() + +def template(source): +    return lambda context: pystache.render(source, context) + +# Create the template that we will use to generate the Pocco HTML page. +pocco_template = template(open(path.join(path.dirname(__file__), +                                         "resources/pocco.html")).read()) + +# The CSS styles we"d like to apply to the documentation. +pocco_styles = open(path.join(path.dirname(__file__), +                              "resources/pocco.css")).read() + +# The start of each Pygments highlight block. +highlight_start = "<div class=\"highlight\"><pre>" + +# The end of each Pygments highlight block. +highlight_end = "</pre></div>" + +# Run the script. +# For each source file passed in as an argument, generate the documentation. +if __name__ == "__main__": +    sources = list(sys.argv[1:]) +    sources.sort() +    if sources: +        ensure_directory() +        css = open("docs/pocco.css", "w") +        css.write(pocco_styles) +        css.close() + +        def next_file(): +            generate_documentation(sources.pop(0)) +            if sources: +                next_file() +        next_file()  | 
