Trocando figurinhas sobre o terminal

Uma das minhas diversões nesta Copa foi montar um álbum de figurinhas. Na verdade, montei o álbum porque me filho queria muito, mas me diverti também, eu acho.

Álbum de figurinhas da Copa do Mundo de 2018, aberto na página da França, com três figurinhas faltando

Infelizmente, não completei ainda

Parte importante de colecionar figurinhas é trocar as repetidas. Através de mensagens em grupos de WhatsApp, dizemos quais repetidas temos e quais figurinhas ainda precisamos. Como programador, me recusei a ficar comparando as listas, então escrevi um programinha em Python (com doctests e tudo) para encontrar intersecções.

O laptop sumido

Semana passada vieram à minha casa para trocar figurinhas. Eu tinha as listas de repetidas e necessárias, tanto minhas quanto da outra colecionadora, mas o meu script estava em um outro laptop. Eu nem sabia onde esse laptop estava, e a visita estava com pressa.

Não daria tempo de achar o computador, ou de reescrever o programa. Ou mesmo de comparar manualmente.

Hora de usar alguns comandos Unix!

O formato das listas

As listas geralmente têm esse formato:

15, 18, 26, 31, 40, 45 (2), 49, 51, 110, 115, 128, 131 (2), 143, 151, 161, 162, 183 (2), 216 (2), 221, 223, 253, 267 (3), 269, 280, 287, 296, 313, 325, 329, 333 (2), 353 (3), 355, 357, 359, 362, 365, 366, 371, 373, 384, 399, 400, 421 (2), 445, 457, 469, 470, 498 (2), 526, 536, 553, 560, 568, 570, 585, 591 (2), 604 (2), 639 (2), 660.

Basicamente, eu precisava remover tudo que não fossem dígitos, além dos números entre parênteses, e comparar duas listas. Fácil até.

Preprocessando com sed

Primeiro, preciso remover os contadores entre parênteses:

$ cat list.txt | sed 's/([^)]*)//g'
15, 18, 26, 31, [...] 591 , 604 , 639 , 660.

(Eu sei, UUOC. Que seja.)

Depois, coloco cada número em uma linha:

$ cat list.txt | sed 's/([^)]*)//g' | sed 's/, */\n/g'

Depois, limpo cada linha de qualquer caractere que não seja um dígito:

cat list.txt | sed 's/([^)]*)//g' | sed 's/, */\n/g' | sed 's/[^0-9]*\([0-9]*\)[^0-9]*/\1/g'

(Na prática chamo sed apenas uma vez, passando duas expressões. Aqui acho que fica mais claro invocar sed várias vezes.)

Por fim, ordeno os valores:

$ cat list.txt | sed 's/([^)]*)//g' | sed 's/, */\n/g' | sed 's/[^0-9]*\([0-9]*\)[^0-9]*/\1/g' | sort -n > mine-needed.txt

Faço isso com a lista de figuras necessárias, e também com a lista de figuras repetidas, conseguindo dois arquivos.

Encontrando intersecções com grep

Agora, preciso compará-los. Há muitas opções, e eu escolhi usar grep.

No caso, chamei grep passando um dos arquivos como entrada, e o outro arquivo como uma lista de padrões para casar, através da opção -f. Além disso, apenas o matching completo das linhas importa, então usaremos a flag -x. Por fim, pedi para grep comparar strings diretamente (ao invés de considerá-las expressões regulares) com a flag -F.

$ fgrep -Fxf mine-needed.txt theirs-repeated.txt
253
269
333
470
639

Pronto! Em um minuto, já sei quais figuras quero. Basta fazer o mesmo com as minhas repetidas.

Por que isto é interessante?

Para mim, hoje, estes one-liners não são grande coisa. O interessante é que, quando comecei a usar o terminal, eles seriam incríveis. Sério, olhe quantos pipes usamos para preprocessar os arquivos! E esse truque com o grep? Eu penava só para fazer uma regex que funcionasse! Na verdade, até solucionar esse problema, eu nem conhecia a opção -x.

Certa vez ajudei um amigo meu a processar um bom número de arquivos. Ele já levava mais de duas horas tentando fazer isso com Java, e resolvemos juntos em dez minutos com shell script. Ele então me falou o quanto queria saber shell script e me perguntou como aprender.

Pois bem, pequenos exemplos como estes, por simples que sejam, me ensinaram muito. É assim que se aprende: tentando resolver problemas, conhecendo comandos e opções aos poucos. E, no final, esta é uma habilidade muito valiosa.

Então, espero que esta pequena brincadeira enriqueça seu dia, também. Certamente enriqueceu o meu – queria ter pensado nela antes de gastar tanto tempo no script Python que escrevi!

Dê uma chance a Doctest

Um dos meus módulos Python preferidos é doctest. Com ele, é possível executar trechos de código inseridos em documentação. Você poderia, por exemplo, escrever algo assim no seu arquivo turorial.md

>>> f()
1

…e executar python -mdoctest tutorial.md. Se f() retornar 1, nada acontecerá. Se retornar algo diferente, porém, aparecerá uma mensagem de erro similar a esta:


É uma ferramenta impressionante, mas também é impopular. O problema é que Doctest é frequentemente utilizado de maneira inadequada. Por exemplo, é comum tentar escrever testes unitários como doctests. Grande erro!

Ainda assim, considero injusto desconsiderar o módulo devido a estes enganos. Doctest pode, e deve, ser usado para o que faz melhor: manter sua documentação viva, e até guiar seu desenvolvimento!

Deixe-me mostrar um exemplo.

Quando não se sabe o que fazer

Esses dias, estava escrevendo uma classe que alteraria um documento HTML utilizando xml.dom.minidom. Em um determinado momento, eu precisava de uma função que mapeasse classes CSS para nodes do documento. Esta seria uma função bem complicada por si só! Não sabia por onde começar.

Teoricamente, testes unitários seriam úteis aqui. Só não seriam tão práticos: esta era uma função interna, privada, um detalhe de implementação. Para testá-la, ela teria de ser exposta. Também precisaríamos de um novo arquivo para os testes. E, afinal, test cases não são tão legíveis.

Lendo a documentação do futuro

Ao invés disso, documentei a função primeiro. Escrevi um paragrafozinho informando o que ela faria. Só isso já clareou minha mente um pouco:

Given an xml.dom.minidom.Node, returns a map
from every “class” attribute to a list of nodes
with this class.

Então, pensei em como escrever a mesma coisa, mas como um exemplo de código. Na minha cabeça, esta função (que chamei de get_css_class_dict()) receberia um documento xml.dom.minidom. Então, criei um de exemplo:

Dado este exemplo, eu esperaria que a função retornasse um dicionário. Meu documento tem duas classes, “a” e “b”, e portanto o dicionário teria duas chaves. Cada chave teria uma lista dos nós que possuíssem tais classes. Algo mais ou menos assim:

Coloquei esses rascunhos na docstring de get_css_class_dict(). O resultado até agora foi essa função:

Dava para fazer algo similar com testes unitários, mas haveria muito mais código à volta, poluindo a documentação. Além disso,  a prosa graciosamente complementa o código, dando ritmo à leitura.

Executo os doctests, e o resultado é esse:

Basicamente, estou seguindo test-driven development, mas com documentação executável. Consegui, de uma vez, um exemplo legível e um teste básico.

Agora basta implementar a função! Usei recursão e, se o código não ficou o mais sucinto possível de primeira…

…ao menos funciona como esperado:

Opa, espere aí! O que foi isso?!

Quando a documentação erra

Pois bem, há um errozinho no meu doctest! O span não possui a classe b: é o div que a possui. Basta alterar a linha

[<DOM Element: span at ...>]

para

[<DOM Element: div at ...>]

e o doctest passará.

Não é uma maravilha? Descobri quase imediatamente um deslize na documentação. Mais que isto: se algum dia o comportamento da minha função mudar, o exemplo da minha docstring vai falhar! Saberei exatamente onde a documentação precisará de atualizações.

Fazendo doctests valerem a pena

Esta é a razão de ser de doctest. Nossa documentação possuía um erro sutil, e ao executá-la pudemos percebê-lo. Doctests não garantem a corretude do código; doctests garantem a corretude da documentação. É um aspecto bem compreendido do pacote, mas poucas pessoas parecem acreditar que isto valha a pena.

Eu acho que vale! Documentação tem a fama de ser um trabalho desagradável, mas não precisa ser. Assim como TDD torna testes mais empolgantes, é possível tornar documentação divertida com doctests.

Além disso, do mesmo modo que dificuldades com TDD indicam limitações de design, dificuldades em escrever doctest apontam para problemas na API. Se foi difícil escrever um exemplo conciso e claro, cercado de prosa, para sua API, ela provavelmente é complicada demais, não é?

Dê uma chance a Doctest

No final, entendo as restrições de doctests. São inadequados para testes unitários, por exemplo. Entretanto, doctest torna tão fácil e divertido documentar! Não entendo por que continua tão impopular.

Ainda assim, a maior vantagem para mim é como doctest torna o processo de desenvolvimento mais fácil. Há um tempo atrás, brinquei que deveríamos criar o DocDD (documentation-driven development):

Com Doctest, isto não é apenas uma brincadeira.

Usando diferentes perfis no Firefox

Pessoas estão retornado ao Firefox. Excelente! Porém, os filhos pródigos podem se decepcionar. O Firefox, sinto dizer, é pesado e instável. Felizmente, existem maneiras de amenizar estes problemas, e uma delas é criar perfis diferentes.

É simples criar perfis utilizando a extensão ProfileSwitcher. Após instalá-la, vá ao menu “Arquivo”, opção “Abrir gerenciador de perfil” e escolha “Modo normal”. Ele perguntará se você quer fechar o perfil atual; responda que não. A tela abaixo aparecerá:

Screenshot do gerenciador de perfis

No screenshot já há dois perfis. O primeiro, “default” é o que uso para a maior parte das minhas tarefas: cuidar do e-mail, ver redes sociais, assistir MOOCs etc. Originalmente, eu só tinha esse perfil, e ele sempre tinha duzentas, trezentas abas. Invariavelmente, o navegador travava. Grande parte destas abas eram sobre programação e estavam relacionadas a projetos de software pessoais. Daí criei o outro perfil, “Dev.” É com ele que trabalho em meus projetos.

Agora, digamos que eu tenha de resolver problemas do trabalho em casa. Naturalmente, quero evitar proliferação de abas em algum perfil. Assim vou criar outro. Clico em “Create Profile“, e aparecerá esta tela meio assustadora:

Primeira tela do "Create Profile" Wizard: explica o que são perfis e como usá-los

Não tema, porém: basta clicar em “Next” e aparecerá a tela abaixo. Nela, damos o nome do novo perfil — no caso, chamaremos de “Work”.

Tela de seleção de nome de perfil. O nome selecionado é "Work".

E voilà! uma nova janela do Firefox aparecerá.

Vantagens dos múltiplos perfis

Esta nova janela roda como um processo diferente da janela original. Uma consequência é que, nesta nova janela, não estou autenticado nos sites em que já loguei. Isto é uma vantagem: agora posso autenticar em dois e-mails diferentes….

Two GMail accounts

…ou, como costumo fazer com meus perfis “Dev”, posso usar duas contas diferentes no Twitter – uma pessoal e outra mais profissional.

Duas contas do TwitterQuanto à estabilidade, há ao menos duas vantagens. Como cada perfil é um processo separado, se um deles travar o outro continuará funcionando. Fica-se até mais confiante para reiniciar o navegador: se uma janela ficar muito lenta, mato-a.

Além disso, as abas acabam divididas entre dois processos diferentes. As trezentas abas abertas em meu browser agora são aproximadamente cem em cada perfil. Isto consome mais recursos, mas o sistema operacional aguenta — o Firefox não.

Ademais, perfis separados ajudam a manter o foco. Quando estou no meu perfil “Dev”, não há notícias de jornais e amigos me distraindo: apenas desenvolvo e pesquiso. Já no meu perfil de trabalho, há apenas sites profissionais.

Sincronizando perfis

É útil compartilhar algumas informações entre os perfis — por exemplo, o histórico de navegação. Preocupações com privacidade à parte, o Firefox Sync é uma boa solução para isto. Basta ir no menu “Editar”, submenu “Preferências”; na aba que se abrir, escolha a opção “Sync” no meu da esquerda.

Tela de preferência do Sync Na primeira vez em que for configurar o Sync, será necessário criar uma conta. Habilite a opção “Escolha o que sincronizar”; assim você decidirá o que será enviado as servidores da Mozilla.

Tela para criação de conta do syncUm e-mail lhe será enviado. Nele, há um link para que confirme a conta. Clique no lnk. Após isto, é só voltar em nas preferências do Sync (menu “Editar”, submenu “Preferências” etc. etc.) para configurar os detalhes. No meu novo perfil, eu vou desabilitar a sincronização de abas, favoritos e senhas, por mera preferência pessoal. Também faço questão de dar um nome ao dispositivo, identificando a máquina e o perfil.

Tela de configuração do Sync

Movendo abas entre perfis

O Sync também é bom para enviar abas para outros perfis e dispositivos. Por exemplo, às vezes uma notícia interessante sobre política brasileira aparece na minha timeline técnica no Twitter. Não a leio então; ao invés disso, a envio para meu perfil pessoal. Outras vezes, ao pesquisar algo para minha tarefa no trabalho, encontro um link interessante mas não relacionado ao que busco. Para não me distrair, mando o link para i perfil “Dev” de casa.

Isto é feito através da extensão Send Tab to Device. Uma vez instalada, basta clicar em qualquer site com o botão direito em uma área vazia: ao final do menu de contexto estará a opção “Send Tab to Device:”

Opção "Send Tab to Device" no menu de contexto

Uma caixa de diálogo listando todos os perfis, de todas as máquinas, aparecerá. Escolhe-se um e clica-se em “OK.” A aba será aberta no outro perfil em breve. Quando faço isso, fecho a aba — só a verei no outro perfil.

"Send to Device" dialogNão é preciso sequer abrir a aba. Você pode clicar com o botão direito em um link e enviá-lo para outro dispositivo. Ideal para evitar o problema da Wikipédia:

send-link-to-device

Ponto extra: listando perfis no ícone do Ubuntu

Na prática, nunca utilizo o ProfileSwitcher: prefiro abrir os perfis diferentes diretamente do lançador do Firefox. Guiado por esta página de wiki, foi fácil configurá-lo no Ubuntu.

Primeiramente, abrimos o arquivo /user/share/applications/firefox.desktop e salvamos uma cópia em ~/.local/share/applications/firefox.desktop. Nela, procuramos pela linha abaixo:

Actions=NewWindow;NewPrivateWindow;

À linha adicionaremos uma opção. Ela pode ter qualquer nome, então vamos chamá-la de OpenWorkProfile. Note o ponto-e-vírgula ao final:

Actions=NewWindow;NewPrivateWindow;OpenWorkProfile;

Pronto, declaramos a ação. Agora basta adicionar as linhas abaixo ao final do arquivo:

[Desktop Action OpenWorkProfile]
Name=Open the "Work" profiler
Name[pt_BR]=Abrir perfil "Work"
Exec=firefox -no-remote -profile Work
OnlyShowIn=Unity;

Nas duas primeiras linhas, definimos o nome que aparecerá no menu — neste caso em inglês e português. Depois, o comando Exec instrui o Firefox a ser chamado com as opções -no-remote (para que a nova janela seja um processo diferente) e -profile (seguida pelo nome do perfil a ser utilizado).

Finalmente, chamamos sudo update-desktop-database. O menu de contexto do lançador será atualizado. Para vê-lo, basta clicar com o botão direito no ícone do Firefox, que as ações serão listadas.

Um menu de contexto onde se lista a nova ação

Na prática, eu também adicionei uma ação para o perfil de desenvolvimento. Há outra que abre o gerenciador de perfis, de modo que posso criar novos perfis ou escolher algum da lista. Você pode ver como fiz isso no arquivo completo.

all-actionsNão sei como fazer algo semelhante no Windows e no Mac OS X, mas estou certo de que é possível, e talvez não muito complicado.

É uma pena que a interface do Firefox não favoreça mais o uso de perfis. Ainda assim, depois de um pouquinho de trabalho, usar perfis é até fácil. Quando a empolgação com o Firefox passar e sua instabilidade tornar-se insustentável, pense com carinho na hipótese de utilizá-los.

Criando um feed RSS com Delicious, Yahoo! Pipes e Readability

A retirada das funcionalidades sociais (e posterior desligamento) do Google Reader matou uma das mais notáveis comunidades virtuais, mas trouxe vida a um mercado antes monopolizado. Por exemplo, uns malucos apareceram do nada com um clone chamado The Old Reader (e passaram por maus bocados por isso). Dada sua completude e qualidade (além do quase heroísmo e a notável competência dos criadores), escolhi o Old Reader como meu novo leitor.

The Old Reader fornece praticamente tudo que havia no Google Reader. Uma das poucas exceções é a habilidade de compartilhar links arbitrários: só posso compartilhar o que vier de meus feeds. Tentei usar o Delicious como alternativa. Adicionava meu link em uma tag específica, assinava o feed da tag e depois o compartilhava. O resultado, porém, não foi bom: o feed do Delicious só compartilha o link e o título, sem o conteúdo.

Feed RSS do Delicious, visto no Firefox

Feed RSS do Delicious, visto no Firefox

Aí entram os Yahoo! Pipes. Esta ferramenta maluca permite recuperar conteúdo da Internet (em especial, feeds), processá-los e publicá-los em, entre outros formatos, RSS. Cheguei a brincar com eles um pouco, há muito tempo; assinava um ou outro pipe, geralmente expandindo webcomics, mas nada sério e nada que eu tenha feito. Ainda assim, Pipes pareciam a solução: é fácil recuperar feeds em um pipe, e é fácil recuperar o conteúdo de um link.

Entretanto, eu não poderia simplesmente recuperar o conteúdo de um link e jogá-lo na descrição de um item RSS. Uma página contém muitas coisas: menus, cabeçalhos, propaganda… Para piorar, boa parte da formatação se perderia quando o HTML fosse incluído no feed. Experimentei copiar o conteúdo de interesse e colá-lo no comentário do Delicious, mas o resultado foi lamentável: não era possível adicionar parágrafos, muito menos HTML. Como, então, extrair o conteúdo?

Minha primeira abordagem foi adicionar, no campo Comment do meu bookmark, uma expressão XPath que retornasse o conteúdo de interesse.

Link to share through Delicious and the XPath expression to retrieve the relevant content.

Link to share through Delicious and the XPath expression to retrieve the relevant content.

Daí criei um pipe que percorria o feed com o módulo Loop e, para cada link, baixava o conteúdo. Para isto, usava o módulo XPath Fetch Page, que também retirava o elemento apontado pela expressão XPath do bookmark e o colocava na descrição do feed. Veja o screenshot do pipe:

Expansor dos feeds RSS do Delicious que usa XPath para extrair o conteúdo relevante.

Expansor dos feeds RSS do Delicious que usa XPath para extrair o conteúdo relevante.

O feed resultante era bom mas o processo não era satisfatório. Eu tinha de descobrir qual era o XPath mais adequado para cada página. Frequentemente, não tinha como testar a expressão, de modo que compartilhava links sem conteúdo. Não era possível criar uma expressão para todas as páginas, naturalmente, e Pipes não fornecem uma ferramenta poderosa o suficiente para isso. Como seria bom, pensei, ter algo como um Readability

Feed RSS do Delicious, expandido com XPath, visto no Firefox.

Feed RSS do Delicious, expandido com XPath, visto no Firefox. Agora sim!

E assim a resposta brilhou na minha frente.

Readability é uma aplicação  que torna legíveis artigos em páginas muito sobrecarregadas. Originalmente um mero bookmarklet, hoje é uma aplicação cliente-servidor multiplataforma. Parece mágica: depois de “descobrir” o que é relevante na página, Readability apresenta este conteúdo de maneira padronizada e legível. Eu o uso há muito tempo, tanto para tornar artigos legíveis quanto para enviá-los para meu Kindle.

Huffington Post, al natural e alterado por Readability.

Huffington Post, al natural e alterado por Readability

Para nossa sorte, Readability provê uma API para seu parser! Obtendo um token de autenticação – que é passado como um parâmetro na URL ou via POST – é possível fazer um número considerável de requisições e receber respostas em JSON. Assim,  para cada item no feed, montei uma URL para invocar a API (com o URL Builder) e a pus em um campo; num loop seguinte, invoquei a URL com Fetch Data e atribuí o campo content da resposta à descrição do item.

Este pipe usa a Parser API de Readability para expandir os links.

Este pipe usa a Parser API de Readability para expandir os links.

¡Voilà! Todas as minhas URLs aparecem lindamente expandidas no meu feed.

Depois, vi que o pipe poderia ser ainda mais incrementado: agora, o Delicious sharing tag expander é parametrizável (qualquer um pode usá-lo, passando o nome de usuário no Delicious, a tag escolhida e uma chave de API) e também adiciona a descrição do bookmark como um comentário.

Como extrair o conteúdo de uma página via Readability é uma necessidade comum, isolei esta parte em outro pipeURL Readabilitifier – e a utilizei como um módulo no original. Também a reaproveitei no Feed Readabilitifier, um pipe parametrizado que expande os links de um feed RSS em sua descrição. Uso-o especialmente para expandir feeds truncados (por exemplo, compare este RSS da Folha de São Paulo com a versão expandida) mas também pode ser usado como submódulo, como no meu pipe que expande um dos RSS mais chatos do mundo, o do Arts & Letters Daily.

Rodando testes antes do commit em Mercurial

Como todo mundo, entrei de cabeça na onda dos sistemas de versão distribuídos, como Git. Por várias razões, porém, meu DVCS “do coração” é Mercurial. (Razões as quais pretendo explicar em breve, por sinal).

De qualquer forma, vai aí minha primeira dica sobre o Mercurial. Frequentemente, estou consertando um bug em um projeto…

$ nano module1.c

…e, como uso TDD, rodo os testes:

$ make test
./run_all
................................................................................

OK (80 tests)

Quando os testes passam e o bug está corrigido, comito o código alterado:

$ hg commit -m "Bug #123 corrected"

Daí, passo a trabalhar em outra funcionalidade no mesmo projeto, escrevendo os testes primeiro:

$ nano test/module2.c

Novamente no espírito de TDD, vou rodar os testes, para quebrarem. Aperto então Control+P (ou ) para chegar ao comando que roda os testes novamente (make test). Infelizmente, porém, às vezes eu aperto Enter muito cedo, o que resulta em comitar minhas alterações recentes:

$ hg commit -m "Bug #123 corrected"

Isto é ruim porque cria uma versão com código quebrando no Mercurial. A solução paliativa é executar hg rollback (obrigado de novo, Stack Overflow!). Entretanto,  hg rollback é a pílula do dia seguinte do Mercurial: envolve vários riscos e deve ser usado com cuidado.

Então tive um estalo: por que não rodo os testes sempre antes do commit? A resposta é que eu comito por acidente, claro, mas posso fazer o Mercurial rodá-los antes de confirmar um commit: bastaria criar um hook. Para isto, alterei o programa que roda os testes para retornar um valor diferente de 0 (zero) quando os testes falhassem. Assim make test retorna um valor diferente de zero. Antes eu tinha algo assim:

void RunAllTests(void) {
    CuString *output = CuStringNew();
    CuSuite* suite = CuSuiteNew();
    CuSuiteAddSuite(suite, test_project_suite());
    // ... mais coisas aqui
    CuSuiteRun(suite);
    CuSuiteSummary(suite, output);
    CuSuiteDetails(suite, output);
    printf("%sn", output->buffer);
    CuStringDelete(output);
    CuSuiteDelete(suite);
}
int main(void) {
    RunAllTests();
    return 0;
}

Agora eu tinha algo assim:

int RunAllTests(void) { // Retorna int ao invés de void
    CuString *output = CuStringNew();
    CuSuite* suite = CuSuiteNew();
    CuSuiteAddSuite(suite, test_project_suite());
    // ... mais coisas aqui
    CuSuiteRun(suite);
    CuSuiteSummary(suite, output);
    CuSuiteDetails(suite, output);
    printf("%sn", output->buffer);
    CuStringDelete(output);
    CuSuiteDelete(suite);
    return suite->failCount; // Retorna contagem de erros
}
int main(void) {
    return RunAllTests(); // Retorna contagem
}

(Caso você esteja se perguntando, estou utiliando CuTest, o melhor e mais cacofônico framework de testes para C.)

Após fazer esta alteração, adicionei as linhas abaixo no arquivo .hg/hgrc do projeto:

[hooks]
pretxncommit.surefire = make test

O que acontece quando vou comitar erroneamente agora? Veja só:

$ hg commit -m "Bug #123 fixed"
cc -c  -Wall -std=c99 -Iinclude -Icutest src/test/util.c -o test_util.o
cc   run_all.o test_secretary.o  CuTest.o libsecretary.a   -o run_all
./run_all
...........................................................F....................

There was 1 failure:
1) test_util_beginning_of_day: src/test/util.c:34: expected 1 but was 0

!!!FAILURES!!!
Runs: 80 Passes: 79 Fails: 1

make: *** [test] Error 1
transaction abort!
rollback completed
abort: pretxncommit.surefire hook exited with status 2

Os testes falham, o que faz o programa que os roda retornar um valor diferente de zero. O programa, falhando, faz o make falhar, retornando também um valor diferente de zero. Como o make falhou, o hook falha também, impedindo que Mercurial siga em frente com o commit. Oras, o hook foi executado na fase pretxncommit (pre transaction commit), logo antes de Mercurial registrar o commit do código. Como o hook falhou, o commit não é efetivamente feito e meu histórico fica limpo.

Este exemplo utiliza testes escritos em C, mas serve para qualquer projeto. Eventualmente, não se pode alterar o programa que roda os testes, mas pode-se criar um script que lê a saída dos testes e retorna o valor correto.

Problemas na instalação do módulo de SNMP do Apache

Se você está tentando instalar o módulo SNMP do Apache e seguiu todas as instruções daqui, mas encontrou no error.log uma mensagem como

Cannot find module (APACHE2-MIB): At line 0 in (none)
APACHE2-MIB::serverName.0: Unknown Object Identifier
APACHE2-MIB::serverTmpDir.0: Unknown Object Identifier
APACHE2-MIB::agentHttpAddress.0: Unknown Object Identifier
APACHE2-MIB::serverStatus.0: Unknown Object Identifier
APACHE2-MIB::serverVersion.0: Unknown Object Identifier
APACHE2-MIB::serverBuilt.0: Unknown Object Identifier
APACHE2-MIB::serverRoot.0: Unknown Object Identifier
APACHE2-MIB::serverPidfile.0: Unknown Object Identifier
APACHE2-MIB::serverRestart.0: Unknown Object Identifier
APACHE2-MIB::totalServerPorts.0: Unknown Object Identifier
APACHE2-MIB::serverPortNumber.1: Unknown Object Identifier

provavelmente o MIB do Apache não está onde devia.

Dentro do diretório onde você compilou o mod-ap2-snmp, haverá um subdiretório chamado mib. Dentro dele, haverá um arquivo chamado APACHE2-MIB.TXT. Copie esse arquivo para o diretório dos MIBs do Net-SNMP. (Na minha máquina, esse diretório era /usr/local/share/snmp/mibs/)

Dois detalhes:

  • a extrensão do arquivo deve estar em minúsculas – então a cópia se chamará APACHE2-MIB.txt. Não sei por que o pessoal do mod-ap2-snmp colocou esse nome com extensão em maiúscula, deve ser bug.
  • o arquivo deve ser legível para todos: rode chmod a+r /usr/local/share/snmp/mibs/APACHE2-MIB.txt nele. Talvez essa seja uma permissão muito aberta e só um grupo precise vê-lo, mas eu a apliquei e o snmpget conseguiu lê-lo. Sugestões sobre permissões mais seguras são bem-vindas, mas não sei se é necessário restringir a visibilidade desse arquivo…

Esses passos resolveram as mensagens acima. Sobraram essas:

[Thu Mar 04 00:16:41 2010] [error] AP2_SNMP: Can't set MIB value.
[Thu Mar 04 00:16:41 2010] [error] APACHE2-MIB::serverStatus.0
[Thu Mar 04 00:16:42 2010] [error] AP2_SNMP: Can't set MIB value.
[Thu Mar 04 00:16:42 2010] [error] APACHE2-MIB::serverName.0
[Thu Mar 04 00:16:43 2010] [error] AP2_SNMP: Can't set MIB value.
[Thu Mar 04 00:16:43 2010] [error] APACHE2-MIB::serverName.0
[Thu Mar 04 00:16:43 2010] [error] AP2_SNMP: Can't set MIB value.
[Thu Mar 04 00:16:43 2010] [error] APACHE2-MIB::serverTmpDir.0
[Thu Mar 04 00:16:43 2010] [error] AP2_SNMP: Can't set MIB value.
[Thu Mar 04 00:16:43 2010] [error] APACHE2-MIB::agentHttpAddress.0
[Thu Mar 04 00:16:43 2010] [error] AP2_SNMP: Can't set MIB value.
[Thu Mar 04 00:16:43 2010] [error] APACHE2-MIB::serverStatus.0
[Thu Mar 04 00:16:43 2010] [error] AP2_SNMP: Can't set MIB value.
[Thu Mar 04 00:16:43 2010] [error] APACHE2-MIB::serverVersion.0
[Thu Mar 04 00:16:43 2010] [error] AP2_SNMP: Can't set MIB value.
[Thu Mar 04 00:16:43 2010] [error] APACHE2-MIB::serverBuilt.0
[Thu Mar 04 00:16:43 2010] [error] AP2_SNMP: Can't set MIB value.
[Thu Mar 04 00:16:43 2010] [error] APACHE2-MIB::serverRoot.0
[Thu Mar 04 00:16:43 2010] [error] AP2_SNMP: Can't set MIB value.
[Thu Mar 04 00:16:43 2010] [error] APACHE2-MIB::serverPidfile.0
[Thu Mar 04 00:16:43 2010] [error] AP2_SNMP: Can't set MIB value.
[Thu Mar 04 00:16:43 2010] [error] APACHE2-MIB::serverRestart.0
[Thu Mar 04 00:16:43 2010] [error] AP2_SNMP: Can't set MIB value.
[Thu Mar 04 00:16:43 2010] [error] APACHE2-MIB::totalServerPorts.0
[Thu Mar 04 00:16:43 2010] [error] AP2_SNMP: Can't set MIB value.
[Thu Mar 04 00:16:43 2010] [error] APACHE2-MIB::serverPortNumber.1

O problema aqui é que iniciei o Apache antes de iniciar o agente snmp (snmpd). Basta derrubar o apache, levantar o agente (no caso, na minha máquina, ele está em /usr/local/sbin/snmpd) e levantar novamente o Apache. Feito isso, comece a recuperar as informações!

$ snmpget -v 3 -u usuario -l authNoPriv -a MD5 -A senha  localhost APACHE2-MIB::serverName.0
APACHE2-MIB::serverName.0 = STRING: 127.0.1.1

Note, porém, que são necessários usuário e senha para trabalhar com SNMPv3 – que é o padrão, ao que parece, para o mod-ap2-snmp e é o melhor protocolo, de qualquer forma. Para criá-los, veja esse link.

Congelamento de disco rígido

Afirma a lenda que, se seu disco rígido parar de funcionar, você ainda terá a oportunidade de recuperar dados importantes congelando-o. Mas será verdade?

Tive a oportunidade de conferir. Depois de três dias trabalhando, fui tentar enviar minhas alterações de um projeto ao Subversion. O servidor estava fora do ar, mas não era um grande problema: eu poderia enviar as alterações no dia seguinte. Chego no dia seguinte no trabalho para enviar os dados e… bem, o HD da minha máquina havia pifado.

Fria: um de nós dois teria de entrar em uma

Fria: um de nós dois teria de entrar em uma

Depois de dias tentando recuperar os dados do HD, desisti. Seria mais fácil reescrever tudo o que eu havia feito, pelo visto. Já aceitando meu destino, fui fazer outras tarefas.

Algum tempo depois, encontro um amigo meu. Aproveito, conto minha história desinteressadamente, como uma curiosidade. Meu amigo me pergunta:

Já tentou congelar o HD?

Já havia ouvido falar desta técnica. Se o seu disco rígido parou de funcionar. tente colocá-lo no congelador. Esse meu amigo sempre garantiu que o truque funcionava, mas nunca vi ninguém fazê-lo…

Pois bem, como pior não poderia ficar, resolvi tentar. Peguei o HD, embrulhei-o muito bem em uma sacola plástica, pus no congelador e saí para trabalhar em outro projeto, em outro lugar.

Ao final da tarde, voltei à empresa. Tirei o disco rígido do congelador, coloquei-o no notebook e liguei a máquina com um CD do Ubuntu. Quando fui ver… funcionou! Consegui acessar meus arquivos, e os copiei para outra máquina com scp!

Se você for tentar fazer isso algum dia, tome cuidado de embrulhar muito bem o disco, para que não molhe. Note que isto provavelmente não deve funcionar sempre… mas funcionou uma vez, isto posso garantir!

Fazendo DMA funcionar no Debian/Ubuntu

Até o meio do ano passado, meu computador era um Athlon XP 2.3 GHz com 128 MB de memória e 40 GB de HD. Fraquinho, mas funcionava até bem. Eu continuaria utilizando-o cotidianamente se não tivesse de utilizar o OpenOffice.org e o Eclipse. Além disso, estava querendo brincar com outros sistemas operacionais, e preferia virtualizá-los. Isso obviamente era inviável na máquina antiga.

Vendo que não havia mais para onde escapar, comprei outro computador. O computador era de um amigo meu, tinha 80 GB de HD, 1 GB de memória, gravadora de DVD e uma placa GForce 2200, ou algo assim. É uma máquina ótima para meus objetivos. Formatei as partições, instalei um Debian e configurei a máquina.

Notei, porém, que o computador estava bastante lento, muito mais lento que minha máquina antiga. Depois de pesquisar, descobri que era um problema com o HD e DMA. O syslog estava cheio de mensagens como:

Apr 8 00:17:41 localhost kernel: ide: failed opcode was: unknown
Apr 8 00:17:47 localhost kernel: hda: status timeout: status=0xd0 { Busy }
Apr 8 00:17:47 localhost kernel:
Apr 8 00:17:47 localhost kernel: ide: failed opcode was: unknown
Apr 8 00:17:47 localhost kernel: hdb: DMA disabled
Apr 8 00:17:47 localhost kernel: hda: drive not ready for command
Apr 8 00:17:47 localhost kernel: ide0: reset: success
Apr 8 00:14:07 localhost kernel: hdb: dma_timer_expiry: dma status == 0x41
Apr 8 00:14:07 localhost kernel: hdb: DMA timeout error
Apr 8 00:14:07 localhost kernel: hdb: dma timeout error: status=0x58 { DriveReady SeekComplete DataRequest }

Aparentemente, havia algum erro ao carregar os módulos do DMA. Pesquisando sobre o problema, vi sugestões para verificar a saída do comando hdparm -i /dev/hda. O resultado foi algo como:

/dev/hda:

Model=SAMSUNG SP0802N, FwRev=TK100-24, SerialNo=S00JJ10XB83245
Config={ HardSect NotMFM HdSw>15uSec Fixed DTR>10Mbs }
RawCHS=16383/16/63, TrkSize=34902, SectSize=554, ECCbytes=4
BuffType=DualPortCache, BuffSize=2048kB, MaxMultSect=16, MultSect=off
CurCHS=16383/16/63, CurSects=16514064, LBA=yes, LBAsects=156365903
IORDY=on/off, tPIO={min:240,w/IORDY:120}, tDMA={min:120,rec:120}
PIO modes:  pio0 pio1 pio2 pio3 pio4
DMA modes:  mdma0 mdma1 mdma2
UDMA modes: udma0 udma1 udma2 udma3 udma4 *udma5
AdvancedPM=no WriteCache=enabled
Drive conforms to: ATA/ATAPI-7 T13 1532D revision 0:  ATA/ATAPI-1,2,3,4,5,6,7


* signifies the current active mode

Em resumo, a linha

UDMA modes: udma0 udma1 udma2 udma3 udma4 *udma5

indicava que o módulo do DMA estava carregado! O que, então, estava dando errado?

Em uma breve pesquisa no Google, encontrei essa thread nos Ubuntu Forums. Nela, alguem sugere verificar se o HD estava configurado para Master ao invés de Cable Select. Meu HD já estava jumpeado e cabeado como Master. Todas as outras soluções que encontrei não funcionaram para ninguém – inclusive, não funcionavam para mim. O que fazer?

Pois bem, em um experimento, eu coloquei o HD como Secondary Master; antes, reconfigurei o GRUB para que o root do kernel passasse a ser /dev/hdc1 e editei o /etc/fstab trocando /dev/hda por /dev/hdc (e vice-versa). Liguei a máquina e… Voi là! O problema sumiu!

Hoje, porém, fui tentar configurar o HD como Primary Master e pesquisar por uma solução menos estranha. Infelizmente, tive novamente o mesmo problema com DMA, não importasse o que eu fizesse. Desisti e voltei o HD para Secondary Master – afinal, estava só explonrando possibilidades. Quando reinicio a máquina, o erro de DMA reaparece, agora com o HD como Secondary Master! Desligo então a máquina, tiro e recoloco o cabo flat no HD e reinicio a máquina. Voi là!2 o problema foi resolvido novamente.

Então, se você está tendo esse problema, tente tirar e recolocar o cabo flat, que pode estar frouxo. Se isso não funcionar, dá uma olhada na thread citada, que ela tem boas dicas. Entretanto, se ela não te ajudar, tente colocar o HD como Secondary Slave (ou como Master Slave, se já estiver como Secondary Slave). Vai que funciona, não é?

HTH. Até mais!

Dividindo uma imagem em várias páginas com ImageMagick

Esses dias, um amigo me pergunta no Google Talk:

duvida de linux
tenho uma imagem
bem grande
e quero que ela seja impressa
em varias paginas
tipo um pedaco numa pagina
outro pedaco em outra

(Antes de prosseguir, uma nota: uma imagem nos formatos JPEG, PNG etc. não possui um tamanho, mas sim uma resolução. Assim, você pode exibir a imagem em qualquer tamanho, mas a resolução provavelmente vai impor limites à qualidade da imagem. Do mesmo modo, uma imagem de, digamos 1900 x 1200 pixels não tem um tamanho definido, mas sim uma resolução, e pode ser impressa tanto numa folha A3, folha A4 ou qualquer outra, variando apenas a qualidade da impressão.)

Quando alguém me fala de processar imagens no Linux, a primeira coisa que me vem a mente é a suíte ImageMagick. Entre as ferramentas do ImageMagick, a que mais uso é o convert, um programa de linha de comando que permite executar inúmeras operações sobre imagens, como converter de formato, redimensionar, gerar negativo, extrair um pedaço etc. etc.

A minha abordagem seria, então, dividir a imagem em pequenas imagens contíguas e retangulares. Para imprimir numa folha A4, por exemplo, as pequenas imagens deveriam ter proporções de uma folha A4. Como fazer isso?

O primeiro passo é descobrir como recuperar um retângulo de uma imagem. Isto é bem simples com o convert, basta utilizar a opção -crop. Para nossa missão, nós usaremos essa opção com uma string na forma

<width>x<height>+<x>+<y>

onde <width> é a largura da imagem resultante, em pixels; <height> é a altura da imagem resultante, em pixels; <x> e <y> são as coordenadas do pixel a partir de onde a imagem será cortada. Desse modo, se quisermos extrair um retângulo de 100 x 100 pixels de uma figura no arquivo lena.png de 512 x 512 pixels a partir do centro, faríamos algo como

convert -crop 100x100+256+256 lena.png output.png

Se a imagem for essa:

Lena Söderberg, SFW

Lena Söderberg, SFW

o resultado do comando acima será:

Resultado do corte da imagem

Resultado do corte da imagem

(Note que a opção -crop pode ser utilizada de outras maneiras. Confira na documentação da opção.)

Agora, precisamos gerar várias imagens a partir da primeira. Para não ficar fazendo isso na mão, podemos usar o comando for junto com o comando seq. (Se você não sabe usar o comando for e o comando seq do bash, essa página explica muito bem como funcionam.) Desse modo, se eu quisesse dividir a imagem da Lena acima em retângulos de 100 x 200 pixels, eu faria algo como:

for i in `seq 0 $((512/100))`; do
    for j in `seq 0 $((512/200))`; do
        convert -crop 100x200+$((i*100))+$((j*200)) 
            lena.png lena-$j-$i.png
    done
done

Estou dividindo a imagem numa planilha de imagens. Para cada linha, eu vou gerar 512/100 +1 = 6 imagens; para cada coluna, eu vou gerar 512/200 +1 = 3 imagens. (Se você não entendeu o “+1”, lembre-se que estamos contando a partir de zero, como em C, Java etc). A primeira imagem será o retângulo que vai do pixel de coordenada (0, 0) até o pixel de coordenada (100, 200); a segunda imagem irá do pixel de coordenada (100, 0) até o pixel de coordenada (200, 200); do mesmo modo, a primeira imagem da linha abaixo irá do pixel de coordenada (0, 200) até o pixel de coordenada (100, 400) etc. etc., contando as coordenadas a partir do canto superior esquerdo.

Ao rodar isso, gerei dezoito imagens. O resultado, que uni em uma imagem só por praticidade, pode ser visto abaixo. As linhas brancas separam as imagens geradas.

A borda branca separa as inúmeras imagens que foram geradas

Lena, dividida

(Note como o convert, ao encontrar um retângulo com um pedaço vazio, gera a maior figura possível. Isso pode ser notado nas bordas direita e inferior.)

Vamos generalizar o algoritmo. Faremos um script que recebe como parâmetro as dimensões originais do arquivo, as dimensões das imagens a serem geradas e o nome do arquivo original. O resultado será algo como

file=$1
originalx=$2
originaly=$3
slicex=$4
slicey=$5
numberx=$((originalx/slicex))
numbery=$((originaly/slicey))

for i in `seq 0 $numberx`; do
  for j in `seq 0 $numbery`; do
    convert -crop ${slicex}x${slicey}+$((i*slicex))+$((j*slicey)) 
        $file $file-$j-$i.png
  done
done

Agora, é só rodar o script dando como argumentos as dimensões originais e algumas dimensões proporcionais ao papel que queremos utilizar. Obteremos imagens que caberão perfeitamente no papel. (Vale lembrar que uma imagem não tem um tamanho em si, mas uma resolução: a qualidade final pode não ficar muito boa, dependendo da resolução da sua imagem.)

Para facilitar o trabalho, vamos juntar todas as imagens em um único arquivo PDF, o que facilitaria a impressão. O convert do ImageMagick pode fazer isso de maneira bem simples: se invocarmos o  convert passando como parâmetro uma série de imagens e, ao final, o nome de um arquivo com extensão .pdf, o resultado será um arquivo PDF com uma imagem por página. Assim sendo, ao chamar algo como

convert fig1.png fig2.png fig3.png one-per-page.pdf

one-per-page.pdf vai conter, ao vai conter, ao final, três páginas. Na primeira, estará fig1.png; na segunda, teremos fig2.png e, na terceira página, estará fig3.png.

Assim, vamos complementar o script criando uma variável que armazena o nome de todos os arquivos gerados (separados por um espaço em branco). Após gerar todas as imagens, vamos colocá-las todas em um arquivo PDF. O script final, você pode vê-lo no pastebin.

Eu apliquei o script sobre nossa imagem, usando as dimensões proporcionais a papel A4 (210 x 297 mm):

./split.sh lena.png 512 512 210 297

O resultado pode ser encontrado aqui. As páginas acabaram em formato A7, mas, se mandar imprimir, elas preencherão toda a folha A4 sem problemas.

O script está disponível para quem quiser fazer qualquer uso dele. Ademais, pode ser melhorado: é possível, por exemplo, fazer com que o ImageMagick descubra ele mesmo as dimensões iniciais da imagem. Entretanto, acredito que ele já possa ser bem útil

Obrigado, Renan Mendes, pela dica sobre como gerar PDFs. A todos, até mais!

Módulos do VirtualBox no Debian Lenny

Eu estava tentando usar o VirtualBox no meu computador de casa, que roda Debian Lenny. No começo, até funcionou, mas o kernel Linux foi atualizado em algum momento, e os módulos do VirtualBox pararam de funcionar.

Instalei todos os módulos possíveis do repositório, mas o VirtualBox se recusava a funcionar. Procuro no Google alguma solução, e não encontro nada, exceto que devo recompilar os módulos. Eu realmente não estava disposto a fazer isso…

Entretanto, pesquisa vai, pesquisa vem, encontrei uma solução melhor. Envolve compilar os módulos também, mas de maneira mais “debiana”.

Para compilar os módulos, primeiro, atualize a referência aos pacotes com os repositorios:

# apt-get update

Agora, atualize os pacotes instalados em sua máquina:

# apt-get upgrade

Feito isso, instale o pacote com o código-fonte dos módulos do VirtualBox:

# apt-get install virtualbox-ose-source

O pulo do gato é utilizar a ferramenta module-assisant para compilar o módulo. Uma vez que o código-fonte do módulo esteja instalado, basta executar:

# m-a a-i virtualbox-ose

O m-a compila o módulo. A opção a-i diz ao module-assistant para instalar os módulos automaticamente.

Voilà! Seus módulos estão funcionando. No máximo, vai precisar carregar os módulos:

# modprobe vboxdrv

Se isso resolver seu problema, agradeça ao Daniel Baumann lá do e-mail. Tudo bem que dizer que tudo isso é óbvio foi exagero dele, mas a ajuda valeu bastante 🙂