Rework option parsing, change --no-filename.
continuous-integration/drone/push Build is failing Details

Options are now better accessible, --no-filename accepts the values filesystem,
in-epub or all.
This commit is contained in:
tastytea 2021-05-27 17:20:00 +02:00
parent c376ce8466
commit e64591f204
Signed by: tastytea
GPG Key ID: CFC39497F1B26E07
7 changed files with 157 additions and 84 deletions

View File

@ -58,9 +58,11 @@ Print _NUMBER_ words of context around matches.
*--nocolor*:: *--nocolor*::
Do not color matches. Do not color matches.
*--no-filename*:: *--no-filename* _WHICH_::
Suppress the mentioning of EPUB file names on output. File names inside the
EPUB, chapters and page numbers will still be output. Suppress the mentioning of file names on output. _WHICH_ is filesystem for the
file names on your file systems, in-epub for the file names inside the EPUB or
all. Chapters and page numbers will still be output.
*-r*, *--recursive*: *-r*, *--recursive*:
Read all files under each directory, recursively, following symbolic links only Read all files under each directory, recursively, following symbolic links only

View File

@ -21,8 +21,6 @@
#include <boost/locale/generator.hpp> #include <boost/locale/generator.hpp>
#include <boost/locale/message.hpp> #include <boost/locale/message.hpp>
#include <boost/program_options/errors.hpp>
#include <boost/program_options/variables_map.hpp>
#include <fmt/format.h> #include <fmt/format.h>
#include <fmt/ostream.h> // For compatibility with fmt 4. #include <fmt/ostream.h> // For compatibility with fmt 4.
#include <termcolor/termcolor.hpp> #include <termcolor/termcolor.hpp>
@ -42,7 +40,6 @@
int main(int argc, char *argv[]) int main(int argc, char *argv[])
{ {
namespace po = boost::program_options;
using namespace epubgrep; using namespace epubgrep;
using boost::locale::translate; using boost::locale::translate;
@ -63,10 +60,10 @@ int main(int argc, char *argv[])
cout.imbue(std::locale()); cout.imbue(std::locale());
cerr.imbue(std::locale()); cerr.imbue(std::locale());
po::variables_map vm; options::options opts;
try try
{ {
vm = options::parse_options(argc, argv); opts = options::parse_options(argc, argv);
} }
catch (std::exception &e) catch (std::exception &e)
{ // Exceptions we can't recover from or ones we don't know. { // Exceptions we can't recover from or ones we don't know.
@ -75,7 +72,7 @@ int main(int argc, char *argv[])
return EXIT_FAILURE; return EXIT_FAILURE;
} }
if (vm.count("help") + vm.count("version") > 0) if (opts.help || opts.version)
{ {
return EXIT_SUCCESS; return EXIT_SUCCESS;
} }
@ -83,31 +80,26 @@ int main(int argc, char *argv[])
int return_code{EXIT_SUCCESS}; int return_code{EXIT_SUCCESS};
vector<fs::path> input_files; vector<fs::path> input_files;
if (vm.count("input-file") == 0) if (opts.input_file.empty())
{ {
cout << "NO INPUT FILE\n"; cout << "NO INPUT FILE\n";
// TODO: Read data from stdin. // TODO: Read data from stdin.
return EXIT_FAILURE; return EXIT_FAILURE;
} }
for (const auto &filepath : vm["input-file"].as<vector<string>>()) for (const auto &filepath : opts.input_file)
{ {
if (vm.count("recursive") + vm.count("dereference-recursive") == 0) if (!opts.recursive && !opts.dereference_recursive)
{ {
input_files.emplace_back(filepath); input_files.emplace_back(filepath);
} }
else else
{ {
bool follow_symlinks{false};
if (vm.count("dereference-recursive") > 0)
{
follow_symlinks = true;
}
try try
{ {
auto files_in_dir{ auto files_in_dir{
files::list_recursive(filepath, follow_symlinks)}; files::list_recursive(filepath,
opts.dereference_recursive)};
input_files.insert(input_files.end(), files_in_dir.begin(), input_files.insert(input_files.end(), files_in_dir.begin(),
files_in_dir.end()); files_in_dir.end());
} }
@ -130,45 +122,25 @@ int main(int argc, char *argv[])
} }
} }
search::options opts; search::settings search_settings;
if (vm.count("basic-regexp") > 0) search_settings.regex = opts.regex;
{ search_settings.grep_like = opts.grep;
opts.regex = search::regex_kind::basic; search_settings.ignore_case = opts.ignore_case;
} search_settings.raw = opts.raw;
if (vm.count("extended-regexp") > 0) search_settings.context = opts.context;
{
opts.regex = search::regex_kind::extended;
}
if (vm.count("perl-regexp") > 0)
{
opts.regex = search::regex_kind::perl;
}
if (vm.count("grep") > 0)
{
opts.grep_like = true;
}
if (vm.count("ignore-case") > 0)
{
opts.ignore_case = true;
}
if (vm.count("raw") > 0)
{
opts.raw = true;
}
opts.context = vm["context"].as<std::uint64_t>();
vector<vector<search::match>> matches_all; vector<vector<search::match>> matches_all;
vector<std::future<int>> futurepool; vector<std::future<int>> futurepool;
auto search_file{ auto search_file{
[&vm, &matches_all, &opts](fs::path filepath) [&opts, &matches_all, &search_settings](fs::path filepath)
{ {
for (const auto &regex : vm["regexp"].as<vector<string>>()) for (const auto &regex : opts.regexp)
{ {
try try
{ {
matches_all.emplace_back( matches_all.emplace_back(
search::search(filepath, regex, opts)); search::search(filepath, regex, search_settings));
} }
catch (const std::exception &e) catch (const std::exception &e)
{ {
@ -231,43 +203,54 @@ int main(int argc, char *argv[])
fs::path last_epub; fs::path last_epub;
for (const auto &match : matches_file) for (const auto &match : matches_file)
{ {
if (input_files.size() <= 1 || vm.count("no-filename") == 0) if (input_files.size() > 1 && !opts.no_fn_fs)
{ {
if (match.epub_filepath != last_epub) if (match.epub_filepath != last_epub)
{ {
if (vm.count("nocolor") == 0) if (!opts.nocolor)
{ {
cout << termcolor::yellow; cout << termcolor::yellow;
} }
cout << format(translate(" In {0:s}: \n").str().data(), cout << format(translate(" In {0:s}: \n").str().data(),
fs::relative(match.epub_filepath)); fs::relative(match.epub_filepath));
last_epub = match.epub_filepath; last_epub = match.epub_filepath;
if (!opts.nocolor)
if (vm.count("nocolor") == 0)
{ {
cout << termcolor::reset; cout << termcolor::reset;
} }
} }
} }
cout << match.filepath;
vector<string> prefix;
if (!opts.no_fn_epub)
{
prefix.emplace_back(match.filepath);
}
if (!match.headline.empty()) if (!match.headline.empty())
{ {
cout << ", " << match.headline; prefix.emplace_back(match.headline);
} }
if (!match.page.empty()) if (!match.page.empty())
{ {
cout << ", page " << match.page; prefix.emplace_back("page " + match.page);
}
for (const auto &part : prefix)
{
cout << part;
if (part != *(prefix.rbegin()))
{
cout << ", ";
}
} }
cout << ": " << match.context.first; cout << ": " << match.context.first;
if (vm.count("nocolor") == 0) if (!opts.nocolor)
{ {
cout << termcolor::bright_magenta << match.text cout << termcolor::bright_magenta;
<< termcolor::reset;
} }
else cout << match.text;
if (!opts.nocolor)
{ {
cout << match.text; cout << termcolor::reset;
} }
cout << match.context.second << '\n'; cout << match.context.second << '\n';
} }

View File

@ -31,7 +31,9 @@
#include <exception> #include <exception>
#include <fstream> #include <fstream>
#include <iostream> #include <iostream>
#include <stdexcept>
#include <string> #include <string>
#include <vector>
namespace epubgrep::options namespace epubgrep::options
{ {
@ -41,7 +43,7 @@ namespace po = boost::program_options;
using boost::locale::translate; using boost::locale::translate;
using std::cout; using std::cout;
po::variables_map parse_options(int argc, char *argv[]) options parse_options(int argc, char *argv[])
{ {
po::options_description options_visible(translate("Available options")); po::options_description options_visible(translate("Available options"));
// clang-format off // clang-format off
@ -73,9 +75,9 @@ po::variables_map parse_options(int argc, char *argv[])
translate("Print NUMBER words of context around matches.") translate("Print NUMBER words of context around matches.")
.str().data()) .str().data())
("nocolor", translate("Do not color matches.") .str().data()) ("nocolor", translate("Do not color matches.") .str().data())
("no-filename", ("no-filename",po::value<std::string>()->value_name(translate("WHICH")),
translate("Suppress the mentioning of EPUB file names on output.") translate("Suppress the mentioning of file names on output. "
.str().data()) "WHICH is filesystem, in-epub or all.").str().data())
("recursive,r", ("recursive,r",
translate("Read all files under each directory, recursively.") translate("Read all files under each directory, recursively.")
.str().data()) .str().data())
@ -139,7 +141,7 @@ po::variables_map parse_options(int argc, char *argv[])
"conditions.\n"); "conditions.\n");
} }
return vm; return parse_again(vm);
} }
fs::path get_config_path() fs::path get_config_path()
@ -172,4 +174,62 @@ fs::path get_config_path()
return "epubgrep.conf"; return "epubgrep.conf";
} }
options parse_again(const po::variables_map &vm)
{
options opts;
opts.help = vm.count("help") > 0;
opts.version = vm.count("version") > 0;
if (vm.count("basic-regexp") > 0)
{
opts.regex = regex_kind::basic;
}
if (vm.count("extended-regexp") > 0)
{
opts.regex = regex_kind::extended;
}
if (vm.count("perl-regexp") > 0)
{
opts.regex = regex_kind::perl;
}
opts.grep = vm.count("grep") > 0;
opts.ignore_case = vm.count("ignore-case") > 0;
opts.nocolor = vm.count("nocolor") > 0;
if (vm.count("no-filename") > 0)
{
if (vm["no-filename"].as<std::string>() == "filesystem")
{
opts.no_fn_fs = true;
}
else if (vm["no-filename"].as<std::string>() == "in-epub")
{
opts.no_fn_epub = true;
}
else if (vm["no-filename"].as<std::string>() == "all")
{
opts.no_fn_fs = true;
opts.no_fn_epub = true;
}
else
{
throw std::runtime_error{"'--no-filename' must be either "
"filesystem, in-epub or all."};
}
}
opts.recursive = vm.count("recursive") > 0;
opts.dereference_recursive = vm.count("dereference-recursive") > 0;
if (vm.count("regexp") > 0)
{
opts.regexp = vm["regexp"].as<std::vector<std::string>>();
}
if (vm.count("input-file") > 0)
{
opts.input_file = vm["input-file"].as<std::vector<std::string>>();
}
return opts;
}
} // namespace epubgrep::options } // namespace epubgrep::options

View File

@ -21,13 +21,43 @@
#include <boost/program_options/variables_map.hpp> #include <boost/program_options/variables_map.hpp>
#include <cstddef>
#include <cstdint>
#include <string>
#include <vector>
namespace epubgrep::options namespace epubgrep::options
{ {
namespace po = boost::program_options; namespace po = boost::program_options;
enum class regex_kind
{
basic,
extended,
perl
};
struct options
{
bool help{false};
bool version{false};
regex_kind regex{regex_kind::basic};
bool grep{false};
bool ignore_case{false};
std::vector<std::string> regexp;
bool raw{false};
std::uint64_t context{0};
bool nocolor{false};
bool no_fn_fs{false};
bool no_fn_epub{false};
bool recursive{false};
bool dereference_recursive{false};
std::vector<std::string> input_file;
};
//! Parse options and return them. //! Parse options and return them.
[[nodiscard]] po::variables_map parse_options(int argc, char *argv[]); [[nodiscard]] options parse_options(int argc, char *argv[]);
/*! /*!
* @brief Returns the path of the config file. * @brief Returns the path of the config file.
@ -38,6 +68,9 @@ namespace po = boost::program_options;
*/ */
[[nodiscard]] fs::path get_config_path(); [[nodiscard]] fs::path get_config_path();
//! Parse variables map and return nice options struct.
[[nodiscard]] options parse_again(const po::variables_map &vm);
} // namespace epubgrep::options } // namespace epubgrep::options
#endif // EPUBGREP_OPTIONS_HPP #endif // EPUBGREP_OPTIONS_HPP

View File

@ -32,23 +32,23 @@ namespace epubgrep::search
using std::string; using std::string;
std::vector<match> search(const fs::path &filepath, std::string_view regex, std::vector<match> search(const fs::path &filepath, std::string_view regex,
const options &opts) const settings &opts)
{ {
boost::regex::flag_type flags{}; boost::regex::flag_type flags{};
switch (opts.regex) switch (opts.regex)
{ {
case regex_kind::basic: case options::regex_kind::basic:
{ {
flags = opts.grep_like ? boost::regex::grep : boost::regex::basic; flags = opts.grep_like ? boost::regex::grep : boost::regex::basic;
break; break;
} }
case regex_kind::extended: case options::regex_kind::extended:
{ {
flags = opts.grep_like ? boost::regex::egrep : boost::regex::extended; flags = opts.grep_like ? boost::regex::egrep : boost::regex::extended;
break; break;
} }
case regex_kind::perl: case options::regex_kind::perl:
{ {
flags = boost::regex::perl; flags = boost::regex::perl;
break; break;

View File

@ -18,6 +18,7 @@
#define EPUBGREP_SEARCH_HPP #define EPUBGREP_SEARCH_HPP
#include "fs-compat.hpp" #include "fs-compat.hpp"
#include "options.hpp"
#include <boost/regex.hpp> #include <boost/regex.hpp>
@ -42,16 +43,9 @@ struct match
std::string page; //!< The page number, if available. std::string page; //!< The page number, if available.
}; };
enum class regex_kind struct settings
{ {
basic, options::regex_kind regex{options::regex_kind::basic};
extended,
perl
};
struct options
{
regex_kind regex{regex_kind::basic};
bool grep_like{false}; bool grep_like{false};
bool ignore_case{false}; bool ignore_case{false};
bool raw{false}; bool raw{false};
@ -61,7 +55,7 @@ struct options
//! Search file, return matches. //! Search file, return matches.
[[nodiscard]] std::vector<match> search(const fs::path &filepath, [[nodiscard]] std::vector<match> search(const fs::path &filepath,
std::string_view regex, std::string_view regex,
const options &opts); const settings &opts);
//! Strip HTML, remove newlines, condense spaces. //! Strip HTML, remove newlines, condense spaces.
void cleanup_text(std::string &text); void cleanup_text(std::string &text);

View File

@ -1,4 +1,5 @@
#include "fs-compat.hpp" #include "fs-compat.hpp"
#include "options.hpp"
#include "search.hpp" #include "search.hpp"
#include <catch.hpp> #include <catch.hpp>
@ -21,13 +22,13 @@ SCENARIO("Searching works")
SECTION("search() doesn't fail and returns the right lines") SECTION("search() doesn't fail and returns the right lines")
{ {
std::vector<epubgrep::search::match> matches; std::vector<epubgrep::search::match> matches;
epubgrep::search::options opts; epubgrep::search::settings opts;
WHEN("We search for ‘📙+\\w? using extended regular expressions") WHEN("We search for ‘📙+\\w? using extended regular expressions")
{ {
try try
{ {
opts.regex = epubgrep::search::regex_kind::extended; opts.regex = epubgrep::options::regex_kind::extended;
matches = epubgrep::search::search(zipfile, "📙+\\w?", opts); matches = epubgrep::search::search(zipfile, "📙+\\w?", opts);
} }
catch (const std::exception &) catch (const std::exception &)
@ -104,7 +105,7 @@ SCENARIO("Searching works")
try try
{ {
opts.context = 1; opts.context = 1;
opts.regex = epubgrep::search::regex_kind::extended; opts.regex = epubgrep::options::regex_kind::extended;
matches = epubgrep::search::search( matches = epubgrep::search::search(
zipfile, R"(work\s[\w]+\.\W[\w']+\Wstay)", opts); zipfile, R"(work\s[\w]+\.\W[\w']+\Wstay)", opts);
} }