segunda-feira, 24 de dezembro de 2007

Lua-Gtk

A linguagem de programação Lua foi planejada para ser uma linguagem de extensão para aplicações escritas em C.

O lado negativo disso é a linguagem não foi pensada para o desenvolvimento de aplicações totalmente em Lua, mas por outro lado a grande vantagem é que tudo que existe em C pode ser portado para Lua.

Assim, mesmo não havendo um toolkit gráfico padrão para Lua, a biblioteca GTK+ foi portada para Lua, sob o nome Lua-Gtk.

Lua-Gtk implementa perfeitamente a versão 2.x do GTK+, com ênfase na orientação a objetos.

Para quem conhece a biblioteca GTK+, o módulo Lua-Gtk não traz segredo algum. Depois de ter instalado Lua-Gtk, vamos implementar um exemplo «Olá Mundo!» clássico.

Em C, nosso hello.c fica assim:
#include <gtk/gtk.h>

static void hello(GtkWidget *, gpointer);
static gboolean delete_event(GtkWidget *, GdkEvent *, gpointer);

int main(int argc, char **argv) {
GtkWidget *window;
GtkWidget *button;

gtk_init(&argc, &argv);

window = gtk_window_new(GTK_WINDOW_TOPLEVEL);

g_signal_connect(
G_OBJECT(window), "delete_event",
G_CALLBACK(delete_event), NULL
);
gtk_container_set_border_width (GTK_CONTAINER(window), 10);

button = gtk_button_new_with_label("Hello World");

g_signal_connect(
G_OBJECT(button), "clicked",
G_CALLBACK(hello), NULL
);
g_signal_connect_swapped(
G_OBJECT(button), "clicked",
G_CALLBACK(gtk_widget_destroy), G_OBJECT (window)
);
gtk_container_add(GTK_CONTAINER(window), button);
gtk_widget_show(button);
gtk_widget_show (window);

gtk_main ();
return 0;
}

static void hello(GtkWidget *widget, gpointer data) {
g_print("Hello World\n");
gtk_main_quit();
}

static gboolean delete_event(GtkWidget *widget, GdkEvent *event, gpointer data) {
g_print("delete event occurred\n");
return TRUE;
}


Para compilá-lo:
bash$ gcc hello.c -o hello `pkg-config --cflags --libs gtk+-2.0`
bash$ ./hello


A versão Lua, hello.lua é muito parecida, se você comparar linha a linha:
require "gtk"

function hello(widget, data)
print "Hello World"
gtk.main_quit()
end

function delete_event(widget, event, data)
print "dete event occurred"
return true
end

gtk.init(#{...}, {...})

window = gtk.window_new(gtk.GTK_WINDOW_TOPLEVEL)

window:connect("delete_event", delete_event)
window:set_border_width(10)

button = gtk.button_new_with_label("Hello World")

button:connect("clicked", hello)
window:add(button)
button:show()
window:show()

gtk.main()


Para executar:
bash$ lua hello.lua


Fica aqui a dica! Para saber mais, veja o manual de referência.

Se quiserem, depois escrevo mais sobre o assunto.

[]'s
Cacilhas, La Batalema

sábado, 8 de dezembro de 2007

Prettify no Kodumaro

Gente,

Acrescentei Prettify ao Kodumaro.

Prettify é um recurso ECMAScript/CSS para syntax highlighting de códigos em C/C++, Java, Python, Bash, SQL, HTML, XML, CSS, JavaScript e Makefile. Também trabalha bem com Ruby, AWK e Perl.

Não funciona com Smalltalk ou Lisp.

A linguagem é detectada automaticamente.

Para usar Prettify, basta incluir o framework:
<script src="http://vps.jsloader.com/assets/jsloader.js"></script>
<script><!--
JSLoader.load("ria", "prettify", "1.0");
//--></script>


Então no body:
<body onLoad="prettyPrint();">


Feito isso, basta registrar a classe de pre ou code como prettyprint:
<pre><code class="prettyprint">
...
</code></pre>


[]'s
Cacilhas La Batalema

domingo, 2 de dezembro de 2007

Lendo diretórios em C/C++

Outro dia me perguntaram como listar o conteúdo de um diretório em C.

Não é difícil. Já fiz isso algumas vezes, mas é claro que de cabeça não vou ficar guardando isso: pra que existem os comandos man e apropos?

Os cabeçalhos necessários para leitura de diretório são:
#include <sys/types.h>
#include <sys/stat.h>
#include <dirent.h>
#include <unistd.h>


Precisamos também das bibliotecas string.h – ou string em C++ –, time.h e stdio.h – ou iostream em C++.

A função para abertura de um diretório é opendir(), recebe como parâmetro uma string representando o nome do diretório e retorna um ponteiro para descritor de diretório, DIR.

Para abrir um diretório:
DIR *dir = opendir(dirname);


A função readdir() reitera sobre o diretório, retornando um ponteiro para estrutura do tipo dirent ou NULL caso não haja mais entradas.

Então uma forma de reiterar sobre um diretório é:
struct dirent *ent;
while (ent = readdir(dir)) {
    ...
}


Ao final da reiteração, é preciso fechar o diretório:
closedir(dir);


A estrutura dirent é declarada assim:
struct dirent {
    long     d_ino;                 /* número de inodes */
    off_t    d_off;                 /* offset para este dirent */
    unsigned short d_reclen;        /* tamanho de d_name */
    char     d_name[NAME_MAX+1];    /* nome do arquivo */
};


Se quiser mais informações sobre cada entrada, crie uma estrutura stat para a entrada. Para tanto use a função stat():
struct stat buf;
char aux[256];

strcpy(aux, dirname);
#ifdef WIN32
strcat(aux, "\\");
#else
strcat(aux, "/");
#endif
strcat(aux, ent->d_name);

stat(aux, &buf);


A estrutura stat é definida assim:
struct stat {
    dev_t     st_dev;       /* ID do dispositivo */
    ino_t     st_ino;       /* número do inode */
    mode_t    st_mode;      /* modo de proteção */
    nlink_t   st_nlink;     /* número de hard links */
    uid_t     st_uid;       /* ID do usuário dono */
    gid_t     st_gid;       /* ID do grupo dono */
    dev_t     st_rdev;      /* ID do dispositivo se for arquivo especial */
    off_t     st_size;      /* tamanho total em bytes */
    blksize_t st_blksize;   /* tamanho do bloco */
    blkcnt_t  st_blocks;    /* número de blocos alocados */
    time_t    st_atime;     /* hora do último acesso */
    time_t    st_mtime;     /* hora da última modificação */
    time_t    st_ctime;     /* hora da última troca de estado */
};


Um campo interessante é st_mode. O modo de acesso pode ser obtido como:
char *acc = (char *) malloc(sizeof(char) * 6);
sprintf(acc, "0%o", buf.st_mode % 4096);


E é possível saber se a entrada se trata de um arquivo ou diretório de forma similar:
int isDir = buf.st_mode & S_IFDIR;


É possível usar as funções da biblioteca time.h para facilitar a leitura dos campos st_atime, st_mtime e st_ctime.

Por exemplo, para exibir a última modificação de um arquivo:
printf("Última modificação: %s", ctime( &(buf.st_mtime) ));


Espero que este artigo seja útil. =)

[]'s
Cacilhas La Batalema

domingo, 9 de setembro de 2007

Criando um módulo binário em Lua

Lua Uma das coisas que tornam a linguagem de programação Lua interessante é justamente sua poderosa API com a linguagem C.

Com ela é possível criar módulos em C, explorando todo poder e flexibilidade dessa incrível linguagem.

De fato a linguagem Lua não passa de uma linguagem de extensão para C e o interpretador Lua é uma programa escrito em C para ser uma máquina virtual.

A criação de um módulo de baixo nível – como são conhecidos os módulos em C para Lua – não tem nenhum truque ou sortilégio. =)

Basicamente você precisa de uma função int luaopen_módulo(lua_State *) que chame a função lua_register() para registrar o módulo. Essa função recebe três parâmetros: o estado Lua, o nome do módulo e um vetor do tipo luaL_reg com a lista de funções a serem exportadas. A função deve retornar 1.

As funções a serem exportadas devem ser estáticas, recebendo o estado Lua e retornando inteiro.

Os parâmetros vindos de Lua são recolhidos com as funções lua_totipo() e o retorno é dado pelas funções lua_pushtipo(). A função C real deve retornar um número representando a quantidade de valores a serem retornados para Lua.

Confuso até aqui, né? Mas tudo se esclarecerá com um exemplo.

Exemplo


Vamos criar um módulo que permita mudar e informar o diretório atual. Para tanto, precisaremos do cabeçalho unistd.h.

Precisaremos também do cabeçalho stdlib.h – para o malloc() – e dos cabeçalhos de Lua:
#include <stdlib.h>
#include <unistd.h>

#include <lua.h>
#include <luaxlib.h>
#include <lualib.h>


Agora podemos declarar os cabeçalhos das funções para mudar e informar o diretório atual, e uma função para ajustar informações sobre o módulo:
static int dir_chdir(lua_State *);
static int dir_cwd(lua_State *);
static void set_info(lua_State *);


Repare que todas as funções recebem como único argumento o estado Lua.

Vamos agora ao vetor que define as funções do módulo. Deve ser um vetor do tipo luaL_reg. Esse tipo é uma estrutura onde o primeiro elemento é uma string representando o nome da funções e o segundo elemento a função em si. O último elemento do vetor deve ser equivalente a nulo.

O último elemento do vetor deve ser nulo:
static const luaL_reg Funcs[] = {
{ "chdir", dir_chdir },
{ "cwd", dir_cwd },
{ NULL, NULL }
}


Agora já podemos criar a função de abertura do módulo, como já vimos, luaopen_dir():
int luaopen_dir(lua_State *L) {
luaL_register(L, "dir", Funcs);
set_info(L);
return 1;
}


Em set_info(), o que vemos é lua_pushliteral(), que empilha dados literais no estado, depois «agrupamos» os dados literais com lua_settable() – não vou entrar em detalhes sobre isso.
static void set_info(lua_State *L) {
lua_pushliteral(L, "_COPYRIGHT");
lua_pushliteral(L, "Copyright (C) 2007 Rodrigo Cacilhas");
lua_settable(L, -3);
lua_pushliteral(L, "_DESCRIPTION");
lua_pushliteral(L, "Directory control");
lua_settable(L, -3);
lua_pushliteral(L, "_NAME");
lua_pushliteral(L, "dir");
lua_settable(L, -3);
lua_pushliteral(L, "_VERSION");
lua_pushliteral(L, "1.0");
lua_settable(L, -3);
}


As funções exportadas


Vamos agora às funções exportadas para Lua.

O cabeçalho da primeira função é:
static int dir_chdir(lua_State *L) {
int r;


Usaremos r para fornecer o retorno.

Em Lua, a função chdir() deverá receber um parâmetro string. Para pegar esse parâmetro na função em C usamos:
    char *path = (char *) lua_tostring(L, 1);


A chamada da função lua_tostring() recebe como primeiro parâmetro o estado Lua que executou a função e, como segundo parâmetro, o índice do parâmetro passado para a função Lua – 1 indica o primeiro parâmetro.

Então a linha acima recolherá o primeiro parâmetro passado em Lua como uma string.

Agora é código C simples:
    if (chdir(path) == 0)
r = 1;
else
r = 0;


Ou seja, se o status for zero, o retorno será 1 (verdadeiro), senão será 0 (falso).

Agora precisamos empilhar a resposta no estado Lua como booleano:
    lua_pushboolean(L, r);


Por último avisamos o estado de que um valor foi retornado:
    return 1;
}


Vamos agora à segunda função: ela retornará uma string representando o diretório atual ou nulo caso ocorra um erro – veja que é possível retornar quantidades e tipos diferentes em chamadas diferentes:
static int dir_cwd(lua_State *L) {
char *path = (char *) malloc(sizeof(char) * 1024);
getcwd(path, 1023);

if (path == NULL) {
lua_pushnil(L);
lua_pushstring(L, "getcwd error");
return 2; // dois retornos
} else {
lua_pushstring(L, path);
return 1; // um retorno
}
}


Compilando


Agora é preciso compilar o módulo – chamei o arquivo de dir.c:
bash$ gcc -fPIC -Wall -c dir.c
bash$ ld -lm -shared dir.o -o dir.so


Agora, como superusuário, mova a biblioteca compartilhada dir.so para o diretório $LUA_CPATH – geralmente /usr/local/lib/lua/5.1/ ou /usr/lib/lua/5.1/.

Testando


Vamos então testar! Levante a máquina virtual Lua e experimente:
lua> require "dir"
lua> print(dir.chdir "/tmp")
true
lua> print(dir.cwd())
/tmp


Se tudo correu bem, você terá algo similar a isso. =)

Dúvidas nos comentários, por favor!

[]'s
Cacilhas

domingo, 12 de agosto de 2007

Mais sobre OpenID

OpenID Outro dia publiquei um artigo sobre OpenID, que é um sistema de descentralização de autenticação e autorização.

Como ainda estava começando a entender seu funcionamento, cometi alguns erros, nada muito esdrúxulo ou que atrapalhe seu entendimento posterior.

Bem, agora, depois de duas semanas estudando sobre o funcionamento de um consumidor, vou explicar aqui um pouco do que descobri.

Numa transação de autenticação OpenID, há pelo menos três nós envolvidos: cliente, consumidor (RP, relying party) e provedor (OP, provider).

O cliente é o usuário que quer se autenticar, geralmente com seu navegador.

O consumidor é o sistema onde o cliente quer se autenticar.

O provedor é o serviço OpenID onde o cliente se cadastrou.

A autenticação então funciona mais ou menos assim:
  1. o cliente acessa o consumidor e informa sua URL de identificação;
  2. o consumidor «canonicaliza» a URL, e procura saber qual a identificação verdadeira (IDP) e quem é o provedor;
  3. o consumidor toma quaisquer medidas necessárias para verificar a identificação e redireciona o cliente para o provedor;
  4. cliente e provedor se entendem e, a partir daí, a conexão volta para o consumidor;
  5. a partir dos dados voltados, o consumidor sabe se o cliente está autorizado ou não.


Alternativamente o provedor pode enviar dados extra sobre o cliente, em formato SRE (ou sreg) ou AX.

Há dois modos de transação entre consumidor e provedor: dumb mode e smart mode.

Dumb mode


O dumb mode é usado quando o consumidor não suporta cálculos de criptografia.

Primeiro o cliente informa sua URL e o consumidor a usa para descobrir qual sua identificação e quem é o provedor. Na versão 1.0 esses dados se encontram nas seguintes tags:
<link rel="openid.delegate" href="identidade" />
<link rel="openid.server" href="provedor" />


Na versão 2.0:
<link rel="openid2.local_id" href="identidade" />
<link rel="openid2.provider" href="provedor" />


É interessante verificar todos.

Obtidos esses dados, o próximo passo é o ajuste de verificação (checkid_setup): o consumidor redireciona o cliente para o provedor, passando os seguintes dados em GET ou POST:
  • openid.mode: o modo, checkid_setup;
  • openid.identity: a identidade do cliente;
  • openid.return_to: a URL para onde o provedor deve redirecionar o cliente depois da autenticação;
  • openid.trust_root: o nome do servidor do consumidor;
  • openid.ns: a versão do OpenID, http://specs.openid.net/auth/2.0;
  • openid.claimed_id: a URL original fornecida pelo cliente (geralmente igual à identidade).


Depois disso o cliente se resolverá com o provedor. Depois o provedor redirecionará o cliente de volta ao consumidor, para a URL informada por return_to, passando os seguintes parâmetros em POST:
  • openid.mode: pode ser id_res se tudo correu bem, ou cancel;
  • openid.identity: a identidade do cliente;
  • openid.return_to: a mesma URL que o provedor recebeu no passo anterior;
  • openid.signed: lista de parâmetros cobertos pela assinatura;
  • openid.sig: string chave da assinatura;
  • openid.invalid_handle: se o provedor rejeitou algum parâmetro, ele será informado aqui.


No entanto é preciso verificar se esses dados vieram do provedor mesmo ou se foram forjados. Para tanto, valos ao próximo passo, a verificação da autenticação (check_authentication).

O consumidor acessa diretamente o provedor via HTTP reenviando os dados recebidos, mas mudando o parâmetro openid.mode para check_authentication.

O provedor deve responder o consumidor com uma página texto, com os parâmetros terminados por mudança de linha (LF), cada linha no formato chave:valor.

As chaves recebidas são:
  • is_valid: true caso o provedor tenha mesmo enviado os dados ou false;
  • invalid_handle: caso algum campo não tenha sido enviada pelo provedor, ele será informado aqui.


No entanto não é possível trocar dados de persona, que são as informações sobre o usuário.

Se for necessário obter informações de persona, é preciso usar o smart mode.

Smart mode


Esse modo é usado quando se deseja trocar dados de persona, como nome completo, endereço de correio, língua ou país.

Após o cliente ter enviado ao consumidor sua URL e o consumidor tenha identificado o provedor e a identidade, o consumidor precisa então escolher um par gerador-módulo para criptografia Diffie-Hellman. Geralmente é usado 2 como gerador e um primo seguro de 1024 bits como módulo.

O consumidor deve então escolher uma chave privada, um número grande maior que um e menor que o módulo menos um, e calcular a chave pública:
geradorchave_privada  chave_publica mod módulo


Todos esses números grandes devem ser convertidos em formato btwoc (big-endian signed two's complement), representados como base64.

Então é feita a associação: o consumidor acessa diretamente o provedor via HTTP, passando os seguintes dados via GET ou POST:
  • openid.mode: associate, indicando que se trata de uma associação;
  • openid.assoc_type: HMAC-SHA1 para chaves de 160 bits ou HMAC-SHA256 para chaves de 256 bits (acoselho 256b);
  • openid.session_type: DH-SHA1 ou DH-SHA256, conforme o parâmetro anterior;
  • openid.dh_modulus: o módulo escolhido no formato correto (base64 !! btwoc);
  • openid.dh_gen: o gerador no formato correto(se for 2, será Ag==);
  • openid.dh_consumer_public: a chave pública calculada.


A resposta será similar à resposta ao check_authorization, ou seja, parâmetros encerrados por mudança de linha e dois pontos (:) separando chave e valor.

Os parâmetros são:
  • assoc_type: o tipo de associação (o mesmo enviado pelo consumidor);
  • assoc_handle: uma chave que deve ser informada a cada conexão posterior;
  • expires_in: indica quando o assoc_handle expira;
  • session_type: o mesmo tipo enviado pelo consumidor;
  • dh_server_public: a chave pública do servidor;
  • enc_mac_key: o segredo criptogrado.


Então o consumidor deve armazenar esses dados e, assim como no dumb mode, efetuar a verificação de autenticação (checkid_setup), da mesma forma vista anteriormente, mas com umas chaves a mais.

O primeiro é a chave openid.assoc_handle, informando a mesma chave recebida anteriormente.

Caso queira obter dados de SRE (Simple Registration Extension, ou sreg), os parâmetros extra passados são:
  • openid.ns.reg: a versão: http://openid.net/extensions/sreg/1.1;
  • openid.sreg.required: os campos requeridos separados por vírgulas;
  • openid.sreg.optional: campos opcionais separados por vírgulas;
  • openid.sreg.policy_url: uma URL que informe o que será feito com os dados.


Caso queira obter dados de AX (Attribute Exchange), siga a URL. =P

Quando o cliente for redirecionado de volta para o consumidor, o consumidor receberá via POST os mesmos dados recebidos no dumb mode, mas com alguns parâmetros extra: openid.sreg.*, onde * é cada um dos campos requeridos e opcionais solicitados.

Resumo


Ou seja, em dumb mode:
  • checkid_setup
  • check_authentication


Em smart mode:
  • associate
  • checkid_setup


Alternativamente pode ser usado checkid_immadiate em vez de checkid_setup, geralmente quando trabalando com AJAX. Veja as especificações.

Espero ter ajudado.

[]'s
Cacilhas

quarta-feira, 8 de agosto de 2007

Pylons, ToscaWidgets e Unicode

Alguns dias atrás o Torcato anunciou que estava iniciando um pequeno projeto pra estudar Pylons, o Ferlons. Logo pedi autorização pra entrar no projeto e conhecer um pouco mais do ToscaWidgets.

Widgets são velhos conhecidos dos desenvolvedores Plone. A idéia é não escrever o formulário HTML, mas construir uma estrutura de dados que represente um campo em nosso esquema. Por exemplo, imagine uma classe usuário, com o nome do usuário e a data da admissão. Em vez de escrever um formulário para esses dados, o uso de widgets nos permite representar essas estruturas programaticamente, de forma que a geração do formulário seja automática e rica, incluindo um javascript para nos mostrar um calendário onde podemos escolher a data, e um pouco de CSS.

O TurboGears já inclui widgets há um bom tempo. Alberto Valderde extraiu os widgets do core do TurboGears e criou o projeto ToscaWidgets, que permite que esse widgets sejam usados por outros frameworks. Em nosso caso, incluímos o ToscaWidgets no Ferlons (Pylons).

Porém, incluí-lo não foi tarefa trivial, porque tivemos dois problemas chatos com Unicode. O método padrão para mostrar o widget renderizado é o display, que retorna um stream. Ao executar um print para esse stream, o resultado é uma string, mas ele tem caracteres Unicode lá dentro. O Mako passa um unicode() no resultado disso daí, e o resultado é um temido UnicodeDecodeError. Após alguns (4 dias :D) de testes, descobri como corrigir: em vez de chamar form.display no meu template, agora chamo form.render().decode('utf-8').

O outro bug era no validador, novamente relacionado com Unicode. Estamos usando o validador UnicodeString do FormEncode, porém ele não funcionava corretamente em nosso formulário. Após uma breve discussão na lista do TurboGears, descobri que o UnicodeString do FormEncode tem um problema com o ToscaWidgets, mas o próprio ToscaWidgets possui um validador Unicode, que funciona corretamente. Na verdade, o método o ToscaWidgets espera que o método from_python retorne uma string Unicode, ao passo que o método from_python do UnicodeString do FormEncode retorna uma string UTF-8. Um exemplo do que estou falando:

>>> from toscawidgets.widgets.forms import validators as tosca_validators


>>> tosca_unicode = tosca_validators.UnicodeString()
>>> tosca_unicode.from_python('é')
'\xc3\xa9'
>>> tosca_unicode.from_python(u'é')
u'\xe9'
>>> import formencode.validators as formencode_validators
>>> formencode_unicode = formencode_validators.UnicodeString()
>>> formencode_unicode.from_python('é')
'\xc3\xa9'
>>> formencode_unicode.from_python(u'é')
'\xc3\xa9'

Agora, livres (eu acho!) dos bugs de Unicode, ficamos livres pra realmente implementar o código. Parece fácil escrevendo aqui, mas tomou uma semana minha pra descobrir isso.

domingo, 29 de julho de 2007

Orientação a objetos

Orientação a objetos é o paradigma de programação por «simulação», ou seja, em vez de o programador pensar em procedimentos ou funções, ele usa o conceito de objetos, similares aos reais, que interagem entre si.

Cada objeto tem características próprias, executa procedimentos próprios e interage de uma maneira própria. Essa técnica também é chamada «programação antropomórfica» ou «programação por personificação».

Veja que não é necessário uma linguagem ou sintaxe com suporte a orientação a objetos, apenas que o programador seja capaz de pensar orientadamente a objetos. Porém uma linguagem ou sintaxe com suporte próprio ajuda bastante.

Na abordagem tradicional procedimental o foco da programação está nos procedimentos imperativos. Na programação funcional o foco está nas funções, passagem de parâmetros e tratamento de retorno.

Já na orientação a objetos o foco está nos objetos simulados e na interação entre eles.

Por exemplo, no famoso jogo da cobrinha, a cobra seria um objeto com características – tamanho por exemplo – e procedimentos – virar à esquerda, por exemplo. Outros objetos seriam a maça e as paredes.

A própria cobra é composta de secções, que por si são objetos com características – posição – e procedimentos – mudar a posição.

O jogo surge da interação entre os objetos.

As características de um objeto são chamadas atributos e os procedimentos métodos. Algumas linguagens permitem atributos especiais que executam métodos de forma transparente. Esses atributos são chamados propriedades.

Atributos e métodos usados para interagir com outros objetos são chamados interface.

Então orientação a objetos consiste em elaborar objetos similares a coisas físicas, com características próprias e uma interface, de forma a que da interação entre os objetos surja o resultado desejado.

Sintaxe


Não… não é da sintaxe de criação, comandos class e similares que vou falar.

Quero falar de como a sintaxe de uma linguagem deve ser para incitar a orientação a objetos.

Segundo Smalltalk, para uma perfeita orientação a objetos as instruções de um programa devem ser mensagens envidas a objetos.

Para incentivar a orientação a objetos é interessante que o primeiro elemento de uma mensagem seja o objeto chamado.

Por exemplo, fatorial de 5 em Perl (procedimental):
fatorial 5


Em Smalltalk:
5 fatorial


Em Perl, procedimental, é informado um procedimento – fatorial – a ser executado e é passado a esse procedimento o parâmetro 5.

Em Smalltalk, orientada a objetos, é informado um objeto – 5 – ao qual é passada a mensagem, dizendo para ele executar um método internamente.

Observação: em Haskell, linguagem funcional, não imperativa, a sintaxe seria, no caso em questão apenas, idêntica à de Perl, mas a lógica é diferente: é informada uma função matemática que necessita de um parâmetro. Ou seja, é diferente você dizer «execute esse comando» de «o resultado dessa função é».

Sintaxe ou paradigma?


É preciso se perguntar: «estou orientado a objetos ou estou usando uma linguagem que facilita orientação a objetos para programar sequencialmente?»

Não é pela linguagem ser orientada a objetos que o programa será. Também não é pela linguagem não ser orientada que o programa não será.

Programa é a representação de um algoritmo em uma linguagem determinada; se o algoritmo for orientado a objetos o programa também será, mas se o algoritmo não for, não faz diferença se a linguagem em si é ou não.

Aliás, muitos programadores deixam de estruturar por usar uma linguagem orientada a objetos, resultando em programas sequenciais.

Gente, muito cuidado no planejamento. Não confiem na linguagem para fazer tudo pra vocês.


Bem, está aí mais um artigo conceitual.

[]'s
Cacilhas

sábado, 28 de julho de 2007

Smalltalk

VisualWorks Faz tempo que quero escrever algo sobre Smalltalk.

Smalltalk é a linguagem de programação orientada a objetos. O conceito de orientação a objetos foi criado pela equipe que desenvolveu Smalltalk, no MIT durante o final da década de 1960, ainda antes da criação de C. A primeira versão oficial foi Smalltalk-71.

[update 2008-04-19]Pequena correção: o conceito de orientação a objetos foi aprimorado em Smalltalk. A linguagem Simula já era orientada a objetos antes de Smalltalk.[/update]


Em Smalltalk tudo são objetos. Números são objetos, classes são objetos, métodos são objetos, blocos de código, strings, etc., tudo mesmo.

Outros conceitos que leigos acreditam ter sido criados na década de 1990 também proveem de Smalltalk, como bytecode e máquina virtual. É como Smalltalk trabalha.

Para ter Smalltalk instalado, eu uso Cincom VisualWorks.


Este artigo tem por objetivo exibir alguns exemplos.

Vamos então ao famoso «Olá Mundo!»:

Se você também instalou VisualWorks, inicie um novo workspace, digite e execute:
Transcript show: 'Hello World'.
Transcript cr.


Dica: é preciso selecionar o código para executá-lo.

O botão para execução é o raiozinho (Do it).


Os comandos são chamados mensagens e têm o formato:
objeto método[: parâmetros].

Onde os parâmetros são outros objetos passados ao método do objeto que recebe a mensagem.

O objeto Transcript é a janela principal do VisualWorks, que tem o método show (mostrar). Como parâmetro, estamos passando o objeto 'Hello World', uma string.

O método cr de Transcript insere uma mudança de linha.

Chamadas a diversos métodos de um mesmo objeto podem ser concatenadas com ;:
Transcript show: 'Hello World'; cr.


Na verdade não é obrigatório usar o ponto como terminador de mensagem. Ele é apenas um separador de mensagens.

Podemos «picar» a string:
Transcript show: ('Hello World' copyFrom: 2 to: 4).
Transcript cr.


Vai exibir ell: assim como Pascal e Lua, os índices em Smalltalk começam em 1.

Para exibir um número:
Transcript show: (((3 + 5) negated) printString).
Transcript cr.


Vamos agora criar um coleção:
| ord |
ord := SortedCollection new.
ord add: 'Forca de Expressao'.
ord add: 'Monte Gasppa'.
ord add: 'Walter Cruz - devlog'.
Transcript show: (ord at: 1).
Transcript cr.


Ou ainda:
| ord |
ord := SortedCollection new.
ord add: 'Forca de Expressao'.
ord add: 'Monte Gasppa'.
ord add: 'Walter Cruz - devlog'.
ord do: [ :each | Transcript show: each; cr ].


Para exibir todos os número de 1 a 10:
(1 to: 10) do: [ :index |
Transcript show: (index printString); cr
].


Vamos ficar por aqui por enquanto… quando eu estiver mais desenferrujado, escreverei mais alguma coisa.

Apropósito, mais informações podem ser obtidas aqui.

[]'s
Cacilhas

OpenID

OpenID Passei essa semana quebrando a cabeça no trabalho para desenvolver um protótipo de módulo relying party em Lua.

Relying party – também chamado consumer – é um dos nós que participam de uma autenticação OpenID.

O que é OpenID?


OpenID é um sistema livre descentralizado de autenticação e autorização centrado no usuário.

Ou seja, atualmente em cada sistema com autenticação, você precisa fazer um cadastro. Então você usuário é obrigado a manter uma redundância inconveniente de dados, que eventualmente precisam ser atualizados.

Para piorar os sistemas centralizam dados de todos os usuários, inclusive as senhas. Daí você usuário precisa ter uma senha para cada sistema, tendo de lembrar toda uma fauna de sEnH45 complicadas – ou de 123mudar.

Alguns arriscam sua identidade digital usando a mesma senha para tudo, o que não é aconselhável nem de longe.

OpenID surge em meio a essa realidade como uma solução para o problema.

Você usuário centraliza todos seus dados e sua senha em um único servidor de sua confiança, chamado server-agent ou OpenID provider.

Então você pode autenticar-se em sistemas que ofereçam suporte a OpenID – chamados relying parties ou consumers – sem que o sistema conheça sua senha, apenas fornecendo uma URL conhecida como identidade OpenID (identity). Ainda mais: quando for atualizar seus dados, só é preciso atualizar no agente servidor, pois não há redundância.

É um sistema descentralizado porque você não é obrigado a cadastrar seus dados no sistema onde deseja ser autenticado.

Protocolo


Há ferramentas prontas para Python, JSP, Ruby e PHP.

No entanto Lua é uma linguagem com uma comunidade muito pequena ainda para reagir tão rápido às novidades. Considerando-se ainda a filosofia da linguagem – SIMPLE: simple, light-weight and extensible, simples, leve e extensível –, é compreensível a necessidade de desenvolver os próprios módulos.

Se por um lado é inconveniente, por outro, se você realmente gosta de programar, Lua se torna uma linguagem muito divertida.

Em miúdos: se você trabalha com programação mas não gosta, faz isso só pra ganhar dinheiro, pare de ler por aqui e vai trabalhar. Espere alguém fazer um módulo por você para poder usar OpenID. =P


Continuando então o artigo – agora para programadores de verdade e curiosos –, o problema para desenvolver um módulo consumidor é entender o protocolo.

O protocolo OpenID negocia tunelado dentro do protocolo HTTP.

Resumidamente, a lógica é a seguinte:
  1. O cliente (navegador) acessa o consumidor;
  2. O consumidor envia um formulário ao cliente solicitando sua identidade OpenID;
  3. O cliente informa ao consumidor sua identidade;
  4. O consumidor normaliza a identidade – fica algo como http://user.example.com/;
  5. O consumidor acessa via HTTP a URL da identidade e pega o código HTML;
  6. O consumidor procura na URL marcadores link com atributos rel contendo openid.server, openid.delegate, openid2.provider e openid2.local_id – esses marcadores informam através do atributo href quem é o agente servidor e qual a identidade real do cliente;
  7. O consumidor acessa via HTTP o agente servidor em busca das chaves de comunicação – esse processo é chamado associação;
  8. Tendo sido feita a associação, o consumidor usa as chaves para redirecionar o cliente para a página de autenticação do agente servidor;
  9. O cliente se autentica no agente servidor informando sua senha, se já não estiver atenticado;
  10. O agente servidor pergunta ao cliente se ele autoriza o consumidor a ter acesso a seu perfil;
  11. Se o cliente permitir, o agente servidor redireciona o cliente para o consumidor, passando os dados solicitados.


Vamos então para o primeiro passo que precisamos implementar:

Formulário


O action do formulário deve apontar para o script que irá negociar com o agente servidor e precisa ter uma entrada texto para a identidade.

Esta entrada geralmente tem a identificação openid_login e seu formato CSS padrão pode ser encontrado aqui.

Normalização


O próximo passo já é executado no script.

Ele deve pegar a URL e normalizá-la.

Isto é feito assim:
  • =id não muda;
  • xri://=id vira =id;
  • xri://$dns*id.ex.com vira http://id.ex.com/;
  • xri://$ip*a.b.c.d vira http://a.b.c.d/;
  • http://id.ex.com/ e https://id.ex.com/ não muda;
  • id.ex.com vira http://id.ex.com/.


Na verdade estamos aqui neste artigo interessados apenas nas normalizações que geram URLs HTTP e HTTPS.

Associação


Na associação o consumidor acessa o agente servidor via HTTP.

O agente servidor é obtido de um desses marcadores tirados da consulta ao HTML da identidade:
  • <link rel="openid.server" href="http://example.com/server" />
  • <link rel="openid2.provider" href="http://example.com/server" />
  • <link rel="openid.server openid2.provider" href="http://example.com/server" />


Observação: para uma consulta HTTP em Lua você precisa da função socket.http.request() de LuaSocket.

Na associação envie ao agente servidor via GET os seguintes parâmetros:
  • openid.mode=associate
  • openid.assoc_type=HMAC-SHA1
  • openid.sesion_type=no-encryption


Quanto a criptografia, o ideal é usar DH-SHA1 (Diffie Hellman), mas, até que alguém faça um módulo de baixo nível para Lua de gerenciamento de chaves grandes, Lua é muito lenta para lidar com tais chaves e somos obrigados a trabalhar sem criptografia. =(

Feita esta consulta, o agente servidor deve retornar duas chaves no corpo:
  • assoc_handle – «maçaneta» (alguma tradução melhor?) de associação;
  • mac_key – chave de código de autenticação de mensagem.


Guarde essas informações!

Redirecionamento


Agora é preciso redirecionar o cliente para a página de login do agente servidor.

Faça isso contruindo uma URL apontando para o agente servidor e levando os seguintes parâmetros (via GET mesmo):
  • openid.ns – indica a versão de OpenID usada, para 2.0: http://specs.openid.net/auth/2.0
  • openid.mode=checkid_setup
  • openid.identity – a identidade do cliente
  • openid.claimed_id – novamente a identidade
  • openid.assoc_handle – a «maçaneta» de associação recebida
  • openid.return_to – a URL do script que irá tratar a resposta


Depois disso o cliente vai negociar diretamente com o agente servidor.

Recebendo de volta


No final o cliente será redirecionado de volta para o consumidor, mais especificamente para a URL informada por openid.return_to.

Esse script receberá parâmetros POST, sendo o principal openid.mode, que, se for id_res, o cliente está autenticado e autorizou o consumidor.

Outros parâmetros


Todos os parâmetros podem ser encontrados na especificação do protocolo.

TODO


Este artigo aborda o básico do básico da atenticação.

Com ele ainda não é possível recolher dados de SRE (informações de perfil), que deveriam ser passados nos subparâmetros de openid.sreg.

Mas para tanto é preciso usar criptografia – pelo que entendi, posso estar errado.

De qualquer forma, aqui está uma boa explicação do funcionamento do protocolo OpenID.

[]'s
Cacilhas

segunda-feira, 16 de julho de 2007

Pylons: conhecendo aplicando

Tomei a coragem de escrever um post sobre Pylons, mas ao contrário do que disse numa outra oportunidade, não vou fazer o exemplo do livro de receitas. Tomo por base para este post e outros, uma aplicação de gestão de férias de empregados. Algumas coisas me influenciaram a escolher esse tipo de sistema, mas o principal é a raridade de encontrar exemplo parecido.

Outra diferença importante será o fato de não expor o código completo nos posts. Gostaria de colocar somente os mais importantes. O código-fonte está disponível aqui.

Análise de Requisitos

Queremos que o empregado possa acessar a aplicação via navegador web para solicitar suas férias. Ele pode marcá-las para no mínimo 20 dias após o pedido. Ainda pode sugerir duas datas para que o seu chefe imediato possa escolher a mais adequada. Essas datas precisam ter uma distância de pelo menos 15 dias corridos.

O empregado tem a opção de informar se deseja vender um terço das férias ou toda ela para receber em troca o bônus pecuniário.

As férias podem ser parceladas na seguinte forma: duas parcelas de 15 dias ou uma de 10 mais outra de 20 dias. Caso venda parte de suas férias não haverá parcelamento.

O parcelamento também é uma sugestão do empregado. O seu gerente é quem toma a decisão final.

Uma solicitação só pode ser aceita caso não haja uma anterior pendente. O sistema deve avisar com antecedência de um mês para o gerente do empregado que uma solicitação está a um mês para alcançar a data do início do período de férias mais próximo que o usuário pediu. Quando esta data chegar o sistema avisará novamente quando aproximar-se a segunda sugestão.

O gerente quando for atender uma solicitação deve ter em conta quais empregados já possuem férias para serem gozadas nas datas sugeridas pelo empregado. O sistema deve mostrar isso.

Após haver a possibilidade de férias em uma das datas, o próximo passo será verificar a possibilidade de compra das férias. Caso haja e se for total, essa opção será marcada. O empregado receberá seu abono na folha de pagamento seguinte à primeira data de sugestão. Caso seja a terça parte, o sistema registrará a data de início e fim em 20 dias corridos.

Não havendo possibilidade de venda ou o gerente não querendo comprar, resta saber se haverá parcelamento ou não. Fica a critério do gerente usar as datas sugeridas para parcelar ou somente uma delas.

Ao fechar o pedido de solicitação do empregado, estas férias estarão em estado de espera de gozo. O empregado não poderá fazer uma nova solicitação até o proximo ano ou até que tenha gozado. Além do mais, a nova solicitação será para o período de seguinte.

O sistema ainda poderá informar ao empregado, ao logar-se no sistema, que estará a ponto de acumular três férias sem gozar. Caso ocorra essa acumulação, automaticamente o período de férias mais antigo será prescrevido.

O projeto

O projeto será construído usando o framework Pylons e por isso dei o nome para ele de Ferlons, uma junção de férias e Pylons. Os formulários serão simples porém escolherei um template visualmente agradável na web para animar as coisas.

Pré-requisitos

Saber um pouquinho de Python, possuir a versão 2.4 ou 2.5 da linguagem instalada na máquina e a biblioteca do SQLite (claro, você poder usar o MySQL ou o PostgreSQL). Os comandos abaixo instalam o Pylons e o SQLAlchemy.


easy_install Pylons SQLAlchemy


Caso não tenha o easy_install na sua máquina acesse a página de downloads do Pylons para explicações.

Acompanhem os próximos posts onde cuidarei de mostrar um pouco mais. Possivelmente o uso do AuthKit.

terça-feira, 3 de julho de 2007

Pylons

Esse texto é baseado em um e-mail que eu mandei para a lista PythonBrasil, porém mais completo que o e-mail.


Pylons é uma das opções que o desenvolvedor Python tem dentro dos frameworks para desenvolvimento web.

Um dos seus diferenciais é que ele é todo centrado em WSGI - Web Server Gateway Interface, que é uma PEP que descreve como os aplicativos devem se comunicar com o servidor web. Qual a vantagem disso? Uma modularização razoavelmente padronizada que é totalmente interessante.


Suporta SQLObject e SQLAlchemy (a maioria atualmente esteja dando preferência ao último). Embora o SQL Alchemy use o padrão de projeto DataMapper é possível transformá-lo em um ActiveRecord usando o Elixir. Isso com certeza irá agradar os usuários do rails.


Não só isso – a integração com JavaScript é feita à la Rails, incluindo os helpers com suporte a Prototype e Scriptaculous e o sistema de despacho do Pylons é baseado no do Ruby (Routes).


Usando os mecanismos do WSGI, a autenticação pode ser feita por um middleware, o AuthKit. Para algo ultra-simples, um cadastro que apenas um usuário vá usar, é possível gerar um formulário de login usando apenas as configurações do middleware. Simples e funcional.


Quais seriam as vantagens desses middlewares e do amplo suporte a WSGI? Um exemplo simples: você tem uma aplicação com o Pylons, e precisa adicionar um wiki a ela. É fácil desenvolver um, mas que tal usar o MoinMoin? Mas e a base de usuários, terei de duplicar? Bom, como o MoinMoin também implementa interfaces pra WSGI, você embute ele em um controlador e usa a própria autenticação do AuthKit. Interessante não?


Para modelos, o Pylons usa o Buffet, que é uma API de intercambiabilidade de motores de modelo. No cardápio, Myghty, Mako, Genshi e Kid e Cheetah (por enquanto).

Dos três principais frameworks pra web em Python (Django[2003-2004],TurboGears[início de 2005] e Pylons[fim de 2005]), o Pylons é o mais novo. Mas nem por isso deixa de surpreender: a movimentação para o TurboGears 2.0 é que ele seja implementado em cima do Pylons! Só por esse tipo de notícia já vale a pena colocar o Pylons na sua lista de coisas a testar!

sábado, 23 de junho de 2007

LuaWsgi

Lua/Python Como eu havia prometido nas Reflexões de Monte Gasppa e Giulia C., aqui está o artigo sobre LuaWsgi.

Histórico


Eu ia contar aqui a história de como surgiu o LuaWsgi, mas decidi não fazer isso.

Basta dizer que envolve intriga, maus entendidos e até uma certa dose de má fé.

Mas também gostaria de dizer que desenvolvi o primeiro protótipo funcional em apenas um domingo de diversão.

Outra coisa que é importante dizer é que LuaWsgi nasceu como uma terapia e resolvi publicá-lo para ser meu trabalho de conclusão de curso.

O que é WSGI?


WSGI é uma proposta formal de melhoria para Python, PEP, que sugere uma nova abordagem para o desenvolvimento web mais simpática para programadores.

A forma original como páginas web foram concebidas é muito interessante para não-programadores, pois o HTTP era apenas uma extensão do FTP voltada para a visualização.

Usando HTML, uma linguagem de marcação, era possível criar páginas atraentes com pouco ou nenhum conhecimento de programação.

Porém «marcação» não permite muitos recursos usados na programação. Para suprir essa necessidade, foram criados o CGI as páginas dinâmicas, que são geradas por um programa.

Depois criaram os servlets.

Até aqui, no entanto, o formato continuava desconfortável para programadores – apesar de «arrastadores de componentes» (component drag-n-droppers) se sentirem verdadeiros programadores assim e de alguns servlets serem bastante amigáveis.

Então Phillip J. Eby sugeriu uma API mais interessante para Python: WSGI.

E WSGI é ainda mais! A proposta foi tão bem feita que pode ser implementada em quase qualquer linguagem de programação, quase como um protocolo!

Para saber mais leia a PEP 333.

O que é Lua?


Lua é uma linguagem de programação livre projetada e implementada em 1993 por uma equipe no Lablua da PUC-Rio.

Atualmente se encontra na versão 5.1.2.

É uma linguagem estruturada, orientada a tabelas e com suporte a orientação a objetos.

Sua tipagem é dinâmica, como Python. Não é tão forte quanto a tipagem de Python, mas possui sete tipos bem definidos e de conversão simplificada quando possível.

Para saber mais, leia o livro do Roberto Ierusalimschy.

O que é LuaWsgi?


É uma implementação de WSGI para linguagem de programação Lua da forma mais precisa possível.

Dependências


Para instalar LuaWsgi você precisa instalar antes:


Mas há um jeito mais fácil…

Você pode simplesmente instalar o Projeto Kepler, que já traz todos os módulos que vamos precisar.

Até agora – o momento em que escrevo – não há uma versão final do Kepler 1.1. Se quando você for baixar houver, baixe-a. Se não, baixe o último snapshot.

Obs.: para instalar Kepler 1.1, você precisa ter instalado Expat, ZZipLib, algum SGBD e GNU Readline (você provavelmente possui algum ou todas as dependências, mas é bom verificar).

Não siga as instruções de instalação de Kepler. Faça o seguinte:

Desempacote o tarball (tar xzvf kepler-*.tar.gz). Isso irá gerar um diretório kepler-1.1/. Acesse-o.

Então execute o ./configure com as seguintes opções:
bash$ ./configure --prefix=/usr \
--sysconfdir=/etc \
--lua-suffix='' \
--kepler-web=/var/xavante \
--with-optional=lualogging,luasql,luaexpat,luazip,md5 \
--enable-lua


O que estamos fazendo é dizendo para o configurador onde o instalador deve colocar os arquivos, que módulos queremos usar (todos) e para instalar Lua também.

Você também pode usar a opção --with-luasql-driver= para especificar qual SGBD você está usando. Para obter informações tente:
bash$ ./configure --help


Se tudo correr bem, o resto é instalação padrão:
bash$ make
bash$ sudo make install


Se não houver problemas, você já terá todos os módulos que precisará instalados – e mais alguns úteis. Se quiser testar o servidor web Xavante, ele também estará instalado – mas aí já é outro assunto.

Opa! Falta ainda instalar o lua-unistd!

Você tem duas escolhas: 1baixar o fonte e compilá-lo ou 2baixar o binário pré-compilado.

Acredito que o binário seja suficiente, mas se você tiver problemas, para compilar o fonte o comando é:
bash$ gcc -c -fPIC -O3 -Wall unistd.c
bash$ ld -shared -lm -lcrypt unistd.o -o libunistd.so.1.0.1


Mova a biblioteca para /usr/lib/lua/5.1/ e crie neste mesmo diretório uma ligação (link) simbólica chamada unistd.so:
bash# ln -s libunistd.so.1.0.1 unistd.so


Configurando o sistema


Apesar de Kepler não precisar de variáveis de ambiente, LuaWsgi precisa. Crie as seguintes variáveis:
export LUA_PATH=?.lua;?/?.lua;/usr/share/lua/5.1/?.lua;/usr/share/lua/5.1/?/?.lua


Se estiver difícil de visualizar no navegador, trata-se da seguinte sequência, trocando as mudanças de linha por ponto-e-vírgula:
?.lua
?/?.lua
/usr/share/lua/5.1/?.lua
/usr/share/lua/5.1/?/?.lua


A outra variável é:
export LUA_CPATH=?.so;l?.so;/usr/lib/lua/5.1/?.so;/usr/lib/lua/5.1/?/?.so


Idem acima:
?.so
l?.so
/usr/lib/lua/5.1/?.so
/usr/lib/lua/5.1/?/?.so


Aconselho colocar esses comandos em /etc/profile

Agora, para testar, execute o seguinte:
bash$ lua
Lua 5.1.2 Copyright (C) 1994-2007 Lua.org, PUC-Rio
> require "unistd"


Se nenhum erro ocorrer, você já está com o ambiente configurado! ;)

Finalmente: instalando LuaWsgi


Essa parte é complicada… =D hehehehehe

Baixe o pacote mais recente da página de arquivos (a versão mais recente no momento em que escrevo é a 7.06.1).

Desempacote o tarball em /usr/share/lua/5.1/:
bash# tar xzvf luawsgi-*.tar.gz -C /usr/share/lua/5.1/


E pronto!

Primeiro exemplo


Crie um arquivo (pode ser em seu homedir) chamado teste.lua:
require "wsgi"
require "wsgi.session"
require "middleware.auth"


local function authhandler(user, pass)
return user == "eu" and pass == "senha"
end


local function app(environ, start_response)
-- Cria ambiente de sessão
local session = wsgi.session(environ, start_response)

-- Se não houver um login, lê de POST
if not session.login then
print "Criando login com dados de POST"
session.login = environ.params.login
end
print("Usuário: " .. session.login)

-- Configura status e cabeçalho (não envia)
start_response { "200 OK";
["Content-Type"] = "text/html; charset=utf-8",
}

-- Encerra retornando o corpo da página
return {
"<html>\n",
"<head>\n",
"<title>Ola ",
(environ.params.login or ""),
"</title>\n",
"</head>\n",
"<body>\n",
'<form action="" method="POST">\n',
'<input type="text" id="login" ',
'name="login" />\n',
'<input type="submit" />\n',
"</body>\n",
"</html>\n",
}
end


-- Este middleware garante autenticação
app = middleware.auth(app, authhandler)
wsgi.serve { app; host="*", port=8001 }


O submódulo wsgi.session permite um acesso fácil a sessão e o submódulo middleware.auth oferece recursos para autenticação.

Salve execute:
bash$ lua teste.lua


E acesse no navegador http://localhost:8001. Veja na função authhandler() que o usuário é «eu» e a senha é «senha».

Descendo o rio… AJAX


Vamos usar AJAX? Para isso temos o submódulo wsgi.ajax. Crie testeDeAjax.lua:
require "wsgi"
require "wsgi.ajax"


local function app(environ, start_response)
-- Cria ambiente AJAX
local ajax = wsgi.ajax(environ, start_response)

ajax.export("som", function (x, y) return x + y end)
ajax.export("sub", function (x, y) return x - y end)
ajax.export("mul", function (x, y) return x * y end)
ajax.export("div", function (x, y) return x / y end)

-- Descomente para depuração
--ajax.setdebug()

-- Responde AJAX
if ajax.handle then
return ajax.handle()
end

-- Configura status e cabeçalho
start_response{ "200 OK";
["Content-Type"] = "text/html; charset=utf-8",
["Cache-Control"] = "no-cache, must-revalidate",
["Pragma"] = "no-cache",
}

-- Retorna o corpo da página (recheado de JavaScript!)
return {[[<html>
<head>
<title>Teste de AJAX</title>
<script language="javascript"><!--
]],
ajax.get_js(),
[[
function set_x(x) {
document.getElementById('x').value = x;
document.getElementById('y').value = '';
}

function som() {
var x, y;
x = document.getElementById('x').value;
y = document.getElementById('y').value;
ajax_som(x, y, set_x);
document.getElementById('y').focus();
return true;
}

function sub() {
var x, y;
x = document.getElementById('x').value;
y = document.getElementById('y').value;
ajax_sub(x, y, set_x);
document.getElementById('y').focus();
return true;
}

function mul() {
var x, y;
x = document.getElementById('x').value;
y = document.getElementById('y').value;
ajax_mul(x, y, set_x);
document.getElementById('y').focus();
return true;
}

function div() {
var x, y;
x = document.getElementById('x').value;
y = document.getElementById('y').value;
ajax_div(x, y, set_x);
document.getElementById('y').focus();
return true;
}
//--></script>
<style rel="stylesheet"><!--
body {
font-size: 14pt;
text-align: center;
}
--></style>
</head>
<body onLoad="document.getElementById('x').focus(); return true;">
<input type="txt" id="x" value="2" />
?
<input type="txt" id="y" value="3" />
<br />
<input type="button" value="+" onClick="som()" />
<input type="button" value="-" onClick="sub()" />
<input type="button" value="×" onClick="mul()" />
<input type="button" value="÷" onClick="div()" />
</body>
</html>
]]
}
end

wsgi.serve { app; host="*", port=8001 }


Execute:
bash$ lua testeDeAjax.lua


Agora acesse: http://localhost:8001.

Uma brincadeira com JSON


Fala-se muito atualmente em XML atualmente, que é um recurso interessante e até eficiente para o que se propõe.

Mas é um descaramento afirmar que XML seja pau pra toda obra. Quem diz isso ou está iludido ou está querendo enganar alguém.

Por exemplo, para transferência de dados em tempo real, XML é altamente ineficiente.

Para tanto temos JSON.

Além de LuaWsgi trazer integrado JSON4Lua, o submódulo wsgi.ajax possui uma função que adapta o comportamento de uma função para trabalhar com JSON.

Vamos ao exemplo testeDeJson.lua:
require "wsgi"
require "wsgi.ajax"

local function dobro(t)
print(t.val)
return { val = t.val * 2 }
end

local function app(environ, start_response)
local ajax = wsgi.ajax(environ, start_response)

-- Esta é a função JSON-import → jsonport ;)
ajax.jsonport("nada", dobro)

if ajax.handle then
return ajax.handle()
end

start_response{ "200 OK";
["Content-Type"] = "text/html; charset=utf-8",
["Cache-Control"] = "no-cache, must-revalidate",
["Pragma"] = "no-cache",
}

return {[[<html>
<head>
<title>Teste de AJAX</title>
<script
type="text/javascript"
src="http://www.json.org/json.js"
></script>
<script language="javascript"><!--
]],
ajax.get_js(),
[[
function set_x(x) {
var e = x.parseJSON();
document.getElementById("x").value = e.val;
return true;
}

function nada() {
var o = { "val": document.getElementById("x").value };
ajax_nada(o.toJSONString(), set_x);
return true;
}
//--></script>
<style rel="stylesheet"><!--
body {
font-size: 14pt;
text-align: center;
}
--></style>
</head>
<body>
<input type="text" id="x" value=" " />
<input type="button" value="Vai!" onClick="nada()" />
</body>
</html>
]]
}
end

wsgi.serve { app; host="*", port=8001 }


Mesmo esquema!

Conclusão


Ainda há mais o que ver sobre LuaWsgi, como o middleware publicador (middleware.publisher) e mais algumas coisinhas interessantes, mas este artigo já se tornou extenso demais. =(

Estão todos convidados a experimentar LuaWsgi e, mais importante, a participar de seu desenvolvimento!

[]'s
Rodrigo Cacilhas

sábado, 2 de junho de 2007

Re: desafio de criptoanálise

Baal Há pouco mais de um mês postei um desafio de criptoanálise até um tanto malicioso.

O desafio consistia em uma cifra e algumas perguntas a serem respondidas:
  1. A mensagem é na verdade uma pergunta. Qual a pergunta?
  2. Qual a resposta à pergunta?
  3. Qual o algoritmo criptográfico usado?
  4. Qual o erro cometido?


Algumas dicas foram fornecidas nos comentários:
  • A chave simétrica está escondida no próprio criptograma.
  • É preciso trabalhar com os códigos ASCII dos bytes.
  • É preciso efetuar uma adição com esses códigos.
  • O resultado é binário, e você vai descobrir se deu certo quando o comando file retornar algo diferente de data.


Agora vou responder às perguntas 3 e 4 e deixar o resto com vocês!

Qual o algoritmo criptográfico usado?


O algoritmo criptográfico usado foi Vigenère-Vernam, também conhecido como «one time pad».

Este algoritmo é uma variação da Cifra de Vigenère onde a chave pseudo-aleatória tem o mesmo tamanho da mensagem. Isto torna a cifra matematicamente inquebrável.

Para cifrar a mensagem a chave foi subtraída da mensagem byte a byte, sempre somando 256 quando o resultado era negativo. Portanto, tendo a chave fica simples criar um código que extraia a mensagem.

Qual o erro cometido?


No entanto, como a entropia é muito alta, é impossível (de verdade) identificar a chave.

Mas foi cometido um erro intencional! E já contei qual foi…

Olhem lá nas dicas… num dos comentários eu disse:
É… pelo visto está difícil.

Vou dar mais uma dica: a chave é simétrica e está escondida no próprio criptograma.

[]'s


Eu simplesmente concatenei a chave no final da cifra!

Portanto a primeira metade do arquivo é a cifra em si, a segunda metade é a chave. =P


Que tal quebrar a cifra agora?

Então continuam as duas primeiras perguntas e adiciono mais uma:
— Qual o formato da mensagem (file pode ajudar)?

Vamos lá! A brincadeira ficou mais fácil! Postem seus códigos nos comentários! =D

[]'s
Rodrigo Cacilhas

sexta-feira, 1 de junho de 2007

SQLAlchemy no TurboGears

Depois de tempos postando sobre política posso dizer que estou voltando a ativa novamente. Neste post trataremos de um assunto que nunca mais havia tocado: TurboGears.

A minha framework web favorita escrita em Python está na versão 1.0.2.2 e correndo para a versão 1.1, principalmente depois de um sprint que ocorreu recentemente. Os desenvolvedores substituirão o sistema de templates Kid pelo Genshi e o SQLObject pelo SQLAlchemy. O que posso dizer é que podemos usar o SQLAlchemy atualmente sem muitos problemas. Constatei isso implementando um exemplo bem conhecido em Ruby on Rails: um livro de receitas eletrônico (cookbook em inglês).

Vamos mostrar algum código, uma vez que isso é mais divertido. Precisamos do TurboGears e do SQLAlchemy:
easy_install -U TurboGears
easy_install SQLAlchemy

Antes de criarmos uma infraestrutura para o projeto, gostaria que vocês conseguissem o servidor de banco de dados MySQL. Vamos fazer o nosso exemplo de acordo com o do Rails. Se vocês quisserem fazer o mesmo exemplo em Rails sugiro lerem alguns posts do blog do Brasília on Rails. Vamos criar nosso banco de dados com o seguinte comando num terminal bash do Linux:
$ mysql -u root -p
> create database cookbook;
> grant all on cookbook.* to 'usuario@localhost';

O esquema do banco é o seguinte:
drop table if exists recipes;
drop table if exists categories;
create table categories ( id int not null auto_increment,
name varchar(100) not null default '',
primary key(id)
) engine=InnoDB;

create table recipes ( id int not null auto_increment,
category_id int not null,
title varchar(100) not null default '',
description varchar(255) null,
date date null,
instructions text null,
constraint fk_recipes_categories foreign key (category_id) references categories(id),
primary key(id)
) engine=InnoDB;

Colem este esquema num arquivo (digamos, create.sql) e executem:
mysql cookbook <dbcreate.sql

Feito isso, vamos ao TurboGears:

tg-admin quickstart --sqlalchemy

Chamarei o projeto de cookbook. Selecionem 'não' quando perguntado se querem usar o Identity.

Dentro da pasta do projeto há o arquivo dev.cfg para as configurações no ambiente de desenvolvimento. Lá informaremos à aplicação a string de conexão:

sqlalchemy.dburi="mysql://root:oinc@localhost/cookbook"
Executemos o script start-cookbook.py para rodar o servidor. Depois acesse o endereço http://localhost:8080/ para testar a aplicação.

python start-cookbook.py

Depois disso, vamos no arquivo cookbook/model.py para mapear para objetos o nosso banco com SQLALchemy:
from sqlalchemy import *
from turbogears.database import metadata, session, bind_meta_data
#from sqlalchemy.ext.assignmapper import assign_mapper

bind_meta_data()

categoria_tabela = Table('categories',metadata,autoload=True)

receita_tabela = Table('recipes',metadata,autoload=True)

class Categoria(object):
def __init__(self,name):
self.name = name
def __repr__(self):
return self.name

class Receita(object):
def __init__(self,title):
self.title = title
def __repr__(self):
return self.title
def nome_categoria(self):
return self.categoria.name

mapper(Categoria, categoria_tabela, properties={'receitas' : relation(Receita,backref='categoria')})
mapper(Receita, receita_tabela)

Para o post não ficar muito grande, faremos somente uma página que lista todas as receitas. Eu não cheguei a implementar todo o exemplo do Cookbook, no entanto é muito mais do que mostrarei para vocês daqui pra frente.

Copiem o template welcome.kid e criem o arquivo receita_list.kid. Retirem o corpo da página e coloquem somente o seguinte:
${grid.display(receitas)}

Editem o arquivo cookbook/controllers.py. Escreveremos um método que acesse o banco de dados para pegar todas as receitas e passe essa lista para o template que criamos.

@expose(template="cookbook.templates.receita_list")
def receitas(self):
receitas = session.query(Receita).select()
grid = DataGrid(fields=[('Nome','title'), ('Categoria',Receita.nome_categoria)])
return dict(receitas = receitas, grid = grid)

Para que esse método funcione, temos de importar model.py e widgets.DataGrid.

from model import *
from turbogears.widgets import DataGrid

A primeira linha do método utiliza o objeto session para fazer uma consulta no banco na tabela recipes (mapeada pela classe Receita). Na segunda linha, instanciamos o DataGrid que será usado para gerar a tabela de resultados. Por fim, a última declara os objetos que serão usados em nosso template.

Acessemos a URL http://localhost:8080/receitas. Espero que tenha funcionado! ;) Problemas e dúvidas, por favor, comentem.

terça-feira, 29 de maio de 2007

CSV Sniffer

O módulo csv do python pode lidar não apenas com arquivos separados por vírgula, mas com arquivos separados por qualquer delimitador, como tabulações, por exemplo.

Para especificarmos o 'dialeto' do CSV, especificamos uma classe que herda de csv.Dialect. Um exemplo:


class dialeto(csv.Dialect):
delimiter = ';'
skipinitialspace = True
quotechar = '"'
doublequote = True
lineterminator = "\n"
quoting = csv.QUOTE_ALL
reader = csv.reader(open("tab_municipios.csv", "rb"),dialeto)


Mas podemos deixar isso um pouco mais fácil: usar o sniffer do módulo csv pra detectar o dialeto! Exemplo:



dialeto=csv.Sniffer().sniff(open('tab_municipios.csv', 'r').read())
reader = csv.reader(open("tab_municipios.csv", "rb"),dialeto)


Dica retirada e adaptada de redmountainsw.com

quinta-feira, 10 de maio de 2007

Oddwording

Poliedro Tem corrido por aí uma brincadeira chamada oddwording (do inglês odd words, «palavras ímpares»).

A idéia é pegar uma frase, indexar as palavras a partir de zero e inverter as palavras de índice ímpar.

Fiz três códigos: Python, Lua e Haskell.

Python


Dos três é o menor. Precisei criar apenas uma função oddword(). Essa função gera um reiterador.

Para começar, vamos ao cabeçalho Python padrão:
# -*- coding: UTF-8 -*-

import re
import os, sys


O módulo re (expressões regulares) será usado para quebrar a frase em palavras.

A idéia da função é partir a frase em uma lista de palavras. Será usada uma variável odd, que marca verdadeiro quando o índice for ímpar. Quando for par, retorna a palavra, quando for ímpar, retorna a inversão da palavra:
def oddword(phr):
odd = False
for w in re.findall(r'\S+', phr):
if odd:
yield w[::-1] + " "
odd = False
else:
yield w + " "
odd = True
yield "\n"


Essa função já faz tudo. Vamos a uma «main» que demonstre seu funcionamento:
if __name__ == "__main__":
print("Digite uma frase:")
sys.stdout.write("> ")
ph = sys.stdin.readline().strip()

while ph:
wordproc = oddword(ph)
while True:
try:
word = wordproc.next()
sys.stdout.write(word)
except StopIteration:
break

print("Digite uma frase:")
sys.stdout.write("> ")
ph = sys.stdin.readline().strip()


[update 2007-05-11]
O Carlos Eduardo sugeriu nos comentários o uso de list comprehension.

Então o código Python fica assim:
import re
import os, sys

oddword = lambda fr: " ".join([
(i%2 and e[::-1] or e)
for i, e in enumerate(re.findall(r'\S+', fr))
])

print("Digite uma frase:")
sys.stdout.write("> ")
ph = sys.stdin.readline().strip()

while ph:
print(oddword(ph))

print("Digite uma frase:")
sys.stdout.write("> ")
ph = sys.stdin.readline().strip()


[/update]



Lua


Na verdade comecei fazendo em Lua.

A primeira coisa que fiz foi a função para inverter cada palavra. Comecei com um algoritmo reiterativo estilo C:
function inv(s)
local ns = ""
local i
for i = 1, #s do
ns = s:sub(i, i) .. ns
end
return ns
end


Mas esse código é pouco inteligível. Então, depois de fazer em Haskell, pensei que eu poderia implementar o algoritmo recursivo de Haskell em Lua.

Antes que alguém venha me dizer que recursividade é menos eficiente do que reiteratividade, isso é válido para algoritmos em árvore. Para recursões lineares, o algoritmo recursivo é quase tão eficiente quanto o reiterativo, só que mais claro. Aliás, pretendo escrever outro artigo sobre eficiência de algoritmos.

A função reiterativa ficou assim:
function inv(s)
if #s == 0 then
return ""
else
return inv(s:sub(2)) .. s:sub(1, 1)
end
end


A função oddword() em Lua funciona da mesma forma que em Python, mas usando corrotina.

Em Python, a função para «picotar» a string em palavras é re.findall(), em Lua é string.gmatch() (que retorna um reiterador em vez de um lista):
function oddword(phr)
local co = coroutine.wrap(function ()
local w
local odd = false
for w in phr:gmatch "%S+" do
if odd then
coroutine.yield(inv(w) .. " ")
odd = false
else
coroutine.yield(w .. " ")
odd = true
end
end
coroutine.yield "\n"
end)

return co
end


A variável odd funciona da mesma forma que em Python.

O código exemplo:
local ph
print "Digite uma frase:"
io.write "> "
ph = io.read()

while #ph > 0 do
local word
local wordproc = oddword(ph)

word = wordproc()
while word do
io.write(word)
word = wordproc()
end

print "Digite uma frase:"
io.write "> "
ph = io.read()
end


Haskell


Programação funcional é uma coisa maravilhosa. Certa vez estava conversando com o Torcato, e ele me disse que seu código melhorou muito depois que ele começou a estudar Haskell com mais seriedade. Então me toquei que o meu também.

A programação funcional força o programador a ter bons hábitos de programação, como estruturar o código em funções simples, top-down, usar reiteração de forma eficiente e memoização.

Em função disso infelizmente o código em Haskell é o mais longo. =(

O código começa com o cabeçalho padrão:
module Main where

import IO


Poderia ter feito diferente… poderia ter criado um módulo OddWord e importá-lo no programa principal. Mas preferi fazer assim para ficar mais simples.

Como há um ciclo que se repete, foi criada uma função para tanto, deixando a «main» muito pequeninha:
main = mainloop


Onde mainloop é a função que faz o ciclo. O que essa função faz? Chama outra (readString) que lê a frase. Se a frase for vazia, sai, se não, faz oddwording na frase, exibe o resultado e volta ao princípio.

Ah! Essa função não recebe nem retorna dados (IO ()).
mainloop :: IO ()
mainloop = do
ph <- readString
if ph == ""
then
return ()
else do
let nph = oddword ph
putStrLn nph
mainloop


A função para ler uma string é simples:
readString :: IO String
readString = do
putStrLn "Digite uma frase:"
putStr "> "
s <- getLine
return s


A função oddword precisa:
  1. Converter a frase para uma lista de palavras (strToList);
  2. Inverter as palavras de índice ímpar (invodd);
  3. Converter a lista de palavras de volta para uma string única (listToStr).


Há um pipe simples para concatenar as funções em Haskell: $.

Portanto, a função oddword será simplesmente:
oddword :: String -> String
oddword s = listToStr $ invodd $ strToList s


Agora precisamos definir as funções usadas.

A primeira, strToList, precisa receber uma string, percorrer ela procurando por ocorrências de espaço, retornando uma lista das strings entre os espaços:
strToList :: String -> [String]
strToList "" = []
strToList s = (take spi s) : (strToList (drop (incr spi) s))
where spi = nextspace s


Ops…! Chamamos duas funções que não existem (incr e nextspace)… então vamos precisar defini-las.

A função para retornar um valor incrementado é estupidamente simples, e há duas formas.

Uma é usando lambda:
incr :: Int -> Int
incr = \x = x + 1


A outra é usando preenchimento parcial de parâmetros ([update 2007-05-11]acho que é construção parcial ou algo assim[/update]):
incr :: Int -> Int
incr = (+ 1)


Ambos funcionam igualmente. Tanto faz usar uma ou outra.

A função para identificar o próximo espaço deve retornar zero se encontrar um espaço, se não, 1 mais o retorno da mesma função para a string começando na próxima posição:
nextspace :: String -> Int
nextspace "" = 0
nextspace (x:xs) =
if x == ' '
then
0
else
1 + (nextspace xs)


A próxima função a ser avaliada é listToStr, que é mais simples: deve apenas concatenar os elementos usando espaço como separador:
listToStr :: [String] -> String
listToStr [] = ""
listToStr (x:xs) = x ++ " " ++ (listToStr xs)


Falta apenas uma função… que na verdade vai virar três. =P

A idéia da invodd é inverter só os índices ímpares. Como o índice inicial é considerado par, ela não pode inverter o elemento, e deve chamar uma função que inverta o próximo elemento (cujo índice é ímpar).

Essa nova função (inveven) deve inverter o elemento atual e chamar invodd para o próximo elemento (par):
invodd :: Ord a => [[a]] -> [[a]]
invodd [] = []
invodd (x:xs) = x : (inveven xs)

inveven :: Ord a => [[a]] -> [[a]]
inveven [] = []
inveven (x:xs) = (inv x) : (invodd xs)


Faltou só a função de inversão! Ela é extremamente simples e funciona da mesma forma que a função de inversão em Lua (aliás, lembrando, fiz a função em Lua a partir da função em Haskell):
inv :: Ord a => [a] -> [a]
inv [] = []
inv (x:xs) = (inv xs) ++ [x]


Está feita!!! É só compilar e ver funcionando.


Terminei a brincadeira.

Ah sim! É bom citar que o Walter me disse que a brincadeira começou no Dr. Dobbs.

[]'s
Rodrigo Cacilhas

terça-feira, 24 de abril de 2007

Desafio de criptoanálise

Baal Certa vez um jornalista (esqueci o nome dele) dito especialista em criptoanálise e espionagem escreveu um livro (romance, se não me engano) onde tentava provar que não existe algoritmo criptográfico seguro (inquebrável).

Segundo ele, o que há são algoritmos virtualmente inquebráveis, ou seja, não possuímos ainda ferramentas para quebrá-los, mas no futuro os computadores quânticos poderão quebrar criptogramas de quaisquer tipos numa fração de segundos.

No entanto ele está errado.

Existem algoritmos matematicamente inquebráveis.

Para que um computador atual não consiga quebrar um criptograma basta que a probabilidade de quebra tenda a zero, mas um computador quântico (hipotético) poderia quebrar qualquer criptograma em fração de segundos não importando quão ínfima seja a probabilidade de quebra, a não ser que…

A não ser que a probabilidade seja zero. Agora a quebra de paradigmas: existem algoritmos criptográficos onde a probabilidade de quebra é exatamente zero.


Dito isso tudo, segue aqui o desafio:

Tenho um criptograma aqui (MD5), cifrado usando um algoritmo seguro.

Porém «cometi um erro» propositalmente que permite a quebra do criptograma.

A brincadeira é responder às seguintes perguntas:

  1. A mensagem é na verdade uma pergunta. Qual a pergunta?

  2. Qual a resposta à pergunta?

  3. Qual o algoritmo criptográfico usado?

  4. Qual o erro cometido?



Podem ser usadas quaisquer linguagens de programação (gostaria de ver a solução em Python).

Divirtam-se! E por favor usem os comentários do Kodumaro para discutir o assunto.

[]'s
Rodrigo Cacilhas

PS: Alguém poderia por favor calcular a entropia do criptograma?

sábado, 14 de abril de 2007

Processando XML com Lua

Lua O Projeto Kepler oferece um módulo de interface com Expat, um processador XML. O módulo se chama LuaExpat.

Na verdade o módulo oferece diretamente apenas uma função: lxp.new(), que retorna um objeto processador de XML (parser). Esta função recebe como parâmetro uma tabela especial de callbacks, que são as funções responsáveis por tratar os elementos XML.

Se for passado como parâmetro uma tabela vazia, o parser apenas verificará a integridade do código XML.

Portanto, para usar LuaExpat para tratar XML, é preciso conhecer duas coisas: os callbacks e o parser.

Callbacks


Na tabela de callbacks, as chaves devem possuir nomes específicos que indicam em que caso cada callback será usado. Os valores são funções: os callbacks.

Há toda uma lista de callbacks (neste momento em que escrevo há quinze callbacks). Vamos dar uma olhadinha apenas em três principais: StartElement, EndElement e CharacterData.


StartElement é chamado quando é encontrada a abertura de um elemento (tag), por exemplo <xhtml:div id="main">. A função possui três argumentos: parser, elementName (nome do elemento) e attributes (atributos).

O primeiro argumento recebe o próprio parser.

O segundo, elementName, recebe o nome do elemento (no exemplo, xhtml:div).

O terceiro, attributes, recebe uma tabela com os atributos, tanto de forma indexada quanto associativa. Assim, no exemplo (<xhtml:div id="main">):
{
[1] = "main";
id = "main"
}



EndElement é chamado quando é encontrado o fechamento de um elemento, por exemplo </xhtml:div>. A função possui dois argumentos: parser e elementName (nome do elemento).


Quando é encontrado um elemento simples (<elem></elem> ou <elem/>), é chamado o callback StartElement e imediatamente o callback EndElement.


CharacterData é chamado quando é encontrada uma string CDATA (conteúdo de um elemento). A função recebe dois argumentos: parser e string (o texto).

Parser


O parser é criado pela função lxp.new(), que recebe a tabela de callbacks como parâmetro.

O parser possui diversos métodos, sendo os principais parse() (que processa uma string como parte do documento XML) e close() (método de chamada obrigatória que fecha o parser).

O método parse() deve ser chamado sem parâmetros para indicar o fim do documento.

A cada chamada de parse() são retornados cinco valores:
  1. true se correu bem ou nil se ocorreu algum erro;
  2. nil se correu bem ou uma mensagem de erro no caso de erro;
  3. número da linha ou nil;
  4. número da coluna ou nil;
  5. posição absoluta ou nil;


A última chamada (sem parâmetros) retornará true se a estrutura XML estiver bem formada ou nil e uma mensagem de erro se tiver ocorrido algum erro em algum momento.


Vamos a um exemplo:
require "lxp"

local fd, l, st, erro
local contador = 0
local p = lxp.new {
StartElement = function (self, nome, atributos)
io.write("+ ", (" "):rep(contador), nome, "\n")
contador = contador + 1
end,

EndElement = function (self, nome)
contador = contador - 1
io.write("- ", (" "):rep(contador), nome, "\n")
end,

CharacterData = function (self, texto)
io.write("* ", (" "):rep(contador + 1), texto, "\n")
end,
}

-- O Leonardo percebeu um erro meu aqui: é arg, não argv
fd = io.input(arg[1])

for l in fd:lines() do
p:parse(l .. "\n")
end

fd:close()
st, erro = p:parse()
p:close()

if not st then
print("Ocorreu o seguinte erro:", erro)
end


Salve este código no arquivo xmlparser.lua. Pegue um arquivo XML qualquer e execute:
bash$ lua xmlparser.lua nome-do-arquivo.xml


Se não houver nenhum disponível, use este:
<?xml version="1.0"?>
<elem1>
texto
<elem2/>
mais texto
</elem1>


Para saber mais: LuaExpat.

[]'s
Rodrigo Cacilhas