Initial commit (Clean history)
This commit is contained in:
@@ -0,0 +1,203 @@
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from .abc import Writer
|
||||
|
||||
import re
|
||||
|
||||
|
||||
class _WriterOutput(object):
|
||||
def __init__(self, start, end=None, generate_end=None, anchor_position=None):
|
||||
if generate_end is None:
|
||||
generate_end = _constant(end)
|
||||
|
||||
self.start = start
|
||||
self.generate_end = generate_end
|
||||
self.anchor_position = anchor_position
|
||||
|
||||
|
||||
def _constant(value):
|
||||
def get():
|
||||
return value
|
||||
|
||||
return get
|
||||
|
||||
|
||||
class _MarkdownState(object):
|
||||
def __init__(self):
|
||||
self._list_state_stack = []
|
||||
self.list_state = None
|
||||
self.list_item_has_closed = False
|
||||
|
||||
def update_list_state(self, list_state):
|
||||
self._list_state_stack.append(self.list_state)
|
||||
self.list_state = list_state
|
||||
|
||||
def pop_list_state(self):
|
||||
self.list_state = self._list_state_stack.pop()
|
||||
|
||||
|
||||
class _MarkdownListState(object):
|
||||
def __init__(self, ordered, indentation):
|
||||
self.ordered = ordered
|
||||
self.count = 0
|
||||
self.indentation = indentation
|
||||
|
||||
|
||||
def _symmetric_wrapped(end):
|
||||
return _Wrapped(end, end)
|
||||
|
||||
|
||||
class _Wrapped(object):
|
||||
def __init__(self, start, end):
|
||||
self._start = start
|
||||
self._end = end
|
||||
|
||||
def __call__(self, attributes, markdown_state):
|
||||
return _WriterOutput(self._start, self._end)
|
||||
|
||||
|
||||
def _hyperlink(attributes, markdown_state):
|
||||
href = attributes.get("href", "")
|
||||
if href:
|
||||
return _WriterOutput(
|
||||
"[", "]({0})".format(href),
|
||||
anchor_position="before",
|
||||
)
|
||||
else:
|
||||
return _default_output
|
||||
|
||||
|
||||
def _image(attributes, markdown_state):
|
||||
src = attributes.get("src", "")
|
||||
alt_text = attributes.get("alt", "")
|
||||
if src or alt_text:
|
||||
return _WriterOutput("".format(alt_text, src), "")
|
||||
else:
|
||||
return _default_output
|
||||
|
||||
|
||||
def _list(ordered):
|
||||
def call(attributes, markdown_state):
|
||||
if markdown_state.list_state is None:
|
||||
start = ""
|
||||
end_text = "\n"
|
||||
indentation = 0
|
||||
else:
|
||||
start = "\n"
|
||||
end_text = ""
|
||||
indentation = markdown_state.list_state.indentation + 1
|
||||
|
||||
def generate_end():
|
||||
markdown_state.pop_list_state()
|
||||
return end_text
|
||||
|
||||
markdown_state.update_list_state(_MarkdownListState(
|
||||
ordered=ordered,
|
||||
indentation=indentation,
|
||||
))
|
||||
|
||||
return _WriterOutput(start, generate_end=generate_end)
|
||||
|
||||
return call
|
||||
|
||||
|
||||
def _list_item(attributes, markdown_state):
|
||||
markdown_state.list_item_has_closed = False
|
||||
|
||||
list_state = markdown_state.list_state or _MarkdownListState(ordered=False, indentation=0)
|
||||
list_state.count += 1
|
||||
|
||||
if list_state.ordered:
|
||||
bullet = "{0}.".format(list_state.count)
|
||||
else:
|
||||
bullet = "-"
|
||||
|
||||
def generate_end():
|
||||
if markdown_state.list_item_has_closed:
|
||||
return ""
|
||||
else:
|
||||
markdown_state.list_item_has_closed = True
|
||||
return "\n"
|
||||
|
||||
return _WriterOutput(
|
||||
start=("\t" * list_state.indentation) + bullet + " ",
|
||||
generate_end=generate_end
|
||||
)
|
||||
|
||||
|
||||
def _init_writers():
|
||||
writers = {
|
||||
"p": _Wrapped("", "\n\n"),
|
||||
"br": _Wrapped("", " \n"),
|
||||
"strong": _symmetric_wrapped("__"),
|
||||
"em": _symmetric_wrapped("*"),
|
||||
"a": _hyperlink,
|
||||
"img": _image,
|
||||
"ol": _list(ordered=True),
|
||||
"ul": _list(ordered=False),
|
||||
"li": _list_item,
|
||||
}
|
||||
|
||||
for level in range(1, 7):
|
||||
writers["h{0}".format(level)] = _Wrapped("#" * level + " ", "\n\n")
|
||||
|
||||
return writers
|
||||
|
||||
|
||||
_writers = _init_writers()
|
||||
_default_output = _WriterOutput("", "")
|
||||
|
||||
def _default_writer(attributes, markdown_state):
|
||||
return _default_output
|
||||
|
||||
|
||||
class MarkdownWriter(Writer):
|
||||
def __init__(self):
|
||||
self._fragments = []
|
||||
self._element_stack = []
|
||||
self._markdown_state = _MarkdownState()
|
||||
|
||||
def text(self, text):
|
||||
self._fragments.append(_escape_markdown(text))
|
||||
|
||||
def start(self, name, attributes=None):
|
||||
if attributes is None:
|
||||
attributes = {}
|
||||
|
||||
output = _writers.get(name, _default_writer)(attributes, self._markdown_state)
|
||||
self._element_stack.append(output.generate_end)
|
||||
|
||||
anchor_before_start = output.anchor_position == "before"
|
||||
if anchor_before_start:
|
||||
self._write_anchor(attributes)
|
||||
|
||||
self._fragments.append(output.start)
|
||||
|
||||
if not anchor_before_start:
|
||||
self._write_anchor(attributes)
|
||||
|
||||
|
||||
|
||||
def end(self, name):
|
||||
end = self._element_stack.pop()
|
||||
output = end()
|
||||
self._fragments.append(output)
|
||||
|
||||
def self_closing(self, name, attributes=None):
|
||||
self.start(name, attributes)
|
||||
self.end(name)
|
||||
|
||||
def append(self, other):
|
||||
self._fragments.append(other)
|
||||
|
||||
def as_string(self):
|
||||
return "".join(self._fragments)
|
||||
|
||||
def _write_anchor(self, attributes):
|
||||
html_id = attributes.get("id")
|
||||
if html_id:
|
||||
self._fragments.append('<a id="{0}"></a>'.format(html_id))
|
||||
|
||||
|
||||
def _escape_markdown(value):
|
||||
return re.sub(r"([\`\*_\{\}\[\]\(\)\#\+\-\.\!])", r"\\\1", re.sub("\\\\", "\\\\\\\\", value))
|
||||
Reference in New Issue
Block a user