Se você é um programador profissional, provavelmente já teve que se virar com a linguagem de programação C. Se programa em mais de uma linguagem e C é uma delas, provavelmente não é sua primeira escolha para escrever pequenos utilitários na linha de comando — também não é a minha. Ainda sim, nesse curto post vou discutir um utilitário que escrevi em C, porque fiz essa escolha e como ela pagou dividendos para mim.

Nota: Esse é um post curto mesmo, não vou entrar em detalhes de nenhum dos assuntos abordados; mas alguns dos projetos mencionados podem ser úteis para você e ler esse post deve levar alguns minutos

Uma carta de amor ao modelo UNIX

Quanto mais aprendo sobre as ferramentas incluídas no coreutils, mais percebo a genialidade do modelo UNIX. Ferramentas como grep, sed, awk, xargs, grep, cat, echo, etc., projetadas para realizar uma única tarefa da forma mais simples possível. É impressionante o quão longe podemos ir só com um pipeline de UNIX pipes e o segredo está em combinar essas ferramentas para criar outras pecinhas reutilizáveis.

Esse é um script que tenho no meu PATH, chamado cabal-version:

#!/bin/bash
cabal info $1 |
grep -i “versions available” |
perl -n -e ‘/([^,\s]+),$/ && print $1’

Dado um nome de pacote, usa o cabal-install, o gerenciador de pacotes da linguagem de programação Haskell para conseguir suas informações, encontrar as versões disponíveis e imprimir a mais nova.

Outro exemplo divertido, chamado npm-clone:

#!/bin/bash
npm info -j $1 | jq -r ‘.homepage’ | xargs hub clone

Pega um nome de pacote do NPM, o gerenciador de pacotes do Node.js, e o usa para conseguir a URL da sua homepage (que provavelmente é um repositório do GitHub) e tenta passar isso para o hub, para copiar o código localmente. Isso me deixa com um comando, encontrar o código de um pacote, se precisar o consertar e contribuir com minhas melhorias.

Há uma coisa sobre esses scripts. Eles combinam programas escritos em linguagens diferentes, que não sabem nada uns dos outros, para realizar a tarefa em questão. Os programas usados neles são:

  • grep (para filtrar texto)
  • cabal (o gerenciador de pacotes do Haskell)
  • jq (um sed/grep para JSON; um utilitário para a manipulação de JavaScript Object Notation)
  • xargs (transforma stdin em argumentos da linha de comando, com suporte para concorrência por meio de muitos processos)
  • perl (a linguagem de programação Perl)
  • npm (o gerenciador de pacotes do Node.js)

O problema: escrever scripts que operam sobre a raiz de projetos

O que queria fazer era poder escrever scripts sobre a raiz de um projeto. Por exemplo, quero de qualquer lugar em um projeto, poder executar find para encontrar um arquivo nele. Percebi que não havia um utilitário pequeno e simples que fizesse isso.

Olhei no código-fonte do projectile, um plugin do Emacs que me dá esse tipo de funcionalidade no editor. Enquanto estou em um arquivo, posso rapidamente executar uma busca sobre todos os arquivos no projeto, encontrar arquivos por nome no projeto etc.

O plugin funciona detectando a raiz do projeto a partir de uma lista de arquivos que indicam que chegamos ao topo. Ou seja:

  • Listamos os arquivos no diretório atual
  • Procuramos por package.json, projeto.cabal, .git e outros arquivos desse tipo
  • Decidimos que esse é o diretório que procuramos ou vamos para o diretório pai e tentamos de novo

Porque escolhi C

Volta e meia eu tenho que usar uma máquina que não é minha ou é nova, que não tem minhas centenas de linhas de dotfiles e automação ou minhas linguagens preferidas instaladas. Além disso, para utilitários pequenos, acho um pouco contra intuitivo usar algo como node.js a não ser que traga benefícios muito claros. Isso é porque há um custo de memória e tempo de inicialização desse tipo de plataforma. Se estamos escrevendo um servidor isso não importa tanto, mas se estamos escrevendo algo que queremos rodar com xargs -P32, por exemplo, a invocação do xargs que cria 32 processos paralelos para a avaliação de um pipe, vai ser bem relevante.

Então pensei; posso escrever Python, que sempre está disponível e é uma linguagem legal e eficiente; ou C que também está sempre disponível, roda em todo o lugar e oferece um bom exercício. Afinal, a maior parte dos programas que escrevi em C foram tarefas de aula, não coisas que eu usei no dia-a-dia.

Optei por C, para refrescar meu conhecimento sobre as bibliotecas padrão e poder usar o programa em qualquer plataforma, sem nem ter que instalar algo (posso só copiar um binário).

Parsing de argumentos e clib, um gerenciador de pacotes para C

Uma bela contribuição do TJ Holowaychuk para o Open-Source foi o pequeno gerenciador de pacotes de C clib. É muito simples; lê um repositório do GitHub, baixa um familiar package.json com as dependências e as instala. Depois é só incluir os headers e arquivos no seu Makefile e as coisas funcionam como se esperaria.

Uma das pérolas que estão preparadas para funcionar com o clib é um port da biblioteca commander, com encarnações em Ruby e Node.js.

Gosto muito dessa biblioteca no Node.js, inclusive comecei a escrever uma versão para a linguagem de programação D em: https://github.com/yamadapc/commander.d

Em C, seu uso fica:

É um pouco mais verbose do que em uma linguagem dinâmica, mas é bom o suficiente.

Usando essa biblioteca, um pouco de glob matching e tempo, escrevi o projectroot. É um executável que resolve meu problema, de algum lugar em um projeto, imprime a raiz:

$ projectroot
/Users/yamadapc/program/github.com/yamadapc/projectroot

Escrevi em outro lugar sobre como usei isso para manejar daemons do Emacs. E esse foi o use-case para o programinha. A ideia era usar BASH para ter daemons escopados por projeto:

function ep-start { emacs --daemon=$(basename $(projectroot)) $@ }

E isso funcionou por um bom tempo. Até que comecei a ter problemas com essa estratégia e decidi escrever um outro pequeno executável para manejar meus daemons. Mas agora não queria muito escrever C. Recentemente tive segundos encontros com a linguagem de programação Haskell e percebi muitos de seus benefícios para programas grandes e pequenos. Decidi escrever esse novo programa em Haskell.

Mas que desperdício de tempo enorme, concentra nas coisas que está construindo e não nas ferramentas!

Bom… Não tenho tanto a dizer sobre isso. É verdade. Não deveria desperdiçar muitos minutos indo de uma linguagem para a outra ou manejando meus daemons do Emacs ou nada do gênero. Claro que demorei muito mais tempo para escrever esse post, do que gastei escrevendo esses dois programas (menos de 1h para as versões iniciais + pequenas melhorias e avanços ao longo do tempo), então não tenho tanto remorso.

De todo jeito, fiquei feliz em escolher outra linguagem, porque tive que chamar C do Haskell, o que foi surpreendentemente simples:

foreign import ccall "find_project_root" find_project_root :: CString -> IO CString

Não cabe discutir o que se passa nessa linha ou como a usar em Haskell (isso fica pra outra hora), mas acho sensacional que pude usar o código que escrevi em C sem desperdiçar quase nenhum tempo.

Se nada mais, essa é uma boa razão para escrever seus scripts em C. Reutilizar em outros projetos, outras linguagens.

O código-fonte em Haskell para o edm, Emacs Daemon Manager, está em: http://haskellbr.com/git/haskellbr/edm

O código fonte em C para o projectroot está em: http://github.com/yamadapc/projectroot

E o código fonte para os bindings de Haskell para o projectroot está em: https://github.com/yamadapc/haskell-projectroot

@yamadapc