Added RSS export.
continuous-integration/drone/push Build is passing Details

This commit is contained in:
tastytea 2019-09-06 00:53:58 +02:00
parent 7557064215
commit c9b82fd774
Signed by: tastytea
GPG Key ID: CFC39497F1B26E07
11 changed files with 228 additions and 14 deletions

View File

@ -5,8 +5,8 @@
an URI to the archived version, the current date and time, title, description,
the full text of the page and optional tags.
The database can be filtered by time, tags and full text and exported to CSV or
AsciiDoc.
The database can be filtered by time, tags and full text and exported to CSV,
AsciiDoc, JSON or RSS.
Archiving is done using the Wayback machine from the
https://archive.org/[Internet Archive].

View File

@ -2,7 +2,7 @@ include(CMakeFindDependencyMacro)
include(GNUInstallDirs)
find_depencency(Poco
COMPONENTS Foundation Net NetSSL Data DataSQLite JSON
COMPONENTS Foundation Net NetSSL Data DataSQLite JSON XML
CONFIG REQUIRED)
find_dependency(PkgConfig REQUIRED)
pkg_check_modules(libxdg-basedir REQUIRED IMPORTED_TARGET libxdg-basedir)

46
include/export/rss.hpp Normal file
View File

@ -0,0 +1,46 @@
/* This file is part of remwharead.
* Copyright © 2019 tastytea <tastytea@tastytea.de>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef REMWHAREAD_RSS_HPP
#define REMWHAREAD_RSS_HPP
#include <string>
#include "export.hpp"
namespace remwharead
{
namespace Export
{
using std::string;
/*!
* @brief Export as RSS feed.
*
* @since 0.8.0
*
* @headerfile rss.hpp remwharead/export/rss.hpp
*/
class RSS : protected ExportBase
{
public:
using ExportBase::ExportBase;
virtual void print() const override;
};
}
}
#endif // REMWHAREAD_RSS_HPP

View File

@ -45,6 +45,7 @@
#include "export/export.hpp"
#include "export/simple.hpp"
#include "export/json.hpp"
#include "export/rss.hpp"
#include "search.hpp"
#include "sqlite.hpp"
#include "time.hpp"

View File

@ -35,7 +35,8 @@ namespace remwharead
asciidoc,
bookmarks,
simple,
json
json,
rss
};
}

View File

@ -2,7 +2,7 @@
:doctype: manpage
:Author: tastytea
:Email: tastytea@tastytea.de
:Date: 2019-09-03
:Date: 2019-09-06
:Revision: 0.0.0
:man source: remwharead
:man manual: General Commands Manual
@ -24,7 +24,7 @@ remwharead - Saves URIs of things you want to remember in a database
the full text of the page and optional tags.
The database can be filtered by time, tags and full text and exported to CSV,
AsciiDoc, a bookmarks file or JSON.
AsciiDoc, a bookmarks file, JSON or RSS.
Archiving is done using the Wayback machine from the
https://archive.org/[Internet Archive].
@ -36,7 +36,7 @@ Add tags to _URI_, delimited by commas.
*-e* _format_, *--export* _format_::
Export to _format_. Possible values are _csv_, _asciidoc_, _bookmarks_,
_simple_ or _json_. See _FORMATS_.
_simple_, _json_ or _rss_. See _FORMATS_.
*-f* _file_, *--file* _file_::
Save output to _file_. Default is stdout.
@ -139,13 +139,19 @@ Export as JSON array. See https://tools.ietf.org/html/rfc8259[RFC 8259]. Each
object contains the members _uri_, _archive_uri_, _datetime_, _tags_ (array),
_title_, _description_ and _fulltext_.
=== rss
Export as http://www.rssboard.org/rss-specification[RSS] feed. Because the URL
of the feed is unknown to *remwharead*, the generated feed is slightly out of
specification (the element _link_ in _channel_ is empty).
== SEARCH EXPRESSIONS
A search expression is either a single term, or several terms separated by _AND_
or _OR_. _AND_ takes precedence. The expression _Mountain AND Big OR Vegetable_
finds all things that have either Mountain and Big, or Vegetable in them. You can
use _||_ instead of _OR_ and _&&_ instead of _AND_. Note that *--search-tags*
only matches whole tags, Pill does not match Pillow.
finds all things that have either Mountain and Big, or Vegetable in them. You
can use _||_ instead of _OR_ and _&&_ instead of _AND_. Note that
*--search-tags* only matches whole tags, Pill does not match Pillow.
== PROTOCOL SUPPORT

View File

@ -10,4 +10,4 @@ Version: @PROJECT_VERSION@
Cflags: -I${includedir}
Libs: -L${libdir} -l${name} -lPocoData -lstdc++fs
Requires.private: libxdg-basedir, icu-uc, icu-i18n
Libs.private: -lPocoFoundation -lPocoNet -lPocoNetSSL -lPocoDataSQLite -lPocoJSON
Libs.private: -lPocoFoundation -lPocoNet -lPocoNetSSL -lPocoDataSQLite -lPocoJSON -lPocoXML

View File

@ -29,6 +29,7 @@
#include "export/bookmarks.hpp"
#include "export/simple.hpp"
#include "export/json.hpp"
#include "export/rss.hpp"
#include "search.hpp"
using namespace remwharead;
@ -191,6 +192,19 @@ int App::main(const std::vector<std::string> &args)
}
break;
}
case export_format::rss:
{
if (file.is_open())
{
Export::RSS(entries, file).print();
file.close();
}
else
{
Export::RSS(entries).print();
}
break;
}
default:
{
break;

View File

@ -143,6 +143,10 @@ void App::handle_options(const std::string &name, const std::string &value)
{
_format = export_format::json;
}
else if (value == "rss")
{
_format = export_format::rss;
}
else
{
cerr << "Error: Unknown format.\n";

View File

@ -3,7 +3,9 @@ include(GNUInstallDirs)
find_package(PkgConfig REQUIRED)
pkg_check_modules(libxdg-basedir REQUIRED IMPORTED_TARGET libxdg-basedir)
# Some distributions do not contain Poco*Config.cmake recipes.
find_package(Poco COMPONENTS Foundation Net NetSSL Data DataSQLite JSON CONFIG)
find_package(Poco
COMPONENTS Foundation Net NetSSL Data DataSQLite JSON XML
CONFIG)
file(GLOB_RECURSE sources_lib *.cpp)
file(GLOB_RECURSE headers_lib ../../include/*.hpp)
@ -28,7 +30,8 @@ target_link_libraries(${PROJECT_NAME}
# If no Poco*Config.cmake recipes are found, look for headers in standard dirs.
if(PocoNetSSL_FOUND)
target_link_libraries(${PROJECT_NAME}
PRIVATE Poco::Foundation Poco::Net Poco::NetSSL Poco::DataSQLite Poco::JSON
PRIVATE Poco::Foundation Poco::Net Poco::NetSSL Poco::DataSQLite
Poco::JSON Poco::XML
PUBLIC Poco::Data)
else()
find_file(Poco_h NAMES "Poco/Poco.h"
@ -42,7 +45,7 @@ else()
"but the files seem to be in the standard directories. "
"Let's hope this works.")
target_link_libraries(${PROJECT_NAME}
PRIVATE PocoFoundation PocoNet PocoNetSSL PocoDataSQLite PocoJSON
PRIVATE PocoFoundation PocoNet PocoNetSSL PocoDataSQLite PocoJSON PocoXML
PUBLIC PocoData)
endif()
endif()

139
src/lib/export/rss.cpp Normal file
View File

@ -0,0 +1,139 @@
/* This file is part of remwharead.
* Copyright © 2019 tastytea <tastytea@tastytea.de>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <ctime>
#include <Poco/XML/XMLWriter.h>
#include <Poco/SAX/AttributesImpl.h>
#include <Poco/DateTime.h>
#include <Poco/DateTimeFormatter.h>
#include <Poco/Timestamp.h>
#include "time.hpp"
#include "version.hpp"
#include "export/rss.hpp"
namespace remwharead
{
using std::cerr;
using std::endl;
using std::time_t;
using Poco::XML::XMLWriter;
using Poco::XML::AttributesImpl;
using Poco::DateTime;
using Poco::DateTimeFormatter;
using Poco::Timestamp;
void Export::RSS::print() const
{
try
{
XMLWriter writer(_out, XMLWriter::CANONICAL);
AttributesImpl attrs_rss, attrs_guid;
constexpr char timefmt_rfc822[] = "%w, %d %b %Y %H:%M:%S %Z";
attrs_rss.addAttribute("", "", "version", "", "2.0");
attrs_rss.addAttribute("", "", "xmlns:atom", "",
"http://www.w3.org/2005/Atom");
attrs_guid.addAttribute("", "", "isPermaLink", "", "false");
writer.startDocument();
writer.startElement("", "", "rss", attrs_rss);
writer.startElement("", "", "channel");
writer.startElement("", "", "title");
writer.characters("Visited things");
writer.endElement("", "", "title");
writer.startElement("", "", "link");
// FIXME: There has to be an URL here.
writer.endElement("", "", "link");
writer.startElement("", "", "description");
writer.characters("Export from remwharead.");
writer.endElement("", "", "description");
writer.startElement("", "", "generator");
writer.characters(string("remwharead ") + global::version);
writer.endElement("", "", "generator");
const string now = DateTimeFormatter::format(DateTime(),
timefmt_rfc822);
writer.startElement("", "", "lastBuildDate");
writer.characters(now);
writer.endElement("", "", "lastBuildDate");
for (const Database::entry &entry : _entries)
{
writer.startElement("", "", "item");
writer.startElement("", "", "title");
writer.characters(entry.title);
writer.endElement("", "", "title");
writer.startElement("", "", "link");
writer.characters(entry.uri);
writer.endElement("", "", "link");
writer.startElement("", "", "guid", attrs_guid);
writer.characters(entry.uri + " at " +
timepoint_to_string(entry.datetime));
writer.endElement("", "", "guid");
const time_t time = system_clock::to_time_t(entry.datetime);
const string time_visited = DateTimeFormatter::format(
Timestamp::fromEpochTime(time), timefmt_rfc822);
writer.startElement("", "", "pubDate");
writer.characters(time_visited);
writer.endElement("", "", "pubDate");
string description = entry.description;
if (!description.empty())
{
description += "\n\n";
}
if (!entry.tags.empty())
{
description += "Tags: ";
for (const string &tag : entry.tags)
{
description += tag;
if (tag != *(entry.tags.rbegin()))
{
description += ", ";
}
}
}
if (!entry.archive_uri.empty())
{
description += "\n\nArchived version: " + entry.archive_uri;
}
writer.startElement("", "", "description");
writer.characters(description);
writer.endElement("", "", "description");
writer.endElement("", "", "item");
}
writer.endElement("", "", "channel");
writer.endElement("", "", "rss");
writer.endDocument();
_out << endl;
}
catch (std::exception &e)
{
cerr << "Error in " << __func__ << ": " << e.what() << endl;
}
}
}