kindred 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|