From 6f45b7657968f6dbbdf2ef8acb5d7898a33f0cde Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?c=C4=83t=C4=83lin?= Date: Sat, 4 Mar 2023 23:16:00 +0200 Subject: [PATCH] feat: add -c/--config flag to every command --- Makefile | 6 ++-- README.md | 38 +++++++++++++++++---- halig/__init__.py | 3 -- halig/main.py | 68 +++++++++++++++++++++++++++++-------- noxfile.py | 16 +++++++-- pyproject.toml | 38 ++++++++++++++++----- tests/commands/conftest.py | 14 ++++---- tests/commands/test_edit.py | 9 +++-- tests/commands/test_show.py | 14 ++++++-- tests/conftest.py | 8 +++-- 10 files changed, 163 insertions(+), 51 deletions(-) diff --git a/Makefile b/Makefile index 74b44d6..92153b3 100644 --- a/Makefile +++ b/Makefile @@ -12,12 +12,14 @@ tests: dist: pdm build +build: + pdm build + publish-pypi: pdm publish -u $(PYPI_REGISTRY_USERNAME) -P $(PYPI_REGISTRY_PASSWORD) - 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: make publish-pypi diff --git a/README.md b/README.md index a02d6fb..277b9f9 100644 --- a/README.md +++ b/README.md @@ -1,17 +1,43 @@ # 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 pip install halig ``` -## cli mode +PS: I recommend using [pipx](https://pypa.github.io/pipx/) instead + +## Setup TLDR ```shell -$ halig edit some_notebook # edit today's note -$ halig edit some_notebook/foo # edit /path/to/some_notebook/foo.age -$ halig notebooks # list current notebooks +set -e +ssh-keygen -t ed25519 +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 /some_notebook +halig edit some_notebook/foo # edit /some_notebook/foo.age +halig notebooks # list current notebooks + ``` \ No newline at end of file diff --git a/halig/__init__.py b/halig/__init__.py index b0e7b86..e69de29 100644 --- a/halig/__init__.py +++ b/halig/__init__.py @@ -1,3 +0,0 @@ -from typer import Typer - -app = Typer() diff --git a/halig/main.py b/halig/main.py index cadc5cf..ab9edf7 100644 --- a/halig/main.py +++ b/halig/main.py @@ -1,32 +1,72 @@ -#!/usr/bin/env python +#!/usr/bin/env python3 from pathlib import Path +from typing import Optional -from halig import app from halig.commands.edit import EditCommand from halig.commands.notebooks import NotebooksCommand from halig.commands.show import ShowCommand from halig.settings import load_from_file +from typer import Typer, Option, Argument -@app.command() -def notebooks(max_depth: int = -1): - if max_depth < 0: - max_depth = float("inf") # type: ignore[assignment] - settings = load_from_file() - command = NotebooksCommand(settings=settings, max_depth=max_depth) +app = Typer(pretty_exceptions_enable=False) + +config_option = Option( + None, + "--config", + "-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() -@app.command() -def edit(note: Path): - settings = load_from_file() +@app.command(help="Edit or add a note into a notebook") +def edit(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 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.run() -@app.command() -def show(note: Path): - settings = load_from_file() +@app.command(help="Show a note's contents") +def show( + 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.run() diff --git a/noxfile.py b/noxfile.py index 00739fc..4e09711 100644 --- a/noxfile.py +++ b/noxfile.py @@ -5,10 +5,20 @@ VERSIONS = ["3.10", "3.11"] @nox.session(python=VERSIONS) def tests(session): - session.run('pdm', 'export', '-G', 'tests', '-f', 'requirements', '-o', 'requirements.txt', external=True) - session.install('-r', "requirements.txt") + session.run( + "pdm", + "export", + "-G", + "tests", + "-f", + "requirements", + "-o", + "requirements.txt", + external=True, + ) + session.install("-r", "requirements.txt") session.run("make", "tests", external=True) - session.run('rm', "requirements.txt", external=True) + session.run("rm", "requirements.txt", external=True) @nox.session(python=VERSIONS) diff --git a/pyproject.toml b/pyproject.toml index e466617..3eb730f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,11 +1,3 @@ -[tool.pdm.build] -includes = [] - -[build-system] -requires = ["pdm-backend"] -build-backend = "pdm.backend" - - [project] authors = [ { name = "cătălin", email = "185504a9@duck.com" }, @@ -20,9 +12,37 @@ dependencies = [ "pendulum>=2.1.2", ] name = "halig" -version = "0.1.2" +version = "0.1.7" description = "age-encrypted, file-based, note-taking CLI app" 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] halig = "halig.main:app" diff --git a/tests/commands/conftest.py b/tests/commands/conftest.py index 18e05a7..6f36e1c 100644 --- a/tests/commands/conftest.py +++ b/tests/commands/conftest.py @@ -10,15 +10,15 @@ from halig.settings import Settings @pytest.fixture() def notes(notebooks_path: Path): - personal = (notebooks_path / "Personal") - work = (notebooks_path / "Work") + personal = notebooks_path / "Personal" + work = notebooks_path / "Work" personal.mkdir() work.mkdir() - personal_todos = (personal / "todos.age") + personal_todos = personal / "todos.age" personal_todos.touch() - work_todos = (work / "todos.age") + work_todos = work / "todos.age" work_todos.touch() dailies = work / "Dailies" @@ -47,7 +47,9 @@ def current_note(notes, settings, encryptor) -> Path: @pytest.fixture 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()) with note_path.open("wb") as f: f.write(data) @@ -60,4 +62,4 @@ def mock_edit(mocker): with open(callargs[1], "wb") as f: f.write("edited".encode()) - mocker.patch('halig.commands.edit.subprocess.call', side_effect=edit) + mocker.patch("halig.commands.edit.subprocess.call", side_effect=edit) diff --git a/tests/commands/test_edit.py b/tests/commands/test_edit.py index f90c84b..da1ac72 100644 --- a/tests/commands/test_edit.py +++ b/tests/commands/test_edit.py @@ -8,11 +8,16 @@ def test_edit_raises_invalid_age_file(notes, settings: Settings): note_path = settings.notebooks_root_path / "foo.txt" note_path.touch() 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): - 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 edit_command.run() with current_note.open("rb") as f: diff --git a/tests/commands/test_show.py b/tests/commands/test_show.py index fc36c32..705261c 100644 --- a/tests/commands/test_show.py +++ b/tests/commands/test_show.py @@ -8,18 +8,26 @@ from halig.settings import Settings def test_show_raises_note_path_does_not_exist(notes, settings: Settings): 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): note_path = settings.notebooks_root_path / "foo.txt" note_path.touch() 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): - 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.decrypt() == "foo" diff --git a/tests/conftest.py b/tests/conftest.py index 33b3760..9a33095 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -12,9 +12,11 @@ from halig.settings import Settings @pytest.fixture() def halig_ssh_public_key(): - return "ssh-ed25519 " \ - "AAAAC3NzaC1lZDI1NTE5AAAAIGjHhIF/DlVCb2dRFMlKia7nij1Aq+zRDCaMIwe/VKDh" \ - " foo@bar" + return ( + "ssh-ed25519 " + "AAAAC3NzaC1lZDI1NTE5AAAAIGjHhIF/DlVCb2dRFMlKia7nij1Aq+zRDCaMIwe/VKDh" + " foo@bar" + ) @pytest.fixture()