#include #include #include #include #include #include #include #include #include #include #include #include #include namespace newsbeuter { configcontainer::configcontainer() // create the config options and set their resp. default value and type : config_data { { "always-display-description", configdata("false", configdata_t::BOOL) }, { "article-sort-order", configdata("date-asc", configdata_t::STR) }, { "articlelist-format", configdata("%4i %f %D %6L %?T?|%-17T| &?%t", configdata_t::STR) }, { "auto-reload", configdata("no", configdata_t::BOOL) }, { "bookmark-autopilot", configdata("false", configdata_t::BOOL) }, { "bookmark-cmd", configdata("", configdata_t::STR) }, { "bookmark-interactive", configdata("false", configdata_t::BOOL) }, { "browser", configdata("lynx", configdata_t::PATH) }, { "cache-file", configdata("", configdata_t::PATH) }, { "cleanup-on-quit", configdata("yes", configdata_t::BOOL) }, { "confirm-exit", configdata("no", configdata_t::BOOL) }, { "cookie-cache", configdata("", configdata_t::PATH) }, { "datetime-format", configdata("%b %d", configdata_t::STR) }, { "delete-read-articles-on-quit", configdata("false", configdata_t::BOOL) }, { "display-article-progress", configdata("yes", configdata_t::BOOL) }, { "download-full-page", configdata("false", configdata_t::BOOL) }, { "download-path", configdata("~/", configdata_t::PATH) }, { "download-retries", configdata("1", configdata_t::INT) }, { "download-timeout", configdata("30", configdata_t::INT) }, { "error-log", configdata("", configdata_t::PATH) }, { "external-url-viewer", configdata("", configdata_t::PATH) }, { "feed-sort-order", configdata("none-desc", configdata_t::STR) }, { "feedhq-flag-share", configdata("", configdata_t::STR) }, { "feedhq-flag-star", configdata("", configdata_t::STR) }, { "feedhq-login", configdata("", configdata_t::STR) }, { "feedhq-min-items", configdata("20", configdata_t::INT) }, { "feedhq-password", configdata("", configdata_t::STR) }, { "feedhq-passwordfile", configdata("", configdata_t::PATH) }, { "feedhq-passwordeval", configdata("", configdata_t::STR) }, { "feedhq-show-special-feeds", configdata("true", configdata_t::BOOL) }, { "feedhq-url", configdata("https://feedhq.org/", configdata_t::STR) }, { "feedlist-format", configdata("%4i %n %11u %t", configdata_t::STR) }, { "goto-first-unread", configdata("true", configdata_t::BOOL) }, { "goto-next-feed", configdata("yes", configdata_t::BOOL) }, { "history-limit", configdata("100", configdata_t::INT) }, { "html-renderer", configdata("internal", configdata_t::PATH) }, { "http-auth-method", configdata("any", std::unordered_set({ "any", "basic", "digest", "digest_ie", "gssnegotiate", "ntlm", "anysafe" })) }, { "ignore-mode", configdata("download", std::unordered_set({ "download", "display" })) }, { "keep-articles-days", configdata("0", configdata_t::INT) }, { "mark-as-read-on-hover", configdata("false", configdata_t::BOOL) }, { "max-browser-tabs", configdata("10", configdata_t::INT) }, { "markfeedread-jumps-to-next-unread", configdata("false", configdata_t::BOOL) }, { "max-download-speed", configdata("0", configdata_t::INT) }, { "max-downloads", configdata("1", configdata_t::INT) }, { "max-items", configdata("0", configdata_t::INT) }, { "newsblur-login", configdata("", configdata_t::STR) }, { "newsblur-min-items", configdata("20", configdata_t::INT) }, { "newsblur-password", configdata("", configdata_t::STR) }, { "newsblur-passwordfile", configdata("", configdata_t::PATH) }, { "newsblur-passwordeval", configdata("", configdata_t::STR) }, { "newsblur-url", configdata("https://newsblur.com", configdata_t::STR) }, { "notify-always", configdata("no", configdata_t::BOOL) }, { "notify-beep", configdata("no", configdata_t::BOOL) }, { "notify-format", configdata( _("newsboat: finished reload, %f unread " "feeds (%n unread articles total)"), configdata_t::STR) }, { "notify-program", configdata("", configdata_t::PATH) }, { "notify-screen", configdata("no", configdata_t::BOOL) }, { "notify-xterm", configdata("no", configdata_t::BOOL) }, { "oldreader-flag-share", configdata("", configdata_t::STR) }, { "oldreader-flag-star", configdata("", configdata_t::STR) }, { "oldreader-login", configdata("", configdata_t::STR) }, { "oldreader-min-items", configdata("20", configdata_t::INT) }, { "oldreader-password", configdata("", configdata_t::STR) }, { "oldreader-passwordfile", configdata("", configdata_t::PATH) }, { "oldreader-passwordeval", configdata("", configdata_t::STR) }, { "oldreader-show-special-feeds", configdata("true", configdata_t::BOOL) }, { "openbrowser-and-mark-jumps-to-next-unread", configdata("false", configdata_t::BOOL) }, { "opml-url", configdata("", configdata_t::STR, true) }, { "pager", configdata("internal", configdata_t::PATH) }, { "player", configdata("", configdata_t::PATH) }, { "podcast-auto-enqueue", configdata("no", configdata_t::BOOL) }, { "prepopulate-query-feeds", configdata("false", configdata_t::BOOL) }, { "ssl-verifyhost", configdata("true", configdata_t::BOOL) }, { "ssl-verifypeer", configdata("true", configdata_t::BOOL) }, { "proxy", configdata("", configdata_t::STR) }, { "proxy-auth", configdata("", configdata_t::STR) }, { "proxy-auth-method", configdata("any", std::unordered_set({ "any", "basic", "digest", "digest_ie", "gssnegotiate", "ntlm", "anysafe" })) }, { "proxy-type", configdata("http", std::unordered_set({ "http", "socks4", "socks4a", "socks5", "socks5h" })) }, { "refresh-on-startup", configdata("no", configdata_t::BOOL) }, { "reload-only-visible-feeds", configdata("false", configdata_t::BOOL) }, { "reload-threads", configdata("1", configdata_t::INT) }, { "reload-time", configdata("60", configdata_t::INT) }, { "save-path", configdata("~/", configdata_t::PATH) }, { "search-highlight-colors", configdata("black yellow bold", configdata_t::STR, true) }, { "show-keymap-hint", configdata("yes", configdata_t::BOOL) }, { "show-read-articles", configdata("yes", configdata_t::BOOL) }, { "show-read-feeds", configdata("yes", configdata_t::BOOL) }, { "suppress-first-reload", configdata("no", configdata_t::BOOL) }, { "swap-title-and-hints", configdata("no", configdata_t::BOOL) }, { "text-width", configdata("0", configdata_t::INT) }, { "toggleitemread-jumps-to-next-unread", configdata("false", configdata_t::BOOL) }, { "ttrss-flag-publish", configdata("", configdata_t::STR) }, { "ttrss-flag-star", configdata("", configdata_t::STR) }, { "ttrss-login", configdata("", configdata_t::STR) }, { "ttrss-mode", configdata("multi", std::unordered_set({ "single", "multi" })) }, { "ttrss-password", configdata("", configdata_t::STR) }, { "ttrss-passwordfile", configdata("", configdata_t::PATH) }, { "ttrss-passwordeval", configdata("", configdata_t::STR) }, { "ttrss-url", configdata("", configdata_t::STR) }, { "ocnews-login", configdata("", configdata_t::STR) }, { "ocnews-password", configdata("", configdata_t::STR) }, { "ocnews-passwordfile", configdata("", configdata_t::PATH) }, { "ocnews-passwordeval", configdata("", configdata_t::STR) }, { "ocnews-flag-star", configdata("", configdata_t::STR) }, { "ocnews-url", configdata("", configdata_t::STR) }, { "urls-source", configdata("local", std::unordered_set({ "local", "opml", "oldreader", "ttrss", "newsblur", "feedhq", "ocnews" })) }, { "use-proxy", configdata("no", configdata_t::BOOL) }, { "user-agent", configdata("", configdata_t::STR) }, /* title formats: */ { "articlelist-title-format", configdata( _("%N %V - Articles in feed '%T' (%u unread, %t total) - %U"), configdata_t::STR) }, { "dialogs-title-format", configdata(_("%N %V - Dialogs"), configdata_t::STR) }, { "feedlist-title-format", configdata( _("%N %V - Your feeds (%u unread, %t total)%?T? - tag `%T'&?"), configdata_t::STR) }, { "filebrowser-title-format", configdata( _("%N %V - %?O?Open File&Save File? - %f"), configdata_t::STR) }, { "help-title-format", configdata(_("%N %V - Help"), configdata_t::STR) }, { "itemview-title-format", configdata( _("%N %V - Article '%T' (%u unread, %t total)"), configdata_t::STR) }, { "searchresult-title-format", configdata( _("%N %V - Search result (%u unread, %t total)"), configdata_t::STR) }, { "selectfilter-title-format", configdata(_("%N %V - Select Filter"), configdata_t::STR) }, { "selecttag-title-format", configdata(_("%N %V - Select Tag"), configdata_t::STR) }, { "urlview-title-format", configdata(_("%N %V - URLs"), configdata_t::STR) } } { } configcontainer::~configcontainer() { } void configcontainer::register_commands(configparser& cfgparser) { // this registers the config options defined above in the configuration parser // -> if the resp. config option is encountered, it is passed to the configcontainer for (auto cfg : config_data) { cfgparser.register_handler(cfg.first, this); } } void configcontainer::handle_action(const std::string& action, const std::vector& params) { std::string resolved_action = lookup_alias(action); configdata& cfgdata = config_data[resolved_action]; // configdata_t::INVALID indicates that the action didn't exist, and that the returned object was created ad-hoc. if (cfgdata.type == configdata_t::INVALID) { LOG(level::WARN, "configcontainer::handler_action: unknown action %s", action); throw confighandlerexception(action_handler_status::INVALID_COMMAND); } LOG(level::DEBUG, "configcontainer::handle_action: action = %s, type = %u", action, cfgdata.type); if (params.size() < 1) { throw confighandlerexception(action_handler_status::TOO_FEW_PARAMS); } switch (cfgdata.type) { case configdata_t::BOOL: if (!is_bool(params[0])) throw confighandlerexception(strprintf::fmt(_("expected boolean value, found `%s' instead"), params[0])); cfgdata.value = params[0]; break; case configdata_t::INT: if (!is_int(params[0])) throw confighandlerexception(strprintf::fmt(_("expected integer value, found `%s' instead"), params[0])); cfgdata.value = params[0]; break; case configdata_t::ENUM: if (cfgdata.enum_values.find(params[0]) == cfgdata.enum_values.end()) throw confighandlerexception(strprintf::fmt(_("invalid configuration value `%s'"), params[0])); // fall-through case configdata_t::STR: case configdata_t::PATH: if (cfgdata.multi_option) cfgdata.value = utils::join(params, " "); else cfgdata.value = params[0]; break; default: // should not happen throw confighandlerexception(action_handler_status::INVALID_COMMAND); } } bool configcontainer::is_bool(const std::string& s) { const char * bool_values[] = { "yes", "no", "true", "false", 0 }; for (int i=0; bool_values[i] ; ++i) { if (s == bool_values[i]) return true; } return false; } std::string configcontainer::lookup_alias(const std::string& s) { // this assumes that the config_data table is consistent. std::string alias = s; while (alias != "" && config_data[alias].type == configdata_t::ALIAS) { alias = config_data[alias].default_value; } return alias; } bool configcontainer::is_int(const std::string& s) { const char * s1 = s.c_str(); for (; *s1; s1++) { if (!isdigit(*s1)) return false; } return true; } std::string configcontainer::get_configvalue(const std::string& key) { std::string retval = config_data[lookup_alias(key)].value; if (config_data[key].type == configdata_t::PATH) { retval = utils::resolve_tilde(retval); } return retval; } int configcontainer::get_configvalue_as_int(const std::string& key) { std::istringstream is(config_data[lookup_alias(key)].value); int i; is >> i; return i; } bool configcontainer::get_configvalue_as_bool(const std::string& key) { std::string value = config_data[lookup_alias(key)].value; if (value == "true" || value == "yes") return true; return false; } void configcontainer::set_configvalue(const std::string& key, const std::string& value) { LOG(level::DEBUG,"configcontainer::set_configvalue(%s [resolved: %s],%s) called", key, lookup_alias(key), value); config_data[lookup_alias(key)].value = value; } void configcontainer::reset_to_default(const std::string& key) { std::string resolved_key = lookup_alias(key); config_data[resolved_key].value = config_data[resolved_key].default_value; } void configcontainer::toggle(const std::string& key) { std::string resolved_key = lookup_alias(key); if (config_data[resolved_key].type == configdata_t::BOOL) { set_configvalue(resolved_key, std::string(get_configvalue_as_bool(resolved_key) ? "false" : "true")); } } void configcontainer::dump_config(std::vector& config_output) { for (auto cfg : config_data) { std::string configline = cfg.first + " "; assert(cfg.second.type != configdata_t::INVALID); switch (cfg.second.type) { case configdata_t::BOOL: case configdata_t::INT: configline.append(cfg.second.value); if (cfg.second.value != cfg.second.default_value) configline.append(strprintf::fmt(" # default: %s", cfg.second.default_value)); break; case configdata_t::ENUM: case configdata_t::STR: case configdata_t::PATH: if (cfg.second.multi_option) { std::vector tokens = utils::tokenize(cfg.second.value, " "); for (auto token : tokens) { configline.append(utils::quote(token) + " "); } } else { configline.append(utils::quote(cfg.second.value)); if (cfg.second.value != cfg.second.default_value) { configline.append(strprintf::fmt(" # default: %s", cfg.second.default_value)); } } break; case configdata_t::ALIAS: // skip entry, generate no output continue; case configdata_t::INVALID: default: assert(0); break; } config_output.push_back(configline); } } std::vector configcontainer::get_suggestions(const std::string& fragment) { std::vector result; for (auto cfg : config_data) { if (cfg.first.substr(0, fragment.length()) == fragment) result.push_back(cfg.first); } std::sort(result.begin(), result.end()); return result; } }