#! /usr/bin/env python3 # Copyright 2019, 2020 Steinar Knutsen # # Licensed under the EUPL, Version 1.2 or - as soon they will be approved by the # European Commission - subsequent versions of the EUPL (the "Licence"); You may # not use this work except in compliance with the Licence. You may obtain a copy # of the Licence at: # # https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12 # # Unless required by applicable law or agreed to in writing, software # distributed under the Licence is distributed on an "AS IS" basis, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # Licence for the specific language governing permissions and limitations under # the Licence. import sys, getopt help_text = """itxt2html [-h] [-s] -h print this text -s strip the break and monospace markers from the generated HTML Read plain text from stdin and generate HTML according to the following rules: - One Level of indent is four spaces in input _| - Pre-formatted blocks always start and end with a line of 80 consecutive "-" characters _| - Forced line break is inserted by a ending a line with "_|" _| - Indent where (#spaces % 4) != 0 is considered an error""" HEAD = """<!DOCTYPE html> <html> <head> <style type="text/css"> body { font-family: sans-serif; } pre { font-family: monospace; } div.rightshift { margin-left: 2em; } div.padded { padding-top: 1em; } </style> """ BODY = "</head><body>\n" FOOTER = "</body>\n" TITLE_TEMPLATE = "<title>%s" MONO = "--------------------------------------------------------------------------------" INDENT = 4 SPACE = " " START_MONO = "
\n"
END_MONO = "
\n" SHIFT_RIGHT = '
\n' PADDING = '
\n' PARA_END = "
\n" BREAK_MARKER = "_|" BREAK = "
\n" quote_table = { '&': "&", '<': "<", '>': ">" } def get_indent(line): indent = 0 for c in line: if c == SPACE: indent += 1 else: break if (indent % INDENT) != 0: raise Exception("Indent not multiplum of INDENT constant.") return indent >> 2 def cook(line): unwritten = i = 0 buffer = [] while i < len(line): subst = quote_table.get(line[i]) if subst != None: if unwritten != i: buffer.append(line[unwritten:i]) unwritten = i + 1 buffer.append(subst) i += 1 if unwritten < len(line): buffer.append(line[unwritten:]) return "".join(buffer) def ismonomarker(line): return line.rstrip() == MONO def write_line(destination, line, in_mono, stripped_output): if not stripped_output: destination.write(cook(line)) return if ismonomarker(line): return if in_mono: destination.write(cook(line)) else: if line.rstrip().endswith(BREAK_MARKER): stripped = line.rstrip()[:-len(BREAK_MARKER)] + "\n" destination.write(cook(stripped)) else: destination.write(cook(line)) def main(source, destination, title, stripped_output): previndent = currindent = 0 in_mono = False previous_line_empty = False suppress_vertical_padding = False padstack = [] prevline = "" destination.write(HEAD) destination.write(TITLE_TEMPLATE % cook(title)) destination.write(BODY) for line in source: if in_mono: write_line(destination, line, in_mono, stripped_output) if ismonomarker(line): in_mono = False destination.write(END_MONO) suppress_vertical_padding = True prevline = line else: if len(line.strip()) == 0: previous_line_empty = True while len(padstack) > 0 and padstack[-1] == PADDING: padstack.pop() destination.write(PARA_END) prevline = line continue elif ismonomarker(line): in_mono = True for p in padstack: destination.write(PARA_END) del padstack[:] destination.write(START_MONO) previndent = 0 write_line(destination, line, in_mono, stripped_output) prevline = line continue currindent = get_indent(line) if previndent > currindent: i = 0 while i < (previndent - currindent): destination.write(PARA_END) removed = padstack.pop() if removed == SHIFT_RIGHT: i += 1 # not really needed to check for vertical padding suppression # here, but do it for maintanability if previous_line_empty and not suppress_vertical_padding: destination.write(PADDING) padstack.append(PADDING) elif currindent > previndent: for i in range(currindent - previndent): destination.write(SHIFT_RIGHT) padstack.append(SHIFT_RIGHT) if previous_line_empty and not suppress_vertical_padding: destination.write(PADDING) padstack.append(PADDING) elif previous_line_empty and not suppress_vertical_padding: destination.write(PADDING) padstack.append(PADDING) elif prevline.rstrip().endswith(BREAK_MARKER): destination.write(BREAK) previous_line_empty = False prevline = line previndent = currindent suppress_vertical_padding = False write_line(destination, line, in_mono, stripped_output) for p in padstack: destination.write(PARA_END) destination.write(FOOTER) if __name__ == "__main__": stripped_output = False options, arguments = getopt.gnu_getopt(sys.argv[1:], "hs") for name, value in options: if name == "-h": print(help_text) sys.exit(0) if name == "-s": stripped_output = True main(sys.stdin, sys.stdout, arguments[0], stripped_output)