= Parse config files with Lua

Not all applications need configuration files, because many applications benefit from starting fresh each time it's launched.
Simple utilities, for instance, rarely require preferences or settings that persist across uses.
However, when you write a complex application, it's often nice for users to be able to configure how it they interact with it and how it interacts with their system.
That's what configuration files are for, and this article discusses some of the ways you can implement persistent settings with the Lua programming language.

== Choose a format

The important thing about configuration files is that they are consistent and predictable.
The last thing you want to do is dump information into a file under the auspices of saving user preferences, and then spend days writing code to reverse engineer the random bits of information that have ended up in the file.

There are LINK-TO-CONFIG-FILE-FORMATS-ARTICLE[several popular formats for configuration files].
Lua has libraries for most common configuration formats, but in this article I use the INI format.

== Installing the library

The central hub for Lua libraries is https://opensource.com/article/19/11/getting-started-luarocks[luarocks.org].
You can search for libraries on the website, or you can install and use the `luarocks` terminal command.

On Linux, you can install it from your distribution's software repository.
For example:

[source,bash]
----
$ sudo dnf install luarocks
----

On MacOS, use https://opensource.com/article/20/11/macports[MacPorts] or https://opensource.com/article/20/6/homebrew-mac[Homebrew].

On Windows, use https://opensource.com/article/20/3/chocolatey[Chocolatey].

Once `luarocks` is installed, you can use the `search` subcommand to search for an appropriate library.
If you don't know the name of a library, you can search for a keyword, like `ini` or `xml` or `json`, depending on what's relevant to what you're trying to do.
In this case, you can just search for `inifile`, which is the library I use to parse text files in the INI format:

[source,bash]
----
$ luarocks search inifile
Search results:
inifile
1.0-2 (rockspec) - https://luarocks.org
1.0-2 (src) - https://luarocks.org
1.0-1 (rockspec) - https://luarocks.org
[...]
----

A common trap programmers fall into is installing a library on *their* system and forgetting to bundle the library with their application.
To avoid this, use the `--tree` option to install the library to a local folder within your project directory.
If you don't have a project directory, create one first, and then install:

[source,bash]
----
$ mkdir demo
$ cd demo
$ luarocks install --tree=local inifile
----

The `--tree` option tells `luarocks` to create a new directory, called `local` in this case, and to install your library into it.
With this simple trick, you install all the dependency code you want to use in your project into the project directory itself.

== Code setup

First, create some INI data in a file called `myconfig.ini`:

[source,text]
----
[example]
name=Tux
species=penguin
enabled=false

[demo]
name=Beastie
species=demon
enabled=false
----

Save the file as `myconfig.ini` into your home directory, *not* into your project directory.
You usually want configuration files to exist outside your application so that even when a user uninstalls your application, the data they've generated by using the application remains on their system.
Users may remove unnecessary config files manually, but many don't, and the result is that when an application is reinstalled, it appears to have retained all of their preferences.

Config file locations are technically unimportant, but each OS has either a specification or a tradition of where they ought to be placed.
On Linux, this is defined by the https://www.freedesktop.org/wiki/Specifications[Freedesktop specification].
This dictates that configuration files are to be saved in a hidden folder named `~/.config`.
For clarity during this exercise, however, just save the file in your home directory so that it's easy to find and use.

Create a second file named `main.lua` and open it in your favourite text editor.

First, you must tell Lua where you've placed the additional library you want it to use.
The `package.path` variable determines where Lua looks for libraries.
You can view Lua's default package path in a terminal:

[source,bash]
----
$ Lua
> print(package.path)
/?.lua;/usr/share/lua/5.3/?.lua;/usr/share/lua/5.3/?/init.lua;/usr/lib64/lua/5.3/?.lua;/usr/lib64/lua/5.3/?/init.lua
----

In your Lua code, append your local library location to `package.path`:

[source,lua]
----
package.path = package.path .. ';local/share/lua/5.3/?.lua
----

== Parsing INI files with Lua

With the package location established, the next thing to do is to require the `inifile` library, and then handle some operating system logistics.
Even though this is a simple example application, the code needs to get the user's home directory location from the OS, and establish how to communicate filesystem paths back to the OS when necessary.

[source,lua]
----
package.path = package.path .. ';local/share/lua/5.3/?.lua
inifile = require('inifile')

-- find home directory
home = os.getenv('HOME')

-- detect path separator
-- returns '/' for Linux and Mac
-- and '\' for Windows
d = package.config:sub(1,1)
----

Now you can use `inifile` to parse data from the config file into a Lua table.
Once the data has been placed into a table, you can query the table as you would any other Lua table.

[source,lua]
----
-- parse the INI file and
-- put values into a table called conf
conf = inifile.parse(home .. d .. 'myconfig.ini')

-- print the data for review
print(conf['example']['name'])
print(conf['example']['species'])
print(conf['example']['enabled'])
----

Run the code in a terminal to see the results:

[source,bash]
----
$ lua ./main.lua
Tux
penguin
1
----

That looks correct.
Try doing the same for the `demo` block.

== Saving data in the INI format

Not all parser libraries read and write data (often called _encoding_ and _decoding_), but the `inifile` library does.
That means you can use it to make changes to a configuration file, too.

To change a value in a configuration file, you set the variable representing the value in the parsed table, and then you write the table back to the configuration file:

[source,lua]
----
-- set enabled to true
conf['example']['enabled'] = true
conf['demo']['enabled'] = true

-- save the change
inifile.save(home .. d .. 'myconfig.ini', conf)
----

Take a look at the configuration file now:


[source,bash]
----
$ cat ~/myconfig.ini
[example]
name=Tux
species=penguin
enabled=true

[demo]
name=Beastie
species=demon
enabled=true
----

== Config files

The ability to save data about how a user wants to use an application is an important part of programming.
Fortunately, it's a common task for programmers, so much of the work has probably already been done.
Find a good library for encoding and decoding into an open format, and you can provide a persistent and consistent user experience.

The entire demo code for reference:

[source,lua]
----
package.path = package.path .. ';local/share/lua/5.3/?.lua'
inifile = require('inifile')

-- find home directory
home = os.getenv('HOME')

-- detect path separator
-- returns '/' for Linux and Mac
-- and '\' for Windows
d = package.config:sub(1,1)

-- parse the INI file and
-- put values into a table called conf
conf = inifile.parse(home .. d .. 'myconfig.ini')

-- print the data for review
print(conf['example']['name'])
print(conf['example']['species'])
print(conf['example']['enabled'])

-- enable Tux
conf['example']['enabled'] = true

-- save the change
inifile.save(home .. d .. 'myconfig.ini', conf)
----