Advertisement
mvaganov

testing/learning utility library for node.js

Mar 10th, 2015
343
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
  1. /**
  2.  * Custom functionality written by Michael Vaganov, while learning Node.js and JavaScript
  3.  * MIT License.
  4.  * @module mvaganov
  5.  */
  6.  
  7. var url = require("url");
  8.  
  9. /**
  10.  * add functionality to the String implementation.... pretty sweet that JavaScript can do this
  11.  */
  12. function addToStrings()
  13. {   "use strict";
  14.     if (typeof String.prototype.startsWith !== 'function')
  15.     {
  16.         String.prototype.startsWith = function (str){
  17.             return this.substring(0, str.length) === str; //this.slice(0, str.length) === str;
  18.         }
  19.     }
  20.     if (typeof String.prototype.replaceAll !== 'function')
  21.     {
  22.         /**
  23.          * @param {string} str1
  24.          * @param {string} str2
  25.          * @param {(boolean|null)} ignore ignore case?
  26.          * @return {string} a new string, a copy of this one, with all instances of str1 replaced with str2.
  27.          */
  28.         String.prototype.replaceAll = function(str1, str2, ignore)
  29.         {
  30.             return this.replace(new RegExp(
  31.                 str1.replace(
  32.                     /([\/\,\!\\\^\$\{\}\[\]\(\)\.\*\+\?\|\<\>\-\&])/g, "\\$&"
  33.                 ),
  34.                 (ignore?"gi":"g")),
  35.                 (typeof(str2)=="string")?str2.replace(/\$/g,"$$$$"):str2
  36.             );
  37.         }
  38.     }
  39.     if (typeof String.prototype.indexOfOneOfThese !== 'function')
  40.     {
  41.         /**
  42.          * @param {Array<String>} listOfDelimeters possible string delimeters that are being sought after
  43.          * @param {number=} start where to start looking in the string
  44.          * @return {Array<number>} [index that has one of these first, which delimeter was actually found here].
  45.          * if none of these exist, returns [this.length, -1]
  46.          * return[0] is the index
  47.          * return[1] is the index of the delimeter that was found
  48.          */
  49.         String.prototype.indexOfOneOfThese = function (listOfDelimeters, start)
  50.         {
  51.             var bestIndex = this.length;
  52.             var foundDelimeter = -1;
  53.             if(start == null) start = 0;
  54.             for(var i = 0; i < listOfDelimeters.length; ++i)
  55.             {
  56.                 var index = this.indexOf(listOfDelimeters[i], start);
  57.                 if(index >= 0 && index < bestIndex)
  58.                 {
  59.                     foundDelimeter  = i;
  60.                     bestIndex = index;
  61.                 }
  62.             }
  63.             return [bestIndex, foundDelimeter];
  64.         }
  65.     }
  66.     if (typeof String.prototype.splitByOneOfThese !== 'function')
  67.     {
  68.         /**
  69.          * @param {Array<String>} listOfDelimeters possible string delimeters that are being sought after
  70.          * @param {Number} maxSplits how many times to split. will split from left to right. -1 means no limit
  71.          * @return {Array<String>} as split, except with multiple delimeter tokens
  72.          */
  73.         String.prototype.splitByOneOfThese = function (listOfDelimeters, maxSplits)
  74.         {
  75.             if(maxSplits == null) maxSplits = -1;
  76.             var splitted = [], index = 0, whereToSplit, segment, splitCount = 0;
  77.             for(var i = 0; index < this.length; ++i)
  78.             {
  79.                 if(maxSplits >= 0 && splitCount >= maxSplits)
  80.                 {
  81.                     whereToSplit = [this.length, -1];
  82.                 }
  83.                 else
  84.                 {
  85.                     whereToSplit = this.indexOfOneOfThese(listOfDelimeters, index);
  86.                 }
  87.                 segment = this.slice(index, whereToSplit[0]);
  88.                 //console.log("("+index+", "+whereToSplit[0]+"... "+whereToSplit[1]+") ="+segment);
  89.                 splitCount++;
  90.                 if(segment.length > 0)
  91.                 {
  92.                     splitted.push(segment);
  93.                 }
  94.                 index = whereToSplit[0];
  95.                 if(whereToSplit[1] != -1)
  96.                 {
  97.                     index += listOfDelimeters[whereToSplit[1]].length;
  98.                 }
  99.             }
  100.             return splitted;
  101.         }
  102.     }
  103.     if (typeof String.prototype.splitIntoTable !== 'function')
  104.     {
  105.         /**
  106.          * @param {Array<String>} listOfEntryDelimeters example: {@code ["{", "}", ","]}
  107.          * @param {Array<String>} listOfAssignmentDelimeters example: {@code ["=",":"]}
  108.          * @return {Object<String,String>} a key/value pair table. see {@link String#prototype#parseCookies} as an example
  109.          */
  110.         String.prototype.splitIntoTable = function (listOfEntryDelimeters, listOfAssignmentDelimeters)
  111.         {
  112.             // first, split by entry delimeters
  113.             var entries = this.splitByOneOfThese(listOfEntryDelimeters);
  114.             // then split in half by the assignment delimeter
  115.             var table = {};
  116.             for(var i = 0; i < entries.length; ++i)
  117.             {
  118.                 var pair = entries[i].splitByOneOfThese(listOfAssignmentDelimeters, 1);
  119.                 if(pair.length > 1)
  120.                 {
  121.                     table[pair[0]] = pair[1];
  122.                 }
  123.                 else
  124.                 {
  125.                     table[pair[0]] = null;
  126.                 }
  127.             }
  128.             return table;
  129.         }
  130.     }
  131.     if (typeof String.prototype.parseCookies !== 'function')
  132.     {
  133.         /**
  134.          * @return {Map<String,String>} a table of cookies parameters, assuming this is formatted like an html cookie.
  135.          */
  136.         String.prototype.parseCookies = function ()
  137.         {
  138.             return this.splitIntoTable([";", " "], ["=", ":"]);
  139.         }
  140.     }
  141.     if (typeof ObjectId.prototype.getTimestamp !== 'function')
  142.     {
  143.         ObjectId.prototype.getTimestamp = function()
  144.         {
  145.             return new Date(parseInt(this.toString().slice(0,8), 16)*1000);
  146.         }
  147.     }
  148. }
  149. addToStrings();
  150.  
  151. /** a list of all of the modules this machine can access, built by the command line "npm ls --json" */
  152. var npmListing = null;
  153. var processStarted = false;
  154. function gatherNpmListing() {
  155.     if(processStarted) return;
  156.     processStarted = true;
  157.     // try to get other installed modules from the npm command line tool
  158.     require("child_process").exec("npm ls --json",
  159.         function(err, stdout, stderr) {
  160.             if (err) return console.log(err)
  161.             npmListing = JSON.parse(stdout);
  162.             // add the libraries to the npmListing
  163.             function addTheModuleToThisListing(npmListing, moduleName)
  164.             {
  165.                 console.log(moduleName);
  166.                 if(moduleName != null)
  167.                 {
  168.                     try{
  169.                         npmListing["module"] = require(moduleName);
  170.                     }catch(err){
  171.                         npmListing["module"] = err;
  172.                         //console.log("couldn't load "+moduleName);
  173.                     }
  174.                 }
  175.                 if(npmListing["dependencies"])
  176.                 {
  177.                     for(var d in npmListing.dependencies)
  178.                     {
  179.                         addTheModuleToThisListing(npmListing.dependencies[d], d);
  180.                     }
  181.                 }
  182.                 // after the root is done, print.
  183.                 if(moduleName == null)
  184.                 {
  185.                     printReflectionInConsole(npmListing, "npmListing", 3);
  186.                 }
  187.             }(npmListing); // call the function right after defining it
  188.         }
  189.     );
  190. }
  191.  
  192. /** @param {Object} obj print an object as a JSON string, including function code */
  193. function toJSONWithFuncs(obj)
  194. {
  195.     Object.prototype.toJSON = function()
  196.     {
  197.         var sobj = {}, i;
  198.         for (i in this)
  199.             if (this.hasOwnProperty(i))
  200.                 sobj[i] = typeof this[i] == 'function' ? this[i].toString() : this[i];
  201.         return sobj;
  202.     };
  203.     var str = JSON.stringify(obj);
  204.     delete Object.prototype.toJSON;
  205.     return str;
  206. }
  207.  
  208. /** how deep a tree to show when including the HTML traversal of JavaScriptObjects @type Number */
  209. var maxIndentLevel = 0;
  210. /** if true, will spam the server's console whenever {@link #createReflectedHtmlForJso} is called @type Boolean */
  211. var printInConsole = false;//true;
  212. var showPrivateVariables = true;
  213. var javaScriptObjectTraversalParameter = "jso";
  214.  
  215. /**
  216.  * creates a simple directory traversal interface using anchor tags
  217.  * @param {?} obj which object is being traversed (can be any type, including null)
  218.  * @param {?String=} pathStr the path (through parent objects) taken to get here (optional, can be null)
  219.  * @param {Number=} indentLevel used to properly indent, and prevent infinite recursion. limited by {@link #maxIndentLevel} (optional)
  220.  * @return {String} the HTML script that will allow traversal. the key parameter is {@link javaScriptObjectTraversalParameter}
  221.  */
  222. function createReflectedHtmlForJso(obj, pathStr, indentLevel)
  223. {   "use strict";
  224.     var indentation = "", i = 0, j = 0, props = [], functions = [], childText = [], nChildTexts = 0, key, strResult = "";
  225.     // default arguments
  226.     if ( typeof pathStr === 'undefined')        pathStr = "";
  227.     if ( typeof indentLevel === 'undefined')    indentLevel = 0;
  228.     // write the "path" at the top
  229.     if(indentLevel == 0)
  230.     {
  231.         strResult = "\n<h1>@ ";
  232.         var arr = pathStr.split(".");
  233.         for(i = 0; i < arr.length; ++i)
  234.         {
  235.             if(i > 0) strResult += ".";
  236.             strResult += "<a href=\"/?"+javaScriptObjectTraversalParameter+"=";
  237.             for(j = 0; j < i; ++j)
  238.             {
  239.                 strResult += arr[j] + ".";
  240.             }
  241.             strResult += arr[i] + "\">"+arr[i]+"</a>";
  242.         }
  243.         strResult += "</h1>\n";
  244.     }
  245.     // for debug output in the console...
  246.     if(printInConsole)
  247.         for(i=0; i < indentLevel; i++)
  248.             indentation = indentation.concat("    ");
  249.     // reflect a JS functions
  250.     if(typeof obj === 'function')
  251.     {
  252.         strResult += "<pre>"+obj.toString()+"</pre>";
  253.         if(printInConsole) console.log(obj.toString());
  254.     }
  255.     // reflect simple types
  256.     else if(typeof obj === 'string' || typeof obj === 'number' || typeof obj === 'boolean')
  257.     {
  258.         strResult += "<b>"+(typeof obj)+"</b> = "+obj+"\n";
  259.         if(printInConsole) console.log((typeof obj)+" = "+obj);
  260.     }
  261.     // reflect objects
  262.     else if(typeof obj === 'object')
  263.     {
  264.         // null objects don't need all of this processing
  265.         if(obj != null)
  266.         {
  267.             // asks an object for all of it's members
  268.             for(key in obj)
  269.             {
  270.                 if(obj.hasOwnProperty(key) && (showPrivateVariables || !key.startsWith('_')))
  271.                 {
  272.                     if(typeof obj[key] !== 'function')
  273.                     {
  274.                         if(printInConsole) console.log(indentation+key+" : "+typeof obj[key]+" = "+obj[key]);
  275.                         props.push(key);
  276.                         if(typeof obj[key] === 'object'
  277.                         && indentLevel < maxIndentLevel)
  278.                         {
  279.                             var childStr;
  280.                             if(obj[key] == null)
  281.                                 childStr = "";
  282.                             else
  283.                                 childStr = createReflectedHtmlForJso(obj[key], pathStr+"."+key, indentLevel+1);
  284.                             if(indentLevel < maxIndentLevel)
  285.                             {
  286.                                 childText[nChildTexts] = childStr;
  287.                                 nChildTexts += 1;
  288.                             }
  289.                         }
  290.                     }
  291.                     else
  292.                     {
  293.                         functions.push(key);
  294.                     }
  295.                 }
  296.             }
  297.             strResult += "<ul>"
  298.             j = 0;
  299.             // print child elements
  300.             for(i = 0; i < props.length; ++i)
  301.             {
  302.                 key = props[i];
  303.                 strResult += "<li><a href=\"/?"+javaScriptObjectTraversalParameter+"="+pathStr+"."+key+"\">"+key+"</a> : <b>"+(typeof obj[key])+"</b> = <i>"+obj[key]+"</i>";
  304.                 // include members from the first tier of child objects
  305.                 if(typeof obj[key] === 'object' && obj[key] != null)
  306.                 {
  307.                     strResult += "["+Object.keys(obj[key]).length+"]";
  308.                     if(indentLevel < maxIndentLevel)
  309.                     {
  310.                         strResult += childText[j];
  311.                         j++;
  312.                     }
  313.                 }
  314.                 strResult += "\n";
  315.             }
  316.             for(i = 0; i < functions.length; ++i)
  317.             {
  318.                 key = functions[i];
  319.                 var codestr = obj[key].toString();
  320.                 codestr = codestr.slice(0, codestr.indexOf('{'));
  321.                 if(printInConsole) console.log(indentation+key+" : "+typeof obj[key]+" = "+codestr);
  322.                 codestr = codestr.replaceAll(" ", "&nbsp");
  323.                 codestr = codestr.replaceAll("\n", "<br>");
  324.                 strResult += "<li><a href=\"/?"+javaScriptObjectTraversalParameter+"="+pathStr+"."+key+"\">"+key+"</a> : <font face=\"courier\">"+codestr+"</font>\n";
  325.             }
  326.             strResult += "</ul>";
  327.         }
  328.         else
  329.         {
  330.             strResult += "<b>null</b>";
  331.         }
  332.     }
  333.     else
  334.     {
  335.         strResult += "unknown JavaScriptObject<pre>"+obj+"</pre>";
  336.         if(printInConsole) console.log("unknown type ("+(typeof obj)+"): "+obj);
  337.     }
  338.     return strResult;
  339. }
  340.  
  341. /**
  342.  * @nosideeffects
  343.  * @param {?Object=} obj what object to print out
  344.  * @param {?String=} name how to label this object when printing in the console
  345.  * @param {Number=} depth how many children deep to print out. if null, {@link #maxIndentLevel} is used.
  346.  */
  347.  function printReflectionInConsole(obj, name, depth)
  348. {
  349.     if(name != null)
  350.     {
  351.         console.log("[["+name+"]]");
  352.     }
  353.     var oldMax = maxIndentLevel;
  354.     if(depth != null)
  355.     {
  356.         maxIndentLevel = depth;
  357.     }
  358.     printInConsole = true;
  359.     createReflectedHtmlForJso(obj);
  360.     printInConsole = false;
  361.     if(oldMax != maxIndentLevel)
  362.     {
  363.         maxIndentLevel = oldMax;
  364.     }
  365. }
  366.  
  367. function jsoNavigation(nameToEvaluate, localVariables)
  368. {
  369.     var whatObjectToLookAt = null;
  370.     var explicitlyNull = false;
  371.     var strOutput = "";
  372.     var jso = localVariables;
  373.     if(nameToEvaluate != null && nameToEvaluate !== "")
  374.     {
  375.         try{
  376.             //whatObjectToLookAt = eval(nameToEvaluate); // breaks if member has a strange character in the name
  377.             var p = nameToEvaluate.split('.');
  378.             var cursor = eval(p[0]);
  379.             for(var i = 1; i < p.length; ++i)
  380.             {
  381.                 cursor = cursor[p[i]];
  382.             }
  383.             whatObjectToLookAt = cursor;
  384.             //console.log("found <"+nameToEvaluate+">");
  385.             explicitlyNull = true;
  386.         }catch(err){
  387.             console.log("couldn't parse \""+nameToEvaluate+"\" : "+err);
  388.         }
  389.     }
  390.     //console.log(whatObjectToLookAt+" "+explicitlyNull)
  391.     if(whatObjectToLookAt == null && !explicitlyNull)
  392.     {
  393.         strOutput += "<a href=\"/?"+javaScriptObjectTraversalParameter+
  394.             "=jso\">jso</a><br>\n";
  395.         //console.log(strOutput);
  396.     }
  397.     else
  398.     {
  399.         var reflectedString = createReflectedHtmlForJso(whatObjectToLookAt, nameToEvaluate);
  400.         strOutput += reflectedString;
  401.     }
  402.     return strOutput;
  403. }
  404.  
  405.  /**
  406.  * @param request {HTTPRequest} the HTTP request
  407.  * @param request {HTTPResponse} the HTTP response
  408.  */
  409.  function jsoNavHtml(request, response)
  410. {
  411.     extraVariables = ["./mvaganov", "./router", "./helloserver", "./requesthandler", "./db",
  412.         // "formidable", "express"
  413.         // "http", "fs", "sys", "util", "querystring" // these are in process.moduleLoadList
  414.         ];
  415.     // get native modules from 'process'
  416.     var i;
  417.     var loadListModules = process.moduleLoadList;
  418.     var nativeModleLabel = "NativeModule ";
  419.     var entry;
  420.     for(i = 0; i < loadListModules.length; ++i)
  421.     {
  422.         entry = loadListModules[i];
  423.         if(entry.startsWith(nativeModleLabel))
  424.         {
  425.             extraVariables.push(entry.slice(nativeModleLabel.length, entry.length));
  426.         }
  427.     }
  428.     // load the basic (known) modules up...
  429.     var localVariables = [];
  430.     for(i = 0; i < extraVariables.length; ++i)
  431.     {
  432.         var str = extraVariables[i];
  433.         var name = str;
  434.         if(name.startsWith("./"))
  435.         {
  436.             name = name.slice(2, name.length);
  437.         }
  438.         var loadedModule;
  439.         try
  440.         {
  441.             loadedModule = require(str);
  442.         }
  443.         catch(err)
  444.         {
  445.             loadedModule = "ERROR: could not load module."
  446.         }
  447.         localVariables[name] = loadedModule;
  448.     }
  449.     // add the global process, and the request/response of this HTTP event
  450.     localVariables["process"] = process;
  451.     localVariables["request"] = request;
  452.     localVariables["response"] = response;
  453.     // try to get other installed modules from the npm command line tool
  454.     gatherNpmListing();
  455.     if(npmListing != null)
  456.     {
  457.         localVariables["npmListing"] = npmListing;
  458.     }
  459.  
  460.     //var q = querystring.parse(request.url);
  461.     var parsedURL = url.parse(request.url);
  462.     var fullpath = parsedURL.path;
  463.     var argsIndex = fullpath.indexOfOneOfThese(['?', '&'])[0];
  464.     //var pathname = fullpath.slice(0, argsIndex);
  465.     var pathargs = fullpath.slice(argsIndex+1, fullpath.length);
  466.     var arguments = pathargs.splitIntoTable(["&", "?"], ["="]);
  467.     //printReflectionInConsole(arguments, "---ARGUMENTS---");
  468.     var nameToEvaluate = arguments[javaScriptObjectTraversalParameter];
  469.     if(nameToEvaluate != null)
  470.     {
  471.         return jsoNavigation(nameToEvaluate, localVariables);
  472.     }
  473.     return "";
  474. }
  475.  
  476. function jsoNav(request, response, next)
  477. {
  478.     var htmlOutput = jsoNavHtml(request, response);
  479.     if(htmlOutput && htmlOutput.length > 0)
  480.     {
  481.         response.setHeader("Content-Type", "text/html");
  482.         response.write(htmlOutput);
  483.         response.end();
  484.     }
  485.     else
  486.     {
  487.         next();
  488.     }
  489. }
  490.  
  491. /** TODO reasearch the following
  492.  
  493. * for a game loop on the server: http://nodejs.org/api/globals.html#globals_settimeout_cb_ms
  494.  
  495. * for heavy lifty C code, call exec:
  496. function execCbPageTest()
  497. {
  498.     var exec = require("child_process").exec;
  499.     exec("dir",
  500.         function resultCallBack(error, stdout, stderr)
  501.         {
  502.             if (error !== null) {
  503.                 console.log('exec error: ' + error);
  504.             }
  505.             stdout = stdout.toString();
  506.             stdout = stdout.replaceAll("<", "&lt");
  507.             stdout = stdout.replaceAll(">", "&gt");
  508.             response.writeHead(200, {"Content-Type": "text/html"});
  509.             response.write("<pre>"+stdout+"</pre>");
  510.             router.writeHtmlComponents(response, htmlComponentsToAdd);
  511.             response.end();
  512.         }
  513.     );
  514. }
  515.  
  516. * learn more about Express via this tutorial: http://expressjs.com/guide.html
  517.  
  518. * define object prototypes by using the prototype keyword, or __proto__ (would that work?)
  519.  
  520. * events, and event emitters http://www.sitepoint.com/nodejs-events-and-eventemitter/
  521.  
  522. * https://npmjs.org/browse/keyword/middleware/0/
  523.   * gauth Middleware component to authenticate users through their Google account
  524.   * authenticated ensure a request is authenticated
  525.  
  526. * JSDocs used by Google:
  527.   https://developers.google.com/closure/compiler/docs/js-for-compiler#tags
  528.   http://google-styleguide.googlecode.com/svn/trunk/javascriptguide.xml?showone=Comments#Comments
  529.  
  530.  
  531. * modules to check out
  532.   socket.io - real-time network communication
  533.   request - simplified http reqeust client. has authentication, and other cool stuff?
  534.   grunt - large scale automation
  535.   mocha - testing framework
  536.   async - more powerful asynchronous tools & structures for node.js
  537.   mongoose - ORM (object relational managed) database
  538.   passport - simple authentication for node.js
  539.  
  540. * modules that seem bad
  541.   redis - big fancy data structure store... like a global variables crutch?
  542. */
  543. exports.jsoNav = jsoNav;
  544. exports.jsoNavHtml = jsoNavHtml;
  545. exports.printReflectionInConsole = printReflectionInConsole;
  546. exports.logjso = printReflectionInConsole;
  547. exports.toJSONWithFuncs = toJSONWithFuncs;
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement