isPlainObject = require 'lodash/isPlainObject' defaultStyle = require './defaultStyle' ParsedError = require './ParsedError' nodePaths = require './nodePaths' RenderKid = require 'renderkid' merge = require 'lodash/merge' arrayUtils = pluckByCallback: (a, cb) -> return a if a.length < 1 removed = 0 for value, index in a if cb value, index removed++ continue if removed isnt 0 a[index - removed] = a[index] if removed > 0 a.length = a.length - removed a pluckOneItem: (a, item) -> return a if a.length < 1 reached = no for value, index in a if not reached if value is item reached = yes continue else a[index - 1] = a[index] a.length = a.length - 1 if reached a instance = null module.exports = class PrettyError self = @ @_filters: 'module.exports': (item) -> return unless item.what? item.what = item.what.replace /\.module\.exports\./g, ' - ' return @_getDefaultStyle: -> defaultStyle() @start: -> unless instance? instance = new self instance.start() instance @stop: -> instance?.stop() constructor: -> @_useColors = yes @_maxItems = 50 @_packagesToSkip = [] @_pathsToSkip = [] @_skipCallbacks = [] @_filterCallbacks = [] @_parsedErrorFilters = [] @_aliases = [] @_renderer = new RenderKid @_style = self._getDefaultStyle() @_renderer.style @_style start: -> @_oldPrepareStackTrace = Error.prepareStackTrace prepeare = @_oldPrepareStackTrace or (exc, frames) -> result = exc.toString() frames = frames.map (frame) -> " at #{frame.toString()}" result + "\n" + frames.join "\n" # https://code.google.com/p/v8/wiki/JavaScriptStackTraceApi Error.prepareStackTrace = (exc, trace) => stack = prepeare.apply(null, arguments) @render {stack, message: exc.toString().replace /^.*: /, ''}, no @ stop: -> Error.prepareStackTrace = @_oldPrepareStackTrace @_oldPrepareStackTrace = null config: (c) -> if c.skipPackages? if c.skipPackages is no @unskipAllPackages() else @skipPackage.apply @, c.skipPackages if c.skipPaths? if c.skipPaths is no @unskipAllPaths() else @skipPath.apply @, c.skipPaths if c.skip? if c.skip is no @unskipAll() else @skip.apply @, c.skip if c.maxItems? @setMaxItems c.maxItems if c.skipNodeFiles is yes @skipNodeFiles() else if c.skipNodeFiles is no @unskipNodeFiles() if c.filters? if c.filters is no @removeAllFilters() else @filter.apply @, c.filters if c.parsedErrorFilters? if c.parsedErrorFilters is no @removeAllParsedErrorFilters() else @filterParsedError.apply @, c.parsedErrorFilters if c.aliases? if isPlainObject c.aliases @alias path, alias for path, alias of c.aliases else if c.aliases is no @removeAllAliases() @ withoutColors: -> @_useColors = false @ withColors: -> @_useColors = true @ skipPackage: (packages...) -> @_packagesToSkip.push String pkg for pkg in packages @ unskipPackage: (packages...) -> arrayUtils.pluckOneItem(@_packagesToSkip, pkg) for pkg in packages @ unskipAllPackages: -> @_packagesToSkip.length = 0 @ skipPath: (paths...) -> @_pathsToSkip.push path for path in paths @ unskipPath: (paths...) -> arrayUtils.pluckOneItem(@_pathsToSkip, path) for path in paths @ unskipAllPaths: -> @_pathsToSkip.length = 0 @ skip: (callbacks...) -> @_skipCallbacks.push cb for cb in callbacks @ unskip: (callbacks...) -> arrayUtils.pluckOneItem(@_skipCallbacks, cb) for cb in callbacks @ unskipAll: -> @_skipCallbacks.length = 0 @ skipNodeFiles: -> @skipPath.apply @, nodePaths unskipNodeFiles: -> @unskipPath.apply @, nodePaths filter: (callbacks...) -> @_filterCallbacks.push cb for cb in callbacks @ removeFilter: (callbacks...) -> arrayUtils.pluckOneItem(@_filterCallbacks, cb) for cb in callbacks @ removeAllFilters: -> @_filterCallbacks.length = 0 @ filterParsedError: (callbacks...) -> @_parsedErrorFilters.push cb for cb in callbacks @ removeParsedErrorFilter: (callbacks...) -> arrayUtils.pluckOneItem(@_parsedErrorFilters, cb) for cb in callbacks @ removeAllParsedErrorFilters: -> @_parsedErrorFilters.length = 0 @ setMaxItems: (maxItems = 50) -> if maxItems is 0 then maxItems = 50 @_maxItems = maxItems|0 @ alias: (stringOrRx, alias) -> @_aliases.push {stringOrRx, alias} @ removeAlias: (stringOrRx) -> arrayUtils.pluckByCallback @_aliases, (pair) -> pair.stringOrRx is stringOrRx @ removeAllAliases: -> @_aliases.length = 0 @ _getStyle: -> @_style appendStyle: (toAppend) -> merge @_style, toAppend @_renderer.style toAppend @ _getRenderer: -> @_renderer render: (e, logIt = no, useColors = @_useColors) -> obj = @getObject e rendered = @_renderer.render(obj, useColors) console.error rendered if logIt is yes rendered getObject: (e) -> unless e instanceof ParsedError e = new ParsedError e @_applyParsedErrorFiltersOn e header = title: do -> ret = {} # some errors are thrown to display other errors. # we call them wrappers here. if e.wrapper isnt '' ret.wrapper = "#{e.wrapper}" ret.kind = e.kind ret colon: ':' message: String(e.message).trim() traceItems = [] count = -1 for item, i in e.trace continue unless item? continue if @_skipOrFilter(item, i) is yes count++ break if count > @_maxItems if typeof item is 'string' traceItems.push item: custom: item continue traceItems.push do -> markupItem = item: header: pointer: do -> return '' unless item.file? file: item.file colon: ':' line: item.line footer: do -> foooter = addr: item.shortenedAddr if item.extra? then foooter.extra = item.extra foooter markupItem.item.header.what = item.what if typeof item.what is 'string' and item.what.trim().length > 0 markupItem obj = 'pretty-error': header: header if traceItems.length > 0 obj['pretty-error'].trace = traceItems obj _skipOrFilter: (item, itemNumber) -> if typeof item is 'object' return yes if item.modName in @_packagesToSkip return yes if item.path in @_pathsToSkip for modName in item.packages return yes if modName in @_packagesToSkip if typeof item.shortenedAddr is 'string' for pair in @_aliases item.shortenedAddr = item.shortenedAddr.replace pair.stringOrRx, pair.alias for cb in @_skipCallbacks return yes if cb(item, itemNumber) is yes for cb in @_filterCallbacks cb(item, itemNumber) return no _applyParsedErrorFiltersOn: (error) -> for cb in @_parsedErrorFilters cb error return for prop in ['renderer', 'style'] then do -> methodName = '_get' + prop[0].toUpperCase() + prop.substr(1, prop.length) PrettyError::__defineGetter__ prop, -> do @[methodName]