Naive Crawler in Clojure

Algum tempo atrás praticando clojure tentei fazer um crawler de web bem simples e sem dependências, apenas para testar alguns recursos. O resultado foi um programa em clojure capaz de navegar nos links a partir de uma página e salvar os conteúdos.

Resolvi acertar uns detalhes e colocar no github o naive-crawler.

O desenvolvimento foi bem incremental, primeiro as funções para lidar com links:

  • make-url recebe uma url atual e um link achado para definir o destino.
  • find-links acha links através de regex, bem falho na verdade.
  • find-urls combina as duas anterior já retornando as urls para serem navegadas .
  • same-domain? checa se o destino é do mesmo domínio da página atual.

Depois persistência:

  • make-db cria um banco de dados.
  • insert insere uma referencia a uma url no db, se ainda não existir.
  • get-it retorna o conteúdo da url salva no db.
  • non-crawled filtra as urls ainda sem conteúdo.
  • set-content salva o conteúdo no db.

Crawler em si:

  • save-page busca o conteúdo da pagina atual, salva-o e captura as urls dessa página.
  • start/-main iniciam o loop do crawler

O desenvolvimento foi bem incremental, no começo apenas fazia url a url, depois adicionei o “same-domain”, controle de erro bem básico, paralelismo simples (testei outras formas também) e depois salvar em disco.

Interessante seria salvar as URLs numa fila (PerssitenQueue? Redis?) e outras threads consumirem essa fila e salvar o conteúdo em um banco de dados (sqlite? neo4j?). E também não tem testes, pois fui fazendo tudo no REPL e esqueci de escrever os testes.

Livros e e-books

Ultimamente tenho lido muitos e-books sobre programação, pelo simples fato da grande oferta de material em inglês, mais barato e de acesso instantâneo, como alguns são muito bons resolvi compartilhar alguns dos títulos:

Seven Languages in Seven Weeks – Apesar do nome sugestivo o autor declara logo no começo que você não vai aprender sete linguagens em sete semanas. É um livro para programadores com alguma experiência que querem principalmente conhecer linguagens em outros paradigmas, assim ele vai gradativamente apresentando sete linguagens que vão cada vez mais se diferenciando das mais comuns hoje em dia e apresentando o ponto forte de cada uma e o que aproveitar delas. Um dos melhores livros que já li, recomendo muito! As linaguagens são: Ruby, IO (awesome!), prolog, Scala, Erlang, Clojure e Haskell.

Web design for developers– Programadores não entendem design (no sentido de arte) então esse livro passa os conceitos básicos para pelo menos seu projeto não ser feito e ser usado. É um bom livro.

Programming Clojure – O livro mais básico de clojure, bom para quem quer começar.

Clojure in Action – O livro mais “real” de clojure, o estudo é profundo e os exemplos são bem reais e muito uteis. Trata de MySql, Hbase, redis, http (compojure, ring e hiccup), messaging com rabbitmq e distribuição de trabalhos entre maquinas, Map/reduce entre ooutros. O mais completo eu acho sobre clojure.

The Joy of Clojure – O livro mais clojure de clojure, é o que melhor entende a ideia da linguagem e como melhor aproveita-la, meu favorito nessa área.

CouchDB The Definitive Guide – Aborda tudo que deve saber para fazer uma aplicação com CouchDB. Boa leitura.

ExtJS in Action – Achei meio fraco.

Pois é, bastante clojure. Alguns ainda na prateleira para ler (sem ordem):

  • Pragmatic Guide to Git
  • Pragmatic Guide to Javascript
  • Erlang and OTP in Action
  • Programing Erlang
  • EventSourcing in Action
  • Real-World Functional Programing
  • Secrets of the JavaScript Ninja
  • Hadoop in Action
  • Jquery Novice to Ninja
  • Lucene in Action
  • On Lisp

Realmente estou precisando ler os de Javascript.

Terminais múltiplos com screen

Quem usa Linux no desktop pode livremente abrir mais de um terminal ou mais de uma aba no terminal, quem está acostumado com o Vim e o Emacs também pode ter múltiplas janelas dentro do editor. Mas as vezes é útil em um mesmo terminal, como numa sessão ssh, você ter mais de uma janela, ai entra o screen!

O uso do screen é bem simples, ele cria uma sessão de shell (bash, zsh, etc…) da qual você pode se “desplugar” e voltar depois, aonde você inclusive manter múltiplas sessões e janelas.

Para iniciar uma sessão usando um apelido, basta usar o comando “screen -S nome” e para reconectar a uma sessão interrompida “screen -r -S nome”.

Os comandos para manipular o screen começam com CTRL+A, você pode se desconectar de uma sessão sem encerrá-la com “CTRL+A CTRL+D”, pode salvar o histórico usando “CTRL+A SHIFT+H”, e ao encerrar a sessão tudo vai para um log.

Dentro de uma sessão você pode iniciar outro shell usando “CTRL+A CTRL+C”, ver a lista de shells usando “CTRL+A CTRL+W”, ir para o próximo shell usando “CTRL+A CTRL+A”.

Você pode ter janelas em uma sessão como no vim/emacs. Divide a janela horizontalmente usando “CTRL+A SHIFT+S”, divida a janela verticalmente usando “CTRL+A | “ (é o caractere de “pipe”, sabe?). Você alternar entre janelas usando “CTRL+A CTRL+I”, para fechar a janela usa-se “CTRL+A SHIFT+X”. Veja que uma janela começa sem shell nenhum.

O screen permite ainda que mais de um usuário se conecte a uma sessão, para permitir que sua sessão tenha mais de um usuário você pode usar “CTRL+A :multiuser on”, e adicione um usuário usando “CTRL+A :acladd user”, agora o outro usuário pode por exemplo fazer ssh para sua maquina e entrar na sua sessão usando “screen -x user/nome_da_sessao”. Para usar o multiusuário o screen deve rodar com “setuid”.

Lembre de não sair usando “ctrl+d” ou o screen vai encerrar, de agora em diante use o “ctrl+a ctrl+d” para manter a sessão rodando.

Persistência de dados com Redis

Conhecendo o Redis

Redis é um banco de dados para persistência de dados no modelo Key/Value escrito em C ANSI para sistemas POSIX, com uma interface de rede embutida. Isso é o que diz no site.

O que quer dizer que Redis é um "Key/Value storage engine", parecido com o memcached mas persistente. Suporta dados em String e Intergers, Lists, Sets e Ordered Sets além de Hash Tables (que suportam apenas Strings e Intergers). Usa operações atômicas (uma alteração por vez), e suporta get/set, push/pop, add/remove, union, intersect, diffs e muito mais, incluindo opções de ordenação.

Ele mantêm, por padrão, todos os dados em memória, e salva snapshots para o disco eventualmente de forma assíncrona, ou escrevendo cada mudança para um "Append Only File" (o que é mais interessante). Ele é capaz de refazer o AOF em background. Também tem suporte trivial a replicação master-slave.

O Redis é insamente rápido para escritas e leituras(muito mesmo!), tem bibliotecas para "tipo" todas as linguagens, é desenvolvido em ritmo alucinante e não funciona bem no windows.

A prioridade para próxima versão é o projeto Redis Cluster.

Instalação e Configuração do Redis

Sendo um sistema de evolução muito rápido fique de olho no código-fonte no no site oficial e nos branches do github e pegue a ultima versão. Aqui vou usar a versão 2.0 do github, seguem passos para instalação:

$ git clone git://github.com/antirez/redis.git
$ cd redis
$ make

Pronto o sistema está construído. Você pode agora configurar o servidor redis pelo arquivo redis.conf. Alguns detalhes interessantes da configuração:

Uma boa opção é usar o Redis com AOF (append only file), a cada escrita ele vai adicionar esse dado ao log que será a forma de persistência. Basta usar a opção "appendonly" como "yes".

Importante novidade no Redis é o uso de memoria virtual (VM), como o Redis mantém tudo em memoria isso limita a capacidade de armazenamento a memoria disponível. Usando VM o Redis pode armazenar mais dados que a memoria disponível fazendo paginação de forma inteligente.

Para ativar a VM use a opção "vm-enable" como "yes". Você pode definir o máximo de memoria para a VM usando a opção "vm-maxmemory", se esse valor for zero o sistema vai manter apenas as chaves em memoria e todo os dados em disco. Tem ainda a opção do número de threads trabalhando na paginação com "vm-max-threads", você pode usar seu numero de "cores" (+2) ou 0 para ter blocking de leitura.

Usar paginação pode ser mais lento, mas ainda assim é bem rápido e permite uma capacidade maior de armazenamento. Claro, só vai ficar mais lento se usar o sistema até ser necessário paginar.

O ideal é usar o sistema com AOF para maior consistência e durabilidade (não há impacto na performance) e usar VM com um número razoável de threads, para caso seu sistema cresça bastante você não ter nenhum susto.

Você tem ainda a opção "slaveof" para torná-lo slave de outro servidor e pode definir a opção "requirepass" para usar autenticação.

Para iniciar o servidor basta executar:

$ ./redis-server

Feitas as configurações você pode rodar os testes de sistema(demora bastante!):

$ make test

E tem também um utilitário de benchmark, bom para testar diferentes configurações:

./redis-benchmark -n 1000 -q

Usando Redis

Com o servidor iniciado (./redis-server) você pode testar o redis usando o shell interativo, basta lançá-lo usando:

$ ./redis-cli

Você pode passar para o cli as operações que quer testar, ex:

$ ./redis-cli set foo "bar"
$ ./redis-cli get foo

Ou apenas usar o redis-cli para entrar em um prompt interativo.

Primeiro vamos ver um pouco dos tipos de dados do Redis.

String: O tipo mais básico, não tem mistério, são strings ou inteiros. É, inteiros também.

redis> set foo "bar"
OK
redis> get foo
"bar"
redis> set hello "Hello, "
OK
redis> append hello "Redis!"
(integer) 13
redis> mget foo hello
1."bar"
2."Hello, Redis!"
redis> substr hello 2 4
"llo"
redis> set um 1
OK
redis> incr um
(integer) 2
redis> incrby um 5
(integer) 7
redis> decr um
(integer) 6
redis> get um
"6"

List: Listas no Redis não são arrays, mas Linked Lists. São mais simples e rápidas, não são associativas, aceitam Strings (que podem ser inteiros, lembra). São muito rápidas para acesso nas pontas e para adição de itens e não tão rápidas para acessos aleatórios (pelo índice).

redis> rpush nums 1
redis> rpush nums 2
redis> lpush nums 0
redis> lrange nums 0 2
1."0"
2."1"
3."2"
redis> lrange nums 0 1
1."0"
2."1"
redis> lrange nums 0 -1
1."0"
2."1"
3."2"

Explicando um pouco, "rpush" insere um item a direita na lista (final), "lpush" a esquerda (inicio) e "lrange" retorna parte da lista.

redis> rpush msgs "Oi"
redis> rpush msgs "Meu nome"
redis> rpush msgs "É"
redis> rpush msgs "Redis!"
redis>lrange msgs 0 -1
1."Oi"
2."Meu nome"
3."É"
4."Redis!"
redis> rpop msgs
"Redis!"
redis> rpop msgs
"É"
redis> lpop msgs
"Oi"

O comando "lpop" retorna o elemento a esquerda da lista (começo) e o remove, o "rpop" o mesmo para a direita (final).

Set: É uma coleção de Strings não ordenadas.

redis> sadd users "diogok"
redis> sadd users "voce"
redis> sadd users "ninguem"
redis> smembers users
1."ninguem"
2."voce"
3."diogok"
redis> sadd admins "ninguem"
redis> sinter users admins
1."ninguem"
redis> sunion users admins
1."ninguem"
2."diogok"
3."voce"
redis> sdiff users admins
1."diogok"
2."voce"
redis> sdiff admins users
(empty list or set)
redis> srem "ninguem"

A vantagem dos sets é permitir operações de grupos como uniões (sunion), intercessões (sinter) e diferenças (sdiff).

Sorted Set: Parecido com Sets porem ordenados.

redis> zadd log 2210 "login"
redis> zadd log 2215 "view email"
redis> zadd log 2225 "view twitter"
redis>zrange log 0 -1
redis>zrevrange log 0 -1
redis> zrem log 2210

Hash: É um tipo novo (>= 2.0) que define hash tables, que contém chaves e conteúdos string.

redis> hset diogok name "Diogo"
redis> hmset diogok email "manifesto at manifesto.blog.br" password "123"
redis> hget diogok name
"Diogo"
redis> hkeys diogok
1."name"
2."email"
3."password"
redis> hvals
1."Diogo"
2."manifesto at manifesto.blog.br"
3."123"
redis> hgetall diogok
1."name"
2."Diogo"
3."email"
4."manifesto at manifesto.blog.br"
5."password"
6."123"
redis> hset diogok counter 1
redis> hincrby diogok counter 3
redis> hget diogok counter
4
redis> hdel diogok counter

Conclusão

Isso cobre o básico do uso do Redis, agora é escolher a biblioteca para sua linguagem e consultar a documentação do Redis e a lista completa de comandos.

O Redis se mostra muito interessante por se extremamente rápido, principalmente na escrita, e seus tipos de dados como set e ordered list o torna muito prático para certas aplicaçções:

  • Os Ordered Set são ótimos para listas manterem ordem temporal (noticias, tweets, logs…)
  • Os Sets servem muito bem para traçar grafos e comparar listas (amigos em comum, aprovações…)
  • Lists são perfeitos para queues ou listas de processamentos (urls para minerar, mensagens para enviar, dados a processar…)
  • Hash Tables com Strings armazenam dados genéricos de forma mais compacta.
  • Cada um desses casos tem performance excelente!

O mais importante ao adotar o Redis(ou qualquer “nosql” na verdade) é entender a nova modelagem. Um sistema em memória não é bom entulhar com dados mal formatados, deve-se retirar periodicamente esses dados para uma armazenagem secundária. Deve-se pensar também numa estratégia consisa de chaves, ex:

redis> sadd users diogok
redis> hmset users:diogok name "diogo" web "manifesto.blog.br"
redis> sadd users voce
redis> hmset users:voce name "voce" web "google.com"
redis> sadd conhecidos:diogok voce
redis> sinter conhecidos:diogok conhecidos:voce
(nenhum conhecido em comum)

Você pode ver um clone do twitter com php+redis ou uma aplicação de notas com ruby+redis e ainda um motor de busca com python+redis como exemplo.

Em novo servidor

É isso ae, esse post é apenas para registrar que o Manifesto está rodando em novo servidor, um vps na DreamHost. Ia ser Linode, mas meu chefe evangelizou a DreamHost bem demais para eu ignorar.

O VPS roda apenas LAMP mesmo, com alguns Joomla!s e um Drupal. A migração foi indolor, nada que tar + scp + mysqldump + mysql não resolva e aparentemente está tudo ok. Meus e-mails agora são todos do google (não estou exatamente satisfeito mas não farei nada por enquanto), e o google apps for your domain é “tri-legal”.

No mais peguei meu domínio: diogok.net e só falta uma página.

É isso ae!

Como construir projetos Clojure com Leiningen

Dois pontos essenciais, não diretamente relacionados ao código, em projetos sérios são gestão de código (ou controle e versionamento de código) e ferramentas de automação de build. Para uma tecnologia tão prática como clojure não faz sentido usar tecnologia arcaicas e duras, então para versão de código você vai usar o GIT.

Para quem trabalha com java provavelmente está acostumado a usar o Ant ou o Maven para gerir os projetos (provavelmente parcialmente disfarçados por uma IDE), e quem já editou um pom.xml de um projeto sabe o inferno que isso se torna. Então, como uma ferramente elegante que é o Clojure, uma ferramenta de build e automação tão elegante quanto é necessária. Eis que temos o Leininghen.

Leininghen (não sei pronunciar) é uma ferramente de build para clojure em clojure desenhada para não te perturbar (adaptação minha da explicação oficial). Não tem XML, hail that. Você tem um arquivo de configuração escrito em, vejam só, Clojure! E é bem simples até (e programável, já que é de fato um programa), e pequeno.

Ele serve basicamente para:

  • Criar um projeto em branco com uma estrutura padrão
  • Baixar as depend&eecirc;ncias
  • Rodar os testes
  • Compilar e empacotar

Ele usa ainda o Clojars.org , um repositório de bibliotecas jar para clojure, como base para dependências, além de visar o GIT.

Mão na massa:

Primeiro você entra na github do leiningen e baixa o script, e coloque-o no seu path e rode o instalador:

$ wget -O lein http://github.com/technomancy/leiningen/raw/stable/bin/lein
$ chmod +x lein
$ sudo mv lein /usr/bin/lein
$ lein self-install

Isso vai baixar o próprio lein para seu usuário pode usar. Agora começamos um projeto.

lein new meuprojeto  

Isso vai criar um projeto “meuprojeto” mínimo, com a estrutura básica de pastas. Mais ou menos assim:

/meuprojeto
/meuprojeto/classes
/meuprojeto/lib
/meuprojeto/src/meuprojeto/core.clj
/meuprojeto/test/meuprojeto/core.clj
/meuprojeto/project.clj
/meuprojeto/README
/meuprojeto/.gitignore

Ele vem com as pastas para as classes compiladas, para as dependências, código fonte e testes (e o primeiro arquivo).

O project.clj é a configuração, o README é para explicação do projeto (e para o GIT) e o .gitignore também. Então agora temos que configurar o project.clj. Ele vem assim:

(defproject meuprojeto "1.0.0-SNAPSHOT"
:description "FIXME: write"
:dependencies [[org.clojure/clojure "1.1.0"]
[org.clojure/clojure-contrib "1.1.0"]])

Podemos editá-lo para tornar nosso projeto mais completo.

(defproject meuprojeto "1.0.0-SNAPSHOT"
:description "Projeto de teste do lein"
:main meuprojeto.core
:url "http://www.meuprojeto.com"
:dependencies [[org.clojure/clojure "1.1.0-master-SNAPSHOT"]
[org.clojars.okkop/clojure-couchdb "0.2"]
[org.clojure/clojure-contrib "1.1.0-master-SNAPSHOT"]])

As informação de versionamento e descrição, além de organização, vão ser usados se enviar sua biblioteca ao clojars.org. O main será definido na geração do jar, e as dependências para construir tudo e os paths.

Com o project.clj pronto você pode fazer o lein instalar as dependências:

$ lein deps

Isso vai baixar os jars na pasta lib,e ele acerta o path na hora rodar.

Agora você pode programar, criar seus testes e fazer eles passarem. Conforme programando você pode rodar todos os testes ou apenas um namespace usando o lein:

$ lein test
$ lein test meuprojeto.dbclient-test

Então quando esse release estiver pronto para distribuir, o código já no repositório (você fez os commits e os pushs e etc) você pode construir os pacotes:

$ lein uberjar

Esse vai compilar as suas classes, criar um jar “clean” (sem as depend&eecirc;ncias) e um standalone, que vai conter as bibliotecas que estão prontos para distribuir e ser executado

BONUS: Se você construiu uma biblioteca e quer torná-la disponível pelo lein você pode publicá-la no clojars.org, basta se cadastrar e depois enviar o jar “clean”: 

$ lein jar
$ scp meuprojeto.jar clojars@clojars.org:

Presto!

Desenvolvimento de módulos para o Drupal

Desenvolvimento de módulos para o Drupal.

Minhas atividades web receberam recentemente um adereço interessante: O Drupal. E uma das minhas tarefas recentes foi o estudo para uma apresentação sobre desenvolvimento de módulos para o mesmo. Embora apresentação não esteja tão boa quanto gostaria estou disponibilizando o conteúdo. Pretendia acertar algumas coisas antes, mas vou deixar isso para depois.

A apresentação tem duas partes, os slides e um documento que criei antes para me basear.

Você baixar o PDF da Apresentação sobre Desenvolvimento de Módulos para Drupal. Vou reproduzir o documento (rascunho) na integra abaixo.

O Drupal e desenvolvimento de módulos para Drupal

O que é o Drupal:

Drupal é um framework para gestão de conteúdo. É uma ferramenta livre, de código aberto e comunidade atuante.
Drupal fornece a base para construção de portais de internet e intranet para gestão de conteúdo (e conhecimento), permitindo e incentivando a atuação colaborativa, compartilhamento de recursos e publicação de conteúdo de uma forma geral.
É extensível através de módulos, que são como plugins ou add-ons para acrescentar ou alterar funcionalidades no sistema. Quase tudo no Drupal é um módulo.
É desenvolvido em PHP e roda sobre a plataforma Apache + MySql (ou PostgreSQL).
Praticamente todas as funcionalidade do Drupal são módulos, como o conteúdo, o controle de acesso e etc, e todo funcionamento do Drupal é padronizado através de suas interfaces de Hooks e API. 

Drupal: http://drupal.org/

Conteúdo (nodes):

O Conteúdo no Drupal tem sua base em alguns elementos:

  • Campos (Fields)
  • Tipos de conteúdo (Content type)
  • Conteúdo (nó ou node)

Os tipos de conteúdo possuem uma série de campos que podem ser de vários tipos (texto, mapa, data, arquivos, imagens, relacionamentos com outros tipos e etc) que definem o modelo de um conteúdo. Cada conteúdo (ou página) é um nó no drupal, e possui um URL, identificador e o conteúdo dos seus campos de acordo com o tipo de conteúdo ao qual pertence.

Hooks:

Hooks são as formas dos módulos se conectarem ao Drupal e aos outros módulos. O próprio núcleo (core) do Drupal implementa os Hooks que precisa.

Um hook é um gancho a uma chamada do sistema, são funções definidas em pontos chaves do sistema (como carregar um conteúdo, renderizar um bloco, listar itens de menu, salvar um conteúdo, enviar um formulário, etc), assim os módulos registram funções próprias para serem chamadas nesses pontos do sistema.

Quando o sistema vai executar uma ação que possua um gancho (hook), ele executa então todas as funções registradas para esse gancho, fazendo assim a integração do sistema.

Para se registrar um hook basta criar uma função com o nome sendo o o nome do módulo + “_” + o nome do hook. Assim para implementar o hook “menu” no módulo “meumodulo” basta criarmos a função “meumodulo_menu”. 

Exemplo:

A ação de visualizar uma página especifica de um conteúdo, no momento de carregar esse conteúdo existe um gancho chamado “nodeapi”. Então quando o sistema prepara esta página, ao carregar o conteúdo ele passa esse “node” a ser exibido para todas os módulos que registraram uma função no hook “nodeapi”, ele podem então executar ações e alterar esse node, que depois será exibido para o usuário, através da implementação da hook “nodeapi” do próprio módulo “node”.

Mesmo módulos podem criar hooks para que outros módulos possam usar. É assim que drupal integra suas próprias funcionalidades.

São mais de 80 Hooks no núcleo do drupal, mais o que cada módulo pode oferecer (cck, node, contentaccess, etc).

Alguns Hooks importantes são:
 
  • form – Formulários
  • nodeapi – Trabalhar com nodes
  • menu – Itens de menu e páginas (urls)
  • block – Blocos de conteúdo
  • user – Controle de usuário
  • cron – Ações agendadas
Para mais sobre hooks: http://api.drupal.org/api/group/hooks.

APIs:

Outra parte essencial do Drupal são as APIs. APIs são conjuntos de funções relacionadas a alguma utilidade ou parte do sistema. Diferentes dos hooks elas são para serem chamadas livremente aonde quer que os módulos precisem de suas funcionalidades. As APIs são basicamente todas as funções fornecidas pelo core e por todos os módulos do Drupal.

Toda função de todo módulo é disponível para todo outro módulo, tornando o sistema extremamente padronizado através do reuso de recursos.

Então as APIs do drupal são funções fornecidas pelo sistema, como:

  • db_query – Para consultas ao banco de dados
  • t – Para recuperar string traduzidas (internacionalização)
  • drupal_set_message – Mensagens para o usuário
  • node_load – Carregar módulos
  • user_load – Buscar dados de um usuário
  • form_set_error – Mensagens de erro

São milhares de funções disponíveis: Cache API, File API, Field API, Database API, RDF API, Schema API, etc. Mais o que cada módulo pode disponibilizar, e existem muitos módulos que lançam extensas APIs: CCK, Openlayers e etc oferecem APIs.

Para mais sobre API: http://drupal.org/node/326 .

Desenvolvendo seu módulo:

Você não precisa desenvolver um módulo.

O sistema de conteúdo do Drupal é muito flexível, ele pode por si só atender uma grande demanda. E também existem milhares de módulos prontos para as mais variadas funções, alguns extremamente flexíveis como o CCK (que estende o sistema de conteúdo do Drupal). Ainda assim alguém já pode estar trabalhando em um módulo parecido com o que você acha que precisa, e é preferível que se unam os esforços em um só módulo, ou até se já existe um módulo com funcionalidade próxima é mais aconselhável que se unam num módulo só.

A comunidade drupal preza muito por padronização e qualidade, e sempre recomenda (e efetua) a fusão de módulos semelhantes por um módulo mais completo e de maior qualidade.

Mais ainda assim não existe tudo pronto, e em casos de necessidades especificas pode-se precisar de um novo módulo.

O primeiro passo é o http://drupal.org/project/modules aonde você pode conferir os módulos já existentes e ver tem algum que vale a pena usar ou adaptar. A comunidade Drupal é realmente muito forte em integração e união. 

O segundo lugar a visitar é http://groups.drupal.org/contributed-module-ideas , é um grupo de ideias e pedidos para módulos, da mesma forma que pode achar o que precisa  já pronto, pode-se achar ainda em planejamento e até inserir sua ideia e conseguir colaboradores (ou alguém que indique aonde isso já foi feito).

Se for então começar a colocar a mão na massa, a principal referência é http://drupal.org/contributors-guide .

Desenvolvendo seu módulo, mão na massa:

Decidido o módulo que quer, as funcionalidade e etc é hora de começar a programar mesmo. Existe uma padronização importante no código do drupal, que facilita manutenção e colaboração, e está definido no http://drupal.org/coding-standards , de fácil e rápida leitura. A parte de SQL (http://drupal.org/node/2497) também é importante e simples.

A estrutura mais básica de um módulo consiste em três arquivos:

  • nome_do_modulo.info
  • nome_do_modulo.module
  • nome_do_modulo.install

O arquivo de INFO contêm a descrição e informações básicas do módulo, como um arquivo de propriedades, as principais informações são:

  • name: O nome do módulo
  • description: A descrição
  • core: Versão do drupal ao qual se destina
  • version: Versão da instalação [opcional se hospedado no drupal.org]
  • dependencies: Módulos do qual depende [opcional]
  • package: O “pacote” de módulos ao qual pertence, ex: “content”, “views”,”others” [opcional]

Segue um exemplo de arquivo .info:

name = Meu Primeiro Módulo
description = Testa o sistema de módulos do Drupal
version = 0.1
core = 6.x
dependencies[] = cck
O arquivos INSTALL contêm as instruções de instalação (se houver alguma), e é escrito em PHP seguindo o esquema de “hooks”. O arquivo pode implementar dois hooks: “install” e “uninstall”, então se o o módulo se chama “meumodulo”, basta implementar esses dois hooks criando as funções “meumodulo_install” e “meumodulo_uninstall”:
<?php
function meumodulo_install() {
}
function meumodulo_uninstall() {
}

?>

Dentro das funções programa-se os passos da instalação.

Parte comum da instalação de um módulo é a criação de tabelas no banco de dados, para isso usa-se o hook de schema do drupal, implementa o hook como “meumodulo_schema”, no mesmo arquivo:

function meumodulo_schema() {
    $schema[‘meumodulo’] = array(
    ‘description’=>’descrição’,
    ‘fields’=>array(
        ‘id’=>array(‘type’=>’int’,’not null’=>true),
        ‘name’=>array(‘type’=>’text’,’default’=>”)
    ) ,
    ‘primary key’=> array(‘id’)
    );
    return $schema;    
}

Nesse caso precisamos ainda definir que queremos instalar um schema nos hooks install e uninstall:

function meumodulo_install() {
    drupal_install_schema(‘meumodulo’);
}
function meumodulo_uninstall() {
    drupal_uninstall_schema(‘meumodulo’);
}

Finalmente o arquivo MODULE contém os códigos do modulo em si, implementando os hooks e as funcionalidades que o módulo vai oferecer, também é um arquivo PHP comum. Supondo que o módulo adicione uma página simples que exibe alguns dados salvos no banco de dados. Primeiro para criar uma página implementamos o hook “menu” para essa url:

<?php
function meumodulo_menu() {
    $menu = array();
    $menu[‘meumodulo/lista’] = array(
        ‘title’ => ‘Lista de itens do meu modulo’,
        ‘description’ => ”,
        ‘page callback’ => ‘meumodulo_lista’,
        ‘type’ => MENU_NORMAL_ITEM
    ) ;
    return $menu;
}

Agora implementamos o “meumodulo_lista” para montar a página:

function meumodulo_lista() {
    $query = db_query(‘SELECT nome FROM {meumodulo}’);
    $rows= array();
    while($row = db_fectch_array($query)) {
        $rows[] = array($row[‘nome’]);        
    }   
    $header = array(‘Nomes’);
    return theme(‘table’,$header,$rows);        
}

Nessa função usamos 2 APIS importante: A de banco de dados (db_*) e de temas.

A de banco de dados, db_query monta uma consulta SQL e realiza essa no banco de dados usado no Drupal, enquanto db_fetch_array transforma o resultado da consulta em um array e avança o cursor nesse resultado.

A função theme recebe o tipo de thema a ser usado, e os parâmetros de acordo com esse tema, e então monta o html de acordo com o tema atual do drupal. No caso usamos o template de table e passamos a lista de cabeçalhos e as linhas da tabela. Combinações mais complexas são possíveis.

Agora vamos adicionar uma página que permitiria inserir esses dados no banco de dados, primeiro acrescentariamos na nossa implementação do “menu” a nova página:

function meumodulo_menu() {
(…)
    $menu[‘admin/settings/meumodulos’] = array(
        ‘title’ => ‘Admin da lista de items do meu modulo’,
        ‘description’ => ”,
       ‘page callback’ => ‘drupal_get_form’,
        ‘page arguments’ => array(‘meumodulo_admin’),
        ‘access arguments’ => array(‘access administration pages’),

        ‘type’ => MENU_NORMAL_ITEM
    ) ;
(…)
}

Dessa vez adicionamos a restrição de apenas quem tenha acesso a páginas administrativas, e ao invés de montarmos a página vamos usar a api de formulário drupal_get_form, implementamos então o meumodulo_admin:

function meumodulo_admin {
    $form = array();
    $form[‘meucampo’] = array(
        ‘#type’=>’text’,
        ‘#title’=>’Nome’,
        ‘#default_value’=>’Insira um nome’
    );
    $form[‘submit’] = array(
        ‘#type’=>’submit’,
        ‘#value’=>’Save’,
        ‘#submit’=>array(‘meumodulo_admin_submit’)
    );
    return $form;
}
function meumodulo_admin_submit($form,$state) {
    $novo_nome = $state[‘meucampo’];
    db_query(‘insert into {meumodulo}  (nome) values (‘%s’);’,$novo_nome);
}

E assim a primeira funcionalidade do módulo estaria pronta, e pronto para ser instalado.

A instalação de um módulo no Drupal se dá apenas copiando sua pasta para a pasta “modules” do drupal e ativando o módulo através da interface administrativa do drupal.

E depois?

Acontece que geralmente um módulo não é tão simples, e precisa de várias funcionalidades, e como não é possível saber todas ou cobrir todas aqui, segue um roteiro para facilitar o desenvolvimento:

  1. Pense bem no que o módulo vai fornecer e no esquema de banco de dados
  2. Separe as funções do módulo em partes funcionais:
    1. Páginas
    2. Formulários
    3. Listas
    4. Integrações com outros módulos (cck, biblio, openlayer)
    5. Interfaces (blocos/views/temas)
    6. Interações com conteúdo (nodes)
    7. Etc
  3. Para cada função do módulo consulte a lista de hooks para ver qual pode ser usado.
  4. Escolhido o(s) hook(s) a implementar veja se há uma API que ajude na tarefa
  5. Implemente conforme imaginado
  6. Teste
  7. Publique

Outros links importantes:

MagicSQL: O pior framework de ORM e SQL para PHP.

Disclaimer: eu que fiz.

A muito tempo atrás, numa terra distante, um amigo me perguntou sobre Orientação a Objetos em PHP, e sobre ORM, como resposta eu fiz essa pequena biblioteca para demonstrar o básico do básico. O “framework” MagicSQL!

Sinceramente tenho vergonha dela, não era nada sério e nem tomei muitos cuidados na época com boas práticas e testes e documentação, era só para ver alguns recursos. Mas ela funciona e eu usei em alguns projetinhos(com isso ela até evolui um bocado), mas se for escolher algum ORM para PHP, faça uma escolha mais séria.

De qualquer forma estava revendo e resolvi publicar o código, “just because”. Você pode conferir o código no Repositório do MagicSQL. Vamos a uma análise chata:

Primeiro ponto: ela funciona. Como eu usei ela um pouco então o que está lá está funcionando nos casos testados(e mais alguns). Ela consegue fazer selects, updates, inserts, deletes e joins sozinha. E_STRICT.

Segundo ponto: não tem configuração. Nenhuma. Você cria a conexão no código mesmo (como se fosse PDO) e instancia o Repositório. O resto é mágica (e não confie em mágica). Ela advinha seu schema usando describe e show tables, e faz uns cálculos para adivinhar os joins (Essa é uma parte engraçada do código).

Not lazy: Cada “requisição” faz só uma query, mesmo com joins. Ou seja, mesmo quando você carrega uma lista de objetos com listas de objetos relacionados, não importa o tamanho do join, é uma query só e tudo é carregado numa tacada só e transformado em objetos. Não queira ver esse código também.

StdClass pública: ele não usa seus objetos, apenas StdClass com atributos públicos. Whatever.

PDO e ArrayObject: Toda lista e resultado são ArrayObject (na verdade MagicCollection), então $arr[0] == $arr->get(0). Sua conexão é um PDO, caso você precise.

Quase sem documentação: Acho até que tem alguns comentários quaisquer, mas não tem PHPDoc e a documentação em si é quase nula. Mas está lá no README, example.php e test.php.

Noob testing: Bom, pelo menos tem testes unitários (ou algo parecido). Na época não tinha escolhido um framework, e como foi algo bem rápido e sujo, tem só um monte de chamadas com ifs e testes para saber se tudo correu bem. Tem até um teste que ainda falha por que nunca precisei da sua implementação (ou porque perdi a implementação).

MySql e SQLite: O objetivo inicial foi o sqlite, depois o MySql porque é o que mais uso. Apesar de usar PDO o método de “discovery” do schema não é padrão.

Usem um ORM PHP de verdade, e não esse, mas o código está ai para sua diversão.

Namespaces on clojure, and compilation.

O uso de namespaces em clojure pode ser bem simples, mas pode ser bem complicado. Vou focar no simples :)

O namespace de clojure está próximo da declaração pacote/classe no java, assim o padrão é o mesmo para nomenclatura (apesar de poder variar), dessa forma o arquivo clojure em src/twitter/search.clj teria normalmente o ns (ns = namespace) twitter.search.

A declaração básica de um namespace é a através do macro “ns”:

(ns twitter.search)  

Com os arquivos definidos com seus devidos namespace, e o classpath (cp) do java configurado certo (apontando para o src/, por exemplo, você pode chamar outros arquivos clojure pelo seu ns:

(ns twitter.main
(:require twitter.search)
)

Agora você teria o ns twitter.search disponível em seu main, mas a forma mais prática é a seguir:

(ns twitter.main
(:require [twitter.search :as search)
)

Assim o conteúdo do ns twitter.search está no simbolo alias search, dessa forma para chamar a função “search-timeline” do twitter.search você faz:

(search/search-timeline "clojure")  

Outra forma útil no ns é o “use”, que traz os simbolos do outro ns para o ns atual, assim:

(ns twitter.main (:use twitter.search))
(search-timeline "clojure")

Agora podemos também compilar nossa aplicação clojure, tendo por exemplo a seguinte estrutura de arquivos:

/app
/app/src
/app/src/twitter
/app/src/twitter/main.clj
/app/src/twitter/search.clj
/app/classes
/app/libs

Considerando que seus .jars do clojure estão no libs, conforme este texto, você deve então passar a lancar o clojure com o seguinte comando (veja o novo classpath):

java -cp ./:./src/:./classes/:libs/clojure.jar:libs/clojure-contrib.jar clojure.main $1  

Agora podemos definir um método main no nosso twitter/main.clj :

(ns twitter.main
(:require [twitter.search :as search]))
(defn -main [word]
(let [tweets (search/search word)]
(println (reduce (fn [t0 t1]
(str t0 "\n"
(get t1 "at") " @" (get t1 "from") " : " (get t1 "text")
)) "Search result" tweets ))
))

E o twitter/search.clj:

(ns twitter.search
(:use clojure.contrib.json.read)
(:require [clojure.contrib.http.agent :as http ])
)

(defn search [word]
(let [json (http/string
(http/http-agent
(str "http://search.twitter.com/search.json?q=" word)) )]
(map
(fn [tweet] {"from" (get tweet "from_user")
"text" (get tweet "text")
"at" (get tweet "created_at")})
(get (read-json json) "results"))
)
)

Como pode-se ver usamos tanto o use como o require, assim como as funções mega-hyper-uteis de map e reduce para excercício.

Mais então para compilar o projeto, lance o REPL executando apenas o “./clj” e mande compilar com:

(compile 'twitter.main)  

Agora suas classes estão na pasta classes, você pode executar a aplicação rodando:

$ java -cp ./libs:./classes:./libs/clojure.jar:./libs/clojure-contrib.jar twitter.main clojure  

Assim vai rodar o -main recebendo o “clojure” como argumento. Crie agora o script de start-up e pronto.

Bonus: uma versão em um script de 30 linhas formatadas de uma busca no twitter com clojure. Basta executar o .clj.

Hello World, em Clojure, novamente.

Já escrevi um pouco sobre clojure aqui, sobre o básico de programação funcional e o básico de clojure, mas resolvi pegar do começo agora, do hello world.

Para preparar o ambiente para o clojure basta pegar as bibliotecas do clojure e do clojure-contrib:

wget http://clojure-contrib.googlecode.com/files/clojure-contrib-1.1.0.zip
wget http://clojure.googlecode.com/files/clojure-1.1.0.zip

Descompacte e pegue os jars clojure.jar e clojure-contrib.jar e coloques numa mesma pasta “libs”, agora crie um lançador simples para o clojure, com o nome de “clj” (com permissão de execução, com o seguinte conteúdo:

#!/bin/sh
java -cp .:libs/clojure.jar:libs/clojure-contrib.jar clojure.main $1

Agora basta chamar o “clj” para ter o REPL, ou passar como parâmetro o arquivo clojure à ser executado.

Agora como primeiro programa, o clássico “Hello world”. Basta criar um arquivo hello.clj com o seguinte conteúdo:

  (println "Hello World")
(defn fac "Nice factorial impl" [x] ( reduce * (range 2 (inc x))))
(println "Factorial of 4 is" (fac 4))
(def myJson "{\"hello\":\"World\"}")
(use 'clojure.contrib.json.read)
(def obj (read-json myJson))
(println (get obj "hello"))

No exemplo, linha a linha, imprimi o Hello World, criei uma função para calculo de fatorial e imprimi o fatorial de 4, criei uma string no formato do json, importei uma biblioteca de json do clojure-contrib, fiz o parse do json e exebi sua propriedade. Basta executar:

$ ./clj hello.clj  

Simples assim.