项目作者: xmonader

项目描述 :
configparser for ini files written in Nim
高级语言: Nim
项目地址: git://github.com/xmonader/nim-configparser.git
创建时间: 2018-05-03T19:19:18Z
项目社区:https://github.com/xmonader/nim-configparser

开源协议:

下载


Nim configparser

this is a pure Ini parser for nim

Nim has advanced parsecfg

Example

  1. let sample1 = """
  2. [general]
  3. appname = configparser
  4. version = 0.1
  5. [author]
  6. name = xmonader
  7. email = notxmonader@gmail.com
  8. """
  9. var d = parseIni(sample1)
  10. # doAssert(d.sectionsCount() == 2)
  11. doAssert(d.getProperty("general", "appname") == "configparser")
  12. doAssert(d.getProperty("general","version") == "0.1")
  13. doAssert(d.getProperty("author","name") == "xmonader")
  14. doAssert(d.getProperty("author","email") == "notxmonader@gmail.com")
  15. d.setProperty("author", "email", "alsonotxmonader@gmail.com")
  16. doAssert(d.getProperty("author","email") == "alsonotxmonader@gmail.com")
  17. doAssert(d.hasSection("general") == true)
  18. doAssert(d.hasSection("author") == true)
  19. doAssert(d.hasProperty("author", "name") == true)
  20. d.deleteProperty("author", "name")
  21. doAssert(d.hasProperty("author", "name") == false)
  22. echo d.toIniString()
  23. let s = d.getSection("author")
  24. echo $s

How to

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

  1. [general]
  2. appname = configparser
  3. version = 0.1
  4. [author]
  5. name = xmonader
  6. 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

  1. import tables, strutils

We will use tables extensively

  1. type Section = ref object
  2. properties: Table[string, string]

Section type contains properties table represents key value pairs

  1. proc setProperty*(this: Section, name: string, value: string) =
  2. this.properties[name] = value

To set property in the underlying properties table

  1. proc newSection*() : Section =
  2. var s = Section()
  3. s.properties = initTable[string, string]()
  4. return s

To create new Section object

  1. proc `$`*(this: Section) : string =
  2. return "<Section" & $this.properties & " >"

Simple toString proc using $ operator

  1. type Ini = ref object
  2. sections: Table[string, Section]

Ini type represents the whole document and contains a table section from sectionName to Section object.

  1. proc newIni*() : Ini =
  2. var ini = Ini()
  3. ini.sections = initTable[string, Section]()
  4. return ini

To craete new Ini object

  1. proc `$`*(this: Ini) : string =
  2. return "<Ini " & $this.sections & " >"

define friendly toString proc using $ operator

Define API

  1. proc setSection*(this: Ini, name: string, section: Section) =
  2. this.sections[name] = section
  3. proc getSection*(this: Ini, name: string): Section =
  4. return this.sections.getOrDefault(name)
  5. proc hasSection*(this: Ini, name: string): bool =
  6. return this.sections.contains(name)
  7. proc deleteSection*(this: Ini, name:string) =
  8. this.sections.del(name)
  9. proc sectionsCount*(this: Ini) : int =
  10. echo $this.sections
  11. return len(this.sections)

Some helper procs around Ini objects for manipulating sections.

  1. proc hasProperty*(this: Ini, sectionName: string, key: string): bool=
  2. return this.sections.contains(sectionName) and this.sections[sectionName].properties.contains(key)
  3. proc setProperty*(this: Ini, sectionName: string, key: string, value:string) =
  4. echo $this.sections
  5. if this.sections.contains(sectionName):
  6. this.sections[sectionName].setProperty(key, value)
  7. else:
  8. raise newException(ValueError, "Ini doesn't have section " & sectionName)
  9. proc getProperty*(this: Ini, sectionName: string, key: string) : string =
  10. if this.sections.contains(sectionName):
  11. return this.sections[sectionName].properties.getOrDefault(key)
  12. else:
  13. raise newException(ValueError, "Ini doesn't have section " & sectionName)
  14. proc deleteProperty*(this: Ini, sectionName: string, key: string) =
  15. if this.sections.contains(sectionName) and this.sections[sectionName].properties.contains(key):
  16. this.sections[sectionName].properties.del(key)
  17. else:
  18. raise newException(ValueError, "Ini doesn't have section " & sectionName)

More helpers around properties in the section objects managed by Ini object

  1. proc toIniString*(this: Ini, sep:char='=') : string =
  2. var output = ""
  3. for sectName, section in this.sections:
  4. output &= "[" & sectName & "]" & "\n"
  5. for k, v in section.properties:
  6. output &= k & sep & v & "\n"
  7. output &= "\n"
  8. return output

Simple proc toIniString to convert the nim structures into Ini text string

Parse!

OK, here comes the cool part

Parser states

  1. type ParserState = enum
  2. 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

  1. proc parseIni*(s: string) : Ini =

Here we define a proc parseIni that takes a string s and creates an Ini object

  1. var ini = newIni()
  2. var state: ParserState = readSection
  3. let lines = s.splitLines
  4. var currentSectionName: string = ""
  5. var currentSection = newSection()
  • ini is the object to be returned after parsing
  • state the current parser state (weather it’s readSection or readKV)
  • lines input string splitted into lines as we are a lines based parser
  • currentSectionName to keep track of what section we are currently in
  • currentSection to populate ini.sections with Section object using setSection proc
  1. for line in lines:

for each line

  1. if line.strip() == "" or line.startsWith(";") or line.startsWith("#"):
  2. continue

We continue if line is safe to igore empty line or starts with ; or #

  1. if line.startsWith("[") and line.endsWith("]"):
  2. state = readSection

if line startswith [ and ends with ] then we set parser state to readSection

  1. if state == readSection:
  2. currentSectionName = line[1..<line.len-1]
  3. ini.setSection(currentSectionName, currentSection)
  4. state = readKV
  5. continue

if parser state is readSection

  • extract section name between [ and ]
  • add section object to the ini under the current section name
  • change state to readKV to read key value pairs
  • continue the loop on the nextline as we’re done processing the section name.
  1. if state == readKV:
  2. let parts = line.split({'='})
  3. if len(parts) == 2:
  4. let key = parts[0].strip()
  5. let val = parts[1].strip()
  6. ini.setProperty(currentSectionName, key, val)

if state is readKV

  • extract key and val by splitting the line on =
  • setProperty under the currentSectionName using key and val
    1. return ini
    Here we return the populated ini object.

Thanks

To you for taking the time to read the tutorial and thanks to everyone at #nim on gitter for their valuable reviews.
Feel free to send me PRs to make this tutorial/library better :)