/**
* Helper Utility for Abstract
*
* @example
*
* // Select specific methods to load
* var my_tools = Abstract.utility( 'if', 'extend', 'flatten', 'unwatch', 'watch' );
*
* @for abstract
* @submodule abstract-utility
* @author potanin@UD
* @date 6/17/13
*/
function Utility() {
return Object.keys( arguments ) ? require( 'lodash' ).pick.apply( null, [ Utility, Array.prototype.slice.call( arguments ) ] ) : Utility;
}
Object.defineProperties( module.exports = Utility, {
json: {
value: {
/**
* Safely parse JSON
*
* @method parse
* @for Json
*
* @param json
* @return {parsed|json}
*/
parse: function parse( json ) {
if( Buffer && json instanceof Buffer ) {
json = json.toString()
}
var parsed = false;
try { parsed = JSON.parse( json ); } catch (e) { parsed = false; }
// Perhaps try to strip some line braks or special characters..
if( !parsed && 'string' === typeof json ) {}
return parsed ? parsed : json;
},
/**
*
* @param obj
* @returns {boolean}
*/
stringify: function stringify( obj ) {
return JSON.stringify( obj );
},
pack: function pack() {},
unpack: function pack() {}
},
configurable: false,
enumerable: true,
writable: true
},
realpath: {
value: function realpath() {
var join_path = require( 'path' ).join;
var fs = require( 'fs' );
var _path = arguments.length > 1 ? join_path.apply( {}, arguments ) : arguments[0];
try {
return fs.realpathSync( _path );
} catch ( error ) {
try {
return require.resolve( _path );
} catch( error ) {
return null;
}
}
},
configurable: false,
enumerable: true,
writable: true
},
console: {
value: {
/**
* Prety Print complex objects
*
* @param data
* @param depth
*
* @requires lodash
* @method console_json
* @return {Object} The first argument.
*/
json: function json( data, depth ) {
try {
if( 'string' === typeof data && ( 'object' === typeof depth || 'function' === typeof depth ) ) {
console.log( "\n" + '===============' + data + ' Properties===============' );
data = depth;
depth = arguments[2] || 1
}
var output = require( 'util' ).inspect( data, false, 'number' === typeof depth ? ( depth - 1 ) : 1, true );
console.log( output );
} catch( error ) { console.error( 'Error with console.json()', error ); }
return arguments[0];
},
/**
* Pretty Print and Object's Methods
*
* @param data
* @param object
*
* @requires lodash
* @method console_methods
* @return {Object} The first argument.
*/
method: function method( data, object ) {
try {
if( 'string' === typeof data && ( 'object' === typeof object || 'function' === typeof object ) ) {
console.log( "\n" + '===============' + data + ' Methods===============' );
data = object;
}
module.exports.json( _.methods( data ) );
} catch( error ) { console.error( 'Error with console.methods()', error ); }
return arguments[0];
},
/**
* Get all Object Keys
*
* @param data
*
* @method console_keys
* @return {Object} The first argument.
*/
keys: function keys( data ) {
function Iterate( target ) {
var result = [];
for( var key in target ) {
result.push( key );
}
return result;
}
console.log([ Object.keys( data ) + Object.getOwnPropertyNames( data ) + Iterate( data ) ]);
return data;
},
/**
* Draw data in table style
*
* @example
*
* //console.table
* console.table([
* { .. row .. }
* ]);
*
* //console.table
* console.table({
* 'head': [ 'I/O ID', 'Endpoint', 'Description' ],
* 'colWidths': [ 30, 50, 50 ],
* 'rows': [
* [ 'one', 'two', 'three' ],
* [ 'one', 'two', 'three' ]
* ]
* });
*
* @param {object} data
* @method table
* @for Utility
*/
table: function table( data ) {
try {
var table = require( 'cli-table' );
} catch (error) {
return console.log( data );
}
var rows = [];
if( _.isObject( data ) && data.rows ) {
table = new table({
'head': data.head || [],
'colWidths': data.colWidths || []
});
rows = data.rows;
} else {
table = new table;
rows = data;
}
_.each( rows, function( row ) {
if( !_.isEmpty( row ) ) { table.push( row ); }
});
try {
console.log( table.toString() );
} catch( error ) {
//console.trace( error.message, error );
}
},
/**
* Console a variable report
*
* @method report
* @for Utility
* @param {object} arguments*
*/
report: function report() {
module.exports.console.json.apply( module.exports, arguments );
module.exports.console.methods.apply( module.exports, arguments );
}
},
configurable: false,
enumerable: true,
writable: true
},
merge: {
/**
* Merge object b with object a.
*
* @example
* var a = { foo: 'bar' }
* , b = { bar: 'baz' };
*
* utils.merge(a, b);
* // => { foo: 'bar', bar: 'baz' }
*
* @param {Object} a
* @param {Object} b
* @return {Object}
*
* @source connect
*/
value: function merge( a, b ){
if (a && b) {
for (var key in b) { a[key] = b[key]; }
}
return a;
},
configurable: false,
enumerable: true,
writable: true
},
embed: {
/**
* Shallow Merge of Current Object with Another, and Function Mounting
*
* Functions in exact same manner as
* the Connect utilities "merge" method
*
* @param {Object} target
*
* @method embed
*
* @chainable
*
* @return {Object}
*/
value: function embed( target ) {
if( 'object' === typeof target || target.keys ) {
for( var key in target ) {
this[key] = target[ key ];
}
}
// If second argument is a function, we do some magic
if( 'function' === typeof target ) {
return Utility.embed( this );
}
return this;
},
configurable: false,
enumerable: true,
writable: true
},
regroup: {
/**
* Re-group an object by a key, and optionally pick specific keys in the new object.
*
* If an object is passed, it is assumed to be grouped, thus flattening the values.
* Arrays are used as they are.
*
* @param target
*/
value: function regroup( object, group_key, keys ) {
if( _.isObject( object ) ) {
object = _.flatten( _.values( object ) );
}
function Group( item ) {
return item[ group_key ];
}
if( group_key ) {
object = _.groupBy( object, Group );
}
if( !_.isEmpty( keys ) ) {
keys = _.isArray( keys ) ? keys : [ keys ];
_.each( object, function( items, key ) {
object[ key ] = _.map( items, function( item ) { return _.pick( item, keys ); })
})
}
return object;
},
configurable: false,
enumerable: true,
writable: true
},
noop: {
/**
* Not a function, that's for sure.
* This method does not accept any arguments.
*/
value: function noop() {
// console.log( arguments )
},
configurable: false,
enumerable: true,
writable: true
},
apply: {
/**
* Creates a continuation function with some arguments already applied.
*
* @uses async
*/
value: require( 'async' ).apply,
configurable: true,
enumerable: true,
writable: true
},
defaults: {
/**
* Configure Defaults for an Object
*
* @returns {Object}
*/
value: function defaults( target, defaults ) {
// If no arguments, return empty object
if( !target && !defaults ) {
return {};
}
// Ensure target is an object
target = Object.keys( target || {} ).length ? target : {};
// Lodash-it
return require( 'lodash' ).defaults.apply( {}, arguments );
},
configurable: true,
enumerable: true,
writable: true
},
watch: {
/**
* Watch a Single Property
*
* @param {Object} prop
* @param {callback} handler
*
* @method watch
* @for abstract-utility
*
* @chainable
*
* @return {Object} newval
*/
value: function watch( target, prop, handler ) {
var oldval = target[prop];
var newval = oldval;
var description = Object.getOwnPropertyDescriptor( target, prop ) || {};
switch( typeof target[prop] ) {
case 'function':break;
case 'object':break;
case 'string':break;
case 'number':break;
}
// Delete original property and replace with getter/setter if possible
if( description.configurable === false || !( delete target[ prop ] ) ) {
return target;
}
return Object.defineProperty( target, prop, {
get: function get() {
return newval;
},
set: function set( val ) {
oldval = newval;
// Set value
newval = val;
// Execute hander and potentially mody value
return newval = handler.call( target, prop, oldval, val );
},
enumerable: description.enumerable,
configurable: true
});
},
configurable: true,
enumerable: true,
writable: true
},
unwatch: {
/**
* Unwatch a Single Property
*
* @param {Object} prop
*
* @method unwatch
* @for abstract-utility
*
* @return null
*/
value: function unwatch( prop ) {
var val = this[prop];
var description = Object.getOwnPropertyDescriptor( this, prop );
// Shouldn't happen, but just in case.
if( description.configurable === false || !( delete this[ prop ] ) ) {
return this;
}
this[prop] = val;
return this;
},
configurable: true,
enumerable: true,
writable: true
},
query: {
/**
* Get property value using a dot notation path.
*
* @param obj
* @param str
* @returns {*}
*/
value: function query( obj, str ) {
if( !str ) {
return obj;
}
if( 'object' === typeof str ) {
// @todo Convert Object to dot notation, using the first full dot notation path.
}
try {
return str.split( '.' ).reduce( function( o, x ) {
return o[x]
}, obj);
} catch( error ) {
return null;
}
},
configurable: true,
enumerable: true,
writable: true
},
unwrap: {
/**
* Unwrap dot notation string to nested Object
*
* @example
*
* Utility.unwrap( 'first_name', 'john' ); // -> { first_name: 'John' }
* Utility.unwrap( 'name.first', 'john' ); // -> { name: { first: 'John' } }
* Utility.unwrap( 'first_name' ); // -> null
* Utility.unwrap( 'name.first' ); // -> null
*
* // Will not modify object if already exists
* Utility.unwrap( 'name.first.fail', 'Anything' );
*
* @param string
* @param value
* @param hash
* @param seperator
* @,etjpd unwrap
* @return {*}
*/
value: function unwrap( string, value, hash, seperator ) {
if( hash == null ) { hash = {}; }
if( seperator == null ) { seperator = '.'; }
var parts = string.split( seperator );
var refHash = hash;
var depth = 0;
parts.forEach( function( part ) {
if( depth == parts.length - 1 ) {
refHash[part] = value;
} else {
if( refHash[part] == null ) { refHash[part] = {}; }
refHash = refHash[part];
}
depth++;
});
return hash;
},
configurable: true,
enumerable: true,
writable: true
},
mixin: {
/**
* Mixin current prototype into target object
*
* Honors property descriptor settings, if available.
*
* @param source
* @param target
*/
value: function mixin( source, target ) {
source = this.prototype || arguments[0] || {};
target = arguments.length == 2 ? arguments[1] : arguments[1] || {};
for( var key in source ) {
try {
Object.defineProperty( target, key, Object.getOwnPropertyDescriptor( source, key ) || {
value: source[key],
enumerable: false,
writable: true,
configurable: true
});
} catch( error ) { console.error( error ); }
}
return target;
},
configurable: true,
writable: true,
enumerable: false
},
inherit: {
/**
* Carefully Inherit Properties
*
* @param target {Object} Object to add properties to.
* @param target {Object} Source object.
* @returns {Object} Extended target.
*/
value: function inherit( target, source ) {
target = target || {};
source = source || {};
//if( target instanceof source ) {}
Object.getOwnPropertyNames( source ).forEach( function( key ) {
if( !target.hasOwnProperty( key ) ) { Object.defineProperty( target, key, Object.getOwnPropertyDescriptor( source, key ) ); }
});
return target;
},
enumerable: true
},
flatten: {
/**
* Flatten Array
*
* @todo Notice - nested arrays will not honor delimiter.
*/
value: function flatten( data, options ) {
options = Utility.defaults( options, {
delimiter: 1
});
return Array.prototype.concat( data ).join( options.delimiter ).toLowerCase();
},
enumerable: true
},
flatten_obj: {
value: function flatten_obj( obj ) {
var list = {};
(function callee(o, r) {
r = r || '';
if (typeof o != 'object') {
return true;
}
for (var c in o) {
if ( callee( o[c], r + "." + c ) ) {
list[r.substring(1) + "." + c] = o[c]
}
}
return false;
})(obj);
return list;
},
enumerable: true
},
inherit_full: {
/**
* Inherit the prototype methods from one constructor into another.
*
* Copy of the Node.js util.inherits method.
*
* @param {function} target Constructor function which needs to inherit the prototype.
* @param {function} constructor Constructor function to inherit prototype from.
*/
value: function inherit_prototype( target, constructor ) {
target.super_ = constructor;
target.prototype = Object.create( constructor.prototype, {
constructor: {
value: constructor,
enumerable: false,
writable: true,
configurable: true
}
});
},
configurable: true,
enumerable: true,
writable: true
},
constructors: {
/**
* Walk up the prototype chain, creating a JSON-Schema-esque structure
*
* @param context {Object} The object to walk through.
* @return {Object} JSON Schema-esque constructor chain.
*/
value: function constructors( context, options ) {
context = context || {};
options = options || {};
var matches = {};
var path = [];
do {
if( context.constructor ) {
path.push( context.constructor.name );
Object.defineProperty( matches, context.constructor.name, {
enumerable: true,
writable: true,
value: {
name: context.constructor.name,
properties: {
constructor: {
type: typeof context.constructor,
properties: Object.getOwnPropertyNames( context.constructor )
},
prototype: {
type: typeof context.constructor.prototype,
properties: Object.keys( context.constructor.prototype )
}
}
}
});
}
} while ( context = Object.getPrototypeOf( context ) );
if( options.format === 'schema' ) {
return matches || {};
}
if( options.delimiter ) {
return path.join( options.delimiter );
}
return path;
},
configurable: true,
enumerable: true,
writable: true
},
unique: {
/**
* Return unique values of an array
*
* @param array
* @returns {Array}
*/
value: function unique( array ){
var b = [];
for(var i=0; i<array.length; i++){
if(b.indexOf(array[i]) == -1) b.push(array[i]);
}
return b;
},
configurable: true,
enumerable: true,
writable: true
},
hash: {
/**
* Generate a unique hash for an Object, using md5 on default.
*
* @example
*
* var _hash = Utility.hash({ type: 'some_object', name: 'Bob'});
*
* console.log( _hash ); // -> 147ce3e2ccb7db6b928b303ce42bdafa
*
* @param obj {Object} Object to generate a hash for.
* @param options {Object} options for hash generation.
* @param options {String} options.type Type of hash to generate, defaulting to md5.
* @param options {String} options.silent_fail Do not throw errors, return empty string if there was an error when true.
*
*/
value: function hash( obj ) {
var result;
var string = JSON.stringify( arguments[0] || {} );
var options = Utility.defaults( arguments[1], {
type: 'md5',
silent_fail: true,
random: false
});
try {
if( options.random ) {
string = Math.random().toString();
}
result = require( 'crypto' ).createHash( options.type.toLowerCase() ).update( string ).digest( 'hex' );
} catch ( error ) {
result = options.silent_fail ? '' : error;
}
return result;
},
configurable: true,
enumerable: true,
writable: true
},
auto: { value: require( 'async' ).auto },
queue: { value: require( 'async' ).queue },
times: { value: require( 'async' ).times },
extend: { value: require( 'lodash' ).extend },
values: { value: require( 'lodash' ).values },
map: { value: require( 'lodash' ).map },
each: { value: require( 'lodash' ).each },
reduce: {
value: function reduce(arr, iterator, memo) {
if (arr.reduce) {
return arr.reduce(iterator, memo);
}
_each(arr, function (x, i, a) {
memo = iterator(memo, x, i, a);
});
return memo;
},
configurable: false,
enumerable: true,
writable: true
},
keys: {
/**
*
* @source async
*/
value: function keys(obj) {
if (Object.keys) {
return Object.keys(obj);
}
var keys = [];
for (var k in obj) {
if (obj.hasOwnProperty(k)) {
keys.push(k);
}
}
return keys;
},
configurable: false,
enumerable: true,
writable: true
},
toArray: { value: require( 'lodash' ).toArray },
where: { value: require( 'lodash' ).where },
if: {
value: {
"PlainObject": require( 'lodash' ).isPlainObject,
"Function": require( 'lodash' ).isFunction,
"Object": require( 'lodash' ).isObject,
"String": require( 'lodash' ).isString,
},
configurable: false,
enumerable: true,
writable: true
},
log: {
/**
* Output variables to console log.
*
* Settings can be configured like so:
* abstract.log.config.depth = 2;
* abstract.log.config.colors = true;
*
* @param {Object|String} data A config object
* @example
* Abstract.log( data )
*
* method log
* @return {Object} Abstract constructor.
* @chainable
*/
value: Object.defineProperties( function() {
var output = {};
// Capture Event Name
if( this.event ) {
output.event = this.event;
}
Utility.each( arguments, function( item, key ) {
if( Utility.if.PlainObject( item ) ) {}
output[ 'format' ] = typeof item;
if( key === 0 ) {
output[ 'data' ] = item;
} else {
output[ key ] = item;
}
});
console.log( require( 'util' ).inspect( output, ( Utility ).log.config ) );
return this;
}, { "config": { value: { "showHidden": true, "depth": 2, "colors": true }, "writable": true } }),
enumerable: true
}
});