halig/halig/main.py

138 lines
4 KiB
Python
Executable file

#!/usr/bin/env python3
from typer import Typer
from pathlib import Path
from typing import Optional
from tempfile import NamedTemporaryFile
from datetime import datetime
from sh import age
from halig.config import (
get_example_config,
get_config,
DEFAULT_CONFIGURATION_PATH,
Config,
)
from halig.exceptions import NoteDoesNotExist
import subprocess
import os
import yaml
from halig.exceptions import handle_errors
from halig import logger
app = Typer(pretty_exceptions_enable=False)
TODAY = f"{datetime.now():%Y-%m-%d}"
TODAY_NOTE = f"{TODAY}.norg.age"
DEFAULT_NEORG_METADATA = f"""@document.meta
title: {TODAY}
description: {TODAY}
authors: {os.getenv("USER")}
categories: [
]
created: {TODAY}
version: 1.0.0
@end
"""
def _wait_for_editor(path):
subprocess.call([os.environ.get("EDITOR") or "vim", path])
def _edit_note(path: Path, config: Config):
path = path.expanduser()
if not path.exists():
raise NoteDoesNotExist
if path.is_dir():
path = path / f"{TODAY}.norg.age"
note_contents = age("-d", "-i", config.encryption_keys.private_key, path)
with NamedTemporaryFile(delete=False) as tmpfile:
tmpfile.write(str(note_contents).encode())
_wait_for_editor(tmpfile.name)
logger.info(f"encrypting updates into {path}")
age("-R", str(config.encryption_keys.public_key), tmpfile.name, _out=str(path))
Path(tmpfile.name).unlink()
def _get_template_data(path: Path) -> str | None:
template_path = path / "template.norg"
if not (template_path.exists() and template_path.is_file()):
return None
with open(template_path) as f:
return f.read()
def _new_note(path: Path, config: Config):
with NamedTemporaryFile(delete=False) as tempfile:
tempfile.write((_get_template_data(path) or DEFAULT_NEORG_METADATA).encode())
subprocess.call([os.environ.get("EDITOR") or "vim", tempfile.name])
file = Path(path / f"{TODAY_NOTE}")
file.touch(exist_ok=True)
print(f"age-encrypting {tempfile.name}")
age("-R", str(config.encryption_keys.public_key), tempfile.name, _out=str(file))
Path(tempfile.name).unlink()
@app.command()
def init(force_recreate: bool = False):
"""Create config file. If the config file already exists, it'll not
be overwritten unless `--force-recreate` flag is provided
"""
if DEFAULT_CONFIGURATION_PATH.exists() and not force_recreate:
logger.error(
"""$HOME/.config/halig/halig.yml already exists.
Execute again with --force-recreate in order to replace the configuration file's
contents with the default one"""
)
exit(1)
DEFAULT_CONFIGURATION_PATH.parent.mkdir(parents=True, exist_ok=True)
DEFAULT_CONFIGURATION_PATH.touch(mode=0o700, exist_ok=True)
with open(DEFAULT_CONFIGURATION_PATH, "w") as f:
yaml.dump(get_example_config().dict(), f, Dumper=yaml.SafeDumper)
@app.command()
@handle_errors
def notebooks(
print_files: bool = False,
print_hidden: bool = False,
configuration_path: Optional[Path] = None,
):
"""Print notebooks and their contents, tree-style"""
config = get_config(configuration_path)
logger.tree(
config.notes_root_path, print_files=print_files, print_hidden=print_hidden
)
@app.command()
@handle_errors
def edit(path: Path, configuration_path: Optional[Path] = None):
"""Edit a new or existing file by providing a path. Note that if only
a dir is provided, an attempt to create or open `<dir>/<current date>.norg.age`
will be made"""
config = get_config(configuration_path)
path = Path(path)
if not path.is_absolute():
path = config.notes_root_path / path
if not path.exists():
logger.error(f"{path} does not exist; available notebooks:\n")
logger.tree(config.notes_root_path)
exit(1)
if path.is_file() or any(
filter(lambda f: f.name == f"{TODAY_NOTE}", path.iterdir()) # type: ignore
):
_edit_note(path, config)
return
_new_note(path, config)
app()