<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
 <title>borntyping</title>
 <link href="/atom.xml" rel="self"/>
 <link href="/"/>
 <updated>2026-03-18T17:01:23+00:00</updated>
 <id></id>
 <author>
   <name>Sam Clements</name>
   <email></email>
 </author>

 
 <entry>
   <title>Writing my own media catalogue, one year later</title>
   <link href="/posts/vancelle-update/"/>
   <updated>2025-09-25T00:00:00+01:00</updated>
   <id>/posts/vancelle-update</id>
   <content type="html">&lt;p&gt;It’s been over a year since I started writing &lt;a href=&quot;./2024-02-19-vancelle.md&quot;&gt;my own system to track media&lt;/a&gt;.
To quickly recap, it’s a small Python webapp that replaced a mix of spreadsheets, notion tables, and Goodreads lists that I used to track games, films and books; and attempted to unify it all into a single database.&lt;/p&gt;

&lt;figure&gt;
  &lt;a href=&quot;/assets/2025-09-25-vancelle-update.png&quot;&gt;
    &lt;img src=&quot;/assets/2025-09-25-vancelle-update.png&quot; alt=&quot;2025-09-25-vancelle-update.png&quot; /&gt;
  &lt;/a&gt;
  &lt;figcaption&gt;Screenshot of Vancelle&apos;s homepage&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;I made a few big changes over that time:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;
    &lt;p&gt;Two or three rewrites of a lot of the internal codebase.&lt;/p&gt;

    &lt;ul&gt;
      &lt;li&gt;
        &lt;p&gt;I wrote my own micro Python-to-HTML library (inspired by &lt;a href=&quot;https://github.com/j4mie/hotmetal&quot;&gt;hotmetal&lt;/a&gt;) that entirely replaced HTML templates. I was fully expecting to regret that later on, but it’s been nice enough to use that I’d happily use it again. It made it very easy to turn things into reusable components, and logic could use all the features of Python rather than the subset Jinja2 provides.&lt;/p&gt;
      &lt;/li&gt;
      &lt;li&gt;
        &lt;p&gt;The app still uses HTMX in a few places, but I used it less and less as I rewrote things. It was usually much easier to do things the traditional web-app way with forms and full page reloads, and I didn’t have much need for interactivity.&lt;/p&gt;
      &lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Replaced &lt;a href=&quot;https://bulma.io/&quot;&gt;Bulma&lt;/a&gt; with &lt;a href=&quot;https://getbootstrap.com/&quot;&gt;Bootstrap&lt;/a&gt;. Bulma was very pretty, but I kept reaching for elements I was used to from Bootstrap, and there was a major version upgrade that seemed to be missing some features that put me off it. Looking back at old screenshots, it’s definitely not quite so nice looking now.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Added a lot of new categories for tracking where I’m at with media. I even ended up adding some meta-categories to group them up. It’s maybe ended up a bit overboard, but it seems to work for me and I don’t end up with too many items in the “now playing” groups (even when I have odd exceptions like games that can’t be completed). The meta-categories and categories I ended up with were Upcoming (upcoming, unreleased, undecided), Playing (playing, replaying, ongoing, infinite), Paused (Paused, Shelved), Completed (played, completed, abandoned), and Other (unsorted, reference, untouched).&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;I gave up on trying to do “REST” style URLs, and made different ways to view the same data entirely separate endpoints. Originally they had a toggle on the page to change a query parameter that selected a kanban-board style view or a simple table, and it ended up being much easier to maintain when I split the endpoints for them entirely, and shared some small bits of code for doing filtering.&lt;/p&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;There were quite a few changes I didn’t do, which I think are more interesting:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;
    &lt;p&gt;I never added support for other data sources. Even where I had multiple sources for the same type of media, I only ended up using one. (In this case I used Goodreads over OpenLibrary, even though the project was created to move my book lists out of Goodreads, because Goodreads had much better cover art images).&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Lots of little buttons and features were broken and I never fixed them. There wasn’t much motivation to fix things when it was easy to work around them. I had some little buttons for things like marking I’d finished a book yesterday or today, and it was easy to work around them by manually inputting a date.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;“Boring” work like adding indexes, debugging slow queries, and upgrading outdated libraries. The whole thing is running on a private network and I’m always going to be the only user, so I’m not very worried about maintained or security.&lt;/p&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I stuck to my approach of writing an app that was entirely for myself. It was really nice to be able to entirely rewrite bits of it just because I wanted to, without worrying about breaking things or deadlines or the time spent. It ended up being a really relaxing project to work on at times.&lt;/p&gt;

&lt;p&gt;If you want, you can take a look at the source at &lt;a href=&quot;https://codeberg.org/borntyping/vancelle&quot;&gt;codeberg.org/borntyping/vancelle&lt;/a&gt;, but don’t expect it to come with instructions.&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>Using a static site generator with Codeberg pages</title>
   <link href="/posts/codeberg-pages/"/>
   <updated>2025-09-01T00:00:00+01:00</updated>
   <id>/posts/codeberg-pages</id>
   <content type="html">&lt;p&gt;I had a set of small sites with various build processes I wanted to port from GitHub Pages to Codeberg pages, and wanted a simple way to publish content to a branch.
I didn’t want to have to use or learn a new CI system, so instead settled on adding some commands to a &lt;a href=&quot;https://just.systems/&quot;&gt;Justfile&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Create an empty &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;pages&lt;/code&gt; branch to host the content:&lt;/p&gt;

&lt;div class=&quot;language-shell highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;git switch &lt;span class=&quot;nt&quot;&gt;--orphan&lt;/span&gt; pages
git commit &lt;span class=&quot;nt&quot;&gt;--allow-empty&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;--message&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;initial commit&quot;&lt;/span&gt;
git switch -
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Configure &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;dist/&lt;/code&gt; (or the directory the build process outputs to) as a worktree with the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;pages&lt;/code&gt; branch.&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;git worktree add dist pages --no-checkout
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Run the static site generator or build, outputting files to the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;dist/&lt;/code&gt; directory.&lt;/p&gt;

&lt;p&gt;Enter the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;dist/&lt;/code&gt; directory—git commands will now operator on the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;pages&lt;/code&gt; branch—and commit and push the built files.
You can run this every time the source changes.&lt;/p&gt;

&lt;div class=&quot;language-shell highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nb&quot;&gt;cd &lt;/span&gt;dist
git add &lt;span class=&quot;nt&quot;&gt;--all&lt;/span&gt;
git commit &lt;span class=&quot;nt&quot;&gt;--message&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;dist&quot;&lt;/span&gt;
git push
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;All done!&lt;/p&gt;

&lt;p&gt;When working from a fresh git checkout, the worktree will need to be configured first:&lt;/p&gt;

&lt;div class=&quot;language-shell highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;git switch pages
git switch -
git worktree add dist pages &lt;span class=&quot;nt&quot;&gt;--no-checkout&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
</content>
 </entry>
 
 <entry>
   <title>Can you use a Kubernetes secret from another namespace?</title>
   <link href="/posts/kubernetes-secret/"/>
   <updated>2024-08-20T00:00:00+01:00</updated>
   <id>/posts/kubernetes-secret</id>
   <content type="html">&lt;p&gt;Yes, and no:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A Pod can only reference a Secret in the same namespace&lt;sup id=&quot;fnref:1&quot;&gt;&lt;a href=&quot;#fn:1&quot; class=&quot;footnote&quot; rel=&quot;footnote&quot; role=&quot;doc-noteref&quot;&gt;1&lt;/a&gt;&lt;/sup&gt;.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This means a Pod manifest can only use Secrets from the same namespace, whether it’s used for environment variables, mounting files, or configuring credentials for a container image registry.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;RBAC can allow a ServiceAccount access to Secrets in other namespaces&lt;sup id=&quot;fnref:2&quot;&gt;&lt;a href=&quot;#fn:2&quot; class=&quot;footnote&quot; rel=&quot;footnote&quot; role=&quot;doc-noteref&quot;&gt;2&lt;/a&gt;&lt;/sup&gt;.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This means an API request made from a Pod running in one namespace can access Secrets in another namespace.&lt;/p&gt;

&lt;div class=&quot;footnotes&quot; role=&quot;doc-endnotes&quot;&gt;
  &lt;ol&gt;
    &lt;li id=&quot;fn:1&quot;&gt;
      &lt;p&gt;See &lt;a href=&quot;https://kubernetes.io/docs/reference/kubernetes-api/workload-resources/pod-v1/#containers&quot;&gt;imagePullReference&lt;/a&gt; and &lt;a href=&quot;https://kubernetes.io/docs/reference/kubernetes-api/workload-resources/pod-v1/#environment-variables&quot;&gt;envFrom&lt;/a&gt; in the Pod API spec. &lt;a href=&quot;#fnref:1&quot; class=&quot;reversefootnote&quot; role=&quot;doc-backlink&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id=&quot;fn:2&quot;&gt;
      &lt;p&gt;Which is how operators that manage secrets across multiple namespaces work. &lt;a href=&quot;#fnref:2&quot; class=&quot;reversefootnote&quot; role=&quot;doc-backlink&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
  &lt;/ol&gt;
&lt;/div&gt;
</content>
 </entry>
 
 <entry>
   <title>Writing my own media catalogue</title>
   <link href="/posts/vancelle/"/>
   <updated>2024-02-19T00:00:00+00:00</updated>
   <id>/posts/vancelle</id>
   <content type="html">&lt;figure&gt;
  &lt;a href=&quot;/assets/2024-02-19-vancelle-works.png&quot;&gt;
    &lt;img src=&quot;/assets/2024-02-19-vancelle-works.png&quot; alt=&quot;2024-02-19-vancelle-works.png&quot; /&gt;
  &lt;/a&gt;
  &lt;figcaption&gt;Screenshot of Vancelle&apos;s index&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;Recently, I had a lot of time away from work, and a set of related problems:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;
    &lt;p&gt;I use Goodreads to track all the books I read, but I couldn’t add non-traditional works like fics from &lt;a href=&quot;https://www.royalroad.com/home&quot;&gt;Royal Road&lt;/a&gt; or &lt;a href=&quot;https://archiveofourown.org/&quot;&gt;Archive of Our Own&lt;/a&gt; to my lists.&lt;/p&gt;

    &lt;ul&gt;
      &lt;li&gt;I looked at alternative sites like &lt;a href=&quot;https://www.thestorygraph.com/&quot;&gt;StoryGraph&lt;/a&gt;, but Goodread’s data exports don’t include all your data. Specifically, start dates and dates for multiple reads aren’t include in their CSV dump.&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Video games are now spread across a lot of different storefronts. If a game wasn’t at the top of Steam’s recently played list, there’s a good chance I’d forget about it even if I was enjoying it.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;There’s several sites out there for tracking films and tv I’ve watched, but I don’t want to spread my lists across multiple services. I’m also not really interested in my lists being public, or in writing reviews for other people to read—I just want to remember if I’ve already watched something!&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;The &lt;a href=&quot;https://www.notion.so/&quot;&gt;Notion&lt;/a&gt; spreadsheet I was using to fill some of these gaps was starting to creak. Manually entering names and looking up covers so my kanban board would look pretty was starting to get frustrating and time-consuming. I also couldn’t support entering multiple sets of dates for books I’d read more than once, or games I’d put down and come back to.&lt;/p&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;figure&gt;
  &lt;a href=&quot;/assets/2024-02-19-vancelle-home.png&quot;&gt;
    &lt;img src=&quot;/assets/2024-02-19-vancelle-home.png&quot; alt=&quot;2024-02-19-vancelle-home.png&quot; /&gt;
  &lt;/a&gt;
  &lt;figcaption&gt;Screenshot of Vancelle&apos;s homepage&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;So, with these in mind, I set out to replace my spreadsheet with a full web application.&lt;/p&gt;

&lt;p&gt;My data model went through lots of iterations, but I eventually settled on having “Works” (data about a work), “Remotes” (data about a work from an external source), and “Records” (a start and end date). Each work could have zero-or-more remotes and records. When displayed on screen, details about a piece of media were collected from the work and it’s remotes, with manually provided details from the work taking priority. This meant I could import data from multiple sources, and they could fill in gaps the other didn’t cover, and I could manually override any details I didn’t like (for instance, a lot of Steam games include ™ and ⓒ symbols in their titles).&lt;/p&gt;

&lt;p&gt;This was also where I discovered my issues with Goodreads data exports. The first external source I added to my app was the ability to import data from my Goodreads account, both as that was the main service I wanted to replace and as the 700+ entries I had in there would be a great starting point for being able to build my app around real data. In the end, I actually ended up building a module that scraped HTML manually exported from Goodread’s infinitely scrolling books index so that I could include the dates I started reading a book.&lt;/p&gt;

&lt;figure&gt;
  &lt;a href=&quot;/assets/2024-02-19-vancelle-create.png&quot;&gt;
    &lt;img src=&quot;/assets/2024-02-19-vancelle-create.png&quot; alt=&quot;2024-02-19-vancelle-create.png&quot; /&gt;
  &lt;/a&gt;
  &lt;figcaption&gt;Screenshot of Vancelle&apos;s &apos;Create a new work&apos; screen&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;A long time ago I’d always have a cloud server around to run various applications on (irc bouncers, a personal website, etc) but for a long time now I’ve been using exclusively cloud services to meet my needs. I didn’t really want to go to all this effort only to fuss over the ongoing costs of a cloud server — not so much due to the price, but the regular checking of if it was still something I needed. Instead, I got a spare Raspberry Pi from a friend, plugged that in next to my desk, and hosted the app on that. I originally used &lt;a href=&quot;https://dokku.com/&quot;&gt;Dokku&lt;/a&gt; for this, but once I had multiple applications running I moved it over to a somewhat-less-powerful &lt;a href=&quot;https://docs.docker.com/compose/&quot;&gt;docker compose&lt;/a&gt; service.&lt;/p&gt;

&lt;p&gt;It’s been really nice to get back some control over my own data—I dropped both Goodreads and a paid Notion subscription due to this! The Raspberry Pi has also worked out great, and once I had it I immediately started finding other uses for it. Having an always-online device that can run &lt;a href=&quot;https://syncthing.net/&quot;&gt;Syncthing&lt;/a&gt; to mediate files between my different personal devices (which are often not on at the same time) has been great, and allowed Obsidian to partially replace both Google Drive and Notion for me while keeping data within my own network. &lt;a href=&quot;https://tailscale.com/&quot;&gt;Tailscale&lt;/a&gt; was also really useful here, as it meant I could easily provision TLS certificates for my devices and serve everything over HTTPS, and I could reach my application when I left the house without having to expose the Raspberry Pi to the open internet.&lt;/p&gt;

&lt;p&gt;If you’re interested in the source code, you can find it at &lt;a href=&quot;https://codeberg.org/borntyping/vancelle&quot;&gt;borntyping/vancelle&lt;/a&gt; on Codeberg. It’s named after a librarian from the &lt;em&gt;Old Kingdom&lt;/em&gt; series by Garth Nix.&lt;/p&gt;

</content>
 </entry>
 
 <entry>
   <title>My essential tools</title>
   <link href="/posts/my-essential-tools/"/>
   <updated>2023-06-14T00:00:00+01:00</updated>
   <id>/posts/my-essential-tools</id>
   <content type="html">&lt;p&gt;A common theme across the tools I use frequently is that they all let me do work faster or with less repetition. I thought I’d list the tools I use a lot and wouldn’t want to go without when doing software development.&lt;/p&gt;

&lt;p&gt;I’ve spent a lot of time installing, configuring, and managing my toolkit — probably more time than I’ve saved! However, even if I’ve not saved any time, I find it a lot easier to both focus and get things right when I can automate something. It’s very easy to make mistakes with repetitive tasks, especially if you have to remember a lot of details that aren’t relevant to your current goal.&lt;/p&gt;

&lt;h3 id=&quot;development&quot;&gt;Development&lt;/h3&gt;

&lt;p&gt;These are the tools I use constantly when working on code.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;&lt;a href=&quot;https://www.jetbrains.com/pycharm/&quot;&gt;PyCharm Professional&lt;/a&gt;&lt;/strong&gt;, &lt;strong&gt;&lt;a href=&quot;https://code.visualstudio.com/&quot;&gt;Visual Studio Code&lt;/a&gt;&lt;/strong&gt;, and &lt;strong&gt;&lt;a href=&quot;https://micro-editor.github.io/&quot;&gt;Micro&lt;/a&gt;&lt;/strong&gt;. I use PyCharm by default as JetBrains code intelligence tools greatly improve my productivity on most codebases. Just being able to quickly (and reliably) jump to definitions or usages is enough to make this my favorite editor. I use VSCode or Micro when I want to edit something quickly.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;&lt;a href=&quot;https://git-cola.github.io/&quot;&gt;Git Cola&lt;/a&gt;&lt;/strong&gt;. A very simple GUI for Git. I use it write commit messages and preview the changes that will be included in a commit.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;&lt;a href=&quot;https://github.com/borntyping/switchbox&quot;&gt;Switchbox&lt;/a&gt;&lt;/strong&gt;. My personal git toolbox, used to automate repetitive branch management tasks like rebasing and cleaning up merged branches.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;&lt;a href=&quot;https://fedoraproject.org/&quot;&gt;Fedora&lt;/a&gt;&lt;/strong&gt;. Has a very developer-friendly package ecosystem. Packages are up-to-date, maintained, and usually allow multiple versions of programming languages like Python to be installed side-by-side. Almost everything works out-of-the-box without me having to think about it.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;shell&quot;&gt;Shell&lt;/h3&gt;

&lt;p&gt;I spend a lot of time working in a terminal, so it’s important to me that it’s nice to use. Several of the tools here simply make it easier to do something I could otherwise do by typing out a longer command.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;&lt;a href=&quot;https://atuin.sh/&quot;&gt;Atuin&lt;/a&gt;&lt;/strong&gt;. Store shell history in a sqlite database. I don’t use all of it’s features, but the interactive UI helps me find commands from my shell history much faster.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;&lt;a href=&quot;https://direnv.net/&quot;&gt;Direnv&lt;/a&gt;&lt;/strong&gt;. Load &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.envrc&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.env&lt;/code&gt; files in the current directory. This makes it really easy to manage environment variables for various projects, without making a mess of my global shell.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;&lt;a href=&quot;https://starship.rs/&quot;&gt;Starship&lt;/a&gt;&lt;/strong&gt;. Shell prompt system. Keeps important information front-and-center when working in a terminal. I almost exactly replicated my previous zsh configuration, which was hand-built over years. Works in any shell, so I’m not as tied to zsh as I once was.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;&lt;a href=&quot;https://github.com/ajeetdsouza/zoxide&quot;&gt;zoxide&lt;/a&gt;&lt;/strong&gt;. Quickly jump to recent directories. It saves a lot of time not needing to type out full paths or remember where things are when working in a terminal.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;&lt;a href=&quot;https://beyondgrep.com/&quot;&gt;ack&lt;/a&gt;&lt;/strong&gt;. Quickly search codebases. Defaults to recursive search and ignoring directories like &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.git&lt;/code&gt; or &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;node_modules&lt;/code&gt;, making it a lot easier to use than &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;grep&lt;/code&gt;.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;&lt;a href=&quot;https://github.com/sharkdp/bat&quot;&gt;bat&lt;/a&gt;&lt;/strong&gt; and &lt;strong&gt;&lt;a href=&quot;https://the.exa.website/&quot;&gt;exa&lt;/a&gt;&lt;/strong&gt;. A &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;cat&lt;/code&gt; replacement with syntax highlighting, and a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ls&lt;/code&gt; replacement with slightly nicer output.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;&lt;a href=&quot;https://github.com/cli/cli&quot;&gt;gh&lt;/a&gt;&lt;/strong&gt;. Automate some common GitHub tasks. I mostly use this to clone repositories quickly. I use &lt;a href=&quot;https://desktop.github.com/&quot;&gt;GitHub Desktop&lt;/a&gt; instead when I need to work with git repositories on Windows machines.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;configuration-management&quot;&gt;Configuration management&lt;/h3&gt;

&lt;p&gt;I generally prefer to have all the tools I need installed on my local development machine. I tend to avoid using Docker or Podman for local development, which helps avoid conflicts when using someone else’s workflow that does.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;&lt;a href=&quot;https://github.com/ansible/ansible&quot;&gt;Ansible&lt;/a&gt;&lt;/strong&gt;. I use Ansible to manage my development machines. It’s a lot more flexible than other approaches I’ve used or seen for managing dotfiles. It’s a nice way to abstract over all the different types of packages I need to install.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;&lt;a href=&quot;https://github.com/python-poetry/poetry/&quot;&gt;Poetry&lt;/a&gt;&lt;/strong&gt;. Manage Python dependencies. There’s lots of options in this space, but Poetry works well for me.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;&lt;a href=&quot;https://pypa.github.io/pipx/&quot;&gt;pipx&lt;/a&gt;&lt;/strong&gt;. Install Python executables in a dedicated space. Keeps the Python tools I use separate from my system installation of Python and separate from codebase-specific virtualenvs.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;&lt;a href=&quot;https://www.npmjs.com/package/npx&quot;&gt;npx&lt;/a&gt;&lt;/strong&gt;. Run NodeJS exectuables without installing them.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;cloud-systems&quot;&gt;Cloud systems&lt;/h3&gt;

&lt;p&gt;These are tools that mostly make a different tool easier to use.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;&lt;a href=&quot;https://github.com/99designs/aws-vault/&quot;&gt;aws-vault&lt;/a&gt;&lt;/strong&gt;. Manage AWS credentials. Integrates with your system keyring and issues temporary credentials when needed. Makes it a lot easier to manage lots of different AWS roles.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;&lt;a href=&quot;https://github.com/ahmetb/kubectx&quot;&gt;kubectx + kubens&lt;/a&gt;&lt;/strong&gt;. Quickly switch between Kubernetes contexts and namespaces. I don’t know why this isn’t built into the kubectl CLI.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;&lt;a href=&quot;https://github.com/stern/stern&quot;&gt;stern&lt;/a&gt;&lt;/strong&gt;. Follow logs from multiple containers. If nothing else, not having to restart a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;kubectl logs&lt;/code&gt; command every time a deployment or pod restarts is very helpful, but stern also provides a nice interactive interface for reading logs.&lt;/li&gt;
&lt;/ul&gt;
</content>
 </entry>
 
 <entry>
   <title>Why I have separate personal and work GitHub accounts</title>
   <link href="/posts/github-accounts/"/>
   <updated>2023-06-08T00:00:00+01:00</updated>
   <id>/posts/github-accounts</id>
   <content type="html">&lt;p&gt;Permissions on a GitHub token are opt-in for an organisation, but you can’t opt out of granting permissions for the account the token belongs to.
For instance, if an GitHub App or GitHub OAuth application at my place of work requests permissions to read private repositories (a very common permission needed for basic actions like cloning), the token issued at the end of the authentication flow will always have permissions to access my personal respositories.
Depending on the settings of the organisation, this may be automatically granted, or may require going though the single-sign on process for that organisation before the token is granted permissions to read from private repositories in the organisation.&lt;/p&gt;

&lt;p&gt;If I use the same account for both personal use of GitHub and work use of GitHub, that restricts how carefully I can control access to resources owned by my account.
A particually relevant example of this was the recent &lt;a href=&quot;https://circleci.com/blog/jan-4-2023-incident-report/&quot;&gt;CircleCI breach&lt;/a&gt;, which was the event that prompted me to start using two GitHub accounts.
If my account was used purely for personal use, I could have happily removed the application from my account and not worried about it again.
But, since my job required working with CircleCI I needed to stay authenticated to it and take the risk that any access tokens were still vulnerable after beeing rotated.&lt;/p&gt;

&lt;p&gt;I also work a lot on developer tooling, which means issuing a lot of tokens to work on GitHub applications locally.
These are subject to much the same problems, and importantly, end up on devices that you don’t own and may get used with unfinished or insecure code.&lt;/p&gt;

&lt;p&gt;My GitHub account was created well over a decade ago now - it’s older than most of the companies I work for, and while the risks here are extremely low I’m a lot more comfortable taking the approach of having a less-valuable GitHub account dedicated to corporate use rather than using something that’s been a very public part of my online presense for a long time.&lt;/p&gt;

&lt;p&gt;There have been some other advantages to this split.
I use separate mobile phones for personal use and work use, and this means I can use the GitHub android app without needing to go though corportate SSO on a personal device, or getting work-related GitHub notifcations while I’m not working.
It’s also nice to have separate GitHub contribution graphs for my own personal work and corportate work.&lt;/p&gt;

&lt;p&gt;You can find me on GitHub at &lt;a href=&quot;https://github.com/borntyping&quot;&gt;@borntyping&lt;/a&gt; and &lt;a href=&quot;https://github.com/borntyping-corporate&quot;&gt;@borntyping-corporate&lt;/a&gt;.&lt;/p&gt;

</content>
 </entry>
 
 <entry>
   <title>Final Fantasy 14 microsites</title>
   <link href="/posts/ffxiv-tools/"/>
   <updated>2023-05-16T00:00:00+01:00</updated>
   <id>/posts/ffxiv-tools</id>
   <content type="html">&lt;p&gt;Recently I built two small microsites that I find helpful for playing Final Fantasy 14.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://borntyping.codeberg.page/ffxiv-daily-quest-tracker/&quot;&gt;ffxiv-daily-quest-tracker&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://borntyping.codeberg.page/ffxiv-signposts/&quot;&gt;ffxiv-signposts&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;While working on a &lt;a href=&quot;https://github.com/backstage/backstage/&quot;&gt;Backstage&lt;/a&gt;/&lt;a href=&quot;https://react.dev/&quot;&gt;React&lt;/a&gt; codebase at &lt;a href=&quot;https://www.kaluza.com/&quot;&gt;Kaluza&lt;/a&gt; I wanted to take a look at what else the 2023 JavaScript ecosystem had to offer, and especially wanted to experience JavaScript development “from scratch” in a much smaller codebase.&lt;/p&gt;

&lt;h2 id=&quot;ffxiv-daily-quest-tracker&quot;&gt;&lt;a href=&quot;https://github.com/borntyping/ffxiv-daily-quest-tracker&quot;&gt;ffxiv-daily-quest-tracker&lt;/a&gt;&lt;/h2&gt;

&lt;p&gt;&lt;img src=&quot;/assets/2023-05-16-ffxiv-tools-daily-quest-tracker.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;The first tool I built was a calculator for a series of long quest chains in FFXIV that can only be progressed a small amount each day. I wanted to be able to work out how long each quest chain would take me to finish - being able to visualise my progress kept me a lot more engaged and encouraged me to finish them.&lt;/p&gt;

&lt;p&gt;I used &lt;a href=&quot;https://alpinejs.dev/&quot;&gt;Alpine.js&lt;/a&gt; for this, alongside &lt;a href=&quot;https://getbootstrap.com/&quot;&gt;Bootstrap&lt;/a&gt;. I was already pretty familiar with Bootstrap, and wanted to build something quite quickly so avoided learning a new CSS framework. &lt;a href=&quot;https://alpinejs.dev/&quot;&gt;Alpine.js&lt;/a&gt; seemed pretty similar to &lt;a href=&quot;https://htmx.org/&quot;&gt;htmx&lt;/a&gt;, a library I’d had some success with previously for a professional project. I didn’t use it here as I wanted to be able to deploy this site to GitHub Pages and avoid the need for a server.&lt;/p&gt;

&lt;p&gt;The JavaScript I wrote using Alpine.js was almost certainly not very idiomatic, but it made it very easy to provide some classes containing the structure of my data and then link that into my HTML using &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;lt;template&amp;gt;&lt;/code&gt; tags. The two-way binding meant I didn’t have to do anything complex to link the fields where a user would provide information to my data model. When the values of those fields change the data model and all the HTML using it immediately updates, even though much of that was in a nested class structure — I didn’t need to structure my codebase to fit the conventions of the library like I would have done with React or Vue.&lt;/p&gt;

&lt;p&gt;Alpine was very simple to use, and I didn’t need to use any build step or dependency management at all, so the end result was very easy to deploy directly to GitHub Pages. The code for the site is ~4 small files (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;index.html&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;index.css&lt;/code&gt; for styles, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;index.js&lt;/code&gt; for code and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;index.json&lt;/code&gt; for data). I’d very happily use Alpine.js again for a website.&lt;/p&gt;

&lt;h2 id=&quot;ffxiv-signposts&quot;&gt;&lt;a href=&quot;https://github.com/borntyping/ffxiv-signposts&quot;&gt;ffxiv-signposts&lt;/a&gt;&lt;/h2&gt;

&lt;p&gt;&lt;img src=&quot;/assets/2023-05-16-ffxiv-tools-signposts.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;The second site was a list of links to other people’s FFXIV sites that I either used or had been recommended. There’s a wonderful ecosystem of helpful tools people have built for this game, even though there’s not much support from the developers of the game for it (e.g. Destiny 2 provides a very full-featured API with access to information about most things in the game, which has been a great base for developers to build extensive and powerful interfaces to that data).&lt;/p&gt;

&lt;p&gt;This site displayed a long list of cards that each had a link to the site and a list of tags. Each tag had a one-line description alongside it explaining how the site related to the tag. There were also some special tags for “complexity” (how much a player would need to know to use the site) and whether a site was an official site from the developers of the game. A pair of toolbars at the top allow selecting only the cards in a certain category, or tag within that category.&lt;/p&gt;

&lt;p&gt;I used &lt;a href=&quot;https://vuejs.org/&quot;&gt;Vue.js&lt;/a&gt; for this, alongside &lt;a href=&quot;https://bulma.io/&quot;&gt;Bulma&lt;/a&gt;. I wanted to use a framework closer to React that used a component model, so that I could understand the approach better by seeing different ways to implement it. I also wanted to try something different to Bootstrap UI when it came to CSS, as I’ve very rarely used anything else for a HTML project in a long time.&lt;/p&gt;

&lt;p&gt;I was initially attracted to Vue.js for it’s promise of being able to work with or without a build step or server, and hoped to take the same approach I did using Alpine.js. That didn’t turn out to be very practical, as a lot of it’s syntax is only supported via a compilation step. I wanted to look at using &lt;a href=&quot;https://deno.com/&quot;&gt;Deno&lt;/a&gt; or &lt;a href=&quot;https://bun.sh/&quot;&gt;Bun&lt;/a&gt; to manage dependencies and compilation, but I couldn’t find enough documentation to bootstrap a working build process — I couldn’t find any documentation on how to actually build a Vue.js project, only instructions on how to use &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;create-vue&lt;/code&gt; to generate all the files I’d need to setup a project with their recommended approach. The development experience and build process using npm and &lt;a href=&quot;https://vitejs.dev/&quot;&gt;Vite&lt;/a&gt; was actually pretty good, but it still frustrates me that I’ve no idea how it works.&lt;/p&gt;

&lt;p&gt;Building components with Vue.js was okay. I didn’t like the templating language much compared to JSX/TSX, and it was frustrating that I’d need &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;{brackets}&lt;/code&gt; in some places but not others. I loved that I could write completely normal CSS in a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;lt;style&amp;gt;&lt;/code&gt; tag and have it scoped to the component though, and that was a far nicer experience than the JSS approach I’d previously used in React. Adding animations around changes to the page (e.g. when switching tabs) was incredibly easy using &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;lt;TransitionGroup&amp;gt;&lt;/code&gt; with very pleasing results.&lt;/p&gt;

&lt;p&gt;I’m not sure I’ve come out with any strong opinion on using Vue.js in the future — in many ways it felt like a slightly different React, though I think for a good comparison I’d need to use both on similar sized projects. I made very little use of Vue.js’ features, especially reactivity which it silently got right without me needing to think about it.&lt;/p&gt;

&lt;p&gt;Meanwhile Bulma was a surprisingly good experience. It’s a lot smaller in size and scope than Bootstrap, and doesn’t come with a full set of components or a JavaScript library. Given that, I expected to be writing far more CSS to extend what it provided, but I actually found myself writing far less CSS than I would in a comparable Bootstrap project. It covered every basic use case I had, and was extremely readable with verbose selectors like &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;field is-grouped is-grouped-multiline&lt;/code&gt;. This library will probably become the first thing I reach for next time I need a CSS framework.&lt;/p&gt;

</content>
 </entry>
 
 <entry>
   <title>Cleaning up merged git branches</title>
   <link href="/posts/switchbox/"/>
   <updated>2023-03-31T00:00:00+01:00</updated>
   <id>/posts/switchbox</id>
   <content type="html">&lt;p&gt;Over the last few years (and jobs) I’ve collected a number of scripts and processes for working with git and centralised forges like GitHub and GitLab. &lt;a href=&quot;https://github.com/borntyping/switchbox&quot;&gt;switchbox&lt;/a&gt; is my latest attempt to collect the bits of that automation I’m currently using, and mostly does some fiddly work to discover branches that have been merged, rebased, or squashed into the main branch of a git repository.&lt;/p&gt;

&lt;p&gt;The most useful bit from my previous tools was the ability to clean up branches and references I wasn’t using anymore. Teams at previous jobs had all enforced a standard way to apply merge requests — for instance, my team Datto enforced linear merge commits. My previous attempt at automating this cleanup, &lt;a href=&quot;https://github.com/datto/git-river&quot;&gt;git-river&lt;/a&gt;, handled standard merge commits but no other cases.&lt;/p&gt;

&lt;p&gt;However, my current job doesn’t yet enforce any standard repository configuration and repositories use a mix of rebasing, squashing, and merge commits. I wanted switchbox to handle any type of merge I could think of, so that I wouldn’t need to implement this next time I encountered a project that used a different approach to merging changes.&lt;/p&gt;

&lt;p&gt;GitHub supports “commit merging”, “commit squashing”, and “commit rebasing”, which I used to name the three strategies I used to find merged branches in switchbox. It does cover a couple other cases beyond those though.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;
    &lt;p&gt;Merge commits are easy: &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;git branch --list --merged $upstream&lt;/code&gt; will list every branch merged into &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;$upstream&lt;/code&gt;. Calling git and parsing it’s output here is simple.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Rebased branches are a little more difficult: &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;git cherry $upstream $branch&lt;/code&gt; will tell us if each commit in &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;$branch&lt;/code&gt; has an equivalent commit in &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;$upstream&lt;/code&gt;. This works for rebases and cherry-picked commits, which both take the contents of a commit and reapply it onto a different parent commit — the commit hash will change, but the contents should be the same.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Squashed commits are painful. We can’t compare branches or commits, so we’re left comparing diffs between commits.&lt;/p&gt;

    &lt;ol&gt;
      &lt;li&gt;
        &lt;p&gt;The first thing to do is find a “merge base” - the common ancestor between our upstream branch and the branch that might have been squashed into it. We can do this with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;git merge-base $upstream $branch&lt;/code&gt;.&lt;/p&gt;
      &lt;/li&gt;
      &lt;li&gt;
        &lt;p&gt;Then we build a diff between the merge base and our potentially-squashed branch with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;git diff $branch $merge_base&lt;/code&gt;.&lt;/p&gt;
      &lt;/li&gt;
      &lt;li&gt;
        &lt;p&gt;Then we iterate through each commit in our upstream branch since the merge base. For each of these commits, we check if the the diff between that commit and it’s parent matches the diff we built in the previous step, with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;git diff $commit ^$commit&lt;/code&gt;. If it does, that means the commit is the result of squashing our branch.&lt;/p&gt;
      &lt;/li&gt;
    &lt;/ol&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Once we’ve found our merged branches from these three strategies, switchbox then deletes them. It runs &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;git remote update&lt;/code&gt; before doing any of this, to update all branches from their remote equivalents.&lt;/p&gt;

</content>
 </entry>
 
 <entry>
   <title>Merging a subdirectory of a git repository, revisited</title>
   <link href="/posts/git-merge-subdirectory-revisited/"/>
   <updated>2020-07-24T00:00:00+01:00</updated>
   <id>/posts/git-merge-subdirectory-revisited</id>
   <content type="html">&lt;p&gt;My previous post on the topic used git’s builtin &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;filter-branch&lt;/code&gt; subcommand, which has been superseded by the &lt;a href=&quot;https://github.com/newren/git-filter-repo&quot;&gt;git-filter-repo&lt;/a&gt; project.&lt;/p&gt;

&lt;p&gt;In the repository being merged, we create a branch named &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;filtered&lt;/code&gt; that only contains the contents of the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;src/&lt;/code&gt; directory, moved to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;src/other/&lt;/code&gt; - the branch no longer contains files that were outside the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;src/&lt;/code&gt; directory.&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;git switch &lt;span class=&quot;nt&quot;&gt;-c&lt;/span&gt; filtered
git-filter-repo &lt;span class=&quot;nt&quot;&gt;--path&lt;/span&gt; src/ &lt;span class=&quot;nt&quot;&gt;--path-rename&lt;/span&gt; src/:src/other/
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;In the repository the subdirectory is being merged into:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;git remote add other ../other-repository/
git fetch other
git merge &lt;span class=&quot;nt&quot;&gt;-s&lt;/span&gt; ours &lt;span class=&quot;nt&quot;&gt;--no-commit&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;--allow-unrelated-histories&lt;/span&gt; other/filtered
git read-tree &lt;span class=&quot;nt&quot;&gt;-mu&lt;/span&gt; other/filtered
git commit
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
</content>
 </entry>
 
 <entry>
   <title>Hiding in D&D</title>
   <link href="/posts/hide-guide/"/>
   <updated>2018-07-09T00:00:00+01:00</updated>
   <id>/posts/hide-guide</id>
   <content type="html">&lt;p&gt;I found the mechancies for hiding&lt;sup id=&quot;fnref:hiding&quot;&gt;&lt;a href=&quot;#fn:hiding&quot; class=&quot;footnote&quot; rel=&quot;footnote&quot; role=&quot;doc-noteref&quot;&gt;1&lt;/a&gt;&lt;/sup&gt; in D&amp;amp;D 5E a little confusing to understand, as the rules are scattered in a few places in the book.&lt;/p&gt;

&lt;p&gt;As an action (or a bonus action for a Rogue), a character can Hide&lt;sup id=&quot;fnref:hide&quot;&gt;&lt;a href=&quot;#fn:hide&quot; class=&quot;footnote&quot; rel=&quot;footnote&quot; role=&quot;doc-noteref&quot;&gt;2&lt;/a&gt;&lt;/sup&gt;. They make a Dexterity (Stealth) check, with the total becoming the DC for other creatures to notice you. A character can’t hide if they can be clearly seen by other creatures, but they don’t need to been in full cover.&lt;/p&gt;

&lt;p&gt;The DM compares the passive Wisdom (Perception) of other creatures to see if they notice the character. Creatures can also use the Search&lt;sup id=&quot;fnref:search&quot;&gt;&lt;a href=&quot;#fn:search&quot; class=&quot;footnote&quot; rel=&quot;footnote&quot; role=&quot;doc-noteref&quot;&gt;3&lt;/a&gt;&lt;/sup&gt;  action, making a Wisdom (Perception) check to discover the character. In either case, success on the creature’s check means the character is no longer hidden.&lt;/p&gt;

&lt;p&gt;A character has advantage on attack rolls against creatures that can’t see them&lt;sup id=&quot;fnref:unseen&quot;&gt;&lt;a href=&quot;#fn:unseen&quot; class=&quot;footnote&quot; rel=&quot;footnote&quot; role=&quot;doc-noteref&quot;&gt;4&lt;/a&gt;&lt;/sup&gt; - for a Rogue, this means they can use Sneak Attack. Once the attack hits or misses, the character is no longer hidden.&lt;/p&gt;

&lt;p&gt;Some addition clarifications come from the game designers, but are not in the books:&lt;/p&gt;

&lt;p&gt;The rules for unseen say “you give away your location”, but this does mean the character is no longer hidden &lt;sup id=&quot;fnref:attacking&quot;&gt;&lt;a href=&quot;#fn:attacking&quot; class=&quot;footnote&quot; rel=&quot;footnote&quot; role=&quot;doc-noteref&quot;&gt;5&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;

&lt;p&gt;A character can be hidden from a creature even if the creature knows where they are&lt;sup id=&quot;fnref:location&quot;&gt;&lt;a href=&quot;#fn:location&quot; class=&quot;footnote&quot; rel=&quot;footnote&quot; role=&quot;doc-noteref&quot;&gt;6&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;

&lt;p&gt;A hidden character can lean out or step out of cover to make an attack without being discovered&lt;sup id=&quot;fnref:cover&quot;&gt;&lt;a href=&quot;#fn:cover&quot; class=&quot;footnote&quot; rel=&quot;footnote&quot; role=&quot;doc-noteref&quot;&gt;7&lt;/a&gt;&lt;/sup&gt;. They are still discovered when the attack hits or misses.&lt;/p&gt;

&lt;p&gt;A DM might call that hiding in the same spot results in disadvantage on the Dexterity (Stealth) check&lt;sup id=&quot;fnref:hidingagain&quot;&gt;&lt;a href=&quot;#fn:hidingagain&quot; class=&quot;footnote&quot; rel=&quot;footnote&quot; role=&quot;doc-noteref&quot;&gt;8&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;

&lt;div class=&quot;footnotes&quot; role=&quot;doc-endnotes&quot;&gt;
  &lt;ol&gt;
    &lt;li id=&quot;fn:hiding&quot;&gt;
      &lt;p&gt;&lt;a href=&quot;https://www.dndbeyond.com/compendium/rules/basic-rules/using-ability-scores#Initiative&quot;&gt;Hiding - Using Ability Scores - Basic Rules&lt;/a&gt; &lt;a href=&quot;#fnref:hiding&quot; class=&quot;reversefootnote&quot; role=&quot;doc-backlink&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id=&quot;fn:hide&quot;&gt;
      &lt;p&gt;&lt;a href=&quot;https://www.dndbeyond.com/compendium/rules/basic-rules/combat#Hide&quot;&gt;Hide - Combat - Basic Rules&lt;/a&gt; &lt;a href=&quot;#fnref:hide&quot; class=&quot;reversefootnote&quot; role=&quot;doc-backlink&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id=&quot;fn:search&quot;&gt;
      &lt;p&gt;&lt;a href=&quot;https://www.dndbeyond.com/compendium/rules/basic-rules/combat#Search&quot;&gt;Search - Combat - Basic Rules&lt;/a&gt; &lt;a href=&quot;#fnref:search&quot; class=&quot;reversefootnote&quot; role=&quot;doc-backlink&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id=&quot;fn:unseen&quot;&gt;
      &lt;p&gt;&lt;a href=&quot;https://www.dndbeyond.com/compendium/rules/basic-rules/combat#UnseenAttackersandTargets&quot;&gt;Unseen Attackers and Targets - Combat - Basic Rules&lt;/a&gt; &lt;a href=&quot;#fnref:unseen&quot; class=&quot;reversefootnote&quot; role=&quot;doc-backlink&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id=&quot;fn:attacking&quot;&gt;
      &lt;p&gt;&lt;a href=&quot;https://twitter.com/JeremyECrawford/status/834926797657550848&quot;&gt;@JeremyECrawford: “You’re no longer hidden the moment your attack hits or misses.”&lt;/a&gt; &lt;a href=&quot;#fnref:attacking&quot; class=&quot;reversefootnote&quot; role=&quot;doc-backlink&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id=&quot;fn:location&quot;&gt;
      &lt;p&gt;&lt;a href=&quot;https://twitter.com/JeremyECrawford/status/834885800626008064&quot;&gt;“Issue that came up most often at Winter Fantasy was rogues wanting to always hide around corner, next end move out and attack hidden.” @JeremyECrawford: “That’s a legitimate use of Cunning Action.”&lt;/a&gt; &lt;a href=&quot;#fnref:location&quot; class=&quot;reversefootnote&quot; role=&quot;doc-backlink&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id=&quot;fn:cover&quot;&gt;
      &lt;p&gt;&lt;a href=&quot;https://twitter.com/JeremyECrawford/status/836115576296611841&quot;&gt;@JeremyECrawford: “Shooting from cover and running out into the open aren’t the same thing.”&lt;/a&gt; &lt;a href=&quot;https://twitter.com/JeremyECrawford/status/834926309843189760&quot;&gt;“So shooting a bow while hidden maybe grant the advantage, but running out in melee dont, does that sound about right?” @JeremyECrawford: “It does!”&lt;/a&gt; &lt;a href=&quot;#fnref:cover&quot; class=&quot;reversefootnote&quot; role=&quot;doc-backlink&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id=&quot;fn:hidingagain&quot;&gt;
      &lt;p&gt;&lt;a href=&quot;https://twitter.com/mikemearls/status/503967787346575360&quot;&gt;@mikemearls: “DM’s call - suggest atk with advantage, but disad to hide again. IMO if rogue sees target from hiding while hidden, can attack”&lt;/a&gt; &lt;a href=&quot;#fnref:hidingagain&quot; class=&quot;reversefootnote&quot; role=&quot;doc-backlink&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
  &lt;/ol&gt;
&lt;/div&gt;
</content>
 </entry>
 
 <entry>
   <title>Package versioning</title>
   <link href="/posts/package-versioning/"/>
   <updated>2018-06-21T00:00:00+01:00</updated>
   <id>/posts/package-versioning</id>
   <content type="html">&lt;p&gt;This post descibes my approach to versioning packaged applications, using version numbers that look like this: &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;3.2.0-2109.12272&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The first part (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;3.2.0&lt;/code&gt;) is a &lt;strong&gt;manually incremented version number&lt;/strong&gt;. It’s incremented for changes that need to be communicated and talked about - “our new feature will be in version 4, which will be deployed next week”.&lt;/p&gt;

&lt;p&gt;The second part (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;2109.12272&lt;/code&gt;) is an &lt;strong&gt;automatically incremented release number&lt;/strong&gt;. It’s entirely managed by the CI/CD process that packages the software. In my case, that’s done using GitLab, and the number is composed from the pipeline and job identifiers.&lt;/p&gt;

&lt;p&gt;Both of these numbers increase monotonically, so that a each time the CI/CD process runs it builds a package with a higher version and release number without a human needing to remember to manually increase it. The automatic release number also ensures that you never build a package with the same version number as an existing one, and so as long as you keep older packages around you can always downgrade to a previous version.&lt;/p&gt;

&lt;p&gt;The approach works very neatly with packaging tools like RPM, which have seperate version and release fields. My build process uses a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;version.txt&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;release.txt&lt;/code&gt; file, the latter of which is created at build time. Both are included in the package name and in the package itself, allowing the application to read them at runtime to display it’s own version number.&lt;/p&gt;

&lt;p&gt;You could quite easily remove the manually incremented version number and loose no technical capability. However, I find simple, easy to remember numbers much easier to communicate than the long digit sequences an automatic release number will provide. Asking someone to check if they are running “version 4 or above” is much simpler than asking if the version number is “12034 or above” - people remember small numbers much more easily. You can also still use familiar &lt;a href=&quot;https://semver.org/&quot;&gt;SemVer&lt;/a&gt; like semantics to describe the scale of change - “we’re upgrading from version 9034 to 9725” says very little about the changes involved, whereas “we’re upgading from version 4.1 to 5.3” implies multiple large changes, or “we’re upgading from version 4.1.0 to 4.1.1” implies a small fix. I find this is especially useful when communicating with non-technical people who use the application.&lt;/p&gt;

&lt;p&gt;However, a downside to this is that humans will likely end up being very lax about changing the version number. This is fine for me - I only use it to commuicate major changes and not small patches - but is unlikely to work for all cases. It’s particually unsuited for libraries or APIs, where tracking API changes is important and strictly following &lt;a href=&quot;https://semver.org/&quot;&gt;SemVer&lt;/a&gt; may be important.&lt;/p&gt;

&lt;h2 id=&quot;managing-version-numbers-in-python&quot;&gt;Managing version numbers in Python&lt;/h2&gt;

&lt;p&gt;&lt;em&gt;The rest of this post goes describes an implementation of the above approach that I use for Python packages.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;A &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;release.txt&lt;/code&gt; file includes the manually updated version number, and an optional &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;release.txt&lt;/code&gt; file includes the CI/CD pipeline and job number. The &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;__version__&lt;/code&gt; attribute in my Python module is set by reading from these files instead of being statically defined.&lt;/p&gt;

&lt;h5 id=&quot;__initpy__&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;__init.py__&lt;/code&gt;&lt;/h5&gt;

&lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&quot;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;An example package.&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&quot;&quot;&lt;/span&gt;

&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;example.version&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;__author__&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;sh&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;Sam Clements&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&apos;&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;__version__&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;example&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;version&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;read_version&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;__file__&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h5 id=&quot;versionpy&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;version.py&lt;/code&gt;&lt;/h5&gt;

&lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&quot;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;Read version numbers from an embedded text file.&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&quot;&quot;&lt;/span&gt;

&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;pathlib&lt;/span&gt;


&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;read_version&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;relative_path&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;version_path&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;pathlib&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;Path&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;relative_path&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;with_name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;version.txt&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;release_path&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;pathlib&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;Path&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;relative_path&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;with_name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;release.txt&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

    &lt;span class=&quot;n&quot;&gt;version&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;version_path&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;read_text&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;().&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;strip&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;release_path&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;exists&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;():&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;release&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;release_path&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;read_text&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;().&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;strip&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;version&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;{}-{}&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;format&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;version&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;release&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;version&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;This is included in the Python package metadata by using the &lt;a href=&quot;https://setuptools.readthedocs.io/en/latest/setuptools.html#configuring-setup-using-setup-cfg-files&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;attr:&lt;/code&gt; directive&lt;/a&gt; in the &lt;a href=&quot;https://setuptools.readthedocs.io/&quot;&gt;setuptools&lt;/a&gt; &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;setup.cfg&lt;/code&gt; file (a relatively recent feature that allows package metadata to be set by reading values from a Python module). For this to work properly, the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;__init__&lt;/code&gt; file has to import as little as possible, so that it’s not including dependencies that may not be installed when you first build or install the package. For my projects, the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;__init__.py&lt;/code&gt; file normally only includes metadata and absolute essentials (e.g. exit handlers with no dependencies).&lt;/p&gt;

&lt;div class=&quot;language-ini highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nn&quot;&gt;[metadata]&lt;/span&gt;
&lt;span class=&quot;py&quot;&gt;name&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;example&lt;/span&gt;
&lt;span class=&quot;py&quot;&gt;version&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;attr: example.__version__&lt;/span&gt;
&lt;span class=&quot;err&quot;&gt;...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;With everything setup to build the application as a Python package, I also use this in a Makefile that builds an RPM with the Python package using &lt;a href=&quot;https://github.com/jordansissel/fpm&quot;&gt;fpm&lt;/a&gt;.&lt;/p&gt;

&lt;div class=&quot;language-makefile highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nv&quot;&gt;CI_PIPELINE_ID&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;?=&lt;/span&gt;0
&lt;span class=&quot;nv&quot;&gt;CI_JOB_ID&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;?=&lt;/span&gt;0

&lt;span class=&quot;nv&quot;&gt;NAME&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;$(&lt;/span&gt;shell python setup.py &lt;span class=&quot;nt&quot;&gt;--name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;nv&quot;&gt;VERSION&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;$(&lt;/span&gt;shell &lt;span class=&quot;nb&quot;&gt;cat &lt;/span&gt;odyssey/version.txt&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;nv&quot;&gt;RELEASE&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;$(&lt;/span&gt;CI_PIPELINE_ID&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;.&lt;span class=&quot;p&quot;&gt;$(&lt;/span&gt;CI_JOB_ID&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;nl&quot;&gt;example/release.txt&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt;
	&lt;span class=&quot;nb&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;$(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;RELEASE&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$@&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;
	
&lt;span class=&quot;nl&quot;&gt;$(NAME)-$(VERSION)-$(RELEASE).x86_64.rpm&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt;
	fpm &lt;span class=&quot;nt&quot;&gt;-s&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;dir&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-t&lt;/span&gt; rpm &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
	&lt;span class=&quot;nt&quot;&gt;--name&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;$(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;NAME&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
	&lt;span class=&quot;nt&quot;&gt;--depends&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;python&quot;&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
	&lt;span class=&quot;nt&quot;&gt;--version&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;$(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;VERSION&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
	&lt;span class=&quot;nt&quot;&gt;--iteration&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;$(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;RELEASE&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
	&lt;span class=&quot;nt&quot;&gt;--maintainer&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;Sam Clements&quot;&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
	.
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

</content>
 </entry>
 
 <entry>
   <title>Merging a subdirectory of a git repository</title>
   <link href="/posts/git-merge-subdirectory/"/>
   <updated>2016-06-29T00:00:00+01:00</updated>
   <id>/posts/git-merge-subdirectory</id>
   <content type="html">&lt;p&gt;In the repository being merged:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;git checkout &lt;span class=&quot;nt&quot;&gt;-b&lt;/span&gt; temp
git filter-branch &lt;span class=&quot;nt&quot;&gt;--prune-empty&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;--subdirectory-filter&lt;/span&gt; &amp;lt;subdirectory&amp;gt;/ temp
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;In the repository the subdirectory is being merged into:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;git remote add other ../&amp;lt;other-repository&amp;gt;
git fetch other
git merge &lt;span class=&quot;nt&quot;&gt;-s&lt;/span&gt; ours &lt;span class=&quot;nt&quot;&gt;--no-commit&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;--allow-unrelated-histories&lt;/span&gt; other/temp
git read-tree &lt;span class=&quot;nt&quot;&gt;--prefix&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&amp;lt;path&amp;gt; &lt;span class=&quot;nt&quot;&gt;-u&lt;/span&gt; other/temp
git commit
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Done!&lt;/p&gt;

&lt;p&gt;The output of the merge should look something like this:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;$ cd other-repository
$ git checkout -b temp
Switched to a new branch &apos;temp&apos;
$ git filter-branch --prune-empty --subdirectory-filter other/ temp
Rewrite 82e8de6f33e9e4e797e0331aac1fc602f68f6bfa (13/13) (0 seconds passed, remaining 0 predicted)    
Ref &apos;refs/heads/temp&apos; was rewritten
$ cd ../repository
$ git merge -s ours --no-commit --allow-unrelated-histories other/temp
Automatic merge went well; stopped before committing as requested
$ git read-tree --prefix=other -u other/temp
$ git commit
[feature/merge bc00b33] Merge remote-tracking branch &apos;other/temp&apos; into feature/FTD-768-build
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
</content>
 </entry>
 
 <entry>
   <title>Interesting papers</title>
   <link href="/posts/interesting-papers/"/>
   <updated>2015-12-02T00:00:00+00:00</updated>
   <id>/posts/interesting-papers</id>
   <content type="html">&lt;ul&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;strong&gt;&lt;a href=&quot;http://sigops.org/sosp/sosp15/current/2015-Monterey/printable/008-tang.pdf&quot;&gt;Holistic Configuration Management at Facebook&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

    &lt;p&gt;How Facebook manages and distributes configuration - from small JSON files to
huge machine-learning datasets - at a massive scale.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;strong&gt;&lt;a href=&quot;http://ppig.org/sites/default/files/2015-PPIG-26th-Sarkar.pdf&quot;&gt;The impact of syntax colouring on program comprehension&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

    &lt;p&gt;Advait Sarkar, 2015.&lt;/p&gt;

    &lt;p&gt;Study measuring the effects of syntax highlighting - notably that it is
useful, and programmers spend less time focusing on keywords when using it.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;strong&gt;&lt;a href=&quot;http://www.tdcommons.org/cgi/viewcontent.cgi?article=1092&amp;amp;context=dpubs_series&quot;&gt;Google Votes: A Liquid Democracy Experiment on a Corporate Social Network&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

    &lt;p&gt;Hardt, Steve and Lopes, Lia C. R., 2015.&lt;/p&gt;

    &lt;p&gt;An experimental voting system built and used inside Google that uses software
to mix direct and representative democracy by allowing users to vote directly
or entrust their vote to another user.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;strong&gt;&lt;a href=&quot;https://dl.dropboxusercontent.com/u/1018963/Articles/SpotifyScaling.pdf&quot;&gt;Scaling Agile at Spotify&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

    &lt;p&gt;Henrik Kniberg &amp;amp; Anders Ivarsson (2012).&lt;/p&gt;

    &lt;p&gt;Describes how Spotify has previously organised their engineers into tribes,
squads, chapters and guilds; aiming to avoid layers of bureaucracy and other
problems often associated with large numbers of employees.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;strong&gt;&lt;a href=&quot;https://www.blackhat.com/docs/eu-14/materials/eu-14-Selvi-Bypassing-HTTP-Strict-Transport-Security-wp.pdf&quot;&gt;Bypassing HTTP Strict Transport Security&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

    &lt;p&gt;Jose Selvi, 2014&lt;/p&gt;

    &lt;p&gt;Attacking NTP to force HSTS policies to expire.&lt;/p&gt;
  &lt;/li&gt;
&lt;/ul&gt;

</content>
 </entry>
 
 <entry>
   <title>Error handling in Rust</title>
   <link href="/posts/error-handling-in-rust/"/>
   <updated>2015-02-06T00:00:00+00:00</updated>
   <id>/posts/error-handling-in-rust</id>
   <content type="html">&lt;p&gt;A short cheatsheet for dealing with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Return&lt;/code&gt; values in &lt;a href=&quot;http://rust-lang.org/&quot;&gt;Rust&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;When a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Result&lt;/code&gt; is &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Ok&amp;lt;T&amp;gt;&lt;/code&gt;:&lt;/p&gt;

&lt;div class=&quot;language-rust highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;result&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;Result&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;str&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;str&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;Ok&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Succeeded&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;result&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.is_ok&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;result&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.is_err&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;result&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.ok&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;Some&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Succeeded&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;result&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.err&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;None&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;other_result&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;Result&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;str&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;str&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;Ok&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Other result&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;result&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.and&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;other_result&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;Ok&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Other result&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;result&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.or&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;other_result&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;Ok&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Succeeded&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;fn&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;example&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;str&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;Result&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;str&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;str&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;Ok&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Example&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;result&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.and_then&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;example&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;Ok&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Example&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;result&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.or_else&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;example&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;Ok&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Succeeded&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;When a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Result&lt;/code&gt; is &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Err&amp;lt;E&amp;gt;&lt;/code&gt;:&lt;/p&gt;

&lt;div class=&quot;language-rust highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;result&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;Result&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;str&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;str&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;Err&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Failed&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;result&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.is_ok&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;result&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.is_err&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;result&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.ok&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;None&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;result&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.err&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;Some&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Failed&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;other_result&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;Result&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;str&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;str&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;Ok&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Other result&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;result&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.and&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;other_result&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;Err&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Failed&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;result&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.or&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;other_result&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;Ok&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Other result&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;fn&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;example&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;str&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;Result&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;str&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;str&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;Ok&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Example&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;result&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.and_then&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;example&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;Err&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Failed&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;result&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.or_else&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;example&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;Ok&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Example&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;When an &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Option&lt;/code&gt; is &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Some&amp;lt;T&amp;gt;&lt;/code&gt;:&lt;/p&gt;

&lt;div class=&quot;language-rust highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;option&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;Option&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;str&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;Some&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Example&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;option&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.is_some&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;option&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.is_none&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;option&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.unwrap&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;Example&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;option&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.unwrap_or&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Other&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;Example&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;option&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.expect&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;the world is ending&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;Example&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;When an &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Option&lt;/code&gt; is &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;None&lt;/code&gt;:&lt;/p&gt;

&lt;div class=&quot;language-rust highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;option&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.is_some&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;option&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.is_none&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;option&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.unwrap&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;// panic!()&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;option&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.unwrap_or&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Other&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;Other&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;option&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.expect&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;the world is ending&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;// panic!(&quot;the world is ending&quot;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
</content>
 </entry>
 
 <entry>
   <title>Backups are important</title>
   <link href="/posts/backups-are-important/"/>
   <updated>2014-11-06T00:00:00+00:00</updated>
   <id>/posts/backups-are-important</id>
   <content type="html">&lt;p&gt;Your laptop will die. Your hard drive will fail. You will lose your USB key. You’ll have a power cut halfway through typing up your essay. Accidents happen, and making sure you don’t lose work - especially assignments and essays - is important.&lt;/p&gt;

&lt;p&gt;Any important work you do should be saved in multiple places. At a minimum, you should have at least two copies on physically separate devices. Preferably, you should have a third copy - this means that if one copy is destroyed or inaccessible, you still have two copies, and are still protected from a second device failing.&lt;/p&gt;

&lt;p&gt;Geographical separation is also useful, and makes it much harder to lose work - this doesn’t need to mean keeping copies of your work in another country, but you should avoid keeping multiple copies in the same place. For example, if you have one copy on a laptop and another on a USB key, you may want to consider not keeping them in the same backpack - if the backpack was stolen, you would lose both copies of the work.&lt;/p&gt;

&lt;h3 id=&quot;work-in-progress&quot;&gt;Work in progress&lt;/h3&gt;

&lt;p&gt;As well as making sure you have multiple copies of your files, you should make sure that you save changes to your files while you are working on them. Most editors will have a feature that automatically saves a copy of your work every few minutes - &lt;em&gt;make sure you enable it&lt;/em&gt;. You don’t need to make a full backup every time you write a sentence, but making sure your work is saved regularly will save you from losing all the work you did that day when your word processor crashes or your laptop runs out of battery.&lt;/p&gt;

&lt;h3 id=&quot;online-file-storage&quot;&gt;Online file storage&lt;/h3&gt;

&lt;p&gt;Various online services allow you to synchronise files and folders between online storage and multiple computers. This is one of the easiest ways to ensure you have backup copies of your work, and is extremely useful when working on something from multiple computers. You can also access your files though a web browser or mobile application, so you’re almost always able to access them.&lt;/p&gt;

&lt;p&gt;Online storage services generally have a free plan, with paid plans available for additional features or extra space. This post doesn’t cover their paid features, and unless noted each service has applications for Linux, OSX and Windows, as well as Android and iOS phones.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://www.dropbox.com/&quot;&gt;Dropbox&lt;/a&gt; provides 2GB of storage (with extra space for using certain features or recruiting people), keeps a 30 day history of changes to files, and has applications for almost any device or operating system.&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://www.google.com/intl/en/drive/&quot;&gt;Google Drive&lt;/a&gt; provides 15GB of storage, and integrates with a set of office software that you can use from your browser or phone (word processing, spreadsheets, presentations). It’s missing a Linux application.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you have existing services or devices you want a service to work with, Amazon and Apple also provide their own storage offerings: &lt;a href=&quot;https://www.amazon.co.uk/clouddrive/learnmore&quot;&gt;Amazon Cloud Drive&lt;/a&gt; provides 5GB of space, though doesn’t have much to show off aside from being available on Amazon Fire devices. &lt;a href=&quot;https://www.icloud.com/&quot;&gt;iCloud&lt;/a&gt; is part of iOS and OSX, and you need an Apple device to use it, and there’s no Android client.&lt;/p&gt;

&lt;h3 id=&quot;version-control&quot;&gt;Version Control&lt;/h3&gt;

&lt;p&gt;&lt;a href=&quot;http://git-scm.com/&quot;&gt;Git&lt;/a&gt; can be used to incrementally record changes to your work and push/pull them to other repositories. &lt;a href=&quot;http://mercurial.selenic.com/&quot;&gt;Mercurial&lt;/a&gt; is a simpler and more user friendly alternative to Git, but both will need you to spend some time learning how version control systems work and how to use the tools they provide. For Computer Science students, basic knowledge of how to use Git and version control is essential, and it’s worth knowing how to use at least one version management system well as it’s almost essential for any software project. Various hosting services for Git allow you to store and publish repositories online.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;http://github.com/&quot;&gt;GitHub&lt;/a&gt; is the most popular, and is used for many open source projects. You can publish an unlimited number of public repositories, but private repositories either require the &lt;a href=&quot;https://education.github.com/pack&quot;&gt;Student Developer Pack&lt;/a&gt; (which provides 2 private GitHub repositories along with many other goodies), or a paid plan.&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;http://bitbucket.org/&quot;&gt;BitBucket&lt;/a&gt; provides unlimited public and private repositories. It’s similar to Github, but provides some different features and is aimed at companies and teams rather than individuals.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You can use Git and Mercurial on your own server with nothing more than SSH access, though there are also applications that will provide an interface similar to Github or Bitbucket. I’d suggest looking at &lt;a href=&quot;https://about.gitlab.com/&quot;&gt;GitLab&lt;/a&gt; or &lt;a href=&quot;https://gitorious.org/&quot;&gt;Gitorious&lt;/a&gt; if you plan on doing this.&lt;/p&gt;

&lt;h3 id=&quot;summary&quot;&gt;Summary&lt;/h3&gt;

&lt;ul&gt;
  &lt;li&gt;Remember to regularly save work in progress.&lt;/li&gt;
  &lt;li&gt;Your work doesn’t exist until there are at least two copies.&lt;/li&gt;
  &lt;li&gt;It doesn’t count if those copies are in the same physical location.&lt;/li&gt;
&lt;/ul&gt;
</content>
 </entry>
 
 <entry>
   <title>Installing Pandoc on Windows</title>
   <link href="/posts/installing-pandoc-on-windows/"/>
   <updated>2014-04-15T00:00:00+01:00</updated>
   <id>/posts/installing-pandoc-on-windows</id>
   <content type="html">&lt;p&gt;Install &lt;a href=&quot;http://johnmacfarlane.net/pandoc/&quot;&gt;pandoc&lt;/a&gt; using the Windows installer from &lt;a href=&quot;http://code.google.com/p/pandoc/downloads/list&quot;&gt;here&lt;/a&gt;. Install pandoc globally using this command (using the path to the version of pandoc you downloaded):&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;msiexec /i pandoc-1.12.3.msi ALLUSERS=1
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Install &lt;a href=&quot;http://miktex.org/&quot;&gt;miktex&lt;/a&gt; from &lt;a href=&quot;http://miktex.org/download&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Run these commands to check that pandoc and miktex are installed:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&quot;C:\Program Files (x86)\Pandoc\pandoc.exe&quot; --version
&quot;C:\Program Files (x86)\MiKTeX 2.9\miktex\bin\pdflatex.exe&quot; --version
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
</content>
 </entry>
 
 <entry>
   <title>Building a RPM for Supervisor</title>
   <link href="/posts/building-a-rpm-for-supervisor/"/>
   <updated>2014-04-09T00:00:00+01:00</updated>
   <id>/posts/building-a-rpm-for-supervisor</id>
   <content type="html">&lt;p&gt;Start with a skeleton specfile, which can be generated using &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;rpmdev-newspec&lt;/code&gt;:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;rpmdev-newspec supervisor.spec
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The example specfile used to write this post can be found &lt;a href=&quot;https://gist.github.com/borntyping/10252256&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;h2 id=&quot;package-metadata&quot;&gt;Package metadata&lt;/h2&gt;

&lt;p&gt;The basic package metadata can be extracted from Supervisors existing package metadata (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;setup.py&lt;/code&gt;) and it’s website (&lt;a href=&quot;http://supervisord.org/&quot;&gt;supervisord.org&lt;/a&gt;).&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;Name: supervisor
Version: 3.0
Release: 1
Summary: A system for controlling process state under UNIX

Group: System Environment/Daemons
License: https://github.com/Supervisor/supervisor/blob/master/LICENSES.txt
URL: http://supervisord.org/

%description
Supervisor is a client/server system that allows its users to control a number
of processes on UNIX-like operating systems.
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;defining-requirements&quot;&gt;Defining requirements&lt;/h2&gt;

&lt;p&gt;Supervisor requires Python 2.4 or above (excluding Python 3.0), Setuptools (a packaging library), and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;meld3&lt;/code&gt; (a library used inside Supervisor). In CentOS, these are packaged as &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;python&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;python-setuptools&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;python-meld3&lt;/code&gt;, and so are added to the package requirements using their package names.&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;Requires: python &amp;gt; 2.4
Requires: python &amp;lt; 3
Requires: python-meld3 &amp;gt;= 0.6.5
Requires: python-setuptools
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;sources&quot;&gt;Sources&lt;/h2&gt;

&lt;p&gt;Supervisor is developed on &lt;a href=&quot;https://github.com/Supervisor/supervisor/&quot;&gt;GitHub&lt;/a&gt;, which provides source tarballs for repositories.&lt;/p&gt;

&lt;p&gt;In the best case, the source url could be defined in the specfile, and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;spectool&lt;/code&gt; could be used to download the tarball into the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;SOURCES&lt;/code&gt; folder:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;    Source0: https://github.com/Supervisor/supervisor/archive/3.0.tar.gz
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;    spectool --get-files --sourcedir supervisor.spec
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Unfortunately, Github’s tarball URLs use a series of redirects that result in a different name for the file, and so the files downloaded by &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;spectool&lt;/code&gt; don’t match the filenames &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;rpmbuild&lt;/code&gt; expects.&lt;/p&gt;

&lt;p&gt;Instead, I’ve used a Makefile that downloads the tarball (and ensures the filename is correct, which also makes it easy to checksum the sources before using them. The Makefile used to build the package is decribed later on in the article. For now, the specfile only has to know the name of the downloaded tarball:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;Source0: supervisor-%{version}.tar.gz
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;building-from-source&quot;&gt;Building from source&lt;/h2&gt;

&lt;p&gt;The package is built across three major steps: &lt;em&gt;prep&lt;/em&gt;, &lt;em&gt;build&lt;/em&gt; and &lt;em&gt;install&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;The preparation step is simple, and does nothing but extract the source tarball. The &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;-n&lt;/code&gt; option describes the name of the tarball to extract, though &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;%{name}-%{version}&lt;/code&gt; is the default anyway:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;%prep
%setup -q -n %{name}-%{version}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The build step uses setuptools to build the package, and then calls it again to install the package into the ‘build root’:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;%build
%{__python} setup.py build

%install
%{__python} setup.py install --skip-build --root $RPM_BUILD_ROOT
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Finally, the specfile has to define the files that will be included in the package:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;%files
%defattr(-,root,root,-)
%doc CHANGES.txt COPYRIGHT.txt LICENSES.txt PLUGINS.rst README.rst
%{_bindir}/echo_supervisord_conf
%{_bindir}/pidproxy
%{_bindir}/supervisorctl
%{_bindir}/supervisord
%{python_sitelib}/*
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;This declares that the files should be owned by root, that Supervisors documentation should be placed in a automatic documentation directory, that each of the scripts Supervisor provides should be included, and that all of the Python modules that were installed should be included.&lt;/p&gt;

&lt;p&gt;This should result in a directory tree like this:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;└── usr
    ├── bin
    │   ├── echo_supervisord_conf
    │   ├── pidproxy
    │   ├── supervisorctl
    │   └── supervisord
    ├── lib
    │   └── python2.6
    │       └── site-packages
    │           ├── supervisor
    │           ├── supervisor-3.0-py2.6.egg-info
    │           └── supervisor-3.0-py2.6-nspkg.pth
    └── share
        └── doc
            └── supervisor-3.0
                ├── CHANGES.txt
                ├── COPYRIGHT.txt
                ├── LICENSES.txt
                ├── PLUGINS.rst
                └── README.rst
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;including-configuration&quot;&gt;Including configuration&lt;/h2&gt;

&lt;p&gt;As well as Supervisor itself, including distribution specific information is useful, and one of the primary reasons to build an RPM for it. Firstly, an init script to mange the service is needed, and that requires default configuration so that Supervisor can be started.&lt;/p&gt;

&lt;p&gt;Two additional sources are used, containing an init script and a Supervisor configuration file.&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;Source0: supervisor-%{version}.tar.gz
Source1: supervisor.init
Source2: supervisor.conf
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The Supervisor configuration used is a simple as possible, only overriding the options needed to run Supervisor as a system service (by default, most of Supervisors files are placed in &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/tmp&lt;/code&gt;):&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;[unix_http_server]
file=/var/run/supervisor.sock

[supervisord]
pidfile=/var/run/supervisord.pid
logfile=/var/log/supervisor/supervisord.log
childlogdir=/var/log/supervisor

[rpcinterface:supervisor]
supervisor.rpcinterface_factory = supervisor.rpcinterface:make_main_rpcinterface

[supervisorctl]
serverurl=unix:///var/run/supervisor.sock

[include]
files = /etc/supervisor.d/*.conf
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;These files are added to the install step, along with the directories they need:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;%install
mkdir -p %{buildroot}/%{_initrddir}
install -p -m 755 %{SOURCE1} %{buildroot}/%{_initrddir}/supervisord
mkdir -p %{buildroot}/%{_sysconfdir}
mkdir -p %{buildroot}/%{_sysconfdir}/supervisord.d
install -p -m 644 %{SOURCE2} %{buildroot}/%{_sysconfdir}/supervisord.conf
mkdir -p %{buildroot}/%{_localstatedir}/log/%{name}
%{__python} setup.py install --skip-build --root $R
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Finally, the package needs to be configured to add the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;supervisord&lt;/code&gt; service to chkconfig using the post-install step:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;%post
/sbin/chkconfig --add %{name}d || :
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;And to then remove it in the pre-uninstall step:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;%preun
if [ $1 = 0 ]; then
    /sbin/service supervisord stop &amp;gt;/dev/null 2&amp;gt;&amp;amp;1 || :
    /sbin/chkconfig --del %{name}d || :
fi
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;$1 = 0&lt;/code&gt; statement &lt;a href=&quot;http://docs.fedoraproject.org/en-US/Fedora_Draft_Documentation/0.1/html/RPM_Guide/ch09s04s05.html&quot;&gt;checks that the package is being removed&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;With these changes, the specfile should be complete, and the directory tree should now include files in &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/etc&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/var&lt;/code&gt;:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;.
├── etc
│   ├── rc.d
│   │   └── init.d
│   │       └── supervisord
│   ├── supervisord.conf
│   └── supervisord.d
├── usr
│   ├── bin
│   │   ├── echo_supervisord_conf
│   │   ├── pidproxy
│   │   ├── supervisorctl
│   │   └── supervisord
│   ├── lib
│   │   └── python2.6
│   │       └── site-packages
│   │           ├── supervisor
│   │           ├── supervisor-3.0-py2.6.egg-info
│   │           └── supervisor-3.0-py2.6-nspkg.pth
│   └── share
│       └── doc
│           └── supervisor-3.0
└── var
    └── log
        └── supervisor
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;automating-the-build-with-make&quot;&gt;Automating the build with make&lt;/h2&gt;

&lt;p&gt;Since &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;rpmbuild&lt;/code&gt; requires all sources to be placed in &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;~/rpmbuild/SOURCES&lt;/code&gt;, I’ve used a makefile to automate the collecting the sources, and building the RPM:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;specfile=supervisor.spec

version=$(shell grep Version ${specfile} | awk &apos;{ print $$2 }&apos;)
release=$(shell grep Release ${specfile} | awk &apos;{ print $$2 }&apos;)

package=supervisor-${version}-${release}.x86_64.rpm
rpmbuild_package=${HOME}/rpmbuild/RPMS/x86_64/${package}

tarball=supervisor-${version}.tar.gz
tarball_url=https://github.com/Supervisor/supervisor/archive/${version}.tar.gz

${package}: ${rpmbuild_package}
    cp ${rpmbuild_package} ${package}

${rpmbuild_package}: sources
    rpmbuild -ba ${specfile}

sources: ${tarball}
    spectool --list-files ${specfile} | awk &apos;{ print $$2 }&apos; | xargs -i cp &quot;{}&quot; ${HOME}/rpmbuild/SOURCES/

${tarball}:
    wget ${tarball_url} --output-document ${tarball}
    md5sum -c sources

.PHONY: sources
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The version and release are read from the specfile, the used to work out the package name. &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;spectool&lt;/code&gt; is used to list the sources required and copy them into &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;rpmbuild&lt;/code&gt;s directories, and wget is used to fetch the source tarball due to the previously mentioned issues with redirects.&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>A simple filesystem watcher</title>
   <link href="/posts/watch-fs/"/>
   <updated>2014-02-14T00:00:00+00:00</updated>
   <id>/posts/watch-fs</id>
   <content type="html">&lt;p&gt;&lt;a href=&quot;https://github.com/borntyping/watch-fs&quot;&gt;watch-fs&lt;/a&gt; is a small, simple tool that watches a directory and runs commands when files change.&lt;/p&gt;

&lt;p&gt;Lots of similar tools already exist - &lt;a href=&quot;https://github.com/remy/nodemon&quot;&gt;nodemon&lt;/a&gt;, my own &lt;a href=&quot;https://github.com/borntyping/spotter&quot;&gt;spotter&lt;/a&gt;, &lt;a href=&quot;https://github.com/rvoicilas/inotify-tools/wiki&quot;&gt;inotifywait&lt;/a&gt;. Each of them have been to complex to use quickly, or in the case of nodemon, are geared towards a specific use case.&lt;/p&gt;

&lt;p&gt;watch-fs aims to work as simply as possible out of the box, with any non-essential features being optional.&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;watch-fs &quot;echo File changed&quot;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;In this example, watch-fs will echo &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;File changed&lt;/code&gt; any time a file changes in the current directory. It will ignore further file changes for a short delay once the command finished. This is so that editors (and commands) that change multiple files don’t result in the command running several times.&lt;/p&gt;

&lt;p&gt;Commands can use the name and path of the file that changed:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;watch-fs &quot;echo File &apos;{name}&apos; changed at path &apos;{path}&apos;&quot;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;A few arguments control optional features:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;watch-fs --first --clear --verbose &quot;rspec&quot;
watch-fs -fcv &quot;rspec&quot;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;--first&lt;/code&gt; runs the command before waiting for changes.&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;--clear&lt;/code&gt; runs &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;clear&lt;/code&gt; before running the command, which keeps the terminal free of output from multiple runs of the command.&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;--verbose&lt;/code&gt; prints the command before running it, which is useful for separating the output or showing what was run when using &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;--clear&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
</content>
 </entry>
 
 <entry>
   <title>Advanced Tox usage</title>
   <link href="/posts/advanced-tox-usage/"/>
   <updated>2014-01-29T00:00:00+00:00</updated>
   <id>/posts/advanced-tox-usage</id>
   <content type="html">&lt;p&gt;This article explains the &lt;a href=&quot;http://tox.readthedocs.org/en/latest/&quot;&gt;Tox&lt;/a&gt; configuration used by several of my Python projects, which both tests the module on multiple versions of python and runs testing-related tools for style guides, static code checking and coverage reports.&lt;/p&gt;

&lt;p&gt;The &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;tox.ini&lt;/code&gt; used here is taken from &lt;a href=&quot;http://borntyping.github.io/supermann/&quot;&gt;Supermann&lt;/a&gt;, and the source can be found &lt;a href=&quot;https://github.com/borntyping/supermann/blob/master/tox.ini&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;h3 id=&quot;running-pytest-to-find-and-run-tests&quot;&gt;Running pytest to find and run tests&lt;/h3&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;py.test&lt;/code&gt; is used to discover, run and write tests. It uses &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;tox.ini&lt;/code&gt; as a configuration file by default, and the two tools work very well in combination. The first two sections tell Tox to use the default Python 2.6 and 2.7 environments, to install &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;pytest&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;mock&lt;/code&gt; as test dependancies, and to uses &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;py.test&lt;/code&gt; to run the modules tests.&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;[tox]
envlist=py26,py27

[testenv]
commands=py.test supermann
deps=
    pytest
    mock

[pytest]
addopts=-qq --strict --tb=short
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;running-flake8-to-check-code&quot;&gt;Running flake8 to check code&lt;/h3&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;flake8&lt;/code&gt; wraps several Python tools for static checking of code - &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;pep8&lt;/code&gt; for style, and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;pyflakes&lt;/code&gt; for static error checking. This configures it to use &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;tox.ini&lt;/code&gt; for it’s configuration - reducing the number of configuration files in the repo.&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;[testenv:py26-flake8]
commands=flake8 --config=tox.ini supermann
basepython=python2.6
deps=flake8

[flake8]
exclude=supermann/riemann/riemann_pb2.py
max-complexity=10
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;running-coverage-to-show-untouched-code&quot;&gt;Running coverage to show untouched code&lt;/h3&gt;

&lt;p&gt;Coverage is by far the most verbose tool configured in my &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;tox.ini&lt;/code&gt;, but the HTML reports it generates are very useful for working out which parts of my code aren’t being run by my tests. This does several new things with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;tox&lt;/code&gt;: it runs two commands (one to generate the data, and another to render a HTML report), and uses the dependencies from the existing &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;testenv&lt;/code&gt; configuration.&lt;/p&gt;

&lt;p&gt;Coverage itself is told to use &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;tox.ini&lt;/code&gt; for it’s configuration, and is configured to ingnore various parts of the codebase (the tests folder, and lines of code that should never run). The coverage files are placed in &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.tox&lt;/code&gt; to keep the repository tidy while working.&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;[testenv:py26-coverage]
basepython=python2.6
commands=
    coverage run --rcfile tox.ini --source supermann -m py.test
    coverage html --rcfile tox.ini
deps=
    {[testenv]deps}
    coverage

[run]
data_file=.tox/py26-coverage/data
omit=supermann/tests/*

[report]
exclude_lines=
    def __repr__
    raise NotImplementedError
    class NullHandler

[html]
title=Supermann coverage report
directory=.tox/py26-coverage
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
</content>
 </entry>
 
 <entry>
   <title>Tox and Travis CI</title>
   <link href="/posts/tox-and-travis/"/>
   <updated>2013-09-02T00:00:00+01:00</updated>
   <id>/posts/tox-and-travis</id>
   <content type="html">&lt;p&gt;Until recently, my approach to building python projects that used &lt;a href=&quot;http://testrun.org/tox/latest/&quot;&gt;Tox&lt;/a&gt; on &lt;a href=&quot;http://travis-ci.org/&quot;&gt;Travis CI&lt;/a&gt; was simply to have two separate configuration files, each duplicating the others settings.&lt;/p&gt;

&lt;p&gt;This would normally work okay, with both Tox and Travis defining a set of python runtimes to use and a command to run on them. However, when it came to the more complex &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;tox.ini&lt;/code&gt; file I’m using for &lt;a href=&quot;https://github.com/borntyping/python-dice/blob/master/tox.ini&quot;&gt;python-dice&lt;/a&gt;, recreating this in the Travis configuration would have difficult to do, and require a lot of maintenance when the Tox settings changed.&lt;/p&gt;

&lt;p&gt;The solution? Using Travis to run Tox, using it’s matrix feature and the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;TOXENV&lt;/code&gt; environment variable to create a different build for each of several Tox environments, like so:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;:::yaml
language: python
env:
- TOXENV=py26
- TOXENV=py27
- TOXENV=py32
- TOXENV=py33
- TOXENV=pypy
install:
- pip install tox
script:
- tox
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
</content>
 </entry>
 
 <entry>
   <title>Playing with the Github Timeline data</title>
   <link href="/posts/github-timeline/"/>
   <updated>2013-05-22T00:00:00+01:00</updated>
   <id>/posts/github-timeline</id>
   <content type="html">&lt;p&gt;I came across &lt;a href=&quot;http://osrc.dfm.io/&quot;&gt;‘The Open Source Report Card’&lt;/a&gt; today, which included some interesting results on &lt;a href=&quot;http://osrc.dfm.io/borntyping&quot;&gt;my report&lt;/a&gt;. It claimed I’d contributed to repositories using &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;VimL&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Go&lt;/code&gt;, neither of which I could remember doing anything in relation to.&lt;/p&gt;

&lt;p&gt;Looking into how it worked led to the &lt;a href=&quot;http://www.githubarchive.org/&quot;&gt;Github Archive&lt;/a&gt;, which is available as a dataset on &lt;a href=&quot;https://bigquery.cloud.google.com/&quot;&gt;Google BigQuery&lt;/a&gt;. Playing with this eventually led to finding the problem - the ‘contributions’ graph included &lt;em&gt;all&lt;/em&gt; of a users events. This included ‘star’ (previously ‘follow’) events, so it was counting repositories I’d starred as repositories that I had contributed to.&lt;/p&gt;

&lt;p&gt;A more accurate result was achieved with the following query:&lt;/p&gt;

&lt;div class=&quot;language-sql highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;    &lt;span class=&quot;cm&quot;&gt;/* Count the number of my events by language excluding stars/follows */&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;SELECT&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;repository_language&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;count&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;repository_language&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;as&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;n&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;FROM&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;githubarchive&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;github&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;timeline&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;WHERE&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;actor&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;&quot;borntyping&quot;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;AND&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;type&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;!=&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;&quot;WatchEvent&quot;&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;GROUP&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;BY&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;repository_language&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;ORDER&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;BY&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;n&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;DESC&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The Github Archive documentation seemed to be devoid of simpler example queries, so this is how I reached the above one, starting from one adapted from the primary example on &lt;a href=&quot;http://www.githubarchive.org/&quot;&gt;githubarchive.org&lt;/a&gt;.&lt;/p&gt;

&lt;div class=&quot;language-sql highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;    &lt;span class=&quot;cm&quot;&gt;/* List all of my repositories, counting the number of events */&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;SELECT&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;repository_name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;repository_owner&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;count&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;repository_name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;as&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;events&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;FROM&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;githubarchive&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;github&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;timeline&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;WHERE&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;repository_owner&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;&quot;borntyping&quot;&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;GROUP&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;BY&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;repository_name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;repository_owner&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;ORDER&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;BY&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;events&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;DESC&lt;/span&gt;

    &lt;span class=&quot;cm&quot;&gt;/* List all of my repositories, counting the number of events, and sorted by language */&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;SELECT&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;repository_name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;repository_owner&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;repository_language&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;count&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;repository_name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;as&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;events&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;FROM&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;githubarchive&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;github&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;timeline&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;WHERE&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;actor&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;&quot;borntyping&quot;&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;GROUP&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;BY&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;repository_name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;repository_owner&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;repository_language&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;ORDER&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;BY&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;repository_language&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;events&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;DESC&lt;/span&gt;

    &lt;span class=&quot;cm&quot;&gt;/* Count the number of my events by language */&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;SELECT&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;repository_language&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;count&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;repository_language&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;as&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;repository_language_count&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;FROM&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;githubarchive&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;github&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;timeline&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;WHERE&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;actor&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;&quot;borntyping&quot;&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;GROUP&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;BY&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;repository_language&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;ORDER&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;BY&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;repository_language_count&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;DESC&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
</content>
 </entry>
 
 <entry>
   <title>Spotter at Show and Tell</title>
   <link href="/posts/spotter/"/>
   <updated>2013-05-04T00:00:00+01:00</updated>
   <id>/posts/spotter</id>
   <content type="html">&lt;iframe width=&quot;640&quot; height=&quot;360&quot; src=&quot;http://www.youtube.com/embed/aQKCjYDjraU?feature=player_embedded#t=1860s&quot; frameborder=&quot;0&quot; allowfullscreen=&quot;&quot;&gt;&lt;/iframe&gt;

&lt;p&gt;My second talk at a BCS Mid Wales Show and Tell event, this time on &lt;a href=&quot;https://github.com/borntyping/spotter&quot;&gt;Spotter&lt;/a&gt;, a command line tool I wrote. The slides are available &lt;a href=&quot;/content/2013-05-04-spotter.pdf&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>ZSH startup time</title>
   <link href="/posts/zsh-startup-time/"/>
   <updated>2013-03-10T00:00:00+00:00</updated>
   <id>/posts/zsh-startup-time</id>
   <content type="html">&lt;p&gt;I’ve had issues with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;zsh&lt;/code&gt; starting up and running very slowly for a while. I’d assumed the problem simply lay with the number of plugins and other scripts run by &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;oh-my-zsh&lt;/code&gt;, but a bit of research has found two solutions that have worked very well for me (your mileage may vary):&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Removing the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;github&lt;/code&gt; plugin. I don’t think I was using it much, if at all, and removing it stoped the noticable delay each time the prompt was shown (&lt;a href=&quot;http://superuser.com/a/269617&quot;&gt;Source&lt;/a&gt;)&lt;/li&gt;
  &lt;li&gt;Adding &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;skip_global_compinit=1&lt;/code&gt; to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.zshenv&lt;/code&gt;. This dropped the startup time of zsh from ~0.85 seconds to ~0.15 seconds. This was benchmarked using &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/usr/bin/time zsh -i -c exit&lt;/code&gt; (&lt;a href=&quot;http://blog.patshead.com/2011/04/improve-your-oh-my-zsh-startup-time-maybe.html&quot;&gt;Source&lt;/a&gt;)&lt;/li&gt;
&lt;/ul&gt;

</content>
 </entry>
 
 <entry>
   <title>Argumented Testing</title>
   <link href="/posts/argumented-testing/"/>
   <updated>2012-11-25T00:00:00+00:00</updated>
   <id>/posts/argumented-testing</id>
   <content type="html">&lt;p&gt;I’ve finally gotten round to ensuring &lt;a href=&quot;https://github.com/borntyping/diceroll&quot;&gt;diceroll&lt;/a&gt; has a set of unit tests, and along the way I’ve ended up creating a very small, but useful testing library, called &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;argumented&lt;/code&gt; (as it lets you agument tests with arguments!).&lt;/p&gt;

&lt;p&gt;The library provides a few functions for ‘packing’ argument sets to be passed into a function or unit test, and a decorator for unpacking a class using these functions and replacing said functions with a set of functions that each call one of the given argument sets.&lt;/p&gt;

&lt;p&gt;You can read more about it &lt;a href=&quot;https://github.com/borntyping/argumented&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>Aleph, version 2.0.0</title>
   <link href="/posts/aleph-v2.0.0/"/>
   <updated>2012-05-29T00:00:00+01:00</updated>
   <id>/posts/aleph-v2.0.0</id>
   <content type="html">&lt;p&gt;Version 2 of the Aleph library has been released - a large jump in version numbers, but justified by a rewrite of most of the library. The library now follows the reactor pattern - inspired in part by a wish to make the library more compatible with a user oriented interface, as opposed to a bot that requires no (local) user input to run.&lt;/p&gt;

&lt;p&gt;Rewriting it to use event handlers and a reactor allowed the use of things I was not able to attempt before - such as having an event handler for STDIN, so that user can interact with the program locally. User input is currently very simple - all it does is exit on any STDIN input event - but has potential for far more interesting applications.&lt;/p&gt;

&lt;p&gt;The use of event handlers mean that individual client objects now handle each message, not the core object. This both sandboxes clients so that they can’t access data from the other clients, and would let you run multiple client handlers using completely different code for each (not that I can actually think of a use for that…).&lt;/p&gt;

&lt;p&gt;There’s very little in the way of other new features, though the next few versions should introduce more in order to better support &lt;a href=&quot;https://github.com/borntyping/strategy&quot;&gt;strategy&lt;/a&gt;.&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>Aleph, version 1.2.0</title>
   <link href="/posts/aleph-v1.2.0/"/>
   <updated>2012-04-22T00:00:00+01:00</updated>
   <id>/posts/aleph-v1.2.0</id>
   <content type="html">&lt;p&gt;Aleph 1.2.0 brings mostly internal changes to the way the Message class works and collection of server and channel meta-data such as channel topics and the server MOTD.&lt;/p&gt;

&lt;p&gt;Previously, message subclasses would look like this, and the messages would be created by checking the messages module to find a class with the name of the irc command.&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;:::python
class JOIN (Message):
    ...
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;However, when it came to add classes for numeric server replies, I found two problems with this approach: that python class and function names cannot start with a number, and that even if I could do that, naming classes with 3-digit numbers would be ugly and confusing.&lt;/p&gt;

&lt;p&gt;The solution to this was to use decorators, allowing message subclasses to be defined like this:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;:::python
@Message.Reply(332)
class RPL_TOPIC (Message):
    ...
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The decorator adds the class to a dictionary of classes stored inside the Message class, and allows the code to access the class for a numeric reply directly, as well as allowing the class to keep a human-readable (read: slightly more human-readable, because RFC 1459 doesn’t define very clear names for the server replies) name.&lt;/p&gt;

&lt;p&gt;I also adapted this to work for ‘normal’ message classes as well, so that the message creation code was not looking in two places (Message.NUMERIC_REPLIES and globals()), and added a second decorator for server commands.&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;:::python
@Message.Command
class JOIN (Message):
    ...
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;This also resulted in a cleaner module, as the IRC command and reply classes are added to Message.MESSAGES, which is then the only location searched for classes to use for an IRC message (instead of searching globals() for a class).&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>Aleph, version 1.1.0</title>
   <link href="/posts/aleph-v1.1.0/"/>
   <updated>2012-02-07T00:00:00+00:00</updated>
   <id>/posts/aleph-v1.1.0</id>
   <content type="html">&lt;p&gt;Aleph version 1.1 brings the CallbackBot class, which is now the default instance of Aleph.&lt;/p&gt;

&lt;p&gt;It’s the first version that allows a bot to be made without subclassing any of the core classes and can be instantiated using only the configuration.&lt;/p&gt;

&lt;p&gt;The ‘callbacks’ config item should be a list or other iterable of tuples containing (message, function), where message is a string or list of strings that name Message types - when one of these message types is generated by an incoming message, the function is called with the Message instance.&lt;/p&gt;

&lt;p&gt;Example script:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;:::python
#!/usr/bin/python

import aleph

bot = aleph.Aleph({
    &apos;nick&apos; : &apos;AlephExampleBot&apos;,

    &apos;servers&apos; : [{
        &apos;id&apos;: &apos;AFNet&apos;,
        &apos;address&apos;: &apos;irc.aberwiki.org&apos;,
        &apos;channels&apos;: [&apos;#aleph&apos;]
    }],

    &apos;callbacks&apos; : [
        (&apos;PRIVATE&apos;, lambda message: message.reply(&apos;Oh my!&apos;)),
        (&apos;PRIVMSG&apos;, lambda message: message.reply(&apos;Hello!&apos;)),
    ],
}).run()
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
</content>
 </entry>
 
 <entry>
   <title>Aleph, version 1.0.0</title>
   <link href="/posts/aleph-v1.0.0/"/>
   <updated>2012-02-01T00:00:00+00:00</updated>
   <id>/posts/aleph-v1.0.0</id>
   <content type="html">&lt;p&gt;A published, working version of the Aleph IRC library is now available. While there are plenty of features not yet implemented, it should now be possible to use it to implement a simple (or complex) IRC bot.&lt;/p&gt;

&lt;p&gt;An example of a bot that logs all incoming messages:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;:::python
#!/usr/bin/python

from aleph.core import Core
from aleph import logger

class AlephTestBot (Core):
    def input (self, message):
        # Log the message
        logger.log(message, 6, message.command)

bot = Aleph({
    &apos;nick&apos; : &apos;AlephExampleBot&apos;,

    &apos;servers&apos; : [{
        &apos;id&apos;: &apos;IrcNet&apos;,
        &apos;address&apos;: &apos;server&apos;,
        &apos;channels&apos;: [&apos;#aleph&apos;]
    }],
}).run()
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Features I hope to add in the near future include:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;A callback interface, so functions can be registered to message types - this will become the default instance of an Aleph bot and will be significantly easier to build a bot with.&lt;/li&gt;
  &lt;li&gt;Better understanding of the irc protocol on the part of the IrcServer class, including things such as reading the MOTD and other data.&lt;/li&gt;
  &lt;li&gt;Documentation that does not rely on looking at the source code.&lt;/li&gt;
&lt;/ul&gt;
</content>
 </entry>
 
 <entry>
   <title>Future Shock</title>
   <link href="/posts/future-shock/"/>
   <updated>2011-11-02T00:00:00+00:00</updated>
   <id>/posts/future-shock</id>
   <content type="html">&lt;p&gt;“Wake up.”&lt;/p&gt;

&lt;p&gt;…&lt;/p&gt;

&lt;p&gt;“Wake up.”&lt;/p&gt;

&lt;p&gt;…&lt;/p&gt;

&lt;p&gt;“I don’t think she’s survived.”&lt;/p&gt;

&lt;p&gt;My eyes blink open, taking in the blur of blue, pink and black above me. The fuzzy shapes slowly merge into a face and body, black hair and pale blue clothing. The face seems a little surprised, although happy.&lt;/p&gt;

&lt;p&gt;“She’s alive. 21st century cyro was better than I thought, maybe. Hey. Can you hear me?”&lt;/p&gt;

&lt;p&gt;I nod.&lt;/p&gt;

&lt;p&gt;“Good. So you can understand what I’m saying? If I remember my history lessons, you should be hearing English or Mandarin right now.”&lt;/p&gt;

&lt;p&gt;It take a moment for me to realize the sound of the blur’s words aren’t coming from their mouth, but almost from the inside of my ear. I close my eyes to better concentrate on the person’s words.&lt;/p&gt;

&lt;p&gt;“I can hear English”, I reply. “But whats happening to my hearing?”&lt;/p&gt;

&lt;p&gt;“There’s a ryth-yk-is-tchn in your ear. Umm, I don’t think it’ll handle that word actually. I think the closest concept you may have is a ‘babel fish’. I speak nyth-en-Yth, and it translates it into late 21st century English so you can understand.”&lt;/p&gt;

&lt;p&gt;“Right -“ I start thinking. “You talk in a diffrent language, and imply that 21st century English isn’t recent. So, I’m not in the 21st century, am I? What century am I in?”&lt;/p&gt;

&lt;p&gt;“The 29th.”&lt;/p&gt;

&lt;p&gt;“Ah. This isn’t going to be very simple, is it?”&lt;/p&gt;

&lt;p&gt;“I’m afraid not. A couple of the others we woke from cyro-sleep… went uthr-ni-kr - umm, ‘time and culture shock’. The good news is, so far, that you seem to be handling it a lot better than they did.”&lt;/p&gt;

&lt;p&gt;“Right. I’m getting up.” I reopen my eyes, and this time they quickly adjust. In front of me is a dark haired girl, wearing a pair of thin rimmed glasses and a light blue shirt. Beyond her, a small white box of a room, brightly lit but with no visible light source. I’m too busy wondering why I’ve just woken up from 8 centuries of cyro-sleep to take in more information about the room. I push forward, realising that all I can do is strain against the straps around my torso and limbs. “Can you take these off me?”&lt;/p&gt;

&lt;p&gt;“I think so. You seem sane enough.”&lt;/p&gt;

&lt;p&gt;She has something that looks like a ridgid peice of paper in her arms, upon which she uses her finger to click on it. The small chamber that contains me beeps, and the straps fold back - I realise that I’m in a chamber thats probably older than me - small patches of rust and streaks down the side indicating leaks. I hate to think about my own condition. One step forward, and another. And a third. After that, my legs decide that they don’t like having to move and ungracefully dump me on the floor in a messy pile of limbs.&lt;/p&gt;

&lt;p&gt;The girl puts her paper in - I think that it just became part of the wall when she put it there - and leans down to pick me up. I’m lifted like a feather, and gently placed into something thats somewhere between a chair and a coccon. A plain white room, with this chair, a cyro chamber against one wall, a blue line that I assume marks the door on the opposite wall - I’m beginning to wonder if I’m inside a really advanced sci-fi sensie. I try and think about leaving, and the world stays solid around me. Maybe this is reality.&lt;/p&gt;

&lt;p&gt;I look at the sheet that she placed on the wall. It’s covered in unreadable symbols, with the exception of an image - I think it might be me - and a phrase in english.&lt;/p&gt;

&lt;p&gt;‘Helen Valentine’. I think that’s my name.&lt;/p&gt;

&lt;p&gt;What do I mean, ‘I think it’s my name’ - of course it is. It’s hardly something you forget. I don’t think my brain is quite working. My thoughts are disconnected, I can’t plan or think. I just want to curl up into a ball and sleep — the ‘chair’ pulls up beneath me. Maybe I should have seen that coming. I mean, psychic chairs — who wouldn’t have guessed?&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>Infinitum: Sara</title>
   <link href="/posts/infinitum-sara/"/>
   <updated>2011-04-22T00:00:00+01:00</updated>
   <id>/posts/infinitum-sara</id>
   <content type="html">&lt;p&gt;I retired to Mesa-1:Luma after eight years in the Marines. I piloted a dropship, a Model 4 Deminutio - “Sara”. Got shot to pieces, and me with it. I was deemed unfit for duty, and poor Sara was consigned to the robofac scrapheap - well, I couldn’t let that happen, so I brought her off the TA with me retirement money, and got her refitted.&lt;/p&gt;

&lt;p&gt;Got rid of all the weaponry, and the fancy systems - most of them were dead, anyways - and made her a bit more comfy, and stocked it with civilian grade flightsuits. And with that, I set up on Luma, flying people into orbit and letting them make the jump back to sea.&lt;/p&gt;

&lt;p&gt;Luma’s a great planet for this kinda sport. Small, with as dense atmosphere, low gravity, and it’s almost entirely sea,. Man made islands and structures making up most of the land mass - it’s a really popular holiday resort, with plenty of people passing though to and from the TA Core systems.&lt;/p&gt;

&lt;p&gt;Not many people get to try jumping out a dropship, not without several years of military service, and so I pull a pretty good business. Brilliantly relaxing - all I do is fly up, watch my passengers jump out, and land again.&lt;/p&gt;

&lt;p&gt;And the sights, they never get old. You watch a group of brightly coloured specks jump down to sea as the sun falls below the horizon, and you tell me it’s not something you couldn’t do forever.&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>Infinitum: Warp</title>
   <link href="/posts/infinitum-warp/"/>
   <updated>2011-02-22T00:00:00+00:00</updated>
   <id>/posts/infinitum-warp</id>
   <content type="html">&lt;p&gt;Our drives are amazing machines. They can pull a ship though a node, between two points anywhere in the universe.&lt;/p&gt;

&lt;p&gt;Not a small ship, like the shuttles and rockets that used to take people and equipment from Earth into orbit - these are ships that dwarf towns. The smallest ships we can fit the drives on are two and a half miles long, 10 billion tons of mass - it’s not something you’d ever want to stand in front of.&lt;/p&gt;

&lt;p&gt;But when you look at the fleet, those ships come under the categories ‘small’ and ‘expendable’. Each Arc ship holds hundreds of these, in the hangar bays of numerous larger ships. This fleet is the result of years of the Arc’s factories working from the raw material of a universe.&lt;/p&gt;

&lt;p&gt;Many say the Arcs are humanities greatest achievement. And who am I to disagree? I work on the engines of the Callista, the last Arc to leave Sol. It’s jump engine is almost as long as the ship itself, a core running though the heart of the ship. It’s surrounded by fusion plants that cluster round it like cubs to their mother. It’s a drive that pulls 10 trillion tons of metal, plastic, biology, gas and water though two completely separate points in space.&lt;/p&gt;

&lt;p&gt;It requires enough power that almost every other major ship system shuts down to achieve it. For a moment, the ship and the space around it plunges into darkness, and when it lights up again the stars around it are completely different, and the secondary engines kick in. Those engines are an achievement in their own right, larger than cites and several miles long, there to push the Arcs though space where there are no jumpnodes to guide us.&lt;/p&gt;

&lt;p&gt;Those engines are a beautiful sight to behold. Five perfect blue flames silently roaring into the beyond, shoving a chunk of metal though space.&lt;/p&gt;

&lt;p&gt;We’ve only found a few nodes from which we could see the same stars as another. Our ships may be an absurd size and weight - but the universe is able to look down on them and laugh at our feeble attempts. For all we know, it goes on forever.&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>Infinitum: Beginnings</title>
   <link href="/posts/infinitum-beginnings/"/>
   <updated>2011-02-22T00:00:00+00:00</updated>
   <id>/posts/infinitum-beginnings</id>
   <content type="html">&lt;p&gt;The solar system where we begun is Sol. Nine planets, an asteroid belt, lots of moons, and a single inhabitable planet - Earth. Earth, the third rock from the Sun: humanities home. It’s where we began… and we’re never going back. It’s a place we’ll never see again, lost to time and already but a memory for most. And what happened to Terra that we could never go back? Twenty-five years after the first Arc Ship, Sol’s one and only jumpnode closed. We were lucky. A few decades late, and humanity may have never reached the stars.&lt;/p&gt;

&lt;p&gt;We left on the Arcs. Humanities greatest achievement, in our thousands of years of history. Ships larger than cities, carrying humanities population in hibernation, waiting to arrive on new planets and to go forth and multiply.&lt;/p&gt;

&lt;p&gt;I was one of them, you know. I was only twenty-five when I left Earth on the Dementer. Such a long time ago… But I was one of the first, one of the first billion humans to reach the heavens, boldly going where no man had gone before. heh …You kids wouldn’t remember that, but there was a time when we would dream of the heavens, thinking of what it would be like to go to space, to find aliens and make war and peace with them. But those were very different times.&lt;/p&gt;

&lt;p&gt;So our Arcs plummeted though space, punching though the ‘nodes with the finesse of a drunken American. And then we found the inhabitable planets, and started to find Life. Oh, that was a shock to some people! Animal and plant life, with even more variation than Earth ever had. And so we did what humanity does best. Claim, colonize, spread, advance. Some would say infect. Sentient life is brilliantly effective at expanding.&lt;/p&gt;

&lt;p&gt;And the Arc’s helped us to do that very efficiently. Able to cross systems in days, and jump across almost infinite amounts of our universe with the aid of the jumpnodes. We found ourselves in sections of the universe that we had never seen, that were untouched by man - for a short time, anyway.&lt;/p&gt;

&lt;p&gt;But like always, we were still followed by disaster. The third arc, the Arc Ship Titania, may have been humanities greatest ever loss. It came out of it’s second jump and was hit by a small moon. Nothing special, no last rally against a honorable enemy, nothing they could do to save themselves from destruction. Titania’s still out there, somewhere. Near the Origin, theres a system with a few small planets and moons - one of them has a beautiful sparkling atmosphere. We call it Splendor.&lt;/p&gt;

&lt;p&gt;Before Titania hit, Splendor had no atmosphere. Now, it has a sky of twisted metal and plastic, debris from the Arc. It’s the biggest human graveyard in history.&lt;/p&gt;

&lt;p&gt;And a few years after that, we lost the Eidolon. They decided that they could not agree with our limits on weaponry, AI and bio-modification, the technologys that made us leave Earth behind. They forgot what those things had done to humanity before. The forgot the destruction that they caused. We no longer know what Eidolon’s goals are. They advance far more aggressively than we did, tearing planets for resources and continuing to live in space. We can only hope they never turn on us.&lt;/p&gt;

&lt;p&gt;Callista was the fourth Arc to leave Sol. They were careful, not wanting to fall to disaster. And they didn’t - but they did discover disaster. Their scientists saw what our jumpdrives were doing to the nodes. We saw the fluctuation in the nodes far too late, and realized that our drives made them unstable. Having something punch though you at almost instantaneous speed will damage anything. We have far better technology now, and can traverse the jumps far more safely. But we didn’t have this before many of the nodes closed from our older, more destructive drives.&lt;/p&gt;

&lt;p&gt;And thats the second reason we can’t ever see Sol again. We lost it.&lt;/p&gt;

&lt;p&gt;Even if the node reopened, we have no idea where it could be. The few systems that make up the Origin? We can’t ever get back to them either. I say ‘we’ can never get back to Sol, and the Origin. But… not all of us left. The final Arc Ship, Absenti. It never left. Neither did any of those who stayed on Earth and the moon colonies, the crazies that thought we could ever renew Earth, after what we did to it. We may never know what happened to them now. Maybe they’re actually terraforming Earth. Maybe they could have found another jumpnode, and left the system to another place. Or they all died. Who knows?&lt;/p&gt;
</content>
 </entry>
 
</feed>
