129 lines
4.4 KiB
Python
129 lines
4.4 KiB
Python
import os
|
|
import pathspec
|
|
import re
|
|
|
|
|
|
def track_todos(
|
|
start_path=".",
|
|
output_file="docs/manual_desenvolvimento/07_Divida_Tecnica_Backlog.md",
|
|
gitignore_file=".gitignore",
|
|
):
|
|
"""
|
|
Extrai TODOs, FIXMEs e HACKs do código e gera um relatório.
|
|
"""
|
|
|
|
# Filtros
|
|
ignore_patterns = [".git", ".agent", "node_modules", "venv", "dist", "build"]
|
|
if os.path.exists(gitignore_file):
|
|
with open(gitignore_file, "r", encoding="utf-8") as f:
|
|
ignore_patterns.extend(f.read().splitlines())
|
|
spec = pathspec.PathSpec.from_lines("gitwildmatch", ignore_patterns)
|
|
|
|
# Regex para capturar comentários
|
|
todo_pattern = re.compile(r"(TODO|FIXME|HACK|BUG|NOTE):\s*(.+)", re.IGNORECASE)
|
|
|
|
todos = []
|
|
|
|
for root, dirs, files in os.walk(start_path):
|
|
dirs[:] = [d for d in dirs if not spec.match_file(os.path.join(root, d))]
|
|
|
|
for file in files:
|
|
file_path = os.path.join(root, file)
|
|
if spec.match_file(file_path):
|
|
continue
|
|
|
|
# Pula arquivos não-texto
|
|
if file.endswith((".png", ".jpg", ".ico", ".pdf", ".exe")):
|
|
continue
|
|
|
|
try:
|
|
with open(file_path, "r", encoding="utf-8", errors="ignore") as f:
|
|
lines = f.readlines()
|
|
for i, line in enumerate(lines):
|
|
match = todo_pattern.search(line)
|
|
if match:
|
|
tag = match.group(1).upper()
|
|
msg = match.group(2).strip()
|
|
# Tenta pegar a próxima linha de interesse (contexto)
|
|
context = ""
|
|
if i + 1 < len(lines):
|
|
context = lines[i + 1].strip()
|
|
# Se for apenas um comentário vazio ou fechamento, tenta mais uma
|
|
if not context and i + 2 < len(lines):
|
|
context = lines[i + 2].strip()
|
|
|
|
todos.append(
|
|
{
|
|
"tag": tag,
|
|
"msg": msg,
|
|
"file": file_path,
|
|
"line": i + 1,
|
|
"context": context[
|
|
:100
|
|
], # Limita tamanho do contexto
|
|
}
|
|
)
|
|
except:
|
|
pass
|
|
|
|
# Estatísticas
|
|
stats = {}
|
|
for item in todos:
|
|
stats[item["tag"]] = stats.get(item["tag"], 0) + 1
|
|
|
|
# Mapeamento de Prioridade e Ícones
|
|
priority = {"BUG": 0, "FIXME": 1, "HACK": 2, "TODO": 3, "NOTE": 4}
|
|
icons = {
|
|
"BUG": "🔴 ERRO (BUG)",
|
|
"FIXME": "🟠 CORRIGIR (FIXME)",
|
|
"HACK": "🏴☠️ GAMBIARRA (HACK)",
|
|
"TODO": "🔵 PENDENTE (TODO)",
|
|
"NOTE": "📝 NOTA (NOTE)",
|
|
}
|
|
|
|
# Gera Markdown
|
|
os.makedirs(os.path.dirname(output_file), exist_ok=True)
|
|
|
|
md = [
|
|
"# 🏚️ Backlog de Dívida Técnica (Gerado Automaticamente)",
|
|
f"> Atualizado em: {os.popen('date /t').read().strip()} {os.popen('time /t').read().strip()}",
|
|
"",
|
|
"## 📊 Resumo de Pendências",
|
|
]
|
|
|
|
for tag, count in sorted(stats.items(), key=lambda x: priority.get(x[0], 99)):
|
|
label = icons.get(tag, tag)
|
|
md.append(f"- **{label}**: {count}")
|
|
|
|
md.extend(
|
|
[
|
|
"",
|
|
"## 📋 Lista Detalhada",
|
|
"",
|
|
"| Tipo | Mensagem | Contexto | Localização |",
|
|
"| :--- | :--- | :--- | :--- |",
|
|
]
|
|
)
|
|
|
|
# Ordena por tipo (BUG > FIXME > TODO)
|
|
todos.sort(key=lambda x: (priority.get(x["tag"], 99), x["file"]))
|
|
|
|
for item in todos:
|
|
label = icons.get(item["tag"], f"**{item['tag']}**")
|
|
# Link relativo limpo para VSCode/Editores
|
|
relative_path = os.path.relpath(item["file"], ".")
|
|
location = (
|
|
f"[`{relative_path}:{item['line']}`]({relative_path}#L{item['line']})"
|
|
)
|
|
ctx = f"`{item['context']}`" if item["context"] else "---"
|
|
md.append(f"| {label} | {item['msg']} | {ctx} | {location} |")
|
|
|
|
with open(output_file, "w", encoding="utf-8") as f:
|
|
f.write("\n".join(md))
|
|
|
|
return f"✅ Backlog de Dívida Técnica atualizado com sucesso: {output_file}"
|
|
|
|
|
|
if __name__ == "__main__":
|
|
print(track_todos())
|