Compare commits

...
3 Commits
Author SHA1 Message Date
tris af14c80803 Added templating for render 2026-07-08 12:25:22 +10:00
tris 44549aaff3 Added basic rendering support and some cleanup 2026-01-29 09:37:15 +11:00
tris e9cc2cc3d0 Minor changes 2025-12-04 09:19:05 +11:00
7 changed files with 151 additions and 11 deletions
+9
View File
@@ -0,0 +1,9 @@
# PyN
CLI tool for manipulating text notes
### Git support
If the notes directory is under git control then modifications will be auto staged.
You can then execute the `pyn sync` command to pull and push from the upstream server.
+54 -5
View File
@@ -1,6 +1,6 @@
from pyn.common import parser, logger
from pyn.config import Config
from pyn import notes
from pyn import notes, templating
import sys
import os
@@ -29,6 +29,10 @@ def cmd_add_note(config, title, notebook):
if not body:
notes.edit_file(config, filename)
notes.run_git(
config, ["add", str(filename.relative_to(config.directory))], quiet=True
)
add_note = group.add_parser("add", aliases=["a"])
add_note.add_argument("--notebook", "-n", help="add to this notebook [general]")
@@ -53,7 +57,7 @@ def cmd_del_note(config: Config, title: str, notebook: str, yes: bool) -> None:
del_note = group.add_parser("delete", aliases=["del"])
del_note.add_argument("--notebook", "-n", help="add to this notebook [general]")
del_note.add_argument("--yes", action="store_true", help="dont prompt for confirmation")
del_note.add_argument("title", help="title for note", nargs="?")
del_note.add_argument("title", help="title for note", nargs="*")
del_note.set_defaults(func=cmd_del_note)
### List notes
@@ -63,7 +67,7 @@ def cmd_list_notes(config: Config, notebook: str, pretty: bool) -> None:
for filename in notes.get_file_list(config, notebook):
if pretty:
e = notes.file_entry(config, filename)
print(f"{e.notebook:>20s}: {e.filename}")
print(f"\033[32m{e.notebook:>20s}\033[0m: {e.filename}")
else:
print(filename)
@@ -79,10 +83,13 @@ list_notes.set_defaults(func=cmd_list_notes)
def cmd_edit_note(config, title, notebook: str) -> None:
filename = notes.search_filenames(config, title, notebook)
notes.edit_file(config, filename)
notes.run_git(
config, ["add", str(filename.relative_to(config.directory))], quiet=True
)
edit_note = group.add_parser("edit", aliases=["e"])
edit_note.add_argument("title", help="note title", nargs="?")
edit_note.add_argument("title", help="note title", nargs="*")
edit_note.add_argument("--notebook", "-n", help="limit to this notebook")
edit_note.set_defaults(func=cmd_edit_note)
@@ -95,6 +102,48 @@ def cmd_show_note(config, title, notebook: str) -> None:
show_note = group.add_parser("show", aliases=["s", "cat"])
show_note.add_argument("title", help="note title", nargs="?")
show_note.add_argument("title", help="note title", nargs="*")
show_note.add_argument("--notebook", "-n", help="limit to this notebook")
show_note.set_defaults(func=cmd_show_note)
def cmd_render_note(config, title, notebook: str) -> None:
filename = notes.search_filenames(config, title, notebook)
with open(filename, "r", encoding="utf-8") as input_file:
text = input_file.read()
html = templating.render_note(text, title=filename.stem)
print(html)
render_note = group.add_parser("render")
render_note.add_argument("title", help="note title", nargs="*")
render_note.add_argument("--notebook", "-n", help="limit to this notebook")
render_note.set_defaults(func=cmd_render_note)
### Git
def cmd_git(config: Config, cmd: list[str]) -> None:
output = notes.run_git(config, cmd)
sys.stdout.write(output)
git = group.add_parser("git", help="run a git command in the note directory")
git.add_argument("cmd", help="git command", nargs="*")
git.set_defaults(func=cmd_git)
### Sync
def cmd_sync(config: Config) -> None:
output = notes.run_git(config, ["pull"])
sys.stdout.write(f"Pull: {output}\n")
output = notes.run_git(config, ["push"])
sys.stdout.write(f"Push: {output}\n")
git = group.add_parser("sync", help="synchronise the notes")
git.set_defaults(func=cmd_sync)
+25 -3
View File
@@ -3,6 +3,7 @@ import pathlib
import sys
from pyn.config import Config
from pyn.common import logger
from datetime import datetime
from shutil import copyfileobj
@@ -76,19 +77,25 @@ def get_file_list(
dirs[:] = [d for d in dirs if d[0] != "."]
for filename in files:
yield d / filename
if filename.endswith(".md"):
yield d.relative_to(root) / filename
def search_filenames(
config: Config, title: str, notebook: str | None = None
config: Config, title: str | list[str], notebook: str | None = None
) -> pathlib.Path:
root = config.directory / notebook if notebook else config.directory
files = "\n".join((str(x) for x in get_file_list(config, notebook)))
if isinstance(title, list):
title = " ".join(title)
cmd = ["fzf", "-1", "-i"]
if title:
cmd.extend(["-q", title])
try:
output = subprocess.check_output(cmd, cwd=root)
output = subprocess.check_output(cmd, cwd=root, input=files.encode())
file_path = root / output.decode("utf-8").strip()
return file_path
except subprocess.CalledProcessError as e:
@@ -123,6 +130,7 @@ def search_contents(
if full_line:
cmd.append("-x")
logger.debug("Executing: %r", cmd)
try:
output = subprocess.check_output(cmd)
for line in output.decode("utf-8").strip().split("\n"):
@@ -132,3 +140,17 @@ def search_contents(
if e.returncode == 1:
return
raise
def run_git(config: Config, cmd: list[str], quiet: bool = False) -> str:
if not (config.directory / ".git").is_dir():
if quiet:
return ""
raise RuntimeError("Notes not currently tracked")
cmd = ["git", "-C", str(config.directory)] + cmd
try:
output = subprocess.check_output(cmd, stderr=subprocess.STDOUT)
return output.decode("utf-8").strip()
except subprocess.CalledProcessError as e:
raise RuntimeError(str(e))
+43
View File
@@ -0,0 +1,43 @@
<!doctype html5>
<html>
<head>
<title>{{ title }}</title>
<style>
BODY {
font-family: Arial, Helvetica, sans-serif;
font-size: 10pt;
}
DIV.page {
margin: 20px;
}
HR {
color: #DDD;
}
H1, H2, H3 {
margin-bottom: 5px;
}
P {
margin-top: 2px;
margin-bottom: 2px;
}
H1 {
font-size: 16pt;
}
H2 {
font-size: 14pt;
}
H3 {
font-size: 12pt;
}
DIV.page P:first-of-type {
color: #999;
text-align: right;
}
</style>
</head>
<body>
<div class="page">
{{ content|safe }}
</div>
</body>
</html>
+11
View File
@@ -0,0 +1,11 @@
from jinja2 import Environment, PackageLoader, select_autoescape
import markdown
env = Environment(loader=PackageLoader("pyn"), autoescape=select_autoescape())
def render_note(note: str, title: str = "") -> str:
content = markdown.markdown(note)
template = env.get_template("render_note.html")
return template.render(content=content, title=title)
+7 -3
View File
@@ -4,6 +4,10 @@ from pyn.commands import group as parent_group
from shutil import get_terminal_size
import sys
LIST_CHAR = "-"
COMPLETE_CHAR = "x"
PENDING_PATTERN = r"^[+\-\*]\s+\[ \]"
COMPLETED_PATTERN = r"^[+\-\*]\s+\[" + COMPLETE_CHAR + r"\]"
p = parent_group.add_parser("todo", help="manipulate todo items")
group = p.add_subparsers()
@@ -35,10 +39,10 @@ def cmd_list_pending(config: Config, notebook: str):
entry = notes.file_entry(config, result.filename)
if entry.notebook != nb:
nb = entry.notebook
print(f"\nNotebook: {entry.notebook}")
print(f"\nNotebook: \033[32m{entry.notebook}\033[0m")
name = result.filename.name
sys.stdout.write(result.snippet)
sys.stdout.write(" " * (cols - len(result.snippet) - len(name)))
sys.stdout.write(" " * (cols - len(result.snippet) - len(name) - 1))
sys.stdout.write(name)
sys.stdout.write("\n")
@@ -62,7 +66,7 @@ def cmd_mark_complete(
item_str = " ".join(item)
results = list(
notes.search_contents(
config, f"* [{unchecked}] {item}", notebook, full_line=True
config, f"* [{unchecked}] {item_str}", notebook, full_line=True
)
)
+2
View File
@@ -8,6 +8,8 @@ authors = [
readme = "README.md"
requires-python = ">=3.13"
dependencies = [
"markdown (>=3.10.1,<4.0.0)",
"jinja2 (>=3.1.6,<4.0.0)"
]
[project.scripts]