rb-tmpl 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.gitignore +6 -0
- data/Gemfile +10 -0
- data/Rakefile +5 -0
- data/lib/rb-tmpl/version.rb +3 -0
- data/lib/rb-tmpl.rb +101 -0
- data/rb-tmpl.gemspec +20 -0
- data/spec/spec_helper.rb +4 -0
- data/spec/tmpl_spec.rb +183 -0
- metadata +74 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
data/Rakefile
ADDED
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
|
data/spec/spec_helper.rb
ADDED
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 == '<script>'
|
|
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
|