Start checking codebase with the codespell tool
[mudpy.git] / doc / source / coder.rst
index 4e956a2..b3ed6da 100644 (file)
@@ -2,7 +2,7 @@
  coder guide
 =============
 
-.. Copyright (c) 2004-2019 mudpy authors. Permission to use, copy,
+.. Copyright (c) 2004-2020 mudpy authors. Permission to use, copy,
    modify, and distribute this software is granted under terms
    provided in the LICENSE file distributed with this software.
 
@@ -21,10 +21,11 @@ version control system
 ~~~~~~~~~~~~~~~~~~~~~~
 
 Git_ is used for version control on the project, and the archive can
-be browsed or cloned anonymously from https://mudpy.org/code/mudpy .
-For now, detailed commits can be E-mailed to fungi@yuggoth.org, but
-there will most likely be a developer mailing list for more open
-presentation and discussion of patches soon.
+be browsed or cloned anonymously from the official
+https://mudpy.org/code/mudpy repository location. For now, detailed
+commits can be E-mailed to fungi@yuggoth.org, but there will most
+likely be a developer mailing list for more open presentation and
+discussion of patches eventually.
 
 A :file:`ChangeLog` is generated automatically from repository
 commit logs, and is included automatically in all sdist_ tarballs. It
@@ -58,83 +59,276 @@ the mudpy source code.
 regression testing
 ~~~~~~~~~~~~~~~~~~
 
-All new commits are tested using a selftest script in the
-``mudpy/tests`` directory of the source archive, to help ensure the
-software is continually usable. Any new features should be
-accompanied by suitable regression tests so that their functionality
-can be maintained properly through future releases. The selftest can
-be invoked with ``tox -e selftest`` after starting the daemon with
-the test configuration provided in the ``mudpy/tests/fixtures``
-directory.
+All new commits are tested using an included
+:mod:`mudpy.test.selftest` script, to help ensure the software is
+continually usable. Any new features should be accompanied by
+suitable regression tests so that their functionality can be
+maintained properly through future releases. The selftest can be
+invoked with :command:`tox -e py3` which will automatically start
+the daemon with the :file:`mudpy/tests/fixtures/test_daemon.yaml`
+test configuration.
 
 style
 -----
 
-This project follows Guido van Rossum and Barry Warsaw's `Style Guide`_
-for Python Code (a.k.a. "PEP-8"). When in need of sample code or other
-examples, any common source code file or text document file distributed
-as part of mudpy should serve as a suitable reference. Testing of all
-new patches with the flake8_ utility should be performed with ``tox
--e flake8`` to ensure adherence to preferred style conventions.
+This project follows Guido van Rossum and Barry Warsaw's `Style
+Guide`_ for Python Code (a.k.a. "PEP-8"). When in need of sample
+code or other examples, any common source code file or text document
+file distributed as part of mudpy should serve as a suitable
+reference. Testing of all new patches with the flake8_ utility
+should be performed by running :command:`tox -e flake8` from the
+repository working directory to ensure adherence to preferred style
+conventions.
 
 .. _Style Guide: :pep:`0008`
 .. _flake8: https://pypi.org/project/flake8
 
+.. _demo:
+
 test and demo walk-through
 --------------------------
 
 The included tox configuration provides testenv definitions for a
 variety of analyzers, regression tests, documentation builds and
-package generation. It also has a ``demo`` testenv which will run the
-server using the provided :file:`etc/mudpy.conf` and other sample
-files. By default it listens on TCP port 4000 at the IPv6 loopback
-address, streams its logging to the terminal via stdout, and grants
-administrative rights automatically to an account named ``admin``
-(once created).
-
-Because all the dependencies besides the ``python3`` interpreter itself
-are available from PyPI, installing them should be fairly similar
-across most GNU/Linux distributions. For example, on Debian 10 (a.k.a.
-"Buster") you need to expressly install the ``pip`` and ``venv`` modules
-since they're packaged separately from the rest of the Python standard
-library. Once that's done, you can perform local installs of ``tox`` and
-``tox-venv`` as a normal non-root user. We're also going to install
-system packages for the ``git`` revision control toolset and an
-extensible console-based MUD client called ``tf5`` (TinyFugue version
-5)::
+package generation. It also has a ``demo`` testenv which will run
+the server using the provided :file:`etc/mudpy.yaml` and other
+sample files. By default it listens on TCP port 4000 at the IPv6
+loopback address, streams its logging to the terminal via stdout,
+and grants administrative rights automatically to an account named
+``admin`` (once created).
+
+Because all the dependencies besides the :command:`python3`
+interpreter itself are available from PyPI, installing them should
+be fairly similar across most GNU/Linux distributions. For example,
+on Debian 10 (a.k.a. *Buster*) you need to expressly install the
+``pip`` and ``venv`` modules since they're packaged separately from
+the rest of the Python standard library. Once that's done, you can
+perform local installs of ``tox`` and ``tox-venv`` as a normal
+non-root user. We're also going to install system packages for the
+``git`` revision control toolset and an extensible console-based MUD
+client called ``tf5`` (TinyFugue version 5)::
 
     sudo apt install git python3-pip python3-venv tf5
     pip install --user tox tox-venv
     exit
 
-The reason for exiting is that, if this is the first time you've ever
-used pip's ``--user`` option, when you log back in your ``~/.profile``
-should see that there's now a ``~/.local/bin`` directory and add it to
-your ``$PATH`` environment variable automatically from that point on.
-Next, retrieve the project source code and switch your current working
-directory to where you've cloned it::
+The reason for exiting is that, if this is the first time you've
+ever used pip's ``--user`` option, when you log back in your
+``~/.profile`` should see that there's now a ``~/.local/bin``
+directory and add it to your ``$PATH`` environment variable
+automatically from that point on. Next, retrieve the project source
+code and switch your current working directory to where you've
+cloned it::
 
     git clone https://mudpy.org/code/mudpy
     cd mudpy
 
 Now you should be able to invoke any tox testenv you like. Just
-running ``tox`` without any additional options will go through the
-defalt battery of checks and is a good way to make sure everything is
-installed and working. Once you're ready to try out the server
-interactively, launch it like this::
+running :command:`tox` without any additional options will go
+through the default battery of checks and is a good way to make sure
+everything is installed and working. Once you're ready to try out
+the server interactively, launch it like this::
 
     tox -e demo
 
-Now in another terminal/session (because the one you've been using is
-busy displaying the server's logs) connect using a MUD client::
+Now in another terminal/session (because the one you've been using
+is busy displaying the server's logs) connect using a MUD client
+(such as :command:`tf5` which we installed above)::
 
     tf5 ip6-localhost 4000
 
-Log in as ``admin`` creating an account and then an avatar and awaken
-it. Try out the ``help`` command and make sure you see some command
-words in red (you're using a color terminal, right?) since those are
-admin-only commands and being able to see them confirms you're an
-administrator. When you're ready to terminate the service you can
-either give the ``halt`` command in your MUD client terminal or press
-the ``control`` and ``c`` keys together in the terminal where you ran
-tox. To exit the MUD client, give it the ``/quit`` command.
+Log in as ``admin`` creating an account and then an avatar and
+awaken it. Try out the :command:`help` command and make sure you see
+some command words in red (you're using a color terminal, right?)
+since those are admin-only commands and being able to see them
+confirms you're an administrator. When you're ready to terminate the
+service you can either give the :command:`halt` command in your MUD
+client terminal or press the ``control`` and ``c`` keys together in
+the terminal where you ran tox. To exit the tf5 MUD client, give it
+the :command:`/quit` command.
+
+miscellanea
+-----------
+
+This section is a collection of various coding-related discussions
+and treatises, mostly here because there's not a better place, and
+so they don't get lost in random E-mail threads.
+
+avatar names
+~~~~~~~~~~~~
+
+It comes up fairly often, so bears mentioning, **there is no
+assumption avatar names will be globally unique**. This is part of
+the reason the default :code:`choose_name` menu just runs
+:func:`mudpy.misc.random_name`, to make impersonation a bit harder.
+The idea is to make sure to be able to support realistic settings
+where multiple people are often given the same names and don't
+really have much choice as to what their parents decided to name
+them at birth, but can still choose a name they like later (perhaps
+through some cultural rite of passage quest, attaining a particular
+guild rank, or just through a command they're allowed to run as soon
+as they awaken that avatar for the first time).
+
+It may be possible to force globally-unique avatar names, but the
+need to treat avatars similarly to non-player characters (actors
+which aren't associated with an account and may be driven by
+scripted routines instead) means it may also be desirable to prevent
+a user from choosing an avatar name which duplicates the name of any
+existing actor (whether or not that actor is a user's avatar). This
+would entail scanning the full dataset to identify actors with
+similar names so they could be rejected or excluded for a new
+avatar, but then raises the question as to what to do when some new
+content adds NPCs with a name which is already in use.
+
+The best way to side-step this challenge is to not rely on avatar
+names for programmatic interaction and instead reference the
+corresponding ID for an avatar's element in the universe contents.
+IDs **are** already guaranteed to be globally unique, so there is no
+ambiguity when using them (an avatar's element ID is constructed
+from the owning account name plus an index integer). Exactly what
+variables you have to work from will depend on the context where
+your hypothetical routine is called.
+
+Taking as an example, let's say what you want is to be able to have
+an area's owner permit a specific avatar to pass through the portals
+(doorways, gates, whatever) which connect it to other areas. This is
+similar to how *guild houses* work in some classical MUDs. Here's
+how I'd go about implementing it:
+
+1. Extend the area element to have two new facets: *owners* (type
+   list) and *visitors* (type list). The first will contain
+   references to the element IDs for the avatars who are allowed to
+   alter the entries of both lists, while the second will be the
+   element IDs for avatars who are allowed to enter the area (maybe
+   also allow owners to enter an area so they don't need to be
+   duplicated in both lists).
+
+2. Implement a command which allows someone to see the corresponding
+   ID for an element (alternatively make it an acquirable ability,
+   skill, spell, item, or however finding that information would
+   best fit into your setting). This is stored in the element's
+   :code:`.key` attribute, and you can play around with it on the
+   command line with :command:`show result` like this::
+
+    > look
+    Center Sample Location
+    This is the Center Sample Location. It is merely provided as an
+    example of what an area might look like.
+    [ Exits: down, east, north, south, up, west ]
+    A sample prop sits here.
+    Utso is here.
+
+    > show result [
+          a.key.split('_', 1)[1]
+          for a in actor.universe.contents[
+              actor.get('location')
+          ].contents.values()
+          if a.key.startswith('actor.avatar_')
+          and a.get('name') == 'Utso'
+      ][0]
+
+    'luser0_0'
+
+   [Put the command on one line, It's merely wrapped here for
+   readability.] In short, this list comprehension takes the
+   internal IDs for elements present in the calling actor's current
+   location, filtered by whether they're avatars and a specific
+   actor name, then splits the group and prefix off (relying on the
+   fact that it uses ``_`` as a prefix separator) and returns the
+   first result. In a non-prototype implementation, the command
+   would probably include a routine which looked something like:
+
+   .. code-block:: python
+
+    location_id = actor.get("location")
+    also_here = actor.universe.contents[location_id].contents
+    for who in also_here.values():
+        if (who.key.startswith("actor.avatar_")
+                and who.get("name") == parameters[0]:
+            message += "Avatar ID: %s" % .key.split("_", 1)[1]
+            break
+
+   That's a rough approximation, and not terribly immersive, but
+   hopefully you get the idea. We could also improve the element
+   attributes with pre-populated backlink references for some of
+   these relationships to reduce code complexity.
+
+3. Implement a command which allows a user to add and remove entries
+   in the owners and visitors lists for their current location,
+   which takes this avatar ID as a parameter.
+
+4. Hook in the :meth:`mudpy.misc.Element.go_to` method to test the
+   owners and visitors lists for the area parameter to make sure
+   self is in one of those lists. Probably also stick in an override
+   to make sure that if the actor's owner account is flagged as
+   administrative then they're also allowed even if they're not on
+   the allowed list.
+
+The resulting workflow this would enable is that after an avatar *A*
+has been set as an owner for area *X* (perhaps by an admin or a
+world builder), *A* when in another area *Y* at the same time as
+some avatar *B* whom they would like to be allowed to visit *X*
+could run the identification command from part #2 above, then they
+would later travel to *X* and run the safe passage command from part
+#3 adding *B* to the visitors list for *X*. After that, *B* is able
+freely enter *X* until *A* runs a similar command to remove them
+from the visitors list for *X* again.
+
+To tackle the immersion problem, an optional substitution cipher
+could be implemented to (reversibly) turn those IDs into something
+more mystical-looking. Also the identification command (or ability,
+skill, spell, tool, whatever) could be limited to only work when the
+area you're running it in is flagged a certain way. Taking this
+concept further, all sorts of elements could have access lists for
+which avatars own them and which avatars are allowed to interact
+with them (in which case maybe the term *visitor* for the latter is
+too area-specific and it needs a slightly more general term?).
+
+However, a far more immersive solution would be to get closable and
+lockable elements (and inventory management) implemented, have a
+means of crafting keys which unlock specific elements, and then when
+creating an area you want to restrict to specific avatars, put
+locked doors for all its portals and give keys for them to anyone
+you want to be able to visit. Or implement non-avatar actors, then
+create guards you can hire or summon or construct to police your
+doorways and check whether visitors are on a list. That list itself
+could even be a piece of paper or a book (a prop), created with
+appropriate materials and some skill which allows the actor to write
+and edit paper notes, given to the guard and held by it as an
+inventory item.
+
+custom commands
+~~~~~~~~~~~~~~~
+
+Command definitions are split into metadata and procedure. The
+metadata needs to be in an element like the basic ones shipped in
+the :file:`share/command.yaml` file, and then a handler function
+added to the :mod:`mudpy.command` module. There's not yet a plugin
+layer to allow those to be added to a separate module. The function
+name needs to match the element base name, or you have to add an
+action facet to the element indicating the name of the function you
+want it to call; the ``command.set`` element has an example of this,
+so that we avoid shadowing Python's built-in :func:`set` function
+and call :func:`mudpy.command.c_set` instead.
+
+The :meth:`mudpy.misc.Element.set` method takes two parameters, the
+name of the facet and the value to pass into it. An example of it in
+action is :meth:`mudpy.misc.User.authenticate` where the user's
+*administrator* facet is set to the value *True* if their username
+is in the ``.mudpy.limit.admins`` list used to bootstrap
+administrators:
+
+.. code-block:: python
+    :emphasize-lines: 9
+
+    def authenticate(self):
+        """Flag the user as authenticated and disconnect duplicates."""
+        if self.state != "authenticated":
+            self.authenticated = True
+            log("User %s authenticated for account %s." % (
+                    self, self.account.subkey), 2)
+            if ("mudpy.limit" in universe.contents and self.account.subkey in
+                    universe.contents["mudpy.limit"].get("admins")):
+                self.account.set("administrator", True)
+                log("Account %s is an administrator." % (
+                        self.account.subkey), 2)