parsecfg.nim 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660
  1. #
  2. #
  3. # Nim's Runtime Library
  4. # (c) Copyright 2010 Andreas Rumpf
  5. #
  6. # See the file "copying.txt", included in this
  7. # distribution, for details about the copyright.
  8. #
  9. ## The `parsecfg` module implements a high performance configuration file
  10. ## parser. The configuration file's syntax is similar to the Windows `.ini`
  11. ## format, but much more powerful, as it is not a line based parser. String
  12. ## literals, raw string literals and triple quoted string literals are supported
  13. ## as in the Nim programming language.
  14. ##
  15. ## Example of how a configuration file may look like:
  16. ##
  17. ## .. include:: ../../doc/mytest.cfg
  18. ## :literal:
  19. ##
  20. ## Here is an example of how to use the configuration file parser:
  21. runnableExamples("-r:off"):
  22. import std/[strutils, streams]
  23. let configFile = "example.ini"
  24. var f = newFileStream(configFile, fmRead)
  25. assert f != nil, "cannot open " & configFile
  26. var p: CfgParser
  27. open(p, f, configFile)
  28. while true:
  29. var e = next(p)
  30. case e.kind
  31. of cfgEof: break
  32. of cfgSectionStart: ## a `[section]` has been parsed
  33. echo "new section: " & e.section
  34. of cfgKeyValuePair:
  35. echo "key-value-pair: " & e.key & ": " & e.value
  36. of cfgOption:
  37. echo "command: " & e.key & ": " & e.value
  38. of cfgError:
  39. echo e.msg
  40. close(p)
  41. ##[
  42. ## Configuration file example
  43. ]##
  44. ## ```none
  45. ## charset = "utf-8"
  46. ## [Package]
  47. ## name = "hello"
  48. ## --threads:on
  49. ## [Author]
  50. ## name = "nim-lang"
  51. ## website = "nim-lang.org"
  52. ## ```
  53. ##[
  54. ## Creating a configuration file
  55. ]##
  56. runnableExamples:
  57. var dict = newConfig()
  58. dict.setSectionKey("","charset", "utf-8")
  59. dict.setSectionKey("Package", "name", "hello")
  60. dict.setSectionKey("Package", "--threads", "on")
  61. dict.setSectionKey("Author", "name", "nim-lang")
  62. dict.setSectionKey("Author", "website", "nim-lang.org")
  63. assert $dict == """
  64. charset=utf-8
  65. [Package]
  66. name=hello
  67. --threads:on
  68. [Author]
  69. name=nim-lang
  70. website=nim-lang.org
  71. """
  72. ##[
  73. ## Reading a configuration file
  74. ]##
  75. runnableExamples("-r:off"):
  76. let dict = loadConfig("config.ini")
  77. let charset = dict.getSectionValue("","charset")
  78. let threads = dict.getSectionValue("Package","--threads")
  79. let pname = dict.getSectionValue("Package","name")
  80. let name = dict.getSectionValue("Author","name")
  81. let website = dict.getSectionValue("Author","website")
  82. echo pname & "\n" & name & "\n" & website
  83. ##[
  84. ## Modifying a configuration file
  85. ]##
  86. runnableExamples("-r:off"):
  87. var dict = loadConfig("config.ini")
  88. dict.setSectionKey("Author", "name", "nim-lang")
  89. dict.writeConfig("config.ini")
  90. ##[
  91. ## Deleting a section key in a configuration file
  92. ]##
  93. runnableExamples("-r:off"):
  94. var dict = loadConfig("config.ini")
  95. dict.delSectionKey("Author", "website")
  96. dict.writeConfig("config.ini")
  97. ##[
  98. ## Supported INI File structure
  99. ]##
  100. # taken from https://docs.python.org/3/library/configparser.html#supported-ini-file-structure
  101. runnableExamples:
  102. import std/streams
  103. var dict = loadConfig(newStringStream("""[Simple Values]
  104. key=value
  105. spaces in keys=allowed
  106. spaces in values=allowed as well
  107. spaces around the delimiter = obviously
  108. you can also use : to delimit keys from values
  109. [All Values Are Strings]
  110. values like this: 19990429
  111. or this: 3.14159265359
  112. are they treated as numbers : no
  113. integers floats and booleans are held as: strings
  114. can use the API to get converted values directly: true
  115. [No Values]
  116. key_without_value
  117. # empty string value is not allowed =
  118. [ Seletion A ]
  119. space around section name will be ignored
  120. [You can use comments]
  121. # like this
  122. ; or this
  123. # By default only in an empty line.
  124. # Inline comments can be harmful because they prevent users
  125. # from using the delimiting characters as parts of values.
  126. # That being said, this can be customized.
  127. [Sections Can Be Indented]
  128. can_values_be_as_well = True
  129. does_that_mean_anything_special = False
  130. purpose = formatting for readability
  131. # Did I mention we can indent comments, too?
  132. """)
  133. )
  134. let section1 = "Simple Values"
  135. assert dict.getSectionValue(section1, "key") == "value"
  136. assert dict.getSectionValue(section1, "spaces in keys") == "allowed"
  137. assert dict.getSectionValue(section1, "spaces in values") == "allowed as well"
  138. assert dict.getSectionValue(section1, "spaces around the delimiter") == "obviously"
  139. assert dict.getSectionValue(section1, "you can also use") == "to delimit keys from values"
  140. let section2 = "All Values Are Strings"
  141. assert dict.getSectionValue(section2, "values like this") == "19990429"
  142. assert dict.getSectionValue(section2, "or this") == "3.14159265359"
  143. assert dict.getSectionValue(section2, "are they treated as numbers") == "no"
  144. assert dict.getSectionValue(section2, "integers floats and booleans are held as") == "strings"
  145. assert dict.getSectionValue(section2, "can use the API to get converted values directly") == "true"
  146. let section3 = "Seletion A"
  147. assert dict.getSectionValue(section3,
  148. "space around section name will be ignored", "not an empty value") == ""
  149. let section4 = "Sections Can Be Indented"
  150. assert dict.getSectionValue(section4, "can_values_be_as_well") == "True"
  151. assert dict.getSectionValue(section4, "does_that_mean_anything_special") == "False"
  152. assert dict.getSectionValue(section4, "purpose") == "formatting for readability"
  153. import strutils, lexbase, streams, tables
  154. import std/private/decode_helpers
  155. import std/private/since
  156. when defined(nimPreviewSlimSystem):
  157. import std/syncio
  158. include "system/inclrtl"
  159. type
  160. CfgEventKind* = enum ## enumeration of all events that may occur when parsing
  161. cfgEof, ## end of file reached
  162. cfgSectionStart, ## a `[section]` has been parsed
  163. cfgKeyValuePair, ## a `key=value` pair has been detected
  164. cfgOption, ## a `--key=value` command line option
  165. cfgError ## an error occurred during parsing
  166. CfgEvent* = object of RootObj ## describes a parsing event
  167. case kind*: CfgEventKind ## the kind of the event
  168. of cfgEof: nil
  169. of cfgSectionStart:
  170. section*: string ## `section` contains the name of the
  171. ## parsed section start (syntax: `[section]`)
  172. of cfgKeyValuePair, cfgOption:
  173. key*, value*: string ## contains the (key, value) pair if an option
  174. ## of the form `--key: value` or an ordinary
  175. ## `key= value` pair has been parsed.
  176. ## `value==""` if it was not specified in the
  177. ## configuration file.
  178. of cfgError: ## the parser encountered an error: `msg`
  179. msg*: string ## contains the error message. No exceptions
  180. ## are thrown if a parse error occurs.
  181. TokKind = enum
  182. tkInvalid, tkEof,
  183. tkSymbol, tkEquals, tkColon, tkBracketLe, tkBracketRi, tkDashDash
  184. Token = object # a token
  185. kind: TokKind # the type of the token
  186. literal: string # the parsed (string) literal
  187. CfgParser* = object of BaseLexer ## the parser object.
  188. tok: Token
  189. filename: string
  190. # implementation
  191. const
  192. SymChars = {'a'..'z', 'A'..'Z', '0'..'9', '_', ' ', '\x80'..'\xFF', '.', '/', '\\', '-'}
  193. proc rawGetTok(c: var CfgParser, tok: var Token) {.gcsafe.}
  194. proc open*(c: var CfgParser, input: Stream, filename: string,
  195. lineOffset = 0) {.rtl, extern: "npc$1".} =
  196. ## Initializes the parser with an input stream. `Filename` is only used
  197. ## for nice error messages. `lineOffset` can be used to influence the line
  198. ## number information in the generated error messages.
  199. lexbase.open(c, input)
  200. c.filename = filename
  201. c.tok.kind = tkInvalid
  202. c.tok.literal = ""
  203. inc(c.lineNumber, lineOffset)
  204. rawGetTok(c, c.tok)
  205. proc close*(c: var CfgParser) {.rtl, extern: "npc$1".} =
  206. ## Closes the parser `c` and its associated input stream.
  207. lexbase.close(c)
  208. proc getColumn*(c: CfgParser): int {.rtl, extern: "npc$1".} =
  209. ## Gets the current column the parser has arrived at.
  210. result = getColNumber(c, c.bufpos)
  211. proc getLine*(c: CfgParser): int {.rtl, extern: "npc$1".} =
  212. ## Gets the current line the parser has arrived at.
  213. result = c.lineNumber
  214. proc getFilename*(c: CfgParser): string {.rtl, extern: "npc$1".} =
  215. ## Gets the filename of the file that the parser processes.
  216. result = c.filename
  217. proc handleDecChars(c: var CfgParser, xi: var int) =
  218. while c.buf[c.bufpos] in {'0'..'9'}:
  219. xi = (xi * 10) + (ord(c.buf[c.bufpos]) - ord('0'))
  220. inc(c.bufpos)
  221. proc getEscapedChar(c: var CfgParser, tok: var Token) =
  222. inc(c.bufpos) # skip '\'
  223. case c.buf[c.bufpos]
  224. of 'n', 'N':
  225. add(tok.literal, "\n")
  226. inc(c.bufpos)
  227. of 'r', 'R', 'c', 'C':
  228. add(tok.literal, '\c')
  229. inc(c.bufpos)
  230. of 'l', 'L':
  231. add(tok.literal, '\L')
  232. inc(c.bufpos)
  233. of 'f', 'F':
  234. add(tok.literal, '\f')
  235. inc(c.bufpos)
  236. of 'e', 'E':
  237. add(tok.literal, '\e')
  238. inc(c.bufpos)
  239. of 'a', 'A':
  240. add(tok.literal, '\a')
  241. inc(c.bufpos)
  242. of 'b', 'B':
  243. add(tok.literal, '\b')
  244. inc(c.bufpos)
  245. of 'v', 'V':
  246. add(tok.literal, '\v')
  247. inc(c.bufpos)
  248. of 't', 'T':
  249. add(tok.literal, '\t')
  250. inc(c.bufpos)
  251. of '\'', '"':
  252. add(tok.literal, c.buf[c.bufpos])
  253. inc(c.bufpos)
  254. of '\\':
  255. add(tok.literal, '\\')
  256. inc(c.bufpos)
  257. of 'x', 'X':
  258. inc(c.bufpos)
  259. var xi = 0
  260. if handleHexChar(c.buf[c.bufpos], xi):
  261. inc(c.bufpos)
  262. if handleHexChar(c.buf[c.bufpos], xi):
  263. inc(c.bufpos)
  264. add(tok.literal, chr(xi))
  265. of '0'..'9':
  266. var xi = 0
  267. handleDecChars(c, xi)
  268. if (xi <= 255): add(tok.literal, chr(xi))
  269. else: tok.kind = tkInvalid
  270. else: tok.kind = tkInvalid
  271. proc handleCRLF(c: var CfgParser, pos: int): int =
  272. case c.buf[pos]
  273. of '\c': result = lexbase.handleCR(c, pos)
  274. of '\L': result = lexbase.handleLF(c, pos)
  275. else: result = pos
  276. proc getString(c: var CfgParser, tok: var Token, rawMode: bool) =
  277. var pos = c.bufpos + 1 # skip "
  278. tok.kind = tkSymbol
  279. if (c.buf[pos] == '"') and (c.buf[pos + 1] == '"'):
  280. # long string literal:
  281. inc(pos, 2) # skip ""
  282. # skip leading newline:
  283. pos = handleCRLF(c, pos)
  284. while true:
  285. case c.buf[pos]
  286. of '"':
  287. if (c.buf[pos + 1] == '"') and (c.buf[pos + 2] == '"'): break
  288. add(tok.literal, '"')
  289. inc(pos)
  290. of '\c', '\L':
  291. pos = handleCRLF(c, pos)
  292. add(tok.literal, "\n")
  293. of lexbase.EndOfFile:
  294. tok.kind = tkInvalid
  295. break
  296. else:
  297. add(tok.literal, c.buf[pos])
  298. inc(pos)
  299. c.bufpos = pos + 3 # skip the three """
  300. else:
  301. # ordinary string literal
  302. while true:
  303. var ch = c.buf[pos]
  304. if ch == '"':
  305. inc(pos) # skip '"'
  306. break
  307. if ch in {'\c', '\L', lexbase.EndOfFile}:
  308. tok.kind = tkInvalid
  309. break
  310. if (ch == '\\') and not rawMode:
  311. c.bufpos = pos
  312. getEscapedChar(c, tok)
  313. pos = c.bufpos
  314. else:
  315. add(tok.literal, ch)
  316. inc(pos)
  317. c.bufpos = pos
  318. proc getSymbol(c: var CfgParser, tok: var Token) =
  319. var pos = c.bufpos
  320. while true:
  321. add(tok.literal, c.buf[pos])
  322. inc(pos)
  323. if not (c.buf[pos] in SymChars): break
  324. while tok.literal.len > 0 and tok.literal[^1] == ' ':
  325. tok.literal.setLen(tok.literal.len - 1)
  326. c.bufpos = pos
  327. tok.kind = tkSymbol
  328. proc skip(c: var CfgParser) =
  329. var pos = c.bufpos
  330. while true:
  331. case c.buf[pos]
  332. of ' ', '\t':
  333. inc(pos)
  334. of '#', ';':
  335. while not (c.buf[pos] in {'\c', '\L', lexbase.EndOfFile}): inc(pos)
  336. of '\c', '\L':
  337. pos = handleCRLF(c, pos)
  338. else:
  339. break # EndOfFile also leaves the loop
  340. c.bufpos = pos
  341. proc rawGetTok(c: var CfgParser, tok: var Token) =
  342. tok.kind = tkInvalid
  343. setLen(tok.literal, 0)
  344. skip(c)
  345. case c.buf[c.bufpos]
  346. of '=':
  347. tok.kind = tkEquals
  348. inc(c.bufpos)
  349. tok.literal = "="
  350. of '-':
  351. inc(c.bufpos)
  352. if c.buf[c.bufpos] == '-':
  353. inc(c.bufpos)
  354. tok.kind = tkDashDash
  355. tok.literal = "--"
  356. else:
  357. dec(c.bufpos)
  358. getSymbol(c, tok)
  359. of ':':
  360. tok.kind = tkColon
  361. inc(c.bufpos)
  362. tok.literal = ":"
  363. of 'r', 'R':
  364. if c.buf[c.bufpos + 1] == '\"':
  365. inc(c.bufpos)
  366. getString(c, tok, true)
  367. else:
  368. getSymbol(c, tok)
  369. of '[':
  370. tok.kind = tkBracketLe
  371. inc(c.bufpos)
  372. tok.literal = "["
  373. of ']':
  374. tok.kind = tkBracketRi
  375. inc(c.bufpos)
  376. tok.literal = "]"
  377. of '"':
  378. getString(c, tok, false)
  379. of lexbase.EndOfFile:
  380. tok.kind = tkEof
  381. tok.literal = "[EOF]"
  382. else: getSymbol(c, tok)
  383. proc errorStr*(c: CfgParser, msg: string): string {.rtl, extern: "npc$1".} =
  384. ## Returns a properly formatted error message containing current line and
  385. ## column information.
  386. result = `%`("$1($2, $3) Error: $4",
  387. [c.filename, $getLine(c), $getColumn(c), msg])
  388. proc warningStr*(c: CfgParser, msg: string): string {.rtl, extern: "npc$1".} =
  389. ## Returns a properly formatted warning message containing current line and
  390. ## column information.
  391. result = `%`("$1($2, $3) Warning: $4",
  392. [c.filename, $getLine(c), $getColumn(c), msg])
  393. proc ignoreMsg*(c: CfgParser, e: CfgEvent): string {.rtl, extern: "npc$1".} =
  394. ## Returns a properly formatted warning message containing that
  395. ## an entry is ignored.
  396. case e.kind
  397. of cfgSectionStart: result = c.warningStr("section ignored: " & e.section)
  398. of cfgKeyValuePair: result = c.warningStr("key ignored: " & e.key)
  399. of cfgOption:
  400. result = c.warningStr("command ignored: " & e.key & ": " & e.value)
  401. of cfgError: result = e.msg
  402. of cfgEof: result = ""
  403. proc getKeyValPair(c: var CfgParser, kind: CfgEventKind): CfgEvent =
  404. if c.tok.kind == tkSymbol:
  405. case kind
  406. of cfgOption, cfgKeyValuePair:
  407. result = CfgEvent(kind: kind, key: c.tok.literal, value: "")
  408. else: discard
  409. rawGetTok(c, c.tok)
  410. if c.tok.kind in {tkEquals, tkColon}:
  411. rawGetTok(c, c.tok)
  412. if c.tok.kind == tkSymbol:
  413. result.value = c.tok.literal
  414. else:
  415. result = CfgEvent(kind: cfgError,
  416. msg: errorStr(c, "symbol expected, but found: " & c.tok.literal))
  417. rawGetTok(c, c.tok)
  418. else:
  419. result = CfgEvent(kind: cfgError,
  420. msg: errorStr(c, "symbol expected, but found: " & c.tok.literal))
  421. rawGetTok(c, c.tok)
  422. proc next*(c: var CfgParser): CfgEvent {.rtl, extern: "npc$1".} =
  423. ## Retrieves the first/next event. This controls the parser.
  424. case c.tok.kind
  425. of tkEof:
  426. result = CfgEvent(kind: cfgEof)
  427. of tkDashDash:
  428. rawGetTok(c, c.tok)
  429. result = getKeyValPair(c, cfgOption)
  430. of tkSymbol:
  431. result = getKeyValPair(c, cfgKeyValuePair)
  432. of tkBracketLe:
  433. rawGetTok(c, c.tok)
  434. if c.tok.kind == tkSymbol:
  435. result = CfgEvent(kind: cfgSectionStart, section: c.tok.literal)
  436. else:
  437. result = CfgEvent(kind: cfgError,
  438. msg: errorStr(c, "symbol expected, but found: " & c.tok.literal))
  439. rawGetTok(c, c.tok)
  440. if c.tok.kind == tkBracketRi:
  441. rawGetTok(c, c.tok)
  442. else:
  443. result = CfgEvent(kind: cfgError,
  444. msg: errorStr(c, "']' expected, but found: " & c.tok.literal))
  445. of tkInvalid, tkEquals, tkColon, tkBracketRi:
  446. result = CfgEvent(kind: cfgError,
  447. msg: errorStr(c, "invalid token: " & c.tok.literal))
  448. rawGetTok(c, c.tok)
  449. # ---------------- Configuration file related operations ----------------
  450. type
  451. Config* = OrderedTableRef[string, OrderedTableRef[string, string]]
  452. proc newConfig*(): Config =
  453. ## Creates a new configuration table.
  454. ## Useful when wanting to create a configuration file.
  455. result = newOrderedTable[string, OrderedTableRef[string, string]]()
  456. proc loadConfig*(stream: Stream, filename: string = "[stream]"): Config =
  457. ## Loads the specified configuration from stream into a new Config instance.
  458. ## `filename` parameter is only used for nicer error messages.
  459. var dict = newOrderedTable[string, OrderedTableRef[string, string]]()
  460. var curSection = "" ## Current section,
  461. ## the default value of the current section is "",
  462. ## which means that the current section is a common
  463. var p: CfgParser
  464. open(p, stream, filename)
  465. while true:
  466. var e = next(p)
  467. case e.kind
  468. of cfgEof:
  469. break
  470. of cfgSectionStart: # Only look for the first time the Section
  471. curSection = e.section
  472. of cfgKeyValuePair:
  473. var t = newOrderedTable[string, string]()
  474. if dict.hasKey(curSection):
  475. t = dict[curSection]
  476. t[e.key] = e.value
  477. dict[curSection] = t
  478. of cfgOption:
  479. var c = newOrderedTable[string, string]()
  480. if dict.hasKey(curSection):
  481. c = dict[curSection]
  482. c["--" & e.key] = e.value
  483. dict[curSection] = c
  484. of cfgError:
  485. break
  486. close(p)
  487. result = dict
  488. proc loadConfig*(filename: string): Config =
  489. ## Loads the specified configuration file into a new Config instance.
  490. let file = open(filename, fmRead)
  491. let fileStream = newFileStream(file)
  492. defer: fileStream.close()
  493. result = fileStream.loadConfig(filename)
  494. proc replace(s: string): string =
  495. var d = ""
  496. var i = 0
  497. while i < s.len():
  498. if s[i] == '\\':
  499. d.add(r"\\")
  500. elif s[i] == '\c' and s[i+1] == '\l':
  501. d.add(r"\c\l")
  502. inc(i)
  503. elif s[i] == '\c':
  504. d.add(r"\n")
  505. elif s[i] == '\l':
  506. d.add(r"\n")
  507. else:
  508. d.add(s[i])
  509. inc(i)
  510. result = d
  511. proc writeConfig*(dict: Config, stream: Stream) =
  512. ## Writes the contents of the table to the specified stream.
  513. ##
  514. ## .. note:: Comment statement will be ignored.
  515. for section, sectionData in dict.pairs():
  516. if section != "": ## Not general section
  517. if not allCharsInSet(section, SymChars): ## Non system character
  518. stream.writeLine("[\"" & section & "\"]")
  519. else:
  520. stream.writeLine("[" & section & "]")
  521. for key, value in sectionData.pairs():
  522. var kv, segmentChar: string
  523. if key.len > 1 and key[0] == '-' and key[1] == '-': ## If it is a command key
  524. segmentChar = ":"
  525. if not allCharsInSet(key[2..key.len()-1], SymChars):
  526. kv.add("--\"")
  527. kv.add(key[2..key.len()-1])
  528. kv.add("\"")
  529. else:
  530. kv = key
  531. else:
  532. segmentChar = "="
  533. kv = key
  534. if value != "": ## If the key is not empty
  535. if not allCharsInSet(value, SymChars):
  536. if find(value, '"') == -1:
  537. kv.add(segmentChar)
  538. kv.add("\"")
  539. kv.add(replace(value))
  540. kv.add("\"")
  541. else:
  542. kv.add(segmentChar)
  543. kv.add("\"\"\"")
  544. kv.add(replace(value))
  545. kv.add("\"\"\"")
  546. else:
  547. kv.add(segmentChar)
  548. kv.add(value)
  549. stream.writeLine(kv)
  550. proc `$`*(dict: Config): string =
  551. ## Writes the contents of the table to string.
  552. ##
  553. ## .. note:: Comment statement will be ignored.
  554. let stream = newStringStream()
  555. defer: stream.close()
  556. dict.writeConfig(stream)
  557. result = stream.data
  558. proc writeConfig*(dict: Config, filename: string) =
  559. ## Writes the contents of the table to the specified configuration file.
  560. ##
  561. ## .. note:: Comment statement will be ignored.
  562. let file = open(filename, fmWrite)
  563. defer: file.close()
  564. let fileStream = newFileStream(file)
  565. dict.writeConfig(fileStream)
  566. proc getSectionValue*(dict: Config, section, key: string, defaultVal = ""): string =
  567. ## Gets the key value of the specified Section.
  568. ## Returns the specified default value if the specified key does not exist.
  569. if dict.hasKey(section):
  570. if dict[section].hasKey(key):
  571. result = dict[section][key]
  572. else:
  573. result = defaultVal
  574. else:
  575. result = defaultVal
  576. proc setSectionKey*(dict: var Config, section, key, value: string) =
  577. ## Sets the Key value of the specified Section.
  578. var t = newOrderedTable[string, string]()
  579. if dict.hasKey(section):
  580. t = dict[section]
  581. t[key] = value
  582. dict[section] = t
  583. proc delSection*(dict: var Config, section: string) =
  584. ## Deletes the specified section and all of its sub keys.
  585. dict.del(section)
  586. proc delSectionKey*(dict: var Config, section, key: string) =
  587. ## Deletes the key of the specified section.
  588. if dict.hasKey(section):
  589. if dict[section].hasKey(key):
  590. if dict[section].len == 1:
  591. dict.del(section)
  592. else:
  593. dict[section].del(key)
  594. iterator sections*(dict: Config): lent string {.since: (1, 5).} =
  595. ## Iterates through the sections in the `dict`.
  596. for section in dict.keys:
  597. yield section