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.
Shortcut to IniLikeGroup.escapedValue of given group. Returns null if the group does not exist.
ditto, localized version
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.
Shortcut to IniLikeGroup.unescapedValue of given group. Returns null if the group does not exist.
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 assert(equal(fileContents.lineSplitter, contents.lineSplitter), "Contents should be preserved as is"); 38 39 IniLikeFile filf; 40 assertNotThrown!IniLikeReadException(filf = new IniLikeFile(tempFile)); 41 assert(filf.fileName() == tempFile); 42 remove(tempFile); 43 } catch(Exception e) { 44 //environmental error in unittests 45 } 46 } 47 48 assert(ilf.escapedValue("NonExistent", "Key") is null); 49 assert(ilf.escapedValue("NonExistent", "Key", "ru") is null); 50 assert(ilf.unescapedValue("NonExistent", "Key") is null); 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.escapedValue("GenericName") == "File manager"); 59 assert(firstEntry.getNode("GenericName").key == "GenericName"); 60 assert(firstEntry.getNode("NonExistent").key is null); 61 assert(firstEntry.getNode("NonExistent").line.type == IniLikeLine.Type.None); 62 63 assert(firstEntry.escapedValue("NeedUnescape") == `yes\\i\tneed`); 64 assert(ilf.escapedValue("First Entry", "NeedUnescape") == `yes\\i\tneed`); 65 66 assert(firstEntry.unescapedValue("NeedUnescape") == "yes\\i\tneed"); 67 assert(ilf.unescapedValue("First Entry", "NeedUnescape") == "yes\\i\tneed"); 68 69 assert(firstEntry.escapedValue("NeedUnescape", "ru") == `да\\я\tнуждаюсь`); 70 assert(ilf.escapedValue("First Entry", "NeedUnescape", "ru") == `да\\я\tнуждаюсь`); 71 72 assert(firstEntry.unescapedValue("NeedUnescape", "ru") == "да\\я\tнуждаюсь"); 73 assert(ilf.unescapedValue("First Entry", "NeedUnescape", "ru") == "да\\я\tнуждаюсь"); 74 75 firstEntry.setUnescapedValue("NeedEscape", "i\rneed\nescape"); 76 assert(firstEntry.escapedValue("NeedEscape") == `i\rneed\nescape`); 77 firstEntry.setUnescapedValue("NeedEscape", "ru", "мне\rнужно\nэкранирование"); 78 assert(firstEntry.escapedValue("NeedEscape", "ru") == `мне\rнужно\nэкранирование`); 79 80 firstEntry.setEscapedValue("GenericName", "Manager of files"); 81 assert(firstEntry.escapedValue("GenericName") == "Manager of files"); 82 firstEntry.setEscapedValue("Authors", "Unknown"); 83 assert(firstEntry.escapedValue("Authors") == "Unknown"); 84 firstEntry.getNode("Authors").setEscapedValue("Known"); 85 assert(firstEntry.escapedValue("Authors") == "Known"); 86 87 assert(firstEntry.escapedValue("GenericName", "ru") == "Файловый менеджер"); 88 firstEntry.setEscapedValue("GenericName", "ru", "Менеджер файлов"); 89 assert(firstEntry.escapedValue("GenericName", "ru") == "Менеджер файлов"); 90 firstEntry.setEscapedValue("Authors", "ru", "Неизвестны"); 91 assert(firstEntry.escapedValue("Authors", "ru") == "Неизвестны"); 92 93 firstEntry.removeEntry("GenericName"); 94 assert(!firstEntry.contains("GenericName")); 95 firstEntry.removeEntry("GenericName", "ru"); 96 assert(!firstEntry.contains("GenericName[ru]")); 97 firstEntry.setEscapedValue("GenericName", "File Manager"); 98 assert(firstEntry.escapedValue("GenericName") == "File Manager"); 99 100 assert(ilf.group("Another Group").escapedValue("Name") == "Commander"); 101 assert(equal(ilf.group("Another Group").byKeyValue(), [ keyValueTuple("Name", "Commander"), keyValueTuple("Comment", "Manage files") ])); 102 103 auto latestCommentNode = ilf.group("Another Group").appendComment("The lastest comment"); 104 assert(latestCommentNode.line.comment == "#The lastest comment"); 105 latestCommentNode.setEscapedValue("The latest comment"); 106 assert(latestCommentNode.line.comment == "#The latest comment"); 107 assert(ilf.group("Another Group").prependComment("The first comment").line.comment == "#The first comment"); 108 109 assert(equal( 110 ilf.group("Another Group").byIniLine(), 111 [IniLikeLine.fromComment("#The first comment"), IniLikeLine.fromKeyValue("Name", "Commander"), IniLikeLine.fromKeyValue("Comment", "Manage files"), IniLikeLine.fromComment("# The last comment"), IniLikeLine.fromComment("#The latest comment")] 112 )); 113 114 auto nameLineNode = ilf.group("Another Group").getNode("Name"); 115 assert(nameLineNode.line.value == "Commander"); 116 auto commentLineNode = ilf.group("Another Group").getNode("Comment"); 117 assert(commentLineNode.line.value == "Manage files"); 118 119 ilf.group("Another Group").addCommentAfter(nameLineNode, "Middle comment"); 120 ilf.group("Another Group").addCommentBefore(commentLineNode, "Average comment"); 121 122 assert(equal( 123 ilf.group("Another Group").byIniLine(), 124 [ 125 IniLikeLine.fromComment("#The first comment"), IniLikeLine.fromKeyValue("Name", "Commander"), 126 IniLikeLine.fromComment("#Middle comment"), IniLikeLine.fromComment("#Average comment"), 127 IniLikeLine.fromKeyValue("Comment", "Manage files"), IniLikeLine.fromComment("# The last comment"), IniLikeLine.fromComment("#The latest comment") 128 ] 129 )); 130 131 ilf.group("Another Group").removeEntry(latestCommentNode); 132 133 assert(equal( 134 ilf.group("Another Group").byIniLine(), 135 [ 136 IniLikeLine.fromComment("#The first comment"), IniLikeLine.fromKeyValue("Name", "Commander"), 137 IniLikeLine.fromComment("#Middle comment"), IniLikeLine.fromComment("#Average comment"), 138 IniLikeLine.fromKeyValue("Comment", "Manage files"), IniLikeLine.fromComment("# The last comment") 139 ] 140 )); 141 142 assert(equal(ilf.byGroup().map!(g => g.groupName), ["First Entry", "Another Group"])); 143 144 assert(!ilf.removeGroup("NonExistent Group")); 145 146 assert(ilf.removeGroup("Another Group")); 147 assert(!ilf.group("Another Group")); 148 assert(equal(ilf.byGroup().map!(g => g.groupName), ["First Entry"])); 149 150 ilf.addGenericGroup("Another Group"); 151 assert(ilf.group("Another Group")); 152 assert(ilf.group("Another Group").byIniLine().empty); 153 assert(ilf.group("Another Group").byKeyValue().empty); 154 155 assertThrown(ilf.addGenericGroup("Another Group")); 156 157 ilf.addGenericGroup("Other Group"); 158 assert(equal(ilf.byGroup().map!(g => g.groupName), ["First Entry", "Another Group", "Other Group"])); 159 160 assertThrown!IniLikeException(ilf.addGenericGroup("")); 161 162 import std.range : isForwardRange; 163 164 const IniLikeFile cilf = ilf; 165 static assert(isForwardRange!(typeof(cilf.byGroup()))); 166 static assert(isForwardRange!(typeof(cilf.group("First Entry").byKeyValue()))); 167 static assert(isForwardRange!(typeof(cilf.group("First Entry").byIniLine()))); 168 169 contents = 170 `[Group] 171 GenericName=File manager 172 [Group] 173 GenericName=Commander`; 174 175 auto shouldThrow = collectException!IniLikeReadException(new IniLikeFile(iniLikeStringReader(contents), "config.ini")); 176 assert(shouldThrow !is null, "Duplicate groups should throw"); 177 assert(shouldThrow.lineNumber == 3); 178 assert(shouldThrow.lineIndex == 2); 179 assert(shouldThrow.fileName == "config.ini"); 180 181 contents = 182 `[Group] 183 Key=Value1 184 Key=Value2`; 185 186 shouldThrow = collectException!IniLikeReadException(new IniLikeFile(iniLikeStringReader(contents))); 187 assert(shouldThrow !is null, "Duplicate key should throw"); 188 assert(shouldThrow.lineNumber == 3); 189 190 contents = 191 `[Group] 192 Key=Value 193 =File manager`; 194 195 shouldThrow = collectException!IniLikeReadException(new IniLikeFile(iniLikeStringReader(contents))); 196 assert(shouldThrow !is null, "Empty key should throw"); 197 assert(shouldThrow.lineNumber == 3); 198 199 contents = 200 `[Group] 201 #Comment 202 Valid=Key 203 NotKeyNotGroupNotComment`; 204 205 shouldThrow = collectException!IniLikeReadException(new IniLikeFile(iniLikeStringReader(contents))); 206 assert(shouldThrow !is null, "Invalid entry should throw"); 207 assert(shouldThrow.lineNumber == 4); 208 209 contents = 210 `#Comment 211 NotComment 212 [Group] 213 Valid=Key`; 214 shouldThrow = collectException!IniLikeReadException(new IniLikeFile(iniLikeStringReader(contents))); 215 assert(shouldThrow !is null, "Invalid comment should throw"); 216 assert(shouldThrow.lineNumber == 2); 217 218 219 contents = `# The leading comment 220 [One] 221 # Comment1 222 Key1=Value1 223 Key2=Value2 224 Key3=Value3 225 [Two] 226 Key1=Value1 227 Key2=Value2 228 Key3=Value3 229 # Comment2 230 [Three] 231 Key1=Value1 232 Key2=Value2 233 # Comment3 234 Key3=Value3`; 235 236 ilf = new IniLikeFile(iniLikeStringReader(contents)); 237 238 ilf.moveGroupToFront(ilf.getNode("Two")); 239 assert(ilf.byNode().map!(g => g.key).equal(["Two", "One", "Three"])); 240 241 ilf.moveGroupToBack(ilf.getNode("One")); 242 assert(ilf.byNode().map!(g => g.key).equal(["Two", "Three", "One"])); 243 244 ilf.moveGroupBefore(ilf.getNode("Two"), ilf.getNode("Three")); 245 assert(ilf.byGroup().map!(g => g.groupName).equal(["Three", "Two", "One"])); 246 247 ilf.moveGroupAfter(ilf.getNode("Three"), ilf.getNode("One")); 248 assert(ilf.byGroup().map!(g => g.groupName).equal(["Three", "One", "Two"])); 249 250 auto groupOne = ilf.group("One"); 251 groupOne.moveLineToFront(groupOne.getNode("Key3")); 252 groupOne.moveLineToBack(groupOne.getNode("Key1")); 253 254 assert(groupOne.byIniLine().equal([ 255 IniLikeLine.fromKeyValue("Key3", "Value3"), IniLikeLine.fromComment("# Comment1"), 256 IniLikeLine.fromKeyValue("Key2", "Value2"), IniLikeLine.fromKeyValue("Key1", "Value1") 257 ])); 258 259 auto groupTwo = ilf.group("Two"); 260 groupTwo.moveLineBefore(groupTwo.getNode("Key1"), groupTwo.getNode("Key3")); 261 groupTwo.moveLineAfter(groupTwo.getNode("Key2"), groupTwo.getNode("Key1")); 262 263 assert(groupTwo.byIniLine().equal([ 264 IniLikeLine.fromKeyValue("Key3", "Value3"), IniLikeLine.fromKeyValue("Key2", "Value2"), 265 IniLikeLine.fromKeyValue("Key1", "Value1"), IniLikeLine.fromComment("# Comment2") 266 ]));
Ini-like file.