#include "regexmanager.h" #include "config.h" #include "confighandlerexception.h" #include "configparser.h" #include "logger.h" #include "stflrichtext.h" #include "strprintf.h" #include "utils.h" namespace newsboat { RegexManager::RegexManager() { // this creates the entries in the map. we need them there to have the // "all" location work. locations["article"]; locations["articlelist"]; locations["feedlist"]; } void RegexManager::dump_config(std::vector& config_output) const { for (const auto& foo : cheat_store_for_dump_config) { config_output.push_back(foo); } } void RegexManager::handle_action(const std::string& action, const std::vector& params) { if (action == "highlight") { handle_highlight_action(params); } else if (action == "highlight-article" || action == "highlight-feed") { handle_highlight_item_action(action, params); } else { throw ConfigHandlerException( ActionHandlerStatus::INVALID_COMMAND); } std::string line = action; for (const auto& param : params) { line.append(" "); line.append(utils::quote(param)); } cheat_store_for_dump_config.push_back(line); } int RegexManager::article_matches(Matchable* item) { for (const auto& Matcher : matchers_article) { if (Matcher.first->matches(item)) { return Matcher.second; } } return -1; } int RegexManager::feed_matches(Matchable* feed) { for (const auto& Matcher : matchers_feed) { if (Matcher.first->matches(feed)) { return Matcher.second; } } return -1; } void RegexManager::remove_last_regex(const std::string& location) { auto& regexes = locations[location]; if (regexes.empty()) { return; } regexes.pop_back(); } void RegexManager::quote_and_highlight(StflRichText& stflString, const std::string& location) { auto& regexes = locations[location]; const std::string text = stflString.plaintext(); for (unsigned int i = 0; i < regexes.size(); ++i) { const auto& regex = regexes[i].first; if (regex == nullptr) { continue; } unsigned int offset = 0; int eflags = 0; while (offset < text.length()) { const auto matches = regex->matches(text.substr(offset), 1, eflags); eflags |= REG_NOTBOL; // Don't match beginning-of-line operator (^) in following checks if (matches.empty()) { break; } const auto& match = matches[0]; if (match.first != match.second) { const std::string marker = strprintf::fmt("<%u>", i); const int match_start = offset + match.first; const int match_end = offset + match.second; stflString.apply_style_tag(marker, match_start, match_end); offset = match_end; } else { offset++; } } } } void RegexManager::handle_highlight_action(const std::vector& params) { if (params.size() < 3) { throw ConfigHandlerException(ActionHandlerStatus::TOO_FEW_PARAMS); } std::string location = params[0]; if (location != "all" && location != "article" && location != "articlelist" && location != "feedlist") { throw ConfigHandlerException(strprintf::fmt( _("`%s' is an invalid dialog type"), location)); } std::string errorMessage; auto regex = Regex::compile(params[1], REG_EXTENDED | REG_ICASE, errorMessage); if (regex == nullptr) { throw ConfigHandlerException(strprintf::fmt( _("`%s' is not a valid regular expression: %s"), params[1], errorMessage)); } std::string colorstr; if (params[2] != "default") { colorstr.append("fg="); if (!utils::is_valid_color(params[2])) { throw ConfigHandlerException(strprintf::fmt( _("`%s' is not a valid color"), params[2])); } colorstr.append(params[2]); } if (params.size() > 3) { if (params[3] != "default") { if (colorstr.length() > 0) { colorstr.append(","); } colorstr.append("bg="); if (!utils::is_valid_color(params[3])) { throw ConfigHandlerException( strprintf::fmt( _("`%s' is not a valid " "color"), params[3])); } colorstr.append(params[3]); } for (unsigned int i = 4; i < params.size(); ++i) { if (params[i] != "default") { if (!colorstr.empty()) { colorstr.append(","); } colorstr.append("attr="); if (!utils::is_valid_attribute( params[i])) { throw ConfigHandlerException( strprintf::fmt( _("`%s' is not " "a valid " "attribute"), params[i])); } colorstr.append(params[i]); } } } if (location != "all") { LOG(Level::DEBUG, "RegexManager::handle_action: adding rx = %s " "colorstr = %s to location %s", params[1], colorstr, location); locations[location].push_back({std::move(regex), colorstr}); } else { std::shared_ptr sharedRegex(std::move(regex)); for (auto& location : locations) { LOG(Level::DEBUG, "RegexManager::handle_action: adding " "rx = " "%s colorstr = %s to location %s", params[1], colorstr, location.first); location.second.push_back({sharedRegex, colorstr}); } } } void RegexManager::handle_highlight_item_action(const std::string& action, const std::vector& params) { if (params.size() < 3) { throw ConfigHandlerException(ActionHandlerStatus::TOO_FEW_PARAMS); } std::string expr = params[0]; std::string fgcolor = params[1]; std::string bgcolor = params[2]; std::string colorstr; if (fgcolor != "default") { colorstr.append("fg="); if (!utils::is_valid_color(fgcolor)) { throw ConfigHandlerException(strprintf::fmt( _("`%s' is not a valid color"), fgcolor)); } colorstr.append(fgcolor); } if (bgcolor != "default") { if (!colorstr.empty()) { colorstr.append(","); } colorstr.append("bg="); if (!utils::is_valid_color(bgcolor)) { throw ConfigHandlerException(strprintf::fmt( _("`%s' is not a valid color"), bgcolor)); } colorstr.append(bgcolor); } for (unsigned int i = 3; i < params.size(); i++) { if (params[i] != "default") { if (!colorstr.empty()) { colorstr.append(","); } colorstr.append("attr="); if (!utils::is_valid_attribute(params[i])) { throw ConfigHandlerException( strprintf::fmt( _("`%s' is not a valid " "attribute"), params[i])); } colorstr.append(params[i]); } } std::shared_ptr m(new Matcher()); if (!m->parse(params[0])) { throw ConfigHandlerException(strprintf::fmt( _("couldn't parse filter expression `%s': %s"), params[0], m->get_parse_error())); } if (action == "highlight-article") { int pos = locations["articlelist"].size(); locations["articlelist"].push_back({nullptr, colorstr}); matchers_article.push_back( std::pair, int>(m, pos)); } else if (action == "highlight-feed") { int pos = locations["feedlist"].size(); locations["feedlist"].push_back({nullptr, colorstr}); matchers_feed.push_back( std::pair, int>(m, pos)); } else { throw ConfigHandlerException( ActionHandlerStatus::INVALID_COMMAND); } } std::string RegexManager::get_attrs_stfl_string(const std::string& location, bool hasFocus) { const auto& attributes = locations[location]; std::string attrstr; for (unsigned int i = 0; i < attributes.size(); ++i) { const std::string& attribute = attributes[i].second; attrstr.append(strprintf::fmt("@style_%u_normal:%s ", i, attribute)); if (hasFocus) { attrstr.append(strprintf::fmt("@style_%u_focus:%s ", i, attribute)); } } return attrstr; } } // namespace newsboat