chronicle-log 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- chronicle_log-0.1.0/PKG-INFO +40 -0
- chronicle_log-0.1.0/README.md +29 -0
- chronicle_log-0.1.0/chronicle/__init__.py +3 -0
- chronicle_log-0.1.0/chronicle/db.py +78 -0
- chronicle_log-0.1.0/chronicle/export.py +38 -0
- chronicle_log-0.1.0/chronicle/main.py +140 -0
- chronicle_log-0.1.0/chronicle_log.egg-info/PKG-INFO +40 -0
- chronicle_log-0.1.0/chronicle_log.egg-info/SOURCES.txt +13 -0
- chronicle_log-0.1.0/chronicle_log.egg-info/dependency_links.txt +1 -0
- chronicle_log-0.1.0/chronicle_log.egg-info/entry_points.txt +2 -0
- chronicle_log-0.1.0/chronicle_log.egg-info/requires.txt +2 -0
- chronicle_log-0.1.0/chronicle_log.egg-info/top_level.txt +1 -0
- chronicle_log-0.1.0/pyproject.toml +21 -0
- chronicle_log-0.1.0/setup.cfg +4 -0
- chronicle_log-0.1.0/tests/test_db.py +26 -0
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: chronicle-log
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: A CLI logbook for founders, builders, and creators.
|
|
5
|
+
Author-email: Your Name <you@example.com>
|
|
6
|
+
License: MIT
|
|
7
|
+
Requires-Python: >=3.9
|
|
8
|
+
Description-Content-Type: text/markdown
|
|
9
|
+
Requires-Dist: typer>=0.9
|
|
10
|
+
Requires-Dist: rich>=13
|
|
11
|
+
|
|
12
|
+
# Chronicle 📖
|
|
13
|
+
|
|
14
|
+
> Log the journey. Tell the story.
|
|
15
|
+
|
|
16
|
+
Chronicle is a minimal CLI logbook built for founders, builders, and solo makers.
|
|
17
|
+
Log your ideas, wins, failures, and progress — with accurate timestamps — as you build.
|
|
18
|
+
|
|
19
|
+
## Install
|
|
20
|
+
|
|
21
|
+
pip install chronicle-log
|
|
22
|
+
|
|
23
|
+
## Usage
|
|
24
|
+
|
|
25
|
+
chronicle log "Had a breakthrough on the auth flow" -c build
|
|
26
|
+
chronicle idea "What if we added voice logging via whisper?"
|
|
27
|
+
chronicle win "First real user signed up"
|
|
28
|
+
chronicle fail "Shipped a bug that deleted sessions. Lesson: always backup."
|
|
29
|
+
chronicle show --limit 20
|
|
30
|
+
chronicle stats
|
|
31
|
+
chronicle export --format markdown
|
|
32
|
+
|
|
33
|
+
## Why?
|
|
34
|
+
|
|
35
|
+
Built by a founder, for founders. Every entry you make today
|
|
36
|
+
becomes the raw material of the story you'll tell tomorrow.
|
|
37
|
+
|
|
38
|
+
## License
|
|
39
|
+
|
|
40
|
+
MIT — free forever.
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# Chronicle 📖
|
|
2
|
+
|
|
3
|
+
> Log the journey. Tell the story.
|
|
4
|
+
|
|
5
|
+
Chronicle is a minimal CLI logbook built for founders, builders, and solo makers.
|
|
6
|
+
Log your ideas, wins, failures, and progress — with accurate timestamps — as you build.
|
|
7
|
+
|
|
8
|
+
## Install
|
|
9
|
+
|
|
10
|
+
pip install chronicle-log
|
|
11
|
+
|
|
12
|
+
## Usage
|
|
13
|
+
|
|
14
|
+
chronicle log "Had a breakthrough on the auth flow" -c build
|
|
15
|
+
chronicle idea "What if we added voice logging via whisper?"
|
|
16
|
+
chronicle win "First real user signed up"
|
|
17
|
+
chronicle fail "Shipped a bug that deleted sessions. Lesson: always backup."
|
|
18
|
+
chronicle show --limit 20
|
|
19
|
+
chronicle stats
|
|
20
|
+
chronicle export --format markdown
|
|
21
|
+
|
|
22
|
+
## Why?
|
|
23
|
+
|
|
24
|
+
Built by a founder, for founders. Every entry you make today
|
|
25
|
+
becomes the raw material of the story you'll tell tomorrow.
|
|
26
|
+
|
|
27
|
+
## License
|
|
28
|
+
|
|
29
|
+
MIT — free forever.
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
# chronicle/db.py
|
|
2
|
+
import sqlite3
|
|
3
|
+
import os
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
from datetime import datetime
|
|
6
|
+
|
|
7
|
+
DB_DIR = Path.home() / ".chronicle"
|
|
8
|
+
DB_PATH = DB_DIR / "logbook.db"
|
|
9
|
+
|
|
10
|
+
def get_connection():
|
|
11
|
+
DB_DIR.mkdir(exist_ok=True)
|
|
12
|
+
conn = sqlite3.connect(DB_PATH)
|
|
13
|
+
conn.row_factory = sqlite3.Row
|
|
14
|
+
return conn
|
|
15
|
+
|
|
16
|
+
def init_db():
|
|
17
|
+
conn = get_connection()
|
|
18
|
+
conn.executescript("""
|
|
19
|
+
CREATE TABLE IF NOT EXISTS entries (
|
|
20
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
21
|
+
timestamp TEXT NOT NULL,
|
|
22
|
+
category TEXT NOT NULL DEFAULT 'general',
|
|
23
|
+
title TEXT,
|
|
24
|
+
content TEXT NOT NULL,
|
|
25
|
+
mood TEXT,
|
|
26
|
+
tags TEXT
|
|
27
|
+
);
|
|
28
|
+
CREATE INDEX IF NOT EXISTS idx_timestamp ON entries(timestamp);
|
|
29
|
+
CREATE INDEX IF NOT EXISTS idx_category ON entries(category);
|
|
30
|
+
""")
|
|
31
|
+
conn.commit()
|
|
32
|
+
conn.close()
|
|
33
|
+
|
|
34
|
+
def add_entry(content: str, category: str = "general",
|
|
35
|
+
title: str = None, mood: str = None, tags: list = None):
|
|
36
|
+
conn = get_connection()
|
|
37
|
+
timestamp = datetime.now().isoformat(sep=" ", timespec="seconds")
|
|
38
|
+
tags_str = ",".join(tags) if tags else None
|
|
39
|
+
conn.execute(
|
|
40
|
+
"INSERT INTO entries (timestamp, category, title, content, mood, tags) "
|
|
41
|
+
"VALUES (?, ?, ?, ?, ?, ?)",
|
|
42
|
+
(timestamp, category, title, content, mood, tags_str)
|
|
43
|
+
)
|
|
44
|
+
conn.commit()
|
|
45
|
+
conn.close()
|
|
46
|
+
|
|
47
|
+
def get_entries(limit: int = 20, category: str = None,
|
|
48
|
+
search: str = None, date: str = None):
|
|
49
|
+
conn = get_connection()
|
|
50
|
+
query = "SELECT * FROM entries WHERE 1=1"
|
|
51
|
+
params = []
|
|
52
|
+
if category:
|
|
53
|
+
query += " AND category = ?"
|
|
54
|
+
params.append(category)
|
|
55
|
+
if search:
|
|
56
|
+
query += " AND (content LIKE ? OR title LIKE ? OR tags LIKE ?)"
|
|
57
|
+
params += [f"%{search}%", f"%{search}%", f"%{search}%"]
|
|
58
|
+
if date:
|
|
59
|
+
query += " AND timestamp LIKE ?"
|
|
60
|
+
params.append(f"{date}%")
|
|
61
|
+
query += " ORDER BY timestamp DESC LIMIT ?"
|
|
62
|
+
params.append(limit)
|
|
63
|
+
rows = conn.execute(query, params).fetchall()
|
|
64
|
+
conn.close()
|
|
65
|
+
return rows
|
|
66
|
+
|
|
67
|
+
def get_stats():
|
|
68
|
+
conn = get_connection()
|
|
69
|
+
stats = {}
|
|
70
|
+
stats["total"] = conn.execute("SELECT COUNT(*) FROM entries").fetchone()[0]
|
|
71
|
+
stats["by_category"] = conn.execute(
|
|
72
|
+
"SELECT category, COUNT(*) as n FROM entries GROUP BY category"
|
|
73
|
+
).fetchall()
|
|
74
|
+
stats["first_entry"] = conn.execute(
|
|
75
|
+
"SELECT timestamp FROM entries ORDER BY timestamp ASC LIMIT 1"
|
|
76
|
+
).fetchone()
|
|
77
|
+
conn.close()
|
|
78
|
+
return stats
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# chronicle/export.py
|
|
2
|
+
from datetime import datetime
|
|
3
|
+
import json
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
|
|
6
|
+
def export_markdown(entries, filename: str = "chronicle_export") -> str:
|
|
7
|
+
"""Export all entries as a Markdown document — book-ready format."""
|
|
8
|
+
lines = [
|
|
9
|
+
"# Chronicle — Founder's Logbook\n",
|
|
10
|
+
f"*Exported: {datetime.now().strftime('%B %d, %Y at %H:%M')}*\n",
|
|
11
|
+
"---\n"
|
|
12
|
+
]
|
|
13
|
+
current_date = None
|
|
14
|
+
for e in reversed(list(entries)): # Chronological order for export
|
|
15
|
+
entry_date = e["timestamp"][:10]
|
|
16
|
+
if entry_date != current_date:
|
|
17
|
+
current_date = entry_date
|
|
18
|
+
lines.append(f"\n## {entry_date}\n")
|
|
19
|
+
if e["title"]:
|
|
20
|
+
lines.append(f"### {e['title']}\n")
|
|
21
|
+
lines.append(f"**[{e['category'].upper()}]** — *{e['timestamp'][11:]}*\n")
|
|
22
|
+
if e["mood"]:
|
|
23
|
+
lines.append(f"*Mood: {e['mood']}*\n")
|
|
24
|
+
lines.append(f"\n{e['content']}\n")
|
|
25
|
+
if e["tags"]:
|
|
26
|
+
lines.append(f"\n*Tags: {e['tags']}*\n")
|
|
27
|
+
lines.append("\n---\n")
|
|
28
|
+
|
|
29
|
+
path = Path(f"{filename}.md")
|
|
30
|
+
path.write_text("\n".join(lines), encoding="utf-8")
|
|
31
|
+
return str(path)
|
|
32
|
+
|
|
33
|
+
def export_json(entries, filename: str = "chronicle_export") -> str:
|
|
34
|
+
"""Export as structured JSON."""
|
|
35
|
+
data = [dict(e) for e in entries]
|
|
36
|
+
path = Path(f"{filename}.json")
|
|
37
|
+
path.write_text(json.dumps(data, indent=2, ensure_ascii=False), encoding="utf-8")
|
|
38
|
+
return str(path)
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
# chronicle/main.py
|
|
2
|
+
import typer
|
|
3
|
+
from rich.console import Console
|
|
4
|
+
from rich.table import Table
|
|
5
|
+
from rich.panel import Panel
|
|
6
|
+
from rich.text import Text
|
|
7
|
+
from typing import Optional, List
|
|
8
|
+
from chronicle.db import init_db, add_entry, get_entries, get_stats
|
|
9
|
+
from chronicle.export import export_markdown, export_json
|
|
10
|
+
|
|
11
|
+
app = typer.Typer(
|
|
12
|
+
name="chronicle",
|
|
13
|
+
help="📖 Chronicle — Log your founder journey, one entry at a time.",
|
|
14
|
+
add_completion=False
|
|
15
|
+
)
|
|
16
|
+
console = Console()
|
|
17
|
+
|
|
18
|
+
CATEGORIES = ["idea", "build", "learn", "fail", "win", "research", "general"]
|
|
19
|
+
MOODS = ["🔥 fire", "😤 grind", "😌 calm", "😩 stuck", "🎯 focused", "🤔 thinking"]
|
|
20
|
+
|
|
21
|
+
@app.callback(invoke_without_command=True)
|
|
22
|
+
def startup(ctx: typer.Context):
|
|
23
|
+
init_db()
|
|
24
|
+
if ctx.invoked_subcommand is None:
|
|
25
|
+
console.print(Panel(
|
|
26
|
+
"[bold cyan]Chronicle[/bold cyan] — Your founder's logbook\n"
|
|
27
|
+
"[dim]Run [bold]chronicle --help[/bold] to see all commands[/dim]",
|
|
28
|
+
border_style="cyan"
|
|
29
|
+
))
|
|
30
|
+
|
|
31
|
+
@app.command("log")
|
|
32
|
+
def log_entry(
|
|
33
|
+
content: str = typer.Argument(..., help="Your log entry"),
|
|
34
|
+
category: str = typer.Option("general", "-c", "--category",
|
|
35
|
+
help=f"Category: {', '.join(CATEGORIES)}"),
|
|
36
|
+
title: Optional[str] = typer.Option(None, "-t", "--title", help="Optional title"),
|
|
37
|
+
mood: Optional[str] = typer.Option(None, "-m", "--mood", help="Your mood/energy"),
|
|
38
|
+
tags: Optional[List[str]] = typer.Option(None, "--tag", help="Add tags (repeat for multiple)")
|
|
39
|
+
):
|
|
40
|
+
"""📝 Log a new entry."""
|
|
41
|
+
add_entry(content, category=category, title=title, mood=mood, tags=tags)
|
|
42
|
+
console.print(f"[green]✓[/green] Entry logged under [bold]{category}[/bold]")
|
|
43
|
+
|
|
44
|
+
@app.command("idea")
|
|
45
|
+
def log_idea(content: str = typer.Argument(..., help="Your idea")):
|
|
46
|
+
"""💡 Quick-log an idea."""
|
|
47
|
+
add_entry(content, category="idea")
|
|
48
|
+
console.print("[yellow]💡[/yellow] Idea captured.")
|
|
49
|
+
|
|
50
|
+
@app.command("win")
|
|
51
|
+
def log_win(content: str = typer.Argument(..., help="What did you achieve?")):
|
|
52
|
+
"""🏆 Log a win, no matter how small."""
|
|
53
|
+
add_entry(content, category="win")
|
|
54
|
+
console.print("[bold green]🏆 WIN logged. Keep going.[/bold green]")
|
|
55
|
+
|
|
56
|
+
@app.command("fail")
|
|
57
|
+
def log_fail(content: str = typer.Argument(..., help="What went wrong?")):
|
|
58
|
+
"""📉 Log a failure or lesson learned."""
|
|
59
|
+
add_entry(content, category="fail")
|
|
60
|
+
console.print("[dim red]📉 Failure logged. This is data, not defeat.[/dim red]")
|
|
61
|
+
|
|
62
|
+
@app.command("show")
|
|
63
|
+
def show_entries(
|
|
64
|
+
limit: int = typer.Option(10, "-n", "--limit", help="Number of entries to show"),
|
|
65
|
+
category: Optional[str] = typer.Option(None, "-c", "--category"),
|
|
66
|
+
search: Optional[str] = typer.Option(None, "-s", "--search"),
|
|
67
|
+
date: Optional[str] = typer.Option(None, "-d", "--date", help="Filter by date: YYYY-MM-DD")
|
|
68
|
+
):
|
|
69
|
+
"""📋 Show your log entries."""
|
|
70
|
+
entries = get_entries(limit=limit, category=category, search=search, date=date)
|
|
71
|
+
if not entries:
|
|
72
|
+
console.print("[dim]No entries found.[/dim]")
|
|
73
|
+
return
|
|
74
|
+
|
|
75
|
+
table = Table(show_header=True, header_style="bold cyan",
|
|
76
|
+
border_style="dim", expand=True)
|
|
77
|
+
table.add_column("ID", style="dim", width=4)
|
|
78
|
+
table.add_column("Timestamp", style="dim", width=20)
|
|
79
|
+
table.add_column("Category", width=10)
|
|
80
|
+
table.add_column("Title / Content")
|
|
81
|
+
table.add_column("Tags", style="dim", width=20)
|
|
82
|
+
|
|
83
|
+
CATEGORY_COLORS = {
|
|
84
|
+
"idea": "yellow", "build": "cyan", "learn": "blue",
|
|
85
|
+
"fail": "red", "win": "green", "research": "magenta", "general": "white"
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
for e in entries:
|
|
89
|
+
color = CATEGORY_COLORS.get(e["category"], "white")
|
|
90
|
+
display = e["title"] if e["title"] else (
|
|
91
|
+
e["content"][:70] + "..." if len(e["content"]) > 70 else e["content"]
|
|
92
|
+
)
|
|
93
|
+
table.add_row(
|
|
94
|
+
str(e["id"]),
|
|
95
|
+
e["timestamp"],
|
|
96
|
+
f"[{color}]{e['category']}[/{color}]",
|
|
97
|
+
display,
|
|
98
|
+
e["tags"] or ""
|
|
99
|
+
)
|
|
100
|
+
console.print(table)
|
|
101
|
+
|
|
102
|
+
@app.command("stats")
|
|
103
|
+
def show_stats():
|
|
104
|
+
"""📊 Show your journey statistics."""
|
|
105
|
+
stats = get_stats()
|
|
106
|
+
first = stats["first_entry"]["timestamp"] if stats["first_entry"] else "N/A"
|
|
107
|
+
console.print(Panel(
|
|
108
|
+
f"[bold]Total Entries:[/bold] {stats['total']}\n"
|
|
109
|
+
f"[bold]Journey Started:[/bold] {first}",
|
|
110
|
+
title="[bold cyan]Chronicle Stats[/bold cyan]",
|
|
111
|
+
border_style="cyan"
|
|
112
|
+
))
|
|
113
|
+
if stats["by_category"]:
|
|
114
|
+
table = Table(show_header=True, header_style="bold", border_style="dim")
|
|
115
|
+
table.add_column("Category")
|
|
116
|
+
table.add_column("Entries", justify="right")
|
|
117
|
+
for row in stats["by_category"]:
|
|
118
|
+
table.add_row(row["category"], str(row["n"]))
|
|
119
|
+
console.print(table)
|
|
120
|
+
|
|
121
|
+
@app.command("export")
|
|
122
|
+
def export_entries(
|
|
123
|
+
format: str = typer.Option("markdown", "-f", "--format",
|
|
124
|
+
help="Export format: markdown | json"),
|
|
125
|
+
output: str = typer.Option("chronicle_export", "-o", "--output",
|
|
126
|
+
help="Output filename (no extension)")
|
|
127
|
+
):
|
|
128
|
+
"""📤 Export your log to Markdown or JSON."""
|
|
129
|
+
entries = get_entries(limit=99999)
|
|
130
|
+
if format == "markdown":
|
|
131
|
+
path = export_markdown(entries, output)
|
|
132
|
+
elif format == "json":
|
|
133
|
+
path = export_json(entries, output)
|
|
134
|
+
else:
|
|
135
|
+
console.print("[red]Unknown format. Use 'markdown' or 'json'[/red]")
|
|
136
|
+
return
|
|
137
|
+
console.print(f"[green]✓[/green] Exported to [bold]{path}[/bold]")
|
|
138
|
+
|
|
139
|
+
if __name__ == "__main__":
|
|
140
|
+
app()
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: chronicle-log
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: A CLI logbook for founders, builders, and creators.
|
|
5
|
+
Author-email: Your Name <you@example.com>
|
|
6
|
+
License: MIT
|
|
7
|
+
Requires-Python: >=3.9
|
|
8
|
+
Description-Content-Type: text/markdown
|
|
9
|
+
Requires-Dist: typer>=0.9
|
|
10
|
+
Requires-Dist: rich>=13
|
|
11
|
+
|
|
12
|
+
# Chronicle 📖
|
|
13
|
+
|
|
14
|
+
> Log the journey. Tell the story.
|
|
15
|
+
|
|
16
|
+
Chronicle is a minimal CLI logbook built for founders, builders, and solo makers.
|
|
17
|
+
Log your ideas, wins, failures, and progress — with accurate timestamps — as you build.
|
|
18
|
+
|
|
19
|
+
## Install
|
|
20
|
+
|
|
21
|
+
pip install chronicle-log
|
|
22
|
+
|
|
23
|
+
## Usage
|
|
24
|
+
|
|
25
|
+
chronicle log "Had a breakthrough on the auth flow" -c build
|
|
26
|
+
chronicle idea "What if we added voice logging via whisper?"
|
|
27
|
+
chronicle win "First real user signed up"
|
|
28
|
+
chronicle fail "Shipped a bug that deleted sessions. Lesson: always backup."
|
|
29
|
+
chronicle show --limit 20
|
|
30
|
+
chronicle stats
|
|
31
|
+
chronicle export --format markdown
|
|
32
|
+
|
|
33
|
+
## Why?
|
|
34
|
+
|
|
35
|
+
Built by a founder, for founders. Every entry you make today
|
|
36
|
+
becomes the raw material of the story you'll tell tomorrow.
|
|
37
|
+
|
|
38
|
+
## License
|
|
39
|
+
|
|
40
|
+
MIT — free forever.
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
README.md
|
|
2
|
+
pyproject.toml
|
|
3
|
+
chronicle/__init__.py
|
|
4
|
+
chronicle/db.py
|
|
5
|
+
chronicle/export.py
|
|
6
|
+
chronicle/main.py
|
|
7
|
+
chronicle_log.egg-info/PKG-INFO
|
|
8
|
+
chronicle_log.egg-info/SOURCES.txt
|
|
9
|
+
chronicle_log.egg-info/dependency_links.txt
|
|
10
|
+
chronicle_log.egg-info/entry_points.txt
|
|
11
|
+
chronicle_log.egg-info/requires.txt
|
|
12
|
+
chronicle_log.egg-info/top_level.txt
|
|
13
|
+
tests/test_db.py
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
chronicle
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# pyproject.toml
|
|
2
|
+
[build-system]
|
|
3
|
+
requires = ["setuptools>=68", "wheel"]
|
|
4
|
+
build-backend = "setuptools.build_meta"
|
|
5
|
+
|
|
6
|
+
[project]
|
|
7
|
+
name = "chronicle-log"
|
|
8
|
+
version = "0.1.0"
|
|
9
|
+
description = "A CLI logbook for founders, builders, and creators."
|
|
10
|
+
authors = [{ name = "Your Name", email = "you@example.com" }]
|
|
11
|
+
license = { text = "MIT" }
|
|
12
|
+
requires-python = ">=3.9"
|
|
13
|
+
dependencies = ["typer>=0.9", "rich>=13"]
|
|
14
|
+
readme = "README.md"
|
|
15
|
+
|
|
16
|
+
[project.scripts]
|
|
17
|
+
chronicle = "chronicle.main:app"
|
|
18
|
+
|
|
19
|
+
[tool.setuptools.packages.find]
|
|
20
|
+
where = ["."]
|
|
21
|
+
include = ["chronicle*"]
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# tests/test_db.py
|
|
2
|
+
import os, pytest
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
|
|
5
|
+
# Override DB path for tests
|
|
6
|
+
import chronicle.db as db
|
|
7
|
+
db.DB_PATH = Path("/tmp/test_chronicle.db")
|
|
8
|
+
|
|
9
|
+
from chronicle.db import init_db, add_entry, get_entries
|
|
10
|
+
|
|
11
|
+
def setup_function():
|
|
12
|
+
if db.DB_PATH.exists():
|
|
13
|
+
db.DB_PATH.unlink()
|
|
14
|
+
init_db()
|
|
15
|
+
|
|
16
|
+
def test_add_and_retrieve():
|
|
17
|
+
add_entry("Test entry", category="build")
|
|
18
|
+
entries = get_entries(limit=1)
|
|
19
|
+
assert len(entries) == 1
|
|
20
|
+
assert entries[0]["content"] == "Test entry"
|
|
21
|
+
assert entries[0]["category"] == "build"
|
|
22
|
+
|
|
23
|
+
def test_search():
|
|
24
|
+
add_entry("Implemented OAuth", category="build", tags=["auth", "security"])
|
|
25
|
+
entries = get_entries(search="OAuth")
|
|
26
|
+
assert any("OAuth" in e["content"] for e in entries)
|