## How to Install Glassfish

Glassfish is a Java Library for creating Java Message Queues. Regular readers my be surprised to know I am also a Java coder since I usually talk about Python and C++, but just as I occasionally speak Italian, I am multilingual. In this case, though, this is all about Java.

Unfortunately, despite what it’s billed to be, Glassfish 5 isn’t as turn-key an application as it appears to be and therefore I wanted to fill in the gaps for would be message queuers—whatever the word is—who may be struggling.

When you check out the readme file, you’ll see the following text:

2. Starting GlassFish=====================The 'asadmin' command-line utility is used to control and manage GlassFish (start, stop, configure, deploy applications, etc).To start GlassFish, just go in the directory where GlassFish is located and type:        On Unix: glassfish5/glassfish/bin asadmin start-domain        On Windows: glassfish5\glassfish\bin asadmin start-domainAfter a few seconds, GlassFish will be up and ready to accept requests. The default 'domain1' domain is configured to listen on port 8080. In your browser, go to http://localhost:8080 to see the default landing page.To manage GlassFish, just go to web administration console: http://localhost:4848

That’s all well and good, but, if you’ve never used Glassfish before, when you follow those steps, you’ll see the following cryptic error:

Exception in thread “main” java.lang.NullPointerException: Cannot invoke “org.glassfish.hk2.api.DynamicConfigurationService.createDynamicConfiguration()” because “dcs” is null
at com.sun.enterprise.module.common_impl.AbstractModulesRegistryImpl.initializeServiceLocator(AbstractModulesRegistryImpl.java:152)
at com.sun.enterprise.module.common_impl.AbstractModulesRegistryImpl.newServiceLocator(AbstractModulesRegistryImpl.java:144)
at com.sun.enterprise.module.common_impl.AbstractModulesRegistryImpl.createServiceLocator(AbstractModulesRegistryImpl.java:218)
at com.sun.enterprise.module.common_impl.AbstractModulesRegistryImpl.createServiceLocator(AbstractModulesRegistryImpl.java:224)
at com.sun.enterprise.module.single.StaticModulesRegistry.createServiceLocator(StaticModulesRegistry.java:88)

$glassfish5/glassfish/bin/asadmin start-domain Clearly, Glashfish is not a turn-key installation. From here, it was up to me. Google was no help. "dcs" is null as a search term was too generic and including the full, topmost error only gave a page with sample Java code, not how to actually start the server. To the best of my ability, I believe the error is related to the DynamicConfigurationService object—that’s what dcs stands for. Looking through the QuickStart document I thought maybe it’s because I didn’t install to my home directory, ~, but moving it there produced the same results. The next thing to try is to downgrade to Java 8. I’m not fond of Java 8 as it was one of the last Java versions to be 32-bit—which is incompatible with MacOS Catalina—but fortunately, Oracle provides a 64-bit, Catalina-compatible version. Once Java 8 was installed, I just needed to tell my terminal to use that version instead of the default one. First, I needed to get the location for Java 8 in the list of installed Java VMs: $ /usr/libexec/java_home -VMatching Java Virtual Machines (3):    15, x86_64:         "OpenJDK 15" /Users/username/Library/Java/JavaVirtualMachines/openjdk-15/Contents/Home    13.0.4, x86_64:     "Zulu 13.33.25" /Users/username/Library/Java/JavaVirtualMachines/azul-13.0.4/Contents/Home    1.8.0_261, x86_64:  "Java SE 8" /Library/Java/JavaVirtualMachines/jdk1.8.0_261.jdk/Contents/Home/Users/username/Library/Java/JavaVirtualMachines/openjdk-15/Contents/Home$ What Java VMs are available? Finally, I had to set the local Terminal to use the Java 8 VM: $ export JAVA_HOME=/usr/libexec/java_home -v 1.8.0_261$ Set the Java VM to Java 8. Success! $ glassfish5/glassfish/bin/asadmin start-domainWaiting for domain1 to start ......Successfully started the domain : domain1domain  Location: /Users/username/glassfish5/glassfish/domains/domain1Log File: /Users/username/glassfish5/glassfish/domains/domain1/logs/server.logAdmin Port: 4848Command start-domain executed successfully.$ Starting Glassfish! I hope that helps and I am so happy with my new job! ## Dusting off the Résumé, Part 2: Converting to HTML Earlier, I shared with you the Schema I use for writing my Résumé in XML. In this post, I want to talk about how that XML becomes something human-readable: Hyper Text Markup Language (HTML). I wrote my first Web Page in 1993, one year after Tim Berners-Lee released the now ubiquitous language of the Internet. Back then, to get information on the Internet you were restricted to dial-up Bulletin Board Systems (BBS), Gopher and Archie for file search and retrieval, and Usenet for staying abreast of the latest computer news or just chew the fat. I was, in fact, a very early adopter, immediately seeing the potential of HTML to revolutionize the world of digital communications. What’s more, unlike JavaScript Object Notation (JSON), which is best for HTML POST requests and related operations, because HTML is a markup language and a full subset of XML, it makes more sense to encode my work history and Curriculum Vitae (CV). This is why I continue to maintain my Résumé and the longer form CV in XML and don’t convert it to JSON. That said, it would be fascicle to convert XML to JSON and many application already do this. The translation from XML to HTML, however, takes more finesse. Fortunately, there’s a subset of XML known as eXtensible Stylesheet Language Transformation (XSLT). XSLT is a rudimentary rule-based language with recursion. It allows me to iterate over fields and concatenate them with commas (,), or hide fields which are deprecated as no longer relevant to the modern software job market. As such, XSLT is a very verbose language which requires many elements to define how each XML component will be handled. Nonetheless, the full transform is included below. <?xml version="1.0" encoding="ISO-8859-1"?><xsl:stylesheet version="1.0"xmlns:xsl="http://www.w3.org/1999/XSL/Transform"><xsl:param name="amazon" select="'http://www.amazon.com/exec/obidos/ASIN/'"/><xsl:param name="deprecated_text" select="'Hidden text has been excized from this document!'"/><xsl:param name="redacted_text" select="'Redacted text has been blacked out from this document!'"/><xsl:param name="col_of_data" select="2"/><xsl:variable name="deprecated"> <span class="deprecated">deprecated</span> <xsl:comment> <xsl:value-of select="$deprecated_text"/>  </xsl:comment></xsl:variable><xsl:variable name="redacted">  <span class="redacted">redacted</span>  <xsl:comment>    <xsl:value-of select="$redacted_text"/> </xsl:comment></xsl:variable><!-- Entities --><!-- Question: How do I get Entities to appear as entities?? --><!-- Pure (PCDATA) Elements --><!-- Names and Addresses --><xsl:template match="name"> <xsl:apply-templates/></xsl:template><xsl:template match="oldname"> <xsl:apply-templates/></xsl:template><xsl:template match="street"> <xsl:apply-templates/></xsl:template><xsl:template match="apartment"> <xsl:choose> <!-- Use param / param-with --> <xsl:when test="@spellout = 'true'"> <xsl:text disable-output-escaping="yes">Apartment&amp;nbsp;</xsl:text> </xsl:when> <xsl:otherwise> <xsl:text disable-output-escaping="yes">Apt.&amp;nbsp;</xsl:text> </xsl:otherwise> </xsl:choose> <xsl:apply-templates/></xsl:template><xsl:template match="city"> <xsl:apply-templates/></xsl:template><xsl:template match="state"> <xsl:apply-templates/></xsl:template><xsl:template match="province"> <xsl:apply-templates/></xsl:template><xsl:template match="postal"> <xsl:apply-templates/></xsl:template><xsl:template match="country"> <xsl:apply-templates/></xsl:template><xsl:template match="e-mail"> <a href="mailto:{.}"> <xsl:choose> <xsl:when test="parent::address"> <xsl:attribute name="class"> <xsl:text>address</xsl:text> </xsl:attribute> </xsl:when> <xsl:when test="parent::reference"> <xsl:attribute name="class"> <xsl:text>reference</xsl:text> </xsl:attribute> </xsl:when> </xsl:choose> <xsl:apply-templates/> </a></xsl:template><xsl:template match="phone"> <xsl:apply-templates/> (H)</xsl:template><xsl:template match="mobile"> <xsl:apply-templates/> (M)</xsl:template><!-- Keys and Headings --><xsl:template match="heading"> <hr class="separator"/> <h2 class="heading"> <xsl:apply-templates/> </h2></xsl:template><xsl:template match="key"> <span class="text_heading"> <xsl:apply-templates/> </span></xsl:template><xsl:template match="value"> <xsl:apply-templates/></xsl:template><!-- Date and Time --><xsl:template match="year"> <xsl:apply-templates/></xsl:template><xsl:template match="month"> <xsl:apply-templates/></xsl:template><xsl:template match="day"> <xsl:apply-templates/></xsl:template><xsl:template match="hour"> <xsl:apply-templates/></xsl:template><xsl:template match="minute"> <xsl:apply-templates/></xsl:template><xsl:template match="second"> <xsl:apply-templates/></xsl:template><!-- Descriptive Elements --><xsl:template match="position"> <span class="position"> <xsl:apply-templates/> </span></xsl:template><xsl:template match="product"> <span class="product"> <xsl:apply-templates/> </span></xsl:template><xsl:template match="degree"> <span class="degree"> <xsl:apply-templates/> </span></xsl:template><xsl:template match="course"> <span class="course"> <xsl:apply-templates/> </span></xsl:template><xsl:template match="author"> <span class="author"> <xsl:apply-templates/> </span></xsl:template><xsl:template match="author-et-al"> <xsl:text> </xsl:text><span class="author"><em>et al</em></span></xsl:template><xsl:template match="publisher"> <span class="publisher"> <xsl:apply-templates/> </span></xsl:template><xsl:template match="subject"> <p class="subject"> <xsl:apply-templates/> </p></xsl:template><xsl:template match="isbn"> <a class="isbn" href="{$amazon}{.}/">    <xsl:apply-templates/>  </a></xsl:template><!-- deprecated --><xsl:template match="deprecated">  <!-- Ingore deprecated text -->  <xsl:copy-of select="$deprecated"/></xsl:template><!-- Compound Elements --><!-- Compound Time --><xsl:template match="date"> <span class="date"> <xsl:if test="day"> <!-- European Date Format --> <xsl:apply-templates select="day"/> <!-- day requires month, so we know that the month is next --> <xsl:text> </xsl:text> </xsl:if> <xsl:if test="month"> <xsl:apply-templates select="month"/> </xsl:if> <xsl:if test="year"> <xsl:if test="month"> <xsl:text> </xsl:text> </xsl:if> <xsl:apply-templates select="year"/> </xsl:if> <xsl:if test="hour"> <xsl:if test="not(*[position() = 1 and self::hour])"> <xsl:text> </xsl:text> </xsl:if> <xsl:apply-templates select="country"/> </xsl:if> <xsl:if test="minute"> <!-- Always follows hour --> <xsl:text>:</xsl:text> <xsl:apply-templates select="minute"/> </xsl:if> <xsl:if test="second"> <!-- Always follows minute --> <xsl:text>:</xsl:text> <xsl:apply-templates select="second"/> </xsl:if> </span></xsl:template><xsl:template match="from"> <span class="from_date"> <xsl:apply-templates/> </span></xsl:template><xsl:template match="to"> <span class="to_date"> <xsl:apply-templates/> </span></xsl:template><xsl:template match="period"> <xsl:choose> <xsl:when test="date"> <p class="period"> <xsl:apply-templates select="date"/> </p> </xsl:when> <xsl:when test="from and to"> <p class="period"> <xsl:apply-templates select="from"/> <xsl:text disable-output-escaping="yes"> &amp;ndash; </xsl:text> <xsl:apply-templates select="to"/> </p> </xsl:when> </xsl:choose></xsl:template><!-- Simple Compound Elements --><xsl:template match="address"> <span class="address"> <xsl:if test="street"> <xsl:apply-templates select="street"/> <xsl:if test="apartment"> <xsl:text>, </xsl:text> <xsl:apply-templates select="apartment"/> </xsl:if> </xsl:if> <xsl:if test="city"> <xsl:if test="not(*[position() = 1 and self::city])"> <xsl:text>, </xsl:text> </xsl:if> <xsl:apply-templates select="city"/> </xsl:if> <xsl:choose> <xsl:when test="state"> <xsl:if test="not(*[position() = 1 and self::state])"> <xsl:text>, </xsl:text> </xsl:if> <xsl:apply-templates select="state"/> </xsl:when> <xsl:when test="province"> <xsl:if test="not(*[position() = 1 and self::province])"> <xsl:text>, </xsl:text> </xsl:if> <xsl:apply-templates select="province"/> </xsl:when> </xsl:choose> <xsl:if test="postal"> <xsl:if test="not(*[position() = 1 and self::postal])"> <xsl:text> </xsl:text> </xsl:if> <xsl:apply-templates select="postal"/> </xsl:if> <xsl:if test="country"> <xsl:if test="not(*[position() = 1 and self::country])"> <xsl:text> </xsl:text> </xsl:if> <xsl:apply-templates select="country"/> </xsl:if> <xsl:if test="e-mail"> <xsl:if test="not(*[position() = 1 and self::e-mail])"> <br/> </xsl:if> <xsl:apply-templates select="e-mail"/> </xsl:if> <!-- TODO: Put the phone and mobile phone on the same line. --> <xsl:if test="phone"> <xsl:if test="not(*[position() = 1 and self::phone])"> <br/> </xsl:if> <xsl:apply-templates select="phone"/> </xsl:if> <xsl:if test="mobile"> <xsl:if test="not(*[position() = 1 and self::mobile])"> <br/> </xsl:if> <xsl:apply-templates select="mobile"/> </xsl:if> </span></xsl:template><xsl:template match="book"> <xsl:choose> <xsl:when test="not(boolean(@deprecated))"> <!-- This is a full Book Definition --> <p class="bibliography"> <xsl:if test="author"> <xsl:for-each select="author"> <xsl:if test="not(position() = 1)"> <xsl:text>, </xsl:text> </xsl:if> <xsl:if test="(position() = last() and not(boolean(../author-et-al)))"> <xsl:text> and </xsl:text> </xsl:if> <xsl:apply-templates select="."/> </xsl:for-each> <xsl:if test="author-et-al"> <xsl:apply-templates select="author-et-al"/> </xsl:if> <xsl:text>. </xsl:text> </xsl:if> <!-- If ISBN is provided, use to link to Amazon --> <xsl:choose> <xsl:when test="isbn"> <a class="book" href="{$amazon}{isbn}/">              <xsl:apply-templates select="name"/>            </a>          </xsl:when>          <xsl:otherwise>            <span class="book">              <xsl:apply-templates select="name"/>            </span>          </xsl:otherwise>        </xsl:choose>        <xsl:if test="publisher | date">          <xsl:text>. </xsl:text>        </xsl:if>        <xsl:if test="address">          <xsl:apply-templates select="address"/>          <!-- Publisher must follow -->          <xsl:text>: </xsl:text>        </xsl:if>        <xsl:if test="publisher">          <xsl:apply-templates select="publisher"/>          <xsl:if test="date">            <xsl:text>, </xsl:text>          </xsl:if>        </xsl:if>        <xsl:if test="date">          <xsl:apply-templates select="date"/>        </xsl:if>      </p>      <xsl:if test="subject">        <xsl:apply-templates select="subject"/>      </xsl:if>    </xsl:when>    <xsl:otherwise>      <!-- Ingore deprecated text -->      <xsl:copy-of select="$deprecated"/> </xsl:otherwise> </xsl:choose></xsl:template><xsl:template match="institution"> <xsl:choose> <xsl:when test="not(boolean(@deprecated))"> <!-- This is a full Institutional Definition --> <!-- Normally a Job or School will access the elements independently, so this is only bibliographical. --> <p class="institution"> <xsl:choose> <xsl:when test="@url"> <span class="institution"> <a href="{@url}"> <xsl:apply-templates select="name"/> </a> </span> </xsl:when> <xsl:otherwise> <span class="institution"> <xsl:apply-templates select="name"/> </span> </xsl:otherwise> </xsl:choose> <xsl:for-each select="oldname"> <xsl:choose> <xsl:when test="position() = 1"> <xsl:text> (Formerly </xsl:text> </xsl:when> <xsl:when test="not(position() = last())"> <xsl:text>; </xsl:text> </xsl:when> </xsl:choose> <xsl:apply-templates/> <xsl:if test="position() = last()"> <xsl:text>)</xsl:text> </xsl:if> </xsl:for-each> <xsl:if test="address"> <xsl:text>, </xsl:text> <xsl:apply-templates select="address"/> </xsl:if> </p> </xsl:when> <xsl:otherwise> <!-- Ingore deprecated text --> <xsl:copy-of select="$deprecated"/>    </xsl:otherwise>  </xsl:choose></xsl:template><xsl:template match="bookref">  <xsl:variable name="ref" select="@ref"/>  <xsl:variable name="book" select="//book[@name = $ref]"/> <xsl:choose> <xsl:when test="not(boolean($book/@deprecated))">      <xsl:choose>        <xsl:when test="$book/@url"> <a class="bookref" href="{$book/@url}">            <xsl:apply-templates select="$book/name"/> </a> </xsl:when> <xsl:when test="$book/isbn">          <a class="bookref" href="{$amazon}{$book/isbn}/">            <xsl:apply-templates select="$book/name"/> </a> </xsl:when> <xsl:otherwise> <span class="bookref"> <xsl:apply-templates select="$book/name"/>          </span>        </xsl:otherwise>      </xsl:choose>    </xsl:when>    <xsl:otherwise>      <!-- Ingore deprecated text -->      <xsl:copy-of select="$deprecated"/> </xsl:otherwise> </xsl:choose></xsl:template><xsl:template match="instref"> <xsl:variable name="ref" select="@ref"/> <xsl:variable name="inst" select="//institution[@name =$ref]"/>  <xsl:choose>    <xsl:when test="not(boolean($inst/@deprecated))"> <!-- This is an Inline Institution, therefore treat as character formatting --> <xsl:choose> <xsl:when test="$inst/@url">          <span class="instref">            <a href="{$inst/@url}"> <xsl:apply-templates select="$inst/name"/>            </a>          </span>        </xsl:when>        <xsl:otherwise>          <span class="instref">            <xsl:apply-templates select="$inst/name"/> </span> </xsl:otherwise> </xsl:choose> <xsl:if test="$inst/address">        <xsl:text>, </xsl:text>        <xsl:apply-templates select="$inst/address"/> </xsl:if> </xsl:when> <xsl:otherwise> <!-- Ingore deprecated text --> <xsl:copy-of select="$deprecated"/>    </xsl:otherwise>  </xsl:choose></xsl:template><!-- Simple Compound Markups --><xsl:template match="buzzword">  <xsl:choose>    <xsl:when test="not(boolean(@deprecated))">      <xsl:choose>        <xsl:when test="@url">          <span class="buzzword">            <a href="{@url}">              <xsl:apply-templates/>            </a>          </span>        </xsl:when>        <xsl:otherwise>          <span class="buzzword">            <xsl:apply-templates/>          </span>        </xsl:otherwise>      </xsl:choose>    </xsl:when>    <xsl:otherwise>      <!-- Ingore deprecated text -->      <xsl:copy-of select="$deprecated"/> </xsl:otherwise> </xsl:choose></xsl:template><xsl:template match="language"> <xsl:choose> <xsl:when test="not(boolean(@deprecated))"> <span class="language"> <xsl:apply-templates/> </span> <!-- Level is Required --> <xsl:text> (</xsl:text> <xsl:value-of select="@level"/> <xsl:text>)</xsl:text> </xsl:when> <xsl:otherwise> <!-- Ingore deprecated text --> <xsl:copy-of select="$deprecated"/>    </xsl:otherwise>  </xsl:choose></xsl:template><xsl:template match="status">  <p class="status">    <span class="text_heading">      <xsl:value-of select="key"/>      <xsl:text>: </xsl:text>    </span>    <xsl:apply-templates select="value"/>  </p></xsl:template><xsl:template match="skill">  <xsl:choose>    <xsl:when test="not(boolean(@deprecated))">      <p class="skill">        <span class="text_heading">          <xsl:value-of select="key"/>          <xsl:text>: </xsl:text>        </span>        <!-- Use for-each to get buzzwords or languages because they             cannot appear in the same skill set, so if one for-each             has not element, it is not executed and saves me the trouble             of writing an xsl:if or writing xsl:for-each             select="*[self::buzzword | self::langauge]" -->        <xsl:for-each select="buzzword[not(boolean(@deprecated))]">          <xsl:if test="not(position() = 1)">            <xsl:text>, </xsl:text>          </xsl:if>          <xsl:apply-templates select="."/>        </xsl:for-each>        <xsl:for-each select="language[not(boolean(@deprecated))]">          <xsl:if test="not(position() = 1)">            <xsl:text>, </xsl:text>          </xsl:if>          <xsl:apply-templates select="."/>        </xsl:for-each>      </p>    </xsl:when>    <xsl:otherwise>      <!-- Ingore deprecated text -->      <xsl:copy-of select="$deprecated"/> </xsl:otherwise> </xsl:choose></xsl:template><xsl:template match="curriculum"> <xsl:choose> <xsl:when test="not(boolean(@deprecated))"> <p class="curriculum"> <xsl:if test="child::key"> <span class="text_heading"> <xsl:value-of select="key"/> <xsl:text>: </xsl:text> </span> </xsl:if> <xsl:for-each select="course"> <xsl:if test="not(position() = 1)"> <xsl:text>, </xsl:text> </xsl:if> <xsl:apply-templates select="."/> </xsl:for-each> </p> </xsl:when> <xsl:otherwise> <!-- Ingore deprecated text --> <xsl:copy-of select="$deprecated"/>    </xsl:otherwise>  </xsl:choose></xsl:template><xsl:template match="tools">  <span class="tools">    <xsl:for-each select="buzzword[not(boolean(@deprecated))]">      <xsl:if test="not(position() = 1)">        <xsl:text>, </xsl:text>      </xsl:if>      <xsl:apply-templates select="."/>    </xsl:for-each>  </span></xsl:template><xsl:template match="task">  <li class="task"/>  <p class="task">    <xsl:apply-templates/>  </p></xsl:template><xsl:template match="interest">  <xsl:choose>    <xsl:when test="@url">      <a class="interest" href="{@url}">        <xsl:apply-templates/>      </a>    </xsl:when>    <xsl:otherwise>      <span class="interest">        <xsl:apply-templates/>      </span>    </xsl:otherwise>  </xsl:choose></xsl:template><xsl:template match="para">  <!-- This will typically be overridden by another template -->  <p>    <xsl:apply-templates/>  </p></xsl:template><xsl:template match="objective">  <xsl:for-each select="para">    <p class="objective">      <xsl:apply-templates/>    </p>  </xsl:for-each></xsl:template><xsl:template match="summary">  <xsl:for-each select="para">    <p class="summary">      <xsl:apply-templates select="."/>    </p>  </xsl:for-each>  <table border="0" width="100%">    <xsl:for-each select="task">      <xsl:choose>        <xsl:when test="position() mod $col_of_data = 1"> <xsl:text disable-output-escaping="yes">&lt;tr&gt;</xsl:text> </xsl:when> <xsl:otherwise> <td> <img src="spacer.gif" class="spacer"/> </td> </xsl:otherwise> </xsl:choose> <!-- Spacer messes the percentage up! --> <td class="task" width="{100 div$col_of_data}%">        <xsl:apply-templates select="."/>      </td>      <xsl:if test="position() mod $col_of_data = 0"> <xsl:text disable-output-escaping="yes">&lt;/tr&gt;</xsl:text> </xsl:if> </xsl:for-each> <!-- Extra Close Row for odd numbers --> <xsl:if test="count(task) mod$col_of_data != 0">      <xsl:text disable-output-escaping="yes">&lt;/tr&gt;</xsl:text>    </xsl:if>  </table></xsl:template><xsl:template match="achievement">  <!-- Do the First Paragraph Separately with Tools, then the rest -->  <!-- Problem with indentation and blank line after -->  <li class="achievement"/>  <p class="achievement">    <xsl:if test="tools">      <!-- Double Spanning on Tools! -->      <xsl:apply-templates select="tools"/>      <xsl:if test="para">        <span class="tools">          <xsl:text>: </xsl:text>        </span>      </xsl:if>    </xsl:if>    <!-- Wacky way of saying "Don't Apply this template,         apply the ones below! -->    <xsl:for-each select="para[1]">      <xsl:apply-templates/>    </xsl:for-each>  </p>  <xsl:for-each select="para[position() &gt; 1]">    <!-- Convert to Unordered List -->    <p class="achievement">      <xsl:apply-templates/>    </p>  </xsl:for-each></xsl:template><xsl:template match="title">  <span class="title">    <h1 class="name">      <xsl:apply-templates select="name"/>      <xsl:for-each select="oldname">        <xsl:if test="position() = 1">          <xsl:text> (</xsl:text>        </xsl:if>        <xsl:apply-templates/>        <xsl:choose>          <xsl:when test="position() = last()">            <xsl:text>)</xsl:text>          </xsl:when>          <xsl:otherwise>            <xsl:text>, </xsl:text>          </xsl:otherwise>        </xsl:choose>      </xsl:for-each>    </h1>    <p class="address">      <xsl:apply-templates select="address"/>    </p>  </span></xsl:template><xsl:template match="bibliography">  <xsl:if test="not(boolean(@deprecated))">    <xsl:if test="heading">      <xsl:apply-templates select="heading"/>    </xsl:if>    <xsl:for-each select="book">      <xsl:apply-templates select="."/>    </xsl:for-each>    <xsl:for-each select="institution">      <xsl:apply-templates select="."/>    </xsl:for-each>  </xsl:if></xsl:template><xsl:template match="job">  <xsl:if test="not(boolean(@deprecated))">    <table width="100%">      <tr>        <td>          <xsl:apply-templates select="institution"/>        </td>        <td>          <img src="spacer.gif" class="spacer"/>        </td>        <td>          <!-- To Do: Please try to if the position is               present, extend this cell and the spacer               to rowspan="2" -->          <!-- Problem: Width of "Period" too small! -->          <xsl:apply-templates select="period"/>        </td>      </tr>      <xsl:if test="position">        <tr>          <td colspan="2">            <xsl:apply-templates select="position"/>          </td>        </tr>      </xsl:if>      <xsl:for-each select="achievement">        <tr>          <td colspan="2">            <xsl:apply-templates select="."/>          </td>        </tr>      </xsl:for-each>    </table>  </xsl:if></xsl:template><xsl:template match="school">  <xsl:if test="not(boolean(@deprecated))">    <table width="100%">      <tr>        <td>          <xsl:apply-templates select="institution"/>        </td>        <td>          <img src="spacer.gif" class="spacer"/>        </td>        <td>          <!-- To Do: Please try to, if the degree is               present, extend this cell and the spacer               to rowspan="2" -->          <!-- Problem: Width of "Period" too small! -->          <xsl:apply-templates select="period"/>        </td>      </tr>      <xsl:if test="degree">        <tr>          <td colspan="2">            <xsl:apply-templates select="degree"/>          </td>        </tr>      </xsl:if>      <xsl:for-each select="achievement">        <tr>          <td colspan="2">            <xsl:apply-templates select="."/>          </td>        </tr>      </xsl:for-each>      <xsl:for-each select="curriculum">        <tr>          <td colspan="2">            <xsl:apply-templates select="."/>          </td>        </tr>      </xsl:for-each>    </table>  </xsl:if></xsl:template><xsl:template match="reference">  <xsl:if test="not(boolean(@deprecated))">    <p class="reference">      <xsl:apply-templates select="name"/>      <xsl:if test="position">        <br/>        <xsl:apply-templates select="position"/>      </xsl:if>      <xsl:if test="instref">        <!-- Since we know the Institution-Reference requires a             position, we will add the comma here before the             institution definition -->        <xsl:text>, </xsl:text>        <xsl:apply-templates select="instref"/>      </xsl:if>      <xsl:if test="e-mail">        <br/>        <xsl:apply-templates select="e-mail"/>      </xsl:if>      <xsl:if test="phone">        <br/>        <xsl:apply-templates select="phone"/>      </xsl:if>      <xsl:if test="mobile">        <br/>        <xsl:apply-templates select="mobile"/>      </xsl:if>    </p>  </xsl:if></xsl:template><xsl:template match="section">  <xsl:if test="not(boolean(@deprecated))">    <xsl:apply-templates select="heading"/>    <xsl:choose>      <xsl:when test="child::objective">        <xsl:apply-templates select="objective"/>      </xsl:when>      <xsl:when test="child::status">        <table border="0" width="100%">          <!-- For Simplicity I will but the Spanning cells first -->          <!-- Ideally, the XSL should use an iterative approach that               would end a row tag when it finds a spanning, then               span the cell across all columns, then reset the               numbering to "1" or some odd number to continue the               data as if the next cell was the first one.  The problem               with this is simply that I can't reset the position()               counter to do this.  So, voilà, the simple approach -->          <xsl:for-each select="status[@spanning = 'true']">            <tr>              <td class="status" colspan="{$col_of_data}"> <xsl:apply-templates select="."/> </td> </tr> </xsl:for-each> <xsl:for-each select="status[not(boolean(@spanning))]"> <xsl:choose> <xsl:when test="position() mod$col_of_data = 1">                <xsl:text disable-output-escaping="yes">&lt;tr&gt;</xsl:text>              </xsl:when>              <xsl:otherwise>                <td>                  <img src="spacer.gif" class="spacer"/>                </td>              </xsl:otherwise>            </xsl:choose>            <!-- Spacer messes the percentage up! -->            <td class="status" width="{100 div $col_of_data}%"> <xsl:apply-templates select="."/> </td> <xsl:if test="position() mod$col_of_data = 0">              <xsl:text disable-output-escaping="yes">&lt;/tr&gt;</xsl:text>            </xsl:if>          </xsl:for-each>          <!-- Extra Close Row for odd numbers -->          <xsl:if test="count(resume/section/summary/task) mod col_of_data != 0"> <xsl:text disable-output-escaping="yes">&lt;/tr&gt;</xsl:text> </xsl:if> </table> </xsl:when> <xsl:when test="child::summary"> <xsl:apply-templates select="summary"/> </xsl:when> <xsl:when test="child::skill"> <xsl:for-each select="skill"> <xsl:apply-templates select="."/> </xsl:for-each> </xsl:when> <xsl:when test="child::job"> <xsl:for-each select="job"> <xsl:apply-templates select="."/> <!-- Blank Line After to separate Records? --> </xsl:for-each> </xsl:when> <xsl:when test="child::school"> <xsl:for-each select="school"> <!-- Blank Line After to separate Records? --> <xsl:apply-templates select="."/> </xsl:for-each> </xsl:when> <xsl:when test="child::interest"> <xsl:for-each select="interest"> <xsl:if test="not(position() = 1)"> <xsl:text>, </xsl:text> </xsl:if> <xsl:apply-templates select="."/> </xsl:for-each> </xsl:when> <xsl:when test="child::reference"> <xsl:for-each select="reference"> <xsl:apply-templates select="."/> </xsl:for-each> </xsl:when> </xsl:choose> </xsl:if></xsl:template><xsl:template match="/"> <html> <head> <link href="Résumé.css" rel="stylesheet" type="text/css"/> <xsl:apply-templates select="resume/title"/> </head> <body> <xsl:for-each select="resume/section[not(boolean(@deprecated))]"> <xsl:apply-templates select="."/> </xsl:for-each> <xsl:apply-templates select="resume/bibliography[not(boolean(@deprecated))]"/> <hr class="separator"/> <xsl:if test="resume/@url"> <table width="100%"> <tr> <td width="33%"></td> <td width="67%"> <p class="online"> <xsl:text>Text available on-line at </xsl:text> <a type="online" href="{resume/@url}"> <xsl:value-of select="resume/@url"/> </a> <xsl:text>.</xsl:text> </p> </td> </tr> </table> </xsl:if> </body> </html></xsl:template></xsl:stylesheet> Résumé.xsl, the eXstensible Stylesheet Language Transform of an XML Résumé. Once the XML has been translated to HTML, it’s still rather rough. However, one can use Cascaded Style Sheets (CSS) to transform that raw XML into something much more pleasant to look at. It also hides Deprecated elements to they don’t appear in the final, rendered page. It sets the font, and colors the hard breaks, and formats everything in neat boxes. Because the XSLT adds class modifiers to a number of the tags, the CSS can be rather richly defined and very specific to each document element. body{ FONT-SIZE: 11pt; FONT-FAMILY: 'Times New Roman'}td{ vertical-align : top;}span..buzzword{ FONT-STYLE: italic; FONT-FAMILY: 'Courier New'}span.institution{}p.summary{ TEXT-ALIGN: justify}hr.separator{ BORDER-RIGHT: blue thick groove; BORDER-TOP: blue thick groove; BORDER-LEFT: blue thick groove; BORDER-BOTTOM: blue thick groove}H2.heading{ BORDER-TOP: medium none; FONT-SIZE: 12pt; COLOR: blue; FONT-FAMILY: Helvetica; LETTER-SPACING: 6pt; TEXT-ALIGN: center}H1.name{ FONT-WEIGHT: bolder; FONT-SIZE: 20pt; TEXT-ALIGN: center}SPAN.title{ COLOR: #ff9900; FONT-STYLE: italic; FONT-FAMILY: Helvetica; TEXT-ALIGN: center}P.address{ TEXT-ALIGN: center}SPAN.address{}SPAN.text_heading{ FONT-WEIGHT: bolder}SPAN.position{ FONT-STYLE: italic; FONT-FAMILY: Arial}SPAN.product{ FONT-STYLE: italic}SPAN.degree{ FONT-STYLE: italic; FONT-FAMILY: Arial}SPAN.course{}SPAN.author{}A.isbn{ VISIBILITY: inherit}SPAN.date{}SPAN.from_date{}SPAN.to_date{}P.period{ COLOR: #800080; FONT-STYLE: italic; TEXT-ALIGN: right}SPAN.publisher{}P.bibliography{}A.book{ VISIBILITY: inherit; FONT-STYLE: italic}SPAN.book{ FONT-STYLE: italic}SPAN.language{}P.subject{}SPAN.instref{ FONT-STYLE: italic}P.institution{ FONT-WEIGHT: bolder; FONT-SIZE: 12pt; FONT-FAMILY: Arial}A.address{ VISIBILITY: inherit}P.status{}P.skill{}P.curriculum{}SPAN.tools{ FONT-WEIGHT: bolder; COLOR: #4c8000}P.task{}SPAN.interest{}A.interest{ VISIBILITY: inherit}P.objective{}HEAD{ FONT-SIZE: 11pt; FONT-FAMILY: 'Times New Roman'}P.reference{}P.online{ TEXT-ALIGN: right}A.online{ VISIBILITY: inherit}SPAN.redacted{ color: black; background-color: black; TEXT-DECORATION: line-through; font-style: italic}SPAN.deprecated{ DISPLAY: none; TEXT-DECORATION: line-through}TD.task{ VERTICAL-ALIGN: top; LIST-STYLE-TYPE: disc}TD.status{ VERTICAL-ALIGN: top;}img.spacer{ width : 10px;}li.task{ list-style-type : disc;}li.achievement{ list-style-type : disc;} Résumé.css, the Cascaded Style Sheet used to transform the Résumé. Once the CSS is applied, the final Résumé can be generated. Although I still had to perform some tweaking to the HTML, mainly to fix deprecated sections to eliminate trailing commas and other cleanups, for the most part, once the CSS was applied, the Résumé looked nearly perfect. The only change to the above CSS, because CSS on this site is site-wide, was that I put everything within a resume2002 class so that the CSS didn’t apply to every post, only to those inside a Résumé block. You can see what the final, 2002 version, with a couple modern edits for some of my subsequent publications, of the Résumé looks like here. I am working on an update to the XML Résumé thanks to figuring out how to get around the Xmplify bug with including Entity definition in an XML Schema. Apparently, if you Entities in your XML, you need to use a DTD to define your XML, you can’t use XML Schema. Fortunately, I did have the full DTD version of my XML specification, so that was easy to solve in the short term while Xmplify is fixed to allow XML Schema with entity-only DOCTYPE sections at the top of XML documents. I have one final note about the code included in the Résumé in XML posts. In part 1, I neglected to take into account multiple spaces would get reduced to one, meaning that the formatting lost all of the proper indentation and made the code hard to read. I was hasty when I generated the HTML-compatable markup for the two documents there. When I generated the above documents, however, I generated the correct HTML using the simply four lines of Python below, once for each file. >>> with open('Résumé.xml', 'rb') as f:... xml = f.read().decode('latin_1')... >>> with open('xml.html', 'w') as f:... f.write(html.escape(xml).replace('\n', '').replace(' ', ' '))... >>> Python code to turn XML into into readable HTML I would love to show you the 2020 version of my Résumé but, alas, I’m still working on it and still have 2002–2012 to cover. I did write entries for 2010–2012, but then realized that I combined the description of two distinct projects into one, so I have to rewrite that section and then finish the first eight years of entries. That killed my Friday when this post was supposed to go out. I figure that will take me another week, and have about five more projects to cover, but won’t know for sure until I go through the remaining Year End Reviews. But I do have the complete last eight years and, again, if you like what you see, I’m still happily available for hire. ## You can have any star you want, as long it is gold One huge flaw with Google‘s iPhone app for Gmail is that it doesn’t support multiple star types. You are only allowed a gold star, while with the computer-based web interface, you can have many different colour stars, warning, and other alerts. This is a huge oversight in the GMail app compounded by the fact that the applications like Safari, which allow the user to simulate Desktop browsing crash when you activate the standard web interface and try to select a star colour other than gold. It used to be you could just open up a desktop browser session to manually set the star level but now even that doesn’t work and still the app can’t handle it. Stars are a very useful aspect to GMail. With stars you can denote more than just that an email is important, but why it is important. For instance, I like to use the blue stars for coupons. I don’t want to mix coupons in with stars to indicate a SpamGourmet email address is about to expire. This, in my opinion, is a major flaw to the Apple iOS GMail app and I hope someday they add an option to modify star type because I had to spend two hours today unable get my coveted blue star and ended up having to get out of bed and go to the computer just for this very simple action. As a software, I know they could do better. As a software engineer, I may just end up doing better. Thanks to the Python interface to Google, I likely will do better. So unless they want to hire me, bugger you, Google! ## The danger of Upgrading WordPress Late last night, just as I completed my post about Tesla trying to scam me, I decided to upgrade WordPress to version 5.4. Normally, this shouldn’t be an issue, but for me, since I run a multisite system, there are extra security issues and directory layout complications that must be taken into account. The first step was, apparently, to backup my database. Since I’ve never backed up the mysql database before, I felt this seemed like a reasonable approach. I certainly didn’t want to pay JetPack to do it; I’m a genuine code jockey, I can do my own backups. After some digging around, I found mysqldump. Unfortunately, all the instructions on how to use it were incorrect. After some further poking around, I finally came across the correct syntax. Essentially, the user name and host have to come before the --all-databases command. Also, the host can’t be localhost, it must be the IP for the local host. Unfortunately, I was not able to find a way to get it to prompt for my password which meant I had to type my password in the command line, leaving all there in the open for any history recall to see. Not very secure at all. mysqldump -h 127.0.0.1 --user=<uid> --password=<pw> --all-databases > mysql.2020.03.31.bak mysqldump command; note <uid> and <pw> are placeholders for the user name and password; you must replace this with your own values. Alas, I was not able to find a way to get mysqldump to prompt for a password. I think if I have more time, I may write a python script which builds the command by first prompting for the password. At least that way, the password wouldn’t be stored in the command line history. The mysqldump command is quite clever. It just stores the list of sql commands that would be required to recreate the databases you have stored. However, the file is rather big and being text, it compresses nicely with bzip2 -9, which is what I did. Once I did this, I was ready for the main Upgrade. I held my breath and pulled the trigger… The install progressed along nicely until it tried to write a file to the wordpress directory. 🤦🏻‍♂️ I logged into my server and sure enough, the permissions on the wordpress directory were 755, which meant the user could add and remove files, but the group and anyone else could not. You see, with my multisite, I try to have all wordpress files with user wp-user and group as www-data, to work with apache. And apache runs all web processes as www-data for both user and group. Thus, when WordPress asked to add a file to its codebase, apache could not write it because the www-data group didn’t have permission, only wp-user did. find /usr/share/wordpress -type 'd' -print0 | xargs -0 sudo chmod 775 Change all the wordpress directors to allow www-data to add and remove files from them. Realizing my mistake, I changed the permissions on all directories to be 775 (both wp-user and www-data could add and remove files). Unfortunately, it was too late. Instead, I had no choice but to blow away my current install and replace it with a fresh, new install of WordPress 5.4. At least, that’s what I did on a high level. The details, though, are a bit more complex. Once I extracted all the wordpress files, I needed to get their ownership to match the settings for my wordpress install. I was able to do this quite easily with the chown command. sudo chown -R wp-user:www-data wordpress/ Command to set the right file ownership for wordpress. Next, I set the directories as above. Finally, the files themselves had to have the right permissions. Namely, they should be readable and writable by wp-user and the www-data group, but only readable to others, not writable. Namely, they needed to be set to permission 664. find wordpress/ -type 'f' -print0 | xargs -0 sudo chmod 664 Change all the wordpress files to allow www-data to modify them. Next, I had to copy over the active wordpress configuration file. This file is actually fairly spartan as all the active site configurations are actually stored in /etc/wordpress; my wp-config.php actually just scans this directory for configurations. The configurations, in turn, point to directories in /srv/www/wp-content with the site-specific files. I thuns needed to bring that file over to the new install. cp /usr/share/wordpress/wp-config.php wordpress Copy the configuration to the new wordpress install. Next, I wanted to preserve the Languages I had installed. I just copied the entire directory over to the local install. cp -r /usr/share/wordpress/wp-content/languages wordpress/wp-content/ Copy the Languages directory to the wordpress install. I also have an upgrades directory that I wanted to preserve. cp -r /usr/share/wordpress/wp-content/upgrades/ wordpress/wp-content/ Copy the Upgrades directory to the wordpress install. Finally, I needed to move the links to my shared, dynamic contents that are for all the sites on my server. Specifically, the uploads, themes, and plugins folders all rest in /var/lib/wordpress/wp-content. (Technically, Uploads rests in blog.restonwriters.org site-specific Uploads directory, but that’s something I’ll fix later to conform with the same layout Themes and Plugins use.) Since these are already symbolic links, they can be moved to the new wordpress install directory to replace the defaults. One caveat however, is the default install for wordpress comes with one plugin and three themes. In order to preserve those, I renamed the default plugins directory to plugins-default, and the default themes directory to themes-default. This was necessary before the symbolic links could be moved since those directories were in the way. mv /usr/share/wordpress/wp-content/uploads wordpress/wp-contentmv /usr/share/wordpress/wp-content/plugins wordpress/wp-contentmv /usr/share/wordpress/wp-content/themes wordpress/wp-content Move the symbolic links to the plugins, themes, and uploads directories. Finally, the apache permissions file needed to be moved as it was also a link, pointing to /etc/wordpress/htaccess. I store the file there because it makes it easier to maintain in case I accidentally bow the .htaccess file away. cp /usr/share/wordpress/.htaccess wordpress Move the symbolic link to the .htaccess file. Once all this is done, it’s a good idea to run the chown and chmod commands from above on the wordpress install directory once more to make sure the copied files and moved links are also properly attributed. Finally, it was time to perform the brain transplant and move my staged wordpress install to the active /usr/share/ directory. I moved the current install to a temporary directory and then moved my staging install to the /usr/share/ directory to replace it. mv /usr/share/wordpress /usr/share/wordpress-oldmv wordpress /usr/share/wordpress Replace the installed wordpress with the new version. Once all this was done, I was able to get to my web page, and wordpress prompted me to upgrade my database. Once this was done my sites were back online. In total, this site and its sister sites were down for a total of about forty-five minutes. It was a long day yesterday and I was exhausted but I did get it done and you can now see the results. I hope you enjoyed my story about hacking UNIX. Please note, I am available for hire if you like what you see! ## Google ate me email From about 2020-03-23T14:30:00Z (10:30 am, Monday) to about 2020-03-23T23:30:00Z (7:30 pm, Monday), Google was redirecting all my email and either bouncing it or deleting it. Let me repeat, google deleted or bounced my email for Nine Hours, as a part of the setup of my setup for a paid Google Apps account. The setup for these accounts are a bit weird. They require you to create a new google entity with your own company URL. Fortunately, I have multiple domains I own and maintain, including this one, TimeHorse.com. I probably should have used my writing group domain, RestonWriters.org. After all, the whole reason I wanted to get a paid Google account is because Meetup was moving to Online-Only meetings, following the outbreak of SARS-COV-2, and I needed a tool that allowed for video conferencing. Skype was a non-starter. For one thing, it’s great for person-to-person communications, but for group chats, it has this annoying habit of muting everyone except the current speaker and you have to wait until that speaker stops to get a word in edgewise. My understanding is WhatsApp has the same problem. Meetup actually suggested using Google Hangouts or Zoom. I happen to like Zoom. I use it for my regular NPVIC Grassroots strategy meetings and for Toastmasters and it’s always worked great. Zoom does support up to a hundred participants, both free and Pro. The only problem is, each of those Zoom sessions are either limited to the free forty-minute block or are using an up-to-24-hour Zoom Pro Account. Since most of my Meetups are at least an hour, breaking meeting up into forty-minute chunks would be tedious. And, at14.99 a month, the professional account is well out of my price range.

Just before the first week of Virtual meetings began, my writing colleagues and I, including Elizabeth Hayes, who runs The Hourlings, tested both free Zoom and Google Hangout. Despite being limited to ten people, we decided on Google Hangout and I mapped it to our official Virtual Meeting URL.

Ten people worked fine for Reston Writers and for the Saturday Morning Review. The Saturday Morning Review actually worked out quite well because Meetup, despite suggesting we move to a virtual platform, still won’t let you delete the venue from your event and mark it as virtual, which, when editing events can cause some confusion. But when the Library cancelled all our events, I just deleted them all from the Meetup Calendar, and recreated them with no Venue and just announced them as occurring in Cyberspace.

#### Stay with me folks, I’m getting to the email…

As Sunday approached, I new ten participants wouldn’t be enough. Google Hangout would be fine for Bewie Bevy of Brainy Books and Saturday Morning Review, and likely The Science Book Club, as they all usually have fewer than ten participants for each meeting. The Hourlings, on the other hand, often had twelve, and sometimes as many as sixteen!

I new Zoom was $14.99 a month, but I read that Google App accounts could up the number of participants to twenty-five. Unfortunately my 2TB Google Drive account didn’t qualify. I had to get a Google Apps account. And that’s where my troubles began. At first, I could only sign up for the$12 per month account, even though I’d read it could be had for $6. Since the setup has a fortnight trial period, I didn’t worry about the financial discrepancy. I set up the account with my business email address for TimeHorse, LLC. I associated it with with that email, it connected to my Gandi Registrar, and my account was ready to go. I created a Google Hangout and assigned it to the Virtual Meeting URL, hoping it would allow twenty-five. The plan was to use it with the Hourlings to verify that fact. It failed! We still could only get ten people into the meetup despite it being a paid account. Unfortunately, since Monday I’ve been on Weather and Safety Leave from work because my Telework agreement was revoked, but that’s a story for another day as this post is long as it is! However, it did allow me to speak to Google and they suggested I try Google Meet. Meet was included with all Google App paid accounts, and it would allow for up to a hundred people and could be as long as I needed. Also, I could downgrade to the$6 per month account and I would still be able to use it. I thus downgraded.

We tried it with Reston Writers Review and it worked wonderfully. We had up to twelve connections simultaneously! But I’m getting ahead of myself.

At around 10:30 am, that Monday, after chatting with Google, I was examining my Google Apps account more closely. It was telling me I had one last step I needed to complete: integrate me email with Gmail.

That’s when my troubles began. You see, what this innocuous, turn-key step says it does is it says it sets up GMail for your company. What it actually does is obliterate all the MX Records (email routing information) of your DNS (Internet routing information) Zone File (routing configuration file) on Gandi and replace it with MX Records that point to Google. The setup wizard doesn’t actually tell you this and I’m totally oblivious.

At current writing, I have 188 forwarded email addresses set up on Gandi with their MX Servers. One of those is my business email, the one Google took over and is my Google Apps login. That’s the email google set up as the official email address used in GMail. Once the GMail setup goes through and I send an email from the GMail interface to my personal email address on the timehorse.com domain.

It never arrives. All day long, I watch my email and, strangely, nothing arrives after 10:30 in the morning. I refresh and refresh, and it’s still nothing. Where have all my emails gone?

It’s not until I’m setting up for Reston Writers that I decide to contact Google about this. I’m crazy-busy setting up the Google Meet, opening up the pieces we’d be reviewing on my computer, and, simultaneously, chatting with Google, trying to figure out why I’m not receiving any email.

Eventually, Google Tech Support starts talking about MX Records and a chill runs down my spine. As you probably gathered by now, I am well versed in DNS records and Zone File manipulation. I even have a Python script which updates my DNS A Record when the IP Address for this server changes.

Fortunately, Gandi’s Email Forwarding page provides a warning when the Zone file doesn’t point to their email server, listing the correct MX Record settings to use Gandi as the mail hosting server. I quickly commented out the Google MX Records and pasted in the Gandi MX Records around 7:30 pm, in the middle of my Reston Writers meeting.

Needless to say, I was miffed that I could not give my full attention to my writers during our weekly writing gettogether. But it’s good I finally did figure out the disastrous actions committed by Google after only nine hours, and not a day or more.

I may never know what was contained in those nine hours of lost emails. I suppose there is one blessing, though. I get too much email already and still have dozens of unread messages I’m desperately trying to catch up on. One Covidapolis, novel-length email after another from every business under the sun. STFU companies, you’re all doing the same thing and I don’t like reading the same message again, and again, and again! You have a plan, that’s all I need to know!

Maybe Google was doing me a favor?

In the end, I was able to solve the problem because I got skills and I’m available for hire!

## Low Acid, Low Acid, my Kingdom for Low Acid!

I ordered some Low Acid Orange Juice from Safeway early on Saturday and was in giddy anticipation all weekend, despite some rather dastardly events that happened on Friday. But let’s not talk about that except to say I’m a wizard with Python and a very diligent worker.

I’ve been craving Low Acid Orange Juice since Covidapolis, and I could see the end in sight. Monday arrives and while I’m chatting on the phone, another call comes from the delivery bloke. I call him back when my call is over and I tell him I’m here to receive the groceries and he agrees to head back to my place.

Since I was in my garage—I miss #CO2Fre—I decided to go out and meet him at the door. Alas, he had only one bag. No Orange Juice! Only the sundry items I bought to get my order to the minimum size for delivery.

Needless to say, I am rather angry at Safeway. They didn’t charge me for the Orange Juice, but all I wanted was Low Acid Orange Juice. They totally wasted my time and money by delivering all but that. They should have told me when I ordered that it was out of stock.

I miss Orange Juice.

## Python INI File Parser

A flexible INI parser can be built in Python. Although tools to do this already exist, it’s important to understand how this might be done in the general case to learn new python techniques in general.

The idea is to parse rudimentary INI files of the form:

Field 1: FooField 2: Bar

Flat INI File Format

In this example we will ignore INI groupings and stick with just the INI name-value pairs.

The challenge is in writing a system that maps from INI names to variable names. The application can also be built with flexible punctuation. For instance:

class ini_parser:    ini = 'test.ini'    ini_sep = ':'    ini_mapping = {        'Field 1': 'field_1',        'Field 2': 'field_2'    }

Code to implement a generic INI parser in Python.

In this example, the INI uses names with spaces. Since Python variable names cannot have spaces, the mapping is necessary to allow for dissimilar local variable names relative to the name used in the INI file.

Once we have this boilerplate, we can build an iniparser around it:

def parse_ini(self):
with open(self.ini) as file_obj:
for line in file_obj:
if self.ini_sep in line:
# Split and trim whitespace around fields
name, value = tuple([x.strip() for x in l.split(self.ini_sep, 1)])
setattr(self, self.ini_mapping[name], value)

ini_parser.parse_ini()

In the above example, self.field_1 would get the value 'Foo', and self.field_2 would get the value 'Bar'. The code only looks at the first colon in the line of an INI file, so that INI names can’t contain colons (:), but any subsequent colon (:) is considered part of the value.

Once software makes changes to an applications settings, it’s important to write those changes back to the INI file:

def write_ini(self):
with open(self.ini, ‘w’) as file_obj:
for ini_name, local_name in self.ini_mapping.items():
value = getattr(self, local_name)
file_obj.write(ini_name + self.ini_sep + ‘ ‘ + value)

ini_parser.write_ini()

Note, for this simple parser, all values will be returned as strings. One could add a type to the ini_parser.ini_mappings to also provide a type for the variable to be assigned, casting value to it. Alternatively, if the variable to be set already exists and has a type, the type command can be used to get that type and then cast value to that type.

Further, if a list type is included in the INI, for instance a series of comma-separated values, then a special list handler will have to be written which will split and strip each value by commas (,) and then join them again with commas (,) when writing. Because lists can be single-element, it’s impossible for software to know by simply searching for commas (,) whether an INI value is a list or not. This is why the type must be included in some way internally to parse list values properly. The code to do this is left as an exercise for the reader.

I hope you enjoyed this little tutorial. Please note, I am available for hire if you like what you see!

## Double Factorial

Most mathematically-inclined folks know what a factorial is. The simplest, recursive definition is given by:

f(0) = 1
f(n) = n * f(n-1) = n!

Figure 1: Definition of Factorial

For all n in the set of Natural Numbers, ℕ.

In the set of real numbers, ℝ, this can be extended to the Gamma function, Γ, which has the form:

$$\Gamma(x) = \int_0^\infty t^{x-1} \mathrm{e}^{-t} \mathrm{d}t$$
$$\forall n \in \mathbb{N}: \Gamma(n) = (n-1)!$$

Figure 2: Definition of Gamma

Both Figure 1 and Figure 2 are typically well known definitions. But what’s less known is the double, triple, and even quadruple factorial:

$$n!! = n (n-2)!!$$
$$n!!! = n (n-3)!!!$$
$$n!!!! = n (n-4)!!!!$$
$$\vdots$$

Figure 3: Higher Level Factorial Functions

The question is, how do you extend this from Natural Numbers to the general case with Real Numbers, like with the Gamma function from Figure 2?

Fortunately, Python’s math library has math.gamma(n) to compute the Gamma function on a given number. Thus, rather than the recursive definition in Figure 1, we can directly define factorial in terms of Gamma:

def factorial(x):    return math.gamma(x+1)

Figure 4: Definition of factorial(x)

The question is, can the Double and Higher Factorials be defined in terms of gamma?

### The Pochhammer Function

The Pochhammer function is defined in therms of Gamma. Specifically:

$$(x)_n = \frac{\Gamma (x+n)}{\Gamma (x)} = x(x+1) \cdots (x+n-1)$$

Figure 5: The Pochhammer Function

The corresponding Python looks like this:

def Pochhammer(n, k):    return math.gamma(k+m)/math.gamma(n)

Figure 6: Definition of Pochhammer

The Pochhammer brings us closer to our ideal continuous multi-factorial method. For instance, we could assume a version of Factorial over the Rational Numbers, ℚ:

def Rational_Multifactorial(n, numerator, denominator):    return pow(numerator/denominator, n*denominator/numerator) * Pochhammer(1, n*denominator/numerator)

Figure 7: Definition of Rational_Multifactorial

Unfortunately, that doesn’t give us a Real solution, ℝ. Instead, if we’re to ask WolframAlpha to work it out for us, we end up with something completely different:

$$x!! = 2^{\frac{x}{2}} (c_2(-1)^x+c_1) \Gamma (1+\frac{x}{2})$$
where,
$$c_1 = \frac{1}{2} + \frac{1}{\sqrt{2 \pi}}$$
$$c_2 = \frac{1}{2} – \frac{1}{\sqrt{2 \pi}}$$

Figure 8: WolframAlpha definition of Double Factorial over the Real

The way that was calculated was the simple recursion in Figure 3 was entered and WolframAlpha was told to find an arithmetic solution. The answer was expressed in terms of the constants c1 and c2, which I then solved for knowing a few fixed values for the multiplication.

The result in Python, becomes:

def double_factorial(x):    c1 = 0.5 + 1/math.sqrt(2*math.pi)    c2 = 0.5 - 1/math.sqrt(2*math.pi)    return pow(2, x/2) * (c2*pow(-1, x)+c1)*math.gamma(x/2+1)

Figure 9: Definition of double_factorial

What’s more, the rational solution used Pochhammer, but the real solution used only Gamma. I was able to use WolframAlpha to compute analytic functions for Triple and Quadruple factorials, with their three and four constants, respectively, but there didn’t seem to be any pattern to the calculations and the solutions move on to using Sine and Cosine. And none of them used Pochhammer. Yet the Rational solution in Figure 7 should work for all levels of Factorial.

I’ve writing up all my functions in Factorial.py, which you can find on my Subversion repository and I’m currently available for hire.