epubgrep/src/output.cpp

227 lines
7.2 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).c_str());
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)").str().c_str())
<< '\n';
}
cout << format(R"( <th id="headline_{0:d}">{1:s}</th>)", count,
translate("Last headline").str().c_str())
<< '\n'
<< format(R"( <th id="page_{0:d}">{1:s}</th>)", count,
translate("Page number").str().c_str())
<< '\n'
<< format(R"( <th id="match_{0:d}">{1:s}</th>)", count,
translate("Match").str().c_str())
<< "\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