mirror of
https://github.com/evennia/evennia.git
synced 2026-03-16 21:06:30 +01:00
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.func()
|
||||
cmdobj.at_post_cmd()
|
||||
except InterruptCommand:
|
||||
pass
|
||||
finally:
|
||||
# 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))
|
||||
for name, args, kwargs in receiver.msg.mock_calls]
|
||||
|
|
@ -88,11 +91,8 @@ class CommandTest(EvenniaTest):
|
|||
retval = sep1 + msg.strip() + sep2 + returned_msg + sep3
|
||||
raise AssertionError(retval)
|
||||
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()
|
||||
except InterruptCommand:
|
||||
pass
|
||||
finally:
|
||||
receiver.msg = old_msg
|
||||
|
||||
return returned_msg
|
||||
|
|
|
|||
|
|
@ -452,13 +452,13 @@ class TestChargen(CommandTest):
|
|||
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(), "testchar", "testchar(", caller=self.player)
|
||||
|
||||
|
||||
# Testing clothing contrib
|
||||
from evennia.contrib import clothing
|
||||
from evennia.objects.objects import DefaultRoom
|
||||
|
||||
class TestClothingCmd(CommandTest):
|
||||
|
||||
|
||||
def test_clothingcommands(self):
|
||||
wearer = create_object(clothing.ClothedCharacter, key="Wearer")
|
||||
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)
|
||||
|
||||
class TestClothingFunc(EvenniaTest):
|
||||
|
||||
|
||||
def test_clothingfunctions(self):
|
||||
wearer = create_object(clothing.ClothedCharacter, key="Wearer")
|
||||
room = create_object(DefaultRoom, key="room")
|
||||
|
|
@ -521,28 +521,28 @@ class TestClothingFunc(EvenniaTest):
|
|||
|
||||
test_hat.wear(wearer, 'on the head')
|
||||
self.assertEqual(test_hat.db.worn, 'on the head')
|
||||
|
||||
|
||||
test_hat.remove(wearer)
|
||||
self.assertEqual(test_hat.db.worn, False)
|
||||
|
||||
|
||||
test_hat.worn = True
|
||||
test_hat.at_get(wearer)
|
||||
self.assertEqual(test_hat.db.worn, False)
|
||||
|
||||
|
||||
clothes_list = [test_shirt, test_hat, test_pants]
|
||||
self.assertEqual(clothing.order_clothes_list(clothes_list), [test_hat, test_shirt, test_pants])
|
||||
|
||||
|
||||
test_hat.wear(wearer, True)
|
||||
test_pants.wear(wearer, True)
|
||||
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
|
||||
from evennia.contrib import custom_gametime
|
||||
|
||||
|
|
@ -850,7 +850,7 @@ from evennia.contrib import turnbattle
|
|||
from evennia.objects.objects import DefaultRoom
|
||||
|
||||
class TestTurnBattleCmd(CommandTest):
|
||||
|
||||
|
||||
# Test combat commands
|
||||
def test_turnbattlecmd(self):
|
||||
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.CmdDisengage(), "", "You can only do that in combat. (see: help fight)")
|
||||
self.call(turnbattle.CmdRest(), "", "Char rests to recover HP.")
|
||||
|
||||
|
||||
class TestTurnBattleFunc(EvenniaTest):
|
||||
|
||||
|
||||
# Test combat functions
|
||||
def test_turnbattlefunc(self):
|
||||
attacker = create_object(turnbattle.BattleCharacter, key="Attacker")
|
||||
|
|
@ -937,3 +937,51 @@ class TestTurnBattleFunc(EvenniaTest):
|
|||
self.assertTrue(turnhandler.db.fighters == [joiner, attacker, defender])
|
||||
# Remove the script at the end
|
||||
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