Skip to content

Commit

Permalink
Tests, plus new --dump option, refs #2, closes #3
Browse files Browse the repository at this point in the history
  • Loading branch information
simonw committed Mar 9, 2023
1 parent 7ab475a commit ebe6aa6
Show file tree
Hide file tree
Showing 3 changed files with 104 additions and 16 deletions.
40 changes: 29 additions & 11 deletions apple_notes_to_sqlite/cli.py
@@ -1,4 +1,5 @@
import click
import json
import secrets
import sqlite_utils
import subprocess
Expand All @@ -20,38 +21,54 @@
set noteCreated to (noteCreatedDate as «class isot» as string)
set noteUpdatedDate to the modification date of eachNote
set noteUpdated to (noteUpdatedDate as «class isot» as string)
log "{split}{split}" & "\n"
log "{split}-id: " & noteId & "\n"
log "{split}-created: " & noteCreated & "\n"
log "{split}-updated: " & noteUpdated & "\n"
log "{split}-title: " & noteTitle & "\n\n"
log noteBody & "\n"
log "{split}{split}" & "\n"
end repeat
end tell
"""
""".strip()


@click.command()
@click.version_option()
@click.argument("db_path")
@click.argument(
"db_path",
type=click.Path(file_okay=True, dir_okay=False, allow_dash=False),
required=False,
)
@click.option("--stop-after", type=int, help="Stop after this many notes")
def cli(db_path, stop_after):
@click.option("--dump", is_flag=True, help="Output notes to standard output")
def cli(db_path, stop_after, dump):
"Export Apple Notes to SQLite"
db = sqlite_utils.Database(db_path)
if not db_path and not dump:
raise click.UsageError(
"Please specify a path to a database file, or use --dump to see the output",
)
expected_count = stop_after
if not expected_count:
expected_count = count_notes()
# Use click progressbar
i = 0
with click.progressbar(
length=expected_count, label="Exporting notes", show_eta=True, show_pos=True
) as bar:
if dump:
for note in extract_notes():
db["notes"].insert(note, pk="id", replace=True)
bar.update(1)
click.echo(json.dumps(note))
i += 1
if stop_after and i >= stop_after:
break
else:
db = sqlite_utils.Database(db_path)
with click.progressbar(
length=expected_count, label="Exporting notes", show_eta=True, show_pos=True
) as bar:
for note in extract_notes():
db["notes"].insert(note, pk="id", replace=True)
bar.update(1)
i += 1
if stop_after and i >= stop_after:
break


def count_notes():
Expand Down Expand Up @@ -80,10 +97,11 @@ def extract_notes():
line = line.decode("mac_roman").strip()
if line == f"{split}{split}":
if note.get("id"):
note["body"] = "\n".join(body)
note["body"] = "\n".join(body).strip()
yield note
note = {}
body = []
continue
found_key = False
for key in ("id", "title", "created", "updated"):
if line.startswith(f"{split}-{key}: "):
Expand Down
2 changes: 1 addition & 1 deletion setup.py
Expand Up @@ -32,6 +32,6 @@ def get_long_description():
apple-notes-to-sqlite=apple_notes_to_sqlite.cli:cli
""",
install_requires=["click", "sqlite-utils"],
extras_require={"test": ["pytest"]},
extras_require={"test": ["pytest", "pytest-subprocess"]},
python_requires=">=3.7",
)
78 changes: 74 additions & 4 deletions tests/test_apple_notes_to_sqlite.py
@@ -1,10 +1,80 @@
from click.testing import CliRunner
from apple_notes_to_sqlite.cli import cli
from apple_notes_to_sqlite.cli import cli, COUNT_SCRIPT
import sqlite_utils
import json
import os
from unittest.mock import patch

FAKE_OUTPUT = b"""
abcdefg-id: note-1
abcdefg-created: 2023-03-08T16:36:41
abcdefg-updated: 2023-03-08T15:36:41
abcdefg-title: Title 1
def test_version():
This is the content of note 1
abcdefgabcdefg
abcdefg-id: note-2
abcdefg-created: 2023-03-08T16:36:41
abcdefg-updated: 2023-03-08T15:36:41
abcdefg-title: Title 2
This is the content of note 2
abcdefgabcdefg
""".strip()

EXPECTED_NOTES = [
{
"id": "note-1",
"created": "2023-03-08T16:36:41",
"updated": "2023-03-08T15:36:41",
"title": "Title 1",
"body": "This is the content of note 1",
},
{
"id": "note-2",
"created": "2023-03-08T16:36:41",
"updated": "2023-03-08T15:36:41",
"title": "Title 2",
"body": "This is the content of note 2",
},
]


@patch("secrets.token_hex")
def test_apple_notes_to_sqlite(mock_token_hex, fp):
fp.register_subprocess(["osascript", "-e", COUNT_SCRIPT], stdout=b"2")
fp.register_subprocess(["osascript", "-e", fp.any()], stdout=FAKE_OUTPUT)
mock_token_hex.return_value = "abcdefg"
runner = CliRunner()
with runner.isolated_filesystem():
assert not os.path.exists("notes.db")
# Run the CLI
result = runner.invoke(cli, ["notes.db"])
assert result.exit_code == 0
# Check that the database was created
assert os.path.exists("notes.db")
db = sqlite_utils.Database("notes.db")
# Check that the notes table was created
assert db.table_names() == ["notes"]
# Check that the notes were inserted
assert list(db["notes"].rows) == EXPECTED_NOTES


@patch("secrets.token_hex")
def test_apple_notes_to_sqlite_dump(mock_token_hex, fp):
fp.register_subprocess(["osascript", "-e", COUNT_SCRIPT], stdout=b"2")
fp.register_subprocess(["osascript", "-e", fp.any()], stdout=FAKE_OUTPUT)
mock_token_hex.return_value = "abcdefg"
runner = CliRunner()
with runner.isolated_filesystem():
result = runner.invoke(cli, ["--version"])
assert not os.path.exists("notes.db")
result = runner.invoke(cli, ["--dump"])
# Check the output
assert result.exit_code == 0
assert result.output.startswith("cli, version ")
# Should still be no database
assert not os.path.exists("notes.db")
# Output should be newline-delimited JSON
notes = []
for line in result.output.splitlines():
notes.append(json.loads(line))
assert notes == EXPECTED_NOTES

0 comments on commit ebe6aa6

Please sign in to comment.