parsecfg.nim 20 KB

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