Add source code
Added: - esv.d: reusable D interface to the ESV web API - main.d: the main program - Makefile - README.md - modified version of dini (dependency) - man pages - licenses
This commit is contained in:
commit
a0341e72d7
14 changed files with 2882 additions and 0 deletions
681
import/dini/parser.d
Normal file
681
import/dini/parser.d
Normal file
|
@ -0,0 +1,681 @@
|
|||
/**
|
||||
* INI parsing functionality.
|
||||
*
|
||||
* Examples:
|
||||
* ---
|
||||
* auto ini = Ini.ParseString("test = bar")
|
||||
* writeln("Value of test is ", ini["test"]);
|
||||
* ---
|
||||
*/
|
||||
module dini.parser;
|
||||
|
||||
import std.algorithm : min, max, countUntil;
|
||||
import std.array : split, replaceInPlace, join;
|
||||
import std.file : readText;
|
||||
import std.stdio : File;
|
||||
import std.string : strip, splitLines;
|
||||
import std.traits : isSomeString;
|
||||
import std.range : ElementType;
|
||||
import std.conv : to;
|
||||
import dini.reader : UniversalINIReader, INIException, INIToken;
|
||||
|
||||
|
||||
/**
|
||||
* Represents ini section
|
||||
*
|
||||
* Example:
|
||||
* ---
|
||||
* Ini ini = Ini.Parse("path/to/your.conf");
|
||||
* string value = ini.getKey("a");
|
||||
* ---
|
||||
*/
|
||||
struct IniSection
|
||||
{
|
||||
/// Section name
|
||||
protected string _name = "root";
|
||||
|
||||
/// Parent
|
||||
/// Null if none
|
||||
protected IniSection* _parent;
|
||||
|
||||
/// Childs
|
||||
protected IniSection[] _sections;
|
||||
|
||||
/// Keys
|
||||
protected string[string] _keys;
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Creates new IniSection instance
|
||||
*
|
||||
* Params:
|
||||
* name = Section name
|
||||
*/
|
||||
public this(string name)
|
||||
{
|
||||
_name = name;
|
||||
_parent = null;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Creates new IniSection instance
|
||||
*
|
||||
* Params:
|
||||
* name = Section name
|
||||
* parent = Section parent
|
||||
*/
|
||||
public this(string name, IniSection* parent)
|
||||
{
|
||||
_name = name;
|
||||
_parent = parent;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets section key
|
||||
*
|
||||
* Params:
|
||||
* name = Key name
|
||||
* value = Value to set
|
||||
*/
|
||||
public void setKey(string name, string value)
|
||||
{
|
||||
_keys[name] = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if specified key exists
|
||||
*
|
||||
* Params:
|
||||
* name = Key name
|
||||
*
|
||||
* Returns:
|
||||
* True if exists, false otherwise
|
||||
*/
|
||||
public bool hasKey(string name) @safe nothrow @nogc
|
||||
{
|
||||
return (name in _keys) !is null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets key value
|
||||
*
|
||||
* Params:
|
||||
* name = Key name
|
||||
*
|
||||
* Returns:
|
||||
* Key value
|
||||
*/
|
||||
public string getKey(string name)
|
||||
{
|
||||
if(!hasKey(name)) {
|
||||
return "";
|
||||
}
|
||||
|
||||
return _keys[name];
|
||||
}
|
||||
|
||||
|
||||
/// ditto
|
||||
alias getKey opCall;
|
||||
|
||||
/**
|
||||
* Gets key value or defaultValue if key does not exist
|
||||
*
|
||||
* Params:
|
||||
* name = Key name
|
||||
* defaultValue = Default value
|
||||
*
|
||||
* Returns:
|
||||
* Key value or defaultValue
|
||||
*
|
||||
*/
|
||||
public string getKey(string name, string defaultValue) @safe nothrow
|
||||
{
|
||||
return hasKey(name) ? _keys[name] : defaultValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes key
|
||||
*
|
||||
* Params:
|
||||
* name = Key name
|
||||
*/
|
||||
public void removeKey(string name)
|
||||
{
|
||||
_keys.remove(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds section
|
||||
*
|
||||
* Params:
|
||||
* section = Section to add
|
||||
*/
|
||||
public void addSection(ref IniSection section)
|
||||
{
|
||||
_sections ~= section;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if specified section exists
|
||||
*
|
||||
* Params:
|
||||
* name = Section name
|
||||
*
|
||||
* Returns:
|
||||
* True if exists, false otherwise
|
||||
*/
|
||||
public bool hasSection(string name)
|
||||
{
|
||||
foreach(ref section; _sections)
|
||||
{
|
||||
if(section.name() == name)
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns reference to section
|
||||
*
|
||||
* Params:
|
||||
* Section name
|
||||
*
|
||||
* Returns:
|
||||
* Section with specified name
|
||||
*/
|
||||
public ref IniSection getSection(string name)
|
||||
{
|
||||
foreach(ref section; _sections)
|
||||
{
|
||||
if(section.name() == name)
|
||||
return section;
|
||||
}
|
||||
|
||||
throw new IniException("Section '"~name~"' does not exist");
|
||||
}
|
||||
|
||||
|
||||
/// ditto
|
||||
public alias getSection opIndex;
|
||||
|
||||
/**
|
||||
* Removes section
|
||||
*
|
||||
* Params:
|
||||
* name = Section name
|
||||
*/
|
||||
public void removeSection(string name)
|
||||
{
|
||||
IniSection[] childs;
|
||||
|
||||
foreach(section; _sections)
|
||||
{
|
||||
if(section.name != name)
|
||||
childs ~= section;
|
||||
}
|
||||
|
||||
_sections = childs;
|
||||
}
|
||||
|
||||
/**
|
||||
* Section name
|
||||
*
|
||||
* Returns:
|
||||
* Section name
|
||||
*/
|
||||
public string name() @property
|
||||
{
|
||||
return _name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Array of keys
|
||||
*
|
||||
* Returns:
|
||||
* Associative array of keys
|
||||
*/
|
||||
public string[string] keys() @property
|
||||
{
|
||||
return _keys;
|
||||
}
|
||||
|
||||
/**
|
||||
* Array of sections
|
||||
*
|
||||
* Returns:
|
||||
* Array of sections
|
||||
*/
|
||||
public IniSection[] sections() @property
|
||||
{
|
||||
return _sections;
|
||||
}
|
||||
|
||||
/**
|
||||
* Root section
|
||||
*/
|
||||
public IniSection root() @property
|
||||
{
|
||||
IniSection s = this;
|
||||
|
||||
while(s.getParent() != null)
|
||||
s = *(s.getParent());
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
/**
|
||||
* Section parent
|
||||
*
|
||||
* Returns:
|
||||
* Pointer to parent, or null if parent does not exists
|
||||
*/
|
||||
public IniSection* getParent()
|
||||
{
|
||||
return _parent;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if current section has parent
|
||||
*
|
||||
* Returns:
|
||||
* True if section has parent, false otherwise
|
||||
*/
|
||||
public bool hasParent()
|
||||
{
|
||||
return _parent != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Moves current section to another one
|
||||
*
|
||||
* Params:
|
||||
* New parent
|
||||
*/
|
||||
public void setParent(ref IniSection parent)
|
||||
{
|
||||
_parent.removeSection(this.name);
|
||||
_parent = &parent;
|
||||
parent.addSection(this);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Parses filename
|
||||
*
|
||||
* Params:
|
||||
* filename = Configuration filename
|
||||
* doLookups = Should variable lookups be resolved after parsing?
|
||||
*/
|
||||
public void parse(string filename, bool doLookups = true)
|
||||
{
|
||||
parseString(readText(filename), doLookups);
|
||||
}
|
||||
|
||||
public void parse(File* file, bool doLookups = true)
|
||||
{
|
||||
string data = file.byLine().join().to!string;
|
||||
parseString(data, doLookups);
|
||||
}
|
||||
|
||||
public void parseWith(Reader)(string filename, bool doLookups = true)
|
||||
{
|
||||
parseStringWith!Reader(readText(filename), doLookups);
|
||||
}
|
||||
|
||||
public void parseWith(Reader)(File* file, bool doLookups = true)
|
||||
{
|
||||
string data = file.byLine().join().to!string;
|
||||
parseStringWith!Reader(data, doLookups);
|
||||
}
|
||||
|
||||
public void parseString(string data, bool doLookups = true)
|
||||
{
|
||||
parseStringWith!UniversalINIReader(data, doLookups);
|
||||
}
|
||||
|
||||
public void parseStringWith(Reader)(string data, bool doLookups = true)
|
||||
{
|
||||
IniSection* section = &this;
|
||||
|
||||
auto reader = Reader(data);
|
||||
alias KeyType = reader.KeyType;
|
||||
while (reader.next()) switch (reader.type) with (INIToken) {
|
||||
case SECTION:
|
||||
section = &this;
|
||||
string name = reader.value.get!string;
|
||||
auto parts = name.split(":");
|
||||
|
||||
// [section : parent]
|
||||
if (parts.length > 1)
|
||||
name = parts[0].strip;
|
||||
|
||||
IniSection child = IniSection(name, section);
|
||||
|
||||
if (parts.length > 1) {
|
||||
string parent = parts[1].strip;
|
||||
child.inherit(section.getSectionEx(parent));
|
||||
}
|
||||
section.addSection(child);
|
||||
section = §ion.getSection(name);
|
||||
break;
|
||||
|
||||
case KEY:
|
||||
section.setKey(reader.value.get!KeyType.name, reader.value.get!KeyType.value);
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
if(doLookups == true)
|
||||
parseLookups();
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses lookups
|
||||
*/
|
||||
public void parseLookups()
|
||||
{
|
||||
foreach (name, ref value; _keys)
|
||||
{
|
||||
ptrdiff_t start = -1;
|
||||
char[] buf;
|
||||
|
||||
foreach (i, c; value) {
|
||||
if (c == '%') {
|
||||
if (start != -1) {
|
||||
IniSection sect;
|
||||
string newValue;
|
||||
char[][] parts;
|
||||
|
||||
if (buf[0] == '.') {
|
||||
parts = buf[1..$].split(".");
|
||||
sect = this.root;
|
||||
}
|
||||
else {
|
||||
parts = buf.split(".");
|
||||
sect = this;
|
||||
}
|
||||
|
||||
newValue = sect.getSectionEx(parts[0..$-1].join(".").idup).getKey(parts[$-1].idup);
|
||||
|
||||
value.replaceInPlace(start, i+1, newValue);
|
||||
start = -1;
|
||||
buf = [];
|
||||
}
|
||||
else {
|
||||
start = i;
|
||||
}
|
||||
}
|
||||
else if (start != -1) {
|
||||
buf ~= c;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
foreach(child; _sections) {
|
||||
child.parseLookups();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns section by name in inheriting(names connected by dot)
|
||||
*
|
||||
* Params:
|
||||
* name = Section name
|
||||
*
|
||||
* Returns:
|
||||
* Section
|
||||
*/
|
||||
public IniSection getSectionEx(string name)
|
||||
{
|
||||
IniSection* root = &this;
|
||||
auto parts = name.split(".");
|
||||
|
||||
foreach(part; parts) {
|
||||
root = (&root.getSection(part));
|
||||
}
|
||||
|
||||
return *root;
|
||||
}
|
||||
|
||||
/**
|
||||
* Inherits keys from section
|
||||
*
|
||||
* Params:
|
||||
* Section to inherit
|
||||
*/
|
||||
public void inherit(IniSection sect)
|
||||
{
|
||||
this._keys = sect.keys().dup;
|
||||
}
|
||||
|
||||
|
||||
public void save(string filename)
|
||||
{
|
||||
import std.file;
|
||||
|
||||
if (exists(filename))
|
||||
remove(filename);
|
||||
|
||||
File file = File(filename, "w");
|
||||
|
||||
foreach (section; _sections) {
|
||||
file.writeln("[" ~ section.name() ~ "]");
|
||||
|
||||
string[string] propertiesInSection = section.keys();
|
||||
foreach (key; propertiesInSection.keys) {
|
||||
file.writeln(key ~ " = " ~ propertiesInSection[key]);
|
||||
}
|
||||
|
||||
file.writeln();
|
||||
}
|
||||
|
||||
file.close();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Parses Ini file
|
||||
*
|
||||
* Params:
|
||||
* filename = Path to ini file
|
||||
*
|
||||
* Returns:
|
||||
* IniSection root
|
||||
*/
|
||||
static Ini Parse(string filename, bool parseLookups = true)
|
||||
{
|
||||
Ini i;
|
||||
i.parse(filename, parseLookups);
|
||||
return i;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Parses Ini file with specified reader
|
||||
*
|
||||
* Params:
|
||||
* filename = Path to ini file
|
||||
*
|
||||
* Returns:
|
||||
* IniSection root
|
||||
*/
|
||||
static Ini ParseWith(Reader)(string filename, bool parseLookups = true)
|
||||
{
|
||||
Ini i;
|
||||
i.parseWith!Reader(filename, parseLookups);
|
||||
return i;
|
||||
}
|
||||
|
||||
static Ini ParseString(string data, bool parseLookups = true)
|
||||
{
|
||||
Ini i;
|
||||
i.parseString(data, parseLookups);
|
||||
return i;
|
||||
}
|
||||
|
||||
static Ini ParseStringWith(Reader)(string data, bool parseLookups = true)
|
||||
{
|
||||
Ini i;
|
||||
i.parseStringWith!Reader(data, parseLookups);
|
||||
return i;
|
||||
}
|
||||
}
|
||||
|
||||
// Compat
|
||||
alias INIException IniException;
|
||||
|
||||
/// ditto
|
||||
alias IniSection Ini;
|
||||
|
||||
|
||||
///
|
||||
Struct siphon(Struct)(Ini ini)
|
||||
{
|
||||
import std.traits;
|
||||
Struct ans;
|
||||
if(ini.hasSection(Struct.stringof))
|
||||
foreach(ti, Name; FieldNameTuple!(Struct))
|
||||
{
|
||||
alias ToType = typeof(ans.tupleof[ti]);
|
||||
if(ini[Struct.stringof].hasKey(Name))
|
||||
ans.tupleof[ti] = to!ToType(ini[Struct.stringof].getKey(Name));
|
||||
}
|
||||
return ans;
|
||||
}
|
||||
|
||||
unittest {
|
||||
struct Section {
|
||||
int var;
|
||||
}
|
||||
|
||||
auto ini = Ini.ParseString("[Section]\nvar=3");
|
||||
auto m = ini.siphon!Section;
|
||||
assert(m.var == 3);
|
||||
}
|
||||
|
||||
|
||||
unittest {
|
||||
auto data = q"(
|
||||
key1 = value
|
||||
|
||||
# comment
|
||||
|
||||
test = bar ; comment
|
||||
|
||||
[section 1]
|
||||
key1 = new key
|
||||
num = 151
|
||||
empty
|
||||
|
||||
|
||||
[ various ]
|
||||
"quoted key"= VALUE 123
|
||||
|
||||
quote_multiline = """
|
||||
this is value
|
||||
"""
|
||||
|
||||
escape_sequences = "yay\nboo"
|
||||
escaped_newlines = abcd \
|
||||
efg
|
||||
)";
|
||||
|
||||
auto ini = Ini.ParseString(data);
|
||||
assert(ini.getKey("key1") == "value");
|
||||
assert(ini.getKey("test") == "bar ; comment");
|
||||
|
||||
assert(ini.hasSection("section 1"));
|
||||
with (ini["section 1"]) {
|
||||
assert(getKey("key1") == "new key");
|
||||
assert(getKey("num") == "151");
|
||||
assert(getKey("empty") == "");
|
||||
}
|
||||
|
||||
assert(ini.hasSection("various"));
|
||||
with (ini["various"]) {
|
||||
assert(getKey("quoted key") == "VALUE 123");
|
||||
assert(getKey("quote_multiline") == "\n this is value\n");
|
||||
assert(getKey("escape_sequences") == "yay\nboo");
|
||||
assert(getKey("escaped_newlines") == "abcd efg");
|
||||
}
|
||||
}
|
||||
|
||||
unittest {
|
||||
auto data = q"EOF
|
||||
key1 = value
|
||||
|
||||
# comment
|
||||
|
||||
test = bar ; comment
|
||||
|
||||
[section 1]
|
||||
key1 = new key
|
||||
num = 151
|
||||
empty
|
||||
|
||||
EOF";
|
||||
|
||||
auto ini = Ini.ParseString(data);
|
||||
assert(ini.getKey("key1") == "value");
|
||||
assert(ini.getKey("test") == "bar ; comment");
|
||||
assert(ini.hasSection("section 1"));
|
||||
assert(ini["section 1"]("key1") == "new key");
|
||||
assert(ini["section 1"]("num") == "151");
|
||||
assert(ini["section 1"]("empty") == "");
|
||||
}
|
||||
|
||||
unittest {
|
||||
auto data = q"EOF
|
||||
[def]
|
||||
name1=value1
|
||||
name2=value2
|
||||
|
||||
[foo : def]
|
||||
name1=Name1 from foo. Lookup for def.name2: %name2%
|
||||
EOF";
|
||||
|
||||
// Parse file
|
||||
auto ini = Ini.ParseString(data, true);
|
||||
|
||||
assert(ini["foo"].getKey("name1")
|
||||
== "Name1 from foo. Lookup for def.name2: value2");
|
||||
}
|
||||
|
||||
unittest {
|
||||
auto data = q"EOF
|
||||
[section]
|
||||
name=%value%
|
||||
EOF";
|
||||
|
||||
// Create ini struct instance
|
||||
Ini ini;
|
||||
Ini iniSec = IniSection("section");
|
||||
ini.addSection(iniSec);
|
||||
|
||||
// Set key value
|
||||
ini["section"].setKey("value", "verify");
|
||||
|
||||
// Now, you can use value in ini file
|
||||
ini.parseString(data);
|
||||
|
||||
assert(ini["section"].getKey("name") == "verify");
|
||||
}
|
||||
|
||||
|
||||
unittest {
|
||||
import dini.reader;
|
||||
|
||||
alias MyReader = INIReader!(
|
||||
UniversalINIFormat,
|
||||
UniversalINIReader.CurrentFlags & ~INIFlags.ProcessEscapes,
|
||||
UniversalINIReader.CurrentBoxer
|
||||
);
|
||||
auto ini = Ini.ParseStringWith!MyReader(`path=C:\Path`);
|
||||
assert(ini("path") == `C:\Path`);
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue