(λ (x) (create x) '(knowledge))

Tying up loose ends

Prepping toAPK for release, and life updates. · April 22, 2020

In just a few days, I'll officially be a Mainer. I'll officially be working in the DevOps field, building infrastructure on Linux, and even writing Lisp code for a living. The future feels bright and exciting, filled with opportunity, challenges, and lobster rolls. Definitely filled with lobster rolls! I usually have mixed feelings about moving into a new role with a new company. I usually let myself get anxious with the what ifs that bounce around my head, but honestly, this time is different. I'm able to do so much for my family, move into a position with a high level of ownership, and lots of creative freedom. And truly and honestly hone my Lisp skills. There's years of work, getting to a point where I feel confident in my software and administration skills to even take a shot at this kind of position, and here I am taking it.

Another thing that I never really thought I would be doing, making tooling that might actually end up in a Linux distributions arsenal. toAPK is turning out to be more than a quick "good enough" script to help me be lazy. It now produces compliant APKBUILDs, and allows me to move directly from "I want this" to compiling the source. And since it's so useful now, that means I want other people to use it!

All of that means rethinking how I handle things. Not everyone knows Lua, let alone Fennel. And it's probably a bit silly to require people to manually build code configuration files, so we're going to add a dotfile!

The original toAPK configuration file was a Lua table:

local clientconf = {} clientconf.Info = { flastname = "Will Sinatra", Email = "wpsinatra@gmail.com" } return clientconf

While that might seem pretty simple to a developer, it's additional overhead that we really don't want right now. So why not something like:

flastname:Will Sinatra email:wpsinatra@gmail.com

Now that we can work with. It's super simple, super straight to the point. And it's entirely in line with how toAPK already operates. When toAPK is fed a PKGBUILD, it really just checks each line for a matching string. If we have pkgver=1.0 and split the string at the =, then we can readily verify "pkgver" == "pkgver. The same logic goes here, when I want to create a Contributor/Maintainer string for a new package I want flastname == flastname & email == email.

(fn configure [] (each [line (io.lines ".toAPK")] (do (var dat (split line ":")) (if (= (. dat 1) "flastname") (do (tset cc "flastname" (. dat 2)))) (if (= (. dat 1) "email") (do (tset cc "email" (. dat 2)))))))

This works perfectly to just grab the values out of the .toAPK file to intern them in a global client configuration table, but what if the file doesn't exist? What if someone forgets their ":", or puts a space somewhere weird? What if my configuration style makes sense only because I came up with it? Lots of questions, which we can answer with more lispy goodness.

(fn exists? [file] (local f (io.open file "r")) (if (= f nil) false (io.close f)))

We add a very simple existence check function which can be passed a file path. If the file doesn't exist we'll get a nil value and can return false, which can be a trigger for a user warning. If the file is there, then that's perfect, we can try to parse it! To ensure that each user has a unique .toAPK file we add a global variable which we can populate by probing the user's ENV variables.

(var dotpath ".toAPK") (fn setdotpath [] (local envcheck (assert (io.popen "printf $HOME"))) (local home (assert (envcheck:read "*a"))) (set dotpath (.. home "/.toAPK")))

With this we will get the right configuration path based on the user. Our configuration file won't be statically set during installation, and every user can have a unique configuration. We can wrap all of this up nicely by adding a simple propagation function which will prompt the user for input, and output to their home directory a formatted .toAPK file, and all of that can be handled with simple io.write calls. Something like this:

(fn propagate_conf [] (print "Generating .toAPK..") (local cf (io.open dotpath "w")) (print "Enter your first and last name.") (cf:write (.. "flastname:" (io.read))) ...

At the end of everything we have a set of functions that will check for the configuration file in the issuing users directory, error out if it doesn't exist, or parse the data into our APKBUILDtbl. All of that tied together looks a little like this:

(fn configure [] (if (= (exists? dotpath) false) (do (print "[WARN] .toAPK not detected!") (print "[INFO] use toAPK -c to configure user variables") (os.exit))) ... (tset APKBUILDtbl "Contributor" (.. "# Contributor: \"" cc.flastname " " cc.email "\"")) (tset APKBUILDtbl "Maintainer" (.. "# Maintainer: \"" cc.flastname " " cc.email "\"")))

None of that is earth shattering or new, but I've never tried to make a dotfile before, and have typically defaulted to providing data as s-expressions or tables, and just dealing with everything be statically defined. This will be a nice little basis to work with when I'm making tools for my future team.