A biblioteca arg representa uma abordagem minimalista para o parsing de argumentos de linha de comando, realizando toda a operação em aproximadamente 180 linhas de código. Este artigo examina sua estrutura interna e demonstra como um design conciso pode suportar funcionalidades robustas para ferramentas de interface de linha de comando (CLI).
Arquitetura Central: Um Design Conciso em 180 Linhas
A implementação principal reside em um único arquivo JavaScript, exposta através de uma função de parsing. O design segue um modelo de "configuração por objeto", com três componentes principais:
- Sistema de Definição de Parâmetros: Declara tipos de argumentos e aliases usando pares chave-valor.
- Mecanismo de Tratamento de Erros: Uma classe de erro personalizada para gerenciar falhas de parsing de forma unificada.
- Motor de Parsing: Processa iterativamente a entrada do usuário, convertendo argumentos brutos em uma estrutura de dados estruturada.
Essa abordagem mantém o código altamente conciso enquanto preserva extensibilidade.
Definição de Parâmetros: Uma Sintaxe de Configuração Intuitiva
A biblioteca emprega sintaxe de objetos nativa do JavaScript para definir regras de parsing, suportando múltiplos tipos e configurações complexas.
const parsedArgs = parseArguments({
'--valor-numerico': Number, // Argumento numérico
'--texto': String, // Argumento de string
'--ativar': Boolean, // Flag booleana
'-t': '--texto' // Alias para argumento longo
});
Tipos de Parâmetros Básicos
Três tipos primitivos são suportados, cada um com comportamento de parsing distinto:
- Boolean: Não requer valor; sua presença ativa o valor
true. Exemplo:--ativar. - String: Requer um valor explícito. Exemplo:
--texto exemplo. - Number: O valor é automaticamente convertido para número. Exemplo:
--valor-numerico 42.
Recursos Avançados: Arrays e Tipos Customizados
A notação de array permite a coleta de múltiplos valores:
// Agrupamento de múltiplos valores em um array
const resultado = parseArguments({ '--tags': [String] }, { argv: ['--tags', 'javascript', '--tags', 'node'] });
// Resultado esperado: { _: [], '--tags': ['javascript', 'node'] }
Funções customizadas possibilitam transformações arbitrárias:
// Função de transformação personalizada
const transformador = (valor, nome) => `${nome}:${valor}`;
const dados = parseArguments({ '--campo': transformador }, { argv: ['--campo', '123'] });
// Resultado esperado: { _: [], '--campo': '--campo:123' }
Motor de Parsing: Fluxo de Processamento Eficiente
O motor utiliza uma estratégia iterativa para analisar os argumentos. O processo pode ser segmentado em quatro fases principais.
1. Pré-processamento de Argumentos
Argumentos curtos combinados são separados em flags individuais. Por exemplo, -vv é decomposto em dois -v.
// Lógica de separação de argumentos curtos
const argEntrada = '-vv';
const argumentosSeparados = argEntrada.length === 2
? [argEntrada]
: argEntrada.slice(1).split('').map(caractere => `-${caractere}`);
2. Resolução de Aliases
Aliases são resolvidos iterativamente até que se alcance o nome do argumento definitivo.
// Loop de resolução de aliases
let nomeArg = 'aliasCurto';
const mapaAliases = { 'aliasCurto': 'argumentoLongo' };
while (nomeArg in mapaAliases) {
nomeArg = mapaAliases[nomeArg];
}
3. Validação de Argumentos
Argumentos não definidos são tratados de acordo com o modo de operação. Em modo estrito, erros são lançados; em modo permissivo, são adiiconados a uma lista genérica.
// Verificação de argumento conhecido
const argumentoValido = nomeArg in manipuladores;
if (!argumentoValido) {
if (modoPermissivo) {
resultado._.push(argumentoOriginal);
} else {
throw new ErroDeParsing(`Opção desconhecida: ${nomeArg}`, 'OPCAO_DESCONHECIDA');
}
}
4. Processamento de Valores
Flags e argumentos que requerem valores são tratados de maneira distinta.
// Processamento baseado no tipo
if (ehFlag) {
resultado[nomeArg] = tipoManipulador(true, nomeArg, resultado[nomeArg]);
} else {
const valor = argumentos[i + 1];
resultado[nomeArg] = tipoManipulador(valor, nomeArg, resultado[nomeArg]);
i++; // Pular o valor processado
}
Utilitários: Simplificação de Casos Comuns
Contagem de Flags: arg.COUNT
Permite contabilizar a ocorrência de flags, como em -v, -vv.
// Implementação do contador
parseArguments.COUNT = parseArguments.flag((valor, nome, contagemExistente) => (contagemExistente || 0) + 1);
// Exemplo de uso
const verbosidade = parseArguments({ '--verbose': parseArguments.COUNT }, { argv: ['-v', '--verbose', '-v'] });
// Resultado esperado: { _: [], '--verbose': 3 }
Flags Customizáveis: arg.flag()
Transforma uma função em uma manipuladora de flags, dispensando a necessidade de um valor.
// Criação de uma flag customizada
const criarFlag = (fn) => {
fn.ehFlag = true;
return fn;
};
// Exemplo
const flagEspecial = criarFlag(() => 'valor-fixado');
const config = parseArguments({ '--config': flagEspecial }, { argv: ['--config'] });
// Resultado esperado: { _: [], '--config': 'valor-fixado' }
Tratamento de Erros: Feedback Clara e Específica
Uma classe de erro dedicada fornece códigos e mensagens claras para diferentes cenários de falha.
class ErroDeParsing extends Error {
constructor(mensagem, codigo) {
super(mensagem);
this.nome = 'ErroDeParsing';
this.codigo = codigo;
}
}
Códigos de erro comuns incluem OPCAO_DESCONHECIDA, VALOR_FALTANDO e TIPO_INVALIDO.
Cenário Prático: Parsing de Entrada Complexa
Considere a seguinte entrada de exemplo e sua configuração:
const argumentosBrutos = [
'--numero', '100', '-t', '-', 'hello', '--extra', 'mundo'
];
const definicoes = {
'--numero': Number,
'--texto': String,
'--outro': Boolean,
'-o': '--outro',
'--extra': '--outro',
'-t': '--texto'
};
const resultadoFinal = parseArguments(definicoes, { argv: argumentosBrutos });
// Saída aproximada: { _: ['hello', 'mundo'], '--numero': 100, '--texto': '-', '--outro': true }
Este exemplo demonstra a capacidade de lidar com tipos diversos, aliases e coleta de valores genéricos.