IniLikeFile

Ini-like file.

Constructors

this
this()

Construct empty IniLikeFile, i.e. without any groups or values

this
this(string fileName, ReadOptions readOptions)

Read from file.

this
this(IniLikeReader reader, string fileName, ReadOptions readOptions)

Read from range of inilike.range.IniLikeReader. Note: All exceptions thrown within constructor are turning into IniLikeReadException.

Members

Enums

DuplicateGroupPolicy
enum DuplicateGroupPolicy

Behavior on group with duplicate name in the file.

DuplicateKeyPolicy
enum DuplicateKeyPolicy

Behavior on duplicate key in the group.

Functions

addGenericGroup
IniLikeGroup addGenericGroup(string groupName)

Create new group using groupName.

appendLeadingComment
string appendLeadingComment(string line)

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.

byGroup
auto byGroup()

Range of groups in order how they were defined in file.

byNode
auto byNode()

Iterate over GroupNodes.

clearLeadingComments
void clearLeadingComments()

Remove all coments met before groups.

createGroupByName
IniLikeGroup createGroupByName(string groupName)

Reimplement in derive class.

fileName
string fileName()

File path where the object was loaded from.

getNode
auto getNode(string groupName)

Get GroupNode by groupName.

group
inout(IniLikeGroup) group(string groupName)

Get group by name.

insertGroup
auto insertGroup(IniLikeGroup group)

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.

leadingComments
auto leadingComments()

Leading comments.

moveGroupAfter
void moveGroupAfter(GroupNode other, GroupNode toMove)

Move group after other.

moveGroupBefore
void moveGroupBefore(GroupNode other, GroupNode toMove)

Move group before other.

moveGroupToBack
void moveGroupToBack(GroupNode toMove)

Move the group to make it the last.

moveGroupToFront
void moveGroupToFront(GroupNode toMove)

Move the group to make it the first.

onCommentInGroup
void onCommentInGroup(string comment, IniLikeGroup currentGroup, string groupName)

Add comment for group. This function is called only in constructor and can be reimplemented in derived classes.

onGroup
IniLikeGroup onGroup(string groupName)

Create IniLikeGroup by groupName during file parsing.

onKeyValue
void onKeyValue(string key, string value, IniLikeGroup currentGroup, string groupName)

Add key/value pair for group. This function is called only in constructor and can be reimplemented in derived classes.

onLeadingComment
void onLeadingComment(string comment)

Add comment before groups. This function is called only in constructor and can be reimplemented in derived classes.

prependLeadingComment
string prependLeadingComment(string line)

Prepend leading comment (e.g. for setting shebang line).

putGroup
auto putGroup(IniLikeGroup group)

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.

readOptions
ReadOptions readOptions()
Undocumented in source. Be warned that the author may not have intended to support it.
removeGroup
bool removeGroup(string groupName)

Remove group by name. Do nothing if group with such name does not exist.

save
void save(OutRange sink, WriteOptions options)

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.

saveToFile
void saveToFile(string fileName, WriteOptions options)

Save object to the file using .ini-like format.

saveToString
string saveToString(WriteOptions options)

Save object to string using .ini like format.

Static functions

createEmptyGroup
createEmptyGroup(string groupName)

Can be used in derived classes to create instance of IniLikeGroup.

Structs

GroupNode
struct GroupNode

Wrapper for internal ListMap node.

ReadOptions
struct ReadOptions

Behavior of ini-like file reading.

WriteOptions
struct WriteOptions

Behavior of ini-like file saving.

Examples

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

Meta