Projeto: ferramenta CLI
LidoNesta aula, você construirá uma ferramenta CLI completa em Python usando argparse para processar arquivos de texto, aplicar transformações e gerar saídas formatadas. O projeto integra leitura de arquivos, processamento de dados e interface de linha de comando, consolidando habilidades essenciais para automação e ferramentas de produtividade.
Nesta aula, vamos consolidar os conhecimentos de Python construindo uma ferramenta de linha de comando (CLI) completa. Você aprenderá a criar um programa profissional que recebe argumentos, lê arquivos, processa o conteúdo e gera uma saída útil. Este projeto prático combina vários tópicos essenciais: manipulação de argumentos com argparse, leitura de arquivos, processamento de texto e formatação de saída.
Ao final, você terá uma ferramenta chamada textstats que analisa arquivos de texto e fornece estatísticas como contagem de palavras, linhas e caracteres, além de permitir opções como ignorar palavras vazias e gerar um relatório detalhado. Vamos desenvolver passo a passo, entendendo cada componente e como eles se integram.
argparse
O módulo argparse é a forma padrão de criar interfaces de linha de comando em Python. Ele permite definir argumentos posicionais e opcionais, gerar mensagens de ajuda automaticamente e tratar erros de forma consistente. Com argparse, você pode especificar tipos, valores padrão, ações e até mesmo subcomandos, tornando suas ferramentas muito mais robustas e amigáveis.
No nosso projeto, usaremos argparse para definir:
- Um argumento posicional
arquivoque indica o caminho do arquivo a ser processado. - Uma opção
--palavrasque, quando presente, exibe a lista de palavras mais frequentes. - Uma opção
--min-lengthque define o comprimento mínimo para uma palavra ser considerada (padrão 1). - Uma opção
--saidaque permite especificar um arquivo de saída para o relatório.
Vamos ver um exemplo básico de configuração do parser:
import argparse
parser = argparse.ArgumentParser(description='Analisa um arquivo de texto e mostra estatísticas.')
parser.add_argument('arquivo', help='Caminho do arquivo de texto')
parser.add_argument('--palavras', action='store_true', help='Lista as palavras mais frequentes')
parser.add_argument('--min-length', type=int, default=1, help='Comprimento mínimo das palavras (padrão: 1)')
parser.add_argument('--saida', help='Arquivo de saída para o relatório (opcional)')
args = parser.parse_args()
print(f'Arquivo: {args.arquivo}')
print(f'Palavras? {args.palavras}')
print(f'Comprimento mínimo: {args.min_length}')
print(f'Saída: {args.saida}')Observe como action='store_true' transforma a opção em um booleano: se a flag estiver presente, o valor será True; caso contrário, False. O parâmetro type=int garante que o valor seja convertido para inteiro, e default define o valor usado quando a opção não é fornecida.
Leitura de arquivos
Para ler arquivos em Python, utilizamos a função open() que retorna um objeto de arquivo. É fundamental usar o gerenciador de contexto with, que garante que o arquivo seja fechado corretamente, mesmo se ocorrer uma exceção. Podemos ler o conteúdo inteiro com read(), ou linha por linha com readline() ou iterando sobre o objeto.
No nosso projeto, vamos ler o arquivo de texto e dividir em linhas. Também precisamos lidar com possíveis erros, como arquivo não encontrado. Vamos usar try/except para capturar FileNotFoundError e outras exceções.
def ler_arquivo(caminho):
"""Lê o conteúdo do arquivo e retorna uma lista de linhas."""
try:
with open(caminho, 'r', encoding='utf-8') as arquivo:
linhas = arquivo.readlines()
return linhas
except FileNotFoundError:
print(f'Erro: arquivo "{caminho}" não encontrado.')
return None
except Exception as e:
print(f'Erro ao ler o arquivo: {e}')
return NoneObserve que usamos encoding='utf-8' para garantir a leitura correta de caracteres acentuados. A função retorna None em caso de erro, permitindo que o chamador decida como prosseguir.
Processamento
O processamento é o coração da ferramenta. Precisamos analisar o texto para extrair estatísticas. Vamos contar linhas (considerando linhas vazias ou não), palavras (definidas como sequências de caracteres alfanuméricos), caracteres (incluindo espaços e quebras de linha) e também a frequência das palavras, respeitando o comprimento mínimo.
Para isso, usaremos expressões regulares (re) para encontrar palavras. Vamos criar uma função processar_texto que recebe as linhas e o comprimento mínimo e retorna um dicionário com as estatísticas.
import re
from collections import Counter
def processar_texto(linhas, min_length=1):
"""Processa as linhas e retorna estatísticas."""
texto = ''.join(linhas)
num_linhas = len(linhas)
num_caracteres = len(texto)
# Encontra todas as palavras (sequências de letras, números e underscore)
palavras = re.findall(r'\b\w+\b', texto, flags=re.UNICODE)
# Filtra por comprimento mínimo
palavras_filtradas = [p.lower() for p in palavras if len(p) >= min_length]
num_palavras = len(palavras_filtradas)
# Contagem de frequência
frequencia = Counter(palavras_filtradas)
return {
'linhas': num_linhas,
'palavras': num_palavras,
'caracteres': num_caracteres,
'frequencia': frequencia
}Usamos re.findall com o padrão \b\w+\b para capturar palavras completas. A função Counter da biblioteca collections facilita a contagem de frequência. Também convertemos todas as palavras para minúsculas para evitar diferenças de capitalização.
É importante notar que a definição de "palavra" pode variar. Aqui consideramos sequências de letras, números e underscore. Se quiséssemos apenas letras, usaríamos [a-zA-Zá-úÁ-Ú]+ com suporte a acentos, mas \w já cobre acentos em Python com o flag UNICODE.
Saída
A saída da nossa ferramenta deve ser clara e útil. Vamos imprimir um resumo no terminal e, se o usuário solicitar, a lista de palavras mais frequentes. Também oferecemos a opção de salvar o relatório em um arquivo.
Para exibir as palavras mais frequentes, usamos most_common(n) do Counter. Vamos permitir que o usuário especifique quantas palavras exibir através de uma opção --top (padrão 10).
def gerar_relatorio(estats, top_n=10):
"""Gera uma string com o relatório formatado."""
linhas = [
'=== Relatório de Análise ===',
f'Arquivo: {args.arquivo}',
f'Linhas: {estats["linhas"]}',
f'Palavras: {estats["palavras"]}',
f'Caracteres: {estats["caracteres"]}',
''
]
if args.palavras:
linhas.append(f'Palavras mais frequentes (mínimo {args.min_length} caracteres):')
for palavra, contagem in estats['frequencia'].most_common(top_n):
linhas.append(f' {palavra}: {contagem}')
linhas.append('')
return '\n'.join(linhas)
def imprimir_relatorio(relatorio):
print(relatorio)
def salvar_relatorio(relatorio, caminho):
try:
with open(caminho, 'w', encoding='utf-8') as arquivo:
arquivo.write(relatorio)
print(f'Relatório salvo em {caminho}')
except Exception as e:
print(f'Erro ao salvar relatório: {e}')No main, integramos tudo: lemos o arquivo, processamos e geramos a saída. Se o usuário passou --saida, salvamos o relatório; caso contrário, imprimimos no terminal.
def main():
parser = argparse.ArgumentParser(description='Analisa um arquivo de texto e mostra estatísticas.')
parser.add_argument('arquivo', help='Caminho do arquivo de texto')
parser.add_argument('--palavras', action='store_true', help='Lista as palavras mais frequentes')
parser.add_argument('--min-length', type=int, default=1, help='Comprimento mínimo das palavras (padrão: 1)')
parser.add_argument('--saida', help='Arquivo de saída para o relatório (opcional)')
parser.add_argument('--top', type=int, default=10, help='Quantas palavras listar (padrão: 10)')
args = parser.parse_args()
linhas = ler_arquivo(args.arquivo)
if linhas is None:
return
estats = processar_texto(linhas, args.min_length)
relatorio = gerar_relatorio(estats, args.top)
if args.saida:
salvar_relatorio(relatorio, args.saida)
else:
imprimir_relatorio(relatorio)
if __name__ == '__main__':
main()Com isso, temos uma ferramenta funcional. Vamos testar com um arquivo de exemplo e opções variadas.
Boas práticas e observações finais
Ao construir ferramentas CLI, lembre-se de:
- Sempre usar
if __name__ == '__main__'para permitir que o módulo seja importado sem executar o main. - Tratar exceções de forma elegante, fornecendo mensagens claras ao usuário.
- Documentar as opções com
helppara que a saída de--helpseja informativa. - Considerar a codificação dos arquivos (UTF-8 é o padrão recomendado).
- Testar com diferentes entradas, incluindo arquivos vazios, com acentos e com palavras de tamanhos variados.
Este projeto pode ser expandido com mais funcionalidades, como suporte a múltiplos arquivos, filtros por expressão regular, ou geração de gráficos. O importante é dominar os conceitos de argumentos, I/O e processamento de texto.
Referências
- Documentação oficial do módulo argparse
- Documentação da função open()
- Documentação do módulo re (expressões regulares)
- Documentação do Counter
- Tutorial de Entrada e Saída do Python
- Tutorial de argparse (Python Docs)
Exercícios
Modifique a ferramenta para aceitar múltiplos arquivos como argumentos posicionais (nargs='+') e processar cada um separadamente, mostrando o nome do arquivo antes das estatísticas.
✓ Resposta:import argparse import re from collections import Counter def ler_arquivo(caminho): try: with open(caminho, 'r', encoding='utf-8') as f: return f.readlines() except Exception as e: print(f'Erro ao ler {caminho}: {e}') return None def processar_texto(linhas, min_length): texto = ''.join(linhas) palavras = re.findall(r'\b\w+\b', texto, flags=re.UNICODE) palavras_filtradas = [p.lower() for p in palavras if len(p) >= min_length] return {'linhas': len(linhas), 'palavras': len(palavras_filtradas), 'caracteres': len(texto), 'frequencia': Counter(palavras_filtradas)} def main(): parser = argparse.ArgumentParser(description='Analisa arquivos de texto') parser.add_argument('arquivos', nargs='+', help='Caminhos dos arquivos') parser.add_argument('--min-length', type=int, default=1) parser.add_argument('--top', type=int, default=10) args = parser.parse_args() for arquivo in args.arquivos: linhas = ler_arquivo(arquivo) if linhas is None: continue estats = processar_texto(linhas, args.min_length) print(f'\n--- {arquivo} ---') print(f'Linhas: {estats["linhas"]}') print(f'Palavras: {estats["palavras"]}') print(f'Caracteres: {estats["caracteres"]}') if args.top > 0: print('Frequência:') for palavra, contagem in estats['frequencia'].most_common(args.top): print(f' {palavra}: {contagem}') if __name__ == '__main__': main()Adicione uma opção
--ignorar-vaziasque remove linhas vazias da contagem de linhas e do processamento de texto.✓ Resposta:# No processar_texto, se ignorar_vazias for True, filtra linhas vazias antes de juntar: def processar_texto(linhas, min_length, ignorar_vazias=False): if ignorar_vazias: linhas = [l for l in linhas if l.strip()] texto = ''.join(linhas) # ... resto igualCrie uma função que, dado um texto, retorne a palavra mais longa e a mais curta (considerando o comprimento mínimo). Incorpore isso ao relatório.
✓ Resposta:def encontrar_extremos(palavras): if not palavras: return None, None mais_longa = max(palavras, key=len) mais_curta = min(palavras, key=len) return mais_longa, mais_curta # No processar_texto, após filtrar, chame esta função e adicione ao dicionário: palavras_filtradas = [p.lower() for p in palavras if len(p) >= min_length] mais_longa, mais_curta = encontrar_extremos(palavras_filtradas) # Adicione ao retorno: 'mais_longa': mais_longa, 'mais_curta': mais_curtaImplemente uma opção
--reverseque inverte a ordem das linhas no relatório (imprime de trás para frente).✓ Resposta:# No main, após ler as linhas, se args.reverse, inverta a lista: if args.reverse: linhas = linhas[::-1] # Depois processe normalmente.Adicione tratamento para arquivos binários (por exemplo, extensão .bin) que exiba uma mensagem de erro e não processe.
✓ Resposta:# Na função ler_arquivo, verifique a extensão: import os if caminho.endswith('.bin'): print(f'Erro: arquivo binário não suportado: {caminho}') return None # Caso contrário, proceda normalmente.