gemini-to-html.py (3770B) - raw


      1 #!/usr/bin/env python
      2 # -*- coding: utf-8 -*-
      3 # Copyright (C) 2021 Oscar Benedito <oscar@oscarbenedito.com>
      4 #
      5 # This program is free software: you can redistribute it and/or modify
      6 # it under the terms of the GNU Affero General Public License as
      7 # published by the Free Software Foundation, either version 3 of the
      8 # License, or (at your option) any later version.
      9 #
     10 # This program is distributed in the hope that it will be useful,
     11 # but WITHOUT ANY WARRANTY; without even the implied warranty of
     12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     13 # GNU Affero General Public License for more details.
     14 #
     15 # You should have received a copy of the GNU Affero General Public License
     16 # along with this program.  If not, see <https://www.gnu.org/licenses/>.
     17 
     18 # Convert text/gemini files to HTML.
     19 #
     20 # Usage:
     21 #
     22 #     ./gemini-to-html.py [file.gmi]
     23 #
     24 # If no file is specified, standard input is used.
     25 
     26 import sys
     27 import re
     28 
     29 
     30 css = """
     31 body { margin: 1em auto; max-width: 700px; line-height: 1.5; font-family: sans-serif; padding: 0 1em; }
     32 a { color: #02d; }
     33 a:hover { color: #309; }
     34 .a:before { content: "⇒ "; color: #888; }
     35 pre { background-color: #eee; padding: 1rem; overflow-x: auto; }
     36 blockquote { padding: 0 0 0 1.2rem; border-left: 3px solid; }
     37 """
     38 
     39 
     40 if len(sys.argv) == 1:
     41     data = sys.stdin.readlines()
     42 elif len(sys.argv) == 2:
     43     try:
     44         with open(sys.argv[1], 'r') as f:
     45             data = f.readlines()
     46     except IOError:
     47         sys.stderr.write('Error reading file {}.\n'.format(sys.argv[1]))
     48         sys.exit(1)
     49 else:
     50     sys.stderr.write('Usage: {} file.gmi.\n'.format(sys.argv[0]))
     51     sys.exit(1)
     52 
     53 
     54 print('<!DOCTYPE html>')
     55 print('<html>')
     56 print('<head>')
     57 print('<meta charset="utf-8"/>')
     58 print('<meta name="viewport" content="width=device-width, initial-scale=1"/>')
     59 if data[0][:1] == '#' and data[0][:2] != '##':
     60     print('<title>{}</title>'.format(data[0][1:].strip()))
     61 if css is not None:
     62     print('<style>{}</style>'.format(css))
     63 print('</head>')
     64 print('<body>')
     65 
     66 
     67 state = ''
     68 
     69 for line in data:
     70     line = line[:-1]
     71     if state == 'ul':
     72         if line[:2] == '* ':
     73             print('<li>{}</li>'.format(line[2:]))
     74             continue
     75         else:
     76             print('</ul>')
     77             state = ''
     78     elif state[:3] == 'pre':
     79         if line[:3] == '```':
     80             print('</code></pre>')
     81             state = ''
     82             continue
     83         else:
     84             if state == 'pre-first':
     85                 sys.stdout.write('{}'.format(line))
     86                 state = 'pre'
     87             else:
     88                 sys.stdout.write('\n{}'.format(line))
     89             continue
     90 
     91     if line[:2] == '=>':
     92         # re.sub
     93         m = re.match('^=>[ \t]*(\S+)(?:[ \t]+(.+))?', line)
     94         if m is None:
     95             sys.stderr.write('Incorrect syntax on line of type link:\n'
     96                              '    {}'.format(line))
     97             print('<p>{}</p>'.format(line))
     98             continue
     99         text = m.group(2) if m.group(2) is not None else m.group(1)
    100         print('<p class="a"><a href="{}">{}</a></p>'.format(m.group(1), text))
    101     elif line[:3] == '```':
    102         if len(line) > 3:
    103             sys.stdout.write('<pre aria-label="{}"><code>'.format(line[3:]))
    104         else:
    105             sys.stdout.write('<pre><code>')
    106         state = 'pre-first'
    107     elif line[:3] == '###':
    108         print('<h3>{}</h3>'.format(line[3:]))
    109     elif line[:2] == '##':
    110         print('<h2>{}</h2>'.format(line[2:]))
    111     elif line[:1] == '#':
    112         print('<h1>{}</h1>'.format(line[1:]))
    113     elif line[:2] == '* ':
    114         print('<ul>\n<li>{}</li>'.format(line[2:]))
    115         state = 'ul'
    116     elif line[:1] == '>':
    117         print('<blockquote>{}</blockquote>'.format(line[1:]))
    118     else:
    119         print('<p>{}</p>'.format(line))
    120 
    121 print('</body>\n</html>')