150 lines
4.2 KiB
Python
Executable File
150 lines
4.2 KiB
Python
Executable File
#!/bin/python3
|
|
# hrtime - transgender research website
|
|
# Copyright (C) 2025 Olive <hello@grasswren.net>
|
|
# see LICENCE file for licensing information
|
|
|
|
import re
|
|
import sys
|
|
|
|
|
|
def ch(st: str) -> str:
|
|
st = re.sub(r'&', r'&', st)
|
|
st = re.sub(r'>', r'>', st)
|
|
st = re.sub(r'<', r'<', st)
|
|
return st
|
|
|
|
|
|
def qch(st: str) -> str:
|
|
return re.sub('\'', r''', ch(st))
|
|
|
|
|
|
def mdh(line: str) -> str:
|
|
ret = ''
|
|
while len(line) > 0:
|
|
match = re.match(r'!\[(.*?)\]\((.*?)\)', line)
|
|
if match != None:
|
|
line = line[match.span()[1]:]
|
|
ret += f"<span class='n' title='{qch(match.group(2))}'>" + \
|
|
f'{ch(match.group(1))}</span>'
|
|
continue
|
|
|
|
match = re.match(r'\[(.*?)\]\(([^ ]*)\)', line)
|
|
if match != None:
|
|
line = line[match.span()[1]:]
|
|
ret += f"<a href='{qch(match.group(2))}' target='_blank'" \
|
|
+ f'>{ch(match.group(1))}</a>'
|
|
continue
|
|
|
|
match = re.match(r'`(.)(.*?)`', line)
|
|
if match != None:
|
|
line = line[match.span()[1]:]
|
|
ret += r"<code" + (f" class='{match.group(1)}'" if match.group(1) \
|
|
!= '/' else '') + f'>{ch(match.group(2))}</code>'
|
|
continue
|
|
|
|
ret += ch(line[0])
|
|
line = line[1:]
|
|
|
|
ret = re.sub(r'NOBREAK', r'⁠', ret)
|
|
return ret
|
|
|
|
|
|
def md(line: str) -> str:
|
|
# allows '|' and '||' escaping to work
|
|
ret, do = '', True
|
|
for sec in line.strip().split('|'):
|
|
if len(sec) == 0:
|
|
ret += '|'
|
|
else:
|
|
ret += mdh(sec) if do else sec
|
|
do = not do
|
|
return ret
|
|
|
|
|
|
def media(line: str):
|
|
file, alt = tuple(line.strip().split('|'))
|
|
file = qch(file)
|
|
if file[-5:] in ('.webp'):
|
|
print(f"<img src='{file}' alt='{qch(alt)}'>")
|
|
if file[-4:] in ('.mp4'):
|
|
print('<video controls>')
|
|
print(f"<source src='{file}' type='video/mp4' alt='{qch(alt)}'>")
|
|
print('</video>')
|
|
if file[-4:] in ('.pdf'):
|
|
print(f"<object data='{file}' type='application/pdf' height='80%'>")
|
|
print(f"<a href='{file}'>{ch(alt)}</a>")
|
|
print('</object>')
|
|
|
|
|
|
def doc(lines: list[str]):
|
|
head = ''
|
|
with open('head.html', 'r') as file:
|
|
head = file.read()
|
|
|
|
lines[0] = lines[0].strip()
|
|
if len(lines[0]) > 0:
|
|
lines[0] = ' — ' + lines[0]
|
|
head = re.sub(r'TITLE', lines[0], head, count=2)
|
|
print(head, end='')
|
|
lines = lines[1:]
|
|
|
|
mode=''
|
|
for line in lines:
|
|
if line[1:4] == ' ':
|
|
if mode != '</code></pre>':
|
|
if line[0] != '/':
|
|
cls = f" class='{line[0]}'"
|
|
print(f'<pre><code{cls}>', end='')
|
|
mode = '</code></pre>'
|
|
print(ch(line[4:].rstrip()))
|
|
continue
|
|
elif line[:2] == ' ':
|
|
if mode != '</ul>':
|
|
print("<ul class='l'>")
|
|
mode = '</ul>'
|
|
print(f'<li>{md(line[2:])}</li>')
|
|
continue
|
|
elif line[:2] == '- ':
|
|
if mode != '</ul>':
|
|
print('<ul>')
|
|
mode = '</ul>'
|
|
print(f'<li>{md(line[2:])}</li>')
|
|
continue
|
|
|
|
if mode != '':
|
|
print(mode)
|
|
mode = ''
|
|
|
|
if len(line) == 1:
|
|
print('</section>\n<section>')
|
|
elif line[:2] == '# ':
|
|
print(f'<h1>{md(line[2:])}</h1>')
|
|
elif line[:3] == '## ':
|
|
print(f'<h2>{md(line[3:])}</h2>')
|
|
elif line[:2] == '! ':
|
|
media(line[2:])
|
|
elif line[:2] == '@ ':
|
|
title, link, desc, date = tuple(line[2:].split('|'))
|
|
print('<article>\n<div>')
|
|
print(f"<h1><a href='{qch(link)}'>{md(title)}</a></h1>")
|
|
print('</div>\n<div>')
|
|
print(f'<p>{md(desc)}</p>')
|
|
print(f'<p>(<time>{date.strip()}</time>)</p>')
|
|
print('</div>\n</article>')
|
|
else:
|
|
print('<p>' + md(line) + '</p>')
|
|
|
|
if mode != '':
|
|
print(mode)
|
|
|
|
with open('foot.html', 'r') as file:
|
|
print(file.read(), end='')
|
|
|
|
|
|
if __name__ == '__main__':
|
|
if len(sys.argv) != 2:
|
|
print('usage: md [file]')
|
|
else:
|
|
with open(sys.argv[1], 'r') as file:
|
|
doc(list(file))
|