2 min read

Python Showcase Project: CLI Journal

This project demonstrates how to combine multiple Python modules and concepts into a functional Command Line Interface (CLI) tool.

Goal: Create a tool named pyjournal that allows users to add and list journal entries from their terminal.

The Code

Save the following code as pyjournal.py.

import argparse
import json
import logging
import sys
from datetime import datetime
from pathlib import Path
from typing import List, Dict

# 1. Logging Setup
logging.basicConfig(level=logging.INFO, format='%(message)s')

# 2. Class Definition (OOP)
class JournalEntry:
    def __init__(self, title: str, content: str, timestamp: str = None):
        self.title = title
        self.content = content
        self.timestamp = timestamp or datetime.now().strftime("%Y-%m-%d %H:%M:%S")

    def to_dict(self) -> Dict[str, str]:
        return {
            "title": self.title,
            "content": self.content,
            "timestamp": self.timestamp
        }

    @classmethod
    def from_dict(cls, data: Dict[str, str]):
        return cls(data["title"], data["content"], data["timestamp"])

# 3. File Management Class
class JournalManager:
    def __init__(self, filepath: Path):
        self.filepath = filepath
        self.entries: List[JournalEntry] = []
        self.load()

    def load(self):
        if not self.filepath.exists():
            return
        try:
            # 4. JSON Handling
            content = self.filepath.read_text()
            data = json.loads(content)
            self.entries = [JournalEntry.from_dict(e) for e in data]
        except (json.JSONDecodeError, OSError) as e:
            logging.error(f"Error loading journal: {e}")

    def save(self):
        try:
            data = [e.to_dict() for e in self.entries]
            # 4. JSON Handling
            self.filepath.write_text(json.dumps(data, indent=4))
        except OSError as e:
            logging.error(f"Error saving journal: {e}")

    def add_entry(self, title: str, content: str):
        entry = JournalEntry(title, content)
        self.entries.append(entry)
        self.save()
        logging.info(f"Entry '{title}' added.")

    def list_entries(self):
        if not self.entries:
            print("No entries found.")
            return
        print(f"{'TIMESTAMP':<20} | {'TITLE':<20} | {'CONTENT'}")
        print("-" * 60)
        for entry in self.entries:
            print(f"{entry.timestamp:<20} | {entry.title:<20} | {entry.content}")

def main():
    # 5. Argument Parsing
    parser = argparse.ArgumentParser(description="Simple CLI Journal")
    subparsers = parser.add_subparsers(dest="command", required=True)

    # 'add' command
    add_parser = subparsers.add_parser("add", help="Add a new entry")
    add_parser.add_argument("title", help="Title of the entry")
    add_parser.add_argument("content", help="Content of the entry")

    # 'list' command
    subparsers.add_parser("list", help="List all entries")

    args = parser.parse_args()

    # 6. Pathlib for cross-platform paths
    journal_file = Path.home() / "my_journal.json"
    manager = JournalManager(journal_file)

    if args.command == "add":
        manager.add_entry(args.title, args.content)
    elif args.command == "list":
        manager.list_entries()

if __name__ == "__main__":
    main()

Concepts Used

  1. Logging: Used instead of print for status messages, allowing for different severity levels.
  2. Classes & OOP: The JournalEntry class encapsulates the data, while JournalManager encapsulates the logic for handling that data.
  3. Pathlib: Used Path.home() to find the user's home directory reliably on Windows, Mac, or Linux.
  4. JSON: Used json.loads and json.dumps to serialize the Python objects into a text format for storage.
  5. Argparse: Used subparsers to create distinct commands (add vs list) with their own specific arguments.
  6. Type Hinting: Used List and Dict to make the code self-documenting and IDE-friendly.

How to Run

python pyjournal.py add "First Day" "Started learning Python today."
python pyjournal.py add "Progress" "Wrote a CLI tool."
python pyjournal.py list

programming/python/python