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