Implementing the Config Management for my CLI
This is Part 3 in Building a CLI in Rust Series
In the last post, I mapped out the overall API I want to implement. In this post, I will focus on writing the config parsing for the rest of the project.
Reset
I recently bit the bullet and converted my neovim configuration to use LazyVim. A relatively flexible and customizable Neovim configuration. I was tired of maintaining an editor config I rarely use. This configuration is very familiar to use and I think I won’t have to think about tiny details or plugin management or other things for a long time. I was already procrastinating doing this series so I thought I would be a good test drive to assess the development experience of the new configuration.
The first thing to do was to part ways with the old version of the project. I created a branch called rust-rewrite
and
deleted everything except the LICENCE.md
and .gitignore
. then executed cargo init
to mark the new beginning.
Config Management
There are several cases that we need to cover dealing with config file. Let’s go over them step by step.
- Config should be defined in a pre-determined location.
- If Config file exists, we must read it and return the result.
- If config file does not exists, we first check if the parent folder is present.
- We create the folder structure if it doesn’t exist.
- Finally we create the empty config and return to be used.
- We could throw an error, But I thought that once the config is created, we can pass to the next step and we can handle the invalid configuration there.
Config Location
The config will reside in the .config/lazydraft
folder, similar to the previous version. Full path will contain
$HOME
variable in it. So we can start by getting the valid HOME
environment variable.
Before doing that, I created a type called ConfigResult
to define the Success and Error states of config parsing.
After building the config path, we can check that if the file exists or not. If it exists, we can parse the config and return it.
Config Struct
Speaking of config, I mentioned that I want to keep the config as a JSON file. I found that dealing with yaml
files
are more cumbersome than it needs to be. With JSON, I can be sure about the structure and the format easier. That part
could change in the future but I think It will be easy to replace If I want to implement a new solution. The only thing
I would need update is to config parsing method. Here is the code for the Config Struct:
I decided to use Serde crate to handle our config file. Config
derives Serialize
and
Deserialize
trait. To display the config on the screen, we also need to implement a fmt
function for the Config
. I
also added an utility function to return if any of the config parameter is defined empty. We can use this function to
check if we have a valid config or not.
Config parsing logic
If the config is present, We can open the config file, attach a reader, and read the config file to the Config
struct
using Serde’s serde_json
module.
The next part is to check the existence of parent folder structure. We do this by getting the parent of the file path
and checking whether it exists or not. If not, we create the whole folder chain using create_dir_all
function.
Final part is to create the empty config file and initializing with an empty config. We create the file using create
from the File
struct and according to the success, we do the following:
- Create an empty config struct.
- Convert it to pretty string and serialize it.
- Write the content to the empty config file.
- Return the initialized config.
Here is the final part that executes these steps:
Result
We now have a working config object that we can use inside our Commands! I find the exhaustive nature of Rust’s standard
library a joy to work with. Writing code and understanding the logic is quite clear. Chaining the functions and using
match
statements also helps you to define errors out of existence.
Here is the current state of our project:
And our empty initialized config:
In the next post I will start to implement the commands, starting with list
. See you then!
References: