Construct empty IniLikeFile, i.e. without any groups or values
Read from file.
Read from range of inilike.range.IniLikeReader. Note: All exceptions thrown within constructor are turning into IniLikeReadException.
Behavior on group with duplicate name in the file.
Behavior on duplicate key in the group.
Create new group using groupName.
Add leading comment. This will be appended to the list of leadingComments. Note: # will be prepended automatically if line is not empty and does not have # at the start. The last new line character will be removed if present. Others will be replaced with whitespaces.
Range of groups in order how they were defined in file.
Iterate over GroupNodes.
Remove all coments met before groups.
Reimplement in derive class.
File path where the object was loaded from.
Get GroupNode by groupName.
Get group by name.
Insert group into IniLikeFile object and use its name as key. Prerequisites: group must be non-null. It also should not be held by some other IniLikeFile object.
Leading comments.
Move group after other.
Move group before other.
Move the group to make it the last.
Move the group to make it the first.
Add comment for group. This function is called only in constructor and can be reimplemented in derived classes.
Create IniLikeGroup by groupName during file parsing.
Add key/value pair for group. This function is called only in constructor and can be reimplemented in derived classes.
Add comment before groups. This function is called only in constructor and can be reimplemented in derived classes.
Prepend leading comment (e.g. for setting shebang line).
Append group to group list without associating group name with it. Can be used to add groups with duplicated names. Prerequisites: group must be non-null. It also should not be held by some other IniLikeFile object.
Remove group by name. Do nothing if group with such name does not exist.
Use Output range or delegate to retrieve strings line by line. Those strings can be written to the file or be showed in text area. Note: Output strings don't have trailing newline character.
Save object to the file using .ini-like format.
Save object to string using .ini like format.
Can be used in derived classes to create instance of IniLikeGroup.
Wrapper for internal ListMap node.
Behavior of ini-like file reading.
Behavior of ini-like file saving.
1 import std.file; 2 import std.path; 3 import std.stdio; 4 5 string contents = 6 `# The first comment 7 [First Entry] 8 # Comment 9 GenericName=File manager 10 GenericName[ru]=Файловый менеджер 11 NeedUnescape=yes\\i\tneed 12 NeedUnescape[ru]=да\\я\tнуждаюсь 13 # Another comment 14 [Another Group] 15 Name=Commander 16 Comment=Manage files 17 # The last comment`; 18 19 auto ilf = new IniLikeFile(iniLikeStringReader(contents), "contents.ini"); 20 assert(ilf.fileName() == "contents.ini"); 21 assert(equal(ilf.leadingComments(), ["# The first comment"])); 22 assert(ilf.group("First Entry")); 23 assert(ilf.group("Another Group")); 24 assert(ilf.getNode("Another Group").group is ilf.group("Another Group")); 25 assert(ilf.group("NonExistent") is null); 26 assert(ilf.getNode("NonExistent").isNull()); 27 assert(ilf.getNode("NonExistent").key() is null); 28 assert(ilf.getNode("NonExistent").group() is null); 29 assert(ilf.saveToString(IniLikeFile.WriteOptions.exact) == contents); 30 31 version(inilikeFileTest) 32 { 33 string tempFile = buildPath(tempDir(), "inilike-unittest-tempfile"); 34 try { 35 assertNotThrown!IniLikeReadException(ilf.saveToFile(tempFile)); 36 auto fileContents = cast(string)std.file.read(tempFile); 37 static if( __VERSION__ < 2067 ) { 38 assert(equal(fileContents.splitLines, contents.splitLines), "Contents should be preserved as is"); 39 } else { 40 assert(equal(fileContents.lineSplitter, contents.lineSplitter), "Contents should be preserved as is"); 41 } 42 43 IniLikeFile filf; 44 assertNotThrown!IniLikeReadException(filf = new IniLikeFile(tempFile)); 45 assert(filf.fileName() == tempFile); 46 remove(tempFile); 47 } catch(Exception e) { 48 //environmental error in unittests 49 } 50 } 51 52 auto firstEntry = ilf.group("First Entry"); 53 54 assert(!firstEntry.contains("NonExistent")); 55 assert(firstEntry.contains("GenericName")); 56 assert(firstEntry.contains("GenericName[ru]")); 57 assert(firstEntry.byNode().filter!(node => node.isNull()).empty); 58 assert(firstEntry["GenericName"] == "File manager"); 59 assert(firstEntry.value("GenericName") == "File manager"); 60 assert(firstEntry.getNode("GenericName").key == "GenericName"); 61 assert(firstEntry.getNode("NonExistent").key is null); 62 assert(firstEntry.getNode("NonExistent").line.type == IniLikeLine.Type.None); 63 64 assert(firstEntry.value("NeedUnescape") == `yes\\i\tneed`); 65 assert(firstEntry.readEntry("NeedUnescape") == "yes\\i\tneed"); 66 assert(firstEntry.localizedValue("NeedUnescape", "ru") == `да\\я\tнуждаюсь`); 67 assert(firstEntry.readEntry("NeedUnescape", "ru") == "да\\я\tнуждаюсь"); 68 69 firstEntry.writeEntry("NeedEscape", "i\rneed\nescape"); 70 assert(firstEntry.value("NeedEscape") == `i\rneed\nescape`); 71 firstEntry.writeEntry("NeedEscape", "ru", "мне\rнужно\nэкранирование"); 72 assert(firstEntry.localizedValue("NeedEscape", "ru") == `мне\rнужно\nэкранирование`); 73 74 firstEntry["GenericName"] = "Manager of files"; 75 assert(firstEntry["GenericName"] == "Manager of files"); 76 firstEntry["Authors"] = "Unknown"; 77 assert(firstEntry["Authors"] == "Unknown"); 78 firstEntry.getNode("Authors").setValue("Known"); 79 assert(firstEntry["Authors"] == "Known"); 80 81 assert(firstEntry.localizedValue("GenericName", "ru") == "Файловый менеджер"); 82 firstEntry["GenericName", "ru"] = "Менеджер файлов"; 83 assert(firstEntry.localizedValue("GenericName", "ru") == "Менеджер файлов"); 84 firstEntry.setLocalizedValue("Authors", "ru", "Неизвестны"); 85 assert(firstEntry.localizedValue("Authors", "ru") == "Неизвестны"); 86 87 firstEntry.removeEntry("GenericName"); 88 assert(!firstEntry.contains("GenericName")); 89 firstEntry.removeEntry("GenericName", "ru"); 90 assert(!firstEntry.contains("GenericName[ru]")); 91 firstEntry["GenericName"] = "File Manager"; 92 assert(firstEntry["GenericName"] == "File Manager"); 93 94 assert(ilf.group("Another Group")["Name"] == "Commander"); 95 assert(equal(ilf.group("Another Group").byKeyValue(), [ keyValueTuple("Name", "Commander"), keyValueTuple("Comment", "Manage files") ])); 96 97 auto latestCommentNode = ilf.group("Another Group").appendComment("The lastest comment"); 98 assert(latestCommentNode.line.comment == "#The lastest comment"); 99 latestCommentNode.setValue("The latest comment"); 100 assert(latestCommentNode.line.comment == "#The latest comment"); 101 assert(ilf.group("Another Group").prependComment("The first comment").line.comment == "#The first comment"); 102 103 assert(equal( 104 ilf.group("Another Group").byIniLine(), 105 [IniLikeLine.fromComment("#The first comment"), IniLikeLine.fromKeyValue("Name", "Commander"), IniLikeLine.fromKeyValue("Comment", "Manage files"), IniLikeLine.fromComment("# The last comment"), IniLikeLine.fromComment("#The latest comment")] 106 )); 107 108 auto nameLineNode = ilf.group("Another Group").getNode("Name"); 109 assert(nameLineNode.line.value == "Commander"); 110 auto commentLineNode = ilf.group("Another Group").getNode("Comment"); 111 assert(commentLineNode.line.value == "Manage files"); 112 113 ilf.group("Another Group").addCommentAfter(nameLineNode, "Middle comment"); 114 ilf.group("Another Group").addCommentBefore(commentLineNode, "Average comment"); 115 116 assert(equal( 117 ilf.group("Another Group").byIniLine(), 118 [ 119 IniLikeLine.fromComment("#The first comment"), IniLikeLine.fromKeyValue("Name", "Commander"), 120 IniLikeLine.fromComment("#Middle comment"), IniLikeLine.fromComment("#Average comment"), 121 IniLikeLine.fromKeyValue("Comment", "Manage files"), IniLikeLine.fromComment("# The last comment"), IniLikeLine.fromComment("#The latest comment") 122 ] 123 )); 124 125 ilf.group("Another Group").removeEntry(latestCommentNode); 126 127 assert(equal( 128 ilf.group("Another Group").byIniLine(), 129 [ 130 IniLikeLine.fromComment("#The first comment"), IniLikeLine.fromKeyValue("Name", "Commander"), 131 IniLikeLine.fromComment("#Middle comment"), IniLikeLine.fromComment("#Average comment"), 132 IniLikeLine.fromKeyValue("Comment", "Manage files"), IniLikeLine.fromComment("# The last comment") 133 ] 134 )); 135 136 assert(equal(ilf.byGroup().map!(g => g.groupName), ["First Entry", "Another Group"])); 137 138 assert(!ilf.removeGroup("NonExistent Group")); 139 140 assert(ilf.removeGroup("Another Group")); 141 assert(!ilf.group("Another Group")); 142 assert(equal(ilf.byGroup().map!(g => g.groupName), ["First Entry"])); 143 144 ilf.addGenericGroup("Another Group"); 145 assert(ilf.group("Another Group")); 146 assert(ilf.group("Another Group").byIniLine().empty); 147 assert(ilf.group("Another Group").byKeyValue().empty); 148 149 assertThrown(ilf.addGenericGroup("Another Group")); 150 151 ilf.addGenericGroup("Other Group"); 152 assert(equal(ilf.byGroup().map!(g => g.groupName), ["First Entry", "Another Group", "Other Group"])); 153 154 assertThrown!IniLikeException(ilf.addGenericGroup("")); 155 156 import std.range : isForwardRange; 157 158 const IniLikeFile cilf = ilf; 159 static assert(isForwardRange!(typeof(cilf.byGroup()))); 160 static assert(isForwardRange!(typeof(cilf.group("First Entry").byKeyValue()))); 161 static assert(isForwardRange!(typeof(cilf.group("First Entry").byIniLine()))); 162 163 contents = 164 `[Group] 165 GenericName=File manager 166 [Group] 167 GenericName=Commander`; 168 169 auto shouldThrow = collectException!IniLikeReadException(new IniLikeFile(iniLikeStringReader(contents), "config.ini")); 170 assert(shouldThrow !is null, "Duplicate groups should throw"); 171 assert(shouldThrow.lineNumber == 3); 172 assert(shouldThrow.lineIndex == 2); 173 assert(shouldThrow.fileName == "config.ini"); 174 175 contents = 176 `[Group] 177 Key=Value1 178 Key=Value2`; 179 180 shouldThrow = collectException!IniLikeReadException(new IniLikeFile(iniLikeStringReader(contents))); 181 assert(shouldThrow !is null, "Duplicate key should throw"); 182 assert(shouldThrow.lineNumber == 3); 183 184 contents = 185 `[Group] 186 Key=Value 187 =File manager`; 188 189 shouldThrow = collectException!IniLikeReadException(new IniLikeFile(iniLikeStringReader(contents))); 190 assert(shouldThrow !is null, "Empty key should throw"); 191 assert(shouldThrow.lineNumber == 3); 192 193 contents = 194 `[Group] 195 #Comment 196 Valid=Key 197 NotKeyNotGroupNotComment`; 198 199 shouldThrow = collectException!IniLikeReadException(new IniLikeFile(iniLikeStringReader(contents))); 200 assert(shouldThrow !is null, "Invalid entry should throw"); 201 assert(shouldThrow.lineNumber == 4); 202 203 contents = 204 `#Comment 205 NotComment 206 [Group] 207 Valid=Key`; 208 shouldThrow = collectException!IniLikeReadException(new IniLikeFile(iniLikeStringReader(contents))); 209 assert(shouldThrow !is null, "Invalid comment should throw"); 210 assert(shouldThrow.lineNumber == 2); 211 212 213 contents = `# The leading comment 214 [One] 215 # Comment1 216 Key1=Value1 217 Key2=Value2 218 Key3=Value3 219 [Two] 220 Key1=Value1 221 Key2=Value2 222 Key3=Value3 223 # Comment2 224 [Three] 225 Key1=Value1 226 Key2=Value2 227 # Comment3 228 Key3=Value3`; 229 230 ilf = new IniLikeFile(iniLikeStringReader(contents)); 231 232 ilf.moveGroupToFront(ilf.getNode("Two")); 233 assert(ilf.byNode().map!(g => g.key).equal(["Two", "One", "Three"])); 234 235 ilf.moveGroupToBack(ilf.getNode("One")); 236 assert(ilf.byNode().map!(g => g.key).equal(["Two", "Three", "One"])); 237 238 ilf.moveGroupBefore(ilf.getNode("Two"), ilf.getNode("Three")); 239 assert(ilf.byGroup().map!(g => g.groupName).equal(["Three", "Two", "One"])); 240 241 ilf.moveGroupAfter(ilf.getNode("Three"), ilf.getNode("One")); 242 assert(ilf.byGroup().map!(g => g.groupName).equal(["Three", "One", "Two"])); 243 244 auto groupOne = ilf.group("One"); 245 groupOne.moveLineToFront(groupOne.getNode("Key3")); 246 groupOne.moveLineToBack(groupOne.getNode("Key1")); 247 248 assert(groupOne.byIniLine().equal([ 249 IniLikeLine.fromKeyValue("Key3", "Value3"), IniLikeLine.fromComment("# Comment1"), 250 IniLikeLine.fromKeyValue("Key2", "Value2"), IniLikeLine.fromKeyValue("Key1", "Value1") 251 ])); 252 253 auto groupTwo = ilf.group("Two"); 254 groupTwo.moveLineBefore(groupTwo.getNode("Key1"), groupTwo.getNode("Key3")); 255 groupTwo.moveLineAfter(groupTwo.getNode("Key2"), groupTwo.getNode("Key1")); 256 257 assert(groupTwo.byIniLine().equal([ 258 IniLikeLine.fromKeyValue("Key3", "Value3"), IniLikeLine.fromKeyValue("Key2", "Value2"), 259 IniLikeLine.fromKeyValue("Key1", "Value1"), IniLikeLine.fromComment("# Comment2") 260 ]));
Ini-like file.