fifty 0.0.3 → 0.0.5
Sign up to get free protection for your applications and to get access to all the features.
- data/README.md +2 -2
- data/lib/fifty/helpers/locales.rb +36 -0
- data/lib/fifty/helpers.rb +6 -97
- data/lib/fifty.rb +217 -122
- metadata +3 -2
data/README.md
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
|
1
|
+
![Logo](https://raw.github.com/louismullie/fifty/master/logo.png)
|
2
2
|
|
3
3
|
Have you ever written the same template or helper twice? Do you have trouble sleeping at night because of code duplication? Then Fifty may right for you. It allows you to save keystrokes by leveraging:
|
4
4
|
|
@@ -183,4 +183,4 @@ js_data :client_data, $data.to_json
|
|
183
183
|
hbs_helper :add, %t{
|
184
184
|
function (a, b) { return a + b; }
|
185
185
|
}
|
186
|
-
```
|
186
|
+
```
|
@@ -0,0 +1,36 @@
|
|
1
|
+
module Fifty::Helpers::Locales
|
2
|
+
|
3
|
+
require 'i18n'
|
4
|
+
require 'yaml'
|
5
|
+
|
6
|
+
Fifty.class_eval do
|
7
|
+
class << self
|
8
|
+
attr_accessor :locales
|
9
|
+
end
|
10
|
+
self.locales = ['./config/locales/*.yml']
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.compile
|
14
|
+
locales = Fifty.locales.map { |p| Dir[p] }.flatten
|
15
|
+
hash = {}
|
16
|
+
locales.each do |file|
|
17
|
+
name = File.basename(file, '.yml')
|
18
|
+
yaml = YAML.load(File.read(file))
|
19
|
+
hash.merge!(yaml)
|
20
|
+
end
|
21
|
+
|
22
|
+
Fifty.helper :t, "function(path) {
|
23
|
+
var current = locales[locale];
|
24
|
+
var elements = path.split('.');
|
25
|
+
while (elements.length > 0 ) {
|
26
|
+
current = current[elements.shift()];
|
27
|
+
}
|
28
|
+
return current;
|
29
|
+
}"
|
30
|
+
|
31
|
+
Fifty.shared :locale, "'#{I18n.locale.to_s}'"
|
32
|
+
Fifty.shared :locales, hash.to_json
|
33
|
+
|
34
|
+
end
|
35
|
+
|
36
|
+
end
|
data/lib/fifty/helpers.rb
CHANGED
@@ -1,99 +1,8 @@
|
|
1
1
|
module Fifty::Helpers
|
2
|
-
|
3
|
-
module Locales
|
4
|
-
|
5
|
-
require 'i18n'
|
6
|
-
require 'yaml'
|
7
|
-
require 'json'
|
8
|
-
|
9
|
-
Fifty.helper :t, "function(path) {
|
10
|
-
var current = locales[locale];
|
11
|
-
var elements = path.split('.');
|
12
|
-
while (elements.length > 0 ) {
|
13
|
-
current = current[elements.shift()];
|
14
|
-
}
|
15
|
-
return current;
|
16
|
-
}"
|
17
|
-
|
18
|
-
Fifty.class_eval do
|
19
|
-
class << self
|
20
|
-
attr_accessor :locales
|
21
|
-
end
|
22
|
-
self.locales = ['./config/locales/*.yml']
|
23
|
-
end
|
24
|
-
|
25
|
-
def self.compile
|
26
|
-
locales = Fifty.locales.map { |p| Dir[p] }.flatten
|
27
|
-
hash = {}
|
28
|
-
locales.each do |file|
|
29
|
-
name = File.basename(file, '.yml')
|
30
|
-
yaml = YAML.load(File.read(file))
|
31
|
-
hash.merge!(yaml)
|
32
|
-
end
|
33
|
-
Fifty.shared :locale, "'#{I18n.locale.to_s}'"
|
34
|
-
Fifty.shared :locales, hash.to_json
|
35
|
-
end
|
36
|
-
|
37
|
-
end
|
38
|
-
|
39
|
-
module Render
|
40
|
-
|
41
|
-
Fifty.shared :Fifty, "{
|
42
|
-
|
43
|
-
render: function(template, data) {
|
44
|
-
var hbs = $('#_' + template).html();
|
45
|
-
var tmpl = Handlebars.compile(hbs);
|
46
|
-
return tmpl(data);
|
47
|
-
},
|
48
|
-
|
49
|
-
getAndRender: function(name, params) {
|
50
|
-
$.getJSON('/' + name + '.json',
|
51
|
-
function (data) {
|
52
|
-
Fifty.render(name, data);
|
53
|
-
}, $.param(params));
|
54
|
-
},
|
55
|
-
|
56
|
-
postAndRender: function(name, params) {
|
57
|
-
$.post('/' + name + '.json', $.param(params),
|
58
|
-
function (data) {
|
59
|
-
Fifty.render(name, data);
|
60
|
-
});
|
61
|
-
},
|
62
|
-
|
63
|
-
replace: function(id, name, data) {
|
64
|
-
$(id).html(Fifty.render(name, data));
|
65
|
-
},
|
66
|
-
|
67
|
-
getAndReplace: function(id, name, params) {
|
68
|
-
$(id).html(Fifty.getAndRender(name, params));
|
69
|
-
},
|
70
|
-
|
71
|
-
postAndReplace: function(id, name, params) {
|
72
|
-
$(id).html(Fifty.postAndRender(name, params));
|
73
|
-
},
|
74
|
-
|
75
|
-
append: function(id, name, data) {
|
76
|
-
$(id).append(Fifty.render(name, data));
|
77
|
-
},
|
78
|
-
|
79
|
-
getAndAppend: function(id, name, params) {
|
80
|
-
$(id).append(Fifty.getAndRender(name, params));
|
81
|
-
},
|
82
|
-
|
83
|
-
postAndAppend: function(id, name, params) {
|
84
|
-
$(id).append(Fifty.postAndRender(name, params));
|
85
|
-
},
|
86
|
-
|
87
|
-
postAndRemove: function(id, name, params) {
|
88
|
-
$.post('/' + name + '.json', $.param(param),
|
89
|
-
function () {
|
90
|
-
$(id).remove();
|
91
|
-
});
|
92
|
-
}
|
93
|
-
|
94
|
-
}"
|
95
|
-
|
96
|
-
end
|
97
2
|
|
98
|
-
|
99
|
-
|
3
|
+
path = File.expand_path(__FILE__)
|
4
|
+
base = File.dirname(path)
|
5
|
+
dir = File.join(base, 'helpers', '*')
|
6
|
+
Dir[dir].each { |file| require file }
|
7
|
+
|
8
|
+
end
|
data/lib/fifty.rb
CHANGED
@@ -1,207 +1,274 @@
|
|
1
1
|
module Fifty
|
2
2
|
|
3
|
+
# Require dependencies.
|
3
4
|
require 'handlebars'
|
4
5
|
require 'haml'
|
6
|
+
require 'json'
|
5
7
|
|
6
|
-
|
8
|
+
# Version of the gem.
|
9
|
+
VERSION = '0.0.5'
|
10
|
+
|
11
|
+
# Class-level variables.
|
7
12
|
|
8
13
|
class << self
|
14
|
+
# Glob-style paths to templates.
|
9
15
|
attr_accessor :views
|
16
|
+
# Hash of compiled helpers.
|
17
|
+
attr_accessor :helpers
|
18
|
+
# Hash of compiled shared data.
|
19
|
+
attr_accessor :shareds
|
20
|
+
# Hash of compiled partials
|
21
|
+
attr_accessor :partials
|
22
|
+
# Boolean - have we compiled?
|
10
23
|
attr_accessor :compiled
|
24
|
+
# Context of the calling script.
|
11
25
|
attr_accessor :context
|
26
|
+
# Logger for debug info.
|
27
|
+
attr_accessor :logger
|
28
|
+
# Handlebars runtime.
|
29
|
+
attr_accessor :hbs
|
12
30
|
end
|
13
31
|
|
32
|
+
# Default values for class variables.
|
14
33
|
self.views = ['./views/**/*.haml']
|
15
|
-
self.compiled = false
|
16
34
|
|
17
|
-
|
35
|
+
self.helpers = {}
|
36
|
+
self.shareds = {}
|
37
|
+
self.partials = {}
|
18
38
|
|
19
|
-
|
20
|
-
|
21
|
-
end
|
39
|
+
self.compiled = false
|
40
|
+
self.logger = $stdout
|
22
41
|
|
23
|
-
|
24
|
-
|
42
|
+
# Initialize the Handlebars runtime.
|
43
|
+
self.hbs = Handlebars::Context.new
|
25
44
|
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
45
|
+
# Handle missing Handlebars partials.
|
46
|
+
# We don't register partials server-side.
|
47
|
+
self.hbs.partial_missing do |name|
|
48
|
+
hbs = Fifty.render_haml(name)
|
49
|
+
Fifty.hbs2html(hbs, @@current)
|
30
50
|
end
|
31
51
|
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
52
|
+
# Register a Handlebars helper with
|
53
|
+
# a name and a Javascript function.
|
54
|
+
def self.helper(name, helper)
|
55
|
+
code = helper_registerer(name, helper)
|
56
|
+
helpers[name] = eval_js(code)
|
36
57
|
end
|
37
58
|
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
59
|
+
# Make a piece of Ruby data available
|
60
|
+
# to the client as a JSON object.
|
61
|
+
def self.shared(name, data)
|
62
|
+
code = "var #{name} = #{data};\n"
|
63
|
+
shareds[name] = eval_js(code)
|
42
64
|
end
|
43
65
|
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
path = File.dirname(file)
|
52
|
-
|
53
|
-
name = '_' + file.gsub('./', '')
|
54
|
-
.gsub('.haml', '')
|
55
|
-
.split('/')[1..-1].join('-')
|
56
|
-
|
57
|
-
unless @@view_paths.include?(path)
|
58
|
-
puts path.inspect
|
59
|
-
@@view_paths << path
|
60
|
-
end
|
61
|
-
template = File.read(file)
|
62
|
-
|
63
|
-
partial = Haml::Engine.
|
64
|
-
new(template).render(context, layout: false)
|
65
|
-
partial = self.escape_hbs(partial)
|
66
|
-
@@partials[name] = partial
|
67
|
-
puts "Registered #{name}"
|
68
|
-
|
66
|
+
# Output the helpers, the partials,
|
67
|
+
# and the data inside script tags.
|
68
|
+
def self.headers
|
69
|
+
scripts = ['helpers', 'partials', 'shareds']
|
70
|
+
headers = ''
|
71
|
+
scripts.each do |type|
|
72
|
+
headers += header(type)
|
69
73
|
end
|
74
|
+
headers
|
70
75
|
end
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
new(info[0].strip).render(context)
|
78
|
-
partial = self.escape_hbs(partial)
|
79
|
-
@@partials[name] = partial
|
76
|
+
|
77
|
+
# Generate a header of a given type.
|
78
|
+
def self.header(type)
|
79
|
+
code, list = '', self.send(type)
|
80
|
+
list.each do |name, code|
|
81
|
+
code += code + "\n"
|
80
82
|
end
|
83
|
+
script_tag(type, code)
|
81
84
|
end
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
+
|
86
|
+
# Main API for rendering templates with Fifty.
|
87
|
+
def fifty(view, data, globals = {})
|
88
|
+
Fifty.compile unless Fifty.compiled
|
89
|
+
Fifty.hbs2html(Fifty.haml2hbs(view, globals), data)
|
85
90
|
end
|
86
91
|
|
87
|
-
|
88
|
-
|
89
|
-
@js.runtime.eval(code)
|
90
|
-
end
|
91
|
-
end
|
92
|
+
# Alias for Fifty.
|
93
|
+
alias :fy :fifty
|
92
94
|
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
95
|
+
# Compile static and inline HAML
|
96
|
+
# templates to HTML/HBS templates.
|
97
|
+
def self.compile
|
98
|
+
compile_template_files
|
99
|
+
compile_inline_templates
|
100
|
+
self.compiled = true
|
99
101
|
end
|
100
102
|
|
101
|
-
|
102
|
-
|
103
|
-
|
103
|
+
# Return a list of the template files,
|
104
|
+
# as defined in the list of view paths,
|
105
|
+
# excluding the templates containing
|
106
|
+
# the word "layout" (which are assumed
|
107
|
+
# to be static and part of the page
|
108
|
+
# that is always rendered server-side).
|
109
|
+
def self.get_template_files
|
110
|
+
views.map { |p| Dir[p] }.flatten
|
111
|
+
.select { |x| x.index('layout').nil? }
|
104
112
|
end
|
105
113
|
|
106
|
-
|
107
|
-
|
108
|
-
|
114
|
+
# Iterate over all template files
|
115
|
+
# and compile each one to HBS.
|
116
|
+
def self.compile_template_files
|
117
|
+
get_template_files.each do |file|
|
118
|
+
compile_template_file(file)
|
119
|
+
end
|
109
120
|
end
|
110
121
|
|
111
|
-
|
112
|
-
|
113
|
-
def self.
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
122
|
+
# Compile a single HAML template
|
123
|
+
# file to HTML with HBS.
|
124
|
+
def self.compile_template_file(file)
|
125
|
+
contents = File.read(file)
|
126
|
+
partial = render_raw_haml(contents)
|
127
|
+
name = path_to_name(file)
|
128
|
+
template = escape_hbs(partial)
|
129
|
+
reg = partial_registerer(name, template)
|
130
|
+
partials[name] = reg
|
131
|
+
rescue
|
132
|
+
debug "Couldn't compile #{file}"
|
133
|
+
raise
|
134
|
+
else
|
135
|
+
debug "Compiled #{file}"
|
121
136
|
end
|
122
137
|
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
138
|
+
# Get the inline templates defined
|
139
|
+
# in Sinatra::Application, excluding
|
140
|
+
# templates containing the word "layout".
|
141
|
+
def self.get_inline_templates
|
142
|
+
Sinatra::Application.templates
|
143
|
+
.map { |p| Dir[p] }.flatten
|
144
|
+
.reject { |x| x.to_s.index('layout') }
|
129
145
|
end
|
130
146
|
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
shared_js += code + "\n"
|
147
|
+
# Iterate over inline templates and compile them.
|
148
|
+
def self.compile_inline_templates
|
149
|
+
get_inline_templates.each do |name, info|
|
150
|
+
compile_inline_template(name, info)
|
136
151
|
end
|
137
|
-
script_tag('shared', shared_js)
|
138
|
-
|
139
152
|
end
|
140
153
|
|
141
|
-
|
142
|
-
|
154
|
+
# Compile a single inline template into HTML/HBS.
|
155
|
+
def self.compile_inline_template(name, info)
|
156
|
+
partial = render_raw_haml(info[0].strip)
|
157
|
+
partials[name] = escape_hbs(partial)
|
158
|
+
rescue
|
159
|
+
debug "Couldn't compile inline #{name}"
|
160
|
+
raise
|
161
|
+
else
|
162
|
+
debug "Compiled inline #{name}"
|
143
163
|
end
|
144
164
|
|
145
|
-
|
146
|
-
|
147
|
-
private
|
148
|
-
|
165
|
+
# Render a HAML template into HTML/HBS.
|
149
166
|
def self.haml2hbs(name, options = {})
|
150
|
-
|
151
|
-
result = render_haml(name, context, layout: false)
|
167
|
+
result = render_haml(name, layout: false)
|
152
168
|
while data = result.match(/{{> ([^}]*)}}/)
|
153
|
-
partial = render_haml($1,
|
169
|
+
partial = render_haml($1, layout: false)
|
154
170
|
result = result.gsub(data[0], partial)
|
155
171
|
end
|
156
172
|
render_layout(context, result, options)
|
173
|
+
rescue
|
174
|
+
debug "Couldn't render template #{name}"
|
175
|
+
raise
|
157
176
|
end
|
158
177
|
|
178
|
+
# If the :layout option is set to true,
|
179
|
+
# render the supplied template inside the layout.
|
159
180
|
def self.render_layout(context, insert, options)
|
160
|
-
|
161
|
-
|
181
|
+
return insert unless options[:layout]
|
182
|
+
layout = if options[:layout].is_a?(Symbol)
|
183
|
+
options[:layout]
|
184
|
+
else
|
185
|
+
options[:layout] ? true : false
|
186
|
+
end
|
187
|
+
render_haml(layout, options) { insert }
|
162
188
|
end
|
163
189
|
|
190
|
+
# Find a template and get its contents.
|
164
191
|
def self.get_template(name)
|
165
192
|
inlines = Sinatra::Application.templates
|
166
|
-
if path =
|
193
|
+
if path = find_template_file(name)
|
167
194
|
File.read(path)
|
168
195
|
elsif partial = inlines[name.intern]
|
169
196
|
partial.first
|
170
197
|
else
|
171
|
-
raise "
|
198
|
+
raise "Couldn't find template for #{name}."
|
172
199
|
end
|
173
200
|
end
|
174
201
|
|
175
|
-
|
176
|
-
|
202
|
+
# Find a template file given it's name.
|
203
|
+
def self.find_template_file(name)
|
204
|
+
name = name.to_s.gsub('-', '/')
|
205
|
+
file = File.join('views', name) + '.haml'
|
177
206
|
return file if File.readable?(file)
|
178
207
|
end
|
179
208
|
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
209
|
+
# Get the contents of a HAML template by name and
|
210
|
+
# render the template to HTML with nested HBS.
|
211
|
+
def self.render_haml(name, options = {}, &block)
|
212
|
+
args = [get_template(name), options]
|
213
|
+
Haml::Engine.new(*args).render(context, &block)
|
214
|
+
rescue
|
215
|
+
debug "Couldn't render template #{name}"
|
216
|
+
raise
|
184
217
|
end
|
185
218
|
|
219
|
+
# Render actual HAML content.
|
220
|
+
def self.render_raw_haml(haml)
|
221
|
+
args = [ context, layout: false ]
|
222
|
+
Haml::Engine.new(haml).render(*args)
|
223
|
+
end
|
224
|
+
|
225
|
+
# Convert a file path to a template name.
|
226
|
+
def self.path_to_name(path)
|
227
|
+
trimmed = path.gsub('./', '').gsub('.haml', '')
|
228
|
+
'_' + trimmed.split('/')[1..-1].join('-')
|
229
|
+
end
|
230
|
+
|
231
|
+
# Render HBS to HTML.
|
186
232
|
def self.hbs2html(html, data)
|
187
|
-
@@current = data
|
188
|
-
puts "-> with partials #{data}"
|
189
|
-
unescape_hbs(@@handlebars.compile(html).call(data))
|
233
|
+
unescape_hbs(hbs.compile(html).call(@@current = data))
|
190
234
|
end
|
191
235
|
|
192
|
-
|
193
|
-
|
236
|
+
# Output a debug text to the logger.
|
237
|
+
def self.debug(text)
|
238
|
+
logger.puts "[Fifty]: #{text}"
|
239
|
+
end
|
240
|
+
|
241
|
+
# * Javascript-generating methods * #
|
242
|
+
|
243
|
+
# Evaluate Javascript code within
|
244
|
+
# the context of the JS runtime
|
245
|
+
# and return the code as plaintext.
|
246
|
+
def self.eval_js(code)
|
247
|
+
hbs.instance_eval do
|
248
|
+
@js.runtime.eval(code)
|
249
|
+
end
|
250
|
+
code
|
194
251
|
end
|
195
252
|
|
253
|
+
# Generates a Javascript script tag with inline code.
|
196
254
|
def self.script_tag(id, code, type = 'text/javascript')
|
197
255
|
"\n<script id='#{id}' type='#{type}'>#{code}</script>\n"
|
198
256
|
end
|
199
257
|
|
200
|
-
|
258
|
+
# Generates JS code to register a Handlebars helper.
|
259
|
+
def self.helper_registerer(name, fn)
|
260
|
+
"Handlebars.registerHelper('#{name}', #{fn});"
|
261
|
+
end
|
262
|
+
|
263
|
+
# Generates JS code to register a Handlebars partial.
|
264
|
+
def self.partial_registerer(name, code)
|
201
265
|
"Handlebars.registerPartial('#{name[1..-1]}', " +
|
202
266
|
"'#{escape_javascript(code)}');"
|
203
267
|
end
|
204
268
|
|
269
|
+
# * Helpers for escaping JS/HBS * #
|
270
|
+
|
271
|
+
# Helper map for JS escaping.
|
205
272
|
JS_ESCAPE_MAP = {
|
206
273
|
'\\' => '\\\\',
|
207
274
|
'</' => '<\/',
|
@@ -212,9 +279,37 @@ module Fifty
|
|
212
279
|
"'" => "\\'"
|
213
280
|
}
|
214
281
|
|
282
|
+
# Escape text for inclusion into a quoted JS string.
|
215
283
|
def self.escape_javascript(javascript)
|
216
284
|
regexp = /(\\|<\/|\r\n|\342\200\250|[\n\r"'])/u
|
217
285
|
javascript.gsub(regexp) {|match| JS_ESCAPE_MAP[match] }
|
218
286
|
end
|
219
287
|
|
288
|
+
# Escape double and triple-mustaches.
|
289
|
+
def self.escape_hbs(partial)
|
290
|
+
2.times do
|
291
|
+
partial.gsub!("{{", "{%{")
|
292
|
+
partial.gsub!("}}", "}%}")
|
293
|
+
end
|
294
|
+
partial
|
295
|
+
end
|
296
|
+
|
297
|
+
# Unescape double and triple-mustaches.
|
298
|
+
def self.unescape_hbs(html)
|
299
|
+
html.gsub("{%{", "{{").gsub("}%}", "}}")
|
300
|
+
end
|
301
|
+
|
302
|
+
# * Helper-related methods * #
|
303
|
+
|
304
|
+
# When Fifty is included into an application,
|
305
|
+
# compile each of the helpers.
|
306
|
+
def self.included(base)
|
307
|
+
helpers = Fifty::Helpers.constants
|
308
|
+
.map { |c| Fifty::Helpers.const_get(c) }
|
309
|
+
helpers.each { |h| h.compile }
|
310
|
+
end
|
311
|
+
|
312
|
+
# Require all helpers into Fifty.
|
313
|
+
require_relative 'fifty/helpers'
|
314
|
+
|
220
315
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: fifty
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.5
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2013-03-
|
12
|
+
date: 2013-03-27 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: json
|
@@ -83,6 +83,7 @@ extensions: []
|
|
83
83
|
extra_rdoc_files: []
|
84
84
|
files:
|
85
85
|
- README.md
|
86
|
+
- lib/fifty/helpers/locales.rb
|
86
87
|
- lib/fifty/helpers.rb
|
87
88
|
- lib/fifty.rb
|
88
89
|
homepage: https://github.com/louismullie/fifty
|