Compare commits
5 commits
13c34b08e2
...
e960f56b93
| Author | SHA1 | Date | |
|---|---|---|---|
| e960f56b93 | |||
| 3ef552bbe5 | |||
| e84adc4392 | |||
| 84a16ee307 | |||
| 7251504dc4 |
2 changed files with 39 additions and 15 deletions
17
test/test_cli.py
Normal file
17
test/test_cli.py
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
from pathlib import Path
|
||||
from unittest.mock import Mock, patch
|
||||
|
||||
from topen import add_annotation, open_editor
|
||||
|
||||
|
||||
def test_open_editor_escapes_shell():
|
||||
"""Ensure filenames with spaces/metas do not allow shell injection."""
|
||||
with patch("subprocess.run") as run_mock:
|
||||
open_editor(Path("my note$1.txt"), "vim")
|
||||
run_mock.assert_called_once_with(["vim", "my note$1.txt"], check=True)
|
||||
|
||||
|
||||
def test_add_annotation_calls_tasklib():
|
||||
task = Mock()
|
||||
add_annotation(task, "hello")
|
||||
task.add_annotation.assert_called_once_with("hello")
|
||||
37
topen.py
37
topen.py
|
|
@ -63,11 +63,9 @@ def main(cfg: "TConf | None" = None, io: "_IO | None" = None) -> int:
|
|||
|
||||
fpath = get_notes_file(uuid, notes_dir=cfg.notes_dir, notes_ext=cfg.notes_ext)
|
||||
|
||||
if not fpath.parent.exists():
|
||||
fpath.parent.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
_ensure_parent_dir(fpath)
|
||||
io.out(f"Editing note: {fpath}")
|
||||
open_editor(fpath, editor=cfg.notes_editor)
|
||||
open_editor(fpath, editor=cfg.notes_editor, io=io)
|
||||
|
||||
if fpath.exists():
|
||||
if is_annotation_missing(task, annotation_content=cfg.notes_annot):
|
||||
|
|
@ -97,9 +95,13 @@ def get_notes_file(uuid: str, notes_dir: Path, notes_ext: str) -> Path:
|
|||
return Path(notes_dir).joinpath(f"{uuid}.{notes_ext}")
|
||||
|
||||
|
||||
def open_editor(file: Path, editor: str) -> None:
|
||||
def open_editor(file: Path, editor: str, io: "_IO | None" = None) -> None:
|
||||
"""Opens a file with the chosen editor."""
|
||||
_ = subprocess.run(f"{editor} {file}", shell=True)
|
||||
try:
|
||||
_ = subprocess.run([editor, str(file)], check=True)
|
||||
except subprocess.CalledProcessError:
|
||||
if io:
|
||||
io.err("Editor exited with an error, aborting.\n")
|
||||
|
||||
|
||||
def is_annotation_missing(task: Task, annotation_content: str) -> bool:
|
||||
|
|
@ -135,16 +137,21 @@ class Opt:
|
|||
is_flag: bool = False
|
||||
|
||||
|
||||
def _real_path(p: Path | str) -> Path:
|
||||
def _expand_path(p: Path | str) -> Path:
|
||||
return Path(os.path.expandvars(p)).expanduser()
|
||||
|
||||
|
||||
def _ensure_parent_dir(file: Path) -> None:
|
||||
if not file.parent.exists():
|
||||
file.parent.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
|
||||
def _determine_default_task_rc() -> Path:
|
||||
if _real_path("~/.taskrc").exists():
|
||||
return _real_path("~/.taskrc")
|
||||
if _real_path("$XDG_CONFIG_HOME/task/taskrc").exists():
|
||||
return _real_path("$XDG_CONFIG_HOME/task/taskrc")
|
||||
return _real_path("~/.config/task/taskrc")
|
||||
if _expand_path("~/.taskrc").exists():
|
||||
return _expand_path("~/.taskrc")
|
||||
if _expand_path("$XDG_CONFIG_HOME/task/taskrc").exists():
|
||||
return _expand_path("$XDG_CONFIG_HOME/task/taskrc")
|
||||
return _expand_path("~/.config/task/taskrc")
|
||||
|
||||
|
||||
def _strtobool(val: str) -> bool:
|
||||
|
|
@ -258,11 +265,11 @@ class TConf:
|
|||
"""If set topen will give no feedback on note editing."""
|
||||
|
||||
def __post_init__(self):
|
||||
self.task_rc = _real_path(self.task_rc)
|
||||
self.task_data = _real_path(self.task_data)
|
||||
self.task_rc = _expand_path(self.task_rc)
|
||||
self.task_data = _expand_path(self.task_data)
|
||||
if self.notes_dir == NON_EXISTENT_PATH:
|
||||
self.notes_dir = self._default_notes_dir()
|
||||
self.notes_dir = _real_path(self.notes_dir)
|
||||
self.notes_dir = _expand_path(self.notes_dir)
|
||||
if not self.notes_editor:
|
||||
self.notes_editor = (
|
||||
os.getenv("EDITOR")
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue