ini.bmx 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567
  1. ' Copyright (c) 2022-2023 Bruce A Henderson
  2. '
  3. ' Permission is hereby granted, free of charge, to any person obtaining a copy
  4. ' of this software and associated documentation files (the "Software"), to deal
  5. ' in the Software without restriction, including without limitation the rights
  6. ' to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  7. ' copies of the Software, and to permit persons to whom the Software is
  8. ' furnished to do so, subject to the following conditions:
  9. '
  10. ' The above copyright notice and this permission notice shall be included in
  11. ' all copies or substantial portions of the Software.
  12. '
  13. ' THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  14. ' IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  15. ' FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  16. ' AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  17. ' LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  18. ' OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  19. ' THE SOFTWARE.
  20. '
  21. SuperStrict
  22. Rem
  23. bbdoc: An INI reader/writer.
  24. End Rem
  25. Module Text.Ini
  26. ModuleInfo "Version: 1.02"
  27. ModuleInfo "Author: Bruce A Henderson"
  28. ModuleInfo "License: MIT"
  29. ModuleInfo "ini.h - Copyright (c) 2015 Mattias Gustavsson"
  30. ModuleInfo "Copyright: 2022-2023 Bruce A Henderson"
  31. ModuleInfo "History: 1.02"
  32. ModuleInfo "History: Fixed SetName update the name copy"
  33. ModuleInfo "History: Added some helper methods"
  34. ModuleInfo "History: Added tests"
  35. ModuleInfo "History: 1.01"
  36. ModuleInfo "History: Fixed missing terminator"
  37. ModuleInfo "History: 1.00"
  38. ModuleInfo "History: Initial Release"
  39. Import "common.bmx"
  40. Rem
  41. bbdoc: Represents the contents of an ini file.
  42. about: The global section index is defined by #INI_GLOBAL_SECTION.
  43. You should call #Free() when you are finished working with it, to allow it to clean up any resources it is using.
  44. End Rem
  45. Type TIni
  46. Private
  47. Field iniPtr:Byte Ptr
  48. Field sections:TIniSection[]
  49. Public
  50. Rem
  51. bbdoc: Creates a new empty #TIni instance.
  52. End Rem
  53. Method New()
  54. iniPtr = ini_create(Null)
  55. InitSections()
  56. End Method
  57. Private
  58. Method New(data:Byte[])
  59. iniPtr = ini_load(data, Null)
  60. InitSections()
  61. End Method
  62. Method InitSections()
  63. Local count:Int = ini_section_count(iniPtr)
  64. sections = New TIniSection[count]
  65. For Local i:Int = 0 Until count
  66. sections[i] = New TIniSection(Self, i)
  67. Next
  68. End Method
  69. Public
  70. Rem
  71. bbdoc: Loads an ini file at the given @path.
  72. returns: The loaded ini file, or #Null if the file could not be loaded.
  73. End Rem
  74. Function Load:TIni(path:String)
  75. Local stream:TStream = ReadStream(path)
  76. If stream Then
  77. Local ini:TIni = Load(stream)
  78. stream.Close()
  79. Return ini
  80. End If
  81. End Function
  82. Rem
  83. bbdoc: Loads an ini file from the given @stream.
  84. returns: The loaded ini file from the stream, or #Null if the file could not be loaded.
  85. about: Does not close the #TStream.
  86. End Rem
  87. Function Load:TIni(stream:TStream)
  88. Local data:Byte[]
  89. If stream Then
  90. data = New Byte[1024]
  91. Local size:Int
  92. While Not stream.Eof()
  93. If size=data.length data=data[..size*3/2]
  94. size:+stream.Read( (Byte Ptr data)+size,data.length-size )
  95. Wend
  96. data = data[..size + 1]
  97. data[size] = 0
  98. End If
  99. If data Then
  100. Local ini:TIni = New TIni(data)
  101. Return ini
  102. End If
  103. End Function
  104. Rem
  105. bbdoc: Saves the ini file to the specified @path.
  106. End Rem
  107. Method Save(path:String)
  108. Local stream:TStream = WriteStream(path)
  109. If stream Then
  110. Save(stream)
  111. stream.Close()
  112. End If
  113. End Method
  114. Rem
  115. bbdoc: Saves the ini file to the specified @stream.
  116. about: Does not close the #TStream.
  117. End Rem
  118. Method Save(stream:TStream)
  119. Local size:Int = ini_save(iniPtr, Null, 0)
  120. Local data:Byte[size]
  121. size = ini_save(iniPtr, data, size)
  122. If size > 1 Then
  123. stream.WriteBytes(data, size - 1)
  124. End If
  125. End Method
  126. Rem
  127. bbdoc: Returns the number of sections.
  128. about: There's at least one section in an ini file (the global section), but there can be many more,
  129. each specified in the file by the section name wrapped in square brackets `[ ]`
  130. End Rem
  131. Method CountSections:Int()
  132. Return ini_section_count(iniPtr)
  133. End Method
  134. Rem
  135. bbdoc: Returns the section at the given @index, or #Null if the index is out of bounds.
  136. about: #INI_GLOBAL_SECTION can be used to get the global section.
  137. End Rem
  138. Method GetSection:TIniSection(index:Int)
  139. If index < 0 Or index >= ini_section_count(iniPtr) Then
  140. Return Null
  141. End If
  142. Return sections[index]
  143. End Method
  144. Rem
  145. bbdoc: Finds a section by @name.
  146. returns: The named section, or #Null if not found.
  147. End Rem
  148. Method FindSection:TIniSection(name:String)
  149. name = name.ToUpper()
  150. For Local section:TIniSection = Eachin sections
  151. If Not section.upperName Then
  152. section.upperName = section.name.ToUpper()
  153. End If
  154. If name = section.upperName Then
  155. Return section
  156. End If
  157. Next
  158. End Method
  159. Rem
  160. bbdoc: Adds a section with the specified @name.
  161. about: There is no check done to see if a section with the specified name already exists - multiple sections of the same name are allowed.
  162. End Rem
  163. Method AddSection:TIniSection(name:String)
  164. Local index:Int = bmx_ini_section_add(iniPtr, name)
  165. If index = INI_NOT_FOUND Then
  166. Return Null
  167. End If
  168. Local section:TIniSection = New TIniSection(Self, index)
  169. sections :+ [section]
  170. Return section
  171. End Method
  172. Rem
  173. bbdoc: Removes a section at the given @index.
  174. End Rem
  175. Method RemoveSection(index:Int)
  176. If index < 0 Or index >= ini_section_count(iniPtr) Then
  177. Return
  178. End If
  179. If index = 0 Then ' we just clear the global section, rather than remove it.
  180. Local s:TIniSection = GetSection(0)
  181. s.Clear()
  182. Return
  183. End If
  184. ini_section_remove(iniPtr, index)
  185. ' not the last section? We'll need to fix the array
  186. If index < sections.Length - 1 Then
  187. Local secs:TIniSection[sections.Length - 1]
  188. Local n:Int
  189. For Local i:Int = 0 Until sections.Length
  190. If i <> index Then
  191. Local section:TIniSection = sections[i]
  192. secs[n] = section
  193. section.index = n
  194. n :+ 1
  195. End If
  196. Next
  197. sections = secs
  198. Else
  199. sections = sections[..index]
  200. End If
  201. End Method
  202. Rem
  203. bbdoc: Sets the value for the property with the given @section and @name.
  204. about: If the section or property does not exist, it is created.
  205. End Rem
  206. Method Set(section:String, name:String, value:String)
  207. Local s:TIniSection
  208. If section.Trim() = Null Then
  209. s = GetSection(INI_GLOBAL_SECTION)
  210. Else
  211. s = FindSection(section)
  212. End If
  213. If Not s Then
  214. s = AddSection(section)
  215. End If
  216. If s Then
  217. Local p:TIniProperty = s.FindProperty(name)
  218. If Not p Then
  219. p = s.AddProperty(name, value)
  220. End If
  221. If p Then
  222. p.SetValue(value)
  223. End If
  224. End If
  225. End Method
  226. Rem
  227. bbdoc: Sets the value for the property with the given @name in the global section.
  228. about: If the property does not exist, it is created.
  229. End Rem
  230. Method Set(name:String, value:String)
  231. Set(Null, name, value)
  232. End Method
  233. Rem
  234. bbdoc: Returns the value for the property with the given @section and @name, or #Null if not found.
  235. End Rem
  236. Method Get:String(section:String, name:String)
  237. Local s:TIniSection
  238. If section = Null Then
  239. s = GetSection(INI_GLOBAL_SECTION)
  240. Else
  241. s = FindSection(section)
  242. End If
  243. If s Then
  244. Local p:TIniProperty = s.FindProperty(name)
  245. If p Then
  246. Return p.GetValue()
  247. End If
  248. End If
  249. End Method
  250. Rem
  251. bbdoc: Returns the value for the property with the given @name in the global section, or #Null if not found.
  252. End Rem
  253. Method Get:String(name:String)
  254. Return Get(Null, name)
  255. End Method
  256. Rem
  257. bbdoc: Frees instance and associated data.
  258. End Rem
  259. Method Free()
  260. If sections Then
  261. For Local section:TIniSection = Eachin sections
  262. section.Free()
  263. Next
  264. sections = Null
  265. End If
  266. If iniPtr Then
  267. ini_destroy(iniPtr)
  268. iniPtr = Null
  269. End If
  270. End Method
  271. Method Delete()
  272. Free()
  273. End Method
  274. End Type
  275. Rem
  276. bbdoc: An ini section.
  277. about: Represents a distinct section within an INI file.
  278. In the structure of an INI file, sections are used to group related key-value pairs.
  279. A section can either be global, meaning it applies to the entire INI file, or named, identified by a unique header enclosed in
  280. square brackets (e.g., `[section name]`). This type facilitates the parsing, manipulation, and storage of these sections,
  281. allowing for organized access to the contained data.
  282. End Rem
  283. Type TIniSection
  284. Private
  285. Field ini:TIni
  286. Field index:Int
  287. Field name:String
  288. Field upperName:String
  289. Field properties:TIniProperty[]
  290. Method New()
  291. End Method
  292. Method New(ini:TIni, index:Int)
  293. Self.ini = ini
  294. Self.index = index
  295. name = bmx_ini_section_name(ini.iniPtr, index)
  296. InitProperties()
  297. End Method
  298. Method InitProperties()
  299. Local count:Int = ini_property_count(ini.iniPtr, index)
  300. properties = New TIniProperty[count]
  301. For Local i:Int = 0 Until count
  302. properties[i] = New TIniProperty(Self, i)
  303. Next
  304. End Method
  305. Public
  306. Rem
  307. bbdoc: Returns the name of the section, or #Null if it is the global section.
  308. End Rem
  309. Method GetName:String()
  310. Return name
  311. End Method
  312. Rem
  313. bbdoc: Returns the index of the section.
  314. about: The global section is represented by the index #INI_GLOBAL_SECTION.
  315. End Rem
  316. Method GetIndex:Int()
  317. Return index
  318. End Method
  319. Rem
  320. bbdoc: Sets the name of the section.
  321. End Rem
  322. Method SetName(name:String)
  323. bmx_ini_section_name_set(ini.iniPtr, index, name)
  324. Self.name = bmx_ini_section_name(ini.iniPtr, index)
  325. upperName = Null
  326. End Method
  327. Rem
  328. bbdoc: Returns the number of properties in the section.
  329. End Rem
  330. Method CountProperties:Int()
  331. Return ini_property_count(ini.iniPtr, index)
  332. End Method
  333. Rem
  334. bbdoc: Returns a property at the given @index position, or #Null if the @index is out of range.
  335. End Rem
  336. Method GetProperty:TIniProperty(index:Int)
  337. If index < 0 Or index >= ini_property_count(ini.iniPtr, Self.index) Then
  338. Return Null
  339. End If
  340. Return properties[index]
  341. End Method
  342. Rem
  343. bbdoc: Returns the property value with the given @name, or #Null if not found.
  344. End Rem
  345. Method Get:String(name:String)
  346. Local p:TIniProperty = FindProperty(name)
  347. If p Then
  348. Return p.GetValue()
  349. End If
  350. End Method
  351. Rem
  352. bbdoc: Sets the property with the given @name to the specified @value.
  353. about: If the property does not exist, it is created.
  354. End Rem
  355. Method Set(name:String, value:String)
  356. Local p:TIniProperty = FindProperty(name)
  357. If Not p Then
  358. p = AddProperty(name, value)
  359. End If
  360. If p Then
  361. p.SetValue(value)
  362. End If
  363. End Method
  364. Rem
  365. bbdoc: Finds the property with the given @name, or #Null if not found.
  366. End Rem
  367. Method FindProperty:TIniProperty(name:String)
  368. name = name.ToUpper()
  369. For Local property:TIniProperty = Eachin properties
  370. If Not property.upperName Then
  371. property.upperName = property.name.ToUpper()
  372. End If
  373. If name = property.upperName Then
  374. Return property
  375. End If
  376. Next
  377. End Method
  378. Rem
  379. bbdoc: Adds a new property to the section using the specified @name and @value
  380. End Rem
  381. Method AddProperty:TIniProperty(name:String, value:String)
  382. bmx_ini_property_add(ini.iniPtr, index, name, value)
  383. Local property:TIniProperty = New TIniProperty(Self, CountProperties() - 1)
  384. properties :+ [property]
  385. Return property
  386. End Method
  387. Rem
  388. bbdoc: Removes the property with the given @index from the section.
  389. End Rem
  390. Method RemoveProperty(index:Int)
  391. If index < 0 Or index >= ini_property_count(ini.iniPtr, Self.index) Then
  392. Return
  393. End If
  394. ini_property_remove(ini.iniPtr, Self.index, index)
  395. ' not the last property? We'll need to fix the array
  396. If index < properties.Length - 1 Then
  397. Local props:TIniProperty[properties.Length - 1]
  398. Local n:Int
  399. For Local i:Int = 0 Until properties.Length
  400. If i <> index Then
  401. Local prop:TIniProperty = properties[i]
  402. props[n] = prop
  403. prop.index = n
  404. n :+ 1
  405. End If
  406. Next
  407. properties = props
  408. Else
  409. properties = properties[..index]
  410. End If
  411. End Method
  412. Rem
  413. bbdoc: Removes all properties from the section.
  414. End Rem
  415. Method Clear()
  416. While properties.Length > 0
  417. RemoveProperty(properties.Length - 1)
  418. Wend
  419. End Method
  420. Rem
  421. bbdoc: Removes the section from the ini file.
  422. about: On removal, the section is freed and this instance, and all referenced properties should no longer be used.
  423. End Rem
  424. Method Remove()
  425. ini.RemoveSection(index)
  426. End Method
  427. Method Free()
  428. properties = Null
  429. End Method
  430. End Type
  431. Rem
  432. bbdoc: An ini property, with a name and value.
  433. about: An individual key-value pair, commonly referred to as a "property", within an INI file.
  434. Each property comprises a distinct key and its associated value, serving as the basic data element in the INI file structure.
  435. End Rem
  436. Type TIniProperty
  437. Private
  438. Field section:TIniSection
  439. Field index:Int
  440. Field name:String
  441. Field upperName:String
  442. Method New()
  443. End Method
  444. Method New(section:TIniSection, index:Int)
  445. Self.section = section
  446. Self.index = index
  447. name = bmx_ini_property_name(section.ini.iniPtr, section.index, index)
  448. End Method
  449. Public
  450. Rem
  451. bbdoc: Returns the name of the property.
  452. End Rem
  453. Method GetName:String()
  454. Return name
  455. End Method
  456. Rem
  457. bbdoc: Returns the index of the property.
  458. End Rem
  459. Method GetIndex:Int()
  460. Return index
  461. End Method
  462. Rem
  463. bbdoc: Sets the name the property.
  464. about: Names should not contain the `=` character, as it is used to separate the name from the value.
  465. End Rem
  466. Method SetName(name:String)
  467. bmx_ini_property_name_set(section.ini.iniPtr, section.index, index, name)
  468. Self.name = bmx_ini_property_name(section.ini.iniPtr, section.index, index)
  469. End Method
  470. Rem
  471. bbdoc: Returns the value for the property, or #Null if it is not set.
  472. End Rem
  473. Method GetValue:String()
  474. Return bmx_ini_property_value(section.ini.iniPtr, section.index, index)
  475. End Method
  476. Rem
  477. bbdoc: Sets the value for the property.
  478. End Rem
  479. Method SetValue(value:String)
  480. bmx_ini_property_value_set(section.ini.iniPtr, section.index, index, value)
  481. End Method
  482. Rem
  483. bbdoc: Removes the property from its section.
  484. about: On removal, the property is freed and this instance should no longer be used.
  485. End Rem
  486. Method Remove()
  487. section.RemoveProperty(index)
  488. End Method
  489. End Type