commit 4a5ede59e4bfb56f3fa1a04ec39049aa42281263
parent a97079db345a0ab584483de1c395b1d3940b3348
Author: Oscar Benedito <oscar@oscarbenedito.com>
Date: Mon, 4 Jan 2021 21:56:46 +0100
Add gemini-to-html.py
Diffstat:
A | gemini-to-html.py | | | 121 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
1 file changed, 121 insertions(+), 0 deletions(-)
diff --git a/gemini-to-html.py b/gemini-to-html.py
@@ -0,0 +1,121 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+# Copyright (C) 2021 Oscar Benedito <oscar@oscarbenedito.com>
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as
+# published by the Free Software Foundation, either version 3 of the
+# License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <https://www.gnu.org/licenses/>.
+
+# Convert text/gemini files to HTML.
+#
+# Usage:
+#
+# ./gemini-to-html.py [file.gmi]
+#
+# If no file is specified, standard input is used.
+
+import sys
+import re
+
+
+css = """
+body { margin: 1em auto; max-width: 700px; line-height: 1.5; font-family: sans-serif; padding: 0 1em; }
+a { color: #02d; }
+a:hover { color: #309; }
+.a:before { content: "⇒ "; color: #888; }
+pre { background-color: #eee; padding: 1rem; overflow-x: auto; }
+blockquote { padding: 0 0 0 1.2rem; border-left: 3px solid; }
+"""
+
+
+if len(sys.argv) == 1:
+ data = sys.stdin.readlines()
+elif len(sys.argv) == 2:
+ try:
+ with open(sys.argv[1], 'r') as f:
+ data = f.readlines()
+ except IOError:
+ sys.stderr.write('Error reading file {}.\n'.format(sys.argv[1]))
+ sys.exit(1)
+else:
+ sys.stderr.write('Usage: {} file.gmi.\n'.format(sys.argv[0]))
+ sys.exit(1)
+
+
+print('<!DOCTYPE html>')
+print('<html>')
+print('<head>')
+print('<meta charset="utf-8"/>')
+print('<meta name="viewport" content="width=device-width, initial-scale=1"/>')
+if data[0][:1] == '#' and data[0][:2] != '##':
+ print('<title>{}</title>'.format(data[0][1:].strip()))
+if css is not None:
+ print('<style>{}</style>'.format(css))
+print('</head>')
+print('<body>')
+
+
+state = ''
+
+for line in data:
+ line = line[:-1]
+ if state == 'ul':
+ if line[:2] == '* ':
+ print('<li>{}</li>'.format(line[2:]))
+ continue
+ else:
+ print('</ul>')
+ state = ''
+ elif state[:3] == 'pre':
+ if line[:3] == '```':
+ print('</code></pre>')
+ state = ''
+ continue
+ else:
+ if state == 'pre-first':
+ sys.stdout.write('{}'.format(line))
+ state = 'pre'
+ else:
+ sys.stdout.write('\n{}'.format(line))
+ continue
+
+ if line[:2] == '=>':
+ # re.sub
+ m = re.match('^=>[ \t]*(\S+)(?:[ \t]+(.+))?', line)
+ if m is None:
+ sys.stderr.write('Incorrect syntax on line of type link:\n'
+ ' {}'.format(line))
+ print('<p>{}</p>'.format(line))
+ continue
+ text = m.group(2) if m.group(2) is not None else m.group(1)
+ print('<p class="a"><a href="{}">{}</a></p>'.format(m.group(1), text))
+ elif line[:3] == '```':
+ if len(line) > 3:
+ sys.stdout.write('<pre aria-label="{}"><code>'.format(line[3:]))
+ else:
+ sys.stdout.write('<pre><code>')
+ state = 'pre-first'
+ elif line[:3] == '###':
+ print('<h3>{}</h3>'.format(line[3:]))
+ elif line[:2] == '##':
+ print('<h2>{}</h2>'.format(line[2:]))
+ elif line[:1] == '#':
+ print('<h1>{}</h1>'.format(line[1:]))
+ elif line[:2] == '* ':
+ print('<ul>\n<li>{}</li>'.format(line[2:]))
+ state = 'ul'
+ elif line[:1] == '>':
+ print('<blockquote>{}</blockquote>'.format(line[1:]))
+ else:
+ print('<p>{}</p>'.format(line))
+
+print('</body>\n</html>')