Royal Mail QR code not displaying in your email? Take it out yourself.

Where’s my QR code? – © ray rui on Unsplash

I have to return something I bought online a few days ago. The return process is pretty slick: this company sends you a link to `royalmail.com/track-my-return/details/$company_code`, where you’re supposed to find a form to fill with your details and those of the purchase.

Once you submit the form, you receive an email with a label to affix on your parcel or a QR code to brandish in a post office, which is scanned and produces the same label you could print at home – just through a label printer.

I love using the QR code, so imagine my dismay when I found that the code wouldn’t display in the email. I quickly checked whether the client I used prevents images from being loaded – nope.

I then tried a different browser: no joy either. I forwarded the email to another address, to no avail.

A quick search on the interwebs showed that I was not alone.

In the end I gave up and looked at the email content. It’s a standard HTML image tag, with the QR code embedded as a Base64-encoded JPEG image.

Excerpt of the source code of the email in which the QR code is embedded as a Base64-encoded image.
Sample of the Base64-encoded image

Being far from front-end development I naively thought the problem might be due the presence of “3D” appended to the equal sign. Turns out, that’s perfectly normal.

The last thing left was decoding the encoded string myself. After some trial and error, it all boiled down to some simple Python:

# Assuming the encoded string has been saved verbatim in 'encoded.b64'
import base64
with open('encoded.b64', 'r') as f:
    encoded_lines = f.readlines()
encoded_string = ''.join(line.rstrip('=\n') for line in encoded_lines)
# Add padding if needed
if excess_length := (len(encoded_string) % 3):
    encoded_string += '=' * (3 - excess_length)
with open('qr_code.jpeg', 'wb') as f:
    f.write(base64.b64decode(encoded_string))

And now that I have my QR code back, excuse me but I’ll go to the post office!

Unit testing in Haskell

Photo by Benjamin Lehman on Pexels.com

I’ve started dipping my feet in Haskell, curious to see what I could learn about functional programming. Though I’m fully aware I’m just at the beginning, I must admit it’s going pretty well so far.

One thing that I have found confusing is unit testing. For better or for worse, I stumbled upon HUnit, which, going by the name, I thought might still be close to my comfort zone, given my previous experience with other x-unit frameworks.

Indeed, all the assertions come from JUnit so there is some familiarity. The official docs also reassure you that writing tests is not that difficult. They also mention that, by running runTestTT you’ll run all those tests. Easy! Well, not that fast.

I first had to understand how to run this as part of a project managed through cabal. As such, I headed to the official docs for cabal and, lo and behold, there’s a section specifically for test suites. Great! Well, sort of.

The doc hints at the fact that test suites sections must specify “interface types” and that those can either be exitcode-stdio-1.0 or detailed-0.9. It also says that test suites that use the exitcode-stdio “are executables that indicate test failure with a non-zero exit code when run; they may provide human-readable log information through the standard output and error channels”. By comparison, test suites using the detailed interface “are modules exporting the symbol tests :: IO [Test]” which makes next to zero sense to me, so I decided to go with the former.

And with that, I wrote my glorious first test.

module Main where

import Test.HUnit

tests = TestList [
    TestLabel "test1"
        (TestCase $ assertEqual "Test that 4 is equal to 5" 4 5),
                 ]

main :: IO ()
main = do
    runTestTT tests
    return ()

Asserting that 4 == 5 is a sure-fire way to make a test fail, right?

No.

When I run it, much to my dismay, I got this:

$ cabal configure --enable-tests && cabal build && cabal test
Up to date
Build profile: -w ghc-9.2.4 -O1
In order, the following will be built (use -v for more details):
 - example-0.1.0.0 (test:test-example) (additional components to build)
Preprocessing test suite 'test-example' for example-0.1.0.0..
Building test suite 'test-example' for example-0.1.0.0..
Build profile: -w ghc-9.2.4 -O1
In order, the following will be built (use -v for more details):
 - example-0.1.0.0 (test:test-example) (ephemeral targets)
Preprocessing test suite 'test-example' for example-0.1.0.0..
Building test suite 'test-example' for example-0.1.0.0..
Running 1 test suites...
Test suite test-example: RUNNING...
Test suite test-example: PASS
Test suite logged to:
/haskell/example/dist-newstyle/build/x86_64-linux/ghc-9.2.4/example-0.1.0.0/t/test-example/test/example-0.1.0.0-test-example.log
1 of 1 test suites (1 of 1 test cases) passed.

So I began to scratch my head and navigate the interwebs in search of an explanation.

Eventually, I gave in and asked on StackOverflow. A kind soul pointed out that I should instead use runTestTTAndExit.

Indeed, that solved the issue but… that function is conveniently not mentioned in the official docs and even a search on StackOverflow itself shows only that specific answer.

There are lots of things that still confuse me (what are frameworks such as tasty and test-framework that people mentions in other posts? What is the “detailled” interface actually used for? What’s the role of Quickcheck? What’s hspec? How does everything fit together?) but those are for another day.

A whirlwind tour of itertools

Photo by Miguel Á. Padriñán on Pexels.com

I did a short presentation at work a few days ago, about the itertools Python’s library, as we use some of the generator functions in that collection, but not everyone was familiar with them.

I’ve borrowed the categorisation of those functions from the excellent “Fluent Python” book by Luciano Ramalho, who’s just published the second edition of it. Aimed at intermediate/advanced developers, I cannot recommend it highly enough! A true gem, in my opinion.

Unfortunately, I cannot embed the slides here, it seems, so here’s the link to Zoho.

Though some examples will be specific to my work domain, most of the (succinct) descriptions can be helpful for people to refresh their memory or make them curious so that they delve into it deeply. Plus, it’s a useful refresher for me, too.

We clearly didn’t go through all of the generator functions – just the most important to us, for our application, but I won’t rule out extending the presentation in the future!

The sum of Armstrong numbers

Yesterday I got hooked up by a challenge posted in the Python Developers Community on LinkedIn, about defining a function to sum all Armstrong numbers in a range, that is all numbers that are equals to the sum of their digits, each raised to the power of the number of digits.

Better explained with an example: 153 is an Armstrong number, because 1**3 + 5**3 + 3**3 equals 153.

An additional constraint was that the boundaries of the range could be specified in any order. In other words: (4, 20) and (20, 4) denote the same range.

My solution was this:

from decimal import Decimal
def is_armstrong_number(number):
digits = Decimal(number).as_tuple.digits()
exp = len(digits)
return sum(d**exp for d in digits) == number
def sum_all_armstrong_numbers(start, end):
start_, end_ = sorted([start, end])
return sum(number for number in range(start_, end_+1)
if is_armstrong_number(number))
view raw armstrong.py hosted with ❤ by GitHub

Later in the evening, I thought I could give it a shot in Haskell, a language I’m fascinated with, but which I still know too little. So, here’s my original attempt (apologies for the lack of syntax highlighting on WordPress: I’ve only uploaded the final form as a Gist – more later):

digits :: (Integral n) => n -> [n]
digits n
  | n < 10 = [n]
  | otherwise = digits (n `div` 10) ++ [n `mod` 10]

numberLength :: Int -> Int
numberLength = length . digits

powerNumber :: Int -> [Int]
powerNumber n = map (\d -> (d ^ exponent)) listOfDigits
    where exponent = numberLength n
          listOfDigits = digits n

isArmstrong :: Int -> Bool
isArmstrong a = a == sum (powerNumber a)

sortEnds :: Int -> Int -> [Int]
sortEnds a b = if a < b then [a, b] else [b, a]

sumAllArmstrongNumber :: Int -> Int -> Int
sumAllArmstrongNumber a b = sum([x | x <- [start..end], isArmstrong x])
    where [start, end] = sortEnds a b

And I’m rather chuffed to say that, although it’s probably (likely!) non-idiomatic Haskell, it does work!

Addendum

As soon as I wrote the last sentence, I thought I could ask the wiser crowd on CodeReview StackExchange for a code review. A kind soul replied with an amazing dissection paired with a crystal-clear explanation.

Here’s the code in its final form:

digits :: (Integral n) => n -> [n]
digits = reverse . go
where go n
| n < 10 = [n]
| otherwise = r : digits q
where (q, r) = n `quotRem` 10
numberLength :: Int -> Int
numberLength = length . digits
powerNumber :: Int -> [Int]
powerNumber n = [d ^ exponent | d <- digits n]
where exponent = numberLength n
isArmstrong :: Int -> Bool
isArmstrong a = a == sum (powerNumber a)
sortEnds :: Int -> Int -> (Int, Int)
sortEnds a b = if a < b then (a, b) else (b, a)
sumAllArmstrongNumber :: Int -> Int -> Int
sumAllArmstrongNumber a b = sum [x | x <- [start..end], isArmstrong x]
where (start, end) = sortEnds a b
view raw armstrong.hs hosted with ❤ by GitHub

Use a closure to help debugging

There was a test running particularly slow, but I couldn’t pinpoint where it was spending most time.

The quick and dirty way would be to add a bunch of print functions – and that’s what I did as a first approximation. However, I quickly realised how tedious it was to mentally compute the delta between two timestamps – and at that point I thought about writing a tool to help with that.

What I needed, was a function that could remember the timestamps as they got printed. that’s a perfect application for a closure – a variable in the enclosing scope remembered by the function. Enter get_timer().

from datetime import datetime

def get_timer():
    """Return a function that, when invoked, prints the current time
    and the difference between that and the last time it was invoked.
    """

    times = []

    def t(message=""):
        """Print the current time and the delta since the last
        printed time. Optionally accept a message to print at the end.
        """
        last = datetime.utcnow()

        try:
            previous = times[-1]
        except IndexError:
            previous = last

        print("{:<30}  delta: {:<12.6f} {:<40}".format(
            last.isoformat(),
            (last-previous).total_seconds(),
            message))

        times.append(last)

    return t

get_timer() will instantiate an empty list, which is going to be remembered by the inner function, and accessed every time the inner function is invoked. Note that there’s not really a need for a full blown array: a simple variable would be enough.

Using a list is definitely helpful if you want to extend this tool to print, for instance, the time difference between the current call and the first call.

The way you use it is extremely simple:

def function_to_analyse():
    timer = get_timer()
    timer("Before `some_operation")
    some_operation()
    timer("After `some_operation` | Before `other_operation`")
    other_operation()
    timer("After `some_operation`")

This is now part of my toolbox and has already saved me some serious time in situations where simple prints were not enough and using a code profiler was just too much. Hope it’ll come in handy to you, too!

Restoring the hierarchy of a flat FIX message

This is something I had to do a while back, but realised I have never written down what I did. Given I’m facing a similar problem now, for a different application, I think it’s worth revisiting how I did it back in the days.

FIX messages can have what are called “repeating groups”, that is, blocks of key-value pairs that can appear multiple times, inside a field of a specific type (NumInGroup). One such example is tag 552 – NoSides, which allows you to specify how many sides will follow.

Without going into much detail, it’s worth noting that you can have “nested repeating groups”. For instance, NoSides can contain tag 576 – NoClearingInstructions, which will tell – you guessed it – how many clearing instructions will follow.

A made-up, quite contrived, example could be then: 552=2 54=1 576=2 577=0 577=12 54=2 576=1 577=0. This is the string (more or less) that I could use to send to an exchange.

In my tests in Python, I use a slightly higher-level abstraction, which translates to something like this:

message = {
    'NoSides': [{
        'Side': BUY,  # 1
        'NoClearingInstructions': [{
            'ClearingInstruction': NORMAL_TRADE,  # 0
        }, {
            'ClearingInstruction': CUSTOMER_TRADE,  # 12
        }],
    }, {
        'Side': SELL,  # 2
        'NoClearingInstructions': [{
            'ClearingInstruction': NORMAL_TRADE,  # 0
        }],
    }]
}

So far so good, it’s easy to flatten a dictionary of lists and dictionaries: we just need to traverse it using a depth-first approach. However, when a response comes back from the exchange, we’ll need to restore the hierarchy somehow.

The way I tackled this problem is by having two independent iterators: one to advance and peek at the next tag, the other to advance as we go by. As we encounter repeating groups (nested or not), we call the same function recursively.

We assume here that we have access to a couple of ancillary functions:

  • is_repeating_group_tag, which returns True or False, depending on whether the tag is of type NumInGroup or not;
  • get_valid_tags_for_repeating_group_tag, which returns a collection of tags that could potentially appear in the repeating group tag we pass as argument.

For clarity, we also assume that the message comes in as a list of key, value tuples (not hard to build starting from a string of key=value pairs separated by some delimiter).

def restore_hierarchy(iterator, tags=None):
    message = OrderedDict()
    while True:
        try:
            iterator1, iterator2 = tee(iterator1)
            tag, value = next(iterator2)

            # If we have tags it means we are at least one-level deep
            # in the recursion. If the tag we are parsing is not in
            # the list of tags passed, it means it does not belong to
            # this repeating group.
            if tags and tag not in tags:
                break

            # If the tag we are parsing is already present in the
            # message we are building, it means it belongs to another
            # repeating group.
            if tag in message:
                break

            # Advance the first iterator to sync up with the second
            next(iterator1)

            # If the tag is that of a repeating group, we instantiate
            # a list and get a list of potential tags for that
            # repeating group and we call this function recursively.
            # We do that for the number of times the repeating group
            # indicates as its value.
            if is_repeating_group_tag(tag):
                message[genericName] = []
                subTags = get_valid_tags_for_repeating_group_tag(tag)
                for _ in range(int(value)):
                    message[tag].append(restore_hierarchy(
                        iterator1, subTags))
            else:
                message[tag] = value

        except StopIteration:
            break

    return message

The incredible usefulness of strace

Debugging things that go wrong is what I do day in and day out. Navigating through stack traces, scouring log files, and examining code line by line is something I really love. I admit I love it less when I can’t crack the riddle after I’ve fired all my bullets.

And that is when I resort to the big ammo in my arsenal. Enter strace.

strace allows you to trace system calls made by binaries that you start or that are already running. The output can be daunting, depending on what you filter on — or if you don’t filter at all — but there are some flags that make it more digestible and useful. Here are some that I use relatively frequently.

  • -o <filename> the output is usually very verbose: this flag makes strace write on a file for later consumption
  • -p <PID> attach to a running process by specifying its ID
  • -f trace child processes. Very useful if the parent process forks other processes: strace will attach to them automatically
  • -tt prefix each line with a time stamp with microsecond resolution (use only -t to get rid of micros)
  • -T append each line with the time spent in each call
  • -C when strace exits (whether because the underlying process exited or you decided to stop it) display a summary table with all system calls logged, how many times they were called, errors, time spent. Different from -c in that the latter suppresses all other output (usually not what you want).

To learn more how to analyse the output, I refer you to Julia Evans who from time to time creates some lovely e-zines: one of them is on strace.

Learn strace. Love strace. It’ll never let you down.

ssh-agent and LD_LIBRARY_PATH

Quick post about a quirk that cost me a few hours in debugging: trying to understand why the LD_LIBRARY_PATH environmental variable was getting unset in my tmux sessions, lunched through ssh-agent (basically, by running ssh-agent tmux).

Long story short, it has nothing to do with tmux — and all with ssh-agent.

According to this page,

Since ssh-agent(1) has the SGID bit set, it ignores LD_LIBRARY_PATH and does not propagate the variable to its children.

There are some workarounds on that page already, but the one that’s not mentioned there and did the trick for me was to run eval `ssh-agent` and, subsequently, tmux.

On StackOverflow, off-topic questions and code reviews

I was reviewing the “first posts” queue on StackOverflow yesterday and one of questions I had to review was not particularly clear nor very well written.

Basically, the poster had a script which was looping and interacting with the user and her question was (I think) how to exit this infinite loop at some point.

The question was unclear, and the code wasn’t particularly elegant, not really what can be regarded as a Minimal Working Example, yet it was actually running. Needless to say, by the time I was ready to make some comments and edits, the post had already been closed as “off-topic”.

In my mind, that’s a bit of a shame. I understand it wasn’t the best question in the world but, picture this: there’s someone who’s trying to write some program, she manages to cobble together some code (we all started out like that, come on), she actually makes it run, stumble upon a blocker, turns to the wisdom of the (geek) crowd for help… only to get told to ask a better question. Like, “Yeah yeah, sorry, got no time, can’t understand what you want, go and play hide and seek on the Autobahn”.

I don’t know, it feels wrong. I mean, people will have to start off at some point, they can make mistakes, right? Which is why I flagged the post and asked it to move it to the Code Review StackExchange — awesome for this particular type of questions.

Got no response, most likely my request was ignored, no big deal. Anyhow, I edited the post today and asked (again) for it to be moved to CR. I’m not sure it’ll ever be re-opened and moved, so what I thought of doing is give my view of that code here on my blog and offering solution. It can (of course) still be improved, but it’s a start.

Data structures

#Phones
p = ["BPCM", "BPSH", "RPSS", "RPLL", "YPLL", "YPLL"]
p_desc = ["Compact", "Clam Shell", "RoboPhone - 5-inch screen and 64 GB memory", "RoboPhone - 6-inch screen and 256 GB memory", "Y-Phone Standard – 6-inch screen and 64GB memory" , "Y-Phone Deluxe – 6-inch screen and 256GB memory", ""]
p_price = [29.99, 49.99, 199.99, 499.99, 549.99, 649.99]

#Tablets
t = ["RTMS", "RTLM", "YTLM", "YTLL"]
t_desc = ["RoboTab – 8-inch screen and 64GB memory", "RoboTab – 10-inch screen and 128GB memory", "Y-tab Standard - 10-inch screen and 128GB memory", "Y-tab Deluxe - 10-inch screen and 256GB memory"]
t_price = [149.99, 299.99, 499.99, 599.99]

The snippet above shows something interesting. for phones you have a list of product codes, followed by a list of descriptions, followed by a list of prices. The poster commented she wasn’t able to use 2-D array, presumably because she wanted to have a list of lists, the latter being something like “code, description, price”.

Since the structure is followed by all the items in the code, a better solution would be to collect these properties into an object.

class Item:
    """Class representing a generic item, having a code, description and a price"""

    def __init__(self, code, description, price):
        self.code = code
        self.description = description
        self.price = price

    def __str__(self):
        return "{} ({}) - price: {}".format(self.description, self.code, self.price)

    def __repr__(self):
        return "Item({}, {}, {})".format(self.code, self.description, self.price)

So that you can define a phone or a tablet as Item("BPCM", "Compact", Decimal("29.99"))

An additional improvement we can do is the use of something better than lists for collecting our items. I’d suggest a simplified mapping where items are stored by code:

class ItemCollection:
    """Collection of Items, stored by code"""

    def __init__(self, *items):
        self.collection = {item.code: item for item in items}

    def __getitem__(self, code):
        return self.collection[code]

    def __iter__(self):
        return iter(self.collection.items())

    def codes(self):
        return self.collection.keys()

Flow control

running = True

while running == True:
  print ("Do you want a phone or a tablet")

Couple of things we can do better here. First, checking wether a value is True (or False) should be done through the identity operator (is) not the equality one (==). This is because there is only one True object. Second, we can get rid of this check altogether, as we can simply break out of the loop at some point, depending on what the user of the script decides.

This would become something like:

while True:
    ...
    add_another = present_choices("Add another device? ", {"Y", "N"}).upper()
    if add_another != "Y":
        break

Input/Output

 if phone_tablet == ("phone"):
    print("\nCode -")
    print (p)
    print("\nDescription -")
    print(p_desc)
    print("\nprice -")
    print(p_price, "\n")
    p_choice = input ("Enter the code of the phone \n")
    while p_choice not in p:
      print("Unvalid code, try again")
      p_choice = input("Enter the code again ")
    pass

Lots of prints going on here. We can probably tidy them up. Also there’s a superfluous pass statement that can be removed and some typos (Unvalid). What we can notice is also that the prints are repeated in a similar fashion throughout the code, though they’re not exactly the same. We could probably reuse them and create a function. Something like:

def show_items(items, intro_text=None, outro_text=None):
    """Print to the standard output the items in the collection

    :param items: ItemCollection instance
    :param intro_text: (optional) text to prepend to the output
    :param outro_text: (optional) text to append to the output
    """
    line_format = "{:<4} | {:<50} | {:<8}"
    header = line_format.format("Code", "Description", "Cost")
    separator = "-" * len(header)

    if intro_text:
        print(intro_text)
        print()

    print()
    print(header)
    print(separator)
    for _, item in items:
        print(line_format.format(item.code, item.description, item.price))
    print(separator)

    if outro_text:
        print(outro_text)
        print()

It could be made even more generic (notice that it has knowledge of “Code”, “Description” and “Cost”) and, in general, better (fewer print()s) but it’s a valid starting point.

Getting the input is also pretty much the same operation, regardless of the type of the device (enter something, if not valid try again, otherwise continue). Here, too, we can reuse our code if we put it into a function.

def present_choices(prompt, valid_choices, error_prompt="Invalid choice. ") -> str:
    """present_choices: ask the user for input

    :param prompt: string to present to the user (e.g. "Type 'phone' or 'tablet': ")
    :param valid_choices: collection of inputs that can be accepted
    :param error_prompt: string to present to the user in case she fails to give a valid choice,
        defaults to "Invalid choice. ".

    :return: string representing the choice made
    """
    choice = input(prompt)
    while choice not in valid_choices:
        choice = input(f"{error_prompt}{prompt}")

    return choice

General comments

Other things that can be improved in general:

  • Respect PEP 8 for better maintainability – it always pays off.
  • Write unit tests to fend off regressions – no quibbling!
  • Type hints could also help. I’m guilty myself here, coming from a background almost exclusively made of Py2.
  • Encoding directive at the beginning of the script. This is another thing that caught me off: the original code contains some non-ASCII characters and Python will complain when trying to parse it unless you add something like -*- coding: utf-8 -*- at the top of the script.
  • The use of f-strings, reducing the amount of code needed to populate strings.
  • Anything else I missed? Anything that’s not quite right? Let me know in a command: I’ll be happy to know (how would anyone learn without feedback?!)

My attempt at a solution would look like this:

#!/usr/bin/env python3
# -*- coding: utf-8 -*-

from decimal import Decimal


class Item:
    """Class representing a generic item, having a code, description and a price"""

    def __init__(self, code, description, price):
        self.code = code
        self.description = description
        self.price = price

    def __str__(self):
        return "{} ({}) - price: {}".format(self.description, self.code, self.price)

    def __repr__(self):
        return "Item({}, {}, {})".format(self.code, self.description, self.price)


class ItemCollection:
    """Collection of Items, stored by code"""

    def __init__(self, *items):
        self.collection = {item.code: item for item in items}

    def __getitem__(self, code):
        return self.collection[code]

    def __iter__(self):
        return iter(self.collection.items())

    def codes(self):
        return self.collection.keys()


phones = ItemCollection(
    Item("BPCM", "Compact", Decimal("29.99")),
    Item("BPSH", "Clam Shell", Decimal("49.99")),
    Item("RPSS", "RoboPhone - 5-inch screen and 64 GB memory", Decimal("199.99")),
    Item("RPLL", "RoboPhone - 6-inch screen and 256 GB memory", Decimal("499.99")),
    Item("YPLL", "Y-Phone Standard – 6-inch screen and 64GB memory", Decimal("549.99")),
    Item("YPLL", "Y-Phone Deluxe – 6-inch screen and 256GB memory", Decimal("649.99")))


tablets = ItemCollection(
    Item("RTMS", "RoboTab – 8-inch screen and 64GB memory", Decimal("149.99")),
    Item("RTLM", "RoboTab – 10-inch screen and 128GB memory", Decimal("299.99")),
    Item("YTLM", "Y-tab Standard - 10-inch screen and 128GB memory", Decimal("499.99")),
    Item("YTLL", "Y-tab Deluxe - 10-inch screen and 256GB memory", Decimal("599.99")))

sim_cards = ItemCollection(
    Item("SMNO", "SIM Free (no SIM card purchased)", Decimal("0.00")),
    Item("SMPG", "Pay As You Go (SIM card purchased)",  Decimal("9.99")))

cases = ItemCollection(
    Item("CSST", "Standard", Decimal("0.00")),
    Item("CSLX", "Luxury", Decimal("50.00")))

chargers = ItemCollection(
    Item("CGCR", "Car", Decimal("19.99")),
    Item("CGHM", "Home", Decimal("15.99")),
    Item("CGNO", "No charger", Decimal("0.00")))


total_price = Decimal("0")

def show_items(items, intro_text=None, outro_text=None):
    """Print to the standard output the items in the collection

    :param items: ItemCollection instance
    :param intro_text: (optional) text to prepend to the output
    :param outro_text: (optional) text to append to the output
    """
    line_format = "{:<4} | {:<50} | {:<8}"
    header = line_format.format("Code", "Description", "Cost")
    separator = "-" * len(header)

    if intro_text:
        print(intro_text)
        print()

    print()
    print(header)
    print(separator)
    for _, item in items:
        print(line_format.format(item.code, item.description, item.price))
    print(separator)

    if outro_text:
        print(outro_text)
        print()


def present_choices(prompt, valid_choices, error_prompt="Invalid choice. ") -> str:
    """present_choices: ask the user for input

    :param prompt: string to present to the user (e.g. "Type 'phone' or 'tablet': ")
    :param valid_choices: collection of inputs that can be accepted
    :param error_prompt: string to present to the user in case she fails to give a valid choice,
        defaults to "Invalid choice. ".

    :return: string representing the choice made
    """
    choice = input(prompt)
    while choice not in valid_choices:
        choice = input(f"{error_prompt}{prompt}")

    return choice


while True:

    device_type = present_choices("Which type of device would you like? Type 'phone' or 'tablet': ",
                                  {"phone", "tablet"})

    devices = phones if device_type == "phone" else tablets
    show_items(devices)
    device_code = present_choices("Enter the code of the device of your choice: ", devices.codes())

    show_items(sim_cards)
    sim_card_code = present_choices("Enter the code of the SIM card of your choice: ", sim_cards.codes())

    show_items(cases)
    case_code = present_choices("Enter the code of the case of your choice: ", cases.codes())

    show_items(chargers)
    charger_code = present_choices("Enter the code of the case of your choice: ", chargers.codes())

    selection = ItemCollection(
        devices[device_code],
        sim_cards[sim_card_code],
        cases[case_code],
        chargers[charger_code])

    price = sum(i.price for (_, i) in selection)
    total_price += price
    show_items(selection, intro_text="Summary of your selection", outro_text=f"The total price is {price}")

    add_another = present_choices("Add another device? ", {"Y", "N"}).upper()
    if add_another != "Y":
        break


print(f"The total price is {total_price}")

Who knows, maybe the poster will never stumble upon this blog post.

Or maybe she will, in which case I’d tell her “Hang on, you’ll soon be on the other side of the fence, doing code reviews for those who have just started!”.

Customising tool bar and menu items in Evolution

Ever wondered how to add or remove buttons from the toolbar in Evolution? Me too.

It never annoyed me so much as to prompt for a fix but today I took the time to duck duck for a solution and the answer was just a couple of links away. The Evolution team decided not to include a graphical editor to move around buttons and edit toolbars and menus in general but, at the same time, did want to allow users willing to personalise their email client to be able to do so.

Turns out, the client’s views are defined in .ui files, usually relegated in /usr/share/evolution/ui/ (or $PREFIX/share/evolution/ui/ for non-standard installations).

The quite readable XML files in those folders will dictate how buttons and menus have to appear when you fire up your client. To override them, just copy the relevant UI files over to your local config folder – usually $HOME/.config/evolution/ui/ and edit them as necessary.

Save, restart the client and, lo and behold, more (or less) buttons in your toolbar!

This is actually documented in the app’s help, but, unfortunately, I found out about it only after finding the commit explaining the overriding mechanism following a link in a thread by another user trying to accomplish the same thing. Oh, well!