From b32b63da2712b1159969924329a0e9685e7b3471 Mon Sep 17 00:00:00 2001 From: Death Mask Salesman Date: Sun, 13 Oct 2019 16:33:31 +0000 Subject: [PATCH] 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). --- utils/patch.json.gz | Bin 0 -> 1554 bytes utils/unmark.py | 142 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 142 insertions(+) create mode 100644 utils/patch.json.gz create mode 100644 utils/unmark.py diff --git a/utils/patch.json.gz b/utils/patch.json.gz new file mode 100644 index 0000000000000000000000000000000000000000..6cca28e4f6edee9eabbc44de3985ffd9286fd035 GIT binary patch literal 1554 zcmV+t2JQJDiwFnfNRXC}>Y%<)(K zB+whWl39URG*{Zw?!hr#8XC@}m5(VOrY@R`WZ|M&sZtr&v5wxAPRwL0oQSjwnykaG zBcmxbO3%odTq27+#~Ux)!jaO{K#r*63YjR8Wos&tyJJ&!n_838xlHW3@JC;|cZ3tJ zz{o5U&K*%M*1)V&;8c#hp^2nS?eWZ<3oTn~!O!1dVgC07`z2XuyH2{Hs*Z8x8rbHzs>gtAp~Pe-$?- ztu_ts)_x8u4x{&PA3av9RaArzk+HKlH7lJNk-E4=0w9M-dZGMD@8;2^QaXsA$w*5d z7uNhK6Yt{67LRaN+Ies*(^m9M!GD-CkZ4O?qq8!T!pSi+Hj|zNX_d%!^ZuQcB3)Af zLGz89DIst}LmFzyft!Zy(Woz`GVnMW(Vjq^DU<@<(Nb6?pf7}IsBY5=oKdE7RU z@k6+yTsfyuj1>|j@YD$m7hJ=^xk7Pqee(HZ@5AQsamdNRBAJs*td!cl=-B_#9h_Z_ z`e(gP=eGah0FD>XoJdpUk%Gu5DV>Kn{}%k*rghC=GB?J_&^u5y(z5B)03T|>$|4g9 z>v_ztMyZ^NGV@U}fv^Axr4$^;sqkV-)>LYE>d5kUGCXrTQliVogC7sf?taf*G4 zmfZ`3yO?mtTsH{?U_!cuT&I(0qV$;d{)8VDr4PA=4hvSyHp;2VQXGKnbbS$s(~9T> zdI~ZM?%(oKu=1tVR2JMxg2m@!aA(yLYMsD!A!YcMR}BIryk_*AG#%cYRx%4cOKgs; zE~Aa#Br5|jvy^rY$ssZHG?bypjFzk+Rd}u&%{|05T1uH0RNYe5w3N8WQb)7=2&{;i zP`bTIcpFkx;-PBTaa)pSz3$y$)cbUM+vx>G4r(yDv9i7pu;GPnON&#`e z#FeObXrIFzq3!eU!9g?m1?ba=pOhAvcnlx06e<%e?=X3`laP9;tkF3P17}ZI>oW|T zGYkhgZ$mtz$yf)y*qRRAj;f3Kzp` zK&1h@ZaxK}8;j3v8i%0afQz%<$qkCZNlC+#quaA@uXp@4t|yOc0r7bMIB;ilBJ07t z7qSBS6n2aP2nAm}UomenY`^7Nx#9U>`#GYnevYS2bCf3a-d=Yu1{^l%0N^Ay_^J!@ z8%Uyz=uNj`pR0Gn7>u9u$<4y(jHd$cLsAC;Lj;?Z#04S)Ebczl82nCU3B4v0I{6kj zuA;eEZVRSrNmjlIE8tH#f1jNHosNIX`2UbG8uRvsTZZKfNtZgUQ3HMMc$MUb%iB+M zDKX5kx80SKOeJ)U#aF}a^6r|Jzi}Eb_;O~MU?Qzts<5VRLf&0R)RR+osRUdt2FP=K z-95v8*@^svA6NSkb2qKGF8-gy=3#<|di-HP{HOoJv+8KZ_-%CeukK&{1B1$t#qkdS E0J{_kQUCw| literal 0 HcmV?d00001 diff --git a/utils/unmark.py b/utils/unmark.py new file mode 100644 index 0000000..bb519b6 --- /dev/null +++ b/utils/unmark.py @@ -0,0 +1,142 @@ +""" 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)