Refactored AsciiDoc export into a class.
This commit is contained in:
parent
008a5fe0f2
commit
98bb87ed93
309
src/adoc.cpp
309
src/adoc.cpp
|
@ -29,183 +29,186 @@ using std::regex;
|
||||||
using std::regex_replace;
|
using std::regex_replace;
|
||||||
using tagpair = std::pair<string,vector<Database::entry>>;
|
using tagpair = std::pair<string,vector<Database::entry>>;
|
||||||
|
|
||||||
void export_adoc(const vector<Database::entry> &entries, ostream &out)
|
namespace Export
|
||||||
{
|
{
|
||||||
try
|
void AsciiDoc::print() const
|
||||||
{
|
{
|
||||||
out << "= Visited things\n"
|
try
|
||||||
<< ":Author: remwharead " << global::version << endl
|
|
||||||
<< ":Date: "
|
|
||||||
<< timepoint_to_string(system_clock::now()) << endl
|
|
||||||
<< ":TOC: right" << endl
|
|
||||||
<< ":TOCLevels: 2" << endl
|
|
||||||
<< endl;
|
|
||||||
|
|
||||||
std::map<string,vector<Database::entry>> alltags;
|
|
||||||
string day;
|
|
||||||
for (const Database::entry &entry : entries)
|
|
||||||
{
|
{
|
||||||
const string datetime = timepoint_to_string(entry.datetime);
|
_out << "= Visited things\n"
|
||||||
const string newday = datetime.substr(0, datetime.find('T'));
|
<< ":Author: remwharead " << global::version << endl
|
||||||
const string time = datetime.substr(datetime.find('T') + 1);
|
<< ":Date: "
|
||||||
if (newday != day)
|
<< timepoint_to_string(system_clock::now()) << endl
|
||||||
{
|
<< ":TOC: right" << endl
|
||||||
day = newday;
|
<< ":TOCLevels: 2" << endl
|
||||||
out << "== " << day << endl << endl;
|
<< endl;
|
||||||
}
|
|
||||||
|
|
||||||
out << "[[dt_" << datetime << "]]\n";
|
std::map<string,vector<Database::entry>> alltags;
|
||||||
out << "* link:" << entry.uri;
|
string day;
|
||||||
if (!entry.title.empty())
|
for (const Database::entry &entry : _entries)
|
||||||
{
|
{
|
||||||
out << '[' << entry.title << ']';
|
const string datetime = timepoint_to_string(entry.datetime);
|
||||||
}
|
const string newday = datetime.substr(0, datetime.find('T'));
|
||||||
else
|
const string time = datetime.substr(datetime.find('T') + 1);
|
||||||
{
|
if (newday != day)
|
||||||
out << "[]";
|
|
||||||
}
|
|
||||||
out << " +" << endl;
|
|
||||||
|
|
||||||
out << '_' << time.substr(0, 5) << '_';
|
|
||||||
if (!entry.archive_uri.empty())
|
|
||||||
{
|
|
||||||
out << " (link:" << entry.archive_uri << "[archived version])";
|
|
||||||
}
|
|
||||||
|
|
||||||
bool separator = false;
|
|
||||||
for (const string &tag : entry.tags)
|
|
||||||
{
|
|
||||||
if (tag.empty())
|
|
||||||
{
|
{
|
||||||
continue;
|
day = newday;
|
||||||
}
|
_out << "== " << day << endl << endl;
|
||||||
if (!separator)
|
|
||||||
{
|
|
||||||
out << "\n| ";
|
|
||||||
separator = true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
auto globaltag = alltags.find(tag);
|
_out << "[[dt_" << datetime << "]]\n";
|
||||||
if (globaltag != alltags.end())
|
_out << "* link:" << entry.uri;
|
||||||
|
if (!entry.title.empty())
|
||||||
{
|
{
|
||||||
globaltag->second.push_back(entry);
|
_out << '[' << entry.title << ']';
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
alltags.insert({ tag, { entry } });
|
_out << "[]";
|
||||||
}
|
}
|
||||||
|
_out << " +" << endl;
|
||||||
|
|
||||||
out << "xref:t_" << replace_in_tags(tag) << "[" << tag << ']';
|
_out << '_' << time.substr(0, 5) << '_';
|
||||||
if (tag != *(entry.tags.rbegin()))
|
if (!entry.archive_uri.empty())
|
||||||
{
|
{
|
||||||
out << ", ";
|
_out << " (link:" << entry.archive_uri << "[archived version])";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool separator = false;
|
||||||
|
for (const string &tag : entry.tags)
|
||||||
|
{
|
||||||
|
if (tag.empty())
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (!separator)
|
||||||
|
{
|
||||||
|
_out << "\n| ";
|
||||||
|
separator = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto globaltag = alltags.find(tag);
|
||||||
|
if (globaltag != alltags.end())
|
||||||
|
{
|
||||||
|
globaltag->second.push_back(entry);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
alltags.insert({ tag, { entry } });
|
||||||
|
}
|
||||||
|
|
||||||
|
_out << "xref:t_" << replace_in_tags(tag) << "[" << tag << ']';
|
||||||
|
if (tag != *(entry.tags.rbegin()))
|
||||||
|
{
|
||||||
|
_out << ", ";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!entry.description.empty())
|
||||||
|
{
|
||||||
|
_out << " +" << endl << entry.description;
|
||||||
|
}
|
||||||
|
_out << endl << endl;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!entry.description.empty())
|
if (!alltags.empty())
|
||||||
{
|
{
|
||||||
out << " +" << endl << entry.description;
|
print_tags(alltags);
|
||||||
}
|
}
|
||||||
out << endl << endl;
|
|
||||||
}
|
}
|
||||||
|
catch (std::exception &e)
|
||||||
if (!alltags.empty())
|
|
||||||
{
|
{
|
||||||
adoc_print_tags(alltags, out);
|
cerr << "Error in " << __func__ << ": " << e.what() << endl;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (std::exception &e)
|
|
||||||
|
const string AsciiDoc::replace_in_tags(string text) const
|
||||||
{
|
{
|
||||||
cerr << "Error in " << __func__ << ": " << e.what() << endl;
|
// TODO: Find a better solution.
|
||||||
|
const std::map<const string, const string> searchreplace =
|
||||||
|
{
|
||||||
|
{ " ", "-" }, { "§", "-" },
|
||||||
|
{ "$", "-" }, { "%", "-" },
|
||||||
|
{ "&", "-" }, { "/", "-" },
|
||||||
|
{ "=", "-" }, { "^", "-" },
|
||||||
|
{ "!", "-" }, { "?", "-" },
|
||||||
|
{ "₀", "0" }, { "⁰", "0" },
|
||||||
|
{ "₁", "1" }, { "¹", "1" },
|
||||||
|
{ "₂", "2" }, { "²", "2" },
|
||||||
|
{ "₃", "3" }, { "³", "3" },
|
||||||
|
{ "₄", "4" }, { "⁴", "4" },
|
||||||
|
{ "₅", "5" }, { "⁵", "5" },
|
||||||
|
{ "₆", "6" }, { "⁶", "6" },
|
||||||
|
{ "₇", "7" }, { "⁷", "7" },
|
||||||
|
{ "₈", "8" }, { "⁸", "8" },
|
||||||
|
{ "₉", "9" }, { "⁹", "9" }
|
||||||
|
};
|
||||||
|
for (const std::pair<const string, const string> &sr : searchreplace)
|
||||||
|
{
|
||||||
|
size_t pos = 0;
|
||||||
|
while ((pos = text.find(sr.first, pos)) != std::string::npos)
|
||||||
|
{
|
||||||
|
text.replace(pos, sr.first.length(), sr.second);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return text;
|
||||||
|
}
|
||||||
|
|
||||||
|
void AsciiDoc::print_tags(
|
||||||
|
const std::map<string,vector<Database::entry>> &tags) const
|
||||||
|
{
|
||||||
|
_out << "== Tags\n\n";
|
||||||
|
vector<tagpair> sortedtags(tags.size());
|
||||||
|
std::move(tags.begin(), tags.end(), sortedtags.begin());
|
||||||
|
std::sort(sortedtags.begin(), sortedtags.end(),
|
||||||
|
[](const tagpair &a, tagpair &b)
|
||||||
|
{
|
||||||
|
if (a.second.size() != b.second.size())
|
||||||
|
{ // Sort by number of occurrences if they are different.
|
||||||
|
return a.second.size() > b.second.size();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{ // Sort by tag names otherwise.
|
||||||
|
std::locale loc;
|
||||||
|
const std::collate<char> &coll =
|
||||||
|
std::use_facet<std::collate<char>>(loc);
|
||||||
|
return (coll.compare(
|
||||||
|
a.first.data(), a.first.data()
|
||||||
|
+ a.first.length(),
|
||||||
|
b.first.data(), b.first.data()
|
||||||
|
+ b.first.length()) == -1);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
bool othertags = false;
|
||||||
|
for (const auto &tag : sortedtags)
|
||||||
|
{
|
||||||
|
if (tag.second.size() == 1)
|
||||||
|
{
|
||||||
|
if (!othertags)
|
||||||
|
{
|
||||||
|
_out << "=== Less used tags\n\n";
|
||||||
|
othertags = true;
|
||||||
|
}
|
||||||
|
_out << "=";
|
||||||
|
}
|
||||||
|
|
||||||
|
_out << "=== [[t_" << replace_in_tags(tag.first) << "]]"
|
||||||
|
<< tag.first << endl;
|
||||||
|
for (const Database::entry &entry : tag.second)
|
||||||
|
{
|
||||||
|
const string datetime = timepoint_to_string(entry.datetime);
|
||||||
|
const string date = datetime.substr(0, datetime.find('T'));
|
||||||
|
string title = entry.title;
|
||||||
|
if (title.empty())
|
||||||
|
{
|
||||||
|
title = "++" + entry.uri + "++";
|
||||||
|
}
|
||||||
|
_out << endl << "* xref:dt_" << datetime
|
||||||
|
<< '[' << title << "] _(" << date << ")_" << endl;
|
||||||
|
}
|
||||||
|
_out << endl;
|
||||||
|
}
|
||||||
|
_out << endl;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const string replace_in_tags(string text)
|
|
||||||
{
|
|
||||||
// TODO: Find a better solution.
|
|
||||||
const std::map<const string, const string> searchreplace =
|
|
||||||
{
|
|
||||||
{ " ", "-" }, { "§", "-" },
|
|
||||||
{ "$", "-" }, { "%", "-" },
|
|
||||||
{ "&", "-" }, { "/", "-" },
|
|
||||||
{ "=", "-" }, { "^", "-" },
|
|
||||||
{ "!", "-" }, { "?", "-" },
|
|
||||||
{ "₀", "0" }, { "⁰", "0" },
|
|
||||||
{ "₁", "1" }, { "¹", "1" },
|
|
||||||
{ "₂", "2" }, { "²", "2" },
|
|
||||||
{ "₃", "3" }, { "³", "3" },
|
|
||||||
{ "₄", "4" }, { "⁴", "4" },
|
|
||||||
{ "₅", "5" }, { "⁵", "5" },
|
|
||||||
{ "₆", "6" }, { "⁶", "6" },
|
|
||||||
{ "₇", "7" }, { "⁷", "7" },
|
|
||||||
{ "₈", "8" }, { "⁸", "8" },
|
|
||||||
{ "₉", "9" }, { "⁹", "9" }
|
|
||||||
};
|
|
||||||
for (const std::pair<const string, const string> &sr : searchreplace)
|
|
||||||
{
|
|
||||||
size_t pos = 0;
|
|
||||||
while ((pos = text.find(sr.first, pos)) != std::string::npos)
|
|
||||||
{
|
|
||||||
text.replace(pos, sr.first.length(), sr.second);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return text;
|
|
||||||
}
|
|
||||||
|
|
||||||
void adoc_print_tags(const std::map<string,vector<Database::entry>> &tags,
|
|
||||||
ostream &out)
|
|
||||||
{
|
|
||||||
out << "== Tags\n\n";
|
|
||||||
vector<tagpair> sortedtags(tags.size());
|
|
||||||
std::move(tags.begin(), tags.end(), sortedtags.begin());
|
|
||||||
std::sort(sortedtags.begin(), sortedtags.end(),
|
|
||||||
[](const tagpair &a, tagpair &b)
|
|
||||||
{
|
|
||||||
if (a.second.size() != b.second.size())
|
|
||||||
{ // Sort by number of occurrences if they are different.
|
|
||||||
return a.second.size() > b.second.size();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{ // Sort by tag names otherwise.
|
|
||||||
std::locale loc;
|
|
||||||
const std::collate<char> &coll =
|
|
||||||
std::use_facet<std::collate<char>>(loc);
|
|
||||||
return (coll.compare(
|
|
||||||
a.first.data(), a.first.data()
|
|
||||||
+ a.first.length(),
|
|
||||||
b.first.data(), b.first.data()
|
|
||||||
+ b.first.length()) == -1);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
bool othertags = false;
|
|
||||||
for (const auto &tag : sortedtags)
|
|
||||||
{
|
|
||||||
if (tag.second.size() == 1)
|
|
||||||
{
|
|
||||||
if (!othertags)
|
|
||||||
{
|
|
||||||
out << "=== Less used tags\n\n";
|
|
||||||
othertags = true;
|
|
||||||
}
|
|
||||||
out << "=";
|
|
||||||
}
|
|
||||||
|
|
||||||
out << "=== [[t_" << replace_in_tags(tag.first) << "]]"
|
|
||||||
<< tag.first << endl;
|
|
||||||
for (const Database::entry &entry : tag.second)
|
|
||||||
{
|
|
||||||
const string datetime = timepoint_to_string(entry.datetime);
|
|
||||||
const string date = datetime.substr(0, datetime.find('T'));
|
|
||||||
string title = entry.title;
|
|
||||||
if (title.empty())
|
|
||||||
{
|
|
||||||
title = "++" + entry.uri + "++";
|
|
||||||
}
|
|
||||||
out << endl << "* xref:dt_" << datetime
|
|
||||||
<< '[' << title << "] _(" << date << ")_" << endl;
|
|
||||||
}
|
|
||||||
out << endl;
|
|
||||||
}
|
|
||||||
out << endl;
|
|
||||||
}
|
|
||||||
|
|
|
@ -45,18 +45,27 @@ namespace Export
|
||||||
using ExportBase::ExportBase;
|
using ExportBase::ExportBase;
|
||||||
|
|
||||||
void print() const;
|
void print() const;
|
||||||
|
|
||||||
|
private:
|
||||||
//! replaces " with "".
|
//! replaces " with "".
|
||||||
const string quote(string field) const;
|
const string quote(string field) const;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
class AsciiDoc : protected ExportBase
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
using ExportBase::ExportBase;
|
||||||
|
|
||||||
|
void print() const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
//! Replaces characters in tags that asciidoctor doesn't like.
|
||||||
|
const string replace_in_tags(string text) const;
|
||||||
|
void print_tags(const std::map<string,vector<Database::entry>> &tags) const;
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void export_adoc(const vector<Database::entry> &entries, ostream &out = cout);
|
|
||||||
//! Replaces characters in tags that asciidoctor doesn't like.
|
|
||||||
const string replace_in_tags(string text);
|
|
||||||
void adoc_print_tags(const std::map<string,vector<Database::entry>> &tags,
|
|
||||||
ostream &out = cout);
|
|
||||||
|
|
||||||
|
|
||||||
//! Export as Netscape bookmark file.
|
//! Export as Netscape bookmark file.
|
||||||
void export_bookmarks(const vector<Database::entry> &entries,
|
void export_bookmarks(const vector<Database::entry> &entries,
|
||||||
|
|
|
@ -111,12 +111,12 @@ int main(const int argc, const char *argv[])
|
||||||
{
|
{
|
||||||
if (file.is_open())
|
if (file.is_open())
|
||||||
{
|
{
|
||||||
export_adoc(entries, file);
|
Export::AsciiDoc(entries, file).print();
|
||||||
file.close();
|
file.close();
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
export_adoc(entries);
|
Export::AsciiDoc(entries).print();
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
|
@ -47,7 +47,7 @@ SCENARIO ("The AsciiDoc export works correctly")
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
std::ostringstream output;
|
std::ostringstream output;
|
||||||
export_adoc({ entry }, output);
|
Export::AsciiDoc({ entry }, output).print();
|
||||||
const string adoc = output.str();
|
const string adoc = output.str();
|
||||||
|
|
||||||
const regex re_header
|
const regex re_header
|
||||||
|
|
Loading…
Reference in New Issue