code and games
Built with Hugo and Hyde-Y.

Overengineering a Business Card

· Read in about 4 min · (709 Words)

I was recently inspired by the terminal-based business card fad started by @bitandbang back in December:

The npxcard package certainly would have made it trivial to make my own, however being the ornery sort that I am I decided instead to scratch make a Go-based version which could respond to a curl request.

I wound up making multiple packages to do so, mostly because I could imagine myself expanding on some of them further in directions more and/or less focused on terminal-based business cards, though it’s unlikely I will.


First up is my new box library. Half the appeal of the terminal-based card design is the nice linework around the outside. Creating such a box programmatically is surprisingly difficult, as the ANSI characters used to color and style the lines and their contents for terminal display are printable otherwise, making it difficult to determine box width and line padding. Fortunately the stripansi library handles this issue quite well, allowing me to remove ansi from strings before determining the string’s length.

After that was properly measuring the “length” of a string. Using the Go len() built-in will return the number of bytes, not the number of runes/characters. For this, Go’s excellent utf8 standard library provides utf8.RuneCountInString().

fmt.Println(len("ẗẽśţíñḡ")) // 17
fmt.Println(utf8.RuneCountInString("ẗẽśţíñḡ")) // 7

Once that was sorted, it was pretty trivial to throw in a selection of box styles.

┌────────────────┐ ╔═══════════════╗
│  DefaultStyle  │ ║  DoubleStyle  ║
└────────────────┘ ╚═══════════════╝
╭────────────────╮ +----------------+
│  RoundedStyle  │ |  ClassicStyle  |
╰────────────────╯ +----------------+

And since it wasn’t too difficult, the ability to add multiple horizontal sections to a box:

│  Section 1  │
│  Section 2  │
│  Section 3  │

The box style definitions include the characters necessary to create vertical sections as well, however creating a functional tabular layout system capable of using it is outside of the scope of the goal of creating a business card of the design I had in mind, so I’ve left that for later (if at all).

The Box’s Contents

I felt like defining a system for handling the contents of the box would have been too restrictive for the box library itself, unnecessarily restricting it to “card”-like usage. So I created a separate card library for this.

Taking inspiration from npxcard’s data structure, a card section contains an optional header and list of items. Each item has a label and data, and when printed will bold and align the labels, like so:

Putting the Card Together

Once these libraries were completed - along with a small utility alignment library - I put them all together in a new project I inventively named mycard. Feel free to fork and modify this repository to create your own business card variants. Also look at the wonderful color library by the venerable Fatih Arslan. While it is no longer maintained it is perfectly sufficient for ANSI styling in Go and used extensively throughout this project.

Running the Card

Once the binary was uploaded to my web server, I created a systemd service for the card at /etc/systemd/system/mycard.service which looks like this:

Description=My Card



Being a systemd n00b, most of the above is from online examples and a bit of trial and error. Your mileage may vary.

With a little bit of trickery in Caddy, I was able to keep the domain functional as an ingress for my blog. The Caddyfile does some User-Agent sniffing and proxies curl requests to the card process: {
  rewrite {
    if {>User-Agent} has curl
    to /card
  proxy /card localhost:8000

This looks for the word “curl” in the User-Agent header and if found proxies the request to localhost:8000 where I have the mycard service running. This also exposes the card at the route /card which is browser-visitable, which I don’t mind. Going to will let you see the card in all its ANSI glory. If you’d rather not do this you can use any route you’d like in lieu of /card, and/or protect that route with internal /card if you don’t want it to be visitable without an internal rewrite.