kindred 0.0.1
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.
- checksums.yaml +7 -0
- data/.gitignore +15 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +316 -0
- data/Rakefile +2 -0
- data/app/assets/javascripts/app.coffee +1 -0
- data/app/assets/javascripts/kindred.js +13 -0
- data/app/assets/javascripts/models/active_page.coffee +97 -0
- data/app/assets/javascripts/models/base.coffee +153 -0
- data/app/assets/javascripts/models/setup.coffee +97 -0
- data/app/assets/javascripts/utilities/binder.coffee +29 -0
- data/app/assets/javascripts/utilities/listener.coffee +74 -0
- data/app/assets/javascripts/utilities/logger.coffee +12 -0
- data/app/assets/javascripts/utilities/stack_trace.js +511 -0
- data/app/assets/javascripts/utilities/template.coffee +3 -0
- data/app/assets/javascripts/utilities/uuid.coffee +9 -0
- data/app/assets/javascripts/utilities/virtual_class.coffee +25 -0
- data/app/helpers/template_helper.rb +59 -0
- data/kindred.gemspec +26 -0
- data/lib/kindred/engine.rb +8 -0
- data/lib/kindred/version.rb +3 -0
- data/lib/kindred.rb +5 -0
- metadata +123 -0
@@ -0,0 +1,511 @@
|
|
1
|
+
// Domain Public by Eric Wendelin http://www.eriwen.com/ (2008)
|
2
|
+
// Luke Smith http://lucassmith.name/ (2008)
|
3
|
+
// Loic Dachary <loic@dachary.org> (2008)
|
4
|
+
// Johan Euphrosine <proppy@aminche.com> (2008)
|
5
|
+
// Oyvind Sean Kinsey http://kinsey.no/blog (2010)
|
6
|
+
// Victor Homyakov <victor-homyakov@users.sourceforge.net> (2010)
|
7
|
+
/*global module, exports, define, ActiveXObject*/
|
8
|
+
(function(global, factory) {
|
9
|
+
if (typeof exports === 'object') {
|
10
|
+
// Node
|
11
|
+
module.exports = factory();
|
12
|
+
} else if (typeof define === 'function' && define.amd) {
|
13
|
+
// AMD
|
14
|
+
define(factory);
|
15
|
+
} else {
|
16
|
+
// Browser globals
|
17
|
+
global.printStackTrace = factory();
|
18
|
+
}
|
19
|
+
}(this, function() {
|
20
|
+
/**
|
21
|
+
* Main function giving a function stack trace with a forced or passed in Error
|
22
|
+
*
|
23
|
+
* @cfg {Error} e The error to create a stacktrace from (optional)
|
24
|
+
* @cfg {Boolean} guess If we should try to resolve the names of anonymous functions
|
25
|
+
* @return {Array} of Strings with functions, lines, files, and arguments where possible
|
26
|
+
*/
|
27
|
+
function printStackTrace(options) {
|
28
|
+
options = options || {guess: true};
|
29
|
+
var ex = options.e || null, guess = !!options.guess, mode = options.mode || null;
|
30
|
+
var p = new printStackTrace.implementation(), result = p.run(ex, mode);
|
31
|
+
return (guess) ? p.guessAnonymousFunctions(result) : result;
|
32
|
+
}
|
33
|
+
|
34
|
+
printStackTrace.implementation = function() {
|
35
|
+
};
|
36
|
+
|
37
|
+
printStackTrace.implementation.prototype = {
|
38
|
+
/**
|
39
|
+
* @param {Error} [ex] The error to create a stacktrace from (optional)
|
40
|
+
* @param {String} [mode] Forced mode (optional, mostly for unit tests)
|
41
|
+
*/
|
42
|
+
run: function(ex, mode) {
|
43
|
+
ex = ex || this.createException();
|
44
|
+
mode = mode || this.mode(ex);
|
45
|
+
if (mode === 'other') {
|
46
|
+
return this.other(arguments.callee);
|
47
|
+
} else {
|
48
|
+
return this[mode](ex);
|
49
|
+
}
|
50
|
+
},
|
51
|
+
|
52
|
+
createException: function() {
|
53
|
+
try {
|
54
|
+
this.undef();
|
55
|
+
} catch (e) {
|
56
|
+
return e;
|
57
|
+
}
|
58
|
+
},
|
59
|
+
|
60
|
+
/**
|
61
|
+
* Mode could differ for different exception, e.g.
|
62
|
+
* exceptions in Chrome may or may not have arguments or stack.
|
63
|
+
*
|
64
|
+
* @return {String} mode of operation for the exception
|
65
|
+
*/
|
66
|
+
mode: function(e) {
|
67
|
+
if (typeof window !== 'undefined' && window.navigator.userAgent.indexOf('PhantomJS') > -1) {
|
68
|
+
return 'phantomjs';
|
69
|
+
}
|
70
|
+
|
71
|
+
if (e['arguments'] && e.stack) {
|
72
|
+
return 'chrome';
|
73
|
+
}
|
74
|
+
|
75
|
+
if (e.stack && e.sourceURL) {
|
76
|
+
return 'safari';
|
77
|
+
}
|
78
|
+
|
79
|
+
if (e.stack && e.number) {
|
80
|
+
return 'ie';
|
81
|
+
}
|
82
|
+
|
83
|
+
if (e.stack && e.fileName) {
|
84
|
+
return 'firefox';
|
85
|
+
}
|
86
|
+
|
87
|
+
if (e.message && e['opera#sourceloc']) {
|
88
|
+
// e.message.indexOf("Backtrace:") > -1 -> opera9
|
89
|
+
// 'opera#sourceloc' in e -> opera9, opera10a
|
90
|
+
// !e.stacktrace -> opera9
|
91
|
+
if (!e.stacktrace) {
|
92
|
+
return 'opera9'; // use e.message
|
93
|
+
}
|
94
|
+
if (e.message.indexOf('\n') > -1 && e.message.split('\n').length > e.stacktrace.split('\n').length) {
|
95
|
+
// e.message may have more stack entries than e.stacktrace
|
96
|
+
return 'opera9'; // use e.message
|
97
|
+
}
|
98
|
+
return 'opera10a'; // use e.stacktrace
|
99
|
+
}
|
100
|
+
|
101
|
+
if (e.message && e.stack && e.stacktrace) {
|
102
|
+
// e.stacktrace && e.stack -> opera10b
|
103
|
+
if (e.stacktrace.indexOf("called from line") < 0) {
|
104
|
+
return 'opera10b'; // use e.stacktrace, format differs from 'opera10a'
|
105
|
+
}
|
106
|
+
// e.stacktrace && e.stack -> opera11
|
107
|
+
return 'opera11'; // use e.stacktrace, format differs from 'opera10a', 'opera10b'
|
108
|
+
}
|
109
|
+
|
110
|
+
if (e.stack && !e.fileName) {
|
111
|
+
// Chrome 27 does not have e.arguments as earlier versions,
|
112
|
+
// but still does not have e.fileName as Firefox
|
113
|
+
return 'chrome';
|
114
|
+
}
|
115
|
+
|
116
|
+
return 'other';
|
117
|
+
},
|
118
|
+
|
119
|
+
/**
|
120
|
+
* Given a context, function name, and callback function, overwrite it so that it calls
|
121
|
+
* printStackTrace() first with a callback and then runs the rest of the body.
|
122
|
+
*
|
123
|
+
* @param {Object} context of execution (e.g. window)
|
124
|
+
* @param {String} functionName to instrument
|
125
|
+
* @param {Function} callback function to call with a stack trace on invocation
|
126
|
+
*/
|
127
|
+
instrumentFunction: function(context, functionName, callback) {
|
128
|
+
context = context || window;
|
129
|
+
var original = context[functionName];
|
130
|
+
context[functionName] = function instrumented() {
|
131
|
+
callback.call(this, printStackTrace().slice(4));
|
132
|
+
return context[functionName]._instrumented.apply(this, arguments);
|
133
|
+
};
|
134
|
+
context[functionName]._instrumented = original;
|
135
|
+
},
|
136
|
+
|
137
|
+
/**
|
138
|
+
* Given a context and function name of a function that has been
|
139
|
+
* instrumented, revert the function to it's original (non-instrumented)
|
140
|
+
* state.
|
141
|
+
*
|
142
|
+
* @param {Object} context of execution (e.g. window)
|
143
|
+
* @param {String} functionName to de-instrument
|
144
|
+
*/
|
145
|
+
deinstrumentFunction: function(context, functionName) {
|
146
|
+
if (context[functionName].constructor === Function &&
|
147
|
+
context[functionName]._instrumented &&
|
148
|
+
context[functionName]._instrumented.constructor === Function) {
|
149
|
+
context[functionName] = context[functionName]._instrumented;
|
150
|
+
}
|
151
|
+
},
|
152
|
+
|
153
|
+
/**
|
154
|
+
* Given an Error object, return a formatted Array based on Chrome's stack string.
|
155
|
+
*
|
156
|
+
* @param e - Error object to inspect
|
157
|
+
* @return Array<String> of function calls, files and line numbers
|
158
|
+
*/
|
159
|
+
chrome: function(e) {
|
160
|
+
return (e.stack + '\n')
|
161
|
+
.replace(/^[\s\S]+?\s+at\s+/, ' at ') // remove message
|
162
|
+
.replace(/^\s+(at eval )?at\s+/gm, '') // remove 'at' and indentation
|
163
|
+
.replace(/^([^\(]+?)([\n$])/gm, '{anonymous}() ($1)$2')
|
164
|
+
.replace(/^Object.<anonymous>\s*\(([^\)]+)\)/gm, '{anonymous}() ($1)')
|
165
|
+
.replace(/^(.+) \((.+)\)$/gm, '$1@$2')
|
166
|
+
.split('\n')
|
167
|
+
.slice(0, -1);
|
168
|
+
},
|
169
|
+
|
170
|
+
/**
|
171
|
+
* Given an Error object, return a formatted Array based on Safari's stack string.
|
172
|
+
*
|
173
|
+
* @param e - Error object to inspect
|
174
|
+
* @return Array<String> of function calls, files and line numbers
|
175
|
+
*/
|
176
|
+
safari: function(e) {
|
177
|
+
return e.stack.replace(/\[native code\]\n/m, '')
|
178
|
+
.replace(/^(?=\w+Error\:).*$\n/m, '')
|
179
|
+
.replace(/^@/gm, '{anonymous}()@')
|
180
|
+
.split('\n');
|
181
|
+
},
|
182
|
+
|
183
|
+
/**
|
184
|
+
* Given an Error object, return a formatted Array based on IE's stack string.
|
185
|
+
*
|
186
|
+
* @param e - Error object to inspect
|
187
|
+
* @return Array<String> of function calls, files and line numbers
|
188
|
+
*/
|
189
|
+
ie: function(e) {
|
190
|
+
return e.stack
|
191
|
+
.replace(/^\s*at\s+(.*)$/gm, '$1')
|
192
|
+
.replace(/^Anonymous function\s+/gm, '{anonymous}() ')
|
193
|
+
.replace(/^(.+)\s+\((.+)\)$/gm, '$1@$2')
|
194
|
+
.split('\n')
|
195
|
+
.slice(1);
|
196
|
+
},
|
197
|
+
|
198
|
+
/**
|
199
|
+
* Given an Error object, return a formatted Array based on Firefox's stack string.
|
200
|
+
*
|
201
|
+
* @param e - Error object to inspect
|
202
|
+
* @return Array<String> of function calls, files and line numbers
|
203
|
+
*/
|
204
|
+
firefox: function(e) {
|
205
|
+
return e.stack.replace(/(?:\n@:0)?\s+$/m, '')
|
206
|
+
.replace(/^(?:\((\S*)\))?@/gm, '{anonymous}($1)@')
|
207
|
+
.split('\n');
|
208
|
+
},
|
209
|
+
|
210
|
+
opera11: function(e) {
|
211
|
+
var ANON = '{anonymous}', lineRE = /^.*line (\d+), column (\d+)(?: in (.+))? in (\S+):$/;
|
212
|
+
var lines = e.stacktrace.split('\n'), result = [];
|
213
|
+
|
214
|
+
for (var i = 0, len = lines.length; i < len; i += 2) {
|
215
|
+
var match = lineRE.exec(lines[i]);
|
216
|
+
if (match) {
|
217
|
+
var location = match[4] + ':' + match[1] + ':' + match[2];
|
218
|
+
var fnName = match[3] || "global code";
|
219
|
+
fnName = fnName.replace(/<anonymous function: (\S+)>/, "$1").replace(/<anonymous function>/, ANON);
|
220
|
+
result.push(fnName + '@' + location + ' -- ' + lines[i + 1].replace(/^\s+/, ''));
|
221
|
+
}
|
222
|
+
}
|
223
|
+
|
224
|
+
return result;
|
225
|
+
},
|
226
|
+
|
227
|
+
opera10b: function(e) {
|
228
|
+
// "<anonymous function: run>([arguments not available])@file://localhost/G:/js/stacktrace.js:27\n" +
|
229
|
+
// "printStackTrace([arguments not available])@file://localhost/G:/js/stacktrace.js:18\n" +
|
230
|
+
// "@file://localhost/G:/js/test/functional/testcase1.html:15"
|
231
|
+
var lineRE = /^(.*)@(.+):(\d+)$/;
|
232
|
+
var lines = e.stacktrace.split('\n'), result = [];
|
233
|
+
|
234
|
+
for (var i = 0, len = lines.length; i < len; i++) {
|
235
|
+
var match = lineRE.exec(lines[i]);
|
236
|
+
if (match) {
|
237
|
+
var fnName = match[1] ? (match[1] + '()') : "global code";
|
238
|
+
result.push(fnName + '@' + match[2] + ':' + match[3]);
|
239
|
+
}
|
240
|
+
}
|
241
|
+
|
242
|
+
return result;
|
243
|
+
},
|
244
|
+
|
245
|
+
/**
|
246
|
+
* Given an Error object, return a formatted Array based on Opera 10's stacktrace string.
|
247
|
+
*
|
248
|
+
* @param e - Error object to inspect
|
249
|
+
* @return Array<String> of function calls, files and line numbers
|
250
|
+
*/
|
251
|
+
opera10a: function(e) {
|
252
|
+
// " Line 27 of linked script file://localhost/G:/js/stacktrace.js\n"
|
253
|
+
// " Line 11 of inline#1 script in file://localhost/G:/js/test/functional/testcase1.html: In function foo\n"
|
254
|
+
var ANON = '{anonymous}', lineRE = /Line (\d+).*script (?:in )?(\S+)(?:: In function (\S+))?$/i;
|
255
|
+
var lines = e.stacktrace.split('\n'), result = [];
|
256
|
+
|
257
|
+
for (var i = 0, len = lines.length; i < len; i += 2) {
|
258
|
+
var match = lineRE.exec(lines[i]);
|
259
|
+
if (match) {
|
260
|
+
var fnName = match[3] || ANON;
|
261
|
+
result.push(fnName + '()@' + match[2] + ':' + match[1] + ' -- ' + lines[i + 1].replace(/^\s+/, ''));
|
262
|
+
}
|
263
|
+
}
|
264
|
+
|
265
|
+
return result;
|
266
|
+
},
|
267
|
+
|
268
|
+
// Opera 7.x-9.2x only!
|
269
|
+
opera9: function(e) {
|
270
|
+
// " Line 43 of linked script file://localhost/G:/js/stacktrace.js\n"
|
271
|
+
// " Line 7 of inline#1 script in file://localhost/G:/js/test/functional/testcase1.html\n"
|
272
|
+
var ANON = '{anonymous}', lineRE = /Line (\d+).*script (?:in )?(\S+)/i;
|
273
|
+
var lines = e.message.split('\n'), result = [];
|
274
|
+
|
275
|
+
for (var i = 2, len = lines.length; i < len; i += 2) {
|
276
|
+
var match = lineRE.exec(lines[i]);
|
277
|
+
if (match) {
|
278
|
+
result.push(ANON + '()@' + match[2] + ':' + match[1] + ' -- ' + lines[i + 1].replace(/^\s+/, ''));
|
279
|
+
}
|
280
|
+
}
|
281
|
+
|
282
|
+
return result;
|
283
|
+
},
|
284
|
+
|
285
|
+
phantomjs: function(e) {
|
286
|
+
var ANON = '{anonymous}', lineRE = /(\S+) \((\S+)\)/i;
|
287
|
+
var lines = e.stack.split('\n'), result = [];
|
288
|
+
|
289
|
+
for (var i = 1, len = lines.length; i < len; i++) {
|
290
|
+
lines[i] = lines[i].replace(/^\s+at\s+/gm, '');
|
291
|
+
var match = lineRE.exec(lines[i]);
|
292
|
+
if (match) {
|
293
|
+
result.push(match[1] + '()@' + match[2]);
|
294
|
+
}
|
295
|
+
else {
|
296
|
+
result.push(ANON + '()@' + lines[i]);
|
297
|
+
}
|
298
|
+
}
|
299
|
+
|
300
|
+
return result;
|
301
|
+
},
|
302
|
+
|
303
|
+
// Safari 5-, IE 9-, and others
|
304
|
+
other: function(curr) {
|
305
|
+
var ANON = '{anonymous}', fnRE = /function(?:\s+([\w$]+))?\s*\(/, stack = [], fn, args, maxStackSize = 10;
|
306
|
+
var slice = Array.prototype.slice;
|
307
|
+
while (curr && stack.length < maxStackSize) {
|
308
|
+
fn = fnRE.test(curr.toString()) ? RegExp.$1 || ANON : ANON;
|
309
|
+
try {
|
310
|
+
args = slice.call(curr['arguments'] || []);
|
311
|
+
} catch (e) {
|
312
|
+
args = ['Cannot access arguments: ' + e];
|
313
|
+
}
|
314
|
+
stack[stack.length] = fn + '(' + this.stringifyArguments(args) + ')';
|
315
|
+
try {
|
316
|
+
curr = curr.caller;
|
317
|
+
} catch (e) {
|
318
|
+
stack[stack.length] = 'Cannot access caller: ' + e;
|
319
|
+
break;
|
320
|
+
}
|
321
|
+
}
|
322
|
+
return stack;
|
323
|
+
},
|
324
|
+
|
325
|
+
/**
|
326
|
+
* Given arguments array as a String, substituting type names for non-string types.
|
327
|
+
*
|
328
|
+
* @param {Arguments,Array} args
|
329
|
+
* @return {String} stringified arguments
|
330
|
+
*/
|
331
|
+
stringifyArguments: function(args) {
|
332
|
+
var result = [];
|
333
|
+
var slice = Array.prototype.slice;
|
334
|
+
for (var i = 0; i < args.length; ++i) {
|
335
|
+
var arg = args[i];
|
336
|
+
if (arg === undefined) {
|
337
|
+
result[i] = 'undefined';
|
338
|
+
} else if (arg === null) {
|
339
|
+
result[i] = 'null';
|
340
|
+
} else if (arg.constructor) {
|
341
|
+
// TODO constructor comparison does not work for iframes
|
342
|
+
if (arg.constructor === Array) {
|
343
|
+
if (arg.length < 3) {
|
344
|
+
result[i] = '[' + this.stringifyArguments(arg) + ']';
|
345
|
+
} else {
|
346
|
+
result[i] = '[' + this.stringifyArguments(slice.call(arg, 0, 1)) + '...' + this.stringifyArguments(slice.call(arg, -1)) + ']';
|
347
|
+
}
|
348
|
+
} else if (arg.constructor === Object) {
|
349
|
+
result[i] = '#object';
|
350
|
+
} else if (arg.constructor === Function) {
|
351
|
+
result[i] = '#function';
|
352
|
+
} else if (arg.constructor === String) {
|
353
|
+
result[i] = '"' + arg + '"';
|
354
|
+
} else if (arg.constructor === Number) {
|
355
|
+
result[i] = arg;
|
356
|
+
} else {
|
357
|
+
result[i] = '?';
|
358
|
+
}
|
359
|
+
}
|
360
|
+
}
|
361
|
+
return result.join(',');
|
362
|
+
},
|
363
|
+
|
364
|
+
sourceCache: {},
|
365
|
+
|
366
|
+
/**
|
367
|
+
* @return {String} the text from a given URL
|
368
|
+
*/
|
369
|
+
ajax: function(url) {
|
370
|
+
var req = this.createXMLHTTPObject();
|
371
|
+
if (req) {
|
372
|
+
try {
|
373
|
+
req.open('GET', url, false);
|
374
|
+
//req.overrideMimeType('text/plain');
|
375
|
+
//req.overrideMimeType('text/javascript');
|
376
|
+
req.send(null);
|
377
|
+
//return req.status == 200 ? req.responseText : '';
|
378
|
+
return req.responseText;
|
379
|
+
} catch (e) {
|
380
|
+
}
|
381
|
+
}
|
382
|
+
return '';
|
383
|
+
},
|
384
|
+
|
385
|
+
/**
|
386
|
+
* Try XHR methods in order and store XHR factory.
|
387
|
+
*
|
388
|
+
* @return {XMLHttpRequest} XHR function or equivalent
|
389
|
+
*/
|
390
|
+
createXMLHTTPObject: function() {
|
391
|
+
var xmlhttp, XMLHttpFactories = [
|
392
|
+
function() {
|
393
|
+
return new XMLHttpRequest();
|
394
|
+
}, function() {
|
395
|
+
return new ActiveXObject('Msxml2.XMLHTTP');
|
396
|
+
}, function() {
|
397
|
+
return new ActiveXObject('Msxml3.XMLHTTP');
|
398
|
+
}, function() {
|
399
|
+
return new ActiveXObject('Microsoft.XMLHTTP');
|
400
|
+
}
|
401
|
+
];
|
402
|
+
for (var i = 0; i < XMLHttpFactories.length; i++) {
|
403
|
+
try {
|
404
|
+
xmlhttp = XMLHttpFactories[i]();
|
405
|
+
// Use memoization to cache the factory
|
406
|
+
this.createXMLHTTPObject = XMLHttpFactories[i];
|
407
|
+
return xmlhttp;
|
408
|
+
} catch (e) {
|
409
|
+
}
|
410
|
+
}
|
411
|
+
},
|
412
|
+
|
413
|
+
/**
|
414
|
+
* Given a URL, check if it is in the same domain (so we can get the source
|
415
|
+
* via Ajax).
|
416
|
+
*
|
417
|
+
* @param url {String} source url
|
418
|
+
* @return {Boolean} False if we need a cross-domain request
|
419
|
+
*/
|
420
|
+
isSameDomain: function(url) {
|
421
|
+
return typeof location !== "undefined" && url.indexOf(location.hostname) !== -1; // location may not be defined, e.g. when running from nodejs.
|
422
|
+
},
|
423
|
+
|
424
|
+
/**
|
425
|
+
* Get source code from given URL if in the same domain.
|
426
|
+
*
|
427
|
+
* @param url {String} JS source URL
|
428
|
+
* @return {Array} Array of source code lines
|
429
|
+
*/
|
430
|
+
getSource: function(url) {
|
431
|
+
// TODO reuse source from script tags?
|
432
|
+
if (!(url in this.sourceCache)) {
|
433
|
+
this.sourceCache[url] = this.ajax(url).split('\n');
|
434
|
+
}
|
435
|
+
return this.sourceCache[url];
|
436
|
+
},
|
437
|
+
|
438
|
+
guessAnonymousFunctions: function(stack) {
|
439
|
+
for (var i = 0; i < stack.length; ++i) {
|
440
|
+
var reStack = /\{anonymous\}\(.*\)@(.*)/,
|
441
|
+
reRef = /^(.*?)(?::(\d+))(?::(\d+))?(?: -- .+)?$/,
|
442
|
+
frame = stack[i], ref = reStack.exec(frame);
|
443
|
+
|
444
|
+
if (ref) {
|
445
|
+
var m = reRef.exec(ref[1]);
|
446
|
+
if (m) { // If falsey, we did not get any file/line information
|
447
|
+
var file = m[1], lineno = m[2], charno = m[3] || 0;
|
448
|
+
if (file && this.isSameDomain(file) && lineno) {
|
449
|
+
var functionName = this.guessAnonymousFunction(file, lineno, charno);
|
450
|
+
stack[i] = frame.replace('{anonymous}', functionName);
|
451
|
+
}
|
452
|
+
}
|
453
|
+
}
|
454
|
+
}
|
455
|
+
return stack;
|
456
|
+
},
|
457
|
+
|
458
|
+
guessAnonymousFunction: function(url, lineNo, charNo) {
|
459
|
+
var ret;
|
460
|
+
try {
|
461
|
+
ret = this.findFunctionName(this.getSource(url), lineNo);
|
462
|
+
} catch (e) {
|
463
|
+
ret = 'getSource failed with url: ' + url + ', exception: ' + e.toString();
|
464
|
+
}
|
465
|
+
return ret;
|
466
|
+
},
|
467
|
+
|
468
|
+
findFunctionName: function(source, lineNo) {
|
469
|
+
// FIXME findFunctionName fails for compressed source
|
470
|
+
// (more than one function on the same line)
|
471
|
+
// function {name}({args}) m[1]=name m[2]=args
|
472
|
+
var reFunctionDeclaration = /function\s+([^(]*?)\s*\(([^)]*)\)/;
|
473
|
+
// {name} = function ({args}) TODO args capture
|
474
|
+
// /['"]?([0-9A-Za-z_]+)['"]?\s*[:=]\s*function(?:[^(]*)/
|
475
|
+
var reFunctionExpression = /['"]?([$_A-Za-z][$_A-Za-z0-9]*)['"]?\s*[:=]\s*function\b/;
|
476
|
+
// {name} = eval()
|
477
|
+
var reFunctionEvaluation = /['"]?([$_A-Za-z][$_A-Za-z0-9]*)['"]?\s*[:=]\s*(?:eval|new Function)\b/;
|
478
|
+
// Walk backwards in the source lines until we find
|
479
|
+
// the line which matches one of the patterns above
|
480
|
+
var code = "", line, maxLines = Math.min(lineNo, 20), m, commentPos;
|
481
|
+
for (var i = 0; i < maxLines; ++i) {
|
482
|
+
// lineNo is 1-based, source[] is 0-based
|
483
|
+
line = source[lineNo - i - 1];
|
484
|
+
commentPos = line.indexOf('//');
|
485
|
+
if (commentPos >= 0) {
|
486
|
+
line = line.substr(0, commentPos);
|
487
|
+
}
|
488
|
+
// TODO check other types of comments? Commented code may lead to false positive
|
489
|
+
if (line) {
|
490
|
+
code = line + code;
|
491
|
+
m = reFunctionExpression.exec(code);
|
492
|
+
if (m && m[1]) {
|
493
|
+
return m[1];
|
494
|
+
}
|
495
|
+
m = reFunctionDeclaration.exec(code);
|
496
|
+
if (m && m[1]) {
|
497
|
+
//return m[1] + "(" + (m[2] || "") + ")";
|
498
|
+
return m[1];
|
499
|
+
}
|
500
|
+
m = reFunctionEvaluation.exec(code);
|
501
|
+
if (m && m[1]) {
|
502
|
+
return m[1];
|
503
|
+
}
|
504
|
+
}
|
505
|
+
}
|
506
|
+
return '(?)';
|
507
|
+
}
|
508
|
+
};
|
509
|
+
|
510
|
+
return printStackTrace;
|
511
|
+
}));
|
@@ -0,0 +1,9 @@
|
|
1
|
+
class App.UUID
|
2
|
+
@generate = ->
|
3
|
+
d = new Date().getTime()
|
4
|
+
uuid = "xxxxxxxx-xxxx-4xxx-xxxx-xxxxxxxxxxxx".replace(/[xy]/g, (c) ->
|
5
|
+
r = (d + Math.random() * 16) % 16 | 0
|
6
|
+
d = Math.floor(d / 16)
|
7
|
+
((if c is "x" then r else (r & 0x7 | 0x8))).toString 16
|
8
|
+
)
|
9
|
+
uuid
|
@@ -0,0 +1,25 @@
|
|
1
|
+
App.VirtualClass = (classes...)->
|
2
|
+
classes.reduceRight (Parent, Child)->
|
3
|
+
class Child_Projection extends Parent
|
4
|
+
constructor: ->
|
5
|
+
# Temporary replace Child.__super__ and call original `constructor`
|
6
|
+
child_super = Child.__super__
|
7
|
+
Child.__super__ = Child_Projection.__super__
|
8
|
+
Child.apply @, arguments
|
9
|
+
Child.__super__ = child_super
|
10
|
+
|
11
|
+
# If Child.__super__ not exists, manually call parent `constructor`
|
12
|
+
unless child_super?
|
13
|
+
super
|
14
|
+
|
15
|
+
# Mixin prototype properties, except `constructor`
|
16
|
+
for own key of Child::
|
17
|
+
if Child::[key] isnt Child
|
18
|
+
Child_Projection::[key] = Child::[key]
|
19
|
+
|
20
|
+
# Mixin static properties, except `__super__`
|
21
|
+
for own key of Child
|
22
|
+
if Child[key] isnt Object.getPrototypeOf(Child::)
|
23
|
+
Child_Projection[key] = Child[key]
|
24
|
+
|
25
|
+
Child_Projection
|
@@ -0,0 +1,59 @@
|
|
1
|
+
module TemplateHelper
|
2
|
+
def template(model: nil, collection: nil, target_uuid: nil, &block)
|
3
|
+
model_name = if collection.present?
|
4
|
+
ActiveModel::Naming.singular(collection.first)
|
5
|
+
else
|
6
|
+
model
|
7
|
+
end
|
8
|
+
|
9
|
+
@kindred_hash ||= {}
|
10
|
+
@kindred_hash.merge!({
|
11
|
+
model_name => {
|
12
|
+
template: capture(&block),
|
13
|
+
collection: collection,
|
14
|
+
target_uuid: target_uuid,
|
15
|
+
}
|
16
|
+
})
|
17
|
+
self.controller.instance_variable_set(:@kindred_hash, @kindred_hash)
|
18
|
+
return nil
|
19
|
+
end
|
20
|
+
|
21
|
+
def target(object)
|
22
|
+
"data-target data-target-uuid=" + k_try(object, :uuid).to_s
|
23
|
+
end
|
24
|
+
|
25
|
+
def k_content_tag(element, attribute = nil, object = nil, content_or_options_with_block = nil, options = {}, escape = true, &block)
|
26
|
+
content_tag(element, nil, options.merge({data: { attr: attribute, k_uuid: k_try(object, :uuid), val: ""} }))
|
27
|
+
end
|
28
|
+
|
29
|
+
def k_hidden_field_tag(name, value=nil, object=nil, delegate_to=nil, options = {})
|
30
|
+
hidden_field_tag name, value, options.merge({data: { attr: name, k_uuid: k_try(object, :uuid), val: value } })
|
31
|
+
end
|
32
|
+
|
33
|
+
def k_text_field_tag(object, attribute, options={})
|
34
|
+
text_field_tag attribute, nil, options.merge({data: { attr: attribute, k_uuid: k_try(object, :uuid), val: "" } })
|
35
|
+
end
|
36
|
+
|
37
|
+
def k_check_box_tag(object, name, value=nil, checked = false, options = {})
|
38
|
+
check_box_tag name, value, checked, options.merge({data: { attr: name, k_uuid: k_try(object, :uuid), val: ""} })
|
39
|
+
end
|
40
|
+
|
41
|
+
def k_select_tag(object, name, option_tags = nil, options = {})
|
42
|
+
select_tag name, option_tags, options.merge(data: { attr: name, k_uuid: k_try(object, :uuid), val: "" })
|
43
|
+
end
|
44
|
+
|
45
|
+
def error_for(object, attribute)
|
46
|
+
tag("small", data: {error: "", k_uuid: '', attr: attribute}, class: "error")
|
47
|
+
end
|
48
|
+
|
49
|
+
def kindred_model_data
|
50
|
+
"<div data-kindred-model style='display:none;'></div>".html_safe
|
51
|
+
end
|
52
|
+
|
53
|
+
def k_try(object, method)
|
54
|
+
unless object.is_a?(Symbol)
|
55
|
+
object.try method
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
end
|
data/kindred.gemspec
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'kindred/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "kindred"
|
8
|
+
spec.version = Kindred::VERSION
|
9
|
+
spec.authors = ["Mike Piccolo"]
|
10
|
+
spec.email = ["mpiccolo@newleaders.com"]
|
11
|
+
spec.summary = %q{}
|
12
|
+
spec.description = %q{}
|
13
|
+
spec.homepage = ""
|
14
|
+
spec.license = "MIT"
|
15
|
+
|
16
|
+
spec.files = `git ls-files -z`.split("\x0")
|
17
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
18
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
|
+
spec.require_paths = ["lib"]
|
20
|
+
|
21
|
+
spec.add_runtime_dependency "rails", ">= 3.0"
|
22
|
+
spec.add_runtime_dependency "happy_place", ">= 0.0.6"
|
23
|
+
|
24
|
+
spec.add_development_dependency "bundler", "~> 1.7"
|
25
|
+
spec.add_development_dependency "rake", "~> 10.0"
|
26
|
+
end
|