Kartones Blog

Be the change you want to see in this world

Plain Text Accounting

Plain Text Accounting (also known as PTA) is a term I arrived at by accident.

For controlling my finances I've used Excel files for a long time, but since I use Linux I've had to deal with OpenOffice and LibreOffice... which kind of work, but aren't great, and above all, LibreOffice Calc 7 is so painfully slow both opening itself and saving files (even if they are tiny less than 10KB ones!).

After months of frustration and trying different approaches, like separate files per year or minimizing the amount of data, I recently gave up and decided to search for "as simple as possible alternatives". I also wanted to avoid "the cloud", because I don't use Office Online nor Google Docs and won't change my mind only because of this single reason.

I tried searching for "text based accounting", and I found "plain text accounting"... Or to be more precise, ledger, a CLI and plain text-based double-entry accounting system.

ledger's documentation looks initially quite daunting, with so many options, and features, and ways of writing your finances. But I strive for simple solutions, so just reading the very basic introduction and checking some examples, I realized I can already use it in a practical way with a minimal subset of the features it provides. And then I can slowly improve my skills with the software, or simply leave as it is if serves me well enough.

Sometimes an example is both easier and enough, so let me begin with a minimal one:

2022/06/01 Miscellaneous
  Expenses:Services:ServiceA       $ 123
  Expenses:Assorted:Eating         $ 456
  Expenses:Phone                     $ 5
  * Assets:Checking

2022/06/02 Employer
  * Assets:Checking              $ 10000

; this is a sample comment

First, we have an entry line (labelled Miscellaneous), with three line items, Classified as category Expenses (and some sub-categories) and a fourth entry line that refers to Assets:Checking. That last line makes ledger know that previous amounts need to be deducted (in this case, from an initial amount of $ 0). We also have another entry (Employer), containing in this case first a line item with an amount associated with Assets:Checking and an empty Income line item, so ledger adds the amount to the checking asset instead of subtracting from it.

I am still learning the way the formatting works, so there might be even more minimalistic approaches, but this works for me.

Running the CLI tool (I like the Docker way) we get:

docker run --rm -v "$PWD":/data dcycle/ledger:1 -f /data/sample.dat register checking
22-Jun-01 Miscellaneous         Assets:Checking              $ -584       $ -584
22-Jun-02 Employer              Assets:Checking             $ 10000       $ 9416

We can see how it properly deducts expenses and adds incomes, listing all entries ordered by date, and displaying at the rightmost column the balance result. So the last entry line's rightmost column represents the final balance.

While this initially looks cumbersome compared with adding numbers and labels on GUI cells, in the end is mostly similar at a conceptual level, and I haven't had to do any manual summing or counting either. Plus I have something that is quite portable to a future different system if I ever want to, and is blazingly fast.

I moved my current month finances to the system and all numbers matched perfectly, so from now on this is what I'll use.

Book Review: Boinas Verdes: De Commandos a Pyro Studios


Boinas Verdes: De Commandos a Pyro Studios book cover

Title: Boinas Verdes: De Commandos a Pyro Studios: un turbulento viaje del estrellato al olvido

Author(s): Jaume Esteve

The late nineties were a golden era for videogames, with so many great titles coming out, and most of them being released on PC. The Spanish game development scene was a far cry from the 8-bits era, but there were a few good studios remaining, and new ones appeared, like Pyro Studios. Their first game, Commandos: Behind Enemy Lines would would become an international success, but also a national example of how Spain could also create triple-A titles. The saga would spawn a trilogy, plus a mission pack (of the first game) and a first-person shooter spin-off, and the studio would release a few other titles, before pivoting to only mobile games, and lastly disappearing in 2017.

This book narrates the story of all the Commandos series games, but also tells us about cancelled prototypes of other titles, and the last decade, when they slowly faded away into nothingness. A story full of insanely hard work, some good and some (many?) bad decisions, and many, many human problems that would impede the studio from flourishing, and instead sentenced it. A story of some well known characters in the Spanish game development scene, but also of other hard workers that helped building the titles and might not have had enough recognition until now.

It is hard for me to not be subjective evaluating Boinas Verdes, because of two reasons. On the one hand I played all the titles, although, as we'll read that was common, I was one of those people who finished the first game, but then only purchased but didn't really finished the following ones (although I might now, except the FPS). On the other hand, I've had the pleasure to meet and briefly chat with a few of the main characters (except in the case of Unai Landa, that we've met a few times) and I admire them (even if, in some cases, they are not perfect).

The title covers a breadth of topics, and I was happy to find some technical details explained here and there, although always in a simple language that anybody can understand without technical knowledge. And how could a discussion about Pyro not mention Cops, a doomed title that could have been amazing (at least technically) but never materialized after 3 iterations and a lot of money invested? If anything, reading this book felt short, but around 250 pages it is quite packed, including some great illustrations and sketches of scenery and characters.

In summary, a great tribute to a great saga.

Ubuntu /bin and /sbin symlink to /usr/bin

Today I wanted to write about a quick Linux and Docker/containers tip. I might write more of these, as I have gathered a non-trivial amount of Linux and Ubuntu tips, tweaks and miscellaneous things (plus some related adventures at work) that might be worth sharing.

Since Ubuntu 20.04 (and earlier at Debian), /bin and /sbin are symlinks to /usr/bin. While this sounds nice and simple, it is good to be aware of this change when migrating containers from Ubuntu bionic to focal, because, if they copied any binary or script to /bin, now they will error.

The error if attempting to move stuff to /bin, while hinting the issue, is not 100% clear. It'll be something along the lines of:

ERROR: cannot copy to non-directory: /var/lib/docker/overlay2/<a-big-hash>/merge/bin

If you inspect the contents of the layer, or just do a quick test like RUN ls -al /bin before moving your script(s), you will notice the presence of the symlink instead of either nothing or a folder.

The solution is simple, just move the scripts to /usr/bin instead. And for clarity, change your script invocations to also run /usr/bin/<script-name>.sh and the like. Although /bin/<script-name>.sh will still work, it might not be clear why there are multiple, different paths.

Python Hashbang/Shebang

Until recently, I barely knew about Hashbangs or Shebangs other than, when building a shell script, you have to put #!/bin/bash at the first line of the file.

What I wasn't aware is that you can create scripts out of many file types, because the hashbang is a declaration of which interpreter to use when running the script... which means that you can run things like Python files as scripts.

Python works as a really good cross-platform language because the os and sys libraries allow to build platform-independent code (e.g. via path.join). But there is a small catch that you need to be aware of when marking a file as a script.

In general, you should strive for always pointing to python3:

#!/usr/bin/env python3

But if you still need to use python 2 (remember it reached end of life January 2020!) you should instead use python2:

#!/usr/bin/env python2

And, if you really need compatibility with both versions of python, the only solution is to use the generic python, because you can't specify both previous ones:

#!/usr/bin/env python

But here is the trick: Depending on the operating system, and especially on the more recent versions of Ubuntu, MacOS and the like, both the installed python version and/or any downloaded one might only be installed as python3. There is no guarantee that there will exist a symlink to the generic python (and can be dangerous to manually setup one).

How to solve supporting both versions then? Well, after reading the PEP-394, they advise to only use python if you plan to run the python script inside a virtual environment (venv or virtualenv).

So there you have it, a two-step approach to support both versions at once:

a) Always run the script inside a virtualenv (easy if you use Docker nowadays)

b) Define the shebang as

#!/usr/bin/env python

Web Speech API

I recently learned that there's a pretty decent Web Speech API, and I have the perfect (although outdated) pet project for it, my choose your adventure system: Lots of text that, with minimal cleaning, make for a nice reading.

There is a very practical online demo that will also give you a feeling of how good or bad the speech synthesizer will sound. And trust me, you should test it, as at least under Ubuntu Linux, Firefox sounds quite terrible, meanwhile Chrome is decent enough that I decided to target that browser for speech.

Most of the API consists on two objects, a single SpeechSynthesis instance to handle the main configuration, and then one SpeechSynthesisUtterance instance per sentence you wish to get read by the API.

I won't enter in the details of how to use it, because I merely followed the nice explanations and checked the source code of the demo (which are concise and good), but I will warn about one issue that I'm having with Chrome: If the sentence is too long, it gets cut, and the API seems to hang, not playing any further sound until closing and re-opening again the browser application!

In order to workaround the length limitation, I am applying the following simple split:

fullText.split(/[.,\:;!\?]| - /).forEach((textFragment) => {
    const text = textFragment.trim();
    if (text.length > 0) {
      const utterance = new SpeechSynthesisUtterance(text);
      utterance.voice = desiredVoice;
      utterance.pitch = pitchValue;
      utterance.rate = rateValue;

You can perfectly enqueue any number of "sentences" (utterances), just make sure that each is not too big.

I might improve the logic a bit in the future, mostly if I still find too big sentences I will try to discern the aprox. max length and split to the previous full word. Also note that when reading numbers (e.g. 5.6) it will split them. But overall, it is a nice first step, and really simple to use.

Previous entries