closure-templates 0.1.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +7 -0
- data/Gemfile +4 -0
- data/README.md +118 -0
- data/Rakefile +2 -0
- data/closure-templates.gemspec +21 -0
- data/lib/closure-templates.rb +102 -0
- data/lib/closure-templates/version.rb +5 -0
- data/lib/jar/soy-latest.jar +0 -0
- data/test/person.rb +33 -0
- data/test/templates/examples.nested.soy +35 -0
- data/test/templates/examples.simple.soy +57 -0
- data/test/test_closure-templates.rb +70 -0
- metadata +78 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
data/README.md
ADDED
@@ -0,0 +1,118 @@
|
|
1
|
+
# Closure Templates
|
2
|
+
|
3
|
+
Gem for using [Google Closure Templates](http://code.google.com/closure/templates/) with [jRuby](http://jruby.org/).
|
4
|
+
|
5
|
+
## Installation
|
6
|
+
|
7
|
+
gem install closure-templates
|
8
|
+
|
9
|
+
## Usage
|
10
|
+
|
11
|
+
Assuming a simple template:
|
12
|
+
|
13
|
+
/** myapp.soy */
|
14
|
+
|
15
|
+
{namespace myapp.templates}
|
16
|
+
|
17
|
+
/**
|
18
|
+
* Hello there template
|
19
|
+
* @param name The name of the person to greet
|
20
|
+
*/
|
21
|
+
{template}
|
22
|
+
Hi {$name}
|
23
|
+
{/template}
|
24
|
+
|
25
|
+
Use it in jRuby:
|
26
|
+
|
27
|
+
require 'closure-templates'
|
28
|
+
|
29
|
+
ClosureTemplates.config(
|
30
|
+
:template_directory => '/path/to/soy/templates',
|
31
|
+
:output_directory => '/path/to/where/generated/javascript/should/go'
|
32
|
+
)
|
33
|
+
|
34
|
+
# render a template
|
35
|
+
ClosureTemplates.render('myapp.templates.hello', :name => 'Dylan')
|
36
|
+
# returns 'Hi Dylan'
|
37
|
+
|
38
|
+
Use it in JavaScript:
|
39
|
+
|
40
|
+
<script type="text/javascript" src="soyutils.js"></script>
|
41
|
+
<!-- following was generated by ClosureTemplates -->
|
42
|
+
<script type="text/javascript" src="myapp.templates.js"></script>
|
43
|
+
<script type="text/javascript">
|
44
|
+
var output = myapp.templates.hello({ name: 'Dylan' });
|
45
|
+
</script>
|
46
|
+
|
47
|
+
You will need to include either [soyutils.js](http://code.google.com/p/closure-templates/source/browse/trunk/javascript/soyutils.js) (if you are not also using Google Closure Library) or [soyutils_usegoog.js](http://code.google.com/p/closure-templates/source/browse/trunk/javascript/soyutils_usegoog.js) (if you are). Read more [here](http://code.google.com/closure/templates/docs/javascript_usage.html#utilities).
|
48
|
+
|
49
|
+
## Details
|
50
|
+
|
51
|
+
### Rails Usage
|
52
|
+
|
53
|
+
Currently I have an initializer that looks like this:
|
54
|
+
|
55
|
+
require 'closure-templates'
|
56
|
+
|
57
|
+
ClosureTemplates.config(
|
58
|
+
:template_directory => "#{Rails.root}/app/templates",
|
59
|
+
:output_directory => "#{Rails.root}/app/assets/javascripts/templates"
|
60
|
+
)
|
61
|
+
|
62
|
+
Then I render the templates where needed (HAML example):
|
63
|
+
|
64
|
+
!= ClosureTemplates.render('myapp.posts.index', :posts => @posts)
|
65
|
+
|
66
|
+
### Template Inputs
|
67
|
+
|
68
|
+
Google Closure Templates have some requirements for the data passed in, specifically (from [SoyData.java](http://code.google.com/p/closure-templates/source/browse/trunk/java/src/com/google/template/soy/data/SoyData.java#37)):
|
69
|
+
|
70
|
+
> Note that in order for the conversion process to succeed, the given data structure must
|
71
|
+
> correspond to a valid SoyData tree. Some requirements include:
|
72
|
+
> (a) all Maps within your data structure must have string keys that are identifiers,
|
73
|
+
> (b) all non-leaf nodes must be Maps or Lists,
|
74
|
+
> (c) all leaf nodes must be null, boolean, int, double, or String (corresponding to Soy
|
75
|
+
> primitive data types null, boolean, integer, float, string).
|
76
|
+
|
77
|
+
This gem makes some attempts to meet these requirements by converting hash keys to strings and calling the 'attributes' method (by default) on objects which respond to it. This allows the use of Rails models, for example, to be passed to templates and have their basic attributes accessible. You can customize the method that the gem uses to try to get a hash of attributes by passing the 'attribute_method' option to the config method (see below).
|
78
|
+
|
79
|
+
### Config Options
|
80
|
+
|
81
|
+
These are all passed to the ClosureTemplates.config method, which must be called before the first ClosureTemplates.render.
|
82
|
+
|
83
|
+
#### :template_directory
|
84
|
+
|
85
|
+
*default*: templates
|
86
|
+
|
87
|
+
Where you keep your soy templates. You should specify this.
|
88
|
+
|
89
|
+
#### :output_directory
|
90
|
+
|
91
|
+
*default*: closure_js
|
92
|
+
|
93
|
+
Where you want the gem to put the generated JavaScript files. You should specify this.
|
94
|
+
|
95
|
+
#### :recompile
|
96
|
+
|
97
|
+
*default*: true
|
98
|
+
|
99
|
+
The gem maintains a list of the soy template files and can check to see if any of them have been modified when calling render. If one has been modified, then if :recompile is true, it will regenerate the Java object and the JavaScript files from the latest soy templates, which is nice for development. You could turn this off in production to get slightly faster rendering.
|
100
|
+
|
101
|
+
#### :attribute_method
|
102
|
+
|
103
|
+
*default*: attributes
|
104
|
+
|
105
|
+
This is the method the gem will try to use to get a more Closure Templates friendly representation of Ruby Objects before passing them into the template. It will call this method on objects if they respond to it. If you want to customize which properties (or make methods available) you can customize this method to be one you implement in your classes.
|
106
|
+
|
107
|
+
### Attributions
|
108
|
+
|
109
|
+
Idea for this gem came from a discussion on the Google Closure Template discuss group:
|
110
|
+
|
111
|
+
[Closure Templates in Ruby?](https://groups.google.com/group/closure-templates-discuss/browse_thread/thread/50d977fcc121953b)
|
112
|
+
|
113
|
+
where Aliaksandr Zahatski pointed me to a [gist](https://gist.github.com/1160217) by Ilya Grigorik which led me to the [Closure Sprockets](https://github.com/igrigorik/closure-sprockets) gem also by Ilya. I took code and ideas from both places as starting points for this gem.
|
114
|
+
|
115
|
+
### License
|
116
|
+
|
117
|
+
(MIT License) - Copyright (c) 2011 Dylan Vaughn
|
118
|
+
|
data/Rakefile
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
3
|
+
require "closure-templates/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = "closure-templates"
|
7
|
+
s.version = Closure::Templates::VERSION
|
8
|
+
s.platform = Gem::Platform::RUBY
|
9
|
+
s.authors = ["Dylan Vaughn"]
|
10
|
+
s.email = ["dylancvaughn@gmail.com"]
|
11
|
+
s.homepage = "https://github.com/dylanvaughn/closure-templates"
|
12
|
+
s.summary = %q{Google Closure Templates for jRuby / Rails}
|
13
|
+
s.description = %q{Generates methods / code for server and client-side use of Google Closure Templates with jRuby}
|
14
|
+
|
15
|
+
s.add_development_dependency('test-unit')
|
16
|
+
|
17
|
+
s.files = `git ls-files`.split("\n")
|
18
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
19
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
20
|
+
s.require_paths = ["lib"]
|
21
|
+
end
|
@@ -0,0 +1,102 @@
|
|
1
|
+
require 'fileutils'
|
2
|
+
require 'java'
|
3
|
+
require "#{File.dirname(__FILE__)}/jar/soy-latest.jar"
|
4
|
+
|
5
|
+
require 'closure-templates/version'
|
6
|
+
|
7
|
+
java_import "com.google.template.soy.SoyFileSet"
|
8
|
+
java_import "com.google.template.soy.data.SoyMapData"
|
9
|
+
java_import "com.google.template.soy.tofu.SoyTofu"
|
10
|
+
java_import "com.google.template.soy.jssrc.SoyJsSrcOptions"
|
11
|
+
|
12
|
+
class ClosureTemplates
|
13
|
+
@@files = {}
|
14
|
+
@@template_dir = nil
|
15
|
+
@@output_dir = nil
|
16
|
+
@@tofu = nil
|
17
|
+
@@initialized = false
|
18
|
+
@@recompile = nil
|
19
|
+
@@soyJsSrcOptions = nil
|
20
|
+
@@attr_method = nil
|
21
|
+
|
22
|
+
def self.config(opts)
|
23
|
+
@@template_dir = opts[:template_directory] || 'templates'
|
24
|
+
@@output_dir = opts[:output_directory] || 'closure_js'
|
25
|
+
@@recompile = !opts[:recompile].nil? ? opts[:recompile] : true
|
26
|
+
@@attr_method = opts[:attribute_method] || 'attributes'
|
27
|
+
@@soyJsSrcOptions = SoyJsSrcOptions.new
|
28
|
+
|
29
|
+
@@soyJsSrcOptions.setShouldProvideRequireSoyNamespaces(true)
|
30
|
+
FileUtils.mkdir(@@output_dir) unless File.directory?(@@output_dir)
|
31
|
+
self.compile
|
32
|
+
|
33
|
+
@@initialized = true
|
34
|
+
end
|
35
|
+
|
36
|
+
def self.render(template, assigns = {})
|
37
|
+
if !@@initialized
|
38
|
+
raise "ERROR: Not configured!\nConfigure by calling 'ClosureTemplates.config(:template_directory => path_to_templates, :output_directory => path_to_where_js_should_go)' before calling render.\n"
|
39
|
+
end
|
40
|
+
if @@tofu.nil?
|
41
|
+
raise "ERROR: No templates found in #{@@template_dir}"
|
42
|
+
end
|
43
|
+
if @@recompile
|
44
|
+
@@files.keys.each do |f|
|
45
|
+
if File.mtime(f).to_i > @@files[f]
|
46
|
+
self.compile
|
47
|
+
break
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
locals = assigns.dup
|
52
|
+
locals.keys.each do |key|
|
53
|
+
val = locals.delete(key)
|
54
|
+
if val.is_a?(Array)
|
55
|
+
locals[key.to_s] = val.map { |v| convert_for_closure(v) }
|
56
|
+
else
|
57
|
+
locals[key.to_s] = convert_for_closure(val)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
@@tofu.render(template, locals, nil)
|
61
|
+
end
|
62
|
+
|
63
|
+
def self.convert_for_closure(val)
|
64
|
+
val = val.send(@@attr_method) if val.respond_to?(@@attr_method)
|
65
|
+
if val.is_a?(Hash)
|
66
|
+
val.keys.each do |k|
|
67
|
+
v = val.delete(k)
|
68
|
+
val[k.to_s] = v.is_a?(Integer) ? v.to_java(:int) : v
|
69
|
+
end
|
70
|
+
end
|
71
|
+
val
|
72
|
+
end
|
73
|
+
|
74
|
+
def self.compile
|
75
|
+
@@files = {}
|
76
|
+
files_in_order = []
|
77
|
+
sfs_builder = SoyFileSet::Builder.new
|
78
|
+
Dir.glob("#{@@template_dir}/**/*.soy") do |file|
|
79
|
+
files_in_order << file
|
80
|
+
@@files[file] = File.mtime(file).to_i
|
81
|
+
sfs_builder.add(java.io.File.new(file))
|
82
|
+
end
|
83
|
+
if @@files.keys.size == 0
|
84
|
+
# no templates
|
85
|
+
@@tofu = nil
|
86
|
+
else
|
87
|
+
sfs = sfs_builder.build
|
88
|
+
|
89
|
+
# ruby
|
90
|
+
@@tofu = sfs.compileToJavaObj(true)
|
91
|
+
|
92
|
+
# javascript
|
93
|
+
sfs.compileToJsSrc(@@soyJsSrcOptions, nil).each_with_index do |js_out, index|
|
94
|
+
file_path = File.join(@@output_dir, files_in_order[index].gsub(/^#{@@template_dir}\//, '').gsub(/soy$/, 'js'))
|
95
|
+
File.open(file_path, 'w') do |f|
|
96
|
+
f.write(js_out)
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
end
|
Binary file
|
data/test/person.rb
ADDED
@@ -0,0 +1,33 @@
|
|
1
|
+
class Person
|
2
|
+
|
3
|
+
def initialize(first_name, last_name)
|
4
|
+
@first_name = first_name
|
5
|
+
@last_name = last_name
|
6
|
+
end
|
7
|
+
|
8
|
+
def age
|
9
|
+
35
|
10
|
+
end
|
11
|
+
|
12
|
+
def first_name
|
13
|
+
@first_name
|
14
|
+
end
|
15
|
+
|
16
|
+
def last_name
|
17
|
+
@last_name
|
18
|
+
end
|
19
|
+
|
20
|
+
def full_name
|
21
|
+
"#{first_name} #{last_name}"
|
22
|
+
end
|
23
|
+
|
24
|
+
def attributes
|
25
|
+
{
|
26
|
+
"first_name" => first_name,
|
27
|
+
"last_name" => last_name,
|
28
|
+
"full_name" => full_name,
|
29
|
+
"age" => age
|
30
|
+
}
|
31
|
+
end
|
32
|
+
|
33
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
{namespace examples.nested}
|
2
|
+
|
3
|
+
/**
|
4
|
+
* Greets a person using "Hello" by default.
|
5
|
+
* @param name The name of the person.
|
6
|
+
* @param? greetingWord Optional greeting word to use instead of "Hello".
|
7
|
+
*/
|
8
|
+
{template .helloName}
|
9
|
+
{if not $greetingWord}
|
10
|
+
Hello {$name}!
|
11
|
+
{else}
|
12
|
+
{$greetingWord} {$name}!
|
13
|
+
{/if}
|
14
|
+
{/template}
|
15
|
+
|
16
|
+
/**
|
17
|
+
* Greets a person and optionally a list of other people.
|
18
|
+
* @param name The name of the person.
|
19
|
+
* @param additionalNames The additional names to greet. May be an empty list.
|
20
|
+
*/
|
21
|
+
{template .hello}
|
22
|
+
// Greet the person.
|
23
|
+
{call .helloName data="all" /}<br>
|
24
|
+
// Greet the additional people.
|
25
|
+
{foreach $additionalName in $additionalNames}
|
26
|
+
{call .helloName}
|
27
|
+
{param name: $additionalName /}
|
28
|
+
{/call}
|
29
|
+
{if not isLast($additionalName)}
|
30
|
+
<br> // break after every line except the last
|
31
|
+
{/if}
|
32
|
+
{ifempty}
|
33
|
+
No additional people to greet.
|
34
|
+
{/foreach}
|
35
|
+
{/template}
|
@@ -0,0 +1,57 @@
|
|
1
|
+
{namespace examples.simple}
|
2
|
+
|
3
|
+
/**
|
4
|
+
* Greets a person using "Hello" by default.
|
5
|
+
* @param name The name of the person.
|
6
|
+
*/
|
7
|
+
{template .helloSimple}
|
8
|
+
Hiya {$name}!
|
9
|
+
{/template}
|
10
|
+
|
11
|
+
/**
|
12
|
+
* Greets a object
|
13
|
+
* @param object The named thing.
|
14
|
+
*/
|
15
|
+
{template .helloObject}
|
16
|
+
First name is: {$object.first_name} and last name is: {$object.last_name} and full name is: {$object.full_name}
|
17
|
+
{/template}
|
18
|
+
|
19
|
+
/**
|
20
|
+
* Tells you your age (testing numbers)
|
21
|
+
* @param object The named thing.
|
22
|
+
*/
|
23
|
+
{template .helloObjectInt}
|
24
|
+
{$object.first_name}'s age is {$object.age}
|
25
|
+
{/template}
|
26
|
+
|
27
|
+
|
28
|
+
/**
|
29
|
+
* Greets a list of objects
|
30
|
+
* @param objects An array of the things.
|
31
|
+
*/
|
32
|
+
{template .helloObjects}
|
33
|
+
{foreach $object in $objects}
|
34
|
+
{call .helloObject}
|
35
|
+
{param object: $object /}
|
36
|
+
{/call}
|
37
|
+
<br />
|
38
|
+
{/foreach}
|
39
|
+
{/template}
|
40
|
+
|
41
|
+
|
42
|
+
/**
|
43
|
+
* Data tests
|
44
|
+
* @param data A hash of different data types
|
45
|
+
*/
|
46
|
+
{template .dataObjects}
|
47
|
+
String is {$data.string}, int is {$data.int}, float is {$data.float}, null is {$data.null}, bool_true is {$data.bool_true}, bool_false is {$data.bool_false}
|
48
|
+
{/template}
|
49
|
+
|
50
|
+
|
51
|
+
/**
|
52
|
+
* Data tests
|
53
|
+
* @param data A hash of different bools
|
54
|
+
*/
|
55
|
+
{template .booleans}
|
56
|
+
{if $data.bool_true}True is true.{/if} {if not $data.bool_false}False is false.{/if}
|
57
|
+
{/template}
|
@@ -0,0 +1,70 @@
|
|
1
|
+
require 'test/unit'
|
2
|
+
require 'closure-templates'
|
3
|
+
require 'person'
|
4
|
+
|
5
|
+
class TestClosureTemplates < Test::Unit::TestCase
|
6
|
+
|
7
|
+
def setup
|
8
|
+
ClosureTemplates.config(:template_directory => "#{File.dirname(__FILE__)}/templates", :output_directory => "test/js")
|
9
|
+
end
|
10
|
+
|
11
|
+
def test_simple_template
|
12
|
+
assert_equal ClosureTemplates.render('examples.simple.helloSimple', { :name => 'Bob' }), 'Hiya Bob!'
|
13
|
+
end
|
14
|
+
|
15
|
+
def test_more_complicated_template
|
16
|
+
assert_equal ClosureTemplates.render('examples.nested.hello', { :name => 'Jane', :additionalNames => ['Sue', 'Frank'] }), 'Hello Jane!<br>Hello Sue!<br>Hello Frank!'
|
17
|
+
end
|
18
|
+
|
19
|
+
def test_simple_template_without_locals
|
20
|
+
assert_raise NativeException do
|
21
|
+
ClosureTemplates.render('examples.simple.helloSimple')
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def test_empty_template_dir
|
26
|
+
assert_nothing_raised do
|
27
|
+
ClosureTemplates.config(:template_directory => "#{File.dirname(__FILE__)}/no_templates", :output_directory => "test/js")
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def test_calling_render_with_no_templates
|
32
|
+
assert_raise RuntimeError do
|
33
|
+
ClosureTemplates.config(:template_directory => "#{File.dirname(__FILE__)}/no_templates", :output_directory => "test/js")
|
34
|
+
ClosureTemplates.render('examples.simple.helloSimple')
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def test_calling_render_with_incorrect_template_name
|
39
|
+
assert_raise NativeException do
|
40
|
+
ClosureTemplates.render('examples.simple.notSoSimple', { :name => 'Bob' })
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def test_calling_with_ruby_object
|
45
|
+
dylan = Person.new('Dylan', 'Vaughn')
|
46
|
+
assert_equal ClosureTemplates.render('examples.simple.helloObject', :object => dylan), 'First name is: Dylan and last name is: Vaughn and full name is: Dylan Vaughn'
|
47
|
+
end
|
48
|
+
|
49
|
+
def test_calling_with_ruby_object_with_int
|
50
|
+
dylan = Person.new('Dylan', 'Vaughn')
|
51
|
+
assert_equal ClosureTemplates.render('examples.simple.helloObjectInt', :object => dylan), "Dylan's age is 35"
|
52
|
+
end
|
53
|
+
|
54
|
+
def test_calling_with_array_of_ruby_objects
|
55
|
+
dylan = Person.new('Dylan', 'Vaughn')
|
56
|
+
dana = Person.new('Dana', 'Vaughn')
|
57
|
+
assert_equal ClosureTemplates.render('examples.simple.helloObjects', :objects => [dylan, dana]), 'First name is: Dylan and last name is: Vaughn and full name is: Dylan Vaughn<br />First name is: Dana and last name is: Vaughn and full name is: Dana Vaughn<br />'
|
58
|
+
end
|
59
|
+
|
60
|
+
def test_data_types
|
61
|
+
data = { :string => 'string', :int => 2, :float => 3.4, :null => nil, :bool_true => true, :bool_false => false }
|
62
|
+
assert_equal ClosureTemplates.render('examples.simple.dataObjects', :data => data), 'String is string, int is 2, float is 3.4, null is null, bool_true is true, bool_false is false'
|
63
|
+
end
|
64
|
+
|
65
|
+
def test_booleans
|
66
|
+
data = { :bool_true => true, :bool_false => false }
|
67
|
+
assert_equal ClosureTemplates.render('examples.simple.booleans', :data => data), 'True is true. False is false.'
|
68
|
+
end
|
69
|
+
|
70
|
+
end
|
metadata
ADDED
@@ -0,0 +1,78 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: closure-templates
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
prerelease:
|
5
|
+
version: 0.1.1
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Dylan Vaughn
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
|
13
|
+
date: 2011-11-11 00:00:00 -08:00
|
14
|
+
default_executable:
|
15
|
+
dependencies:
|
16
|
+
- !ruby/object:Gem::Dependency
|
17
|
+
name: test-unit
|
18
|
+
prerelease: false
|
19
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
20
|
+
none: false
|
21
|
+
requirements:
|
22
|
+
- - ">="
|
23
|
+
- !ruby/object:Gem::Version
|
24
|
+
version: "0"
|
25
|
+
type: :development
|
26
|
+
version_requirements: *id001
|
27
|
+
description: Generates methods / code for server and client-side use of Google Closure Templates with jRuby
|
28
|
+
email:
|
29
|
+
- dylancvaughn@gmail.com
|
30
|
+
executables: []
|
31
|
+
|
32
|
+
extensions: []
|
33
|
+
|
34
|
+
extra_rdoc_files: []
|
35
|
+
|
36
|
+
files:
|
37
|
+
- .gitignore
|
38
|
+
- Gemfile
|
39
|
+
- README.md
|
40
|
+
- Rakefile
|
41
|
+
- closure-templates.gemspec
|
42
|
+
- lib/closure-templates.rb
|
43
|
+
- lib/closure-templates/version.rb
|
44
|
+
- lib/jar/soy-latest.jar
|
45
|
+
- test/person.rb
|
46
|
+
- test/templates/examples.nested.soy
|
47
|
+
- test/templates/examples.simple.soy
|
48
|
+
- test/test_closure-templates.rb
|
49
|
+
has_rdoc: true
|
50
|
+
homepage: https://github.com/dylanvaughn/closure-templates
|
51
|
+
licenses: []
|
52
|
+
|
53
|
+
post_install_message:
|
54
|
+
rdoc_options: []
|
55
|
+
|
56
|
+
require_paths:
|
57
|
+
- lib
|
58
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
59
|
+
none: false
|
60
|
+
requirements:
|
61
|
+
- - ">="
|
62
|
+
- !ruby/object:Gem::Version
|
63
|
+
version: "0"
|
64
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
65
|
+
none: false
|
66
|
+
requirements:
|
67
|
+
- - ">="
|
68
|
+
- !ruby/object:Gem::Version
|
69
|
+
version: "0"
|
70
|
+
requirements: []
|
71
|
+
|
72
|
+
rubyforge_project:
|
73
|
+
rubygems_version: 1.5.0
|
74
|
+
signing_key:
|
75
|
+
specification_version: 3
|
76
|
+
summary: Google Closure Templates for jRuby / Rails
|
77
|
+
test_files: []
|
78
|
+
|