UNPKG

wizkit

Version:

The functional programming programming utility and terminal logger.

888 lines (784 loc) 29.4 kB
/* Wizkit is a helpfull program to aid in the development of javascript programs by providing functional programming techniques and logging enhancment. Copyright (C) 2016 Robert Edward Steckroth II <RobertSteckroth@gmail.com> This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see <http://www.gnu.org/licenses/>. Program author: Robert Edward Steckroth II <RobertSteckroth@gmail.com> */ // TODO: add overide_msg field // TODO: callbacks need property getting recondition for error_cb paramters var Colors_Safe = null var BrowserColors = function() { // This will replace the nodejs terminal color output with browser complient console.log modify_string. this._css_arguments = [] this.default_color = "grey" this.theme = "light" } BrowserColors.prototype = { // Empty the array anytime it is retrived get css_arguments() { return this._css_arguments }, remove_output: function() { return this._css_arguments.splice(0) }, format: function(args) { this._css_arguments.push("color: "+this.theme_transform(args.callee.name, this.theme)) this._css_arguments.push("color: "+this.theme_transform(this.default_color, this.theme)) return "%c"+args[0].toString()+"%c" }, theme_transform: function(c, announce) { if ( announce === "light" ) return (c === "white" && "black") || (c === "magenta" && "#682f01") || // string in native (c === "yellow" && "#a1ab00") || // undefine in native (c === "cyan" && "cyan") || // null in native c else return c }, white: function white() { return this.format(arguments) }, grey: function grey() { return this.format(arguments) }, cyan: function cyan() { return this.format(arguments) }, magenta: function magenta() { return this.format(arguments) }, red: function red() { return this.format(arguments) }, blue: function blue() { return this.format(arguments) }, yellow: function yellow() { return this.format(arguments) }, green: function green() { return this.format(arguments) }, black: function black() { return this.format(arguments) }, } var Wizkit = function(args) { this.log_title = null var settings = null for ( var i = 0; i < arguments.length; i++ ) // If one of the arguments is an arguments object it will have a length too if ( typeof arguments[i] === "object" && arguments[i].length ) { for ( var x = 0; x < arguments[i].length; x++ ) if ( typeof arguments[i][x] === "string" && !this.log_title ) this.log_title = arguments[i][x] else if ( arguments[i][x].is_wizkit ) settings = arguments[i][x].mutable_options } else if ( typeof arguments[i] === "string" ) { this.log_title = arguments[i] } else if ( arguments[i].is_wizkit ) { settings = arguments[i].mutable_options } if ( !settings ) settings = this.mutable_options for ( var n in settings ) { if ( n === "log_title" && this.log_title ) continue this[n] = settings[n] } } Wizkit.prototype = { set_option: function() { return new this.Logger().set_option.apply(this, arguments) }, set debug(val) { this._debug = val }, get debug() { return this._debug }, get text() { return function() { return "" } }, set use_color(val) { this._use_color = val }, get use_color() { return this._use_color }, get max_line_characters() { return this._max_line_characters }, set max_line_characters(val) { this._max_line_characters = val }, get log_title() { return this._log_title }, set log_title(val) { this._log_title = val }, get compress_level() { return this._compress_level }, set compress_level(val) { this._compress_level = val }, get use_title() { return this._use_title }, set use_title(val) { this._use_title = val }, get indentation_string() { return this._indentation_string }, set indentation_string(val) { this._indentation_string = val }, get is_wizkit() { return this instanceof Wizkit }, get contains_text() { // If there is any output availible including newlines, spaces and/or tabs or characters return false }, escape: function(obj, def) { // a generic escaping function // If the obj is not a string or returns empty, then return the default value or the original object if no default value was provided. if ( typeof obj === "string" && obj.length > 0 ) return obj.replace(/[\ \'\"\!\@\#\$\%\^\&\*\(\)\+\;\:\?\>\<\[\]\{\}\`\~\/\\]/g, '_').replace(/_+/g, '_') //"' quoate is for Bitbucket parsing fix with web page source display else if ( arguments.length > 1 ) return def else return obj }, new_copy: function() { return new this.Logger().new_copy.apply(this, arguments) }, get mutable_options() { return new this.Logger().mutable_options.call(this) }, FakeLogger: function() { // The FakeLogger is used to disable logging when debug is set to false (it is efficiant this way). var This = this var f = function() { return This } for ( var o in Wizkit.prototype.Logger.prototype ) if ( o.substr(0,4) !== "log_" ) // Things like "log_true" and "log_false" should never be fake this[o] = f this.__defineGetter__("log_false", function() { return function() { return false } }) this.__defineGetter__(o, function() { return f }) this.__defineGetter__("log_true", function() { return function() { return true } }) this["toString"] = function() { } // prevent reduntant parsing when an always logger is passed into a logger with debuging set to true or false }, Logger: function(settings) { // The Logger constructer is sub-classed here in Wizkit if ( settings ) for ( var n in settings ) this[n] = settings[n] this.contains_text = false this._last_command = undefined this._output = "" this._colorless_output = "" this._cache = [] this._ignore_separater = false if ( Wizkit.is_node_run ) this._colors = Colors_Safe else this._colors = new BrowserColors() }, get PROGRAM_POINT() { return new Error().stack.split("\n").pop().replace(/ */, "") }, protect_callbacks: function() { for ( var o in arguments ) { if ( typeof arguments[o] === "function" ) arguments[o].__defineGetter__("never_switch_proto_flag", function() { return true }) for ( var i in arguments[o] ) if ( typeof arguments[o][i] === "function" ) // These will not allow the prototype to be switch with wizkit callback generators arguments[o][i].__defineGetter__("never_switch_proto_flag", function() { return true }) } }, /* unset_callbacks_protection: function() { for ( var o in arguments ) { if ( typeof arguments[o] === "function" && arguments[o]["never_switch_proto_flag"] ) delete arguments[o]["never_switch_proto_flag"] // Remove the getter from the object for ( var i in arguments[o] ) if ( typeof arguments[o][i] === "function" && arguments[o][i]["never_switch_proto_flag"] ) delete arguments[o][i]["never_switch_proto_flag"] // Remove the getter from the function } }, */ get always() { var This = this return function() { return new This.Logger(This.mutable_options)._log_option({"ignore_separater": true}) } }, get pass() { var This = this return function(bool) { return This.debug === "verbose" && new This.Logger(This.mutable_options)._log_option({"ignore_separater": true}) || new This.FakeLogger() } }, get clear() { // Does nothing at the root level of the logger object var This = this return function() { return This.pass() } }, get log() { // Provides constructer based namespace for the Logger to be used with Wizkit var This = this return function() { return This.debug === "verbose" && new This.Logger(This.mutable_options).log.apply(This, arguments) || new This.FakeLogger() } }, get line() { var This = this return function() { return This.debug === "verbose" && new This.Logger(This.mutable_options).line.apply(This, arguments) || new This.FakeLogger() } }, get add_line() { var This = this return function() { return This.debug === "verbose" && new This.Logger(This.mutable_options).add_line.apply(This, arguments) || new This.FakeLogger() } }, get add() { return function() { var This = this return This.debug === "verbose" && new This.Logger(This.mutable_options).add.apply(This, arguments) || new This.FakeLogger() } }, get sp() { var This = this return function() { return This.debug === "verbose" && new This.Logger(This.mutable_options)._log_option({"ignore_separater": true}).sp.apply(This, arguments) || new This.FakeLogger() } }, get tab() { var This = this return function() { return This.debug === "verbose" && new This.Logger(This.mutable_options)._log_option({"ignore_separater": true}).tab.apply(This, arguments) || new This.FakeLogger() } }, get space() { var This = this return function() { return This.debug === "verbose" && new This.Logger(This.mutable_options)._log_option({"ignore_separater": true}).space.apply(This, arguments) || new This.FakeLogger() } }, get cb() { // A simple alias return this.success_cb }, reorder_arguments: function(args) { var arg_func = arg_obj = arg_msg = null for ( var x in args ) { if ( typeof args[x] === "function" ) { arg_func = args[x] for ( var n in args ) { if ( n === x ) { continue } else if ( typeof args[n] === "string" || (args[n] instanceof Wizkit.prototype.Logger) ) { arg_msg = args[n] } else if ( typeof args[n] === "object" ) { arg_obj = args[n] } } break } } args[0] = arg_func args[1] = arg_obj args[2] = arg_msg }, success_cb: function(f, proto_accept, msg) { this.reorder_arguments(arguments) // The msg is the only part of the callback which can be re-set after a cal to the callback setting if ( f && f._is_wizkit_callback ) { // Set the msg to the one passed in or use the msg already set to the callback. f.msg = msg || f.msg // If the function passed in was aready set with wizkit then return here after changing the msg return f } // This will be the wrapped function around the one that was passed in var rf = function() { if ( typeof f === "function" ) { f.pro = f.pro || {} // Better safe than sorry with multi-level object comparisons // msg is availible inside the prototype and outside as a function object property if ( rf.msg instanceof Wizkit.prototype.Logger ) { f.pro.msg = rf.msg.text() } else { f.pro.msg = rf.msg } // f.pro holds the prototype of the passed in function f.apply(f.pro, arguments) } // This returns true as a convenience functionality-wise. It is not integral to this script. return true } if ( typeof f === "function" ) { //var f.length // We make sure that the number or paramters this function accepts is the same as the passed in function Object.defineProperty(rf, "len", {value: f.length}) if ( !f["never_switch_proto_flag"] ) { // Do not allow for prototype switching if this flag is set f.pro = (typeof proto_accept === "object" && proto_accept) || f.prototype } else if ( typeof proto_accept === "object" ) { f.pro = f.prototype var output = this.debug !== false && this.always() || this.pass() output.line("<: Error "+output.PROGRAM_POINT).line().tab("Warning message:") .sp("an attempt to switch the callback of a function with the [never_switch_proto_flag] property set was made. This is not harmfull but may be unintended by the programer.").log() } } rf._is_wizkit_callback = true rf.msg = msg return rf }, error_cb: function(f, proto_accept, msg) { this.reorder_arguments(arguments) // The msg is the only part of the callback which can be re-set after a cal to the callback setting if ( f && f._is_wizkit_callback ) { f.msg = msg || f.msg return f } var output = this.debug !== false && this.always() || this.pass() // Output needs to correspond to the debug state logger var rf = function() { // Return a wrapper for the passed into function (f) which gets called whether f is undefind or not output.line("<: Error "+output.PROGRAM_POINT).line() var new_args = [] for ( var o in arguments ) { // Handle all of the arguments passed into the callback if ( !arguments[o] ) continue output.line().tab("Error message:").sp(arguments[o]) if ( (arguments[o] instanceof Error) || (arguments[o] instanceof Wizkit) ) // throw the parameter if it is on of the Error types new_args.push(arguments[o].toString()) else new_args.push(arguments[o]) } output.log().clear() if ( typeof f === "function" ) { f.pro = f.pro || {} // Better safe than sorry with multi-level object comparisons if ( rf.msg instanceof Wizkit.prototype.Logger ) { f.pro.msg = rf.msg.text() } else { f.pro.msg = rf.msg } f.apply(f.pro, new_args) } return false } if ( typeof f === "function" ) { // We make sure that the number or paramters this function accepts is the same as the passed in function Object.defineProperty(rf, "len", {value: f.length}) if ( !f["never_switch_proto_flag"] ) { // Do not allow for prototype switching if this flag is set f.pro = (typeof proto_accept === "object" && proto_accept) || f.prototype } else if ( typeof proto_accept === "object" ) { f.pro = f.prototype var output = this.debug !== false && this.always() || this.pass() output.line("<: Error "+output.PROGRAM_POINT).line().tab("Warning message:") .sp("an attempt to switch the callback of a function with the [never_switch_proto_flag] property set was made.", "This is not harmfull but might be unintended by the programer.").log() } } rf.__defineGetter__("is_wizkit_callback", function() { return true }) rf.msg = msg return rf }, } Wizkit.prototype.Logger.prototype = { get is_wizkit() { return (this instanceof Wizkit.prototype.Logger) // Just to check if the object is a Wizkit object }, get toString() { var This = this return function() { return This._output } }, get text() { var This = this return function() { return This._colorless_output } }, set_option: function() { for ( var i = 0; i < arguments.length; i++ ) for ( var n in arguments[i] ) if ( this[n] ) this[n] = arguments[i][n] return this }, new_copy: function() { var passed_wizkit = this for ( var x in arguments.length ) { if ( arguments[x].is_wizkit ) { //passed_wizkit = arguments[x] break } } arguments[arguments.length] = passed_wizkit arguments.length++ return new Wizkit(arguments) }, mutable_options: function() { return { debug: typeof this.debug !== "undefined" ? this.debug : "verbose", use_color: typeof this.use_color !== "undefined" ? !!this.use_color : true, max_line_characters: typeof this.max_line_characters !== "undefined" ? (parseInt(this.max_line_characters) || 300) : 300, log_title: typeof this.log_title !== "undefined" ? this.log_title : "", compress_level: typeof this.compress_level !== "undefined" ? parseInt(this.compress_level) : 1, use_title: typeof this.use_title !== "undefined" ? !!this.use_title : true, indentation_string: typeof this.indentation_string !== "undefined" ? this.indentation_string.toString() : " " } }, get colors() { return this._colors }, set debug(val) { this._debug = val }, get debug() { return this._debug }, get max_line_characters() { return this._max_line_characters }, set max_line_characters(val) { this._max_line_characters = val }, get log_title() { return this._log_title }, set log_title(val) { this._log_title = val }, get compress_level() { return this._compress_level }, set compress_level(val) { this._compress_level = val }, get use_title() { return this._use_title }, set use_title(val) { this._use_title = val }, get indentation_string() { return this._indentation_string }, set indentation_string(val) { this._indentation_string = val }, get PROGRAM_POINT() { return new Error().stack.split("\n").pop().replace(/ */, "") }, get pass() { var This = this return function() { return This } }, get clear() { var This = this return function() { This._output = "" This._colorless_output = "" if ( !Wizkit.is_node_run ) this.colors.remove_output() This.contains_text = false return This } }, get sp() { // Alias for space this.last_command = "sp" var This = this return function() { var args = Array.prototype.slice.call(arguments) args.push(" ") return This._chain.apply(This, args) } }, get space() { this.last_command = "space" var This = this return function() { var args = Array.prototype.slice.call(arguments) args.push(" ") return This._chain.apply(This, args) } }, get add_space() { this.last_command = "add_space" var This = this return function() { var args = Array.prototype.slice.call(arguments) args.push(" ") return This._chain.apply(This, args) } }, get add_tab() { // This does not function in gecko browsers this.last_command = "add_tab" var This = this return function() { var args = Array.prototype.slice.call(arguments) args.push("\t\t") return This._chain.apply(This, args) } }, get tab() { this.last_command = "tab" var This = this return function() { var args = Array.prototype.slice.call(arguments) args.push("\t\t") return This._chain.apply(This, args) } }, get add_line() { // Each segement of log_line will be on a seperate line this.last_command = "add_line" var This = this return function() { var args = Array.prototype.slice.call(arguments) args.push("\n") return This._chain.apply(This, args) } }, get line() { // Alias for add_line this.last_command = "line" var This = this return function() { var args = Array.prototype.slice.call(arguments) args.push("\n") return This._chain.apply(This, args) } }, // All other printing commands should call this get add() { this.last_command = "add" var This = this return function() { var args = Array.prototype.slice.call(arguments) args.push("") return This._chain.apply(This, args) } }, get _chain() { var This = this return function() { This._cache = [] if ( !arguments.length ) return This var args_len = arguments.length if ( !This.ignore_separater && args_len === 1 ) { This._output += arguments[0] This._colorless_output += arguments[0] } for ( var i = 0; i < args_len-1; i++ ) { var separater = "" if ( !!This.last_command && !This.ignore_separater && !(typeof arguments[i] === "string" && !arguments[i].length) ) separater = arguments[args_len-1] This._output += separater This._colorless_output += separater var outp = This.stringify_object(arguments[i], this.indentation_string) This._output += outp if ( !This.use_color ) { This._colorless_output += outp } else { This.use_color = false This._cache = [] This._colorless_output += This.stringify_object(arguments[i], this.indentation_string) This.use_color = true } This.ignore_separater = false This.contains_text = true } return This } }, get log() { var This = this return function() { if ( arguments.length > 0 ) { // Call the last command used before this log again. if ( This[This.last_command] ) { This[This.last_command].apply(This, arguments) } else { This._log_option({"ignore_separater": true}).space.apply(This, arguments) } } if ( This._output ) { // Append the logging title if there is one set and then turn the entire output string // into an array to pass with the console.log.apply // var title_str = This.log_title ? this.colors.blue("[")+This.log_title+this.colors.blue("] ") : "" var title_str = This.log_title && This.use_title ? "["+This.log_title+"] " : "" var c_args = [title_str+This._output] // append any css arguments if the browser segment of our custom colors object was utilized if ( This.colors instanceof BrowserColors ) c_args = c_args.concat(This.colors.css_arguments) console.log.apply(console, c_args) // TODO: document.write and other printing methods? } return This } }, get log_false() { var This = this return function() { This.log.apply(This, arguments) return false } }, get log_true() { var This = this return function() { This.log.apply(This, arguments) return true } }, _log_option: function(option) { // Pretty much an internal function for ( var o in option ) this[o] = option[o] return this }, stringify_object: function(msg, indent_string, compound) { // compound will be placed at the start of each new object in the output (used internally) var Buffer = Buffer || function(){} indent_string = typeof indent_string === "string" && indent_string || " " compound = compound || "" this.use_quotes = this.use_quotes || false if ( this.compress_level > 3 ) compress_sep = "" else if ( this.compress_level === 3 ) compress_sep = " " else if ( this.compress_level === 2 ) compress_sep = "\t\t" else if ( this.compress_level <= 1 ) compress_sep = "\n" if ( this.compress_level > 1 ) indent_string = "" var modify_string = null if ( !this.use_color ) { modify_string = (function () { // Simply sends back the argument to superficially support the colors module var rs = function(s) { return s } return { white: rs, grey: rs, cyan: rs, magenta: rs, red: rs, blue: rs, black: rs, yellow: rs, green: rs } })() } else { modify_string = this.colors } if ( (typeof self !== "undefined" && msg == self) || (typeof global !== "undefined" && msg == global) || (typeof document !== "undefined" && msg == document) ) return "" // passing in a global is too much work if ( msg && msg instanceof Wizkit.prototype.Logger ) { msg = this.stringify_object(msg.text()) } else if ( msg === null ) { msg = modify_string.cyan("null") } else if ( typeof msg == "boolean" ) { msg = modify_string.red(msg.toString()) } else if ( msg !== msg ) { // NaN is the only js object which is not equal to itself msg = modify_string.cyan("NaN") } else if ( msg instanceof Error ) { // NaN is the only js object which is not equal to itself var stack_string = msg.stack.split("\n") msg = modify_string.red(stack_string.splice(0,1)) + "\n" + modify_string.yellow(stack_string.join("\n")) } else if ( typeof msg === "undefined" ) { msg = modify_string.yellow("undefined") } else if ( msg.constructor === Buffer || msg instanceof Buffer ) { msg = modify_string.blue("Nodje Buffer() with length of: ")+modify_string.blue(""+msg.length)//+Object.keys(msg).toString()) } else if ( typeof msg === "object" || new Object().toString.call(msg) === "[object Function]" ) { var m = "" this.use_quotes = true // put quotes around strings if the request comes from here (is an Object) var reg = new RegExp(indent_string, "g") var level = compound.match(reg) level = level && level.length || 0 var value_keys_length = Object.keys(msg).length // Used to determine loop index position // TODO: This shouldn't object check twice with non-color stingify if ( typeof this._cache[level] === "undefined") this._cache[level] = [] if ( value_keys_length ) this._cache[level].push(msg) for ( var n = 0; n < level-1; n++ ) { for ( var x = 0; x < this._cache[n].length; x++ ) { if ( typeof msg === "object" && msg === this._cache[n][x] ) { return "[Circular duplicate of -> "+this._cache[n][x].toString()+"]" // Circular reference found, discard object processing } } } if ( new Object().toString.call(msg) === "[object Function]" ) { // compound+=indent_string // Extra space after comma at end of string is important for m.substr(0, m.length-2) below //m += "{ "+modify_string.blue("func(")+"\n"+modify_string.blue((msg.toString()+"\n").replace(/(.*)\n/g, compound+indent_string+"$1\n"))+compound+modify_string.blue(")")+", " m += modify_string.blue((msg.toString()+"\n").replace(/(.*)\n/g, compound+"$1\n"))+compound // Move the entire function text over to the current tab } else if ( msg.constructor === Array ) { m += "[" if ( !value_keys_length ) m += "]," + (this._compress_level > 2 && "" || " ") } else { m += "{" + (this._compress_level > 2 && "" || " ") if ( !value_keys_length ) m += "}," + (this._compress_level > 2 && "" || " ") } var cnt = 0, character_count = 0, str_add for ( var o in msg ) { // Loop through the Object values if ( cnt === 0 || typeof msg[o] === "object" || this.compress_level < 2 ) { // Is this the first loop m += compress_sep+compound+indent_string } if ( msg.constructor !== Array ) { str_add = this.stringify_object(o)+":" + (this._compress_level > 2 && "" || " ") character_count += str_add.length m += str_add } // recursively call this function to parse the object. The indent_string is added to the begining to nest the output value = this.stringify_object(msg[o], indent_string, compound+indent_string) // Add the indent_string to the indent_string chain m += value + "," + (this._compress_level > 2 && "" || " ") character_count += (value+","+(this._compress_level > 2 && "" || " ")).length if ( cnt === value_keys_length-1 ) { // Is this he last loop of the value m = m.substr(0, m.length-2)+compress_sep+compound // Remove last space and comma at the end of the value loop if ( msg.constructor === Array ) m += "]," + (this._compress_level > 2 && "" || " ") else m += "}," + (this._compress_level > 2 && "" || " ") } else if ( character_count >= this.max_line_characters ) { m += "\n"+compound+indent_string character_count = 0 } cnt++ } // if ( this.compress_level < 4 ) m = m.substr(0, m.length-2) // Remove last space and comma at the end of the Object loop if ( compound ) m = compress_sep+compound+m msg = m this.use_quotes = false } else if ( typeof msg === "number" ) { msg = modify_string.green(msg.toString())// .replace(/\./, modify_string.grey("."))) // If there is a decimal, color it white } else if ( typeof msg === "string" ) { if ( this.use_quotes ) msg = modify_string.blue("\"")+modify_string.magenta(msg)+modify_string.blue("\"") else msg = modify_string.magenta(msg) } else { msg = modify_string.red(msg.toString()) } return msg }, } Wizkit.is_node_run = typeof module !== "undefined" && typeof module.exports !== "undefined" if ( Wizkit.is_node_run ) { Colors_Safe = require("colors/safe") module.exports = Wizkit }