Not all applications need configuration files; many applications benefit from starting fresh each time they are launched. Simple utilities, for instance, rarely require preferences or settings that persist across uses. However, when you write a complex application, it's nice for users to be able to configure how 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. You do not want to 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 several popular formats for configuration files. Lua has libraries for most of the common configuration formats; in this article, I'll use the INI format.
Installing the library
The central hub for Lua libraries is 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:
$ sudo dnf install luarocks
On macOS, use MacPorts or Homebrew. On Windows, use 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:
$ 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 it with their application. This can create problems for users who don't have the library installed. 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:
$ 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 install your library into it. With this simple trick, you can install all the dependency code your project uses directly into the project directory.
Code setup
First, create some INI data in a file called myconfig.ini
:
[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 generate while using the application remains on their system. Users might remove unnecessary config files manually, but many don't. As a result, if they reinstall an application, it will retain all of their preferences.
Config file locations are technically unimportant, but each operating system (OS) has a specification or a tradition of where they ought to be placed. On Linux, this is defined by the Freedesktop specification. It dictates that configuration files are to be saved in a hidden folder named ~/.config
. For clarity during this exercise, 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 favorite 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:
$ 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
:
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 OS 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:
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:
-- 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:
$ lua ./main.lua
Tux
penguin
false
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.
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:
-- 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:
$ 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.
Here's the entire demo code for reference:
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)
Comments are closed.