feat: add -c/--config flag to every command

This commit is contained in:
cătălin 2023-03-04 23:16:00 +02:00
commit 6f45b76579
Signed by: catalin
GPG key ID: 686088EF78EE4083
10 changed files with 163 additions and 51 deletions

View file

@ -12,12 +12,14 @@ tests:
dist: dist:
pdm build pdm build
build:
pdm build
publish-pypi: publish-pypi:
pdm publish -u $(PYPI_REGISTRY_USERNAME) -P $(PYPI_REGISTRY_PASSWORD) pdm publish -u $(PYPI_REGISTRY_USERNAME) -P $(PYPI_REGISTRY_PASSWORD)
publish-roboces: publish-roboces:
pdm publish --build -u $(ROBOCES_REGISTRY_USERNAME) -p $(ROBOCES_REGISTRY_PASSWORD) -r roboces pdm publish -u $(ROBOCES_REGISTRY_USERNAME) -P $(ROBOCES_REGISTRY_PASSWORD) -r https://git.roboces.dev/api/packages/catalin/pypi
publish: publish:
make publish-pypi make publish-pypi

View file

@ -1,17 +1,43 @@
# halig # halig
[(r)age](https://github.com/woodruffw/pyrage) encrypted note-taking CLI app ![PyPI](https://img.shields.io/pypi/v/halig?logo=python)
![PyPI - License](https://img.shields.io/pypi/l/halig)
![PyPI - Python Version](https://img.shields.io/pypi/pyversions/halig)
[![pdm-managed](https://img.shields.io/badge/pdm-managed-blueviolet)](https://pdm.fming.dev)
## install [(R)age](https://github.com/woodruffw/pyrage) encrypted note-taking CLI app.
`halig` opens, using your favorite `$EDITOR`, an in-memory copy of a file and upon save-and-exit,
it encrypts the new contents into an [age](https://github.com/FiloSottile/age) file that
you can store, _relatively_ safe, anywhere.
## Install
```shell ```shell
pip install halig pip install halig
``` ```
## cli mode PS: I recommend using [pipx](https://pypa.github.io/pipx/) instead
## Setup TLDR
```shell ```shell
$ halig edit some_notebook # edit today's note set -e
$ halig edit some_notebook/foo # edit /path/to/some_notebook/foo.age ssh-keygen -t ed25519
$ halig notebooks # list current notebooks mkdir -p "${XDG_CONFIG_HOME:-$HOME/.config}/halig"
cat << EOF > "${XDG_CONFIG_HOME:-$HOME/.config}/halig/halig.yml"
---
notebooks_root_path: /home/$(id -un)/Documents/Notebooks
identity_path: /home/$(id -un)/.ssh/id_ed25519
recipient_path: /home/$(id -un)/.ssh/id_ed25519.pub
EOF
```
## Usage TLDR
```shell
halig edit some_notebook # edit today's note relative to <notebooks_root_path>/some_notebook
halig edit some_notebook/foo # edit <notebooks_root_path>/some_notebook/foo.age
halig notebooks # list current notebooks
``` ```

View file

@ -1,3 +0,0 @@
from typer import Typer
app = Typer()

View file

@ -1,32 +1,72 @@
#!/usr/bin/env python #!/usr/bin/env python3
from pathlib import Path from pathlib import Path
from typing import Optional
from halig import app
from halig.commands.edit import EditCommand from halig.commands.edit import EditCommand
from halig.commands.notebooks import NotebooksCommand from halig.commands.notebooks import NotebooksCommand
from halig.commands.show import ShowCommand from halig.commands.show import ShowCommand
from halig.settings import load_from_file from halig.settings import load_from_file
from typer import Typer, Option, Argument
@app.command() app = Typer(pretty_exceptions_enable=False)
def notebooks(max_depth: int = -1):
if max_depth < 0: config_option = Option(
max_depth = float("inf") # type: ignore[assignment] None,
settings = load_from_file() "--config",
command = NotebooksCommand(settings=settings, max_depth=max_depth) "-c",
help="Configuration file. Must be YAML and schema compatible",
)
@app.command(help="List all notebooks and notes, tree-style")
def notebooks(
level: int = Option(
-1,
"--level",
"-l",
help="Tree max recursion level; negative numbers indicate a value of infinity",
),
config: Optional[Path] = config_option,
):
if level < 0:
level = float("inf") # type: ignore[assignment]
settings = load_from_file(config)
command = NotebooksCommand(settings=settings, max_depth=level)
command.run() command.run()
@app.command() @app.command(help="Edit or add a note into a notebook")
def edit(note: Path): def edit(note: Path = Argument(
settings = load_from_file() ...,
help="""A valid, settings-relative path.
Be aware that valid can also mean implicit notes, that is, pointing to a
current-day note just by its notebook name. For example, if today is
2023-04-04 and you have a notebook containing a 2023-04-04.age note,
simply pointing to the notebook's name, e.g. `halig edit notebook` will
edit the 2023-04-04.age note. Also keep in mind that the note may or may
not exist and it'll be created accordingly; the only requirement is that
the notebook folder structure is correct and exists""",
), config: Optional[Path] = config_option):
settings = load_from_file(config)
command = EditCommand(settings=settings, note_path=note) command = EditCommand(settings=settings, note_path=note)
command.run() command.run()
@app.command() @app.command(help="Show a note's contents")
def show(note: Path): def show(
settings = load_from_file() note: Path = Argument(
...,
help="""A valid, settings-relative path.
Be aware that valid can also mean implicit notes, that is, pointing to a
current-day note just by its notebook name. For example, if today is
2023-04-04 and you have a notebook containing a 2023-04-04.age note,
simply pointing to the notebook's name, e.g. `halig show notebook` will
print the 2023-04-04.age note""",
),
config: Optional[Path] = config_option,
):
settings = load_from_file(config)
command = ShowCommand(settings=settings, note_path=note) command = ShowCommand(settings=settings, note_path=note)
command.run() command.run()

View file

@ -5,10 +5,20 @@ VERSIONS = ["3.10", "3.11"]
@nox.session(python=VERSIONS) @nox.session(python=VERSIONS)
def tests(session): def tests(session):
session.run('pdm', 'export', '-G', 'tests', '-f', 'requirements', '-o', 'requirements.txt', external=True) session.run(
session.install('-r', "requirements.txt") "pdm",
"export",
"-G",
"tests",
"-f",
"requirements",
"-o",
"requirements.txt",
external=True,
)
session.install("-r", "requirements.txt")
session.run("make", "tests", external=True) session.run("make", "tests", external=True)
session.run('rm', "requirements.txt", external=True) session.run("rm", "requirements.txt", external=True)
@nox.session(python=VERSIONS) @nox.session(python=VERSIONS)

View file

@ -1,11 +1,3 @@
[tool.pdm.build]
includes = []
[build-system]
requires = ["pdm-backend"]
build-backend = "pdm.backend"
[project] [project]
authors = [ authors = [
{ name = "cătălin", email = "185504a9@duck.com" }, { name = "cătălin", email = "185504a9@duck.com" },
@ -20,9 +12,37 @@ dependencies = [
"pendulum>=2.1.2", "pendulum>=2.1.2",
] ]
name = "halig" name = "halig"
version = "0.1.2" version = "0.1.7"
description = "age-encrypted, file-based, note-taking CLI app" description = "age-encrypted, file-based, note-taking CLI app"
readme = "README.md" readme = "README.md"
keywords = ["cli", "notes", "age", "rage", "encryption", "notebook"]
license = { text = "GPL-3.0-or-later" }
classifiers = [
"Development Status :: 4 - Beta",
"Environment :: Console",
"License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)",
"Natural Language :: English",
"Operating System :: POSIX :: Linux",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Topic :: Terminals",
"Topic :: Utilities",
"Typing :: Typed"
]
[project.urls]
Homepage = "https://git.roboces.dev/catalin/halig"
Repository = "https://git.roboces.dev/catalin/halig"
Documentation = "https://git.roboces.dev/catalin/halig"
Changelog = "https://git.roboces.dev/catalin/halig"
[tool.pdm.build]
excludes = ["**/.pytest_cache/**"]
includes = []
[build-system]
requires = ["pdm-backend"]
build-backend = "pdm.backend"
[project.scripts] [project.scripts]
halig = "halig.main:app" halig = "halig.main:app"

View file

@ -10,15 +10,15 @@ from halig.settings import Settings
@pytest.fixture() @pytest.fixture()
def notes(notebooks_path: Path): def notes(notebooks_path: Path):
personal = (notebooks_path / "Personal") personal = notebooks_path / "Personal"
work = (notebooks_path / "Work") work = notebooks_path / "Work"
personal.mkdir() personal.mkdir()
work.mkdir() work.mkdir()
personal_todos = (personal / "todos.age") personal_todos = personal / "todos.age"
personal_todos.touch() personal_todos.touch()
work_todos = (work / "todos.age") work_todos = work / "todos.age"
work_todos.touch() work_todos.touch()
dailies = work / "Dailies" dailies = work / "Dailies"
@ -47,7 +47,9 @@ def current_note(notes, settings, encryptor) -> Path:
@pytest.fixture @pytest.fixture
def current_daily(notes, settings, encryptor) -> Path: def current_daily(notes, settings, encryptor) -> Path:
note_path = settings.notebooks_root_path / "Work" / "Dailies" / f"{utils.now().date()}.age" note_path = (
settings.notebooks_root_path / "Work" / "Dailies" / f"{utils.now().date()}.age"
)
data = encryptor.encrypt("foo".encode()) data = encryptor.encrypt("foo".encode())
with note_path.open("wb") as f: with note_path.open("wb") as f:
f.write(data) f.write(data)
@ -60,4 +62,4 @@ def mock_edit(mocker):
with open(callargs[1], "wb") as f: with open(callargs[1], "wb") as f:
f.write("edited".encode()) f.write("edited".encode())
mocker.patch('halig.commands.edit.subprocess.call', side_effect=edit) mocker.patch("halig.commands.edit.subprocess.call", side_effect=edit)

View file

@ -8,11 +8,16 @@ def test_edit_raises_invalid_age_file(notes, settings: Settings):
note_path = settings.notebooks_root_path / "foo.txt" note_path = settings.notebooks_root_path / "foo.txt"
note_path.touch() note_path.touch()
with pytest.raises(ValueError, match="is not a valid AGE file"): with pytest.raises(ValueError, match="is not a valid AGE file"):
EditCommand(note_path, settings=settings, ) EditCommand(
note_path,
settings=settings,
)
def test_edit_current_note(mock_edit, current_note, settings: Settings, encryptor): def test_edit_current_note(mock_edit, current_note, settings: Settings, encryptor):
edit_command = EditCommand(note_path=settings.notebooks_root_path, settings=settings) edit_command = EditCommand(
note_path=settings.notebooks_root_path, settings=settings
)
assert edit_command.note_path == current_note assert edit_command.note_path == current_note
edit_command.run() edit_command.run()
with current_note.open("rb") as f: with current_note.open("rb") as f:

View file

@ -8,18 +8,26 @@ from halig.settings import Settings
def test_show_raises_note_path_does_not_exist(notes, settings: Settings): def test_show_raises_note_path_does_not_exist(notes, settings: Settings):
with pytest.raises(ValueError, match="does not exist"): with pytest.raises(ValueError, match="does not exist"):
ShowCommand(Path('foo'), settings=settings, ) ShowCommand(
Path("foo"),
settings=settings,
)
def test_show_raises_note_path_is_not_age_valid(notes, settings: Settings): def test_show_raises_note_path_is_not_age_valid(notes, settings: Settings):
note_path = settings.notebooks_root_path / "foo.txt" note_path = settings.notebooks_root_path / "foo.txt"
note_path.touch() note_path.touch()
with pytest.raises(ValueError, match="is not a valid AGE file"): with pytest.raises(ValueError, match="is not a valid AGE file"):
ShowCommand(note_path, settings=settings, ) ShowCommand(
note_path,
settings=settings,
)
def test_show_current_note(current_note, settings): def test_show_current_note(current_note, settings):
show_command = ShowCommand(note_path=settings.notebooks_root_path, settings=settings) show_command = ShowCommand(
note_path=settings.notebooks_root_path, settings=settings
)
assert show_command.note_path == current_note assert show_command.note_path == current_note
assert show_command.decrypt() == "foo" assert show_command.decrypt() == "foo"

View file

@ -12,9 +12,11 @@ from halig.settings import Settings
@pytest.fixture() @pytest.fixture()
def halig_ssh_public_key(): def halig_ssh_public_key():
return "ssh-ed25519 " \ return (
"AAAAC3NzaC1lZDI1NTE5AAAAIGjHhIF/DlVCb2dRFMlKia7nij1Aq+zRDCaMIwe/VKDh" \ "ssh-ed25519 "
" foo@bar" "AAAAC3NzaC1lZDI1NTE5AAAAIGjHhIF/DlVCb2dRFMlKia7nij1Aq+zRDCaMIwe/VKDh"
" foo@bar"
)
@pytest.fixture() @pytest.fixture()