closure-templates 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,7 @@
1
+ *.gem
2
+ .bundle
3
+ Gemfile.lock
4
+ pkg/*
5
+ .rvmrc
6
+ build.sh
7
+ test/js/*.js
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source "http://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in closure-templates.gemspec
4
+ gemspec
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,2 @@
1
+ require 'bundler'
2
+ Bundler::GemHelper.install_tasks
@@ -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
@@ -0,0 +1,5 @@
1
+ module Closure
2
+ module Templates
3
+ VERSION = "0.1.1"
4
+ end
5
+ 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
+