pinkman 0.9.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (46) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +10 -0
  3. data/.rspec +2 -0
  4. data/.travis.yml +5 -0
  5. data/Gemfile +4 -0
  6. data/LICENSE.txt +21 -0
  7. data/README.md +41 -0
  8. data/Rakefile +28 -0
  9. data/app/assets/javascripts/pinkman.js +10 -0
  10. data/app/assets/javascripts/pinkman_base/ajax.coffee +90 -0
  11. data/app/assets/javascripts/pinkman_base/collection.coffee +387 -0
  12. data/app/assets/javascripts/pinkman_base/common.coffee +56 -0
  13. data/app/assets/javascripts/pinkman_base/controller.coffee +164 -0
  14. data/app/assets/javascripts/pinkman_base/glue.coffee +15 -0
  15. data/app/assets/javascripts/pinkman_base/handlebars.js +4608 -0
  16. data/app/assets/javascripts/pinkman_base/hogan.js +576 -0
  17. data/app/assets/javascripts/pinkman_base/markup.js +483 -0
  18. data/app/assets/javascripts/pinkman_base/mixins.coffee +37 -0
  19. data/app/assets/javascripts/pinkman_base/object.js.coffee.erb +195 -0
  20. data/app/assets/javascripts/pinkman_base/pinkman.coffee +131 -0
  21. data/app/assets/javascripts/pinkman_base/render.coffee.erb +80 -0
  22. data/app/assets/javascripts/pinkman_base/tools.coffee +4 -0
  23. data/app/helpers/pinkman_helper.rb +48 -0
  24. data/bin/console +14 -0
  25. data/bin/setup +8 -0
  26. data/lib/generators/pinkman/USAGE +16 -0
  27. data/lib/generators/pinkman/api_generator.rb +60 -0
  28. data/lib/generators/pinkman/install_generator.rb +25 -0
  29. data/lib/generators/pinkman/model_generator.rb +41 -0
  30. data/lib/generators/pinkman/resource_generator.rb +15 -0
  31. data/lib/generators/pinkman/serializer_generator.rb +33 -0
  32. data/lib/generators/pinkman/templates/api.rb.erb +87 -0
  33. data/lib/generators/pinkman/templates/api_controller.rb +2 -0
  34. data/lib/generators/pinkman/templates/collection.js.erb +5 -0
  35. data/lib/generators/pinkman/templates/object.js.erb +3 -0
  36. data/lib/generators/pinkman/templates/serializer.rb.erb +15 -0
  37. data/lib/pinkman.rb +57 -0
  38. data/lib/pinkman/serializer.rb +6 -0
  39. data/lib/pinkman/serializer/base.rb +42 -0
  40. data/lib/pinkman/serializer/scope.rb +48 -0
  41. data/lib/pinkman/version.rb +3 -0
  42. data/pinkman.gemspec +46 -0
  43. data/public/javascripts/pinkman.min.js +1 -0
  44. data/public/jquery.pinkman.min.js +0 -0
  45. data/public/pinkman.min.js +29 -0
  46. metadata +242 -0
@@ -0,0 +1,576 @@
1
+ /*
2
+ * Copyright 2011 Twitter, Inc.
3
+ * Licensed under the Apache License, Version 2.0 (the "License");
4
+ * you may not use this file except in compliance with the License.
5
+ * You may obtain a copy of the License at
6
+ *
7
+ * http://www.apache.org/licenses/LICENSE-2.0
8
+ *
9
+ * Unless required by applicable law or agreed to in writing, software
10
+ * distributed under the License is distributed on an "AS IS" BASIS,
11
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ * See the License for the specific language governing permissions and
13
+ * limitations under the License.
14
+ */
15
+
16
+
17
+
18
+ var Hogan = {};
19
+
20
+ (function (Hogan, useArrayBuffer) {
21
+ Hogan.Template = function (renderFunc, text, compiler, options) {
22
+ this.r = renderFunc || this.r;
23
+ this.c = compiler;
24
+ this.options = options;
25
+ this.text = text || '';
26
+ this.buf = (useArrayBuffer) ? [] : '';
27
+ }
28
+
29
+ Hogan.Template.prototype = {
30
+ // render: replaced by generated code.
31
+ r: function (context, partials, indent) { return ''; },
32
+
33
+ // variable escaping
34
+ v: hoganEscape,
35
+
36
+ // triple stache
37
+ t: coerceToString,
38
+
39
+ render: function render(context, partials, indent) {
40
+ return this.ri([context], partials || {}, indent);
41
+ },
42
+
43
+ // render internal -- a hook for overrides that catches partials too
44
+ ri: function (context, partials, indent) {
45
+ return this.r(context, partials, indent);
46
+ },
47
+
48
+ // tries to find a partial in the curent scope and render it
49
+ rp: function(name, context, partials, indent) {
50
+ var partial = partials[name];
51
+
52
+ if (!partial) {
53
+ return '';
54
+ }
55
+
56
+ if (this.c && typeof partial == 'string') {
57
+ partial = this.c.compile(partial, this.options);
58
+ }
59
+
60
+ return partial.ri(context, partials, indent);
61
+ },
62
+
63
+ // render a section
64
+ rs: function(context, partials, section) {
65
+ var tail = context[context.length - 1];
66
+
67
+ if (!isArray(tail)) {
68
+ section(context, partials, this);
69
+ return;
70
+ }
71
+
72
+ for (var i = 0; i < tail.length; i++) {
73
+ context.push(tail[i]);
74
+ section(context, partials, this);
75
+ context.pop();
76
+ }
77
+ },
78
+
79
+ // maybe start a section
80
+ s: function(val, ctx, partials, inverted, start, end, tags) {
81
+ var pass;
82
+
83
+ if (isArray(val) && val.length === 0) {
84
+ return false;
85
+ }
86
+
87
+ if (typeof val == 'function') {
88
+ val = this.ls(val, ctx, partials, inverted, start, end, tags);
89
+ }
90
+
91
+ pass = (val === '') || !!val;
92
+
93
+ if (!inverted && pass && ctx) {
94
+ ctx.push((typeof val == 'object') ? val : ctx[ctx.length - 1]);
95
+ }
96
+
97
+ return pass;
98
+ },
99
+
100
+ // find values with dotted names
101
+ d: function(key, ctx, partials, returnFound) {
102
+ var names = key.split('.'),
103
+ val = this.f(names[0], ctx, partials, returnFound),
104
+ cx = null;
105
+
106
+ if (key === '.' && isArray(ctx[ctx.length - 2])) {
107
+ return ctx[ctx.length - 1];
108
+ }
109
+
110
+ for (var i = 1; i < names.length; i++) {
111
+ if (val && typeof val == 'object' && names[i] in val) {
112
+ cx = val;
113
+ val = val[names[i]];
114
+ } else {
115
+ val = '';
116
+ }
117
+ }
118
+
119
+ if (returnFound && !val) {
120
+ return false;
121
+ }
122
+
123
+ if (!returnFound && typeof val == 'function') {
124
+ ctx.push(cx);
125
+ val = this.lv(val, ctx, partials);
126
+ ctx.pop();
127
+ }
128
+
129
+ return val;
130
+ },
131
+
132
+ // find values with normal names
133
+ f: function(key, ctx, partials, returnFound) {
134
+ var val = false,
135
+ v = null,
136
+ found = false;
137
+
138
+ for (var i = ctx.length - 1; i >= 0; i--) {
139
+ v = ctx[i];
140
+ if (v && typeof v == 'object' && key in v) {
141
+ val = v[key];
142
+ found = true;
143
+ break;
144
+ }
145
+ }
146
+
147
+ if (!found) {
148
+ return (returnFound) ? false : "";
149
+ }
150
+
151
+ if (!returnFound && typeof val == 'function') {
152
+ val = this.lv(val, ctx, partials);
153
+ }
154
+
155
+ return val;
156
+ },
157
+
158
+ // higher order templates
159
+ ho: function(val, cx, partials, text, tags) {
160
+ var compiler = this.c;
161
+ var options = this.options;
162
+ options.delimiters = tags;
163
+ var text = val.call(cx, text);
164
+ text = (text == null) ? String(text) : text.toString();
165
+ this.b(compiler.compile(text, options).render(cx, partials));
166
+ return false;
167
+ },
168
+
169
+ // template result buffering
170
+ b: (useArrayBuffer) ? function(s) { this.buf.push(s); } :
171
+ function(s) { this.buf += s; },
172
+ fl: (useArrayBuffer) ? function() { var r = this.buf.join(''); this.buf = []; return r; } :
173
+ function() { var r = this.buf; this.buf = ''; return r; },
174
+
175
+ // lambda replace section
176
+ ls: function(val, ctx, partials, inverted, start, end, tags) {
177
+ var cx = ctx[ctx.length - 1],
178
+ t = null;
179
+
180
+ if (!inverted && this.c && val.length > 0) {
181
+ return this.ho(val, cx, partials, this.text.substring(start, end), tags);
182
+ }
183
+
184
+ t = val.call(cx);
185
+
186
+ if (typeof t == 'function') {
187
+ if (inverted) {
188
+ return true;
189
+ } else if (this.c) {
190
+ return this.ho(t, cx, partials, this.text.substring(start, end), tags);
191
+ }
192
+ }
193
+
194
+ return t;
195
+ },
196
+
197
+ // lambda replace variable
198
+ lv: function(val, ctx, partials) {
199
+ var cx = ctx[ctx.length - 1];
200
+ var result = val.call(cx);
201
+
202
+ if (typeof result == 'function') {
203
+ result = coerceToString(result.call(cx));
204
+ if (this.c && ~result.indexOf("{\u007B")) {
205
+ return this.c.compile(result, this.options).render(cx, partials);
206
+ }
207
+ }
208
+
209
+ return coerceToString(result);
210
+ }
211
+
212
+ };
213
+
214
+ var rAmp = /&/g,
215
+ rLt = /</g,
216
+ rGt = />/g,
217
+ rApos =/\'/g,
218
+ rQuot = /\"/g,
219
+ hChars =/[&<>\"\']/;
220
+
221
+
222
+ function coerceToString(val) {
223
+ return String((val === null || val === undefined) ? '' : val);
224
+ }
225
+
226
+ function hoganEscape(str) {
227
+ str = coerceToString(str);
228
+ return hChars.test(str) ?
229
+ str
230
+ .replace(rAmp,'&amp;')
231
+ .replace(rLt,'&lt;')
232
+ .replace(rGt,'&gt;')
233
+ .replace(rApos,'&#39;')
234
+ .replace(rQuot, '&quot;') :
235
+ str;
236
+ }
237
+
238
+ var isArray = Array.isArray || function(a) {
239
+ return Object.prototype.toString.call(a) === '[object Array]';
240
+ };
241
+
242
+ })(typeof exports !== 'undefined' ? exports : Hogan);
243
+
244
+
245
+
246
+
247
+ (function (Hogan) {
248
+ // Setup regex assignments
249
+ // remove whitespace according to Mustache spec
250
+ var rIsWhitespace = /\S/,
251
+ rQuot = /\"/g,
252
+ rNewline = /\n/g,
253
+ rCr = /\r/g,
254
+ rSlash = /\\/g,
255
+ tagTypes = {
256
+ '#': 1, '^': 2, '/': 3, '!': 4, '>': 5,
257
+ '<': 6, '=': 7, '_v': 8, '{': 9, '&': 10
258
+ };
259
+
260
+ Hogan.scan = function scan(text, delimiters) {
261
+ var len = text.length,
262
+ IN_TEXT = 0,
263
+ IN_TAG_TYPE = 1,
264
+ IN_TAG = 2,
265
+ state = IN_TEXT,
266
+ tagType = null,
267
+ tag = null,
268
+ buf = '',
269
+ tokens = [],
270
+ seenTag = false,
271
+ i = 0,
272
+ lineStart = 0,
273
+ otag = '{{',
274
+ ctag = '}}';
275
+
276
+ function addBuf() {
277
+ if (buf.length > 0) {
278
+ tokens.push(new String(buf));
279
+ buf = '';
280
+ }
281
+ }
282
+
283
+ function lineIsWhitespace() {
284
+ var isAllWhitespace = true;
285
+ for (var j = lineStart; j < tokens.length; j++) {
286
+ isAllWhitespace =
287
+ (tokens[j].tag && tagTypes[tokens[j].tag] < tagTypes['_v']) ||
288
+ (!tokens[j].tag && tokens[j].match(rIsWhitespace) === null);
289
+ if (!isAllWhitespace) {
290
+ return false;
291
+ }
292
+ }
293
+
294
+ return isAllWhitespace;
295
+ }
296
+
297
+ function filterLine(haveSeenTag, noNewLine) {
298
+ addBuf();
299
+
300
+ if (haveSeenTag && lineIsWhitespace()) {
301
+ for (var j = lineStart, next; j < tokens.length; j++) {
302
+ if (!tokens[j].tag) {
303
+ if ((next = tokens[j+1]) && next.tag == '>') {
304
+ // set indent to token value
305
+ next.indent = tokens[j].toString()
306
+ }
307
+ tokens.splice(j, 1);
308
+ }
309
+ }
310
+ } else if (!noNewLine) {
311
+ tokens.push({tag:'\n'});
312
+ }
313
+
314
+ seenTag = false;
315
+ lineStart = tokens.length;
316
+ }
317
+
318
+ function changeDelimiters(text, index) {
319
+ var close = '=' + ctag,
320
+ closeIndex = text.indexOf(close, index),
321
+ delimiters = trim(
322
+ text.substring(text.indexOf('=', index) + 1, closeIndex)
323
+ ).split(' ');
324
+
325
+ otag = delimiters[0];
326
+ ctag = delimiters[1];
327
+
328
+ return closeIndex + close.length - 1;
329
+ }
330
+
331
+ if (delimiters) {
332
+ delimiters = delimiters.split(' ');
333
+ otag = delimiters[0];
334
+ ctag = delimiters[1];
335
+ }
336
+
337
+ for (i = 0; i < len; i++) {
338
+ if (state == IN_TEXT) {
339
+ if (tagChange(otag, text, i)) {
340
+ --i;
341
+ addBuf();
342
+ state = IN_TAG_TYPE;
343
+ } else {
344
+ if (text.charAt(i) == '\n') {
345
+ filterLine(seenTag);
346
+ } else {
347
+ buf += text.charAt(i);
348
+ }
349
+ }
350
+ } else if (state == IN_TAG_TYPE) {
351
+ i += otag.length - 1;
352
+ tag = tagTypes[text.charAt(i + 1)];
353
+ tagType = tag ? text.charAt(i + 1) : '_v';
354
+ if (tagType == '=') {
355
+ i = changeDelimiters(text, i);
356
+ state = IN_TEXT;
357
+ } else {
358
+ if (tag) {
359
+ i++;
360
+ }
361
+ state = IN_TAG;
362
+ }
363
+ seenTag = i;
364
+ } else {
365
+ if (tagChange(ctag, text, i)) {
366
+ tokens.push({tag: tagType, n: trim(buf), otag: otag, ctag: ctag,
367
+ i: (tagType == '/') ? seenTag - ctag.length : i + otag.length});
368
+ buf = '';
369
+ i += ctag.length - 1;
370
+ state = IN_TEXT;
371
+ if (tagType == '{') {
372
+ if (ctag == '}}') {
373
+ i++;
374
+ } else {
375
+ cleanTripleStache(tokens[tokens.length - 1]);
376
+ }
377
+ }
378
+ } else {
379
+ buf += text.charAt(i);
380
+ }
381
+ }
382
+ }
383
+
384
+ filterLine(seenTag, true);
385
+
386
+ return tokens;
387
+ }
388
+
389
+ function cleanTripleStache(token) {
390
+ if (token.n.substr(token.n.length - 1) === '}') {
391
+ token.n = token.n.substring(0, token.n.length - 1);
392
+ }
393
+ }
394
+
395
+ function trim(s) {
396
+ if (s.trim) {
397
+ return s.trim();
398
+ }
399
+
400
+ return s.replace(/^\s*|\s*$/g, '');
401
+ }
402
+
403
+ function tagChange(tag, text, index) {
404
+ if (text.charAt(index) != tag.charAt(0)) {
405
+ return false;
406
+ }
407
+
408
+ for (var i = 1, l = tag.length; i < l; i++) {
409
+ if (text.charAt(index + i) != tag.charAt(i)) {
410
+ return false;
411
+ }
412
+ }
413
+
414
+ return true;
415
+ }
416
+
417
+ function buildTree(tokens, kind, stack, customTags) {
418
+ var instructions = [],
419
+ opener = null,
420
+ token = null;
421
+
422
+ while (tokens.length > 0) {
423
+ token = tokens.shift();
424
+ if (token.tag == '#' || token.tag == '^' || isOpener(token, customTags)) {
425
+ stack.push(token);
426
+ token.nodes = buildTree(tokens, token.tag, stack, customTags);
427
+ instructions.push(token);
428
+ } else if (token.tag == '/') {
429
+ if (stack.length === 0) {
430
+ throw new Error('Closing tag without opener: /' + token.n);
431
+ }
432
+ opener = stack.pop();
433
+ if (token.n != opener.n && !isCloser(token.n, opener.n, customTags)) {
434
+ throw new Error('Nesting error: ' + opener.n + ' vs. ' + token.n);
435
+ }
436
+ opener.end = token.i;
437
+ return instructions;
438
+ } else {
439
+ instructions.push(token);
440
+ }
441
+ }
442
+
443
+ if (stack.length > 0) {
444
+ throw new Error('missing closing tag: ' + stack.pop().n);
445
+ }
446
+
447
+ return instructions;
448
+ }
449
+
450
+ function isOpener(token, tags) {
451
+ for (var i = 0, l = tags.length; i < l; i++) {
452
+ if (tags[i].o == token.n) {
453
+ token.tag = '#';
454
+ return true;
455
+ }
456
+ }
457
+ }
458
+
459
+ function isCloser(close, open, tags) {
460
+ for (var i = 0, l = tags.length; i < l; i++) {
461
+ if (tags[i].c == close && tags[i].o == open) {
462
+ return true;
463
+ }
464
+ }
465
+ }
466
+
467
+ Hogan.generate = function (tree, text, options) {
468
+ var code = 'var _=this;_.b(i=i||"");' + walk(tree) + 'return _.fl();';
469
+ if (options.asString) {
470
+ return 'function(c,p,i){' + code + ';}';
471
+ }
472
+
473
+ return new Hogan.Template(new Function('c', 'p', 'i', code), text, Hogan, options);
474
+ }
475
+
476
+ function esc(s) {
477
+ return s.replace(rSlash, '\\\\')
478
+ .replace(rQuot, '\\\"')
479
+ .replace(rNewline, '\\n')
480
+ .replace(rCr, '\\r');
481
+ }
482
+
483
+ function chooseMethod(s) {
484
+ return (~s.indexOf('.')) ? 'd' : 'f';
485
+ }
486
+
487
+ function walk(tree) {
488
+ var code = '';
489
+ for (var i = 0, l = tree.length; i < l; i++) {
490
+ var tag = tree[i].tag;
491
+ if (tag == '#') {
492
+ code += section(tree[i].nodes, tree[i].n, chooseMethod(tree[i].n),
493
+ tree[i].i, tree[i].end, tree[i].otag + " " + tree[i].ctag);
494
+ } else if (tag == '^') {
495
+ code += invertedSection(tree[i].nodes, tree[i].n,
496
+ chooseMethod(tree[i].n));
497
+ } else if (tag == '<' || tag == '>') {
498
+ code += partial(tree[i]);
499
+ } else if (tag == '{' || tag == '&') {
500
+ code += tripleStache(tree[i].n, chooseMethod(tree[i].n));
501
+ } else if (tag == '\n') {
502
+ code += text('"\\n"' + (tree.length-1 == i ? '' : ' + i'));
503
+ } else if (tag == '_v') {
504
+ code += variable(tree[i].n, chooseMethod(tree[i].n));
505
+ } else if (tag === undefined) {
506
+ code += text('"' + esc(tree[i]) + '"');
507
+ }
508
+ }
509
+ return code;
510
+ }
511
+
512
+ function section(nodes, id, method, start, end, tags) {
513
+ return 'if(_.s(_.' + method + '("' + esc(id) + '",c,p,1),' +
514
+ 'c,p,0,' + start + ',' + end + ',"' + tags + '")){' +
515
+ '_.rs(c,p,' +
516
+ 'function(c,p,_){' +
517
+ walk(nodes) +
518
+ '});c.pop();}';
519
+ }
520
+
521
+ function invertedSection(nodes, id, method) {
522
+ return 'if(!_.s(_.' + method + '("' + esc(id) + '",c,p,1),c,p,1,0,0,"")){' +
523
+ walk(nodes) +
524
+ '};';
525
+ }
526
+
527
+ function partial(tok) {
528
+ return '_.b(_.rp("' + esc(tok.n) + '",c,p,"' + (tok.indent || '') + '"));';
529
+ }
530
+
531
+ function tripleStache(id, method) {
532
+ return '_.b(_.t(_.' + method + '("' + esc(id) + '",c,p,0)));';
533
+ }
534
+
535
+ function variable(id, method) {
536
+ return '_.b(_.v(_.' + method + '("' + esc(id) + '",c,p,0)));';
537
+ }
538
+
539
+ function text(id) {
540
+ return '_.b(' + id + ');';
541
+ }
542
+
543
+ Hogan.parse = function(tokens, text, options) {
544
+ options = options || {};
545
+ return buildTree(tokens, '', [], options.sectionTags || []);
546
+ },
547
+
548
+ Hogan.cache = {};
549
+
550
+ Hogan.compile = function(text, options) {
551
+ // options
552
+ //
553
+ // asString: false (default)
554
+ //
555
+ // sectionTags: [{o: '_foo', c: 'foo'}]
556
+ // An array of object with o and c fields that indicate names for custom
557
+ // section tags. The example above allows parsing of {{_foo}}{{/foo}}.
558
+ //
559
+ // delimiters: A string that overrides the default delimiters.
560
+ // Example: "<% %>"
561
+ //
562
+ options = options || {};
563
+
564
+ var key = text + '||' + !!options.asString;
565
+
566
+ var t = this.cache[key];
567
+
568
+ if (t) {
569
+ return t;
570
+ }
571
+
572
+ t = this.generate(this.parse(this.scan(text, options.delimiters), text, options), text, options);
573
+ return this.cache[key] = t;
574
+ };
575
+ })(typeof exports !== 'undefined' ? exports : Hogan);
576
+