ORIENT/modules/system/assets/js/eventlogs/exception-beautifier.js

366 lines
15 KiB
JavaScript

/*
* Exception Beautifier plugin
*/
+function ($) {
"use strict";
var ExceptionBeautifier = function (el, options) {
var self = this
self.$el = $(el)
self.options = options || {}
// Init
self.init()
}
ExceptionBeautifier.DEFAULTS = {}
ExceptionBeautifier.REGEX = {
phpline: /^(#[0-9]+)\s+(.+\.php)(?:\(([0-9]+)\))?\s*:(.*)/,
artisan: /^(#[0-9]+)\s+(.+artisan)(?:\(([0-9]+)\))?\s*:(.*)/,
internalLine: /^(#[0-9]+)\s+(\[internal function\]\s*:)(.*)/,
defaultLine: /^(#[0-9]+)\s*(.*)/,
className: /([a-z0-9]+\\[a-z0-9\\]+(?:\.\.\.)?)/gi,
filePath: /((?:[A-Z]:)?(?:[\\\/][\w\.-_~@%]+)\.(?:php|js|css|less|yaml|txt|ini))(\(([0-9]+)\)|:([0-9]+)|\s|$)/gi,
staticCall: /::([^( ]+)\(([^()]*|(?:[^(]*\(.+\)[^)]*))\)/,
functionCall: /->([^(]+)\(([^()]*|(?:[^(]*\(.+\)[^)]*))\)/,
closureCall: /\{closure\}\(([^()]*|(?:[^(]*\(.+\)[^)]*))\)/
}
ExceptionBeautifier.extensions = []
ExceptionBeautifier.prototype.init = function () {
var self = this,
markup
ExceptionBeautifier.extensions.forEach(function (extension) {
if (typeof extension.onInit === 'function') {
extension.onInit(self)
}
})
markup = self.parseSource(self.$el.text())
self.$el
.addClass('plugin-exception-beautifier')
.empty()
.append(markup)
}
ExceptionBeautifier.prototype.parseSource = function (raw) {
var self = this,
source = raw,
markup = {lines: []},
start = 0,
end
/*
* We only heavily parse stacktrace messages.
* Standard messages are only applied a simple transform : newline to <br> and tab/spaces indentation to &nbsp;
*/
if (source.indexOf('Stack trace:') < 0) {
source = '{exception-beautifier-message-container}{exception-beautifier-message}' + self.formatMessage(source) + '{/exception-beautifier-message}{/exception-beautifier-message-container}'
} else {
end = source.indexOf('Stack trace:', start)
markup.message = source.substring(start, end)
start = source.indexOf('#', end)
while ((end = source.indexOf('#', start + 1)) > 0) {
markup.lines.push(self.parseLine(source.substring(start, end)))
start = end
}
markup.lines.push(self.parseLine(source.substring(start)))
source = '{exception-beautifier-message-container}' +
'{exception-beautifier-message}' + self.formatMessage(markup.message) + '{/exception-beautifier-message}' +
'{/exception-beautifier-message-container}' +
'{exception-beautifier-stacktrace#div}'
markup.lines.forEach(function (line) {
source += '{exception-beautifier-stacktrace-line}' + self.formatStackTraceLine(line) + '{/exception-beautifier-stacktrace-line}'
})
source += '{/exception-beautifier-stacktrace#div}'
ExceptionBeautifier.extensions.forEach(function (extension) {
if (typeof extension.onParse === 'function') {
extension.onParse(self)
}
})
}
markup = $(self.buildMarkup('{exception-beautifier-container}' + source + '{/exception-beautifier-container}'))
return self.finalizeMarkup(markup, raw)
}
ExceptionBeautifier.prototype.parseLine = function (str) {
var line = {},
matches
if ((matches = str.match(ExceptionBeautifier.REGEX.phpline)) || (matches = str.match(ExceptionBeautifier.REGEX.artisan))) {
line.type = 'phpline'
line.number = $.trim(matches[1])
line.file = $.trim(matches[2])
line.lineNumber = $.trim(matches[3])
line.function = $.trim(matches[4])
}
else if (matches = str.match(ExceptionBeautifier.REGEX.internalLine)) {
line.type = 'internal'
line.number = $.trim(matches[1])
line.internal = $.trim(matches[2])
line.function = $.trim(matches[3])
} else if (matches = str.match(ExceptionBeautifier.REGEX.defaultLine)) {
line.type = 'default'
line.number = $.trim(matches[1])
line.function = $.trim(matches[2])
}
return line
}
ExceptionBeautifier.prototype.formatMessage = function (str) {
var self = this
return self.formatLineCode(
str
.replace(/^\s+/, '')
.replace(/\r\n|\r|\n/g, '{x-newline}')
.replace(/\t| {2}/g, '{x-tabulation}')
)
}
ExceptionBeautifier.prototype.formatFilePath = function (path, line) {
return '{exception-beautifier-file}' + path + '{/exception-beautifier-file}'
}
ExceptionBeautifier.prototype.formatStackTraceLine = function (line) {
var self = this
if (line.function) {
line.function = self.formatLineCode(line.function)
}
switch (line.type) {
case 'phpline':
return '{exception-beautifier-stacktrace-line-number}' + line.number + '{/exception-beautifier-stacktrace-line-number}' +
self.formatFilePath(line.file, line.lineNumber) +
'{exception-beautifier-line-number}(' + line.lineNumber + '):{/exception-beautifier-line-number} ' +
'{exception-beautifier-stacktrace-line-function}' + line.function + '{/exception-beautifier-stacktrace-line-function}'
case 'internal':
return '{exception-beautifier-stacktrace-line-number}' + line.number + '{/exception-beautifier-stacktrace-line-number}' +
'{exception-beautifier-stacktrace-line-internal}' + line.internal + '{/exception-beautifier-stacktrace-line-internal}' +
'{exception-beautifier-stacktrace-line-function}' + line.function + '{/exception-beautifier-stacktrace-line-function}'
case 'default':
return '{exception-beautifier-stacktrace-line-number}' + line.number + '{/exception-beautifier-stacktrace-line-number}' +
'{exception-beautifier-stacktrace-line-function}' + line.function + '{/exception-beautifier-stacktrace-line-function}'
}
return ''
}
ExceptionBeautifier.prototype.formatLineCode = function (str) {
var self = this
if (str.match(/^\s*(call_user_func|spl_autoload_call)/)) {
str = str.replace(/^\s*(?:call_user_func|spl_autoload_call)([^(]*)\((.*)\)/, function (str, suffix, parameters) {
return '{exception-beautifier-system-function}call_user_func' + suffix + '({/exception-beautifier-system-function}' +
self.formatFunctionParameters(parameters) +
'{exception-beautifier-system-function}){/exception-beautifier-system-function}'
})
} else if (str.match(ExceptionBeautifier.REGEX.closureCall)) {
str = str.replace(ExceptionBeautifier.REGEX.closureCall, function (str, parameters) {
return '{exception-beautifier-function}{closure}({/exception-beautifier-function}' +
self.formatFunctionParameters(parameters) +
'{exception-beautifier-function}){/exception-beautifier-function}'
})
} else if (str.match(ExceptionBeautifier.REGEX.functionCall)) {
str = str.replace(ExceptionBeautifier.REGEX.functionCall, function (str, functionName, parameters) {
return '{exception-beautifier-function}→' + functionName + '({/exception-beautifier-function}' +
self.formatFunctionParameters(parameters) +
'{exception-beautifier-function}){/exception-beautifier-function}'
})
} else if (str.match(ExceptionBeautifier.REGEX.staticCall)) {
str = str.replace(ExceptionBeautifier.REGEX.staticCall, function (str, functionName, parameters) {
return '{exception-beautifier-function}::' + functionName + '({/exception-beautifier-function}' +
self.formatFunctionParameters(parameters) +
'{exception-beautifier-function}){/exception-beautifier-function}'
})
}
str = str.replace(ExceptionBeautifier.REGEX.filePath, function (str, path, line, lineNumber, altLineNumber) {
return self.formatFilePath(path, (lineNumber || '') + (altLineNumber || '')) +
($.trim(line).length > 0 ? ('{exception-beautifier-line-number}' + line + '{/exception-beautifier-line-number}') : ' ')
})
str = str.replace(ExceptionBeautifier.REGEX.className, function (str, name) {
return '{exception-beautifier-class}' + name + '{/exception-beautifier-class}'
})
return str
}
ExceptionBeautifier.prototype.formatFunctionParameters = function (parameters) {
return parameters
.replace(/^([0-9]+)|([^a-z\\])([0-9]+)$|^([0-9]+)([^a-z\\])|([^a-z\\])([0-9]+)([^a-z\\])/g, '$2$6{exception-beautifier-number}$1$3$4$7{/exception-beautifier-number}$5$8')
.replace(/^Array$|([^a-z\\])Array$|^Array([^a-z\\])|([^a-z\\])Array([^a-z\\])/g, '$1$3{exception-beautifier-code}Array{/exception-beautifier-code}$2$4')
.replace(/^Closure$|(\()Closure(\))/g, '$1{exception-beautifier-code}Closure{/exception-beautifier-code}$2')
.replace(/Object\(([^)]+)\)/g, '{exception-beautifier-code}Object({/exception-beautifier-code}$1{exception-beautifier-code}){/exception-beautifier-code}')
.replace(/"((?:\\.|[^"])*)"/g, '{exception-beautifier-string}"$1"{/exception-beautifier-string}')
.replace(/'((?:\\.|[^'])*)'/g, '{exception-beautifier-string}\'$1\'{/exception-beautifier-string}')
}
ExceptionBeautifier.prototype.buildMarkup = function (str) {
var self = this,
start = str.indexOf('{exception-beautifier-'),
cssOffset = 'exception-beautifier-'.length,
end, endtag, tmp, matches, tag, html, css, attrs, markup = ''
if (start >= 0) {
if (start > 0) {
markup += self.buildMarkup(str.substring(0, start))
}
while (start >= 0) {
end = endtag = str.indexOf('}', start)
if ((tmp = str.indexOf(' ', start)) >= 0) {
end = Math.min(end, tmp)
}
tag = str.substring(start + 1, end)
end = str.indexOf('{/' + tag + '}', start)
start = str.indexOf('}', start)
if (end < 0) {
throw 'Markup error tag {' + tag + '} not closed'
}
html = 'span'
attrs = ''
css = tag
if (matches = tag.match(/(.+)#([a-z]+)$/)) {
css = matches[1]
html = matches[2]
}
css = 'beautifier-' + css.substr(cssOffset)
if (tmp >= 0 && tmp < endtag) {
attrs = str.substring(tmp, endtag)
}
markup += '<' + html + ' class="' + css + '"' + attrs + '>'
markup += self.buildMarkup(str.substring(start + 1, end))
markup += '</' + html + '>'
end = end + ('{/' + tag + '}').length
start = str.indexOf('{exception-beautifier-', end)
if (start > end || start < 0) {
markup += self.buildMarkup(str.substring(end, start < 0 ? undefined : start))
}
}
} else {
markup += $.oc.escapeHtmlString(str)
.replace(/\{x-newline\}/g, '<br>')
.replace(/\{x-tabulation\}/g, '&nbsp;&nbsp;')
}
return markup
}
ExceptionBeautifier.prototype.finalizeMarkup = function (markup, source) {
var stacktrace,
messageContainer,
tabs
markup.find('.beautifier-file').each(function () {
$(this).find('.beautifier-class').each(function () {
var $el = $(this)
$el.replaceWith($el.text())
})
})
markup.find('.beautifier-file+.beautifier-line-number').each(function () {
var $el = $(this)
$el.appendTo($el.prev())
})
messageContainer = markup.find('.beautifier-message-container')
stacktrace = markup.find('.beautifier-stacktrace')
.addClass('hidden')
if (!!stacktrace.length) {
$('<a class="beautifier-toggle-stacktrace" href="javascript:;"><span>' + $.oc.lang.get('eventlog.show_stacktrace') + '</span></a>')
.appendTo(messageContainer)
.on('click', function (event) {
var $el = $(this)
event.preventDefault()
event.stopPropagation()
$('.beautifier-stacktrace', markup).toggleClass('hidden')
$el.hide()
})
}
tabs = $('<div class="control-tabs content-tabs tabs-inset">' +
'<ul class="nav nav-tabs">' +
'<li class="active"><a href="#beautifier-tab-formatted">' + $.oc.lang.get('eventlog.tabs.formatted') + '</a></li>' +
'<li><a href="#beautifier-tab-raw">' + $.oc.lang.get('eventlog.tabs.raw') + '</a></li>' +
'</ul><div class="tab-content">' +
'<div class="tab-pane pane-inset active" id="beautifier-tab-formatted"></div>' +
'<div class="tab-pane pane-inset" id="beautifier-tab-raw"></div>' +
'</div></div>')
if (source.indexOf('Message-ID:') > 0) {
markup = '<div class="beautifier-formatted-content">' + source.trim().replace(/\r\n|\r|\n/g, '<br>').replace(/ {2}/g, '&nbsp;&nbsp;') + '</div>'
}
tabs.find('#beautifier-tab-formatted').append(markup)
tabs.find('#beautifier-tab-raw').append('<div class="beautifier-raw-content">' + $.oc.escapeHtmlString(source.trim()).replace(/\r\n|\r|\n/g, '<br>').replace(/ {2}/g, '&nbsp;&nbsp;') + '</div>')
tabs.ocTab({closable: false})
return tabs
}
// EXCEPTION BEAUTIFIER PLUGIN DEFINITION
// ============================
$.fn.exceptionBeautifier = function (option) {
var args = arguments,
result
this.each(function () {
var $this = $(this)
var data = $this.data('oc.exceptionBeautifier')
var options = $.extend({}, ExceptionBeautifier.DEFAULTS, $this.data(), typeof option == 'object' && option)
if (!data) $this.data('oc.exceptionBeautifier', (data = new ExceptionBeautifier(this, options)))
if (typeof option == 'string') result = data[option].call($this)
if (typeof result != 'undefined') return false
})
return result ? result : this
}
$.fn.exceptionBeautifier.Constructor = ExceptionBeautifier
$(document).render(function () {
$('[data-plugin="exception-beautifier"]').exceptionBeautifier()
})
}(window.jQuery)