Configuring a Config File for the Unsplash Image Markdown Generator

About nine months ago, I made a small script in Elvish that generates markdown code to prettily embed Unsplash images in posts. Although I don't post as much anymore, I still use it whenever I think about using an Unsplash image in my posts. I haven't improved or updated the script since the first version released so many months ago, but it still gets the job done. There are some quirks (e.g. It'll write '$nil' instead of leaving it blank if a certain piece of data is not provided by the photo author), but they are tolerable. Although I should still go back to them after this...

Recently, I had to redownload a fresh copy of the script from my GitHub as my previous copy had been wiped in a disk format. To use the script, an Unsplash API access key is required. I logged onto Unsplash, grabbed my access key, and started looking for where I should paste it for the script to function... oh, I have to paste it in the script itself.

"I wrote that?". Photo by bruce mars on Unsplash.


This does not look good.

Typically, in most contexts, the API key should be stored separately from the code. For software deployed in containers, the key is typically stored as an environment variable configured in the container itself. For other cases, they typically live in configuration files. There are a few good reasons why you should not let the API key live directly in the code itself, but most notably, it creates a risk of accidentally committing and uploading the key to a public code repository online. Besides that, it isn't user friendly if the key has to be provided by the user, which is exactly the case for this script. Of course, it isn't hard to insert the key into the script, but why would you let a user do this?

So I'm just going to change this by a little...


Considering the nature of this script, it makes the most sense to use a configuration file. Since I also have some plans to make a GUI version of this some time in the future with C#, I should pick a file format that can be read and parsed easily by both Elvish and C#. JSON turns out to be a natural choice, as both languages have built-in options to parse JSON strings. So, we make a very simple JSON file with only one property, the access_key...

{
    "access_key": ""
}

Before trying to read the file, the script should check if the file exists. Although Elvish offers the ideas of exceptions, using them isn't as intuitive and sweet as I would have expected. The error message included in them isn't clean enough to be prettily printed in their default state. So, to fulfil my dream of being able to provide beautiful error messages, I'll just manually check for them!

use path 

# read the config file for access key and some other configurables

if (not (path:is-regular "config.json")) {
    print "Config file 'config.json' is not found!\n"
    exit 2
}

We are now sure that the file exists. But, how do we... read it?

As far as I can tell, the Elvish documentation doesn't have anything about reading a file's contents without using external tools like cat. External tools are something that I am trying to avoid, as they typically cause a lot of dependency problems when cross-platform is a goal. For example, there is no cat on Windows. I could use conditionals to check for the platform the user is currently on, but doesn't that sound very ugly?

We're not talking about this cat, but a cat photo rarely goes wrong. Photo by Bogdan Farca on Unsplash.


Fortunately, there seems to be a way. And who would have expected that I would learn this from a site like GTFOBins?

We can now read the file, turn it into a JSON, and save it as a variable!

var config_map = (echo (slurp < "config.json") | from-json)

I wonder why this doesn't seem to be documented in the official Elvish documentation. Perhaps I should open an issue...

Now that we have the whole configuration as a JSON, we can check if the access_key property is there, and if it is, save it to a global variable called access_key so that the HTTP request can use it later. Similar to the previous situation, I have a dream of being able to print errors beautifully...

var access_key = $nil

if (has-key $config_map "access_key") {
    set access_key = $config_map["access_key"]
} else {
    print "Property 'access_key' is not set in config.json!\n"
    exit 3
}

After this, the value can be used directly when building the authorization header. That was quick!

var auth_header = (str:join "" ["Authorization: Client-ID " $access_key])

Sometimes I wonder, why didn't I do this earlier? I could probably had done that in the first version as well... Maybe it just didn't came to my mind.

Now that it has a configuration file, more possibilities had opened up - for example, it should now be possible to specify the output should be instead of sticking to one fixed format! Implementing that might be difficult, though... perhaps I will have to do some regex magic for that? Either way, that's for future me :^)

There are still a few more things to do with this script (and this project in overall), so maybe we'll meet on this topic again some time later. See ya!


Header image