provozni zaloha
This commit is contained in:
@@ -44,6 +44,33 @@ def _connect() -> sqlite3.Connection:
|
||||
conn.close()
|
||||
|
||||
|
||||
def _ordered_ids(conn: sqlite3.Connection, *, read: bool) -> list[int]:
|
||||
"""Internal ids of one bookmark set in display order.
|
||||
|
||||
Unread (`read=False`) is what `list` shows, read (`read=True`) what `history`
|
||||
shows. Display IDs are 1-based positions here, computed on the fly — never
|
||||
stored — so they renumber whenever the set changes.
|
||||
"""
|
||||
if read:
|
||||
rows = conn.execute(
|
||||
"SELECT id FROM bookmarks WHERE read_at IS NOT NULL ORDER BY read_at DESC"
|
||||
).fetchall()
|
||||
else:
|
||||
rows = conn.execute(
|
||||
"SELECT id FROM bookmarks WHERE read_at IS NULL ORDER BY created_at DESC"
|
||||
).fetchall()
|
||||
return [row["id"] for row in rows]
|
||||
|
||||
|
||||
def _resolve_display_id(conn: sqlite3.Connection, display_id: int, *, read: bool) -> int | None:
|
||||
"""Translate a display ID into an internal id, or None if out of range."""
|
||||
order = _ordered_ids(conn, read=read)
|
||||
idx = display_id - 1
|
||||
if idx < 0 or idx >= len(order):
|
||||
return None
|
||||
return order[idx]
|
||||
|
||||
|
||||
def _parse_tags(raw: str) -> list[str]:
|
||||
"""Parse comma-separated tags into a deduplicated sorted list."""
|
||||
if not raw:
|
||||
@@ -68,12 +95,12 @@ def _domain(url: str) -> str:
|
||||
|
||||
|
||||
def _print_bookmark(
|
||||
row: sqlite3.Row, *, show_status: bool = False, show_read_date: bool = False
|
||||
row: sqlite3.Row, display_id: int, *, show_status: bool = False, show_read_date: bool = False
|
||||
) -> None:
|
||||
"""Format and print a single bookmark row."""
|
||||
"""Format and print a single bookmark row under its display ID."""
|
||||
tags = json.loads(row["tags"])
|
||||
tag_str = f" [{', '.join(tags)}]" if tags else ""
|
||||
print(f"#{row['id']} {_domain(row['url'])}{tag_str}")
|
||||
print(f"#{display_id} {_domain(row['url'])}{tag_str}")
|
||||
print(f" {row['description']}")
|
||||
print(f" {row['url']}")
|
||||
line = f" added: {row['created_at'][:10]}"
|
||||
@@ -94,13 +121,14 @@ def cmd_add(args: argparse.Namespace) -> None:
|
||||
(args.url, args.description, json.dumps(tags, ensure_ascii=False), now),
|
||||
)
|
||||
conn.commit()
|
||||
bid = conn.execute("SELECT last_insert_rowid()").fetchone()[0]
|
||||
tag_info = f" [{', '.join(tags)}]" if tags else ""
|
||||
print(f"Added bookmark #{bid}: {args.url}{tag_info}")
|
||||
# Newest unread sorts first, so a fresh bookmark is always display #1.
|
||||
print(f"Added bookmark #1: {args.url}{tag_info}")
|
||||
|
||||
|
||||
def cmd_list(args: argparse.Namespace) -> None:
|
||||
with _connect() as conn:
|
||||
display_by_id = {nid: i + 1 for i, nid in enumerate(_ordered_ids(conn, read=False))}
|
||||
if args.tag:
|
||||
rows = conn.execute(
|
||||
"""SELECT * FROM bookmarks
|
||||
@@ -121,49 +149,44 @@ def cmd_list(args: argparse.Namespace) -> None:
|
||||
)
|
||||
return
|
||||
|
||||
# Display IDs come from the full unread set so a tag-filtered list keeps the
|
||||
# same numbers `read`/`show` resolve against (gaps are expected when filtered).
|
||||
for r in rows:
|
||||
_print_bookmark(r)
|
||||
_print_bookmark(r, display_by_id[r["id"]])
|
||||
print()
|
||||
|
||||
|
||||
def cmd_read(args: argparse.Namespace) -> None:
|
||||
with _connect() as conn:
|
||||
internal_id = _resolve_display_id(conn, args.id, read=False)
|
||||
if internal_id is None:
|
||||
print(f"No unread bookmark #{args.id}.")
|
||||
return
|
||||
now = datetime.now(timezone.utc).isoformat()
|
||||
cur = conn.execute(
|
||||
"UPDATE bookmarks SET read_at = ? WHERE id = ? AND read_at IS NULL",
|
||||
(now, args.id),
|
||||
)
|
||||
affected = cur.rowcount
|
||||
conn.execute("UPDATE bookmarks SET read_at = ? WHERE id = ?", (now, internal_id))
|
||||
conn.commit()
|
||||
if affected == 0:
|
||||
print(f"Bookmark #{args.id} not found or already marked as read.")
|
||||
else:
|
||||
print(f"Marked bookmark #{args.id} as read.")
|
||||
print(f"Marked bookmark #{args.id} as read.")
|
||||
|
||||
|
||||
def cmd_unread(args: argparse.Namespace) -> None:
|
||||
with _connect() as conn:
|
||||
cur = conn.execute(
|
||||
"UPDATE bookmarks SET read_at = NULL WHERE id = ? AND read_at IS NOT NULL",
|
||||
(args.id,),
|
||||
)
|
||||
affected = cur.rowcount
|
||||
internal_id = _resolve_display_id(conn, args.id, read=True)
|
||||
if internal_id is None:
|
||||
print(f"No read bookmark #{args.id} in history.")
|
||||
return
|
||||
conn.execute("UPDATE bookmarks SET read_at = NULL WHERE id = ?", (internal_id,))
|
||||
conn.commit()
|
||||
if affected == 0:
|
||||
print(f"Bookmark #{args.id} not found or not marked as read.")
|
||||
else:
|
||||
print(f"Unmarked bookmark #{args.id}.")
|
||||
print(f"Unmarked bookmark #{args.id}.")
|
||||
|
||||
|
||||
def cmd_show(args: argparse.Namespace) -> None:
|
||||
with _connect() as conn:
|
||||
row = conn.execute(
|
||||
"SELECT * FROM bookmarks WHERE id = ?", (args.id,)
|
||||
).fetchone()
|
||||
if not row:
|
||||
print(f"Bookmark #{args.id} not found.")
|
||||
return
|
||||
_print_bookmark(row, show_status=True)
|
||||
internal_id = _resolve_display_id(conn, args.id, read=False)
|
||||
if internal_id is None:
|
||||
print(f"No unread bookmark #{args.id}.")
|
||||
return
|
||||
row = conn.execute("SELECT * FROM bookmarks WHERE id = ?", (internal_id,)).fetchone()
|
||||
_print_bookmark(row, args.id, show_status=True)
|
||||
|
||||
|
||||
def cmd_history(args: argparse.Namespace) -> None:
|
||||
@@ -176,8 +199,8 @@ def cmd_history(args: argparse.Namespace) -> None:
|
||||
print("No read bookmarks.")
|
||||
return
|
||||
|
||||
for r in rows:
|
||||
_print_bookmark(r, show_read_date=True)
|
||||
for display_id, r in enumerate(rows, start=1):
|
||||
_print_bookmark(r, display_id, show_read_date=True)
|
||||
print()
|
||||
|
||||
|
||||
@@ -197,15 +220,15 @@ def main() -> None:
|
||||
|
||||
# read (mark as read)
|
||||
p_read = sub.add_parser("read", help="Mark bookmark as read")
|
||||
p_read.add_argument("id", type=int, help="Bookmark ID")
|
||||
p_read.add_argument("id", type=int, help="Display ID from `list`")
|
||||
|
||||
# unread (unmark)
|
||||
p_unread = sub.add_parser("unread", help="Unmark bookmark as read")
|
||||
p_unread.add_argument("id", type=int, help="Bookmark ID")
|
||||
p_unread.add_argument("id", type=int, help="Display ID from `history`")
|
||||
|
||||
# show (display details)
|
||||
p_show = sub.add_parser("show", help="Show bookmark details")
|
||||
p_show.add_argument("id", type=int, help="Bookmark ID")
|
||||
p_show.add_argument("id", type=int, help="Display ID from `list`")
|
||||
|
||||
# history (list read)
|
||||
sub.add_parser("history", help="List read bookmarks")
|
||||
|
||||
Reference in New Issue
Block a user