wizkit
Version:
The functional programming programming utility and terminal logger.
888 lines (784 loc) • 29.4 kB
JavaScript
/*
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
}