Add support for DayOne2

This commit is contained in:
dbxnr 2020-01-10 15:05:10 +00:00
parent 41c4c241cc
commit 6f84eec165
10 changed files with 208 additions and 29 deletions

View file

@ -0,0 +1,12 @@
default_hour: 9
default_minute: 0
editor: ''
template: false
encrypt: false
highlight: true
journals:
default: features/journals/dayone2.json
linewrap: 80
tagsymbols: '@'
timeformat: '%Y-%m-%d %H:%M'
indent_character: "|"

View file

@ -0,0 +1,27 @@
{
"entries": [
{
"audios": [],
"creationDate": "2020-01-10T12:22:12Z",
"photos": [],
"starred": false,
"tags": [],
"text": "Entry Number One.",
"timeZone": "Europe/London",
"uuid": "EBB5A5F4057F461E8F176E18AB7E0493"
},
{
"audios": [],
"creationDate": "2020-01-10T12:21:48Z",
"photos": [],
"starred": true,
"tags": [],
"text": "Entry Number Two.\n\nAnd a bit of text over here.",
"timeZone": "Europe/London",
"uuid": "EC61E5D4E66C44E6AC796E863E8BF7E7"
}
],
"metadata": {
"version": "1.2"
}
}

15
features/dayone2.feature Normal file
View file

@ -0,0 +1,15 @@
Feature: Day One 2.0 implementation details.
Scenario: Loading a Day One 2.0 journal
Given we use the config "dayone2.yaml"
When we run "jrnl -n 2"
Then we should get no error
and the output should be
"""
2020-01-10 12:21 Entry Number Two.
| And a bit of text over here.
2020-01-10 12:22 Entry Number One.
"""

View file

@ -385,6 +385,12 @@ def open_journal(name, config, legacy=False):
sys.exit(1)
elif config["journal"].strip("/").endswith(".json"):
# Loads a Day One v2.0 journal
from .dayone2 import DayOne2
return DayOne2(**config).open()
if not config["encrypt"]:
if legacy:
return LegacyJournal(name, **config).open()

View file

@ -133,20 +133,9 @@ def parse_args(args=None):
metavar="TYPE",
dest="import_",
choices=plugins.IMPORT_FORMATS,
help="Import entries into your journal. TYPE can be {}, and it defaults to jrnl if nothing else is specified.".format(
plugins.util.oxford_list(plugins.IMPORT_FORMATS)
),
help="Import entries into your journal. TYPE can be one of "\
"{0}".format(plugins.util.oxford_list(plugins.IMPORT_FORMATS)),
default=False,
const="jrnl",
nargs="?",
)
exporting.add_argument(
"-i",
metavar="INPUT",
dest="input",
help="Optionally specifies input file when using --import.",
default=False,
const=None,
)
exporting.add_argument(
"--encrypt",
@ -181,14 +170,14 @@ def guess_mode(args, config):
compose = True
export = False
import_ = False
if args.import_ is not False:
if args.import_:
compose = False
export = False
import_ = True
elif (
args.decrypt is not False
or args.encrypt is not False
or args.export is not False
args.decrypt
or args.encrypt
or args.export
or any((args.short, args.tags, args.edit))
):
compose = False
@ -357,6 +346,10 @@ def run(manual_args=None):
else:
sys.exit()
# Import mode
if args.import_:
return plugins.get_importer(args.import_, args.text)
# This is where we finally open the journal!
try:
journal = open_journal(journal_name, config)
@ -364,12 +357,8 @@ def run(manual_args=None):
print(f"[Interrupted while opening journal]", file=sys.stderr)
sys.exit(1)
# Import mode
if mode_import:
plugins.get_importer(args.import_).import_(journal, args.input)
# Writing mode
elif mode_compose:
if mode_compose:
raw = " ".join(args.text).strip()
log.debug('Appending raw line "%s" to journal "%s"', raw, journal_name)
journal.new_entry(raw)

29
jrnl/dayone2.py Normal file
View file

@ -0,0 +1,29 @@
import json
from datetime import datetime
from .Entry import Entry
from . import Journal
class DayOne2(Journal.PlainJournal):
def __init__(self, **kwargs):
super().__init__(**kwargs)
def _parse(self, json_data):
entries = []
json_string = json.loads(json_data)
for entry in json_string["entries"]:
entries.append(
Entry(
self,
date=datetime.strptime(entry["creationDate"], "%Y-%m-%dT%H:%M:%SZ"),
text=entry["text"],
starred=entry["starred"],
)
)
return entries

View file

@ -1,6 +1,7 @@
#!/usr/bin/env python
# encoding: utf-8
from .dayone2_importer import DayOne2Importer
from .text_exporter import TextExporter
from .jrnl_importer import JRNLImporter
from .json_exporter import JSONExporter
@ -20,7 +21,9 @@ __exporters = [
YAMLExporter,
FancyExporter,
] + template_exporters
__importers = [JRNLImporter]
__importers = [DayOne2Importer,
JRNLImporter]
__exporter_types = {name: plugin for plugin in __exporters for name in plugin.names}
__importer_types = {name: plugin for plugin in __importers for name in plugin.names}
@ -36,8 +39,8 @@ def get_exporter(format):
return None
def get_importer(format):
def get_importer(file_format, path):
for importer in __importers:
if hasattr(importer, "names") and format in importer.names:
return importer
if hasattr(importer, "names") and file_format in importer.names:
return importer(path)
return None

View file

@ -0,0 +1,45 @@
from datetime import datetime
from jrnl.Entry import Entry
from jrnl.plugins.json_importer import JSONImporter
class DayOne2Importer(JSONImporter):
names = ["dayone2"]
extension = "json"
def __init__(self, path):
self.type = 'Day One 2'
self.path = path
self.keys = ['audios', 'creationDate', 'photos',
'starred', 'tags', 'text', 'timeZone', 'uuid']
JSONImporter.__init__(self)
self.convert_journal()
def convert_journal(self):
print(self._convert())
def validate_schema(self):
for key in self.json['entries'][0]:
try:
assert key in self.keys
except AssertionError:
raise KeyError(f"{self.path} is not the expected Day One 2 format.")
print(f"{self.path} validated as Day One 2.")
return True
def parse_json(self):
entries = []
for entry in self.json["entries"]:
entries.append(
Entry(
self,
date=datetime.strptime(entry["creationDate"], "%Y-%m-%dT%H:%M:%SZ"),
text=entry["text"],
starred=entry["starred"],
)
)
self.journal.entries = entries
return entries

View file

@ -10,13 +10,16 @@ class JRNLImporter:
names = ["jrnl"]
@staticmethod
def import_(journal, input=None):
def __init__(self, input):
self.input = input
self.import_(self.input)
def import_(self, journal, input=None):
"""Imports from an existing file if input is specified, and
standard input otherwise."""
old_cnt = len(journal.entries)
old_entries = journal.entries
if input:
if self.input:
with open(input, "r", encoding="utf-8") as f:
other_journal_txt = f.read()
else:

View file

@ -0,0 +1,50 @@
import json
import os
from abc import abstractmethod
from pathlib import Path
from jrnl.Journal import PlainJournal
from jrnl.plugins.text_exporter import TextExporter
class JSONImporter(PlainJournal, TextExporter):
"""This importer reads a JSON file and returns a dict. """
def __init__(self):
PlainJournal.__init__(self)
self.journal = PlainJournal()
self.path = self.path[0]
self.filename = os.path.splitext(self.path)[0]
self.json = self.import_file()
def __str__(self):
return f"{self.type} journal with {len(self.journal)} " \
f"entries located at {self.path}"
def _convert(self):
if self.validate_schema():
self.data = self.parse_json()
self.create_file(self.filename + '.txt')
return self.export(self.journal, self.filename + '.txt')
def import_file(self):
"""Reads a JSON file and returns a dict."""
if os.path.exists(self.path):
try:
with open(self.path) as f:
return json.load(f)
except json.JSONDecodeError:
print(f"{self.path} is not valid JSON.")
elif Path(self.path).suffix != '.json':
print(f"{self.path} must be a JSON file.")
else:
print(f"{self.path} does not exist.")
@abstractmethod
def parse_json(self):
raise NotImplementedError
@abstractmethod
def validate_schema(self):
raise NotImplementedError