mirror of
https://github.com/evennia/evennia.git
synced 2026-03-16 21:06:30 +01:00
748 lines
No EOL
27 KiB
Markdown
748 lines
No EOL
27 KiB
Markdown
# Parsing command arguments, theory and best practices
|
|
|
|
|
|
This tutorial will elaborate on the many ways one can parse command arguments. The first step after
|
|
[adding a command](Beginner-Tutorial/Part1/Beginner-Tutorial-Adding-Commands.md) usually is to parse its arguments. There are lots of
|
|
ways to do it, but some are indeed better than others and this tutorial will try to present them.
|
|
|
|
If you're a Python beginner, this tutorial might help you a lot. If you're already familiar with
|
|
Python syntax, this tutorial might still contain useful information. There are still a lot of
|
|
things I find in the standard library that come as a surprise, though they were there all along.
|
|
This might be true for others.
|
|
|
|
In this tutorial we will:
|
|
|
|
- Parse arguments with numbers.
|
|
- Parse arguments with delimiters.
|
|
- Take a look at optional arguments.
|
|
- Parse argument containing object names.
|
|
|
|
## What are command arguments?
|
|
|
|
I'm going to talk about command arguments and parsing a lot in this tutorial. So let's be sure we
|
|
talk about the same thing before going any further:
|
|
|
|
> A command is an Evennia object that handles specific user input.
|
|
|
|
For instance, the default `look` is a command. After having created your Evennia game, and
|
|
connected to it, you should be able to type `look` to see what's around. In this context, `look` is
|
|
a command.
|
|
|
|
> Command arguments are additional text passed after the command.
|
|
|
|
Following the same example, you can type `look self` to look at yourself. In this context, `self`
|
|
is the text specified after `look`. `" self"` is the argument to the `look` command.
|
|
|
|
Part of our task as a game developer is to connect user inputs (mostly commands) with actions in the
|
|
game. And most of the time, entering commands is not enough, we have to rely on arguments for
|
|
specifying actions with more accuracy.
|
|
|
|
Take the `say` command. If you couldn't specify what to say as a command argument (`say hello!`),
|
|
you would have trouble communicating with others in the game. One would need to create a different
|
|
command for every kind of word or sentence, which is, of course, not practical.
|
|
|
|
Last thing: what is parsing?
|
|
|
|
> In our case, parsing is the process by which we convert command arguments into something we can
|
|
work with.
|
|
|
|
We don't usually use the command argument as is (which is just text, of type `str` in Python). We
|
|
need to extract useful information. We might want to ask the user for a number, or the name of
|
|
another character present in the same room. We're going to see how to do all that now.
|
|
|
|
## Working with strings
|
|
|
|
In object terms, when you write a command in Evennia (when you write the Python class), the
|
|
arguments are stored in the `args` attribute. Which is to say, inside your `func` method, you can
|
|
access the command arguments in `self.args`.
|
|
|
|
### self.args
|
|
|
|
To begin with, look at this example:
|
|
|
|
```python
|
|
class CmdTest(Command):
|
|
|
|
"""
|
|
Test command.
|
|
|
|
Syntax:
|
|
test [argument]
|
|
|
|
Enter any argument after test.
|
|
|
|
"""
|
|
|
|
key = "test"
|
|
|
|
def func(self):
|
|
self.msg(f"You have entered: {self.args}.")
|
|
```
|
|
|
|
If you add this command and test it, you will receive exactly what you have entered without any
|
|
parsing:
|
|
|
|
```
|
|
> test Whatever
|
|
You have entered: Whatever.
|
|
> test
|
|
You have entered: .
|
|
```
|
|
|
|
> The lines starting with `>` indicate what you enter into your client. The other lines are what
|
|
you receive from the game server.
|
|
|
|
Notice two things here:
|
|
|
|
1. The left space between our command key ("test", here) and our command argument is not removed.
|
|
That's why there are two spaces in our output at line 2. Try entering something like "testok".
|
|
2. Even if you don't enter command arguments, the command will still be called with an empty string
|
|
in `self.args`.
|
|
|
|
Perhaps a slight modification to our code would be appropriate to see what's happening. We will
|
|
force Python to display the command arguments as a debug string using a little shortcut.
|
|
|
|
```python
|
|
class CmdTest(Command):
|
|
|
|
"""
|
|
Test command.
|
|
|
|
Syntax:
|
|
test [argument]
|
|
|
|
Enter any argument after test.
|
|
|
|
"""
|
|
|
|
key = "test"
|
|
|
|
def func(self):
|
|
self.msg(f"You have entered: {self.args!r}.")
|
|
```
|
|
|
|
The only line we have changed is the last one, and we have added `!r` between our braces to tell
|
|
Python to print the debug version of the argument (the repr-ed version). Let's see the result:
|
|
|
|
```
|
|
> test Whatever
|
|
You have entered: ' Whatever'.
|
|
> test
|
|
You have entered: ''.
|
|
> test And something with '?
|
|
You have entered: " And something with '?".
|
|
```
|
|
|
|
This displays the string in a way you could see in the Python interpreter. It might be easier to
|
|
read... to debug, anyway.
|
|
|
|
I insist so much on that point because it's crucial: the command argument is just a string (of type
|
|
`str`) and we will use this to parse it. What you will see is mostly not Evennia-specific, it's
|
|
Python-specific and could be used in any other project where you have the same need.
|
|
|
|
### Stripping
|
|
|
|
As you've seen, our command arguments are stored with the space. And the space between the command
|
|
and the arguments is often of no importance.
|
|
|
|
> Why is it ever there?
|
|
|
|
Evennia will try its best to find a matching command. If the user enters your command key with
|
|
arguments (but omits the space), Evennia will still be able to find and call the command. You might
|
|
have seen what happened if the user entered `testok`. In this case, `testok` could very well be a
|
|
command (Evennia checks for that) but seeing none, and because there's a `test` command, Evennia
|
|
calls it with the arguments `"ok"`.
|
|
|
|
But most of the time, we don't really care about this left space, so you will often see code to
|
|
remove it. There are different ways to do it in Python, but a command use case is the `strip`
|
|
method on `str` and its cousins, `lstrip` and `rstrip`.
|
|
|
|
- `strip`: removes one or more characters (either spaces or other characters) from both ends of the
|
|
string.
|
|
- `lstrip`: same thing but only removes from the left end (left strip) of the string.
|
|
- `rstrip`: same thing but only removes from the right end (right strip) of the string.
|
|
|
|
Some Python examples might help:
|
|
|
|
```python
|
|
>>> ' this is '.strip() # remove spaces by default
|
|
'this is'
|
|
>>> " What if I'm right? ".lstrip() # strip spaces from the left
|
|
"What if I'm right? "
|
|
>>> 'Looks good to me...'.strip('.') # removes '.'
|
|
'Looks good to me'
|
|
>>> '"Now, what is it?"'.strip('"?') # removes '"' and '?' from both ends
|
|
'Now, what is it'
|
|
```
|
|
|
|
Usually, since we don't need the space separator, but still want our command to work if there's no
|
|
separator, we call `lstrip` on the command arguments:
|
|
|
|
```python
|
|
class CmdTest(Command):
|
|
|
|
"""
|
|
Test command.
|
|
|
|
Syntax:
|
|
test [argument]
|
|
|
|
Enter any argument after test.
|
|
|
|
"""
|
|
|
|
key = "test"
|
|
|
|
def parse(self):
|
|
"""Parse arguments, just strip them."""
|
|
self.args = self.args.lstrip()
|
|
|
|
def func(self):
|
|
self.msg(f"You have entered: {self.args!r}.")
|
|
```
|
|
|
|
> We are now beginning to override the command's `parse` method, which is typically useful just for
|
|
argument parsing. This method is executed before `func` and so `self.args` in `func()` will contain
|
|
our `self.args.lstrip()`.
|
|
|
|
Let's try it:
|
|
|
|
```
|
|
> test Whatever
|
|
You have entered: 'Whatever'.
|
|
> test
|
|
You have entered: ''.
|
|
> test And something with '?
|
|
You have entered: "And something with '?".
|
|
> test And something with lots of spaces
|
|
You have entered: 'And something with lots of spaces'.
|
|
```
|
|
|
|
Spaces at the end of the string are kept, but all spaces at the beginning are removed:
|
|
|
|
> `strip`, `lstrip` and `rstrip` without arguments will strip spaces, line breaks and other common
|
|
separators. You can specify one or more characters as a parameter. If you specify more than one
|
|
character, all of them will be stripped from your original string.
|
|
|
|
### Convert arguments to numbers
|
|
|
|
As pointed out, `self.args` is a string (of type `str`). What if we want the user to enter a
|
|
number?
|
|
|
|
Let's take a very simple example: creating a command, `roll`, that allows to roll a six-sided die.
|
|
The player has to guess the number, specifying the number as argument. To win, the player has to
|
|
match the number with the die. Let's see an example:
|
|
|
|
```
|
|
> roll 3
|
|
You roll a die. It lands on the number 4.
|
|
You played 3, you have lost.
|
|
> dice 1
|
|
You roll a die. It lands on the number 2.
|
|
You played 1, you have lost.
|
|
> dice 1
|
|
You roll a die. It lands on the number 1.
|
|
You played 1, you have won!
|
|
```
|
|
|
|
If that's your first command, it's a good opportunity to try to write it. A command with a simple
|
|
and finite role always is a good starting choice. Here's how we could (first) write it... but it
|
|
won't work as is, I warn you:
|
|
|
|
```python
|
|
from random import randint
|
|
|
|
from evennia import Command
|
|
|
|
class CmdRoll(Command):
|
|
|
|
"""
|
|
Play random, enter a number and try your luck.
|
|
|
|
Usage:
|
|
roll <number>
|
|
|
|
Enter a valid number as argument. A random die will be rolled and you
|
|
will win if you have specified the correct number.
|
|
|
|
Example:
|
|
roll 3
|
|
|
|
"""
|
|
|
|
key = "roll"
|
|
|
|
def parse(self):
|
|
"""Convert the argument to a number."""
|
|
self.args = self.args.lstrip()
|
|
|
|
def func(self):
|
|
# Roll a random die
|
|
figure = randint(1, 6) # return a pseudo-random number between 1 and 6, including both
|
|
self.msg(f"You roll a die. It lands on the number {figure}.")
|
|
|
|
if self.args == figure: # THAT WILL BREAK!
|
|
self.msg(f"You played {self.args}, you have won!")
|
|
else:
|
|
self.msg(f"You played {self.args}, you have lost.")
|
|
```
|
|
|
|
If you try this code, Python will complain that you try to compare a number with a string: `figure`
|
|
is a number and `self.args` is a string and can't be compared as-is in Python. Python doesn't do
|
|
"implicit converting" as some languages do. By the way, this might be annoying sometimes, and other
|
|
times you will be glad it tries to encourage you to be explicit rather than implicit about what to
|
|
do. This is an ongoing debate between programmers. Let's move on!
|
|
|
|
So we need to convert the command argument from a `str` into an `int`. There are a few ways to do
|
|
it. But the proper way is to try to convert and deal with the `ValueError` Python exception.
|
|
|
|
Converting a `str` into an `int` in Python is extremely simple: just use the `int` function, give it
|
|
the string and it returns an integer, if it could. If it can't, it will raise `ValueError`. So
|
|
we'll need to catch that. However, we also have to indicate to Evennia that, should the number be
|
|
invalid, no further parsing should be done. Here's a new attempt at our command with this
|
|
converting:
|
|
|
|
```python
|
|
from random import randint
|
|
|
|
from evennia import Command, InterruptCommand
|
|
|
|
class CmdRoll(Command):
|
|
|
|
"""
|
|
Play random, enter a number and try your luck.
|
|
|
|
Usage:
|
|
roll <number>
|
|
|
|
Enter a valid number as argument. A random die will be rolled and you
|
|
will win if you have specified the correct number.
|
|
|
|
Example:
|
|
roll 3
|
|
|
|
"""
|
|
|
|
key = "roll"
|
|
|
|
def parse(self):
|
|
"""Convert the argument to number if possible."""
|
|
args = self.args.lstrip()
|
|
|
|
# Convert to int if possible
|
|
# If not, raise InterruptCommand. Evennia will catch this
|
|
# exception and not call the 'func' method.
|
|
try:
|
|
self.entered = int(args)
|
|
except ValueError:
|
|
self.msg(f"{args} is not a valid number.")
|
|
raise InterruptCommand
|
|
|
|
def func(self):
|
|
# Roll a random die
|
|
figure = randint(1, 6) # return a pseudo-random number between 1 and 6, including both
|
|
self.msg(f"You roll a die. It lands on the number {figure}.")
|
|
|
|
if self.entered == figure:
|
|
self.msg(f"You played {self.entered}, you have won!")
|
|
else:
|
|
self.msg(f"You played {self.entered}, you have lost.")
|
|
```
|
|
|
|
Before enjoying the result, let's examine the `parse` method a little more: what it does is try to
|
|
convert the entered argument from a `str` to an `int`. This might fail (if a user enters `roll
|
|
something`). In such a case, Python raises a `ValueError` exception. We catch it in our
|
|
`try/except` block, send a message to the user and raise the `InterruptCommand` exception in
|
|
response to tell Evennia to not run `func()`, since we have no valid number to give it.
|
|
|
|
In the `func` method, instead of using `self.args`, we use `self.entered` which we have defined in
|
|
our `parse` method. You can expect that, if `func()` is run, then `self.entered` contains a valid
|
|
number.
|
|
|
|
If you try this command, it will work as expected this time: the number is converted as it should
|
|
and compared to the die roll. You might spend some minutes playing this game. Time out!
|
|
|
|
Something else we could want to address: in our small example, we only want the user to enter a
|
|
positive number between 1 and 6. And the user can enter `roll 0` or `roll -8` or `roll 208` for
|
|
that matter, the game still works. It might be worth addressing. Again, you could write a
|
|
condition to do that, but since we're catching an exception, we might end up with something cleaner
|
|
by grouping:
|
|
|
|
```python
|
|
from random import randint
|
|
|
|
from evennia import Command, InterruptCommand
|
|
|
|
class CmdRoll(Command):
|
|
|
|
"""
|
|
Play random, enter a number and try your luck.
|
|
|
|
Usage:
|
|
roll <number>
|
|
|
|
Enter a valid number as argument. A random die will be rolled and you
|
|
will win if you have specified the correct number.
|
|
|
|
Example:
|
|
roll 3
|
|
|
|
"""
|
|
|
|
key = "roll"
|
|
|
|
def parse(self):
|
|
"""Convert the argument to number if possible."""
|
|
args = self.args.lstrip()
|
|
|
|
# Convert to int if possible
|
|
try:
|
|
self.entered = int(args)
|
|
if not 1 <= self.entered <= 6:
|
|
# self.entered is not between 1 and 6 (including both)
|
|
raise ValueError
|
|
except ValueError:
|
|
self.msg(f"{args} is not a valid number.")
|
|
raise InterruptCommand
|
|
|
|
def func(self):
|
|
# Roll a random die
|
|
figure = randint(1, 6) # return a pseudo-random number between 1 and 6, including both
|
|
self.msg(f"You roll a die. It lands on the number {figure}.")
|
|
|
|
if self.entered == figure:
|
|
self.msg(f"You played {self.entered}, you have won!")
|
|
else:
|
|
self.msg(f"You played {self.entered}, you have lost.")
|
|
```
|
|
|
|
Using grouped exceptions like that makes our code easier to read, but if you feel more comfortable
|
|
checking, afterward, that the number the user entered is in the right range, you can do so in a
|
|
latter condition.
|
|
|
|
> Notice that we have updated our `parse` method only in this last attempt, not our `func()` method
|
|
which remains the same. This is one goal of separating argument parsing from command processing,
|
|
these two actions are best kept isolated.
|
|
|
|
### Working with several arguments
|
|
|
|
Often a command expects several arguments. So far, in our example with the "roll" command, we only
|
|
expect one argument: a number and just a number. What if we want the user to specify several
|
|
numbers? First the number of dice to roll, then the guess?
|
|
|
|
> You won't win often if you roll 5 dice but that's for the example.
|
|
|
|
So we would like to interpret a command like this:
|
|
|
|
> roll 3 12
|
|
|
|
(To be understood: roll 3 dice, my guess is the total number will be 12.)
|
|
|
|
What we need is to cut our command argument, which is a `str`, break it at the space (we use the
|
|
space as a delimiter). Python provides the `str.split` method which we'll use. Again, here are
|
|
some examples from the Python interpreter:
|
|
|
|
>>> args = "3 12"
|
|
>>> args.split(" ")
|
|
['3', '12']
|
|
>>> args = "a command with several arguments"
|
|
>>> args.split(" ")
|
|
['a', 'command', 'with', 'several', 'arguments']
|
|
>>>
|
|
|
|
As you can see, `str.split` will "convert" our strings into a list of strings. The specified
|
|
argument (`" "` in our case) is used as delimiter. So Python browses our original string. When it
|
|
sees a delimiter, it takes whatever is before this delimiter and append it to a list.
|
|
|
|
The point here is that `str.split` will be used to split our argument. But, as you can see from the
|
|
above output, we can never be sure of the length of the list at this point:
|
|
|
|
>>> args = "something"
|
|
>>> args.split(" ")
|
|
['something']
|
|
>>> args = ""
|
|
>>> args.split(" ")
|
|
['']
|
|
>>>
|
|
|
|
Again we could use a condition to check the number of split arguments, but Python offers a better
|
|
approach, making use of its exception mechanism. We'll give a second argument to `str.split`, the
|
|
maximum number of splits to do. Let's see an example, this feature might be confusing at first
|
|
glance:
|
|
|
|
>>> args = "that is something great"
|
|
>>> args.split(" ", 1) # one split, that is a list with two elements (before, after)
|
|
['that', 'is something great']
|
|
>>>
|
|
|
|
Read this example as many times as needed to understand it. The second argument we give to
|
|
`str.split` is not the length of the list that should be returned, but the number of times we have
|
|
to split. Therefore, we specify 1 here, but we get a list of two elements (before the separator,
|
|
after the separator).
|
|
|
|
> What will happen if Python can't split the number of times we ask?
|
|
|
|
It won't:
|
|
|
|
>>> args = "whatever"
|
|
>>> args.split(" ", 1) # there isn't even a space here...
|
|
['whatever']
|
|
>>>
|
|
|
|
This is one moment I would have hoped for an exception and didn't get one. But there's another way
|
|
which will raise an exception if there is an error: variable unpacking.
|
|
|
|
We won't talk about this feature in details here. It would be complicated. But the code is really
|
|
straightforward to use. Let's take our example of the roll command but let's add a first argument:
|
|
the number of dice to roll.
|
|
|
|
```python
|
|
from random import randint
|
|
|
|
from evennia import Command, InterruptCommand
|
|
|
|
class CmdRoll(Command):
|
|
|
|
"""
|
|
Play random, enter a number and try your luck.
|
|
|
|
Specify two numbers separated by a space. The first number is the
|
|
number of dice to roll (1, 2, 3) and the second is the expected sum
|
|
of the roll.
|
|
|
|
Usage:
|
|
roll <dice> <number>
|
|
|
|
For instance, to roll two 6-figure dice, enter 2 as first argument.
|
|
If you think the sum of these two dice roll will be 10, you could enter:
|
|
|
|
roll 2 10
|
|
|
|
"""
|
|
|
|
key = "roll"
|
|
|
|
def parse(self):
|
|
"""Split the arguments and convert them."""
|
|
args = self.args.lstrip()
|
|
|
|
# Split: we expect two arguments separated by a space
|
|
try:
|
|
number, guess = args.split(" ", 1)
|
|
except ValueError:
|
|
self.msg("Invalid usage. Enter two numbers separated by a space.")
|
|
raise InterruptCommand
|
|
|
|
# Convert the entered number (first argument)
|
|
try:
|
|
self.number = int(number)
|
|
if self.number <= 0:
|
|
raise ValueError
|
|
except ValueError:
|
|
self.msg(f"{number} is not a valid number of dice.")
|
|
raise InterruptCommand
|
|
|
|
# Convert the entered guess (second argument)
|
|
try:
|
|
self.guess = int(guess)
|
|
if not 1 <= self.guess <= self.number * 6:
|
|
raise ValueError
|
|
except ValueError:
|
|
self.msg(f"{self.guess} is not a valid guess.")
|
|
raise InterruptCommand
|
|
|
|
def func(self):
|
|
# Roll a random die X times (X being self.number)
|
|
figure = 0
|
|
for _ in range(self.number):
|
|
figure += randint(1, 6)
|
|
|
|
self.msg(f"You roll {self.number} dice and obtain the sum {figure}.")
|
|
|
|
if self.guess == figure:
|
|
self.msg(f"You played {self.guess}, you have won!")
|
|
else:
|
|
self.msg(f"You played {self.guess}, you have lost.")
|
|
```
|
|
|
|
The beginning of the `parse()` method is what interests us most:
|
|
|
|
```python
|
|
try:
|
|
number, guess = args.split(" ", 1)
|
|
except ValueError:
|
|
self.msg("Invalid usage. Enter two numbers separated by a space.")
|
|
raise InterruptCommand
|
|
```
|
|
|
|
We split the argument using `str.split` but we capture the result in two variables. Python is smart
|
|
enough to know that we want what's left of the space in the first variable, what's right of the
|
|
space in the second variable. If there is not even a space in the string, Python will raise a
|
|
`ValueError` exception.
|
|
|
|
This code is much easier to read than browsing through the returned strings of `str.split`. We can
|
|
convert both variables the way we did previously. Actually there are not so many changes in this
|
|
version and the previous one, most of it is due to name changes for clarity.
|
|
|
|
> Splitting a string with a maximum of splits is a common occurrence while parsing command
|
|
arguments. You can also see the `str.rspli8t` method that does the same thing but from the right of
|
|
the string. Therefore, it will attempt to find delimiters at the end of the string and work toward
|
|
the beginning of it.
|
|
|
|
We have used a space as a delimiter. This is absolutely not necessary. You might remember that
|
|
most default Evennia commands can take an `=` sign as a delimiter. Now you know how to parse them
|
|
as well:
|
|
|
|
>>> cmd_key = "tel"
|
|
>>> cmd_args = "book = chest"
|
|
>>> left, right = cmd_args.split("=") # mighht raise ValueError!
|
|
>>> left
|
|
'book '
|
|
>>> right
|
|
' chest'
|
|
>>>
|
|
|
|
### Optional arguments
|
|
|
|
Sometimes, you'll come across commands that have optional arguments. These arguments are not
|
|
necessary but they can be set if more information is needed. I will not provide the entire command
|
|
code here but just enough code to show the mechanism in Python:
|
|
|
|
Again, we'll use `str.split`, knowing that we might not have any delimiter at all. For instance,
|
|
the player could enter the "tel" command like this:
|
|
|
|
> tel book
|
|
> tell book = chest
|
|
|
|
The equal sign is optional along with whatever is specified after it. A possible solution in our
|
|
`parse` method would be:
|
|
|
|
```python
|
|
def parse(self):
|
|
args = self.args.lstrip()
|
|
|
|
# = is optional
|
|
try:
|
|
obj, destination = args.split("=", 1)
|
|
except ValueError:
|
|
obj = args
|
|
destination = None
|
|
```
|
|
|
|
This code would place everything the user entered in `obj` if she didn't specify any equal sign.
|
|
Otherwise, what's before the equal sign will go in `obj`, what's after the equal sign will go in
|
|
`destination`. This makes for quick testing after that, more robust code with less conditions that
|
|
might too easily break your code if you're not careful.
|
|
|
|
> Again, here we specified a maximum numbers of splits. If the users enters:
|
|
|
|
> tel book = chest = chair
|
|
|
|
Then `destination` will contain: `" chest = chair"`. This is often desired, but it's up to you to
|
|
set parsing however you like.
|
|
|
|
## Evennia searches
|
|
|
|
After this quick tour of some `str` methods, we'll take a look at some Evennia-specific features
|
|
that you won't find in standard Python.
|
|
|
|
One very common task is to convert a `str` into an Evennia object. Take the previous example:
|
|
having `"book"` in a variable is great, but we would prefer to know what the user is talking
|
|
about... what is this `"book"`?
|
|
|
|
To get an object from a string, we perform an Evennia search. Evennia provides a `search` method on
|
|
all typeclassed objects (you will most likely use the one on characters or accounts). This method
|
|
supports a very wide array of arguments and has [its own tutorial](Beginner-Tutorial/Part1/Beginner-Tutorial-Searching-Things.md).
|
|
Some examples of useful cases follow:
|
|
|
|
### Local searches
|
|
|
|
When an account or a character enters a command, the account or character is found in the `caller`
|
|
attribute. Therefore, `self.caller` will contain an account or a character (or a session if that's
|
|
a session command, though that's not as frequent). The `search` method will be available on this
|
|
caller.
|
|
|
|
Let's take the same example of our little "tel" command. The user can specify an object as
|
|
argument:
|
|
|
|
```python
|
|
def parse(self):
|
|
name = self.args.lstrip()
|
|
```
|
|
|
|
We then need to "convert" this string into an Evennia object. The Evennia object will be searched
|
|
in the caller's location and its contents by default (that is to say, if the command has been
|
|
entered by a character, it will search the object in the character's room and the character's
|
|
inventory).
|
|
|
|
```python
|
|
def parse(self):
|
|
name = self.args.lstrip()
|
|
|
|
self.obj = self.caller.search(name)
|
|
```
|
|
|
|
We specify only one argument to the `search` method here: the string to search. If Evennia finds a
|
|
match, it will return it and we keep it in the `obj` attribute. If it can't find anything, it will
|
|
return `None` so we need to check for that:
|
|
|
|
```python
|
|
def parse(self):
|
|
name = self.args.lstrip()
|
|
|
|
self.obj = self.caller.search(name)
|
|
if self.obj is None:
|
|
# A proper error message has already been sent to the caller
|
|
raise InterruptCommand
|
|
```
|
|
|
|
That's it. After this condition, you know that whatever is in `self.obj` is a valid Evennia object
|
|
(another character, an object, an exit...).
|
|
|
|
### Quiet searches
|
|
|
|
By default, Evennia will handle the case when more than one match is found in the search. The user
|
|
will be asked to narrow down and re-enter the command. You can, however, ask to be returned the
|
|
list of matches and handle this list yourself:
|
|
|
|
```python
|
|
def parse(self):
|
|
name = self.args.lstrip()
|
|
|
|
objs = self.caller.search(name, quiet=True)
|
|
if not objs:
|
|
# This is an empty list, so no match
|
|
self.msg(f"No {name!r} was found.")
|
|
raise InterruptCommand
|
|
|
|
self.obj = objs[0] # Take the first match even if there are several
|
|
```
|
|
|
|
All we have changed to obtain a list is a keyword argument in the `search` method: `quiet`. If set
|
|
to `True`, then errors are ignored and a list is always returned, so we need to handle it as such.
|
|
Notice in this example, `self.obj` will contain a valid object too, but if several matches are
|
|
found, `self.obj` will contain the first one, even if more matches are available.
|
|
|
|
### Global searches
|
|
|
|
By default, Evennia will perform a local search, that is, a search limited by the location in which
|
|
the caller is. If you want to perform a global search (search in the entire database), just set the
|
|
`global_search` keyword argument to `True`:
|
|
|
|
```python
|
|
def parse(self):
|
|
name = self.args.lstrip()
|
|
self.obj = self.caller.search(name, global_search=True)
|
|
```
|
|
|
|
## Conclusion
|
|
|
|
Parsing command arguments is vital for most game designers. If you design "intelligent" commands,
|
|
users should be able to guess how to use them without reading the help, or with a very quick peek at
|
|
said help. Good commands are intuitive to users. Better commands do what they're told to do. For
|
|
game designers working on MUDs, commands are the main entry point for users into your game. This is
|
|
no trivial. If commands execute correctly (if their argument is parsed, if they don't behave in
|
|
unexpected ways and report back the right errors), you will have happier players that might stay
|
|
longer on your game. I hope this tutorial gave you some pointers on ways to improve your command
|
|
parsing. There are, of course, other ways you will discover, or ways you are already using in your
|
|
code. |