/* This file is part of epubgrep. * Copyright © 2021 tastytea * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published by * the Free Software Foundation, version 3. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ #include "files.hpp" #include "fs-compat.hpp" #include "options.hpp" #include "search.hpp" #include #include #include #include #include #include // For compatibility with fmt 4. #include #include #include #include #include #include #include #include #include #include #include #include #include int main(int argc, char *argv[]) { namespace po = boost::program_options; using namespace epubgrep; using boost::locale::translate; using fmt::format; using std::cerr; using std::cout; using std::string; using std::vector; // locale_generator("").name.c_str() returns "*" instead of "". That's why // the global C locale isn't changed. So we have to set it additionally. std::setlocale(LC_ALL, ""); boost::locale::generator locale_generator; locale_generator.add_messages_path("translations"); locale_generator.add_messages_path("/usr/share/locale"); locale_generator.add_messages_domain("epubgrep"); std::locale::global(locale_generator("")); cout.imbue(std::locale()); cerr.imbue(std::locale()); po::variables_map vm; try { vm = options::parse_options(argc, argv); } catch (std::exception &e) { // Exceptions we can't recover from or ones we don't know. cerr << '\n' << translate("ERROR: ") << e.what() << '\n'; cerr << translate("Error while parsing options.") << '\n'; return EXIT_FAILURE; } if (vm.count("help") + vm.count("version") > 0) { return EXIT_SUCCESS; } int return_code{EXIT_SUCCESS}; vector input_files; if (vm.count("input-file") == 0) { cout << "NO INPUT FILE\n"; // TODO: Read data from stdin. return EXIT_FAILURE; } for (const auto &filepath : vm["input-file"].as>()) { if (vm.count("recursive") + vm.count("dereference-recursive") == 0) { 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)}; input_files.insert(input_files.end(), files_in_dir.begin(), files_in_dir.end()); } catch (const fs::filesystem_error &e) { if (e.code().value() == 20) { // Is not a directory. input_files.emplace_back(filepath); continue; } cerr << '\n' << format(translate("ERROR: Could not open {0:s}: {1:s}") .str() .data(), e.path1(), e.what()) << '\n'; return_code = EXIT_FAILURE; } } } 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(); vector> matches_all; vector> futurepool; auto search_file{ [&vm, &matches_all, &opts](fs::path filepath) { for (const auto ®ex : vm["regexp"].as>()) { try { matches_all.emplace_back( search::search(filepath, regex, opts)); } catch (const std::exception &e) { cerr << '\n' << translate("ERROR: ") << e.what() << '\n'; cerr << format(translate("Error while searching {0:s}.") .str() .data(), filepath) << '\n'; return EXIT_FAILURE; } } return EXIT_SUCCESS; }}; auto futures_cleanup{ [&futurepool, &return_code](const bool wait = false) { using namespace std::chrono_literals; for (auto it{futurepool.begin()}; it != futurepool.end();) { if (!wait && it->wait_for(100ms) != std::future_status::ready) { ++it; continue; } if (int ret{}; (ret = it->get()) != EXIT_SUCCESS) { return_code = ret; } futurepool.erase(it); } return EXIT_SUCCESS; }}; const auto max_threads{ [] { auto n{static_cast(std::thread::hardware_concurrency())}; return static_cast(std::ceil(n / 2 + n / 4)); }()}; for (const auto &filepath : input_files) { if (futurepool.size() >= max_threads) { futures_cleanup(); } futurepool.emplace_back( std::async(std::launch::async, search_file, filepath)); } futures_cleanup(true); for (const auto &matches_file : matches_all) { fs::path last_epub; for (const auto &match : matches_file) { if (input_files.size() <= 1 || vm.count("no-filename") == 0) { if (match.epub_filepath != last_epub) { if (vm.count("nocolor") == 0) { 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) { cout << termcolor::reset; } } } cout << match.filepath; if (!match.headline.empty()) { cout << ", " << match.headline; } if (!match.page.empty()) { cout << ", page " << match.page; } cout << ": " << match.context.first; if (vm.count("nocolor") == 0) { cout << termcolor::bright_magenta << match.text << termcolor::reset; } else { cout << match.text; } cout << match.context.second << '\n'; } } return return_code; }