Skip to content

Instantly share code, notes, and snippets.

@muellerzr
Last active May 6, 2025 13:58
Show Gist options
  • Save muellerzr/4c805515e0d66ee95bca87b4de3aebe2 to your computer and use it in GitHub Desktop.
Save muellerzr/4c805515e0d66ee95bca87b4de3aebe2 to your computer and use it in GitHub Desktop.
Quick journal entry maker in Python
import json
import os
from datetime import datetime
import argparse
from typing import Dict, List, Optional
"""
Quick Journal is a simple CLI journal that allows you to write, search, and export journal entries.
Specialized for ML experiment tracking with predefined fields.
To use:
python journal.py write -c "experiment_name"
python journal.py list -c "experiment_name"
python journal.py search -q "good results" -c "experiment_name"
python journal.py export -f "txt"
Note: Press Ctrl+D to save your entry
"""
class Journal:
def __init__(self):
self.journal_dir = os.path.join(os.path.expanduser("~"), ".quick-journal")
self.ensure_journal_directory()
self.experiment_fields = [
"wandb_run",
"results",
"what_went_right",
"what_went_wrong",
"next_steps"
]
def ensure_journal_directory(self) -> None:
if not os.path.exists(self.journal_dir):
os.makedirs(self.journal_dir)
def get_current_file(self) -> str:
current_month = datetime.now().strftime("%Y-%m")
return os.path.join(self.journal_dir, f"{current_month}.json")
def load_entries(self, filepath: str) -> List[Dict]:
if os.path.exists(filepath):
with open(filepath, 'r') as f:
return json.load(f)
return []
def save_entries(self, filepath: str, entries: List[Dict]) -> None:
with open(filepath, 'w') as f:
json.dump(entries, f, indent=2)
def write_entry(self, content: Dict[str, str], category: str) -> None:
filepath = self.get_current_file()
now = datetime.now()
date_str = now.strftime("%Y-%m-%d")
time_str = now.strftime("%H:%M")
entries = self.load_entries(filepath)
entry = {
"timestamp": f"{date_str} {time_str}",
"date": date_str,
"time": time_str,
"category": category,
**content
}
entries.append(entry)
self.save_entries(filepath, entries)
print(f"\nEntry saved successfully!")
print(f"Date: {date_str}")
print(f"Time: {time_str}")
print(f"Experiment: {category}")
def get_all_entries(self) -> List[Dict]:
all_entries = []
for filename in os.listdir(self.journal_dir):
if filename.endswith('.json'):
filepath = os.path.join(self.journal_dir, filename)
all_entries.extend(self.load_entries(filepath))
return sorted(all_entries, key=lambda x: x['timestamp'], reverse=True)
def search_entries(self, query: Optional[str] = None, category: Optional[str] = None) -> List[Dict]:
entries = self.get_all_entries()
if query:
entries = [e for e in entries if query.lower() in str(e).lower()]
if category:
entries = [e for e in entries if e['category'] == category]
return entries
def export_journal(self, format_type: str = "txt") -> None:
entries = self.get_all_entries()
now = datetime.now()
export_filename = f"journal-export-{now.strftime('%Y-%m-%d')}"
if format_type == "json":
filepath = os.path.join(self.journal_dir, f"{export_filename}.json")
self.save_entries(filepath, entries)
else:
filepath = os.path.join(self.journal_dir, f"{export_filename}.txt")
with open(filepath, 'w') as f:
for entry in entries:
f.write(f"Date: {entry['date']}\n")
f.write(f"Time: {entry['time']}\n")
f.write(f"Experiment: {entry['category']}\n")
f.write(f"W&B Run: {entry.get('wandb_run', 'N/A')}\n")
f.write(f"\nResults:\n{entry.get('results', 'N/A')}\n")
f.write(f"\nWhat Went Right:\n{entry.get('what_went_right', 'N/A')}\n")
f.write(f"\nWhat Went Wrong:\n{entry.get('what_went_wrong', 'N/A')}\n")
f.write(f"\nNext Steps:\n{entry.get('next_steps', 'N/A')}\n")
f.write("\n---\n\n")
print(f"Exported journal to {filepath}")
def display_entries(entries: List[Dict]) -> None:
if not entries:
print("No entries found.")
return
for entry in entries:
print("\n" + "="*50)
print(f"Date: {entry['date']}")
print(f"Time: {entry['time']}")
print(f"Experiment: {entry['category']}")
print("-"*50)
print(f"W&B Run: {entry.get('wandb_run', 'N/A')}")
print(f"\nResults:\n{entry.get('results', 'N/A')}")
print(f"\nWhat Went Right:\n{entry.get('what_went_right', 'N/A')}")
print(f"\nWhat Went Wrong:\n{entry.get('what_went_wrong', 'N/A')}")
print(f"\nNext Steps:\n{entry.get('next_steps', 'N/A')}")
print("="*50)
def prompt_experiment_entry() -> Dict[str, str]:
content = {}
print("\nEnter experiment details:")
# W&B Run
print("\nW&B Run Name (press Enter when done):")
wandb_run = input("> ").strip()
content['wandb_run'] = wandb_run
# Results
print("\nResults (press Enter twice when done):")
lines = []
while True:
line = input("> ")
if line.strip() == "" and (not lines or lines[-1].strip() == ""):
if lines:
lines.pop() # Remove the last empty line
break
lines.append(line)
content['results'] = "\n".join(lines)
# What Went Right
print("\nWhat Went Right (press Enter twice when done):")
lines = []
while True:
line = input("> ")
if line.strip() == "" and (not lines or lines[-1].strip() == ""):
if lines:
lines.pop() # Remove the last empty line
break
lines.append(line)
content['what_went_right'] = "\n".join(lines)
# What Went Wrong
print("\nWhat Went Wrong (press Enter twice when done):")
lines = []
while True:
line = input("> ")
if line.strip() == "" and (not lines or lines[-1].strip() == ""):
if lines:
lines.pop() # Remove the last empty line
break
lines.append(line)
content['what_went_wrong'] = "\n".join(lines)
# Next Steps
print("\nNext Steps (press Enter twice when done):")
lines = []
while True:
line = input("> ")
if line.strip() == "" and (not lines or lines[-1].strip() == ""):
if lines:
lines.pop() # Remove the last empty line
break
lines.append(line)
content['next_steps'] = "\n".join(lines)
return content
def main():
parser = argparse.ArgumentParser(description="Quick Journal - A simple CLI journal")
subparsers = parser.add_subparsers(dest="command", help="Commands")
# Write command
write_parser = subparsers.add_parser("write", help="Write a new journal entry")
write_parser.add_argument("-c", "--category", required=True, help="Experiment name")
# List command
list_parser = subparsers.add_parser("list", help="List journal entries")
list_parser.add_argument("-c", "--category", help="Filter by experiment name")
# Search command
search_parser = subparsers.add_parser("search", help="Search journal entries")
search_parser.add_argument("-q", "--query", help="Search text")
search_parser.add_argument("-c", "--category", help="Filter by experiment name")
# Export command
export_parser = subparsers.add_parser("export", help="Export journal entries")
export_parser.add_argument("-f", "--format", choices=["txt", "json"], default="txt", help="Export format")
args = parser.parse_args()
journal = Journal()
if args.command == "write":
now = datetime.now()
print(f"\nQuick Journal - Experiment: {args.category}")
print(f"Date: {now.strftime('%Y-%m-%d')}")
print(f"Time: {now.strftime('%H:%M')}")
content = prompt_experiment_entry()
journal.write_entry(content, args.category)
elif args.command == "list":
entries = journal.search_entries(category=args.category)
display_entries(entries)
elif args.command == "search":
entries = journal.search_entries(args.query, args.category)
display_entries(entries)
elif args.command == "export":
journal.export_journal(args.format)
else:
parser.print_help()
if __name__ == "__main__":
main()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment