/** * Common functions for dealing with entries in ini-like file. * 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.common; package { import std.algorithm; import std.range; import std.string; import std.traits; import std.typecons; import std.conv : to; static if( __VERSION__ < 2066 ) enum nogc = 1; auto keyValueTuple(String)(String key, String value) { alias KeyValueTuple = Tuple!(String, "key", String, "value"); return KeyValueTuple(key, value); } } private @nogc @safe auto simpleStripLeft(inout(char)[] s) pure nothrow { size_t spaceNum = 0; while(spaceNum < s.length) { const char c = s[spaceNum]; if (c == ' ' || c == '\t') { spaceNum++; } else { break; } } return s[spaceNum..$]; } private @nogc @safe auto simpleStripRight(inout(char)[] s) pure nothrow { size_t spaceNum = 0; while(spaceNum < s.length) { const char c = s[$-1-spaceNum]; if (c == ' ' || c == '\t') { spaceNum++; } else { break; } } return s[0..$-spaceNum]; } /** * Test whether the string s represents a comment. */ @nogc @safe bool isComment(const(char)[] s) pure nothrow { s = s.simpleStripLeft; return !s.empty && s[0] == '#'; } /// unittest { assert( isComment("# Comment")); assert( isComment(" # Comment")); assert(!isComment("Not comment")); assert(!isComment("")); } /** * Test whether the string s represents a group header. * Note: "[]" is not considered as valid group header. */ @nogc @safe bool isGroupHeader(const(char)[] s) pure nothrow { s = s.simpleStripRight; return s.length > 2 && s[0] == '[' && s[$-1] == ']'; } /// unittest { assert( isGroupHeader("[Group]")); assert( isGroupHeader("[Group] ")); assert(!isGroupHeader("[]")); assert(!isGroupHeader("[Group")); assert(!isGroupHeader("Group]")); } /** * Retrieve group name from header entry. * Returns: group name or empty string if the entry is not group header. */ @nogc @safe auto parseGroupHeader(inout(char)[] s) pure nothrow { s = s.simpleStripRight; if (isGroupHeader(s)) { return s[1..$-1]; } else { return null; } } /// unittest { assert(parseGroupHeader("[Group name]") == "Group name"); assert(parseGroupHeader("NotGroupName") == string.init); assert(parseGroupHeader("[Group name]".dup) == "Group name".dup); } /** * Parse entry of kind Key=Value into pair of Key and Value. * Returns: tuple of key and value strings or tuple of empty strings if it's is not a key-value entry. * Note: this function does not check whether parsed key is valid key. */ @nogc @trusted auto parseKeyValue(String)(String s) pure nothrow if (isSomeString!String && is(ElementEncodingType!String : char)) { auto t = s.findSplit("="); auto key = t[0]; auto value = t[2]; if (key.length && t[1].length) { return keyValueTuple(key, value); } return keyValueTuple(String.init, String.init); } /// unittest { assert(parseKeyValue("Key=Value") == tuple("Key", "Value")); assert(parseKeyValue("Key=") == tuple("Key", string.init)); assert(parseKeyValue("=Value") == tuple(string.init, string.init)); assert(parseKeyValue("NotKeyValue") == tuple(string.init, string.init)); assert(parseKeyValue("Key=Value".dup) == tuple("Key".dup, "Value".dup)); } private @nogc @safe bool simpleCanFind(in char[] str, char c) pure nothrow { for (size_t i=0; i= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || c == '-'; } if (key.empty) { return false; } for (size_t i = 0; i= score2) { return tuple(firstLocale, firstValue); } else { return tuple(secondLocale, secondValue); } } /// unittest { string locale = "ru_RU.UTF-8@jargon"; assert(chooseLocalizedValue(string.init, "ru_RU", "Программист", "ru@jargon", "Кодер") == tuple(string.init, string.init)); assert(chooseLocalizedValue(locale, "fr_FR", "Programmeur", string.init, "Programmer") == tuple(string.init, "Programmer")); assert(chooseLocalizedValue(locale, string.init, "Programmer", "de_DE", "Programmierer") == tuple(string.init, "Programmer")); assert(chooseLocalizedValue(locale, "fr_FR", "Programmeur", "de_DE", "Programmierer") == tuple(string.init, string.init)); assert(chooseLocalizedValue(string.init, string.init, "Value", string.init, string.init) == tuple(string.init, "Value")); assert(chooseLocalizedValue(locale, string.init, "Value", string.init, string.init) == tuple(string.init, "Value")); assert(chooseLocalizedValue(locale, string.init, string.init, string.init, "Value") == tuple(string.init, "Value")); assert(chooseLocalizedValue(locale, "ru_RU", "Программист", "ru@jargon", "Кодер") == tuple("ru_RU", "Программист")); assert(chooseLocalizedValue(locale, "ru_RU", "Программист", "ru_RU@jargon", "Кодер") == tuple("ru_RU@jargon", "Кодер")); assert(chooseLocalizedValue(locale, "ru", "Разработчик", "ru_RU", "Программист") == tuple("ru_RU", "Программист")); } /** * Check if value needs to be escaped. This function is currently tolerant to single slashes and tabs. * Returns: true if value needs to escaped, false otherwise. * See_Also: $(D escapeValue) */ @nogc @safe bool needEscaping(String)(String value) nothrow pure if (isSomeString!String && is(ElementEncodingType!String : char)) { for (size_t i=0; i