pynotes-cli 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.
- pynotes_cli-0.1.0/PKG-INFO +8 -0
- pynotes_cli-0.1.0/README.md +84 -0
- pynotes_cli-0.1.0/pynotes/__init__.py +0 -0
- pynotes_cli-0.1.0/pynotes/db.py +50 -0
- pynotes_cli-0.1.0/pynotes/main.py +57 -0
- pynotes_cli-0.1.0/pynotes/prompts.py +59 -0
- pynotes_cli-0.1.0/pynotes/ui.py +35 -0
- pynotes_cli-0.1.0/pynotes_cli.egg-info/PKG-INFO +8 -0
- pynotes_cli-0.1.0/pynotes_cli.egg-info/SOURCES.txt +13 -0
- pynotes_cli-0.1.0/pynotes_cli.egg-info/dependency_links.txt +1 -0
- pynotes_cli-0.1.0/pynotes_cli.egg-info/entry_points.txt +2 -0
- pynotes_cli-0.1.0/pynotes_cli.egg-info/requires.txt +3 -0
- pynotes_cli-0.1.0/pynotes_cli.egg-info/top_level.txt +1 -0
- pynotes_cli-0.1.0/pyproject.toml +17 -0
- pynotes_cli-0.1.0/setup.cfg +4 -0
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
# Pynotes
|
|
2
|
+
|
|
3
|
+
Pynotes is a lightning-fast terminal-based Markdown personal journal and notes tool. It allows you to seamlessly create, read, list, and filter your notes directly from the command line, while storing everything locally in a lightweight SQLite database.
|
|
4
|
+
|
|
5
|
+
## What it Does
|
|
6
|
+
|
|
7
|
+
- **Create & Edit**: Interactively create notes with titles, tags, and Markdown content. Edit existing notes smoothly.
|
|
8
|
+
- **List & Filter**: View all your notes in a beautifully formatted table or filter them down by specific tags.
|
|
9
|
+
- **View Notes**: Render your notes with full Markdown syntax highlighting right in your terminal.
|
|
10
|
+
- **Local Storage**: Keeps all your data secure and accessible on your local machine (`~/.pynotes.db`).
|
|
11
|
+
|
|
12
|
+
## Project Structure
|
|
13
|
+
|
|
14
|
+
```text
|
|
15
|
+
pynotes/
|
|
16
|
+
├── pyproject.toml # Project metadata and dependencies configuration
|
|
17
|
+
├── README.md # Project documentation and instructions
|
|
18
|
+
└── pynotes/ # Main application package
|
|
19
|
+
├── __init__.py # Marks the directory as a Python package
|
|
20
|
+
├── main.py # Entry point defining the CLI commands using Typer
|
|
21
|
+
├── db.py # Handles SQLite database connection and queries
|
|
22
|
+
├── ui.py # Manages terminal output rendering using Rich
|
|
23
|
+
└── prompts.py # Handles interactive user inputs using Questionary
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## Tech Stack
|
|
27
|
+
|
|
28
|
+
- **[Python 3.10+](https://www.python.org/)**
|
|
29
|
+
- **[Typer](https://typer.tiangolo.com/)**: For building the powerful command-line interface.
|
|
30
|
+
- **[Rich](https://rich.readthedocs.io/)**: For beautiful terminal formatting, tables, and Markdown rendering.
|
|
31
|
+
- **[Questionary](https://questionary.readthedocs.io/)**: For interactive and user-friendly CLI prompts.
|
|
32
|
+
- **[SQLite3](https://docs.python.org/3/library/sqlite3.html)**: For local database storage (built-in).
|
|
33
|
+
|
|
34
|
+
## Instructions to Run
|
|
35
|
+
|
|
36
|
+
### 1. Installation
|
|
37
|
+
|
|
38
|
+
It is recommended to install `pynotes` in editable mode so that you can easily access the CLI from anywhere in your terminal.
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
# Clone the repository and navigate to the directory
|
|
42
|
+
cd path/to/pynotes
|
|
43
|
+
|
|
44
|
+
# Install the package and its dependencies
|
|
45
|
+
pip install -e .
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
### 2. Usage
|
|
49
|
+
|
|
50
|
+
Once installed, the `pynotes` command will be globally available in your terminal environment.
|
|
51
|
+
|
|
52
|
+
**Initialize the Database:**
|
|
53
|
+
Initialize your local database at `~/.pynotes.db`.
|
|
54
|
+
```bash
|
|
55
|
+
pynotes init
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
**Create a New Note:**
|
|
59
|
+
```bash
|
|
60
|
+
pynotes new
|
|
61
|
+
```
|
|
62
|
+
Follow the interactive prompts to add your title, tags, and Markdown content. Press `Alt+Enter` (or `Esc` then `Enter` on Mac) to save the content.
|
|
63
|
+
|
|
64
|
+
**List All Notes:**
|
|
65
|
+
```bash
|
|
66
|
+
pynotes list
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
**Filter Notes by Tag:**
|
|
70
|
+
```bash
|
|
71
|
+
pynotes list --tag <your-tag>
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
**View a Specific Note:**
|
|
75
|
+
View the full rendered Markdown of a note by providing its ID.
|
|
76
|
+
```bash
|
|
77
|
+
pynotes view <note_id>
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
**Edit a Note:**
|
|
81
|
+
Edit the title, tags, or content of an existing note.
|
|
82
|
+
```bash
|
|
83
|
+
pynotes edit <note_id>
|
|
84
|
+
```
|
|
File without changes
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import sqlite3
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
|
|
4
|
+
DB_PATH = Path.home() / ".pynotes.db"
|
|
5
|
+
|
|
6
|
+
def get_connection():
|
|
7
|
+
return sqlite3.connect(DB_PATH)
|
|
8
|
+
|
|
9
|
+
def init_db():
|
|
10
|
+
with get_connection() as conn:
|
|
11
|
+
conn.execute("""
|
|
12
|
+
CREATE TABLE IF NOT EXISTS notes (
|
|
13
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
14
|
+
title TEXT NOT NULL,
|
|
15
|
+
tags TEXT NOT NULL,
|
|
16
|
+
content TEXT NOT NULL,
|
|
17
|
+
created_at DATE DEFAULT CURRENT_DATE
|
|
18
|
+
)
|
|
19
|
+
""")
|
|
20
|
+
|
|
21
|
+
def add_note(title: str, tags: str, content: str):
|
|
22
|
+
with get_connection() as conn:
|
|
23
|
+
conn.execute(
|
|
24
|
+
"INSERT INTO notes (title, tags, content) VALUES (?, ?, ?)",
|
|
25
|
+
(title, tags, content)
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
def get_notes(tag: str = None):
|
|
29
|
+
with get_connection() as conn:
|
|
30
|
+
if tag:
|
|
31
|
+
# Simple wildcard search for tags
|
|
32
|
+
return conn.execute(
|
|
33
|
+
"SELECT id, title, tags, created_at FROM notes WHERE tags LIKE ?",
|
|
34
|
+
(f"%{tag}%",)
|
|
35
|
+
).fetchall()
|
|
36
|
+
return conn.execute("SELECT id, title, tags, created_at FROM notes").fetchall()
|
|
37
|
+
|
|
38
|
+
def get_note(note_id: int):
|
|
39
|
+
with get_connection() as conn:
|
|
40
|
+
return conn.execute(
|
|
41
|
+
"SELECT title, tags, content FROM notes WHERE id = ?",
|
|
42
|
+
(note_id,)
|
|
43
|
+
).fetchone()
|
|
44
|
+
|
|
45
|
+
def update_note(note_id: int, title: str, tags: str, content: str):
|
|
46
|
+
with get_connection() as conn:
|
|
47
|
+
conn.execute(
|
|
48
|
+
"UPDATE notes SET title = ?, tags = ?, content = ? WHERE id = ?",
|
|
49
|
+
(title, tags, content, note_id)
|
|
50
|
+
)
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import typer
|
|
2
|
+
from pynotes import db, ui, prompts
|
|
3
|
+
|
|
4
|
+
app = typer.Typer(help="A lightning-fast terminal Markdown journal.")
|
|
5
|
+
|
|
6
|
+
@app.callback()
|
|
7
|
+
def main_callback():
|
|
8
|
+
db.init_db()
|
|
9
|
+
|
|
10
|
+
@app.command()
|
|
11
|
+
def init():
|
|
12
|
+
ui.display_success("Knowledge base initialized at ~/.pynotes.db")
|
|
13
|
+
|
|
14
|
+
@app.command()
|
|
15
|
+
def new():
|
|
16
|
+
note_data = prompts.prompt_new_note()
|
|
17
|
+
|
|
18
|
+
if note_data and note_data["title"]:
|
|
19
|
+
db.add_note(note_data["title"], note_data["tags"], note_data["content"])
|
|
20
|
+
ui.display_success("Note saved successfully!")
|
|
21
|
+
else:
|
|
22
|
+
ui.display_error("Note creation cancelled.")
|
|
23
|
+
|
|
24
|
+
@app.command(name="list")
|
|
25
|
+
def list_notes(tag: str = typer.Option(None, help="Filter notes by tag")):
|
|
26
|
+
#list notes ( and filter tag)
|
|
27
|
+
notes = db.get_notes(tag)
|
|
28
|
+
ui.display_notes_table(notes)
|
|
29
|
+
|
|
30
|
+
@app.command()
|
|
31
|
+
def view(note_id: int):
|
|
32
|
+
note = db.get_note(note_id)
|
|
33
|
+
|
|
34
|
+
#view note w/ id
|
|
35
|
+
if note:
|
|
36
|
+
ui.display_note(title=note[0], content=note[2])
|
|
37
|
+
else:
|
|
38
|
+
ui.display_error(f"Note with ID {note_id} not found.")
|
|
39
|
+
|
|
40
|
+
@app.command()
|
|
41
|
+
def edit(note_id: int):
|
|
42
|
+
note = db.get_note(note_id)
|
|
43
|
+
if not note:
|
|
44
|
+
ui.display_error(f"Note with ID {note_id} not found.")
|
|
45
|
+
return
|
|
46
|
+
|
|
47
|
+
ui.display_success(f"Editing note: {note[0]}")
|
|
48
|
+
note_data = prompts.prompt_edit_note(existing_title=note[0], existing_tags=note[1], existing_content=note[2])
|
|
49
|
+
|
|
50
|
+
if note_data and note_data["title"]:
|
|
51
|
+
db.update_note(note_id, note_data["title"], note_data["tags"], note_data["content"])
|
|
52
|
+
ui.display_success("Note updated successfully!")
|
|
53
|
+
else:
|
|
54
|
+
ui.display_error("Note edit cancelled.")
|
|
55
|
+
|
|
56
|
+
if __name__ == "__main__":
|
|
57
|
+
app()
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import questionary
|
|
2
|
+
|
|
3
|
+
def prompt_new_note():
|
|
4
|
+
title = questionary.text("Enter note title:").ask()
|
|
5
|
+
if not title:
|
|
6
|
+
return None
|
|
7
|
+
|
|
8
|
+
tags = questionary.text("Enter tags (comma separated):").ask()
|
|
9
|
+
|
|
10
|
+
# multiline=True allows hitting Enter for new lines.
|
|
11
|
+
# The user presses Alt+Enter (or Esc then Enter on Mac) to submit.
|
|
12
|
+
content = questionary.text(
|
|
13
|
+
"Content (Markdown supported - Press Alt+Enter to save):",
|
|
14
|
+
multiline=True
|
|
15
|
+
).ask()
|
|
16
|
+
|
|
17
|
+
return {
|
|
18
|
+
"title": title,
|
|
19
|
+
"tags": tags,
|
|
20
|
+
"content": content
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
def prompt_edit_note(existing_title: str, existing_tags: str, existing_content: str):
|
|
24
|
+
title = questionary.text("Edit note title:", default=existing_title).ask()
|
|
25
|
+
if not title:
|
|
26
|
+
return None
|
|
27
|
+
|
|
28
|
+
tags = questionary.text("Edit tags (comma separated):", default=existing_tags).ask()
|
|
29
|
+
|
|
30
|
+
content = questionary.text(
|
|
31
|
+
"Edit Content (Markdown supported - Press Alt+Enter to save):",
|
|
32
|
+
default=existing_content,
|
|
33
|
+
multiline=True
|
|
34
|
+
).ask()
|
|
35
|
+
|
|
36
|
+
return {
|
|
37
|
+
"title": title,
|
|
38
|
+
"tags": tags,
|
|
39
|
+
"content": content
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
def prompt_edit_note(existing_title: str, existing_tags: str, existing_content: str):
|
|
43
|
+
title = questionary.text("Edit note title:", default=existing_title).ask()
|
|
44
|
+
if not title:
|
|
45
|
+
return None
|
|
46
|
+
|
|
47
|
+
tags = questionary.text("Edit tags (comma separated):", default=existing_tags).ask()
|
|
48
|
+
|
|
49
|
+
content = questionary.text(
|
|
50
|
+
"Edit Content (Markdown supported - Press Alt+Enter to save):",
|
|
51
|
+
default=existing_content,
|
|
52
|
+
multiline=True
|
|
53
|
+
).ask()
|
|
54
|
+
|
|
55
|
+
return {
|
|
56
|
+
"title": title,
|
|
57
|
+
"tags": tags,
|
|
58
|
+
"content": content
|
|
59
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
from rich.console import Console
|
|
2
|
+
from rich.table import Table
|
|
3
|
+
from rich.markdown import Markdown
|
|
4
|
+
from rich import box
|
|
5
|
+
|
|
6
|
+
console = Console()
|
|
7
|
+
|
|
8
|
+
def display_notes_table(notes):
|
|
9
|
+
if not notes:
|
|
10
|
+
console.print("[yellow]No notes found.[/yellow]")
|
|
11
|
+
return
|
|
12
|
+
|
|
13
|
+
table = Table(title="Pynotes", box=box.SIMPLE)
|
|
14
|
+
table.add_column("ID", justify="right", style="cyan", no_wrap=True)
|
|
15
|
+
table.add_column("Title", style="magenta")
|
|
16
|
+
table.add_column("Tags", style="green")
|
|
17
|
+
table.add_column("Date", style="dim")
|
|
18
|
+
|
|
19
|
+
for note in notes:
|
|
20
|
+
table.add_row(str(note[0]), note[1], note[2], note[3])
|
|
21
|
+
|
|
22
|
+
console.print(table)
|
|
23
|
+
|
|
24
|
+
def display_note(title: str, content: str):
|
|
25
|
+
console.print(f"\n[magenta]# {title}[/magenta]")
|
|
26
|
+
console.print("-" * 50)
|
|
27
|
+
# Renders the text as actual markdown (bolding, code blocks, etc.)
|
|
28
|
+
console.print(Markdown(content))
|
|
29
|
+
console.print("\n")
|
|
30
|
+
|
|
31
|
+
def display_success(message: str):
|
|
32
|
+
console.print(f"[green]✔ {message}[/green]")
|
|
33
|
+
|
|
34
|
+
def display_error(message: str):
|
|
35
|
+
console.print(f"[red]✖ {message}[/red]")
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
README.md
|
|
2
|
+
pyproject.toml
|
|
3
|
+
pynotes/__init__.py
|
|
4
|
+
pynotes/db.py
|
|
5
|
+
pynotes/main.py
|
|
6
|
+
pynotes/prompts.py
|
|
7
|
+
pynotes/ui.py
|
|
8
|
+
pynotes_cli.egg-info/PKG-INFO
|
|
9
|
+
pynotes_cli.egg-info/SOURCES.txt
|
|
10
|
+
pynotes_cli.egg-info/dependency_links.txt
|
|
11
|
+
pynotes_cli.egg-info/entry_points.txt
|
|
12
|
+
pynotes_cli.egg-info/requires.txt
|
|
13
|
+
pynotes_cli.egg-info/top_level.txt
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
pynotes
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "pynotes-cli"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
description = "A terminal Markdown personal journal and notes tool."
|
|
5
|
+
requires-python = ">=3.9"
|
|
6
|
+
dependencies = [
|
|
7
|
+
"typer>=0.9.0",
|
|
8
|
+
"rich>=13.0.0",
|
|
9
|
+
"questionary>=2.0.0"
|
|
10
|
+
]
|
|
11
|
+
|
|
12
|
+
[project.scripts]
|
|
13
|
+
pynotes = "pynotes.main:app"
|
|
14
|
+
|
|
15
|
+
[build-system]
|
|
16
|
+
requires = ["setuptools>=61.0"]
|
|
17
|
+
build-backend = "setuptools.build_meta"
|