import threading from datetime import datetime 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 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.""" return parser.render(cls.tokenize(wikitext, parser, propval)) @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.prop.name 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) @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 nv, deps = propval.nice_value() 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.parent[propval.prop.name] ext = translators.extension(propval.value) 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.parent[propval.prop.name] wikitext = propval.value ef = translators.extension(wikitext) href = '%s.%s%s' % (propval.element.ename, propval.prop.name, ef) if 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.prop.name)), End(LINK, href)]) elif ef == '.txt': return parser.render([ Text(unicode(wikitext,'utf-8'))]) else: return parser.render([ Start(LINK, href), Text("[%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) @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: datetime.strptime(w, TIMESTAMP_FORMAT), 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 references(flavor): """A list of links to other elements.""" default = [] @staticmethod def _enames(wikitext): import wiki refs = wiki.get_reference_enames(wikitext) return refs @staticmethod def toPython(wikitext,parser,propval=None): from .model import Element return [e for e in (Element.get_by(ename=e) for e in references._enames(wikitext)) if e is not None] @staticmethod def tokenize(wikitext, parser, propval=None): for ename in references._enames(wikitext): yield Start(UNORDERED_ITEM, 1) yield Entity(LINK, ename) yield End(UNORDERED_ITEM, 1) class reference(flavor): """A single link to another element.""" default = None @staticmethod def _ename(wikitext): import wiki refs = wiki.get_reference_enames(wikitext) if len(refs) > 1: raise wiki.WikiException("Expected 1 reference, got %d!" % (len(refs))) if len(refs) == 0: return None else: return refs.pop() @staticmethod def toPython(wikitext,parser,propval=None): from .model import Element return Element.get_by(ename=reference._ename(wikitext)) 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 @staticmethod def tokenize(wikitext, parser, propval=None): ename = reference._ename(wikitext) if ename is not None: yield Entity(LINK, ename) class labeledrefs(flavor): """A list of links to other elements with label text.""" default = [] @staticmethod def _ename_label_pairs(wikitext): import wiki reflabels = wiki.get_reference_enames_with_labels(wikitext) return reflabels @staticmethod def toPython(wikitext,parser,propval=None): from .model import Element return [[e, parser.render(l, format=Format())] for e,l in ((Element.get_by(ename=e), l) for e,l in labeledrefs._ename_label_pairs(wikitext)) if e is not None] @staticmethod def tokenize(wikitext, parser, propval=None): for ename,label in labeledrefs._ename_label_pairs(wikitext): yield Start(UNORDERED_ITEM, 1) yield Entity(LINK, ename) yield Text(": ") for t in label: yield t yield End(UNORDERED_ITEM, 1) FLAVORS = { 'text': text, 'string': string, 'integer': integer, 'boolean': boolean, 'references': references, 'reference': reference, 'labeledrefs': labeledrefs, 'blob': blob, 'macro': macro, 'raw': raw, 'timestamp': timestamp, }