eyeballs 0.3.2

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.
Files changed (47) hide show
  1. data/CHANGELOG +15 -0
  2. data/README.md +293 -0
  3. data/Rakefile +45 -0
  4. data/bin/eyeballs +9 -0
  5. data/config.ru +2 -0
  6. data/dist/jquery-1.4.2.min.js +154 -0
  7. data/dist/jquery.livequery.js +226 -0
  8. data/dist/mustache.js +324 -0
  9. data/eyeballs.gemspec +86 -0
  10. data/eyeballs.js.gemspec +83 -0
  11. data/lib/eyeballs.rb +11 -0
  12. data/lib/eyeballs/app_detector.rb +29 -0
  13. data/lib/eyeballs/app_generator.rb +30 -0
  14. data/lib/eyeballs/cli.rb +14 -0
  15. data/lib/eyeballs/controller_generator.rb +26 -0
  16. data/lib/eyeballs/model_generator.rb +26 -0
  17. data/lib/eyeballs/scaffold_generator.rb +66 -0
  18. data/spec/app_generator_spec.rb +51 -0
  19. data/spec/controller_generator_spec.rb +22 -0
  20. data/spec/model_generator_spec.rb +23 -0
  21. data/spec/rack_app_detector_spec.rb +25 -0
  22. data/spec/scaffold_generator_spec.rb +42 -0
  23. data/spec/spec_helper.rb +42 -0
  24. data/src/jquery.o_O.couchdb.js +107 -0
  25. data/src/jquery.o_O.dom.js +37 -0
  26. data/src/jquery.o_O.js +120 -0
  27. data/src/jquery.o_O.rails.js +68 -0
  28. data/src/o_O.js +286 -0
  29. data/src/o_O.localstorage.js +60 -0
  30. data/templates/app_root/index.html +26 -0
  31. data/templates/controller.js +3 -0
  32. data/templates/model.js +3 -0
  33. data/templates/scaffold_controller.js +75 -0
  34. data/templates/scaffold_edit.html.mustache +13 -0
  35. data/templates/scaffold_index.html +47 -0
  36. data/templates/scaffold_partial.html.mustache +12 -0
  37. data/test/unit/qunit.css +119 -0
  38. data/test/unit/qunit.js +1069 -0
  39. data/test/unit/test_controller.html +137 -0
  40. data/test/unit/test_dom.html +81 -0
  41. data/test/unit/test_dom_with_callbacks.html +121 -0
  42. data/test/unit/test_form.html +42 -0
  43. data/test/unit/test_localstorage.html +79 -0
  44. data/test/unit/test_model.html +136 -0
  45. data/test/unit/test_model_with_callbacks.html +118 -0
  46. data/test/unit/test_rails.html +97 -0
  47. metadata +117 -0
@@ -0,0 +1,226 @@
1
+ /*! Copyright (c) 2010 Brandon Aaron (http://brandonaaron.net)
2
+ * Dual licensed under the MIT (MIT_LICENSE.txt)
3
+ * and GPL Version 2 (GPL_LICENSE.txt) licenses.
4
+ *
5
+ * Version: 1.1.1
6
+ * Requires jQuery 1.3+
7
+ * Docs: http://docs.jquery.com/Plugins/livequery
8
+ */
9
+
10
+ (function($) {
11
+
12
+ $.extend($.fn, {
13
+ livequery: function(type, fn, fn2) {
14
+ var self = this, q;
15
+
16
+ // Handle different call patterns
17
+ if ($.isFunction(type))
18
+ fn2 = fn, fn = type, type = undefined;
19
+
20
+ // See if Live Query already exists
21
+ $.each( $.livequery.queries, function(i, query) {
22
+ if ( self.selector == query.selector && self.context == query.context &&
23
+ type == query.type && (!fn || fn.$lqguid == query.fn.$lqguid) && (!fn2 || fn2.$lqguid == query.fn2.$lqguid) )
24
+ // Found the query, exit the each loop
25
+ return (q = query) && false;
26
+ });
27
+
28
+ // Create new Live Query if it wasn't found
29
+ q = q || new $.livequery(this.selector, this.context, type, fn, fn2);
30
+
31
+ // Make sure it is running
32
+ q.stopped = false;
33
+
34
+ // Run it immediately for the first time
35
+ q.run();
36
+
37
+ // Contnue the chain
38
+ return this;
39
+ },
40
+
41
+ expire: function(type, fn, fn2) {
42
+ var self = this;
43
+
44
+ // Handle different call patterns
45
+ if ($.isFunction(type))
46
+ fn2 = fn, fn = type, type = undefined;
47
+
48
+ // Find the Live Query based on arguments and stop it
49
+ $.each( $.livequery.queries, function(i, query) {
50
+ if ( self.selector == query.selector && self.context == query.context &&
51
+ (!type || type == query.type) && (!fn || fn.$lqguid == query.fn.$lqguid) && (!fn2 || fn2.$lqguid == query.fn2.$lqguid) && !this.stopped )
52
+ $.livequery.stop(query.id);
53
+ });
54
+
55
+ // Continue the chain
56
+ return this;
57
+ }
58
+ });
59
+
60
+ $.livequery = function(selector, context, type, fn, fn2) {
61
+ this.selector = selector;
62
+ this.context = context;
63
+ this.type = type;
64
+ this.fn = fn;
65
+ this.fn2 = fn2;
66
+ this.elements = [];
67
+ this.stopped = false;
68
+
69
+ // The id is the index of the Live Query in $.livequery.queries
70
+ this.id = $.livequery.queries.push(this)-1;
71
+
72
+ // Mark the functions for matching later on
73
+ fn.$lqguid = fn.$lqguid || $.livequery.guid++;
74
+ if (fn2) fn2.$lqguid = fn2.$lqguid || $.livequery.guid++;
75
+
76
+ // Return the Live Query
77
+ return this;
78
+ };
79
+
80
+ $.livequery.prototype = {
81
+ stop: function() {
82
+ var query = this;
83
+
84
+ if ( this.type )
85
+ // Unbind all bound events
86
+ this.elements.unbind(this.type, this.fn);
87
+ else if (this.fn2)
88
+ // Call the second function for all matched elements
89
+ this.elements.each(function(i, el) {
90
+ query.fn2.apply(el);
91
+ });
92
+
93
+ // Clear out matched elements
94
+ this.elements = [];
95
+
96
+ // Stop the Live Query from running until restarted
97
+ this.stopped = true;
98
+ },
99
+
100
+ run: function() {
101
+ // Short-circuit if stopped
102
+ if ( this.stopped ) return;
103
+ var query = this;
104
+
105
+ var oEls = this.elements,
106
+ els = $(this.selector, this.context),
107
+ nEls = els.not(oEls);
108
+
109
+ // Set elements to the latest set of matched elements
110
+ this.elements = els;
111
+
112
+ if (this.type) {
113
+ // Bind events to newly matched elements
114
+ nEls.bind(this.type, this.fn);
115
+
116
+ // Unbind events to elements no longer matched
117
+ if (oEls.length > 0)
118
+ $.each(oEls, function(i, el) {
119
+ if ( $.inArray(el, els) < 0 )
120
+ $.event.remove(el, query.type, query.fn);
121
+ });
122
+ }
123
+ else {
124
+ // Call the first function for newly matched elements
125
+ nEls.each(function() {
126
+ query.fn.apply(this);
127
+ });
128
+
129
+ // Call the second function for elements no longer matched
130
+ if ( this.fn2 && oEls.length > 0 )
131
+ $.each(oEls, function(i, el) {
132
+ if ( $.inArray(el, els) < 0 )
133
+ query.fn2.apply(el);
134
+ });
135
+ }
136
+ }
137
+ };
138
+
139
+ $.extend($.livequery, {
140
+ guid: 0,
141
+ queries: [],
142
+ queue: [],
143
+ running: false,
144
+ timeout: null,
145
+
146
+ checkQueue: function() {
147
+ if ( $.livequery.running && $.livequery.queue.length ) {
148
+ var length = $.livequery.queue.length;
149
+ // Run each Live Query currently in the queue
150
+ while ( length-- )
151
+ $.livequery.queries[ $.livequery.queue.shift() ].run();
152
+ }
153
+ },
154
+
155
+ pause: function() {
156
+ // Don't run anymore Live Queries until restarted
157
+ $.livequery.running = false;
158
+ },
159
+
160
+ play: function() {
161
+ // Restart Live Queries
162
+ $.livequery.running = true;
163
+ // Request a run of the Live Queries
164
+ $.livequery.run();
165
+ },
166
+
167
+ registerPlugin: function() {
168
+ $.each( arguments, function(i,n) {
169
+ // Short-circuit if the method doesn't exist
170
+ if (!$.fn[n]) return;
171
+
172
+ // Save a reference to the original method
173
+ var old = $.fn[n];
174
+
175
+ // Create a new method
176
+ $.fn[n] = function() {
177
+ // Call the original method
178
+ var r = old.apply(this, arguments);
179
+
180
+ // Request a run of the Live Queries
181
+ $.livequery.run();
182
+
183
+ // Return the original methods result
184
+ return r;
185
+ }
186
+ });
187
+ },
188
+
189
+ run: function(id) {
190
+ if (id != undefined) {
191
+ // Put the particular Live Query in the queue if it doesn't already exist
192
+ if ( $.inArray(id, $.livequery.queue) < 0 )
193
+ $.livequery.queue.push( id );
194
+ }
195
+ else
196
+ // Put each Live Query in the queue if it doesn't already exist
197
+ $.each( $.livequery.queries, function(id) {
198
+ if ( $.inArray(id, $.livequery.queue) < 0 )
199
+ $.livequery.queue.push( id );
200
+ });
201
+
202
+ // Clear timeout if it already exists
203
+ if ($.livequery.timeout) clearTimeout($.livequery.timeout);
204
+ // Create a timeout to check the queue and actually run the Live Queries
205
+ $.livequery.timeout = setTimeout($.livequery.checkQueue, 20);
206
+ },
207
+
208
+ stop: function(id) {
209
+ if (id != undefined)
210
+ // Stop are particular Live Query
211
+ $.livequery.queries[ id ].stop();
212
+ else
213
+ // Stop all Live Queries
214
+ $.each( $.livequery.queries, function(id) {
215
+ $.livequery.queries[ id ].stop();
216
+ });
217
+ }
218
+ });
219
+
220
+ // Register core DOM manipulation methods
221
+ $.livequery.registerPlugin('append', 'prepend', 'after', 'before', 'wrap', 'attr', 'removeAttr', 'addClass', 'removeClass', 'toggleClass', 'empty', 'remove', 'html');
222
+
223
+ // Run Live Queries when the Document is ready
224
+ $(function() { $.livequery.play(); });
225
+
226
+ })(jQuery);
data/dist/mustache.js ADDED
@@ -0,0 +1,324 @@
1
+ /*
2
+ mustache.js — Logic-less templates in JavaScript
3
+
4
+ See http://mustache.github.com/ for more info.
5
+ */
6
+
7
+ var Mustache = function() {
8
+ var Renderer = function() {};
9
+
10
+ Renderer.prototype = {
11
+ otag: "{{",
12
+ ctag: "}}",
13
+ pragmas: {},
14
+ buffer: [],
15
+ pragmas_implemented: {
16
+ "IMPLICIT-ITERATOR": true
17
+ },
18
+
19
+ render: function(template, context, partials, in_recursion) {
20
+ // reset buffer
21
+ // TODO: make this non-lazy
22
+ if(!in_recursion)
23
+ this.buffer = [];
24
+ // fail fast
25
+ if(!this.includes("", template)) {
26
+ if(in_recursion) {
27
+ return template;
28
+ } else {
29
+ this.send(template);
30
+ return;
31
+ }
32
+ }
33
+
34
+ if(!in_recursion) {
35
+ this.buffer = [];
36
+ }
37
+
38
+ template = this.render_pragmas(template);
39
+ var html = this.render_section(template, context, partials);
40
+ if(in_recursion) {
41
+ return this.render_tags(html, context, partials, in_recursion);
42
+ }
43
+
44
+ this.render_tags(html, context, partials, in_recursion);
45
+ },
46
+
47
+ /*
48
+ Sends parsed lines
49
+ */
50
+ send: function(line) {
51
+ if(line != "") {
52
+ this.buffer.push(line);
53
+ }
54
+ },
55
+
56
+ /*
57
+ Looks for %PRAGMAS
58
+ */
59
+ render_pragmas: function(template) {
60
+ // no pragmas
61
+ if(!this.includes("%", template)) {
62
+ return template;
63
+ }
64
+
65
+ var that = this;
66
+ var regex = new RegExp(this.otag + "%([\\w_-]+) ?([\\w]+=[\\w]+)?"
67
+ + this.ctag);
68
+ return template.replace(regex, function(match, pragma, options) {
69
+ if(!that.pragmas_implemented[pragma]) {
70
+ throw({message: "This implementation of mustache doesn't understand the '"
71
+ + pragma + "' pragma"});
72
+ }
73
+ that.pragmas[pragma] = {};
74
+ if(options) {
75
+ var opts = options.split("=");
76
+ that.pragmas[pragma][opts[0]] = opts[1];
77
+ }
78
+ return "";
79
+ // ignore unknown pragmas silently
80
+ });
81
+ },
82
+
83
+ /*
84
+ Tries to find a partial in the global scope and render it
85
+ */
86
+ render_partial: function(name, context, partials) {
87
+ if(!partials || !partials[name]) {
88
+ throw({message: "unknown_partial '" + name + "'"});
89
+ }
90
+ if(typeof(context[name]) != "object") {
91
+ return partials[name];
92
+ }
93
+ return this.render(partials[name], context[name], partials, true);
94
+ },
95
+
96
+ /*
97
+ Renders inverted (^) and normal (#) sections
98
+ */
99
+ render_section: function(template, context, partials) {
100
+ if(!this.includes("#", template) && !this.includes("^", template)) {
101
+ return template;
102
+ }
103
+
104
+ var that = this;
105
+ // CSW - Added "+?" so it finds the tighest bound, not the widest
106
+ var regex = new RegExp(this.otag + "(\\^|\\#)(.+)" + this.ctag +
107
+ "\\s*([\\s\\S]+?)" + this.otag + "\\/\\2" + this.ctag +
108
+ "\\s*", "mg");
109
+
110
+ // for each {{#foo}}{{/foo}} section do...
111
+ return template.replace(regex, function(match, type, name, content) {
112
+ var value = that.find(name, context);
113
+ if(type == "^") { // inverted section
114
+ if(!value || that.is_array(value) && value.length == 0) {
115
+ // false or empty list, render it
116
+ return that.render(content, context, partials, true);
117
+ } else {
118
+ return "";
119
+ }
120
+ } else if(type == "#") { // normal section
121
+ if(that.is_array(value)) { // Enumerable, Let's loop!
122
+ return that.map(value, function(row) {
123
+ return that.render(content, that.merge(context,
124
+ that.create_context(row)), partials, true);
125
+ }).join("");
126
+ } else if(that.is_object(value)) { // Object, Use it as subcontext!
127
+ return that.render(content,
128
+ that.merge(context, that.create_context(value)), partials, true);
129
+ } else if(typeof value === "function") {
130
+ // higher order section
131
+ return value.call(context, content, function(text) {
132
+ return that.render(text, context, partials, true);
133
+ })
134
+ } else if(value) { // boolean section
135
+ return that.render(content, context, partials, true);
136
+ } else {
137
+ return "";
138
+ }
139
+ }
140
+ });
141
+ },
142
+
143
+ /*
144
+ Replace {{foo}} and friends with values from our view
145
+ */
146
+ render_tags: function(template, context, partials, in_recursion) {
147
+ // tit for tat
148
+ var that = this;
149
+
150
+ var new_regex = function() {
151
+ return new RegExp(that.otag + "(=|!|>|\\{|%)?([^\/#\^]+?)\\1?" +
152
+ that.ctag + "+", "g");
153
+ };
154
+
155
+ var regex = new_regex();
156
+ var lines = template.split("\n");
157
+ for (var i=0; i < lines.length; i++) {
158
+ lines[i] = lines[i].replace(regex, function(match, operator, name) {
159
+ switch(operator) {
160
+ case "!": // ignore comments
161
+ return "";
162
+ case "=": // set new delimiters, rebuild the replace regexp
163
+ that.set_delimiters(name);
164
+ regex = new_regex();
165
+ return "";
166
+ case ">": // render partial
167
+ return that.render_partial(name, context, partials);
168
+ case "{": // the triple mustache is unescaped
169
+ return that.find(name, context);
170
+ default: // escape the value
171
+ return that.escape(that.find(name, context));
172
+ }
173
+ }, this);
174
+ if(!in_recursion) {
175
+ this.send(lines[i]);
176
+ }
177
+ }
178
+
179
+ if(in_recursion) {
180
+ return lines.join("\n");
181
+ }
182
+ },
183
+
184
+ set_delimiters: function(delimiters) {
185
+ var dels = delimiters.split(" ");
186
+ this.otag = this.escape_regex(dels[0]);
187
+ this.ctag = this.escape_regex(dels[1]);
188
+ },
189
+
190
+ escape_regex: function(text) {
191
+ // thank you Simon Willison
192
+ if(!arguments.callee.sRE) {
193
+ var specials = [
194
+ '/', '.', '*', '+', '?', '|',
195
+ '(', ')', '[', ']', '{', '}', '\\'
196
+ ];
197
+ arguments.callee.sRE = new RegExp(
198
+ '(\\' + specials.join('|\\') + ')', 'g'
199
+ );
200
+ }
201
+ return text.replace(arguments.callee.sRE, '\\$1');
202
+ },
203
+
204
+ /*
205
+ find `name` in current `context`. That is find me a value
206
+ from the view object
207
+ */
208
+ find: function(name, context) {
209
+ name = this.trim(name);
210
+ if(typeof context[name] === "function") {
211
+ return context[name].apply(context);
212
+ }
213
+ if(context[name] !== undefined) {
214
+ return context[name];
215
+ }
216
+ // silently ignore unkown variables
217
+ return "";
218
+ },
219
+
220
+ // Utility methods
221
+
222
+ /* includes tag */
223
+ includes: function(needle, haystack) {
224
+ return haystack.indexOf(this.otag + needle) != -1;
225
+ },
226
+
227
+ /*
228
+ Does away with nasty characters
229
+ */
230
+ escape: function(s) {
231
+ return ((s == null) ? "" : s).toString().replace(/&(?!\w+;)|["<>\\]/g, function(s) {
232
+ switch(s) {
233
+ case "&": return "&amp;";
234
+ case "\\": return "\\\\";;
235
+ case '"': return '\"';;
236
+ case "<": return "&lt;";
237
+ case ">": return "&gt;";
238
+ default: return s;
239
+ }
240
+ });
241
+ },
242
+
243
+ /*
244
+ Merges all properties of object `b` into object `a`.
245
+ `b.property` overwrites a.property`
246
+ */
247
+ merge: function(a, b) {
248
+ var _new = {};
249
+ for(var name in a) {
250
+ if(a.hasOwnProperty(name)) {
251
+ _new[name] = a[name];
252
+ }
253
+ };
254
+ for(var name in b) {
255
+ if(b.hasOwnProperty(name)) {
256
+ _new[name] = b[name];
257
+ }
258
+ };
259
+ return _new;
260
+ },
261
+
262
+ // by @langalex, support for arrays of strings
263
+ create_context: function(_context) {
264
+ if(this.is_object(_context)) {
265
+ return _context;
266
+ } else if(this.pragmas["IMPLICIT-ITERATOR"]) {
267
+ var iterator = this.pragmas["IMPLICIT-ITERATOR"].iterator || ".";
268
+ var ctx = {};
269
+ ctx[iterator] = _context;
270
+ return ctx;
271
+ }
272
+ },
273
+
274
+ is_object: function(a) {
275
+ return a && typeof a == "object";
276
+ },
277
+
278
+ is_array: function(a) {
279
+ return Object.prototype.toString.call(a) === '[object Array]';
280
+ },
281
+
282
+ /*
283
+ Gets rid of leading and trailing whitespace
284
+ */
285
+ trim: function(s) {
286
+ return s.replace(/^\s*|\s*$/g, "");
287
+ },
288
+
289
+ /*
290
+ Why, why, why? Because IE. Cry, cry cry.
291
+ */
292
+ map: function(array, fn) {
293
+ if (typeof array.map == "function") {
294
+ return array.map(fn);
295
+ } else {
296
+ var r = [];
297
+ var l = array.length;
298
+ for(var i=0;i<l;i++) {
299
+ r.push(fn(array[i]));
300
+ }
301
+ return r;
302
+ }
303
+ }
304
+ };
305
+
306
+ return({
307
+ name: "mustache.js",
308
+ version: "0.3.0-dev",
309
+
310
+ /*
311
+ Turns a template and view into HTML
312
+ */
313
+ to_html: function(template, view, partials, send_fun) {
314
+ var renderer = new Renderer();
315
+ if(send_fun) {
316
+ renderer.send = send_fun;
317
+ }
318
+ renderer.render(template, view, partials);
319
+ if(!send_fun) {
320
+ return renderer.buffer.join("\n");
321
+ }
322
+ }
323
+ });
324
+ }();