feat: add -c/--config flag to every command
This commit is contained in:
parent
570c29d9f1
commit
6f45b76579
10 changed files with 163 additions and 51 deletions
6
Makefile
6
Makefile
|
|
@ -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
|
||||||
|
|
|
||||||
38
README.md
38
README.md
|
|
@ -1,17 +1,43 @@
|
||||||
# halig
|
# halig
|
||||||
|
|
||||||
[(r)age](https://github.com/woodruffw/pyrage) encrypted note-taking CLI app
|

|
||||||
|

|
||||||
|

|
||||||
|
[](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
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
@ -1,3 +0,0 @@
|
||||||
from typer import Typer
|
|
||||||
|
|
||||||
app = Typer()
|
|
||||||
|
|
@ -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()
|
||||||
|
|
||||||
|
|
|
||||||
16
noxfile.py
16
noxfile.py
|
|
@ -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)
|
||||||
|
|
|
||||||
|
|
@ -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"
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
|
||||||
|
|
@ -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:
|
||||||
|
|
|
||||||
|
|
@ -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"
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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()
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue