HashCode

An organizer of symbols

Archive for the ‘Extending Ruby’ Category

RGhost 0.8 e RGhost Barcode publicado

Thursday, June 19th, 2008

Rubeiros,

Publiquei hoje um nova versão do RGhost http://rghost.rubyforge.org totalmente refatorada.
Bati forte em desempenho e na doçura da sintaxe, ficando mais fácil programar em Ruby e gerar códigos em Postscript.
Fiz um teste estuprador até 10K páginas com template e passou legal.

Abaixo algumas das mudanças.

  • Namespace RGhost adicionado e algumas nomes de classes/módulos mudaram.
  • Teste de ambiente e de fonts como RGhost::Config.is_ok?.render :pdf, :filename => “/tmp/test.pdf” e environment_fonts.
  • Retirado o suporte aos 4 códigos de barras GNU Barcode.
  • Criado o projeto rghost_barcode http://rghost-barcode.rubyforge.org que suporta 32 tipos de códigos de barras incluindo uns doidão.
  • Suporte a qualquer fonte TTF e Type 1.
  • Possibilita agora a cópia da API para a memória de impressoras de alto volume.
  • Tag scope para personalização de fontes.
  • Reformulação de vetores e adicionado: canto arredondados, polígono, etc.
  • Melhora do uso de templates registrando-os do dicionário interno da pilha PS.
  • As classes TextArea e Text foram reformuladas, suporte a alinhamento do texto, tags, e outros.
  • Virtual page.
  • Reestruturado o suporte ao ActiveRecord, agora podemos usar n Grid::Rails em um doc.
  • Alguns callbacks mudaram de posição como o before_page_create e o odd_pages.
  • Documentação em Inglês
  • o resto eu esqueci :)

Se alguém se interessar por traduzir o manual em português é só enviar um email para mim em pvt que eu mando o odt.

Agradeço a todos que reportaram bugs e me ajudaram a testar.

Desculpem a demora. Espero que gostem.

Distribuição com PL/Ruby e DbLink no Postgresql

Saturday, January 12th, 2008

Depois de uma escravização no fim do ano passado e no inicio deste, estou de volta para “espalhar a maldade”!

Vou falar sobre PL/Ruby ou melhor fazer funcionar.

PL/Ruby é uma linguagem procedural acoplada dentro do Postgresql. Entre as linguagem do Postgres é a mais flexível e fácil.
Demonstrarei inicialmente “o jeitão da coisa” e depois uma implementação distribuída.

Instalação
Instale o banco e o módulo plruby, se o postres já estiver instalado, instale apenas o módulo(claro!)

apt-get install postgresql-8.1 postgresql-8.1-plruby

Agora conecte via psql

psql template1

Criaremos um banco novo para todos os testes deste tutorial, o banco “ruby_test”, então vamos lá

template1=# create database ruby_test;

conecte no banco novo

template1=# \c ruby_test

Crie a função/linguagem via o script plruby.sql que faz parte do pacote postgresql-8.1-plruby, se a sua distro difere de ubuntu, verifique onde o arquivo plruby.sql está e invoque com \i semelhante ao exemplo abaixo.

ruby_test=# \i /usr/share/postgresql-8.1-plruby/plruby.sql

Jeitão
Para familiarizarmos um pouco vamos criar uma função que produz uma seqüencia de números separados por espaço.
Portanto a função abaixo cria um Range do primeiro argumento(args[0]) até o segundo argumento(args[1]), converte o range para Array e depois para String separando os elementos por espaço.

create or replace function string_array(int,int) returns text as $RUBY$


  (args[0]..args[1]).to_a.join " "

$RUBY$ language plruby;

Executando

ruby_test=# select string_array(10,20);

Produzindo

"10 11 12 13 14 15 16 17 18 19 20"

Agora uma função que procura um padrão dentro do um texto, se o padrão for encontrado, o mesmo ficará entre tags <b>.

create or replace function find(varchar,text) returns text as $RUBY$


  args[1].gsub(/(#{args[0]})/,'<b>\1</b>')

$RUBY$ language plruby;
ruby_test=# select find('[tT]he','The method PL#context and PL#context= give the possibility to store information between the call')

Produto

"<b>The</b> method PL#context and PL#context= give <b>the</b> possibility to store information between <b>the</b> call"

Até agora demonstramos duas funções, faz uma lógica e retorna apenas uma tupla.
Ficou bem elegante essa implementação do PL/Ruby para vários registros(setof) sendo que cada registro é um objeto lançado pelo operador yield do Ruby.
Uma função em PL/Ruby para resolver a função(x ao quadrado vezes a raíz de 2)

f(x) = x^2 * raiz(2)

sendo x inteiro variando de x1 a x2.

create or replace function fx(int,int) returns setof float as $RUBY$


  (args[0]..args[1]).each {|x|  yield x.to_i**2 * Math.sqrt(2) }

$RUBY$ language plruby;

Executando com * from pois estamos pegando vários registros

ruby_test=# select * from fx(1,10);

Resultando em

1.4142135623731
5.65685424949238
12.7279220613579
22.6274169979695
35.3553390593274
50.9116882454314
69.2964645562817
90.5096679918781
114.551298552221
141.42135623731

Manipulando dados
Vamos criar uma tabela denominada “users” para povoarmos de dados para os testes.

ruby_test=# CREATE TABLE users(
  id serial not null primary key,
  name varchar(255) not null ,
  login varchar(80),
  email varchar(80),
  password varchar(80)
) ;

Inserindo alguns registros

ruby_test=# insert into users(name,login,email,password) values ('Fulano de tal','fulano','fulano@server.com','fulanopwd');
ruby_test=# insert into users(name,login,email,password) values ('Maria da Silva','maria','maria.silva@server.com','mariapwd');
ruby_test=# insert into users(name,login,email,password) values ('Sem nome','semnome','semnome@server.com','semnomepwd');

Daqui a pouco faremos a distribuição dos dados via trigger para bancos remotos, portanto criaremos uma função para criação dos inserts.
A função make_insert faz engenharia reversa na ddl e nos dados utilizando o módulo PL disponível no plruby

CREATE OR REPLACE FUNCTION make_insert(users) RETURNS text AS $RUBY$

    row=args[0]
    tn=PL.args_type.to_s
    cn=row.keys.join(',')
    values= row.values.map{|m| (m)?"'#{m}'":'NULL' }.join(',')
    "INSERT INTO #{tn}(#{cn}) values (#{values})"

$RUBY$ language plruby;

Veja o exemplo da execução

ruby_test=# select make_insert(users) from users;

Resultado

INSERT INTO users(name,id,password,login,email) values ('Fulano de tal','1','fulanopwd','fulano','fulano@server.com')
INSERT INTO users(name,id,password,login,email) values ('Maria da Silva','2','mariapwd','maria','maria.silva@server.com')
INSERT INTO users(name,id,password,login,email) values ('Sem nome','3','semnomepwd','semnome','semnome@server.com')

Sem aquela concatenação demasiada do plpgsql usando pipes e aspas para todo lado. Ah! a função make_insert é genérica para qualquer tabela mas no argumento de criação a sua tabela. Tipo

CREATE OR REPLACE FUNCTION make_insert(sua_tabela) RETURNS text AS $RUBY$
...

Semelhante aos parâmentos tabela%rowtype do postgres.

Distribuindo os dados
Já vou dizendo logo, isso não é comparável ao Slony-I, isso é uma simples utilização do dblink do postgresql.
As funções de dblink ficam no pacote contrib do postgres, a instalação com apt-get

apt-get install postgresql-contrib-8.1

Crie as funções com \i onde o argumento é o arquivo dblink.sql

ruby_test=#  \i /usr/share/postgresql/8.1/contrib/dblink.sql

Lembrando a assinatura da função dblink_exec é

select dblink_exec("connection string","query")

Exemplo

select dblink_exec('hostaddr=127.0.0.1 dbname=mydb user=shairon password=senha','insert into...');

Partiremos da seguinte extrategia: criaremos uma tabela chamada nodes onde armazenaremos os dados dos bancos de dados, selecionaremos apenas os nodes ativos e enviaremos os registros para os mesmos através da trigger replica_plruby(T) no evento after insert na tabela users master(A).
Veja o diagrama abaixo.
replica_plruby.png

Então temos a tabela nodes.

create table nodes(id serial not null primary key, host varchar(80), username varchar(80), password varchar(80), active boolean )

Dados para teste

insert into nodes(host,dbname,username,password,active) values ('127.0.0.1','mydb1','foo','bar',true);
insert into nodes(host,dbname,username,password,active) values ('192.168.0.7','mydb2','foo','bar',true);
insert into nodes(host,dbname,username,password,active) values ('10.0.0.38','mydb3','foo','bar',true);

Criando a trigger replica_plruby, acompanhe pelos comentários

CREATE OR REPLACE FUNCTION replica_plruby() RETURNS trigger AS  $RUBY$

  #obtendo a quantidade de nós ativos
  count=PL::exec("select count(*) from nodes where active=true",1,"value").to_s.to_i
  if count > 0
     #obtendo os nós ativos
     PL::Plan.new("select * from nodes where active=true").each do |row|
       #montando a conexão
       #cada row é um hash  tipo {"dbname"=>"delme", "username"=>"shairon" ...}
       conn="hostaddr=#{row['host']} dbname=#{row['dbname']} user=#{row['username']} password=#{row['password']}"
       #Nome das culunas via metadata
       keys  = new.keys.join(",")
       #Os valores com tratamento de nulo
       values = new.values.map{|m| (m)?"''#{m}''":'NULL' }.join(',')
       #Query final
       query = "INSERT INTO #{tg['relname']}(#{keys}) values (#{values})"
       #Disparo
       PL.exec("select dblink_exec('#{conn}','#{query}')")
     end
  end
  new
$RUBY$ LANGUAGE plruby;

Pronto! Insira novos dados na tabela users para ver o resultado.

Referências
Nos arquivos de documentação dos pacotes

ruby1.8
postgresql-8.1
postgresql-8.1-plruby
postgresql-contrib-8.1

Shared Object Híbrido, Java e Ruby

Saturday, September 29th, 2007

Pense em dois softwares, ambos monitoram um dispositivo via driver, aparentemente iguais diferindo apenas pela linguagem em que foram concebidos, um em Java e outro em Ruby. Conseqüentemente, duas bibliotecas fazendo interface entre o driver e o ambiente da linguagem de auto nível.
Resolvi me aventurar nessa idéia e criei uma solução híbrida, uma biblioteca compatível com Ruby e Java.
jni_ruby.png
Testei com um SmartCard, como é algo específico(e você não vai comprar um só pra testar isso :)) usarei a função getenv definida em stdlib.h .

Vamos dividir em três estágios:
- Criar um teste jni(java)
- Criar um teste em ruby(Extending)
- Juntar Java e Ruby com C

Ingredientes(debian packages)
- sun-java6-jdk
- ruby1.8
- ruby1.8-dev
- gcc-4.1

Java

Vamos seguir este fluxo

jni_java_flow.png

Crie um diretório “java” para o teste. Entre no diretório.

1. Criar o arquivo fonte java
Primeiramente crie o arquivo EnviromentVariable.java que depois de compilado será o protótipo para implementação em C.

 class EnviromentVariable {  

  public native String get(String varname);  

 }

2. Gerar o bytecode
Compile

 javac EnviromentVariable.java

3. Gerar os headers

 javah -jni EnviromentVariable

O javah criará um arquivo .h com o conteúdo

/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class EnviromentVariable */
#ifndef _Included_EnviromentVariable
#define _Included_EnviromentVariable

#ifdef __cplusplus
extern "C" {
#endif
/*

 * Class:     EnviromentVariable
 * Method:    get
 * Signature: (Ljava/lang/String;)Ljava/lang/String;
 */

JNIEXPORT jstring JNICALL Java_EnviromentVariable_get(JNIEnv *, jobject, jstring);

#ifdef __cplusplus
}
#endif
#endif

4. Criar o arquivo fonte C
Agora faremos a lógica da biblioteca, portando é necessário a criação do arquivo enviroment_variable_java.c com o conteúdo


#include  <stdio.h>
#include  <stdlib.h> //getenv vem daqui
#include "EnviromentVariable.h"

JNIEXPORT jstring JNICALL Java_EnviromentVariable_get(JNIEnv *jvm, jobject obj, jstring varname){
  //Convertendo uma String Java em C String
  char * c_varname=(char *) (*jvm)->GetStringUTFChars(jvm, varname,NULL);

  //Obtendo o valor da variável e criando uma string para retorno
  return (*jvm)->NewStringUTF(jvm, getenv(c_varname) );
}

5. Gerar o shared object
O home da JVM na minha máquina está em /usr/lib/jvm/java-6-sun, verifique na sua distro onde foi instalada. Pra facilitar export a variável JAVA_HOME

export JAVA_HOME=/usr/lib/jvm/java-6-sun

Agora vamos gerar o libEnviromentVariable.so

gcc enviroment_variable_java.c \\
-shared -o libEnviromentVariable.so\\
-I. -I$JAVA_HOME/include -I$JAVA_HOME/include/linux

6. Testando
Para testarmos, crie um arquivo Main.java com o conteúdo.

class Main{
 static{
   System.loadLibrary("EnviromentVariable");
 }
 static public void main(String args[]){
  System.out.println(new EnviromentVariable().get("PATH"));
 }
}

Observe a cima vamos pegar o valor da variável PATH do seu sistema. Lembrando que a variável tem que existir.
Compile Main.java

javac Main.java

Execute o programa Main com a opção -Djava.library.path=”O diretório onde está o .so” no nosso caso em ‘.’

java -Djava.library.path=. Main

O output semelhante a

/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/local/shairon/bin:/usr/lib/jvm/java-6-sun/bin

Depois teste mudando a variável PATH para uma outra variável válida.

Ruby

Vamos seguir o fluxo
jni_ruby_flowx.png
Crie outro diretório no mesmo nível do java mas com o nome de “ruby”. Entre do diretório.

1. Criar o arquivo fonte ruby

Crie o arquivo enviroment_variable_ruby.c conforme abaixo.

#include  <ruby.h>
#include  <stdlib.h>

static VALUE t_get(VALUE self, VALUE varname){
    //Converte String Ruby em C String
   char *c_varname=STR2CSTR(varname);

   //Obtendo o valor da variável e criando uma string para retorno
   return rb_str_new2( getenv(c_varname)); 

}

VALUE cEnviromentVariable;

//Função de inicialização do arquivo
void Init_libEnviromentVariable() {

  //Cria a classe EnviromentVariable
  cEnviromentVariable = rb_define_class("EnviromentVariable", rb_cObject);

  //Associa o método t_get com EnviromentVariable#get
  rb_define_method(cEnviromentVariable, "get", t_get, 1);
}

2. Criar o arquivo para geração do makefile

Vamos criar o arquivo extconfig.rb para geração do makefile. Abaixo

require "mkmf"
create_makefile("libEnviromentVariable")

Execute

ruby extconfig.rb

3. Compile e instale

make

depois

make install

Abrao irb e digite:

require 'libEnviromentVariable'
puts EnviromentVariable.new.get("PATH")

Java e Ruby estão chamando diretamente o getenv, algo fácil pois é só uma chamada de função. Imagine se fosse algo mais complexo? O desenvolvedor iria reutilizar o código em C via copy-paste, nada elegante.
Daqui em diante vamos fazer Ruby e Java usar uma função comum e gerar apenas um Shared Object.

Solução Híbrida

Antes de mais nada observe que o módulo “mkfm” identifica onde estão os headers necessários para a compilação do .so em ruby, durante a compilação aparece o caminho detectado, no meu caso em /usr/lib/ruby/1.8/i486-linux, portanto declarare um variável de ambiente RUBY_HOME para facilitar o entendimento.

export RUBY_HOME=/usr/lib/ruby/1.8/i486-linux

Agora o arquivo que compartilha a mesma função entre Java e Ruby, o arquivo enviroment_variable.c. Verifique a disposição dos seus arquivos e diretório. A estrutura usada é:
1p.png

O conteúdo de enviroment_variable.c, acompanhe pelos comentários.

#include 
#include "java/EnviromentVariable.h"
#include 
#include 

//Função que será compartilhada

char *var(char *varname){
  return getenv(varname);
}

//JAVA SIDE
JNIEXPORT jstring JNICALL Java_EnviromentVariable_get(JNIEnv *jvm, jobject obj, jstring varname){

  char * c_varname=(char *)(*jvm)->GetStringUTFChars(jvm, varname,NULL);

 //Chama a função var e retorna uma String para JVM
  return (*jvm)->NewStringUTF(jvm, var(c_varname) );
}

//RUBY SIDE

static VALUE t_get(VALUE self, VALUE varname){
  char *c_varname=STR2CSTR(varname);
  //Chama a função var e retorna uma String para JVM
  return rb_str_new2( var(c_varname));
}

VALUE cEnviromentVariable;

void Init_libEnviromentVariable() {
  cEnviromentVariable = rb_define_class("EnviromentVariable", rb_cObject);
  rb_define_method(cEnviromentVariable, "get", t_get, 1);
}

Linkando e Compilando

Lembrando que já definimos as variáveis de ambiente $JAVA_HOME e $RUBY_HOME.

Gerando o objeto

gcc -c enviroment_variable.c \
-fPIC -Wall -g -fno-strict-aliasing -O2 \
-I$RUBY_HOME \
-I$JAVA_HOME/include \
-I$JAVA_HOME/include/linux

depois o Shared Object

gcc -L"/usr/lib" \
-shared -o libEnviromentVariable.so enviroment_variable.o \\
-I$RUBY_HOME \\
-I$JAVA_HOME/include \\
-I$JAVA_HOME/include/linux \\
-lruby1.8 -lpthread -ldl -lcrypt -lm -lc

Pronto! Geramos o bacalhau :) o arquivo libEnviromentVariable.so está pronto para ser usado.
Testando em java

java -Djava.library.path=. -cp ./java Main

em Ruby pelo irb

require 'libEnviromentVariable'
puts  EnviromentVariable.new.get("PATH")

Alguns outputs do irb

rb(main):001:0> require 'libEnviromentVariable'
=> true
irb(main):002:0> envs=EnviromentVariable.new
=> #
irb(main):003:0> envs.get("USERNAME")
=> "shairon"
irb(main):004:0> envs.get("TERM")
=> "xterm"
irb(main):005:0> envs.get("SHELL")
=> "/bin/bash"

obs.: depois troque enviroment por environment. :)

Referências

Extending Ruby
Java Native Interface Specification

Instropecção para declaração/atribuição de valores default

Thursday, September 20th, 2007

Sabemos que o attr_accessor cria automaticamente os métodos para ler e escrever em variáveis de instância. Mas nem sempre necessitamos apenas da declaração das variáveis mas também de definir valores iniciais as mesmas. Como abaixo.

class ConfigFile
  attr_accessor :path,:alternative,:append
  def initialize(path='/etc/my.conf',alternative='/usr/local/etc/my.conf',append=false)
    @path,@alternative,@append=path,alternative,append
  end
end

Verificando uma instância sem argumentos

puts ConfigFile.new.inspect
#=> ConfigFile:0xb7a46b60 @append=false, @path="/etc/my.conf", @alternative="/usr/local/etc/my.conf">

Normale. Na classe acima fica de fácil assimilação a atribuição das variáveis de entrada com as variáveis de instância mas é necessário saber a ordem dos paramentos. Observe que escrevi cada variável quatro vezes, nada DRY.

Atribuição por Hash

Então temos o advento da atribuição por Hash(rails-style), portanto para a mesma classe, teríamos algo assim:

class ConfigFile
  attr_accessor :path,:alternative,:append
  def initialize(options={:path => '/etc/my.conf',:alternative=> '/usr/local/etc/my.conf',:append=>false})
    @path,@alternative,@append=options[:path],options[:alternative],options[:append]
  end
end

Vixi, a mesma coisa quatro vezes. Com dois agravante, se passarmos apenas uma entrada no Hash não termos os outros valores

ConfigFile.new :append => false
#=> ConfigFile:0xb7aebea8 @append=false, @path=nil, @alternative=nil

agora suponhamos que sem querer passo “apend” no lugar de “append”, ficaria assim:

ConfigFile.new :apend => "errei!!"
#=> ConfigFile:0xb7ae8320 @append=nil, @path=nil, @alternative=nil

Sem erro informando que “apend” não foi definido.

Módulo AttrDefault

Criei um módulo para facilitar a vida … o AttrDefault.

module AttrDefault
  def initialize(options={})
    _options=self.class.const_get('ATTR_DEFAULT').merge(options)
    instance_eval do
      _options.each do |k,v|
        self.send("#{k}=",v)
      end
    end
  end

  def redo_default(variable)
    send("#{variable}=",self.class.const_get('ATTR_DEFAULT')[variable])
  end

  Object.instance_eval do
    def attr_default(values)
      self.const_set("ATTR_DEFAULT",{}) unless self.const_defined? "ATTR_DEFAULT"
      self.class_eval do
        values.each do |k,v|
          attr_accessor k
          self::ATTR_DEFAULT[k]=v
        end
      end
    end
  end

end

Que

- Declara cada chave do Hash como variável de instância.
- Cria os métodos de acesso via attr_accessor.
- Atribui o valor default a cada variável de instância.
- Ao instanciar um novo objeto, faz override no valor default da variável caso exista, senão existir raise error.
- Redefine o valor se necessário.

Usando
Definição da classe, inclua o módulo AttrDefault e defina os atributos via Hash com attr_default

class ConfigFile
  include AttrDefault
  attr_default :path=>'/etc/my.conf', :alternative=>'/usr/local/etc/my.conf',:append=>true
end

Testando

conf=ConfigFile.new
conf.inspect #=> ConfigFile:0xb7ce3f1c @alternative="/usr/local/etc/my.conf", @path="/etc/my.conf", @append=true

Substituindo valores

conf=ConfigFile.new :path => "/tmp/newpath.conf", :append => false
conf.inspect        #=> ConfigFile:0xb7cbd90c @path="/tmp/newpath.conf", @append=false, @alternative="/usr/local/etc/my.conf"
puts conf.path      #=> /tmp/newpath.conf
conf.path="/novo/path"
puts conf.path      #=> /novo/path

Voltando o valor default

conf.redo_default :path
puts conf.path      #=> /etc/my.conf

Ruby Consumindo Objetos Java via RMI

Wednesday, September 12th, 2007

Demonstrarei uma solução de um programa Ruby consumindo um objeto Java via RMI. Usarei o gem Rjb - Ruby Java Bridge.

Ingredientes
+ debian packages
- sun-java6-jdk
- ruby1.8
- ruby1.8-dev
- gcc-4.1
+ ruby gem
- rjb

Instalação
Instale os pacotes, antes de instalar o gem RJB verifique as variáveis de ambiente JAVA_HOME e LD_LIBRARY_PATH, pois durante instalação do gem será compilado um objeto.

JAVA_HOME apontando para o home da JVM(os paths a seguir são relativos ao Ubuntu, verifique onde está a JVM na sua distro)

export JAVA_HOME=/usr/lib/jvm/java-6-sun

LD_LIBRARY_PATH apontando para os shared objects da JVM

export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:$JAVA_HOME/jre/lib/i386/client

Agora o gem
Instalação normale

gem install rjb

O gem perguntará qual versão você gostaria de instalar, estou usando a 1.0.6.

Bulk updating Gem source index for: http://gems.rubyforge.org
Select which gem to install for your platform (i486-linux)
 1. rjb 1.0.6 (ruby)
 2. rjb 1.0.6 (mswin32)
 3. rjb 1.0.4 (ruby)
 4. rjb 1.0.4 (mswin32)
 5. Skip this gem
 6. Cancel installation
> 1
Building native extensions.  This could take a while...
Successfully installed rjb-1.0.6

Montando a estrutura do RMI
No nosso teste vamos ler um arquivo em Java passando o caminho do arquivo. Vamos aos passos.

Criando a interface

import java.rmi.RemoteException;
import java.rmi.Remote;

public interface AbstractAnyFile extends Remote {
    String get(String filePath) throws RemoteException;
}

Depois a implementação da interface com a lógica de leitura do arquivo.

import java.rmi.RemoteException;
import java.rmi.Naming;
import java.rmi.server.UnicastRemoteObject;
import java.io.*;
public class AnyFile extends UnicastRemoteObject implements AbstractAnyFile {

  public AnyFile() throws RemoteException { }

  public String get(String filePath) {

    try{
      InputStream in = new FileInputStream(new File(filePath));
      StringBuilder ret = new StringBuilder();
      int lidos=0;
      byte b[] = new byte[2048];
      String temp = null;
      while ( (lidos=in.read(b)) != -1){
         temp = new String(b,0,lidos);
         ret.append(temp);
      }
      return ret.toString();
    }catch(Exception e){
       return "Deu pau "+e;
    }
  }

  static void bind(){

    try { 

      Naming.rebind("RemoteFile", new AnyFile() );

    }catch(Exception e){
      System.out.println("Problema ao inicializar " + e.getMessage());
    }
   }

  static public void main(String args[]) {  AnyFile.bind();  }
}

lembranças do passado …
Observe que registramos um objeto AnyFile com o nome de RemoteFile.

Levantando o servidor(Binding)
Copile os .java

javac *.java

Gere o stub

rmic AnyFile

Inicie o rmiregistry(em background)

rmiregistry &

Inicie o servidor

java AnyFile

Do lado Ruby(cliente)
Acompanhe pelos comentários

require 'rubygems'
require 'rjb'

#garantindo a localização da JVM
ENV['JAVA_HOME']='/usr/lib/jvm/java-6-sun'

#Iniciando a JVM
Rjb::load

#Atribuindo a classe Naming a variável naming
naming = Rjb::import('java.rmi.Naming')

#Obtendo o objeto AnyFile pelo nome de RemoteFile(já associado)
myfile = naming.lookup('//127.0.0.1/RemoteFile')

#Obtendo o arquivo remoto '/tmp/teste.txt' através do objeto myfile e imprimindo
puts myfile.get("/tmp/teste.txt")

“Se der tudo certo vai funcionar :)”

Possíveis problemas
Quando for instalar o gem

Building native extensions.  This could take a while...
ERROR:  While executing gem ... (Gem::Installer::ExtensionBuildError)
    ERROR: Failed to build gem native extension.

ruby extconf.rb install rjb
checking for jni.h... no
*** extconf.rb failed ***

Falta de exportar as variáveis JAVA_HOME e LD_LIBRARY_PATH ou não encontrou a JVM.

Em ruby

-:7:in `load': can't create Java VM (RuntimeError)
	from -:7

Também não achou a JVM

Impressão direta via TCP/IP com RubyGhost

Tuesday, July 10th, 2007

Geralmente as impressoras de médio e grande porte disponibilizam uma porta TCP conhecida como ‘porta bruta’ de número 9100. Demonstrarei aqui uma forma de imprimir diretamente neste tipo de printer, para o nosso exemplo usaremos o driver postscript genérico da Adobe(PS-Adobe-3.0).
Models Contas e Clientes

class Contas < ActiveRecord::Base
  belongs_to :clientes
end
class Clientes < ActiveRecord::Base
  has_one :contas
end

Configurando as colunas

require 'rghost'
clientes=Clientes.find :all, :include => :contas, :limit => 10000

grid=DataGrid::RailsGrid.new :width => 4 , :align => :center
grid.col :codigo, :title => "Código do Cliente", :width => 5
grid.col :nome, :title => "Nome"
grid.col :created_on, :title => "Data de Cadastro ", :format => :eurodate
grid.col lambda { contas.login }, :title => "Login"
grid.data(clientes)

Criando o documento com dados do ActiveRecord

doc=Document.new :paper => :A4, :landscape => true, :duplex => true
doc.before_page_create do
  set Image.for("/tmp/fatura.eps")
end
doc.set grid
doc.done

Enviando o documento

printer = TCPSocket.open('192.168.1.70', 9100)
printer.write doc.render_stream(:ps)

Deve ter saído lá na impressora… :)

Para mais informações no RGhost - RubyForge . Documentação em português no RubyForge

Rghost v0.7 Encodings e proxy charset

Wednesday, June 13th, 2007

Com a versão 0.7 podemos agora usar diferentes codificação de fontes e conversão de charset de Ruby para Postscript.
Encodings disponíveis

  • AdobeExpert
  • AdobeLatinEncoding
  • Bengali
  • CodePage1250
  • CodePage1251
  • CodePage1252
  • CodePage1253
  • CodePage1254
  • CodePage1256
  • CodePage1257
  • CodePage1258
  • CodePage874
  • IsoLatin
  • MacCentralEuropean
  • MacCyrillice
  • MacGreek
  • MacHebrew
  • TeX-CorkEncoding
  • TeX-LGR-Greek
  • TeXMathExtensionEncoding
  • TeXMathItalicEncoding
  • TeXMathSymbolEncoding
  • TeX-T2AModified2Encoding
  • TeX-T2BAdobeEncoding
  • TeX-T2CAdobeEncoding
  • TeX-X2AdobeEncoding
  • TeX-XL2encoding
  • US-ASCII
  • UTF-8

Ideal para código fonte Ruby no mesmo formato dos dados do documento. Existem duas formas de setar o encoding da fonte:
Antes de usar o documento

RubyGhostConfig::GS[:font_encoding] = 'CodePage1252'

Na criação do documento

doc=Document.new :font_encoding => 'CodePage1252'

Agora quando o charset do código fonte está diferente dos dados é necessário a conversão dos formatos. Para este fim usamos um bloco retornando uma string antes da criação do documento.

Exemplo para código fonte no padrão UTF-8 com acentuação.

RubyGhostConfig::GS[:charset_convert]= lambda {|text| Iconv::iconv('latin1','utf8', text).to_s}

O gem e o manual da versão 0.7 disponível no RubyForge.

RGhost (Beta)

Tuesday, June 5th, 2007

Rghost é uma API de criação e conversão de documento.

Uma forma de criação e vários outputs.
flow2.png
Usa o ghostscript framework para conversão de formatos. Com:

  • Legado de 16 anos de conversão de documento do Ghostscript.
  • Renderização
    • Matriz de dados otimizada
    • Permite configurar a pilha de dados interna
    • Homologado até 5.800 páginas(=~ 464.000 registros) para Linux Intel
    • Homologado até 10.000 páginas(=~ 800.000 registros) para Linux Sparc
  • Unidades
    • Cm, inch e psunit
    • Permite personalização de unidade
  • Fontes
    • 9 fontes internas(pfb)
    • GNU Barcodes(ean,i25, code39, code128)
    • Fontes binárias
    • Distorção de fontes
    • Fontes asiáticas
    • Suporte a Latin1(ISO-8859-1)
  • Cursores
    • Ponto absoluto, ponto relativo, deslocamento da origem, etc
    • Salto de página e controles de faixa dinâmicos
    • Papel
    • Disposição landscape e portrait
    • Papeis pré-definidos
    • Papeis com tamanho personalizado
    • Controle de margem de silêncio
  • Texto
    • Área de texto com tags.
    • Texto em ponto absoluto e relativo
    • Alinhamento ao ponto
    • Alinhamento pela página
    • Avaliação de variáveis internas em postscript
  • Cores
    • CMYK
    • RGB
    • Grayscale
  • Geometrias
    • Arcos, retângulos, linhas, etc
    • Predefinições auxiliares de linhas e backgrounds de faixas
  • Imagens e Templates de entrada
    • GIF e JPEG
    • EPS
  • Grade de dados para
    • Matriz de dados
    • CSV
    • ActiveRecord
    • Personalização de colunas de dados
    • Formatador de dados
    • Estilos predefinidos
  • Document Callbacks
    • :before_page_create, :after_page_create,:odd_pages,:even_pages, :before_document_create,:first_page, last_page e :after_document_create
  • Grid Callbacks
    • :before_row, :after_row, :even_row,:odd_row, :before_column, :after_column, :even_column, :odd_column, :before_table_create e :after_table_create
  • Conversor de PDF
  • Outputs(mais usados)
    • PDF, PS, PNG, EPS, JPG, TIF, laserjet, deskjet e X11

Versão Beta, por favor reporte bugs e sugestões.

RDOC
Manual Ruby Ghost Engine(pdf)

gem install rghost

Ruby Ghost Engine - Ensaio com PDF

Tuesday, May 22nd, 2007

Hoje cheguei a primeira versão do RGEngine(Ruby Ghost Engine), entro agora na fase de teste e de criação do gem.
Criei uma aplicação para demonstrar um pouco.

Identification Card(Crachá)

Dados e imagens.


cast_isaac.jpg
Name: ISAAC MENDEZ
Actor: Santiago Cabrera
Code: 85216954

cast_hiro.jpg
Name: HIRO NAKAMURA
Actor: Masi Oka
Code: 96354125

cast_simone.jpg
Name: SIMONE DEVEAUX
Actor: Tawny Cypress
Code: 52963541

cast_claire.jpg
Name: CLAIRE BENNET
Actor: Hayden Panettiere
Code: 87425149

cast_micah.jpg
Name: MICAH SANDERS
Actor: Noah Gray-Cabey
Code: 25415241

cast_nathan.jpg
Name: NATHAN PETRELLI
Actor: Adrian Pasdar
Code: 85414536

cast_matt.jpg
Name: MATT PARKMAN
Actor: Greg Grunberg
Code: 99632541

cast_mohinder.jpg
Name: MOHINDER SURESH
Actor: Sendhil Ramamurthy
Code: 25145214

cast_niki.jpg
Name: NIKI SANDERS
Actor: Ali Larter
Code: 25414521

cast_peter.jpg
Name: PETER PETRELLI
Actor: Milo Ventimiglia
Code: 58963541

Template:(template_heroes.jpg)

Programa:


heroes=Heroes.find(:all) #from ActiveRecord::Base

doc=Document.new :paper =>  [319,490]
doc.load_font(:code39)

img_dir='/app/identification/img/'

doc.before_page_create do
  set Image.for(img_dir+"template_heroes.jpg")
end

heroes.each do |h|

  doc.image(img_dir+h.photo, :x=> 2.45, :y=> 9.8, :zoom => 120)
  doc.font :name => "HelveticaBold", :size => 16
  doc.text_in : x => 2.1, :y => 7.5, :text => h.name
  doc.text_in : x => 2.2, :y => 5.5, :text => h.actor

  doc.font :barcode => :code39, :size => 50
  doc.text_in : x => 2.8, :y => 0.8, :text => h.code

  doc.next_page

end

doc.render :pdf
#or
doc.render :jpg, :multipage => true

Output: HeroesIdentification.pdf

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