diff --git a/README.md b/README.md index e3e3583db..98f96fbab 100644 --- a/README.md +++ b/README.md @@ -83,6 +83,15 @@ roll("1d20") # (return code, final result, dice breakdown (if enabled)) ``` +Or, use the command-line interface (see `--help`): +```sh +$ python3 -m gnoll 2d4 +6 +$ function gnoll() { python3 -m gnoll --breakdown "$@" ; } +$ gnoll 3d6 + 10 +[5, 5, 4] --> 24 +``` + ### 🛠️ Installing From Source #### Basic Requirements ```bash diff --git a/src/python/code/gnoll/__init__.py b/src/python/code/gnoll/__init__.py index 99ae23ccb..16afc4f91 100644 --- a/src/python/code/gnoll/__init__.py +++ b/src/python/code/gnoll/__init__.py @@ -48,20 +48,18 @@ def roll(s, mock=None, mock_const=3, breakdown=False, - builtins=False): + builtins=False, + keep_temp_file=False): """Parse some dice notation with GNOLL. @param s the string to parse @param verbose whether to enable verbosity (primarily for debug) @param mock override the internal random number generator (for testing). @param mock_const the seed value for overriding with mocks @param breakdown get the details of each dice rolled, not just the final result + @param keep_temp_file don't delete the temporary file @param force_dll_reload destroy the dll/shared object and reload (inadvisable) @return return code, final result, dice breakdown (None if disabled) """ - temp = tempfile.NamedTemporaryFile(prefix="gnoll_roll_", - suffix=".die", - delete=False) - def make_native_type(v): """ Change a string to a more appropriate type if possible. @@ -87,36 +85,48 @@ def extract_from_dice_file(lines, seperator): v = [list(map(make_native_type, x)) for x in v] return v - die_file = temp.name - os.remove(die_file) + try: + temp = tempfile.NamedTemporaryFile(prefix="gnoll_roll_", + suffix=".die", + delete=False) + temp.close() + + die_file = temp.name + + out_file = str(die_file).encode("ascii") + if verbose: + print("Rolling: ", s) + print("Output in:", out_file) + + s = s.encode("ascii") - out_file = str(die_file).encode("ascii") - if verbose: - print("Rolling: ", s) - print("Output in:", out_file) + return_code = libc.roll_full_options( + s, + out_file, + verbose, # enable_verbose + breakdown, # enable_introspect + mock is not None, # enable_mock + builtins, # enable_builtins + mock, + mock_const, + ) + if return_code != 0: + raise_gnoll_error(return_code) - s = s.encode("ascii") + with open(out_file, encoding="utf-8") as f: + lines = f.readlines() - return_code = libc.roll_full_options( - s, - out_file, - verbose, # enable_verbose - breakdown, # enable_introspect - mock is not None, # enable_mock - builtins, # enable_builtins - mock, - mock_const, - ) - if return_code != 0: - raise_gnoll_error(return_code) + dice_breakdown = extract_from_dice_file(lines, ",") + result = extract_from_dice_file(lines, ";") - with open(out_file, encoding="utf-8") as f: - lines = f.readlines() + return int(return_code), result, dice_breakdown - dice_breakdown = extract_from_dice_file(lines, ",") - result = extract_from_dice_file(lines, ";") + finally: + if not keep_temp_file: + if verbose: + print("Deleting:", out_file) - return int(return_code), result, dice_breakdown + os.remove(die_file) if __name__ == "__main__": diff --git a/src/python/code/gnoll/__main__.py b/src/python/code/gnoll/__main__.py new file mode 100644 index 000000000..3f3e83e4a --- /dev/null +++ b/src/python/code/gnoll/__main__.py @@ -0,0 +1,92 @@ +"""Roll some dice with GNOLL.""" + +import sys +import argparse +import gnoll + + +def parse_cmdline_args(args): + """Extract values from the commandline + @param args - the arguments from the commandline (excluding the python3 call) + """ + p = argparse.ArgumentParser( + description=__doc__, + usage='python3 -m gnoll [options] EXPR', + add_help=False) + + p.add_argument( + 'EXPR', + nargs='+', + help='a dice expression to evaluate' + '(multiple arguments will be joined with spaces)' + ) + + g = p.add_argument_group('main options') + g.add_argument( + '-h', + '--help', + action='help', + help='show this help message and exit' + ) + g.add_argument( + '-b', + '--breakdown', + action='store_true', + help='show a breakdown into individual dice' + ) + g.add_argument( + '--no-builtins', + action='store_true', + help='disable built-in macros' + ) + + g = p.add_argument_group('debugging options') + g.add_argument( + '-v', + '--verbose', + action='store_true', + help='enable verbosity' + ) + g.add_argument( + '--keep-temp-file', + action='store_true', + help="don't delete the created temporary file" + ) + g.add_argument( + '--mock', + metavar='TYPE', + type=int, + help='mocking type' + ) + g.add_argument( + '--mock-const', + metavar='N', + type=int, + default=3, + help='mocking constant' + ) + + a = p.parse_args(args) + a.EXPR = ' '.join(a.EXPR) + return a + + +def main(EXPR, no_builtins, **kwargs): + """ + The entry point for gnoll when called via `python -m gnoll` + @param EXPR - the expression + @param no_builtins - a flag to disable builtins + @param **kwargs - other key word arguments to be passed to gnoll.roll + """ + _, [[result]], breakdown = gnoll.roll( + EXPR, + builtins=not no_builtins, + **kwargs) + if breakdown: + print(breakdown[0], '-->', result) + else: + print(result) + + +if __name__ == '__main__': + main(**vars(parse_cmdline_args(sys.argv[1:])))