inifile.lua

For LÖVE 0.10.2

inifile.lua by bartbes is a library for reading and saving INI files.

It can be found here (the version on this page is a little bit different).

About INI files

From Wikipedia...

The INI file format is an informal standard for configuration files for some platforms or software. INI files are simple text files with a basic structure composed of "sections" and "properties".

...

The name "INI file" comes from the filename extension usually used, ".INI", that stands for "initialization".

What inifile.lua can do

  • Read INI files from files into tables
  • Save INI files from tables to files
  • Interpret numbers and boolean values as actual numbers and booleans
  • Works in a LÖVE program as well as a plain Lua program

Using inifile.lua

inifile.lua creates a table with two functions, parse and save.

require 'inifile'
inifile.parse('example.ini')
inifile.save('example.ini', iniTable)

inifile.lua also returns the table it creates, so the user can choose their own name for it. For example...

woooo = require 'inifile'
woooo.parse('example.ini')
woooo.save('example.ini', iniTable)

When read by inifile.lua, INI files are saved in the format of

t[section][key] = value

For example, this INI file...

[square]
name=A square
x=321
y=123
fill=true

[background]
red=4
green=117
blue=140

... will result in a table equivalent to this:

{
  square = {
    name = 'A square',
    x = 321,
    y = 123,
    fill = true
 },
 background = {
    red = 4,
    green = 117,
    blue = 140
  }
}

Here is a usage example:

example = inifile.parse('example.ini')
print(example['square']['name']) --> A square

inifile.lua can also take tables in the format of the one above and save them as INI files.

Here is a usage example:

test = {}

test.woo = {}
test.woo.shape = 'rhombus'
test.woo.phrase = 'I\'m a rhombus!'

inifile.save('rhombus.ini', test)

The resulting file may look like:

[woo]
phrase=I'm a rhombus!
shape=rhombus

As keys in Lua are unordered, the order of the output is also unknown.

Okay so let's actually read inifile.lua!

inifile is the table which will hold the two functions.

inifile = {}

lines will store the function for reading through lines in a file, and write will store the function for writing a string to a file. These functions will be different depending on whether inifile.lua thinks it's in a LÖVE program or not.

These two variables are local, which means they won't be accessible outside of this file.

Note that variables can be declared to be local without giving them any value.

local lines
local write

In Lua, anything that is not nil or false is true in a conditional statement. In a LÖVE program, love is a table which holds all the LÖVE-related functions.

If love is true, then it is assumed that it is being used within LÖVE.

if love then

Variables can be set to functions, just like they can be set to numbers or strings.

love.filesystem.lines is an iterator which iterates over the lines in the file.

   lines = love.filesystem.lines

love.filesystem.write writes a string to a file.

   write = love.filesystem.write
else
   lines = function(name) return assert(io.open(name)):lines() end
   write = function(name, contents) return assert(io.open(name, "w")):write(contents) end
end

inifile.parse

inifile.parse works like this:

  • It takes one argument which is the filename of the file to read.
  • A table is created to store everything. (t = {})
  • The lines in the file are looped through.
  • If the line is a [section], then...
    • The section variable is set to its name, without brackets.
    • A new table is created with the key of the section name (t[section] = {}) if it doesn't already exist.
  • If the line is a line with a key and a value ("key = value"), then...
    • The key and value are saved as two separate variables (named key and value).
    • If the value is a number or a boolean value, it's set to the actual number or boolean value, rather than a string.
    • The value is stored in the section table with its key. (t[section][key] = value)
  • The table is returned.
function inifile.parse(name)

t is the table which will be returned at the end of this function.

   local t = {}

section will store the current section (that is [this part]) of the INI file.

   local section

The lines in the file are looped through using a generic for loop with the lines function that was previously defined. line is each line as a string.

If this is running in a LÖVE program, this line is equivalent to for line in love.filesystem.lines(name) do

   for line in lines(name) do

s will be the section name, or nil if this line isn't a line with a section name.

Strings in Lua have methods, which are equivalent to the functions in the string library. For example, line:match("^%[([^%]]+)%]$") is equivalent to string.match(line, "^%[([^%]]+)%]$")

The string.match function returns substrings by matching patterns.

How this pattern works

line:match("^%[([^%]]+)%]$")

Starts with [ (% is the escape character.)
(Start capture)
A set, which doesn't include ]
One or more of that (set which doesn't include ])
(End of capture)
The character ]
Ends with that (the character ])

Things that are used in this pattern

()Captures what is within parentheses
%]The character "]" (% is the escape character.)
[]A set
[^x]A set not including x
x+1 or more repetitions of x
^When at the beginning of a pattern, it will match only at the beginning of the subject string.
$When at the end of a pattern, it will match only at the end of the subject string.
      local s = line:match("^%[([^%]]+)%]$")

If the last line didn't return nil and this is the start of a section...

      if s then

The value of s is saved in the variable section, so it can be retained for lines which aren't section titles (when s will be nil).

         section = s

If there is already a section with the same name (that is, t[section] exists; this could happen if an INI file has two sections with the same name), nothing is done (t[section] = t[section]). However if t[section] is nil, then it set to a new table.

Lua's disjunction operator or returns its first argument if it's not nil and false, otherwise it returns its second argument.

         t[section] = t[section] or {}
      end

If this is a line which has a key and a value, the variables key and value are set to them.

line:match("^(%w+)%s-=%s-(.+)$")

How this pattern works

Starts with:
(Start capture)
Alphanumeric characters, one or more of them
(End of capture)
Space characters, zero or more of them
The character =
Space characters, zero or more of them
(Start capture)
All characters, one or more of them
(End of capture)
Ends with that (one or more of all characters)

Things that are used in this pattern

()Captures what is within parentheses
.All characters
%wAlphanumeric characters (letters and numbers)
%sSpace characters
x+1 or more repetitions of x
x-0 or more repetitions of x, with the shortest possible chain.
^When at the beginning of a pattern, it will match only at the beginning of the subject string.
$When at the end of a pattern, it will match only at the end of the subject string.
      local key, value = line:match("^(%w+)%s-=%s-(.+)$")

Checks if the last line returned two values (i.e. neither is nil).

      if key and value then

If value is a number, it is set to its number equivalent, rather than a string.

tonumber will return the number equivalent of the string it is given, or nil if it has no number equivalent (for example, tonumber("123") will return the number 123, while tonumber("Hey, this isn't a number!") will return nil.)

         if tonumber(value) then value = tonumber(value) end

If value has the name of a boolean value, value is set to the actual boolean value instead of a string.

         if value == "true" then value = true end
         if value == "false" then value = false end

The value is inserted into the section table with its key.

         t[section][key] = value
      end
   end

The filled table is returned.

   return t
end

inifile.save

inifile.save takes two arguments:

  • name is the name of the file to write to.
  • t is the table to write from.

inifile.save works like this:

  • A blank string for the output is created.
  • The table is looped through.
  • Each section title is surrounded by brackets and is added to the output string, along with a new line.
  • The section itself is looped through.
  • The keys and values of this section are added to the string in the format "key=value", with a new line at the end.
  • A new line is added after every section.
  • The output is written to a file with the filename of the argument name.
function inifile.save(name, t)

contents will hold the string which will be written to the file.

   local contents = ""

t is looped through using pairs, with section being the name of each section and s being the table of keys and values contained in that section.

   for section, s in pairs(t) do

The name of the section [surrounded by brackets] is added to contents.

String methods can be used on literal strings if the strings are within parentheses. In this case, ("[%s]\n"):format(section) is equivalent to string.format("[%s]\n", section).

string.format can take a specially formatted string and fills in the values with variables it's given.

For example, instead of writing
"The date is "..year.."/"..month.."/"..day
you could write
string.format("The date is %d/%d/%d", year, month, day)
or alternatively using the string method
("The date is %d/%d/%d"):format(year, month, day).

For more information on the symbols string.format can use, see this.

\n is the escape sequence for new lines.

      contents = contents .. ("[%s]\n"):format(section)

s is looped through using pairs, with key and value being the keys and values of this section.

      for key, value in pairs(s) do

If key was the string test and value was 123, then this line would append test=123 to contents, with a new line at the end.
Lua's tostring function can convert booleans and numbers to strings (e.g. true becomes "true", 123 becomes "123").

         contents = contents .. ("%s=%s\n"):format(key, tostring(value))
      end

A new line is added after every section.

      contents = contents .. "\n"
   end

The file is saved to the save directory with the filename of the argument name and the string of contents.

   write(name, contents)
end

The table is returned so the user can choose their own name for it.

return inifile



HTML generated by Locco.

inifile.lua is copyright Bart van Strien, released under the Simplified BSD License.

Copyright is waived on all original parts of this code walkthrough.