import sys, re
from bazbase.wiki import (macros,environment,illegal,divided,
WikiException, NoContentException,
safesplit, parse_macro_args, recurse,
baz_eval, full_eval, leafprop, render_propval,
render_allow_override, get_format,
get_element, get_current_element, get_propname,
get_topthis, get_context, get_moverride,
atom, eoverride, moverride, context,
makeRestricted, tokens_text, string_tokenize)
from bazbase.flavors import FORMATS
from redbeans.tokens import *
from redbeans.latex import LATEX_SIZES
from . import custom
def moverride_thunk(ename, pname, form):
with moverride(dict(form=form)):
for t in recurse(ename, pname):
yield t
def get_headfoot(form, ename):
return (moverride_thunk(ename, u'topleft', form),
moverride_thunk(ename, u'topright', form),
moverride_thunk(ename, u'bottomleft', form),
moverride_thunk(ename, u'bottomright', form))
def html_div_headers(headfoot):
yield Literal('
')
yield Literal('')
yield Literal('')
yield Literal('')
def html_table_header(headfoot):
yield Literal('| ')
for t in headfoot[0]:
yield t
yield Literal('')
for t in headfoot[1]:
yield t
yield Literal(' | |
')
def html_table_footer(headfoot):
yield Literal('')
def latex_cmd_headers(headfoot):
yield Literal('\\lhead{')
for t in headfoot[0]:
yield t
yield Literal('}\\rhead{')
for t in headfoot[1]:
yield t
yield Literal('}\\lfoot{')
for t in headfoot[2]:
yield t
yield Literal('}\\rfoot{')
for t in headfoot[3]:
yield t
yield Literal('}\n')
def latex_arg_headers(headfoot):
first = True
for hf in headfoot:
if not first:
yield Literal('}{')
else:
first = False
for t in hf:
yield t
def form(arglist, content=None):
"""Placeholder that evaluates to 'sheet' or 'card' or 'badge' when
evaluating headers and footers."""
return ""
macros.form = form
def sheet(arglist, content):
"""Render content as a sheet. Takes an element to get headers and
footers from, and optionally a 'color' parameter for paper color."""
args = parse_macro_args(arglist)
color = ""
headfoot = [(),(),(),()]
for key,val in args.items():
if key == 'color':
color = baz_eval(val).strip()
elif key == 1:
headfoot = get_headfoot(u'sheet', val)
else:
raise WikiException("Unknown arg '%s' in sheet!"
% (key))
if not color.strip():
color = "white"
if get_format() == FORMATS['html']:
yield Literal('' % color)
for t in html_div_headers(headfoot):
yield t
for t in content:
yield t
yield Entity(ENV_BREAK, True)
yield Literal('
')
elif get_format() == FORMATS['tex']:
yield Literal('\\begin{sheet}\n')
for t in latex_cmd_headers(headfoot):
yield t
for t in content:
yield t
yield Entity(ENV_BREAK, True)
yield Literal('\n\\end{sheet}')
else:
for t in content:
yield t
macros.sheet = sheet
# In inches, (width, height, font size).
SIZES = {
'normal': (3.25, 2, 0), # item card sized
'large': (3.6, 3.1, 0), # ability card sized
'tall': (2.5, 3.4, 0), # approximately playing card sized
'mini': (2.5, 1.5, -2),
}
def card(arglist, content):
"""Render content as a card. Takes an element to get headers and
footers from, and optionally a 'color' parameter for paper color
and a 'size' parameter."""
parts = safesplit(content, ('flip',))
args = parse_macro_args(arglist)
color = ""
size = "normal"
headfoot = [(),(),(),()]
for key,val in args.items():
if key == 'color':
color = baz_eval(val).strip()
elif key == 'size':
size = baz_eval(val).strip()
elif key == 1:
headfoot = get_headfoot(u'card', val)
else:
raise WikiException("Unknown arg '%s' in card!"
% (key))
if not color.strip():
color = "white"
if size not in SIZES:
raise WikiException("Unknown card size '%s'; valid options are: %s."
% (size, ', '.join(SIZES.keys())))
if len(parts) > 2:
raise WikiException("More than one ##flip## in a ##card##!")
else:
if len(parts) < 2:
front = parts[0]
back = ""
else:
front,back = parts
if get_format() == FORMATS['html']:
yield Literal('\n' % (color, size))
for t in html_table_header(headfoot):
yield t
yield Literal('\n| \n')
for t in front:
yield t
yield Literal('\n | \n')
yield Literal('\n')
for t in back:
yield t
yield Literal('\n | \n
\n')
for t in html_table_footer(headfoot):
yield t
yield Literal('
\n')
elif get_format() == FORMATS['tex']:
w, h, font = SIZES[size]
font = LATEX_SIZES[font]
yield Literal('\\begin{card}{%sin}{%sin}{\\%s}\n' % (w, h, font))
yield Literal('\\front{')
for t in latex_cmd_headers(headfoot):
yield t
for t in front:
yield t
yield Literal('}\n\\back{')
for t in back:
yield t
yield Literal('}\n\\end{card}\n')
else:
for t in front:
yield t
yield Entity(ENV_BREAK)
for t in back:
yield t
macros.card = card
macros.flip = illegal('flip', 'card', 'sign')
def packet(arglist,content):
"""Render content as a packet. Takes a label for the outside and
an element to get headers and footers from, and optionally a
'color' parameter for paper color and a 'style' parameter ("envelope"
or "folding")."""
args = parse_macro_args(arglist)
label = ""
color = ""
style = "Envelope"
headfoot = [(), (), (), ()]
for key,val in args.items():
if key == 'color':
color = baz_eval(val).strip()
elif key == 1:
label = full_eval(val)
elif key == 2:
headfoot = get_headfoot(u'packet', val)
elif key == 'style':
style = baz_eval(val).strip().capitalize()
if style not in ("Envelope","Folding"):
raise WikiException('The only valid packet styles are "envelope" and "folding"!')
else:
raise WikiException("Unknown arg '%s' in packet!"
% (key))
if not color.strip():
color = "white"
if get_format() == FORMATS['html']:
yield Literal('\n' % (style.lower(),
color))
for t in html_table_header(headfoot):
yield t
yield Literal('\n| \n')
for t in label:
yield t
yield Literal('\n | \n')
yield Literal('\n')
for t in content:
yield t
yield Literal('\n | \n
\n')
for t in html_table_footer(headfoot):
yield t
yield Literal('
\n')
elif get_format() == FORMATS['tex']:
yield Literal('\\%sPacket{' % (style,))
for t in latex_arg_headers(headfoot):
yield t
yield Literal('}{')
for t in label:
yield t
yield Literal('}{')
for t in content:
yield t
yield Literal('}')
else:
yield Text(u"Packet: ")
for t in label:
yield t
macros.packet = packet
def notebook(arglist,content):
args = parse_macro_args(arglist)
front = ""
color = ""
headfoot = [(), (), (), ()]
for key,val in args.items():
if key == 'color':
color = baz_eval(val).strip()
elif key == 1:
front = full_eval(val)
elif key == 2:
headfoot = get_headfoot(u'notebook', val)
else:
raise WikiException("Unknown arg '%s' in notebook!"
% (key))
if not color.strip():
color = "white"
if get_format() == FORMATS['html']:
yield Literal('\n' % (color))
yield Literal('
')
for t in html_div_headers(headfoot):
yield t
for t in front:
yield t
yield Literal('
')
for t in content:
yield t
yield Literal('
\n')
elif get_format() == FORMATS['tex']:
yield Literal('\\startnotebook{')
for t in latex_arg_headers(headfoot):
yield t
yield Literal('}{')
for t in front:
yield t
yield Literal('}')
for t in content:
yield t
yield Literal('\n\\endnotebook\n')
else:
yield Text(u"Notebook: ")
for t in front:
yield t
macros.notebook = notebook
def page(arglist,content):
"""Dummy page for previewing outside a notebook."""
args = parse_macro_args(arglist)
if 1 in args:
title = baz_eval(args[1]).strip()
else:
title = getattr(get_format(), 'gknextpage', 1)
get_format().gknextpage = title+1
title = unicode(title)
if get_format() == FORMATS['html']:
yield Literal('')
yield Text(title)
yield Literal('
\n')
for t in content:
yield t
yield Literal('')
elif get_format() == FORMATS['tex']:
yield Literal(r"\begin{nbpage}{")
yield Text(title)
yield Literal('}\n')
for t in content:
yield t
yield Literal('\n\\end{nbpage}\n')
else:
yield Start(HEADING, 2)
yield Text(title)
yield End(HEADING, 2)
for t in content:
yield t
macros.page = page
# TODO(xavid): unify badges and items more?
def badge(arglist,content):
args = parse_macro_args(arglist)
color = ""
style = "light"
size = "normal"
headfoot = [(), (), (), ()]
for key,val in args.items():
if key == 'color':
color = baz_eval(val).strip()
elif key == 1:
headfoot = get_headfoot(u'badge', val)
elif key == 'size':
size = baz_eval(val).strip()
elif key == 'style':
style = baz_eval(val).strip()
valid_styles = ("light", "dark", "simple", "mini")
if style not in valid_styles:
raise WikiException('The only valid badge styles are: %s!'
% ', '.join(valid_styles))
else:
raise WikiException("Unknown arg '%s' in badge!"
% (key))
if not color.strip():
color = "white"
if size not in SIZES:
raise WikiException("Unknown badge size '%s'; valid options are: %s."
% (size, ', '.join(SIZES.keys())))
if get_format() == FORMATS['html']:
yield Literal('\n' % (style.lower(), color,
size))
for t in html_div_headers(headfoot):
yield t
for t in content:
yield t
yield Literal('
\n')
elif get_format() == FORMATS['tex']:
w, h, font = SIZES[size]
font = LATEX_SIZES[font]
yield Literal('\\NameBadge[%s]{%sin}{%sin}{\\%s}{'
% (style, w, h, font))
for t in latex_arg_headers(headfoot):
yield t
yield Literal('}{')
for t in content:
yield t
yield Literal('}')
else:
yield Text(u"Badge: ")
for t in content:
yield t
macros.badge = badge
def sign(arglist,content):
args = parse_macro_args(arglist)
parts = safesplit(content, ('flip',))
assert len(parts) in (1, 2), parts
name = None
color = u""
size = None
location = u""
blurb = None
headfoot = [(), (), (), ()]
for key,val in args.items():
if key == 'color':
color = baz_eval(val).strip()
elif key == 'blurb':
blurb = [Text(baz_eval(val).strip())]
elif key == 'location':
location = baz_eval(val).strip()
elif key == 'size':
size = baz_eval(val).strip()
elif key == 1:
headfoot_ename = val
headfoot = get_headfoot(u'sign', val)
elif key == 2:
name = [[Text(baz_eval(val).strip())]] * 2
else:
raise WikiException("Unknown arg '%s' in packet!"
% (key))
if not color.strip():
color = "white"
if name is None:
name = [moverride_thunk(headfoot_ename, u'name', u'sign')]
if len(parts) == 2:
name.append(moverride_thunk(headfoot_ename, u'name', u'signback'))
if blurb is None:
blurb = moverride_thunk(headfoot_ename, u'blurb', u'sign')
if size is None:
size = baz_eval(u'%s.%s' % (headfoot_ename, u'size'))
size = size.capitalize()
if size not in ("Big","Medium","Small"):
raise WikiException("Sign size must be big, medium, or small, not %s!"
% size)
if get_format() == FORMATS['html']:
for i in xrange(len(parts)):
yield Literal('\n' % (size.lower(), color))
for t in html_div_headers(headfoot):
yield t
yield Literal('
')
for t in name[i]:
yield t
yield Literal('
')
yield Literal('
\n')
for t in parts[i]:
yield t
yield Literal('
\n')
yield Literal('
')
for t in blurb:
yield t
yield Literal('
\n')
yield Literal('
\n')
elif get_format() == FORMATS['tex']:
yield Literal(r'\headfoot{')
for t in latex_arg_headers(headfoot):
yield t
yield Literal('}\\Sign[\\%s]{' % (size,))
for t in name[0]:
yield t
yield Literal('}{')
yield Text(location)
yield Literal('}{')
for t in parts[0]:
yield t
yield Entity(ENV_BREAK, True)
yield Literal('}{')
if len(parts) > 1:
for t in name[1]:
yield t
yield Literal('}{')
for t in parts[1]:
yield t
yield Entity(ENV_BREAK, True)
else:
yield Literal('}{')
yield Literal('}{')
for t in blurb:
yield t
yield Literal('}')
else:
yield Text(u'Sign: ')
for t in content:
yield t
macros.sign = sign
def pagefold(arglist, content):
args = parse_macro_args(arglist)
parts = safesplit(content, ('break',))
headfoot_ename = args.pop(1)
headfoot = get_headfoot(u'pagefold', headfoot_ename)
assert len(args) == len(parts)
if get_format() == FORMATS['tex']:
yield Literal('\\begin{pagefold}\n')
for t in latex_cmd_headers(headfoot):
yield t
total = 0
flipped = False
for i in xrange(len(parts)):
part = parts[i]
height = int(args[i + 2])
yield Literal(r'\pfpart{')
yield Literal(str((height / 100.) * 10.3))
yield Literal(r'in}{')
for t in part:
yield t
yield Literal('}\n')
total += height
if total == 100:
if not flipped:
yield Literal('\\clearpage\n')
flipped = True
total = 0
elif total > 100:
raise WikiException("Pagefold side doesn't add up to 100!")
else:
yield Literal('\\hrule\n')
yield Literal('\\end{pagefold}\n')
else:
# TODO(xavid): cleaner HTML version.
for part in parts:
for t in part:
yield t
yield Entity(HRULE)
macros.pagefold = pagefold
macros.upsidedown = environment('upsidedown', texmode='command')
#macros.header = divided('header','gap','left','right')
#macros.footer = divided('footer','gap','left','right')
#macros.gap = illegal('gap','header','footer')
def imageheader(arglist, content=None):
args = parse_macro_args(arglist)
if 1 not in args or 2 not in args or 3 not in args or len(args) != 3:
raise WikiException(
"##imageheader## takes exactly 3 parameters!")
if content is None:
raise NoContentException("##imageheader## needs content!")
width = baz_eval(args[2]).strip()
height = baz_eval(args[3]).strip()
img = Entity(IMAGE, "%s^d%sx%s" % (args[1], width, height))
if get_format() == FORMATS['html']:
yield Literal('\n')
elif get_format() == FORMATS['tex']:
yield Literal('\\imageheader{')
yield img
yield Literal('}{%spx}{%spx}{' % (width, height))
for t in content:
yield t
yield Literal('}')
else:
yield Start(HEADER, 1)
for t in content:
yield t
yield End(HEADER, 1)
macros.imageheader = imageheader
macros.secret = environment(
'secret', doc="""Mark this content as secret, for printing.""")
def labels(arglist, content):
args = parse_macro_args(arglist)
h = baz_eval(args[1])
w = baz_eval(args[2])
v_margin = baz_eval(args[3])
h_margin = baz_eval(args[4])
if 5 in args:
v_gap = baz_eval(args[5])
else:
v_gap = 0
if 6 in args:
h_gap = baz_eval(args[6])
else:
h_gap = 0
if get_format() == FORMATS['tex']:
rows = int(11. / (h + v_gap))
cols = int(8.5 / (w + h_gap))
yield Literal('\\LabelCols=%s\n' % cols)
yield Literal('\\LabelRows=%s\n' % rows)
yield Literal('\\LeftPageMargin=%sin\n' % h_margin)
yield Literal('\\RightPageMargin=%sin\n' % h_margin)
yield Literal('\\TopPageMargin=%sin\n' % v_margin)
yield Literal('\\BottomPageMargin=%sin\n' % v_margin)
yield Literal('\\InterLabelColumn=%sin\n' % h_gap)
yield Literal('\\InterLabelRow=%sin\n' % v_gap)
yield Literal('\\LabelSetup\n')
for t in content:
if t.style == UNORDERED_ITEM:
if t.op == START:
yield Literal(r'\addresslabel{\centering ')
elif t.op == END:
yield Literal('}\n')
else:
assert False, t
else:
yield t
else:
for t in content:
yield t
macros.labels = labels
def current_run():
return '1'
def runtime(arglist):
ename, propname = arglist[0].split('.', 1)
element = get_element(ename)
propname = get_propname(propname)
propval = element.get_propval(propname)
run = current_run()
if False: #propval.has_overlay(run):
yield Start(STRIKE)
yield Entity(MACRO, ('%s.%s' % (ename, propname), None))
yield End(STRIKE)
yield Text(u' ')
yield Start(BOLD)
yield Text(propval.get_overlay(run))
yield End(BOLD)
else:
for t in recurse(ename, propname): yield t
macros.runtime = runtime
def todo(arglist, content=None):
makeRestricted()
yield Start(BOLD)
yield Start(ITALIC)
yield Text(u"TODO: ")
if arglist:
yield Text(u' '.join(arglist))
if content:
for t in content:
yield t
yield Text(u" :TODO")
yield End(ITALIC)
yield End(BOLD)
macros.todo = todo
macros.TODO = todo
def quote(start, end=None):
def env(arglist, content):
parts = safesplit(content, ('break',))
if len(parts) > 2:
raise WikiException("Too many parts in a quote!")
yield start
yield Start(ITALIC)
for t in parts[0]:
yield t
yield End(ITALIC)
if end is not None:
yield end
if len(parts) > 1:
yield Start(RIGHT)
yield Start(ITALIC)
for t in parts[1]:
yield t
yield End(ITALIC)
yield End(RIGHT)
return env
macros.cenquote = quote(Start(CENTER), End(CENTER))
macros.bigquote = quote(Entity(NOINDENT))
GENDER_CONV_MAP = {'?': 'A'}
def pronoun_helper(mfna):
genderp = u'gender'
#leaf = get_element(u'leaf')
#assert leaf is not None
#gen = render_allow_override(leaf, genderp)
#if gen is None:
e = get_current_element()
assert e is not None
gen_full = render_allow_override(e, genderp, '?')
if ' ' in gen_full:
raise WikiException("Unknown gender '%s'" % gen_full)
gen = gen_full[0:1].upper()
if 'A' not in mfna:
mfna['A'] = mfna['M'] + [Text(u'/')] + mfna['F']
for g in ('N', 'S'):
if g not in mfna:
mfna[g] = mfna['A']
if gen in GENDER_CONV_MAP:
gen = GENDER_CONV_MAP[gen]
if gen in mfna:
return mfna[gen]
else:
raise WikiException("Unknown gender: %s" % gen_full)
def mfn_generic(*order):
def mfn(arglist, content):
parts = safesplit(content, ('break',))
mfna = {}
for x in xrange(min(len(order), len(parts))):
mfna[order[x]] = parts[x]
return pronoun_helper(mfna)
return mfn
macros.mf = macros.mfn = mfn_generic('M', 'F', 'N')
macros.fm = macros.fmn = mfn_generic('F', 'M', 'N')
def pronoun(**mfna):
for k in mfna:
mfna[k] = [Text(unicode(mfna[k]))]
def pn(arglist, content=None):
return pronoun_helper(mfna)
return pn
# Helpers
def _pron(mac, M, F, **mfna):
mfna['M'] = M
mfna['F'] = F
# Doing this explicitly causes the uppercasing to DTRT.
if 'A' not in mfna:
mfna['A'] = '%s/%s' % (F, M)
if 'S' not in mfna and mac.startswith('th'):
mfna['S'] = mac[2:]
for a in [mac] + mfna.values():
if not hasattr(macros, a):
pn = pronoun(**mfna)
if a == mac:
pn.hidden = 'pronoun'
else:
pn.hidden = 'pronounvariation'
setattr(macros, a, pn)
upper = dict((k, mfna[k][0].upper() + mfna[k][1:]) for k in mfna)
mac = mac[0].upper()+mac[1:]
for a in [mac] + upper.values():
if not hasattr(macros, a):
pn = pronoun(**upper)
pn.hidden = 'pronounupper'
setattr(macros, a, pn)
# First in the list wins
_pron('they', 'he', 'she', N='it')
_pron('them', 'him', 'her', N='it')
_pron('their', 'his', 'her', N='its')
_pron('theirs', 'his', 'hers', N='its')
_pron('themself', 'himself', 'herself', N='itself')
_pron('spouse', 'husband', 'wife', N='spouse', A='spouse')
_pron('offspring', 'son', 'daughter', N='child', A='child')
_pron('kid', 'boy', 'girl', N='kid', A='kid')
_pron('sibling', 'brother', 'sister', N='sibling', A='sibling')
_pron('parent', 'father', 'mother', N='parent', A='parent')
_pron('uncle', 'uncle', 'aunt')
_pron('nephew', 'nephew', 'niece')
_pron('person', 'man', 'woman', N='person', A='person')
_pron('human', 'man', 'woman', N='human', A='human')
_pron('gender', 'male', 'female', N='neuter', A='uncertain', S='other')
# For sarcastic titles.
_pron('mister', 'mister', 'miss')
def youish(you):
def y(arglist, content):
current = get_element(u'current')
if get_element(u'leaf') == current:
yield Text(you)
else:
had_content = False
for t in content:
had_content = True
yield t
if not had_content:
for t in recurse(current, u'name'):
yield t
return y
macros.You = youish(u'You')
macros.you = youish(u'you')
PRE=u'<<>>'
SUF=u'<<>>'
def parsename(name):
gd = dict((k, '') for k in ('first', 'middle', 'last',
'prefix', 'suffix'))
gd['full'] = name
if SUF in name:
name, gd['suffix'] = name.split(SUF, 1)
gd['full'] = gd['full'].replace(SUF, ' ')
elif ", " in name:
name, gd['suffix'] = name.split(", ", 1)
if PRE in name:
gd['prefix'], name = name.split(PRE, 1)
gd['full'] = gd['full'].replace(PRE, ' ')
bits = name.split()
assert len(bits) >= 1
if len(bits) == 1:
gd['first'] = bits[0]
elif not gd['prefix'] and bits[0].endswith('.'):
gd['prefix'] = bits[0]
if len(bits) == 2:
gd['last'] = bits[1]
elif len(bits) == 3:
gd['first'], gd['last'] = bits[1:]
else:
gd['first'] = bits[1]
gd['last'] = bits[-1]
gd['middle'] = ' '.join(bits[2:-1])
else:
if len(bits) == 2:
gd['first'], gd['last'] = bits
else:
gd['first'] = bits[0]
gd['last'] = bits[-1]
gd['middle'] = ' '.join(bits[1:-1])
gd['informal'] = gd['first'] or gd['last']
gd['formal'] = gd['last'] or gd['first']
if gd['prefix']:
gd['formal'] = "%s %s" % (gd['prefix'], gd['formal'])
# TODO(xavid): handle Japanese/etc. name order
if 'last' in gd:
gd['surname'] = gd['last']
if 'first' in gd:
gd['given'] = gd['first']
return gd
def namepart(part, nameprop=u'name'):
def np(arglist, content=None):
argstr = u' '.join(arglist)
if argstr.strip():
namep = argstr.strip()
else:
namep = nameprop
e = get_current_element()
name = render_allow_override(e, namep,
moverrides=dict(pre=PRE, suf=SUF))
if name is None:
raise WikiException("%s.%s is not defined for name parsing!"
% (e.ename,namep))
#print >>sys.stderr, repr(name)
namedict = parsename(name)
yield Text(namedict[part])
np.hidden = 'namepart'
return np
macros.pre = atom(
" ",
doc="Marks the end of a title or prefix for name parsing.")
macros.suf = atom(
" ",
doc="Marks the start of an end title or suffix for name parsing.")
macros.first = namepart('first')
macros.middle = namepart('middle')
macros.last = namepart('last')
macros.full = namepart('full')
macros.given = namepart('given')
macros.surname = namepart('surname')
macros.formal = namepart('formal')
macros.informal = namepart('informal')
macros.player = leafprop(u'player')
macros.contact = leafprop(u'contact')