Use Base classes for importer and exporters.

This commit is contained in:
MinchinWeb 2021-05-05 21:35:50 -06:00
parent 51fc091335
commit 2706314306
10 changed files with 185 additions and 105 deletions

119
jrnl/plugins/base.py Normal file
View file

@ -0,0 +1,119 @@
#!/usr/bin/env python
# encoding: utf-8
# Copyright (C) 2012-2021 jrnl contributors
# License: https://www.gnu.org/licenses/gpl-3.0.html
"""
Base class for Importers and Exporters.
"""
import os
import re
import unicodedata
from jrnl import __version__
from jrnl.color import ERROR_COLOR
from jrnl.color import RESET_COLOR
class BaseImporter:
"""Base Importer class (to sub-class)"""
# names = ["jrnl"]
# version = __version__
@classmethod
def class_path(cls):
return cls.__module__
@staticmethod
def import_(journal, input=None):
raise NotImplementedError
class BaseExporter:
"""Base Exporter class (to sub-class)"""
# names = ["text", "txt"]
# extension = "txt"
# version = __version__
@classmethod
def class_path(cls):
return cls.__module__
@classmethod
def export_entry(cls, entry):
"""Returns a string representation of a single entry."""
raise NotImplementedError
@classmethod
def export_journal(cls, journal):
"""Returns a string representation of an entire journal."""
return "\n".join(cls.export_entry(entry) for entry in journal)
@classmethod
def write_file(cls, journal, path):
"""Exports a journal into a single file."""
try:
with open(path, "w", encoding="utf-8") as f:
f.write(cls.export_journal(journal))
return (
f"[Journal '{journal.name}' exported (as a single file) to {path}]"
)
except IOError as e:
return f"[{ERROR_COLOR}ERROR{RESET_COLOR}: {e.filename} {e.strerror}]"
except NotImplementedError:
return (
f"[{ERROR_COLOR}ERROR{RESET_COLOR}: This exporter doesn't support "
"exporting as a single file.]"
)
@classmethod
def make_filename(cls, entry):
return entry.date.strftime("%Y-%m-%d") + "_{}.{}".format(
cls._slugify(str(entry.title)), cls.extension
)
@classmethod
def write_files(cls, journal, path):
"""Exports a journal into individual files for each entry."""
try:
for entry in journal.entries:
try:
full_path = os.path.join(path, cls.make_filename(entry))
with open(full_path, "w", encoding="utf-8") as f:
f.write(cls.export_entry(entry))
except IOError as e:
return "[{2}ERROR{3}: {0} {1}]".format(
e.filename, e.strerror, ERROR_COLOR, RESET_COLOR
)
except NotImplementedError:
return (
f"[{ERROR_COLOR}ERROR{RESET_COLOR}: This exporter doesn't support "
"exporting as individual files.]"
)
else:
return f"[Journal '{journal.name}' exported (as multiple files) to {path}]"
def _slugify(string):
"""Slugifies a string.
Based on public domain code from https://github.com/zacharyvoase/slugify
"""
normalized_string = str(unicodedata.normalize("NFKD", string))
no_punctuation = re.sub(r"[^\w\s-]", "", normalized_string).strip().lower()
slug = re.sub(r"[-\s]+", "-", no_punctuation)
return slug
@classmethod
def export(cls, journal, output=None):
"""Exports to individual files if output is an existing path, or into
a single file if output is a file name, or returns the exporter's
representation as string if output is None."""
if output and os.path.isdir(output): # multiple files
return cls.write_files(journal, output)
elif output: # single file
return cls.write_file(journal, output)
else:
return cls.export_journal(journal)

View file

@ -2,13 +2,15 @@
# encoding: utf-8 # encoding: utf-8
# Copyright (C) 2012-2021 jrnl contributors # Copyright (C) 2012-2021 jrnl contributors
# License: https://www.gnu.org/licenses/gpl-3.0.html # License: https://www.gnu.org/licenses/gpl-3.0.html
from collections import Counter from collections import Counter
from jrnl import __version__ from jrnl.plugins.base import BaseExporter
from jrnl.plugins.exporter.text_exporter import Exporter as TextExporter
from ... import __version__
class Exporter(TextExporter): class Exporter(BaseExporter):
"""This Exporter lists dates and their respective counts, for heatingmapping etc.""" """This Exporter lists dates and their respective counts, for heatingmapping etc."""
names = ["dates"] names = ["dates"]

View file

@ -5,14 +5,17 @@
from textwrap import TextWrapper from textwrap import TextWrapper
from jrnl.plugins.exporter.text_exporter import Exporter as TextExporter from jrnl.plugins.base import BaseExporter
from ... import __version__
class Exporter(TextExporter): class Exporter(BaseExporter):
"""This Exporter can convert entries and journals into text with unicode box drawing characters.""" """This Exporter can convert entries and journals into text with unicode box drawing characters."""
names = ["fancy", "boxed"] names = ["fancy", "boxed"]
extension = "txt" extension = "txt"
version = __version__
border_a = "" border_a = ""
border_b = "" border_b = ""

View file

@ -5,15 +5,18 @@
import json import json
from jrnl.plugins.exporter.text_exporter import Exporter as TextExporter from jrnl.plugins.base import BaseExporter
from jrnl.plugins.util import get_tags_count from jrnl.plugins.util import get_tags_count
from ... import __version__
class Exporter(TextExporter):
class Exporter(BaseExporter):
"""This Exporter can convert entries and journals into json.""" """This Exporter can convert entries and journals into json."""
names = ["json"] names = ["json"]
extension = "json" extension = "json"
version = __version__
@classmethod @classmethod
def entry_to_dict(cls, entry): def entry_to_dict(cls, entry):

View file

@ -9,14 +9,17 @@ import sys
from jrnl.color import RESET_COLOR from jrnl.color import RESET_COLOR
from jrnl.color import WARNING_COLOR from jrnl.color import WARNING_COLOR
from jrnl.plugins.exporter.text_exporter import Exporter as TextExporter from jrnl.plugins.base import BaseExporter
from ... import __version__
class Exporter(TextExporter): class Exporter(BaseExporter):
"""This Exporter can convert entries and journals into Markdown.""" """This Exporter can convert entries and journals into Markdown."""
names = ["md", "markdown"] names = ["md", "markdown"]
extension = "md" extension = "md"
version = __version__
@classmethod @classmethod
def export_entry(cls, entry, to_multifile=True): def export_entry(cls, entry, to_multifile=True):

View file

@ -6,12 +6,15 @@
from jrnl.plugins.exporter.text_exporter import Exporter as TextExporter from jrnl.plugins.exporter.text_exporter import Exporter as TextExporter
from jrnl.plugins.util import get_tags_count from jrnl.plugins.util import get_tags_count
from ... import __version__
class Exporter(TextExporter): class Exporter(TextExporter):
"""This Exporter can lists the tags for entries and journals, exported as a plain text file.""" """This Exporter can lists the tags for entries and journals, exported as a plain text file."""
names = ["tags"] names = ["tags"]
extension = "tags" extension = "tags"
version = __version__
@classmethod @classmethod
def export_entry(cls, entry): def export_entry(cls, entry):

View file

@ -3,83 +3,20 @@
# Copyright (C) 2012-2021 jrnl contributors # Copyright (C) 2012-2021 jrnl contributors
# License: https://www.gnu.org/licenses/gpl-3.0.html # License: https://www.gnu.org/licenses/gpl-3.0.html
import os
import re
import unicodedata
from jrnl import __version__ from jrnl import __version__
from jrnl.color import ERROR_COLOR from jrnl.plugins.base import BaseExporter
from jrnl.color import RESET_COLOR
from ... import __version__
class Exporter: class Exporter(BaseExporter):
"""This Exporter can convert entries and journals into text files.""" """This Exporter can convert entries and journals into text files."""
names = ["text", "txt"] names = ["text", "txt"]
extension = "txt" extension = "txt"
version = __version__ version = __version__
@classmethod
def class_path(cls):
return cls.__module__
@classmethod @classmethod
def export_entry(cls, entry): def export_entry(cls, entry):
"""Returns a string representation of a single entry.""" """Returns a string representation of a single entry."""
return str(entry) return str(entry)
@classmethod
def export_journal(cls, journal):
"""Returns a string representation of an entire journal."""
return "\n".join(cls.export_entry(entry) for entry in journal)
@classmethod
def write_file(cls, journal, path):
"""Exports a journal into a single file."""
try:
with open(path, "w", encoding="utf-8") as f:
f.write(cls.export_journal(journal))
return f"[Journal exported to {path}]"
except IOError as e:
return f"[{ERROR_COLOR}ERROR{RESET_COLOR}: {e.filename} {e.strerror}]"
@classmethod
def make_filename(cls, entry):
return entry.date.strftime("%Y-%m-%d") + "_{}.{}".format(
cls._slugify(str(entry.title)), cls.extension
)
@classmethod
def write_files(cls, journal, path):
"""Exports a journal into individual files for each entry."""
for entry in journal.entries:
try:
full_path = os.path.join(path, cls.make_filename(entry))
with open(full_path, "w", encoding="utf-8") as f:
f.write(cls.export_entry(entry))
except IOError as e:
return "[{2}ERROR{3}: {0} {1}]".format(
e.filename, e.strerror, ERROR_COLOR, RESET_COLOR
)
return "[Journal exported to {}]".format(path)
def _slugify(string):
"""Slugifies a string.
Based on public domain code from https://github.com/zacharyvoase/slugify
"""
normalized_string = str(unicodedata.normalize("NFKD", string))
no_punctuation = re.sub(r"[^\w\s-]", "", normalized_string).strip().lower()
slug = re.sub(r"[-\s]+", "-", no_punctuation)
return slug
@classmethod
def export(cls, journal, output=None):
"""Exports to individual files if output is an existing path, or into
a single file if output is a file name, or returns the exporter's
representation as string if output is None."""
if output and os.path.isdir(output): # multiple files
return cls.write_files(journal, output)
elif output: # single file
return cls.write_file(journal, output)
else:
return cls.export_journal(journal)

View file

@ -5,30 +5,20 @@
from xml.dom import minidom from xml.dom import minidom
from jrnl.plugins.exporter.json_exporter import Exporter as JSONExporter # from jrnl.plugins.exporter.json_exporter import Exporter as JSONExporter
from jrnl.plugins.base import BaseExporter
from jrnl.plugins.util import get_tags_count from jrnl.plugins.util import get_tags_count
from ... import __version__
class Exporter(JSONExporter):
# class Exporter(JSONExporter):
class Exporter(BaseExporter):
"""This Exporter can convert entries and journals into XML.""" """This Exporter can convert entries and journals into XML."""
names = ["xml"] names = ["xml"]
extension = "xml" extension = "xml"
version = __version__
@classmethod
def export_entry(cls, entry, doc=None):
"""Returns an XML representation of a single entry."""
doc_el = doc or minidom.Document()
entry_el = doc_el.createElement("entry")
for key, value in cls.entry_to_dict(entry).items():
elem = doc_el.createElement(key)
elem.appendChild(doc_el.createTextNode(value))
entry_el.appendChild(elem)
if not doc:
doc_el.appendChild(entry_el)
return doc_el.toprettyxml()
else:
return entry_el
@classmethod @classmethod
def entry_to_xml(cls, entry, doc): def entry_to_xml(cls, entry, doc):
@ -45,6 +35,21 @@ class Exporter(JSONExporter):
entry_el.appendChild(doc.createTextNode(entry.fulltext)) entry_el.appendChild(doc.createTextNode(entry.fulltext))
return entry_el return entry_el
@classmethod
def export_entry(cls, entry, doc=None):
"""Returns an XML representation of a single entry."""
doc_el = doc or minidom.Document()
entry_el = doc_el.createElement("entry")
for key, value in cls.entry_to_dict(entry).items():
elem = doc_el.createElement(key)
elem.appendChild(doc_el.createTextNode(value))
entry_el.appendChild(elem)
if not doc:
doc_el.appendChild(entry_el)
return doc_el.toprettyxml()
else:
return entry_el
@classmethod @classmethod
def export_journal(cls, journal): def export_journal(cls, journal):
"""Returns an XML representation of an entire journal.""" """Returns an XML representation of an entire journal."""

View file

@ -10,14 +10,17 @@ import sys
from jrnl.color import ERROR_COLOR from jrnl.color import ERROR_COLOR
from jrnl.color import RESET_COLOR from jrnl.color import RESET_COLOR
from jrnl.color import WARNING_COLOR from jrnl.color import WARNING_COLOR
from jrnl.plugins.exporter.text_exporter import Exporter as TextExporter from jrnl.plugins.base import BaseExporter
from ... import __version__
class Exporter(TextExporter): class Exporter(BaseExporter):
"""This Exporter can convert entries and journals into Markdown formatted text with YAML front matter.""" """This Exporter can convert entries and journals into Markdown formatted text with YAML front matter."""
names = ["yaml"] names = ["yaml"]
extension = "md" extension = "md"
version = __version__
@classmethod @classmethod
def export_entry(cls, entry, to_multifile=True): def export_entry(cls, entry, to_multifile=True):
@ -132,9 +135,10 @@ class Exporter(TextExporter):
def export_journal(cls, journal): def export_journal(cls, journal):
"""Returns an error, as YAML export requires a directory as a target.""" """Returns an error, as YAML export requires a directory as a target."""
print( print(
"{}ERROR{}: YAML export must be to individual files. Please specify a directory to export to.".format( (
ERROR_COLOR, RESET_COLOR f"[{ERROR_COLOR}ERROR{RESET_COLOR}: YAML export must be to "
"individual files. Please specify a directory to export to.]"
), ),
file=sys.stderr, file=sys.stderr,
) )
return raise NotImplementedError

View file

@ -5,18 +5,17 @@
import sys import sys
from jrnl import __version__ from jrnl.plugins.base import BaseImporter
class Importer: from ... import __version__
class Importer(BaseImporter):
"""This plugin imports entries from other jrnl files.""" """This plugin imports entries from other jrnl files."""
names = ["jrnl"] names = ["jrnl"]
version = __version__ version = __version__
@classmethod
def class_path(cls):
return cls.__module__
@staticmethod @staticmethod
def import_(journal, input=None): def import_(journal, input=None):
"""Imports from an existing file if input is specified, and """Imports from an existing file if input is specified, and
@ -34,7 +33,9 @@ class Importer:
journal.import_(other_journal_txt) journal.import_(other_journal_txt)
new_cnt = len(journal.entries) new_cnt = len(journal.entries)
print( print(
"[{} imported to {} journal]".format(new_cnt - old_cnt, journal.name), "[{} entries imported to '{}' journal]".format(
new_cnt - old_cnt, journal.name
),
file=sys.stderr, file=sys.stderr,
) )
journal.write() journal.write()