Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 13 additions & 9 deletions .mise/tasks/list
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@ require_readable_notes_state "$notes_dir"

python3 - "$REPO_DIR" "$notes_dir" "${usage_json:-false}" "${usage_recent:-}" "${usage_tag:-}" "${usage_type:-}" "${usage_status:-}" <<'PY'
import json
import subprocess
import sys
from pathlib import Path

Expand All @@ -35,6 +34,7 @@ status_filter = sys.argv[7]

sys.path.insert(0, str(repo_dir / "lib"))
from frontmatter import filter_notes, iter_notes # noqa: E402
from table_output import print_table # noqa: E402

rows = [
note.row()
Expand Down Expand Up @@ -69,13 +69,17 @@ elif not rows:
else:
print("No notes found.")
else:
table = "Title\tType\tStatus\tTags\tUpdated\n" + "\n".join(
f"{row['title']}\t{row['type']}\t{row['status']}\t{', '.join(row['tags'])}\t{row['date']}" for row in rows
)
subprocess.run(
["gum", "table", "--print", "--separator", "\t"],
input=table,
text=True,
check=True,
print_table(
["Title", "Type", "Status", "Tags", "Updated"],
[
[
row["title"],
row["type"],
row["status"],
", ".join(row["tags"]),
row["date"],
]
for row in rows
],
)
PY
22 changes: 13 additions & 9 deletions .mise/tasks/search
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@ require_readable_notes_state "$notes_dir"

python3 - "$REPO_DIR" "$notes_dir" "${usage_query:-}" "${usage_json:-false}" "${usage_tag:-}" "${usage_type:-}" "${usage_status:-}" "${usage_limit:-50}" <<'PY'
import json
import subprocess
import sys
from pathlib import Path

Expand All @@ -37,6 +36,7 @@ limit = sys.argv[8]

sys.path.insert(0, str(repo_dir / "lib"))
from frontmatter import filter_notes, iter_notes # noqa: E402
from table_output import print_table # noqa: E402

try:
limit_count = int(limit)
Expand Down Expand Up @@ -96,13 +96,17 @@ elif not rows:
suffix = f" with {' '.join(filters)}" if filters else ""
print(f"No notes found matching: {query}{suffix}")
else:
table = "Title\tType\tStatus\tUpdated\tMatches\n" + "\n".join(
f"{row['title']}\t{row['type']}\t{row['status']}\t{row['date']}\t{' | '.join(row['matches'][:2])}" for row in rows
)
subprocess.run(
["gum", "table", "--print", "--separator", "\t"],
input=table,
text=True,
check=True,
print_table(
["Title", "Type", "Status", "Updated", "Matches"],
[
[
row["title"],
row["type"],
row["status"],
row["date"],
" | ".join(row["matches"][:2]),
]
for row in rows
],
)
PY
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

**Collective memory, encrypted.**

[![tests: 475](https://img.shields.io/badge/tests-475-brightgreen?style=flat)](test/)
[![tests: 478](https://img.shields.io/badge/tests-478-brightgreen?style=flat)](test/)
![lints: 8](https://img.shields.io/badge/lints-8-blue?style=flat)
[![license: MIT](https://img.shields.io/badge/license-MIT-blue?style=flat)](LICENSE)

Expand Down
36 changes: 36 additions & 0 deletions lib/table_output.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
"""Shared table presentation for human-facing Notes commands."""

from __future__ import annotations

import os
import subprocess
from collections.abc import Iterable, Sequence


def sanitize_cell(value: object) -> str:
return str(value).replace("\t", " ").replace("\n", "⏎")


def format_tsv(headers: Sequence[object], rows: Iterable[Sequence[object]]) -> str:
lines = ["\t".join(sanitize_cell(value) for value in headers)]
lines.extend("\t".join(sanitize_cell(value) for value in row) for row in rows)
return "\n".join(lines)


def print_table(headers: Sequence[object], rows: Iterable[Sequence[object]]) -> None:
table = format_tsv(headers, rows)
gum = os.environ.get("GUM", "gum")

try:
result = subprocess.run(
[gum, "table", "--print", "--separator", "\t"],
input=f"{table}\n",
text=True,
check=True,
capture_output=True,
)
except (OSError, subprocess.CalledProcessError):
print(table)
return

print(result.stdout, end="")
23 changes: 23 additions & 0 deletions test/list.bats
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,29 @@ setup() {
echo "$output" | python3 -c "import sys, json; data = json.load(sys.stdin); assert data[0]['tags'] == ['alpha', 'beta', 'gamma']"
}

@test "list falls back to TSV when gum is unavailable" {
notes new -- --slug alpha --title "Alpha Note" --tags "testing" --updated "2026-03-14"
export GUM="$BATS_TEST_TMPDIR/missing-gum"

run notes list

[ "$status" -eq 0 ]
[[ "$output" == *$'Title\tType\tStatus\tTags\tUpdated'* ]]
[[ "$output" == *$'Alpha Note\t'* ]]
[[ "$output" == *$'testing\t2026-03-14'* ]]
}

@test "list --json bypasses gum presentation" {
notes new -- --slug beta --title "Beta Note" --tags "testing" --updated "2026-03-15"
make_failing_gum

run notes list -- --json

[ "$status" -eq 0 ]
[ ! -e "$GUM_LOG" ]
echo "$output" | python3 -c "import sys, json; assert json.load(sys.stdin)[0]['title'] == 'Beta Note'"
}

@test "list --recent limits output" {
notes new -- --slug old --title "Old Note" --tags "test" --updated "2026-03-10"
notes new -- --slug mid --title "Mid Note" --tags "test" --updated "2026-03-15"
Expand Down
32 changes: 32 additions & 0 deletions test/search.bats
Original file line number Diff line number Diff line change
Expand Up @@ -84,3 +84,35 @@ EOF
[ "$status" -eq 0 ]
[[ "$output" == *"No notes found matching: absent"* ]]
}

@test "search falls back to sanitized TSV when gum table fails" {
python3 <<'PY'
import os
from pathlib import Path

notes_dir = Path(os.environ["NOTES_CALLER_PWD"]) / "notes"
(notes_dir / "unusual.md").write_text(
"---\n"
'title: "Tab\tTitle"\n'
"type: note\n"
"status: active\n"
"tags: [test]\n"
"created: 2026-01-01\n"
"updated: 2026-01-02\n"
"---\n\n"
"# Tab\tTitle\n"
"Body with\ta\ttab\ttoo.\n",
encoding="utf-8",
)
PY
make_failing_gum

run notes search Tab

[ "$status" -eq 0 ]
[ -s "$GUM_LOG" ]
[[ "$output" == *$'Title\tType\tStatus\tUpdated\tMatches'* ]]
[[ "$output" == *"Tab Title"* ]]
[[ "$output" == *"# Tab Title"* ]]
[[ "$output" != *"partial gum output"* ]]
}
13 changes: 13 additions & 0 deletions test/test_helper.bash
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,19 @@ SH
}
export -f make_failing_xargs_overlay

make_failing_gum() {
export GUM="$BATS_TEST_TMPDIR/failing-gum"
export GUM_LOG="$BATS_TEST_TMPDIR/gum.log"
cat > "$GUM" <<'SH'
#!/usr/bin/env bash
printf 'called\n' >> "${GUM_LOG:?}"
printf 'partial gum output\n'
exit 71
SH
chmod +x "$GUM"
}
export -f make_failing_gum

# rudi() wrapper — calls rudi against the same target repo.
rudi() {
if [ -z "${NOTES_CALLER_PWD:-}" ]; then
Expand Down
Loading