#include "parg.hh" using Parg = OB::Parg; #include "crex.hh" using Crex = OB::Crex; #include "ansi_escape_codes.hh" namespace aec = OB::ANSI_Escape_Codes; #include #include #include #include #include int program_options(Parg& pg); std::regex_constants::syntax_option_type regex_options(Parg& pg); std::regex_constants::match_flag_type regex_flags(Parg& pg); bool is_tty(); void regex_print(std::string const& regex, std::string const& text, Crex::Matches const& matches, bool color); void regex_print_color(std::string const& regex, std::string const& text, Crex::Matches const& matches); void regex_print_no_color(std::string const& regex, std::string const& text, Crex::Matches const& matches); void regex_print_json(std::string& regex, std::string& text, Crex::Matches const& matches); std::string replace(std::string str, std::string key, std::string val); int program_options(Parg& pg) { pg.name("crex").version("0.2.5 (07.10.2018)"); pg.description("explore, test, and check regular expressions"); pg.usage("[flags] [options] [--] [arguments]"); pg.usage("[-e|-b|-x|-a|-g|-E] [-io] [-c|-j] [-r regex] [-s string]"); pg.usage("[-v|--version]"); pg.usage("[-h|--help]"); pg.info("Examples", { "crex -r '(hello)' -s 'hello world!'", "printf 'Hello World!' | crex -r '(hello)' -ioec", "crex --help", "crex --version", }); pg.info("Exit Codes", {"0 -> normal", "1 -> error"}); pg.info("Repository", { "https://github.com/octobanana/crex.git", }); pg.info("Homepage", { "https://octobanana.com/software/crex", }); pg.author("Brett Robinson (octobanana) "); // enable piped stdin pg.set_stdin(); // single flags pg.set("help,h", "print the help output"); pg.set("version,v", "print the program version"); // combinable flags pg.set("color,c", "print out results in color"); pg.set("json,j", "print out results in json"); // options pg.set("string,s", "", "str", "the string to search"); pg.set("regex,r", "", "str", "the regular expression to use"); pg.set("icase,i", "character matching should be performed without regard to case"); pg.set("optimize,o", "instructs the regular expression engine to make matching faster, with the potential cost of making construction slower"); pg.set("ecmascript,e", "use the modified ECMAScript regular expression grammar"); pg.set("basic,b", "use the basic POSIX regular expression grammar"); pg.set("extended,x", "use the extended POSIX regular expression grammar"); pg.set("awk,a", "use the regular expression grammar used by the awk utility in POSIX"); pg.set("grep,g", "use the regular expression grammar used by the grep utility in POSIX"); pg.set("egrep,E", "use the regular expression grammar used by the grep utility, with the -E option, in POSIX"); int status {pg.parse()}; if (status < 0) { std::cout << pg.help() << "\n"; std::cout << "Error: " << pg.error() << "\n"; return -1; } if (pg.get("help")) { std::cout << pg.help(); return 1; } if (pg.get("version")) { std::cout << pg.name() << " v" << pg.version() << "\n"; return 1; } if (! pg.find("regex") || (! pg.find("string") && pg.get_stdin().empty())) { std::cout << pg.help() << "\n"; std::cout << "Error: " << "expected '-r' and '-s' options" << "\n"; return -1; } return 0; } std::regex_constants::syntax_option_type regex_options(Parg& pg) { std::regex_constants::syntax_option_type opts {}; if (pg.get("icase")) { opts |= std::regex::icase; } if (pg.get("optimize")) { opts |= std::regex::optimize; } if (pg.get("ecmascript")) { opts |= std::regex::ECMAScript; } else if (pg.get("basic")) { opts |= std::regex::basic; } else if (pg.get("extended")) { opts |= std::regex::extended; } else if (pg.get("awk")) { opts |= std::regex::awk; } else if (pg.get("grep")) { opts |= std::regex::grep; } else if (pg.get("egrep")) { opts |= std::regex::egrep; } else { opts |= std::regex::ECMAScript; } return opts; } std::regex_constants::match_flag_type regex_flags(Parg& pg) { std::regex_constants::match_flag_type flgs {std::regex_constants::match_not_null}; return flgs; } bool is_tty() { return isatty(STDOUT_FILENO); } std::string replace(std::string str, std::string key, std::string val) { size_t pos {0}; for (;;) { pos = str.find(key, pos); if (pos == std::string::npos) break; str.replace(pos, key.size(), val); pos += val.size(); } return str; } class Color_Ring { public: Color_Ring(std::vector colors): _colors {colors} { } std::string next() { auto res = get(); if (++_pos > _colors.size() - 1) { _pos = 0; } return res; } std::string get() const { return _colors.at(_pos); } Color_Ring& reset() { _pos = 0; return *this; } private: std::vector const _colors; size_t _pos {0}; }; struct Style { std::vector h1 {aec::fg_white, aec::bold, aec::underline}; std::vector h2 {aec::fg_green, aec::bold}; std::vector h3 {aec::fg_magenta, aec::bold}; std::vector num {aec::fg_cyan, aec::bold}; std::string match {aec::fg_green}; std::string plain {}; std::vector special {aec::fg_white, aec::bold}; Color_Ring ring {{ aec::fg_magenta, aec::fg_blue, aec::fg_cyan, aec::fg_green, aec::fg_yellow, aec::fg_red }}; }; void regex_print(std::string const& regex, std::string const& text, Crex::Matches const& matches, bool color) { if (color) { regex_print_color(regex, text, matches); } else { regex_print_no_color(regex, text, matches); } } void regex_print_color(std::string const& regex, std::string const& text, Crex::Matches const& matches) { Style style; std::stringstream ss_head; std::stringstream ss; ss_head << aec::wrap("Regex", style.h1) << "\n" << regex << "\n\n" << aec::wrap("Text", style.h1) << "\n"; ss << "\n\n" << aec::wrap("Matches", style.h1) << "[" << aec::wrap(matches.size(), style.num) << "]\n"; size_t pos {0}; for (size_t i = 0; i < matches.size(); ++i) { style.ring.reset(); auto const& match = matches.at(i); ss << aec::wrap("Match", style.h2) << "[" << aec::wrap(i + 1, style.num) << "] " << aec::wrap(match.at(0).second.first, style.num) << "-" << aec::wrap(match.at(0).second.second, style.num) << aec::wrap(" |", style.special); std::string const& tmatch {match.at(0).first}; size_t const offset {match.at(0).second.first}; size_t prev {0}; size_t begin {0}; size_t end {0}; std::stringstream ss_text; std::stringstream ss_match; for (size_t j = 1; j < match.size(); ++j) { auto ftext = aec::wrap(match.at(j).first, style.ring.next()); begin = match.at(j).second.first - offset; end = match.at(j).second.second - offset; if (prev <= begin) { ss_text << aec::wrap(tmatch.substr(prev, begin - prev), style.plain); } ss_text << ftext; prev = end + 1; ss_match << aec::wrap("Group", style.h3) << "[" << aec::wrap(j, style.num) << "] " << aec::wrap(begin + offset, style.num) << "-" << aec::wrap(end + offset, style.num) << aec::wrap(" |", style.special) << ftext << aec::wrap("|\n", style.special); } if (match.size() < 2) { ss_text << aec::wrap(tmatch, style.plain); } else if (prev < tmatch.size()) { ss_text << aec::wrap(tmatch.substr(prev, begin - prev), style.plain); } ss_match << "\n"; ss << ss_text.str() << aec::wrap("|\n", style.special) << ss_match.str(); if (pos < offset) { ss_head << aec::wrap(text.substr(pos, offset - pos), style.plain); } pos = offset + match.at(0).first.size() ; ss_head << aec::wrap("(", style.special) << ss_text.str() << aec::wrap(")[", style.special) << aec::wrap(i + 1, style.num) << aec::wrap("]", style.special); } if (pos < text.size()) { ss_head << aec::wrap(text.substr(pos, text.size() - pos), style.plain); } std::cout << ss_head.str() << ss.str(); } void regex_print_no_color(std::string const& regex, std::string const& text, Crex::Matches const& matches) { std::stringstream ss; ss << "Regex\n" << regex << "\n\n" << "Text\n" << text << "\n\n" << "Matches[" << matches.size() << "]\n"; for (size_t i = 0; i < matches.size(); ++i) { auto const& match = matches.at(i); ss << "Match[" << i + 1 << "] " << match.at(0).second.first << "-" << match.at(0).second.second << " |" << match.at(0).first << "|\n"; for (size_t j = 1; j < match.size(); ++j) { ss << "Group[" << j << "] " << match.at(j).second.first << "-" << match.at(j).second.second << " |" << match.at(j).first << "|\n"; } ss << "\n"; } std::cout << ss.str(); } void regex_print_json(std::string& regex, std::string& text, Crex::Matches const& matches) { auto const get_matches = [&]() { std::stringstream ss; for (size_t i = 0; i < matches.size(); ++i) { auto const& match = matches.at(i); ss << "["; for (size_t j = 0; j < match.size(); ++j) { auto mtext = match.at(j).first; mtext = replace(mtext, "\"", "\\\""); mtext = replace(mtext, "\\", "\\\\"); ss << "{" << "\"mtext\":\"" << mtext << "\"," << "\"begin\":" << match.at(j).second.first << "," << "\"end\":" << match.at(j).second.second << "}"; if (j != match.size() - 1) { ss << ","; } } ss << "]"; if (i != matches.size() - 1) { ss << ","; } } return ss.str(); }; std::stringstream ss; regex = replace(regex, "\"", "\\\""); regex = replace(regex, "\\", "\\\\"); text = replace(text, "\"", "\\\""); text = replace(text, "\\", "\\\\"); ss << "{" << "\"regex\":\"" << regex << "\"," << "\"text\":\"" << text << "\"," << "\"matches\":" << "[" << get_matches() << "]}"; std::cout << ss.str(); } int main(int argc, char *argv[]) { Parg pg {argc, argv}; int pstatus {program_options(pg)}; if (pstatus > 0) return 0; if (pstatus < 0) return 1; try { std::string regex {pg.get("regex")}; std::string text {pg.get_stdin() + pg.get("string")}; if (regex.empty() || text.empty()) { if (regex.empty()) { throw std::runtime_error( "regex is empty, expected '-r' option" "\nView the help output with '-h'" ); } if (text.empty()) { throw std::runtime_error( "text is empty, expected '-s' option or piped stdin" "\nView the help output with '-h'" ); } } OB::Crex crex {}; crex.regex(regex); crex.text(text); crex.options(regex_options(pg)); crex.flags(regex_flags(pg)); if (! crex.run()) { throw std::runtime_error("no matches found"); } if (pg.get("json")) { regex_print_json(regex, text, crex.matches()); } else { regex_print(regex, text, crex.matches(), pg.get("color")); } } catch (std::exception const& e) { std::cerr << "Error: " << e.what() << "\n"; return 1; } catch (...) { std::cerr << "Error: an unexpected error occurred\n"; return 1; } return 0; }