Last active
May 6, 2025 13:58
-
-
Save muellerzr/4c805515e0d66ee95bca87b4de3aebe2 to your computer and use it in GitHub Desktop.
Quick journal entry maker in Python
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
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