226 lines
7.1 KiB
C++
226 lines
7.1 KiB
C++
/* This file is part of epubgrep.
|
|
* Copyright © 2021 tastytea <tastytea@tastytea.de>
|
|
*
|
|
* 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 <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#include "output.hpp"
|
|
|
|
#include "version.hpp"
|
|
|
|
#include <boost/locale/message.hpp>
|
|
#include <fmt/format.h>
|
|
#include <fmt/ostream.h> // For compatibility with fmt 4.
|
|
#include <nlohmann/json.hpp>
|
|
#include <termcolor/termcolor.hpp>
|
|
|
|
#include <cstdint>
|
|
#include <iostream>
|
|
#include <sstream>
|
|
|
|
namespace epubgrep::output
|
|
{
|
|
|
|
using boost::locale::translate;
|
|
using fmt::format;
|
|
using std::cout;
|
|
|
|
void print_matches(const std::vector<search::match> &matches,
|
|
const options::options &opts, bool single_file)
|
|
{
|
|
if (!single_file && !opts.no_fn_fs)
|
|
{
|
|
if (!opts.nocolor)
|
|
{
|
|
cout << termcolor::yellow;
|
|
}
|
|
cout << format(translate(" In {0:s}: \n").str().c_str(),
|
|
fs::relative(matches[0].filepath_epub));
|
|
if (!opts.nocolor)
|
|
{
|
|
cout << termcolor::reset;
|
|
}
|
|
}
|
|
|
|
for (const auto &match : matches)
|
|
{
|
|
std::vector<std::string> metadata;
|
|
if (!opts.no_fn_epub)
|
|
{
|
|
metadata.emplace_back(match.filepath_inside);
|
|
}
|
|
if (!match.headline.empty())
|
|
{
|
|
// <https://github.com/ikalnytskyi/termcolor/issues/45>
|
|
if (!opts.nocolor && termcolor::_internal::is_colorized(cout))
|
|
{
|
|
std::stringstream ss;
|
|
ss << termcolor::colorize << termcolor::underline
|
|
<< match.headline << termcolor::reset << termcolor::italic;
|
|
metadata.emplace_back(ss.str());
|
|
}
|
|
else
|
|
{
|
|
metadata.emplace_back(match.headline);
|
|
}
|
|
}
|
|
if (!match.page.empty())
|
|
{
|
|
metadata.emplace_back("page " + match.page);
|
|
}
|
|
if (!metadata.empty())
|
|
{
|
|
if (!opts.nocolor)
|
|
{
|
|
cout << termcolor::italic;
|
|
}
|
|
for (const auto &part : metadata)
|
|
{
|
|
cout << part;
|
|
if (part != *(metadata.rbegin()))
|
|
{
|
|
cout << ", ";
|
|
}
|
|
}
|
|
cout << ": ";
|
|
if (!opts.nocolor)
|
|
{
|
|
cout << termcolor::reset;
|
|
}
|
|
}
|
|
cout << match.context.first;
|
|
if (!opts.nocolor)
|
|
{
|
|
cout << termcolor::bright_magenta;
|
|
}
|
|
cout << match.text;
|
|
if (!opts.nocolor)
|
|
{
|
|
cout << termcolor::reset;
|
|
}
|
|
cout << match.context.second << '\n';
|
|
}
|
|
}
|
|
|
|
void json_all(const std::vector<std::vector<search::match>> &matches_all)
|
|
{
|
|
nlohmann::json json;
|
|
|
|
json["generator"] = {{"epubgrep", std::string(version)}};
|
|
|
|
for (const auto &matches : matches_all)
|
|
{
|
|
for (const auto &match : matches)
|
|
{
|
|
json["matches"].push_back(
|
|
{{"filepath_epub", match.filepath_epub.string()},
|
|
{"filepath_inside", match.filepath_inside},
|
|
{"match", match.text},
|
|
{"context", {match.context.first, match.context.second}},
|
|
{"headline", match.headline},
|
|
{"page", match.page}});
|
|
}
|
|
}
|
|
|
|
cout << json.dump() << '\n';
|
|
}
|
|
|
|
void html_all(const std::vector<std::vector<search::match>> &matches_all,
|
|
const options::options &opts)
|
|
{
|
|
std::uint64_t count{1};
|
|
|
|
cout << "<!DOCTYPE html>\n";
|
|
// Translators: Replace “en” with your language code here.
|
|
cout << format(R"(<html lang="{0:s}">)", translate("en").str());
|
|
cout << "<head><title>epubgrep output</title>"
|
|
"<style>article { margin: 1em; }</style>"
|
|
"</head><body>\n\n";
|
|
|
|
for (const auto &matches : matches_all)
|
|
{
|
|
const auto identifier{
|
|
[&opts, count, &matches]
|
|
{
|
|
if (opts.no_fn_fs)
|
|
{
|
|
return format(translate("File {0:d}").str(), count);
|
|
}
|
|
return fs::relative(matches[0].filepath_epub).string();
|
|
}()};
|
|
|
|
// Start article, table and print table header.
|
|
cout << format(R"(<article aria-labelledby="file_{0:d}">)", count)
|
|
<< "\n <table>\n"
|
|
<< format(R"( <caption id="file_{0:d}">{1:s}</caption>)", count,
|
|
identifier)
|
|
<< '\n'
|
|
<< " <tr>\n";
|
|
if (!opts.no_fn_epub)
|
|
{
|
|
cout << format(R"( <th id="file_path_{0:d}">{1:s}</th>)",
|
|
count, translate("File path (in EPUB file)"))
|
|
<< '\n';
|
|
}
|
|
cout << format(R"( <th id="headline_{0:d}">{1:s}</th>)", count,
|
|
translate("Last headline"))
|
|
<< '\n'
|
|
<< format(R"( <th id="page_{0:d}">{1:s}</th>)", count,
|
|
translate("Page number"))
|
|
<< '\n'
|
|
<< format(R"( <th id="match_{0:d}">{1:s}</th>)", count,
|
|
translate("Match"))
|
|
<< "\n </tr>\n";
|
|
|
|
for (const auto &match : matches)
|
|
{
|
|
const auto lang{[&match]
|
|
{
|
|
if (!match.language.empty())
|
|
{
|
|
return format(R"( lang="{0:s}")",
|
|
match.language);
|
|
}
|
|
return std::string{};
|
|
}()};
|
|
cout << " <tr>\n";
|
|
if (!opts.no_fn_epub)
|
|
{
|
|
cout << format(
|
|
R"( <td headers="file_path_{0:d}">{1:s}</td>)", count,
|
|
match.filepath_inside)
|
|
<< '\n';
|
|
}
|
|
cout << format(
|
|
R"( <td headers="headline_{0:d}"{1:s}>{2:s}</td>)", count,
|
|
lang, match.headline)
|
|
<< '\n';
|
|
cout << format(R"( <td headers="page_{0:d}">{1:s}</td>)",
|
|
count, match.page)
|
|
<< '\n';
|
|
cout << format(R"( <td headers="match_{0:d}"{1:s}>{2:s})"
|
|
R"(<strong>{3:s}</strong>{4:s}</td>)",
|
|
count, lang, match.context.first, match.text,
|
|
match.context.second)
|
|
<< '\n';
|
|
cout << " </tr>\n";
|
|
}
|
|
cout << " </table>\n</article>\n\n";
|
|
++count;
|
|
}
|
|
|
|
cout << "</body></html>\n";
|
|
}
|
|
|
|
} // namespace epubgrep::output
|