#!/usr/bin/env python
"""jemdoc version 0.6.3, 2008-06-18."""
# Copyright (C) 2007-2008 Jacob Mattingley (jacobm@stanford.edu).
#
# 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 .
#
# The LaTeX equation portions of this file were initially based on
# latexmath2png, by Kamil Kisiel (kamil@kamikisiel.net).
#
import sys
import os
import re
import time
import StringIO
from subprocess import *
import tempfile
def info():
print __doc__
print 'Platform: ' + sys.platform + '.'
print 'Python: %s, located at %s.' % (sys.version[:5], sys.executable)
print 'Equation support:',
(supported, message) = testeqsupport()
if supported:
print 'yes.'
else:
print 'no.'
print message
def testeqsupport():
supported = True
msg = ''
p = Popen('latex --version', shell=True, stdout=PIPE, stderr=PIPE)
rc = p.wait()
if rc != 0:
msg += ' latex: not found.\n'
supported = False
else:
msg += ' latex: ' + p.stdout.readlines()[0].rstrip() + '.\n'
p = Popen('dvipng --version', shell=True, stdout=PIPE, stderr=PIPE)
rc = p.wait()
if rc != 0:
msg += ' dvipng: not found.\n'
supported = False
else:
msg += ' dvipng: ' + p.stdout.readlines()[0].rstrip() + '.\n'
return (supported, msg[:-1])
class controlstruct(object):
def __init__(self, infile, outfile=None, conf=None, inname=None, eqs=True,
eqdir='eqs', eqdpi=130):
self.inname = inname
self.inf = infile
self.outf = outfile
self.conf = conf
self.linenum = 0
self.otherfiles = []
self.eqs = eqs
self.eqdir = eqdir
self.eqdpi = eqdpi
# Default to supporting equations until we know otherwise.
self.eqsupport = True
self.eqcache = True
self.eqpackages = []
self.eqbd = {} # equation base depth.
self.baseline = None
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]
| ' % f.tablerow, b)
# jemjemjemjem
# Deal with |, meaning |
r = re.compile(r"(? | ' % f.tablecol, b)
#while re.search(r, b):
#f.tablecol += 1
#b = re.subn(r, r' | ' % f.tablecol, b, 1)
## 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 = {'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', 'range'])
d['special'] = putbsbs(['cols', 'optvar', 'param', 'problem', 'norm2',
'norm1', 'value', 'minimize', 'maximize',
'rows', 'rand', 'randn', 'printval'])
d['error'] = putbsbs(['\w*Error',])
d['commentuntilend'] = '#'
d['strings'] = True
elif lang == 'sh':
d['statement'] = putbsbs(['cd', 'ls', 'sudo'])
d['operator'] = ['>']
d['builtin'] = putbsbs(['python', '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.
if 'commentuntilend' in hl:
r = re.compile('(' + hl['commentuntilend'] + '.*$)')
l = r.sub(r'', l)
if 'statement' in hl:
r = re.compile('(' + '|'.join(hl['statement']) + ')')
l = r.sub(r'\1', l)
if 'operator' in hl:
r = re.compile('(' + '|'.join(hl['operator']) + ')')
l = r.sub(r'\1', l)
if 'builtin' in hl:
r = re.compile('(' + '|'.join(hl['builtin']) + ')')
l = r.sub(r'\1', l)
if 'special' in hl:
r = re.compile('(' + '|'.join(hl['special']) + ')')
l = r.sub(r'\1', l)
if 'error' in hl:
r = re.compile('(' + '|'.join(hl['error']) + ')')
l = r.sub(r'\1', l)
l = re.sub('CLASS', 'class', l)
out(f, l + '\n')
def geneq(f, eq, dpi, wl, outname, packages=None):
# First check if there is an existing file.
eqname = os.path.join(f.eqdir, outname + '.png')
eqdepths = {}
if f.eqcache:
try:
dc = open(os.path.join(f.eqdir, '.eqdepthcache'), 'r')
for l in dc:
a = l.split()
eqdepths[a[0]] = int(a[1])
dc.close()
if os.path.exists(eqname) and eqname in eqdepths:
return (eqdepths[eqname], eqname)
except IOError:
print 'eqdepthcache read failed.'
# Open tex file.
tempdir = tempfile.gettempdir()
fd, texfile = tempfile.mkstemp('.tex', '', tempdir, True)
basefile = texfile[:-4]
g = os.fdopen(fd, 'w')
if packages is None:
packages = []
# Add the default list here.
packages += []
preamble = '\documentclass{article}\n'
for p in packages:
preamble += '\usepackage{%s}\n' % p
preamble += '\pagestyle{empty}\n\\begin{document}\n'
g.write(preamble)
# Write the equation itself.
if wl:
g.write('\\[%s\\]' % eq)
else:
g.write('$%s$' % eq)
# Finish off the tex file.
g.write('\n\\newpage\n\end{document}')
g.close()
try:
# Generate the DVI file
latexcmd = 'latex -file-line-error-style -interaction=nonstopmode ' + \
'-output-directory %s %s' % (tempdir, texfile)
p = Popen(latexcmd, shell=True, stdout=PIPE)
rc = p.wait()
if rc != 0:
for l in p.stdout.readlines():
print ' ' + l.rstrip()
raise Exception('latex error')
dvifile = basefile + '.dvi'
dvicmd = 'dvipng --freetype0 -Q 9 -z 3 --depth -q -T tight -D %i -bg Transparent -o %s %s' % (dpi, eqname, dvifile)
# discard warnings, as well.
p = Popen(dvicmd, shell=True, stdout=PIPE, stderr=PIPE)
rc = p.wait()
if rc != 0:
print p.stderr.readlines()
raise Exception('dvipng error')
depth = int(p.stdout.readlines()[-1].split('=')[-1])
finally:
# Clean up.
exts = ['.tex', '.aux', '.dvi', '.log']
for ext in exts:
g = basefile + ext
if os.path.exists(g):
os.remove(g)
# Update the cache if we're using it.
if f.eqcache and eqname not in eqdepths:
try:
dc = open(os.path.join(f.eqdir, '.eqdepthcache'), 'a')
dc.write(eqname + ' ' + str(depth) + '\n')
dc.close()
except IOError:
print 'eqdepthcache update failed.'
return (depth, eqname)
def dashlist(f, ordered=False):
level = 0
if ordered:
char = '.'
ul = 'ol'
else:
char = '-'
ul = 'ul'
while pc(f) == char:
(s, newlevel) = np(f, True, False)
# 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, '<%s>\n' % ul)
elif newlevel < level:
out(f.outf, '\n')
for i in range(level - newlevel):
#out(f.outf, '\n%s>\n' % ul)
# demote means place '' in the file.
out(f.outf, '%s>\n' % ul)
#out(f.outf, '\n')
out(f.outf, '\n')
else:
# same level, make a new list item.
out(f.outf, '\n\n')
out(f.outf, ' ' + br(s, f) + ' ')
level = newlevel
for i in range(level):
out(f.outf, '\n\n%s>\n' % ul)
def colonlist(f):
out(f.outf, '\n')
while pc(f) == ':':
s = np(f, eatblanks=False)
r = re.compile(r'\s*{(.*?)(?|\n', br(defpart, f))
hb(f.outf, '| \n', br(rest, f))
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.
stringmode = False
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:]
elif stringmode:
if l.rstrip().endswith('"""'):
out(f.outf, l + '')
stringmode = False
else:
out(f.outf, l)
continue
# 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
elif g[1] in ('python', 'py') and l.strip().startswith('"""'):
out(f.outf, '' + l)
stringmode = True
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 inserttitle(f, t):
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), f))
hb(f.outf, f.conf['doctitleend'], t)
def procfile(f):
f.linenum = 0
menu = None
# convert these to a dictionary.
showfooter = True
showsourcelink = False
showlastupdated = True
showlastupdatedtime = True
nodefaultcss = False
fwtitle = 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('nofooter'):
showfooter = False
elif b.startswith('nodate'):
showlastupdated = False
elif b.startswith('notime'):
showlastupdatedtime = False
elif b.startswith('fwtitle'):
fwtitle = True
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 fwtitle:
out(f.outf, f.conf['fwtitlestart'])
inserttitle(f, t)
out(f.outf, f.conf['fwtitleend'])
if menu:
out(f.outf, f.conf['menustart'])
insertmenuitems(*menu)
out(f.outf, f.conf['menuend'])
else:
out(f.outf, f.conf['nomenu'])
if not fwtitle:
inserttitle(f, t)
infoblock = False
imgblock = False
tableblock = False
while 1: # wait for EOF.
p = pc(f)
if p == '':
break
# look for lists.
elif p == '-':
dashlist(f, False)
elif p == '.':
dashlist(f, True)
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, f))
# 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
elif tableblock:
out(f.outf, '\n')
tableblock = False
nl(f)
continue
else:
if pc(f) == '{':
l = allreplace(nl(f))
r = re.compile(r'(?= 1:
g[0] = br(g[0], f)
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 and g[1] == 'table':
# handles
# {title}{table}{name}
# one | two ||
# three | four ||
name = ''
if len(g) >= 3 and g[2]:
name += ' id="%s"' % g[2]
out(f.outf, '