From 0313ffe56ef0b14f056bd91bfd0361fb23fcaf49 Mon Sep 17 00:00:00 2001 From: Josh Goldberg Date: Sun, 8 Mar 2015 04:04:56 -0400 Subject: [PATCH] Added StringFilr --- StringFilr.js | 266 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 266 insertions(+) create mode 100644 StringFilr.js diff --git a/StringFilr.js b/StringFilr.js new file mode 100644 index 0000000..bb5c4e3 --- /dev/null +++ b/StringFilr.js @@ -0,0 +1,266 @@ +/** + * StringFilr.js + * + * A general utility for retrieving data from an Object based on nested class + * names. You can think of the internal "library" Object as a tree structure, + * such that you can pass in a listing (in any order) of the path to data for + * retrieval. + * + * @example + * // Creating and using a StringFilr to store simple measurements. + * var StringFiler = new StringFilr({ + * "library": { + * "cup": "8oz", + * "gallon": "128oz", + * "half": { + * "cup": "4oz", + * "gallon": "64oz", + * } + * } + * }); + * console.log(StringFiler.get("cup")); // "8oz" + * console.log(StringFiler.get("half cup")); // "4oz" + * + * @example + * // Creating and using a StringFilr to store order-sensitive information. + * var StringFiler = new StringFilr({ + * "library": { + * "milk": { + * "chocolate": "A lighter chocolate" + * }, + * "chocolate": { + * "milk": "Milk mixed with syrup" + * } + * } + * }); + * console.log(StringFiler.get("milk chocolate")); // "A lighter chocolate" + * console.log(StringFiler.get("chocolate milk")); // "Milk mixed with syrup" + * + * @example + * // Creating and using a StringFilr to store a few people's measurements. + * var StringFiler = new StringFilr({ + * "normal": "color", + * "library": { + * "my": { + * "color": { + * "eye": "blue-green", + * "hair": "dirty blonde" + * }, + * "major": "Computer Science" + * }, + * "Mariah's": { + * "color": { + * "eye": "brown", + * "hair": "blonde" + * }, + * "major": "Biomedical Engineering" + * }, + * "Brandon's": { + * "color": { + * "eye": "black", + * "hair": "black" + * }, + * "major": "Computer Science" + * } + * } + * }); + * console.log(StringFiler.get("my major")); // "Computer Science" + * console.log(StringFiler.get("Mariah's eye color")); // "brown" + * console.log(StringFiler.get("Brandon's hair")); // "black" + * + * @author "Josh Goldberg" + */ +function StringFilr(settings) { + "use strict"; + if (!this || this === window) { + return new StringFilr(settings); + } + var self = this, + + // The library of data. + library, + + // Listing of previously found lookups, for speed's sake. + cache, + + // Optional default class to use when no suitable option is found. + normal, + + // Whether to crash when a sub-object in reset has no normal child. + requireNormalKey; + + /** + * Resets the StringFilr. + * + * @constructor + * @param {Object} library An Object containing data stored as children + * of sub-Objects. + * @param {String} [normal] A String to use as a default key to recurse + * on. Defaults to undefined, so falsy. + * @param {Boolean} [requireNormalKey] Whether it's ok for the library to + * have Objects that don't contain the + * normal key. Defaults to false. + */ + self.reset = function (settings) { + library = settings.library; + normal = settings.normal; + requireNormalKey = settings.requireNormalKey; + + cache = {}; + + if (requireNormalKey) { + if (!normal) { + throw new Error("StringFilr is given requireNormalKey, but no normal class."); + } + + var caught = findLackingNormal(library, "base", []); + if (caught.length) { + throw new Error("Found " + caught.length + " library " + + "sub-directories missing the normal: " + + "\r\n " + caught.join("\r\n ")); + } + } + }; + + /** + * @return {Object} The base library of stored information. + */ + self.getLibrary = function () { + return library; + }; + + /** + * @return {Object} The complete cache of cached output. + */ + self.getCache = function () { + return cache; + }; + + /** + * Completely clears the cache Object. + */ + self.clearCache = function () { + cache = {}; + }; + + /** + * Clears the cached entry for a key. + * + * @param {String} key + */ + self.clearCached = function(key) { + if (normal) { + key = key.replace(normal, ''); + } + + delete cache[key]; + } + + /** + * Retrieves the deepest matching data in the library for a key. + * + * @param {String} key + * @return {Mixed} + */ + self.get = function(key) { + var result; + + if (normal) { + key = key.replace(normal, ""); + } + + // Quickly return a cached result if it exists + if (cache.hasOwnProperty(key)) { + return cache[key] + }; + + result = followClass(key.split(/\s+/g), library); + + cache[key] = result; + return result; + } + + /** + * Utility helper to recursively check for tree branches in the library + * that don't have a key equal to the normal. For each sub-directory that + * is caught, the path to it is added to output. + * + * @param {Object} current The current location being searched within + * the library. + * @param {String} path The current path within the library. + * @param {String[] output An Array of the String paths to parts that + * don't have a matching key. + * @return {String[]} output + */ + self.findLackingNormal = function (current, path, output) { + var i; + + if (!current.hasOwnProperty(normal)) { + output.push(path); + } + + if (typeof current[i] === "object") { + for (i in current) { + findLackingNormal(current[i], path + " " + i, output); + } + } + + return output; + }; + + /** + * Utility function to follow a path into the library (this is the driver + * for searching into the library). For each available key, if it matches + * a key in current, it is removed from keys and recursion happens on the + * sub-directory in current. + * + * @param {String[]} keys The currently available keys to search within. + * @param {Object} current The current location being searched within + * the library. + * @return {Mixed} The most deeply matched part of the library. + */ + function followClass(keys, current) { + var key, i; + + // If keys runs out, we're done + if (!keys || !keys.length) { + return current; + } + + // For each key in the current array... + for (i in keys) { + key = keys[i]; + + // ...if it matches, recurse on the other keys + if (current.hasOwnProperty(key)) { + keys.splice(i, 1); + return followClass(keys, current[key]); + } + } + + // If no key matched, try the normal (default) + if (normal && current.hasOwnProperty(normal)) { + return followClass(keys, current[normal]); + } + + // Nothing matches anything; we're done. + return current; + } + + /** + * Simple utility function to get the last (deepest) parts of self.get + * results. This is useful because they're normally in reverse-order. + * + * @param {Mixed} results + */ + function getResultsFinal(results) { + if (typeof results[2] === "object") { + return getResultsFinal(results[2]); + } + + return results; + } + + + self.reset(settings || {}); +} \ No newline at end of file