From 041e0637bc41e77edf94282e6b64fad89384f29e Mon Sep 17 00:00:00 2001 From: mdipierro Date: Mon, 30 Jul 2012 20:23:05 -0500 Subject: [PATCH] markmin and languages autolinks, thanks Vladyslav --- VERSION | 2 +- gluon/contrib/markmin/markmin2html.py | 185 +++++++++++++++++++------- gluon/languages.py | 71 ++++++---- 3 files changed, 181 insertions(+), 77 deletions(-) diff --git a/VERSION b/VERSION index fbbe1d31..fbcfe843 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -Version 2.00.0 (2012-07-30 16:25:32) dev +Version 2.00.0 (2012-07-30 20:23:00) dev diff --git a/gluon/contrib/markmin/markmin2html.py b/gluon/contrib/markmin/markmin2html.py index 2c6da1e7..893c4950 100755 --- a/gluon/contrib/markmin/markmin2html.py +++ b/gluon/contrib/markmin/markmin2html.py @@ -71,15 +71,22 @@ a list with tables in it: -----------:blockquoteclass[blockquoteid] This this a new paragraph -with a table. Table has header and footer: +with a table. Table has header, footer, sections, odd and even rows: ------------------------------- **Title 1**|**Title 2**|**Title 3** ============================== data 1 | data 2 | 2.00 -data 4 |data5(long)| 23.00 - |data 8 | 33.50 +data 3 |data4(long)| 23.00 + |data 5 | 33.50 ============================== -Total: | 3 items | 58.50 +New section|New data | 5.00 +data 1 |data2(long)|100.45 + |data 3 | 12.50 +data 4 | data 5 | .33 +data 6 |data7(long)| 8.01 + |data 8 | 514 +============================== +Total: | 9 items |698,79 ------------------------------:tableclass1[tableid2] ## Multilevel @@ -515,14 +522,8 @@ regex_num=re.compile(r"^\s*[+-]?((\d+(\.\d*)?)|\.\d+)([eE][+-]?[0-9]+)?\s*$") regex_list=re.compile('^(?:(#{1,6}|\.+|\++|\-+)(\.)?\s+)?(.*)$') regex_bq_headline=re.compile('^(?:(\.+|\++|\-+)(\.)?\s+)?(-{3}-*)$') regex_tq=re.compile('^(-{3}-*)(?::(?P[a-zA-Z][_a-zA-Z\-\d]*)(?:\[(?P

[a-zA-Z][_a-zA-Z\-\d]*)\])?)?$') -regex_qr = re.compile(r'(?/=])qr:(?P\w+://[\w\d\-+?&%/:.]+)',re.M) -regex_embed = re.compile(r'(?/=])embed:(?P\w+://[\w\d\-+_=?%&/:.]+)', re.M) -regex_iframe = re.compile(r'(?/=])iframe:(?P\w+://[\w\d\-+=?%&/:.]+)', re.M) -regex_auto_image = re.compile(r'(?/=])(?P\w+://[\w\d\-+_=%&/:.]+\.(jpeg|JPEG|jpg|JPG|gif|GIF|png|PNG)(\?[\w\d/\-+_=%&:.]+)?)',re.M) -regex_auto_video = re.compile(r'(?/=])(?P\w+://[\w\d\-+_=%&/:.]+\.(mp4|MP4|mpeg|MPEG|mov|MOV|ogv|OGV)(\?[\w\d/\-+_=%&:.]+)?)',re.M) -regex_auto_audio = re.compile(r'(?/=])(?P\w+://[\w\d\-+_=%&/:.]+\.(mp3|MP3|wav|WAV|ogg|OGG)(\?[\w\d/\-+_=%&:.]+)?)',re.M) +regex_proto = re.compile(r'(?/=])(?P

\w+):(?P\w+://[\w\d\-+=?%&/:.]+)', re.M) regex_auto = re.compile(r'(?/=])(?P\w+://[\w\d\-+_=?%&/:.]+)',re.M) - regex_link=re.compile(r'('+LINK+r')|\[\[(?P.+?)\]\]') regex_link_level2=re.compile(r'^(?P\S.*?)?(?:\s+\[(?P.+?)\])?(?:\s+(?P\S+))?(?:\s+(?P

popup))?\s*$') regex_media_level2=re.compile(r'^(?P\S.*?)?(?:\s+\[(?P.+?)\])?(?:\s+(?P\S+))?\s+(?P

img|IMG|left|right|center|video|audio)(?:\s+(?P\d+px))?\s*$') @@ -536,7 +537,48 @@ def markmin_escape(text): """ insert \\ before markmin control characters: '`:*~[]{}@$ """ return regex_markmin_escape.sub(lambda m: '\\'+m.group(0).replace('\\','\\\\'), text) -def render(text,extra={},allowed={},sep='p',URL=None,environment=None,latex='google',auto=True,class_prefix='',id_prefix='markmin_'): +def autolinks(url): + """ + it automatically converts the url to link, + image, video or audio tag + """ + u_url=url.lower() + if u_url.endswith(('jpeg','gif','png')): + return '' % url + elif u_url.endswith(('mp4','mpeg','mov','ogv')): + return '' % url + elif u_url.endswith(('mp3','wav','ogg')): + return '' % url + return '%s' % (url,url) + +def protolinks(proto, url): + """ + it converts url to html-string using appropriate proto-prefix: + Uses for construction "proto:url", e.g.: + "iframe:http://www.example.com/path" will call protolinks() + with parameters: + proto="iframe" + url="http://www.example.com/path" + """ + if proto in ('iframe','embed'): #== 'iframe': + return ''%url + #elif proto == 'embed': # NOTE: embed is a synonym to iframe now + # return '%s>'%(url,class_prefix,url) + elif proto == 'qr': + return 'qr code'%url + return proto+':'+url + +def render(text, + extra={}, + allowed={}, + sep='p', + URL=None, + environment=None, + latex='google', + autolinks=autolinks, + protolinks=protolinks, + class_prefix='', + id_prefix='markmin_'): """ Arguments: - text is the text to be processed @@ -546,8 +588,18 @@ def render(text,extra={},allowed={},sep='p',URL=None,environment=None,latex='goo allowed = dict(code=('python','cpp','java')) - sep can be 'p' to separate text in

...

or can be 'br' to separate text using
- - auto is a True/False value (default is True) - - enables auto links processing for iframe,embed,qr,url,image,video,audio + - URL - + - environment is a dictionary of environment variables (can be accessed with @{variable} + - latex - + - autolinks is a function to convert auto urls to html-code (default is autolinks(url) ) + - protolinks is a function to convert proto-urls (e.g."proto:url") to html-code + (default is protolinks(proto,url)) + - class_prefix is a prefix for ALL classes in markmin text. E.g. if class_prefix='my_' + then for ``test``:cls class will be changed to "my_cls" (default value is '') + - id_prefix is prefix for ALL ids in markmin text (default value is 'markmin_'). E.g.: + -- [[id]] will be converted to + -- [[link #id]] will be converted to link + -- ``test``:cls[id] will be converted to test >>> render('this is\\n# a section\\n\\nparagraph') '

this is

a section

paragraph

' @@ -583,7 +635,7 @@ def render(text,extra={},allowed={},sep='p',URL=None,environment=None,latex='goo '
  1. this
  2. is
  3. a list

and this

  1. is
  2. another
' >>> render("----\\na | b\\nc | d\\n----\\n") - '
ab
cd
' + '
ab
cd
' >>> render("----\\nhello world\\n----\\n") '
hello world
' @@ -630,6 +682,15 @@ def render(text,extra={},allowed={},sep='p',URL=None,environment=None,latex='goo >>> render("auto-image: (http://example.com/image.jpeg)") '

auto-image: ()

' + >>> render("qr: (qr:http://example.com/image.jpeg)") + '

qr: (qr code)

' + + >>> render("embed: (embed:http://example.com/page)") + '

embed: ()

' + + >>> render("iframe: (iframe:http://example.com/page)") + '

iframe: ()

' + >>> render("title1: [[test message [simple \[test\] title] http://example.com ]] test") '

title1: test message test

' @@ -780,14 +841,11 @@ def render(text,extra={},allowed={},sep='p',URL=None,environment=None,latex='goo text = regex_link.sub(mark_link, text) text = escape(text) - if auto: - text = regex_iframe.sub('',text) - text = regex_embed.sub('\g',text) - text = regex_qr.sub('qr code',text) - text = regex_auto_image.sub('', text) - text = regex_auto_video.sub('', text) - text = regex_auto_audio.sub('', text) - text = regex_auto.sub('\g', text) + if protolinks: + text = regex_proto.sub(lambda m: protolinks(*m.group('p','k')), text) + + if autolinks: + text = regex_auto.sub(lambda m: autolinks(m.group('k')), text) ############################################################# # normalize spaces @@ -904,6 +962,7 @@ def render(text,extra={},allowed={},sep='p',URL=None,environment=None,latex='goo tout=[] thead=[] tbody=[] + rownum=0 t_id = '' t_cls = '' @@ -914,9 +973,10 @@ def render(text,extra={},allowed={},sep='p',URL=None,environment=None,latex='goo if s.count('=')==len(s) and len(s)>3: # header or footer if not thead: # if thead list is empty: thead = tout - else: # if tbody list is empty: + else: tbody.extend(tout) tout = [] + rownum=0 lineno+=1 continue @@ -926,12 +986,17 @@ def render(text,extra={},allowed={},sep='p',URL=None,environment=None,latex='goo t_id = m.group('p') or '' break - tout.append(''+''.join(['%s'% \ - (' class="num"' - if regex_num.match(f) - else '', - f.strip() - ) for f in s.split('|')])+'') + if rownum % 2: + tr = '' + else: + tr = '' if rownum == 0 else '' + tout.append(tr+''.join(['%s'% \ + (' class="num"' + if regex_num.match(f) + else '', + f.strip() + ) for f in s.split('|')])+'') + rownum+=1 lineno+=1 t_cls = ' class="%s%s"'%(class_prefix, t_cls) if t_cls and t_cls != 'id' else '' @@ -990,7 +1055,8 @@ def render(text,extra={},allowed={},sep='p',URL=None,environment=None,latex='goo URL, environment, latex, - auto, + autolinks, + protolinks, class_prefix, id_prefix) ) @@ -1107,7 +1173,7 @@ def render(text,extra={},allowed={},sep='p',URL=None,environment=None,latex='goo style = ' style="float:%s"' % p if p in ('video','audio'): t = render(t, {}, {}, 'br', URL, environment, latex, - auto, class_prefix, id_prefix) + autolinks, protolinks, class_prefix, id_prefix) return '<%(p)s controls="controls"%(title)s%(width)s>%(t)s' \ % dict(p=p, title=title, width=width, k=k, t=t) alt = ' alt="%s"'%escape(t).replace(META, DISABLED_META) if t else '' @@ -1127,14 +1193,14 @@ def render(text,extra={},allowed={},sep='p',URL=None,environment=None,latex='goo k = escape(k) title = ' title="%s"' % a.replace(META, DISABLED_META) if a else '' target = ' target="_blank"' if p == 'popup' else '' - t = render(t, {}, {}, 'br', URL, environment, latex, auto, - class_prefix, id_prefix) if t else k + t = render(t, {}, {}, 'br', URL, environment, latex, autolinks, + protolinks, class_prefix, id_prefix) if t else k return '%(t)s' \ % dict(k=k, title=title, target=target, t=t) return '%s' % (escape(id_prefix+t), render(a, {},{},'br', URL, - environment, latex, auto, - class_prefix, id_prefix)) + environment, latex, autolinks, + protolinks, class_prefix, id_prefix)) parts = text.split(LINK) text = parts[0] @@ -1172,13 +1238,13 @@ def render(text,extra={},allowed={},sep='p',URL=None,environment=None,latex='goo return LATEX % code.replace('"','\"').replace('\n',' ') elif b in html_colors: return '%s' \ - % (b, render(code,{},{},'br',URL,environment,latex,auto)) + % (b, render(code,{},{},'br',URL,environment,latex,autolinks, protolinks)) elif b in ('c', 'color') and p: c=p.split(':') fg='color: %s;' % c[0] if c[0] else '' bg='background-color: %s;' % c[1] if len(c)>1 and c[1] else '' return '%s' \ - % (fg, bg, render(code,{},{},'br', URL, environment, latex, auto)) + % (fg, bg, render(code,{},{},'br', URL, environment, latex, autolinks, protolinks)) cls = ' class="%s%s"'%(class_prefix,b) if b and b != 'id' else '' id = ' id="%s%s"'%(id_prefix,escape(p)) if p else '' beg=(code[:1]=='\n') @@ -1190,8 +1256,8 @@ def render(text,extra={},allowed={},sep='p',URL=None,environment=None,latex='goo text = text.translate(ttab_out) return text -def markmin2html(text, extra={}, allowed={}, sep='p', auto=True): - return render(text, extra, allowed, sep, auto=auto) +def markmin2html(text, extra={}, allowed={}, sep='p', autolinks=autolinks, protolinks=protolinks): + return render(text, extra, allowed, sep, autolinks=autolinks, protolinks=protolinks) if __name__ == '__main__': import sys @@ -1214,15 +1280,18 @@ if __name__ == '__main__': if sys.argv[1:2] == ['-h']: style=dedent(""" """)[1:] print html % dict(title="Markmin markup language", style=style, body=markmin2html(__doc__)) @@ -1236,9 +1305,29 @@ if __name__ == '__main__': elif len(sys.argv) > 1: fargv = open(sys.argv[1],'r') try: - print html % dict(title=sys.argv[1], style='', body=markmin2html(fargv.read())) + markmin_text=fargv.read() + + # embed css file from second parameter into html file + if len(sys.argv) > 2: + if sys.argv[2].startswith('@'): + markmin_style = '' + else: + fargv2 = open(sys.argv[2],'r') + try: + markmin_style = "" + finally: + fargv2.close() + else: + markmin_style = "" + + print html % dict(title=sys.argv[1], style=markmin_style, body=markmin2html(markmin_text)) finally: fargv.close() - else: - doctest.testmod() + else: + print "Usage: "+sys.argv[0]+" -h | -t | file.markmin [file.css|@path_to/css]" + print "where: -h - print __doc__" + print " -t - timeit __doc__ (for testing purpuse only)" + print " file.markmin [file.css] - process file.markmin + built in file.css (optional)" + print " file.markmin [@path_to/css] - process file.markmin + link path_to/css (optional)" + doctest.testmod() diff --git a/gluon/languages.py b/gluon/languages.py index ec9c9e06..542a22ec 100644 --- a/gluon/languages.py +++ b/gluon/languages.py @@ -28,10 +28,10 @@ from string import maketrans __all__ = ['translator', 'findT', 'update_all_languages'] -# used as a default filter i translator.M() +# used as default filter in translator.M() markmin = lambda s: render( regex_param.sub( lambda m: '{' + markmin_escape(m.group('s')) + '}', - s ), sep='br', auto=False ) + s ), sep='br', autolinks=None, id_prefix='' ) NUMBERS = (int,long,float) @@ -377,7 +377,8 @@ class lazyT(object): never to be called explicitly, returned by translator.__call__() or translator.M() """ - m = s = T = f = t = M = None + m = s = T = f = t = None + M = is_copy = False def __init__( self, @@ -388,34 +389,35 @@ class lazyT(object): ftag = None, M = False ): - self.M = M if isinstance(message, lazyT): self.m = message.m - self.s = symbols or message.s - self.T = T or message.T - self.f = filter or message.f - self.t = ftag or message.t + self.s = message.s + self.T = message.T + self.f = message.f + self.t = message.t + self.M = message.M + self.is_copy = True else: self.m = message self.s = symbols self.T = T self.f = filter self.t = ftag + self.M = M + self.is_copy = False def __repr__(self): - return "" % (repr(str(self.m)), ) + return "" % (repr(Utf8(self.m)), ) def __str__(self): return str(self.T.apply_filter(self.m, self.s, self.f, self.t) if self.M else self.T.translate(self.m, self.s)) def __eq__(self, other): - return (self.T.apply_filter(self.m, self.s, self.f, self.t) if self.M else - self.T.translate(self.m, self.s)) == other + return str(self) == str(other) def __ne__(self, other): - return (self.T.apply_filter(self.m, self.s, self.f, self.t) if self.M else - self.T.translate(self.m, self.s)) != other + return str(self) != str(other) def __add__(self, other): return '%s%s' % (self, other) @@ -423,14 +425,17 @@ class lazyT(object): def __radd__(self, other): return '%s%s' % (other, self) + def __mul__(self, other): + return str(self) * other + def __cmp__(self,other): - return cmp(str(self),str(other)) + return cmp(str(self), str(other)) def __hash__(self): return hash(str(self)) def __getattr__(self, name): - return getattr(str(self),name) + return getattr(str(self), name) def __getitem__(self, i): return str(self)[i] @@ -457,7 +462,8 @@ class lazyT(object): return str(self) def __mod__(self, symbols): - return lazyT(self.m,symbols,self.T,self.f,self.t,self.M) + if self.is_copy: return lazyT(self) + return lazyT(self.m, symbols, self.T, self.f, self.t, self.M) class translator(object): @@ -787,18 +793,21 @@ class translator(object): """ w,i = m.group('w','i') c = w[0] - word = w[c=='\\':] if c not in '!?': - return self.plural(word, symbols[int(i or 0)]) + return self.plural(w, symbols[int(i or 0)]) elif c == '?': - part2 = w[max(1,w.find('?',1)+1):] + (p1, sep, p2) = w[1:].partition("?") + part1 = p1 if sep else "" + (part2, sep, part3) = (p2 if sep else p1).partition("?") + if not sep: part3 = part2 if i is None: - # ?[word]?number or ?number - num = part2 + # ?[word]?number[?number] or ?number + if not part2: return m.group(0) + num = int(part2) else: - # ?[word]?word2[number], ?word2[number] or ?word2[number] - num = symbols[int(i or 0)] - return w[1:abs(w.find('?',1))] if int(num) == 1 else part2 + # ?[word]?word2[?word3][number] + num = int(symbols[int(i or 0)]) + return part1 if num==1 else part3 if num==0 else part2 elif w.startswith('!!!'): word = w[3:] fun = upper_fun @@ -815,16 +824,22 @@ class translator(object): def sub_dict(m): """ word(var), !word(var), !!word(var), !!!word(var) word(num), !word(num), !!word(num), !!!word(num) + ?word2(var), ?word1?word2(var), ?word1?word2?word0(var) + ?word2(num), ?word1?word2(num), ?word1?word2?word0(num) """ w,n = m.group('w','n') c = w[0] - word = w[c=='\\':] n = int(n) if n.isdigit() else symbols[n] if c not in '!?': - return self.plural(word, n) + return self.plural(w, n) elif c == '?': - # ?[word]?word2(var or num), ?word2(var or num) or ?word2(var or num) - return w[1:abs(w.find('?',1))] if int(n) == 1 else w[max(1,w.find('?',1)+1):] + # ?[word1]?word2[?word0](var or num), ?[word1]?word2(var or num) or ?word2(var or num) + (p1, sep, p2) = w[1:].partition("?") + part1 = p1 if sep else "" + (part2, sep, part3) = (p2 if sep else p1).partition("?") + if not sep: part3 = part2 + num = int(n) + return part1 if num==1 else part3 if num==0 else part2 elif w.startswith('!!!'): word = w[3:] fun = upper_fun