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 CHANGED
@@ -1,4 +1,4 @@
1
- ## Fifty
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
- end
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
- VERSION = '0.0.3'
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
- @@handlebars = Handlebars::Context.new
35
+ self.helpers = {}
36
+ self.shareds = {}
37
+ self.partials = {}
18
38
 
19
- @@handlebars.partial_missing do |a|
20
- Fifty.hbs2html(Fifty.render_haml(a, context), @@current)
21
- end
39
+ self.compiled = false
40
+ self.logger = $stdout
22
41
 
23
- @@helpers, @@shared, @@partials = {}, {}, {}
24
- @@view_paths = []
42
+ # Initialize the Handlebars runtime.
43
+ self.hbs = Handlebars::Context.new
25
44
 
26
- def self.helper(name, fn)
27
- code = self.register_helper(name, fn)
28
- @@helpers[name] = code
29
- self.eval_js(code)
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
- def self.shared(name, code)
33
- code = "var #{name} = #{code};\n"
34
- @@shared[name] = code
35
- self.eval_js(code)
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
- def self.compile
39
- self.compile_statics
40
- self.compile_inlines
41
- self.compiled = true
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
- def self.compile_statics
45
- views = self.views.map { |p| Dir[p] }.flatten
46
- context = binding.of_caller(3)
47
- views.each do |file|
48
-
49
- puts "Trying to register #{file}"
50
- next if file.index('layout')
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
- def self.compile_inlines
73
- inlines = Sinatra::Application.templates
74
- inlines.each do |name, info|
75
- next if name == :layout
76
- partial = Haml::Engine.
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
- def self.register_helper(name, fn)
84
- "Handlebars.registerHelper('#{name}', #{fn});"
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
- def self.eval_js(code)
88
- @@handlebars.instance_eval do
89
- @js.runtime.eval(code)
90
- end
91
- end
92
+ # Alias for Fifty.
93
+ alias :fy :fifty
92
94
 
93
- def self.escape_hbs(partial)
94
- 2.times do
95
- partial.gsub!("{{", "{%{")
96
- partial.gsub!("}}", "}%}")
97
- end
98
- partial
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
- def self.included(base)
102
- base.class_eval {
103
- Fifty::Helpers::Locales.compile }
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
- def fifty(view, data, globals = {})
107
- Fifty.compile unless Fifty.compiled
108
- Fifty.hbs2html(Fifty.haml2hbs(view, globals), data)
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
- alias :fu :fifty
112
-
113
- def self.core_js
114
-
115
- core_js = ''
116
- @@helpers.each do |name, code|
117
- core_js += code + "\n"
118
- end
119
- script_tag('helpers', core_js)
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
- def self.partials_js
124
- partials_js = ''
125
- @@partials.each do |name, code|
126
- partials_js += register_partial(name, code) + "\n"
127
- end
128
- script_tag('partials', partials_js)
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
- def self.shared_js
132
-
133
- shared_js = "\n"
134
- @@shared.each do |name, code|
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
- def self.helpers
142
- [core_js, partials_js, shared_js].join
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
- require_relative 'fifty/helpers'
146
-
147
- private
148
-
165
+ # Render a HAML template into HTML/HBS.
149
166
  def self.haml2hbs(name, options = {})
150
- puts "Rendering #{name} from HAML to HBS"
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, context, layout: false)
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
- !options[:layout] ? insert :
161
- render_haml(:layout, context) { insert }
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 = find_haml_template(name)
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 "No template for #{name}."
198
+ raise "Couldn't find template for #{name}."
172
199
  end
173
200
  end
174
201
 
175
- def self.find_haml_template(name)
176
- file = './views/' + name.to_s.gsub('-', '/') + '.haml'
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
- def self.render_haml(name, context = nil, options = {}, &block)
181
- puts "Rendering HAML template #{name}"
182
- Haml::Engine.new(get_template(name),
183
- options).render(context, &block)
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
- def self.unescape_hbs(html)
193
- html.gsub("{%{", "{{").gsub("}%}", "}}")
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
- def self.register_partial(name, code)
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.3
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-18 00:00:00.000000000 Z
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