HashCode

An organizer of symbols

Archive for April, 2007

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)”

Console For The Web

Thursday, April 19th, 2007

Vou demonstrar ‘uma’ solução para utilização de recursos no estilo console como irb, bash, psql, gs e outros.
A estratégia é simples, vamos abrir um pipe com a aplicação e trocar mensagens.

Já vou dizendo, vou utilizar uma variável global, sem delongas se-pode-ou-não-pode se existe é por que pode usar, mas com cautela. Isso me fez lembra do goto do C, todo mundo condenava a utilização(até eu) mas o meu professor de Linguagens Formais e Compiladores (Rafael Corvinel) me tirou das trevas. Faziamos interpretadores de gramáticas em C utilizando funções e condicionais normalmente, ele disse “esse código funciona e está bem estruturado” vamos focar agora em desempenho… “vamos fazer com goto” é meu caro a coisa funfou mais rápido dos que a forma estruturada, portando não estou mais nas trevas nem no dogmatismo. Qual é o seu foco?

A classe ConsoleForWeb

class ConsoleForWeb

 attr_reader :output
 def initialize(pipe_name,open_now=false,mode="r+")
      @output=''
      @pipe_name=pipe_name
      @mode=mode
      open if open_now
  end
  def run(input)
      if @io
        @io.puts input.to_s
        @io.flush

      else
          raise Exception.new("Pipe is closed")
      end
  end

 def open
    unless @io
       @io=IO.popen(@pipe_name,@mode)
       @io.sync=false

       Thread.new do
           loop do
              @output << "<div>#{@io.readline.chomp}</div>"
           end
        end

    end
 end

 def close
   @io.close if id @io
 end
 def closed?
      @io.closed?
 end

end

No initialize é esperado o nome do pipe(executável do programa), se é para abrir o pipe durante a inicialização da instância e mode de abertura(para ler e escrever ‘r+’)

O método run executa o comando, output obtém o retorno(attr_reader) e open e close… não precisa falar.

View

Utilizei dois arquivos, o index.rhtml para criar a caixinha para ficar parecendo um console e o partial _console.rhtml que é o cara que vai renderizar as respostas dos nossos comandos. Veja.

index.rhtml

<%= stylesheet_link_tag 'scaffold' %>
<%= stylesheet_link_tag 'erails' %>
<%= javascript_include_tag "prototype" %>
<%= javascript_include_tag "controls" %>
<%= javascript_include_tag "effects" %>
<%= javascript_include_tag "erails" %>

<%= form_tag({:action=> "run",
              :controller => "testes"},
              :onload=>"posload();",
              :id=> 'irb',
              :onsubmit => "return false"
)%>
    <div class="console" id="console" onclick="posload();">
        <%= render :partial => "console" %>
    </div>
<%= end_form_tag %>

Observe que temos um javascript erails.js para enviar as informações em Ajax e atualizar o nosso console e um stylesheet chamado erails.css… o tema (disponíveis para download, lá em baixo)

_console.rhtml

 <%= @output.to_s %>
 >><input name="caixa" class="text" onkeypress="run(event,'irb');" type="text" id="caixa" />
 <script> posload(); </script>

Para cada requisição/resposta o _console.rhtml será chamado para renderizar a resposta e criar uma caixa de input.

Controller

Crie um controller chamado TestesController com duas ações, uma para index(cria o console) e a ação run que executa os comandos na classe ConsoleForWeb.

class TestesController < ApplicationController

  def index
    render :action => "index"
  end

  def run
    e=params[:caixa]
    $irb=ConsoleForWeb.new('irb --simple-prompt',true) unless $irb
    $irb.run(e.chomp) if e
    sleep 0.3
    @output=$irb.output.dup

    render :partial => "console"

end

Veja uma atribuição unica para a variável $irb portanto utilizaremos apenas uma variável global para armazenar a instância de ConsoleForWeb. Vamos brincar agora.

erails3.png

script/console

Vamos especializar a classe ConsoleForWeb para fazer proxy e verificação de comando, neste exemplo inibiremos os comandos exit, quit, kill e clear. Utilizaremos um console do rails

class IrbForWeb < ConsoleForWeb
  def initialize
    super('/local/projects/bossapp/script/console',true)
  end

  def run(cmd)
    case cmd
      when /^(exit|quit|kill)/:  @output << "han?\n"
      when /^(clear|\^C)/:  @output=""
      else
          super
      end
  end
end

off: Esse han? é em homenagem ao Solaris 2.4.3.
No controller o método run fica assim

   def run
       e=params[:caixa]
       $irb=IrbForWeb.new unless $irb
       $irb.run(e.chomp) if e
       sleep 0.3
       @output=$irb.output.dup

       render :partial => "console"

   end

Exemplo script/console
erails2.png
Onde colocar as classes ConsoleForWeb e IrbForWeb? Eu coloquei-as no mesmo arquivo do controller por ser um teste, coloque no lib ou em outro local que satisfaça suas necessidades.

É isso. Não é a melhor e nem a pior implementação … é “uma implementação”. Os arquivos estão aqui.

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