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('
') for t in headfoot[1]: yield t yield Literal('
') yield Literal('
') for t in headfoot[2]: yield t yield Literal('
') yield Literal('
') for t in headfoot[3]: yield t 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('') for t in headfoot[2]: yield t yield Literal('') for t in headfoot[3]: yield t 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') yield Literal('\n\n') for t in html_table_footer(headfoot): yield t yield Literal('
\n') for t in front: yield t yield Literal('\n\n') for t in back: yield t yield Literal('\n
\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') yield Literal('\n\n') for t in html_table_footer(headfoot): yield t yield Literal('
\n') for t in label: yield t yield Literal('\n\n') for t in content: yield t yield Literal('\n
\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') yield Literal('\n') yield Literal('\n') yield Literal('
') yield img yield Literal('\n') for t in content: yield t 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')