diff options
Diffstat (limited to 'browser/fsm.py')
-rw-r--r-- | browser/fsm.py | 117 |
1 files changed, 117 insertions, 0 deletions
diff --git a/browser/fsm.py b/browser/fsm.py new file mode 100644 index 0000000..eccfc6e --- /dev/null +++ b/browser/fsm.py @@ -0,0 +1,117 @@ +import html +import sys +import urllib.parse + + +class StackFSM(object): + """ + Implement a finite state machine that uses a stack to + manage state. + """ + + def __init__(self): + self._state_stack = [] + + def _current_state(self): + if len(self._state_stack) > 0: + return self._state_stack[-1] + return None + + def update(self): + fn = self._current_state() + if fn is not None: + fn() + + def push_state(self, state): + self._state_stack.append(state) + + def pop_state(self): + return self._state_stack.pop() + + +class Parser(object): + + def __init__(self, document, output=None): + self._document = document + if output is None: + output = sys.stdout + self._output = output + self._offset = 0 + self._blanks = 0 + self._fsm = StackFSM() + + def parse(self): + self._fsm.push_state(self.text_state) + while self._fsm._current_state() is not None and len(self._document) > self._offset: + self._fsm.update() + + def text_state(self): + line = self._document[self._offset] + if line.strip() == '': + self._blanks += 1 + else: + self._blanks = 0 + if line.startswith('```'): + self._fsm.push_state(self.pre_state) + self._output.write('<pre>\n') + self._offset += 1 + elif line.startswith('* '): + self._fsm.push_state(self.list_state) + self._output.write('<ul>\n') + elif line.startswith('=>'): + self._fsm.push_state(self.link_state) + self._output.write('<ul>\n') + else: + if line.startswith('# '): + self._output.write('<h1>{}</h1>\n'.format(html.escape(line[2:]))) + elif line.startswith('## '): + self._output.write('<h2>{}</h2>\n'.format(html.escape(line[3:]))) + elif line.startswith('### '): + self._output.write('<h3>{}</h3>\n'.format(html.escape(line[4:]))) + elif line.startswith('> '): + self._output.write('<blockquote>{}</blockquote>\n'.format(html.escape(line[2:]))) + elif line.strip() == '': + if self._blanks > 1: + self._output.write('<br/>\n') + else: + self._output.write('<p>{}</p>\n'.format(html.escape(line))) + self._offset += 1 + + def pre_state(self): + line = self._document[self._offset] + if line.startswith('```'): + self._fsm.pop_state() + self._output.write('</pre>\n') + self._offset += 1 + else: + self._output.write(line + '\n') + self._offset += 1 + + def list_state(self): + line = self._document[self._offset] + if line.startswith('* '): + self._output.write('<li>{}</li>\n'.format(html.escape(line[2:]))) + self._offset += 1 + else: + self._fsm.pop_state() + self._output.write('</ul>\n') + + def link_state(self): + line = self._document[self._offset] + if line.startswith('=>'): + parts = line[2:].split(None, 1) + url = parts[0] + url_parts = urllib.parse.urlsplit(url) + if url_parts.scheme in ('gemini', ''): + external = '' + else: + external = open('external_link.svg').read() + if len(parts) == 1: + text = url + else: + text = html.escape(parts[1]) + self._output.write('<li class="link"><a href="{}">{}</a>{}</li>\n'.format(url, text, external)) + self._offset += 1 + else: + self._fsm.pop_state() + self._output.write('</ul>\n') |