Module: Fifty

Defined in:
lib/fifty.rb

Defined Under Namespace

Modules: Helpers

Constant Summary collapse

VERSION =

Version of the gem.

'0.1.4'
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 HTML 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

.updatedObject

Time last updated at.



31
32
33
# File 'lib/fifty.rb', line 31

def updated
  @updated
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.



99
100
101
102
103
# File 'lib/fifty.rb', line 99

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.



174
175
176
177
178
179
180
181
182
# File 'lib/fifty.rb', line 174

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.



167
168
169
170
171
# File 'lib/fifty.rb', line 167

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.



141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
# File 'lib/fifty.rb', line 141

def self.compile_template_file(file)
  contents = File.read(file)
  partial = render_raw_haml(contents)
  name = path_to_name(file)
  path = './.hbs/' + name.to_s + '.handlebars'
  File.open(path, 'w+') { |f| f.write(partial) }
  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.



118
119
120
121
122
123
124
# File 'lib/fifty.rb', line 118

def self.compile_template_files
  FileUtils.mkdir_p('./.hbs')
  get_template_files.each do |file|
    updated[file] = File.mtime(file)
    compile_template_file(file)
  end
end

.debug(text) ⇒ Object

Output a debug text to the logger.



256
257
258
259
260
261
262
# File 'lib/fifty.rb', line 256

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.



312
313
314
315
316
317
318
# File 'lib/fifty.rb', line 312

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

.escape_javascript(javascript) ⇒ Object

Escape text for inclusion into a quoted JS string.



306
307
308
309
# File 'lib/fifty.rb', line 306

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.



269
270
271
272
273
274
# File 'lib/fifty.rb', line 269

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.



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

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



160
161
162
163
164
# File 'lib/fifty.rb', line 160

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

.get_template(name) ⇒ Object

Find a template and get its contents.



210
211
212
213
214
215
216
217
218
219
# File 'lib/fifty.rb', line 210

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



111
112
113
114
# File 'lib/fifty.rb', line 111

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.



185
186
187
188
189
190
191
192
193
194
195
# File 'lib/fifty.rb', line 185

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.



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

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

.header(type) ⇒ Object

Generate a header of a given type.



80
81
82
83
84
85
86
# File 'lib/fifty.rb', line 80

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.



72
73
74
75
76
77
# File 'lib/fifty.rb', line 72

def self.headers
  scripts = ['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.



58
59
60
61
# File 'lib/fifty.rb', line 58

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.



282
283
284
# File 'lib/fifty.rb', line 282

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.



333
334
335
336
337
# File 'lib/fifty.rb', line 333

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.



287
288
289
290
# File 'lib/fifty.rb', line 287

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.



245
246
247
248
# File 'lib/fifty.rb', line 245

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

.recompile_templatesObject



126
127
128
129
130
131
132
133
134
135
136
137
# File 'lib/fifty.rb', line 126

def self.recompile_templates
  FileUtils.mkdir_p('./.hbs')
  recompiled = false
  get_template_files.each do |file|
    if File.mtime(file) != updated[file]
      compile_template_file(file)
      updated[file] = File.mtime(file)
      recompiled = true
    end
  end
  yield if recompiled
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.



230
231
232
233
234
235
236
# File 'lib/fifty.rb', line 230

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.



199
200
201
202
203
204
205
206
207
# File 'lib/fifty.rb', line 199

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.



239
240
241
242
# File 'lib/fifty.rb', line 239

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.



277
278
279
# File 'lib/fifty.rb', line 277

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.



65
66
67
68
# File 'lib/fifty.rb', line 65

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.



321
322
323
324
325
326
327
# File 'lib/fifty.rb', line 321

def self.unescape_hbs(html)
  2.times do
    html.gsub!("{%{", "{{")
    html.gsub!("}%}", "}}")
  end
  html
end

Instance Method Details

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

Main API for rendering templates with Fifty.



89
90
91
92
# File 'lib/fifty.rb', line 89

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