1 /** 2 * Utilities for loading INI files. 3 * 4 * Authors: 5 * Mike Bierlee, m.bierlee@lostmoment.com 6 * Copyright: 2022-2025 Mike Bierlee 7 * License: 8 * This software is licensed under the terms of the MIT license. 9 * The full terms of the license can be found in the LICENSE file. 10 */ 11 12 module mirage.ini; 13 14 import mirage.config : ConfigDictionary; 15 import mirage.keyvalue : KeyValueConfigFactory, SupportHashtagComments, SupportSemicolonComments, 16 SupportExclamationComments, SupportSections, NormalizeQuotedValues, SupportEqualsSeparator, 17 SupportColonSeparator, SupportKeysWithoutValues, SupportMultilineValues; 18 19 /** 20 * Creates configuration dictionaries from INI files. 21 * 22 * Format specifications: 23 * https://en.wikipedia.org/wiki/INI_file#Format 24 */ 25 class IniConfigFactory : KeyValueConfigFactory!( 26 SupportHashtagComments.yes, 27 SupportSemicolonComments.yes, 28 SupportExclamationComments.no, 29 SupportSections.yes, 30 NormalizeQuotedValues.yes, 31 SupportEqualsSeparator.yes, 32 SupportColonSeparator.yes, 33 SupportKeysWithoutValues.no, 34 SupportMultilineValues.yes 35 ) { 36 } 37 38 /** 39 * Parse configuration from the given INI config string. 40 41 * Params: 42 * contents = Text contents of the config to be parsed. 43 * Returns: The parsed configuration. 44 */ 45 ConfigDictionary parseIniConfig(const string contents) { 46 return new IniConfigFactory().parseConfig(contents); 47 } 48 49 /** 50 * Load a INI configuration file from disk. 51 * 52 * Params: 53 * filePath = Path to the INI configuration file. 54 * Returns: The loaded configuration. 55 */ 56 ConfigDictionary loadIniConfig(const string filePath) { 57 return new IniConfigFactory().loadFile(filePath); 58 } 59 60 version (unittest) { 61 import std.process : environment; 62 63 @("Parse INI config") 64 unittest { 65 auto config = parseIniConfig(" 66 globalSection = yes 67 multi = 'we are \\ 68 multi!' 69 70 [supersection] 71 thefirst = here 72 73 [supersection.sub] 74 sandwich=maybe tasty 75 76 [.way] 77 advertisement? = nah ; For real, not sponsored! 78 79 # Although money would be cool 80 [back] 81 to: basics 82 much = \"very much whitespace\" 83 many = 'very many whitespace' 84 "); 85 86 assert(config.get("globalSection") == "yes"); 87 assert(config.get("multi") == "we are multi!"); 88 assert(config.get("supersection.thefirst") == "here"); 89 assert(config.get("supersection.sub.sandwich") == "maybe tasty"); 90 assert(config.get("supersection.sub.way.advertisement?") == "nah"); 91 assert(config.get("back.much") == "very much whitespace"); 92 assert(config.get("back.many") == "very many whitespace"); 93 } 94 95 @("Load INI file") 96 unittest { 97 auto config = loadIniConfig("testfiles/fuzzy.ini"); 98 99 assert(config.get("globalSection") == "yes"); 100 assert(config.get("supersection.thefirst") == "here"); 101 assert(config.get("supersection.sub.sandwich") == "maybe tasty"); 102 assert(config.get("supersection.sub.way.advertisement?") == "nah"); 103 assert(config.get("back.much") == "very much whitespace"); 104 assert(config.get("back.many") == "very many whitespace"); 105 } 106 107 @("Substitute env vars") 108 unittest { 109 environment["MIRAGE_TEST_INI_VAR"] = "I am ini"; 110 auto config = parseIniConfig(" 111 [app] 112 startInfo = ${MIRAGE_TEST_INI_VAR} 113 "); 114 115 assert(config.get("app.startInfo") == "I am ini"); 116 } 117 118 @("Use value from other key") 119 unittest { 120 auto config = parseIniConfig(" 121 [app] 122 startInfo = \"Let's get started!\" 123 124 [logger] 125 startInfo = ${app.startInfo} 126 "); 127 128 assert(config.get("app.startInfo") == "Let's get started!"); 129 assert(config.get("logger.startInfo") == "Let's get started!"); 130 } 131 }