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,483 @@
1
+ /*
2
+ Markup.js v1.5.21: http://github.com/adammark/Markup.js
3
+ MIT License
4
+ (c) 2011 - 2014 Adam Mark
5
+ */
6
+ var Mark = {
7
+ // Templates to include, by name. A template is a string.
8
+ includes: {},
9
+
10
+ // Global variables, by name. Global variables take precedence over context variables.
11
+ globals: {},
12
+
13
+ // The delimiter to use in pipe expressions, e.g. {{if color|like>red}}.
14
+ delimiter: ">",
15
+
16
+ // Collapse white space between HTML elements in the resulting string.
17
+ compact: false,
18
+
19
+ // Shallow-copy an object.
20
+ _copy: function (a, b) {
21
+ b = b || [];
22
+
23
+ for (var i in a) {
24
+ b[i] = a[i];
25
+ }
26
+
27
+ return b;
28
+ },
29
+
30
+ // Get the value of a number or size of an array. This is a helper function for several pipes.
31
+ _size: function (a) {
32
+ return a instanceof Array ? a.length : (a || 0);
33
+ },
34
+
35
+ // This object represents an iteration. It has an index and length.
36
+ _iter: function (idx, size) {
37
+ this.idx = idx;
38
+ this.size = size;
39
+ this.length = size;
40
+ this.sign = "#";
41
+
42
+ // Print the index if "#" or the count if "##".
43
+ this.toString = function () {
44
+ return this.idx + this.sign.length - 1;
45
+ };
46
+ },
47
+
48
+ // Pass a value through a series of pipe expressions, e.g. _pipe(123, ["add>10","times>5"]).
49
+ _pipe: function (val, expressions) {
50
+ var expression, parts, fn, result;
51
+
52
+ // If we have expressions, pull out the first one, e.g. "add>10".
53
+ if ((expression = expressions.shift())) {
54
+
55
+ // Split the expression into its component parts, e.g. ["add", "10"].
56
+ parts = expression.split(this.delimiter);
57
+
58
+ // Pull out the function name, e.g. "add".
59
+ fn = parts.shift().trim();
60
+
61
+ try {
62
+ // Run the function, e.g. add(123, 10) ...
63
+ result = Mark.pipes[fn].apply(null, [val].concat(parts));
64
+
65
+ // ... then pipe again with remaining expressions.
66
+ val = this._pipe(result, expressions);
67
+ }
68
+ catch (e) {
69
+ }
70
+ }
71
+
72
+ // Return the piped value.
73
+ return val;
74
+ },
75
+
76
+ // TODO doc
77
+ _eval: function (context, filters, child) {
78
+ var result = this._pipe(context, filters),
79
+ ctx = result,
80
+ i = -1,
81
+ j,
82
+ opts;
83
+
84
+ if (result instanceof Array) {
85
+ result = "";
86
+ j = ctx.length;
87
+
88
+ while (++i < j) {
89
+ opts = {
90
+ iter: new this._iter(i, j)
91
+ };
92
+ result += child ? Mark.up(child, ctx[i], opts) : ctx[i];
93
+ }
94
+ }
95
+ else if (result instanceof Object) {
96
+ result = Mark.up(child, ctx);
97
+ }
98
+
99
+ return result;
100
+ },
101
+
102
+ // Process the contents of an IF or IF/ELSE block.
103
+ _test: function (bool, child, context, options) {
104
+ // Process the child string, then split it into the IF and ELSE parts.
105
+ var str = Mark.up(child, context, options).split(/\{\{\s*else\s*\}\}/);
106
+
107
+ // Return the IF or ELSE part. If no ELSE, return an empty string.
108
+ return (bool === false ? str[1] : str[0]) || "";
109
+ },
110
+
111
+ // Determine the extent of a block expression, e.g. "{{foo}}...{{/foo}}"
112
+ _bridge: function (tpl, tkn) {
113
+ tkn = tkn == "." ? "\\." : tkn.replace(/\$/g, "\\$");
114
+
115
+ var exp = "{{\\s*" + tkn + "([^/}]+\\w*)?}}|{{/" + tkn + "\\s*}}",
116
+ re = new RegExp(exp, "g"),
117
+ tags = tpl.match(re) || [],
118
+ t,
119
+ i,
120
+ a = 0,
121
+ b = 0,
122
+ c = -1,
123
+ d = 0;
124
+
125
+ for (i = 0; i < tags.length; i++) {
126
+ t = i;
127
+ c = tpl.indexOf(tags[t], c + 1);
128
+
129
+ if (tags[t].indexOf("{{/") > -1) {
130
+ b++;
131
+ }
132
+ else {
133
+ a++;
134
+ }
135
+
136
+ if (a === b) {
137
+ break;
138
+ }
139
+ }
140
+
141
+ a = tpl.indexOf(tags[0]);
142
+ b = a + tags[0].length;
143
+ d = c + tags[t].length;
144
+
145
+ // Return the block, e.g. "{{foo}}bar{{/foo}}" and its child, e.g. "bar".
146
+ return [tpl.substring(a, d), tpl.substring(b, c)];
147
+ }
148
+ };
149
+
150
+ // Inject a template string with contextual data and return a new string.
151
+ Mark.up = function (template, context, options) {
152
+ context = context || {};
153
+ options = options || {};
154
+
155
+ // Match all tags like "{{...}}".
156
+ var re = /\{\{(.+?)\}\}/g,
157
+ // All tags in the template.
158
+ tags = template.match(re) || [],
159
+ // The tag being evaluated, e.g. "{{hamster|dance}}".
160
+ tag,
161
+ // The expression to evaluate inside the tag, e.g. "hamster|dance".
162
+ prop,
163
+ // The token itself, e.g. "hamster".
164
+ token,
165
+ // An array of pipe expressions, e.g. ["more>1", "less>2"].
166
+ filters = [],
167
+ // Does the tag close itself? e.g. "{{stuff/}}".
168
+ selfy,
169
+ // Is the tag an "if" statement?
170
+ testy,
171
+ // The contents of a block tag, e.g. "{{aa}}bb{{/aa}}" -> "bb".
172
+ child,
173
+ // The resulting string.
174
+ result,
175
+ // The global variable being evaluated, or undefined.
176
+ global,
177
+ // The included template being evaluated, or undefined.
178
+ include,
179
+ // A placeholder variable.
180
+ ctx,
181
+ // Iterators.
182
+ i = 0,
183
+ j = 0;
184
+
185
+ // Set custom pipes, if provided.
186
+ if (options.pipes) {
187
+ this._copy(options.pipes, this.pipes);
188
+ }
189
+
190
+ // Set templates to include, if provided.
191
+ if (options.includes) {
192
+ this._copy(options.includes, this.includes);
193
+ }
194
+
195
+ // Set global variables, if provided.
196
+ if (options.globals) {
197
+ this._copy(options.globals, this.globals);
198
+ }
199
+
200
+ // Optionally override the delimiter.
201
+ if (options.delimiter) {
202
+ this.delimiter = options.delimiter;
203
+ }
204
+
205
+ // Optionally collapse white space.
206
+ if (options.compact !== undefined) {
207
+ this.compact = options.compact;
208
+ }
209
+
210
+ // Loop through tags, e.g. {{a}}, {{b}}, {{c}}, {{/c}}.
211
+ while ((tag = tags[i++])) {
212
+ result = undefined;
213
+ child = "";
214
+ selfy = tag.indexOf("/}}") > -1;
215
+ prop = tag.substr(2, tag.length - (selfy ? 5 : 4));
216
+ prop = prop.replace(/`(.+?)`/g, function (s, p1) {
217
+ return Mark.up("{{" + p1 + "}}", context);
218
+ });
219
+ testy = prop.trim().indexOf("if ") === 0;
220
+ filters = prop.split("|");
221
+ filters.shift(); // instead of splice(1)
222
+ prop = prop.replace(/^\s*if/, "").split("|").shift().trim();
223
+ token = testy ? "if" : prop.split("|")[0];
224
+ ctx = context[prop];
225
+
226
+ // If an "if" statement without filters, assume "{{if foo|notempty}}"
227
+ if (testy && !filters.length) {
228
+ filters = ["notempty"];
229
+ }
230
+
231
+ // Does the tag have a corresponding closing tag? If so, find it and move the cursor.
232
+ if (!selfy && template.indexOf("{{/" + token) > -1) {
233
+ result = this._bridge(template, token);
234
+ tag = result[0];
235
+ child = result[1];
236
+ i += tag.match(re).length - 1; // fast forward
237
+ }
238
+
239
+ // Skip "else" tags. These are pulled out in _test().
240
+ if (/^\{\{\s*else\s*\}\}$/.test(tag)) {
241
+ continue;
242
+ }
243
+
244
+ // Evaluating a global variable.
245
+ else if ((global = this.globals[prop]) !== undefined) {
246
+ result = this._eval(global, filters, child);
247
+ }
248
+
249
+ // Evaluating an included template.
250
+ else if ((include = this.includes[prop])) {
251
+ if (include instanceof Function) {
252
+ include = include();
253
+ }
254
+ result = this._pipe(Mark.up(include, context, options), filters);
255
+ }
256
+
257
+ // Evaluating a loop counter ("#" or "##").
258
+ else if (prop.indexOf("#") > -1) {
259
+ options.iter.sign = prop;
260
+ result = this._pipe(options.iter, filters);
261
+ }
262
+
263
+ // Evaluating the current context.
264
+ else if (prop === ".") {
265
+ result = this._pipe(context, filters);
266
+ }
267
+
268
+ // Evaluating a variable with dot notation, e.g. "a.b.c"
269
+ else if (prop.indexOf(".") > -1) {
270
+ prop = prop.split(".");
271
+ ctx = Mark.globals[prop[0]];
272
+
273
+ if (ctx) {
274
+ j = 1;
275
+ }
276
+ else {
277
+ j = 0;
278
+ ctx = context;
279
+ }
280
+
281
+ // Get the actual context
282
+ while (ctx && j < prop.length) {
283
+ ctx = ctx[prop[j++]];
284
+ }
285
+
286
+ result = this._eval(ctx, filters, child);
287
+ }
288
+
289
+ // Evaluating an "if" statement.
290
+ else if (testy) {
291
+ result = this._pipe(ctx, filters);
292
+ }
293
+
294
+ // Evaluating an array, which might be a block expression.
295
+ else if (ctx instanceof Array) {
296
+ result = this._eval(ctx, filters, child);
297
+ }
298
+
299
+ // Evaluating a block expression.
300
+ else if (child) {
301
+ result = ctx ? Mark.up(child, ctx) : undefined;
302
+ }
303
+
304
+ // Evaluating anything else.
305
+ else if (context.hasOwnProperty(prop)) {
306
+ result = this._pipe(ctx, filters);
307
+ }
308
+
309
+ // Evaluating special case: if the resulting context is actually an Array
310
+ if (result instanceof Array) {
311
+ result = this._eval(result, filters, child);
312
+ }
313
+
314
+ // Evaluating an "if" statement.
315
+ if (testy) {
316
+ result = this._test(result, child, context, options);
317
+ }
318
+
319
+ // Replace the tag, e.g. "{{name}}", with the result, e.g. "Adam".
320
+ template = template.replace(tag, result === undefined ? "???" : result);
321
+ }
322
+
323
+ return this.compact ? template.replace(/>\s+</g, "><") : template;
324
+ };
325
+
326
+ // Freebie pipes. See usage in README.md
327
+ Mark.pipes = {
328
+ empty: function (obj) {
329
+ return !obj || (obj + "").trim().length === 0 ? obj : false;
330
+ },
331
+ notempty: function (obj) {
332
+ return obj && (obj + "").trim().length ? obj : false;
333
+ },
334
+ blank: function (str, val) {
335
+ return !!str || str === 0 ? str : val;
336
+ },
337
+ more: function (a, b) {
338
+ return Mark._size(a) > b ? a : false;
339
+ },
340
+ less: function (a, b) {
341
+ return Mark._size(a) < b ? a : false;
342
+ },
343
+ ormore: function (a, b) {
344
+ return Mark._size(a) >= b ? a : false;
345
+ },
346
+ orless: function (a, b) {
347
+ return Mark._size(a) <= b ? a : false;
348
+ },
349
+ between: function (a, b, c) {
350
+ a = Mark._size(a);
351
+ return a >= b && a <= c ? a : false;
352
+ },
353
+ equals: function (a, b) {
354
+ return a == b ? a : false;
355
+ },
356
+ notequals: function (a, b) {
357
+ return a != b ? a : false;
358
+ },
359
+ like: function (str, pattern) {
360
+ return new RegExp(pattern, "i").test(str) ? str : false;
361
+ },
362
+ notlike: function (str, pattern) {
363
+ return !Mark.pipes.like(str, pattern) ? str : false;
364
+ },
365
+ upcase: function (str) {
366
+ return String(str).toUpperCase();
367
+ },
368
+ downcase: function (str) {
369
+ return String(str).toLowerCase();
370
+ },
371
+ capcase: function (str) {
372
+ return str.replace(/(?:^|\s)\S/g, function (a) { return a.toUpperCase(); });
373
+ },
374
+ chop: function (str, n) {
375
+ return str.length > n ? str.substr(0, n) + "..." : str;
376
+ },
377
+ tease: function (str, n) {
378
+ var a = str.split(/\s+/);
379
+ return a.slice(0, n).join(" ") + (a.length > n ? "..." : "");
380
+ },
381
+ trim: function (str) {
382
+ return str.trim();
383
+ },
384
+ pack: function (str) {
385
+ return str.trim().replace(/\s{2,}/g, " ");
386
+ },
387
+ round: function (num) {
388
+ return Math.round(+num);
389
+ },
390
+ clean: function (str) {
391
+ return String(str).replace(/<\/?[^>]+>/gi, "");
392
+ },
393
+ size: function (obj) {
394
+ return obj.length;
395
+ },
396
+ length: function (obj) {
397
+ return obj.length;
398
+ },
399
+ reverse: function (arr) {
400
+ return [].concat(arr).reverse();
401
+ },
402
+ join: function (arr, separator) {
403
+ return arr.join(separator);
404
+ },
405
+ limit: function (arr, count, idx) {
406
+ return arr.slice(+idx || 0, +count + (+idx || 0));
407
+ },
408
+ split: function (str, separator) {
409
+ return str.split(separator || ",");
410
+ },
411
+ choose: function (bool, iffy, elsy) {
412
+ return !!bool ? iffy : (elsy || "");
413
+ },
414
+ toggle: function (obj, csv1, csv2, str) {
415
+ return csv2.split(",")[csv1.match(/\w+/g).indexOf(obj + "")] || str;
416
+ },
417
+ sort: function (arr, prop) {
418
+ var fn = function (a, b) {
419
+ return a[prop] > b[prop] ? 1 : -1;
420
+ };
421
+ return [].concat(arr).sort(prop ? fn : undefined);
422
+ },
423
+ fix: function (num, n) {
424
+ return (+num).toFixed(n);
425
+ },
426
+ mod: function (num, n) {
427
+ return (+num) % (+n);
428
+ },
429
+ divisible: function (num, n) {
430
+ return num && (+num % n) === 0 ? num : false;
431
+ },
432
+ even: function (num) {
433
+ return num && (+num & 1) === 0 ? num : false;
434
+ },
435
+ odd: function (num) {
436
+ return num && (+num & 1) === 1 ? num : false;
437
+ },
438
+ number: function (str) {
439
+ return parseFloat(str.replace(/[^\-\d\.]/g, ""));
440
+ },
441
+ url: function (str) {
442
+ return encodeURI(str);
443
+ },
444
+ bool: function (obj) {
445
+ return !!obj;
446
+ },
447
+ falsy: function (obj) {
448
+ return !obj;
449
+ },
450
+ first: function (iter) {
451
+ return iter.idx === 0;
452
+ },
453
+ last: function (iter) {
454
+ return iter.idx === iter.size - 1;
455
+ },
456
+ call: function (obj, fn) {
457
+ return obj[fn].apply(obj, [].slice.call(arguments, 2));
458
+ },
459
+ set: function (obj, key) {
460
+ Mark.globals[key] = obj; return "";
461
+ },
462
+ log: function (obj) {
463
+ console.log(obj);
464
+ return obj;
465
+ }
466
+ };
467
+
468
+ // Shim for IE.
469
+ if (typeof String.prototype.trim !== "function") {
470
+ String.prototype.trim = function() {
471
+ return this.replace(/^\s+|\s+$/g, "");
472
+ }
473
+ }
474
+
475
+ // Export for Node.js and AMD.
476
+ if (typeof module !== "undefined" && module.exports) {
477
+ module.exports = Mark;
478
+ }
479
+ else if (typeof define === "function" && define.amd) {
480
+ define(function() {
481
+ return Mark;
482
+ });
483
+ }