import re def publish(master_fn): ''' We use one master file that contains all the Showell markup, and then we publish to Django templates. master_fn should be the name of a file that has several sections with banners like you get from the unix "more" command... :::::::::::::: ./apps/comment/templates/comment_form.html :::::::::::::: [some markup to be preprocessed] Each section of the master file gets converted and written to the output file. ''' f = open(master_fn) fn = body = None while True: line = f.readline() if not line: break if line.startswith('::::'): if fn: convert(fn, body) fn = f.readline().strip() f.readline() body = '' else: body += line convert(fn, body) def convert(fn, in_body): out_body = convert_text(in_body) open(fn, 'w').write(out_body) def convert_text(in_body): ''' You can call convert_text directly to convert Showell markup to HTML/Django markup. ''' lines = [] indenter = Indenter() for line in in_body.split('\n'): m = re.match('(\s*)(.*\S)(.*)', line) if m: prefix, line, cruft = m.groups() if line.startswith('>>'): block_tag(prefix, line, indenter) elif line.startswith('%%'): django_block(prefix, line, indenter) elif line.startswith('DEDENT'): indenter.dedent() elif line.startswith('END_DEDENT'): indenter.end_dedent(prefix) else: line = fix_line(line) indenter.add(prefix, line+cruft) else: indenter.add('', line) return indenter.body() def block_tag(prefix, line, indenter): ''' Block tags have syntax like this: >> table >> tr >> td foo >> td bar FORM is just a shortcut. ''' m = re.match('>> (.*)', line) markup = m.group(1) m = re.match('FORM (.*)', markup) if m: markup = 'form action="{%% url %s %%}" method="POST"' % m.group(1) start_tag = '<%s>' % markup end_tag = '' % markup.split()[0] indenter.push(prefix, start_tag, end_tag) def django_block(prefix, line, indenter): ''' Enable code like this: %% extends 'base.html' %% load smartif %% block body %% for book in books {{ book }} %% if condition Display this %% elif condition Display that %% else %% include 'other.html' ''' m = re.match('%% (.*)', line) markup = m.group(1) tag = markup.split()[0] start_tag = '{%% %s %%}' % markup if tag in ['elif', 'else', 'include', 'extends', 'load']: indenter.insert(prefix, start_tag) else: end_tag = '{%% end%s %%}' % tag indenter.push(prefix, start_tag, end_tag) def fix_line(line): ''' Fix up individual lines of HTML: List item one | b | li ; br Home LINK home.home LINK is just a django-specific shortcut. This is a bit crufty...there are some mini features that just allowed me to match some old markup without creating diffs. ''' if line == '': return '' if line[-1] in [':', ',']: return fix_line(line[:-1]) + line[-1] if line.endswith('| ()'): line = line[:-len('| ()')] return '(%s)' % fix_line(line).rstrip() if line.endswith('; br'): line = line[:-len('; br')] return fix_line(line).rstrip() + '
' if line.endswith('; hr'): line = line[:-len('; hr')] return fix_line(line).rstrip() + '
' m = re.match('(.*)\| (.*)', line) if m: content, markup = m.groups() return inline_html_tag(content, markup) m = re.match('(.*) LINK (.*)', line) if m: label, url = m.groups() return '%s' % (url.rstrip(), label.rstrip()) content, markup = m.groups() return inline_html_tag(content, markup) return line def inline_html_tag(content, markup): ''' Enclose one HTML tag a time hello | b => hello HIDDEN is just a django-specific shortcut. / means write a singleton tag like NOCLOSE is legacy to avoid diffs...it writes the singleton tag without the /> at the end. ''' content = fix_line(content.strip()) markup = markup.strip() if markup.startswith('HIDDEN'): name = markup.split()[1] return '' % name if markup.endswith(' NOCLOSE'): markup = markup[:-len(' NOCLOSE')] start_tag = '<%s>' % markup end_tag = '' elif markup.endswith(' /'): start_tag = '<%s>' % markup end_tag = '' else: start_tag = '<%s>' % markup end_tag = '' % markup.split()[0] return start_tag + content + end_tag class Indenter: ''' Example usage: indenter = Indenter() indenter.push('', 'Start', 'End') indenter.push(' ', 'Foo', '/Foo') indenter.add (' ', 'bar') indenter.add (' ', 'yo') print indenter.body() ''' def __init__(self): self.stack = [] self.lines = [] self.adj = 0 def push(self, prefix, start, end): self.add(prefix, start) self.stack.append((prefix, end)) def dedent(self): self.adj += 4 def end_dedent(self, prefix): self.pop(prefix) self.adj -= 4 def add(self, prefix, line): if line: self.pop(prefix) self.insert(prefix, line) def insert(self, prefix, line): prefix = prefix[self.adj:] self.lines.append(prefix+line) def pop(self, prefix): while self.stack: start_prefix, end = self.stack[-1] if len(prefix) <= len(start_prefix): whitespace_lines = [] while self.lines and self.lines[-1] == '': whitespace_lines.append(self.lines.pop()) self.insert(start_prefix, end) self.lines += whitespace_lines self.stack.pop() else: return def body(self): self.pop('') return '\n'.join(self.lines) if __name__ == '__main__': publish('TEMPLATES')