Created
June 18, 2025 12:18
-
-
Save francisrstokes/05fe36ba7469d67e94b8804c2170ee52 to your computer and use it in GitHub Desktop.
Note taking and searching CLI app using fzf
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env python | |
import subprocess | |
import argparse | |
from datetime import datetime | |
from pathlib import Path | |
import os | |
# Configurable stuff, should probably use environment variables | |
editor = "code" | |
notes_dir = ".note_fzf" | |
user_home = Path.home() | |
notes_path = Path(user_home, notes_dir) | |
# Argument parser setup | |
parser = argparse.ArgumentParser() | |
parser.add_argument("-m", "--message", type=str, help="Add [MESSAGE] to todays note", required=False) | |
parser.add_argument("-t", "--tag", type=str, help="Add [TAG] to todays note", required=False) | |
fzf_group = parser.add_mutually_exclusive_group() | |
fzf_group.add_argument("-l", "--list", action="store_true", help="List and search for a note to open (by day)", required=False) | |
fzf_group.add_argument("-s", "--search", action="store_true", help="List and search for a note to open (by content)", required=False) | |
# Utils | |
month_names = ["jan", "feb", "mar", "apr", "may", "jun", "jul", "aug", "sep", "oct", "nov", "dec"] | |
def todays_note_dir(): | |
now = datetime.now() | |
return Path(notes_path, f"{now.year}-{month_names[now.month-1]}-{now.day}") | |
def make_today(): | |
note_dir = todays_note_dir() | |
subprocess.getoutput(f"mkdir -p {note_dir}") | |
subprocess.getoutput(f"touch {note_dir}/note.md {note_dir}/tags") | |
return note_dir | |
def write_to_days_note(note_dir, message): | |
with open(f"{note_dir}/note.md", "a") as f: | |
f.write(f"{message}\n") | |
def tag_days_note(note_dir, tag): | |
with open(f"{note_dir}/tags", "a") as f: | |
f.write(f"{tag}\n") | |
def run_command(command): | |
return subprocess.getoutput(command).strip() | |
# Any time the command is run, ensure the note directory exists | |
if not notes_path.exists(): | |
subprocess.getoutput(f"mkdir -p {notes_path}") | |
# Parse args and figure out what mode we're running in | |
args = parser.parse_args() | |
is_fzf_operation = args.list or args.search | |
has_content = args.message is not None or args.tag is not None | |
if is_fzf_operation: | |
selection = "" | |
if args.list: | |
selection = run_command(f"find {notes_path} -mindepth 1 -maxdepth 1 -type d | fzf --layout reverse --preview 'bat {{}}/note.md'") | |
elif args.search: | |
command = f"rg --with-filename --line-number '' {notes_path}" | |
command += " | fzf --delimiter ':' --preview 'bat --style=numbers --color=always $(dirname {1})/note.md --line-range {2}:'" | |
command += " | cut -d':' -f1" | |
selection = run_command(command) | |
if selection == "": | |
exit(1) | |
if not has_content: | |
open_path = selection if args.search else f"{selection}/note.md" | |
os.execvp(editor, [editor, open_path]) | |
else: | |
if args.message is not None: | |
write_to_days_note(make_today(), args.tag) | |
if args.tag is not None: | |
tag_days_note(make_today(), args.message) | |
elif has_content: | |
if args.message is not None: | |
write_to_days_note(make_today(), args.message) | |
if args.tag is not None: | |
tag_days_note(make_today(), args.tag) | |
else: | |
os.execvp(editor, [editor, f"{make_today()}/note.md"]) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment