Added RSS export.
continuous-integration/drone/push Build is passing
Details
continuous-integration/drone/push Build is passing
Details
This commit is contained in:
parent
7557064215
commit
c9b82fd774
|
@ -5,8 +5,8 @@
|
||||||
an URI to the archived version, the current date and time, title, description,
|
an URI to the archived version, the current date and time, title, description,
|
||||||
the full text of the page and optional tags.
|
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
|
The database can be filtered by time, tags and full text and exported to CSV,
|
||||||
AsciiDoc.
|
AsciiDoc, JSON or RSS.
|
||||||
|
|
||||||
Archiving is done using the Wayback machine from the
|
Archiving is done using the Wayback machine from the
|
||||||
https://archive.org/[Internet Archive].
|
https://archive.org/[Internet Archive].
|
||||||
|
|
|
@ -2,7 +2,7 @@ include(CMakeFindDependencyMacro)
|
||||||
include(GNUInstallDirs)
|
include(GNUInstallDirs)
|
||||||
|
|
||||||
find_depencency(Poco
|
find_depencency(Poco
|
||||||
COMPONENTS Foundation Net NetSSL Data DataSQLite JSON
|
COMPONENTS Foundation Net NetSSL Data DataSQLite JSON XML
|
||||||
CONFIG REQUIRED)
|
CONFIG REQUIRED)
|
||||||
find_dependency(PkgConfig REQUIRED)
|
find_dependency(PkgConfig REQUIRED)
|
||||||
pkg_check_modules(libxdg-basedir REQUIRED IMPORTED_TARGET libxdg-basedir)
|
pkg_check_modules(libxdg-basedir REQUIRED IMPORTED_TARGET libxdg-basedir)
|
||||||
|
|
|
@ -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
|
|
@ -45,6 +45,7 @@
|
||||||
#include "export/export.hpp"
|
#include "export/export.hpp"
|
||||||
#include "export/simple.hpp"
|
#include "export/simple.hpp"
|
||||||
#include "export/json.hpp"
|
#include "export/json.hpp"
|
||||||
|
#include "export/rss.hpp"
|
||||||
#include "search.hpp"
|
#include "search.hpp"
|
||||||
#include "sqlite.hpp"
|
#include "sqlite.hpp"
|
||||||
#include "time.hpp"
|
#include "time.hpp"
|
||||||
|
|
|
@ -35,7 +35,8 @@ namespace remwharead
|
||||||
asciidoc,
|
asciidoc,
|
||||||
bookmarks,
|
bookmarks,
|
||||||
simple,
|
simple,
|
||||||
json
|
json,
|
||||||
|
rss
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
:doctype: manpage
|
:doctype: manpage
|
||||||
:Author: tastytea
|
:Author: tastytea
|
||||||
:Email: tastytea@tastytea.de
|
:Email: tastytea@tastytea.de
|
||||||
:Date: 2019-09-03
|
:Date: 2019-09-06
|
||||||
:Revision: 0.0.0
|
:Revision: 0.0.0
|
||||||
:man source: remwharead
|
:man source: remwharead
|
||||||
:man manual: General Commands Manual
|
: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 full text of the page and optional tags.
|
||||||
|
|
||||||
The database can be filtered by time, tags and full text and exported to CSV,
|
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
|
Archiving is done using the Wayback machine from the
|
||||||
https://archive.org/[Internet Archive].
|
https://archive.org/[Internet Archive].
|
||||||
|
@ -36,7 +36,7 @@ Add tags to _URI_, delimited by commas.
|
||||||
|
|
||||||
*-e* _format_, *--export* _format_::
|
*-e* _format_, *--export* _format_::
|
||||||
Export to _format_. Possible values are _csv_, _asciidoc_, _bookmarks_,
|
Export to _format_. Possible values are _csv_, _asciidoc_, _bookmarks_,
|
||||||
_simple_ or _json_. See _FORMATS_.
|
_simple_, _json_ or _rss_. See _FORMATS_.
|
||||||
|
|
||||||
*-f* _file_, *--file* _file_::
|
*-f* _file_, *--file* _file_::
|
||||||
Save output to _file_. Default is stdout.
|
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),
|
object contains the members _uri_, _archive_uri_, _datetime_, _tags_ (array),
|
||||||
_title_, _description_ and _fulltext_.
|
_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
|
== SEARCH EXPRESSIONS
|
||||||
|
|
||||||
A search expression is either a single term, or several terms separated by _AND_
|
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_
|
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
|
finds all things that have either Mountain and Big, or Vegetable in them. You
|
||||||
use _||_ instead of _OR_ and _&&_ instead of _AND_. Note that *--search-tags*
|
can use _||_ instead of _OR_ and _&&_ instead of _AND_. Note that
|
||||||
only matches whole tags, Pill does not match Pillow.
|
*--search-tags* only matches whole tags, Pill does not match Pillow.
|
||||||
|
|
||||||
== PROTOCOL SUPPORT
|
== PROTOCOL SUPPORT
|
||||||
|
|
||||||
|
|
|
@ -10,4 +10,4 @@ Version: @PROJECT_VERSION@
|
||||||
Cflags: -I${includedir}
|
Cflags: -I${includedir}
|
||||||
Libs: -L${libdir} -l${name} -lPocoData -lstdc++fs
|
Libs: -L${libdir} -l${name} -lPocoData -lstdc++fs
|
||||||
Requires.private: libxdg-basedir, icu-uc, icu-i18n
|
Requires.private: libxdg-basedir, icu-uc, icu-i18n
|
||||||
Libs.private: -lPocoFoundation -lPocoNet -lPocoNetSSL -lPocoDataSQLite -lPocoJSON
|
Libs.private: -lPocoFoundation -lPocoNet -lPocoNetSSL -lPocoDataSQLite -lPocoJSON -lPocoXML
|
||||||
|
|
|
@ -29,6 +29,7 @@
|
||||||
#include "export/bookmarks.hpp"
|
#include "export/bookmarks.hpp"
|
||||||
#include "export/simple.hpp"
|
#include "export/simple.hpp"
|
||||||
#include "export/json.hpp"
|
#include "export/json.hpp"
|
||||||
|
#include "export/rss.hpp"
|
||||||
#include "search.hpp"
|
#include "search.hpp"
|
||||||
|
|
||||||
using namespace remwharead;
|
using namespace remwharead;
|
||||||
|
@ -191,6 +192,19 @@ int App::main(const std::vector<std::string> &args)
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
case export_format::rss:
|
||||||
|
{
|
||||||
|
if (file.is_open())
|
||||||
|
{
|
||||||
|
Export::RSS(entries, file).print();
|
||||||
|
file.close();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Export::RSS(entries).print();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
{
|
{
|
||||||
break;
|
break;
|
||||||
|
|
|
@ -143,6 +143,10 @@ void App::handle_options(const std::string &name, const std::string &value)
|
||||||
{
|
{
|
||||||
_format = export_format::json;
|
_format = export_format::json;
|
||||||
}
|
}
|
||||||
|
else if (value == "rss")
|
||||||
|
{
|
||||||
|
_format = export_format::rss;
|
||||||
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
cerr << "Error: Unknown format.\n";
|
cerr << "Error: Unknown format.\n";
|
||||||
|
|
|
@ -3,7 +3,9 @@ include(GNUInstallDirs)
|
||||||
find_package(PkgConfig REQUIRED)
|
find_package(PkgConfig REQUIRED)
|
||||||
pkg_check_modules(libxdg-basedir REQUIRED IMPORTED_TARGET libxdg-basedir)
|
pkg_check_modules(libxdg-basedir REQUIRED IMPORTED_TARGET libxdg-basedir)
|
||||||
# Some distributions do not contain Poco*Config.cmake recipes.
|
# 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 sources_lib *.cpp)
|
||||||
file(GLOB_RECURSE headers_lib ../../include/*.hpp)
|
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 no Poco*Config.cmake recipes are found, look for headers in standard dirs.
|
||||||
if(PocoNetSSL_FOUND)
|
if(PocoNetSSL_FOUND)
|
||||||
target_link_libraries(${PROJECT_NAME}
|
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)
|
PUBLIC Poco::Data)
|
||||||
else()
|
else()
|
||||||
find_file(Poco_h NAMES "Poco/Poco.h"
|
find_file(Poco_h NAMES "Poco/Poco.h"
|
||||||
|
@ -42,7 +45,7 @@ else()
|
||||||
"but the files seem to be in the standard directories. "
|
"but the files seem to be in the standard directories. "
|
||||||
"Let's hope this works.")
|
"Let's hope this works.")
|
||||||
target_link_libraries(${PROJECT_NAME}
|
target_link_libraries(${PROJECT_NAME}
|
||||||
PRIVATE PocoFoundation PocoNet PocoNetSSL PocoDataSQLite PocoJSON
|
PRIVATE PocoFoundation PocoNet PocoNetSSL PocoDataSQLite PocoJSON PocoXML
|
||||||
PUBLIC PocoData)
|
PUBLIC PocoData)
|
||||||
endif()
|
endif()
|
||||||
endif()
|
endif()
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue