Commits

Ronald Oussoren committed ee1633b

A small, but very necessary, side project: rewrite the tools that are used
to rebuild the pyobjc website.

This checkin introduces the basic structures as well as code for publishing
sample code (as syntax-colored HTML + a zipfile with all resources).

Most of the website is not present in this version, and the L&F sucks, but that
should be easy enough to fix.

Comments (0)

Files changed (7)

pyobjc-website/ReadMe.txt

+This project will one day be used to replace the PyObjC website by something
+new and shiny, but is pretty useless in its current state.
+
+Usage::
+
+   python setup.py build
+  
+This will recreate the website in 'htdocs'. The website is a collection of 
+static (html) files.
+
+Done:
+- Regenerate online examples from the source tree
+
+Todo:
+- Update all examples to have a summary.txt (short description for 
+  the lists of examples) and readme.txt (a longer description). The readme.txt
+  is optional.
+- (Examples) The summary should be extracted from the readme file
+- Use the current website to create better template files
+- Home page and news
+- Extract documentation for pyobjc-core and framework wrappers
+- Download page
+- The current L&F on pyobjc.sf.net sucks, find something better

pyobjc-website/lib/samples.py

+"""
+Code for converting an example in a repository checkout to data on the PyObjC
+website
+
+A sample of the website consists of a zipfile with all code for the sample,
+and index.html with a description of the example and furthermore HTML files
+with colorized source code.
+"""
+
+__all__ = ('convertSample',)
+
+from pygments import highlight
+from pygments.lexers import get_lexer_for_filename
+from pygments.formatters import HtmlFormatter
+
+from docutils.core import publish_parts
+
+from genshi.template import MarkupTemplate
+
+from distutils import log
+
+
+import zipfile
+import os
+import shutil
+
+gSkipDirectories = ('.svn', 'CVS')
+gZipTemplate = "PyObjCExample-%s.zip"
+
+gTemplateDir = os.path.join(
+        os.path.dirname(os.path.dirname(os.path.abspath(__file__))),
+        'templates')
+
+def zipDirectory(outputname, inputdir):
+    """
+    Create a zipfile containing all files in a directory, but skipping
+    version-management turds.
+    """
+
+    zf = zipfile.ZipFile(outputname, 'w')
+    basename = os.path.basename(outputname)[:-4]
+
+    while inputdir.endswith(os.path.sep):
+        inputdir = inputdir[:-1]
+
+    for dirpath, dirnames, filenames in os.walk(inputdir):
+        for name in gSkipDirectories:
+            if name in dirnames:
+                dirnames.remove(name)
+
+        # Relpath is the directory name in the zipfile,
+        # prepend the sample name to ensure we don't extract
+        # into the current directory.
+        relpath = dirpath[len(inputdir)+1:]
+        relpath = os.path.join(basename, relpath)
+
+        for fn in filenames:
+            zf.write(
+                os.path.join(dirpath, fn), 
+                os.path.join(relpath, fn),
+                zipfile.ZIP_DEFLATED)
+    zf.close()
+
+def colorizeSources(inputdir, outputdir):
+    """
+    Return a list of colorized files:
+        [(relativepath, coloredName, html-style, html-body), ...]
+
+    This will *not* write the actual files
+    """
+
+    coloredFiles = []
+
+    for dirpath, dirnames, filenames in os.walk(inputdir):
+        for name in gSkipDirectories:
+            if name in dirnames:
+                dirnames.remove(name)
+        
+        for name in dirnames:
+            # Don't bother looking inside nib files.
+            if name.endswith('.nib'):
+                dirnames.remove(name)
+
+        relpath = dirpath[len(inputdir)+1:]
+
+        for fn in sorted(filenames):
+            if relpath:
+                path = os.path.join(relpath, fn)
+            else:
+                path = fn
+
+
+            ext = os.path.splitext(fn)[-1]
+            if ext not in ('.py', '.pyw', '.m', '.h'):
+                # Skip all files that aren't clearly source code
+                continue
+
+            colorFn = 'source--%s.html' % (path.replace(os.path.sep, '-'),)
+
+            fullpath = os.path.join(dirpath, fn)
+            lexer = get_lexer_for_filename(fullpath)
+            formatter = HtmlFormatter(linenos = False, cssclass='source')
+            result = highlight(open(fullpath, 'r').read(), lexer, formatter)
+            style=formatter.get_style_defs()
+            coloredFiles.append((path, colorFn, style, result))
+
+    return coloredFiles
+
+def restToHTML(inputFile):
+    """
+    Read a reStructuredText file and return the HTML representation of it
+    """
+    input = open(inputFile, 'rU').read()
+    output = publish_parts(
+            source=input,
+            source_path=inputFile,
+            writer_name='html', 
+            settings_overrides=dict(
+                input_encoding='utf-8',
+                initial_header_level=2,
+            ))
+    #print output.keys()
+
+    return output['html_body']
+
+
+
+def convertSample(name, inputdir, outputdir):
+    """
+    Convert an example from the source tree to part of the website
+
+    Returns a small summary of the example.
+    """
+
+    if os.path.exists(outputdir):
+        shutil.rmtree(outputdir)
+    os.makedirs(outputdir)
+
+    zipname = gZipTemplate % name
+    zipDirectory(os.path.join(outputdir, zipname), inputdir)
+
+    coloredFiles = colorizeSources(inputdir, outputdir)
+
+    readme = os.path.join(inputdir, "ReadMe.txt")
+    summary = os.path.join(inputdir, "Summary.txt")
+
+    if os.path.exists(readme):
+        readme = restToHTML(readme)
+
+    elif os.path.exists(summary):
+        readme = restToHTML(summary)
+
+
+    else:
+        readme = "A PyObjC Example without documentation"
+
+    tmpl = MarkupTemplate(
+            open(os.path.join(gTemplateDir, "sample-index.html"), 'r').read())
+    stream = tmpl.generate(
+            title=name,
+            sources=[item[:2] for item in coloredFiles],
+            zipname=zipname,
+            readme=readme)
+    fp = open(os.path.join(outputdir, "index.html"), 'w')
+    fp.write(stream.render('html'))
+    fp.close()
+
+
+    tmpl = MarkupTemplate(
+            open(os.path.join(gTemplateDir, "sample-source.html"), 'r').read())
+    for realpath, htmlpath, style, body in coloredFiles:
+        sources=[item[:2] for item in coloredFiles if item[1] != htmlpath]
+        sources.insert(0, (realpath, htmlpath))
+        stream = tmpl.generate(
+            title='%s -- %s'%(name, realpath),
+            sources=sources,
+            style=style,
+            zipname=zipname,
+            body=body)
+        fp = open(os.path.join(outputdir, htmlpath), 'w')
+        fp.write(stream.render('html'))
+        fp.close()
+
+    # XXX: We should extract a small summary from the readme file to use
+    # on the sample index package (and not a seperate summary file).
+    if os.path.exists(summary):
+        return restToHTML(summary)
+    return ""
+
+def samplesForProject(title, package, inputdir, outputdir):
+    inputdir = os.path.join(inputdir, 'Examples')
+    outputdir = os.path.join(outputdir, package)
+    if os.path.exists(outputdir):
+        shutil.rmtree(outputdir)
+
+    if not os.path.exists(inputdir):
+        return None
+
+    samples = []
+    for dirpath, dirnames, filenames in os.walk(inputdir):
+        for dn in dirnames:
+            if os.path.exists(os.path.join(dirpath, dn, 'setup.py')):
+                # Found a sample
+                relpath = os.path.join(dirpath[len(inputdir)+1:], dn)
+                summary = convertSample(dn, os.path.join(dirpath, dn),
+                        os.path.join(outputdir, relpath))
+                samples.append((relpath, dn, summary))
+
+    if samples:
+        tmpl = MarkupTemplate(
+            open(os.path.join(gTemplateDir, "sample-framework-index.html"), 'r').read())
+        stream = tmpl.generate(
+            title="Examples for %s" % (title,),
+            samples=samples)
+        fp = open(os.path.join(outputdir, "index.html"), 'w')
+        fp.write(stream.render('html'))
+        fp.close()
+    return samples
+
+def generateSamples(basedir, outputdir, frameworkList):
+    log.info("Generating HTML for sample code")
+    allSamples = []
+    if os.path.exists(outputdir):
+        shutil.rmtree(outputdir)
+
+    for nm in frameworkList:
+        title = 'The %s framework' % ( nm[len('pyobjc-framework-'):], )
+        log.info(" - %s" % title)
+        listing = samplesForProject(
+                title,
+                nm,
+                os.path.join(basedir, nm),
+                outputdir)
+
+        if listing:
+            print allSamples
+            allSamples.append((title, nm, listing))
+
+    if allSamples:
+        tmpl = MarkupTemplate(
+            open(os.path.join(gTemplateDir, "sample-global-index.html"), 'r').read())
+        stream = tmpl.generate(
+            samplelist=allSamples)
+        fp = open(os.path.join(outputdir, "index.html"), 'w')
+        fp.write(stream.render('html'))
+        fp.close()
+
+
+
+if __name__ == "__main__":
+    #convertSample("ClassBrowser", "../../pyobjc-framework-Cocoa/Examples/AppKit/ClassBrowser", "samples/ClassBrowser")
+    #convertSample("AutoSample", "../../pyobjc-framework-Automator/Examples/AutoSample", "samples/AutoSample")
+    samplesForProject('TTILE', 'pyobjc-framework-Cocoa', '../../pyobjc-framework-Cocoa', 'samples')

pyobjc-website/setup.py

+"""
+A simple setup.py file for building and publishing the website. 
+
+Usage:
+    python setup.py build
+
+       This will regenerate the website from the current checkout
+
+    python setup.py publish [--skip-build]
+
+       This will upload the website to the server, building it beforehand
+       unless the ``--skip-build`` option is used.
+
+Note that this is *NOT* a valid python package, we just use setuptools 
+facilities to ensure that all software we need is actually present.
+"""
+from setuptools import Command, setup
+
+from distutils.errors  import DistutilsError
+from distutils import log
+
+
+import sys, os
+
+# The truly interesting code is in the 'lib' directory, make that available.
+sys.path.append('lib')
+
+import samples
+
+
+
+#
+# Distutils command definitions
+#
+
+class buildsite (Command):
+    description = "Build the website"
+    user_options = []
+
+    def initialize_options(self):
+        self.finalized = False
+
+    def finalize_options(self):
+        self.finalized = True
+
+
+    def run(self):
+        if not os.path.exists('../pyobjc-core'):
+            raise DistutilsError(
+                    "Run me in a complete checkout of the pyobjc trunk")
+
+        frameworkList = [dn for dn in os.listdir('..') if dn.startswith('pyobjc-framework') ]
+
+
+        samples.generateSamples('..', 'htdocs/examples', frameworkList)
+
+class publishsite (Command):
+    description = "Publish the website"
+    user_options = [
+        ('skip-build', None,
+            "Skip the build phase")
+    ]
+
+
+    def initialize_options(self):
+        self.finalized = False
+        self.skip_build = False
+
+    def finalize_options(self):
+        self.finalized = True
+
+
+    def run(self):
+        if not self.skip_build:
+            self.run_command('build')
+
+        raise DistutilsError("publishsite is not implemented yet")
+
+
+#
+# Run distutils
+#
+
+setup(
+        name = "pyobjc-website",
+        description = "PyObjC's website",
+        cmdclass=dict(
+            build=buildsite,
+            publish=publishsite,
+        ),
+        setup_requires = [
+            'docutils',
+            'Genshi',
+            'Pygments',
+        ]
+)

pyobjc-website/templates/sample-framework-index.html

+<html xmlns:py="http://genshi.edgewall.org/">
+  <head>
+    <title py:content="title">Sample name</title>
+  </head>
+
+  <body>
+    <h1 py:content="title">Sample name</h1>
+
+    <div py:for="path, name, summary in samples">
+      <a href="${path}/index.html"><h2>${name}</h2></a>
+      <p py:if="summary">Markup(${summary})</p>
+    </div>
+  </body>
+</html>

pyobjc-website/templates/sample-global-index.html

+<html xmlns:py="http://genshi.edgewall.org/">
+  <head>
+    <title>PyObjC Examples</title>
+  </head>
+
+  <body>
+    <h1>PyObjC Examples</h1>
+
+    <div py:for="title, subpath, samples in samplelist">
+      <a href="${subpath}/index.html"><h2>${title}</h2></a>
+      <div py:for="path, name, summary in samples">
+        <a href="${path}/index.html"><h3>${name}</h3></a>
+        <p py:if="summary">Markup(${summary})</p>
+      </div>
+    </div>
+  </body>
+</html>

pyobjc-website/templates/sample-index.html

+<html xmlns:py="http://genshi.edgewall.org/">
+  <head>
+    <title py:content="title">Sample name</title>
+
+    <style>
+      .readme {
+        background-color: #f8f8f8;
+        border: 1px solid;
+        margin-left: 2em;
+        padding: 1em;
+      }
+    </style>
+
+    <script type="text/javascript">
+      function selectSource() {
+          source=document.forms.sourceselector.sources.value;
+          if (source != "") {
+            document.location = source;
+          }
+      }
+    </script>
+  </head>
+
+  <body>
+    <h1 py:content="title">Sample name</h1>
+
+    <p><a href="${zipname}">Download the sample</a></p>
+
+    <form name="sourceselector">
+      <select name="sources" onchange="selectSource()">
+        <option value="" selected="selected">Select a source file</option>
+        <option py:for="name, path in sources" value="${path}">${name}</option>
+      </select>
+    </form>
+
+    <div class="readme" py:content="Markup(readme)">Readme for the example</div>
+  </body>
+</html>

pyobjc-website/templates/sample-source.html

+<html xmlns:py="http://genshi.edgewall.org/">
+  <head>
+    <title py:content="title">Sample name</title>
+    <style py:content="Markup(style)"><!-- style goes here --></style>
+
+    <script type="text/javascript">
+      function selectSource() {
+        source=document.forms.sourceselector.sources.value;
+        if (source != "") {
+          document.location = source;
+        }
+      }
+    </script>
+
+  </head>
+
+  <body>
+    <h1 py:content="title">Sample name</h1>
+
+    <p><a href="${zipname}">Download the sample</a></p>
+
+    <form name="sourceselector">
+      <select name="sources" onchange="selectSource()">
+        <option py:for="name, path in sources" value="${path}">${name}</option>
+      </select>
+    </form>
+
+    <p py:content="Markup(body)">The actual source code</p>
+  </body>
+</html>
Tip: Filter by directory path e.g. /media app.js to search for public/media/app.js.
Tip: Use camelCasing e.g. ProjME to search for ProjectModifiedEvent.java.
Tip: Filter by extension type e.g. /repo .js to search for all .js files in the /repo directory.
Tip: Separate your search with spaces e.g. /ssh pom.xml to search for src/ssh/pom.xml.
Tip: Use ↑ and ↓ arrow keys to navigate and return to view the file.
Tip: You can also navigate files with Ctrl+j (next) and Ctrl+k (previous) and view the file with Ctrl+o.
Tip: You can also navigate files with Alt+j (next) and Alt+k (previous) and view the file with Alt+o.