Module: Fifty

Defined in:
lib/fifty.rb

Defined Under Namespace

Modules: Helpers

Constant Summary collapse

VERSION =

Version of the gem.

'0.1.0'
JS_ESCAPE_MAP =

Helper map for JS escaping.

{
  '\\'    => '\\\\',
  '</'    => '<\/',
  "\r\n"  => '\n',
  "\n"    => '\n',
  "\r"    => '\n',
  '"'     => '\\"',
  "'"     => "\\'"
}

Class Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Class Attribute Details

.compiledObject

Boolean - have we compiled?



23
24
25
# File 'lib/fifty.rb', line 23

def compiled
  @compiled
end

.contextObject

Context of the calling script.



25
26
27
# File 'lib/fifty.rb', line 25

def context
  @context
end

.hbsObject

Handlebars runtime.



29
30
31
# File 'lib/fifty.rb', line 29

def hbs
  @hbs
end

.helpersObject

Hash of compiled helpers.



17
18
19
# File 'lib/fifty.rb', line 17

def helpers
  @helpers
end

.loggerObject

Logger for debug info.



27
28
29
# File 'lib/fifty.rb', line 27

def logger
  @logger
end

.partialsObject

Hash of compiled partials



21
22
23
# File 'lib/fifty.rb', line 21

def partials
  @partials
end

.sharedsObject

Hash of compiled shared data.



19
20
21
# File 'lib/fifty.rb', line 19

def shareds
  @shareds
end

.viewsObject

Glob-style paths to templates.



15
16
17
# File 'lib/fifty.rb', line 15

def views
  @views
end

Class Method Details

.compileObject

Compile static and inline HAML templates to HTML/HBS templates.



95
96
97
98
99
# File 'lib/fifty.rb', line 95

def self.compile
  compile_template_files
  compile_inline_templates
  self.compiled = true
end

.compile_inline_template(name, info) ⇒ Object

Compile a single inline template into HTML/HBS.



153
154
155
156
157
158
159
160
161
# File 'lib/fifty.rb', line 153

def self.compile_inline_template(name, info)
  partial = render_raw_haml(info[0].strip)
  partials[name] = escape_hbs(partial)
rescue
  debug "Couldn't compile inline #{name}"
  raise
else
  debug "Compiled inline #{name}"
end

.compile_inline_templatesObject

Iterate over inline templates and compile them.



146
147
148
149
150
# File 'lib/fifty.rb', line 146

def self.compile_inline_templates
  get_inline_templates.each do |name, info|
    compile_inline_template(name, info)
  end
end

.compile_template_file(file) ⇒ Object

Compile a single HAML template file to HTML with HBS.



122
123
124
125
126
127
128
129
130
131
132
133
134
# File 'lib/fifty.rb', line 122

def self.compile_template_file(file)
  contents = File.read(file)
  partial = render_raw_haml(contents)
  name = path_to_name(file)
  template = escape_hbs(partial)
  reg = partial_registerer(name, template)
  partials[name] = reg
rescue
  debug "Couldn't compile #{file}"
  raise
else
  debug "Compiled #{file}"
end

.compile_template_filesObject

Iterate over all template files and compile each one to HBS.



114
115
116
117
118
# File 'lib/fifty.rb', line 114

def self.compile_template_files
  get_template_files.each do |file|
    compile_template_file(file)
  end
end

.debug(text) ⇒ Object

Output a debug text to the logger.



235
236
237
238
239
240
241
# File 'lib/fifty.rb', line 235

def self.debug(text)
  if logger.respond_to? :info
    logger.info "[Fifty]: #{text}"
  else
    logger.puts "[Fifty]: #{text}"
  end
end

.escape_hbs(partial) ⇒ Object

Escape double and triple-mustaches.



291
292
293
294
295
296
297
# File 'lib/fifty.rb', line 291

def self.escape_hbs(partial)
  4.times do
    partial.gsub!("{{", "{%{")
    partial.gsub!("}}", "}%}")
  end
  partial
end

.escape_javascript(javascript) ⇒ Object

Escape text for inclusion into a quoted JS string.



285
286
287
288
# File 'lib/fifty.rb', line 285

def self.escape_javascript(javascript)
  regexp = /(\\|<\/|\r\n|\342\200\250|[\n\r"'])/u
  javascript.gsub(regexp) {|match| JS_ESCAPE_MAP[match] }
end

.eval_js(code) ⇒ Object

Evaluate Javascript code within the context of the JS runtime and return the code as plaintext.



248
249
250
251
252
253
# File 'lib/fifty.rb', line 248

def self.eval_js(code)
  hbs.instance_eval do
    @js.runtime.eval(code)
  end
  code
end

.find_template_file(name) ⇒ Object

Find a template file given it’s name.



201
202
203
204
205
# File 'lib/fifty.rb', line 201

def self.find_template_file(name)
  name = name.to_s.gsub('-', '/')
  file = File.join('views', name) + '.haml'
  return file if File.readable?(file)
end

.get_inline_templatesObject

Get the inline templates defined in Sinatra::Application, excluding templates containing the word “layout”.



139
140
141
142
143
# File 'lib/fifty.rb', line 139

def self.get_inline_templates
  Sinatra::Application.templates
  .map { |p| Dir[p] }.flatten
  .reject { |x| x.to_s.index('layout') }
end

.get_template(name) ⇒ Object

Find a template and get its contents.



189
190
191
192
193
194
195
196
197
198
# File 'lib/fifty.rb', line 189

def self.get_template(name)
  inlines = Sinatra::Application.templates
  if path = find_template_file(name)
    File.read(path)
  elsif partial = inlines[name.intern]
    partial.first
  else
    raise "Couldn't find template for #{name}."
  end
end

.get_template_filesObject

Return a list of the template files, as defined in the list of view paths, excluding the templates containing the word “layout” (which are assumed to be static and part of the page that is always rendered server-side).



107
108
109
110
# File 'lib/fifty.rb', line 107

def self.get_template_files
  views.map { |p| Dir[p] }.flatten
  .select { |x| x.index('layout').nil? }
end

.haml2hbs(name, options = {}) ⇒ Object

Render a HAML template into HTML/HBS.



164
165
166
167
168
169
170
171
172
173
174
# File 'lib/fifty.rb', line 164

def self.haml2hbs(name, options = {})
  result = render_haml(name, layout: false)
  while data = result.match(/{{> ([^}]*)}}/)
    partial = render_haml($1, layout: false)
    result = result.gsub(data[0], partial)
  end
  render_layout(context, result, options)
rescue
  debug "Couldn't render template #{name}"
  raise
end

.hbs2html(html, data) ⇒ Object

Render HBS to HTML.



230
231
232
# File 'lib/fifty.rb', line 230

def self.hbs2html(html, data)
  unescape_hbs(hbs.compile(html).call(@@current = data))
end

.header(type) ⇒ Object

Generate a header of a given type.



76
77
78
79
80
81
82
# File 'lib/fifty.rb', line 76

def self.header(type)
  header, list = '', self.send(type)
  list.each do |name, code|
    header += code + "\n"
  end
  script_tag(type, header)
end

.headersObject

Output the helpers, the partials, and the data inside script tags.



68
69
70
71
72
73
# File 'lib/fifty.rb', line 68

def self.headers
  scripts = ['helpers', 'partials', 'shareds']
  headers = ''
  scripts.each { |type| headers += header(type) }
  headers
end

.helper(name, helper) ⇒ Object

Register a Handlebars helper with a name and a Javascript function.



54
55
56
57
# File 'lib/fifty.rb', line 54

def self.helper(name, helper)
  code = helper_registerer(name, helper)
  helpers[name] = eval_js(code)
end

.helper_registerer(name, fn) ⇒ Object

Generates JS code to register a Handlebars helper.



261
262
263
# File 'lib/fifty.rb', line 261

def self.helper_registerer(name, fn)
  "Handlebars.registerHelper('#{name}', #{fn});"
end

.included(base) ⇒ Object

When Fifty is included into an application, compile each of the helpers.



308
309
310
311
312
# File 'lib/fifty.rb', line 308

def self.included(base)
  helpers =  Fifty::Helpers.constants
  .map { |c| Fifty::Helpers.const_get(c) }
  helpers.each { |h| h.compile }
end

.partial_registerer(name, code) ⇒ Object

Generates JS code to register a Handlebars partial.



266
267
268
269
# File 'lib/fifty.rb', line 266

def self.partial_registerer(name, code)
  "Handlebars.registerPartial('#{name[1..-1]}', " +
  "'#{escape_javascript(code)}');"
end

.path_to_name(path) ⇒ Object

Convert a file path to a template name.



224
225
226
227
# File 'lib/fifty.rb', line 224

def self.path_to_name(path)
  trimmed = path.gsub('./', '').gsub('.haml', '')
  '_' + trimmed.split('/')[1..-1].join('-')
end

.render_haml(name, options = {}, &block) ⇒ Object

Get the contents of a HAML template by name and render the template to HTML with nested HBS.



209
210
211
212
213
214
215
# File 'lib/fifty.rb', line 209

def self.render_haml(name, options = {}, &block)
  Haml::Engine.new(get_template(name), options)
    .render(context, options[:locals] || {}, &block)
rescue
  debug "Couldn't render template #{name}"
  raise
end

.render_layout(context, insert, options) ⇒ Object

If the :layout option is set to true, render the supplied template inside the layout.



178
179
180
181
182
183
184
185
186
# File 'lib/fifty.rb', line 178

def self.render_layout(context, insert, options)
  return insert unless options[:layout]
  layout = if options[:layout].is_a?(Symbol)
    options[:layout]
  else
    options[:layout] ? :layout : false
  end
  render_haml(layout, options) { insert }
end

.render_raw_haml(haml) ⇒ Object

Render actual HAML content.



218
219
220
221
# File 'lib/fifty.rb', line 218

def self.render_raw_haml(haml)
  args = [ context, layout: false ]
  Haml::Engine.new(haml).render(*args)
end

.script_tag(id, code, type = 'text/javascript') ⇒ Object

Generates a Javascript script tag with inline code.



256
257
258
# File 'lib/fifty.rb', line 256

def self.script_tag(id, code, type = 'text/javascript')
  "\n<script id='#{id}' type='#{type}'>#{code}</script>\n"
end

.shared(name, data) ⇒ Object

Make a piece of Ruby data available to the client as a JSON object.



61
62
63
64
# File 'lib/fifty.rb', line 61

def self.shared(name, data)
  code = "var #{name} = #{data};\n"
  shareds[name] = eval_js(code)
end

.unescape_hbs(html) ⇒ Object

Unescape double and triple-mustaches.



300
301
302
# File 'lib/fifty.rb', line 300

def self.unescape_hbs(html)
  html.gsub("{%{", "{{").gsub("}%}", "}}")
end

Instance Method Details

#fifty(view, data, globals = {}) ⇒ Object Also known as: fy

Main API for rendering templates with Fifty.



85
86
87
88
# File 'lib/fifty.rb', line 85

def fifty(view, data, globals = {})
  Fifty.compile unless Fifty.compiled
  Fifty.hbs2html(Fifty.haml2hbs(view, globals), data)
end