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)
|
||||
|
||||
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()
|
||||
|
|
33
jrnl/cli.py
33
jrnl/cli.py
|
@ -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
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
|
||||
# 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
|
||||
|
|
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"]
|
||||
|
||||
@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:
|
||||
|
|
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