Source: core.js

/**
 * BBOP language extensions to JavaScript, complimenting Underscore.js.
 * Purpose: Helpful basic utilities and operations to fix common needs in JS.
 *
 * @module bbop-core 
 */

var us = require('underscore');
var each = us.each;

///
///
///

/**
 * Return the best guess (true/false) for whether or not a given
 * object is being used as a hash.
 *
 * @function
 * @name module:bbop-core#is_hash
 * @param {} in_thing - the thing in question
 * @returns {boolean} boolean
 */
function _is_hash(in_thing){
    var retval = false;
    if( in_thing && us.isObject(in_thing) &&
	! us.isArray(in_thing) &&
	! us.isFunction(in_thing) ){
	retval = true;
    }
    return retval;
}

/**
 * Return the string best guess for what the input is, null if it
 * can't be identified. In addition to the _is_a property convention,
 * current core output strings are: 'null', 'array', 'boolean',
 * 'number', 'string', 'function', and 'object'.
 * 
 * @function
 * @name module:bbop-core#what_is
 * @param {any} in_thing - the thing in question
 * @returns {string} string
 */
function _what_is(in_thing){
    var retval = null;
    if( typeof(in_thing) != 'undefined' ){
	
	// If it's an object, try and guess the 'type', otherwise, let
	// typeof.
	if( in_thing == null ){
	    retval = 'null';
	}else if( typeof(in_thing) == 'object' ){
	    
	    // Look for the 'is_a' property that I should be using.
	    if( typeof(in_thing._is_a) != 'undefined' ){
		retval = in_thing._is_a;
	    }else{
		if( us.isArray(in_thing) ){
		    retval = 'array';
		}else{
		    retval = 'object';
		}		
	    }
	}else{
	    retval = typeof(in_thing);
	}
    }
    return retval;
}

/**
 * Dump an object to a string form as best as possible. More meant for
 * debugging. This is meant to be an Object walker. For a slightly
 * different take (Object identification), see <to_string>.
 *
 * @see module:bbop-core.to_string
 * @function
 * @name module:bbop-core#dump
 * @param {} in_thing - something
 * @returns {string} string
 */
function _dump(thing){

    var retval = '';
    
    var what = _what_is(thing);
    if( what == null ){
	retval = 'null';
    }else if( what == 'null' ){
	retval = 'null';
    }else if( what == 'string' ){
	retval = '"' + thing + '"';
    }else if( what == 'boolean' ){
	if( thing ){
	    retval = "true";
	}else{
	    retval = "false";
	}
    }else if( what == 'array' ){
	
	var astack = [];
	each(thing, function(item, i){
	    astack.push(_dump(item));
	});
	retval = '[' + astack.join(', ') + ']';
	
    }else if( what == 'object' ){
	
	var hstack = [];
	each(thing, function(val, key){
	    hstack.push('"'+ key + '": ' + _dump(val));
	});
	retval = '{' + hstack.join(', ') + '}';
	
    }else{
	retval = thing;
    }
    
    return retval;
}

/**
 * (Deep) clone an object down to its atoms.
 *
 * @function
 * @name module:bbop-core#clone
 * @param {any} thing - whatever
 * @returns {any} a new whatever
 */
function _clone(thing){

    var clone = null;
    
    if( typeof(thing) === 'undefined' ){
	// Nothin' doin'.
	//print("looks undefined");
    }else if( typeof(thing) === 'function' ){
	// Dunno about this case...
	//print("looks like a function");
	clone = thing;
    }else if( typeof(thing) === 'boolean' ||
	      typeof(thing) === 'number' ||
	      typeof(thing) === 'string' ){
		  // Atomic types can be returned as-is (i.e. assignment in
		  // JS is the same as copy for atomic types).
		  //print("cloning atom: " + thing);
		  clone = thing;
	      }else if( typeof(thing) === 'object' ){
		  // Is it a null, hash, or an array?
		  if( thing == null ){
		      clone = null;
		  }else if( Array.isArray(thing) ){
		      // Looks like an array!
		      //print("looks like an array");
		      clone = [];
		      for(var i = 0; i < thing.length; i++){
			  clone[i] = _clone(thing[i]);
		      }
		  }else{
		      // Looks like a hash!
		      //print("looks like a hash");
		      clone = {};
		      for(var h in thing){
			  clone[h] = _clone(thing[h]);
		      }
		  }
	      }else{
		  // Then I don't know what it is--might be platform dep.
		  //print("no idea what it is");
	      }
    return clone;
}

/**
 * Attempt to return a two part split on the first occurrence of a
 * character.
 *
 * Returns '' for parts not found.
 * 
 * Unit tests make the edge cases clear.
 * 
 * @function
 * @name module:bbop-core#first_split
 * @param {String} character - the character to split on
 * @param {String} string - the string to split
 * @returns {Array} list of first and second parts
 */
function _first_split(character, string){

    var retlist = null;
    
    var eq_loc = string.indexOf(character);
    if( eq_loc == 0 ){
	retlist = ['', string.substr(eq_loc +1, string.length)];
    }else if( eq_loc > 0 ){
	var before = string.substr(0, eq_loc);
	var after = string.substr(eq_loc +1, string.length);
	retlist = [before, after];
    }else{
	retlist = ['', ''];
    }
    
    return retlist;
}

// Exportable body.
module.exports = {

    clone: _clone,
    dump: _dump,
    first_split: _first_split,
    is_hash: _is_hash,
    what_is: _what_is,

    /**
     * Crop a string nicely.
     * 
     * Returns: Nothing. Side-effects: throws an error if the namespace
     * defined by the strings is not currently found.
     * 
     * @param {} str - the string to crop
     * @param {} lim - the final length to crop to (optional, defaults to 10)
     * @param {} suff - the string to add to the end (optional, defaults to '')
     * @returns {string} cropped string
     */
    crop: function(str, lim, suff){
	var ret = str;
	
	var limit = 10;
	if( lim ){ limit = lim; }

	var suffix = '';
	if( suff ){ suffix = suff; }
	
	if( str.length > limit ){
	    ret = str.substring(0, (limit - suffix.length)) + suffix;
	}
	return ret;
    },

    /**
     * Fold a pair of hashes together, using the first one as an initial
     * template--only the keys in the default hash will be defined in the
     * final hash--and the second hash getting precedence.
     * 
     * The can be quite useful when defining functions--essentially
     * allowing a limited default value system for arguments.
     * 
     * @see module:bbop-core.merge
     * @param {object} default_hash - Template hash.
     * @param {object} arg_hash - Argument hash to match.
     * @returns {object} a new hash
     */
    fold: function(default_hash, arg_hash){

	if( ! default_hash ){ default_hash = {}; }
	if( ! arg_hash ){ arg_hash = {}; }

	var ret_hash = {};
	for( var key in default_hash ){
	    if( ! us.isUndefined(arg_hash[key]) ){
		ret_hash[key] = arg_hash[key];
	    }else{
		ret_hash[key] = default_hash[key];
	    }
	}
	return ret_hash;
    },

    /**
     * Merge a pair of hashes together, the second hash getting
     * precedence. This is a superset of the keys both hashes.
     * 
     * @see module:bbop-core.fold
     * @param {} older_hash - first pass
     * @param {} newer_hash - second pass
     * @returns {object} a new hash
     */
    merge: function(older_hash, newer_hash){

	if( ! older_hash ){ older_hash = {}; }
	if( ! newer_hash ){ newer_hash = {}; }

	var ret_hash = {};
	function _add (val, key){
	    ret_hash[key] = val;
	}
	each(older_hash, _add);
	each(newer_hash, _add);
	return ret_hash;
    },

    /**
     * Get the hash keys from a hash/object, return as an array.
     *
     * @param {} arg_hash - the hash in question
     * @returns {Array} an array of keys
     */
    get_keys: function(arg_hash){

	if( ! arg_hash ){ arg_hash = {}; }
	var out_keys = [];
	for (var out_key in arg_hash) {
	    if (arg_hash.hasOwnProperty(out_key)) {
		out_keys.push(out_key);
	    }
	}
	
	return out_keys;
    },

    /**
     * Returns a hash form of the argument array/list. For example ['a',
     * 'b'] would become {'a': true, 'b': true} or [['a', '12'], ['b',
     * '21']] would become {'a': '12', 'b': '21'}. Using mixed sub-lists
     * is undefined.
     *
     * @param {Array} list - the list to convert
     * @returns {object} a hash
     */
    hashify: function(list){
	var rethash = {};

	if( list && list[0] ){
	    if( us.isArray(list[0]) ){
		each(list, function(item){
		    var key = item[0];
		    var val = item[1];
		    if( ! us.isUndefined(key) ){
			rethash[key] = val;
		    }
		});
	    }else{
		each(list, function(item){
		    rethash[item] = true;
		});
	    }
	}

	return rethash;
    },

    // /**
    //  * Returns true if it things the two incoming arguments are value-wise
    //  * the same.
    //  * 
    //  * Currently only usable for simple (atomic single layer) hashes,
    //  * atomic lists, boolean, null, number, and string values. Will return
    //  * false otherwise.
    //  * 
    //  * @param {} thing1 - thing one
    //  * @param {} thing2 - thing two
    //  *
    //  * Returns: boolean
    //  */
    // is_same: function(thing1, thing2){

    // 	var retval = false;

    // 	// If is hash...steal the code from test.js.
    // 	if( _is_hash(thing1) && _is_hash(thing2) ){
    
    // 	    var same_p = true;
    
    // 	    // See if the all of the keys in hash1 are defined in hash2
    // 	    // and that they have the same ==.
    // 	    for( var k1 in thing1 ){
    // 		if( typeof thing2[k1] === 'undefined' ||
    // 		    thing1[k1] !== thing2[k1] ){
    // 			same_p = false;
    // 			break;
    // 		    }
    // 	    }

    // 	    // If there is still no problem...
    // 	    if( same_p ){
    
    // 		// Reverse of above.
    // 		for( var k2 in thing2 ){
    // 		    if( typeof thing1[k2] === 'undefined' ||
    // 			thing2[k2] !== thing1[k2] ){
    // 			    same_p = false;
    // 			    break;
    // 			}
    // 		}
    // 	    }

    // 	    retval = same_p;

    // 	}else if( bbop.core.is_array(thing1) && bbop.core.is_array(thing2) ){
    // 	    // If it's an array convert and pass it off to the hash function.
    // 	    retval = bbop.core.is_same(bbop.core.hashify(thing1),
    // 				       bbop.core.hashify(thing2));
    // 	}else{
    
    // 	    // So, we're hopefully dealing with an atomic type. If they
    // 	    // are the same, let's go ahead and try.
    // 	    var t1_is = _what_is(thing1);
    // 	    var t2_is = _what_is(thing2);
    // 	    if( t1_is == t2_is ){
    // 		if( t1_is == 'null' ||
    // 		    t1_is == 'boolean' ||
    // 		    t1_is == 'null' ||
    // 		    t1_is == 'number' ||
    // 		    t1_is == 'string' ){
    // 			if( thing1 == thing2 ){
    // 			    retval = true;
    // 			}
    // 		    }
    // 	    }
    // 	}

    // 	return retval;
    // },

    /**
     * Return the best guess (true/false) for whether or not a given
     * object is being used as an array.
     *
     * @param {} in_thing - the thing in question
     * @returns {boolean} boolean
     */
    is_array: function(in_thing){
	var retval = false;
	if( in_thing &&
	    Array.isArray(in_thing) ){
	    retval = true;
	}
	return retval;
    },

    /**
     * Return true/false on whether or not the object in question has any
     * items of interest (iterable?).
     *
     * @param {} in_thing - the thing in question
     * @returns {boolean} boolean
     */
    is_empty: function(in_thing){
	var retval = false;
	if( us.isArray(in_thing) ){
	    if( in_thing.length == 0 ){
		retval = true;
	    }
	}else if( _is_hash(in_thing) ){
	    var in_hash_keys = us.keys(in_thing);
	    if( in_hash_keys.length == 0 ){
		retval = true;
	    }
	}else{
	    // TODO: don't know about this case yet...
	    //throw new Error('unsupported type in is_empty');	
	    retval = false;
	}
	return retval;
    },

    /**
     * Return true/false on whether or not the passed object is defined.
     *
     * @param {} in_thing - the thing in question
     * @returns {boolean} boolean
     */
    is_defined: function(in_thing){
	var retval = true;
	if( typeof(in_thing) === 'undefined' ){
	    retval = false;
	}
	return retval;
    },

    /**
     * Take an array or hash and pare it down using a couple of functions
     * to what we want.
     * 
     * Both parameters are optional in the sense that you can set them to
     * null and they will have no function; i.e. a null filter will let
     * everything through and a null sort will let things go in whatever
     * order.
     *
     * @param {Array|Object} in_thing - hash or array
     * @param {Function} filter_function - hash (function(key, val)) or array (function(item, i)); this function must return boolean true or false.
     * @param {Function} sort_function - function to apply to elements: function(a, b); this function must return an integer as the usual sort functions do.
     * @returns {Array} array
     */
    pare: function(in_thing, filter_function, sort_function){

	var ret = [];
	
	// Probably an not array then.
	if( typeof(in_thing) === 'undefined' ){
	    // this is a nothing, to nothing....
	}else if( typeof(in_thing) != 'object' ){
	    throw new Error('Unsupported type in bbop.core.pare: ' +
			    typeof(in_thing) );
	}else if( us.isArray(in_thing) ){
	    // An array; filter it if filter_function is defined.
	    if( filter_function ){	
		each(in_thing, function(item, index){
		    if( filter_function(item, index) ){
			// filter out item if true
		    }else{
			ret.push(item);
		    }
		});
	    }else{
		each(in_thing, function(item, index){ ret.push(item); });
	    }
	}else if( us.isFunction(in_thing) ){
	    // Skip is function (which is also an object).
	}else if( us.isObject(in_thing) ){
	    // Probably a hash; filter it if filter_function is defined.
	    if( filter_function ){	
		each(in_thing, function(val, key){
		    if( filter_function(key, val) ){
			// Remove matches to the filter.
		    }else{
			ret.push(val);
		    }
		});
	    }else{
		each(in_thing, function(val, key){ ret.push(val); });
	    }
	}else{
	    // No idea what this is--skip.
	}

	// For both: sort if there is anything.
	if( ret.length > 0 && sort_function ){
	    ret.sort(sort_function);	    
	}

	return ret;
    },

    /**
     * Essentially add standard 'to string' interface to the string class
     * and as a stringifier interface to other classes. More meant for
     * output--think REPL. Only atoms, arrays, and objects with a
     * to_string function are handled.
     *
     * @see module:bbop-core.dump
     * @param {any} in_thing - something
     * @returns {string} string
     */
    to_string: function(in_thing){

	// First try interface, then the rest.
	if( in_thing &&
	    typeof(in_thing.to_string) !== 'undefined' &&
	    typeof(in_thing.to_string) == 'function' ){
		return in_thing.to_string();
	    }else{
		
		var what = _what_is(in_thing);
		if( what == 'number' ){
		    return in_thing.toString();
		}else if( what == 'string' ){
		    return in_thing;
		}else if( what == 'array' ){
		    return _dump(in_thing);
		    // }else if( what == 'object' ){
		    //     return bbop.core.dump(in_thing);
		    // }else{
		    //     return '[unsupported]';
		}else{
		    return in_thing;
		}
	    }
    },

    /**
     * Check to see if all top-level objects in a namespace supply an
     * "interface".
     * 
     * Mostly intended for use during unit testing.
     *
     * TODO: Unit test this to make sure it catches both prototype (okay I
     * think) and uninstantiated objects (harder/impossible?).
     *
     * @param {} iobj - the object/constructor in question
     * @param {} interface_list - the list of interfaces (as a strings) we're looking for
     * @returns {boolean} boolean
     */
    has_interface: function(iobj, interface_list){
	var retval = true;
	each(interface_list, function(iface){
	    //print('|' + typeof(in_key) + ' || ' + typeof(in_val));
	    //print('|' + in_key + ' || ' + in_val);
	    if( typeof(iobj[iface]) == 'undefined' &&
		typeof(iobj.prototype[iface]) == 'undefined' ){
		    retval = false;
		    throw new Error(_what_is(iobj) +
				    ' breaks interface ' + iface);
                }
	});
	return retval;
    },

    /**
     * Assemble an object into a GET-like query. You probably want to see
     * the tests to get an idea of what this is doing.
     * 
     * The last argument of double hashes gets quoted (Solr-esque),
     * otherwise not. It will try and avoid adding additional sets of
     * quotes to strings.
     *
     * This does nothing to make the produced "URL" in any way safe.
     * 
     * WARNING: Not a hugely clean function--there are a lot of special
     * cases and it could use a good (and safe) clean-up.
     * 
     * @param {} qargs - hash/object
     * @returns {string} string
     */
    get_assemble: function(qargs){

	var mbuff = [];
	for( var qname in qargs ){
	    var qval = qargs[qname];

	    // null is technically an object, but we don't want to render
	    // it.
	    if( qval != null ){
		if( typeof qval == 'string' || typeof qval == 'number' ){
		    // Is standard name/value pair.
		    var nano_buffer = [];
		    nano_buffer.push(qname);
		    nano_buffer.push('=');
		    nano_buffer.push(qval);
		    mbuff.push(nano_buffer.join(''));
		}else if( typeof qval == 'object' ){
		    if( typeof qval.length != 'undefined' ){
			// Is array (probably).
			// Iterate through and double on.
			for(var qval_i = 0; qval_i < qval.length ; qval_i++){
			    var nano_buff = [];
			    nano_buff.push(qname);
			    nano_buff.push('=');
			    nano_buff.push(qval[qval_i]);
			    mbuff.push(nano_buff.join(''));
			}
		    }else{
			// // TODO: The "and" case is pretty much like
			// // the array, the "or" case needs to be
			// // handled carefully. In both cases, care will
			// // be needed to show which filters are marked.
			// Is object (probably).
			// Special "Solr-esque" handling.
			for( var sub_name in qval ){
			    var sub_vals = qval[sub_name];
			    
			    // Since there might be an array down there,
			    // ensure that there is an iterate over it.
			    if( _what_is(sub_vals) != 'array' ){
				sub_vals = [sub_vals];
			    }
			    
			    each(sub_vals, function(sub_val){
				var nano_buff = [];
				nano_buff.push(qname);
				nano_buff.push('=');
				nano_buff.push(sub_name);
				nano_buff.push(':');
				if( typeof sub_val !== 'undefined' && sub_val ){
				    // Do not double quote strings.
				    // Also, do not requote if we already
				    // have parens in place--that
				    // indicates a complicated
				    // expression. See the unit tests.
				    var val_is_a = _what_is(sub_val);
				    if( val_is_a == 'string' &&
					sub_val.charAt(0) == '"' &&
					sub_val.charAt(sub_val.length -1) == '"' ){
					    nano_buff.push(sub_val);
					}else if( val_is_a == 'string' &&
						  sub_val.charAt(0) == '(' &&
						  sub_val.charAt(sub_val.length -1) == ')' ){
						      nano_buff.push(sub_val);
						  }else{
						      nano_buff.push('"' + sub_val + '"');
						  }
				}else{
				    nano_buff.push('""');
				}
				mbuff.push(nano_buff.join(''));
			    });
			}
		    }
		}else if( typeof qval == 'undefined' ){
		    // This happens in some cases where a key is tried, but no
		    // value is found--likely equivalent to q="", but we'll
		    // let it drop.
		    // var nano_buff = [];
		    // nano_buff.push(qname);
		    // nano_buff.push('=');
		    // mbuff.push(nano_buff.join(''));	    
		}else{
		    throw new Error("bbop.core.get_assemble: unknown type: " + 
				    typeof(qval));
		}
	    }
	}
	
	return mbuff.join('&');
    },

    /**
     * Random number generator of fixed length. Return a random number
     * string of length len.
     *
     * @param {} len - the number of random character to return.
     * @returns {string} string
     */
    randomness: function(len){

	var random_base = [
	    '1', '2', '3', '4', '5', '6', '7', '8', '9', '0',
	    'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm',
	    'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z'
	];
	var length = len || 10;
	var cache = new Array();
	for( var ii = 0; ii < length; ii++ ){
	    var rbase_index = Math.floor(Math.random() * random_base.length);
	    cache.push(random_base[rbase_index]);
	}
	return cache.join('');
    },

    /**
     * Return the parameters part of a URL.
     *
     * Unit tests make the edge cases clear.
     * 
     * @param {} url - url (or similar string)
     * @returns {Array} list of part lists
     */
    url_parameters: function(url){

	var retlist = [];

	// Pull parameters.
	var tmp = url.split('?');
	var path = '';
	var parms = [];
	if( ! tmp[1] ){ // catch bad url--nothing before '?'
	    parms = tmp[0].split('&');
	}else{ // normal structure
	    path = tmp[0];
	    parms = tmp[1].split('&');
	}

	// Decompose parameters.
	each(parms, function(p){
	    var c = _first_split('=', p);
	    if( ! c[0] && ! c[1] ){
		retlist.push([p]);
	    }else{
		retlist.push(c);		  
	    }
	});
	
	return retlist;
    },

    /**
     * Convert a string into something consistent for urls (getting icons,
     * etc.). Return a munged/hashed-down version of the resource.
     * Assembles, converts spaces to underscores, and all lowercases.
     * 
     * @param {} base - base url for the resource(s)
     * @param {} resource - the filename or whatever to be transformed
     * @param {} extension - *[optional]* the extension of the resource
     * @returns {string} string
     */
    resourcify: function(base, resource, extension){

	var retval = base + '/' + resource;

	// Add the extension if it is there.
	if( extension ){
	    retval += '.' + extension;	
	}

	// Spaces to underscores and all lowercase.
	//return retval.replace(/\ /g, "_", "g").toLowerCase();
	return retval.replace(/\ /g, "_").toLowerCase();
    },

    /**
     * RFC 4122 v4 compliant UUID generator.
     * From: http://stackoverflow.com/questions/105034/how-to-create-a-guid-uuid-in-javascript/2117523#2117523
     *
     * @returns {string} string
     */
    uuid: function(){

	// Replace x (and y) in string.
	function replacer(c) {
	    var r = Math.random()*16|0, v = c == 'x' ? r : (r&0x3|0x8);
	    return v.toString(16);
	}
	var target_str = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx';
	return target_str.replace(/[xy]/g, replacer);
    },

    /**
     * A sort function to put numbers in ascending order.
     * 
     * Useful as the argument to .sort().
     * 
     * See: https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Array/sort
     * 
     * @param {number} a - the first number
     * @param {number} b - the second number
     * @returns {number} number of their relative worth
     */
    numeric_sort_ascending: function(a, b){
	return a - b;
    },

    /**
     * A sort function to put numbers in descending order.
     * 
     * Useful as the argument to .sort().
     * 
     * See: https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Array/sort
     * 
     * @param {number} a - the first number
     * @param {number} b - the second number
     * @returns {number} number of their relative worth
     */
    numeric_sort_descending: function(a, b){
	return b - a;
    },

    /**
     * Remove the quotes from a string.
     * 
     * @param {string} str - the string to dequote
     * @returns {string} the dequoted string (or the original string)
     */
    dequote: function(str){
	var retstr = str;

	if( ! us.isUndefined(str) && str.length > 2 ){
	    var end = str.length -1;
	    if( str.charAt(0) == '"' && str.charAt(end) == '"' ){
		retstr = str.substr(1, end -1);
	    }
	}

	return retstr;
    },

    /**
     * Make sure that a substring exists at the beginning or end (or both)
     * of a string.
     * 
     * @param {} str - the string to ensure that has the property
     * @param {} add - the string to check for (and possibly add)
     * @param {} place - *[optional]* "front"|"back", place to ensure (defaults to both)
     * @returns {string} a new string with the property enforced
     */
    ensure: function(str, add, place){

	// 
	var do_front = false;
	var do_back = false;
	if( us.isUndefined(place) ){
	    do_front = true;
	    do_back = true;
	}else if( place == 'front' ){
	    do_front = true;
	}else if( place == 'back' ){
	    do_back = true;
	}else{
	    // Don't know what it is, not doing anything.
	}

	//
	var strlen = str.length;
	var addlen = add.length;
	var front_substr = str.substr(0, addlen);
	var back_substr = str.substr((strlen - addlen), (strlen -1));

	//
	var front_add = '';
	if( do_front && front_substr != add ){
	    front_add = add;
	}
	var back_add = '';
	if( do_back && back_substr != add ){
	    back_add = add;
	}

	// console.log('do_front: ' + do_front);
	// console.log('do_back: ' + do_back);
	// console.log('str.length: ' + strlen);
	// console.log('add.length: ' + addlen);
	// console.log('front_substr: ' + front_substr);
	// console.log('back_substr: ' + back_substr);
	// console.log('front_add: ' + front_add);
	// console.log('back_add: ' + back_add);

	return front_add + str + back_add;
    },

    /**
     * Trim the leading and trailing whitespace from a string.
     * Named differently so as not to confuse with JS 1.8.1's trim().
     * 
     * @param {string} str - the string to ensure that has the property
     * @returns {string} the trimmed string
     */
    chomp: function(str){

	var retstr = '';

	retstr = str.replace(/^\s+/,'');
	retstr = retstr.replace(/\s+$/,'');

	return retstr;
    },

    /**
     * Break apart a string on certain delimiter.
     * 
     * @param {} str - the string to ensure that has the property
     * @param {} delimiter - *[optional]* either a string or a simple regexp; defaults to ws
     *
     * @returns {Array} a list of separated substrings
     */
    splode: function(str, delimiter){

	var retlist = null;

	if( ! us.isUndefined(str) ){
	    if( us.isUndefined(delimiter) ){
		delimiter = /\s+/;
	    }
	    
	    retlist = str.split(delimiter);
	}

	return retlist;
    },

    // // Giving up on this for now: the general case seems too hard to work with 
    // // in so many different, contradictory, and changing environments.
    // /**
    //  * Getting a cross-platform that can evaluate to the global namespace
    //  * seems a little bit problematic. This is an attempt to wrap that all
    //  * away.
    //  * 
    //  * This is not an easy problem--just within browsers there are a lot
    //  * of issues:
    //  * http://perfectionkills.com/global-eval-what-are-the-options/ After
    //  * that, the server side stuff tries various ways to keep you from
    //  * affecting the global namespace in certain circumstances.
    //  * 
    //  * @param {} to_eval - the string to evaluate
    //  * 
    //  * Returns:
    //  *  A list with the following fields: retval, retval_str, okay_p, env_type.
    //  */
    // evaluate: function(to_eval){

    //     var retval = null;
    //     var retval_str = '';
    //     var okay_p = true;
    //     var env_type = 'server';

    //     // Try and detect our environment.
    //     try{
    // 	if( bbop.core.is_defined(window) &&
    // 	    bbop.core.is_defined(window.eval) &&
    // 	    bbop.core.what_is(window.eval) == 'function' ){
    // 		env_type = 'browser';
    // 	    }
    //     } catch (x) {
    // 	// Probably not a browser then, right? Hopefully all the
    // 	// servers that we'll run into are the same (TODO: check
    // 	// nodejs).
    //     }
    //     print('et: ' + env_type);

    //     // Now try for the execution.
    //     try{
    // 	// Try and generically evaluate.
    // 	if( env_type == 'browser' ){
    // 	    print('eval as if (browser)');
    // 	    retval = window.eval(to_eval);
    // 	}else{
    // 	    // TODO: Does this work?
    // 	    print('eval as else (server)');
    // 	    //retval = this.eval(to_eval);		
    // 	    retval = bbop.core.global.eval(to_eval);
    // 	}
    //     }catch (x){
    // 	// Bad things happened.
    // 	print('fail on: (' + retval +'): ' + to_eval);
    // 	retval_str = '[n/a]';
    // 	okay_p = false;
    //     }
    
    //     // Make whatever the tmp_ret is prettier for the return string.
    //     if( bbop.core.is_defined(retval) ){
    // 	if( bbop.core.what_is(retval) == 'string' ){
    // 	    retval_str = '"' + retval + '"';
    // 	}else{
    // 	    retval_str = retval;
    // 	}
    //     }else{
    // 	// Return as-is.
    //     }

    //     return [retval, retval_str, okay_p, env_type];
    // };

    /**
     * What seems to be a typical idiom for subclassing in JavaScript.
     * 
     * This attempt has been scraped together from bits here and there and
     * lucid explanations from Mozilla:
     * 
     * https://developer.mozilla.org/en-US/docs/JavaScript/Introduction_to_Object-Oriented_JavaScript
     * https://developer.mozilla.org/en-US/docs/JavaScript/Guide/Details_of_the_Object_Model
     * https://developer.mozilla.org/en-US/docs/JavaScript/Guide/Inheritance_Revisited
     * https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Operators/new
     * 
     * @param {} subclass - the subclass object
     * @param {} superclass - the superclass object
     */
    extend: function(subclass, baseclass){

	// Create a temporary nothing so that we don't fiddle the
	// baseclass's(?) with what we do to subclass later on.
	function tmp_object(){}

	// This nothings prototype gets the base class's.
	tmp_object.prototype = baseclass.prototype;

	// We instantiate the tmp_object, whose prototype is the
	// baseclass's; we make subclass's prototype this object, giving
	// us something that is very much like baseclass.
	subclass.prototype = new tmp_object; // same as: "new tmp_object();"

	// Now we go back and make the constructor of subclass actually
	// subclass again--we blew it away in the last step. Now we have a
	// subclass constructor with the protoype of baseclass.
	subclass.prototype.constructor = subclass;

	// // Create a property to allow access to the constructor of
	// // baseclass. This is useful when subclass needs access to
	// // baseclass's constructor for property setting.
	// subclass.base_constructor = baseclass;

	// // Create a property to
	// subclass.parent_class = baseclass.prototype;
    },

    /**
     * BBOP JS logger object. Using .kvetch(), you can automatically log a
     * message in almost any environment you find yourself in--browser,
     * server wherever. Also, if you have jQuery available and an element
     * with the id "bbop-logger-console-textarea",
     * "bbop-logger-console-text", or "bbop-logger-console-html", the
     * logger will append to that element (with a "\n" (autoscroll), "\n",
     * or "<br />" terminator respectively) instead.
     *
     * @constructor
     * @param {string} initial_context - (optional) initial context as string.
     */
    logger: function(initial_context){

	/**
	 * Different debugging available per object. Externally toggle
	 * between true and false to switch on and off the logging.
	 *
	 * @variable {boolean}
	 */
	this.DEBUG = false;

	var anchor = this;

	// Define an optional context to tag onto the front of messages.
	this._context = [];
	if( initial_context ){
	    this._context = [initial_context];
	}

	/**
	 * Define the ability to reset the contex.
	 * 
	 * @param {string} new_initial_context - (optional) new context to start with
	 */
	this.reset_context = function(new_initial_context){
	    if( new_initial_context ){
		this._context = [new_initial_context];
	    }else{
		this._context = [];	    
	    }
	};

	/**
	 * Add an additional logging context to the stack.
	 * 
	 * @param {string} new_context - New context to add to the context stack.
	 */
	this.push_context = function(new_context){
	    this._context.push(new_context);
	};

	/**
	 * Remove the last context if it's there.
	 */
	this.pop_context = function(){
	    var popped_context = null;
	    if( this._context.length > 0 ){
		popped_context = this._context.pop();
	    }
	    return popped_context;
	};

	// Generalizer console (or whatever) printing.
	this._console_sayer = function(){};

	if( typeof(jQuery) != 'undefined' && jQuery('#' + 'bbop-logger-console-html') != 'undefined' && jQuery('#' + 'bbop-logger-console-html').length ){
	    // Our own logging console takes precedence. 
	    this._console_sayer = function(msg){
		var area = jQuery('#'+ 'bbop-logger-console-html');
		area.append(msg + "<br />");
		try{
    		    area.scrollTop(area[0].scrollHeight);
		} catch (x) {
		    // could scroll
		}
		//jQuery('#'+'bbop-logger-console-html').append(msg + "<br />");
	    };
	}else if( typeof(console) != 'undefined' && typeof(console.log) == 'function' ){
	    // This may be okay for Chrome and a subset of various
	    // console loggers. This should now include FF's Web
	    // Console and NodeJS.  this._console_sayer =
	    // function(msg){ console.log(msg + "\n"); }; These
	    // usually seem to have "\n" incorporated now.
	    this._console_sayer = function(msg){ console.log(msg); };
	}else if( typeof(opera) != 'undefined' && typeof(opera.postError) == 'function' ){
	    // If Opera is in there, probably Opera.
	    this._console_sayer = function(msg){ opera.postError(msg + "\n"); };
	}else if( typeof(window) != 'undefined' && typeof(window.dump) == 'function' ){
	    // From developer.mozilla.org: To see the dump output you
	    // have to enable it by setting the preference
	    // browser.dom.window.dump.enabled to true. You can set
	    // the preference in about:config or in a user.js
	    // file. Note: this preference is not listed in
	    // about:config by default, you may need to create it
	    // (right-click the content area -> New -> Boolean).
	    this._console_sayer = function(msg){ dump( msg + "\n"); };
	}else if( typeof(window) != 'undefined' && typeof(window.console) != 'undefined' && typeof(window.console.log) == 'function' ){
	    // From developer.apple.com: Safari's "Debug" menu allows
	    // you to turn on the logging of JavaScript errors. To
	    // display the debug menu in Mac OS X, open a Terminal
	    // window and type: "defaults write com.apple.Safari
	    // IncludeDebugMenu 1" Need the wrapper function because
	    // safari has personality problems.
	    this._console_sayer = function(msg){ window.console.log(msg + "\n"); };
	}else if( typeof(build) == 'function' && typeof(getpda) == 'function' && typeof(pc2line) == 'function' && typeof(print) == 'function' ){
	    // This may detect SpiderMonkey on the comand line.
	    this._console_sayer = function(msg){ print(msg); };
	}else if( typeof(org) != 'undefined' && typeof(org.rhino) != 'undefined' && typeof(print) == 'function' ){
	    // This may detect Rhino on the comand line.
	    this._console_sayer = function(msg){ print(msg); };
	}
	
	/**
	 * Log a string to somewhere. Also return a string to (mostly for
	 * the unit tests).
	 * 
	 * @param {string} initial_context - (optional) initial context as string
	 * @returns {string} string to print out to wherever we found
	 */
	this.kvetch = function(string){
	    var ret_str = null;
	    if( anchor.DEBUG == true ){

		// Make sure there is something there no matter what.
		if( typeof(string) == 'undefined' ){ string = ''; }

		// Redefined the string a little if we have contexts.
		if( anchor._context.length > 0 ){
		    var cstr = anchor._context.join(':');
		    string = cstr + ': '+ string;
		}

		// Actually log to the console.
		anchor._console_sayer(string);

		// Bind for output.
		ret_str = string;
	    }
	    return ret_str;
	};
    }

};