Add pygments parsing of code

This commit is contained in:
Griatch 2021-11-16 01:47:08 +01:00
parent fe5f47ef6d
commit 930f5beaa8
8 changed files with 132 additions and 115 deletions

View file

@ -750,19 +750,17 @@
<p>Digging around a bit I found <a href="https://github.com/trentm/googlecode2github.git">googlecode2github</a>. This download contains python scripts for converting the wiki as well as Issues. I didn't really get the issues-converter to work, so I had to find another solution for that (see next section).</p>
<p>All in all, the initial wiki conversion worked decently - all the pages were converted over and were readable. I was even to the point of declaring success when finding the damn thing messed up the links. Googe Code writes links like this: [MyLink Text to see on page]. The script converted this to [[MyLink|Text to see on page]]. Which may look fine except it isn't. GitHub actually wants the syntax in the inverse order: [[Text to see on page|MyLink]].</p>
<p>Furthermore, in Google Code's wiki, code blocks were marked with</p>
<pre><code>
{{{
<div class="highlight" style="background: #f8f8f8"><pre style="line-height: 125%;"><span></span>{{{
&lt;verbatim code&gt;
}}}
</pre></div>
</code></pre>
<p>In markdown, code blocks are created just by indenting the block by four spaces. The converter dutifully did this - but it didn't add empty lines above and below the block, which is another thing markdown requires. The result was that all code ended up mixed into the running text output.</p>
<p>I could have gone back and fixed the converter script, but I suspected there would be enough small things to fix anyway. So in the end I went through 80+ pages of fixing link syntax and adding empty lines by hand. After that I could finally push the first converted wiki version up to the GitHub wiki repository.</p>
<p>Some time later I also found that there is a way to let GitHub wiki pages use syntax highlighting for the language of your choice. The way to do this is to enclose your code blocks like this:</p>
<pre><code>
```python
<div class="highlight" style="background: #f8f8f8"><pre style="line-height: 125%;"><span></span>```python
@ -771,8 +769,8 @@
```
</pre></div>
</code></pre>
<p>This is apparently &quot;GitHub-flavoured&quot; markdown. So another stint into all the pages followed, to update everything for prettiness.</p>
<h3>Converting Google Code Issues</h3>
<p>I didn't want to loose our Issues from Google Code. I looked around a bit and tested some conversions for this (it helps to be able to create and delete repos on GitHub with abandon when things fail). I eventually settled on <a href="https://github.com/arthur-debert/google-code-issues-migrator">google-code-issues-migrator</a>.</p>

View file

@ -566,46 +566,45 @@
<h2>Evennia Autodocs</h2>
<p>Following the big library merger I sat down to write a more comprehensive autodoc utility. We had been distributing a Doxygen config file with the repo for a long time, but I wanted something that integrated with our github wiki, using markdown in the source (because frankly, while Sphinx produces very pretty output, ReST markup looks really ugly in source code, in my opinion).</p>
<p>The result was the api2md program, which is now a part of our wiki repository. It allows our source code to be decorated in &quot;Google style&quot;, very readable output:</p>
<pre><code class="language-python">
def funcname(a, b, c, d=False): &quot;&quot;&quot;
<div class="highlight" style="background: #f8f8f8"><pre style="line-height: 125%;"><span></span><span style="color: #008000; font-weight: bold">def</span> <span style="color: #0000FF">funcname</span>(a, b, c, d<span style="color: #666666">=</span><span style="color: #008000; font-weight: bold">False</span>): <span style="color: #BA2121">&quot;&quot;&quot; </span>
    This is a brief introduction to the
<span style="color: #BA2121">    This is a brief introduction to the</span>
function/class/method
<span style="color: #BA2121"> function/class/method </span>
<span style="color: #BA2121"> </span>
    Args:
<span style="color: #BA2121">    Args: </span>
        a (str): This is a string argument that
<span style="color: #BA2121">        a (str): This is a string argument that</span>
we can talk about over multiple lines.
<span style="color: #BA2121"> we can talk about over multiple lines. </span>
        b (int or str): Another argument
<span style="color: #BA2121">        b (int or str): Another argument </span>
        c (list): A list argument
<span style="color: #BA2121">        c (list): A list argument </span>
        d (bool, optional): An optional keyword argument
<span style="color: #BA2121">        d (bool, optional): An optional keyword argument </span>
<span style="color: #BA2121"> </span>
    Returns:
<span style="color: #BA2121">    Returns: </span>
        str: The result of the function
<span style="color: #BA2121">        str: The result of the function </span>
<span style="color: #BA2121"> </span>
    Notes:
<span style="color: #BA2121">    Notes: </span>
        This is an example function. If `d=True`, something
<span style="color: #BA2121">        This is an example function. If `d=True`, something </span>
amazing will happen.
<span style="color: #BA2121"> amazing will happen. </span>
<span style="color: #BA2121"> </span>
    &quot;&quot;&quot;
<span style="color: #BA2121">    &quot;&quot;&quot;</span>
</pre></div>
</code></pre>
<p>This will be parsed and converted to a Markdown entry and put into the Github wiki, one page per module. The result is the automatically generated <a href="https://github.com/evennia/evennia/wiki/evennia">Evennia API autodocs</a>, reachable as any other wiki page.</p>
<p>The convertion/prettification of all core functions of Evennia to actually <em>use</em> the Google-style docstrings took almost all year, finishing late in autumn. But now almost all of Evennia uses this style. Coincidentally this also secures us at a 45% comment/code ratio. This places us in the top 10% of well-documented open-source projects according to <a href="https://www.openhub.net/p/evennia/factoids#FactoidCommentsVeryHigh">openhub</a> (gotta love statistics).</p>
<h2>Imaginary realities / Optional Realities</h2>
@ -1079,16 +1078,15 @@ def funcname(a, b, c, d=False): &quot;&quot;&quot;
<h2>Evennia as a Python library package</h2>
<p>Evennia has until now been solely distributed as a version controlled source tree (first under SVN, then Mercurial and now via GIT and Github). In its current inception you clone the tree and find inside it a game/ directory where you create your game. A problem we have when helping newbies is that we can't easily put pre-filled templates in there - if people used them there might be merge conflicts when we update the templates upstream. So the way people configure Evennia is to make copies of template modules and then change the settings to point to that copy rather than the default module. This works well but it means a higher threshold of setup for new users and a lot of describing text. Also, while learning GIT is a useful skill, it's another hurdle to get past for those who just want to change something minor to see if Evennia is for them.</p>
<p>In the devel branch, Evennia is now a library. The game/ folder is no longer distributed as part of the repository but is created dynamically by using the new binary evennia launcher program, which is also responsible for creating (or migrating) the database as well as operating the server:</p>
<pre><code>
evennia --init mygame
<div class="highlight" style="background: #f8f8f8"><pre style="line-height: 125%;"><span></span>evennia --init mygame
cd mygame
evennia migrate
evennia start
</pre></div>
</code></pre>
<p>Since this new folder is <em>not</em> under our source tree, we can set up and copy pre-made template modules to it that people can just immediately start filling in without worrying about merge conflicts. We can also dynamically create a setting file that fits the environment as well as set up a correct tree for overloading web functionality and so on. It also makes it a lot easier for people wanting to create multiple games and to put their work under separate version control.</p>
<p>Rather than traversing the repository structure as before you henceforth will just do import evennia in your code to have access to the entirety of the API. And finally this means it will (eventually) be possible to install Evennia from <a href="https://pypi.python.org/pypi/Evennia-MUD-Server/Beta">pypi</a> with something like pip install evennia. This will greatly ease the first steps for those not keen on learning GIT.</p>
<h2>For existing users</h2>

View file

@ -797,14 +797,16 @@
</ol>
<h2>The MonitorHandler</h2>
<p><strong>evennia.MONITOR_HANDLER</strong> is the new singleton managing monitoring of on-object field/attribute changes. It is used like this:</p>
<pre><code>MONITOR_HANDLER.add(obj, field_or_attrname, callback, **kwargs)
</code></pre>
<div class="highlight" style="background: #f8f8f8"><pre style="line-height: 125%;"><span></span>MONITOR_HANDLER.add(obj, field_or_attrname, callback, **kwargs)
</pre></div>
<p>Here <strong>obj</strong> is a database entity, like a Character or another Object. The <strong>field_or_attrname</strong> is a string giving the name of a <strong>db_</strong>* database field (like <strong>&quot;db_key&quot;, &quot;db_location&quot;</strong> etc). Any name not starting with <strong>db_</strong> is assumed to be the name of an on-object Attribute (like <strong>&quot;health&quot;</strong>). Henceforth, whenever this field or attribute changes in any way (that is, whenever it is re-saved to the database), the <strong>callback</strong> will be called with the optional <strong>kwargs</strong>, as well as a way to easily get to the changed value. As all handlers you can also list and remove monitors using the standard <strong>MONITOR_HANDLER</strong>.<strong>remove()</strong>, <strong>.all()</strong> etc.</p>
<h2>The TickerHandler</h2>
<p><strong>evennia.TICKER_HANDLER</strong> should be familiar to Evennia users from before - it's been around for a good while. It allows for creating arbitrary &quot;tickers&quot; that is being &quot;subscribed&quot; to - one ticker will call all subscribers rather than each object or function having its own timer.</p>
<p>Before, the syntax for adding a new ticker required you specify a typeclassed entity and the name of the method on it to call every N seconds. This will now change. This is the new callsign for creating a new ticker:</p>
<pre><code>TICKER_HANDLER.add(interval, callback, idstring=&quot;&quot;, persistent=True, *args, **kwargs)
</code></pre>
<div class="highlight" style="background: #f8f8f8"><pre style="line-height: 125%;"><span></span>TICKER_HANDLER.add(interval, callback, idstring=&quot;&quot;, persistent=True, *args, **kwargs)
</pre></div>
<p>Here**, interval,** like before, defines how often to call **callback(*args, <strong>kwargs)</strong>.</p>
<p>The big change here is that <strong>callback</strong> should be given as a valid, already imported callable, which can be <em>either</em> an on-entity method (like obj.func) or a global function in any module (like world.test.func) - the TickerHandler will analyze it and internally store it properly.</p>
<p><strong>idstring</strong> works as before, to separate tickers with the same intervals. Finally <strong>persistent</strong>=<strong>False</strong> means the ticker will behave the same way a Script with <strong>persistent=False</strong> does: it will survive a server reload but will <em>not</em> survive a server shutdown. This latter functionality is particularly useful for client-side commands since the client Session will also not survive a shutdown.</p>

View file

@ -817,38 +817,37 @@
<p>Next I added one new migration in the Account app - this is the migration that copies the data from the player to the equivalent account-named copies in the database. Since Player will not exist if you run this from scratch you have to make sure that the Player model exists at that point in the migration chain. You can't just do this with a normal import and traceback, you need to use the migration infrastructure. This kind of check works:</p>
</li>
</ul>
<pre><code class="language-python">
  # ...
<div class="highlight" style="background: #f8f8f8"><pre style="line-height: 125%;"><span></span>  <span style="color: #408080; font-style: italic"># ... </span>
  def forwards(apps, schema_editor):
  <span style="color: #008000; font-weight: bold">def</span> <span style="color: #0000FF">forwards</span>(apps, schema_editor):
    try:
    <span style="color: #008000; font-weight: bold">try</span>:
        PlayerDB = apps.get_model(&quot;players&quot;, &quot;PlayerDB&quot;)
        PlayerDB <span style="color: #666666">=</span> apps<span style="color: #666666">.</span>get_model(<span style="color: #BA2121">&quot;players&quot;</span>, <span style="color: #BA2121">&quot;PlayerDB&quot;</span>)
    except LookupError:
    <span style="color: #008000; font-weight: bold">except</span> <span style="color: #D2413A; font-weight: bold">LookupError</span>:
        return
        <span style="color: #008000; font-weight: bold">return</span>
    # copy data from player-tables to database tables here
    <span style="color: #408080; font-style: italic"># copy data from player-tables to database tables here </span>
 class Migrations(migrations.Migration):
 <span style="color: #008000; font-weight: bold">class</span> <span style="color: #0000FF; font-weight: bold">Migrations</span>(migrations<span style="color: #666666">.</span>Migration):
    # ...
    <span style="color: #408080; font-style: italic"># ... </span>
    operations = [
    operations <span style="color: #666666">=</span> [
        migrations.RunPython(forwards, migrations.RunPython.noop)
        migrations<span style="color: #666666">.</span>RunPython(forwards, migrations<span style="color: #666666">.</span>RunPython<span style="color: #666666">.</span>noop)
    ]
</pre></div>
</code></pre>
<ul>
<li>
<p>Now, a lot of my other apps/models has ForeignKey or Many2Many relations to the Player model. Aided by viewing the tables in the database I visited all of those and added a migration to each where I duplicated the player-relation with a duplicate account relation. So at this point I had set up a parallel, co-existing duplicate of the Player model, named Account.</p>
@ -860,24 +859,23 @@
<p>Since the player folder is not there, it's migrations are not there either. So in another app (any would work) I made a migration to remove the player tables from the database. Thing is, the missing player app means other migrations also cannot reference it. It is possible I could have waited to remove the player/ folder so as to be able to do this bit in pure Python. On the other hand, that might have caused issues since you would be trying to migrate with two Auth users - not sure. As it were, I ended up purging the now useless player-tables with raw SQL:</p>
</li>
</ul>
<pre><code class="language-python">
from django.db import connection
<div class="highlight" style="background: #f8f8f8"><pre style="line-height: 125%;"><span></span> <span style="color: #008000; font-weight: bold">from</span> <span style="color: #0000FF; font-weight: bold">django.db</span> <span style="color: #008000; font-weight: bold">import</span> connection
    # ...
    <span style="color: #408080; font-style: italic"># ... </span>
    def _table_exists(db_cursor, tablename):
    <span style="color: #008000; font-weight: bold">def</span> <span style="color: #0000FF">_table_exists</span>(db_cursor, tablename):
    &quot;Returns bool if table exists or not&quot;
    <span style="color: #BA2121">&quot;Returns bool if table exists or not&quot;</span>
    sql_check_exists = &quot;SELECT * from %s;&quot; % tablename
    sql_check_exists <span style="color: #666666">=</span> <span style="color: #BA2121">&quot;SELECT * from </span><span style="color: #BB6688; font-weight: bold">%s</span><span style="color: #BA2121">;&quot;</span> <span style="color: #666666">%</span> tablename
    ### [Renaming Django&#x27;s Auth User and App](https://evennia.blogspot.com/2017/08/renaming-djangos-auth-user-and-app.html)
    <span style="color: #408080; font-style: italic">### [Renaming Django&#39;s Auth User and App](https://evennia.blogspot.com/2017/08/renaming-djangos-auth-user-and-app.html)</span>
</pre></div>
</code></pre>
<p><a href="https://4.bp.blogspot.com/-DRHk1mmLB0Y/WaCUd8tmYbI/AAAAAAAAHaU/QXkryhYVJBIVWPykT08nSokCHfFc6-2LACLcBGAs/s1600/birds-1976981_640.jpg"><img src="https://4.bp.blogspot.com/-DRHk1mmLB0Y/WaCUd8tmYbI/AAAAAAAAHaU/QXkryhYVJBIVWPykT08nSokCHfFc6-2LACLcBGAs/s400/birds-1976981_640.jpg" alt="" /></a></p>
<p>And with this, the migration's design was complete. Below is how to actually <em>use</em> it ...</p>
<h3>Running the final migration</h3>

View file

@ -585,12 +585,11 @@
<p>In our development branch I've just pushed the first version of the new OLC (OnLine Creator) system. This is a system to allow builders (who may have limited coding knowledge) to customize and spawn new in-game objects more easily without code access. It's started with the <strong>olc</strong> command in-game. This is a visual system for manipulating Evennia <em>Prototypes</em>.</p>
<h3>Briefly on Prototypes</h3>
<p>The <em>Prototype</em> is an Evennia concept that has been around a good while. The prototype is a Python dictionary that holds specific keys with values representing properties on a game object. Here's an example of a simple prototype:</p>
<pre><code>
{&quot;key&quot;: &quot;My house&quot;,
<div class="highlight" style="background: #f8f8f8"><pre style="line-height: 125%;"><span></span> {&quot;key&quot;: &quot;My house&quot;,
&quot;typeclass&quot;: &quot;typeclasses.houses.MyHouse&quot;}
</pre></div>
</code></pre>
<p>By passing this dict to the spawner, a new object named &quot;My house&quot; will be created. It will be set up with the given typeclass (a 'typeclass' is, in Evennia lingo, a Python class with a database backend). A prototype can specify all aspects of an in-game object - its attributes (like description and other game-specific properties), tags, aliases, location and so on. Prototypes also support inheritance - so you can expand on an existing template without having to add everything fresh every time.</p>
<p>There are two main reasons for the Prototypes existing in Evennia:</p>
<ul>

View file

@ -653,115 +653,112 @@
<p><strong>&gt; command [target]</strong></p>
<p>The parsing I made actually allows for a more complex syntax, but in the end this was all that was really needed, since the currently 'focused' object does not need to be specified. This is the process of using one object with another:</p>
<p><strong>&gt; examine key</strong></p>
<pre><code>
~~ key (examining) ~~
<div class="highlight" style="background: #f8f8f8"><pre style="line-height: 125%;"><span></span>~~ key (examining) ~~
This is a brass key.
(To unlock something with it, use insert into &lt;target&gt;)
</pre></div>
</code></pre>
<p><strong>&gt; insert into door</strong></p>
<pre><code>You unlock the **door** with the **key**!
</code></pre>
<div class="highlight" style="background: #f8f8f8"><pre style="line-height: 125%;"><span></span>You unlock the **door** with the **key**!
</pre></div>
<p>(the <em>into</em> is optional). Here, we focus on the key. We get the key's description and a hint that you can <em>insert</em> it into things. We then insert it into the door, which is another object in the room. The <em>insert</em> command knows that we are focusing on the key already and that it should look into the room for an object door to use this with.</p>
<p>Technically, these on-object 'actions' (like <em>insert</em> above), are dynamically generated. Here is an example of the key object:</p>
<pre><code class="language-python">
class Key(EvscaperoomObject):
<div class="highlight" style="background: #f8f8f8"><pre style="line-height: 125%;"><span></span><span style="color: #008000; font-weight: bold">class</span> <span style="color: #0000FF; font-weight: bold">Key</span>(EvscaperoomObject):
def at_focus_insert(self, caller, **kwargs):
<span style="color: #008000; font-weight: bold">def</span> <span style="color: #0000FF">at_focus_insert</span>(<span style="color: #008000">self</span>, caller, <span style="color: #666666">**</span>kwargs):
target = kwargs[&#x27;args&#x27;]
target <span style="color: #666666">=</span> kwargs[<span style="color: #BA2121">&#39;args&#39;</span>]
obj = caller.search(obj)
obj <span style="color: #666666">=</span> caller<span style="color: #666666">.</span>search(obj)
if not obj:
<span style="color: #008000; font-weight: bold">if</span> <span style="color: #AA22FF; font-weight: bold">not</span> obj:
return
<span style="color: #008000; font-weight: bold">return</span>
if obj.check_flag(&quot;can_use_key&quot;):
<span style="color: #008000; font-weight: bold">if</span> obj<span style="color: #666666">.</span>check_flag(<span style="color: #BA2121">&quot;can_use_key&quot;</span>):
obj.handle_insert(self)
obj<span style="color: #666666">.</span>handle_insert(<span style="color: #008000">self</span>)
</pre></div>
</code></pre>
<p>Not shown here is that I made a wrapper for the &quot;no-match&quot; command of Evennia. This command fires when no other commands match. I made this instead analyze the currently 'focused' object to see if it had a method at_focus_&lt;command_name&gt; on it. If so, I inject the supplied arguments into that method as a keyword argument <em>args</em>.</p>
<p>So when you focus on the key and give the <em>insert</em> command, the at_focus_insert method on the key will be called with a target to insert the key into_._ We search for the target (the door in the example), check if it even accepts keys and then pass the key to that object to handle. It would then be up to the door to figure out if this particular key unlocks it.</p>
<p>I created a library of base objects that I can just use as mixins for the object I want to create. Here's an example:</p>
<pre><code class="language-python">
from evscaperoom import objects
<div class="highlight" style="background: #f8f8f8"><pre style="line-height: 125%;"><span></span><span style="color: #008000; font-weight: bold">from</span> <span style="color: #0000FF; font-weight: bold">evscaperoom</span> <span style="color: #008000; font-weight: bold">import</span> objects
class Box(objects.Openable,
<span style="color: #008000; font-weight: bold">class</span> <span style="color: #0000FF; font-weight: bold">Box</span>(objects<span style="color: #666666">.</span>Openable,
objects.CodeInput,
objects<span style="color: #666666">.</span>CodeInput,
objects.Movable):
objects<span style="color: #666666">.</span>Movable):
# ...
<span style="color: #408080; font-style: italic"># ... </span>
</pre></div>
</code></pre>
<p>This class will offer actions to open, insert a code and move the object around. It will need some more configuration and addition of messages to show etc. But overall, this method-to-command solution ended up being very stable and extremely easy to use to make complex, interactive objects.</p>
<h3>Room states</h3>
<p>I think of the escape room as going through a series of <em>states</em>. A change of state could for example be that the user solved a puzzle to open a secret wall. That wall is now open, making new items and puzzles available. This means room description should change along with new objects being created or old ones deleted.</p>
<p>I chose to represent states as Python modules in a folder. To be a state, each module needs to have a global-level class <strong>State</strong> inheriting from my new <strong>BaseState</strong> class. This class has methods for initializing and cleaning up the state, as well as was for figuring out which state to go to next. As the system initializes the new state, it gets the current room as argument, so it can modify it.</p>
<p>This is a (simplified) example of a state module:</p>
<pre><code class="language-python">
# module `state_001_start.py`
<div class="highlight" style="background: #f8f8f8"><pre style="line-height: 125%;"><span></span><span style="color: #408080; font-style: italic"># module `state_001_start.py` </span>
from evscaperoom.state import BaseState
<span style="color: #008000; font-weight: bold">from</span> <span style="color: #0000FF; font-weight: bold">evscaperoom.state</span> <span style="color: #008000; font-weight: bold">import</span> BaseState
from evscaperoom import objects
<span style="color: #008000; font-weight: bold">from</span> <span style="color: #0000FF; font-weight: bold">evscaperoom</span> <span style="color: #008000; font-weight: bold">import</span> objects
MUG_DESC = &quot;&quot;&quot;
MUG_DESC <span style="color: #666666">=</span> <span style="color: #BA2121">&quot;&quot;&quot; </span>
A blue mug filled with a swirling liquid.
<span style="color: #BA2121">A blue mug filled with a swirling liquid. </span>
On it is written &quot;DRINK ME&quot; with big letters.
<span style="color: #BA2121">On it is written &quot;DRINK ME&quot; with big letters. </span>
&quot;&quot;&quot;
<span style="color: #BA2121">&quot;&quot;&quot;</span>
class Mug(objects.EvscapeRoomObject):
<span style="color: #008000; font-weight: bold">class</span> <span style="color: #0000FF; font-weight: bold">Mug</span>(objects<span style="color: #666666">.</span>EvscapeRoomObject):
def at_focus_drink(self, caller, **kwargs):
<span style="color: #008000; font-weight: bold">def</span> <span style="color: #0000FF">at_focus_drink</span>(<span style="color: #008000">self</span>, caller, <span style="color: #666666">**</span>kwargs):
caller.msg(f&quot;You drink {self.key}.&quot;)
caller<span style="color: #666666">.</span>msg(<span style="color: #BA2121">f&quot;You drink </span><span style="color: #BB6688; font-weight: bold">{</span><span style="color: #008000">self</span><span style="color: #666666">.</span>key<span style="color: #BB6688; font-weight: bold">}</span><span style="color: #BA2121">.&quot;</span>)
self.next_state() # trigger next state
<span style="color: #008000">self</span><span style="color: #666666">.</span>next_state() <span style="color: #408080; font-style: italic"># trigger next state </span>
class State(BaseState):
<span style="color: #008000; font-weight: bold">class</span> <span style="color: #0000FF; font-weight: bold">State</span>(BaseState):
hints = [&quot;You are feeling a little thirsty...&quot;,
hints <span style="color: #666666">=</span> [<span style="color: #BA2121">&quot;You are feeling a little thirsty...&quot;</span>,
&quot;Drink from the mug, dummy.&quot;]
<span style="color: #BA2121">&quot;Drink from the mug, dummy.&quot;</span>]
next_state = &quot;state_002_big_puzzle&quot;
next_state <span style="color: #666666">=</span> <span style="color: #BA2121">&quot;state_002_big_puzzle&quot;</span>
def init(self):
<span style="color: #008000; font-weight: bold">def</span> <span style="color: #0000FF">init</span>(<span style="color: #008000">self</span>):
mug = self.create_object(
mug <span style="color: #666666">=</span> <span style="color: #008000">self</span><span style="color: #666666">.</span>create_object(
Mug, key=&quot;wooden mug&quot;, aliases=[&quot;mug&quot;])
Mug, key<span style="color: #666666">=</span><span style="color: #BA2121">&quot;wooden mug&quot;</span>, aliases<span style="color: #666666">=</span>[<span style="color: #BA2121">&quot;mug&quot;</span>])
mug.db.desc = MUG_DESC.strip()
mug<span style="color: #666666">.</span>db<span style="color: #666666">.</span>desc <span style="color: #666666">=</span> MUG_DESC<span style="color: #666666">.</span>strip()
</pre></div>
</code></pre>
<p>In this simple state, a mug is created, and when you drink from it, the next state is triggered. The base object has a helper function to trigger the next state since I found that interactive with an object is almost always the reason for switching states.</p>
<p>The state-class has a lot of useful properties to set, such as which the next state should be (this can be overridden in case of branching paths). You can also store</p>
<p>a sequence of hints specific for that state.</p>
@ -770,19 +767,20 @@ class State(BaseState):
<p>As the amount of text grew (the Evscaperoom has close to 10 000 lines of code, a lot of which is content strings), it became clear that it would not be feasible to manually supply third-persion version strings as well.</p>
<p>The solution was to add parsing and translation of pronouns and verbs (a concept I first saw on the game <em>Armageddon</em>).</p>
<p>I write the string like this:</p>
<pre><code>
OPEN_TEXT = &quot;~You ~open the *door.&quot;
<div class="highlight" style="background: #f8f8f8"><pre style="line-height: 125%;"><span></span> OPEN_TEXT = &quot;~You ~open the *door.&quot;
</pre></div>
</code></pre>
<p>The ~ marks text that should be parsed for second/third-person use (I'll discuss the *door marking in the next section). This I then send to a helper method that either sends it only to you (which means it comes back pretty much the same, but without the special markers) or to you <em>and</em> to the room, in which it will look different depending on who receives it:</p>
<p>I see</p>
<pre><code>You open the [door].
<div class="highlight" style="background: #f8f8f8"><pre style="line-height: 125%;"><span></span>You<span style="color: #bbbbbb"> </span><span style="color: #008000; font-weight: bold">open</span><span style="color: #bbbbbb"> </span>the<span style="color: #bbbbbb"> </span><span style="color: #666666">[</span>door<span style="color: #666666">]</span>.<span style="color: #bbbbbb"></span>
<span style="color: #bbbbbb"> </span>
</pre></div>
</code></pre>
<p>Others see</p>
<pre><code>Griatch opens the [door].
</code></pre>
<div class="highlight" style="background: #f8f8f8"><pre style="line-height: 125%;"><span></span>Griatch<span style="color: #bbbbbb"> </span>opens<span style="color: #bbbbbb"> </span>the<span style="color: #bbbbbb"> </span><span style="color: #666666">[</span>door<span style="color: #666666">]</span>.<span style="color: #bbbbbb"> </span>
</pre></div>
<p>English is luckily pretty easy to use for this kind of automatic translation - in general you can just add an &quot;s&quot; to the end of the verb. I made a simple mapping for the few irregular verbs I ended up using.</p>
<p>Overall, this made it quick to present multiple viewpoints with minimal extra text to write.</p>
<p><a href="https://1.bp.blogspot.com/-HdLAfVl8P5A/XOAWjd_zoDI/AAAAAAAAKek/KnWDYcsFZP0w1Fb9DEFTd64BkWmnAgIFACPcBGAYYCw/s1600/Screenshot%2Bfrom%2B2019-05-17%2B11-49-20.png"><img src="https://1.bp.blogspot.com/-HdLAfVl8P5A/XOAWjd_zoDI/AAAAAAAAKek/KnWDYcsFZP0w1Fb9DEFTd64BkWmnAgIFACPcBGAYYCw/s320/Screenshot%2Bfrom%2B2019-05-17%2B11-49-20.png" alt="Shows the various accessibility options for showing items." title="option menu" /></a></p>

View file

@ -20,16 +20,21 @@ Here starts the main text ...
"""
import glob
import shutil
from dataclasses import dataclass
from collections import defaultdict
from dateutil import parser as dateparser
from datetime import datetime
from os import mkdir, symlink, remove, chdir
from os import symlink, remove, chdir
from os.path import abspath, dirname, join as pathjoin, sep
import mistletoe
import jinja2
import mistletoe
from mistletoe import HTMLRenderer
from pygments import highlight
from pygments.styles import get_style_by_name as get_style
from pygments.lexers import get_lexer_by_name as get_lexer, guess_lexer
from pygments.formatters.html import HtmlFormatter
CURRDIR = dirname(abspath(__file__))
SOURCE_DIR = pathjoin(CURRDIR, "markdown")
@ -83,6 +88,24 @@ class BlogPage:
calendar: dict
class PygmentsRenderer(HTMLRenderer):
"""
Custom syntax highlighter for misteltoe (based on
https://github.com/miyuchina/mistletoe/blob/master/contrib/pygments_renderer.py)
"""
formatter = HtmlFormatter()
formatter.noclasses = True
def __init__(self, *extras, style='default'):
super().__init__(*extras)
self.formatter.style = get_style(style)
def render_block_code(self, token):
code = token.children[0].content
lexer = get_lexer(token.language) if token.language else guess_lexer(code)
return highlight(code, lexer, self.formatter)
def md2html():
"""
Generate all blog pages, with one page per year.
@ -137,7 +160,7 @@ def md2html():
markdown_post = "\n".join(lines)
# convert markdown to html
html = mistletoe.markdown(markdown_post)
html = mistletoe.markdown(markdown_post, PygmentsRenderer)
# build the permalink
anchor = "{}".format(

View file

@ -526,8 +526,9 @@ a[href="devblog-posts"]:focus {
width: initial;
float: none;
}
.blog_post > .highlight > pre {
line-height: 0.1em!important;
}
/* Media alternatives */