Ini-like file.
Example
import std.file;
import std.path;
import std.stdio;
string contents =
`# The first comment
[First Entry]
# Comment
GenericName=File manager
GenericName[ru]=Файловый менеджер
NeedUnescape=yes\\i\tneed
NeedUnescape[ru]=да\\я\tнуждаюсь
# Another comment
[Another Group]
Name=Commander
Comment=Manage files
# The last comment`;
auto ilf = new IniLikeFile(iniLikeStringReader(contents), "contents.ini");
assert(ilf.fileName() == "contents.ini");
assert(equal(ilf.leadingComments(), ["# The first comment"]));
assert(ilf.group("First Entry"));
assert(ilf.group("Another Group"));
assert(ilf.getNode("Another Group").group is ilf.group("Another Group"));
assert(ilf.group("NonExistent") is null);
assert(ilf.getNode("NonExistent").isNull());
assert(ilf.getNode("NonExistent").key() is null);
assert(ilf.getNode("NonExistent").group() is null);
assert(ilf.saveToString(IniLikeFile.WriteOptions.exact) == contents);
version(inilikeFileTest)
{
string tempFile = buildPath(tempDir(), "inilike-unittest-tempfile");
try {
assertNotThrown!IniLikeReadException(ilf.saveToFile(tempFile));
auto fileContents = cast(string)std.file.read(tempFile);
assert(equal(fileContents.lineSplitter, contents.lineSplitter), "Contents should be preserved as is");
IniLikeFile filf;
assertNotThrown!IniLikeReadException(filf = new IniLikeFile(tempFile));
assert(filf.fileName() == tempFile);
remove(tempFile);
} catch(Exception e) {
//environmental error in unittests
}
}
assert(ilf.escapedValue("NonExistent", "Key") is null);
assert(ilf.escapedValue("NonExistent", "Key", "ru") is null);
assert(ilf.unescapedValue("NonExistent", "Key") is null);
auto firstEntry = ilf.group("First Entry");
assert(!firstEntry.contains("NonExistent"));
assert(firstEntry.contains("GenericName"));
assert(firstEntry.contains("GenericName[ru]"));
assert(firstEntry.byNode().filter!(node => node.isNull()).empty);
assert(firstEntry.escapedValue("GenericName") == "File manager");
assert(firstEntry.getNode("GenericName").key == "GenericName");
assert(firstEntry.getNode("NonExistent").key is null);
assert(firstEntry.getNode("NonExistent").line.type == IniLikeLine.Type.None);
assert(firstEntry.escapedValue("NeedUnescape") == `yes\\i\tneed`);
assert(ilf.escapedValue("First Entry", "NeedUnescape") == `yes\\i\tneed`);
assert(firstEntry.unescapedValue("NeedUnescape") == "yes\\i\tneed");
assert(ilf.unescapedValue("First Entry", "NeedUnescape") == "yes\\i\tneed");
assert(firstEntry.escapedValue("NeedUnescape", "ru") == `да\\я\tнуждаюсь`);
assert(ilf.escapedValue("First Entry", "NeedUnescape", "ru") == `да\\я\tнуждаюсь`);
assert(firstEntry.unescapedValue("NeedUnescape", "ru") == "да\\я\tнуждаюсь");
assert(ilf.unescapedValue("First Entry", "NeedUnescape", "ru") == "да\\я\tнуждаюсь");
firstEntry.setUnescapedValue("NeedEscape", "i\rneed\nescape");
assert(firstEntry.escapedValue("NeedEscape") == `i\rneed\nescape`);
firstEntry.setUnescapedValue("NeedEscape", "ru", "мне\rнужно\nэкранирование");
assert(firstEntry.escapedValue("NeedEscape", "ru") == `мне\rнужно\nэкранирование`);
firstEntry.setEscapedValue("GenericName", "Manager of files");
assert(firstEntry.escapedValue("GenericName") == "Manager of files");
firstEntry.setEscapedValue("Authors", "Unknown");
assert(firstEntry.escapedValue("Authors") == "Unknown");
firstEntry.getNode("Authors").setEscapedValue("Known");
assert(firstEntry.escapedValue("Authors") == "Known");
assert(firstEntry.escapedValue("GenericName", "ru") == "Файловый менеджер");
firstEntry.setEscapedValue("GenericName", "ru", "Менеджер файлов");
assert(firstEntry.escapedValue("GenericName", "ru") == "Менеджер файлов");
firstEntry.setEscapedValue("Authors", "ru", "Неизвестны");
assert(firstEntry.escapedValue("Authors", "ru") == "Неизвестны");
firstEntry.removeEntry("GenericName");
assert(!firstEntry.contains("GenericName"));
firstEntry.removeEntry("GenericName", "ru");
assert(!firstEntry.contains("GenericName[ru]"));
firstEntry.setEscapedValue("GenericName", "File Manager");
assert(firstEntry.escapedValue("GenericName") == "File Manager");
assert(ilf.group("Another Group").escapedValue("Name") == "Commander");
assert(equal(ilf.group("Another Group").byKeyValue(), [ keyValueTuple("Name", "Commander"), keyValueTuple("Comment", "Manage files") ]));
auto latestCommentNode = ilf.group("Another Group").appendComment("The lastest comment");
assert(latestCommentNode.line.comment == "#The lastest comment");
latestCommentNode.setEscapedValue("The latest comment");
assert(latestCommentNode.line.comment == "#The latest comment");
assert(ilf.group("Another Group").prependComment("The first comment").line.comment == "#The first comment");
assert(equal(
ilf.group("Another Group").byIniLine(),
[IniLikeLine.fromComment("#The first comment"), IniLikeLine.fromKeyValue("Name", "Commander"), IniLikeLine.fromKeyValue("Comment", "Manage files"), IniLikeLine.fromComment("# The last comment"), IniLikeLine.fromComment("#The latest comment")]
));
auto nameLineNode = ilf.group("Another Group").getNode("Name");
assert(nameLineNode.line.value == "Commander");
auto commentLineNode = ilf.group("Another Group").getNode("Comment");
assert(commentLineNode.line.value == "Manage files");
ilf.group("Another Group").addCommentAfter(nameLineNode, "Middle comment");
ilf.group("Another Group").addCommentBefore(commentLineNode, "Average comment");
assert(equal(
ilf.group("Another Group").byIniLine(),
[
IniLikeLine.fromComment("#The first comment"), IniLikeLine.fromKeyValue("Name", "Commander"),
IniLikeLine.fromComment("#Middle comment"), IniLikeLine.fromComment("#Average comment"),
IniLikeLine.fromKeyValue("Comment", "Manage files"), IniLikeLine.fromComment("# The last comment"), IniLikeLine.fromComment("#The latest comment")
]
));
ilf.group("Another Group").removeEntry(latestCommentNode);
assert(equal(
ilf.group("Another Group").byIniLine(),
[
IniLikeLine.fromComment("#The first comment"), IniLikeLine.fromKeyValue("Name", "Commander"),
IniLikeLine.fromComment("#Middle comment"), IniLikeLine.fromComment("#Average comment"),
IniLikeLine.fromKeyValue("Comment", "Manage files"), IniLikeLine.fromComment("# The last comment")
]
));
assert(equal(ilf.byGroup().map!(g => g.groupName), ["First Entry", "Another Group"]));
assert(!ilf.removeGroup("NonExistent Group"));
assert(ilf.removeGroup("Another Group"));
assert(!ilf.group("Another Group"));
assert(equal(ilf.byGroup().map!(g => g.groupName), ["First Entry"]));
ilf.addGenericGroup("Another Group");
assert(ilf.group("Another Group"));
assert(ilf.group("Another Group").byIniLine().empty);
assert(ilf.group("Another Group").byKeyValue().empty);
assertThrown(ilf.addGenericGroup("Another Group"));
ilf.addGenericGroup("Other Group");
assert(equal(ilf.byGroup().map!(g => g.groupName), ["First Entry", "Another Group", "Other Group"]));
assertThrown!IniLikeException(ilf.addGenericGroup(""));
import std.range : isForwardRange;
const IniLikeFile cilf = ilf;
static assert(isForwardRange!(typeof(cilf.byGroup())));
static assert(isForwardRange!(typeof(cilf.group("First Entry").byKeyValue())));
static assert(isForwardRange!(typeof(cilf.group("First Entry").byIniLine())));
contents =
`[Group]
GenericName=File manager
[Group]
GenericName=Commander`;
auto shouldThrow = collectException!IniLikeReadException(new IniLikeFile(iniLikeStringReader(contents), "config.ini"));
assert(shouldThrow !is null, "Duplicate groups should throw");
assert(shouldThrow.lineNumber == 3);
assert(shouldThrow.lineIndex == 2);
assert(shouldThrow.fileName == "config.ini");
contents =
`[Group]
Key=Value1
Key=Value2`;
shouldThrow = collectException!IniLikeReadException(new IniLikeFile(iniLikeStringReader(contents)));
assert(shouldThrow !is null, "Duplicate key should throw");
assert(shouldThrow.lineNumber == 3);
contents =
`[Group]
Key=Value
=File manager`;
shouldThrow = collectException!IniLikeReadException(new IniLikeFile(iniLikeStringReader(contents)));
assert(shouldThrow !is null, "Empty key should throw");
assert(shouldThrow.lineNumber == 3);
contents =
`[Group]
#Comment
Valid=Key
NotKeyNotGroupNotComment`;
shouldThrow = collectException!IniLikeReadException(new IniLikeFile(iniLikeStringReader(contents)));
assert(shouldThrow !is null, "Invalid entry should throw");
assert(shouldThrow.lineNumber == 4);
contents =
`#Comment
NotComment
[Group]
Valid=Key`;
shouldThrow = collectException!IniLikeReadException(new IniLikeFile(iniLikeStringReader(contents)));
assert(shouldThrow !is null, "Invalid comment should throw");
assert(shouldThrow.lineNumber == 2);
contents = `# The leading comment
[One]
# Comment1
Key1=Value1
Key2=Value2
Key3=Value3
[Two]
Key1=Value1
Key2=Value2
Key3=Value3
# Comment2
[Three]
Key1=Value1
Key2=Value2
# Comment3
Key3=Value3`;
ilf = new IniLikeFile(iniLikeStringReader(contents));
ilf.moveGroupToFront(ilf.getNode("Two"));
assert(ilf.byNode().map!(g => g.key).equal(["Two", "One", "Three"]));
ilf.moveGroupToBack(ilf.getNode("One"));
assert(ilf.byNode().map!(g => g.key).equal(["Two", "Three", "One"]));
ilf.moveGroupBefore(ilf.getNode("Two"), ilf.getNode("Three"));
assert(ilf.byGroup().map!(g => g.groupName).equal(["Three", "Two", "One"]));
ilf.moveGroupAfter(ilf.getNode("Three"), ilf.getNode("One"));
assert(ilf.byGroup().map!(g => g.groupName).equal(["Three", "One", "Two"]));
auto groupOne = ilf.group("One");
groupOne.moveLineToFront(groupOne.getNode("Key3"));
groupOne.moveLineToBack(groupOne.getNode("Key1"));
assert(groupOne.byIniLine().equal([
IniLikeLine.fromKeyValue("Key3", "Value3"), IniLikeLine.fromComment("# Comment1"),
IniLikeLine.fromKeyValue("Key2", "Value2"), IniLikeLine.fromKeyValue("Key1", "Value1")
]));
auto groupTwo = ilf.group("Two");
groupTwo.moveLineBefore(groupTwo.getNode("Key1"), groupTwo.getNode("Key3"));
groupTwo.moveLineAfter(groupTwo.getNode("Key2"), groupTwo.getNode("Key1"));
assert(groupTwo.byIniLine().equal([
IniLikeLine.fromKeyValue("Key3", "Value3"), IniLikeLine.fromKeyValue("Key2", "Value2"),
IniLikeLine.fromKeyValue("Key1", "Value1"), IniLikeLine.fromComment("# Comment2")
]));