#!/usr/bin/env python """jemdoc version 0.4.0, 2007-12-02.""" # Copyright (C) 2007 Jacob Mattingley. # # This file is part of jemdoc. # # jemdoc is free software; you can redistribute it and/or modify it under the # terms of the GNU General Public License as published by the Free Software # Foundation; either version 3 of the License, or (at your option) any later # version. # # jemdoc is distributed in the hope that it will be useful, but WITHOUT ANY # WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR # A PARTICULAR PURPOSE. See the GNU General Public License for more details. # # You should have received a copy of the GNU General Public License along with # this program. If not, see . import sys import os import re import time import StringIO def info(): print __doc__ print 'Platform is ' + sys.platform + '.' print 'Using Python version %s, located at %s.' % (sys.version[:5], sys.executable) class controlstruct(object): def __init__(self, infile, outfile=None, conf=None, inname=None): self.inname = inname self.inf = infile self.outf = outfile self.conf = conf self.linenum = 0 self.otherfiles = [] def pushfile(self, newfile): self.otherfiles.insert(0, self.inf) self.inf = open(newfile) def nextfile(self): self.inf.close() self.inf = self.otherfiles.pop(0) def showhelp(): a = """Usage: jemdoc [OPTIONS] [SOURCEFILE] Produces html markup from a jemdoc SOURCEFILE. Most of the time you can use jemdoc without any additional flags. For example, typing jemdoc index will produce an index.html from index.jemdoc, using a default configuration. Some configuration options can be overridden by specifying a configuration file. You can use jemdoc --show-config to print a sample configuration file (which includes all of the default options). Any or all of the configuration [blocks] can be overwritten by including them in a configuration file, and running, for example, jemdoc -c mywebsite.conf index.jemdoc You can view version and installation details with jemdoc --version See http://jemdoc.jaboc.net/ for many more details.""" b = '' for l in a.splitlines(True): if l.startswith(' '*4): b += l[4:] else: b += l print b def standardconf(): a = """[firstbit] [defaultcss] [windowtitle] # used in header for window title. | [doctitle] # used at top of document.

|

[subtitle]
|
[doctitleend]
[bodystart] [menustart]
[menuend]
[menucategory] [menuitem] [specificcss] [currentmenuitem] [nomenu]
[menulastbit]
[nomenulastbit] [bodyend] [infoblock]
[codeblock]
[blocktitle]
|
[infoblockcontent]
[codeblockcontent]
    
    [codeblockend]
    
[codeblockcontenttt]
[codeblockendtt]
[infoblockend]
[footerstart] [lastupdated] Page generated |, by jemdoc. [sourcelink] (source) """ b = '' for l in a.splitlines(True): if l.startswith(' '): b += l[4:] else: b += l return b class JandalError(Exception): pass def raisejandal(msg, line=0): if line == 0: s = "%s" % msg else: s = "line %d: %s" % (line, msg) raise JandalError(s) def readnoncomment(f): l = f.readline() if l == '': return l elif l[0] == '#': # jem: be a little more generous with the comments we accept? return readnoncomment(f) else: return l.rstrip() + '\n' # leave just one \n and no spaces etc. def parseconf(cns): syntax = {} warn = False # jem. make configurable? # manually add the defaults as a file handle. fs = [StringIO.StringIO(standardconf())] for sname in cns: fs.append(open(sname)) for f in fs: while pc(controlstruct(f)) != '': l = readnoncomment(f) r = re.match(r'\[(.*)\]\n', l) if r: tag = r.group(1) s = '' l = readnoncomment(f) while l not in ('\n', ''): s += l l = readnoncomment(f) syntax[tag] = s f.close() return syntax def insertmenuitems(f, mname, current, prefix): m = open(mname) while pc(controlstruct(m)) != '': l = readnoncomment(m) l = l.strip() if l == '': continue r = re.match(r'\s*(.*?)\s*\[(.*)\]', l) if r: # then we have a menu item. link = r.group(2) # Don't use prefix if we have an absolute link. if '://' not in r.group(2): link = prefix + allreplace(link) # replace spaces with nbsps. menuitem = re.sub(r'(?&$%\.~[\]-]""", r'\\\g<0>', s) def replacequoted(b): """Quotes {{raw html}} sections.""" r = re.compile(r'\{\{(.*?)\}\}', re.M + re.S) m = r.search(b) while m: qb = quote(m.group(1)) b = b[:m.start()] + qb + b[m.end():] m = r.search(b, m.start()) return b def replacepercents(b): # replace %sections% as +{{sections}}+. Do not replace if within a link. r = re.compile(r'(?' % " ".join(bits) + b[m.end():] m = r.search(b, m.start()) return b def replacelinks(b): # works with [link.html new link style]. r = re.compile(r'(?%s<\/a>' % (link, linkname) + b[m.end():] m = r.search(b, m.start()) return b def br(b): """Does simple text replacements on a block of text. ('block replacements')""" # Deal with literal backspaces. b = re.sub(r'\\\\', 'jemLITerl33talBS', b) # Deal with {{html embedding}}. b = replacequoted(b) b = allreplace(b) b = b.lstrip('-. \t') # remove leading spaces, tabs, dashes, dots. b = replaceimages(b) # jem not sure if this is still used. b = replacelinks(b) b = replacepercents(b) # Deal with /italics/ first because the '/' in other tags would otherwise # interfere. r = re.compile(r'(?\1', b) # Deal with *bold*. r = re.compile(r'(?\1', b) # Deal with +monospace+. r = re.compile(r'(?\1', b) # Deal with "double quotes". r = re.compile(r'(?', b) # Deal with paragraph break. Caution! Should only use when we're already in # a paragraph. r = re.compile(r"(?

', b) ## Deal with zero width \_. #r = re.compile(r"(?", re.M + re.S) b = re.sub(r, r'>', b) r = re.compile(r"(?\1', l) if l.startswith('>>>'): hb(f, '|\n', l) else: out(f, l + '\n') def putbsbs(l): for i in range(len(l)): l[i] = '\\b' + l[i] + '\\b' return l def gethl(lang): # disable comments by default, by choosing unlikely regex. d = {'statement':[], 'commentstart':'######%%%%%', 'operator':[], 'builtin':[], 'error':[], 'strings':False} if lang in ('py', 'python'): d['statement'] = putbsbs(['break', 'continue', 'del', 'except', 'exec', 'finally', 'pass', 'print', 'raise', 'return', 'try', 'with', 'global', 'assert', 'lambda', 'yield', 'def', 'class', 'for', 'while', 'if', 'elif', 'else', 'import', 'from', 'as']) d['operator'] = putbsbs(['and', 'in', 'is', 'not', 'or']) d['builtin'] = putbsbs(['True', 'False', 'set', 'open', 'frozenset', 'enumerate', 'object', 'hasattr', 'getattr', 'filter', 'eval', 'zip', 'vars', 'unicode', 'type', 'str', 'repr', 'round']) d['error'] = putbsbs(['\w*Error',]) d['commentstart'] = '#' d['strings'] = True elif lang == 'sh': d['statement'] = putbsbs(['cd', 'ls', 'sudo']) d['operator'] = ['>'] d['builtin'] = putbsbs(['curl', 'wget', '(?|\n', allreplace(l)) else: l = allreplace(l) # handle strings. if hl['strings']: r = re.compile(r'(".*?")') l = r.sub(r'\1', l) r = re.compile(r"('.*?')") l = r.sub(r'\1', l) # handle comments. r = re.compile(hl['commentstart']) l = r.sub(r'\1', l) if hl['statement']: r = re.compile('(' + '|'.join(hl['statement']) + ')') l = r.sub(r'\1', l) if hl['operator']: r = re.compile('(' + '|'.join(hl['operator']) + ')') l = r.sub(r'\1', l) if hl['builtin']: r = re.compile('(' + '|'.join(hl['builtin']) + ')') l = r.sub(r'\1', l) if hl['error']: r = re.compile('(' + '|'.join(hl['error']) + ')') l = r.sub(r'\1', l) l = re.sub('CLASS', 'class', l) out(f, l + '\n') def dashlist(f): level = 0 while pc(f) == '-': (s, newlevel) = np(f, True) # first adjust list number as appropriate. if newlevel > level: for i in range(newlevel - level): if newlevel > 1: out(f.outf, '\n') out(f.outf, '

\n
  • ') else: out(f.outf, '
  • \n
  • ') out(f.outf, '

    ' + br(s) + '

    ') level = newlevel for i in range(level): out(f.outf, '
  • \n\n') def dotlist(f): level = 0 while pc(f) == '.': (s, newlevel) = np(f, True) # first adjust list number as appropriate. if newlevel > level: for i in range(newlevel - level): if newlevel > 1: out(f.outf, '\n') out(f.outf, '
      \n
    1. ') elif newlevel < level: for i in range(level - newlevel): out(f.outf, '
    2. \n
    \n
  • ') else: out(f.outf, '
  • \n
  • ') out(f.outf, '

    ' + br(s) + '

    ') level = newlevel for i in range(level): out(f.outf, '
  • \n\n') def colonlist(f): out(f.outf, '
    \n') while pc(f) == ':': s = np(f) r = re.compile(r'\s*{(.*?)(?|\n', br(defpart)) hb(f.outf, '

    |

    \n', br(rest)) out(f.outf, '
    \n') def codeblock(f, g): if g[1] == 'raw': raw = True else: raw = False out(f.outf, f.conf['codeblock']) if g[0]: hb(f.outf, f.conf['blocktitle'], g[0]) if g[1] == 'jemdoc': out(f.outf, f.conf['codeblockcontenttt']) else: out(f.outf, f.conf['codeblockcontent']) # Now we are handling code. # Handle \~ and ~ differently. while 1: # wait for EOF. l = nl(f, codemode=True) if not l: break elif l.startswith('~'): break elif l.startswith('\\~'): l = l[1:] elif l.startswith('\\{'): l = l[1:] # jem revise pyint out of the picture. if g[1] == 'pyint': pyint(f.outf, l) else: if raw: out(f.outf, l) elif g[1] == 'jemdoc': # doing this more nicely needs python 2.5. for x in ('#', '~', '>>>', '\~', '{'): if str(l).lstrip().startswith(x): out(f.outf, '
    ')
                            out(f.outf, l + '
    ') break else: for x in (':', '.', '-'): if str(l).lstrip().startswith(x): out(f.outf, '
    ' + prependnbsps(l)) break else: if str(l).lstrip().startswith('='): out(f.outf, prependnbsps(l) + '
    ') else: out(f.outf, l) else: if l.startswith('\\#include{') or l.startswith('\\#includeraw{'): out(f.outf, l[1:]) elif l.startswith('#') and doincludes(f, l[1:]): continue else: language(f.outf, l, gethl(g[1])) if not raw: if g[1] == 'jemdoc': out(f.outf, f.conf['codeblockendtt']) else: out(f.outf, f.conf['codeblockend']) def prependnbsps(l): g = re.search('(^ *)(.*)', l).groups() return g[0].replace(' ', ' ') + g[1] def procfile(f): f.linenum = 0 menu = None showsourcelink = False showlastupdated = True showlastupdatedtime = True nodefaultcss = False css = [] title = None while pc(f, False) == '#': l = f.inf.readline() f.linenum += 1 if doincludes(f, l[1:]): continue if l.startswith('# jemdoc:'): l = l[len('# jemdoc:'):] a = l.split(',') # jem only handle one argument for now. for b in a: b = b.strip() if b.startswith('menu'): sidemenu = True r = re.compile(r'(? 3 or len(g) < 2: raise SyntaxError('sidemenu error on line %d' % f.linenum) if len(g) == 2: menu = (f, g[0], g[1], '') else: menu = (f, g[0], g[1], g[2]) elif b.startswith('nodate'): showlastupdated = False elif b.startswith('notime'): showlastupdatedtime = False elif b.startswith('showsource'): showsourcelink = True elif b.startswith('nodefaultcss'): nodefaultcss = True elif b.startswith('addcss'): r = re.compile(r'(?)|( ) *', ' ', t) else: t = None if title: hb(f.outf, f.conf['windowtitle'], title) out(f.outf, f.conf['bodystart']) if menu: out(f.outf, f.conf['menustart']) insertmenuitems(*menu) out(f.outf, f.conf['menuend']) else: out(f.outf, f.conf['nomenu']) if t is not None: hb(f.outf, f.conf['doctitle'], t) # Look for a subtitle. if pc(f) != '\n': hb(f.outf, f.conf['subtitle'], br(np(f))) hb(f.outf, f.conf['doctitleend'], t) infoblock = False imgblock = False while 1: # wait for EOF. p = pc(f) if p == '': break # look for lists. elif p == '-': dashlist(f) elif p == '.': dotlist(f) elif p == ':': colonlist(f) # look for titles. elif p == '=': (s, c) = nl(f, True) # trim trailing \n. s = s[:-1] hb(f.outf, '|\n' % (c, c), br(s)) # look for comments. elif p == '#': l = nl(f) elif p == '\n': nl(f) # look for blocks. elif p == '~': nl(f) if infoblock: out(f.outf, f.conf['infoblockend']) infoblock = False nl(f) continue elif imgblock: out(f.outf, '\n') imgblock = False nl(f) continue else: if pc(f) == '{': l = allreplace(nl(f)) r = re.compile(r'(?= 1: g[0] = br(g[0]) if len(g) in (0, 1): # info block. out(f.outf, f.conf['infoblock']) infoblock = True if len(g) == 1: # info block. hb(f.outf, f.conf['blocktitle'], g[0]) out(f.outf, f.conf['infoblockcontent']) elif len(g) == 2: codeblock(f, g) elif len(g) >= 4 and g[1] == 'img_left': # handles # {}{img_left}{source}{alttext}{width}{height}{linktarget}. g += ['']*(7 - len(g)) if g[4].isdigit(): g[4] += 'px' if g[5].isdigit(): g[5] += 'px' out(f.outf, '\n
    \n') if g[6]: out(f.outf, '' % g[6]) out(f.outf, '%s') if g[6]: out(f.outf, '') out(f.outf, ' ') imgblock = True else: raise JandalError("couldn't handle block", f.linenum) else: s = br(np(f)) if s: hb(f.outf, '

    |

    \n', s) if showlastupdated or showsourcelink: out(f.outf, f.conf['footerstart']) if showlastupdated: if showlastupdatedtime: ts = '%Y-%m-%d %H:%M:%S %Z' else: ts = '%Y-%m-%d' s = time.strftime(ts, time.localtime(time.time())) hb(f.outf, f.conf['lastupdated'], s) if showsourcelink: hb(f.outf, f.conf['sourcelink'], f.inname) out(f.outf, f.conf['footerend']) if menu: out(f.outf, f.conf['menulastbit']) else: out(f.outf, f.conf['nomenulastbit']) out(f.outf, f.conf['bodyend']) if f.outf is not sys.stdout: f.outf.close() def main(): if len(sys.argv) == 1 or sys.argv[1] in ('--help', '-h'): showhelp() raise SystemExit if sys.argv[1] == '--show-config': print standardconf() raise SystemExit if sys.argv[1] == '--version': info() raise SystemExit outoverride = False confoverride = False outname = None confnames = [] for i in range(1, len(sys.argv), 2): if sys.argv[i] == '-o': if outoverride: raise RuntimeError("only one output file / directory, please") outname = sys.argv[i+1] outoverride = True elif sys.argv[i] == '-c': if confoverride: raise RuntimeError("only one config file, please") confnames.append(sys.argv[i+1]) confoverride = True elif sys.argv[i].startswith('-'): raise RuntimeError('unrecognised argument %s, try --help' % sys.argv[i]) else: break conf = parseconf(confnames) innames = [] for j in range(i, len(sys.argv)): # First, if not a file and no dot, try opening .jemdoc. Otherwise, fall back # to just doing exactly as asked. inname = sys.argv[j] if not os.path.isfile(inname) and '.' not in inname: inname += '.jemdoc' innames.append(inname) if outname is not None and not os.path.isdir(outname) and len(innames) > 1: raise RuntimeError('cannot handle one outfile with multiple infiles') for inname in innames: if outname is None: thisout = re.sub(r'.jemdoc$', '', inname) + '.html' elif os.path.isdir(outname): # if directory, prepend directory to automatically generated name. thisout = outname + re.sub(r'.jemdoc$', '', inname) + '.html' else: thisout = outname infile = open(inname) outfile = open(thisout, 'w') f = controlstruct(infile, outfile, conf, inname) procfile(f) # if __name__ == '__main__': main()