summaryrefslogtreecommitdiff
path: root/browser/fsm.py
diff options
context:
space:
mode:
Diffstat (limited to 'browser/fsm.py')
-rw-r--r--browser/fsm.py117
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')