Acabo de subir a Github clj-haml, un apaño para tener algo parecido a Haml (http://haml.hamptoncatlin.com/) para clojure.

Haml:
%div#things
%span#rice Chicken Fried
%p.beans{ :food => 'true' } The magical fruit
%h1.class.otherclass#id La La La
clj-haml:
(h= :div#things
(h= :span#rice "Chicken Fried")
(h= :p.beans {:food "true"} "The magical fruit")
(h= :h1.class.otherclass#id "La La La"))
El HTML generado en ambos casos:
<div id='things'>
<span id='rice'>Chicken Fried</span>
<p class='beans' food='true'>The magical fruit</p>
<h1 id='id' class='class otherclass'>La La La</h1>
</div>
Sólo hay una función:
(h= :selector attributes contents /)
Todos los parámetros son opcionales, :selector es un keyword que puede incluir el nombre de una etiqueta HTML seguido de un id y algunas clases especificadas siguiendo la notación CSS: :p#myid.class1.clas2, :.class1#myid, :br
Attributes es un mapa asociativo con pares clave valor para nombre de atributo y valor, por ejemplo: {:href "/test.html"}.
Contents son unos cuantos forms, potencialmente conteniendo otras llamadas (h=) que se evaluarán y cuyo valor devuelto se insertará en la etiqueta que se está generando.
Por último, el / final opcional, si presente, hace que la etiqueta generada sea con autocierre (h= :br /) -> <br/>, (h= :br) -> <br></br>.
Como en Haml vamos.
La podéis descargar aquí (http://github.com/antoniogarrote/clj-haml/tree/master).
servido por Antonio
sin comentarios
compártelo
No deja de ser curioso el título de este post: java-lisp-ruby todos juntos en una sola línea. La magia que hace todo esto posible es la máquina virtual de Java, en concreto la versión 6 con un poco del futuro código de la versión 7 metido en un jar.
Java es el VHS de los lenguajes, no es el mejor pero al final está en todos los hogares. Y su secreto está en la plataforma sobre la que se ejecuta la máquina virtual JVM, una plataforma Open Source, muy optimizada por largos años de desarrollo y tan omnipresente como el mismo Java. SUN es consciente de los problemas de edad que empieza a tener Java y está intentando combatirlos en dos frentes diferentes, por un lado, rejuveneciendo Java con características más dinámicas y potentes (hace poco se anunción que Java7 soportará funciones lambda aunque esta no es precisamente una caracteristica nueva en otros lenguajes), por otro lado, esta impulsando el que lenguajes de programación más modernos y dinámicos se puedan ejecutar sobre la JVM y puedan acceder a la inmensa cantidad de bibliotecas disponibles actualmente en Java. La especificación donde se está trabajando con este fin es la JSR232 (https://scripting.dev.java.net/) y parece que la cosa está funcionando, actualmente ya se ejecutan unos 200 lenguajes sobre la JVM (ruby, python, groovy, haskell, javascript...).
Os quiero enseñar un ejemplo práctico de qué posibilidades abre lo anterior: un poco de código que permite acceder a una base de datos mediante ActiveRecord, una parte de Ruby on Rails, dese un dialecto de Lisp.
El dialecto de Lisp es Clojure, una revisión de Lisp ideal para una nueva generación de programadores, no necesariamente educados en scheme o common lisp, sino que se están acercando por primera vez al Lenguaje desde py-ruby y similares.
Clojure (http://clojure.org/), quita unos cuantos paréntesis y añade muchas de las cosas buenas de lenguajes como Haskell (estructuras de datos inmutables, listas infintas, list comprehension...) o Erlang (modelo concurrente de actores) ejecutándose sobre la JVM.
Un ejemplo de como se usa esta biblioteca la podemos ver en el código siguiente:
;; iniciamos ruby usando RS232 y JRuby
(start-ruby)
;;nos conectamos a una base de datos usando ActiveRecord
(start-active-record {:adapter (rslit 'jdbcmysql)
:encoding (rslit 'utf8)
:database (rslit 'unkasoftportal_development)
:username (rslit 'root)
:passowrd (rslit "")
:host (rslit 'localhost)})
;;cargamos un modelo ActiveRecord desde un script ruby
(ruby-require '("~/Desarrollo/unkasoft/mobile_portal_2/trunk/app/models/wap_banner"))
;;guardamos una busqueda en la base de datos
(ar-find-all! 'test 'WapBanner ":order => 'link ASC'")
;; ejemplo de como se puede mezclar codigo clojure que accede a objetos ruby
(count '(get-ruby-ref 'test))
;;recuperamos los hash de atributos de modelos active record
(ar-col-attrs 'test)
;; resultado: [#=(org.jruby.RubyHash. {"id" 1, "image_url" "http://foo.com/img.jpg", "line" "esto es un test", "link" "http://google.com"})]
(keys (first (ar-col-attrs 'test)))
;; resultado ("id" "image_url" "line" "link")
(find (first (ar-col-attrs 'test)) "image_url")
;; resultado "http://foo.com/img.jpg"
Las funciones desarrolladas son una pequeña prueba, pero se podrían extender para ofrecer una verdadera biblioteca de acceso a base de datos para Clojure Lisp a través de ruby/ActiveRecord. De ahí a tener un framework web LispOnRails hay un paso.
Lo más curioso de todo esto es que el desarrollo de estas funciones es muy parecido a escribir una macro para Ruby dentro de funciones Lisp (que a su vez se definen como macros Lisp) sustituyendo `,@ por funciones Java.
Os dejo el resto del código, si sois programadores de CommonLisp/Scheme, está página de la web de Clojure (http://clojure.org/lisps) os puede ser útil para ver las diferencias que presenta Clojure frente a estos dialectos de Lisp.
Creo que vamos a empezar a ver cosas muy interesantes dentro de poco en el mundo Java.
(defn ruby-start []
(do
(def rubyEngine (.. (new javax.script.ScriptEngineManager)
(getEngineByName "jruby")))
(def rubyContext (. rubyEngine (getContext)))))
(defn ruby-eval [script]
(. rubyEngine (eval script)))
(defn ruby-require [libs]
(when (not (nil? libs))
(let [ script (.. "require '" (concat (first libs)) (concat "'"))]
(ruby-eval script))
(recur (rest libs))))
(defn rhlit [clj-map]
(let [buffer (.append (new StringBuffer) "{")]
(loop [hash-keys (keys clj-map)]
(if (not (nil? hash-keys))
(do
(let [the-key (first hash-keys)]
(.append buffer (str the-key))
(.append buffer " => ")
(.append buffer (str (last (find clj-map the-key))))
(if (not (nil? (rest hash-keys)))
(.append buffer ",")
(.append buffer "}")))
(recur (rest hash-keys)))
(.toString buffer)))))
(defn rslit [clj-str]
(.toString
(.. (new StringBuffer)
(append "\"")
(append (str clj-str))
(append "\""))))
(defn start-active-record [configuration]
(ruby-require '("rubygems", "active_record"))
(let [script (.. "ActiveRecord::Base.establish_connection("
(concat (rhlit configuration))
(concat ")"))]
(ruby-eval script)))
(defn ruby-ref [name]
(.concat "@clj_ruby_ref_" (str name)))
(defn get-ruby-ref [rref]
(ruby-eval (ruby-ref rref)))
(defn set-ruby-ref! [rref rval]
(ruby-eval (.. (ruby-ref rref)
(concat "= ")
(concat rval))))
(defn ar-find-all! [rr class options]
(let [script (.. (new StringBuffer)
(append (ruby-ref rr))
(append " = ")
(append (str class))
(append ".")
(append "find(:all"))]
(do
(if (not (nil? options))
(.. script
(append ",")
(append options)
(append ")"))
(.. script
(append ")")))
(ruby-eval (.toString script)))))
(defn ar-col-attrs [rr]
(let [arr-size (count (get-ruby-ref rr))]
(if (= arr-size 0)
[]
(loop [i 0
col []]
(if (< i arr-size)
(let [elem (ruby-eval (.. (ruby-ref rr)
(concat "[")
(concat (str i))
(concat "].attributes")))]
(recur (inc i) (conj col elem)))
col)))))
servido por Antonio
sin comentarios
compártelo
Pruebas y más pruebas con JRuby. Hoy ha tocado darle vueltas al misteriosos funcionamiento del classpath cuando se ejecuta JRuby.
Imaginemos el siguiente escenario, creamos una nueva aplicación rails con JRuby y accedemos a la consola con un $jruby script/console.
Una vez lanzado la consola y cargado en entorno, preguntamos a jruby cual es el classpath de la aplicación.
Para ello podemos consultar el valor de $CLASSPATH para obtener una lista las rutas en el classpath de jruby:
>> $CLASSPATH
=> ["file:/usr/local/jruby/jruby-1.1b1/lib/ruby/gems/1.8/gems/jruby-openssl-0.0.4/lib/jopenssl.jar", "file:/usr/local/jruby/jruby-1.1b1/lib/ruby/gems/1.8/gems/ActiveRecord-JDBC-0.5/lib/jdbc_adapter_internal.jar"]
Alternativamente podemos preguntarle a Java que nos diga el valor del classpath usando la clase System:
>> java.lang.System.get_properties.get_property "java.class.path"
=> "/usr/local/jruby/jruby-1.1b1/lib/bsf.jar:/usr/local/jruby/jruby-1.1b1/lib/jruby.jar:/usr/local/jruby/jruby-1.1b1/lib/mysql-connector-java-5.1.5-bin.jar"
Hasta aquí nada raro, JRuby incluye en el classpath por defecto las rutas del classpath del entorno desde donde se ejecutando el proceso java que lanza el interprete JRuby y los ficheros localizados en el directorio $JRUBY_HOME/lib.
De este modo usando la función include_class podremos incluir cualquier clase java que pertenezca a las clases estandar del JDK o que se encuentren localizada en cualquiera de las rutas anteriores.
Supongamos que ahora queremos utilizar en nuestra aplicacion una serie de bibliotecas java para su uso desde rails. Una posible solución es crear un directorio como RAILS_ROOT/lib/classes, copiar ahí todos los jars con las clases que queramos utilizar y hacer desde environment.rb algo como:
if RUBY_PLATFORM =~ /java/
if File.exist?(File.join(RAILS_ROOT,'lib','classes'))
Dir.open(File.join(RAILS_ROOT,'lib','classes')).each do |filename|
if filename =~ /.jar|.zip/
$CLASSPATH << File.expand_path(File.join(RAILS_ROOT,'lib','classes',filename))
end
end
end
end
Si ahora abrimos la consola y preguntamos por el valor de $CLASSPATH, obtendremos el comportamiento deseado:
>> $CLASSPATH
=> ["file:/usr/local/jruby/jruby-1.1b1/lib/ruby/gems/1.8/gems/jruby-openssl-0.0.4/lib/jopenssl.jar", "file:/usr/local/jruby/jruby-1.1b1/lib/ruby/gems/1.8/gems/ActiveRecord-JDBC-0.5/lib/jdbc_adapter_internal.jar", "file:/Users/antoniogarrotehernandez/Desarrollo/Caja4JRuby/lib/classes/antlr-2.7.6.jar", "file:/Users/antoniogarrotehernandez/Desarrollo/Caja4JRuby/lib/classes/asm.jar", "file:/Users/antoniogarrotehernandez/Desarrollo/Caja4JRuby/lib/classes/Caja4HibernateModel.jar", "file:/Users/antoniogarrotehernandez/Desarrollo/Caja4JRuby/lib/classes/cglib-2.1.3.jar", "file:/Users/antoniogarrotehernandez/Desarrollo/Caja4JRuby/lib/classes/commons-collections-2.1.1.jar", "file:/Users/antoniogarrotehernandez/Desarrollo/Caja4JRuby/lib/classes/commons-logging-1.0.4.jar", "file:/Users/antoniogarrotehernandez/Desarrollo/Caja4JRuby/lib/classes/dom4j-1.6.1.jar", "file:/Users/antoniogarrotehernandez/Desarrollo/Caja4JRuby/lib/classes/ehcache-1.2.3.jar", "file:/Users/antoniogarrotehernandez/Desarrollo/Caja4JRuby/lib/classes/ejb3-persistence.jar", "file:/Users/antoniogarrotehernandez/Desarrollo/Caja4JRuby/lib/classes/hibernate-annotations.jar", "file:/Users/antoniogarrotehernandez/Desarrollo/Caja4JRuby/lib/classes/hibernate-commons-annotations.jar", "file:/Users/antoniogarrotehernandez/Desarrollo/Caja4JRuby/lib/classes/hibernate-entitymanager.jar", "file:/Users/antoniogarrotehernandez/Desarrollo/Caja4JRuby/lib/classes/hibernate-validator.jar", "file:/Users/antoniogarrotehernandez/Desarrollo/Caja4JRuby/lib/classes/hibernate3.jar", "file:/Users/antoniogarrotehernandez/Desarrollo/Caja4JRuby/lib/classes/javassist.jar", "file:/Users/antoniogarrotehernandez/Desarrollo/Caja4JRuby/lib/classes/jboss-archive-browsing.jar", "file:/Users/antoniogarrotehernandez/Desarrollo/Caja4JRuby/lib/classes/jta.jar"]
Y por supuesto podremos incluir las clases de cualquiera de esos jars.
Por ejemplo un POJO correspondiente a un modelo Hibernate:
>> include_class "model.Post"
=> ["model.Post"]
Parece, que tenemos todo lo necesario para utilizar cualquier clase java que queramos. Intentemos obtener una entidad persistente de ese modelo hibernate usando las interfaces JPA de la especificación EJB3.0:
>> javax.persistence.Persistence.create_entity_manager_factory "Caja4HibernateModelPU"
NativeException: javax.persistence.PersistenceException: No Persistence provider for EntityManager named Caja4HibernateModelPU
...
Algo está fallando.
El problema es que con la técnica anterior hemos modificado el classpath de JRuby, pero no el classpath de la JVM que ejecuta JRuby. Esto provoca que cuando las clases de la implementación JPA de hibernate buscan el fichero persistence.xml con la configuración de la unidad de persistencia, no la encuentran en el classpath, a pesar de aparecer listada en el valor de $CLASSPATH.
Podemos comprobar esto preguntandole a la clase System por el classpath:
>> java.lang.System.get_properties.get_property "java.class.path"
=> "/usr/local/jruby/jruby-1.1b1/lib/bsf.jar:/usr/local/jruby/jruby-1.1b1/lib/jruby.jar:/usr/local/jruby/jruby-1.1b1/lib/mysql-connector-java-5.1.5-bin.jar"
>>
Ni rastro de los jars.
Las posibles soluciones a esto por ahora pasan por:
- copiar todos los jars $JRUBY_HOME/lib
- modificar el script jruby para que incluya las rutas
- fijar la variable CLASSPATH en la shell que lanza el script server
La solución por la que he optado es crear una tarea Rake que utilice una modificación del script anterior para que genere un script que fije el classpath y para modificar el classpath con un $source script/setup_classpath:
desc 'setup classpath script for the application'
task :setup_classpath_script => :environment do
actual = ""
jars = Dir.entries(File.join(RAILS_ROOT,"lib","classes")).map{ |f|
if(f =~ /.jar|.zip/)
puts "Added: #{File.expand_path(f,File.join(RAILS_ROOT,"lib","classes"))}"
File.expand_path(f,File.join(RAILS_ROOT,"lib","classes"))
else
nil
end
}.compact
if(ENV['CLASSPATH']!=nil && ENV['CLASSPATH']!="" && ENV['CLASSPATH']!=" " && jars.length>0)
actual = ENV['CLASSPATH']+":"
else
actual = ENV['CLASSPATH']
end
to_append = ""
if(jars.length>0)
to_append = jars.join(":")
end
File.open(File.join(RAILS_ROOT,"script","setup_classpath"),"w") do |f|
f << "export CLASSPATH=#{actual+to_append}"
end
`chmod uga+x #{File.expand_path(File.join(RAILS_ROOT,'script','setup_classpath'))}`
puts "You can setup the classpath executing \n$source script/setup_classpath"
end
De todas formas, esto no pasa de ser un parche temporal. (jruby 1.1b1) Además curiosamente, con esta técnica, la clase System devuelve el classpath correcto pero si preguntamos a $CLASSPATH ninguno de estos jars aparecerán listados. A pesar de que podremos crear las clases sin ningún problema:
>> javax.persistence.Persistence.create_entity_manager_factory "Caja4HibernateModelPU"
18-dic-2007 23:20:45 org.hibernate.cfg.annotations.Version
INFO: Hibernate Annotations 3.3.0.GA
18-dic-2007 23:20:45 org.hibernate.cfg.Environment
INFO: Hibernate 3.2.5
18-dic-2007 23:20:45 org.hibernate.cfg.Environment
INFO: hibernate.properties not found
18-dic-2007 23:20:45 org.hibernate.cfg.Environment buildBytecodeProvider
INFO: Bytecode provider name : cglib
18-dic-2007 23:20:45 org.hibernate.cfg.Environment
INFO: using JDK 1.4 java.sql.Timestamp handling
18-dic-2007 23:20:46 org.hibernate.ejb.Version
INFO: Hibernate EntityManager 3.3.1.GA
18-dic-2007 23:20:46 org.hibernate.cfg.AnnotationBinder bindClass
INFO: Binding entity from annotated class: model.Post
18-dic-2007 23:20:46 org.hibernate.cfg.annotations.QueryBinder bindQuery
INFO: Binding Named query: Post.findById => SELECT p FROM Post p WHERE p.id = :id
18-dic-2007 23:20:46 org.hibernate.cfg.annotations.QueryBinder bindQuery
INFO: Binding Named query: Post.findByTitle => SELECT p FROM Post p WHERE p.title = :title
18-dic-2007 23:20:46 org.hibernate.cfg.annotations.QueryBinder bindQuery
INFO: Binding Named query: Post.findByCreatedAt => SELECT p FROM Post p WHERE p.createdAt = :createdAt
18-dic-2007 23:20:46 org.hibernate.cfg.annotations.EntityBinder bindTable
INFO: Bind entity model.Post on table posts
18-dic-2007 23:20:47 org.hibernate.validator.Version
INFO: Hibernate Validator 3.0.0.GA
18-dic-2007 23:20:47 org.hibernate.connection.DriverManagerConnectionProvider configure
INFO: Using Hibernate built-in connection pool (not for production use!)
18-dic-2007 23:20:47 org.hibernate.connection.DriverManagerConnectionProvider configure
INFO: Hibernate connection pool size: 20
18-dic-2007 23:20:47 org.hibernate.connection.DriverManagerConnectionProvider configure
INFO: autocommit mode: true
18-dic-2007 23:20:47 org.hibernate.connection.DriverManagerConnectionProvider configure
INFO: using driver: com.mysql.jdbc.Driver at URL: jdbc:mysql://localhost:3306/caja4
18-dic-2007 23:20:47 org.hibernate.connection.DriverManagerConnectionProvider configure
INFO: connection properties: {user=root, password=****, autocommit=true, release_mode=auto}
18-dic-2007 23:20:48 org.hibernate.cfg.SettingsFactory buildSettings
INFO: RDBMS: MySQL, version: 5.0.45
18-dic-2007 23:20:48 org.hibernate.cfg.SettingsFactory buildSettings
INFO: JDBC driver: MySQL-AB JDBC Driver, version: mysql-connector-java-5.1.5 ( Revision: ${svn.Revision} )
18-dic-2007 23:20:48 org.hibernate.dialect.Dialect
INFO: Using dialect: org.hibernate.dialect.MySQLDialect
18-dic-2007 23:20:48 org.hibernate.transaction.TransactionFactoryFactory buildTransactionFactory
INFO: Transaction strategy: org.hibernate.transaction.JDBCTransactionFactory
18-dic-2007 23:20:48 org.hibernate.transaction.TransactionManagerLookupFactory getTransactionManagerLookup
INFO: No TransactionManagerLookup configured (in JTA environment, use of read-write or transactional second-level cache is not recommended)
18-dic-2007 23:20:48 org.hibernate.cfg.SettingsFactory buildSettings
INFO: Automatic flush during beforeCompletion(): disabled
18-dic-2007 23:20:48 org.hibernate.cfg.SettingsFactory buildSettings
INFO: Automatic session close at end of transaction: disabled
18-dic-2007 23:20:48 org.hibernate.cfg.SettingsFactory buildSettings
INFO: JDBC batch size: 15
18-dic-2007 23:20:48 org.hibernate.cfg.SettingsFactory buildSettings
INFO: JDBC batch updates for versioned data: disabled
18-dic-2007 23:20:48 org.hibernate.cfg.SettingsFactory buildSettings
INFO: Scrollable result sets: enabled
18-dic-2007 23:20:48 org.hibernate.cfg.SettingsFactory buildSettings
INFO: JDBC3 getGeneratedKeys(): enabled
18-dic-2007 23:20:48 org.hibernate.cfg.SettingsFactory buildSettings
INFO: Connection release mode: auto
18-dic-2007 23:20:48 org.hibernate.cfg.SettingsFactory buildSettings
INFO: Maximum outer join fetch depth: 2
18-dic-2007 23:20:48 org.hibernate.cfg.SettingsFactory buildSettings
INFO: Default batch fetch size: 1
18-dic-2007 23:20:48 org.hibernate.cfg.SettingsFactory buildSettings
INFO: Generate SQL with comments: disabled
18-dic-2007 23:20:48 org.hibernate.cfg.SettingsFactory buildSettings
INFO: Order SQL updates by primary key: disabled
18-dic-2007 23:20:48 org.hibernate.cfg.SettingsFactory buildSettings
INFO: Order SQL inserts for batching: disabled
18-dic-2007 23:20:48 org.hibernate.cfg.SettingsFactory createQueryTranslatorFactory
INFO: Query translator: org.hibernate.hql.ast.ASTQueryTranslatorFactory
18-dic-2007 23:20:48 org.hibernate.hql.ast.ASTQueryTranslatorFactory
INFO: Using ASTQueryTranslatorFactory
18-dic-2007 23:20:48 org.hibernate.cfg.SettingsFactory buildSettings
INFO: Query language substitutions: {}
18-dic-2007 23:20:48 org.hibernate.cfg.SettingsFactory buildSettings
INFO: JPA-QL strict compliance: enabled
18-dic-2007 23:20:48 org.hibernate.cfg.SettingsFactory buildSettings
INFO: Second-level cache: enabled
18-dic-2007 23:20:48 org.hibernate.cfg.SettingsFactory buildSettings
INFO: Query cache: disabled
18-dic-2007 23:20:48 org.hibernate.cfg.SettingsFactory createCacheProvider
INFO: Cache provider: org.hibernate.cache.NoCacheProvider
18-dic-2007 23:20:48 org.hibernate.cfg.SettingsFactory buildSettings
INFO: Optimize cache for minimal puts: disabled
18-dic-2007 23:20:48 org.hibernate.cfg.SettingsFactory buildSettings
INFO: Structured second-level cache entries: disabled
18-dic-2007 23:20:48 org.hibernate.cfg.SettingsFactory buildSettings
INFO: Statistics: disabled
18-dic-2007 23:20:48 org.hibernate.cfg.SettingsFactory buildSettings
INFO: Deleted entity synthetic identifier rollback: disabled
18-dic-2007 23:20:48 org.hibernate.cfg.SettingsFactory buildSettings
INFO: Default entity-mode: pojo
18-dic-2007 23:20:48 org.hibernate.cfg.SettingsFactory buildSettings
INFO: Named query checking : enabled
18-dic-2007 23:20:48 org.hibernate.impl.SessionFactoryImpl
INFO: building session factory
18-dic-2007 23:20:48 org.hibernate.impl.SessionFactoryObjectFactory addInstance
INFO: Not binding factory to JNDI, no JNDI name configured
18-dic-2007 23:20:48 org.hibernate.tool.hbm2ddl.SchemaUpdate execute
INFO: Running hbm2ddl schema update
18-dic-2007 23:20:48 org.hibernate.tool.hbm2ddl.SchemaUpdate execute
INFO: fetching database metadata
18-dic-2007 23:20:48 org.hibernate.tool.hbm2ddl.SchemaUpdate execute
INFO: updating schema
18-dic-2007 23:20:49 org.hibernate.tool.hbm2ddl.TableMetadata
INFO: table found: caja4.posts
18-dic-2007 23:20:49 org.hibernate.tool.hbm2ddl.TableMetadata
INFO: columns: [updated_at, title, created_at, body, id]
18-dic-2007 23:20:49 org.hibernate.tool.hbm2ddl.TableMetadata
INFO: foreign keys: []
18-dic-2007 23:20:49 org.hibernate.tool.hbm2ddl.TableMetadata
INFO: indexes: [primary]
18-dic-2007 23:20:49 org.hibernate.tool.hbm2ddl.SchemaUpdate execute
INFO: schema update complete
=> #
servido por Antonio
sin comentarios
compártelo