Merge branch 'unixcommand' of https://github.com/vlegoff/evennia into vlegoff-unixcommand
This commit is contained in:
commit
248f20e8e5
3 changed files with 356 additions and 21 deletions
|
|
@ -73,6 +73,9 @@ class CommandTest(EvenniaTest):
|
||||||
cmdobj.parse()
|
cmdobj.parse()
|
||||||
cmdobj.func()
|
cmdobj.func()
|
||||||
cmdobj.at_post_cmd()
|
cmdobj.at_post_cmd()
|
||||||
|
except InterruptCommand:
|
||||||
|
pass
|
||||||
|
finally:
|
||||||
# clean out prettytable sugar. We only operate on text-type
|
# clean out prettytable sugar. We only operate on text-type
|
||||||
stored_msg = [args[0] if args and args[0] else kwargs.get("text",utils.to_str(kwargs, force_string=True))
|
stored_msg = [args[0] if args and args[0] else kwargs.get("text",utils.to_str(kwargs, force_string=True))
|
||||||
for name, args, kwargs in receiver.msg.mock_calls]
|
for name, args, kwargs in receiver.msg.mock_calls]
|
||||||
|
|
@ -88,11 +91,8 @@ class CommandTest(EvenniaTest):
|
||||||
retval = sep1 + msg.strip() + sep2 + returned_msg + sep3
|
retval = sep1 + msg.strip() + sep2 + returned_msg + sep3
|
||||||
raise AssertionError(retval)
|
raise AssertionError(retval)
|
||||||
else:
|
else:
|
||||||
returned_msg = "\n".join(stored_msg)
|
returned_msg = "\n".join(str(msg) for msg in stored_msg)
|
||||||
returned_msg = ansi.parse_ansi(returned_msg, strip_ansi=noansi).strip()
|
returned_msg = ansi.parse_ansi(returned_msg, strip_ansi=noansi).strip()
|
||||||
except InterruptCommand:
|
|
||||||
pass
|
|
||||||
finally:
|
|
||||||
receiver.msg = old_msg
|
receiver.msg = old_msg
|
||||||
|
|
||||||
return returned_msg
|
return returned_msg
|
||||||
|
|
|
||||||
|
|
@ -452,13 +452,13 @@ class TestChargen(CommandTest):
|
||||||
self.assertTrue(self.player.db._character_dbrefs)
|
self.assertTrue(self.player.db._character_dbrefs)
|
||||||
self.call(chargen.CmdOOCLook(), "", "You, TestPlayer, are an OOC ghost without form.",caller=self.player)
|
self.call(chargen.CmdOOCLook(), "", "You, TestPlayer, are an OOC ghost without form.",caller=self.player)
|
||||||
self.call(chargen.CmdOOCLook(), "testchar", "testchar(", caller=self.player)
|
self.call(chargen.CmdOOCLook(), "testchar", "testchar(", caller=self.player)
|
||||||
|
|
||||||
# Testing clothing contrib
|
# Testing clothing contrib
|
||||||
from evennia.contrib import clothing
|
from evennia.contrib import clothing
|
||||||
from evennia.objects.objects import DefaultRoom
|
from evennia.objects.objects import DefaultRoom
|
||||||
|
|
||||||
class TestClothingCmd(CommandTest):
|
class TestClothingCmd(CommandTest):
|
||||||
|
|
||||||
def test_clothingcommands(self):
|
def test_clothingcommands(self):
|
||||||
wearer = create_object(clothing.ClothedCharacter, key="Wearer")
|
wearer = create_object(clothing.ClothedCharacter, key="Wearer")
|
||||||
friend = create_object(clothing.ClothedCharacter, key="Friend")
|
friend = create_object(clothing.ClothedCharacter, key="Friend")
|
||||||
|
|
@ -501,7 +501,7 @@ class TestClothingCmd(CommandTest):
|
||||||
self.call(clothing.CmdInventory(), "", "You are not carrying or wearing anything.", caller=wearer)
|
self.call(clothing.CmdInventory(), "", "You are not carrying or wearing anything.", caller=wearer)
|
||||||
|
|
||||||
class TestClothingFunc(EvenniaTest):
|
class TestClothingFunc(EvenniaTest):
|
||||||
|
|
||||||
def test_clothingfunctions(self):
|
def test_clothingfunctions(self):
|
||||||
wearer = create_object(clothing.ClothedCharacter, key="Wearer")
|
wearer = create_object(clothing.ClothedCharacter, key="Wearer")
|
||||||
room = create_object(DefaultRoom, key="room")
|
room = create_object(DefaultRoom, key="room")
|
||||||
|
|
@ -521,28 +521,28 @@ class TestClothingFunc(EvenniaTest):
|
||||||
|
|
||||||
test_hat.wear(wearer, 'on the head')
|
test_hat.wear(wearer, 'on the head')
|
||||||
self.assertEqual(test_hat.db.worn, 'on the head')
|
self.assertEqual(test_hat.db.worn, 'on the head')
|
||||||
|
|
||||||
test_hat.remove(wearer)
|
test_hat.remove(wearer)
|
||||||
self.assertEqual(test_hat.db.worn, False)
|
self.assertEqual(test_hat.db.worn, False)
|
||||||
|
|
||||||
test_hat.worn = True
|
test_hat.worn = True
|
||||||
test_hat.at_get(wearer)
|
test_hat.at_get(wearer)
|
||||||
self.assertEqual(test_hat.db.worn, False)
|
self.assertEqual(test_hat.db.worn, False)
|
||||||
|
|
||||||
clothes_list = [test_shirt, test_hat, test_pants]
|
clothes_list = [test_shirt, test_hat, test_pants]
|
||||||
self.assertEqual(clothing.order_clothes_list(clothes_list), [test_hat, test_shirt, test_pants])
|
self.assertEqual(clothing.order_clothes_list(clothes_list), [test_hat, test_shirt, test_pants])
|
||||||
|
|
||||||
test_hat.wear(wearer, True)
|
test_hat.wear(wearer, True)
|
||||||
test_pants.wear(wearer, True)
|
test_pants.wear(wearer, True)
|
||||||
self.assertEqual(clothing.get_worn_clothes(wearer), [test_hat, test_pants])
|
self.assertEqual(clothing.get_worn_clothes(wearer), [test_hat, test_pants])
|
||||||
|
|
||||||
self.assertEqual(clothing.clothing_type_count(clothes_list), {'hat':1, 'top':1, 'bottom':1})
|
|
||||||
|
|
||||||
self.assertEqual(clothing.single_type_count(clothes_list, 'hat'), 1)
|
|
||||||
|
|
||||||
|
|
||||||
|
self.assertEqual(clothing.clothing_type_count(clothes_list), {'hat':1, 'top':1, 'bottom':1})
|
||||||
|
|
||||||
|
self.assertEqual(clothing.single_type_count(clothes_list, 'hat'), 1)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# Testing custom_gametime
|
# Testing custom_gametime
|
||||||
from evennia.contrib import custom_gametime
|
from evennia.contrib import custom_gametime
|
||||||
|
|
||||||
|
|
@ -850,7 +850,7 @@ from evennia.contrib import turnbattle
|
||||||
from evennia.objects.objects import DefaultRoom
|
from evennia.objects.objects import DefaultRoom
|
||||||
|
|
||||||
class TestTurnBattleCmd(CommandTest):
|
class TestTurnBattleCmd(CommandTest):
|
||||||
|
|
||||||
# Test combat commands
|
# Test combat commands
|
||||||
def test_turnbattlecmd(self):
|
def test_turnbattlecmd(self):
|
||||||
self.call(turnbattle.CmdFight(), "", "You can't start a fight if you've been defeated!")
|
self.call(turnbattle.CmdFight(), "", "You can't start a fight if you've been defeated!")
|
||||||
|
|
@ -858,9 +858,9 @@ class TestTurnBattleCmd(CommandTest):
|
||||||
self.call(turnbattle.CmdPass(), "", "You can only do that in combat. (see: help fight)")
|
self.call(turnbattle.CmdPass(), "", "You can only do that in combat. (see: help fight)")
|
||||||
self.call(turnbattle.CmdDisengage(), "", "You can only do that in combat. (see: help fight)")
|
self.call(turnbattle.CmdDisengage(), "", "You can only do that in combat. (see: help fight)")
|
||||||
self.call(turnbattle.CmdRest(), "", "Char rests to recover HP.")
|
self.call(turnbattle.CmdRest(), "", "Char rests to recover HP.")
|
||||||
|
|
||||||
class TestTurnBattleFunc(EvenniaTest):
|
class TestTurnBattleFunc(EvenniaTest):
|
||||||
|
|
||||||
# Test combat functions
|
# Test combat functions
|
||||||
def test_turnbattlefunc(self):
|
def test_turnbattlefunc(self):
|
||||||
attacker = create_object(turnbattle.BattleCharacter, key="Attacker")
|
attacker = create_object(turnbattle.BattleCharacter, key="Attacker")
|
||||||
|
|
@ -937,3 +937,51 @@ class TestTurnBattleFunc(EvenniaTest):
|
||||||
self.assertTrue(turnhandler.db.fighters == [joiner, attacker, defender])
|
self.assertTrue(turnhandler.db.fighters == [joiner, attacker, defender])
|
||||||
# Remove the script at the end
|
# Remove the script at the end
|
||||||
turnhandler.stop()
|
turnhandler.stop()
|
||||||
|
|
||||||
|
|
||||||
|
# Test of the unixcommand module
|
||||||
|
|
||||||
|
from evennia.contrib.unixcommand import UnixCommand
|
||||||
|
|
||||||
|
class CmdDummy(UnixCommand):
|
||||||
|
|
||||||
|
"""A dummy UnixCommand."""
|
||||||
|
|
||||||
|
key = "dummy"
|
||||||
|
|
||||||
|
def init_parser(self):
|
||||||
|
"""Fill out options."""
|
||||||
|
self.parser.add_argument("nb1", type=int, help="the first number")
|
||||||
|
self.parser.add_argument("nb2", type=int, help="the second number")
|
||||||
|
self.parser.add_argument("-v", "--verbose", action="store_true")
|
||||||
|
|
||||||
|
def func(self):
|
||||||
|
nb1 = self.opts.nb1
|
||||||
|
nb2 = self.opts.nb2
|
||||||
|
result = nb1 * nb2
|
||||||
|
verbose = self.opts.verbose
|
||||||
|
if verbose:
|
||||||
|
self.msg("{} times {} is {}".format(nb1, nb2, result))
|
||||||
|
else:
|
||||||
|
self.msg("{} * {} = {}".format(nb1, nb2, result))
|
||||||
|
|
||||||
|
|
||||||
|
class TestUnixCommand(CommandTest):
|
||||||
|
|
||||||
|
def test_success(self):
|
||||||
|
"""See the command parsing succeed."""
|
||||||
|
self.call(CmdDummy(), "5 10", "5 * 10 = 50")
|
||||||
|
self.call(CmdDummy(), "5 10 -v", "5 times 10 is 50")
|
||||||
|
|
||||||
|
def test_failure(self):
|
||||||
|
"""If not provided with the right info, should fail."""
|
||||||
|
ret = self.call(CmdDummy(), "5")
|
||||||
|
lines = ret.splitlines()
|
||||||
|
self.assertTrue(any(l.startswith("usage:") for l in lines))
|
||||||
|
self.assertTrue(any(l.startswith("dummy: error:") for l in lines))
|
||||||
|
|
||||||
|
# If we specify an incorrect number as parameter
|
||||||
|
ret = self.call(CmdDummy(), "five ten")
|
||||||
|
lines = ret.splitlines()
|
||||||
|
self.assertTrue(any(l.startswith("usage:") for l in lines))
|
||||||
|
self.assertTrue(any(l.startswith("dummy: error:") for l in lines))
|
||||||
|
|
|
||||||
287
evennia/contrib/unixcommand.py
Normal file
287
evennia/contrib/unixcommand.py
Normal file
|
|
@ -0,0 +1,287 @@
|
||||||
|
"""
|
||||||
|
Module containing the UnixCommand class.
|
||||||
|
|
||||||
|
This command allows to use unix-like options in game commands. It is
|
||||||
|
not the best parser for players, but can be really useful for builders
|
||||||
|
when they need to have a single command to do many things with many
|
||||||
|
options.
|
||||||
|
|
||||||
|
The UnixCommand can be ovverridden to have your commands parsed.
|
||||||
|
You will need to override two methods:
|
||||||
|
- The `init_parser` method, which adds options to the parser.
|
||||||
|
- The `func` method, called to execute the command once parsed.
|
||||||
|
|
||||||
|
Here's a short example:
|
||||||
|
|
||||||
|
```python
|
||||||
|
class CmdPlant(UnixCommand):
|
||||||
|
|
||||||
|
'''
|
||||||
|
Plant a tree or plant.
|
||||||
|
|
||||||
|
This command is used to plant a tree or plant in the room you are in.
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
plant orange -a 8
|
||||||
|
plant strawberry --hidden
|
||||||
|
plant potato --hidden --age 5
|
||||||
|
|
||||||
|
'''
|
||||||
|
|
||||||
|
key = "plant"
|
||||||
|
|
||||||
|
def init_parser(self):
|
||||||
|
"Add the arguments to the parser."
|
||||||
|
# 'self.parser' inherits `argparse.ArgumentParser`
|
||||||
|
self.parser.add_argument("key",
|
||||||
|
help="the key of the plant to be planted here")
|
||||||
|
self.parser.add_argument("-a", "--age", type=int,
|
||||||
|
default=1, help="the age of the plant to be planted")
|
||||||
|
self.parser.add_argument("--hidden", action="store_true",
|
||||||
|
help="should the newly-planted plant be hidden to players?")
|
||||||
|
|
||||||
|
def func(self):
|
||||||
|
"func is called only if the parser succeeded."
|
||||||
|
# 'self.opts' contains the parsed options
|
||||||
|
key = self.opts.key
|
||||||
|
age = self.opts.age
|
||||||
|
hidden = self.opts.hidden
|
||||||
|
self.msg("Going to plant '{}', age={}, hidden={}.".format(
|
||||||
|
key, age, hidden))
|
||||||
|
```
|
||||||
|
|
||||||
|
To see the full power of argparse and the types of supported options, visit
|
||||||
|
[the documentation of argparse](https://docs.python.org/2/library/argparse.html).
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
import shlex
|
||||||
|
from textwrap import dedent
|
||||||
|
|
||||||
|
from evennia import Command, InterruptCommand
|
||||||
|
from evennia.utils.ansi import raw
|
||||||
|
|
||||||
|
class ParseError(Exception):
|
||||||
|
|
||||||
|
"""An error occurred during parsing."""
|
||||||
|
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class UnixCommandParser(argparse.ArgumentParser):
|
||||||
|
|
||||||
|
"""A modifier command parser for unix commands.
|
||||||
|
|
||||||
|
This parser is used to replace `argparse.ArgumentParser`. It
|
||||||
|
is aware of the command calling it, and can more easily report to
|
||||||
|
the caller. Some features (like the "brutal exit" of the original
|
||||||
|
parser) are disabled or replaced. This parser is used by UnixCommand
|
||||||
|
and creating one directly isn't recommended nor necessary. Even
|
||||||
|
adding a sub-command will use this replaced parser automatically.
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, prog, description="", epilog="", command=None, **kwargs):
|
||||||
|
"""
|
||||||
|
Build a UnixCommandParser with a link to the command using it.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
prog (str): the program name (usually the command key).
|
||||||
|
description (str): a very brief line to show in the usage text.
|
||||||
|
epilog (str): the epilog to show below options.
|
||||||
|
command (Command): the command calling the parser.
|
||||||
|
|
||||||
|
Kwargs:
|
||||||
|
Additional keyword arguments are directly sent to
|
||||||
|
`argparse.ArgumentParser`. You will find them on the
|
||||||
|
[parser's documentation](https://docs.python.org/2/library/argparse.html).
|
||||||
|
|
||||||
|
Note:
|
||||||
|
It's doubtful you would need to create this parser manually.
|
||||||
|
The `UnixCommand` does that automatically. If you create
|
||||||
|
sub-commands, this class will be used.
|
||||||
|
|
||||||
|
"""
|
||||||
|
prog = prog or command.key
|
||||||
|
super(UnixCommandParser, self).__init__(
|
||||||
|
prog=prog, description=description,
|
||||||
|
conflict_handler='resolve', add_help=False, **kwargs)
|
||||||
|
self.command = command
|
||||||
|
self.post_help = epilog
|
||||||
|
def n_exit(code=None, msg=None):
|
||||||
|
raise ParseError(msg)
|
||||||
|
|
||||||
|
self.exit = n_exit
|
||||||
|
|
||||||
|
# Replace the -h/--help
|
||||||
|
self.add_argument("-h", "--hel", nargs=0, action=HelpAction,
|
||||||
|
help="display the command help")
|
||||||
|
|
||||||
|
def format_usage(self):
|
||||||
|
"""Return the usage line.
|
||||||
|
|
||||||
|
Note:
|
||||||
|
This method is present to return the raw-escaped usage line,
|
||||||
|
in order to avoid unintentional color codes.
|
||||||
|
|
||||||
|
"""
|
||||||
|
return raw(super(UnixCommandParser, self).format_usage())
|
||||||
|
|
||||||
|
def format_help(self):
|
||||||
|
"""Return the parser help, including its epilog.
|
||||||
|
|
||||||
|
Note:
|
||||||
|
This method is present to return the raw-escaped help,
|
||||||
|
in order to avoid unintentional color codes. Color codes
|
||||||
|
in the epilog (the command docstring) are supported.
|
||||||
|
|
||||||
|
"""
|
||||||
|
autohelp = raw(super(UnixCommandParser, self).format_help())
|
||||||
|
return "\n" + autohelp + "\n" + self.post_help
|
||||||
|
|
||||||
|
def print_usage(self, file=None):
|
||||||
|
"""Print the usage to the caller.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
file (file-object): not used here, the caller is used.
|
||||||
|
|
||||||
|
Note:
|
||||||
|
This method will override `argparse.ArgumentParser`'s in order
|
||||||
|
to not display the help on stdout or stderr, but to the
|
||||||
|
command's caller.
|
||||||
|
|
||||||
|
"""
|
||||||
|
if self.command:
|
||||||
|
self.command.msg(self.format_usage().strip())
|
||||||
|
|
||||||
|
def print_help(self, file=None):
|
||||||
|
"""Print the help to the caller.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
file (file-object): not used here, the caller is used.
|
||||||
|
|
||||||
|
Note:
|
||||||
|
This method will override `argparse.ArgumentParser`'s in order
|
||||||
|
to not display the help on stdout or stderr, but to the
|
||||||
|
command's caller.
|
||||||
|
|
||||||
|
"""
|
||||||
|
if self.command:
|
||||||
|
self.command.msg(self.format_help().strip())
|
||||||
|
|
||||||
|
|
||||||
|
class HelpAction(argparse.Action):
|
||||||
|
|
||||||
|
"""Override the -h/--help action in the default parser.
|
||||||
|
|
||||||
|
Using the default -h/--help will call the exit function in different
|
||||||
|
ways, preventing the entire help message to be provided. Hence
|
||||||
|
this override.
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __call__(self, parser, namespace, values, option_string=None):
|
||||||
|
"""If asked for help, display to the caller."""
|
||||||
|
if parser.command:
|
||||||
|
parser.command.msg(parser.format_help().strip())
|
||||||
|
parser.exit(0, "")
|
||||||
|
|
||||||
|
|
||||||
|
class UnixCommand(Command):
|
||||||
|
"""
|
||||||
|
Unix-type commands, supporting short and long options.
|
||||||
|
|
||||||
|
This command syntax uses the Unix-style commands with short options
|
||||||
|
(-X) and long options (--something). The `argparse` module is
|
||||||
|
used to parse the command.
|
||||||
|
|
||||||
|
In order to use it, you should override two methods:
|
||||||
|
- `init_parser`: this method is called when the command is created.
|
||||||
|
It can be used to set options in the parser. `self.parser`
|
||||||
|
contains the `argparse.ArgumentParser`, so you can add arguments
|
||||||
|
here.
|
||||||
|
- `func`: this method is called to execute the command, but after
|
||||||
|
the parser has checked the arguments given to it are valid.
|
||||||
|
You can access the namespace of valid arguments in `self.opts`
|
||||||
|
at this point.
|
||||||
|
|
||||||
|
The help of UnixCommands is derived from the docstring, in a
|
||||||
|
slightly different way than usual: the first line of the docstring
|
||||||
|
is used to represent the program description (the very short
|
||||||
|
line at the top of the help message). The other lines below are
|
||||||
|
used as the program's "epilog", displayed below the options. It
|
||||||
|
means in your docstring, you don't have to write the options.
|
||||||
|
They will be automatically provided by the parser and displayed
|
||||||
|
accordingly. The `argparse` module provides a default '-h' or
|
||||||
|
'--help' option on the command. Typing |whelp commandname|n will
|
||||||
|
display the same as |wcommandname -h|n, though this behavior can
|
||||||
|
be changed.
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, **kwargs):
|
||||||
|
"""
|
||||||
|
The lockhandler works the same as for objects.
|
||||||
|
optional kwargs will be set as properties on the Command at runtime,
|
||||||
|
overloading evential same-named class properties.
|
||||||
|
|
||||||
|
"""
|
||||||
|
super(UnixCommand, self).__init__(**kwargs)
|
||||||
|
|
||||||
|
# Create the empty UnixCommandParser, inheriting argparse.ArgumentParser
|
||||||
|
lines = dedent(self.__doc__.strip("\n")).splitlines()
|
||||||
|
description = lines[0].strip()
|
||||||
|
epilog = "\n".join(lines[1:]).strip()
|
||||||
|
self.parser = UnixCommandParser(None, description, epilog, command=self)
|
||||||
|
|
||||||
|
# Fill the argument parser
|
||||||
|
self.init_parser()
|
||||||
|
|
||||||
|
def init_parser(self):
|
||||||
|
"""
|
||||||
|
Configure the argument parser, adding in options.
|
||||||
|
|
||||||
|
Note:
|
||||||
|
This method is to be overridden in order to add options
|
||||||
|
to the argument parser. Use `self.parser`, which contains
|
||||||
|
the `argparse.ArgumentParser`. You can, for instance,
|
||||||
|
use its `add_argument` method.
|
||||||
|
|
||||||
|
"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
def func(self):
|
||||||
|
"""Override to handle the command execution."""
|
||||||
|
pass
|
||||||
|
|
||||||
|
def get_help(self, caller, cmdset):
|
||||||
|
"""
|
||||||
|
Return the help message for this command and this caller.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
caller (Object or Player): the caller asking for help on the command.
|
||||||
|
cmdset (CmdSet): the command set (if you need additional commands).
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
docstring (str): the help text to provide the caller for this command.
|
||||||
|
|
||||||
|
"""
|
||||||
|
return self.parser.format_help()
|
||||||
|
|
||||||
|
def parse(self):
|
||||||
|
"""
|
||||||
|
Process arguments provided in `self.args`.
|
||||||
|
|
||||||
|
Note:
|
||||||
|
You should not override this method. Consider overriding
|
||||||
|
`init_parser` instead.
|
||||||
|
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
self.opts = self.parser.parse_args(shlex.split(self.args))
|
||||||
|
except ParseError as err:
|
||||||
|
msg = str(err)
|
||||||
|
if msg:
|
||||||
|
self.msg(msg)
|
||||||
|
raise InterruptCommand
|
||||||
Loading…
Add table
Add a link
Reference in a new issue