feat: add import command

This commit is contained in:
cătălin 2023-05-17 19:00:42 +02:00
commit be20284f78
Signed by: catalin
GPG key ID: 0178DF42F43E5FD2
9 changed files with 133 additions and 10 deletions

View file

@ -1 +1 @@
__version__ = "0.3.1"
__version__ = "0.3.2"

View file

@ -0,0 +1,41 @@
from collections.abc import Generator
from pathlib import Path
from halig.commands.base import BaseCommand
from halig.encryption import Encryptor
from halig.settings import Settings
class ImportCommand(BaseCommand):
def __init__(self, settings: Settings, unlink: bool = False):
super().__init__(settings)
self.encryptor = Encryptor(self.settings)
self.unlink = unlink
def get_importables(self) -> Generator[Path, None, None]:
"""Get all markdown files under self.settings.notebooks_root_path
Yields:
a list of Path objects matching any markdown files under
the notebooks root path
"""
return self.settings.notebooks_root_path.glob("**/*.md")
def run(self):
"""For every importable file, encrypt its contents inside
`filename.age`. If `self.unlink` is `True`, unlink the original
file
"""
for file_path in self.get_importables():
with file_path.open("rb") as f:
contents = f.read()
encrypted_contents = self.encryptor.encrypt(contents)
encrypted_file_path = file_path.with_suffix("").with_suffix(".age")
encrypted_file_path.touch()
with encrypted_file_path.open("wb") as f:
f.write(encrypted_contents)
if self.unlink:
file_path.unlink()

View file

@ -3,13 +3,16 @@ COMMANDS_NOTEBOOKS_HELP = "List all notebooks and notes, tree-style"
COMMANDS_EDIT_HELP = "Edit or add a note into a notebook"
COMMANDS_SHOW_HELP = "Show a note's contents"
COMMANDS_VERSION = "Show halig's version"
COMMANDS_IMPORT_HELP = "Encrypt existing unencrypted files"
# OPTIONS
OPTION_CONFIG_HELP = "Configuration file. Must be YAML and schema compatible"
OPTION_LEVEL_HELP = (
"Tree max recursion level; negative numbers indicate a value of infinity"
)
OPTION_UNLINK_HELP = """Setting this will remove the original markdown files;
only the newly encrypted .age files will be preserved. Backup your data first
"""
# ARGUMENTS
ARGUMENT_EDIT_NOTE_HELP = """A valid, settings-relative path.
Be aware that valid can also mean implicit notes, that is, pointing to a

View file

@ -3,11 +3,13 @@ from pathlib import Path
from typing import Optional
from rich import print
from rich.prompt import Prompt
from typer import Argument, Option, Typer
from halig import literals
from halig.__version__ import __version__
from halig.commands.edit import EditCommand
from halig.commands.import_unencrypted import ImportCommand
from halig.commands.notebooks import NotebooksCommand
from halig.commands.show import ShowCommand
from halig.settings import load_from_file
@ -87,6 +89,31 @@ def show(
command.run()
@app.command(name="import", help=literals.COMMANDS_IMPORT_HELP)
@capture
def import_unencrypted(
unlink: bool = Option( # noqa: B008
False,
help=literals.OPTION_UNLINK_HELP,
),
config: Optional[Path] = config_option, # noqa: UP007
):
settings = load_from_file(config)
command = ImportCommand(settings=settings, unlink=unlink)
files_to_unlink = list(command.get_importables())
if files_to_unlink:
if unlink:
should_unlink = Prompt.ask(
f"""Unlink flag set, will delete {len(files_to_unlink)} files.
Have you backed up your data?""",
choices=["y", "Y", "N", "n"],
)
if should_unlink in ["N", "n"]:
command.unlink = False
command.run()
@app.command(help=literals.COMMANDS_VERSION)
@capture
def version():

View file

@ -81,9 +81,7 @@ class Settings(BaseSettings):
@lru_cache
def load_from_file(file_path: Path | None = None) -> Settings:
if file_path is None:
halig_config_home = Path(
platformdirs.user_config_dir("halig", ensure_exists=True),
)
halig_config_home = platformdirs.user_config_path("halig", ensure_exists=True)
file_path = halig_config_home / "halig.yml"
file_path.touch(exist_ok=True)
elif not file_path.exists():

8
pdm.lock generated
View file

@ -243,7 +243,7 @@ dependencies = [
[[package]]
name = "mkdocs-material"
version = "9.1.12"
version = "9.1.13"
requires_python = ">=3.7"
summary = "Documentation that simply works"
dependencies = [
@ -933,9 +933,9 @@ content_hash = "sha256:e698ce4565363b2b89ae66ebc3031f378848822332fe4f2a49019dbb0
{url = "https://files.pythonhosted.org/packages/3b/3f/9531888bc92bafb1bffddca5d9240a7bae9a479d465528883b61808ef9d6/mkdocs-autorefs-0.4.1.tar.gz", hash = "sha256:70748a7bd025f9ecd6d6feeba8ba63f8e891a1af55f48e366d6d6e78493aba84"},
{url = "https://files.pythonhosted.org/packages/fb/5c/6594400290df38f99bf8d9ef249387b56f4ad962667836266f6fe7da8597/mkdocs_autorefs-0.4.1-py3-none-any.whl", hash = "sha256:a2248a9501b29dc0cc8ba4c09f4f47ff121945f6ce33d760f145d6f89d313f5b"},
]
"mkdocs-material 9.1.12" = [
{url = "https://files.pythonhosted.org/packages/03/b5/60bbe928cde37b72908bfd25eed76738141edc3ee983eb57e26a53930dbc/mkdocs_material-9.1.12.tar.gz", hash = "sha256:d4ebe9b5031ce63a265c19fb5eab4d27ea4edadb05de206372e831b2b7570fb5"},
{url = "https://files.pythonhosted.org/packages/57/51/090775b6b334f63cb16a02d82dc76ab46fcf7798a6874d22136ffe88e5ee/mkdocs_material-9.1.12-py3-none-any.whl", hash = "sha256:68c57d95d10104179c8c3ce9a88ee9d2322a5145b3d0f1f38ff686253fb5ec98"},
"mkdocs-material 9.1.13" = [
{url = "https://files.pythonhosted.org/packages/10/10/7245bfb3ae9fe673cb283259a049e75d622418fbdb1cc54d4eef77cce92f/mkdocs_material-9.1.13.tar.gz", hash = "sha256:9102e7604d73e507021847601b0a8b4fe9035422788390183f464fa3b30dd508"},
{url = "https://files.pythonhosted.org/packages/5a/a3/00aaf7d83d44d1bac139df38d0dd30e55b799d13f565b6947f5b7a9e6c0f/mkdocs_material-9.1.13-py3-none-any.whl", hash = "sha256:5705cf8cb6c47c747606bd914bb6c01993ff141295cd259475559a1f09f07d5d"},
]
"mkdocs-material-extensions 1.1.1" = [
{url = "https://files.pythonhosted.org/packages/cd/3f/e5e3c9bfbb42e4cb661f71bcec787ae6bdf4a161b8c4bb68fd7d991c436c/mkdocs_material_extensions-1.1.1.tar.gz", hash = "sha256:9c003da71e2cc2493d910237448c672e00cefc800d3d6ae93d2fc69979e3bd93"},

View file

@ -84,7 +84,7 @@ reportMissingImports = false
reportMissingTypeStubs = false
[tool.ruff]
extend-select = ["W", "C90", "I", "N", "UP", "S", "BLE", "FBT", "B", "A", "COM", "C4", "DTZ", "T10", "EM", "ISC", "T20", "PT", "RSE", "RET", "SIM", "PTH", "ERA", "PGH", "PL", "TRY", "RUF"]
extend-select = ["W", "C90", "I", "N", "UP", "S", "BLE", "B", "A", "COM", "C4", "DTZ", "T10", "EM", "ISC", "T20", "PT", "RSE", "RET", "SIM", "PTH", "ERA", "PGH", "PL", "TRY", "RUF"]
extend-ignore = ["S101", "ISC002"]
[tool.mypy]

View file

@ -29,6 +29,24 @@ def notes(notebooks_path: Path):
dt = dt.subtract(days=day_offset)
(dailies / f"{dt.date()}.age").touch()
@pytest.fixture()
def unencrypted_notes(notebooks_path):
unencrypted_root_path = notebooks_path / "unencrypted"
unencrypted_root_path.mkdir()
for i in range(5):
note = unencrypted_root_path / f"note_{i}.md"
note.touch()
subnote_path = unencrypted_root_path / f"inner_{i}"
subnote_path.mkdir()
for j in range(2):
subnote = subnote_path / f"note_{i}_{j}.md"
subnote.touch()
with subnote.open("w") as f:
f.write(f"subnote {i} {j}")
with note.open("w") as f:
f.write(f"note {i}")
return unencrypted_root_path
@pytest.fixture
def notebooks_command(settings: Settings):

View file

@ -0,0 +1,36 @@
import pytest
from pathlib import Path
from halig.commands.import_unencrypted import ImportCommand
from halig.encryption import Encryptor
@pytest.fixture()
def command(settings) -> ImportCommand:
return ImportCommand(settings)
def test_get_importables(unencrypted_notes: Path, command: ImportCommand):
notes = list(command.get_importables())
assert len(notes) == 15
assert notes == list(unencrypted_notes.glob("**/*.md"))
def test_import(unencrypted_notes: Path, command: ImportCommand, encryptor: Encryptor):
command.run()
notes = command.get_importables()
encrypted_notes = list(unencrypted_notes.glob("**/*.age"))
assert len(encrypted_notes) == len(list(notes)) == 15
for note in encrypted_notes:
with note.open("rb") as f:
encrypted_data = f.read()
data = encryptor.decrypt(encrypted_data)
if "inner" in str(note):
assert f'sub{note.name.replace(".age", "").replace("_"," ")}' == data.decode()
else:
assert note.name.replace(".age", "").replace("_"," ") == data.decode()
def test_import_unlink(unencrypted_notes: Path, command: ImportCommand, encryptor: Encryptor):
command.unlink = True
command.run()
notes = command.get_importables()
encrypted_notes = list(unencrypted_notes.glob("**/*.age"))
assert len(encrypted_notes) == 15
assert len(list(unencrypted_notes.glob("**/*.md"))) == 0