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

Meta