#!/usr/bin/env python
"""jemdoc version 0.7.3, 2012-11-27."""
# Copyright (C) 2007-2012 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.texlines = []
self.analytics = None
self.eqbd = {} # equation base depth.
self.baseline = None
def pushfile(self, newfile):
self.otherfiles.insert(0, self.inf)
self.inf = open(newfile, 'rb')
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, l)
l2 = ''
col = 2
r2s = r2.split(l)
for x in r2s[:-1]:
l2 += x + (' | ' % col)
col += 1
l2 += r2s[-1]
b += l2
# Second to last, remove any remaining quoting backslashes.
b = re.sub(r'\\(?!\\)', '', b)
# Deal with literal backspaces.
b = re.sub('jemLITerl33talBS', r'\\', b)
# Also fix up DOUBLEOPEN and DOUBLECLOSEBRACES.
b = re.sub('DOUBLEOPENBRACE', '{{', b)
b = re.sub('DOUBLECLOSEBRACE', '}}', b)
return b
def allreplace(b):
"""Replacements that should be done on everything."""
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'] = ['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', 'assert']
d['builtin'] = ['True', 'False', 'set', 'open', 'frozenset',
'enumerate', 'object', 'hasattr', 'getattr', 'filter',
'eval', 'zip', 'vars', 'unicode', 'type', 'str',
'repr', 'round', 'range', 'and', 'in', 'is', 'not',
'or']
d['special'] = ['cols', 'optvar', 'param', 'problem', 'norm2', 'norm1',
'value', 'minimize', 'maximize', 'rows', 'rand',
'randn', 'printval', 'matrix']
d['error'] = ['\w*Error',]
d['commentuntilend'] = '#'
d['strings'] = True
elif lang in ['c', 'c++', 'cpp']:
d['statement'] = ['if', 'else', 'printf', 'return', 'for']
d['builtin'] = ['static', 'typedef', 'int', 'float', 'double', 'void',
'clock_t', 'struct', 'long', 'extern', 'char']
d['operator'] = ['#include.*', '#define', '@pyval{', '}@', '@pyif{',
'@py{']
d['error'] = ['\w*Error',]
d['commentuntilend'] = ['//', '/*', ' * ', '*/']
elif lang in ('rb', 'ruby'):
d['statement'] = putbsbs(['while', 'until', 'unless', 'if', 'elsif',
'when', 'then', 'else', 'end', 'begin',
'rescue', 'class', 'def'])
d['operator'] = putbsbs(['and', 'not', 'or'])
d['builtin'] = putbsbs(['true', 'false', 'require', 'warn'])
d['special'] = putbsbs(['IO'])
d['error'] = putbsbs(['\w*Error',])
d['commentuntilend'] = '#'
d['strings'] = True
d['strings'] = True
if lang in ['c++', 'cpp']:
d['builtin'] += ['bool', 'virtual']
d['statement'] += ['new', 'delete']
d['operator'] += ['<<', '>>']
d['special'] = ['public', 'private', 'protected', 'template',
'ASSERT']
elif lang == 'sh':
d['statement'] = ['cd', 'ls', 'sudo', 'cat', 'alias', 'for', 'do',
'done', 'in', ]
d['operator'] = ['>', r'\\', r'\|', ';', '2>', 'monolith>',
'kiwi>', 'ant>', 'kakapo>', 'client>']
d['builtin'] = putbsbs(['gem', 'gcc', 'python', 'curl', 'wget', 'ssh',
'latex', 'find', 'sed', 'gs', 'grep', 'tee',
'gzip', 'killall', 'echo', 'touch',
'ifconfig', 'git', '(?>', '~', '\.\.\.']
d['builtin'] = putbsbs(['csolve'])
d['commentuntilend'] = '%'
d['strings'] = True
elif lang == 'commented':
d['commentuntilend'] = '#'
# Add bsbs (whatever those are).
for x in ['statement', 'builtin', 'special', 'error']:
if x in d:
d[x] = putbsbs(d[x])
return d
def language(f, l, hl):
l = l.rstrip()
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)
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('CLCLclass', 'class', l)
if 'commentuntilend' in hl:
cue = hl['commentuntilend']
if isinstance(cue, (list, tuple)):
for x in cue:
if l.strip().startswith(x):
hb(f, '\n', allreplace(l))
return
if '//' in cue: # Handle this separately.
r = re.compile(r'\/\/.*')
l = r.sub(r'', l)
elif cue == '#': # Handle this separately.
r = re.compile(r'#.*')
l = r.sub(r'', l)
elif cue == '%': # Handle this separately.
r = re.compile(r'%.*')
l = r.sub(r'', l)
elif l.strip().startswith(cue):
hb(f, '\n', allreplace(l))
return
out(f, l + '\n')
def geneq(f, eq, dpi, wl, outname):
# 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'), 'rb')
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, 'wb')
preamble = '\documentclass{article}\n'
for p in f.eqpackages:
preamble += '\usepackage{%s}\n' % p
for p in f.texlines:
# Replace \{ and \} in p with { and }.
# XXX hack.
preamble += re.sub(r'\\(?=[{}])', '', p + '\n')
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()
exts = ['.tex', '.aux', '.dvi', '.log']
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()
exts.remove('.tex')
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.
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'), 'ab')
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
ext_prog = None
elif g[0] == 'filter_through':
# Filter through external program.
raw = False
ext_prog = g[1]
buff = ""
else:
ext_prog = None
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 ext_prog:
buff += l
continue
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 raw:
return
elif ext_prog:
print 'filtering through %s...' % ext_prog
output,_ = Popen(ext_prog, shell=True, stdin=PIPE,
stdout=PIPE).communicate(buff)
out(f.outf, output)
else:
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 = []
js = []
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 f.analytics:
hb(f.outf, f.conf['analytics'], f.analytics)
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
elif p == '\\(':
if not (f.eqs and f.eqsupport):
break
s = nl(f)
# Quickly pull out the equation here:
# Check we don't already have the terminating character in a whole-line
# equation without linebreaks, eg \( Ax=b \):
if not s.strip().endswith('\)'):
while True:
l = nl(f, codemode=True)
if not l:
break
s += l
if l.strip() == '\)':
break
out(f.outf, br(s.strip(), f))
# 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, '