aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--smolbote.qbs2
-rw-r--r--src/3rd-party/toml/LICENSE24
-rw-r--r--src/3rd-party/toml/toml.h1958
-rw-r--r--src/browser.cpp21
-rw-r--r--src/browser.h5
-rw-r--r--src/forms/blockerdialog.cpp5
-rw-r--r--src/forms/downloaddialog.cpp6
-rw-r--r--src/mainwindow.cpp27
-rw-r--r--src/settings.cpp80
-rw-r--r--src/settings.h25
-rw-r--r--src/widgets/webviewtabbar.cpp15
-rw-r--r--test/config.ini34
-rw-r--r--test/config.toml36
13 files changed, 2129 insertions, 109 deletions
diff --git a/smolbote.qbs b/smolbote.qbs
index 032a93e..31cb6b3 100644
--- a/smolbote.qbs
+++ b/smolbote.qbs
@@ -42,7 +42,7 @@ Project {
// condition: qbs.targetOS.contains("windows")
// }
- cpp.includePaths: ['src']
+ cpp.includePaths: ['src', 'src/3rd-party']
cpp.defines: {
if(project.deprecatedWarnings)
defines.push("QT_DEPRECATED_WARNINGS", "QT_DISABLE_DEPRECATED_BEFORE="+project.deprecatedBefore);
diff --git a/src/3rd-party/toml/LICENSE b/src/3rd-party/toml/LICENSE
new file mode 100644
index 0000000..1514196
--- /dev/null
+++ b/src/3rd-party/toml/LICENSE
@@ -0,0 +1,24 @@
+Copyright (c) 2014, MAYAH
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+* Redistributions of source code must retain the above copyright notice, this
+ list of conditions and the following disclaimer.
+
+* Redistributions in binary form must reproduce the above copyright notice,
+ this list of conditions and the following disclaimer in the documentation
+ and/or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
diff --git a/src/3rd-party/toml/toml.h b/src/3rd-party/toml/toml.h
new file mode 100644
index 0000000..ec8c489
--- /dev/null
+++ b/src/3rd-party/toml/toml.h
@@ -0,0 +1,1958 @@
+#ifndef TINYTOML_H_
+#define TINYTOML_H_
+
+#include <algorithm>
+#include <cassert>
+#include <cctype>
+#include <chrono>
+#include <cmath>
+#include <cstdint>
+#include <cstdio>
+#include <ctime>
+#include <iomanip>
+#include <istream>
+#include <sstream>
+#include <stdexcept>
+#include <string>
+#include <map>
+#include <memory>
+#include <utility>
+#include <vector>
+
+namespace toml {
+
+// ----------------------------------------------------------------------
+// Declarations
+
+class Value;
+typedef std::chrono::system_clock::time_point Time;
+typedef std::vector<Value> Array;
+typedef std::map<std::string, Value> Table;
+
+namespace internal {
+template<typename T> struct call_traits_value {
+ typedef T return_type;
+};
+template<typename T> struct call_traits_ref {
+ typedef const T& return_type;
+};
+} // namespace internal
+
+template<typename T> struct call_traits;
+template<> struct call_traits<bool> : public internal::call_traits_value<bool> {};
+template<> struct call_traits<int> : public internal::call_traits_value<int> {};
+template<> struct call_traits<int64_t> : public internal::call_traits_value<int64_t> {};
+template<> struct call_traits<double> : public internal::call_traits_value<double> {};
+template<> struct call_traits<std::string> : public internal::call_traits_ref<std::string> {};
+template<> struct call_traits<Time> : public internal::call_traits_ref<Time> {};
+template<> struct call_traits<Array> : public internal::call_traits_ref<Array> {};
+template<> struct call_traits<Table> : public internal::call_traits_ref<Table> {};
+
+// A value is returned for std::vector<T>. Not reference.
+// This is because a fresh vector is made.
+template<typename T> struct call_traits<std::vector<T>> : public internal::call_traits_value<std::vector<T>> {};
+
+// Formatting flags
+enum FormatFlag {
+ FORMAT_NONE = 0,
+ FORMAT_INDENT = 1
+};
+
+class Value {
+public:
+ enum Type {
+ NULL_TYPE,
+ BOOL_TYPE,
+ INT_TYPE,
+ DOUBLE_TYPE,
+ STRING_TYPE,
+ TIME_TYPE,
+ ARRAY_TYPE,
+ TABLE_TYPE,
+ };
+
+ Value() : type_(NULL_TYPE), null_(nullptr) {}
+ Value(bool v) : type_(BOOL_TYPE), bool_(v) {}
+ Value(int v) : type_(INT_TYPE), int_(v) {}
+ Value(int64_t v) : type_(INT_TYPE), int_(v) {}
+ Value(double v) : type_(DOUBLE_TYPE), double_(v) {}
+ Value(const std::string& v) : type_(STRING_TYPE), string_(new std::string(v)) {}
+ Value(const char* v) : type_(STRING_TYPE), string_(new std::string(v)) {}
+ Value(const Time& v) : type_(TIME_TYPE), time_(new Time(v)) {}
+ Value(const Array& v) : type_(ARRAY_TYPE), array_(new Array(v)) {}
+ Value(const Table& v) : type_(TABLE_TYPE), table_(new Table(v)) {}
+ Value(std::string&& v) : type_(STRING_TYPE), string_(new std::string(std::move(v))) {}
+ Value(Array&& v) : type_(ARRAY_TYPE), array_(new Array(std::move(v))) {}
+ Value(Table&& v) : type_(TABLE_TYPE), table_(new Table(std::move(v))) {}
+
+ Value(const Value& v);
+ Value(Value&& v) noexcept;
+ Value& operator=(const Value& v);
+ Value& operator=(Value&& v) noexcept;
+
+ // Guards from unexpected Value construction.
+ // Someone might use a value like this:
+ // toml::Value v = x->find("foo");
+ // But this is wrong. Without this constructor,
+ // value will be unexpectedly initialized with bool.
+ Value(const void* v) = delete;
+ ~Value();
+
+ // Retruns Value size.
+ // 0 for invalid value.
+ // The number of inner elements for array or table.
+ // 1 for other types.
+ size_t size() const;
+ bool empty() const;
+ Type type() const { return type_; }
+
+ bool valid() const { return type_ != NULL_TYPE; }
+ template<typename T> bool is() const;
+ template<typename T> typename call_traits<T>::return_type as() const;
+
+ friend bool operator==(const Value& lhs, const Value& rhs);
+ friend bool operator!=(const Value& lhs, const Value& rhs) { return !(lhs == rhs); }
+
+ // ----------------------------------------------------------------------
+ // For integer/floating value
+
+ // Returns true if the value is int or double.
+ bool isNumber() const;
+ // Returns number. Convert to double.
+ double asNumber() const;
+
+ // ----------------------------------------------------------------------
+ // For Time value
+
+ // Converts to time_t if the internal value is Time.
+ // We don't have as<std::time_t>(). Since time_t is basically signed long,
+ // it's something like a method to converting to (normal) integer.
+ std::time_t as_time_t() const;
+
+ // ----------------------------------------------------------------------
+ // For Table value
+ template<typename T> typename call_traits<T>::return_type get(const std::string&) const;
+ Value* set(const std::string& key, const Value& v);
+ // Finds a Value with |key|. |key| can contain '.'
+ // Note: if you would like to find a child value only, you need to use findChild.
+ const Value* find(const std::string& key) const;
+ Value* find(const std::string& key);
+ bool has(const std::string& key) const { return find(key) != nullptr; }
+ bool erase(const std::string& key);
+
+ Value& operator[](const std::string& key);
+
+ // Merge table. Returns true if succeeded. Otherwise, |this| might be corrupted.
+ // When the same key exists, it will be overwritten.
+ bool merge(const Value&);
+
+ // Finds a value with |key|. It searches only children.
+ Value* findChild(const std::string& key);
+ const Value* findChild(const std::string& key) const;
+ // Sets a value, and returns the pointer to the created value.
+ // When the value having the same key exists, it will be overwritten.
+ Value* setChild(const std::string& key, const Value& v);
+ Value* setChild(const std::string& key, Value&& v);
+ bool eraseChild(const std::string& key);
+
+ // ----------------------------------------------------------------------
+ // For Array value
+
+ template<typename T> typename call_traits<T>::return_type get(size_t index) const;
+ const Value* find(size_t index) const;
+ Value* find(size_t index);
+ Value* push(const Value& v);
+ Value* push(Value&& v);
+
+ // ----------------------------------------------------------------------
+ // Others
+
+ // Writer.
+ static std::string spaces(int num);
+ static std::string escapeKey(const std::string& key);
+
+ void write(std::ostream*, const std::string& keyPrefix = std::string(), int indent = -1) const;
+ void writeFormatted(std::ostream*, FormatFlag flags) const;
+
+ friend std::ostream& operator<<(std::ostream&, const Value&);
+
+private:
+ static const char* typeToString(Type);
+
+ template<typename T> void assureType() const;
+ Value* ensureValue(const std::string& key);
+
+ template<typename T> struct ValueConverter;
+
+ Type type_;
+ union {
+ void* null_;
+ bool bool_;
+ int64_t int_;
+ double double_;
+ std::string* string_;
+ Time* time_;
+ Array* array_;
+ Table* table_;
+ };
+
+ template<typename T> friend struct ValueConverter;
+};
+
+// parse() returns ParseResult.
+struct ParseResult {
+ ParseResult(toml::Value v, std::string er) :
+ value(std::move(v)),
+ errorReason(std::move(er)) {}
+
+ bool valid() const { return value.valid(); }
+
+ toml::Value value;
+ std::string errorReason;
+};
+
+// Parses from std::istream.
+ParseResult parse(std::istream&);
+
+// ----------------------------------------------------------------------
+// Declarations for Implementations
+// You don't need to understand the below to use this library.
+
+#if defined(_WIN32)
+// Windows does not have timegm but have _mkgmtime.
+inline time_t timegm(std::tm* timeptr)
+{
+ return _mkgmtime(timeptr);
+}
+#endif
+#if defined(_MSC_VER)
+// Visual studio doesn't define gmtime_r, but mingw does
+inline std::tm* gmtime_r(const time_t* timer, std::tm* result)
+{
+ gmtime_s(result, timer);
+ return result;
+}
+#endif
+
+namespace internal {
+
+enum class TokenType {
+ ERROR,
+ END_OF_FILE,
+ END_OF_LINE,
+ IDENT,
+ STRING,
+ MULTILINE_STRING,
+ BOOL,
+ INT,
+ DOUBLE,
+ TIME,
+ COMMA,
+ DOT,
+ EQUAL,
+ LBRACKET,
+ RBRACKET,
+ LBRACE,
+ RBRACE,
+};
+
+class Token {
+public:
+ explicit Token(TokenType type) : type_(type) {}
+ Token(TokenType type, const std::string& v) : type_(type), str_value_(v) {}
+ Token(TokenType type, bool v) : type_(type), int_value_(v) {}
+ Token(TokenType type, std::int64_t v) : type_(type), int_value_(v) {}
+ Token(TokenType type, double v) : type_(type), double_value_(v) {}
+ Token(TokenType type, std::chrono::system_clock::time_point tp) : type_(type), time_value_(tp) {}
+
+ TokenType type() const { return type_; }
+ const std::string& strValue() const { return str_value_; }
+ bool boolValue() const { return int_value_ != 0; }
+ std::int64_t intValue() const { return int_value_; }
+ double doubleValue() const { return double_value_; }
+ std::chrono::system_clock::time_point timeValue() const { return time_value_; }
+
+private:
+ TokenType type_;
+ std::string str_value_;
+ std::int64_t int_value_;
+ double double_value_;
+ std::chrono::system_clock::time_point time_value_;
+};
+
+class Lexer {
+public:
+ explicit Lexer(std::istream& is) : is_(is), lineNo_(1) {}
+
+ Token nextKeyToken();
+ Token nextValueToken();
+
+ int lineNo() const { return lineNo_; }
+
+private:
+ bool current(char* c);
+ void next();
+ bool consume(char c);
+
+ Token nextToken(bool isValueToken);
+
+ void skipUntilNewLine();
+
+ Token nextStringDoubleQuote();
+ Token nextStringSingleQuote();
+
+ Token nextKey();
+ Token nextValue();
+
+ Token parseAsTime(const std::string&);
+
+ std::istream& is_;
+ int lineNo_;
+};
+
+class Parser {
+public:
+ explicit Parser(std::istream& is) : lexer_(is), token_(TokenType::ERROR) { nextKey(); }
+
+ // Parses. If failed, value should be invalid value.
+ // You can get the error by calling errorReason().
+ Value parse();
+ const std::string& errorReason();
+
+private:
+ const Token& token() const { return token_; }
+ void nextKey() { token_ = lexer_.nextKeyToken(); }
+ void nextValue() { token_ = lexer_.nextValueToken(); }
+
+ void skipForKey();
+ void skipForValue();
+
+ bool consumeForKey(TokenType);
+ bool consumeForValue(TokenType);
+ bool consumeEOLorEOFForKey();
+
+ Value* parseGroupKey(Value* root);
+
+ bool parseKeyValue(Value*);
+ bool parseKey(std::string*);
+ bool parseValue(Value*);
+ bool parseBool(Value*);
+ bool parseNumber(Value*);
+ bool parseArray(Value*);
+ bool parseInlineTable(Value*);
+
+ void addError(const std::string& reason);
+
+ Lexer lexer_;
+ Token token_;
+ std::string errorReason_;
+};
+
+} // namespace internal
+
+// ----------------------------------------------------------------------
+// Implementations
+
+inline ParseResult parse(std::istream& is)
+{
+ internal::Parser parser(is);
+ toml::Value v = parser.parse();
+
+ if (v.valid())
+ return ParseResult(std::move(v), std::string());
+
+ return ParseResult(std::move(v), std::move(parser.errorReason()));
+}
+
+inline std::string format(std::stringstream& ss)
+{
+ return ss.str();
+}
+
+template<typename T, typename... Args>
+std::string format(std::stringstream& ss, T&& t, Args&&... args)
+{
+ ss << std::forward<T>(t);
+ return format(ss, std::forward<Args>(args)...);
+}
+
+template<typename... Args>
+#if defined(_MSC_VER)
+__declspec(noreturn)
+#else
+[[noreturn]]
+#endif
+void failwith(Args&&... args)
+{
+ std::stringstream ss;
+ throw std::runtime_error(format(ss, std::forward<Args>(args)...));
+}
+
+namespace internal {
+
+inline std::string removeDelimiter(const std::string& s)
+{
+ std::string r;
+ for (char c : s) {
+ if (c == '_')
+ continue;
+ r += c;
+ }
+ return r;
+}
+
+inline std::string unescape(const std::string& codepoint)
+{
+ std::uint32_t x;
+ std::uint8_t buf[8];
+ std::stringstream ss(codepoint);
+
+ ss >> std::hex >> x;
+
+ if (x <= 0x7FUL) {
+ // 0xxxxxxx
+ buf[0] = 0x00 | ((x >> 0) & 0x7F);
+ buf[1] = '\0';
+ } else if (x <= 0x7FFUL) {
+ // 110yyyyx 10xxxxxx
+ buf[0] = 0xC0 | ((x >> 6) & 0xDF);
+ buf[1] = 0x80 | ((x >> 0) & 0xBF);
+ buf[2] = '\0';
+ } else if (x <= 0xFFFFUL) {
+ // 1110yyyy 10yxxxxx 10xxxxxx
+ buf[0] = 0xE0 | ((x >> 12) & 0xEF);
+ buf[1] = 0x80 | ((x >> 6) & 0xBF);
+ buf[2] = 0x80 | ((x >> 0) & 0xBF);
+ buf[3] = '\0';
+ } else if (x <= 0x10FFFFUL) {
+ // 11110yyy 10yyxxxx 10xxxxxx 10xxxxxx
+ buf[0] = 0xF0 | ((x >> 18) & 0xF7);
+ buf[1] = 0x80 | ((x >> 12) & 0xBF);
+ buf[2] = 0x80 | ((x >> 6) & 0xBF);
+ buf[3] = 0x80 | ((x >> 0) & 0xBF);
+ buf[4] = '\0';
+ } else {
+ buf[0] = '\0';
+ }
+
+ return reinterpret_cast<char*>(buf);
+}
+
+// Returns true if |s| is integer.
+// [+-]?\d+(_\d+)*
+inline bool isInteger(const std::string& s)
+{
+ if (s.empty())
+ return false;
+
+ std::string::size_type p = 0;
+ if (s[p] == '+' || s[p] == '-')
+ ++p;
+
+ while (p < s.size() && '0' <= s[p] && s[p] <= '9') {
+ ++p;
+ if (p < s.size() && s[p] == '_') {
+ ++p;
+ if (!(p < s.size() && '0' <= s[p] && s[p] <= '9'))
+ return false;
+ }
+ }
+
+ return p == s.size();
+}
+
+// Returns true if |s| is double.
+// [+-]? (\d+(_\d+)*)? (\.\d+(_\d+)*)? ([eE] [+-]? \d+(_\d+)*)?
+// 1----------- 2------------- 3----------------------
+// 2 or (1 and 3) should exist.
+inline bool isDouble(const std::string& s)
+{
+ if (s.empty())
+ return false;
+
+ std::string::size_type p = 0;
+ if (s[p] == '+' || s[p] == '-')
+ ++p;
+
+ bool ok = false;
+ while (p < s.size() && '0' <= s[p] && s[p] <= '9') {
+ ++p;
+ ok = true;
+
+ if (p < s.size() && s[p] == '_') {
+ ++p;
+ if (!(p < s.size() && '0' <= s[p] && s[p] <= '9'))
+ return false;
+ }
+ }
+
+ if (p < s.size() && s[p] == '.')
+ ++p;
+
+ while (p < s.size() && '0' <= s[p] && s[p] <= '9') {
+ ++p;
+ ok = true;
+
+ if (p < s.size() && s[p] == '_') {
+ ++p;
+ if (!(p < s.size() && '0' <= s[p] && s[p] <= '9'))
+ return false;
+ }
+ }
+
+ if (!ok)
+ return false;
+
+ ok = false;
+ if (p < s.size() && (s[p] == 'e' || s[p] == 'E')) {
+ ++p;
+ if (p < s.size() && (s[p] == '+' || s[p] == '-'))
+ ++p;
+ while (p < s.size() && '0' <= s[p] && s[p] <= '9') {
+ ++p;
+ ok = true;
+
+ if (p < s.size() && s[p] == '_') {
+ ++p;
+ if (!(p < s.size() && '0' <= s[p] && s[p] <= '9'))
+ return false;
+ }
+ }
+ if (!ok)
+ return false;
+ }
+
+ return p == s.size();
+}
+
+// static
+inline std::string escapeString(const std::string& s)
+{
+ std::stringstream ss;
+ for (size_t i = 0; i < s.size(); ++i) {
+ switch (s[i]) {
+ case '\n': ss << "\\n"; break;
+ case '\r': ss << "\\r"; break;
+ case '\t': ss << "\\t"; break;
+ case '\"': ss << "\\\""; break;
+ case '\'': ss << "\\\'"; break;
+ case '\\': ss << "\\\\"; break;
+ default: ss << s[i]; break;
+ }
+ }
+
+ return ss.str();
+}
+
+} // namespace internal
+
+// ----------------------------------------------------------------------
+// Lexer
+
+namespace internal {
+
+inline bool Lexer::current(char* c)
+{
+ int x = is_.peek();
+ if (x == EOF)
+ return false;
+ *c = static_cast<char>(x);
+ return true;
+}
+
+inline void Lexer::next()
+{
+ int x = is_.get();
+ if (x == '\n')
+ ++lineNo_;
+}
+
+inline bool Lexer::consume(char c)
+{
+ char x;
+ if (!current(&x))
+ return false;
+ if (x != c)
+ return false;
+ next();
+ return true;
+}
+
+inline void Lexer::skipUntilNewLine()
+{
+ char c;
+ while (current(&c)) {
+ if (c == '\n')
+ return;
+ next();
+ }
+}
+
+inline Token Lexer::nextStringDoubleQuote()
+{
+ if (!consume('"'))
+ return Token(TokenType::ERROR, std::string("string didn't start with '\"'"));
+
+ std::string s;
+ char c;
+ bool multiline = false;
+
+ if (current(&c) && c == '"') {
+ next();
+ if (!current(&c) || c != '"') {
+ // OK. It's empty string.
+ return Token(TokenType::STRING, std::string());
+ }
+
+ next();
+ // raw string literal started.
+ // Newline just after """ should be ignored.
+ while (current(&c) && (c == ' ' || c == '\t'))
+ next();
+ if (current(&c) && c == '\n')
+ next();
+ multiline = true;
+ }
+
+ while (current(&c)) {
+ next();
+ if (c == '\\') {
+ if (!current(&c))
+ return Token(TokenType::ERROR, std::string("string has unknown escape sequence"));
+ next();
+ switch (c) {
+ case 't': c = '\t'; break;
+ case 'n': c = '\n'; break;
+ case 'r': c = '\r'; break;
+ case 'u':
+ case 'U': {
+ int size = c == 'u' ? 4 : 8;
+ std::string codepoint;
+ for (int i = 0; i < size; ++i) {
+ if (current(&c) && (('0' <= c && c <= '9') || ('A' <= c && c <= 'F') || ('a' <= c && c <= 'f'))) {
+ codepoint += c;
+ next();
+ } else {
+ return Token(TokenType::ERROR, std::string("string has unknown escape sequence"));
+ }
+ }
+ s += unescape(codepoint);
+ continue;
+ }
+ case '"': c = '"'; break;
+ case '\'': c = '\''; break;
+ case '\\': c = '\\'; break;
+ case '\n':
+ while (current(&c) && (c == ' ' || c == '\t' || c == '\r' || c == '\n')) {
+ next();
+ }
+ continue;
+ default:
+ return Token(TokenType::ERROR, std::string("string has unknown escape sequence"));
+ }
+ } else if (c == '"') {
+ if (multiline) {
+ if (current(&c) && c == '"') {
+ next();
+ if (current(&c) && c == '"') {
+ next();
+ return Token(TokenType::MULTILINE_STRING, s);
+ } else {
+ s += '"';
+ s += '"';
+ continue;
+ }
+ } else {
+ s += '"';
+ continue;
+ }
+ } else {
+ return Token(TokenType::STRING, s);
+ }
+ }
+
+ s += c;
+ }
+
+ return Token(TokenType::ERROR, std::string("string didn't end"));
+}
+
+inline Token Lexer::nextStringSingleQuote()
+{
+ if (!consume('\''))
+ return Token(TokenType::ERROR, std::string("string didn't start with '\''?"));
+
+ std::string s;
+ char c;
+
+ if (current(&c) && c == '\'') {
+ next();
+ if (!current(&c) || c != '\'') {
+ // OK. It's empty string.
+ return Token(TokenType::STRING, std::string());
+ }
+ next();
+ // raw string literal started.
+ // Newline just after """ should be ignored.
+ if (current(&c) && c == '\n')
+ next();
+
+ while (current(&c)) {
+ if (c == '\'') {
+ next();
+ if (current(&c) && c == '\'') {
+ next();
+ if (current(&c) && c == '\'') {
+ next();
+ return Token(TokenType::MULTILINE_STRING, s);
+ } else {
+ s += '\'';
+ s += '\'';
+ continue;
+ }
+ } else {
+ s += '\'';
+ continue;
+ }
+ }
+
+ next();
+ s += c;
+ continue;
+ }
+
+ return Token(TokenType::ERROR, std::string("string didn't end with '\'\'\'' ?"));
+ }
+
+ while (current(&c)) {
+ next();
+ if (c == '\'') {
+ return Token(TokenType::STRING, s);
+ }
+
+ s += c;
+ }
+
+ return Token(TokenType::ERROR, std::string("string didn't end with '\''?"));
+}
+
+inline Token Lexer::nextKey()
+{
+ std::string s;
+ char c;
+ while (current(&c) && (isalnum(c) || c == '_' || c == '-')) {
+ s += c;
+ next();
+ }
+
+ if (s.empty())
+ return Token(TokenType::ERROR, std::string("Unknown key format"));
+
+ return Token(TokenType::IDENT, s);
+}
+
+inline Token Lexer::nextValue()
+{
+ std::string s;
+ char c;
+
+ if (current(&c) && isalpha(c)) {
+ s += c;
+ next();
+ while (current(&c) && isalpha(c)) {
+ s += c;
+ next();
+ }
+
+ if (s == "true")
+ return Token(TokenType::BOOL, true);
+ if (s == "false")
+ return Token(TokenType::BOOL, false);
+ return Token(TokenType::ERROR, std::string("Unknown ident: ") + s);
+ }
+
+ while (current(&c) && (('0' <= c && c <= '9') || c == '.' || c == 'e' || c == 'E' ||
+ c == 'T' || c == 'Z' || c == '_' || c == ':' || c == '-' || c == '+')) {
+ next();
+ s += c;
+ }
+
+ if (isInteger(s)) {
+ std::stringstream ss(removeDelimiter(s));
+ std::int64_t x;
+ ss >> x;
+ return Token(TokenType::INT, x);
+ }
+
+ if (isDouble(s)) {
+ std::stringstream ss(removeDelimiter(s));
+ double d;
+ ss >> d;
+ return Token(TokenType::DOUBLE, d);
+ }
+
+ return parseAsTime(s);
+}
+
+inline Token Lexer::parseAsTime(const std::string& str)
+{
+ const char* s = str.c_str();
+
+ int n;
+ int YYYY, MM, DD;
+ if (sscanf(s, "%d-%d-%d%n", &YYYY, &MM, &DD, &n) != 3)
+ return Token(TokenType::ERROR, std::string("Invalid token"));
+
+ if (s[n] == '\0') {
+ std::tm t;
+ t.tm_sec = 0;
+ t.tm_min = 0;
+ t.tm_hour = 0;
+ t.tm_mday = DD;
+ t.tm_mon = MM - 1;
+ t.tm_year = YYYY - 1900;
+ auto tp = std::chrono::system_clock::from_time_t(timegm(&t));
+ return Token(TokenType::TIME, tp);
+ }
+
+ if (s[n] != 'T')
+ return Token(TokenType::ERROR, std::string("Invalid token"));
+
+ s = s + n + 1;
+
+ int hh, mm;
+ double ss; // double for fraction
+ if (sscanf(s, "%d:%d:%lf%n", &hh, &mm, &ss, &n) != 3)
+ return Token(TokenType::ERROR, std::string("Invalid token"));
+
+ std::tm t;
+ t.tm_sec = static_cast<int>(ss);
+ t.tm_min = mm;
+ t.tm_hour = hh;
+ t.tm_mday = DD;
+ t.tm_mon = MM - 1;
+ t.tm_year = YYYY - 1900;
+ auto tp = std::chrono::system_clock::from_time_t(timegm(&t));
+ ss -= static_cast<int>(ss);
+ // TODO(mayah): workaround GCC 4.9.3 on cygwin does not have std::round, but round().
+ tp += std::chrono::microseconds(static_cast<std::int64_t>(round(ss * 1000000)));
+
+ if (s[n] == '\0')
+ return Token(TokenType::TIME, tp);
+
+ if (s[n] == 'Z' && s[n + 1] == '\0')
+ return Token(TokenType::TIME, tp);
+
+ s = s + n;
+ // offset
+ // [+/-]%d:%d
+ char pn;
+ int oh, om;
+ if (sscanf(s, "%c%d:%d", &pn, &oh, &om) != 3)
+ return Token(TokenType::ERROR, std::string("Invalid token"));
+
+ if (pn != '+' && pn != '-')
+ return Token(TokenType::ERROR, std::string("Invalid token"));
+
+ if (pn == '+') {
+ tp -= std::chrono::hours(oh);
+ tp -= std::chrono::minutes(om);
+ } else {
+ tp += std::chrono::hours(oh);
+ tp += std::chrono::minutes(om);
+ }
+
+ return Token(TokenType::TIME, tp);
+}
+
+inline Token Lexer::nextKeyToken()
+{
+ return nextToken(false);
+}
+
+inline Token Lexer::nextValueToken()
+{
+ return nextToken(true);
+}
+
+inline Token Lexer::nextToken(bool isValueToken)
+{
+ char c;
+ while (current(&c)) {
+ if (c == ' ' || c == '\t' || c == '\r') {
+ next();
+ continue;
+ }
+
+ if (c == '#') {
+ skipUntilNewLine();
+ continue;
+ }
+
+ switch (c) {
+ case '\n':
+ next();
+ return Token(TokenType::END_OF_LINE);
+ case '=':
+ next();
+ return Token(TokenType::EQUAL);
+ case '{':
+ next();
+ return Token(TokenType::LBRACE);
+ case '}':
+ next();
+ return Token(TokenType::RBRACE);
+ case '[':
+ next();
+ return Token(TokenType::LBRACKET);
+ case ']':
+ next();
+ return Token(TokenType::RBRACKET);
+ case ',':
+ next();
+ return Token(TokenType::COMMA);
+ case '.':
+ next();
+ return Token(TokenType::DOT);
+ case '\"':
+ return nextStringDoubleQuote();
+ case '\'':
+ return nextStringSingleQuote();
+ default:
+ if (isValueToken) {
+ return nextValue();
+ } else {
+ return nextKey();
+ }
+ }
+ }
+
+ return Token(TokenType::END_OF_FILE);
+}
+
+} // namespace internal
+
+// ----------------------------------------------------------------------
+
+// static
+inline const char* Value::typeToString(Value::Type type)
+{
+ switch (type) {
+ case NULL_TYPE: return "null";
+ case BOOL_TYPE: return "bool";
+ case INT_TYPE: return "int";
+ case DOUBLE_TYPE: return "double";
+ case STRING_TYPE: return "string";
+ case TIME_TYPE: return "time";
+ case ARRAY_TYPE: return "array";
+ case TABLE_TYPE: return "table";
+ default: return "unknown";
+ }
+}
+
+inline Value::Value(const Value& v) :
+ type_(v.type_)
+{
+ switch (v.type_) {
+ case NULL_TYPE: null_ = v.null_; break;
+ case BOOL_TYPE: bool_ = v.bool_; break;
+ case INT_TYPE: int_ = v.int_; break;
+ case DOUBLE_TYPE: double_ = v.double_; break;
+ case STRING_TYPE: string_ = new std::string(*v.string_); break;
+ case TIME_TYPE: time_ = new Time(*v.time_); break;
+ case ARRAY_TYPE: array_ = new Array(*v.array_); break;
+ case TABLE_TYPE: table_ = new Table(*v.table_); break;
+ default:
+ assert(false);
+ type_ = NULL_TYPE;
+ null_ = nullptr;
+ }
+}
+
+inline Value::Value(Value&& v) noexcept :
+ type_(v.type_)
+{
+ switch (v.type_) {
+ case NULL_TYPE: null_ = v.null_; break;
+ case BOOL_TYPE: bool_ = v.bool_; break;
+ case INT_TYPE: int_ = v.int_; break;
+ case DOUBLE_TYPE: double_ = v.double_; break;
+ case STRING_TYPE: string_ = v.string_; break;
+ case TIME_TYPE: time_ = v.time_; break;
+ case ARRAY_TYPE: array_ = v.array_; break;
+ case TABLE_TYPE: table_ = v.table_; break;
+ default:
+ assert(false);
+ type_ = NULL_TYPE;
+ null_ = nullptr;
+ }
+
+ v.type_ = NULL_TYPE;
+ v.null_ = nullptr;
+}
+
+inline Value& Value::operator=(const Value& v)
+{
+ if (this == &v)
+ return *this;
+
+ this->~Value();
+
+ type_ = v.type_;
+ switch (v.type_) {
+ case NULL_TYPE: null_ = v.null_; break;
+ case BOOL_TYPE: bool_ = v.bool_; break;
+ case INT_TYPE: int_ = v.int_; break;
+ case DOUBLE_TYPE: double_ = v.double_; break;
+ case STRING_TYPE: string_ = new std::string(*v.string_); break;
+ case TIME_TYPE: time_ = new Time(*v.time_); break;
+ case ARRAY_TYPE: array_ = new Array(*v.array_); break;
+ case TABLE_TYPE: table_ = new Table(*v.table_); break;
+ default:
+ assert(false);
+ type_ = NULL_TYPE;
+ null_ = nullptr;
+ }
+
+ return *this;
+}
+
+inline Value& Value::operator=(Value&& v) noexcept
+{
+ if (this == &v)
+ return *this;
+
+ this->~Value();
+
+ type_ = v.type_;
+ switch (v.type_) {
+ case NULL_TYPE: null_ = v.null_; break;
+ case BOOL_TYPE: bool_ = v.bool_; break;
+ case INT_TYPE: int_ = v.int_; break;
+ case DOUBLE_TYPE: double_ = v.double_; break;
+ case STRING_TYPE: string_ = v.string_; break;
+ case TIME_TYPE: time_ = v.time_; break;
+ case ARRAY_TYPE: array_ = v.array_; break;
+ case TABLE_TYPE: table_ = v.table_; break;
+ default:
+ assert(false);
+ type_ = NULL_TYPE;
+ null_ = nullptr;
+ }
+
+ v.type_ = NULL_TYPE;
+ v.null_ = nullptr;
+ return *this;
+}
+
+inline Value::~Value()
+{
+ switch (type_) {
+ case STRING_TYPE:
+ delete string_;
+ break;
+ case TIME_TYPE:
+ delete time_;
+ break;
+ case ARRAY_TYPE:
+ delete array_;
+ break;
+ case TABLE_TYPE:
+ delete table_;
+ break;
+ default:
+ break;
+ }
+}
+
+inline size_t Value::size() const
+{
+ switch (type_) {
+ case NULL_TYPE:
+ return 0;
+ case ARRAY_TYPE:
+ return array_->size();
+ case TABLE_TYPE:
+ return table_->size();
+ default:
+ return 1;
+ }
+}
+
+inline bool Value::empty() const
+{
+ return size() == 0;
+}
+
+template<> struct Value::ValueConverter<bool>
+{
+ bool is(const Value& v) { return v.type() == Value::BOOL_TYPE; }
+ bool to(const Value& v) { v.assureType<bool>(); return v.bool_; }
+
+};
+template<> struct Value::ValueConverter<int64_t>
+{
+ bool is(const Value& v) { return v.type() == Value::INT_TYPE; }
+ int64_t to(const Value& v) { v.assureType<int64_t>(); return v.int_; }
+};
+template<> struct Value::ValueConverter<int>
+{
+ bool is(const Value& v) { return v.type() == Value::INT_TYPE; }
+ int to(const Value& v) { v.assureType<int>(); return static_cast<int>(v.int_); }
+};
+template<> struct Value::ValueConverter<double>
+{
+ bool is(const Value& v) { return v.type() == Value::DOUBLE_TYPE; }
+ double to(const Value& v) { v.assureType<double>(); return v.double_; }
+};
+template<> struct Value::ValueConverter<std::string>
+{
+ bool is(const Value& v) { return v.type() == Value::STRING_TYPE; }
+ const std::string& to(const Value& v) { v.assureType<std::string>(); return *v.string_; }
+};
+template<> struct Value::ValueConverter<Time>
+{
+ bool is(const Value& v) { return v.type() == Value::TIME_TYPE; }
+ const Time& to(const Value& v) { v.assureType<Time>(); return *v.time_; }
+};
+template<> struct Value::ValueConverter<Array>
+{
+ bool is(const Value& v) { return v.type() == Value::ARRAY_TYPE; }
+ const Array& to(const Value& v) { v.assureType<Array>(); return *v.array_; }
+};
+template<> struct Value::ValueConverter<Table>
+{
+ bool is(const Value& v) { return v.type() == Value::TABLE_TYPE; }
+ const Table& to(const Value& v) { v.assureType<Table>(); return *v.table_; }
+};
+
+template<typename T>
+struct Value::ValueConverter<std::vector<T>>
+{
+ bool is(const Value& v)
+ {
+ if (v.type() != Value::ARRAY_TYPE)
+ return false;
+ const Array& array = v.as<Array>();
+ if (array.empty())
+ return true;
+ return array.front().is<T>();
+ }
+
+ std::vector<T> to(const Value& v)
+ {
+ const Array& array = v.as<Array>();
+ if (array.empty())
+ return std::vector<T>();
+ array.front().assureType<T>();
+
+ std::vector<T> result;
+ for (const auto& element : array) {
+ result.push_back(element.as<T>());
+ }
+
+ return result;
+ }
+};
+
+namespace internal {
+template<typename T> inline const char* type_name();
+template<> inline const char* type_name<bool>() { return "bool"; }
+template<> inline const char* type_name<int>() { return "int"; }
+template<> inline const char* type_name<int64_t>() { return "int64_t"; }
+template<> inline const char* type_name<double>() { return "double"; }
+template<> inline const char* type_name<std::string>() { return "string"; }
+template<> inline const char* type_name<toml::Time>() { return "time"; }
+template<> inline const char* type_name<toml::Array>() { return "array"; }
+template<> inline const char* type_name<toml::Table>() { return "table"; }
+} // namespace internal
+
+template<typename T>
+inline void Value::assureType() const
+{
+ if (!is<T>())
+ failwith("type error: this value is ", typeToString(type_), " but ", internal::type_name<T>(), " was requested");
+}
+
+template<typename T>
+inline bool Value::is() const
+{
+ return ValueConverter<T>().is(*this);
+}
+
+template<typename T>
+inline typename call_traits<T>::return_type Value::as() const
+{
+ return ValueConverter<T>().to(*this);
+}
+
+inline bool Value::isNumber() const
+{
+ return is<int>() || is<double>();
+}
+
+inline double Value::asNumber() const
+{
+ if (is<int>())
+ return as<int>();
+ if (is<double>())
+ return as<double>();
+
+ failwith("type error: this value is ", typeToString(type_), " but number is requested");
+}
+
+inline std::time_t Value::as_time_t() const
+{
+ return std::chrono::system_clock::to_time_t(as<Time>());
+}
+
+inline std::string Value::spaces(int num)
+{
+ if (num <= 0)
+ return std::string();
+
+ return std::string(num, ' ');
+}
+
+inline std::string Value::escapeKey(const std::string& key)
+{
+ auto position = std::find_if(key.begin(), key.end(), [](char c) -> bool {
+ if (std::isalnum(c) || c == '_' || c == '-')
+ return false;
+ return true;
+ });
+
+ if (position != key.end()) {
+ std::string escaped = "\"";
+ for (const char& c : key) {
+ if (c == '\\' || c == '"')
+ escaped += '\\';
+ escaped += c;
+ }
+ escaped += "\"";
+
+ return escaped;
+ }
+
+ return key;
+}
+
+inline void Value::write(std::ostream* os, const std::string& keyPrefix, int indent) const
+{
+ switch (type_) {
+ case NULL_TYPE:
+ failwith("null type value is not a valid value");
+ break;
+ case BOOL_TYPE:
+ (*os) << (bool_ ? "true" : "false");
+ break;
+ case INT_TYPE:
+ (*os) << int_;
+ break;
+ case DOUBLE_TYPE: {
+ (*os) << std::fixed << std::showpoint << double_;
+ break;
+ }
+ case STRING_TYPE:
+ (*os) << '"' << internal::escapeString(*string_) << '"';
+ break;
+ case TIME_TYPE: {
+ time_t tt = std::chrono::system_clock::to_time_t(*time_);
+ std::tm t;
+ gmtime_r(&tt, &t);
+ char buf[256];
+ sprintf(buf, "%04d-%02d-%02dT%02d:%02d:%02dZ", t.tm_year + 1900, t.tm_mon + 1, t.tm_mday, t.tm_hour, t.tm_min, t.tm_sec);
+ (*os) << buf;
+ break;
+ }
+ case ARRAY_TYPE:
+ (*os) << '[';
+ for (size_t i = 0; i < array_->size(); ++i) {
+ if (i)
+ (*os) << ", ";
+ (*array_)[i].write(os, keyPrefix, -1);
+ }
+ (*os) << ']';
+ break;
+ case TABLE_TYPE:
+ for (const auto& kv : *table_) {
+ if (kv.second.is<Table>())
+ continue;
+ if (kv.second.is<Array>() && kv.second.size() > 0 && kv.second.find(0)->is<Table>())
+ continue;
+ (*os) << spaces(indent) << escapeKey(kv.first) << " = ";
+ kv.second.write(os, keyPrefix, indent >= 0 ? indent + 1 : indent);
+ (*os) << '\n';
+ }
+ for (const auto& kv : *table_) {
+ if (kv.second.is<Table>()) {
+ std::string key(keyPrefix);
+ if (!keyPrefix.empty())
+ key += ".";
+ key += escapeKey(kv.first);
+ (*os) << "\n" << spaces(indent) << "[" << key << "]\n";
+ kv.second.write(os, key, indent >= 0 ? indent + 1 : indent);
+ }
+ if (kv.second.is<Array>() && kv.second.size() > 0 && kv.second.find(0)->is<Table>()) {
+ std::string key(keyPrefix);
+ if (!keyPrefix.empty())
+ key += ".";
+ key += escapeKey(kv.first);
+ for (const auto& v : kv.second.as<Array>()) {
+ (*os) << "\n" << spaces(indent) << "[[" << key << "]]\n";
+ v.write(os, key, indent >= 0 ? indent + 1 : indent);
+ }
+ }
+ }
+ break;
+ default:
+ failwith("writing unknown type");
+ break;
+ }
+}
+
+inline void Value::writeFormatted(std::ostream* os, FormatFlag flags) const
+{
+ int indent = flags & FORMAT_INDENT ? 0 : -1;
+
+ write(os, std::string(), indent);
+}
+
+// static
+inline FormatFlag operator|(FormatFlag lhs, FormatFlag rhs)
+{
+ return static_cast<FormatFlag>(static_cast<int>(lhs) | static_cast<int>(rhs));
+}
+
+// static
+inline std::ostream& operator<<(std::ostream& os, const toml::Value& v)
+{
+ v.write(&os);
+ return os;
+}
+
+// static
+inline bool operator==(const Value& lhs, const Value& rhs)
+{
+ if (lhs.type() != rhs.type())
+ return false;
+
+ switch (lhs.type()) {
+ case Value::Type::NULL_TYPE:
+ return true;
+ case Value::Type::BOOL_TYPE:
+ return lhs.bool_ == rhs.bool_;
+ case Value::Type::INT_TYPE:
+ return lhs.int_ == rhs.int_;
+ case Value::Type::DOUBLE_TYPE:
+ return lhs.double_ == rhs.double_;
+ case Value::Type::STRING_TYPE:
+ return *lhs.string_ == *rhs.string_;
+ case Value::Type::TIME_TYPE:
+ return *lhs.time_ == *rhs.time_;
+ case Value::Type::ARRAY_TYPE:
+ return *lhs.array_ == *rhs.array_;
+ case Value::Type::TABLE_TYPE:
+ return *lhs.table_ == *rhs.table_;
+ default:
+ failwith("unknown type");
+ }
+}
+
+template<typename T>
+inline typename call_traits<T>::return_type Value::get(const std::string& key) const
+{
+ if (!is<Table>())
+ failwith("type must be table to do get(key).");
+
+ const Value* obj = find(key);
+ if (!obj)
+ failwith("key ", key, " was not found.");
+
+ return obj->as<T>();
+}
+
+inline const Value* Value::find(const std::string& key) const
+{
+ if (!is<Table>())
+ return nullptr;
+
+ std::istringstream ss(key);
+ internal::Lexer lexer(ss);
+
+ const Value* current = this;
+ while (true) {
+ internal::Token t = lexer.nextKeyToken();
+ if (!(t.type() == internal::TokenType::IDENT || t.type() == internal::TokenType::STRING))
+ return nullptr;
+
+ std::string part = t.strValue();
+ t = lexer.nextKeyToken();
+ if (t.type() == internal::TokenType::DOT) {
+ current = current->findChild(part);
+ if (!current || !current->is<Table>())
+ return nullptr;
+ } else if (t.type() == internal::TokenType::END_OF_FILE) {
+ return current->findChild(part);
+ } else {
+ return nullptr;
+ }
+ }
+}
+
+inline Value* Value::find(const std::string& key)
+{
+ return const_cast<Value*>(const_cast<const Value*>(this)->find(key));
+}
+
+inline bool Value::merge(const toml::Value& v)
+{
+ if (this == &v)
+ return true;
+ if (!is<Table>() || !v.is<Table>())
+ return false;
+
+ for (const auto& kv : *v.table_) {
+ if (Value* tmp = find(kv.first)) {
+ // If both are table, we merge them.
+ if (tmp->is<Table>() && kv.second.is<Table>()) {
+ if (!tmp->merge(kv.second))
+ return false;
+ } else {
+ setChild(kv.first, kv.second);
+ }
+ } else {
+ setChild(kv.first, kv.second);
+ }
+ }
+
+ return true;
+}
+
+inline Value* Value::set(const std::string& key, const Value& v)
+{
+ Value* result = ensureValue(key);
+ *result = v;
+ return result;
+}
+
+inline Value* Value::setChild(const std::string& key, const Value& v)
+{
+ if (!valid())
+ *this = Value((Table()));
+
+ if (!is<Table>())
+ failwith("type must be table to do set(key, v).");
+
+ (*table_)[key] = v;
+ return &(*table_)[key];
+}
+
+inline Value* Value::setChild(const std::string& key, Value&& v)
+{
+ if (!valid())
+ *this = Value((Table()));
+
+ if (!is<Table>())
+ failwith("type must be table to do set(key, v).");
+
+ (*table_)[key] = std::move(v);
+ return &(*table_)[key];
+}
+
+inline bool Value::erase(const std::string& key)
+{
+ if (!is<Table>())
+ return false;
+
+ std::istringstream ss(key);
+ internal::Lexer lexer(ss);
+
+ Value* current = this;
+ while (true) {
+ internal::Token t = lexer.nextKeyToken();
+ if (!(t.type() == internal::TokenType::IDENT || t.type() == internal::TokenType::STRING))
+ return false;
+
+ std::string part = t.strValue();
+ t = lexer.nextKeyToken();
+ if (t.type() == internal::TokenType::DOT) {
+ current = current->findChild(part);
+ if (!current || !current->is<Table>())
+ return false;
+ } else if (t.type() == internal::TokenType::END_OF_FILE) {
+ return current->eraseChild(part);
+ } else {
+ return false;
+ }
+ }
+}
+
+inline bool Value::eraseChild(const std::string& key)
+{
+ if (!is<Table>())
+ failwith("type must be table to do erase(key).");
+
+ return table_->erase(key) > 0;
+}
+
+inline Value& Value::operator[](const std::string& key)
+{
+ if (!valid())
+ *this = Value((Table()));
+
+ if (Value* v = findChild(key))
+ return *v;
+
+ return *setChild(key, Value());
+}
+
+template<typename T>
+inline typename call_traits<T>::return_type Value::get(size_t index) const
+{
+ if (!is<Array>())
+ failwith("type must be array to do get(index).");
+
+ if (array_->size() <= index)
+ failwith("index out of bound");
+
+ return (*array_)[index].as<T>();
+}
+
+inline const Value* Value::find(size_t index) const
+{
+ if (!is<Array>())
+ return nullptr;
+ if (index < array_->size())
+ return &(*array_)[index];
+ return nullptr;
+}
+
+inline Value* Value::find(size_t index)
+{
+ return const_cast<Value*>(const_cast<const Value*>(this)->find(index));
+}
+
+inline Value* Value::push(const Value& v)
+{
+ if (!valid())
+ *this = Value((Array()));
+ else if (!is<Array>())
+ failwith("type must be array to do push(Value).");
+
+ array_->push_back(v);
+ return &array_->back();
+}
+
+inline Value* Value::push(Value&& v)
+{
+ if (!valid())
+ *this = Value((Array()));
+ else if (!is<Array>())
+ failwith("type must be array to do push(Value).");
+
+ array_->push_back(std::move(v));
+ return &array_->back();
+}
+
+inline Value* Value::ensureValue(const std::string& key)
+{
+ if (!valid())
+ *this = Value((Table()));
+ if (!is<Table>()) {
+ failwith("encountered non table value");
+ }
+
+ std::istringstream ss(key);
+ internal::Lexer lexer(ss);
+
+ Value* current = this;
+ while (true) {
+ internal::Token t = lexer.nextKeyToken();
+ if (!(t.type() == internal::TokenType::IDENT || t.type() == internal::TokenType::STRING)) {
+ failwith("invalid key");
+ }
+
+ std::string part = t.strValue();
+ t = lexer.nextKeyToken();
+ if (t.type() == internal::TokenType::DOT) {
+ if (Value* candidate = current->findChild(part)) {
+ if (!candidate->is<Table>())
+ failwith("encountered non table value");
+
+ current = candidate;
+ } else {
+ current = current->setChild(part, Table());
+ }
+ } else if (t.type() == internal::TokenType::END_OF_FILE) {
+ if (Value* v = current->findChild(part))
+ return v;
+ return current->setChild(part, Value());
+ } else {
+ failwith("invalid key");
+ }
+ }
+}
+
+inline Value* Value::findChild(const std::string& key)
+{
+ assert(is<Table>());
+
+ auto it = table_->find(key);
+ if (it == table_->end())
+ return nullptr;
+
+ return &it->second;
+}
+
+inline const Value* Value::findChild(const std::string& key) const
+{
+ assert(is<Table>());
+
+ auto it = table_->find(key);
+ if (it == table_->end())
+ return nullptr;
+
+ return &it->second;
+}
+
+// ----------------------------------------------------------------------
+
+namespace internal {
+
+inline void Parser::skipForKey()
+{
+ while (token().type() == TokenType::END_OF_LINE)
+ nextKey();
+}
+
+inline void Parser::skipForValue()
+{
+ while (token().type() == TokenType::END_OF_LINE)
+ nextValue();
+}
+
+inline bool Parser::consumeForKey(TokenType type)
+{
+ if (token().type() == type) {
+ nextKey();
+ return true;
+ }
+
+ return false;
+}
+
+inline bool Parser::consumeForValue(TokenType type)
+{
+ if (token().type() == type) {
+ nextValue();
+ return true;
+ }
+
+ return false;
+}
+
+inline bool Parser::consumeEOLorEOFForKey()
+{
+ if (token().type() == TokenType::END_OF_LINE || token().type() == TokenType::END_OF_FILE) {
+ nextKey();
+ return true;
+ }
+
+ return false;
+}
+
+inline void Parser::addError(const std::string& reason)
+{
+ if (!errorReason_.empty())
+ return;
+
+ std::stringstream ss;
+ ss << "Error: line " << lexer_.lineNo() << ": " << reason;
+ errorReason_ = ss.str();
+}
+
+inline const std::string& Parser::errorReason()
+{
+ return errorReason_;
+}
+
+inline Value Parser::parse()
+{
+ Value root((Table()));
+ Value* currentValue = &root;
+
+ while (true) {
+ skipForKey();
+ if (token().type() == TokenType::END_OF_FILE)
+ break;
+ if (token().type() == TokenType::LBRACKET) {
+ currentValue = parseGroupKey(&root);
+ if (!currentValue) {
+ addError("error when parsing group key");
+ return Value();
+ }
+ continue;
+ }
+
+ if (!parseKeyValue(currentValue)) {
+ addError("error when parsing key Value");
+ return Value();
+ }
+ }
+ return root;
+}
+
+inline Value* Parser::parseGroupKey(Value* root)
+{
+ if (!consumeForKey(TokenType::LBRACKET))
+ return nullptr;
+
+ bool isArray = false;
+ if (token().type() == TokenType::LBRACKET) {
+ nextKey();
+ isArray = true;
+ }
+
+ Value* currentValue = root;
+ while (true) {
+ if (token().type() != TokenType::IDENT && token().type() != TokenType::STRING)
+ return nullptr;
+
+ std::string key = token().strValue();
+ nextKey();
+
+ if (token().type() == TokenType::DOT) {
+ nextKey();
+ if (Value* candidate = currentValue->findChild(key)) {
+ if (candidate->is<Array>() && candidate->size() > 0)
+ candidate = candidate->find(candidate->size() - 1);
+ if (!candidate->is<Table>())
+ return nullptr;
+ currentValue = candidate;
+ } else {
+ currentValue = currentValue->setChild(key, Table());
+ }
+ continue;
+ }
+
+ if (token().type() == TokenType::RBRACKET) {
+ nextKey();
+ if (Value* candidate = currentValue->findChild(key)) {
+ if (isArray) {
+ if (!candidate->is<Array>())
+ return nullptr;
+ currentValue = candidate->push(Table());
+ } else {
+ if (candidate->is<Array>() && candidate->size() > 0)
+ candidate = candidate->find(candidate->size() - 1);
+ if (!candidate->is<Table>())
+ return nullptr;
+ currentValue = candidate;
+ }
+ } else {
+ if (isArray) {
+ currentValue = currentValue->setChild(key, Array());
+ currentValue = currentValue->push(Table());
+ } else {
+ currentValue = currentValue->setChild(key, Table());
+ }
+ }
+ break;
+ }
+
+ return nullptr;
+ }
+
+ if (isArray) {
+ if (!consumeForKey(TokenType::RBRACKET))
+ return nullptr;
+ }
+
+ if (!consumeEOLorEOFForKey())
+ return nullptr;
+
+ return currentValue;
+}
+
+inline bool Parser::parseKeyValue(Value* current)
+{
+ std::string key;
+ if (!parseKey(&key)) {
+ addError("parse key failed");
+ return false;
+ }
+ if (!consumeForValue(TokenType::EQUAL)) {
+ addError("no equal?");
+ return false;
+ }
+
+ Value v;
+ if (!parseValue(&v))
+ return false;
+ if (!consumeEOLorEOFForKey())
+ return false;
+
+ if (current->has(key)) {
+ addError("Multiple same key: " + key);
+ return false;
+ }
+
+ current->setChild(key, std::move(v));
+ return true;
+}
+
+inline bool Parser::parseKey(std::string* key)
+{
+ key->clear();
+
+ if (token().type() == TokenType::IDENT || token().type() == TokenType::STRING) {
+ *key = token().strValue();
+ nextValue();
+ return true;
+ }
+
+ return false;
+}
+
+inline bool Parser::parseValue(Value* v)
+{
+ switch (token().type()) {
+ case TokenType::STRING:
+ case TokenType::MULTILINE_STRING:
+ *v = token().strValue();
+ nextValue();
+ return true;
+ case TokenType::LBRACKET:
+ return parseArray(v);
+ case TokenType::LBRACE:
+ return parseInlineTable(v);
+ case TokenType::BOOL:
+ *v = token().boolValue();
+ nextValue();
+ return true;
+ case TokenType::INT:
+ *v = token().intValue();
+ nextValue();
+ return true;
+ case TokenType::DOUBLE:
+ *v = token().doubleValue();
+ nextValue();
+ return true;
+ case TokenType::TIME:
+ *v = token().timeValue();
+ nextValue();
+ return true;
+ case TokenType::ERROR:
+ addError(token().strValue());
+ return false;
+ default:
+ addError("unexpected token");
+ return false;
+ }
+}
+
+inline bool Parser::parseBool(Value* v)
+{
+ if (token().strValue() == "true") {
+ nextValue();
+ *v = true;
+ return true;
+ }
+
+ if (token().strValue() == "false") {
+ nextValue();
+ *v = false;
+ return true;
+ }
+
+ return false;
+}
+
+inline bool Parser::parseArray(Value* v)
+{
+ if (!consumeForValue(TokenType::LBRACKET))
+ return false;
+
+ Array a;
+ while (true) {
+ skipForValue();
+
+ if (token().type() == TokenType::RBRACKET)
+ break;
+
+ skipForValue();
+ Value x;
+ if (!parseValue(&x))
+ return false;
+
+ if (!a.empty()) {
+ if (a.front().type() != x.type()) {
+ addError("type check failed");
+ return false;
+ }
+ }
+
+ a.push_back(std::move(x));
+ skipForValue();
+ if (token().type() == TokenType::RBRACKET)
+ break;
+ if (token().type() == TokenType::COMMA)
+ nextValue();
+ }
+
+ if (!consumeForValue(TokenType::RBRACKET))
+ return false;
+ *v = std::move(a);
+ return true;
+}
+
+inline bool Parser::parseInlineTable(Value* value)
+{
+ // For inline table, next is KEY, so use consumeForKey here.
+ if (!consumeForKey(TokenType::LBRACE))
+ return false;
+
+ Value t((Table()));
+ bool first = true;
+ while (true) {
+ if (token().type() == TokenType::RBRACE) {
+ break;
+ }
+
+ if (!first) {
+ if (token().type() != TokenType::COMMA) {
+ addError("inline table didn't have ',' for delimiter?");
+ return false;
+ }
+ nextKey();
+ }
+ first = false;
+
+ std::string key;
+ if (!parseKey(&key))
+ return false;
+ if (!consumeForValue(TokenType::EQUAL))
+ return false;
+ Value v;
+ if (!parseValue(&v))
+ return false;
+
+ if (t.has(key)) {
+ addError("inline table has multiple same keys: key=" + key);
+ return false;
+ }
+
+ t.set(key, v);
+ }
+
+ if (!consumeForValue(TokenType::RBRACE))
+ return false;
+ *value = std::move(t);
+ return true;
+}
+
+} // namespace internal
+} // namespace toml
+
+#endif // TINYTOML_H_
diff --git a/src/browser.cpp b/src/browser.cpp
index a1c097a..f709e1e 100644
--- a/src/browser.cpp
+++ b/src/browser.cpp
@@ -20,7 +20,6 @@
#include "browser.h"
#include "mainwindow.h"
-#include "settings.h"
#include <QtWebEngine>
#include <QMessageBox>
@@ -47,12 +46,11 @@ Browser::~Browser()
*/
void Browser::firstRun()
{
- Settings settings;
- if(settings.allKeys().isEmpty()) {
+ if(m_settings->isEmpty()) {
// There are no keys in the settings
QMessageBox::information(0,
tr("Configuration is empty"),
- tr("The configuration file <i>%1</i> is empty. Using default values").arg(settings.staticFilePath()));
+ tr("The configuration file <i>%1</i> is empty. Using default values").arg(m_settings->filePath()));
}
}
@@ -61,10 +59,8 @@ void Browser::firstRun()
*/
bool Browser::preLaunch(QStringList urls)
{
- Settings settings;
-
- if(settings.value("browser/singleInstance", true).toBool()) {
- QString serverName = settings.value("browser/localSocket", "smolbote-singlelock").toString();
+ if(m_settings->value("browser.singleInstance", true).toBool()) {
+ QString serverName = m_settings->value("browser.localSocket", "smolbote-singlelock").toString();
// Check for other running instance
QLocalSocket socket;
@@ -105,6 +101,11 @@ Browser *Browser::instance()
return static_cast<Browser *>(QCoreApplication::instance());
}
+Settings *Browser::settings()
+{
+ return m_settings;
+}
+
BookmarksDialog *Browser::bookmarks()
{
return m_bookmarksManager;
@@ -119,10 +120,10 @@ void Browser::setConfigPath(const QString &path)
{
if(path.isEmpty()) {
// set default config path
- Settings::setFilePath(QStandardPaths::writableLocation(QStandardPaths::AppConfigLocation) + "/config.ini");
+ m_settings = new Settings(QStandardPaths::writableLocation(QStandardPaths::AppConfigLocation) + "/config.toml");
} else {
// set custom config path
- Settings::setFilePath(path);
+ m_settings = new Settings(path);
}
}
diff --git a/src/browser.h b/src/browser.h
index 3d3b297..0a6702f 100644
--- a/src/browser.h
+++ b/src/browser.h
@@ -26,6 +26,9 @@
#include "forms/bookmarksdialog.h"
#include "forms/downloaddialog.h"
#include <QLocalServer>
+#include "settings.h"
+
+#define sSettings Browser::instance()->settings()
class MainWindow;
class Browser : public QApplication
@@ -41,6 +44,7 @@ public:
static Browser *instance();
+ Settings *settings();
BookmarksDialog *bookmarks();
DownloadDialog *downloads();
@@ -55,6 +59,7 @@ private slots:
void handleNewConnection();
private:
+ Settings *m_settings;
QLocalServer *m_localServer;
QVector<MainWindow*> m_windows;
BookmarksDialog *m_bookmarksManager;
diff --git a/src/forms/blockerdialog.cpp b/src/forms/blockerdialog.cpp
index fa613bd..9c6bd0a 100644
--- a/src/forms/blockerdialog.cpp
+++ b/src/forms/blockerdialog.cpp
@@ -21,7 +21,7 @@
#include "blockerdialog.h"
#include "ui_blockerdialog.h"
-#include "settings.h"
+#include "browser.h"
#include <QLabel>
#include <QListWidget>
@@ -29,11 +29,10 @@ BlockerDialog::BlockerDialog(QWidget *parent) :
QDialog(parent),
ui(new Ui::UrlInterceptorDialog)
{
- Settings settings;
ui->setupUi(this);
m_subscription = new BlockerSubscription(this);
- QString sublocation = settings.value("blocker/subscription").toString();
+ QString sublocation = sSettings->value("blocker.path").toString();
if(!sublocation.isEmpty()) {
m_subscription->loadFromFile(sublocation);
}
diff --git a/src/forms/downloaddialog.cpp b/src/forms/downloaddialog.cpp
index 950fa96..d182076 100644
--- a/src/forms/downloaddialog.cpp
+++ b/src/forms/downloaddialog.cpp
@@ -27,7 +27,7 @@
#include <QListWidget>
#include <QLabel>
#include "webengine/downloaditemform.h"
-#include "settings.h"
+#include "browser.h"
DownloadDialog::DownloadDialog(QWidget *parent) :
QDialog(parent),
@@ -46,9 +46,7 @@ DownloadDialog::~DownloadDialog()
void DownloadDialog::addDownload(QWebEngineDownloadItem *item)
{
- Settings settings;
-
- QString filepath = QFileDialog::getSaveFileName(this, tr("Save"), settings.value("downloads/path").toString() + "/" + QFileInfo(item->path()).fileName());
+ QString filepath = QFileDialog::getSaveFileName(this, tr("Save"), sSettings->value("downloads.path").toString() + "/" + QFileInfo(item->path()).fileName());
if(filepath.isEmpty()) {
// user cancelled the save dialog
diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp
index 32c282d..6870a65 100644
--- a/src/mainwindow.cpp
+++ b/src/mainwindow.cpp
@@ -20,7 +20,6 @@
#include "mainwindow.h"
#include "ui_mainwindow.h"
-#include "settings.h"
#include <QMenu>
#include <QMenuBar>
#include <QCloseEvent>
@@ -42,13 +41,11 @@ MainWindow::MainWindow(QUrl defaultUrl, QWidget *parent) :
urlLineEdit(new UrlLineEdit(navigationToolBar)),
progressBar(new LoadingBar(this))
{
- Settings settings;
-
// Load profile and connect its signals
- loadProfile(settings.value("profile").toString());
+ loadProfile(sSettings->value("general.profile").toString());
ui->setupUi(this);
- resize(settings.value("window/width", 800).toInt(), settings.value("window/height", 600).toInt());
+ resize(sSettings->value("window.width", 800).toInt(), sSettings->value("window.height", 600).toInt());
// Browser menu
QMenu *browserMenu = new QMenu(qApp->applicationName(), ui->menuBar);
@@ -64,9 +61,9 @@ MainWindow::MainWindow(QUrl defaultUrl, QWidget *parent) :
QMenu *toolsMenu = new QMenu(tr("Tools"), ui->menuBar);
ui->menuBar->addMenu(toolsMenu);
QAction *downloadsAction = toolsMenu->addAction(tr("Downloads"), Browser::instance()->downloads(), SLOT(show()));
- downloadsAction->setShortcut(QKeySequence::fromString(settings.value("downloads/dialogShortcut").toString()));
+ downloadsAction->setShortcut(QKeySequence::fromString(sSettings->value("downloads.dialogShortcut").toString()));
QAction *bookmarksAction = toolsMenu->addAction(tr("Bookmarks"), Browser::instance()->bookmarks(), SLOT(show()));
- bookmarksAction->setShortcut(QKeySequence(settings.value("bookmarks/dialogShortcut").toString()));
+ bookmarksAction->setShortcut(QKeySequence(sSettings->value("bookmarks.dialogShortcut").toString()));
toolsMenu->addSeparator();
toolsMenu->addAction(tr("Blocker"), blocklistManager, SLOT(show()));
@@ -78,10 +75,10 @@ MainWindow::MainWindow(QUrl defaultUrl, QWidget *parent) :
//profileMenu->addAction(tr("Settings"));
//profileMenu->addAction(tr("Cookies"));
- navigationToolBar->setMovable(settings.value("ui/navtoolbarMovable", true).toBool());
+ navigationToolBar->setMovable(sSettings->value("ui.navtoolbarMovable", true).toBool());
this->addToolBar(Qt::TopToolBarArea, navigationToolBar);
this->addToolBarBreak(Qt::TopToolBarArea);
- tabToolBar->setMovable(settings.value("ui/tabtoolbarMovable", true).toBool());
+ tabToolBar->setMovable(sSettings->value("ui.tabtoolbarMovable", true).toBool());
this->addToolBar(Qt::TopToolBarArea, tabToolBar);
navigationToolBar->addWidget(urlLineEdit);
@@ -95,14 +92,14 @@ MainWindow::MainWindow(QUrl defaultUrl, QWidget *parent) :
// shortcuts
QAction *focusAddressAction = new QAction(this);
- focusAddressAction->setShortcut(QKeySequence::fromString(settings.value("shortcuts/focusAddress").toString()));
+ focusAddressAction->setShortcut(QKeySequence::fromString(sSettings->value("shortcuts.focusAddress").toString()));
connect(focusAddressAction, SIGNAL(triggered(bool)), this, SLOT(focusAddress()));
addAction(focusAddressAction);
if(!defaultUrl.isEmpty()) {
addNewTab(defaultUrl);
} else {
- addNewTab(settings.value("homepage", QUrl("about:blank")).toUrl());
+ addNewTab(sSettings->value("general.homepage", QUrl("about:blank")).toUrl());
}
}
@@ -124,8 +121,7 @@ void MainWindow::addNewTab(const QUrl &url)
if(!url.isEmpty()) {
tabBar->addTab(m_profile, url);
} else {
- Settings settings;
- tabBar->addTab(m_profile, settings.value("newtab").toUrl());
+ tabBar->addTab(m_profile, sSettings->value("general.newtab").toUrl());
}
}
@@ -155,7 +151,7 @@ void MainWindow::about()
"<p>This program comes with ABSOLUTELY NO WARRANTY. "
"This is free software, and you are welcome to redistribute it under the conditions set by the GNU GPLv3.</p>"
"<p>Configuration lives in: %2</p>")
- .arg(qApp->applicationVersion()).arg(Settings::staticFilePath()));
+ .arg(qApp->applicationVersion()).arg(sSettings->filePath()));
}
void MainWindow::loadProfile(const QString &name)
@@ -226,8 +222,7 @@ void MainWindow::handleUrlChanged()
void MainWindow::handleTitleUpdated(const QString &title)
{
- Settings settings;
- setWindowTitle(settings.value("window/title").toString().replace("$title", title).replace("$profile", m_profile->storageName()));
+ setWindowTitle(sSettings->value("window.title").toString().replace("$title", title).replace("$profile", m_profile->storageName()));
}
void MainWindow::execProfileEditor()
diff --git a/src/settings.cpp b/src/settings.cpp
index f030dfd..edbecc0 100644
--- a/src/settings.cpp
+++ b/src/settings.cpp
@@ -21,41 +21,85 @@
#include "settings.h"
#include <QStandardPaths>
-QString Settings::_path = QString("");
+#include <fstream>
-Settings::Settings(QObject *parent) :
- QSettings(_path, QSettings::IniFormat, parent)
+Settings::Settings(const QString &configFile)
{
- setIniCodec("UTF-8");
+ path = configFile;
+ std::ifstream ifs(configFile.toStdString().c_str());
+ toml::ParseResult pr = toml::parse(ifs);
+
+ if(!pr.valid()) {
+ qWarning("Invalid configuration: %s", pr.errorReason.c_str());
+ return;
+ }
+
+ v = pr.value;
}
-void Settings::setFilePath(const QString &path)
+Settings::~Settings()
{
- qDebug("config=[%s]", qUtf8Printable(path));
- _path = path;
}
-QString Settings::staticFilePath()
+QString Settings::filePath() const
{
- return _path;
+ return path;
+}
+
+bool Settings::isEmpty() const
+{
+ return v.empty();
+}
+
+bool Settings::contains(const QString &key)
+{
+ const toml::Value *x = v.find(key.toStdString());
+ if(x) {
+ return true;
+ } else {
+ return false;
+ }
}
QVariant Settings::value(const QString &key, const QVariant &defaultValue) const
{
- // get the actual value
- QString value = QSettings::value(key, defaultValue).toString();
-
- // check if there is a reference
- QRegularExpressionMatch referenceMatch = referencePattern.match(value);
- if(referenceMatch.hasMatch()) {
- QString pattern = referenceMatch.capturedTexts().first();
- value.replace(pattern, this->value(pattern.mid(1, pattern.length()-2)).toString());
+ const toml::Value *x = v.find(key.toStdString());
+
+ // check if value exists
+ if(!x) {
+ return defaultValue;
+ }
+
+ QVariant r;
+ switch (x->type()) {
+ case toml::Value::NULL_TYPE:
+ r = defaultValue;
+ break;
+
+ case toml::Value::BOOL_TYPE:
+ r = QVariant(x->as<bool>());
+ break;
+
+ case toml::Value::INT_TYPE:
+ r = QVariant(x->as<int>());
+ break;
+
+ case toml::Value::STRING_TYPE:
+ r = QVariant(QString::fromStdString(x->as<std::string>()));
+ break;
+
+ default:
+ qWarning("Unhandled type in configuration");
+ r = defaultValue;
+ break;
}
// check if key is a path, in which case replace '~' with the home location
if(key.endsWith(QLatin1String("path"), Qt::CaseInsensitive)) {
+ QString value = r.toString();
value.replace('~', QStandardPaths::writableLocation(QStandardPaths::HomeLocation));
+ r = QVariant(value);
}
- return QVariant(value);
+ return r;
}
diff --git a/src/settings.h b/src/settings.h
index 01eff6b..57d868c 100644
--- a/src/settings.h
+++ b/src/settings.h
@@ -21,28 +21,23 @@
#ifndef SETTINGS_H
#define SETTINGS_H
-#include <QSettings>
-#include <QRegularExpression>
+#include <toml/toml.h>
+#include <QVariant>
-class Settings : public QSettings
+class Settings
{
- Q_OBJECT
public:
- explicit Settings(QObject *parent = 0);
-
- static void setFilePath(const QString &path);
- static QString staticFilePath();
+ Settings(const QString &configFile);
+ ~Settings();
+ QString filePath() const;
+ bool isEmpty() const;
+ bool contains(const QString &key);
QVariant value(const QString &key, const QVariant &defaultValue = QVariant()) const;
- //void setValue(const QString &key, const QVariant &value);
-
-signals:
-
-public slots:
private:
- static QString _path;
- const QRegularExpression referencePattern = QRegularExpression("~[\\w/]+~");
+ toml::Value v;
+ QString path;
};
#endif // SETTINGS_H
diff --git a/src/widgets/webviewtabbar.cpp b/src/widgets/webviewtabbar.cpp
index db44bc9..745a7a5 100644
--- a/src/widgets/webviewtabbar.cpp
+++ b/src/widgets/webviewtabbar.cpp
@@ -19,7 +19,7 @@
******************************************************************************/
#include "webviewtabbar.h"
-#include "settings.h"
+#include "browser.h"
#include <QAction>
WebViewTabBar::WebViewTabBar(QWidget *parent) :
@@ -32,26 +32,25 @@ WebViewTabBar::WebViewTabBar(QWidget *parent) :
connect(this, SIGNAL(currentChanged(int)), this, SLOT(handleCurrentChanged(int)));
connect(this, SIGNAL(tabMoved(int,int)), this, SLOT(updateVectorArrangement(int,int)));
- Settings settings;
- if(settings.contains("shortcuts/tabClose")) {
+ if(sSettings->contains("shortcuts.tabClose")) {
QAction *tabCloseAction = new QAction(this);
- tabCloseAction->setShortcut(QKeySequence::fromString(settings.value("shortcuts/tabClose").toString()));
+ tabCloseAction->setShortcut(QKeySequence::fromString(sSettings->value("shortcuts.tabClose").toString()));
connect(tabCloseAction, &QAction::triggered, [this]() {
this->handleTabClose(currentIndex());
});
addAction(tabCloseAction);
}
- if(settings.contains("shortcuts/tabLeft")) {
+ if(sSettings->contains("shortcuts.tabLeft")) {
QAction *tabLeftAction = new QAction(this);
- tabLeftAction->setShortcut(QKeySequence::fromString(settings.value("shortcuts/tabLeft").toString()));
+ tabLeftAction->setShortcut(QKeySequence::fromString(sSettings->value("shortcuts.tabLeft").toString()));
connect(tabLeftAction, &QAction::triggered, [this]() {
this->setCurrentIndex(currentIndex()-1);
});
addAction(tabLeftAction);
}
- if(settings.contains("shortcuts/tabRight")) {
+ if(sSettings->contains("shortcuts.tabRight")) {
QAction *tabRightAction = new QAction(this);
- tabRightAction->setShortcut(QKeySequence::fromString(settings.value("shortcuts/tabRight").toString()));
+ tabRightAction->setShortcut(QKeySequence::fromString(sSettings->value("shortcuts.tabRight").toString()));
connect(tabRightAction, &QAction::triggered, [this]() {
this->setCurrentIndex(currentIndex()+1);
});
diff --git a/test/config.ini b/test/config.ini
deleted file mode 100644
index ea5c258..0000000
--- a/test/config.ini
+++ /dev/null
@@ -1,34 +0,0 @@
-[browser]
-singleInstance=true
-localSocket=smolbote-singlelock
-
-[general]
-homepage=https://duckduckgo.com
-newtab=about:blank
-profile=Default
-
-[blocker]
-subscription=
-
-[bookmarks]
-dialogShortcut=Ctrl+Shift+B
-path=bookmarks.xbel
-
-[downloads]
-dialogShortcut=Ctrl+Shift+D
-path=~/Downloads
-
-[window]
-height=720
-width=1280
-title=$title — smolbote [$profile]
-
-[shortcuts]
-focusAddress=F4
-tabClose=Ctrl+X
-tabLeft=Shift+Left
-tabRight=Shift+Right
-
-[ui]
-navtoolbarMovable=false
-tabtoolbarMovable=false
diff --git a/test/config.toml b/test/config.toml
new file mode 100644
index 0000000..747282b
--- /dev/null
+++ b/test/config.toml
@@ -0,0 +1,36 @@
+[browser]
+singleInstance=true
+localSocket="smolbote-singlelock"
+
+[general]
+homepage="https://duckduckgo.com"
+newtab="about:blank"
+profile="Default"
+
+[blocker]
+dialogShortcut="Ctrl+Shift+A"
+path=""
+subscriptions=""
+
+[bookmarks]
+dialogShortcut="Ctrl+Shift+B"
+path="bookmarks.xbel"
+
+[downloads]
+dialogShortcut="Ctrl+Shift+D"
+path="~/Downloads"
+
+[window]
+height=720
+width=1280
+title="$title — smolbote [$profile]"
+
+[shortcuts]
+focusAddress="F4"
+tabClose="Ctrl+X"
+tabLeft="Shift+Left"
+tabRight="Shift+Right"
+
+[ui]
+navtoolbarMovable=false
+tabtoolbarMovable=false