Skip to content

Commit

Permalink
Add a Python command-line interface (#441)
Browse files Browse the repository at this point in the history
* In Python, delete the temporary file by default

* Add a Python command-line interface

* Apply style fixes

Signed-off-by: Ian Hunter <ian.hunter@intel.com>

* more style fixes

* fix trailing whitespace

---------

Signed-off-by: Ian Hunter <ian.hunter@intel.com>
Co-authored-by: Ian Hunter <ian.hunter@intel.com>
  • Loading branch information
Kodiologist and ianfhunter authored Nov 17, 2023
1 parent 989487f commit 52d65ac
Show file tree
Hide file tree
Showing 3 changed files with 140 additions and 29 deletions.
9 changes: 9 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
68 changes: 39 additions & 29 deletions src/python/code/gnoll/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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__":
Expand Down
92 changes: 92 additions & 0 deletions src/python/code/gnoll/__main__.py
Original file line number Diff line number Diff line change
@@ -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:])))

0 comments on commit 52d65ac

Please sign in to comment.