diff --git a/evennia/settings_default.py b/evennia/settings_default.py index b41df49aa..ac00b18b2 100644 --- a/evennia/settings_default.py +++ b/evennia/settings_default.py @@ -375,29 +375,41 @@ DUMMYRUNNER_SETTINGS_MODULE = "evennia.server.profiling.dummyrunner_settings" # tuples mapping the exact tag (not a regex!) to the ANSI convertion, like # `(r"%c%r", ansi.ANSI_RED)` (the evennia.utils.ansi module contains all # ANSI escape sequences). Default is to use `|` and `|[` -prefixes. +# Note that to apply all color changes, a full `evennia reboot` is needed! COLOR_ANSI_EXTRA_MAP = [] # Extend the available regexes for adding XTERM256 colors in-game. This is given # as a list of regexes, where each regex must contain three anonymous groups for # holding integers 0-5 for the red, green and blue components Default is # is r'\|([0-5])([0-5])([0-5])', which allows e.g. |500 for red. +# Note that to apply all color changes, a full `evennia reboot` is needed! +COLOR_ANSI_EXTRA_MAP = [] # XTERM256 foreground color replacement +# Note that to apply all color changes, a full `evennia reboot` is needed! COLOR_XTERM256_EXTRA_FG = [] # XTERM256 background color replacement. Default is \|\[([0-5])([0-5])([0-5])' +# Note that to apply all color changes, a full `evennia reboot` is needed! COLOR_XTERM256_EXTRA_BG = [] # Extend the available regexes for adding XTERM256 grayscale values in-game. Given # as a list of regexes, where each regex must contain one anonymous group containing # a single letter a-z to mark the level from white to black. Default is r'\|=([a-z])', # which allows e.g. |=k for a medium gray. # XTERM256 grayscale foreground +# Note that to apply all color changes, a full `evennia reboot` is needed! COLOR_XTERM256_EXTRA_GFG = [] # XTERM256 grayscale background. Default is \|\[=([a-z])' +# Note that to apply all color changes, a full `evennia reboot` is needed! COLOR_XTERM256_EXTRA_GBG = [] # ANSI does not support bright backgrounds, so Evennia fakes this by mapping it to # XTERM256 backgrounds where supported. This is a list of tuples that maps the wanted -# ansi tag (not a regex!) to a valid XTERM256 background tag, such as `(r'{[r', r'{[500')`. +# ansi tag (not a regex!) to a valid XTERM256 tag, such as `(r'|o', r'|531')` +# for orange. By default this is only used for bright backgrounds but +# both bright and dark colors can be mapped this way, and is a way to add +# new shortcuts to xterm colors without having to write the RGB value. +# Note that to apply all color changes, a full `evennia reboot` is needed! COLOR_ANSI_XTERM256_BRIGHT_BG_EXTRA_MAP = [] # If set True, the above color settings *replace* the default |-style color markdown # rather than extend it. +# Note that to apply all color changes, a full `evennia reboot` is needed! COLOR_NO_DEFAULT = False diff --git a/evennia/typeclasses/models.py b/evennia/typeclasses/models.py index be517da73..0944c2d80 100644 --- a/evennia/typeclasses/models.py +++ b/evennia/typeclasses/models.py @@ -24,6 +24,8 @@ layer. This module also contains the Managers for the respective models; inherit from these to create custom managers. +---- + """ from django.db.models import signals @@ -161,7 +163,10 @@ class TypeclassBase(SharedMemoryModelBase): class DbHolder(object): - "Holder for allowing property access of attributes" + """ + Holder for allowing property access of attributes. + + """ def __init__(self, obj, name, manager_name="attributes"): _SA(self, name, _GA(obj, manager_name)) @@ -309,10 +314,8 @@ class TypedObject(SharedMemoryModel): than use the one in the model. Args: - Passed through to parent. - - Keyword Args: - Passed through to parent. + *args: Passed through to parent. + **kwargs: Passed through to parent. Notes: The loading mechanism will attempt the following steps: @@ -725,14 +728,19 @@ class TypedObject(SharedMemoryModel): def __db_get(self): """ Attribute handler wrapper. Allows for the syntax + :: + obj.db.attrname = value and value = obj.db.attrname and del obj.db.attrname and - all_attr = obj.db.all() (unless there is an attribute - named 'all', in which case that will be returned instead). + all_attr = obj.db.all() + + (unless there is an attribute named 'all', in which case that will be + returned instead). + """ try: return self._db_holder @@ -742,14 +750,14 @@ class TypedObject(SharedMemoryModel): # @db.setter def __db_set(self, value): - "Stop accidentally replacing the db object" + """Stop accidentally replacing the db object""" string = "Cannot assign directly to db object! " string += "Use db.attr=value instead." raise Exception(string) # @db.deleter def __db_del(self): - "Stop accidental deletion." + """Stop accidental deletion.""" raise Exception("Cannot delete the db object!") db = property(__db_get, __db_set, __db_del) @@ -761,10 +769,23 @@ class TypedObject(SharedMemoryModel): # @property ndb def __ndb_get(self): """ - A non-attr_obj store (ndb: NonDataBase). Everything stored - to this is guaranteed to be cleared when a server is shutdown. - Syntax is same as for the _get_db_holder() method and - property, e.g. obj.ndb.attr = value etc. + A non-attr_obj store (NonDataBase). Everything stored to this is + guaranteed to be cleared when a server is shutdown. Syntax is same as + for the `.db` property, e.g. + :: + + obj.ndb.attrname = value + and + value = obj.ndb.attrname + and + del obj.ndb.attrname + and + all_attr = obj.ndb.all() + + What makes this preferable over just assigning properties directly on + the object is that Evennia can track caching for these properties and + for example avoid wiping objects with set `.ndb` data on cache flushes. + """ try: return self._ndb_holder @@ -869,28 +890,33 @@ class TypedObject(SharedMemoryModel): @classmethod def web_get_create_url(cls): """ + Returns the URI path for a View that allows users to create new instances of this object. - ex. Chargen = '/characters/create/' - - For this to work, the developer must have defined a named view somewhere - in urls.py that follows the format 'modelname-action', so in this case - a named view of 'character-create' would be referenced by this method. - - ex. - url(r'characters/create/', ChargenView.as_view(), name='character-create') - - If no View has been created and defined in urls.py, returns an - HTML anchor. - - This method is naive and simply returns a path. Securing access to - the actual view and limiting who can create new objects is the - developer's responsibility. - Returns: path (str): URI path to object creation page, if defined. + Examples: + :: + + Chargen = '/characters/create/' + + For this to work, the developer must have defined a named view somewhere + in urls.py that follows the format 'modelname-action', so in this case + a named view of 'character-create' would be referenced by this method. + :: + + url(r'characters/create/', ChargenView.as_view(), name='character-create') + + If no View has been created and defined in urls.py, returns an + HTML anchor. + + Notes: + This method is naive and simply returns a path. Securing access to + the actual view and limiting who can create new objects is the + developer's responsibility. + """ try: return reverse("%s-create" % slugify(cls._meta.verbose_name)) @@ -902,26 +928,29 @@ class TypedObject(SharedMemoryModel): Returns the URI path for a View that allows users to view details for this object. - ex. Oscar (Character) = '/characters/oscar/1/' - - For this to work, the developer must have defined a named view somewhere - in urls.py that follows the format 'modelname-action', so in this case - a named view of 'character-detail' would be referenced by this method. - - ex. - url(r'characters/(?P[\w\d\-]+)/(?P[0-9]+)/$', - CharDetailView.as_view(), name='character-detail') - - If no View has been created and defined in urls.py, returns an - HTML anchor. - - This method is naive and simply returns a path. Securing access to - the actual view and limiting who can view this object is the developer's - responsibility. - Returns: path (str): URI path to object detail page, if defined. + Examples: + :: + + Oscar (Character) = '/characters/oscar/1/' + + For this to work, the developer must have defined a named view somewhere + in urls.py that follows the format 'modelname-action', so in this case + a named view of 'character-detail' would be referenced by this method. + :: + + CharDetailView.as_view(), name='character-detail') + + If no View has been created and defined in urls.py, returns an + HTML anchor. + + Notes: + This method is naive and simply returns a path. Securing access to + the actual view and limiting who can view this object is the + developer's responsibility. + """ try: return reverse( @@ -936,26 +965,31 @@ class TypedObject(SharedMemoryModel): Returns the URI path for a View that allows users to puppet a specific object. - ex. Oscar (Character) = '/characters/oscar/1/puppet/' - - For this to work, the developer must have defined a named view somewhere - in urls.py that follows the format 'modelname-action', so in this case - a named view of 'character-puppet' would be referenced by this method. - - ex. - url(r'characters/(?P[\w\d\-]+)/(?P[0-9]+)/puppet/$', - CharPuppetView.as_view(), name='character-puppet') - - If no View has been created and defined in urls.py, returns an - HTML anchor. - - This method is naive and simply returns a path. Securing access to - the actual view and limiting who can view this object is the developer's - responsibility. - Returns: path (str): URI path to object puppet page, if defined. + Examples: + :: + + Oscar (Character) = '/characters/oscar/1/puppet/' + + For this to work, the developer must have defined a named view somewhere + in urls.py that follows the format 'modelname-action', so in this case + a named view of 'character-puppet' would be referenced by this method. + :: + + url(r'characters/(?P[\w\d\-]+)/(?P[0-9]+)/puppet/$', + CharPuppetView.as_view(), name='character-puppet') + + If no View has been created and defined in urls.py, returns an + HTML anchor. + + Notes: + This method is naive and simply returns a path. Securing access to + the actual view and limiting who can view this object is the developer's + responsibility. + + """ try: return reverse( @@ -970,26 +1004,30 @@ class TypedObject(SharedMemoryModel): Returns the URI path for a View that allows users to update this object. - ex. Oscar (Character) = '/characters/oscar/1/change/' - - For this to work, the developer must have defined a named view somewhere - in urls.py that follows the format 'modelname-action', so in this case - a named view of 'character-update' would be referenced by this method. - - ex. - url(r'characters/(?P[\w\d\-]+)/(?P[0-9]+)/change/$', - CharUpdateView.as_view(), name='character-update') - - If no View has been created and defined in urls.py, returns an - HTML anchor. - - This method is naive and simply returns a path. Securing access to - the actual view and limiting who can modify objects is the developer's - responsibility. - Returns: path (str): URI path to object update page, if defined. + Examples: + :: + + Oscar (Character) = '/characters/oscar/1/change/' + + For this to work, the developer must have defined a named view somewhere + in urls.py that follows the format 'modelname-action', so in this case + a named view of 'character-update' would be referenced by this method. + :: + + url(r'characters/(?P[\w\d\-]+)/(?P[0-9]+)/change/$', + CharUpdateView.as_view(), name='character-update') + + If no View has been created and defined in urls.py, returns an + HTML anchor. + + Notes: + This method is naive and simply returns a path. Securing access to + the actual view and limiting who can modify objects is the developer's + responsibility. + """ try: return reverse( @@ -1003,26 +1041,31 @@ class TypedObject(SharedMemoryModel): """ Returns the URI path for a View that allows users to delete this object. - ex. Oscar (Character) = '/characters/oscar/1/delete/' - - For this to work, the developer must have defined a named view somewhere - in urls.py that follows the format 'modelname-action', so in this case - a named view of 'character-detail' would be referenced by this method. - - ex. - url(r'characters/(?P[\w\d\-]+)/(?P[0-9]+)/delete/$', - CharDeleteView.as_view(), name='character-delete') - - If no View has been created and defined in urls.py, returns an - HTML anchor. - - This method is naive and simply returns a path. Securing access to - the actual view and limiting who can delete this object is the developer's - responsibility. - Returns: path (str): URI path to object deletion page, if defined. + Examples: + :: + + Oscar (Character) = '/characters/oscar/1/delete/' + + For this to work, the developer must have defined a named view somewhere + in urls.py that follows the format 'modelname-action', so in this case + a named view of 'character-detail' would be referenced by this method. + :: + + url(r'characters/(?P[\w\d\-]+)/(?P[0-9]+)/delete/$', + CharDeleteView.as_view(), name='character-delete') + + If no View has been created and defined in urls.py, returns an + HTML anchor. + + Notes: + This method is naive and simply returns a path. Securing access to + the actual view and limiting who can delete this object is the developer's + responsibility. + + """ try: return reverse( diff --git a/evennia/utils/ansi.py b/evennia/utils/ansi.py index 7129cc862..0b63e5051 100644 --- a/evennia/utils/ansi.py +++ b/evennia/utils/ansi.py @@ -1,16 +1,20 @@ """ ANSI - Gives colour to text. -Use the codes defined in ANSIPARSER in your text -to apply colour to text according to the ANSI standard. +Use the codes defined in ANSIPARSER in your text to apply colour to text +according to the ANSI standard. +:: -Examples: This is |rRed text|n and this is normal again. -Mostly you should not need to call parse_ansi() explicitly; -it is run by Evennia just before returning data to/from the -user. Depreciated example forms are available by extending -the ansi mapping. +Mostly you should not need to call `parse_ansi()` explicitly; it is run by +Evennia just before returning data to/from the user. Depreciated/decativated +example forms are available in contribs by extending the ansi mapping + +This module also contains the `ANSIString` custom string-type, which correctly +wraps/manipulates and tracks lengths of strings containing ANSI-markup. + +---- """ import functools @@ -78,16 +82,14 @@ _COLOR_NO_DEFAULT = settings.COLOR_NO_DEFAULT class ANSIParser(object): """ - A class that parses ANSI markup - to ANSI command sequences + A class that parses ANSI markup to ANSI command sequences - We also allow to escape colour codes - by prepending with a \ for xterm256, - an extra | for Merc-style codes + We also allow to escape colour codes by prepending with + an extra `|`, so `||r` will literally print `|r`. """ - # Mapping using {r {n etc + # Mapping using |r, |n etc ansi_map = [ # alternative |-format @@ -586,10 +588,9 @@ def _on_raw(func_name): def _transform(func_name): """ - Some string functions, like those manipulating capital letters, - return a string the same length as the original. This function - allows us to do the same, replacing all the non-coded characters - with the resulting string. + Some string functions, like those manipulating capital letters, return a + string the same length as the original. This function allows us to do the + same, replacing all the non-coded characters with the resulting string. """ @@ -982,13 +983,14 @@ class ANSIString(str, metaclass=ANSIMeta): sep (str): The separator to split the string on. reverse (boolean): Whether to split the string on the last occurrence of the separator rather than the first. + Returns: result (tuple): - prefix (ANSIString): The part of the string before the - separator - sep (ANSIString): The separator itself - postfix (ANSIString): The part of the string after the - separator. + - prefix (ANSIString): The part of the string before the + separator + - sep (ANSIString): The separator itself + - postfix (ANSIString): The part of the string after the + separator. """ if hasattr(sep, "_clean_string"): @@ -1287,19 +1289,26 @@ class ANSIString(str, metaclass=ANSIMeta): Joins together strings in an iterable, using this string between each one. - NOTE: This should always be used for joining strings when ANSIStrings - are involved. Otherwise color information will be discarded by - python, due to details in the C implementation of strings. - Args: iterable (list of strings): A list of strings to join together + Returns: result (ANSIString): A single string with all of the iterable's - contents concatenated, with this string between each. For - example: - ANSIString(', ').join(['up', 'right', 'left', 'down']) - ...Would return: - ANSIString('up, right, left, down') + contents concatenated, with this string between each. + + Examples: + :: + ANSIString(', ').join(['up', 'right', 'left', 'down']) + + Would return + :: + + ANSIString('up, right, left, down') + + Notes: + This should always be used for joining strings when ANSIStrings are + involved. Otherwise color information will be discarded by python, + due to details in the C implementation of strings. """ result = ANSIString("") diff --git a/evennia/utils/batchprocessors.py b/evennia/utils/batchprocessors.py index abceb9cd4..18a8e1a93 100644 --- a/evennia/utils/batchprocessors.py +++ b/evennia/utils/batchprocessors.py @@ -31,7 +31,8 @@ etc. You also need to know Python and Evennia's API. Hence it's recommended that the batch-code processor is limited only to superusers or highly trusted staff. -Batch-command processor file syntax +Batch-Command processor file syntax +----------------------------------- The batch-command processor accepts 'batchcommand files' e.g `batch.ev`, containing a sequence of valid Evennia commands in a @@ -39,66 +40,61 @@ simple format. The engine runs each command in sequence, as if they had been run at the game prompt. Each Evennia command must be delimited by a line comment to mark its -end. - -``` -#INSERT path.batchcmdfile - this as the first entry on a line will - import and run a batch.ev file in this position, as if it was - written in this file. -``` - -This way entire game worlds can be created and planned offline; it is +end. This way entire game worlds can be created and planned offline; it is especially useful in order to create long room descriptions where a real offline text editor is often much better than any online text editor or prompt. +There is only one batchcommand-specific entry to use in a batch-command +files (all others are just like in-game commands): + +- `#INSERT path.batchcmdfile` - this as the first entry on a line will + import and run a batch.ev file in this position, as if it was + written in this file. + + Example of batch.ev file: ----------------------------- +:: -``` -# batch file -# all lines starting with # are comments; they also indicate -# that a command definition is over. + # batch file + # all lines starting with # are comments; they also indicate + # that a command definition is over. -@create box + @create box -# this comment ends the @create command. + # this comment ends the @create command. -@set box/desc = A large box. + @set box/desc = A large box. -Inside are some scattered piles of clothing. + Inside are some scattered piles of clothing. -It seems the bottom of the box is a bit loose. + It seems the bottom of the box is a bit loose. -# Again, this comment indicates the @set command is over. Note how -# the description could be freely added. Excess whitespace on a line -# is ignored. An empty line in the command definition is parsed as a \n -# (so two empty lines becomes a new paragraph). + # Again, this comment indicates the @set command is over. Note how + # the description could be freely added. Excess whitespace on a line + # is ignored. An empty line in the command definition is parsed as a \n + # (so two empty lines becomes a new paragraph). -@teleport #221 + @teleport #221 -# (Assuming #221 is a warehouse or something.) -# (remember, this comment ends the @teleport command! Don'f forget it) + # (Assuming #221 is a warehouse or something.) + # (remember, this comment ends the @teleport command! Don'f forget it) -# Example of importing another file at this point. -#IMPORT examples.batch + # Example of importing another file at this point. + #INSERT examples.batch -@drop box + @drop box -# Done, the box is in the warehouse! (this last comment is not necessary to -# close the @drop command since it's the end of the file) -``` + # Done, the box is in the warehouse! (this last comment is not necessary to + # close the @drop command since it's the end of the file) -------------------------- An example batch file is `contrib/examples/batch_example.ev`. -========================================================================== - - -Batch-code processor file syntax +Batch-Code processor file syntax +-------------------------------- The Batch-code processor accepts full python modules (e.g. `batch.py`) that looks identical to normal Python files. The difference from @@ -113,62 +109,62 @@ the code and re-run sections of it easily during development. Code blocks are marked by commented tokens alone on a line: -#HEADER - This denotes code that should be pasted at the top of all - other code. Multiple HEADER statements - regardless of where - it exists in the file - is the same as one big block. - Observe that changes to variables made in one block is not - preserved between blocks! -#CODE - This designates a code block that will be executed like a - stand-alone piece of code together with any HEADER(s) - defined. It is mainly used as a way to mark stop points for - the interactive mode of the batchprocessor. If no CODE block - is defined in the module, the entire module (including HEADERS) - is assumed to be a CODE block. -#INSERT path.filename - This imports another batch_code.py file and - runs it in the given position. The inserted file will retain - its own HEADERs which will not be mixed with the headers of - this file. +- `#HEADER` - This denotes code that should be pasted at the top of all + other code. Multiple HEADER statements - regardless of where + it exists in the file - is the same as one big block. + Observe that changes to variables made in one block is not + preserved between blocks! +- `#CODE` - This designates a code block that will be executed like a + stand-alone piece of code together with any HEADER(s) + defined. It is mainly used as a way to mark stop points for + the interactive mode of the batchprocessor. If no CODE block + is defined in the module, the entire module (including HEADERS) + is assumed to be a CODE block. +- `#INSERT path.filename` - This imports another batch_code.py file and + runs it in the given position. The inserted file will retain + its own HEADERs which will not be mixed with the headers of + this file. Importing works as normal. The following variables are automatically made available in the script namespace. - `caller` - The object executing the batchscript - `DEBUG` - This is a boolean marking if the batchprocessor is running - in debug mode. It can be checked to e.g. delete created objects - when running a CODE block multiple times during testing. - (avoids creating a slew of same-named db objects) + in debug mode. It can be checked to e.g. delete created objects + when running a CODE block multiple times during testing. + (avoids creating a slew of same-named db objects) +Example batch.py file: +:: -Example batch.py file ------------------------------------ + #HEADER -``` -#HEADER + from django.conf import settings + from evennia.utils import create + from types import basetypes -from django.conf import settings -from evennia.utils import create -from types import basetypes + GOLD = 10 -GOLD = 10 + #CODE -#CODE + obj = create.create_object(basetypes.Object) + obj2 = create.create_object(basetypes.Object) + obj.location = caller.location + obj.db.gold = GOLD + caller.msg("The object was created!") -obj = create.create_object(basetypes.Object) -obj2 = create.create_object(basetypes.Object) -obj.location = caller.location -obj.db.gold = GOLD -caller.msg("The object was created!") + if DEBUG: + obj.delete() + obj2.delete() -if DEBUG: - obj.delete() - obj2.delete() + #INSERT another_batch_file -#INSERT another_batch_file + #CODE -#CODE + script = create.create_script() + +---- -script = create.create_script() -``` """ import re import codecs @@ -206,7 +202,7 @@ def read_batchfile(pythonpath, file_ending=".py"): file_ending (str): The file ending of this file (.ev or .py) Returns: - text (str): The text content of the batch file. + str: The text content of the batch file. Raises: IOError: If problems reading file. @@ -255,19 +251,20 @@ class BatchCommandProcessor(object): """ This parses the lines of a batchfile according to the following rules: - 1) # at the beginning of a line marks the end of the command before - it. It is also a comment and any number of # can exist on - subsequent lines (but not inside comments). - 2) #INSERT at the beginning of a line imports another - batch-cmd file file and pastes it into the batch file as if - it was written there. - 3) Commands are placed alone at the beginning of a line and their - arguments are considered to be everything following (on any - number of lines) until the next comment line beginning with #. - 4) Newlines are ignored in command definitions - 5) A completely empty line in a command line definition is condered - a newline (so two empty lines is a paragraph). - 6) Excess spaces and indents inside arguments are stripped. + + 1. `#` at the beginning of a line marks the end of the command before + it. It is also a comment and any number of # can exist on + subsequent lines (but not inside comments). + 2. `#INSERT` at the beginning of a line imports another + batch-cmd file file and pastes it into the batch file as if + it was written there. + 3. Commands are placed alone at the beginning of a line and their + arguments are considered to be everything following (on any + number of lines) until the next comment line beginning with #. + 4. Newlines are ignored in command definitions + 5. A completely empty line in a command line definition is condered + a newline (so two empty lines is a paragraph). + 6. Excess spaces and indents inside arguments are stripped. """ diff --git a/evennia/utils/create.py b/evennia/utils/create.py index db1ac2390..9adce4528 100644 --- a/evennia/utils/create.py +++ b/evennia/utils/create.py @@ -96,7 +96,7 @@ def create_object( location itself or during unittests. attributes (list): Tuples on the form (key, value) or (key, value, category), (key, value, lockstring) or (key, value, lockstring, default_access). - to set as Attributes on the new object. + to set as Attributes on the new object. nattributes (list): Non-persistent tuples on the form (key, value). Note that adding this rarely makes sense since this data will not survive a reload. diff --git a/evennia/utils/dbserialize.py b/evennia/utils/dbserialize.py index 44f6dc409..ad41937ff 100644 --- a/evennia/utils/dbserialize.py +++ b/evennia/utils/dbserialize.py @@ -604,7 +604,7 @@ def from_pickle(data, db_obj=None): object was removed (or changed in-place) in the database, None will be returned. - Args_ + Args: data (any): Pickled data to unpickle. db_obj (Atribute, any): This is the model instance (normally an Attribute) that _Saver*-type iterables (_SaverList etc) @@ -612,7 +612,7 @@ def from_pickle(data, db_obj=None): that saves assigned data to the database. Skip if not serializing onto a given object. If db_obj is given, this function will convert lists, dicts and sets to their - _SaverList, _SaverDict and _SaverSet counterparts. + `_SaverList`, `_SaverDict` and `_SaverSet` counterparts. Returns: data (any): Unpickled data. diff --git a/evennia/utils/eveditor.py b/evennia/utils/eveditor.py index 3268c553f..8f0323fdd 100644 --- a/evennia/utils/eveditor.py +++ b/evennia/utils/eveditor.py @@ -15,32 +15,34 @@ Features of the editor: To use the editor, just import EvEditor from this module and initialize it: +:: from evennia.utils.eveditor import EvEditor - EvEditor(caller, loadfunc=None, savefunc=None, quitfunc=None, key="", persistent=True) - - caller is the user of the editor, the one to see all feedback. - - loadfunc(caller) is called when the editor is first launched; the - return from this function is loaded as the starting buffer in the - editor. - - safefunc(caller, buffer) is called with the current buffer when - saving in the editor. The function should return True/False depending - on if the saving was successful or not. - - quitfunc(caller) is called when the editor exits. If this is given, - no automatic quit messages will be given. - - key is an optional identifier for the editing session, to be - displayed in the editor. - - persistent means the editor state will be saved to the database making it - survive a server reload. Note that using this mode, the load- save- - and quit-funcs must all be possible to pickle - notable unusable - callables are class methods and functions defined inside other - functions. With persistent=False, no such restriction exists. - - code set to True activates features on the EvEditor to enter Python code. +- `caller` is the user of the editor, the one to see all feedback. +- `loadfunc(caller)` is called when the editor is first launched; the + return from this function is loaded as the starting buffer in the + editor. +- `safefunc(caller, buffer)` is called with the current buffer when + saving in the editor. The function should return True/False depending + on if the saving was successful or not. +- `quitfunc(caller)` is called when the editor exits. If this is given, + no automatic quit messages will be given. +- `key` is an optional identifier for the editing session, to be + displayed in the editor. +- `persistent` means the editor state will be saved to the database making it + survive a server reload. Note that using this mode, the load- save- + and quit-funcs must all be possible to pickle - notable unusable + callables are class methods and functions defined inside other + functions. With persistent=False, no such restriction exists. +- `code` set to True activates features on the EvEditor to enter Python code. In addition, the EvEditor can be used to enter Python source code, and offers basic handling of indentation. +---- + """ import re @@ -229,14 +231,16 @@ class CmdEditorBase(Command): """ Handles pre-parsing - Editor commands are on the form + Usage: :cmd [li] [w] [txt] Where all arguments are optional. - li - line number (int), starting from 1. This could also - be a range given as :. - w - word(s) (string), could be encased in quotes. - txt - extra text (string), could be encased in quotes. + + - li - line number (int), starting from 1. This could also + be a range given as :. + - w - word(s) (string), could be encased in quotes. + - txt - extra text (string), could be encased in quotes. + """ editor = self.caller.ndb._eveditor diff --git a/evennia/utils/evform.py b/evennia/utils/evform.py index dcd936969..90318a366 100644 --- a/evennia/utils/evform.py +++ b/evennia/utils/evform.py @@ -13,34 +13,32 @@ absolute size of the field and will be filled with an `evtable.EvCell` object when displaying the form. Example of input file `testform.py`: +:: -```python -FORMCHAR = "x" -TABLECHAR = "c" + FORMCHAR = "x" + TABLECHAR = "c" -FORM = ''' -.------------------------------------------------. -| | -| Name: xxxxx1xxxxx Player: xxxxxxx2xxxxxxx | -| xxxxxxxxxxx | -| | - >----------------------------------------------< -| | -| Desc: xxxxxxxxxxx STR: x4x DEX: x5x | -| xxxxx3xxxxx INT: x6x STA: x7x | -| xxxxxxxxxxx LUC: x8x MAG: x9x | -| | - >----------------------------------------------< -| | | -| cccccccc | ccccccccccccccccccccccccccccccccccc | -| cccccccc | ccccccccccccccccccccccccccccccccccc | -| cccAcccc | ccccccccccccccccccccccccccccccccccc | -| cccccccc | ccccccccccccccccccccccccccccccccccc | -| cccccccc | cccccccccccccccccBccccccccccccccccc | -| | | -------------------------------------------------- -''' -``` + FORM = ''' + .------------------------------------------------. + | | + | Name: xxxxx1xxxxx Player: xxxxxxx2xxxxxxx | + | xxxxxxxxxxx | + | | + >----------------------------------------------< + | | + | Desc: xxxxxxxxxxx STR: x4x DEX: x5x | + | xxxxx3xxxxx INT: x6x STA: x7x | + | xxxxxxxxxxx LUC: x8x MAG: x9x | + | | + >----------------------------------------------< + | | | + | cccccccc | ccccccccccccccccccccccccccccccccccc | + | cccccccc | ccccccccccccccccccccccccccccccccccc | + | cccAcccc | ccccccccccccccccccccccccccccccccccc | + | cccccccc | ccccccccccccccccccccccccccccccccccc | + | cccccccc | cccccccccccccccccBccccccccccccccccc | + | | | + ------------------------------------------------- The first line of the `FORM` string is ignored. The forms and table markers must mark out complete, unbroken rectangles, each containing @@ -54,8 +52,8 @@ character's width. Use as follows: +:: -```python from evennia import EvForm, EvTable # create a new form from the template @@ -87,32 +85,32 @@ Use as follows: "B": tableB}) print(form) -``` + This produces the following result: +:: + + .------------------------------------------------. + | | + | Name: Tom the Player: Griatch | + | Bouncer | + | | + >----------------------------------------------< + | | + | Desc: A sturdy STR: 12 DEX: 10 | + | fellow INT: 5 STA: 18 | + | LUC: 10 MAG: 3 | + | | + >----------------------------------------------< + | | | + | HP|MV|MP | Skill |Value |Exp | + | ~~+~~+~~ | ~~~~~~~~~~~+~~~~~~~~~~~+~~~~~~~~~~~ | + | **|**|** | Shooting |12 |550/1200 | + | |**|* | Herbalism |14 |990/1400 | + | |* | | Smithing |9 |205/900 | + | | | + ------------------------------------------------ -``` -.------------------------------------------------. -| | -| Name: Tom the Player: Griatch | -| Bouncer | -| | - >----------------------------------------------< -| | -| Desc: A sturdy STR: 12 DEX: 10 | -| fellow INT: 5 STA: 18 | -| LUC: 10 MAG: 3 | -| | - >----------------------------------------------< -| | | -| HP|MV|MP | Skill |Value |Exp | -| ~~+~~+~~ | ~~~~~~~~~~~+~~~~~~~~~~~+~~~~~~~~~~~ | -| **|**|** | Shooting |12 |550/1200 | -| |**|* | Herbalism |14 |990/1400 | -| |* | | Smithing |9 |205/900 | -| | | - ------------------------------------------------ -``` The marked forms have been replaced with EvCells of text and with EvTables. The form can be updated by simply re-applying `form.map()` @@ -127,6 +125,8 @@ small for it). If you try to fit a table into an area it cannot fit into (when including its borders and at least one line of text), the form will raise an error. +---- + """ import re @@ -186,16 +186,15 @@ class EvForm(object): def __init__(self, filename=None, cells=None, tables=None, form=None, **kwargs): """ - Initiate the form + Initiate the form. Keyword Args: filename (str): Path to template file. - cells (dict): A dictionary mapping of {id:text} - tables (dict): A dictionary mapping of {id:EvTable}. - form (dict): A dictionary of {"FORMCHAR":char, - "TABLECHAR":char, - "FORM":templatestring} - if this is given, filename is not read. + cells (dict): A dictionary mapping of `{id:text}`. + tables (dict): A dictionary mapping of `{id:EvTable}`. + form (dict): A dictionary of + `{"FORMCHAR":char, "TABLECHAR":char, "FORM":templatestring}`. + if this is given, filename is not read. Notes: Other kwargs are fed as options to the EvCells and EvTables (see `evtable.EvCell` and `evtable.EvTable` for more info). diff --git a/evennia/utils/evmenu.py b/evennia/utils/evmenu.py index 83f1a9cb6..4ac8a0794 100644 --- a/evennia/utils/evmenu.py +++ b/evennia/utils/evmenu.py @@ -1,12 +1,10 @@ """ -EvMenu - -This implements a full menu system for Evennia. +The EvMenu is a full in-game menu system for Evennia. To start the menu, just import the EvMenu class from this module. -Example usage: -```python +Example usage: +:: from evennia.utils.evmenu import EvMenu @@ -14,11 +12,10 @@ Example usage: startnode="node1", cmdset_mergetype="Replace", cmdset_priority=1, auto_quit=True, cmd_on_exit="look", persistent=True) -``` Where `caller` is the Object to use the menu on - it will get a new -cmdset while using the Menu. The menu_module_path is the python path -to a python module containing function definitions. By adjusting the +cmdset while using the Menu. The `menu_module_path` is the python path +to a python module containing function definitions. By adjusting the keyword options of the Menu() initialization call you can start the menu at different places in the menu definition file, adjust if the menu command should overload the normal commands or not, etc. @@ -32,8 +29,7 @@ no such restrictions exist. The menu is defined in a module (this can be the same module as the command definition too) with function definitions: - -```python +:: def node1(caller): # (this is the start node if called like above) @@ -47,14 +43,13 @@ command definition too) with function definitions: def another_node(caller, input_string, **kwargs): # code return text, options -``` -Where caller is the object using the menu and input_string is the +Where `caller` is the object using the menu and input_string is the command entered by the user on the *previous* node (the command entered to get to this node). The node function code will only be executed once per node-visit and the system will accept nodes with both one or two arguments interchangeably. It also accepts nodes -that takes **kwargs. +that takes `**kwargs`. The menu tree itself is available on the caller as `caller.ndb._evmenu`. This makes it a convenient place to store @@ -65,43 +60,43 @@ The return values must be given in the above order, but each can be returned as None as well. If the options are returned as None, the menu is immediately exited and the default "look" command is called. - text (str, tuple or None): Text shown at this node. If a tuple, the - second element in the tuple is a help text to display at this - node when the user enters the menu help command there. - options (tuple, dict or None): If `None`, this exits the menu. - If a single dict, this is a single-option node. If a tuple, - it should be a tuple of option dictionaries. Option dicts have - the following keys: - - `key` (str or tuple, optional): What to enter to choose this option. - If a tuple, it must be a tuple of strings, where the first string is the - key which will be shown to the user and the others are aliases. - If unset, the options' number will be used. The special key `_default` - marks this option as the default fallback when no other option matches - the user input. There can only be one `_default` option per node. It - will not be displayed in the list. - - `desc` (str, optional): This describes what choosing the option will do. - - `goto` (str, tuple or callable): If string, should be the name of node to go to - when this option is selected. If a callable, it has the signature - `callable(caller[,raw_input][,**kwargs]). If a tuple, the first element - is the callable and the second is a dict with the **kwargs to pass to - the callable. Those kwargs will also be passed into the next node if possible. - Such a callable should return either a str or a (str, dict), where the - string is the name of the next node to go to and the dict is the new, - (possibly modified) kwarg to pass into the next node. If the callable returns - None or the empty string, the current node will be revisited. - - `exec` (str, callable or tuple, optional): This takes the same input as `goto` above - and runs before it. If given a node name, the node will be executed but will not - be considered the next node. If node/callback returns str or (str, dict), these will - replace the `goto` step (`goto` callbacks will not fire), with the string being the - next node name and the optional dict acting as the kwargs-input for the next node. - If an exec callable returns the empty string (only), the current node is re-run. +- `text` (str, tuple or None): Text shown at this node. If a tuple, the + second element in the tuple is a help text to display at this + node when the user enters the menu help command there. +- `options` (tuple, dict or None): If `None`, this exits the menu. + If a single dict, this is a single-option node. If a tuple, + it should be a tuple of option dictionaries. Option dicts have + the following keys: + + - `key` (str or tuple, optional): What to enter to choose this option. + If a tuple, it must be a tuple of strings, where the first string is the + key which will be shown to the user and the others are aliases. + If unset, the options' number will be used. The special key `_default` + marks this option as the default fallback when no other option matches + the user input. There can only be one `_default` option per node. It + will not be displayed in the list. + - `desc` (str, optional): This describes what choosing the option will do. + - `goto` (str, tuple or callable): If string, should be the name of node to go to + when this option is selected. If a callable, it has the signature + `callable(caller[,raw_input][,**kwargs])`. If a tuple, the first element + is the callable and the second is a dict with the kwargs to pass to + the callable. Those kwargs will also be passed into the next node if possible. + Such a callable should return either a str or a (str, dict), where the + string is the name of the next node to go to and the dict is the new, + (possibly modified) kwarg to pass into the next node. If the callable returns + None or the empty string, the current node will be revisited. + - `exec` (str, callable or tuple, optional): This takes the same input as `goto` above + and runs before it. If given a node name, the node will be executed but will not + be considered the next node. If node/callback returns str or (str, dict), these will + replace the `goto` step (`goto` callbacks will not fire), with the string being the + next node name and the optional dict acting as the kwargs-input for the next node. + If an exec callable returns `None`, the current node is re-run. If key is not given, the option will automatically be identified by its number 1..N. Example: - -```python +:: # in menu_module.py @@ -137,10 +132,9 @@ Example: text = "This ends the menu since there are no options." return text, None -``` - When starting this menu with `Menu(caller, "path.to.menu_module")`, the first node will look something like this: +:: This is a node text ______________________________________ @@ -257,10 +251,11 @@ strings is only needed if wanting to pass strippable spaces, otherwise the key:values will be converted to strings/numbers with literal_eval before passed into the callable. -The `> ` option takes a glob or regex to perform different actions depending on user +The "> " option takes a glob or regex to perform different actions depending on user input. Make sure to sort these in increasing order of generality since they will be tested in sequence. +---- """ @@ -1317,24 +1312,32 @@ def list_node(option_generator, select=None, pagesize=10): option_generator (callable or list): A list of strings indicating the options, or a callable that is called as option_generator(caller) to produce such a list. select (callable or str, optional): Node to redirect a selection to. Its `**kwargs` will - contain the `available_choices` list and `selection` will hold one of the elements in - that list. If a callable, it will be called as - select(caller, menuchoice, **kwargs) where menuchoice is the chosen option as a - string and `available_choices` is a kwarg mapping the option keys to the choices - offered by the option_generator. The callable whould return the name of the target node - to goto after this selection (or None to repeat the list-node). Note that if this is not - given, the decorated node must itself provide a way to continue from the node! + contain the `available_choices` list and `selection` will hold one + of the elements in that list. If a callable, it will be called as + `select(caller, menuchoice, **kwargs)` where menuchoice is the + chosen option as a string and `available_choices` is the list of available + options offered by the option_generator. The callable whould return + the name of the target node to goto after this selection (or None to repeat the + list-node). Note that if this is not given, the decorated node + must itself provide a way to continue from the node! pagesize (int): How many options to show per page. Example: - @list_node(['foo', 'bar'], select) - def node_index(caller): - text = "describing the list" - return text, [] + :: + + def _selectfunc(caller, menuchoice, **kwargs): + # menuchoice would be either 'foo' or 'bar' here + # kwargs['available_choices'] would be the list ['foo', 'bar'] + return "the_next_node_to_go_to" + + @list_node(['foo', 'bar'], _selectfunc) + def node_index(caller): + text = "describing the list" + return text, [] Notes: All normal `goto` or `exec` callables returned from the decorated nodes will, if they accept - **kwargs, get a new kwarg 'available_choices' injected. These are the ordered list of named + `**kwargs`, get a new kwarg `available_choices` injected. This is the ordered list of named options (descs) visible on the current node page. """ @@ -1579,13 +1582,13 @@ def get_input(caller, prompt, callback, session=None, *args, **kwargs): greater than 2. The session is then updated by the command and is available (for example in callbacks) through `caller.ndb.getinput._session`. - *args, **kwargs (optional): Extra arguments will be + args, kwargs (optional): Extra arguments will be passed to the fall back function as a list 'args' and all keyword arguments as a dictionary 'kwargs'. - To utilise *args and **kwargs, a value for the + To utilise `*args` and `**kwargs`, a value for the session argument must be provided (None by default) - and the callback function must take *args and - **kwargs as arguments. + and the callback function must take `*args` and + `**kwargs` as arguments. Raises: RuntimeError: If the given callback is not callable. diff --git a/evennia/utils/evmore.py b/evennia/utils/evmore.py index 702b523d6..94b968968 100644 --- a/evennia/utils/evmore.py +++ b/evennia/utils/evmore.py @@ -7,6 +7,7 @@ down in the text (the name comes from the traditional 'more' unix command). To use, simply pass the text through the EvMore object: +:: from evennia.utils.evmore import EvMore @@ -14,17 +15,20 @@ To use, simply pass the text through the EvMore object: EvMore(caller, text, always_page=False, session=None, justify_kwargs=None, **kwargs) One can also use the convenience function msg from this module: +:: from evennia.utils import evmore text = some_long_text_output() evmore.msg(caller, text, always_page=False, session=None, justify_kwargs=None, **kwargs) -Where always_page decides if the pager is used also if the text is not -long enough to need to scroll, session is used to determine which session to relay to -and justify_kwargs are kwargs to pass to utils.utils.justify in order to change the formatting -of the text. The remaining **kwargs will be passed on to the -caller.msg() construct every time the page is updated. +Where always_page decides if the pager is used also if the text is not long +enough to need to scroll, session is used to determine which session to relay +to and `justify_kwargs` are kwargs to pass to `utils.utils.justify` in order to +change the formatting of the text. The remaining `**kwargs` will be passed on to +the `caller.msg()` construct every time the page is updated. + +---- """ from django.conf import settings @@ -124,9 +128,10 @@ def queryset_maxsize(qs): return qs.count() -class EvMore(object): +class EvMore: """ - The main pager object + The main pager object. + """ def __init__( @@ -144,23 +149,25 @@ class EvMore(object): ): """ - Initialization of the inp handler. + Initialization of the Evmore input handler. Args: caller (Object or Account): Entity reading the text. inp (str, EvTable, Paginator or iterator): The text or data to put under paging. + - If a string, paginage normally. If this text contains - one or more `\f` format symbol, automatic pagination and justification - are force-disabled and page-breaks will only happen after each `\f`. + one or more \\\\f (backslash + f) format symbols, automatic + pagination and justification are force-disabled and + page-breaks will only happen after each \\\\f. - If `EvTable`, the EvTable will be paginated with the same - setting on each page if it is too long. The table - decorations will be considered in the size of the page. + setting on each page if it is too long. The table + decorations will be considered in the size of the page. - Otherwise `inp` is converted to an iterator, where each step is - expected to be a line in the final display. Each line - will be run through `iter_callable`. - always_page (bool, optional): If `False`, the - pager will only kick in if `inp` is too big - to fit the screen. + expected to be a line in the final display. Each line + will be run through `iter_callable`. + + always_page (bool, optional): If `False`, the pager will only kick + in if `inp` is too big to fit the screen. session (Session, optional): If given, this session will be used to determine the screen width and will receive all output. justify (bool, optional): If set, auto-justify long lines. This must be turned @@ -176,30 +183,51 @@ class EvMore(object): the caller when the more page exits. Note that this will be using whatever cmdset the user had *before* the evmore pager was activated (so none of the evmore commands will be available when this is run). - kwargs (any, optional): These will be passed on to the `caller.msg` method. + kwargs (any, any): These will be passed on to the `caller.msg` method. Examples: - Basic use: - ``` - super_long_text = " ... " - EvMore(caller, super_long_text) - ``` - Paginator - ``` - from django.core.paginator import Paginator - query = ObjectDB.objects.all() - pages = Paginator(query, 10) # 10 objs per page - EvMore(caller, pages) - ``` - Every page an EvTable - ``` - from evennia import EvTable - def _to_evtable(page): - table = ... # convert page to a table - return EvTable(*headers, table=table, ...) - EvMore(caller, pages, page_formatter=_to_evtable) - ``` + :: + + super_long_text = " ... " + EvMore(caller, super_long_text) + + Paginated query data - this is an optimization to avoid fetching + database data until it's actually paged to. + :: + + from django.core.paginator import Paginator + + query = ObjectDB.objects.all() + pages = Paginator(query, 10) # 10 objs per page + EvMore(caller, pages) + + Automatic split EvTable over multiple EvMore pages + :: + + table = EvMore(*header, table=tabledata) + EvMore(caller, table) + + Every page a separate EvTable (optimization for very large data sets) + :: + + from evennia import EvTable, EvMore + + class TableEvMore(EvMore): + def init_pages(self, data): + pages = # depends on data type + super().init_pages(pages) + + def page_formatter(self, page): + table = EvTable() + + for line in page: + cols = # split raw line into columns + table.add_row(*cols) + + return str(table) + + TableEvMore(caller, pages) """ self._caller = caller @@ -386,9 +414,10 @@ class EvMore(object): def init_f_str(self, text): """ - The input contains \f markers. We use \f to indicate the user wants to - enforce their line breaks on their own. If so, we do no automatic - line-breaking/justification at all. + The input contains \\\\f (backslash + f) markers. We use \\\\f to indicate + the user wants to enforce their line breaks on their own. If so, we do + no automatic line-breaking/justification at all. + """ self._data = text.split("\f") self._npages = len(self._data) @@ -433,14 +462,17 @@ class EvMore(object): Notes: If overridden, this method must perform the following actions: - - read and re-store `self._data` (the incoming data set) if needed for pagination to work. + + - read and re-store `self._data` (the incoming data set) if needed + for pagination to work. - set `self._npages` to the total number of pages. Default is 1. - set `self._paginator` to a callable that will take a page number 1...N and return - the data to display on that page (not any decorations or next/prev buttons). If only - wanting to change the paginator, override `self.paginator` instead. - - set `self._page_formatter` to a callable that will receive the page from `self._paginator` - and format it with one element per line. Default is `str`. Or override `self.page_formatter` - directly instead. + the data to display on that page (not any decorations or next/prev buttons). If only + wanting to change the paginator, override `self.paginator` instead. + - set `self._page_formatter` to a callable that will receive the + page from `self._paginator` and format it with one element per + line. Default is `str`. Or override `self.page_formatter` + directly instead. By default, helper methods are called that perform these actions depending on supported inputs. @@ -520,15 +552,17 @@ def msg( Args: caller (Object or Account): Entity reading the text. text (str, EvTable or iterator): The text or data to put under paging. + - If a string, paginage normally. If this text contains - one or more `\f` format symbol, automatic pagination is disabled - and page-breaks will only happen after each `\f`. + one or more \\\\f (backslash + f) format symbol, automatic pagination is disabled + and page-breaks will only happen after each \\\\f. - If `EvTable`, the EvTable will be paginated with the same - setting on each page if it is too long. The table - decorations will be considered in the size of the page. + setting on each page if it is too long. The table + decorations will be considered in the size of the page. - Otherwise `text` is converted to an iterator, where each step is is expected to be a line in the final display, and each line will be run through repr(). + always_page (bool, optional): If `False`, the pager will only kick in if `text` is too big to fit the screen. diff --git a/evennia/utils/evtable.py b/evennia/utils/evtable.py index 9c345ff87..399e9c013 100644 --- a/evennia/utils/evtable.py +++ b/evennia/utils/evtable.py @@ -1,66 +1,61 @@ """ This is an advanced ASCII table creator. It was inspired by -[prettytable](https://code.google.com/p/prettytable/) but shares no -code. +[prettytable](https://code.google.com/p/prettytable/) but shares no code. Example usage: +:: -```python - from evennia.utils import evtable + from evennia.utils import evtable - table = evtable.EvTable("Heading1", "Heading2", + table = evtable.EvTable("Heading1", "Heading2", table=[[1,2,3],[4,5,6],[7,8,9]], border="cells") - table.add_column("This is long data", "This is even longer data") - table.add_row("This is a single row") - print table -``` + table.add_column("This is long data", "This is even longer data") + table.add_row("This is a single row") + print table Result: +:: -``` -+----------------------+----------+---+--------------------------+ -| Heading1 | Heading2 | | | -+~~~~~~~~~~~~~~~~~~~~~~+~~~~~~~~~~+~~~+~~~~~~~~~~~~~~~~~~~~~~~~~~+ -| 1 | 4 | 7 | This is long data | -+----------------------+----------+---+--------------------------+ -| 2 | 5 | 8 | This is even longer data | -+----------------------+----------+---+--------------------------+ -| 3 | 6 | 9 | | -+----------------------+----------+---+--------------------------+ -| This is a single row | | | | -+----------------------+----------+---+--------------------------+ -``` + +----------------------+----------+---+--------------------------+ + | Heading1 | Heading2 | | | + +~~~~~~~~~~~~~~~~~~~~~~+~~~~~~~~~~+~~~+~~~~~~~~~~~~~~~~~~~~~~~~~~+ + | 1 | 4 | 7 | This is long data | + +----------------------+----------+---+--------------------------+ + | 2 | 5 | 8 | This is even longer data | + +----------------------+----------+---+--------------------------+ + | 3 | 6 | 9 | | + +----------------------+----------+---+--------------------------+ + | This is a single row | | | | + +----------------------+----------+---+--------------------------+ As seen, the table will automatically expand with empty cells to make the table symmetric. Tables can be restricted to a given width: +:: -```python - table.reformat(width=50, align="l") -``` + table.reformat(width=50, align="l") (We could just have added these keywords to the table creation call) This yields the following result: +:: -``` -+-----------+------------+-----------+-----------+ -| Heading1 | Heading2 | | | -+~~~~~~~~~~~+~~~~~~~~~~~~+~~~~~~~~~~~+~~~~~~~~~~~+ -| 1 | 4 | 7 | This is | -| | | | long data | -+-----------+------------+-----------+-----------+ -| | | | This is | -| 2 | 5 | 8 | even | -| | | | longer | -| | | | data | -+-----------+------------+-----------+-----------+ -| 3 | 6 | 9 | | -+-----------+------------+-----------+-----------+ -| This is a | | | | -| single | | | | -| row | | | | -+-----------+------------+-----------+-----------+ -``` + +-----------+------------+-----------+-----------+ + | Heading1 | Heading2 | | | + +~~~~~~~~~~~+~~~~~~~~~~~~+~~~~~~~~~~~+~~~~~~~~~~~+ + | 1 | 4 | 7 | This is | + | | | | long data | + +-----------+------------+-----------+-----------+ + | | | | This is | + | 2 | 5 | 8 | even | + | | | | longer | + | | | | data | + +-----------+------------+-----------+-----------+ + | 3 | 6 | 9 | | + +-----------+------------+-----------+-----------+ + | This is a | | | | + | single | | | | + | row | | | | + +-----------+------------+-----------+-----------+ Table-columns can be individually formatted. Note that if an individual column is set with a specific width, table auto-balancing @@ -68,29 +63,25 @@ will not affect this column (this may lead to the full table being too wide, so be careful mixing fixed-width columns with auto- balancing). Here we change the width and alignment of the column at index 3 (Python starts from 0): +:: -```python + table.reformat_column(3, width=30, align="r") + print table -table.reformat_column(3, width=30, align="r") -print table -``` - -``` -+-----------+-------+-----+-----------------------------+---------+ -| Heading1 | Headi | | | | -| | ng2 | | | | -+~~~~~~~~~~~+~~~~~~~+~~~~~+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~+~~~~~~~~~+ -| 1 | 4 | 7 | This is long data | Test1 | -+-----------+-------+-----+-----------------------------+---------+ -| 2 | 5 | 8 | This is even longer data | Test3 | -+-----------+-------+-----+-----------------------------+---------+ -| 3 | 6 | 9 | | Test4 | -+-----------+-------+-----+-----------------------------+---------+ -| This is a | | | | | -| single | | | | | -| row | | | | | -+-----------+-------+-----+-----------------------------+---------+ -``` + +-----------+-------+-----+-----------------------------+---------+ + | Heading1 | Headi | | | | + | | ng2 | | | | + +~~~~~~~~~~~+~~~~~~~+~~~~~+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~+~~~~~~~~~+ + | 1 | 4 | 7 | This is long data | Test1 | + +-----------+-------+-----+-----------------------------+---------+ + | 2 | 5 | 8 | This is even longer data | Test3 | + +-----------+-------+-----+-----------------------------+---------+ + | 3 | 6 | 9 | | Test4 | + +-----------+-------+-----+-----------------------------+---------+ + | This is a | | | | | + | single | | | | | + | row | | | | | + +-----------+-------+-----+-----------------------------+---------+ When adding new rows/columns their data can have its own alignments (left/center/right, top/center/bottom). @@ -109,6 +100,8 @@ eventual colour outside the table will not transfer "across" a table, you need to re-set the color to have it appear on both sides of the table string. +---- + """ from django.conf import settings diff --git a/evennia/utils/inlinefuncs.py b/evennia/utils/inlinefuncs.py index bca2d2516..7e1f65a51 100644 --- a/evennia/utils/inlinefuncs.py +++ b/evennia/utils/inlinefuncs.py @@ -2,20 +2,18 @@ Inline functions (nested form). This parser accepts nested inlinefunctions on the form +:: -``` -$funcname(arg, arg, ...) -``` + $funcname(arg, arg, ...) -embedded in any text where any arg can be another $funcname{} call. +embedded in any text where any arg can be another `$funcname{}` call. This functionality is turned off by default - to activate, `settings.INLINEFUNC_ENABLED` must be set to `True`. -Each token starts with "$funcname(" where there must be no space -between the $funcname and (. It ends with a matched ending parentesis. -")". +Each token starts with `$funcname(` where there must be no space between the +$funcname and "(". It ends with a matched ending parentesis ")". -Inside the inlinefunc definition, one can use `\` to escape. This is +Inside the inlinefunc definition, one can use \\\\ to escape. This is mainly needed for escaping commas in flowing text (which would otherwise be interpreted as an argument separator), or to escape `}` when not intended to close the function block. Enclosing text in @@ -27,11 +25,10 @@ The available inlinefuncs are defined as global-level functions in modules defined by `settings.INLINEFUNC_MODULES`. They are identified by their function name (and ignored if this name starts with `_`). They should be on the following form: +:: -```python -def funcname (*args, **kwargs): + def funcname (*args, **kwargs): # ... -``` Here, the arguments given to `$funcname(arg1,arg2)` will appear as the `*args` tuple. This will be populated by the arguments given to the @@ -44,19 +41,21 @@ the string is sent to a non-puppetable object. The inlinefunc should never raise an exception. There are two reserved function names: + - "nomatch": This is called if the user uses a functionname that is - not registered. The nomatch function will get the name of the - not-found function as its first argument followed by the normal - arguments to the given function. If not defined the default effect is - to print `` to replace the unknown function. + not registered. The nomatch function will get the name of the + not-found function as its first argument followed by the normal + arguments to the given function. If not defined the default effect is + to print `` to replace the unknown function. - "stackfull": This is called when the maximum nested function stack is reached. When this happens, the original parsed string is returned and the result of the `stackfull` inlinefunc is appended to the end. By default this is an error message. -Error handling: - Syntax errors, notably not completely closing all inlinefunc - blocks, will lead to the entire string remaining unparsed. +Syntax errors, notably not completely closing all inlinefunc blocks, will lead +to the entire string remaining unparsed. + +---- """ @@ -92,9 +91,10 @@ def random(*args, **kwargs): given range. Example: - `$random()` - `$random(5)` - `$random(5, 10)` + + - `$random()` + - `$random(5)` + - `$random(5, 10)` """ nargs = len(args) @@ -574,12 +574,12 @@ def initialize_nick_templates(in_template, out_template): Args: in_template (str): The template to be used for nick recognition. out_template (str): The template to be used to replace the string - matched by the in_template. + matched by the `in_template`. Returns: - regex (regex): Regex to match against strings - template (str): Template with markers {arg1}, {arg2}, etc for - replacement using the standard .format method. + regex, template (regex, str): Regex to match against strings and a + template with markers `{arg1}`, `{arg2}`, etc for replacement using the + standard `.format` method. Raises: NickTemplateInvalid: If the in/out template does not have a matching diff --git a/evennia/utils/optionclasses.py b/evennia/utils/optionclasses.py index 6cd7c1660..c9920740f 100644 --- a/evennia/utils/optionclasses.py +++ b/evennia/utils/optionclasses.py @@ -1,3 +1,9 @@ +""" +Option classes store user- or server Options in a generic way +while also providing validation. + +""" + import datetime from evennia import logger from evennia.utils.ansi import strip_ansi @@ -6,7 +12,7 @@ from evennia.utils.utils import crop from evennia.utils import validatorfuncs -class BaseOption(object): +class BaseOption: """ Abstract Class to deal with encapsulating individual Options. An Option has a name/key, a description to display in relevant commands and menus, and a @@ -109,7 +115,7 @@ class BaseOption(object): def save(self, **kwargs): """ - Stores the current value using .handler.save_handler(self.key, value, **kwargs) + Stores the current value using `.handler.save_handler(self.key, value, **kwargs)` where kwargs are a combination of those passed into this function and the ones specified by the OptionHandler. diff --git a/evennia/utils/search.py b/evennia/utils/search.py index c7231309a..92778b77b 100644 --- a/evennia/utils/search.py +++ b/evennia/utils/search.py @@ -36,7 +36,6 @@ __all__ = ( "search_message", "search_channel", "search_help_entry", - "search_object_tag", "search_script_tag", "search_account_tag", "search_channel_tag", diff --git a/evennia/utils/test_resources.py b/evennia/utils/test_resources.py index 387f93577..7a4d1e155 100644 --- a/evennia/utils/test_resources.py +++ b/evennia/utils/test_resources.py @@ -38,17 +38,19 @@ def unload_module(module): an object, the module in which that object sits will be unloaded. A string should directly give the module pathname to unload. - Example: - # (in a test method) - unload_module(foo) - with mock.patch("foo.GLOBALTHING", "mockval"): - import foo - ... # test code using foo.GLOBALTHING, now set to 'mockval' + Example: + :: + # (in a test method) + unload_module(foo) + with mock.patch("foo.GLOBALTHING", "mockval"): + import foo + ... # test code using foo.GLOBALTHING, now set to 'mockval' - This allows for mocking constants global to the module, since - otherwise those would not be mocked (since a module is only - loaded once). + Notes: + This allows for mocking constants global to the module, since + otherwise those would not be mocked (since a module is only + loaded once). """ if isinstance(module, str): diff --git a/evennia/utils/utils.py b/evennia/utils/utils.py index 183c9f2b5..8882c4a2b 100644 --- a/evennia/utils/utils.py +++ b/evennia/utils/utils.py @@ -1069,7 +1069,7 @@ def run_async(to_execute, *args, **kwargs): Args: to_execute (callable): If this is a callable, it will be - executed with *args and non-reserved *kwargs as arguments. + executed with `*args` and non-reserved `**kwargs` as arguments. The callable will be executed using ProcPool, or in a thread if ProcPool is not available. @@ -1168,7 +1168,7 @@ def check_evennia_dependencies(): def has_parent(basepath, obj): """ - Checks if `basepath` is somewhere in `obj`s parent tree. + Checks if `basepath` is somewhere in `obj`'s parent tree. Args: basepath (str): Python dotpath to compare against obj path. @@ -1495,8 +1495,8 @@ def init_new_account(account): def string_similarity(string1, string2): """ This implements a "cosine-similarity" algorithm as described for example in - *Proceedings of the 22nd International Conference on Computation - Linguistics* (Coling 2008), pages 593-600, Manchester, August 2008. + *Proceedings of the 22nd International Conference on Computation + Linguistics* (Coling 2008), pages 593-600, Manchester, August 2008. The measure-vectors used is simply a "bag of words" type histogram (but for letters). @@ -1605,9 +1605,9 @@ def string_partial_matching(alternatives, inp, ret_index=True): def format_table(table, extra_space=1): """ - Note: `evennia.utils.evtable` is more powerful than this, but this - function can be useful when the number of columns and rows are - unknown and must be calculated on the fly. + Note: `evennia.utils.evtable` is more powerful than this, but this function + can be useful when the number of columns and rows are unknown and must be + calculated on the fly. Args. table (list): A list of lists to represent columns in the @@ -1626,18 +1626,18 @@ def format_table(table, extra_space=1): The function formats the columns to be as wide as the widest member of each column. - Examples: + Example: + :: + + ftable = format_table([[...], [...], ...]) + for ir, row in enumarate(ftable): + if ir == 0: + # make first row white + string += "\\\\n|w" + ""join(row) + "|n" + else: + string += "\\\\n" + "".join(row) + print(string) - ```python - ftable = format_table([[...], [...], ...]) - for ir, row in enumarate(ftable): - if ir == 0: - # make first row white - string += "\n|w" + ""join(row) + "|n" - else: - string += "\n" + "".join(row) - print string - ``` """ if not table: return [[]] @@ -2051,26 +2051,28 @@ def get_all_typeclasses(parent=None): def interactive(func): """ - Decorator to make a method pausable with yield(seconds) - and able to ask for user-input with response=yield(question). - For the question-asking to work, 'caller' must the name - of an argument or kwarg to the decorated function. + Decorator to make a method pausable with yield(seconds) and able to ask for + user-input with `response=yield(question)`. For the question-asking to + work, 'caller' must the name of an argument or kwarg to the decorated + function. - Note that this turns the method into a generator. + Example: + :: - Example usage: - - @interactive - def myfunc(caller): - caller.msg("This is a test") - # wait five seconds - yield(5) - # ask user (caller) a question - response = yield("Do you want to continue waiting?") - if response == "yes": + @interactive + def myfunc(caller): + caller.msg("This is a test") + # wait five seconds yield(5) - else: - # ... + # ask user (caller) a question + response = yield("Do you want to continue waiting?") + if response == "yes": + yield(5) + else: + # ... + + Notes: + This turns the method into a generator! """ from evennia.utils.evmenu import get_input diff --git a/evennia/utils/validatorfuncs.py b/evennia/utils/validatorfuncs.py index 7d6f28cf3..5fe7b11b9 100644 --- a/evennia/utils/validatorfuncs.py +++ b/evennia/utils/validatorfuncs.py @@ -39,8 +39,8 @@ def color(entry, option_key="Color", **kwargs): def datetime(entry, option_key="Datetime", account=None, from_tz=None, **kwargs): """ - Process a datetime string in standard forms while accounting for the inputer's timezone. Always - returns a result in UTC. + Process a datetime string in standard forms while accounting for the + inputer's timezone. Always returns a result in UTC. Args: entry (str): A date string from a user. @@ -48,10 +48,12 @@ def datetime(entry, option_key="Datetime", account=None, from_tz=None, **kwargs) account (AccountDB): The Account performing this lookup. Unless `from_tz` is provided, the account's timezone option will be used. from_tz (pytz.timezone): An instance of a pytz timezone object from the - user. If not provided, tries to use the timezone option of the `account'. + user. If not provided, tries to use the timezone option of the `account`. If neither one is provided, defaults to UTC. + Returns: datetime in UTC. + Raises: ValueError: If encountering a malformed timezone, date string or other format error.