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*::
Do not color matches.
*--no-filename*::
Suppress the mentioning of EPUB file names on output. File names inside the
EPUB, chapters and page numbers will still be output.
*--no-filename* _WHICH_::
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*:
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/message.hpp>
#include <boost/program_options/errors.hpp>
#include <boost/program_options/variables_map.hpp>
#include <fmt/format.h>
#include <fmt/ostream.h> // For compatibility with fmt 4.
#include <termcolor/termcolor.hpp>
@ -42,7 +40,6 @@
int main(int argc, char *argv[])
{
namespace po = boost::program_options;
using namespace epubgrep;
using boost::locale::translate;
@ -63,10 +60,10 @@ int main(int argc, char *argv[])
cout.imbue(std::locale());
cerr.imbue(std::locale());
po::variables_map vm;
options::options opts;
try
{
vm = options::parse_options(argc, argv);
opts = options::parse_options(argc, argv);
}
catch (std::exception &e)
{ // 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;
}
if (vm.count("help") + vm.count("version") > 0)
if (opts.help || opts.version)
{
return EXIT_SUCCESS;
}
@ -83,31 +80,26 @@ int main(int argc, char *argv[])
int return_code{EXIT_SUCCESS};
vector<fs::path> input_files;
if (vm.count("input-file") == 0)
if (opts.input_file.empty())
{
cout << "NO INPUT FILE\n";
// TODO: Read data from stdin.
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);
}
else
{
bool follow_symlinks{false};
if (vm.count("dereference-recursive") > 0)
{
follow_symlinks = true;
}
try
{
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(),
files_in_dir.end());
}
@ -130,45 +122,25 @@ int main(int argc, char *argv[])
}
}
search::options opts;
if (vm.count("basic-regexp") > 0)
{
opts.regex = search::regex_kind::basic;
}
if (vm.count("extended-regexp") > 0)
{
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>();
search::settings search_settings;
search_settings.regex = opts.regex;
search_settings.grep_like = opts.grep;
search_settings.ignore_case = opts.ignore_case;
search_settings.raw = opts.raw;
search_settings.context = opts.context;
vector<vector<search::match>> matches_all;
vector<std::future<int>> futurepool;
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
{
matches_all.emplace_back(
search::search(filepath, regex, opts));
search::search(filepath, regex, search_settings));
}
catch (const std::exception &e)
{
@ -231,43 +203,54 @@ int main(int argc, char *argv[])
fs::path last_epub;
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 (vm.count("nocolor") == 0)
if (!opts.nocolor)
{
cout << termcolor::yellow;
}
cout << format(translate(" In {0:s}: \n").str().data(),
fs::relative(match.epub_filepath));
last_epub = match.epub_filepath;
if (vm.count("nocolor") == 0)
if (!opts.nocolor)
{
cout << termcolor::reset;
}
}
}
cout << match.filepath;
vector<string> prefix;
if (!opts.no_fn_epub)
{
prefix.emplace_back(match.filepath);
}
if (!match.headline.empty())
{
cout << ", " << match.headline;
prefix.emplace_back(match.headline);
}
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;
if (vm.count("nocolor") == 0)
if (!opts.nocolor)
{
cout << termcolor::bright_magenta << match.text
<< termcolor::reset;
cout << termcolor::bright_magenta;
}
else
cout << match.text;
if (!opts.nocolor)
{
cout << match.text;
cout << termcolor::reset;
}
cout << match.context.second << '\n';
}

View File

@ -31,7 +31,9 @@
#include <exception>
#include <fstream>
#include <iostream>
#include <stdexcept>
#include <string>
#include <vector>
namespace epubgrep::options
{
@ -41,7 +43,7 @@ namespace po = boost::program_options;
using boost::locale::translate;
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"));
// clang-format off
@ -73,9 +75,9 @@ po::variables_map parse_options(int argc, char *argv[])
translate("Print NUMBER words of context around matches.")
.str().data())
("nocolor", translate("Do not color matches.") .str().data())
("no-filename",
translate("Suppress the mentioning of EPUB file names on output.")
.str().data())
("no-filename",po::value<std::string>()->value_name(translate("WHICH")),
translate("Suppress the mentioning of file names on output. "
"WHICH is filesystem, in-epub or all.").str().data())
("recursive,r",
translate("Read all files under each directory, recursively.")
.str().data())
@ -139,7 +141,7 @@ po::variables_map parse_options(int argc, char *argv[])
"conditions.\n");
}
return vm;
return parse_again(vm);
}
fs::path get_config_path()
@ -172,4 +174,62 @@ fs::path get_config_path()
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

View File

@ -21,13 +21,43 @@
#include <boost/program_options/variables_map.hpp>
#include <cstddef>
#include <cstdint>
#include <string>
#include <vector>
namespace epubgrep::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.
[[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.
@ -38,6 +68,9 @@ namespace po = boost::program_options;
*/
[[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
#endif // EPUBGREP_OPTIONS_HPP

View File

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

View File

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

View File

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