diff options
author | Edwin van Leeuwen <edwinvanl@tuta.io> | 2022-09-23 19:03:05 +0000 |
---|---|---|
committer | Edwin van Leeuwen <edwinvanl@tuta.io> | 2022-09-23 19:03:05 +0000 |
commit | c6288c5d21c37f5c86928f52a0ec3b364569d983 (patch) | |
tree | 726a3acb8e53a57969c4ef453addd1dd1101827f | |
parent | 78acb58d346e368c69c0ef49f1b25e29041736c0 (diff) |
refactor: Started working on peek implementation
-rw-r--r-- | CMakeLists.txt | 7 | ||||
-rw-r--r-- | src/rttt.hpp | 331 | ||||
-rw-r--r-- | src/rttt/config.hpp | 2 | ||||
-rw-r--r-- | test/catch_peek.cpp | 69 |
4 files changed, 242 insertions, 167 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt index 7c76371..8cab78f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -113,10 +113,9 @@ set (SOURCES src/main.cpp) # Setup executable add_executable(${TARGET} ${SOURCES}) -target_include_directories(${TARGET} PRIVATE - . - src/ - test/include/ +target_include_directories(${TARGET} + PRIVATE src/ + PRIVATE test/include/ ) target_link_libraries(${TARGET} diff --git a/src/rttt.hpp b/src/rttt.hpp index d1efc93..efdd20b 100644 --- a/src/rttt.hpp +++ b/src/rttt.hpp @@ -18,146 +18,143 @@ namespace rttt { - // TODO: move to thing::type - enum class SiteType : int { - Unknown, - HN, - Reddit, - RSS, - Twitter, - }; - - // Consider moving this to view.hpp - enum class list_mode : int { - story, - comment, - feed, - }; - - // TODO: should work with constexpr, but might need newer g++/clang++ - inline std::vector<std::string> split(std::string_view strv, - std::string_view delims = " ", - size_t no_pieces = 0) { - std::vector<std::string> output; - size_t first = 0; - - while (first < strv.size()) { - const auto second = strv.find_first_of(delims, first); - - if (first != second) - output.emplace_back(strv.substr(first, second - first)); - - if (second == std::string_view::npos) { - break; - } else if (no_pieces > 0 && output.size() >= no_pieces - 1) { - output.emplace_back(strv.substr(second + 1, strv.size())); - break; - } - - first = second + 1; +// TODO: move to thing::type +enum class SiteType : int { + Unknown, + HN, + Reddit, + RSS, + Twitter, +}; + +// Consider moving this to view.hpp +enum class list_mode : int { + story, + comment, + feed, +}; + +// TODO: should work with constexpr, but might need newer g++/clang++ +inline std::vector<std::string> split(std::string_view strv, + std::string_view delims = " ", + size_t no_pieces = 0) { + std::vector<std::string> output; + size_t first = 0; + + while (first < strv.size()) { + const auto second = strv.find_first_of(delims, first); + + if (first != second) + output.emplace_back(strv.substr(first, second - first)); + + if (second == std::string_view::npos) { + break; + } else if (no_pieces > 0 && output.size() >= no_pieces - 1) { + output.emplace_back(strv.substr(second + 1, strv.size())); + break; } - return output; + first = second + 1; } - inline void replaceAll(std::string & s, const std::string &search, - const std::string &replace) { - for (size_t pos = 0;; pos += replace.length()) { - pos = s.find(search, pos); - if (pos == std::string::npos) - break; - s.erase(pos, search.length()); - s.insert(pos, replace); - } + return output; +} + +inline void replaceAll(std::string &s, const std::string &search, + const std::string &replace) { + for (size_t pos = 0;; pos += replace.length()) { + pos = s.find(search, pos); + if (pos == std::string::npos) + break; + s.erase(pos, search.length()); + s.insert(pos, replace); } - - inline std::string parse_html(std::string str) { - replaceAll(str, "<p>", "\n"); - - std::string res; - bool inTag = false; - for (auto &ch : str) { - if (ch == '<') { - inTag = true; - } else if (ch == '>') { - inTag = false; - } else { - if (inTag == false) { - res += ch; - } +} + +inline std::string parse_html(std::string str) { + replaceAll(str, "<p>", "\n"); + + std::string res; + bool inTag = false; + for (auto &ch : str) { + if (ch == '<') { + inTag = true; + } else if (ch == '>') { + inTag = false; + } else { + if (inTag == false) { + res += ch; } } - - replaceAll(res, "/", "/"); - replaceAll(res, "'", "'"); - replaceAll(res, ">", ">"); - replaceAll(res, "–", "-"); - replaceAll(res, "“", "\""); - replaceAll(res, "”", "\""); - replaceAll(res, "‘", "'"); - replaceAll(res, "’", "'"); - replaceAll(res, "„", "'"); - replaceAll(res, """, "\""); - replaceAll(res, "&", "&"); - replaceAll(res, "—", "-"); - - return res; } - inline std::tm parse_time(std::string time_string) { - std::tm time = {}; - std::regex e("(\\d{4})-(\\d+)-(\\d+)T(\\d+):(\\d+)"); - std::smatch sm; - std::regex_search(time_string, sm, e); - if (sm.size() > 1) { - time.tm_year = std::stoi(sm[1]) - 1900; - time.tm_mon = std::stoi(sm[2]) - 1; - time.tm_mday = std::stoi(sm[3]); - time.tm_hour = std::stoi(sm[4]); - time.tm_min = std::stoi(sm[5]); - } - return time; + replaceAll(res, "/", "/"); + replaceAll(res, "'", "'"); + replaceAll(res, ">", ">"); + replaceAll(res, "–", "-"); + replaceAll(res, "“", "\""); + replaceAll(res, "”", "\""); + replaceAll(res, "‘", "'"); + replaceAll(res, "’", "'"); + replaceAll(res, "„", "'"); + replaceAll(res, """, "\""); + replaceAll(res, "&", "&"); + replaceAll(res, "—", "-"); + + return res; +} + +inline std::tm parse_time(std::string time_string) { + std::tm time = {}; + std::regex e("(\\d{4})-(\\d+)-(\\d+)T(\\d+):(\\d+)"); + std::smatch sm; + std::regex_search(time_string, sm, e); + if (sm.size() > 1) { + time.tm_year = std::stoi(sm[1]) - 1900; + time.tm_mon = std::stoi(sm[2]) - 1; + time.tm_mday = std::stoi(sm[3]); + time.tm_hour = std::stoi(sm[4]); + time.tm_min = std::stoi(sm[5]); } - - // TODO: Move to text.hpp and rename - inline std::vector<std::string> extractURL(std::string text) { - std::vector<std::string> v; - - // Currently we assume an url always ends with alphanum, / or _ - // ([^/_[:alnum:]]) If this turns out to be false then we could also try to - // filter out trailing punctuation marks etc specifically ([);:.!?] - // std::regex e("(https?://[A-z0-9$–_.+!*‘(),./?=]+?)[),;:.!?]*(\\s|$)"); - // "(https?://[[:alnum:]$-_.+!*‘(),./?=;&#]+?)[^/_[:alnum:]]*(\\s|$)"); - std::regex e("(https?://[[:alnum:]$-_+!*‘,/?=;&#]+?)(\\]\\(|[^/" - "_[:alnum:]]*(\\s|$))"); - std::smatch sm; - while (std::regex_search(text, sm, e)) { - if (sm.size() > 1) - v.push_back(sm[1]); - text = sm.suffix().str(); - } - - return v; + return time; +} + +// TODO: Move to text.hpp and rename +inline std::vector<std::string> extractURL(std::string text) { + std::vector<std::string> v; + + // Currently we assume an url always ends with alphanum, / or _ + // ([^/_[:alnum:]]) If this turns out to be false then we could also try to + // filter out trailing punctuation marks etc specifically ([);:.!?] + // std::regex e("(https?://[A-z0-9$–_.+!*‘(),./?=]+?)[),;:.!?]*(\\s|$)"); + // "(https?://[[:alnum:]$-_.+!*‘(),./?=;&#]+?)[^/_[:alnum:]]*(\\s|$)"); + std::regex e("(https?://[[:alnum:]$-_+!*‘,/?=;&#]+?)(\\]\\(|[^/" + "_[:alnum:]]*(\\s|$))"); + std::smatch sm; + while (std::regex_search(text, sm, e)) { + if (sm.size() > 1) + v.push_back(sm[1]); + text = sm.suffix().str(); } - struct Path { - SiteType type = SiteType::Unknown; - std::string name; - std::string basename; - list_mode mode = list_mode::story; - std::string id; - std::vector<std::string> parts; - }; - - inline Path parse_path(std::string_view path_name) { - Path path; - path.parts = split(path_name, "/"); - auto &v = path.parts; - if (v[0] == "hn") { - path.type = SiteType::HN; - } else if (v[0] == "r") { - path.type = SiteType::Reddit; - } else if (v[0] == "rss") { + return v; +} + +struct Path { + SiteType type = SiteType::Unknown; + std::string name; + std::string basename; + list_mode mode = list_mode::story; + std::string id; + std::vector<std::string> parts; +}; + +inline Path parse_path(std::string_view path_name) { + Path path; + path.parts = split(path_name, "/"); + auto &v = path.parts; + if (v.size() == 1) { + if (v[0] == "rss") { path.type = SiteType::RSS; path.basename = "/rss"; if (v.size() == 2 && v[1] != "front") { @@ -169,43 +166,53 @@ namespace rttt { path.mode = list_mode::story; } return path; - } - if (v.size() == 4) { - if (v[2] == "comments") { - path.mode = list_mode::comment; - } else { - logger::log_ifnot(false); - } - path.name = "/" + v[0] + "/" + v[1] + "/" + v[2] + "/" + v[3]; - path.id = v[3]; - } - path.basename = "/" + v[0] + "/" + v[1]; - if (path.name.empty()) + } else { + path.basename = fmt::format("/{}", v[0]); path.name = path.basename; - return path; + return path; + } } - - inline std::string timeSince(uint64_t t) { - auto delta = current_time() - t; - if (delta < 60) - return std::to_string(delta) + " seconds"; - if (delta < 3600) - return std::to_string(delta / 60) + " minutes"; - if (delta < 24 * 3600) - return std::to_string(delta / 3600) + " hours"; - return std::to_string(delta / 24 / 3600) + " days"; + if (v[0] == "hn") { + path.type = SiteType::HN; + } else if (v[0] == "r") { + path.type = SiteType::Reddit; } - - inline int openInBrowser(std::string uri) { - auto config = rttt::config::load(); - auto base_cmd = config["open_command"].get<std::string>(); + if (v.size() == 4) { + if (v[2] == "comments") { + path.mode = list_mode::comment; + } else { + logger::log_ifnot(false); + } + path.name = "/" + v[0] + "/" + v[1] + "/" + v[2] + "/" + v[3]; + path.id = v[3]; + } + path.basename = "/" + v[0] + "/" + v[1]; + if (path.name.empty()) + path.name = path.basename; + return path; +} + +inline std::string timeSince(uint64_t t) { + auto delta = current_time() - t; + if (delta < 60) + return std::to_string(delta) + " seconds"; + if (delta < 3600) + return std::to_string(delta / 60) + " minutes"; + if (delta < 24 * 3600) + return std::to_string(delta / 3600) + " hours"; + return std::to_string(delta / 24 / 3600) + " days"; +} + +inline int openInBrowser(std::string uri) { + auto config = rttt::config::load(); + auto base_cmd = config["open_command"].get<std::string>(); #ifdef __APPLE__ - if (base_command == "xdg-open") - base_cmd = "open"; + if (base_command == "xdg-open") + base_cmd = "open"; #endif - // std::string cmd = "run-mailcap " + uri + " > /dev/null 2>&1"; - std::string cmd = base_cmd + " \"" + uri + "\" > /dev/null 2>&1 &"; - logger::push("EXECUTING: {}", cmd); - return system(cmd.c_str()); - } + // std::string cmd = "run-mailcap " + uri + " > /dev/null 2>&1"; + std::string cmd = base_cmd + " \"" + uri + "\" > /dev/null 2>&1 &"; + logger::push("EXECUTING: {}", cmd); + return system(cmd.c_str()); +} } // namespace rttt diff --git a/src/rttt/config.hpp b/src/rttt/config.hpp index ed8ef35..360ca99 100644 --- a/src/rttt/config.hpp +++ b/src/rttt/config.hpp @@ -8,7 +8,7 @@ #include "nlohmann/json.hpp" #include "functional.hpp" -#include "rttt/prompt.hpp" +#include "prompt.hpp" namespace rttt { diff --git a/test/catch_peek.cpp b/test/catch_peek.cpp new file mode 100644 index 0000000..2397212 --- /dev/null +++ b/test/catch_peek.cpp @@ -0,0 +1,69 @@ +#define CATCH_CONFIG_MAIN + +#include "catch2/catch.hpp" + +#include "rttt.hpp" +#include "rttt/thing.hpp" + +namespace rttt { +namespace peek { + +struct peek_state { + std::vector<std::string> paths = {"/r/front", "/rss/Mike Blumenkrantz", + "/hn/top", "/r/funny", "/tw/awesomekling"}; + thing::state *thing_state; +}; + +using state = std::shared_ptr<peek_state>; + +bool is_valid_path(std::string_view path) { + return path == "/peek"; +} + +bool is_valid_path(const rttt::Path &path) { + return path.basename == "/peek"; +} + +std::optional<rttt::Path> parse_path(std::string_view path) { + if (peek::is_valid_path(path)) { + return rttt::parse_path(path); + } + return std::nullopt; +} + +auto setup(state state, nlohmann::json config, thing::state * tstate) { + if (!state) + state = std::make_shared<peek_state>(); + state->thing_state = tstate; + if (config.contains("peek") && config["peek"].contains("paths")) { + state->paths = config["peek"]["paths"].get<std::vector<std::string>>(); + } + return state; +} +} // namespace peek +} // namespace rttt + +using namespace rttt; + +// TODO: should we add a thing::is_valid_path(state, path), which uses the state to +// infer which type to test for? +// +SCENARIO("We can parse peek path") { + REQUIRE(peek::is_valid_path("/peek")); + REQUIRE(!peek::is_valid_path("/r/peek")); + + auto pth = peek::parse_path("/peek"); + REQUIRE(pth.has_value()); + REQUIRE(pth.value().parts[0] == "peek"); + REQUIRE(peek::is_valid_path(pth.value())); + + pth = peek::parse_path("/r/peek"); + REQUIRE(!pth.has_value()); +} + +SCENARIO("We can use peek state") { + thing::state thing_state; + peek::state state; + nlohmann::json config; + peek::setup(state, config, &thing_state); +} |