Made CSV expoer RFC 4180 compliant.
This commit is contained in:
parent
dfc382b92c
commit
bb9cff2992
|
@ -29,7 +29,7 @@ remwharead - Remember what you read, and when
|
||||||
Add tags to _URL_, delimited by commas.
|
Add tags to _URL_, delimited by commas.
|
||||||
|
|
||||||
*-e* _format_, *--export* _format_::
|
*-e* _format_, *--export* _format_::
|
||||||
Export to _format_. Possible values are _csv_ and _asciidoc_.
|
Export to _format_. Possible values are _csv_ and _asciidoc_. See _FORMATS_.
|
||||||
|
|
||||||
*-f* _file_, *--file* _file_::
|
*-f* _file_, *--file* _file_::
|
||||||
Save output to _file_. Default is stdout.
|
Save output to _file_. Default is stdout.
|
||||||
|
@ -56,6 +56,15 @@ Print version, copyright and license.
|
||||||
|
|
||||||
`remwharead -e asciidoc | asciidoctor --backend=html5 --out-file=out.html -`
|
`remwharead -e asciidoc | asciidoctor --backend=html5 --out-file=out.html -`
|
||||||
|
|
||||||
|
== FORMATS
|
||||||
|
|
||||||
|
=== CSV
|
||||||
|
|
||||||
|
CSV is short for comma separated values. All fields are quoted and delimited by
|
||||||
|
commas. Line breaks in the full text are converted to "\n". Our CSV
|
||||||
|
implementation follows RFC 4180 and the full MIME media type is
|
||||||
|
`text/csv;charset=utf-8;header=present`.
|
||||||
|
|
||||||
== FILES
|
== FILES
|
||||||
|
|
||||||
* *Database*: `${XDG_DATA_HOME}/remwharead/database.sqlite`
|
* *Database*: `${XDG_DATA_HOME}/remwharead/database.sqlite`
|
||||||
|
|
|
@ -0,0 +1,62 @@
|
||||||
|
/* 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 <iostream>
|
||||||
|
#include <regex>
|
||||||
|
#include "time.hpp"
|
||||||
|
#include "csv.hpp"
|
||||||
|
|
||||||
|
using std::cerr;
|
||||||
|
using std::endl;
|
||||||
|
using std::regex;
|
||||||
|
using std::regex_replace;
|
||||||
|
|
||||||
|
void export_csv(const vector<Database::entry> &entries, ostream &out)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
out << "\"URI\",\"Archived URI\",\"Date & time\",\"Tags\","
|
||||||
|
<< "\"Title\",\"Description\",\"Full text\"\r\n";
|
||||||
|
for (const Database::entry &entry : entries)
|
||||||
|
{
|
||||||
|
string strtags;
|
||||||
|
for (const string &tag : entry.tags)
|
||||||
|
{
|
||||||
|
strtags += tag;
|
||||||
|
if (tag != *(entry.tags.rbegin()))
|
||||||
|
{
|
||||||
|
strtags += ",";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
out << '"' << quote_csv(entry.uri) << "\",\""
|
||||||
|
<< quote_csv(entry.archive_uri) << "\",\""
|
||||||
|
<< timepoint_to_string(entry.datetime) << "\",\""
|
||||||
|
<< quote_csv(strtags) << "\",\""
|
||||||
|
<< quote_csv(entry.title) << "\",\""
|
||||||
|
<< quote_csv(entry.description) << "\",\""
|
||||||
|
<< quote_csv(entry.fulltext_oneline()) << '"'<< "\r\n";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (std::exception &e)
|
||||||
|
{
|
||||||
|
cerr << "Error in " << __func__ << ": " << e.what() << endl;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const string quote_csv(const string &field)
|
||||||
|
{
|
||||||
|
return regex_replace(field, regex("\""), "\"\"");
|
||||||
|
}
|
|
@ -0,0 +1,31 @@
|
||||||
|
/* 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_CSV_HPP
|
||||||
|
#define REMWHAREAD_CSV_HPP
|
||||||
|
|
||||||
|
#include <vector>
|
||||||
|
#include <iostream>
|
||||||
|
#include "sqlite.hpp"
|
||||||
|
|
||||||
|
using std::vector;
|
||||||
|
using std::ostream;
|
||||||
|
using std::cout;
|
||||||
|
|
||||||
|
void export_csv(const vector<Database::entry> &entries, ostream &out = cout);
|
||||||
|
const string quote_csv(const string &field);
|
||||||
|
|
||||||
|
#endif // REMWHAREAD_CSV_HPP
|
20
src/main.cpp
20
src/main.cpp
|
@ -20,6 +20,7 @@
|
||||||
#include "sqlite.hpp"
|
#include "sqlite.hpp"
|
||||||
#include "parse_options.hpp"
|
#include "parse_options.hpp"
|
||||||
#include "url.hpp"
|
#include "url.hpp"
|
||||||
|
#include "csv.hpp"
|
||||||
|
|
||||||
using std::cout;
|
using std::cout;
|
||||||
using std::cerr;
|
using std::cerr;
|
||||||
|
@ -55,24 +56,7 @@ int main(const int argc, const char *argv[])
|
||||||
{
|
{
|
||||||
case export_format::csv:
|
case export_format::csv:
|
||||||
{
|
{
|
||||||
cout << "#URI;Archived URI;Date & time;Tags;Title;Description\n";
|
export_csv(db.retrieve(opts.span[0], opts.span[1]));
|
||||||
for (const Database::entry &entry
|
|
||||||
: db.retrieve(opts.span[0], opts.span[1]))
|
|
||||||
{
|
|
||||||
string strtags;
|
|
||||||
for (const string &tag : entry.tags)
|
|
||||||
{
|
|
||||||
strtags += tag;
|
|
||||||
if (tag != *(entry.tags.rbegin()))
|
|
||||||
{
|
|
||||||
strtags += ",";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
cout << '"' << entry.uri << "\";\"" << entry.archive_uri << "\";\""
|
|
||||||
<< timepoint_to_string(entry.datetime) << "\";\""
|
|
||||||
<< strtags << "\";\"" << entry.title << "\";\""
|
|
||||||
<< entry.description << '"'<< endl;
|
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case export_format::asciidoc:
|
case export_format::asciidoc:
|
||||||
|
|
|
@ -17,6 +17,7 @@
|
||||||
#include <exception>
|
#include <exception>
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
|
#include <regex>
|
||||||
#include <basedir.h>
|
#include <basedir.h>
|
||||||
#include <sqlite/execute.hpp>
|
#include <sqlite/execute.hpp>
|
||||||
#include <sqlite/query.hpp>
|
#include <sqlite/query.hpp>
|
||||||
|
@ -25,6 +26,8 @@
|
||||||
|
|
||||||
using std::cerr;
|
using std::cerr;
|
||||||
using std::endl;
|
using std::endl;
|
||||||
|
using std::regex;
|
||||||
|
using std::regex_replace;
|
||||||
|
|
||||||
Database::Database()
|
Database::Database()
|
||||||
: _connected(false)
|
: _connected(false)
|
||||||
|
@ -60,6 +63,11 @@ Database::operator bool() const
|
||||||
return _connected;
|
return _connected;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const string Database::entry::fulltext_oneline() const
|
||||||
|
{
|
||||||
|
return regex_replace(fulltext, regex("\n"), "\\n");
|
||||||
|
}
|
||||||
|
|
||||||
void Database::store(const Database::entry &data) const
|
void Database::store(const Database::entry &data) const
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
|
|
|
@ -43,6 +43,9 @@ public:
|
||||||
string title;
|
string title;
|
||||||
string description;
|
string description;
|
||||||
string fulltext;
|
string fulltext;
|
||||||
|
|
||||||
|
//! The full text in one line.
|
||||||
|
const string fulltext_oneline() const;
|
||||||
} entry;
|
} entry;
|
||||||
|
|
||||||
Database();
|
Database();
|
||||||
|
|
Loading…
Reference in New Issue