rb-tmpl 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,6 @@
1
+ *.gem
2
+ .bundle
3
+ Gemfile.lock
4
+ pkg/*
5
+ *.swp
6
+ *.swo
data/Gemfile ADDED
@@ -0,0 +1,10 @@
1
+ source 'http://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in rb-tmpl.gemspec
4
+ gemspec
5
+
6
+ group :development do
7
+ gem 'rspec'
8
+ gem 'fakefs'
9
+ end
10
+
data/Rakefile ADDED
@@ -0,0 +1,5 @@
1
+ require 'bundler/gem_tasks'
2
+ require 'rspec/core/rake_task'
3
+
4
+ desc 'Run specs'
5
+ RSpec::Core::RakeTask.new(:spec)
@@ -0,0 +1,3 @@
1
+ module RbTmpl
2
+ VERSION = "0.1.0"
3
+ end
data/lib/rb-tmpl.rb ADDED
@@ -0,0 +1,101 @@
1
+ require 'cgi'
2
+
3
+ module RbTmpl
4
+ class Tmpl
5
+ attr_reader :template_search_paths
6
+ attr_reader :template_extensions
7
+
8
+ def initialize
9
+ @template_search_paths = []
10
+ @template_extensions = []
11
+
12
+ @templates = {}
13
+ @compiled_templates = {}
14
+ end
15
+
16
+ def add(name, template)
17
+ name = name.to_s
18
+ @templates[name] = template
19
+ @compiled_templates.delete(name)
20
+ end
21
+
22
+ def call(name, data = nil)
23
+ name = name.to_s
24
+ code = compiled(name)
25
+ return '' if code.nil?
26
+ data = HashWrapper.new(data) if data.kind_of?(Hash)
27
+ bind = data.instance_eval{ binding } unless data.nil?
28
+ eval(code, bind)
29
+ end
30
+
31
+ protected
32
+ class HashWrapper
33
+ def initialize(hash)
34
+ @hash = hash
35
+ end
36
+
37
+ def method_missing(method, *arguments, &block)
38
+ @hash[method] || @hash[method.to_s]
39
+ end
40
+
41
+ def respond_to(method, include_private)
42
+ super || @hash.key?(method) || @hash.key?(method.to_s)
43
+ end
44
+ end
45
+
46
+ def find_file(name)
47
+ @template_search_paths.map do |path|
48
+ file = File.join(path, name)
49
+ ext_files = @template_extensions.map do |ext|
50
+ File.join(path, name + '.' + ext)
51
+ end
52
+ [file] + ext_files
53
+ end.
54
+ flatten.
55
+ find{ |file| File.exist?(file) }
56
+ end
57
+
58
+ def find_by_file(name)
59
+ file = find_file(name)
60
+ file.nil? ? nil : File.read(file)
61
+ end
62
+
63
+ def find(name)
64
+ return @templates[name] ||= find_by_file(name)
65
+ end
66
+
67
+ def compiled(name)
68
+ @compiled_templates[name] ||= compile(find(name))
69
+ end
70
+
71
+ def compile(template)
72
+ template = template.
73
+ gsub(/\\/, '\\\\').
74
+ gsub(/'/, '\\\\\'').
75
+ gsub(/\$\{([^\}]*)\}/, '{{= \1}}')
76
+ code = "__='"
77
+ code << template.gsub(/\{\{[^\}]*\}\}/) do |full_tag|
78
+ tag_contents = full_tag[2...-2].strip
79
+ _, closing_slash, tag, args = tag_contents.match(/^(\/?)([^\s]+)(.*)/).to_a
80
+ if args
81
+ args = args.
82
+ strip.
83
+ gsub('$index', '__i').
84
+ gsub('$value', '__val')
85
+ end
86
+ is_closing = closing_slash == '/'
87
+ tag_code = case
88
+ when is_closing then 'end'
89
+ when tag == '=' then "__<<CGI.escapeHTML((#{args}).to_s)"
90
+ when tag == 'html' then "__<<(#{args}).to_s"
91
+ when tag == 'if' then 'if ' + args
92
+ when tag == 'else' then 'else'
93
+ when tag == 'each' then '(' + args + ').each_with_index do|__val,__i|'
94
+ end
95
+ "';#{tag_code};__<<'"
96
+ end
97
+ code << "';__"
98
+ code
99
+ end
100
+ end
101
+ end
data/rb-tmpl.gemspec ADDED
@@ -0,0 +1,20 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+ require "rb-tmpl/version"
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = "rb-tmpl"
7
+ s.version = RbTmpl::VERSION
8
+ s.authors = ["Sijmen J. Mulder"]
9
+ s.email = ["sjmulder@gmail.com"]
10
+ s.homepage = "https://github.com/sjmulder/rb-tmpl"
11
+ s.summary = %q{A Ruby implementation of jquery-tmpl}
12
+ s.description = %q{With rb-tmpl you can re-use jquery-tmpl templates on the server, saving you from having to do the same work twice.}
13
+
14
+ s.rubyforge_project = "rb-tmpl"
15
+
16
+ s.files = `git ls-files`.split("\n")
17
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
18
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
19
+ s.require_paths = ["lib"]
20
+ end
@@ -0,0 +1,4 @@
1
+ require 'fakefs'
2
+ require 'lib/rb-tmpl.rb'
3
+
4
+ include RbTmpl
data/spec/tmpl_spec.rb ADDED
@@ -0,0 +1,183 @@
1
+ require 'spec/spec_helper.rb'
2
+
3
+ describe Tmpl do
4
+ it 'loads templates from the add method' do
5
+ tmpl = Tmpl.new
6
+ tmpl.add(:test, 'Foo')
7
+ tmpl.call(:test) == 'Foo'
8
+ end
9
+
10
+ it 'accepts strings and symbols' do
11
+ tmpl = Tmpl.new
12
+ tmpl.add(:foo, 'Foo')
13
+ tmpl.add('bar', 'Bar')
14
+ tmpl.call(:foo).should == 'Foo'
15
+ tmpl.call(:bar).should == 'Bar'
16
+ tmpl.call('foo').should == 'Foo'
17
+ tmpl.call('bar').should == 'Bar'
18
+ end
19
+
20
+ it 'escapes single quotes' do
21
+ tmpl = Tmpl.new
22
+ tmpl.add(:test, '\'Hello!\'')
23
+ tmpl.call(:test).should == '\'Hello!\''
24
+ end
25
+
26
+ it 'escapes escape sequences' do
27
+ tmpl = Tmpl.new
28
+ tmpl.add(:test, 'Dear mum,\n')
29
+ tmpl.call(:test).should == 'Dear mum,\n'
30
+ end
31
+
32
+ it 'loads templates from disk' do
33
+ tmpl = Tmpl.new
34
+ tmpl.template_search_paths << 'templates'
35
+ tmpl.template_search_paths << 'templates/sub/'
36
+ FileUtils.mkdir('templates')
37
+ FileUtils.mkdir('templates/sub')
38
+ File.open('templates/foo', 'w'){ |f| f.write('Foo') }
39
+ File.open('templates/sub/bar', 'w'){ |f| f.write('Bar') }
40
+ tmpl.call(:foo).should == 'Foo'
41
+ tmpl.call(:bar).should == 'Bar'
42
+ end
43
+
44
+ it 'supports extensions for template files' do
45
+ tmpl = Tmpl.new
46
+ tmpl.template_search_paths << '.'
47
+ tmpl.template_extensions << 'tmpl'
48
+ File.open('foo.tmpl', 'w'){ |f| f.write('Foo') }
49
+ tmpl.call(:foo).should == 'Foo'
50
+ end
51
+
52
+ it 'gives added templates precedence over disk templates' do
53
+ tmpl = Tmpl.new
54
+ tmpl.add(:foo, 'Memory')
55
+ tmpl.template_search_paths << '.'
56
+ File.open('foo', 'w'){ |f| f.write('Disk') }
57
+ tmpl.call(:foo).should == 'Memory'
58
+ end
59
+
60
+ it 'gives files without extensions precence over those with' do
61
+ tmpl = Tmpl.new
62
+ tmpl.template_search_paths << '.'
63
+ tmpl.template_extensions << 'tmpl'
64
+ File.open('foo', 'w'){ |f| f.write('Without') }
65
+ File.open('foo.tmpl', 'w'){ |f| f.write('With') }
66
+ tmpl.call(:foo).should == 'Without'
67
+ end
68
+
69
+ it 'searches template paths and extensions in order' do
70
+ tmpl = Tmpl.new
71
+ tmpl.template_search_paths << 'dir1' << 'dir2'
72
+ tmpl.template_extensions << 'ext1' << 'ext2'
73
+ FileUtils.mkdir('dir1')
74
+ FileUtils.mkdir('dir2')
75
+ File.open('dir1/foo.ext1', 'w'){ |f| f.write('1') }
76
+ File.open('dir1/foo.ext2', 'w'){ |f| f.write('2') }
77
+ File.open('dir2/foo', 'w'){ |f| f.write('3') }
78
+ File.open('dir2/foo.ext1', 'w'){ |f| f.write('4') }
79
+ tmpl.call(:foo).should == '1'
80
+ end
81
+
82
+ it 'supports {{=}} with data from an object' do
83
+ class BarData
84
+ def bar; 'Bar'; end
85
+ end
86
+ tmpl = Tmpl.new
87
+ tmpl.add(:foo, '{{= bar}}')
88
+ tmpl.call(:foo, BarData.new).should == 'Bar'
89
+ end
90
+
91
+ it 'supports {{=}} with data from a hash' do
92
+ tmpl = Tmpl.new
93
+ tmpl.add(:foo, '{{= bar}}')
94
+ tmpl.call(:foo, { :bar => 'Bar' }).should == 'Bar'
95
+ end
96
+
97
+ it 'escapes HTML in {{=}}' do
98
+ tmpl = Tmpl.new
99
+ tmpl.add(:foo, '{{= evil}}')
100
+ tmpl.call(:foo, { :evil => '<script>' }).should == '&lt;script&gt;'
101
+ end
102
+
103
+ it 'supports ${} shorthand' do
104
+ tmpl = Tmpl.new
105
+ tmpl.add(:foo, '${bar}')
106
+ tmpl.call(:foo, { :bar => 'Bar' }).should == 'Bar'
107
+ end
108
+
109
+ it 'supports {{html}}' do
110
+ tmpl = Tmpl.new
111
+ tmpl.add(:foo, '{{html code}}')
112
+ tmpl.call(:foo, { :code => '<script>' }).should == '<script>'
113
+ end
114
+
115
+ it 'supports {{if}}' do
116
+ template =
117
+ '{{if t}}t{{/if}}' +
118
+ '{{if f}}f{{/if}}'
119
+ tmpl = Tmpl.new
120
+ tmpl.add(:foo, template)
121
+ tmpl.call(:foo, { :t => true, :f => false }).should == 't'
122
+ end
123
+
124
+ it 'supports {{else}}' do
125
+ tmpl = Tmpl.new
126
+ tmpl.add(:foo, '{{if f}}t{{else}}f{{/if}}')
127
+ tmpl.call(:foo, { :f => false }).should == 'f'
128
+ end
129
+
130
+ it 'supports {{each}}' do
131
+ tmpl = Tmpl.new
132
+ tmpl.add(:foo, '{{each list}}${$index}${$value}{{/each}}')
133
+ data = { :list => [ 'a', 'b' ] }
134
+ tmpl.call(:foo, data).should == '0a1b'
135
+ end
136
+
137
+ it 'supports {{each(i,x)}}' do
138
+ tmpl = Tmpl.new
139
+ tmpl.add(:foo, '{{each(i,x) list}}${i}${x}{{/each}}')
140
+ data = { :list => [ 'a', 'b' ] }
141
+ tmpl.call(:foo, data).should == '0a1b'
142
+ end
143
+
144
+ it 'supports {{tmpl}}' do
145
+ tmpl = Tmp.new
146
+ tmpl.add(:foo, '{{tmpl bar}}')
147
+ tmpl.add(:bar, '?{x}')
148
+ tmpl.call(:foo, {:x => 'Yay'}).should == 'Yay'
149
+ end
150
+
151
+ it 'supports {{tmpl(data)}}' do
152
+ tmpl = Tmpl.new
153
+ tmpl.add(:foo, '${var},{{tmpl(sub) bar}}')
154
+ tmpl.add(:bar, '${var}')
155
+ data = {:var => 'outer', :sub => {:var => 'inner'}}
156
+ tmpl.call(:foo, data).should == 'outer,inner'
157
+ end
158
+
159
+ it 'supports basic expressions' do
160
+ tmpl = Tmpl.new
161
+ tmpl.add(:eq_t, '{{if 1 == 1}}y{{/if}}')
162
+ tmpl.add(:eq_f, '{{if 1 == 2}}n{{/if}}')
163
+ tmpl.add(:lt_f, '{{if 1 < 1}}n{{/if}}')
164
+ tmpl.add(:lt_t, '{{if 1 < 2}}y{{/if}}')
165
+ tmpl.add(:eq_var_t, '{{if var == 1}}y{{/if}}')
166
+ tmpl.add(:eq_var_f, '{{if var == 2}}n{{/if}}')
167
+ tmpl.call(:eq_t).should == 'y'
168
+ tmpl.call(:eq_f).should == ''
169
+ tmpl.call(:lt_f).should == ''
170
+ tmpl.call(:lt_t).should == 'y'
171
+ tmpl.call(:eq_var_t, {:var => 1}).should == 'y'
172
+ tmpl.call(:eq_var_f, {:var => 1}).should == 'n'
173
+ end
174
+
175
+ it 'supports method calls' do
176
+ tmpl = Tmpl.new
177
+ tmpl.add(:foo, '?{list.length}')
178
+ tmpl.call(:foo, {:list => [1, 2]}).call == '2'
179
+ end
180
+
181
+ it 'substitutes common JavaScript expressions'
182
+ it 'substitutes common JavaScript methods'
183
+ end
metadata ADDED
@@ -0,0 +1,74 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: rb-tmpl
3
+ version: !ruby/object:Gem::Version
4
+ hash: 27
5
+ prerelease:
6
+ segments:
7
+ - 0
8
+ - 1
9
+ - 0
10
+ version: 0.1.0
11
+ platform: ruby
12
+ authors:
13
+ - Sijmen J. Mulder
14
+ autorequire:
15
+ bindir: bin
16
+ cert_chain: []
17
+
18
+ date: 2011-07-26 00:00:00 Z
19
+ dependencies: []
20
+
21
+ description: With rb-tmpl you can re-use jquery-tmpl templates on the server, saving you from having to do the same work twice.
22
+ email:
23
+ - sjmulder@gmail.com
24
+ executables: []
25
+
26
+ extensions: []
27
+
28
+ extra_rdoc_files: []
29
+
30
+ files:
31
+ - .gitignore
32
+ - Gemfile
33
+ - Rakefile
34
+ - lib/rb-tmpl.rb
35
+ - lib/rb-tmpl/version.rb
36
+ - rb-tmpl.gemspec
37
+ - spec/spec_helper.rb
38
+ - spec/tmpl_spec.rb
39
+ homepage: https://github.com/sjmulder/rb-tmpl
40
+ licenses: []
41
+
42
+ post_install_message:
43
+ rdoc_options: []
44
+
45
+ require_paths:
46
+ - lib
47
+ required_ruby_version: !ruby/object:Gem::Requirement
48
+ none: false
49
+ requirements:
50
+ - - ">="
51
+ - !ruby/object:Gem::Version
52
+ hash: 3
53
+ segments:
54
+ - 0
55
+ version: "0"
56
+ required_rubygems_version: !ruby/object:Gem::Requirement
57
+ none: false
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ hash: 3
62
+ segments:
63
+ - 0
64
+ version: "0"
65
+ requirements: []
66
+
67
+ rubyforge_project: rb-tmpl
68
+ rubygems_version: 1.8.5
69
+ signing_key:
70
+ specification_version: 3
71
+ summary: A Ruby implementation of jquery-tmpl
72
+ test_files:
73
+ - spec/spec_helper.rb
74
+ - spec/tmpl_spec.rb