mirror of https://github.com/buggins/dlangui.git
214 lines
5.8 KiB
D
214 lines
5.8 KiB
D
/**
|
|
* Parsing contents of ini-like files via range-based interface.
|
|
* Authors:
|
|
* $(LINK2 https://github.com/FreeSlave, Roman Chistokhodov)
|
|
* Copyright:
|
|
* Roman Chistokhodov, 2015-2016
|
|
* License:
|
|
* $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost License 1.0).
|
|
* See_Also:
|
|
* $(LINK2 http://standards.freedesktop.org/desktop-entry-spec/latest/index.html, Desktop Entry Specification)
|
|
*/
|
|
|
|
module inilike.range;
|
|
|
|
import inilike.common;
|
|
|
|
|
|
/**
|
|
* Object for iterating through ini-like file entries.
|
|
*/
|
|
struct IniLikeReader(Range) if (isInputRange!Range && isSomeString!(ElementType!Range) && is(ElementEncodingType!(ElementType!Range) : char))
|
|
{
|
|
/**
|
|
* Construct from other range of strings.
|
|
*/
|
|
this(Range range)
|
|
{
|
|
_range = range;
|
|
}
|
|
|
|
/**
|
|
* Iterate through lines before any group header. It does not check if all lines are comments or empty lines.
|
|
*/
|
|
auto byLeadingLines()
|
|
{
|
|
return _range.until!(isGroupHeader);
|
|
}
|
|
|
|
/**
|
|
* Object representing single group (section) being parsed in .ini-like file.
|
|
*/
|
|
static struct Group(Range)
|
|
{
|
|
private this(Range range, ElementType!Range originalLine)
|
|
{
|
|
_range = range;
|
|
_originalLine = originalLine;
|
|
}
|
|
|
|
/**
|
|
* Name of group being parsed (without brackets).
|
|
* Note: This can become invalid during parsing the Input Range
|
|
* (e.g. if string buffer storing this value is reused in later reads).
|
|
*/
|
|
auto groupName() {
|
|
return parseGroupHeader(_originalLine);
|
|
}
|
|
|
|
/**
|
|
* Original line of group header (i.e. name with brackets).
|
|
* Note: This can become invalid during parsing the Input Range
|
|
* (e.g. if string buffer storing this value is reused in later reads).
|
|
*/
|
|
auto originalLine() {
|
|
return _originalLine;
|
|
}
|
|
|
|
/**
|
|
* Iterate over group entries - may be key-value pairs as well as comments or empty lines.
|
|
*/
|
|
auto byEntry()
|
|
{
|
|
return _range.until!(isGroupHeader);
|
|
}
|
|
|
|
private:
|
|
ElementType!Range _originalLine;
|
|
Range _range;
|
|
}
|
|
|
|
/**
|
|
* Iterate thorugh groups of .ini-like file.
|
|
* Returns: Range of Group objects.
|
|
*/
|
|
auto byGroup()
|
|
{
|
|
static struct ByGroup
|
|
{
|
|
this(Range range)
|
|
{
|
|
_range = range.find!(isGroupHeader);
|
|
ElementType!Range line;
|
|
if (!_range.empty) {
|
|
line = _range.front;
|
|
_range.popFront();
|
|
}
|
|
_currentGroup = Group!Range(_range, line);
|
|
}
|
|
|
|
auto front()
|
|
{
|
|
return _currentGroup;
|
|
}
|
|
|
|
bool empty()
|
|
{
|
|
return _currentGroup.groupName.empty;
|
|
}
|
|
|
|
void popFront()
|
|
{
|
|
_range = _range.find!(isGroupHeader);
|
|
ElementType!Range line;
|
|
if (!_range.empty) {
|
|
line = _range.front;
|
|
_range.popFront();
|
|
}
|
|
_currentGroup = Group!Range(_range, line);
|
|
}
|
|
private:
|
|
Group!Range _currentGroup;
|
|
Range _range;
|
|
}
|
|
|
|
return ByGroup(_range.find!(isGroupHeader));
|
|
}
|
|
private:
|
|
Range _range;
|
|
}
|
|
|
|
/**
|
|
* Convenient function for creation of IniLikeReader instance.
|
|
* Params:
|
|
* range = input range of strings (strings must be without trailing new line characters)
|
|
* Returns: IniLikeReader for given range.
|
|
* See_Also: $(D iniLikeFileReader), $(D iniLikeStringReader)
|
|
*/
|
|
auto iniLikeRangeReader(Range)(Range range)
|
|
{
|
|
return IniLikeReader!Range(range);
|
|
}
|
|
|
|
///
|
|
unittest
|
|
{
|
|
string contents =
|
|
`First comment
|
|
Second comment
|
|
[First group]
|
|
KeyValue1
|
|
KeyValue2
|
|
[Second group]
|
|
KeyValue3
|
|
KeyValue4
|
|
[Empty group]
|
|
[Third group]
|
|
KeyValue5
|
|
KeyValue6`;
|
|
auto r = iniLikeRangeReader(contents.splitLines());
|
|
|
|
auto byLeadingLines = r.byLeadingLines;
|
|
|
|
assert(byLeadingLines.front == "First comment");
|
|
assert(byLeadingLines.equal(["First comment", "Second comment"]));
|
|
|
|
auto byGroup = r.byGroup;
|
|
|
|
assert(byGroup.front.groupName == "First group");
|
|
assert(byGroup.front.originalLine == "[First group]");
|
|
|
|
|
|
assert(byGroup.front.byEntry.front == "KeyValue1");
|
|
assert(byGroup.front.byEntry.equal(["KeyValue1", "KeyValue2"]));
|
|
byGroup.popFront();
|
|
assert(byGroup.front.groupName == "Second group");
|
|
byGroup.popFront();
|
|
assert(byGroup.front.groupName == "Empty group");
|
|
assert(byGroup.front.byEntry.empty);
|
|
byGroup.popFront();
|
|
assert(byGroup.front.groupName == "Third group");
|
|
byGroup.popFront();
|
|
assert(byGroup.empty);
|
|
}
|
|
|
|
/**
|
|
* Convenient function for reading ini-like contents from the file.
|
|
* Throws: $(B ErrnoException) if file could not be opened.
|
|
* Note: This function uses byLineCopy internally. Fallbacks to byLine on older compilers.
|
|
* See_Also: $(D iniLikeRangeReader), $(D iniLikeStringReader)
|
|
*/
|
|
@trusted auto iniLikeFileReader(string fileName)
|
|
{
|
|
import std.stdio : File;
|
|
static if( __VERSION__ < 2067 ) {
|
|
return iniLikeRangeReader(File(fileName, "r").byLine().map!(s => s.idup));
|
|
} else {
|
|
return iniLikeRangeReader(File(fileName, "r").byLineCopy());
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Convenient function for reading ini-like contents from string.
|
|
* Note: on frontends < 2.067 it uses splitLines thereby allocates strings.
|
|
* See_Also: $(D iniLikeRangeReader), $(D iniLikeFileReader)
|
|
*/
|
|
@trusted auto iniLikeStringReader(String)(String contents) if (isSomeString!String && is(ElementEncodingType!String : char))
|
|
{
|
|
static if( __VERSION__ < 2067 ) {
|
|
return iniLikeRangeReader(contents.splitLines());
|
|
} else {
|
|
return iniLikeRangeReader(contents.lineSplitter());
|
|
}
|
|
}
|