1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
|
#!/usr/bin/env python3
# Convert gemtext to HTML, accepting HTML header and footer files
# Copyright 2021 huntingb
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, version 3 of the License.
#
# 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 General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see https://www.gnu.org/licenses/.
# Original code found at:
# https://github.com/huntingb/gemtext-html-converter
# Modified by tslil clingman, April 2021 in the following ways
# - added HTML escaping of all non-pre lines
# - added header and footer file input
# - added generation of <ul> ... </ul> for list items
# - fixed stripping of lines, no longer occurs in pre blocks, and
# otherwise is rstrip only
# - replace := syntax with something my version of python3 accepts
# - modified description below
"""
HUNTER'S SIMPLE GEMTEXT TO HTML CONVERTER
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
A simple script that converts gemtext to HTML.
Takes four arguments from stdin. The first two arguments are the names
of files which will be used as a header and footer for the generated
output, in that order.
The next argument is the name of of a gemtext file, and the last
argument is the name of the desired output file.
The output file consists of, in order: the header; the lines of
gemtext in the input file converted to their HTML equivalents: <h1>,
<h2>, <h3>, <p>, <a>, <blockquote>, <li>, and <pre> tags; the footer.
"""
# importing required libraries
import sys
import re
import html
# A dictionary that maps regex to match at the beginning of gmi lines
# to their corresponding HTML tag names. Used by
# convert_single_line().
tags_dict = {
r"^# (.*)": "h1",
r"^## (.*)": "h2",
r"^### (.*)": "h3",
r"^\* (.*)": "li",
r"^> (.*)": "blockquote",
r"^=>\s*(\S+)(\s+.*)?": "a"
}
# This function takes a string of gemtext as input and returns a
# string of HTML
def convert_single_line(gmi_line):
for pattern in tags_dict.keys():
match = re.match(pattern, gmi_line)
if match:
tag = tags_dict[pattern]
groups = match.groups()
if tag == "a":
href = re.sub("^gemini://", "https://", groups[0])
href = re.sub(r"\.gmi$", ".html", href)
if len(groups) > 1 and groups[1] is not None:
inner_text = groups[1].strip()
else:
inner_text = href
return f"<p><{tag} href='{href}'>{inner_text}</{tag}></p>"
else:
inner_text = html.escape(groups[0].strip())
return f"<{tag}>{inner_text}</{tag}>"
gmi_line = html.escape(gmi_line)
return f"<p>{gmi_line}</p>"
# Reads the contents of the input file line by line and outputs HTML.
# Renders text in preformat blocks (toggled by ```) as multiline <pre>
# tags.
def main(args):
with open(args[3]) as gmi, open(args[4], "w") as output:
# Write header
header = open(args[1])
output.write(header.read())
header.close()
# Parse gmitext
pre = False
listing = False
for line in gmi:
if line.startswith("```"):
pre = not pre
if pre:
line = line.rstrip()
if len(line) > 3:
line = html.escape(line[3:])
output.write(f"<pre title=\"{line}\">\n")
else:
output.write("<pre>\n")
else:
output.write("</pre>\n")
elif pre:
output.write(html.escape(line))
else:
line = line.rstrip()
if line.startswith("*") and not listing:
listing = True
output.write("<ul>\n")
if not line.startswith("*") and listing:
listing = False
output.write("</ul>\n")
output_line = convert_single_line(line)
output.write(output_line+"\n")
# Write footer
footer = open(args[2])
output.write(footer.read())
footer.close()
# Main guard
if __name__ == "__main__":
main(sys.argv)
|