gemini-to-html.py (3172B) - raw
1 #!/usr/bin/env python 2 # -*- coding: utf-8 -*- 3 # Copyright (C) 2021 Oscar Benedito <oscar@oscarbenedito.com> 4 # License: Affero General Public License version 3 or later 5 6 # Convert text/gemini files to HTML. 7 # 8 # Usage: 9 # 10 # ./gemini-to-html.py [file.gmi] 11 # 12 # If no file is specified, standard input is used. 13 14 import sys 15 import re 16 17 18 css = """ 19 body { margin: 1em auto; max-width: 700px; line-height: 1.5; font-family: sans-serif; padding: 0 1em; } 20 a { color: #02d; } 21 a:hover { color: #309; } 22 .a:before { content: "⇒ "; color: #888; } 23 pre { background-color: #eee; padding: 1rem; overflow-x: auto; } 24 blockquote { padding: 0 0 0 1.2rem; border-left: 3px solid; } 25 """ 26 27 28 if len(sys.argv) == 1: 29 data = sys.stdin.readlines() 30 elif len(sys.argv) == 2: 31 try: 32 with open(sys.argv[1], 'r') as f: 33 data = f.readlines() 34 except IOError: 35 sys.stderr.write('Error reading file {}.\n'.format(sys.argv[1])) 36 sys.exit(1) 37 else: 38 sys.stderr.write('Usage: {} file.gmi.\n'.format(sys.argv[0])) 39 sys.exit(1) 40 41 42 print('<!DOCTYPE html>') 43 print('<html>') 44 print('<head>') 45 print('<meta charset="utf-8"/>') 46 print('<meta name="viewport" content="width=device-width, initial-scale=1"/>') 47 if data[0][:1] == '#' and data[0][:2] != '##': 48 print('<title>{}</title>'.format(data[0][1:].strip())) 49 if css is not None: 50 print('<style>{}</style>'.format(css)) 51 print('</head>') 52 print('<body>') 53 54 55 state = '' 56 57 for line in data: 58 line = line[:-1] 59 if state == 'ul': 60 if line[:2] == '* ': 61 print('<li>{}</li>'.format(line[2:])) 62 continue 63 else: 64 print('</ul>') 65 state = '' 66 elif state[:3] == 'pre': 67 if line[:3] == '```': 68 print('</code></pre>') 69 state = '' 70 continue 71 else: 72 if state == 'pre-first': 73 sys.stdout.write('{}'.format(line)) 74 state = 'pre' 75 else: 76 sys.stdout.write('\n{}'.format(line)) 77 continue 78 79 if line[:2] == '=>': 80 # re.sub 81 m = re.match('^=>[ \t]*(\S+)(?:[ \t]+(.+))?', line) 82 if m is None: 83 sys.stderr.write('Incorrect syntax on line of type link:\n' 84 ' {}'.format(line)) 85 print('<p>{}</p>'.format(line)) 86 continue 87 text = m.group(2) if m.group(2) is not None else m.group(1) 88 print('<p class="a"><a href="{}">{}</a></p>'.format(m.group(1), text)) 89 elif line[:3] == '```': 90 if len(line) > 3: 91 sys.stdout.write('<pre aria-label="{}"><code>'.format(line[3:])) 92 else: 93 sys.stdout.write('<pre><code>') 94 state = 'pre-first' 95 elif line[:3] == '###': 96 print('<h3>{}</h3>'.format(line[3:])) 97 elif line[:2] == '##': 98 print('<h2>{}</h2>'.format(line[2:])) 99 elif line[:1] == '#': 100 print('<h1>{}</h1>'.format(line[1:])) 101 elif line[:2] == '* ': 102 print('<ul>\n<li>{}</li>'.format(line[2:])) 103 state = 'ul' 104 elif line[:1] == '>': 105 print('<blockquote>{}</blockquote>'.format(line[1:])) 106 else: 107 print('<p>{}</p>'.format(line)) 108 109 print('</body>\n</html>')