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,
}