mirror of
https://github.com/jrnl-org/jrnl.git
synced 2025-05-20 21:18:32 +02:00
Add support for DayOne2
This commit is contained in:
parent
41c4c241cc
commit
6f84eec165
10 changed files with 208 additions and 29 deletions
12
features/data/configs/dayone2.yaml
Normal file
12
features/data/configs/dayone2.yaml
Normal 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: "|"
|
27
features/data/journals/dayone2.json
Normal file
27
features/data/journals/dayone2.json
Normal 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
15
features/dayone2.feature
Normal 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.
|
||||||
|
"""
|
||||||
|
|
|
@ -385,6 +385,12 @@ def open_journal(name, config, legacy=False):
|
||||||
|
|
||||||
sys.exit(1)
|
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 not config["encrypt"]:
|
||||||
if legacy:
|
if legacy:
|
||||||
return LegacyJournal(name, **config).open()
|
return LegacyJournal(name, **config).open()
|
||||||
|
|
33
jrnl/cli.py
33
jrnl/cli.py
|
@ -133,20 +133,9 @@ def parse_args(args=None):
|
||||||
metavar="TYPE",
|
metavar="TYPE",
|
||||||
dest="import_",
|
dest="import_",
|
||||||
choices=plugins.IMPORT_FORMATS,
|
choices=plugins.IMPORT_FORMATS,
|
||||||
help="Import entries into your journal. TYPE can be {}, and it defaults to jrnl if nothing else is specified.".format(
|
help="Import entries into your journal. TYPE can be one of "\
|
||||||
plugins.util.oxford_list(plugins.IMPORT_FORMATS)
|
"{0}".format(plugins.util.oxford_list(plugins.IMPORT_FORMATS)),
|
||||||
),
|
|
||||||
default=False,
|
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(
|
exporting.add_argument(
|
||||||
"--encrypt",
|
"--encrypt",
|
||||||
|
@ -181,14 +170,14 @@ def guess_mode(args, config):
|
||||||
compose = True
|
compose = True
|
||||||
export = False
|
export = False
|
||||||
import_ = False
|
import_ = False
|
||||||
if args.import_ is not False:
|
if args.import_:
|
||||||
compose = False
|
compose = False
|
||||||
export = False
|
export = False
|
||||||
import_ = True
|
import_ = True
|
||||||
elif (
|
elif (
|
||||||
args.decrypt is not False
|
args.decrypt
|
||||||
or args.encrypt is not False
|
or args.encrypt
|
||||||
or args.export is not False
|
or args.export
|
||||||
or any((args.short, args.tags, args.edit))
|
or any((args.short, args.tags, args.edit))
|
||||||
):
|
):
|
||||||
compose = False
|
compose = False
|
||||||
|
@ -357,6 +346,10 @@ def run(manual_args=None):
|
||||||
else:
|
else:
|
||||||
sys.exit()
|
sys.exit()
|
||||||
|
|
||||||
|
# Import mode
|
||||||
|
if args.import_:
|
||||||
|
return plugins.get_importer(args.import_, args.text)
|
||||||
|
|
||||||
# This is where we finally open the journal!
|
# This is where we finally open the journal!
|
||||||
try:
|
try:
|
||||||
journal = open_journal(journal_name, config)
|
journal = open_journal(journal_name, config)
|
||||||
|
@ -364,12 +357,8 @@ def run(manual_args=None):
|
||||||
print(f"[Interrupted while opening journal]", file=sys.stderr)
|
print(f"[Interrupted while opening journal]", file=sys.stderr)
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
# Import mode
|
|
||||||
if mode_import:
|
|
||||||
plugins.get_importer(args.import_).import_(journal, args.input)
|
|
||||||
|
|
||||||
# Writing mode
|
# Writing mode
|
||||||
elif mode_compose:
|
if mode_compose:
|
||||||
raw = " ".join(args.text).strip()
|
raw = " ".join(args.text).strip()
|
||||||
log.debug('Appending raw line "%s" to journal "%s"', raw, journal_name)
|
log.debug('Appending raw line "%s" to journal "%s"', raw, journal_name)
|
||||||
journal.new_entry(raw)
|
journal.new_entry(raw)
|
||||||
|
|
29
jrnl/dayone2.py
Normal file
29
jrnl/dayone2.py
Normal 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
|
|
@ -1,6 +1,7 @@
|
||||||
#!/usr/bin/env python
|
#!/usr/bin/env python
|
||||||
# encoding: utf-8
|
# encoding: utf-8
|
||||||
|
|
||||||
|
from .dayone2_importer import DayOne2Importer
|
||||||
from .text_exporter import TextExporter
|
from .text_exporter import TextExporter
|
||||||
from .jrnl_importer import JRNLImporter
|
from .jrnl_importer import JRNLImporter
|
||||||
from .json_exporter import JSONExporter
|
from .json_exporter import JSONExporter
|
||||||
|
@ -20,7 +21,9 @@ __exporters = [
|
||||||
YAMLExporter,
|
YAMLExporter,
|
||||||
FancyExporter,
|
FancyExporter,
|
||||||
] + template_exporters
|
] + template_exporters
|
||||||
__importers = [JRNLImporter]
|
|
||||||
|
__importers = [DayOne2Importer,
|
||||||
|
JRNLImporter]
|
||||||
|
|
||||||
__exporter_types = {name: plugin for plugin in __exporters for name in plugin.names}
|
__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}
|
__importer_types = {name: plugin for plugin in __importers for name in plugin.names}
|
||||||
|
@ -36,8 +39,8 @@ def get_exporter(format):
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
def get_importer(format):
|
def get_importer(file_format, path):
|
||||||
for importer in __importers:
|
for importer in __importers:
|
||||||
if hasattr(importer, "names") and format in importer.names:
|
if hasattr(importer, "names") and file_format in importer.names:
|
||||||
return importer
|
return importer(path)
|
||||||
return None
|
return None
|
||||||
|
|
45
jrnl/plugins/dayone2_importer.py
Normal file
45
jrnl/plugins/dayone2_importer.py
Normal 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
|
|
@ -10,13 +10,16 @@ class JRNLImporter:
|
||||||
|
|
||||||
names = ["jrnl"]
|
names = ["jrnl"]
|
||||||
|
|
||||||
@staticmethod
|
def __init__(self, input):
|
||||||
def import_(journal, input=None):
|
self.input = input
|
||||||
|
self.import_(self.input)
|
||||||
|
|
||||||
|
def import_(self, journal, input=None):
|
||||||
"""Imports from an existing file if input is specified, and
|
"""Imports from an existing file if input is specified, and
|
||||||
standard input otherwise."""
|
standard input otherwise."""
|
||||||
old_cnt = len(journal.entries)
|
old_cnt = len(journal.entries)
|
||||||
old_entries = journal.entries
|
old_entries = journal.entries
|
||||||
if input:
|
if self.input:
|
||||||
with open(input, "r", encoding="utf-8") as f:
|
with open(input, "r", encoding="utf-8") as f:
|
||||||
other_journal_txt = f.read()
|
other_journal_txt = f.read()
|
||||||
else:
|
else:
|
||||||
|
|
50
jrnl/plugins/json_importer.py
Normal file
50
jrnl/plugins/json_importer.py
Normal 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
|
Loading…
Add table
Reference in a new issue