As you know, Clojure is a JVM language. Clojure from JRuby is not so complicated. Many people know what's like that. My attempt here is using ClojureScript from Tilt (https://github.com/rtomayko/tilt). Tilt is a well known generic wrapper for Ruby template engines. It covers more than 20 template engines such as ERB, Haml, or CoffeeScript. Not only those, Tilt allows us to write other template wrappers. Thus, Tilt template for ClojureScript is possible.
Here's what I did. Whole code is on github, https://github.com/yokolet/clementine.
Firstly, I copied jars and Clojure code for ClojureScript under vendor/assets directory. The directory can be lib or other. An important point is to set Java's classpath to those jars and directories. See, clementine.rb below:
require 'rubygems'
require "clementine/version"
if defined?(RUBY_ENGINE) && RUBY_ENGINE == "jruby"
require "java"
CLOJURESCRIPT_HOME = File.dirname(__FILE__) + "/../vendor/assets"
$: << CLOJURESCRIPT_HOME + "/lib"
require 'clojure'
%w{compiler.jar goog.jar js.jar}.each {|name| $CLASSPATH << CLOJURESCRIPT_HOME + "/lib/" + name}
%w{clj cljs}.each {|path| $CLASSPATH << CLOJURESCRIPT_HOME + "/src/" + path}
require "clementine/clojurescript_engine"
require "clementine/clojurescript_template"
end
Line 6 sets CLOJURESCRIPT_HOME directory relative to clementine.rb. CLOJURESCRIPT_HOME + "/lib" directory has jars, so I set this directory to $LOAD_PATH so that JRuby can load clojure.jar. Line 8, "require 'clojure'" loads clojure.jar. You can also write "require 'clojure.jar'" instead, but extension doesn't matter for JRuby. Then, I added paths to $CLASSPATH. We can use $CLASSPATH global variable after "require 'java'". $CLASSPATH is Ruby Array and equivalent to Java's classpath. Line 10 and 11 set all paths to run ClojureScript.
The code below is a ClojureScriptEngine class, clojurescript_engine.rb:
require 'java'
%w{RT Keyword PersistentHashMap}.each do |name|
java_import "clojure.lang.#{name}"
end
module Clementine
class ClojureScriptEngine
def initialize(file, options)
@file = file
@options = options
end
def compile
cl_opts = PersistentHashMap.create(convert_options())
RT.loadResourceScript("cljs/closure.clj")
builder = RT.var("cljs.closure", "build")
builder.invoke(@file, cl_opts)
end
def convert_options()
opts = {}
@options.each do |k, v|
cl_key = Keyword.intern(k.to_s)
case
when (v.kind_of? Symbol)
cl_value = Keyword.intern(v.to_s)
else
cl_value = v
end
opts[cl_key] = cl_value
end
opts
end
end
end
This class basically performs ClojureScript REPL,
(cljsc/build "hello.cljs" {:optimizations :advanced :output-to "hello.js"})
. The function, cljsc/build, is defined in cljs/closure.clj file, so the code loads cljs/closure.clj by clojure.lang.RT.oadResourceScript("cljs/closure.clj"). Line 16 gets a reference to the method, then, line 17 invokes the method with arguments.This gist https://gist.github.com/1350398 might be more understandable since it is straightforward.
Lastly, Tilt templete code, clojurescript_template.rb, became as in below:
require 'tilt/template'
module Clementine
class ClojureScriptTemplate < Tilt::Template
self.default_mime_type = 'application/javascript'
def self.engine_initialized?
true
end
def initialize_engine; end
def prepare
@engine = ClojureScriptEngine.new(@file, options)
end
def evaluate(scope, locals, &block)
@output ||= @engine.compile
end
end
end
Probably, you'd better to go to Tilt sites rather than to read my how-to about this code. So, I won't comment anything about this code.
Let's try ClojureScript Tilt template.
$ cd clementine
$ jruby -Ilib -S irb
jruby-1.6.5 :001 > require 'clementine'
=> true
jruby-1.6.5 :002 > require 'tilt'
=> true
jruby-1.6.5 :003 > Tilt.register(Clementine::ClojureScriptTemplate, 'cljs', 'clj')
=> ["cljs", "clj"]
jruby-1.6.5 :004 > template = Tilt.new('/Users/yoko/Works/tmp/clojurescript/hello.clj', 1, {:optimizations => :advanced})
=> #<Clementine::ClojureScriptTemplate:0x5513dd59 @compiled_method={}, @reader=#<Proc:0x517667bd@/usr/local/rvm/gems/jruby-1.6.5@jror/gems/tilt-1.3.3/lib/tilt/template.rb:67>, @engine=#<Clementine::ClojureScriptEngine:0x3494d313 @file="/Users/yoko/Works/tmp/clojurescript/hello.clj", @options={:optimizations=>:advanced}>, @options={:optimizations=>:advanced}, @line=1, @file="/Users/yoko/Works/tmp/clojurescript/hello.clj", @default_encoding=nil, @data="(ns hello)\n(defn ^:export greet []\n (str \"Hello \" n))">
jruby-1.6.5 :005 > template.render
=> "function b(a){throw a;}var f=true,h=null,j=false;function aa(){return function(a){return a}}function k(a){return function(){return this[a]}}function l(a){return function(){return
(snip)
It worked!
This is a preliminary implementation and far from a release at this moment. But, I could confirm ClojureScript is available from Ruby. ClojureScript will be added to Ruby's template engines. My next goal will be ClojureScript from Ruby web application frameworks. It'll be more practical.
No comments:
Post a Comment