1 module parsing.ini_parser; 2 3 import std..string; 4 import std.algorithm; 5 import std.array; 6 7 /// Parse a list of key value pairs from a file in the format (aka ini file): 8 /// ```ini 9 /// # comment 10 /// some_key = some_value 11 /// # also supports sections, sections cna be named anything 12 /// [some_section] 13 /// # sections namespace keys to access the follwing key access the key "some_section.some_key" 14 /// some_key = some value 15 /// ``` 16 /// Comment, key value delimiter and space stripping can be configured with 17 /// constructor parameters this parser will not accept duplicate keys by default 18 /// to allow duplicate keys pass allowOverride to the constructor, it will enforce 19 /// the last instance of a key is the final value for that key. 20 class IniParser 21 { 22 string[string] items; 23 this( 24 string inBuffer, 25 bool paseSections = false, 26 bool stripSpaces = true, 27 bool allowOverride = false, 28 string commentChar = "#", 29 string keyValueSep = "=" 30 ) 31 { 32 auto currentSection = ""; 33 // strip the inBuffer of any white space or spurious whitespace before processing 34 inBuffer = strip(inBuffer); 35 // Split with lineSplitter as a lazy range, and alloc only once 36 // ignore any comment lines 37 auto lines = inBuffer.lineSplitter() 38 .map!(line => strip(line)) 39 .filter!(line => !line.startsWith(commentChar)) 40 .map!((line) { 41 // taking care of inline comments 42 if (auto split = line.findSplit(commentChar)) 43 return split[0]; 44 else 45 return line; 46 }) 47 .map!(line => strip(line)) 48 .array(); 49 50 foreach (line; lines) 51 { 52 // We will parse sections for namespacing purposes 53 if (line.startsWith("[") && paseSections) 54 { 55 if (auto split = line.findSplit("]")) 56 currentSection = strip(split[0][1 .. $]) ~ "."; 57 else 58 throw new Exception(format("Malformed section line: %s", line)); 59 continue; 60 } 61 62 if (auto split = line.findSplit(keyValueSep)) 63 { 64 string key = currentSection ~ strip(split[0]); 65 if (key in items && !allowOverride) 66 throw new Exception(format("key %s already seen", line)); 67 // split any 68 if (stripSpaces) 69 items[key] = strip(split[2]); 70 else 71 items[key] = split[2]; 72 } 73 else 74 throw new Exception(format("Malformed line: %s", line)); 75 } 76 } 77 78 string opIndex(string key) 79 { 80 return items[key]; 81 } 82 83 ulong lenght() 84 { 85 return items.length; 86 } 87 88 unittest 89 { 90 import std.exception; 91 92 auto testStr = " 93 # some comment 94 mainDrive = /dev/sda # some inline comment 95 secondaryDrive = /dev/sdb 96 "; 97 auto testStrSections = " 98 # some comment 99 [scsi] 100 mainDrive = /dev/sda # some inline comment 101 [ sata ] 102 mainDrive = /dev/sdb 103 "; 104 auto testStrInvalid = " 105 # some comment 106 mainDrive = /dev/sda # some inline comment 107 secondaryDrive = /dev/sdb 108 /dev/sdc 109 "; 110 IniParser cl = new IniParser(testStr); 111 assert(cl["mainDrive"] == "/dev/sda", "inline comment not stripped"); 112 assert(cl["secondaryDrive"] == "/dev/sdb", "second line wasn't parsed correctly"); 113 assertThrown(new IniParser(testStrInvalid)); 114 IniParser cls = new IniParser(testStrSections, true); 115 assert(cls["scsi.mainDrive"] == "/dev/sda", "inline comment not stripped"); 116 assert(cls["sata.mainDrive"] == "/dev/sdb", "second line wasn't parsed correctly"); 117 } 118 }