HashCode

An organizer of symbols

Archive for the ‘Linux’ Category

RESTful - Parent Load, com método preload

Friday, August 29th, 2008

Para quem usa RESTful e Nested Resources existe uma ação de carregamento do objeto pai na maioria dos controllers filho.

Se você não entendeu nada do que eu falei, dê uma lida em
RESTful Rails Development, by Ralf Wirdemann & Thomas Baustert

Por exemplo tenho dois models um Person e o outro Personality sendo que Person tem uma Personality e Personality pertence a Person.
Ficando assim o relacionamento no AR

#app/models/person.rb
class Person < ActiveRecord::Base
  has_one :personality
end
#app/models/personality.rb
class Personality < ActiveRecord::Base
  belongs_to :person
end

e as rotas desta forma

ActionController::Routing::Routes.draw do |map|
  map.resources :personalities, :belongs_to => :people, :singular => "personality"
  map.resources :people, :has_one => :personality
end

Ok até agora. “Feitisso” podemos criar um registro Person via /people/new e depois criar um registro Personality via /people/1/personality/new
Quando for criado o novo registro Personality o id do pai(Person) vai como parametro

params[:person_id]

Então você já deve ter visto muito a estrategia de carregamento do objeto pai em uma variável de instância via filtro, em um construção do tipo abaixo:

class PersonalitiesController < ApplicationController
  before_filter :get_person

  private
  def get_person
    @person = Person.find(params[:person_id])
  end

end

Portando antes de executar a action o pai é carregado via filtro.

Para evitar está estrategia que estava repetindo em quase todos meus controllers crie o método preload.
Com preload posso ter uma contrução mais clara e com o mesmo efeito. Então agora posso fazer assim

class PersonalitiesController < ApplicationController
  preload :person
end

Podemos também utilizar include, order, limit, etc

class PersonalitiesController < ApplicationController
  preload :person, :include => :personality
end

Ou até especificar em quais actions o preload será utilizado

class PersonalitiesController < ApplicationController
  preload :person, {:include => :personality}, {:only => [:create]}
end

Olha a origem da mágica

class ApplicationController < ActionController::Base
  def self.preload_object(name,options,filter_options={} )
    before_filter(filter_options) do |controller|
      controller.instance_variable_set("@#{name}", name.to_s.camelize.constantize.find(controller.params["#{name}_id"], options))
    end
  end
end

Apenas um método de classe dentro do ApplicationController e um pouco de meta programação.
Agora vou tentar explicar o médodo preload.

Começando por

  name.to_s.camelize.constantize

o name vem como Symbol no nosso caso :person, então temos

name.to_s  		#=> "person"

O método camelize analiza o conteúdo da String e adapta ao padrão de nome de classes utilizadas em Ruby, exemplo “foo_bar” camelizado passa a ser “FooBar”.

name.to_s.camelize #=> "Person"

Invocando a constante

name.to_s.camelize.constantize #=> Person

ou poderia utilizar

Object.const_get name.to_s.camelize

Já que tenho a classe Person utilizo o find passando o padrão pai_id.

O bloco do before_filter passa um yield do controller, precisamos do controller neste escopo pois estamos em um contexto estático, por conseguinte apenas params[:pai_id] não é visível então utilizei controller.params[:pai_id]

E por último criar a instância @pai apontando para o retorno do Person.find, vou dar um zoom no código :)

class ApplicationController < ActionController::Base
  def self.preload_object(name,options,filter_options={} )
    before_filter(filter_options) do |controller|
		  parent_id = controller.params["#{name}_id"]
			find_return = name.to_s.camelize.constantize.find(parent_id)
			controller.instance_variable_set("@#{name}", find_return)
    end
  end
end

Ou você prefere a versão One-Line-Brain-Fucker?

class ApplicationController < ActionController::Base

  def self.preload_object(name,options,filter_options={} )
    before_filter(filter_options) { |controller| controller.instance_variable_set("@#{name}", name.to_s.camelize.constantize.find(controller.params["#{name}_id"], options)) }
  end

end

Mais DRY, mais DRY …

Append em arquivo no Postgresql via módulo em C

Friday, August 24th, 2007

Estava desenvolvendo um sistema de distribuição de dados no Postgresql e tive a necessidade de armazenar em um arquivo as inserções caso houvesse erro. Relatarei aqui uma solução desenvolvida um módulo(zinho) em C.

Pacotes necessários para a compilação
Na verdade eu não sei bem se os outros são necessários mais o postgresql-server-dev-8.1 é certeza :)

postgresql-8.1
postgresql-client-8.1
postgresql-contrib-8.1
postgresql-server-dev-8.1

Função

#include <stdio.h>
#include "postgres.h"
#include <string.h>
#include "fmgr.h"

//Registrando a função log_pg no escopo do postgres
PG_FUNCTION_INFO_V1(log_pg);

//Datum é o tipo esperado, cast automático do int de retorno
Datum log_pg(PG_FUNCTION_ARGS){

//PG_GETARG_TEXT_P(0) pega o primeiro argumento
//VARDATA faz cast para char *
  char *filename = VARDATA(PG_GETARG_TEXT_P(0));

  FILE *fp;

  if ((fp = fopen(filename, "a"))==NULL)  {
    printf("Cannot open file \n");
    return 1;
  }else{
   //Pega o segundo argumento já convertido para char *
    fputs(VARDATA(PG_GETARG_TEXT_P(1)),fp);
    fclose(fp);
    return 0;
   }
}

Salve o arquivo como log_pg.c
Compile

gcc -fpic -I/usr/include/postgresql -I/usr/include/postgresql/8.1/server -c log_pg.c

Gere o Shared Object

gcc -shared -o log_pg.so log_pg.o

No meu caso a dynamic_library_path está apontando para /usr/lib/postgresql/8.1/lib/ portanto, o log_pg.so vai pra lá.

install log_pg.so  /usr/lib/postgresql/8.1/lib/log_pg.so

Agora no Postgresql duas funções, uma mapeia o log_pg.so em log_me

CREATE or replace FUNCTION log_me(varchar,varchar) RETURNS int
 AS 'log_pg.so', 'log_pg' LANGUAGE C;

e a outra uma abstração para gerar a data corrente e concatena .log ao fim do nome do arquivo (melhor que usar strcpy).

create or replace function logger(varchar,varchar) returns int as $BODY$

  begin
    return log_me(($1 || current_date::varchar ||'.log')::text,$2::text);
  end
$BODY$ language 'plpgsql';

Testando

select logger('/tmp/teste', 'meu primeiro teste');

Saiba mais … libpq - C Library

Ruby & Ghostscript - Um diálogo possível

Friday, April 27th, 2007

Hoje trabalho em um empresa de Telefonia IP que atende outras telefônicas, com uma média de 800 mil minutos/mês. Desenvolvemos nossa própria plataforma, incluindo billing, rota de destinação, interface com cliente, etc., a primeira versão foi desenvolvida em 5 meses utilizando Java. A parte mais delicada do sistema é o tarifador, e o desempenho de atendimento para vários clientes já apresentava QoS baixo mas funcionava.

Tivemos um curso de Design Patterns onde precisaríamos de certificados para uma licitação, o instrutor disse “Vocês conhecem Ruby?”. Eu “What?”, ele disse “depois eu falo..”. No mesmo dia fui pesquisar e encontrei o livro “Programming Ruby The Pragmatic Programmers’ Guide”, apt-get install ruby, abri o Kate e comecei meus “Hello Worlds”, isso era umas 20:30. Aí comecei a exclamar “P.t. que pariu!!”, “Errensga!!” , “Afi!!”, minha esposa me chama para dormir, eu “daqui a pouco”… quando fui ver o as horas 05:48, nem vi as horas passarem. No mesmo dia de manhã eu não fui ao curso(tava quebrado!), fui apenas a tarde. O instrutor “Eai conhecem Ruby”, eu estava anestesiado e só consegui falar PQP! Aí ele me mostrou Rails “The Web Framework”. Nem precisava tanto… só Ruby já bastava.

Após o termino do curso fui “meter as caras” e refazer o tarifador, em uma semana e meia estava pronto e testado, foi para produção no 12º dia de desenvolvimento, mais leve, menos Kloc…

Refizemos a aplicação web de administração/cliente em Rails em 1 mês e 10 dias, ficando apenas os relatórios via web service utilizando a nossa API PsBuilder (PostscriptBuilder gera pdf e ps)

Tentei e teste as ferramentas para relatório do Ruby mas nenhum se adaptava as faturas de 60 mil registros de ligações.

Trabalhei na Xerox(uma escola!) quase 8 anos utilizávamos vários PDL(Page Description Language) em vários OS (SunOS, Solaris, Novell, Linux, AIX, etc). Utilizam muito “Programação Orientada a Estagiário” <-- EU, fui muito "usado"(no bom sentido) tive que aprender XES, PCL, PDL/FDL, HPGL/2 e Postscript, ah! sem saber linguagem de programação, estava no 2º grau ainda, eu era magro nessa época...

Lá na Xerox a última linguagem que aprendi foi VIPP que é a cópia do Postscript que é cópia do InterPress que é a cópia dos scripts JaM. Então qual era a vantagem do VIPP? Funções “helpers” do Postcript. Uma aplicação de gráfico de linhas que demorava uns 5 dias para ser feita em Postcript em VIPP eram duas ou três linhas.

E o interessante que Postscript sempre foi uma ótima linguagem, podemos programar na linguagem ao contrário da maioria dos PDLs que só marcavam as páginas(markup language). Tinha(tem) um grande problema: para usar Postscript nas laser da Xerox tem que pagar royalties altos, elevando o preço dos equipamentos, portanto usávamos PDL’s próprios ou mais baratos como o PCL(da HP).

E o que que isso tem a ver com “Ruby & Ghostscript - Um diálogo possível”?

Do inicio deste mês de abril para cá, venho desenvolvendo uma API para geração de documentos mais profissionais. A estratégia é simples mas trabalhosa:

- Desenvolver funções em Postscript para tarefas mas árduas, callbacks e cursores
- Gerar um código intermediário(em Postscript) originado por Ruby
- Compilar com Ghostscript e gerar os outputs

Posso afirmar agora que teremos uma liberdade e facilidade de criação de documentos profissionais tento para mídia digital ou para arte final em papel como (certificados, faturas, boletos, etc).

Um exemplo mais real:
Após o desenho do template/formulário em aplicações gráficas como CorelDraw, Inkscape, Photoshop, Gimp e outros a API importará o template e o desenvolvedor Ruby apenas posicionará os dados nos campos.

Outro exemplo de flexibilidade:
Adicionar linhas, texto, imagens, vetores, templates, etc no documento ou nos callbacks:
:before_page_create,
:after_page_create,
:odd_pages,
:even_pages,
:before_document_create,
:first_page,
:last_page,
:after_document_create

tendo a possibilidade de condição only ou except . Exemplo:

  d=Document.new

  d.before_page_create :only => [3,4,9] do
    set Text.new "Relatório XXX", :align => :center
  end

  d.even_pages :except => [3] do
     set HorizontalLine.new :bottom, :start_in => '3 cm', :size => '10 cm'
  end

  d.render :pdf

Já terminei as funções em Postscript e criei um módulo via Ruby Extending comunicando com libgs.so, ainda não testei em Windows.
A API será Open Source. Daqui uns 2 meses teremos a primeira versão pública.

Abaixo a lista dos OUTPUTS

Displays

:lvga256 => “Linux vgalib, 256-color VGA modes => [Linux only]“,
:vgalib => “Linux vgalib, 16-color VGA modes => [Linux only]“,
:x11 => “X Windows version 11, release >=4 => [Unix and VMS only]“,
:x11alpha => “X Windows masquerading as a device with alpha capability”,
:x11cmyk => “X Windows masquerading as a 1-bit-per-plane CMYK device”,
:x11cmyk2 => “X Windows as a 2-bit-per-plane CMYK device”,
:x11cmyk4 => “X Windows as a 4-bit-per-plane CMYK device”,
:x11cmyk8 => “X Windows as an 8-bit-per-plane CMYK device”,
:x11gray2 => “X Windows as a 2-bit gray-scale device”,
:x11gray4 => “X Windows as a 4-bit gray-scale device”,
:x11mono => “X Windows masquerading as a black-and-white device”,
:x11rg16x => “X Windows with G5/B5/R6 pixel layout for testing.”,
:x11rg32x => “X Windows with G11/B10/R11 pixel layout for testing.”,

Impressoras

:atx23 => “Practical Automation ATX-23 label printer”,
:atx24 => “Practical Automation ATX-24 label printer”,
:atx38 => “Practical Automation ATX-38 label printer”,
:deskjet => “H-P DeskJet and DeskJet Plus”,
:djet500 => “H-P DeskJet 500; use -r600 for DJ 600 series”,
:fs600 => “Kyocera FS-600 (600 dpi)”,
:laserjet => “H-P LaserJet”,
:ljet2p => “H-P LaserJet IId/IIp/III* with TIFF compression”,
:ljet3 => “H-P LaserJet III* with Delta Row compression”,
:ljet3d => “H-P LaserJet IIID with duplex capability”,
:ljet4 => “H-P LaserJet 4 (defaults to 600 dpi)”,
:ljet4d => “H-P LaserJet 4 (defaults to 600 dpi) with duplex”,
:ljetplus => “H-P LaserJet Plus”,
:lj5mono => “H-P LaserJet 5 & 6 family (PCL XL), bitmap:”,
:lj5gray => “H-P LaserJet 5 & 6 family, gray-scale bitmap;”,
:lp2563 => “H-P 2563B line printer”,
:oce9050 => “OCE 9050 printe”,
:pxlmono => “H-P black-and-white PCL XL printers (LaserJet 5 and 6 family)”,
:pxlcolor => “H-P color PCL XL printers (e.g. Color LaserJet 4500)”,

Formato para Fax

:faxg3 => “Group 3 fax, with EOLs but no header or EOD”,
:faxg32d => “Group 3 2-D fax, with EOLs but no header or EOD”,
:faxg4 => “Group 4 fax, with EOLs but no header or EOD”,
:tiffcrle => “TIFF CCITT RLE 1-dim (= Group 3 fax with no EOLs)”,
:tiffg3 => “TIFF Group 3 fax (with EOLs)”,
:tiffg32d => “TIFF Group 3 2-D fax”,
:tiffg4 => “TIFF Group 4 fax”,

Vetor e Imagem

:epswrite => “EPS output (like PostScript Distillery)”,
:pdfwrite => “PDF output (like Adobe Acrobat Distiller)”,
:pswrite => “PostScript output (like PostScript Distillery)”,
:pxlmono => “Black-and-white PCL XL”,
:pxlcolor => “Color PCL XL”,

Raster file formats and devices

:bit => “Plain bits, monochrome”,
:bitrgb => “Plain bits, RGB”,
:bitcmyk => “Plain bits, CMYK”,
:bmpmono => “Monochrome MS Windows .BMP file format”,
:bmpgray => “8-bit gray .BMP file format”,
:bmpsep1 => “Separated 1-bit CMYK .BMP file format, primarily for testing”,
:bmpsep8 =>”Separated 8-bit CMYK .BMP file format, primarily for testing”,
:bmp16 => “4-bit (EGA/VGA) .BMP file format”,
:bmp256 => “8-bit (256-color) .BMP file format”,
:bmp16m => “24-bit .BMP file format”,
:bmp32b => “32-bit pseudo-.BMP file format”,
:cgmmono => “Monochrome (black-and-white) CGM — LOW LEVEL OUTPUT ONLY”,
:cgm8 => “8-bit (256-color) CGM — DITTO”,
:cgm24 => “24-bit color CGM — DITTO”,
:jpeg => “JPEG format, RGB output”,
:jpeggray => “JPEG format, gray output”,
:miff24 => “ImageMagick MIFF format, 24-bit direct color, RLE compressed”,
:pcxmono=> “PCX file format, monochrome (1-bit black and white)”,
:pcxgray=> “PCX file format, 8-bit gray scale”,
:pcx16 => “PCX file format, 4-bit planar (EGA/VGA) color”,
:pcx256 => “PCX file format, 8-bit chunky color”,
:pcx24b => “PCX file format, 24-bit color (3 8-bit planes)”,
:pcxcmyk => “PCX file format, 4-bit chunky CMYK color”,
:pbm => “Portable Bitmap (plain format)”,
:pbmraw => “Portable Bitmap (raw format)”,
:pgm => “Portable Graymap (plain format)”,
:pgmraw => “Portable Graymap (raw format)”,
:pgnm => “Portable Graymap (plain format), optimizing to PBM if possible”,
:pgnmraw => “Portable Graymap (raw format), optimizing to PBM if possible”,
:pnm => “Portable Pixmap (plain format) (RGB), optimizing to PGM or PBM”,
:pnmraw => “Portable Pixmap (raw format) (RGB), optimizing to PGM or PBM”,
:ppm => “Portable Pixmap (plain format) (RGB)”,
:ppmraw => “Portable Pixmap (raw format) (RGB)”,
:pkm => “Portable inKmap (plain format) (4-bit CMYK => RGB)”,
:pkmraw => “Portable inKmap (raw format) (4-bit CMYK => RGB)”,
:pksm => “Portable Separated map (plain format) (4-bit CMYK => 4 pages)”,
:pksmraw => “Portable Separated map (raw format) (4-bit CMYK => 4 pages)”,
:plan9bm => “Plan 9 bitmap format”,
:pngmono => “Monochrome Portable Network Graphics (PNG)”,
:pnggray => “8-bit gray Portable Network Graphics (PNG)”,
:png16 => “4-bit color Portable Network Graphics (PNG)”,
:png256 => “8-bit color Portable Network Graphics (PNG)”,
:png16m => “24-bit color Portable Network Graphics (PNG)”,
:psmono => “PostScript (Level 1) monochrome image”,
:psgray => “PostScript (Level 1) 8-bit gray image”,
:psrgb => “PostScript (Level 2) 24-bit color image”,
:tiff12nc => “TIFF 12-bit RGB, no compression”,
:tiff24nc => “TIFF 24-bit RGB, no compression (NeXT standard format)”,
:tifflzw => “TIFF LZW (tag = 5) (monochrome)”,
:tiffpack => “TIFF PackBits (tag = 32773) (monochrome)”

Utilitário de Expressões Regulares

Thursday, April 12th, 2007

Tenho uma função em PL/pgSQL que identifica a operadora pelo número e DDD denominada ‘operadora’

CREATE OR REPLACE FUNCTION operadora(text) RETURNS text AS '
declare
  ops text[]:= array['VIVO','CLARO', 'TIM','OI','Amazônia','Telemig','BRT','CTBC','Sercomtel','Nextel'];
  regexps text[]=array[
  '((2[1-9]95|1[1-9](95|7[1-4]))|[49][1-9]9[1-4]|([25-7][1-9]9[6-9]|1[1-9]9[6-8]))[0-9]{6}$',
  '([12568][1-9]9[1-4]|4[1-9]88|[37][1-9]8[124]|6195|1[1-9](76|89))[0-9]{6}$',
  '(((([48][1-9]9[6-9])|([37][1-9]9[1-4])|(1[1-9]8[1-5]|[56][1-9]81)|([29][1-9]8[1-3])|(4381[0-9])|(1[1-9]86))[0-9]{6})|5[1-5]99(11|13|39|8[1-9])[0-9]{4})$',
  '[237-9][1-9]8[6-8][0-9]{6}$',
  '9[1-9]9[6-9][0-9]{6}$',
  '3[1-8]9[6-9][0-9]{6}$',
  '[4-6][1-9]84[0-9]{6}$',
  '((3[1-8](96[0-9]{6}|99[67][0-9]{5}|999[1-9][0-9]{4}))|(1[1-9]99[0-9]{6}))$',
  '4[1-6]99(4[1-6]|9[1-8])[0-9]{4}$',
  '7[0-9]{7}$'
];
begin
  for i in 1..10 loop
    if $1 ~ regexps[i] then --se match retorna a posiç
	return ops[i];
    end if;
  end loop;
    return 'outros';
end
' LANGUAGE 'plpgsql' VOLATILE;

Isso funciona da seguinte forma, para cada operadora em ops temos uma expressão regular em regexps e um loop que faz match para todas as regexps.

Exemplo, a fração que analisa a operadora VIVO no centro oeste esta na regexps[0] em negrito(ou morenito, como estão dizendo isso por ai!)

….([25-7][1-9]9[6-9]|1[1-9]9[6-8]))[0-9]{6}$

As regras para determinar as operadoras são:

Trunking com a Nextel utiliza a série começada com 7. A Vivo em São Paulo está também utilizando a série 7 (71 a 74). A série 95 está sendo utilizada pela Vivo em São Paulo e Rio de Janeiro e pela Claro no Distrito Federal. (Marcado com * na tabela a seguir)

Operadora por área e Banda

Estado (DDD)

Banda A

(96 a 99)

Banda B

(91 a 94)

Banda D

(8)

Banda E

(8)

RJ, ES

(21 a 28)

Vivo
Claro

Oi

86 a 88

TIM

81 a 83

Amazônia

(91 a 99)

Amazônia Celular
Vivo

MG

(31 a 38)

Telemig Celular
TIM

Claro

84

BA, SE

(71 a 79)

Vivo
TIM

Claro

81

Nordeste

(81 a 89)

TIM
Claro

-

PR, SC

(41 a 49)

TIM
Vivo

Claro

88

Brasil Telecom

84

RS

(51 a 55)

Vivo
Claro

TIM

81

Centro Oeste

(61 a 69)

SP (11 a 19)

Vivo*
Claro*

TIM

81-85

 

-

Casos Especiais de Operadoras (sempre tem :)

Operadora

Cidades
CTBC Celular

Minas Gerais (96, 9960-9979 e 9991-9999)

São Paulo(99)

Sercomtel Cel.

Londrina e Tamarana, PR (9941-9946 e 9991-9998)

TIM

Pelotas e região RGS.

(9911, 9913, 9939, 9981-9989)

Londrina e Tamarana, PR, área 43 (81)

São Paulo (86)

Claro

São Paulo (76 e 89)

Vivo

São Paulo (71, 72, 73 e 74)

Para facilitar o desenvolvimento das expressões, reduzi cada regra de cada localidade de cada operadora em ‘mini-expressões’, assim:

2[1-9]95
1[1-9](95|7[1-4])
[49][1-9]9[1-4]
[25-7][1-9]9[6-9]
1[1-9]9[6-8]
....

Depois juntei tudo colocando um ou entre cada mini-expressão.

Funcionar funciona… mas como sempre teremos novas exceções. Um dos casos seria a utilização de Trunking ou banda de outra operadora ou uma norma da Anatel. Portanto se uma operadora faz parceria/pacto/etc com outra operadora teremos que alterar a expressão relativa a operadora, uma tarefa não muito agradável como essa da expressão da TIM.

(((([48][1-9]9[6-9])|([37][1-9]9[1-4])|(1[1-9]8[1-5]|[56][1-9]81)|([29][1-9]8[1-3])|(4381[0-9])|(1[1-9]86))[0-9]{6})|5[1-5]99(11|13|39|8[1-9])[0-9]{4})$

Dividir novamente e depois juntar tudo … em uma masturbação codificante!? … era isso que eu fazia :|.

Como sofremos por falta de informações ou preguiça de pesquisar!

Pesquisando(com preguiça de quebrar a expressão) encontrei o kregexpeditor, que eu ignorava por começar com K de KDE, que ao longo do tempo-linux esta acabando com o meu preconceito KDE/Qt.

Dei um apt-get e instalei o cara.

Puts! Tenho agora um editor gráfico de expressoes regulares!
Posso trocar isso

(((([48][1-9]9[6-9])|([37][1-9]9[1-4])|(1[1-9]8[1-5]|[56][1-9]81)|([29][1-9]8[1-3])|(4381[0-9])|(1[1-9]86))[0-9]{6})|5[1-5]99(11|13|39|8[1-9])[0-9]{4})$

por algo mais amigável como

regexp.png

Com o kregexpeditor vc pode abrir um arquivo texto pra fazer o match , criar e alterar expressões regulares em texto ou no modo gráfico.

Gostei muito do editor, tenho que rever meus conceitos sobre K*

Extending Ruby - HdInfo e NetInfo

Monday, April 2nd, 2007

Vou descorrer aqui sobre minha primeira experiência integrando Ruby com o Sistema Operacional chamado de Extending Ruby. Se você veio do Java como eu chamaria de Ruby Native Interface.

Instalação

Para compilação em C utilizamos os pacotes básicos do gcc e para ruby o adicional ruby-dev, portanto segue os abaixo.
(Não testei as versões anteriores do gcc ou do ruby).

gcc-4.1 	#The GNU C compiler
gcc-4.1-base    #The GNU Compiler Collection (base package)
ruby1.8         #Interpreter of object-oriented scripting lan
ruby1.8-dev     #Header files for compiling extension modules

Algumas coisas antes do teste

Serão necessárias conversões de tipos entre C e Ruby para isso utilizaremos as funções:

char * STR2CSTR(String)      //converte uma String Ruby em um ponteiro de char em C (char *)
VALUE rb_str_new2(char *)   //cria uma String em Ruby baseado em uma String em C

retorno dos métodos e variáveis convertidos em Ruby serão tipados para VALUE, contudo para STR2CSTR receberemos um VALUE(uma coisa ruby).

Uma Prévia do Init_(Nome)

Toda classe ou módulo define uma função Init_Nome, onde Nome é o nome da classe/módulo, que será invocada inicialmente antes de quaisquer outras funções/métodos.

Hello Humans

Vamos criar nosso “Hello Humans”. Crie um arquivo hello.c contendo o seguinte trecho.

#include &lt;ruby.h>

static VALUE t_minha_string(VALUE self){

  VALUE r; //declara r como VALUE
  char *str_c = "Hello Humans!!"; //Cria a string em C

  //Cria a string em Ruby e atribui a r
  r=rb_str_new2(str_c);
  return  r;
}
//Declaração da futura classe Hello
VALUE cHello;

void Init_Hello() {
   //Definindo a classe Hello em ruby herdando de Object
  cHello = rb_define_class("Hello", rb_cObject);

  //Mapeando o t_minha_string para to_s em ruby, 0 é a quantidade de parâmetros
  rb_define_method(cHello, "to_s", t_minha_string, 0);

}

Dá pra simplifica t_minha_string em


static VALUE t_minha_string(VALUE self){

  return rb_str_new2("Hello Humans!!");  

}

Observe a declaração cHello ele deverá ser global como no exemplo acima.
Veja que as coisas se encaixam, cHello recebe uma nova referência com o nome de Hello herdando da classe rb_cObject que é uma constante para classe Object em ruby. E t_minha_string em C refletindo em to_s em Ruby.

Make

Vamos criar agora o MAKEFILE. Você deve estar pensando, “nossa que droga! Aquela tripa de parâmetros que sempre dá erro faltando alguma coisa…”. Pare com isso! Em ruby é sempre mais fácil :). Crie um arquivo extconfig.rb com o conteúdo.

require "mkmf"
create_makefile("Hello");

Rode com

ruby extconfig.rb

A mensagem “creating Makefile” aparecerá no output.
Agora dê um

make

Veja a saída:

gcc -fPIC -Wall -g -fno-strict-aliasing -O2  -fPIC  -I. -I/usr/lib/ruby/1.8/i486-linux -I/usr/lib/ruby/1.8/i486-linux -I.   -c hello.c

gcc -shared  -L"/usr/lib" -o Hello.so hello.o  -lruby1.8  -lpthread -ldl -lcrypt -lm   -lc

Linka e já cria o shared object.
Agora com permissão de escria em /usr/lib, instale com

make install

Veja a saída:

/usr/bin/install -c -m 0755 Hello.so /usr/local/lib/site_ruby/1.8/i486-linux

já copia o .so para o diretório correto do ruby.

Testando

Abra seu irb e digite na seqüencia:

require "Hello"
h=Hello.new
puts h.to_s

Ficando assim.

irb(main):001:0> require "Hello"
=> true
irb(main):002:0> h=Hello.new
=> Hello Humans!!
irb(main):003:0> puts h.to_s
Hello Humans!!
=> nil
irb(main):004:0>

Fácil né :).

Acessando informações do HD e das Interfaces de Rede

Criei duas classes uma HdInfo e outra NetInfo em Extend Ruby.

HdInfo

Aqui poderemos demonstrar mais poder em nossas mãos, vamos obter informações sobre nosso hd e depois setar os keys/values em uma classe herdanda de Hash.

serial_no, model, fw_rev são variáveis da biblioteca linux/hdreg.h
Acompanhe pelos comentários.

/*
Shairon Toledo shairon.toledo@gmail.com
http://www.hashcode.eti.br
Seg Abr  2 13:58:17 BRT 2007
*/
#include "ruby.h"
#include &lt;stdio.h>
#include &lt;stdlib.h>
#include &lt;linux/hdreg.h>
#include &lt;fcntl.h>
#include &lt;sys/ioctl.h>

static VALUE t_init(VALUE self,VALUE anObject){

  struct hd_driveid id;
  int disk ;
  int opened;

  //Abre o ponteiro inicial do disco passando
  //paramentro de entrada, ex: /dev/hda, /dev/hdb
  disk=open(STR2CSTR(anObject), O_RDONLY|O_NONBLOCK);
  //seta as props do driver do disco em id
  //opened armazena o estado para tratar erro, a seguir
  opened = ioctl(disk, HDIO_GET_IDENTITY, &id);

  //Cria os key convertendo um (const char *) para RubyString
  VALUE k[4],v[4];
  k[0]=rb_str_new2("serial_no");
  k[1]=rb_str_new2("model");
  k[2]=rb_str_new2("fw_rev");
  k[3]=rb_str_new2("device_name");

  //cria as String baseados nos params do so struct hd_driveid
  v[0]=rb_str_new2((char*) id.serial_no);
  v[1]=rb_str_new2((char*) id.model);
  v[2]=rb_str_new2((char*) id.fw_rev);
  v[3]=anObject;//copia o valor do param de entrada para v[3]

  int i;
  for (i=0;i<4;i++){ //laço de atribuição
    rb_hash_aset(self,k[i],v[i]);

  }

  VALUE err=(opened)?Qtrue:Qfalse;
  //seta o erro. Q(true|false) são constantes equivanete a TrueClass/FalseClass em Ruby
  rb_iv_set(self, "@error",err); 

  return self;

}
//todo para leitura da variável @error
static VALUE t_error(VALUE self, VALUE anObject){
  //Obtem e retorna o valor
  return  rb_iv_get(self, "@error");

}

VALUE cHdInfo;

void Init_HdInfo() {
  //HdInfo < Hash em C
  cHdInfo = rb_define_class("HdInfo", rb_cHash);
  //Mapeia t_init  para initialize do ruby , 1 parametro =>  nome do device
  rb_define_method(cHdInfo, "initialize", t_init, 1);
  //Mapeia t_error para error? em ruby, 0 parametro
  rb_define_method(cHdInfo, "error?", t_error, 0);
}

Para criar o makefile é o mesmo processo do “Hello Humans” mas com a classe HdInfo. Ex. do extconfig.rb para HdInfo:

require "mkmf"
create_makefile("HdInfo");

Rode com

ruby extconfig.rb

depois

make

e

make install

Vamos testar com irb

irb(main):001:0> require "HdInfo"
=> true
irb(main):002:0> hd=HdInfo.new("/dev/hda")
=> {"serial_no"=>"0881J1FX406355", "fw_rev"=>"TW100-11", "device_name"=>"/dev/hda", "model"=>"SAMSUNG SP0411N"}
irb(main):003:0> hd.each{|k,v| p "#{k}=#{v}"}
 "serial_no=0881J1FX406355"
 "fw_rev=TW100-11"
 "device_name=/dev/hda"
 "model=SAMSUNG SP0411N"
=> {"serial_no"=>"0881J1FX406355", "fw_rev"=>"TW100-11", "device_name"=>"/dev/hda", "model"=>"SAMSUNG SP0411N"}
irb(main):004:0> hd.error?
=> false

Lagalzin!

NetInfo

A classe NetInfo segue o mesmo princípio da HdInfo. E a geração do .so da mesma forma, extconfig.rb, make, make install …
Vamos obter as informações de ipaddress, netmask e mac address da interface de rede(lib net/if.h e arpa/inet.h).
Acompanhe pelos comentários.

/*
Shairon Toledo shairon.toledo@gmail.com
http://www.hashcode.eti.br
Seg Abr  2 11:30:26 BRT 2007
*/
#include "ruby.h"
#include &lt;sys/socket.h>
#include &lt;sys/ioctl.h>
#include &lt;net/if.h>
#include &lt;string.h>
#include &lt;stdio.h>
#include &lt;stdlib.h>
#include &lt;arpa/inet.h>
#include &lt;errno.h>
#include &lt;unistd.h>

static VALUE t_init(VALUE self,VALUE anObject){

      int sock, i;
      struct sockaddr_in *sin;
      struct  ifreq  ifdev;
      char *ifname=STR2CSTR(anObject);  //converte anObject(RubyString) em char*

      VALUE k[4]; //Declara k como objetos ruby

      //Nossas chaves
      k[0]=rb_str_new2("netmask");
      k[1]=rb_str_new2("ipaddress");
       k[2]=rb_str_new2("macaddress");
      k[3]=rb_str_new2("ifname");

      //ifname = ao parâmetro de inicialização
      rb_hash_aset(self,k[3],anObject);

      //Cria o socket para obter as informações
      sock = socket(AF_INET, SOCK_DGRAM, 0);
      sprintf(ifdev.ifr_name, "%s", ifname);

      //SIOCGIFNETMASK infomacões de máscara
      ioctl(sock, SIOCGIFNETMASK, &ifdev);
      sin = (struct sockaddr_in *)&ifdev.ifr_broadaddr;
      //Atribui o valor do ip para a chave k[0] convertendo em RubyString
      rb_hash_aset(self,k[0],rb_str_new2(inet_ntoa(sin->sin_addr)));

      //Idem acima mas para IFADDR
      ioctl(sock, SIOCGIFADDR, &ifdev);
      sin = (struct sockaddr_in *)&ifdev.ifr_broadaddr;
      rb_hash_aset(self,k[1],rb_str_new2(inet_ntoa(sin->sin_addr)));

      //Obtem propriedades físicas da interface
      ioctl(sock, SIOCGIFHWADDR, &ifdev);

      char m[2];
      char *mac_addr= malloc(500*sizeof(char));
      for (i=0;i<6;i++){
        //Formatando
        sprintf(m, "%02X", (unsigned char)ifdev.ifr_hwaddr.sa_data[i] );
        strcat(mac_addr,m);
      }
      //Seta o mac address no self usando o key k[2]
      rb_hash_aset(self,k[2],rb_str_new2(mac_addr));

      close(sock);
      free(mac_addr);
      return self;
}

VALUE cNetInfo;

void Init_NetInfo() {

  //NetInfo < Hash em C
  cNetInfo = rb_define_class("NetInfo", rb_cHash);
  //Mapeia t_init para o initialize do ruby, 1 parametro => if_name
  rb_define_method(cNetInfo, "initialize", t_init, 1);

}

Testando no irb

irb(main):001:0> require "NetInfo"
=> true
irb(main):002:0> net=NetInfo.new("eth0")
=> {"ipaddress"=>"192.168.1.1", "netmask"=>"255.255.255.0", "ifname"=>"eth0", "macaddress"=>"00112F3683C9"}

outro teste, criando um teste.rb

puts NetInfo.new("eth0").inspect
puts NetInfo.new("eth1").inspect
puts NetInfo.new("lo").inspect

output

{"ipaddress"=>"192.168.1.1", "netmask"=>"255.255.255.0", "ifname"=>"eth0", "macaddress"=>"00112F3683C9"}
{"ipaddress"=>"10.1.1.101", "netmask"=>"255.0.0.0", "ifname"=>"eth1", "macaddress"=>"00E07DC96741"}
{"ipaddress"=>"127.0.0.1", "netmask"=>"255.0.0.0", "ifname"=>"lo", "macaddress"=>"000000000000"}

Bem é isso aí. Gostei muito disso principalmente pela facilidada da criação do makefile. Os artefatos dos exemplos estão aqui ext_ruby_info_hd_net.tar.gz.

Referências

http://kasperd.net/~kasperd/comp.os.linux.development.faq
http://www.rubycentral.com/book/ext_ruby.html
http://www.ruby-doc.org/core/classes/Hash.html
file:///usr/include/net/if.h
file:///usr/include/arpa/inet.h