Day 5: Creating INI Parser
this is a pure Ini parser for nim
Nim has advanced parsecfg
What to expect ?
let sample1 = """
[general]
appname = configparser
version = 0.1
[author]
name = xmonader
email = notxmonader@gmail.com
"""
var d = parseIni(sample1)
# doAssert(d.sectionsCount() == 2)
doAssert(d.getProperty("general", "appname") == "configparser")
doAssert(d.getProperty("general","version") == "0.1")
doAssert(d.getProperty("author","name") == "xmonader")
doAssert(d.getProperty("author","email") == "notxmonader@gmail.com")
d.setProperty("author", "email", "alsonotxmonader@gmail.com")
doAssert(d.getProperty("author","email") == "alsonotxmonader@gmail.com")
doAssert(d.hasSection("general") == true)
doAssert(d.hasSection("author") == true)
doAssert(d.hasProperty("author", "name") == true)
d.deleteProperty("author", "name")
doAssert(d.hasProperty("author", "name") == false)
echo d.toIniString()
let s = d.getSection("author")
echo $s
Implementation
You can certainly use regular expressions, like pythons configparser, but we will go for a simpler approach here, also we want to keep it pure so we don't depend on pcre
Ini sample
[general]
appname = configparser
version = 0.1
[author]
name = xmonader
email = notxmonader@gmail.com
Ini file consists of one or more sections and each section consists of one or more key value pairs separated by =
Define your data types
import tables, strutils
We will use tables extensively
type Section = ref object
properties: Table[string, string]
Section
type contains properties
table represents key value pairs
proc setProperty*(this: Section, name: string, value: string) =
this.properties[name] = value
To set property in the underlying properties
table
proc newSection*() : Section =
var s = Section()
s.properties = initTable[string, string]()
return s
To create new Section object
proc `$`*(this: Section) : string =
return "<Section" & $this.properties & " >"
Simple toString
proc using $
operator
type Ini = ref object
sections: Table[string, Section]
Ini
type represents the whole document and contains a table section
from sectionName
to Section
object.
proc newIni*() : Ini =
var ini = Ini()
ini.sections = initTable[string, Section]()
return ini
To create new Ini object
proc `$`*(this: Ini) : string =
return "<Ini " & $this.sections & " >"
define friendly toString
proc using $
operator
Define API
proc setSection*(this: Ini, name: string, section: Section) =
this.sections[name] = section
proc getSection*(this: Ini, name: string): Section =
return this.sections.getOrDefault(name)
proc hasSection*(this: Ini, name: string): bool =
return this.sections.contains(name)
proc deleteSection*(this: Ini, name:string) =
this.sections.del(name)
proc sectionsCount*(this: Ini) : int =
echo $this.sections
return len(this.sections)
Some helper procs around Ini objects for manipulating sections.
proc hasProperty*(this: Ini, sectionName: string, key: string): bool=
return this.sections.contains(sectionName) and this.sections[sectionName].properties.contains(key)
proc setProperty*(this: Ini, sectionName: string, key: string, value:string) =
echo $this.sections
if this.sections.contains(sectionName):
this.sections[sectionName].setProperty(key, value)
else:
raise newException(ValueError, "Ini doesn't have section " & sectionName)
proc getProperty*(this: Ini, sectionName: string, key: string) : string =
if this.sections.contains(sectionName):
return this.sections[sectionName].properties.getOrDefault(key)
else:
raise newException(ValueError, "Ini doesn't have section " & sectionName)
proc deleteProperty*(this: Ini, sectionName: string, key: string) =
if this.sections.contains(sectionName) and this.sections[sectionName].properties.contains(key):
this.sections[sectionName].properties.del(key)
else:
raise newException(ValueError, "Ini doesn't have section " & sectionName)
More helpers around properties in the section objects managed by Ini
object
proc toIniString*(this: Ini, sep:char='=') : string =
var output = ""
for sectName, section in this.sections:
output &= "[" & sectName & "]" & "\n"
for k, v in section.properties:
output &= k & sep & v & "\n"
output &= "\n"
return output
Simple proc toIniString
to convert the nim structures into Ini text string
Parse!
OK, here comes the cool part
Parser states
type ParserState = enum
readSection, readKV
Here we have two states
- readSection: when we are supposed to extract section name from the current line
- readKV: when we are supposed to read the line in key value pair mode
ParseIni proc
proc parseIni*(s: string) : Ini =
Here we define a proc parseIni
that takes a string s
and creates an Ini
object
var ini = newIni()
var state: ParserState = readSection
let lines = s.splitLines
var currentSectionName: string = ""
var currentSection = newSection()
ini
is the object to be returned after parsingstate
the current parser state (weather it'sreadSection
orreadKV
)lines
input string splitted into linesas we are a lines based parser
currentSectionName
to keep track of what section we are currently incurrentSection
to populateini.sections
withSection
object usingsetSection
proc
for line in lines:
for each line
if line.strip() == "" or line.startsWith(";") or line.startsWith("#"):
continue
We continue if line is safe to igore empty line
or starts with ;
or #
if line.startsWith("[") and line.endsWith("]"):
state = readSection
if line startswith [
and ends with ]
then we set parser state to readSection
if state == readSection:
currentSectionName = line[1..<line.len-1]
ini.setSection(currentSectionName, currentSection)
state = readKV
continue
if parser state
is readSection
- extract section name
between [ and ]
- add section object to the ini under the current section name
- change
state
toreadKV
to read key value pairs - continue the loop on the nextline as we're done processing the section name.
if state == readKV:
let parts = line.split({'='})
if len(parts) == 2:
let key = parts[0].strip()
let val = parts[1].strip()
ini.setProperty(currentSectionName, key, val)
if state
is readKV
- extract
key
andval
by splitting the line on=
setProperty
under thecurrentSectionName
usingkey
andval
return ini
Here we return the populated ini
object.