IniLikeFile

Ini-like file.

class IniLikeFile {}

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.

escapedValue
string escapedValue(string groupName, string key)

Shortcut to IniLikeGroup.escapedValue of given group. Returns null if the group does not exist.

escapedValue
string escapedValue(string groupName, string key, string locale, Flag!"nonLocaleFallback" nonLocaleFallback)

ditto, localized version

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.

unescapedValue
string unescapedValue(string groupName, string key, string locale, Flag!"nonLocaleFallback" nonLocaleFallback)

Shortcut to IniLikeGroup.unescapedValue of given group. Returns null if the group does not exist.

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

Meta