diff --git a/.gitignore b/.gitignore index c18dd8d..7a60b85 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ __pycache__/ +*.pyc diff --git a/fhex/__main__.py b/fhex/__main__.py index 98391df..c558885 100644 --- a/fhex/__main__.py +++ b/fhex/__main__.py @@ -1,11 +1,58 @@ import argparse +import sys +import fhex.parse def main(): - parser = argparse.ArgumentParser() + parser = argparse.ArgumentParser( + description="Convert hex strings to and from human-readable strings.") + parser.add_argument("-f", "--format", default="APNF") + parser.add_argument("-x", "--hex", nargs="?", const="-", + help="Hex code to translate to friendly string. If no value is" + + " specified, read hex from stdin.") + parser.add_argument("-s", "--string", nargs="?", const="-", + help="Friendly string to translate to hex. If no value is specified," + + " read string from stdin.") + parser.add_argument("--titlecase", action="store_true", + help="Titlecase friendly string output.") + parser.add_argument("--hyphenate", action="store_true", + help="Hyphenate friendly string output.") + parser.add_argument("-v", "--verbose", action="store_true") + args = parser.parse_args() - print("Hello, world!") + if args.verbose: + print("friendly-hex", file=sys.stderr) + print(" --format =", args.format, file=sys.stderr) + print(" --hex =", args.hex, file=sys.stderr) + print(" --string =", args.string, file=sys.stderr) + print(" --titlecase =", args.titlecase, file=sys.stderr) + print(" --hyphenate =", args.hyphenate, file=sys.stderr) + print(" --verbose =", args.verbose, file=sys.stderr) + print(file=sys.stderr) + + if args.hex is None and args.string is None: + parser.error("One of --hex or --string is required.") + + if args.hex is not None and args.string is None: + # Convert hex to friendly string + if args.hex == "-": + args.hex = sys.stdin.readline().strip() + if args.verbose: + print(f'Read "{args.hex}" from stdin', file=sys.stderr) + + words = fhex.parse.hex_to_friendly(args.hex, args.format) + if args.titlecase: + words = [word.title() for word in words] + + joiner = "-" if args.hyphenate else " " + joined = joiner.join(words) + + print(joined) + + if args.hex is None and args.string is not None: + # Convert friendly string to hex + raise NotImplementedError() if __name__ == "__main__": diff --git a/fhex/parse.py b/fhex/parse.py new file mode 100644 index 0000000..1914af2 --- /dev/null +++ b/fhex/parse.py @@ -0,0 +1,49 @@ +import math + +from fhex.words import ADJECTIVE, PARTICIPLE, NOUN, PHONETIC + + +DEFAULT_FORMAT_DEF = { + "A": ADJECTIVE, + "P": PARTICIPLE, + "N": NOUN, + "F": PHONETIC, +} + + +def hex_to_friendly(hex_str, format_str): + hex_i, fmt_i = 0, 0 + words = [] + fmt_def = DEFAULT_FORMAT_DEF + while hex_i < len(hex_str): + if fmt_i >= len(format_str): + raise Exception("Format string is too short") + fmt_code = format_str[fmt_i] + + if fmt_code not in fmt_def: + raise Exception(f"Unrecognized format code: {fmt_code}") + fmt_list = fmt_def[fmt_code] + + fmt_len = math.log(len(fmt_list), 16) + if fmt_len % 1 != 0: + raise Exception( + f"Word list for {fmt_code} must be a power of 16" + + " (length: {len(fmt_list)})") + fmt_len = int(fmt_len) + + if hex_i + fmt_len > len(hex_str): + raise Exception(f"Not enough hex characters for format code {fmt_code}") + + hex_part = hex_str[hex_i:hex_i + fmt_len] + fmt_list_i = int(hex_part, 16) + word = fmt_list[fmt_list_i] + words.append(word) + + hex_i += fmt_len + fmt_i += 1 + + if fmt_i != len(format_str): + raise Exception("Format string is too long") + + return words +