Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add a Python command-line interface #441

Merged
merged 5 commits into from
Nov 17, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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):
ianfhunter marked this conversation as resolved.
Show resolved Hide resolved
"""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)
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion (non-blocking): You may be able to call this without the args object - I think argparse will automatically collect them for you - then you would be able to remove the parsing code

Suggested change
a = p.parse_args(args)
a = p.parse_args()

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's true in the usual case, but I figured that allowing parse_cmdline_args to be called with an argument would be convenient for testing. Then you don't have to mock sys.argv.

a.EXPR = ' '.join(a.EXPR)
return a

ianfhunter marked this conversation as resolved.
Show resolved Hide resolved

def main(EXPR, no_builtins, **kwargs):
ianfhunter marked this conversation as resolved.
Show resolved Hide resolved
"""
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)

ianfhunter marked this conversation as resolved.
Show resolved Hide resolved

if __name__ == '__main__':
main(**vars(parse_cmdline_args(sys.argv[1:])))
Loading