Skip to content

Commit

Permalink
Add extended metadata support for DayOne Classic (#928)
Browse files Browse the repository at this point in the history
* Updating changelog [ci skip]

* Incrementing version to v2.4 [ci skip]

* [DayOne] remove extra spaces from the titles of edited DayOne entries

Otherwise, a leading space was being introduced

* [DayOne] maintain existing tags stored in DayOne metadata

* [DayOne] brings back extended DayOne attributes

* [DayOne] maintain metadata on edited entries

Fixes #358, See also #159

* [DayOne Exporter] apply black formatting

* [JSON Exporter] add support for extended DayOne Metadata

* [DayOne] [Tests] test that extended DayOne metadata is added to new entries

Co-authored-by: Jrnl Bot <[email protected]>
  • Loading branch information
minchinweb and jrnl-bot authored Jun 6, 2020
1 parent 759c69c commit 4047608
Show file tree
Hide file tree
Showing 6 changed files with 160 additions and 8 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ obj
env/
env*/
venv*/
.venv*/

# PyCharm Project files
.idea/
Expand Down
19 changes: 19 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,24 @@
# Changelog

## [v2.4](https://pypi.org/project/jrnl/v2.4/) (2020-04-25)

[Full Changelog](https://github.com/jrnl-org/jrnl/compare/v2.4-beta...v2.4)

**Implemented enhancements:**

- Upgrade license to GPLv3 [\#918](https://github.com/jrnl-org/jrnl/pull/918) ([wren](https://github.com/wren))

**Build:**

- Update makefile to match pipeline better [\#919](https://github.com/jrnl-org/jrnl/pull/919) ([wren](https://github.com/wren))

**Updated documentation:**

- Clean up readme file [\#924](https://github.com/jrnl-org/jrnl/pull/924) ([wren](https://github.com/wren))
- Clarify that editing config isn't always destructive [\#923](https://github.com/jrnl-org/jrnl/pull/923) ([Epskampie](https://github.com/Epskampie))

# Changelog

## [Unreleased](https://github.com/jrnl-org/jrnl/)

[Full Changelog](https://github.com/jrnl-org/jrnl/compare/v2.4.2...HEAD)
Expand Down
12 changes: 12 additions & 0 deletions features/dayone.feature
Original file line number Diff line number Diff line change
Expand Up @@ -63,3 +63,15 @@ Feature: Dayone specific implementation details.
Then we should get no error
and the output should be parsable as json
and the json output should contain entries.0.uuid = "4BB1F46946AD439996C9B59DE7C4DDC1"

Scenario: Writing into Dayone adds extended metadata
Given we use the config "dayone.yaml"
When we run "jrnl 01 may 1979: Being born hurts."
and we run "jrnl --export json"
Then "entries" in the json output should have 5 elements
and the json output should contain entries.0.creator.software_agent
and the json output should contain entries.0.creator.os_agent
and the json output should contain entries.0.creator.host_name
and the json output should contain entries.0.creator.generation_date
and the json output should contain entries.0.creator.device_agent
and "entries.0.creator.software_agent" in the json output should contain "jrnl"
22 changes: 17 additions & 5 deletions features/steps/export_steps.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,13 +32,21 @@ def check_output_field_not_key(context, field, key):
@then('"{field}" in the json output should contain "{key}"')
def check_output_field_key(context, field, key):
out = context.stdout_capture.getvalue()
out_json = json.loads(out)
assert field in out_json
assert key in out_json[field]
struct = json.loads(out)

for node in field.split("."):
try:
struct = struct[int(node)]
except ValueError:
assert node in struct
struct = struct[node]

assert key in struct


@then("the json output should contain {path}")
@then('the json output should contain {path} = "{value}"')
def check_json_output_path(context, path, value):
def check_json_output_path(context, path, value=None):
""" E.g.
the json output should contain entries.0.title = "hello"
"""
Expand All @@ -50,7 +58,11 @@ def check_json_output_path(context, path, value):
struct = struct[int(node)]
except ValueError:
struct = struct[node]
assert struct == value, struct

if value is not None:
assert struct == value, struct
else:
assert struct is not None


@then("the output should be a valid XML string")
Expand Down
93 changes: 90 additions & 3 deletions jrnl/DayOneJournal.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,13 @@
import time
import uuid
from xml.parsers.expat import ExpatError
import socket
import platform

import pytz
import tzlocal

from . import Entry, Journal
from . import __title__, __version__, Entry, Journal
from . import time as jrnl_time


Expand Down Expand Up @@ -71,6 +73,41 @@ def open(self):
for tag in dict_entry.get("Tags", [])
]

"""Extended DayOne attributes"""
try:
entry.creator_device_agent = dict_entry["Creator"][
"Device Agent"
]
except:
pass
try:
entry.creator_generation_date = dict_entry["Creator"][
"Generation Date"
]
except:
entry.creator_generation_date = date
try:
entry.creator_host_name = dict_entry["Creator"]["Host Name"]
except:
pass
try:
entry.creator_os_agent = dict_entry["Creator"]["OS Agent"]
except:
pass
try:
entry.creator_software_agent = dict_entry["Creator"][
"Software Agent"
]
except:
pass
try:
entry.location = dict_entry["Location"]
except:
pass
try:
entry.weather = dict_entry["Weather"]
except:
pass
self.entries.append(entry)
self.sort()
return self
Expand All @@ -85,6 +122,20 @@ def write(self):

if not hasattr(entry, "uuid"):
entry.uuid = uuid.uuid1().hex
if not hasattr(entry, "creator_device_agent"):
entry.creator_device_agent = "" # iPhone/iPhone5,3
if not hasattr(entry, "creator_generation_date"):
entry.creator_generation_date = utc_time
if not hasattr(entry, "creator_host_name"):
entry.creator_host_name = socket.gethostname()
if not hasattr(entry, "creator_os_agent"):
entry.creator_os_agent = "{}/{}".format(
platform.system(), platform.release()
)
if not hasattr(entry, "creator_software_agent"):
entry.creator_software_agent = "{}/{}".format(
__title__, __version__
)

fn = (
Path(self.config["journal"])
Expand All @@ -102,10 +153,23 @@ def write(self):
tag.strip(self.config["tagsymbols"]).replace("_", " ")
for tag in entry.tags
],
"Creator": {
"Device Agent": entry.creator_device_agent,
"Generation Date": entry.creator_generation_date,
"Host Name": entry.creator_host_name,
"OS Agent": entry.creator_os_agent,
"Software Agent": entry.creator_software_agent,
},
}
if hasattr(entry, "location"):
entry_plist["Location"] = entry.location
if hasattr(entry, "weather"):
entry_plist["Weather"] = entry.weather

# plistlib expects a binary object
with fn.open(mode="wb") as f:
plistlib.dump(entry_plist, f, fmt=plistlib.FMT_XML, sort_keys=False)

for entry in self._deleted_entries:
filename = os.path.join(
self.config["journal"], "entries", entry.uuid + ".doentry"
Expand Down Expand Up @@ -147,7 +211,7 @@ def parse_editable_str(self, edited):
if line.endswith("*"):
current_entry.starred = True
line = line[:-1]
current_entry.title = line[len(date_blob) - 1 :]
current_entry.title = line[len(date_blob) - 1 :].strip()
current_entry.date = new_date
elif current_entry:
current_entry.body += line + "\n"
Expand All @@ -159,10 +223,33 @@ def parse_editable_str(self, edited):
# Now, update our current entries if they changed
for entry in entries:
entry._parse_text()
matched_entries = [e for e in self.entries if e.uuid.lower() == entry.uuid]
matched_entries = [
e for e in self.entries if e.uuid.lower() == entry.uuid.lower()
]
# tags in entry body
if matched_entries:
# This entry is an existing entry
match = matched_entries[0]

# merge existing tags with tags pulled from the entry body
entry.tags = list(set(entry.tags + match.tags))

# extended Dayone metadata
if hasattr(match, "creator_device_agent"):
entry.creator_device_agent = match.creator_device_agent
if hasattr(match, "creator_generation_date"):
entry.creator_generation_date = match.creator_generation_date
if hasattr(match, "creator_host_name"):
entry.creator_host_name = match.creator_host_name
if hasattr(match, "creator_os_agent"):
entry.creator_os_agent = match.creator_os_agent
if hasattr(match, "creator_software_agent"):
entry.creator_software_agent = match.creator_software_agent
if hasattr(match, "location"):
entry.location = match.location
if hasattr(match, "weather"):
entry.weather = match.weather

if match != entry:
self.entries.remove(match)
entry.modified = True
Expand Down
21 changes: 21 additions & 0 deletions jrnl/plugins/json_exporter.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,27 @@ def entry_to_dict(cls, entry):
}
if hasattr(entry, "uuid"):
entry_dict["uuid"] = entry.uuid
if (
hasattr(entry, "creator_device_agent")
or hasattr(entry, "creator_generation_date")
or hasattr(entry, "creator_host_name")
or hasattr(entry, "creator_os_agent")
or hasattr(entry, "creator_software_agent")
):
entry_dict["creator"] = {}
if hasattr(entry, "creator_device_agent"):
entry_dict["creator"]["device_agent"] = entry.creator_device_agent
if hasattr(entry, "creator_generation_date"):
entry_dict["creator"]["generation_date"] = str(
entry.creator_generation_date
)
if hasattr(entry, "creator_host_name"):
entry_dict["creator"]["host_name"] = entry.creator_host_name
if hasattr(entry, "creator_os_agent"):
entry_dict["creator"]["os_agent"] = entry.creator_os_agent
if hasattr(entry, "creator_software_agent"):
entry_dict["creator"]["software_agent"] = entry.creator_software_agent

return entry_dict

@classmethod
Expand Down

0 comments on commit 4047608

Please sign in to comment.