kb.erickguedes.com
Python: De Noob a Hero

Projeto Final — CLI de Gerenciamento de Tarefas

Aula 10 de 10

Projeto: Task Manager CLI

Uma aplicação de linha de comando completa que gerencia tarefas com persistência em JSON.

#!/usr/bin/env python3
"""task-cli — Gerenciador de tarefas via terminal."""

import json
import os
import sys
from datetime import datetime
from pathlib import Path
from typing import Optional
import argparse

DATA_DIR = Path.home() / ".task-cli"
DATA_FILE = DATA_DIR / "tasks.json"

class Task:
    """Representa uma tarefa."""

    def __init__(self, id: int, titulo: str, descricao: str = "",
                 prioridade: str = "media", completa: bool = False):
        self.id = id
        self.titulo = titulo
        self.descricao = descricao
        self.prioridade = prioridade
        self.completa = completa
        self.criada_em = datetime.now().isoformat()
        self.concluida_em: Optional[str] = None

    def to_dict(self):
        return self.__dict__

    @classmethod
    def from_dict(cls, dados: dict):
        t = cls(dados["id"], dados["titulo"])
        t.__dict__.update(dados)
        return t

    def __str__(self):
        status = "✓" if self.completa else "○"
        return f"[{status}] {self.id:03d} | {self.titulo} ({self.prioridade})"

Gerenciamento de Dados

class TaskManager:
    def __init__(self):
        DATA_DIR.mkdir(parents=True, exist_ok=True)
        self.tasks: list[Task] = []
        self._carregar()

    def _carregar(self):
        if DATA_FILE.exists():
            with open(DATA_FILE) as f:
                dados = json.load(f)
                self.tasks = [Task.from_dict(t) for t in dados]

    def _salvar(self):
        with open(DATA_FILE, "w") as f:
            json.dump([t.to_dict() for t in self.tasks], f, indent=2)

    def adicionar(self, titulo: str, descricao: str = "",
                  prioridade: str = "media") -> Task:
        max_id = max((t.id for t in self.tasks), default=0)
        task = Task(max_id + 1, titulo, descricao, prioridade)
        self.tasks.append(task)
        self._salvar()
        return task

    def listar(self, apenas_pendentes: bool = False) -> list[Task]:
        tasks = [t for t in self.tasks if not t.completa] \
                if apenas_pendentes else self.tasks
        return sorted(tasks, key=lambda t: t.id)

    def completar(self, task_id: int) -> Optional[Task]:
        for task in self.tasks:
            if task.id == task_id:
                task.completa = True
                task.concluida_em = datetime.now().isoformat()
                self._salvar()
                return task
        return None

    def remover(self, task_id: int) -> bool:
        for i, task in enumerate(self.tasks):
            if task.id == task_id:
                del self.tasks[i]
                self._salvar()
                return True
        return False

    def buscar(self, termo: str) -> list[Task]:
        return [t for t in self.tasks if termo.lower() in t.titulo.lower()]

CLI com argparse

def criar_parser() -> argparse.ArgumentParser:
    parser = argparse.ArgumentParser(
        description="Gerenciador de Tarefas via Terminal",
        epilog="Use 'task-cli comando --help' para ajuda de cada comando"
    )

    sub = parser.add_subparsers(dest="comando", required=True)

    # add
    add = sub.add_parser("add", help="Adicionar tarefa")
    add.add_argument("titulo", help="Título da tarefa")
    add.add_argument("-d", "--descricao", default="", help="Descrição")
    add.add_argument("-p", "--prioridade",
                     choices=["baixa", "media", "alta"], default="media")

    # list
    ls = sub.add_parser("ls", aliases=["list"], help="Listar tarefas")
    ls.add_argument("--pendentes", action="store_true",
                    help="Apenas pendentes")

    # done
    done = sub.add_parser("done", help="Completar tarefa")
    done.add_argument("id", type=int, help="ID da tarefa")

    # rm
    rm = sub.add_parser("rm", aliases=["remove"], help="Remover tarefa")
    rm.add_argument("id", type=int, help="ID da tarefa")

    # search
    search = sub.add_parser("search", help="Buscar tarefas")
    search.add_argument("termo", help="Termo de busca")

    return parser

def main():
    parser = criar_parser()
    args = parser.parse_args()

    tm = TaskManager()
    cores = {"alta": "\033[91m", "media": "\033[93m",
             "baixa": "\033[92m", "reset": "\033[0m"}

    match args.comando:
        case "add" | "a":
            task = tm.adicionar(args.titulo, args.descricao, args.prioridade)
            print(f"{cores[args.prioridade]}✓ Tarefa #{task.id} criada{cores['reset']}")

        case "ls" | "list":
            tasks = tm.listar(args.pendentes)
            for t in tasks:
                cor = cores.get(t.prioridade, "")
                print(f"{cor}{t}{cores['reset']}")
            print(f"\nTotal: {len(tasks)} tarefas")

        case "done":
            task = tm.completar(args.id)
            if task:
                print(f"✓ Tarefa #{task.id} concluída!")
            else:
                print(f"✗ Tarefa #{args.id} não encontrada")
                sys.exit(1)

        case "rm" | "remove":
            if tm.remover(args.id):
                print(f"✓ Tarefa #{args.id} removida")
            else:
                print(f"✗ Tarefa #{args.id} não encontrada")
                sys.exit(1)

        case "search":
            tasks = tm.buscar(args.termo)
            for t in tasks:
                print(t)

if __name__ == "__main__":
    main()

Uso

python task_cli.py add "Estudar Python" -p alta
python task_cli.py ls
python task_cli.py ls --pendentes
python task_cli.py done 1
python task_cli.py search "Python"

Este projeto final une tudo: funções, classes, JSON, argparse, datetime, pathlib, tipagem e organização. Expanda com categorias, data de vencimento e relatórios!