feat: add import command
This commit is contained in:
parent
d37bffdc51
commit
be20284f78
9 changed files with 133 additions and 10 deletions
|
|
@ -1 +1 @@
|
|||
__version__ = "0.3.1"
|
||||
__version__ = "0.3.2"
|
||||
|
|
|
|||
41
halig/commands/import_unencrypted.py
Normal file
41
halig/commands/import_unencrypted.py
Normal 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()
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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():
|
||||
|
|
|
|||
|
|
@ -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
8
pdm.lock
generated
|
|
@ -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"},
|
||||
|
|
|
|||
|
|
@ -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]
|
||||
|
|
|
|||
|
|
@ -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):
|
||||
|
|
|
|||
36
tests/commands/test_import.py
Normal file
36
tests/commands/test_import.py
Normal 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
|
||||
Loading…
Add table
Add a link
Reference in a new issue