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 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