import threading from datetime import datetime import dateutil.parser import os, sys from redbeans.formats import Format from redbeans.html import HTMLFormat from redbeans.latex import LaTeXFormat from redbeans.creole import tokenize from redbeans.tokens import * from . import translators # Add to this to give additional wiki-based output formats FORMATS = {'txt': Format, 'html': HTMLFormat, 'tex': LaTeXFormat} class flavor(object): # Indexed propvals are OK to search by and are loaded synchonously; # non-indexed propvals are loaded asychronously when editing. indexed = True binary = False # A raw propval never gets its contents treated as wikitext. raw = False # A quick propval can be (top-level) evaluate()d in constant time. quick = False is_ref = False owned = False default = "" default_formats = (u'.html',) @staticmethod def stringify(p): return unicode(p) @staticmethod def getExtensions(propval): return FORMATS.keys() @classmethod def evaluate(cls, wikitext, parser, propval=None): """This is called only for top-level evaluations.""" toks = cls.tokenize(wikitext, parser, propval) if parser.format.text_based: return parser.render(toks) else: return list(parser.irender(toks)) @classmethod def tokenize(cls, wikitext, parser, propval=None): yield Text(cls.stringify(cls.toPython( wikitext, parser, propval=propval))) @classmethod def toPython(cls, wikitext, parser, propval=None): import wiki raise wiki.WikiException("Flavor %s has no python conversion, so you can't use prop '%s' in an expression!"%(cls.__name__,propval.propname if propval is not None else wikitext)) class text(flavor): """Freeform or longer text; not searchable.""" indexed = False big = True @staticmethod def tokenize(wikitext, parser, propval=None): #print "Evaluating test: %s" % repr(wikitext) return tokenize(wikitext, parser.error_func) @staticmethod def toPython(wikitext, parser, propval=None): return parser.parse(wikitext, format=Format()) class raw(flavor): """Short searchable text that's not evaluated.""" raw = True quick = True @staticmethod def toPython(wikitext, parser, propval=None): return wikitext @staticmethod def tokenize(wikitext, parser, propval=None): yield Text(raw.toPython( wikitext, parser, propval=propval)) class macro(text): """Macro text only evaluated in the context of another propval.""" indexed = False big = True quick = True @staticmethod def evaluate(wikitext, parser, propval=None): if propval is not None: import wiki, formatting nv, deps = formatting.nice_value(propval) wiki.addDeps(deps) return parser.render([Start(CODEBLOCK), Text(nv), End(CODEBLOCK)]) else: return text.evaluate(self, wikitext, parser, propval) class blob(flavor): """Binary objects with no markup.""" indexed = False binary = True default_formats = (u'.png',u'.svg',u'.jpg') image_formats = (u'.png',u'.jpg',u'.gif',u'.svg',u'.eps') @staticmethod def getExtensions(propval): # TODO(xavid): This is a horrible hack. while propval.value == '<>': propval = propval.element.get_parent().get_propval( propval.propname) ext = propval.format if ext == 'creole' and not propval.value: # We just have empty.png right now. return ['.png']+FORMATS.keys() else: assert ext != 'creole', propval return [ext]+FORMATS.keys() @staticmethod def evaluate(wikitext, parser, propval=None): # TODO(xavid): This is a horrible hack. assert isinstance(wikitext, str), wikitext if wikitext == '<>': assert propval is not None and str(propval.value) == '<>', repr(str(propval.value)) while propval.value == '<>': propval = propval.element.get_parent().get_propval( propval.propname) wikitext = propval.value if propval is not None: ef = propval.format else: ef = '.dat' href = '%s.%s%s' % (propval.element.ename, propval.propname, ef) if wikitext is '' and parser == '.png': # Empty image. with open(os.path.join(os.path.dirname(__file__), 'data', 'empty.png')) as fil: return fil.read() elif ef == parser or (ef in FORMATS and hasattr(parser,'get_format') and isinstance(parser.get_format(),FORMATS[ef])): return wikitext elif ef in blob.image_formats: return parser.render([ Start(LINK, href), Entity(IMAGE, '%s.%s' % (propval.element.ename, propval.propname)), End(LINK, href)]) elif ef == '.txt': return parser.render([ Text(unicode(wikitext,'utf-8'))]) else: return parser.render([ Start(LINK, href), Text(u"[%s data]" % (ef)), End(LINK, href)]) @staticmethod def tokenize(wikitext, parser, propval=None): assert False class string(flavor): """Short searchable text.""" @staticmethod def tokenize(wikitext, parser, propval=None): return tokenize(wikitext, parser.error_func) @staticmethod def toPython(wikitext,parser,propval=None): assert isinstance(wikitext, unicode) return parser.parse(wikitext, format=Format()) def str_to_bool(s): return s.strip()[0].lower() not in ('n','f','0') class boolean(flavor): """True or false.""" default = False @staticmethod def toPython(wikitext,parser,propval=None): text = parser.parse(wikitext, format=Format()) return str_to_bool(text) def restricted(conv, defa, help, stringify=None): class rflav(flavor): __doc__ = help default = defa @staticmethod def toPython(wikitext,parser,propval=None): text = parser.parse(wikitext, format=Format()) try: show = conv(text) except ValueError: show = defa return show if stringify is not None: rflav.stringify = staticmethod(stringify) return rflav integer = restricted(int, -1, """An integral number.""") TIMESTAMP_FORMAT = "%Y-%m-%d %H:%M:%S" # TODO(xavid): This would probably be cleaner with restricted as a superclass. # Probably everything should stop being static and integer should # be an instance of class restricted. # TODO(xavid): Handle invalid dates as N/A, not as long ago. # We could use a more human-readable format for stringify, but it can't # be pretty.date() unless we make it add a dependency on DISCORDIA. timestamp = restricted(lambda w: dateutil.parser.parse(w), None, """A date and time.""", lambda p: (unicode(p.strftime(TIMESTAMP_FORMAT)) if p is not None else u"")) timestamp.now = staticmethod(lambda: datetime.strftime( datetime.now(), TIMESTAMP_FORMAT)) class Reference(object): def __init__(self, element, args, owner): assert hasattr(element, 'ename'), element self.element = element self.args = args self.owner = owner assert owner is not None, (element, args, owner) for a in args: assert isinstance(a, unicode), args def has_propval(self, propname): return self.element.has_propval(propname) def __repr__(self): return "[Reference: %s/%s]" % (self.element.ename, '/'.join(self.args)) def __eq__(self, other): return (self.element == getattr(other, 'element', None) and self.args == getattr(other, 'args', None)) def maybe_ref(d): from . import structure element = structure.get_element(d['ename']) if element is None: from . import wiki raise wiki.WikiException('Undefined element %s!' % d['ename']) elif d['args'] is not None or d['owner'] is not None: return Reference(element, d['args'] or [], d['owner']) else: return element class references(flavor): """A list of links to other elements.""" default = [] owned = True @staticmethod def _enames(wikitext, propval=None): import wiki dicts, errors = wiki.get_reference_enames(wikitext, propval=propval) #print >>sys.stderr, "refs=", refs return dicts, errors @staticmethod def toPython(wikitext,parser,propval=None): return [maybe_ref(d) for d in references._enames(wikitext, propval)[0] if d is not None] @staticmethod def tokenize(wikitext, parser, propval=None): dicts, errors = references._enames(wikitext, propval) for d in dicts: ename = d['ename'] args = d['args'] yield Start(UNORDERED_ITEM, 1) if args: yield Entity(LINK, ename + '/' + '/'.join(args)) else: yield Entity(LINK, ename) yield End(UNORDERED_ITEM, 1) for e in errors: yield Start(ERROR) for t in tokenize(e): yield t yield End(ERROR) class reference(flavor): """A single link to another element.""" default = None owned = True is_ref = True @staticmethod def _ename(wikitext, propval=None): import wiki dicts, errors = wiki.get_reference_enames(wikitext, propval=propval) if len(dicts) > 1: raise wiki.WikiException("Expected 1 reference, got %d!" % (len(refs))) if len(dicts) == 0: return None, errors else: return dicts.pop(), errors @staticmethod def toPython(wikitext,parser,propval=None): d, errors = reference._ename(wikitext, propval) if d is None: return None return maybe_ref(d) MARKUP = u'[[%s]]' @staticmethod def element_to_string(element): from . import wiki val, deps, metadata = wiki.evaluate(reference.MARKUP % element.ename, 'txt', element=element, flavor=reference) return val # TODO(xavid): share code with references better @staticmethod def tokenize(wikitext, parser, propval=None): d, errors = reference._ename(wikitext, propval) if d is not None: ename = d['ename'] args = d['args'] owner = d['owner'] from . import wiki eover_dict = {} if owner is not None: eover_dict[u'owner'] = owner with wiki.eoverride(eover_dict): if args: yield Entity(LINK, ename + '/' + '/'.join(args)) else: yield Entity(LINK, ename) for e in errors: yield Start(ERROR) for t in tokenize(e): yield t yield End(ERROR) class labeledrefs(flavor): """A list of links to other elements with label text.""" default = [] owned = True @staticmethod def _ename_label_pairs(wikitext, propval=None): import wiki dictlabels, errors = wiki.get_reference_enames_with_labels( wikitext, propval=propval) return dictlabels, errors @staticmethod def toPython(wikitext,parser,propval=None): from . import structure ret = [] def make_render_label(l): def render_label(argstr, content): return l return render_label for e,l in ((structure.get_element(d['ename']), l) for d,l in labeledrefs._ename_label_pairs(wikitext, propval)[0]): if e is not None: ret.append([e, make_render_label(l)]) return ret @staticmethod def tokenize(wikitext, parser, propval=None): elps, errors = labeledrefs._ename_label_pairs(wikitext, propval) for d,label in elps: yield Start(UNORDERED_ITEM, 1) yield Entity(LINK, d['ename']) yield Text(u": ") if label is not None: for t in label: yield t else: yield Error("No label!") yield End(UNORDERED_ITEM, 1) for e in errors: yield Start(ERROR) for t in tokenize(e): yield t yield End(ERROR) FLAVORS = { 'text': text, 'string': string, 'integer': integer, 'boolean': boolean, 'references': references, 'reference': reference, 'labeledrefs': labeledrefs, 'blob': blob, 'macro': macro, 'raw': raw, 'timestamp': timestamp, }