Start checking codebase with the codespell tool
[mudpy.git] / doc / source / coder.rst
1 =============
2  coder guide
3 =============
4
5 .. Copyright (c) 2004-2020 mudpy authors. Permission to use, copy,
6    modify, and distribute this software is granted under terms
7    provided in the LICENSE file distributed with this software.
8
9 This guide attempts to embody a rudimentary set of rules for developer
10 submissions of source code and documentation targeted for inclusion
11 within the mudpy project, as well as pointers to useful resources for
12 those attempting to obtain a greater understanding of the software.
13
14 source
15 ------
16
17 As with any project, the mudpy source code could always be better
18 documented, and contributions to that end are heartily welcomed.
19
20 version control system
21 ~~~~~~~~~~~~~~~~~~~~~~
22
23 Git_ is used for version control on the project, and the archive can
24 be browsed or cloned anonymously from the official
25 https://mudpy.org/code/mudpy repository location. For now, detailed
26 commits can be E-mailed to fungi@yuggoth.org, but there will most
27 likely be a developer mailing list for more open presentation and
28 discussion of patches eventually.
29
30 A :file:`ChangeLog` is generated automatically from repository
31 commit logs, and is included automatically in all sdist_ tarballs. It
32 can be regenerated easily by running :command:`tox -e dist` from the
33 top level directory of the Git repository in a working `developer
34 environment`_.
35
36 .. _Git: https://git-scm.com/
37 .. _sdist: https://packaging.python.org/glossary
38            /#term-source-distribution-or-sdist
39
40 developer environment
41 ~~~~~~~~~~~~~~~~~~~~~
42
43 Basic developer requirements are a POSIX Unix derivative (such as
44 Linux), a modern Python 3 interpreter (any of the minor revisions
45 mentioned in the ``metadata.classifier`` section of
46 :file:`setup.cfg`) and a recent release of the tox_ utility (at least
47 the ``tox.minversion`` mentioned in :file:`tox.ini`). The tox-venv_
48 plug-in for tox is also recommended.
49
50 .. _tox: https://tox.readthedocs.io/
51 .. _tox-venv: https://pypi.org/project/tox-venv/
52
53 application program interface
54 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
55
56 The :doc:`api` API documentation is maintained within docstrings in
57 the mudpy source code.
58
59 regression testing
60 ~~~~~~~~~~~~~~~~~~
61
62 All new commits are tested using an included
63 :mod:`mudpy.test.selftest` script, to help ensure the software is
64 continually usable. Any new features should be accompanied by
65 suitable regression tests so that their functionality can be
66 maintained properly through future releases. The selftest can be
67 invoked with :command:`tox -e py3` which will automatically start
68 the daemon with the :file:`mudpy/tests/fixtures/test_daemon.yaml`
69 test configuration.
70
71 style
72 -----
73
74 This project follows Guido van Rossum and Barry Warsaw's `Style
75 Guide`_ for Python Code (a.k.a. "PEP-8"). When in need of sample
76 code or other examples, any common source code file or text document
77 file distributed as part of mudpy should serve as a suitable
78 reference. Testing of all new patches with the flake8_ utility
79 should be performed by running :command:`tox -e flake8` from the
80 repository working directory to ensure adherence to preferred style
81 conventions.
82
83 .. _Style Guide: :pep:`0008`
84 .. _flake8: https://pypi.org/project/flake8
85
86 .. _demo:
87
88 test and demo walk-through
89 --------------------------
90
91 The included tox configuration provides testenv definitions for a
92 variety of analyzers, regression tests, documentation builds and
93 package generation. It also has a ``demo`` testenv which will run
94 the server using the provided :file:`etc/mudpy.yaml` and other
95 sample files. By default it listens on TCP port 4000 at the IPv6
96 loopback address, streams its logging to the terminal via stdout,
97 and grants administrative rights automatically to an account named
98 ``admin`` (once created).
99
100 Because all the dependencies besides the :command:`python3`
101 interpreter itself are available from PyPI, installing them should
102 be fairly similar across most GNU/Linux distributions. For example,
103 on Debian 10 (a.k.a. *Buster*) you need to expressly install the
104 ``pip`` and ``venv`` modules since they're packaged separately from
105 the rest of the Python standard library. Once that's done, you can
106 perform local installs of ``tox`` and ``tox-venv`` as a normal
107 non-root user. We're also going to install system packages for the
108 ``git`` revision control toolset and an extensible console-based MUD
109 client called ``tf5`` (TinyFugue version 5)::
110
111     sudo apt install git python3-pip python3-venv tf5
112     pip install --user tox tox-venv
113     exit
114
115 The reason for exiting is that, if this is the first time you've
116 ever used pip's ``--user`` option, when you log back in your
117 ``~/.profile`` should see that there's now a ``~/.local/bin``
118 directory and add it to your ``$PATH`` environment variable
119 automatically from that point on. Next, retrieve the project source
120 code and switch your current working directory to where you've
121 cloned it::
122
123     git clone https://mudpy.org/code/mudpy
124     cd mudpy
125
126 Now you should be able to invoke any tox testenv you like. Just
127 running :command:`tox` without any additional options will go
128 through the default battery of checks and is a good way to make sure
129 everything is installed and working. Once you're ready to try out
130 the server interactively, launch it like this::
131
132     tox -e demo
133
134 Now in another terminal/session (because the one you've been using
135 is busy displaying the server's logs) connect using a MUD client
136 (such as :command:`tf5` which we installed above)::
137
138     tf5 ip6-localhost 4000
139
140 Log in as ``admin`` creating an account and then an avatar and
141 awaken it. Try out the :command:`help` command and make sure you see
142 some command words in red (you're using a color terminal, right?)
143 since those are admin-only commands and being able to see them
144 confirms you're an administrator. When you're ready to terminate the
145 service you can either give the :command:`halt` command in your MUD
146 client terminal or press the ``control`` and ``c`` keys together in
147 the terminal where you ran tox. To exit the tf5 MUD client, give it
148 the :command:`/quit` command.
149
150 miscellanea
151 -----------
152
153 This section is a collection of various coding-related discussions
154 and treatises, mostly here because there's not a better place, and
155 so they don't get lost in random E-mail threads.
156
157 avatar names
158 ~~~~~~~~~~~~
159
160 It comes up fairly often, so bears mentioning, **there is no
161 assumption avatar names will be globally unique**. This is part of
162 the reason the default :code:`choose_name` menu just runs
163 :func:`mudpy.misc.random_name`, to make impersonation a bit harder.
164 The idea is to make sure to be able to support realistic settings
165 where multiple people are often given the same names and don't
166 really have much choice as to what their parents decided to name
167 them at birth, but can still choose a name they like later (perhaps
168 through some cultural rite of passage quest, attaining a particular
169 guild rank, or just through a command they're allowed to run as soon
170 as they awaken that avatar for the first time).
171
172 It may be possible to force globally-unique avatar names, but the
173 need to treat avatars similarly to non-player characters (actors
174 which aren't associated with an account and may be driven by
175 scripted routines instead) means it may also be desirable to prevent
176 a user from choosing an avatar name which duplicates the name of any
177 existing actor (whether or not that actor is a user's avatar). This
178 would entail scanning the full dataset to identify actors with
179 similar names so they could be rejected or excluded for a new
180 avatar, but then raises the question as to what to do when some new
181 content adds NPCs with a name which is already in use.
182
183 The best way to side-step this challenge is to not rely on avatar
184 names for programmatic interaction and instead reference the
185 corresponding ID for an avatar's element in the universe contents.
186 IDs **are** already guaranteed to be globally unique, so there is no
187 ambiguity when using them (an avatar's element ID is constructed
188 from the owning account name plus an index integer). Exactly what
189 variables you have to work from will depend on the context where
190 your hypothetical routine is called.
191
192 Taking as an example, let's say what you want is to be able to have
193 an area's owner permit a specific avatar to pass through the portals
194 (doorways, gates, whatever) which connect it to other areas. This is
195 similar to how *guild houses* work in some classical MUDs. Here's
196 how I'd go about implementing it:
197
198 1. Extend the area element to have two new facets: *owners* (type
199    list) and *visitors* (type list). The first will contain
200    references to the element IDs for the avatars who are allowed to
201    alter the entries of both lists, while the second will be the
202    element IDs for avatars who are allowed to enter the area (maybe
203    also allow owners to enter an area so they don't need to be
204    duplicated in both lists).
205
206 2. Implement a command which allows someone to see the corresponding
207    ID for an element (alternatively make it an acquirable ability,
208    skill, spell, item, or however finding that information would
209    best fit into your setting). This is stored in the element's
210    :code:`.key` attribute, and you can play around with it on the
211    command line with :command:`show result` like this::
212
213     > look
214     Center Sample Location
215     This is the Center Sample Location. It is merely provided as an
216     example of what an area might look like.
217     [ Exits: down, east, north, south, up, west ]
218     A sample prop sits here.
219     Utso is here.
220
221     > show result [
222           a.key.split('_', 1)[1]
223           for a in actor.universe.contents[
224               actor.get('location')
225           ].contents.values()
226           if a.key.startswith('actor.avatar_')
227           and a.get('name') == 'Utso'
228       ][0]
229
230     'luser0_0'
231
232    [Put the command on one line, It's merely wrapped here for
233    readability.] In short, this list comprehension takes the
234    internal IDs for elements present in the calling actor's current
235    location, filtered by whether they're avatars and a specific
236    actor name, then splits the group and prefix off (relying on the
237    fact that it uses ``_`` as a prefix separator) and returns the
238    first result. In a non-prototype implementation, the command
239    would probably include a routine which looked something like:
240
241    .. code-block:: python
242
243     location_id = actor.get("location")
244     also_here = actor.universe.contents[location_id].contents
245     for who in also_here.values():
246         if (who.key.startswith("actor.avatar_")
247                 and who.get("name") == parameters[0]:
248             message += "Avatar ID: %s" % .key.split("_", 1)[1]
249             break
250
251    That's a rough approximation, and not terribly immersive, but
252    hopefully you get the idea. We could also improve the element
253    attributes with pre-populated backlink references for some of
254    these relationships to reduce code complexity.
255
256 3. Implement a command which allows a user to add and remove entries
257    in the owners and visitors lists for their current location,
258    which takes this avatar ID as a parameter.
259
260 4. Hook in the :meth:`mudpy.misc.Element.go_to` method to test the
261    owners and visitors lists for the area parameter to make sure
262    self is in one of those lists. Probably also stick in an override
263    to make sure that if the actor's owner account is flagged as
264    administrative then they're also allowed even if they're not on
265    the allowed list.
266
267 The resulting workflow this would enable is that after an avatar *A*
268 has been set as an owner for area *X* (perhaps by an admin or a
269 world builder), *A* when in another area *Y* at the same time as
270 some avatar *B* whom they would like to be allowed to visit *X*
271 could run the identification command from part #2 above, then they
272 would later travel to *X* and run the safe passage command from part
273 #3 adding *B* to the visitors list for *X*. After that, *B* is able
274 freely enter *X* until *A* runs a similar command to remove them
275 from the visitors list for *X* again.
276
277 To tackle the immersion problem, an optional substitution cipher
278 could be implemented to (reversibly) turn those IDs into something
279 more mystical-looking. Also the identification command (or ability,
280 skill, spell, tool, whatever) could be limited to only work when the
281 area you're running it in is flagged a certain way. Taking this
282 concept further, all sorts of elements could have access lists for
283 which avatars own them and which avatars are allowed to interact
284 with them (in which case maybe the term *visitor* for the latter is
285 too area-specific and it needs a slightly more general term?).
286
287 However, a far more immersive solution would be to get closable and
288 lockable elements (and inventory management) implemented, have a
289 means of crafting keys which unlock specific elements, and then when
290 creating an area you want to restrict to specific avatars, put
291 locked doors for all its portals and give keys for them to anyone
292 you want to be able to visit. Or implement non-avatar actors, then
293 create guards you can hire or summon or construct to police your
294 doorways and check whether visitors are on a list. That list itself
295 could even be a piece of paper or a book (a prop), created with
296 appropriate materials and some skill which allows the actor to write
297 and edit paper notes, given to the guard and held by it as an
298 inventory item.
299
300 custom commands
301 ~~~~~~~~~~~~~~~
302
303 Command definitions are split into metadata and procedure. The
304 metadata needs to be in an element like the basic ones shipped in
305 the :file:`share/command.yaml` file, and then a handler function
306 added to the :mod:`mudpy.command` module. There's not yet a plugin
307 layer to allow those to be added to a separate module. The function
308 name needs to match the element base name, or you have to add an
309 action facet to the element indicating the name of the function you
310 want it to call; the ``command.set`` element has an example of this,
311 so that we avoid shadowing Python's built-in :func:`set` function
312 and call :func:`mudpy.command.c_set` instead.
313
314 The :meth:`mudpy.misc.Element.set` method takes two parameters, the
315 name of the facet and the value to pass into it. An example of it in
316 action is :meth:`mudpy.misc.User.authenticate` where the user's
317 *administrator* facet is set to the value *True* if their username
318 is in the ``.mudpy.limit.admins`` list used to bootstrap
319 administrators:
320
321 .. code-block:: python
322     :emphasize-lines: 9
323
324     def authenticate(self):
325         """Flag the user as authenticated and disconnect duplicates."""
326         if self.state != "authenticated":
327             self.authenticated = True
328             log("User %s authenticated for account %s." % (
329                     self, self.account.subkey), 2)
330             if ("mudpy.limit" in universe.contents and self.account.subkey in
331                     universe.contents["mudpy.limit"].get("admins")):
332                 self.account.set("administrator", True)
333                 log("Account %s is an administrator." % (
334                         self.account.subkey), 2)