GodMode9/utils/unmark.py
Death Mask Salesman b32b63da27 Add Unmark as an external tool (#546)
`unmark` is a tool to produce an user manual from the `README.md`.

The `patch.json.gz` file contains a list of patterns to match and
their replacements.
If `unmark` complains about being unable to match a pattern, please open
an issue with the warning and your `README.md` at
[upstream](https://github.com/DMSalesman/Unmark).
2019-10-13 13:33:31 -03:00

143 lines
3.8 KiB
Python

""" Create an user manual from GodMode9's README.md file. """
import argparse as _argparse
import gzip as _gzip
import json as _json
import os as _os
import re as _re
import sys as _sys
def _exit_fatal(msg):
""" Print an error message to stderr and exit. """
print("Fatal: {0}".format(msg), file=_sys.stderr)
exit(1)
def _print_warn(msg):
""" Print a warning to stderr. """
print("Warning: {0}".format(msg, file=_sys.stderr))
def load_input(fname):
""" Load a file. """
with open(fname, "r") as f: # pylint: disable=invalid-name
return f.read()
def load_patch(fname):
""" Load a patch file. """
with _gzip.open(fname, "rb") as f: # pylint: disable=invalid-name
return _json.load(f)
def apply_patch(patch_data, data):
""" Replace strings in data based on the records in patch_data. """
unmatched = []
for pattern in patch_data:
# pattern[0]: original string, pattern[1]: replacement
if data.find(pattern[0]) == -1:
unmatched.append(pattern[0])
else:
data = data.replace(pattern[0], pattern[1])
return (data, unmatched)
def strip_md(data):
""" Remove certain MarkDown from data. """
# NOTE: multiple occurrences of the same char go before single occurrences
# ["__", "_"] ok, ["_", "__"] NOT ok
blacklist = ["__", "**", "\\"]
for pattern in blacklist:
data = data.replace(pattern, "")
return data
def write_output(data, fname, force):
""" Write data to fname. """
mode = "w" if force else "x"
with open(fname, mode) as f: # pylint: disable=invalid-name
f.write(data)
def replace_links(data):
""" Replace links with their anchor text. """
for link in _re.findall(r"\[(.*?)\]\((.*?)\)", data):
pattern = "[{0}]({1})".format(link[0], link[1])
data = data.replace(pattern, link[0])
return data
def main(src, dst, force):
""" Core function. """
basedir = _os.path.dirname(_os.path.abspath(__file__))
patch = _os.path.join(basedir, "patch.json.gz")
try:
data = load_input(src)
except FileNotFoundError:
_exit_fatal("{0}: no such file.".format(src))
except PermissionError:
_exit_fatal("{0}: permission denied.".format(src))
except IsADirectoryError:
_exit_fatal("{0}: is a directory.".format(src))
patch_data = []
try:
patch_data = load_patch(patch)
except FileNotFoundError:
_print_warn("{0}: no such file.".format(patch))
except PermissionError:
_print_warn("{0}: pernission denied.".format(patch))
except _json.decoder.JSONDecodeError:
_print_warn("{0}: malformed JSON file.".format(patch))
if patch_data:
data, unmatched = apply_patch(patch_data, data)
for pattern in unmatched:
_print_warn("unmatched pattern: {0!r}".format(pattern))
data = strip_md(data)
data = replace_links(data)
try:
write_output(data, dst, force)
except FileExistsError:
_exit_fatal("{0}: file already exists. Pass -f to override.".format(dst))
except PermissionError:
_exit_fatal("{0}: permission denied.".format(dst))
except IsADirectoryError:
_exit_fatal("{0}: is a directory.".format(dst))
if __name__ == "__main__":
AP = _argparse.ArgumentParser(
description="Create an user manual from GodMode9's README.md file."
)
AP.add_argument(
"src",
type=str,
help="the original README.md file"
)
AP.add_argument(
"dst",
type=str,
help="the user manual"
)
AP.add_argument(
"-f",
"--force",
default=False,
action="store_true",
help="overwrite the output file, if present"
)
ARGS = AP.parse_args()
main(ARGS.src, ARGS.dst, ARGS.force)