Class IniLikeFile

Ini-like file.

class IniLikeFile ;

Constructors

NameDescription
this Construct empty IniLikeFile, i.e. without any groups or values
this Read from file.
this Read from range of IniLikeReader.

Methods

NameDescription
addGenericGroup Create new group using groupName.
appendLeadingComment Add leading comment. This will be appended to the list of leadingComments.
byGroup Range of groups in order how they were defined in file.
byNode Iterate over GroupNodes.
clearLeadingComments Remove all coments met before groups.
escapedValue Shortcut to IniLikeGroup.escapedValue of given group. Returns null if the group does not exist.
escapedValue ditto, localized version
fileName File path where the object was loaded from.
getNode Get GroupNode by groupName.
group Get group by name.
leadingComments Leading comments.
moveGroupAfter Move group after other.
moveGroupBefore Move group before other.
moveGroupToBack Move the group to make it the last.
moveGroupToFront Move the group to make it the first.
prependLeadingComment Prepend leading comment (e.g. for setting shebang line).
removeGroup Remove group by name. Do nothing if group with such name does not exist.
save 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.
saveToFile Save object to the file using .ini-like format.
saveToString Save object to string using .ini like format.
unescapedValue Shortcut to IniLikeGroup.unescapedValue of given group. Returns null if the group does not exist.
createEmptyGroup Can be used in derived classes to create instance of IniLikeGroup.
createGroupByName Reimplement in derive class.
insertGroup Insert group into IniLikeFile object and use its name as key.
onCommentInGroup Add comment for group. This function is called only in constructor and can be reimplemented in derived classes.
onGroup Create IniLikeGroup by groupName during file parsing.
onKeyValue Add key/value pair for group. This function is called only in constructor and can be reimplemented in derived classes.
onLeadingComment Add comment before groups. This function is called only in constructor and can be reimplemented in derived classes.
putGroup Append group to group list without associating group name with it. Can be used to add groups with duplicated names.

Inner structs

NameDescription
GroupNode Wrapper for internal ListMap node.
ReadOptions Behavior of ini-like file reading.
WriteOptions Behavior of ini-like file saving.

Enums

NameDescription
DuplicateGroupPolicy Behavior on group with duplicate name in the file.
DuplicateKeyPolicy Behavior on duplicate key in the group.

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")
]));