├── .gitignore ├── CMakeLists.txt ├── LICENSE ├── README.md ├── assets └── crex.png ├── build.sh ├── crex.sh ├── env.sh ├── install.sh └── src ├── ansi_escape_codes.cc ├── ansi_escape_codes.hh ├── crex.cc ├── crex.hh ├── main.cc └── parg.hh /.gitignore: -------------------------------------------------------------------------------- 1 | build/ 2 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required (VERSION 3.5 FATAL_ERROR) 2 | 3 | set (TARGET crex) 4 | project (${TARGET}) 5 | 6 | find_program (CCACHE_FOUND ccache) 7 | if (CCACHE_FOUND) 8 | message ("ccache found") 9 | set_property (GLOBAL PROPERTY RULE_LAUNCH_COMPILE ccache) 10 | set_property (GLOBAL PROPERTY RULE_LAUNCH_LINK ccache) 11 | endif(CCACHE_FOUND) 12 | 13 | set (CMAKE_CXX_STANDARD 14) 14 | set (CMAKE_CXX_STANDARD_REQUIRED ON) 15 | 16 | set (CMAKE_CXX_FLAGS "-fdiagnostics-color=auto") 17 | set (CMAKE_C_FLAGS "-fdiagnostics-color=auto") 18 | 19 | set (DEBUG_FLAGS "-Wpedantic -Wall -Wextra -Wcast-align -Wcast-qual -Wctor-dtor-privacy -Wdisabled-optimization -Wformat=2 -Winit-self -Wlogical-op -Wmissing-declarations -Wmissing-include-dirs -Wnoexcept -Wold-style-cast -Woverloaded-virtual -Wredundant-decls -Wshadow -Wsign-conversion -Wsign-promo -Wstrict-null-sentinel -Wstrict-overflow=5 -Wswitch-default -Wundef -Wno-unused -std=c++14 -g") 20 | set (DEBUG_LINK_FLAGS "-fprofile-arcs -ftest-coverage -flto") 21 | 22 | set (RELEASE_FLAGS "-std=c++14 -s -O3") 23 | set (RELEASE_LINK_FLAGS "-flto") 24 | 25 | set (CMAKE_CXX_FLAGS_DEBUG ${DEBUG_FLAGS}) 26 | set (CMAKE_C_FLAGS_DEBUG ${DEBUG_FLAGS}) 27 | set (CMAKE_EXE_LINKER_FLAGS_DEBUG ${DEBUG_LINK_FLAGS}) 28 | 29 | set (CMAKE_CXX_FLAGS_RELEASE ${RELEASE_FLAGS}) 30 | set (CMAKE_C_FLAGS_RELEASE ${RELEASE_FLAGS}) 31 | set (CMAKE_EXE_LINKER_FLAGS_RELEASE ${RELEASE_LINK_FLAGS}) 32 | 33 | message ("CMAKE_BUILD_TYPE is ${CMAKE_BUILD_TYPE}") 34 | 35 | include_directories( 36 | ./src 37 | ./ 38 | ) 39 | 40 | set (SOURCES 41 | src/main.cc 42 | src/crex.cc 43 | src/ansi_escape_codes.cc 44 | ) 45 | 46 | set (HEADERS 47 | ) 48 | 49 | add_executable ( 50 | ${TARGET} 51 | ${SOURCES} 52 | ${HEADERS} 53 | ) 54 | 55 | target_link_libraries ( 56 | ${TARGET} 57 | ) 58 | 59 | install (TARGETS ${TARGET} DESTINATION "/usr/local/bin") 60 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Brett Robinson 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # crex 2 | Explore, test, and check regular expressions in the terminal. 3 | 4 | ![crex example](https://raw.githubusercontent.com/octobanana/crex/master/assets/crex.png) 5 | 6 | Given your regular expression and text, crex will output matches, capture groups, and details. 7 | crex has a range of options, allowing fine grained control over matching and output. 8 | It uses ECMAScript grammar by default, while also accepting posix, extended posix, awk, grep, and extended grep grammars. 9 | Input can be received by piped stdin, or by using the __-s__ option. 10 | Output options include plain, colour, and json formats. 11 | 12 | ## Build 13 | Environment: 14 | * tested on linux 15 | * c++ 14 compiler 16 | * cmake 17 | 18 | Libraries: 19 | * my [parg](https://github.com/octobanana/parg) library, for parsing cli args, included as `./src/parg.hh` 20 | 21 | The following shell commands will build the project: 22 | ```bash 23 | git clone 24 | cd crex 25 | ./build.sh -r 26 | ``` 27 | To build the debug version, run the build script without the -r flag. 28 | 29 | ## Install 30 | The following shell commands will install the project: 31 | ```bash 32 | ./install.sh -r 33 | ``` 34 | -------------------------------------------------------------------------------- /assets/crex.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/octobanana/crex/319b8c43ad895be6c3c47ce7f49f54b235fd9d6c/assets/crex.png -------------------------------------------------------------------------------- /build.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -e 3 | 4 | BUILD_TYPE="Debug" 5 | 6 | if [[ $# > 0 ]]; then 7 | if [[ $1 == "-d" ]]; then 8 | BUILD_TYPE="Debug" 9 | elif [[ $1 == "-r" ]]; then 10 | BUILD_TYPE="Release" 11 | else 12 | printf "usage: ./build.sh [-d|-r]\n"; 13 | exit 1 14 | fi 15 | fi 16 | 17 | # source environment variables 18 | source ./env.sh 19 | 20 | printf "\nBuilding ${APP} in ${BUILD_TYPE} mode\n" 21 | 22 | if [[ ${BUILD_TYPE} == "Debug" ]]; then 23 | printf "\nCompiling ${APP}\n" 24 | mkdir -p build/debug 25 | cd build/debug 26 | cmake ../../ -DCMAKE_BUILD_TYPE=${BUILD_TYPE} 27 | time make 28 | 29 | elif [[ ${BUILD_TYPE} == "Release" ]]; then 30 | printf "\nCompiling ${APP}\n" 31 | mkdir -p build/release 32 | cd build/release 33 | cmake ../../ -DCMAKE_BUILD_TYPE=${BUILD_TYPE} 34 | time make 35 | fi 36 | -------------------------------------------------------------------------------- /crex.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -e 3 | 4 | # name: crex.sh 5 | # info: crex interactive mode using entr 6 | # file_regex contains the regex text 7 | # file_text contains the text to apply the regex on 8 | # while running this script edit to the two files 9 | # and see the output changes on save 10 | 11 | if [[ $# == 2 ]]; then 12 | printf "$1\n$2\n" | entr -c -s "cat ${2} | crex -r \"\$(head -1 ${1})\"" 13 | elif [[ $# == 3 ]]; then 14 | printf "$2\n$3\n" | entr -c -s "cat ${3} | crex ${1} -r \"\$(head -1 ${2})\"" 15 | else 16 | printf "crex.sh:\n" 17 | printf " crex interactive mode using entr\n\n" 18 | printf "usage:\n" 19 | printf " crex.sh \n" 20 | printf " crex.sh \n\n" 21 | printf "example:\n" 22 | printf " crex.sh regex.txt text.txt\n" 23 | printf " crex.sh -oc regex.txt text.txt\n" 24 | exit 1 25 | fi 26 | -------------------------------------------------------------------------------- /env.sh: -------------------------------------------------------------------------------- 1 | printf "\nSetting Environment Variables\n" 2 | 3 | export APP="crex" 4 | -------------------------------------------------------------------------------- /install.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -e 3 | 4 | BUILD_TYPE="Debug" 5 | 6 | if [[ $# > 0 ]]; then 7 | if [[ $1 == "-d" ]]; then 8 | BUILD_TYPE="Debug" 9 | elif [[ $1 == "-r" ]]; then 10 | BUILD_TYPE="Release" 11 | else 12 | printf "usage: ./install.sh [-d|-r]\n"; 13 | exit 1 14 | fi 15 | fi 16 | 17 | # source environment variables 18 | source ./env.sh 19 | 20 | if [[ ${BUILD_TYPE} == "Debug" ]]; then 21 | ./build.sh -d 22 | cd build/debug 23 | 24 | printf "\nInstalling ${APP}\n" 25 | sudo make install 26 | 27 | elif [[ ${BUILD_TYPE} == "Release" ]]; then 28 | ./build.sh -r 29 | cd build/release 30 | 31 | printf "\nInstalling ${APP}\n" 32 | sudo make install 33 | fi 34 | -------------------------------------------------------------------------------- /src/ansi_escape_codes.cc: -------------------------------------------------------------------------------- 1 | #include "ansi_escape_codes.hh" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | namespace OB 9 | { 10 | 11 | namespace ANSI_Escape_Codes 12 | { 13 | 14 | // standard escaped characters 15 | std::string const nl {"\n"}; 16 | std::string const cr {"\r"}; 17 | std::string const tab {"\t"}; 18 | std::string const alert {"\a"}; 19 | 20 | // escape code sequence 21 | std::string const esc {"\033["}; 22 | 23 | // clears all attributes 24 | std::string const reset {esc + "0m"}; 25 | 26 | // style 27 | std::string const bold {esc + "1m"}; 28 | std::string const dim {esc + "2m"}; 29 | std::string const italic {esc + "3m"}; 30 | std::string const underline {esc + "4m"}; 31 | std::string const blink {esc + "5m"}; 32 | std::string const rblink {esc + "6m"}; 33 | std::string const reverse {esc + "7m"}; 34 | std::string const conceal {esc + "8m"}; 35 | std::string const cross {esc + "9m"}; 36 | 37 | // erasing 38 | std::string const erase_end {esc + "K"}; 39 | std::string const erase_start {esc + "1K"}; 40 | std::string const erase_line {esc + "2K"}; 41 | std::string const erase_down {esc + "J"}; 42 | std::string const erase_up {esc + "1J"}; 43 | std::string const erase_screen {esc + "2J"}; 44 | 45 | // cursor visibility 46 | std::string const cursor_hide {esc + "?25l"}; 47 | std::string const cursor_show {esc + "?25h"}; 48 | 49 | // cursor movement 50 | std::string const cursor_home {esc + "0;0H"}; 51 | std::string const cursor_up {esc + "1A"}; 52 | std::string const cursor_down {esc + "1B"}; 53 | std::string const cursor_right {esc + "1C"}; 54 | std::string const cursor_left {esc + "1D"}; 55 | std::string const cursor_save {"\0337"}; 56 | std::string const cursor_load {"\0338"}; 57 | 58 | // foreground color 59 | std::string const fg_black {esc + "30m"}; 60 | std::string const fg_red {esc + "31m"}; 61 | std::string const fg_green {esc + "32m"}; 62 | std::string const fg_yellow {esc + "33m"}; 63 | std::string const fg_blue {esc + "34m"}; 64 | std::string const fg_magenta {esc + "35m"}; 65 | std::string const fg_cyan {esc + "36m"}; 66 | std::string const fg_white {esc + "37m"}; 67 | 68 | // background color 69 | std::string const bg_black {esc + "40m"}; 70 | std::string const bg_red {esc + "41m"}; 71 | std::string const bg_green {esc + "42m"}; 72 | std::string const bg_yellow {esc + "43m"}; 73 | std::string const bg_blue {esc + "44m"}; 74 | std::string const bg_magenta {esc + "45m"}; 75 | std::string const bg_cyan {esc + "46m"}; 76 | std::string const bg_white {esc + "47m"}; 77 | 78 | std::string fg_256(std::string x) 79 | { 80 | auto n = std::stoi(x); 81 | if (n < 0 || n > 256) return {}; 82 | std::stringstream ss; 83 | ss << esc << "38;5;" << x << "m"; 84 | return ss.str(); 85 | } 86 | 87 | std::string bg_256(std::string x) 88 | { 89 | auto n = std::stoi(x); 90 | if (n < 0 || n > 256) return {}; 91 | std::stringstream ss; 92 | ss << esc << "48;5;" << x << "m"; 93 | return ss.str(); 94 | } 95 | 96 | std::string htoi(std::string x) 97 | { 98 | std::stringstream ss; 99 | ss << x; 100 | unsigned int n; 101 | ss >> std::hex >> n; 102 | return std::to_string(n); 103 | } 104 | 105 | bool valid_hstr(std::string& str) 106 | { 107 | std::smatch m; 108 | std::regex rx {"^#?((?:[0-9a-fA-F]{3}){1,2})$"}; 109 | if (std::regex_match(str, m, rx)) 110 | { 111 | std::string hstr {m[1]}; 112 | if (hstr.size() == 3) 113 | { 114 | std::stringstream ss; 115 | ss << hstr[0] << hstr[0] << hstr[1] << hstr[1] << hstr[2] << hstr[2]; 116 | hstr = ss.str(); 117 | } 118 | str = hstr; 119 | return true; 120 | } 121 | return false; 122 | } 123 | 124 | std::string fg_true(std::string x) 125 | { 126 | if (! valid_hstr(x)) return {}; 127 | std::string h1 {x.substr(0, 2)}; 128 | std::string h2 {x.substr(2, 2)}; 129 | std::string h3 {x.substr(4, 2)}; 130 | std::stringstream ss; ss 131 | << esc << "38;2;" 132 | << htoi(h1) << ";" 133 | << htoi(h2) << ";" 134 | << htoi(h3) << "m"; 135 | return ss.str(); 136 | } 137 | 138 | std::string bg_true(std::string x) 139 | { 140 | if (! valid_hstr(x)) return {}; 141 | std::string h1 {x.substr(0, 2)}; 142 | std::string h2 {x.substr(2, 2)}; 143 | std::string h3 {x.substr(4, 2)}; 144 | std::stringstream ss; ss 145 | << esc << "48;2;" 146 | << htoi(h1) << ";" 147 | << htoi(h2) << ";" 148 | << htoi(h3) << "m"; 149 | return ss.str(); 150 | } 151 | 152 | std::string cursor_set(size_t x, size_t y) 153 | { 154 | std::stringstream ss; 155 | ss << esc << y << ";" << x << "H"; 156 | return ss.str(); 157 | } 158 | 159 | } // namespace ANSI_Escape_Codes 160 | 161 | } // namespace OB 162 | -------------------------------------------------------------------------------- /src/ansi_escape_codes.hh: -------------------------------------------------------------------------------- 1 | #ifndef OB_ANSI_ESCAPE_CODES_HH 2 | #define OB_ANSI_ESCAPE_CODES_HH 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | namespace OB 9 | { 10 | 11 | namespace ANSI_Escape_Codes 12 | { 13 | 14 | // standard escaped characters 15 | extern std::string const nl; 16 | extern std::string const cr; 17 | extern std::string const tab; 18 | extern std::string const alert; 19 | 20 | // escape code sequence 21 | extern std::string const esc; 22 | 23 | // clears all attributes 24 | extern std::string const reset; 25 | 26 | // style 27 | extern std::string const bold; 28 | extern std::string const dim; 29 | extern std::string const italic; 30 | extern std::string const underline; 31 | extern std::string const blink; 32 | extern std::string const rblink; 33 | extern std::string const reverse; 34 | extern std::string const conceal; 35 | extern std::string const cross; 36 | 37 | // erasing 38 | extern std::string const erase_end; 39 | extern std::string const erase_start; 40 | extern std::string const erase_line; 41 | extern std::string const erase_down; 42 | extern std::string const erase_up; 43 | extern std::string const erase_screen; 44 | 45 | // cursor visibility 46 | extern std::string const cursor_hide; 47 | extern std::string const cursor_show; 48 | 49 | // cursor movement 50 | extern std::string const cursor_home; 51 | extern std::string const cursor_up; 52 | extern std::string const cursor_down; 53 | extern std::string const cursor_right; 54 | extern std::string const cursor_left; 55 | extern std::string const cursor_save; 56 | extern std::string const cursor_load; 57 | 58 | // foreground color 59 | extern std::string const fg_black; 60 | extern std::string const fg_red; 61 | extern std::string const fg_green; 62 | extern std::string const fg_yellow; 63 | extern std::string const fg_blue; 64 | extern std::string const fg_magenta; 65 | extern std::string const fg_cyan; 66 | extern std::string const fg_white; 67 | 68 | // background color 69 | extern std::string const bg_black; 70 | extern std::string const bg_red; 71 | extern std::string const bg_green; 72 | extern std::string const bg_yellow; 73 | extern std::string const bg_blue; 74 | extern std::string const bg_magenta; 75 | extern std::string const bg_cyan; 76 | extern std::string const bg_white; 77 | 78 | // prototypes 79 | std::string fg_256(std::string x); 80 | std::string bg_256(std::string x); 81 | std::string htoi(std::string x); 82 | bool valid_hstr(std::string& str); 83 | std::string fg_true(std::string x); 84 | std::string bg_true(std::string x); 85 | std::string cursor_set(size_t x, size_t y); 86 | 87 | template 88 | std::string wrap(T const val, std::string const col) 89 | { 90 | std::stringstream ss; ss 91 | << col 92 | << val 93 | << reset; 94 | return ss.str(); 95 | } 96 | 97 | template 98 | std::string wrap(T const val, std::vector const col) 99 | { 100 | std::stringstream ss; 101 | for (auto const& e : col) 102 | { 103 | ss << e; 104 | } 105 | ss << val << reset; 106 | return ss.str(); 107 | } 108 | 109 | } // namespace ANSI_Escape_Codes 110 | 111 | } // namespace OB 112 | 113 | #endif // OB_ANSI_ESCAPE_CODES_HH 114 | -------------------------------------------------------------------------------- /src/crex.cc: -------------------------------------------------------------------------------- 1 | #include "crex.hh" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | namespace OB 9 | { 10 | 11 | Crex::Crex() 12 | { 13 | } 14 | 15 | Crex::~Crex() 16 | { 17 | } 18 | 19 | Crex& Crex::regex(std::string regex_) 20 | { 21 | _regex = std::move(regex_); 22 | return *this; 23 | } 24 | 25 | std::string Crex::regex() const 26 | { 27 | return _regex; 28 | } 29 | 30 | Crex& Crex::text(std::string text_) 31 | { 32 | _text = std::move(text_); 33 | return *this; 34 | } 35 | 36 | std::string Crex::text() const 37 | { 38 | return _text; 39 | } 40 | 41 | Crex& Crex::options(std::regex_constants::syntax_option_type opts_) 42 | { 43 | _opts = std::move(opts_); 44 | return *this; 45 | } 46 | 47 | std::regex_constants::syntax_option_type Crex::options() const 48 | { 49 | return _opts; 50 | } 51 | 52 | Crex& Crex::flags(std::regex_constants::match_flag_type flgs_) 53 | { 54 | _flgs = std::move(flgs_); 55 | return *this; 56 | } 57 | 58 | std::regex_constants::match_flag_type Crex::flags() const 59 | { 60 | return _flgs; 61 | } 62 | 63 | bool Crex::run() 64 | { 65 | std::regex rx {_regex, _opts}; 66 | auto regex_begin = std::sregex_iterator(_text.begin(), _text.end(), rx, _flgs); 67 | auto regex_end = std::sregex_iterator(); 68 | 69 | size_t pos {0}; 70 | for (auto imatch = regex_begin; imatch != regex_end; ++imatch) 71 | { 72 | std::smatch match {*imatch}; 73 | pos = static_cast(match.position()); 74 | std::string tmp {match[0].str()}; 75 | 76 | _matches.emplace_back(Match()); 77 | 78 | _matches.back().emplace_back( 79 | std::make_pair(match[0].str(), 80 | std::make_pair(pos, pos + static_cast(match[0].length()) - 1)) 81 | ); 82 | 83 | size_t pos_match {pos}; 84 | for (size_t i = 1; i < match.size(); ++i) 85 | { 86 | auto v = split(tmp, match[i].str()); 87 | pos_match += v.first.size(); 88 | 89 | _matches.back().emplace_back( 90 | std::make_pair(match[i].str(), 91 | std::make_pair(pos_match, pos_match + static_cast(match[i].length()) - 1)) 92 | ); 93 | 94 | tmp = v.second; 95 | pos_match += static_cast(match[i].length()); 96 | } 97 | } 98 | 99 | return (! _matches.empty()); 100 | } 101 | 102 | Crex::Matches const& Crex::matches() const 103 | { 104 | return _matches; 105 | } 106 | 107 | std::pair Crex::split(std::string str, std::string delim) const 108 | { 109 | std::pair ptok {"", ""}; 110 | auto pos = str.find(delim); 111 | if (pos != std::string::npos) { 112 | ptok.first = str.substr(0, pos); 113 | if (pos + delim.size() != std::string::npos) 114 | { 115 | ptok.second = str.substr(pos + delim.size()); 116 | } 117 | } 118 | return ptok; 119 | } 120 | 121 | } // namespace OB 122 | -------------------------------------------------------------------------------- /src/crex.hh: -------------------------------------------------------------------------------- 1 | #ifndef OB_CREX_HH 2 | #define OB_CREX_HH 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | namespace OB 10 | { 11 | 12 | class Crex 13 | { 14 | public: 15 | using Match = std::vector>>; 16 | using Matches = std::vector; 17 | 18 | Crex(); 19 | ~Crex(); 20 | 21 | Crex& regex(std::string regex_); 22 | std::string regex() const; 23 | 24 | Crex& text(std::string text_); 25 | std::string text() const; 26 | 27 | Crex& options(std::regex_constants::syntax_option_type opts_); 28 | std::regex_constants::syntax_option_type options() const; 29 | 30 | Crex& flags(std::regex_constants::match_flag_type flgs_); 31 | std::regex_constants::match_flag_type flags() const; 32 | 33 | bool run(); 34 | Matches const& matches() const; 35 | 36 | private: 37 | std::string _regex; 38 | std::string _text; 39 | 40 | std::regex_constants::syntax_option_type _opts {}; 41 | std::regex_constants::match_flag_type _flgs {}; 42 | 43 | Matches _matches; 44 | 45 | std::pair split(std::string str, std::string delim) const; 46 | 47 | }; // class Crex 48 | 49 | } // namespace OB 50 | 51 | #endif // OB_CREX_HH 52 | -------------------------------------------------------------------------------- /src/main.cc: -------------------------------------------------------------------------------- 1 | #include "parg.hh" 2 | using Parg = OB::Parg; 3 | 4 | #include "crex.hh" 5 | using Crex = OB::Crex; 6 | 7 | #include "ansi_escape_codes.hh" 8 | namespace aec = OB::ANSI_Escape_Codes; 9 | 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | 16 | int program_options(Parg& pg); 17 | std::regex_constants::syntax_option_type regex_options(Parg& pg); 18 | std::regex_constants::match_flag_type regex_flags(Parg& pg); 19 | bool is_tty(); 20 | void regex_print(std::string const& regex, std::string const& text, 21 | Crex::Matches const& matches, bool color); 22 | void regex_print_color(std::string const& regex, std::string const& text, 23 | Crex::Matches const& matches); 24 | void regex_print_no_color(std::string const& regex, std::string const& text, 25 | Crex::Matches const& matches); 26 | void regex_print_json(std::string& regex, std::string& text, 27 | Crex::Matches const& matches); 28 | std::string replace(std::string str, std::string key, std::string val); 29 | 30 | int program_options(Parg& pg) 31 | { 32 | pg.name("crex").version("0.2.5 (07.10.2018)"); 33 | pg.description("explore, test, and check regular expressions"); 34 | pg.usage("[flags] [options] [--] [arguments]"); 35 | pg.usage("[-e|-b|-x|-a|-g|-E] [-io] [-c|-j] [-r regex] [-s string]"); 36 | pg.usage("[-v|--version]"); 37 | pg.usage("[-h|--help]"); 38 | pg.info("Examples", { 39 | "crex -r '(hello)' -s 'hello world!'", 40 | "printf 'Hello World!' | crex -r '(hello)' -ioec", 41 | "crex --help", 42 | "crex --version", 43 | }); 44 | pg.info("Exit Codes", {"0 -> normal", "1 -> error"}); 45 | pg.info("Repository", { 46 | "https://github.com/octobanana/crex.git", 47 | }); 48 | pg.info("Homepage", { 49 | "https://octobanana.com/software/crex", 50 | }); 51 | pg.author("Brett Robinson (octobanana) "); 52 | 53 | // enable piped stdin 54 | pg.set_stdin(); 55 | 56 | // single flags 57 | pg.set("help,h", "print the help output"); 58 | pg.set("version,v", "print the program version"); 59 | 60 | // combinable flags 61 | pg.set("color,c", "print out results in color"); 62 | pg.set("json,j", "print out results in json"); 63 | 64 | // options 65 | pg.set("string,s", "", "str", "the string to search"); 66 | pg.set("regex,r", "", "str", "the regular expression to use"); 67 | pg.set("icase,i", "character matching should be performed without regard to case"); 68 | pg.set("optimize,o", "instructs the regular expression engine to make matching faster, with the potential cost of making construction slower"); 69 | pg.set("ecmascript,e", "use the modified ECMAScript regular expression grammar"); 70 | pg.set("basic,b", "use the basic POSIX regular expression grammar"); 71 | pg.set("extended,x", "use the extended POSIX regular expression grammar"); 72 | pg.set("awk,a", "use the regular expression grammar used by the awk utility in POSIX"); 73 | pg.set("grep,g", "use the regular expression grammar used by the grep utility in POSIX"); 74 | pg.set("egrep,E", "use the regular expression grammar used by the grep utility, with the -E option, in POSIX"); 75 | 76 | int status {pg.parse()}; 77 | if (status < 0) 78 | { 79 | std::cout << pg.help() << "\n"; 80 | std::cout << "Error: " << pg.error() << "\n"; 81 | return -1; 82 | } 83 | if (pg.get("help")) 84 | { 85 | std::cout << pg.help(); 86 | return 1; 87 | } 88 | if (pg.get("version")) 89 | { 90 | std::cout << pg.name() << " v" << pg.version() << "\n"; 91 | return 1; 92 | } 93 | if (! pg.find("regex") || (! pg.find("string") && pg.get_stdin().empty())) 94 | { 95 | std::cout << pg.help() << "\n"; 96 | std::cout << "Error: " << "expected '-r' and '-s' options" << "\n"; 97 | return -1; 98 | } 99 | return 0; 100 | } 101 | 102 | std::regex_constants::syntax_option_type regex_options(Parg& pg) 103 | { 104 | std::regex_constants::syntax_option_type opts {}; 105 | 106 | if (pg.get("icase")) 107 | { 108 | opts |= std::regex::icase; 109 | } 110 | 111 | if (pg.get("optimize")) 112 | { 113 | opts |= std::regex::optimize; 114 | } 115 | 116 | if (pg.get("ecmascript")) 117 | { 118 | opts |= std::regex::ECMAScript; 119 | } 120 | else if (pg.get("basic")) 121 | { 122 | opts |= std::regex::basic; 123 | } 124 | else if (pg.get("extended")) 125 | { 126 | opts |= std::regex::extended; 127 | } 128 | else if (pg.get("awk")) 129 | { 130 | opts |= std::regex::awk; 131 | } 132 | else if (pg.get("grep")) 133 | { 134 | opts |= std::regex::grep; 135 | } 136 | else if (pg.get("egrep")) 137 | { 138 | opts |= std::regex::egrep; 139 | } 140 | else 141 | { 142 | opts |= std::regex::ECMAScript; 143 | } 144 | 145 | return opts; 146 | } 147 | 148 | std::regex_constants::match_flag_type regex_flags(Parg& pg) 149 | { 150 | std::regex_constants::match_flag_type flgs {std::regex_constants::match_not_null}; 151 | return flgs; 152 | } 153 | 154 | bool is_tty() 155 | { 156 | return isatty(STDOUT_FILENO); 157 | } 158 | 159 | std::string replace(std::string str, std::string key, std::string val) 160 | { 161 | size_t pos {0}; 162 | for (;;) 163 | { 164 | pos = str.find(key, pos); 165 | if (pos == std::string::npos) break; 166 | str.replace(pos, key.size(), val); 167 | pos += val.size(); 168 | } 169 | return str; 170 | } 171 | 172 | class Color_Ring 173 | { 174 | public: 175 | Color_Ring(std::vector colors): 176 | _colors {colors} 177 | { 178 | } 179 | 180 | std::string next() 181 | { 182 | auto res = get(); 183 | if (++_pos > _colors.size() - 1) 184 | { 185 | _pos = 0; 186 | } 187 | return res; 188 | } 189 | 190 | std::string get() const 191 | { 192 | return _colors.at(_pos); 193 | } 194 | 195 | Color_Ring& reset() 196 | { 197 | _pos = 0; 198 | return *this; 199 | } 200 | 201 | private: 202 | std::vector const _colors; 203 | size_t _pos {0}; 204 | }; 205 | 206 | struct Style 207 | { 208 | std::vector h1 {aec::fg_white, aec::bold, aec::underline}; 209 | std::vector h2 {aec::fg_green, aec::bold}; 210 | std::vector h3 {aec::fg_magenta, aec::bold}; 211 | std::vector num {aec::fg_cyan, aec::bold}; 212 | std::string match {aec::fg_green}; 213 | std::string plain {}; 214 | std::vector special {aec::fg_white, aec::bold}; 215 | Color_Ring ring {{ 216 | aec::fg_magenta, 217 | aec::fg_blue, 218 | aec::fg_cyan, 219 | aec::fg_green, 220 | aec::fg_yellow, 221 | aec::fg_red 222 | }}; 223 | }; 224 | 225 | void regex_print(std::string const& regex, std::string const& text, 226 | Crex::Matches const& matches, bool color) 227 | { 228 | if (color) 229 | { 230 | regex_print_color(regex, text, matches); 231 | } 232 | else 233 | { 234 | regex_print_no_color(regex, text, matches); 235 | } 236 | } 237 | 238 | void regex_print_color(std::string const& regex, std::string const& text, 239 | Crex::Matches const& matches) 240 | { 241 | Style style; 242 | 243 | std::stringstream ss_head; 244 | std::stringstream ss; 245 | 246 | ss_head 247 | << aec::wrap("Regex", style.h1) << "\n" 248 | << regex 249 | << "\n\n" 250 | << aec::wrap("Text", style.h1) << "\n"; 251 | 252 | ss 253 | << "\n\n" 254 | << aec::wrap("Matches", style.h1) << "[" << aec::wrap(matches.size(), style.num) << "]\n"; 255 | 256 | size_t pos {0}; 257 | 258 | for (size_t i = 0; i < matches.size(); ++i) 259 | { 260 | style.ring.reset(); 261 | 262 | auto const& match = matches.at(i); 263 | 264 | ss 265 | << aec::wrap("Match", style.h2) << "[" << aec::wrap(i + 1, style.num) << "] " 266 | << aec::wrap(match.at(0).second.first, style.num) 267 | << "-" 268 | << aec::wrap(match.at(0).second.second, style.num) 269 | << aec::wrap(" |", style.special); 270 | 271 | std::string const& tmatch {match.at(0).first}; 272 | size_t const offset {match.at(0).second.first}; 273 | 274 | size_t prev {0}; 275 | size_t begin {0}; 276 | size_t end {0}; 277 | 278 | std::stringstream ss_text; 279 | std::stringstream ss_match; 280 | 281 | for (size_t j = 1; j < match.size(); ++j) 282 | { 283 | auto ftext = aec::wrap(match.at(j).first, style.ring.next()); 284 | 285 | begin = match.at(j).second.first - offset; 286 | end = match.at(j).second.second - offset; 287 | 288 | if (prev <= begin) 289 | { 290 | ss_text 291 | << aec::wrap(tmatch.substr(prev, begin - prev), style.plain); 292 | } 293 | ss_text 294 | << ftext; 295 | 296 | prev = end + 1; 297 | 298 | ss_match 299 | << aec::wrap("Group", style.h3) << "[" << aec::wrap(j, style.num) << "] " 300 | << aec::wrap(begin + offset, style.num) 301 | << "-" 302 | << aec::wrap(end + offset, style.num) 303 | << aec::wrap(" |", style.special) 304 | << ftext 305 | << aec::wrap("|\n", style.special); 306 | } 307 | 308 | if (match.size() < 2) 309 | { 310 | ss_text 311 | << aec::wrap(tmatch, style.plain); 312 | } 313 | else if (prev < tmatch.size()) 314 | { 315 | ss_text 316 | << aec::wrap(tmatch.substr(prev, begin - prev), style.plain); 317 | } 318 | ss_match 319 | << "\n"; 320 | 321 | ss 322 | << ss_text.str() 323 | << aec::wrap("|\n", style.special) 324 | << ss_match.str(); 325 | 326 | if (pos < offset) 327 | { 328 | ss_head 329 | << aec::wrap(text.substr(pos, offset - pos), style.plain); 330 | } 331 | 332 | pos = offset + match.at(0).first.size() ; 333 | 334 | ss_head 335 | << aec::wrap("(", style.special) 336 | << ss_text.str() 337 | << aec::wrap(")[", style.special) 338 | << aec::wrap(i + 1, style.num) 339 | << aec::wrap("]", style.special); 340 | } 341 | 342 | if (pos < text.size()) 343 | { 344 | ss_head 345 | << aec::wrap(text.substr(pos, text.size() - pos), style.plain); 346 | } 347 | 348 | std::cout << ss_head.str() << ss.str(); 349 | } 350 | 351 | void regex_print_no_color(std::string const& regex, std::string const& text, 352 | Crex::Matches const& matches) 353 | { 354 | std::stringstream ss; 355 | 356 | ss 357 | << "Regex\n" 358 | << regex 359 | << "\n\n" 360 | << "Text\n" 361 | << text 362 | << "\n\n" 363 | << "Matches[" << matches.size() << "]\n"; 364 | 365 | for (size_t i = 0; i < matches.size(); ++i) 366 | { 367 | auto const& match = matches.at(i); 368 | 369 | ss 370 | << "Match[" << i + 1 << "] " 371 | << match.at(0).second.first << "-" << match.at(0).second.second 372 | << " |" << match.at(0).first << "|\n"; 373 | 374 | for (size_t j = 1; j < match.size(); ++j) 375 | { 376 | ss 377 | << "Group[" << j << "] " 378 | << match.at(j).second.first << "-" << match.at(j).second.second 379 | << " |" << match.at(j).first << "|\n"; 380 | } 381 | ss 382 | << "\n"; 383 | } 384 | 385 | std::cout << ss.str(); 386 | } 387 | 388 | void regex_print_json(std::string& regex, std::string& text, 389 | Crex::Matches const& matches) 390 | { 391 | auto const get_matches = [&]() { 392 | std::stringstream ss; 393 | 394 | for (size_t i = 0; i < matches.size(); ++i) 395 | { 396 | auto const& match = matches.at(i); 397 | 398 | ss 399 | << "["; 400 | 401 | for (size_t j = 0; j < match.size(); ++j) 402 | { 403 | auto mtext = match.at(j).first; 404 | mtext = replace(mtext, "\"", "\\\""); 405 | mtext = replace(mtext, "\\", "\\\\"); 406 | 407 | ss 408 | << "{" 409 | << "\"mtext\":\"" << mtext << "\"," 410 | << "\"begin\":" << match.at(j).second.first << "," 411 | << "\"end\":" << match.at(j).second.second 412 | << "}"; 413 | 414 | if (j != match.size() - 1) 415 | { 416 | ss 417 | << ","; 418 | } 419 | } 420 | 421 | ss 422 | << "]"; 423 | 424 | if (i != matches.size() - 1) 425 | { 426 | ss 427 | << ","; 428 | } 429 | } 430 | 431 | return ss.str(); 432 | }; 433 | 434 | std::stringstream ss; 435 | 436 | regex = replace(regex, "\"", "\\\""); 437 | regex = replace(regex, "\\", "\\\\"); 438 | 439 | text = replace(text, "\"", "\\\""); 440 | text = replace(text, "\\", "\\\\"); 441 | 442 | ss 443 | << "{" 444 | << "\"regex\":\"" << regex << "\"," 445 | << "\"text\":\"" << text << "\"," 446 | << "\"matches\":" 447 | << "[" 448 | << get_matches() 449 | << "]}"; 450 | 451 | std::cout << ss.str(); 452 | } 453 | 454 | int main(int argc, char *argv[]) 455 | { 456 | Parg pg {argc, argv}; 457 | int pstatus {program_options(pg)}; 458 | if (pstatus > 0) return 0; 459 | if (pstatus < 0) return 1; 460 | 461 | try 462 | { 463 | std::string regex {pg.get("regex")}; 464 | std::string text {pg.get_stdin() + pg.get("string")}; 465 | 466 | if (regex.empty() || text.empty()) 467 | { 468 | if (regex.empty()) 469 | { 470 | throw std::runtime_error( 471 | "regex is empty, expected '-r' option" 472 | "\nView the help output with '-h'" 473 | ); 474 | } 475 | if (text.empty()) 476 | { 477 | throw std::runtime_error( 478 | "text is empty, expected '-s' option or piped stdin" 479 | "\nView the help output with '-h'" 480 | ); 481 | } 482 | } 483 | 484 | OB::Crex crex {}; 485 | crex.regex(regex); 486 | crex.text(text); 487 | crex.options(regex_options(pg)); 488 | crex.flags(regex_flags(pg)); 489 | 490 | if (! crex.run()) 491 | { 492 | throw std::runtime_error("no matches found"); 493 | } 494 | 495 | if (pg.get("json")) 496 | { 497 | regex_print_json(regex, text, crex.matches()); 498 | } 499 | else 500 | { 501 | regex_print(regex, text, crex.matches(), pg.get("color")); 502 | } 503 | } 504 | catch (std::exception const& e) 505 | { 506 | std::cerr << "Error: " << e.what() << "\n"; 507 | return 1; 508 | } 509 | catch (...) 510 | { 511 | std::cerr << "Error: an unexpected error occurred\n"; 512 | return 1; 513 | } 514 | 515 | return 0; 516 | } 517 | -------------------------------------------------------------------------------- /src/parg.hh: -------------------------------------------------------------------------------- 1 | // 2 | // MIT License 3 | // 4 | // Copyright (c) 2018 Brett Robinson 5 | // 6 | // Permission is hereby granted, free of charge, to any person obtaining a copy 7 | // of this software and associated documentation files (the "Software"), to deal 8 | // in the Software without restriction, including without limitation the rights 9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | // copies of the Software, and to permit persons to whom the Software is 11 | // furnished to do so, subject to the following conditions: 12 | // 13 | // The above copyright notice and this permission notice shall be included in all 14 | // copies or substantial portions of the Software. 15 | // 16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | // SOFTWARE. 23 | // 24 | 25 | #ifndef OB_PARG_HH 26 | #define OB_PARG_HH 27 | 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include 33 | #include 34 | #include 35 | #include 36 | #include 37 | #include 38 | 39 | namespace OB 40 | { 41 | class Parg 42 | { 43 | public: 44 | Parg() 45 | { 46 | } 47 | 48 | Parg(int _argc, char** _argv) 49 | { 50 | argc_ = _argc; 51 | argvf(_argv); 52 | } 53 | 54 | Parg& name(std::string const _name) 55 | { 56 | name_ = _name; 57 | return *this; 58 | } 59 | 60 | std::string name() const 61 | { 62 | return name_; 63 | } 64 | 65 | Parg& version(std::string const _version) 66 | { 67 | version_ = _version; 68 | return *this; 69 | } 70 | 71 | std::string version() const 72 | { 73 | return version_; 74 | } 75 | 76 | Parg& usage(std::string const _usage) 77 | { 78 | usage_ += " " + name_ + " " + _usage + "\n"; 79 | return *this; 80 | } 81 | 82 | std::string usage() const 83 | { 84 | return usage_; 85 | } 86 | 87 | Parg& description(std::string const _description) 88 | { 89 | description_ = _description; 90 | return *this; 91 | } 92 | 93 | std::string description() const 94 | { 95 | return description_; 96 | } 97 | 98 | Parg& info(std::string const _title, std::vector const _text) 99 | { 100 | info_.emplace_back(info_pair{_title, _text}); 101 | return *this; 102 | } 103 | 104 | Parg& author(std::string const _author) 105 | { 106 | author_ = _author; 107 | return *this; 108 | } 109 | 110 | std::string author() const 111 | { 112 | return author_; 113 | } 114 | 115 | std::string help() const 116 | { 117 | std::stringstream out; 118 | if (! description_.empty()) 119 | { 120 | out << name_ << ":" << "\n"; 121 | std::stringstream ss; 122 | out << " " << description_ << "\n"; 123 | out << ss.str() << "\n"; 124 | } 125 | 126 | if (! usage_.empty()) 127 | { 128 | out << "Usage: " << "\n" 129 | << usage_ << "\n"; 130 | } 131 | 132 | if (! modes_.empty()) 133 | { 134 | out << "Flags: " << "\n" 135 | << modes_; 136 | } 137 | 138 | if (! options_.empty()) 139 | { 140 | out << "\nOptions: " << "\n" 141 | << options_; 142 | } 143 | 144 | if (! info_.empty()) 145 | { 146 | for (auto const& e : info_) 147 | { 148 | out << "\n" << e.title << ":" << "\n"; 149 | 150 | for (auto const& t : e.text) 151 | { 152 | out << " " << t << "\n"; 153 | } 154 | } 155 | } 156 | 157 | if (! author_.empty()) 158 | { 159 | out << "\nAuthor: " << "\n"; 160 | std::stringstream ss; 161 | ss << " " << author_ << "\n"; 162 | out << ss.str(); 163 | } 164 | 165 | return out.str(); 166 | } 167 | 168 | int parse() 169 | { 170 | if (is_stdin_) 171 | { 172 | pipe_stdin(); 173 | } 174 | status_ = parse_args(argc_, argv_); 175 | return status_; 176 | } 177 | 178 | int parse(int argc, char** argv) 179 | { 180 | if (is_stdin_) 181 | { 182 | pipe_stdin(); 183 | } 184 | argc_ = argc; 185 | argvf(argv); 186 | status_ = parse_args(argc_, argv_); 187 | return status_; 188 | } 189 | 190 | int parse(std::string str) 191 | { 192 | auto args = str_to_args(str); 193 | status_ = parse_args(args.size(), args); 194 | return status_; 195 | } 196 | 197 | std::vector str_to_args(std::string const& str) 198 | { 199 | std::vector args; 200 | 201 | std::string const backslash {"\\"}; 202 | 203 | // parse str into arg vector as if it was parsed by the shell 204 | for (size_t i = 0; i < str.size(); ++i) 205 | { 206 | std::string e {str.at(i)}; 207 | 208 | // default 209 | if (e.find_first_not_of(" \n\t\"'") != std::string::npos) 210 | { 211 | bool escaped {false}; 212 | size_t start {i}; 213 | args.emplace_back(""); 214 | for (;i < str.size(); ++i) 215 | { 216 | e = str.at(i); 217 | if (! escaped && e.find_first_of(" \n\t") != std::string::npos) 218 | { 219 | --i; // put back unmatched char 220 | break; 221 | } 222 | else if (e == backslash) 223 | { 224 | escaped = true; 225 | } 226 | else if (escaped) 227 | { 228 | args.back() += e; 229 | escaped = false; 230 | } 231 | else 232 | { 233 | args.back() += e; 234 | } 235 | } 236 | continue; 237 | } 238 | 239 | // whitespace 240 | else if (e.find_first_of(" \n\t") != std::string::npos) 241 | { 242 | for (;i < str.size(); ++i) 243 | { 244 | e = str.at(i); 245 | if (e.find_first_not_of(" \n\t") != std::string::npos) 246 | { 247 | --i; // put back unmatched char 248 | break; 249 | } 250 | } 251 | continue; 252 | } 253 | 254 | // string 255 | else if (e.find_first_of("\"'") != std::string::npos) 256 | { 257 | std::string quote {e}; 258 | bool escaped {false}; 259 | ++i; // skip start quote 260 | args.emplace_back(""); 261 | size_t start {i}; 262 | for (;i < str.size(); ++i) 263 | { 264 | e = str.at(i); 265 | if (! escaped && e == quote) 266 | { 267 | break; 268 | // skip end quote 269 | } 270 | else if (e == backslash) 271 | { 272 | escaped = true; 273 | } 274 | else if (escaped) 275 | { 276 | args.back() += e; 277 | escaped = false; 278 | } 279 | else 280 | { 281 | args.back() += e; 282 | } 283 | } 284 | } 285 | } 286 | return args; 287 | } 288 | 289 | void set(std::string _name, std::string _info) 290 | { 291 | // sets a flag 292 | std::string delim {","}; 293 | if (_name.find(delim) != std::string::npos) 294 | { 295 | // short and long 296 | bool has_short {false}; 297 | std::string _long; 298 | std::string _short; 299 | 300 | auto const names = delimit(_name, delim); 301 | assert(names.size() >= 1 && names.size() <= 2); 302 | _long = names.at(0); 303 | 304 | if (names.size() == 2) has_short = true; 305 | if (has_short) 306 | { 307 | // short name must be one char 308 | assert(names.at(1).size() == 1); 309 | _short = names.at(1); 310 | } 311 | 312 | flags_[_short] = _long; 313 | data_[_long].long_ = _long; 314 | data_[_long].short_ = _short; 315 | data_[_long].mode_ = true; 316 | data_[_long].value_ = "0"; 317 | modes_.append(" -" + _short + ", --" + _long + "\n"); 318 | } 319 | else 320 | { 321 | if (_name.size() == 1) 322 | { 323 | // short 324 | flags_[_name] = _name; 325 | data_[_name].long_ = _name; 326 | data_[_name].short_ = _name; 327 | data_[_name].mode_ = true; 328 | data_[_name].value_ = "0"; 329 | modes_.append(" -" + _name + "\n"); 330 | } 331 | else 332 | { 333 | // long 334 | data_[_name].long_ = _name; 335 | data_[_name].mode_ = true; 336 | data_[_name].value_ = "0"; 337 | modes_.append(" --" + _name + "\n"); 338 | } 339 | } 340 | 341 | std::stringstream out; 342 | out << " " << _info << "\n"; 343 | modes_.append(out.str()); 344 | } 345 | 346 | void set(std::string _name, std::string _default, std::string _arg, std::string _info) 347 | { 348 | // sets an option 349 | std::string delim {","}; 350 | if (_name.find(delim) != std::string::npos) 351 | { 352 | bool has_short {false}; 353 | std::string _long; 354 | std::string _short; 355 | 356 | auto const names = delimit(_name, delim); 357 | assert(names.size() >= 1 && names.size() <= 2); 358 | _long = names.at(0); 359 | 360 | if (names.size() == 2) has_short = true; 361 | if (has_short) 362 | { 363 | // short name must be one char 364 | assert(names.at(1).size() == 1); 365 | _short = names.at(1); 366 | } 367 | 368 | flags_[_short] = _long; 369 | data_[_long].long_ = _long; 370 | data_[_long].short_ = _short; 371 | data_[_long].mode_ = false; 372 | data_[_long].value_ = _default; 373 | options_.append(" -" + _short + ", --" + _long + "=<" + _arg + ">\n"); 374 | } 375 | else 376 | { 377 | if (_name.size() == 1) 378 | { 379 | // short 380 | flags_[_name] = _name; 381 | data_[_name].long_ = _name; 382 | data_[_name].short_ = _name; 383 | data_[_name].mode_ = false; 384 | data_[_name].value_ = _default; 385 | options_.append(" -" + _name + "=<" + _arg + ">\n"); 386 | } 387 | else 388 | { 389 | // long 390 | data_[_name].long_ = _name; 391 | data_[_name].mode_ = false; 392 | data_[_name].value_ = _default; 393 | options_.append(" --" + _name + "=<" + _arg + ">\n"); 394 | } 395 | } 396 | 397 | std::stringstream out; 398 | out << " " << _info << "\n"; 399 | options_.append(out.str()); 400 | } 401 | 402 | template 403 | T get(std::string const _key) 404 | { 405 | if (data_.find(_key) == data_.end()) 406 | { 407 | throw std::logic_error("parg get '" + _key + "' is not defined"); 408 | } 409 | std::stringstream ss; 410 | ss << data_[_key].value_; 411 | T val; 412 | ss >> val; 413 | return val; 414 | } 415 | 416 | std::string get(std::string const _key) 417 | { 418 | if (data_.find(_key) == data_.end()) 419 | { 420 | throw std::logic_error("parg get '" + _key + "' is not defined"); 421 | } 422 | return data_[_key].value_; 423 | } 424 | 425 | bool find(std::string const _key) const 426 | { 427 | // key must exist 428 | if (data_.find(_key) == data_.end()) return false; 429 | return data_.at(_key).seen_; 430 | } 431 | 432 | Parg& set_pos(bool const _positional = true) 433 | { 434 | is_positional_ = _positional; 435 | return *this; 436 | } 437 | 438 | std::string get_pos() const 439 | { 440 | std::string str; 441 | if (positional_vec_.empty()) 442 | { 443 | return str; 444 | } 445 | for (auto const& e : positional_vec_) 446 | { 447 | str += e + " "; 448 | } 449 | str.pop_back(); 450 | return str; 451 | } 452 | 453 | std::vector get_pos_vec() const 454 | { 455 | return positional_vec_; 456 | } 457 | 458 | Parg& set_stdin(bool const _stdin = true) 459 | { 460 | is_stdin_ = _stdin; 461 | return *this; 462 | } 463 | 464 | std::string get_stdin() const 465 | { 466 | return stdin_; 467 | } 468 | 469 | int status() const 470 | { 471 | return status_; 472 | } 473 | 474 | std::string error() const 475 | { 476 | return error_; 477 | } 478 | 479 | struct Option 480 | { 481 | std::string short_; 482 | std::string long_; 483 | bool mode_; 484 | std::string value_; 485 | bool seen_ {false}; 486 | }; 487 | 488 | struct info_pair 489 | { 490 | std::string title; 491 | std::vector text; 492 | }; 493 | 494 | private: 495 | int argc_ {0}; 496 | std::vector argv_; 497 | std::string name_; 498 | std::string version_; 499 | std::string usage_; 500 | std::string description_; 501 | std::string modes_; 502 | std::string options_; 503 | int options_indent_ {0}; 504 | std::vector info_; 505 | std::string author_; 506 | std::map data_; 507 | std::map flags_; 508 | bool is_positional_ {false}; 509 | std::string positional_; 510 | std::vector positional_vec_; 511 | std::string stdin_; 512 | bool is_stdin_ {false}; 513 | int status_ {0}; 514 | std::string error_; 515 | 516 | void argvf(char** _argv) 517 | { 518 | // removes first arg 519 | if (argc_ < 1) return; 520 | for (int i = 1; i < argc_; ++i) 521 | { 522 | argv_.emplace_back(_argv[i]); 523 | } 524 | // std::cerr << "argv: " << i << " -> " << argv_.at(i) << std::endl; 525 | --argc_; 526 | } 527 | 528 | int pipe_stdin() 529 | { 530 | if (! isatty(STDIN_FILENO)) 531 | { 532 | stdin_.assign((std::istreambuf_iterator(std::cin)), 533 | (std::istreambuf_iterator())); 534 | return 0; 535 | } 536 | stdin_ = ""; 537 | return -1; 538 | } 539 | 540 | std::vector delimit(const std::string str, const std::string delim) const 541 | { 542 | std::vector vtok; 543 | size_t start {0}; 544 | size_t end = str.find(delim); 545 | while (end != std::string::npos) { 546 | vtok.emplace_back(str.substr(start, end - start)); 547 | start = end + delim.length(); 548 | end = str.find(delim, start); 549 | } 550 | vtok.emplace_back(str.substr(start, end)); 551 | return vtok; 552 | } 553 | 554 | int parse_args(int _argc, std::vector _argv) 555 | { 556 | if (_argc < 1) return 1; 557 | 558 | bool dashdash {false}; 559 | 560 | // loop through arg vector 561 | for (int i = 0; i < _argc; ++i) 562 | { 563 | std::string const& tmp {_argv.at(static_cast(i))}; 564 | // std::cerr << "ARG: " << i << " -> " << tmp << std::endl; 565 | 566 | if (dashdash) 567 | { 568 | positional_vec_.emplace_back(tmp); 569 | continue; 570 | } 571 | 572 | if (tmp.size() > 1 && tmp.at(0) == '-' && tmp.at(1) != '-') 573 | { 574 | // short 575 | // std::cerr << "SHORT: " << tmp << std::endl; 576 | 577 | std::string c {tmp.at(1)}; 578 | if (flags_.find(c) != flags_.end() && !(data_.at(flags_.at(c)).mode_)) 579 | { 580 | // short arg 581 | // std::cerr << "SHORT: arg -> " << c << std::endl; 582 | 583 | if (data_.at(flags_.at(c)).seen_) 584 | { 585 | // error 586 | error_ = "flag '-" + c + "' has already been seen"; 587 | return -1; 588 | } 589 | 590 | if (tmp.size() > 2 && tmp.at(2) != '=') 591 | { 592 | data_.at(flags_.at(c)).value_ = tmp.substr(2, tmp.size() - 1); 593 | data_.at(flags_.at(c)).seen_ = true; 594 | } 595 | else if (tmp.size() > 3 && tmp.at(2) == '=') 596 | { 597 | data_.at(flags_.at(c)).value_ = tmp.substr(3, tmp.size() - 1); 598 | data_.at(flags_.at(c)).seen_ = true; 599 | } 600 | else if (i + 1 < _argc) 601 | { 602 | data_.at(flags_.at(c)).value_ = _argv.at(static_cast(i + 1)); 603 | data_.at(flags_.at(c)).seen_ = true; 604 | ++i; 605 | } 606 | else 607 | { 608 | // error 609 | error_ = "flag '-" + c + "' requires an arg"; 610 | return -1; 611 | } 612 | } 613 | else 614 | { 615 | // short mode 616 | for (size_t j = 1; j < tmp.size(); ++j) 617 | { 618 | std::string s {tmp.at(j)}; 619 | 620 | if (flags_.find(s) != flags_.end() && data_.at(flags_.at(s)).mode_) 621 | { 622 | // std::cerr << "SHORT: mode -> " << s << std::endl; 623 | 624 | if (data_.at(flags_.at(s)).seen_) 625 | { 626 | // error 627 | error_ = "flag '-" + s + "' has already been seen"; 628 | return -1; 629 | } 630 | 631 | data_.at(flags_.at(s)).value_ = "1"; 632 | data_.at(flags_.at(s)).seen_ = true; 633 | } 634 | else 635 | { 636 | // error 637 | error_ = "invalid flag '" + tmp + "'"; 638 | return -1; 639 | } 640 | } 641 | } 642 | } 643 | else if (tmp.size() > 2 && tmp.at(0) == '-' && tmp.at(1) == '-') 644 | { 645 | // long || -- 646 | // std::cerr << "LONG: " << tmp << std::endl; 647 | std::string c {tmp.substr(2, tmp.size() - 1)}; 648 | std::string a; 649 | 650 | auto const delim = c.find("="); 651 | if (delim != std::string::npos) 652 | { 653 | c = tmp.substr(2, delim); 654 | a = tmp.substr(3 + delim, tmp.size() - 1); 655 | } 656 | 657 | if (data_.find(c) != data_.end()) 658 | { 659 | if (data_.at(c).seen_) 660 | { 661 | // error 662 | error_ = "option '--" + c + "' has already been seen"; 663 | return -1; 664 | } 665 | 666 | if (data_.at(c).mode_ && a.size() == 0) 667 | { 668 | // std::cerr << "LONG: mode -> " << c << std::endl; 669 | data_.at(c).value_ = "1"; 670 | data_.at(c).seen_ = true; 671 | } 672 | else 673 | { 674 | // std::cerr << "LONG: arg -> " << c << std::endl; 675 | if (a.size() > 0) 676 | { 677 | data_.at(c).value_ = a; 678 | data_.at(c).seen_ = true; 679 | } 680 | else if (i + 1 < _argc) 681 | { 682 | data_.at(c).value_ = _argv.at(static_cast(i + 1)); 683 | data_.at(c).seen_ = true; 684 | ++i; 685 | } 686 | else 687 | { 688 | // error 689 | error_ = "option '--" + c + "' requires an arg"; 690 | return -1; 691 | } 692 | } 693 | } 694 | else 695 | { 696 | // error 697 | error_ = "invalid option '" + tmp + "'"; 698 | return -1; 699 | } 700 | } 701 | else if (tmp.size() > 0 && is_positional_) 702 | { 703 | // positional 704 | // std::cerr << "POS: " << tmp << std::endl; 705 | if (tmp == "--") 706 | { 707 | dashdash = true; 708 | } 709 | else 710 | { 711 | positional_vec_.emplace_back(tmp); 712 | } 713 | } 714 | else 715 | { 716 | // error 717 | error_ = "no match for '" + tmp + "'"; 718 | return -1; 719 | } 720 | } 721 | 722 | return 0; 723 | } 724 | }; // class Parg 725 | } // namespace OB 726 | 727 | #endif // OB_PARG_HH 728 | --------------------------------------------------------------------------------