Skip to content

Instantly share code, notes, and snippets.

@francisrstokes
Created June 18, 2025 12:18
Show Gist options
  • Save francisrstokes/05fe36ba7469d67e94b8804c2170ee52 to your computer and use it in GitHub Desktop.
Save francisrstokes/05fe36ba7469d67e94b8804c2170ee52 to your computer and use it in GitHub Desktop.
Note taking and searching CLI app using fzf
#!/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