<commit_before>/**
 * Module dependencies
 */

var fs = require('fs');
var path = require('path');
var _ = require('@sailshq/lodash');
var chalk = require('chalk');
var machine = require('machine');
var loadHelpers = require('./private/load-helpers');
var iterateHelpers = require('./private/iterate-helpers');

/**
 * Helpers hook
 */

module.exports = function(sails) {

  return {

    defaults: {
      helpers: {
        // Custom usage options:
        usageOpts: {
          arginStyle: 'serial',
          execStyle: 'natural',
        },

        // Experimental: Programmatically provide a dictionary of helpers.
        moduleDefinitions: undefined,
      }
    },

    configure: function() {

      // Check SVR of the `sails` dep in this app's package.json file.
      // If it points at a 1.0 prerelease less than `1.0.0-44`, then log a warning
      // explaining what's going on with helpers (we needed to make a breaking
      // change in a prerelease), and that the old way can be achieved through
      // configuration.  Also mention that, to avoid breaking this app, we've
      // applied the old configuration automatically, but that if the SVR in the
      // package.json is changed, then this protection will disappear, and this
      // app will potentially break.
      //
      // To resolve this, either:
      //  • (A) configure `sails.config.helpers.usageOpts` as `{arginStyle: 'named', execStyle: 'deferred'}`
      //  • or (B) change any code in this app that invokes helpers to take advantage of the new default style
      //           of usage, then upgrade to the latest version of Sails v1.0 and update
      //           your package.json file accordingly to make this warning go away.
      if (sails.config.helpers.usageOpts.arginStyle !== 'named' || sails.config.helpers.usageOpts.execStyle !== 'deferred') {

        var localSailsSVR = (function _gettingSVROfLocalSailsDep(){
          var pjPath = path.resolve(sails.config.appPath, 'package.json');

          var rawPjText;
          try {
            rawPjText = fs.readFileSync(pjPath, 'utf8');
          } catch (unusedErr) {}

          var appPj;
          try {
            appPj = JSON.parse(rawPjText);
          } catch (unusedErr) {}

          return appPj && appPj.dependencies && appPj.dependencies.sails;
        })();//†

        var prerelease = localSailsSVR && localSailsSVR.match(/1\.0\.0\-(.+)$/);
        prerelease = prerelease && prerelease[1];
        var isMinSVRPointedAtSensitiveV1Prerelease = prerelease && Number(prerelease) < 44;

        if (isMinSVRPointedAtSensitiveV1Prerelease) {
          sails.config.helpers.usageOpts.arginStyle = 'named';
          sails.config.helpers.usageOpts.execStyle = 'deferred';
          sails.log.warn(
            'Based on its package.json file, it looks like this app was generated using the Sails beta, '+
            'but with a version prior to v1.0.0-44.  (It depends on sails@'+localSailsSVR+'.)  '+
            'But in the 1.0.0-44 prerelease, a change was introduced that makes helpers support standard, '+
            'serial usage like `.changePassword(\'abc123\'), rather than always being forced to use named '+
            'parameters like `.changePassword({password:\'abc123\'})`.\n'+
            'To avoid breaking this app, backwards-compatible settings have been set automatically for '+
            'you this time to keep your helpers compatible.  (But be sure to resolve this as soon as possible, '+
            'or you might lift one day soon and realize your helpers don\'t work!)\n'+
            'To resolve this, either:\n'+
            ' • (A) configure `sails.config.helpers.usageOpts.arginStyle` as "named" and also '+
            '`sails.config.helpers.usageOpts.execStyle` as "deferred"}`, which will make everything '+
            'work the way it did before.\n'+
            ' • or (B) change any code in this app that invokes helpers.  You\'ll need to either take '+
            'advantage of the new default usage style or tack on `.with()` to each call to continue using '+
            'named parameters.  Then upgrade to the latest version of Sails v1.0 and update your package.json '+
            'file accordingly.\n'+
            '(This warning will continue to display until this has been solved one way or the other.)\n'+
            ' [?] If you\'re unsure, visit https://sailsjs.com/support\n'
          );
        }//ﬁ
      }//ﬁ

      // Define `sails.helpers` here so that it can potentially be used by other hooks.
      // > NOTE: This is NOT `sails.config.helpers`-- this is `sails.helpers`!
      // > (As for sails.config.helpers, it's set automatically based on our `defaults above)
      sails.helpers = {};
      Object.defineProperty(sails.helpers, 'inspect', {
        enumerable: false,
        configurable: false,
        writable: true,
        value: function inspect(){

          // Tree diagram:
          // ```
          //    .
          //    ├── …
          //    │   ├── …
          //    │   │   └── …
          //    │   ├── …
          //    │   └── …
          //    │
          //    ├── …
          //    │   ├── …
          //    │   │   └── …
          //    │   ├── …
          //    │   │   ├── …
          //    │   │   ├── …
          //    │   │   └── …
          //    │   ├── …
          //    │   └── …
          //    │
          //    ├── …
          //    │   └── …
          //    │
          //    ├── …
          //    ├── …
          //    └── …
          // ```
          var treeDiagram = (function(){
            var OFFSET             = '   ';
            var TAB                = '    ';
            var SYMBOL_INITIAL     = '.   ';
            var SYMBOL_NO_BRANCH   = '│   ';
            var SYMBOL_MID_BRANCH  = '├── ';
            var SYMBOL_LAST_BRANCH = '└── ';

            var treeDiagram = '';
            treeDiagram += OFFSET + SYMBOL_INITIAL + '\n';
            iterateHelpers(
              sails.helpers,
              function _onBeforeStartingPack(pack, key, depth, isFirst, isLast, lastnessPerAncestor){
                var indentation = _.reduce(lastnessPerAncestor, function(indentation, wasLast){
                  if (wasLast) {
                    indentation += TAB;
                  } else {
                    indentation += SYMBOL_NO_BRANCH;
                  }
                  return indentation;
                }, '');

                if (isLast) {
                  treeDiagram += OFFSET + indentation + SYMBOL_LAST_BRANCH + key + '\n';
                } else {
                  treeDiagram += OFFSET + indentation + SYMBOL_MID_BRANCH + key + '\n';
                }
              },
              undefined,// « no need for an _onAfterFinishingPack notifier here, so we omit it
              function _onHelper(callable, methodName, depth, isFirst, isLast, lastnessPerAncestor){
                var indentation = _.reduce(lastnessPerAncestor, function(indentation, wasLast){
                  if (wasLast) {
                    indentation += TAB;
                  } else {
                    indentation += SYMBOL_NO_BRANCH;
                  }
                  return indentation;
                }, '');

                if (isLast) {
                  treeDiagram += OFFSET + indentation + SYMBOL_LAST_BRANCH + (callable.toJSON()._fromLocalSailsApp ? chalk.bold.cyan(methodName) : chalk.italic(methodName)) + chalk.gray('()')+'\n';
                  if (depth === 2) {
                    treeDiagram += OFFSET + indentation + '\n';
                  }
                } else {
                  treeDiagram += OFFSET + indentation + SYMBOL_MID_BRANCH + (callable.toJSON()._fromLocalSailsApp ? chalk.bold.cyan(methodName) : chalk.italic(methodName)) + chalk.gray('()')+'\n';
                }
              }
            );
            return treeDiagram;
          })();//†

          // Examples (asynchronous and synchronous)
          var example1 = (function(){
            var exampleArginPhrase = '';
            if (sails.config.helpers.usageOpts.arginStyle === 'named') {
              exampleArginPhrase = '{dir: \'./colorado/\'}';
            } else if (sails.config.helpers.usageOpts.arginStyle === 'serial') {
              exampleArginPhrase = '\'./colorado/\'';
            }

            return 'var contents = await sails.helpers.fs.ls('+exampleArginPhrase+');';
          })();//†
          var example2 = (function(){
            var exampleArginPhrase = '';
            if (sails.config.helpers.usageOpts.arginStyle === 'named') {
              exampleArginPhrase = '{style: \'url-friendly\'}';
            } else if (sails.config.helpers.usageOpts.arginStyle === 'serial') {
              exampleArginPhrase = '\'url-friendly\'';
            }

            if (sails.config.helpers.usageOpts.execStyle === 'deferred') {
              return 'var name = sails.helpers.strings.random('+exampleArginPhrase+').now();';
            } else if (sails.config.helpers.usageOpts.execStyle === 'immediate' || sails.config.helpers.usageOpts.execStyle === 'natural') {
              return 'var name = sails.helpers.strings.random('+exampleArginPhrase+');';
            }
            throw new Error('Consistency violation: Unrecognized arginStyle/execStyle in sails.config.helpers.usageOpts  (This should never happen, since it should have already been validated and prevented from being built- please report at https://sailsjs.com/bugs)');
          })();//†

          return ''+
          '-------------------------------------------------------\n'+
          ' sails.helpers\n'+
          '\n'+
          ' Available methods:\n'+
          treeDiagram+'\n'+
          '\n'+
          ' Example usage:\n'+
          '   '+example1+'\n'+
          '   '+example2+'\n'+
          '\n'+
          ' More info:\n'+
          '   https://sailsjs.com/support\n'+
          '-------------------------------------------------------\n';
        }//ƒ
      });//…)

    },

    initialize: function(done) {

      // Load helpers from the appropriate folder.
      loadHelpers(sails, done);

    },

    /**
     * @experimental
     * (This might change at any time, without a major version release!)
     */
    furnishPack: function(slug, packInfo){
      packInfo = packInfo || {};
      slug = _.map(slug.split('.'), _.kebabCase).join('.');
      var slugKeyPath = _.map(slug.split('.'), _.camelCase).join('.');
      var chunks = slugKeyPath.split('.');

      if (chunks.length > 1) {
        sails.log.verbose(
          'Watch out!  Nesting helpers more than one sub-folder deep can be a liability.  '+
          'It also means that you\'ll need to type more every time you want to use '+
          'your helper.  Instead, try keeping your directory structure as flat as possible; '+
          'i.e. in general, having more explicit filenames is better than having deep, '+
          'complicated folder hierarchies.'
        );
      }

      // If pack already exists, avast.
      if (_.get(sails.helpers, slugKeyPath)) {
        return;
      }

      // Ancestor packs:
      var thisKeyPath;
      var theseChunks;
      var parentKeyPath;
      var parentPackOrRoot;
      for (var i = 0; i < chunks.length - 1; i++) {
        theseChunks = chunks.slice(0,i+1);
        thisKeyPath = theseChunks.join('.');
        parentKeyPath = theseChunks.slice(0, -1).join('.');
        if (!_.get(sails.helpers, thisKeyPath)) {
          parentPackOrRoot = parentKeyPath ? _.get(sails.helpers, parentKeyPath) : sails.helpers;
          parentPackOrRoot[chunks[i]] = machine.pack({
            name: 'sails.helpers.'+chunks.slice(0,i+1).join('.'),
            defs: {},
            customize: sails.config.helpers.usageOpts
          });
        }
      }//∞

      // Main pack:
      parentKeyPath = chunks.slice(0, -1).join('.');
      parentPackOrRoot = parentKeyPath ? _.get(sails.helpers, parentKeyPath) : sails.helpers;
      parentPackOrRoot[chunks[chunks.length - 1]] = machine.pack(_.extend({}, packInfo, {
        name: 'sails.helpers.'+slugKeyPath,
        customize: sails.config.helpers.usageOpts
      }));
    },

    /**
     * @experimental
     * (This might change at any time, without a major version release!)
     */
    furnishHelper: function(identityPlusMaybeSlug, nmDef){

      // Ensure we're starting off with dot-delimited, kebab-cased hops.
      identityPlusMaybeSlug = _.map(identityPlusMaybeSlug.split('.'), _.kebabCase).join('.');

      var chunks = identityPlusMaybeSlug.split('.');

      // slug ('foo-bar.baz-bing.beep.boop')
      // identity ('do-something')
      var slug = chunks.length >= 2 ? chunks.slice(0, -1).join('.') : undefined;
      var identity = _.last(chunks);

      // Camel-case every part of the file path, and join with dots
      // e.g. admin-stuff.foo.do-something => adminStuff.foo.doSomething
      var slugKeyPath = slug ? _.map(slug.split('.'), _.camelCase).join('.') : undefined;
      var fullKeyPath = slug ? slugKeyPath + '.' + machine.getMethodName(identity) : machine.getMethodName(identity);

      if (!_.get(sails.helpers, fullKeyPath)) {

        // Work our way down
        if (slug && !_.get(sails.helpers, slugKeyPath)) {
          this.furnishPack(slug, {
            name: 'sails.helpers.'+slugKeyPath,
            defs: {}
          });
        }//ﬁ

        // And then build the helper last
        // > (can't do it first!  We'd confuse `_.get()`!)

        // Use provided `identity` if no explicit identity was set.
        // (Otherwise, as of machine@v15, this could fail with an ImplementationError.)
        if (!nmDef.identity) {
          nmDef.identity = identity;
        }

        // Attach new method to the appropriate pack.
        // e.g. sails.helpers.userHelpers.foo.myHelper
        if (slug) {
          var parentPack = _.get(sails.helpers, slugKeyPath);
          parentPack.registerDefs(
            (function(){
              var defs = {};
              defs[identity] = nmDef;
              return defs;
            })()//†
          );
        } else {
          sails.helpers[machine.getMethodName(identity)] = machine.buildWithCustomUsage(_.extend({
            def: nmDef
          }, sails.config.helpers.usageOpts));
        }

      }//ﬁ

    },

    /**
     * sails.hooks.helpers.reload()
     *
     * @param  {Dictionary?}   helpers [if specified, these helpers will replace all existing helpers.  Otherwise, if omitted, helpers will be freshly reloaded from disk, and old helpers will be thrown away.]
     * @param  {Function} done    [optional callback]
     *
     * @experimental
     * (This might change at any time, without a major version release!)
     */
    reload: function(helpers, done) {

      // Handle variadic usage
      if (typeof helpers === 'function') {
        done = helpers;
        helpers = undefined;
      }

      // Handle optional callback
      done = done || function _noopCb(err){
        if (err) {
          sails.log.error('Could not reload helpers due to an error:', err, '\n(continuing anyway...)');
        }
      };//ƒ

      // If we received an explicit set of helpers to load, use them.
      // Otherwise reload helpers from the appropriate folder.
      if (helpers) {
        sails.helpers = helpers;
        return done();
      } else {
        return loadHelpers(sails, done);
      }
    }//ƒ

  };

};
<commit_msg>trivial<commit_after>/**
 * Module dependencies
 */

var fs = require('fs');
var path = require('path');
var _ = require('@sailshq/lodash');
var chalk = require('chalk');
var machine = require('machine');
var loadHelpers = require('./private/load-helpers');
var iterateHelpers = require('./private/iterate-helpers');

/**
 * Helpers hook
 */

module.exports = function(sails) {

  return {

    defaults: {
      helpers: {
        // Custom usage options:
        usageOpts: {
          arginStyle: 'serial',
          execStyle: 'natural',
        },

        // Experimental: Programmatically provide a dictionary of helpers.
        moduleDefinitions: undefined,
      }
    },

    configure: function() {

      // Check SVR of the `sails` dep in this app's package.json file.
      // If it points at a 1.0 prerelease less than `1.0.0-44`, then log a warning
      // explaining what's going on with helpers (we needed to make a breaking
      // change in a prerelease), and that the old way can be achieved through
      // configuration.  Also mention that, to avoid breaking this app, we've
      // applied the old configuration automatically, but that if the SVR in the
      // package.json is changed, then this protection will disappear, and this
      // app will potentially break.
      //
      // To resolve this, either:
      //  • (A) configure `sails.config.helpers.usageOpts` as `{arginStyle: 'named', execStyle: 'deferred'}`
      //  • or (B) change any code in this app that invokes helpers to take advantage of the new default style
      //           of usage, then upgrade to the latest version of Sails v1.0 and update
      //           your package.json file accordingly to make this warning go away.
      if (sails.config.helpers.usageOpts.arginStyle !== 'named' || sails.config.helpers.usageOpts.execStyle !== 'deferred') {

        var localSailsSVR = (function _gettingSVROfLocalSailsDep(){
          var pjPath = path.resolve(sails.config.appPath, 'package.json');

          var rawPjText;
          try {
            rawPjText = fs.readFileSync(pjPath, 'utf8');
          } catch (unusedErr) {}

          var appPj;
          try {
            appPj = JSON.parse(rawPjText);
          } catch (unusedErr) {}

          return appPj && appPj.dependencies && appPj.dependencies.sails;
        })();//†

        var prerelease = localSailsSVR && localSailsSVR.match(/1\.0\.0\-(.+)$/);
        prerelease = prerelease && prerelease[1];
        var isMinSVRPointedAtSensitiveV1Prerelease = prerelease && Number(prerelease) < 44;

        if (isMinSVRPointedAtSensitiveV1Prerelease) {
          sails.config.helpers.usageOpts.arginStyle = 'named';
          sails.config.helpers.usageOpts.execStyle = 'deferred';
          sails.log.warn(
            'Based on its package.json file, it looks like this app was generated using the Sails beta, '+
            'but with a version prior to v1.0.0-44.  (It depends on sails@'+localSailsSVR+'.)  '+
            'But in the 1.0.0-44 prerelease, a change was introduced that makes helpers support standard, '+
            'serial usage like `.changePassword(\'abc123\'), rather than always being forced to use named '+
            'parameters like `.changePassword({password:\'abc123\'})`.\n'+
            'To avoid breaking this app, backwards-compatible settings have been set automatically for '+
            'you this time to keep your helpers compatible.  (But be sure to resolve this as soon as possible, '+
            'or you might lift one day soon and realize your helpers don\'t work!)\n'+
            'To resolve this, either:\n'+
            ' • (A) configure `sails.config.helpers.usageOpts.arginStyle` as "named" and also '+
            '`sails.config.helpers.usageOpts.execStyle` as "deferred"`, which will make everything '+
            'work the way it did before.\n'+
            ' • or (B) change any code in this app that invokes helpers.  You\'ll need to either take '+
            'advantage of serial usage in your app, configure the aforementioned usage opts, or tack on '+
            '`.with()` every time you call a helper to continue using named parameters.  Then upgrade to '+
            'the latest version of Sails v1.0 and update your package.json file accordingly.\n'+
            '(This warning will continue to display until this has been solved one way or the other.)\n'+
            ' [?] If you\'re unsure, visit https://sailsjs.com/support\n'
          );
        }//ﬁ
      }//ﬁ

      // Define `sails.helpers` here so that it can potentially be used by other hooks.
      // > NOTE: This is NOT `sails.config.helpers`-- this is `sails.helpers`!
      // > (As for sails.config.helpers, it's set automatically based on our `defaults above)
      sails.helpers = {};
      Object.defineProperty(sails.helpers, 'inspect', {
        enumerable: false,
        configurable: false,
        writable: true,
        value: function inspect(){

          // Tree diagram:
          // ```
          //    .
          //    ├── …
          //    │   ├── …
          //    │   │   └── …
          //    │   ├── …
          //    │   └── …
          //    │
          //    ├── …
          //    │   ├── …
          //    │   │   └── …
          //    │   ├── …
          //    │   │   ├── …
          //    │   │   ├── …
          //    │   │   └── …
          //    │   ├── …
          //    │   └── …
          //    │
          //    ├── …
          //    │   └── …
          //    │
          //    ├── …
          //    ├── …
          //    └── …
          // ```
          var treeDiagram = (function(){
            var OFFSET             = '   ';
            var TAB                = '    ';
            var SYMBOL_INITIAL     = '.   ';
            var SYMBOL_NO_BRANCH   = '│   ';
            var SYMBOL_MID_BRANCH  = '├── ';
            var SYMBOL_LAST_BRANCH = '└── ';

            var treeDiagram = '';
            treeDiagram += OFFSET + SYMBOL_INITIAL + '\n';
            iterateHelpers(
              sails.helpers,
              function _onBeforeStartingPack(pack, key, depth, isFirst, isLast, lastnessPerAncestor){
                var indentation = _.reduce(lastnessPerAncestor, function(indentation, wasLast){
                  if (wasLast) {
                    indentation += TAB;
                  } else {
                    indentation += SYMBOL_NO_BRANCH;
                  }
                  return indentation;
                }, '');

                if (isLast) {
                  treeDiagram += OFFSET + indentation + SYMBOL_LAST_BRANCH + key + '\n';
                } else {
                  treeDiagram += OFFSET + indentation + SYMBOL_MID_BRANCH + key + '\n';
                }
              },
              undefined,// « no need for an _onAfterFinishingPack notifier here, so we omit it
              function _onHelper(callable, methodName, depth, isFirst, isLast, lastnessPerAncestor){
                var indentation = _.reduce(lastnessPerAncestor, function(indentation, wasLast){
                  if (wasLast) {
                    indentation += TAB;
                  } else {
                    indentation += SYMBOL_NO_BRANCH;
                  }
                  return indentation;
                }, '');

                if (isLast) {
                  treeDiagram += OFFSET + indentation + SYMBOL_LAST_BRANCH + (callable.toJSON()._fromLocalSailsApp ? chalk.bold.cyan(methodName) : chalk.italic(methodName)) + chalk.gray('()')+'\n';
                  if (depth === 2) {
                    treeDiagram += OFFSET + indentation + '\n';
                  }
                } else {
                  treeDiagram += OFFSET + indentation + SYMBOL_MID_BRANCH + (callable.toJSON()._fromLocalSailsApp ? chalk.bold.cyan(methodName) : chalk.italic(methodName)) + chalk.gray('()')+'\n';
                }
              }
            );
            return treeDiagram;
          })();//†

          // Examples (asynchronous and synchronous)
          var example1 = (function(){
            var exampleArginPhrase = '';
            if (sails.config.helpers.usageOpts.arginStyle === 'named') {
              exampleArginPhrase = '{dir: \'./colorado/\'}';
            } else if (sails.config.helpers.usageOpts.arginStyle === 'serial') {
              exampleArginPhrase = '\'./colorado/\'';
            }

            return 'var contents = await sails.helpers.fs.ls('+exampleArginPhrase+');';
          })();//†
          var example2 = (function(){
            var exampleArginPhrase = '';
            if (sails.config.helpers.usageOpts.arginStyle === 'named') {
              exampleArginPhrase = '{style: \'url-friendly\'}';
            } else if (sails.config.helpers.usageOpts.arginStyle === 'serial') {
              exampleArginPhrase = '\'url-friendly\'';
            }

            if (sails.config.helpers.usageOpts.execStyle === 'deferred') {
              return 'var name = sails.helpers.strings.random('+exampleArginPhrase+').now();';
            } else if (sails.config.helpers.usageOpts.execStyle === 'immediate' || sails.config.helpers.usageOpts.execStyle === 'natural') {
              return 'var name = sails.helpers.strings.random('+exampleArginPhrase+');';
            }
            throw new Error('Consistency violation: Unrecognized arginStyle/execStyle in sails.config.helpers.usageOpts  (This should never happen, since it should have already been validated and prevented from being built- please report at https://sailsjs.com/bugs)');
          })();//†

          return ''+
          '-------------------------------------------------------\n'+
          ' sails.helpers\n'+
          '\n'+
          ' Available methods:\n'+
          treeDiagram+'\n'+
          '\n'+
          ' Example usage:\n'+
          '   '+example1+'\n'+
          '   '+example2+'\n'+
          '\n'+
          ' More info:\n'+
          '   https://sailsjs.com/support\n'+
          '-------------------------------------------------------\n';
        }//ƒ
      });//…)

    },

    initialize: function(done) {

      // Load helpers from the appropriate folder.
      loadHelpers(sails, done);

    },

    /**
     * @experimental
     * (This might change at any time, without a major version release!)
     */
    furnishPack: function(slug, packInfo){
      packInfo = packInfo || {};
      slug = _.map(slug.split('.'), _.kebabCase).join('.');
      var slugKeyPath = _.map(slug.split('.'), _.camelCase).join('.');
      var chunks = slugKeyPath.split('.');

      if (chunks.length > 1) {
        sails.log.verbose(
          'Watch out!  Nesting helpers more than one sub-folder deep can be a liability.  '+
          'It also means that you\'ll need to type more every time you want to use '+
          'your helper.  Instead, try keeping your directory structure as flat as possible; '+
          'i.e. in general, having more explicit filenames is better than having deep, '+
          'complicated folder hierarchies.'
        );
      }

      // If pack already exists, avast.
      if (_.get(sails.helpers, slugKeyPath)) {
        return;
      }

      // Ancestor packs:
      var thisKeyPath;
      var theseChunks;
      var parentKeyPath;
      var parentPackOrRoot;
      for (var i = 0; i < chunks.length - 1; i++) {
        theseChunks = chunks.slice(0,i+1);
        thisKeyPath = theseChunks.join('.');
        parentKeyPath = theseChunks.slice(0, -1).join('.');
        if (!_.get(sails.helpers, thisKeyPath)) {
          parentPackOrRoot = parentKeyPath ? _.get(sails.helpers, parentKeyPath) : sails.helpers;
          parentPackOrRoot[chunks[i]] = machine.pack({
            name: 'sails.helpers.'+chunks.slice(0,i+1).join('.'),
            defs: {},
            customize: sails.config.helpers.usageOpts
          });
        }
      }//∞

      // Main pack:
      parentKeyPath = chunks.slice(0, -1).join('.');
      parentPackOrRoot = parentKeyPath ? _.get(sails.helpers, parentKeyPath) : sails.helpers;
      parentPackOrRoot[chunks[chunks.length - 1]] = machine.pack(_.extend({}, packInfo, {
        name: 'sails.helpers.'+slugKeyPath,
        customize: sails.config.helpers.usageOpts
      }));
    },

    /**
     * @experimental
     * (This might change at any time, without a major version release!)
     */
    furnishHelper: function(identityPlusMaybeSlug, nmDef){

      // Ensure we're starting off with dot-delimited, kebab-cased hops.
      identityPlusMaybeSlug = _.map(identityPlusMaybeSlug.split('.'), _.kebabCase).join('.');

      var chunks = identityPlusMaybeSlug.split('.');

      // slug ('foo-bar.baz-bing.beep.boop')
      // identity ('do-something')
      var slug = chunks.length >= 2 ? chunks.slice(0, -1).join('.') : undefined;
      var identity = _.last(chunks);

      // Camel-case every part of the file path, and join with dots
      // e.g. admin-stuff.foo.do-something => adminStuff.foo.doSomething
      var slugKeyPath = slug ? _.map(slug.split('.'), _.camelCase).join('.') : undefined;
      var fullKeyPath = slug ? slugKeyPath + '.' + machine.getMethodName(identity) : machine.getMethodName(identity);

      if (!_.get(sails.helpers, fullKeyPath)) {

        // Work our way down
        if (slug && !_.get(sails.helpers, slugKeyPath)) {
          this.furnishPack(slug, {
            name: 'sails.helpers.'+slugKeyPath,
            defs: {}
          });
        }//ﬁ

        // And then build the helper last
        // > (can't do it first!  We'd confuse `_.get()`!)

        // Use provided `identity` if no explicit identity was set.
        // (Otherwise, as of machine@v15, this could fail with an ImplementationError.)
        if (!nmDef.identity) {
          nmDef.identity = identity;
        }

        // Attach new method to the appropriate pack.
        // e.g. sails.helpers.userHelpers.foo.myHelper
        if (slug) {
          var parentPack = _.get(sails.helpers, slugKeyPath);
          parentPack.registerDefs(
            (function(){
              var defs = {};
              defs[identity] = nmDef;
              return defs;
            })()//†
          );
        } else {
          sails.helpers[machine.getMethodName(identity)] = machine.buildWithCustomUsage(_.extend({
            def: nmDef
          }, sails.config.helpers.usageOpts));
        }

      }//ﬁ

    },

    /**
     * sails.hooks.helpers.reload()
     *
     * @param  {Dictionary?}   helpers [if specified, these helpers will replace all existing helpers.  Otherwise, if omitted, helpers will be freshly reloaded from disk, and old helpers will be thrown away.]
     * @param  {Function} done    [optional callback]
     *
     * @experimental
     * (This might change at any time, without a major version release!)
     */
    reload: function(helpers, done) {

      // Handle variadic usage
      if (typeof helpers === 'function') {
        done = helpers;
        helpers = undefined;
      }

      // Handle optional callback
      done = done || function _noopCb(err){
        if (err) {
          sails.log.error('Could not reload helpers due to an error:', err, '\n(continuing anyway...)');
        }
      };//ƒ

      // If we received an explicit set of helpers to load, use them.
      // Otherwise reload helpers from the appropriate folder.
      if (helpers) {
        sails.helpers = helpers;
        return done();
      } else {
        return loadHelpers(sails, done);
      }
    }//ƒ

  };

};
<|endoftext|>


<commit_before>'use strict';

var HttpGateway = require('./gateway');
var store       = require('store');
var qs          = require('querystring');
var _           = require('underscore');

var HttpAuthGateway = HttpGateway.extend({

    /**
     * Location in localStorage to store and retrieve the OAuth token
     *
     * @type {String}
     */
    tokenStorageLocation : 'token',

    /**
     * Prefix for the authorization header
     *
     * @type {String}
     */
    authorizationHeaderPrefix : 'Bearer ',

    /**
     * {@inheritDoc}
     */
    getRequestOptions : function(method, path, data)
    {
        var options, token;

        options = HttpGateway.prototype.getRequestOptions.call(this, method, path, data);

        token = store.get(this.tokenStorageLocation);
        if (token) {
            options.headers.Authorization = this.authorizationHeaderPrefix + token.access_token;
        }

        return options;
    },

    /**
     * {@inheritDoc}
     */
    handleError : function(response, responseData, resolve, reject, method, path, data, headers)
    {
        if (response.statusCode === 401) {
            this.handle401(resolve, reject, method, path, data, headers);

            return;
        }

        HttpGateway.prototype.handleError(response, responseData, resolve, reject, method, path, data, headers);
    },

    /**
     * Handle 401 Unauthorized responses
     *
     * Assume that the oauth access token has expired and the refresh token
     * needs to be exchanged for a new one.
     *
     * The successful response of the refresh token exchange request will be
     * in the following shape:
     * {
     *     "access_token" : "6339f1a7...",
     *     "expires_in"   : 3600,
     *     "token_type"   : "bearer",
     *     "scope"        : null,
     *     "user_id"      : "1"
     * }
     */
    handle401 : function(resolve, reject, method, path, data, headers)
    {
        var gateway, token, refreshData, refreshHeaders, handleSuccess, handleFailure;

        gateway = this;
        token   = store.get(this.tokenStorageLocation);

        data = data || {};

        handleSuccess = function (response) {
            token = _.extend(token, response);

            store.set(this.tokenStorageLocation, token);

            gateway.apiRequest(method, path, data, headers).then(resolve, reject);
        };

        handleFailure = function (errors) {
            var config = gateway.getConfig();

            store.clear();

            if (config.login_url) {
                window.location = config.login_url;
            } else {
                window.location = '/';
            }
        };

        refreshData = qs.stringify({
            client_id     : this.config.client_id,
            grant_type    : 'refresh_token',
            refresh_token : token.refresh_token
        });

        refreshHeaders = {'Content-Type' : 'application/x-www-form-urlencoded'};

        this.apiRequest('POST', '/oauth/token', refreshData, refreshHeaders).then(handleSuccess, handleFailure);
    }

});

module.exports = HttpAuthGateway;
<commit_msg>Refs #37 - Fix scope issue<commit_after>'use strict';

var HttpGateway = require('./gateway');
var store       = require('store');
var qs          = require('querystring');
var _           = require('underscore');

var HttpAuthGateway = HttpGateway.extend({

    /**
     * Location in localStorage to store and retrieve the OAuth token
     *
     * @type {String}
     */
    tokenStorageLocation : 'token',

    /**
     * Prefix for the authorization header
     *
     * @type {String}
     */
    authorizationHeaderPrefix : 'Bearer ',

    /**
     * {@inheritDoc}
     */
    getRequestOptions : function(method, path, data)
    {
        var options, token;

        options = HttpGateway.prototype.getRequestOptions.call(this, method, path, data);

        token = store.get(this.tokenStorageLocation);
        if (token) {
            options.headers.Authorization = this.authorizationHeaderPrefix + token.access_token;
        }

        return options;
    },

    /**
     * {@inheritDoc}
     */
    handleError : function(response, responseData, resolve, reject, method, path, data, headers)
    {
        if (response.statusCode === 401) {
            this.handle401(resolve, reject, method, path, data, headers);

            return;
        }

        HttpGateway.prototype.handleError(response, responseData, resolve, reject, method, path, data, headers);
    },

    /**
     * Handle 401 Unauthorized responses
     *
     * Assume that the oauth access token has expired and the refresh token
     * needs to be exchanged for a new one.
     *
     * The successful response of the refresh token exchange request will be
     * in the following shape:
     * {
     *     "access_token" : "6339f1a7...",
     *     "expires_in"   : 3600,
     *     "token_type"   : "bearer",
     *     "scope"        : null,
     *     "user_id"      : "1"
     * }
     */
    handle401 : function(resolve, reject, method, path, data, headers)
    {
        var gateway, token, refreshData, refreshHeaders, handleSuccess, handleFailure;

        gateway = this;
        token   = store.get(this.tokenStorageLocation);

        data = data || {};

        handleSuccess = function (response) {
            token = _.extend(token, response);

            store.set(gateway.tokenStorageLocation, token);

            gateway.apiRequest(method, path, data, headers).then(resolve, reject);
        };

        handleFailure = function (errors) {
            var config = gateway.getConfig();

            store.clear();

            if (config.login_url) {
                window.location = config.login_url;
            } else {
                window.location = '/';
            }
        };

        refreshData = qs.stringify({
            client_id     : this.config.client_id,
            grant_type    : 'refresh_token',
            refresh_token : token.refresh_token
        });

        refreshHeaders = {'Content-Type' : 'application/x-www-form-urlencoded'};

        this.apiRequest('POST', '/oauth/token', refreshData, refreshHeaders).then(handleSuccess, handleFailure);
    }

});

module.exports = HttpAuthGateway;
<|endoftext|>


<commit_before>//////////////////////////////
// Nunjucks
//  - A Gulp Plugin
//
// Builds Nunjucks to HTML
//////////////////////////////
'use strict';

//////////////////////////////
// Variables
//////////////////////////////
var through = require('through2'),
    gutil = require('gulp-util'),
    path = require('path'),
    fs = require('fs-extra'),
    dateformat = require('dateformat'),
    fm = require('front-matter'),
    marked = require('./markdown'),
    PluginError = gutil.PluginError,
    PLUGIN_NAME = 'nunjucks',
    gulpNunjucks;

//////////////////////////////
// Set Nunjucks stuff
//////////////////////////////

//////////////////////////////
// Export
//////////////////////////////
gulpNunjucks = function (options) {
  options = options || {};

  var nunjucks = gulpNunjucks.compiler;
  var nunjucksPaths = options.paths;
  var nunjucksEnv;

  nunjucksEnv = nunjucks.configure(nunjucksPaths, {
    'noCache': true
  });

  //////////////////////////////
  // Nunjucks Filters and Tags
  //////////////////////////////
  nunjucksEnv.addFilter('attributes', function (file, attribute) {
    var content;
    file = fs.readFileSync(path.join(process.cwd(), file));

    content = fm(file.toString());

    if (content.attributes) {
      if (attribute) {
        return content.attributes[attribute];
      }
      else {
        return content.attributes;
      }
    }
    else {
      return {};
    }

    return file;
  });

  nunjucksEnv.addFilter('body', function (file, attribute) {
    var content;
    file = fs.readFileSync(path.join(process.cwd(), file));

    return content = fm(file.toString()).body;
  });
  nunjucksEnv.addFilter('render', function (file) {
    var content = fs.readFileSync(path.join(process.cwd(), file));

    content = fm(content.toString()).body;

    if (path.extname(file) === '.html') {
      return content;
    }
    else {
      return marked(content);
    }

  });

  nunjucksEnv.addFilter('markdown', function (content) {
    return marked(content);
  });

  nunjucksEnv.addFilter('date', function (content, format) {
    return dateformat(content, format);
  });

  // Iterate over all custom defined filters and add them
  Object.keys(options.filters).forEach(function (filter) {
    nunjucksEnv.addFilter(filter, options.filters[filter]);
  });

  // Iterate over all custom defined tags and add them
  Object.keys(options.tags).forEach(function (tag) {
    nunjucksEnv.addExtension(tag, options.tags[tag]);
  });

  //////////////////////////////
  // Command line arguments for each option
  //////////////////////////////
  process.argv.forEach(function (val, index, array) {
    if (index >= 3) {
      switch (val) {
        case '--fail': options.failOnError = true; break;
      }
    }
  });

  //////////////////////////////
  // Through Object
  //////////////////////////////
  var compile = through.obj(function (file, encoding, cb) {
    var ext = path.extname(file.path),
        context = {};

    /////////////////////////////
    // Default plugin issues
    //////////////////////////////
    if (file.isNull()) {
      return cb();
    }
    if (file.isStream()) {
      this.emit('error', new PluginError(PLUGIN_NAME, 'Streams are not supported!'));
      return cb();
    }

    //////////////////////////////
    // Manipulate Files
    //////////////////////////////
    if (ext === '.html') {
      if (file.meta) {
        context = file.meta;
        context.filename = file.path;

        if (!file.meta.published) {
          context.published = file.stat.birthtime;
        }
        if (!file.meta.updated) {
          context.updated = file.stat.mtime;
        }

        context.stats = file.stat;

        // If a template exists in the meta info, build a content block and extend for it
        if (file.meta.template) {
          file.contents = new Buffer(nunjucksEnv.renderString(
            '{% extends "' + file.meta.template + '" %}' +
            '{% block content %}' + file.contents.toString() + '{% endblock %}'
            , context)
          );
        }
        // Otherwise, just render it!
        else {
          file.contents = new Buffer(nunjucksEnv.renderString(file.contents.toString(), context));
        }
      }
    }

    //////////////////////////////
    // Push the file back to the stream!
    //////////////////////////////
    this.push(file);

    //////////////////////////////
    // Callback to tell us we're done
    //////////////////////////////
    cb();
  })

  return compile;
}

gulpNunjucks.compiler = require('nunjucks');

gulpNunjucks.filters = {};
gulpNunjucks.tags = {};
gulpNunjucks.paths = [];

module.exports = gulpNunjucks;
<commit_msg>:bug: Fix render filter to actually render<commit_after>//////////////////////////////
// Nunjucks
//  - A Gulp Plugin
//
// Builds Nunjucks to HTML
//////////////////////////////
'use strict';

//////////////////////////////
// Variables
//////////////////////////////
var through = require('through2'),
    gutil = require('gulp-util'),
    path = require('path'),
    fs = require('fs-extra'),
    dateformat = require('dateformat'),
    fm = require('front-matter'),
    marked = require('./markdown'),
    PluginError = gutil.PluginError,
    PLUGIN_NAME = 'nunjucks',
    gulpNunjucks;

//////////////////////////////
// Set Nunjucks stuff
//////////////////////////////

//////////////////////////////
// Export
//////////////////////////////
gulpNunjucks = function (options) {
  options = options || {};

  var nunjucks = gulpNunjucks.compiler;
  var nunjucksPaths = options.paths;
  var nunjucksEnv;

  nunjucksEnv = nunjucks.configure(nunjucksPaths, {
    'noCache': true
  });

  //////////////////////////////
  // Nunjucks Filters and Tags
  //////////////////////////////
  nunjucksEnv.addFilter('attributes', function (file, attribute) {
    var content;
    file = fs.readFileSync(path.join(process.cwd(), file));

    content = fm(file.toString());

    if (content.attributes) {
      if (attribute) {
        return content.attributes[attribute];
      }
      else {
        return content.attributes;
      }
    }
    else {
      return {};
    }

    return file;
  });

  nunjucksEnv.addFilter('body', function (file, attribute) {
    var content;
    file = fs.readFileSync(path.join(process.cwd(), file));

    return content = fm(file.toString()).body;
  });

  nunjucksEnv.addFilter('render', function (file) {
    var content = fs.readFileSync(path.join(process.cwd(), file)),
        matter = fm(content.toString());

    content = matter.body;

    if (path.extname(file) !== '.html') {
      content = marked(content);
    }

    return nunjucksEnv.renderString(content, matter.attributes);

  });

  nunjucksEnv.addFilter('markdown', function (content) {
    return marked(content);
  });

  nunjucksEnv.addFilter('date', function (content, format) {
    return dateformat(content, format);
  });

  // Iterate over all custom defined filters and add them
  Object.keys(options.filters).forEach(function (filter) {
    nunjucksEnv.addFilter(filter, options.filters[filter]);
  });

  // Iterate over all custom defined tags and add them
  Object.keys(options.tags).forEach(function (tag) {
    nunjucksEnv.addExtension(tag, options.tags[tag]);
  });

  //////////////////////////////
  // Command line arguments for each option
  //////////////////////////////
  process.argv.forEach(function (val, index, array) {
    if (index >= 3) {
      switch (val) {
        case '--fail': options.failOnError = true; break;
      }
    }
  });

  //////////////////////////////
  // Through Object
  //////////////////////////////
  var compile = through.obj(function (file, encoding, cb) {
    var ext = path.extname(file.path),
        context = {};

    /////////////////////////////
    // Default plugin issues
    //////////////////////////////
    if (file.isNull()) {
      return cb();
    }
    if (file.isStream()) {
      this.emit('error', new PluginError(PLUGIN_NAME, 'Streams are not supported!'));
      return cb();
    }

    //////////////////////////////
    // Manipulate Files
    //////////////////////////////
    if (ext === '.html') {
      if (file.meta) {
        context = file.meta;
        context.filename = file.path;

        if (!file.meta.published) {
          context.published = file.stat.birthtime;
        }
        if (!file.meta.updated) {
          context.updated = file.stat.mtime;
        }

        context.stats = file.stat;

        // If a template exists in the meta info, build a content block and extend for it
        if (file.meta.template) {
          file.contents = new Buffer(nunjucksEnv.renderString(
            '{% extends "' + file.meta.template + '" %}' +
            '{% block content %}' + file.contents.toString() + '{% endblock %}'
            , context)
          );
        }
        // Otherwise, just render it!
        else {
          file.contents = new Buffer(nunjucksEnv.renderString(file.contents.toString(), context));
        }
      }
    }

    //////////////////////////////
    // Push the file back to the stream!
    //////////////////////////////
    this.push(file);

    //////////////////////////////
    // Callback to tell us we're done
    //////////////////////////////
    cb();
  })

  return compile;
}

gulpNunjucks.compiler = require('nunjucks');

gulpNunjucks.filters = {};
gulpNunjucks.tags = {};
gulpNunjucks.paths = [];

module.exports = gulpNunjucks;
<|endoftext|>


<commit_before>jasmine.HtmlReporterHelpers = {};

jasmine.HtmlReporterHelpers.createDom = function(type, attrs, childrenVarArgs) {
  var el = document.createElement(type);

  for (var i = 2; i < arguments.length; i++) {
    var child = arguments[i];

    if (typeof child === 'string') {
      el.appendChild(document.createTextNode(child));
    } else {
      if (child) {
        el.appendChild(child);
      }
    }
  }

  for (var attr in attrs) {
    if (attr == "className") {
      el[attr] = attrs[attr];
    } else {
      el.setAttribute(attr, attrs[attr]);
    }
  }

  return el;
};

jasmine.HtmlReporterHelpers.getSpecStatus = function(child) {
  var results = child.results();
  var status = results.passed() ? 'passed' : 'failed';
  if (results.skipped) {
    status = 'skipped';
  }

  return status;
};

jasmine.HtmlReporterHelpers.appendToSummary = function(child, childElement) {
  var parentDiv = this.dom.summary;
  var parentSuite = (typeof child.parentSuite == 'undefined') ? 'suite' : 'parentSuite';
  var parent = child[parentSuite];

  if (parent) {
    if (typeof this.views.suites[parent.id] == 'undefined') {
      this.views.suites[parent.id] = new jasmine.HtmlReporter.SuiteView(parent, this.dom, this.views);
    }
    parentDiv = this.views.suites[parent.id].element;
  }

  parentDiv.appendChild(childElement);
};

jasmine.HtmlReporterHelpers.addHelpers = function(ctor) {
  for(var fn in jasmine.HtmlReporterHelpers) {
    ctor.prototype[fn] = jasmine.HtmlReporterHelpers[fn];
  }
};

jasmine.HtmlReporter = function(_doc) {
  var self = this;
  var doc = _doc || window.document;

  var reporterView;

  var dom = {};

  // Jasmine Reporter Public Interface
  self.logRunningSpecs = false;

  self.reportRunnerStarting = function(runner) {
    var specs = runner.specs() || [];

    if (specs.length == 0) {
      return;
    }

    createReporterDom();
    $('#content .slide.specs').append(dom.reporter);

    reporterView = new jasmine.HtmlReporter.ReporterView(dom);
    reporterView.addSpecs(specs, self.specFilter);
  };

  self.reportRunnerResults = function(runner) {
    reporterView && reporterView.complete();
  };

  self.reportSuiteResults = function(suite) {
    reporterView.suiteComplete(suite);
  };

  self.reportSpecStarting = function(spec) {
    if (self.logRunningSpecs) {
      self.log('>> Jasmine Running ' + spec.suite.description + ' ' + spec.description + '...');
    }
  };

  self.reportSpecResults = function(spec) {
    reporterView.specComplete(spec);
  };

  self.log = function() {
    var console = jasmine.getGlobal().console;
    if (console && console.log) {
      if (console.log.apply) {
        console.log.apply(console, arguments);
      } else {
        console.log(arguments); // ie fix: console.log.apply doesn't exist on ie
      }
    }
  };

  self.specFilter = function(spec) {
    if (!focusedSpecName()) {
      return true;
    }

    return spec.getFullName().indexOf(focusedSpecName()) === 0;
  };

  return self;

  function focusedSpecName() {
    var specName;

    (function memoizeFocusedSpec() {
      if (specName) {
        return;
      }

      var paramMap = [];
      var params = jasmine.HtmlReporter.parameters(doc);

      for (var i = 0; i < params.length; i++) {
        var p = params[i].split('=');
        paramMap[decodeURIComponent(p[0])] = decodeURIComponent(p[1]);
      }

      specName = paramMap.spec;
    })();

    return specName;
  }

  function createReporterDom() {
    dom.reporter = self.createDom('div', { id: 'HTMLReporter', className: 'jasmine_reporter' },
      dom.banner = self.createDom('div', { className: 'banner' }),
      dom.results = self.createDom('div', {className: 'results'},
        dom.summary = self.createDom('div', { className: 'summary' }))
    );
  }

};
jasmine.HtmlReporter.parameters = function(doc) {
  var paramStr = doc.location.search.substring(1);
  var params = [];

  if (paramStr.length > 0) {
    params = paramStr.split('&');
  }
  return params;
}
jasmine.HtmlReporter.sectionLink = function(sectionName) {
  var link = '?';
  var params = [];

  if (sectionName) {
    params.push('spec=' + encodeURIComponent(sectionName));
  }
  if (params.length > 0) {
    link += params.join("&");
  }

  return link;
};
jasmine.HtmlReporterHelpers.addHelpers(jasmine.HtmlReporter);
jasmine.HtmlReporter.ReporterView = function(dom) {
  this.startedAt = new Date();
  this.runningSpecCount = 0;
  this.completeSpecCount = 0;
  this.passedCount = 0;
  this.failedCount = 0;
  this.skippedCount = 0;

  this.addSpecs = function(specs, specFilter) {
    this.totalSpecCount = specs.length;

    this.views = {
      specs: {},
      suites: {}
    };

    for (var i = 0; i < specs.length; i++) {
      var spec = specs[i];
      this.views.specs[spec.id] = new jasmine.HtmlReporter.SpecView(spec, dom, this.views);
      if (specFilter(spec)) {
        this.runningSpecCount++;
      }
    }
  };

  this.specComplete = function(spec) {
    this.completeSpecCount++;

    if (isUndefined(this.views.specs[spec.id])) {
      this.views.specs[spec.id] = new jasmine.HtmlReporter.SpecView(spec, dom);
    }

    var specView = this.views.specs[spec.id];

    switch (specView.status()) {
      case 'passed':
        this.passedCount++;
        break;

      case 'failed':
        this.failedCount++;
        break;

      case 'skipped':
        this.skippedCount++;
        break;
    }

    specView.refresh();
    this.refresh();
  };

  this.suiteComplete = function(suite) {
    var suiteView = this.views.suites[suite.id];
    if (isUndefined(suiteView)) {
      return;
    }
    suiteView.refresh();
  };

  this.refresh = function() {
    var testsSummary = [];
    if (this.passedCount > 0) {
      testsSummary.push(' ' + this.passedCount);
    }
    if (this.failedCount > 0) {
      testsSummary.push(' ' + this.failedCount);
    }
    $('.specs-summary').html(testsSummary.join(' '));
  };

  this.complete = function() {
    var duration = ((new Date().getTime() - this.startedAt.getTime()) / 1000);
    $(dom.banner).append(' ' + duration + 's');
  };

  return this;

  function isUndefined(obj) {
    return typeof obj === 'undefined';
  }

  function isDefined(obj) {
    return !isUndefined(obj);
  }

  function specPluralizedFor(count) {
    var str = count + " spec";
    if (count > 1) {
      str += "s"
    }
    return str;
  }

};

jasmine.HtmlReporterHelpers.addHelpers(jasmine.HtmlReporter.ReporterView);

jasmine.HtmlReporter.SpecView = function(spec, dom, views) {
  this.spec = spec;
  this.dom = dom;
  this.views = views;

  this.symbol = this.createDom('li', { className: 'pending' });
  $('.specs-bullets').append(this.symbol);

  var nth = $('.specs-bullets').children().length - 1,
      rows = Math.round($('.specs-bullets').height() / $(this.symbol).height());
  $(this.symbol).css({
    top: (nth % rows) * $(this.symbol).height(),
    right: Math.floor(nth / rows) * $(this.symbol).width(),
    opacity: Math.random() * 0.3 + 0.1
  });

  this.summary = this.createDom('div', { className: 'specSummary' },
    this.createDom('a', {
      className: 'description',
      href: jasmine.HtmlReporter.sectionLink(this.spec.getFullName()),
      title: this.spec.getFullName()
    }, this.spec.description)
  );

  this.detail = this.createDom('div', { className: 'specDetail' },
      this.createDom('a', {
        className: 'description',
        href: '?spec=' + encodeURIComponent(this.spec.getFullName()),
        title: this.spec.getFullName()
      }, this.spec.getFullName())
  );
};

jasmine.HtmlReporter.SpecView.prototype.status = function() {
  return this.getSpecStatus(this.spec);
};

jasmine.HtmlReporter.SpecView.prototype.refresh = function() {
  this.symbol.className = this.status();

  switch (this.status()) {
    case 'skipped':
      break;

    case 'passed':
      this.appendSummaryToSuiteDiv();
      break;

    case 'failed':
      this.appendSummaryToSuiteDiv();
      this.appendFailureDetail();
      break;
  }
};

jasmine.HtmlReporter.SpecView.prototype.appendSummaryToSuiteDiv = function() {
  this.summary.className += ' ' + this.status();
  this.appendToSummary(this.spec, this.summary);
};

jasmine.HtmlReporter.SpecView.prototype.appendFailureDetail = function() {
  this.detail.className += ' ' + this.status();

  var resultItems = this.spec.results().getItems();
  var messagesDiv = this.createDom('div', { className: 'messages' });

  for (var i = 0; i < resultItems.length; i++) {
    var result = resultItems[i];

    if (result.type == 'log') {
      messagesDiv.appendChild(this.createDom('div', {className: 'resultMessage log'}, result.toString()));
    } else if (result.type == 'expect' && result.passed && !result.passed()) {
      messagesDiv.appendChild(this.createDom('div', {className: 'resultMessage fail'}, result.message));

      if (result.trace.stack) {
        messagesDiv.appendChild(this.createDom('div', {className: 'stackTrace'}, result.trace.stack));
      }
    }
  }

  if (messagesDiv.childNodes.length > 0) {
    this.summary.appendChild(messagesDiv);
  }
};

jasmine.HtmlReporterHelpers.addHelpers(jasmine.HtmlReporter.SpecView);jasmine.HtmlReporter.SuiteView = function(suite, dom, views) {
  this.suite = suite;
  this.dom = dom;
  this.views = views;

  this.element = this.createDom('div', { className: 'suite' },
    this.createDom('div', { className: 'specTitle' },
      this.createDom('a', {
        className: 'description',
        href: jasmine.HtmlReporter.sectionLink(this.suite.getFullName()),
      }, this.suite.description)
    )
  );

  this.appendToSummary(this.suite, this.element);
};

jasmine.HtmlReporter.SuiteView.prototype.status = function() {
  return this.getSpecStatus(this.suite);
};

jasmine.HtmlReporter.SuiteView.prototype.refresh = function() {
  this.element.className += " " + this.status();
};

jasmine.HtmlReporterHelpers.addHelpers(jasmine.HtmlReporter.SuiteView);
<commit_msg>Remove results message on failing specs (found in stacktrace) #9<commit_after>jasmine.HtmlReporterHelpers = {};

jasmine.HtmlReporterHelpers.createDom = function(type, attrs, childrenVarArgs) {
  var el = document.createElement(type);

  for (var i = 2; i < arguments.length; i++) {
    var child = arguments[i];

    if (typeof child === 'string') {
      el.appendChild(document.createTextNode(child));
    } else {
      if (child) {
        el.appendChild(child);
      }
    }
  }

  for (var attr in attrs) {
    if (attr == "className") {
      el[attr] = attrs[attr];
    } else {
      el.setAttribute(attr, attrs[attr]);
    }
  }

  return el;
};

jasmine.HtmlReporterHelpers.getSpecStatus = function(child) {
  var results = child.results();
  var status = results.passed() ? 'passed' : 'failed';
  if (results.skipped) {
    status = 'skipped';
  }

  return status;
};

jasmine.HtmlReporterHelpers.appendToSummary = function(child, childElement) {
  var parentDiv = this.dom.summary;
  var parentSuite = (typeof child.parentSuite == 'undefined') ? 'suite' : 'parentSuite';
  var parent = child[parentSuite];

  if (parent) {
    if (typeof this.views.suites[parent.id] == 'undefined') {
      this.views.suites[parent.id] = new jasmine.HtmlReporter.SuiteView(parent, this.dom, this.views);
    }
    parentDiv = this.views.suites[parent.id].element;
  }

  parentDiv.appendChild(childElement);
};

jasmine.HtmlReporterHelpers.addHelpers = function(ctor) {
  for(var fn in jasmine.HtmlReporterHelpers) {
    ctor.prototype[fn] = jasmine.HtmlReporterHelpers[fn];
  }
};

jasmine.HtmlReporter = function(_doc) {
  var self = this;
  var doc = _doc || window.document;

  var reporterView;

  var dom = {};

  // Jasmine Reporter Public Interface
  self.logRunningSpecs = false;

  self.reportRunnerStarting = function(runner) {
    var specs = runner.specs() || [];

    if (specs.length == 0) {
      return;
    }

    createReporterDom();
    $('#content .slide.specs').append(dom.reporter);

    reporterView = new jasmine.HtmlReporter.ReporterView(dom);
    reporterView.addSpecs(specs, self.specFilter);
  };

  self.reportRunnerResults = function(runner) {
    reporterView && reporterView.complete();
  };

  self.reportSuiteResults = function(suite) {
    reporterView.suiteComplete(suite);
  };

  self.reportSpecStarting = function(spec) {
    if (self.logRunningSpecs) {
      self.log('>> Jasmine Running ' + spec.suite.description + ' ' + spec.description + '...');
    }
  };

  self.reportSpecResults = function(spec) {
    reporterView.specComplete(spec);
  };

  self.log = function() {
    var console = jasmine.getGlobal().console;
    if (console && console.log) {
      if (console.log.apply) {
        console.log.apply(console, arguments);
      } else {
        console.log(arguments); // ie fix: console.log.apply doesn't exist on ie
      }
    }
  };

  self.specFilter = function(spec) {
    if (!focusedSpecName()) {
      return true;
    }

    return spec.getFullName().indexOf(focusedSpecName()) === 0;
  };

  return self;

  function focusedSpecName() {
    var specName;

    (function memoizeFocusedSpec() {
      if (specName) {
        return;
      }

      var paramMap = [];
      var params = jasmine.HtmlReporter.parameters(doc);

      for (var i = 0; i < params.length; i++) {
        var p = params[i].split('=');
        paramMap[decodeURIComponent(p[0])] = decodeURIComponent(p[1]);
      }

      specName = paramMap.spec;
    })();

    return specName;
  }

  function createReporterDom() {
    dom.reporter = self.createDom('div', { id: 'HTMLReporter', className: 'jasmine_reporter' },
      dom.banner = self.createDom('div', { className: 'banner' }),
      dom.results = self.createDom('div', {className: 'results'},
        dom.summary = self.createDom('div', { className: 'summary' }))
    );
  }

};
jasmine.HtmlReporter.parameters = function(doc) {
  var paramStr = doc.location.search.substring(1);
  var params = [];

  if (paramStr.length > 0) {
    params = paramStr.split('&');
  }
  return params;
}
jasmine.HtmlReporter.sectionLink = function(sectionName) {
  var link = '?';
  var params = [];

  if (sectionName) {
    params.push('spec=' + encodeURIComponent(sectionName));
  }
  if (params.length > 0) {
    link += params.join("&");
  }

  return link;
};
jasmine.HtmlReporterHelpers.addHelpers(jasmine.HtmlReporter);
jasmine.HtmlReporter.ReporterView = function(dom) {
  this.startedAt = new Date();
  this.runningSpecCount = 0;
  this.completeSpecCount = 0;
  this.passedCount = 0;
  this.failedCount = 0;
  this.skippedCount = 0;

  this.addSpecs = function(specs, specFilter) {
    this.totalSpecCount = specs.length;

    this.views = {
      specs: {},
      suites: {}
    };

    for (var i = 0; i < specs.length; i++) {
      var spec = specs[i];
      this.views.specs[spec.id] = new jasmine.HtmlReporter.SpecView(spec, dom, this.views);
      if (specFilter(spec)) {
        this.runningSpecCount++;
      }
    }
  };

  this.specComplete = function(spec) {
    this.completeSpecCount++;

    if (isUndefined(this.views.specs[spec.id])) {
      this.views.specs[spec.id] = new jasmine.HtmlReporter.SpecView(spec, dom);
    }

    var specView = this.views.specs[spec.id];

    switch (specView.status()) {
      case 'passed':
        this.passedCount++;
        break;

      case 'failed':
        this.failedCount++;
        break;

      case 'skipped':
        this.skippedCount++;
        break;
    }

    specView.refresh();
    this.refresh();
  };

  this.suiteComplete = function(suite) {
    var suiteView = this.views.suites[suite.id];
    if (isUndefined(suiteView)) {
      return;
    }
    suiteView.refresh();
  };

  this.refresh = function() {
    var testsSummary = [];
    if (this.passedCount > 0) {
      testsSummary.push(' ' + this.passedCount);
    }
    if (this.failedCount > 0) {
      testsSummary.push(' ' + this.failedCount);
    }
    $('.specs-summary').html(testsSummary.join(' '));
  };

  this.complete = function() {
    var duration = ((new Date().getTime() - this.startedAt.getTime()) / 1000);
    $(dom.banner).append(' ' + duration + 's');
  };

  return this;

  function isUndefined(obj) {
    return typeof obj === 'undefined';
  }

  function isDefined(obj) {
    return !isUndefined(obj);
  }

  function specPluralizedFor(count) {
    var str = count + " spec";
    if (count > 1) {
      str += "s"
    }
    return str;
  }

};

jasmine.HtmlReporterHelpers.addHelpers(jasmine.HtmlReporter.ReporterView);

jasmine.HtmlReporter.SpecView = function(spec, dom, views) {
  this.spec = spec;
  this.dom = dom;
  this.views = views;

  this.symbol = this.createDom('li', { className: 'pending' });
  $('.specs-bullets').append(this.symbol);

  var nth = $('.specs-bullets').children().length - 1,
      rows = Math.round($('.specs-bullets').height() / $(this.symbol).height());
  $(this.symbol).css({
    top: (nth % rows) * $(this.symbol).height(),
    right: Math.floor(nth / rows) * $(this.symbol).width(),
    opacity: Math.random() * 0.3 + 0.1
  });

  this.summary = this.createDom('div', { className: 'specSummary' },
    this.createDom('a', {
      className: 'description',
      href: jasmine.HtmlReporter.sectionLink(this.spec.getFullName()),
      title: this.spec.getFullName()
    }, this.spec.description)
  );

  this.detail = this.createDom('div', { className: 'specDetail' },
      this.createDom('a', {
        className: 'description',
        href: '?spec=' + encodeURIComponent(this.spec.getFullName()),
        title: this.spec.getFullName()
      }, this.spec.getFullName())
  );
};

jasmine.HtmlReporter.SpecView.prototype.status = function() {
  return this.getSpecStatus(this.spec);
};

jasmine.HtmlReporter.SpecView.prototype.refresh = function() {
  this.symbol.className = this.status();

  switch (this.status()) {
    case 'skipped':
      break;

    case 'passed':
      this.appendSummaryToSuiteDiv();
      break;

    case 'failed':
      this.appendSummaryToSuiteDiv();
      this.appendFailureDetail();
      break;
  }
};

jasmine.HtmlReporter.SpecView.prototype.appendSummaryToSuiteDiv = function() {
  this.summary.className += ' ' + this.status();
  this.appendToSummary(this.spec, this.summary);
};

jasmine.HtmlReporter.SpecView.prototype.appendFailureDetail = function() {
  this.detail.className += ' ' + this.status();

  var resultItems = this.spec.results().getItems();
  var messagesDiv = this.createDom('div', { className: 'messages' });

  for (var i = 0; i < resultItems.length; i++) {
    var result = resultItems[i];

    if (result.type == 'expect' && result.passed && !result.passed()) {
      if (result.trace.stack) {
        messagesDiv.appendChild(this.createDom('div', {className: 'stackTrace'}, result.trace.stack));
      }
    }
  }

  if (messagesDiv.childNodes.length > 0) {
    this.summary.appendChild(messagesDiv);
  }
};

jasmine.HtmlReporterHelpers.addHelpers(jasmine.HtmlReporter.SpecView);jasmine.HtmlReporter.SuiteView = function(suite, dom, views) {
  this.suite = suite;
  this.dom = dom;
  this.views = views;

  this.element = this.createDom('div', { className: 'suite' },
    this.createDom('div', { className: 'specTitle' },
      this.createDom('a', {
        className: 'description',
        href: jasmine.HtmlReporter.sectionLink(this.suite.getFullName()),
      }, this.suite.description)
    )
  );

  this.appendToSummary(this.suite, this.element);
};

jasmine.HtmlReporter.SuiteView.prototype.status = function() {
  return this.getSpecStatus(this.suite);
};

jasmine.HtmlReporter.SuiteView.prototype.refresh = function() {
  this.element.className += " " + this.status();
};

jasmine.HtmlReporterHelpers.addHelpers(jasmine.HtmlReporter.SuiteView);
<|endoftext|>


<commit_before>(function ( document ) {
	//var searchCity = 'london';

	// jQuery equivalent selector: body > center > table > tbody > tr:eq(2) > td table:eq(1) > tbody > tr
	var comments = null;
	try {
		commentsTrs = document.querySelectorAll ( 'body > center > table > tbody > tr' )[2].querySelectorAll ( 'td table' )[1].querySelectorAll ( 'tbody > tr' );
	} catch ( e ) {
		alert ( "Ooops, something is wrong. Are you sure you are on a 'Ask HN: Who is hiring?' page?" );
		return;
	}

	for ( var i in commentsTrs ) {
		var commentTr = commentsTrs[i];

		try {
			// jQuery equivalent selector: 'table td:eq(2) span.comment'
			var commentText = commentTr.querySelectorAll ( 'table td' )[2].querySelector ( 'span.comment' ).textContent + '';

			if ( /\b(london|boston)\b/ig.test ( commentText ) === false ) {
				// *khm* I feel like there should be a better way to do this :)
				commentTr.parentNode.parentNode.parentNode.parentNode.parentNode.removeChild ( commentTr.parentNode.parentNode.parentNode.parentNode );
			}
		} catch ( e ) {
		}
	}
} ( document ) );<commit_msg>use jQuery<commit_after>(function ( document ) {
	var script = document.createElement ( 'script' );
	script.src = 'http://ajax.googleapis.com/ajax/libs/jquery/1.9.0/jquery.min.js';
	document.body.appendChild ( script );
	script.onload = function () {
		var searchCities = 'london, boston';
		searchCities = searchCities.split ( ',' );
		for ( var i in searchCities ) {
			searchCities[i] = searchCities[i].trim ();
		}

		$comments = $( 'body > center > table > tbody > tr:eq(2) > td table:eq(1) > tbody > tr' );
		if ( $comments.length === 0 ) {
			alert ( "Ooops, something is wrong. Are you sure you are on a 'Ask HN: Who is hiring?' page?" );
			return;
		}

		$comments.each ( function () {
			var $this = $( this );
			var commentContent = $this.find ( 'table td:eq(2) span.comment' ).text ().toLowerCase ();

			var foundCity = false;
			for ( var i in searchCities ) {
				if ( commentContent.indexOf ( searchCities[i] ) >= 0 ) {
					foundCity = true;
					break;
				}
			}

			if ( foundCity === false ) {
				$this.hide ();
			}
		} );
	}
} ( document ) );<|endoftext|>


<commit_before>// $Id$
/*
file: autoToc.js
author: Jobi Carter
contact: keacarterdev@gmail.com
license: WTFPL
purpose: Automatically generate two table of contents on tiki pages with more than one header. The tocs are composed of clickable links to headers. One toc stays on the top of the page, while the seconds scrolls along with the page (css necessary) and hides until moused over.
last updated: 08/09/13

This script can be implimented in Tiki Wiki via editing the tiki-setup.php file and adding the following to the end of it.
	$headerlib->add_jsfile('path/to/file/here');

Generated output / What this does
	This script will generate two divs which contain a clickable table of contents for the headers on the page. One sits at the top of the page, and the other appears when you scroll past the one on the top. When you scroll past the top toc, a tab appears on the top of the page. When you mouse over this tab, a table of contents will appear. Clicking an item in the toc, or scrolling the page, will make the tab close again. The tab will be semi-transparent until it is moused over, and the toc appears. You can also click the tab to be taken to the top of the page.

disabling the toc
		turn off this script by adding a div with the id "toc-off" to a page.
		this script can be turned off for a category by adding a module with the "toc-off" div, and setting that module to only appear on that category.
		the script can be set to apply only to specific headers via the largestHeader and smallestHeader variables
		the script can be completely disabled by removing the headerLib entry in tiki-setup.php

This script does the following:
		determine if wiki page
		determine if page has the "toc-off"
		determine if more than one header on page
		insert toc HTML
		iterate over page and set Ids for header elements
			This is necessary because WYSIWYG fails to set correct Ids consistently.
		While setting header Ids, generate a HTML indented list of header anchors which point to their respective headers via header id
		set mouseover and onScroll events for two tocs

The following CSS should be applied to your theme's css file for correct behavior. You may need to adjust margin (where the toc that scrolls with you shows up) based on your theme:

	div#outerToc {
	        position:fixed;
	        top:0px;
	        margin-left:25%;
	        background:#E0E0E0;
	        display:none;
	        padding:10px;
	        border-radius:0px 0px 5px 5px;
	        z-index:5;
	}

*/

$.buildAutoToc = function () {
	//if a wiki page, and if there is no toc-off div
	if (document.getElementById("page-data") && !document.getElementById("toc-off") && document.location.href.indexOf("tiki-print.php") == -1) {
		//get headers
		headers = $("div#page-data").find(":header");

		//if there are more than one headers on the page
		if (headers.length > 1) {

			//**************Add the Toc Divs to the Page**************//

			//set target div IDs. these divs will be populated with an autoToc
			//innerToc and toc-static are required. Additional divs may be specified.
			tocDivs = ["innerToc", "toc-static"];

			//string to add html for toc divs
			tocString = "
  Table of Contents, or Click for Page Top

";

			//create the div to hold the tocs, and populate its inner HTML with the tocString
			tocContainer = document.createElement("div");
			tocContainer.innerHTML = tocString;

			//get pageData div, as we want to add toc before that
			page = document.getElementById("page-data");

			//add the tocContainer as first child of page-data div
			firstChild = page.firstChild;
			page.insertBefore(tocContainer, firstChild);

			//Configure starting header and ending header
			largestHeader = 1;
			smallestHeader = 6;

			//**************Iterate over headers and create autoToc HTML List**************//

			//start from indentation level 0, with header one as base
			previousHeaderLevel = 1;

			//create object to store processed IDs.
			var processedId = new Object();

			//function to process header Id generation. If an ID which has been processed is generated again and passed in again, the id name will be incremented to id_[1*]
			function processId(id) {
				if (id in processedId) {
					//if processed before
					//iterate count for header with this ane
					processedId[id] += 1;
					//set the new id to id plus count for header
					newId = id + "_" + processedId[id];
				} else {
					//if not processed before
					//add to "dictionary' with count of 0
					processedId[id] = 0;
					//return id passed in
					newId = id;
				}
				return newId;
			}

			//open HTML list and initialize string which will store the HTML list
			var str = '
';

			//Iterate over the headers
			for (i = 0; i < headers.length; i++) {

				//get header level for header to see if this header should be processed or not
				headerLevel = parseInt(headers[i].tagName[1]);

				//If the header level in range of acceptable headers, begin processing it
				if (headerLevel >= largestHeader && headerLevel <= smallestHeader) {

					//store browser location to append the #[1*/header text] to
					browserLocation = document.location.href;

					//Make the base URL for the link as everything up to page name plus hash. multistructure pages might have issues with this, but we are avoiding multistruction pages to avoid multicategory pages, so should be fine. For links to headers (via ids) to work, you need to have a clean url like cewiki/pagename#id, with nothing after the id string: https://cewiki-dev.colorado.edu/es-Searching+for+a+Student+Record#Overview will work, but https://cewiki-dev.colorado.edu/es-Searching+for+a+Student+Record#Overview&structure=es-Enrollment+Services will not.
					pointTo = browserLocation.substring(0, browserLocation.indexOf('&')) + "#";

					//current header
					currentHeader = headers[i];		// header is a reserved word in IE

					//grab the whole header element to show as the anchor text
					aText = $.trim(currentHeader.innerHTML); //trim incase inner html is text (if <hx>text</hx>)

					//if the header is complicated (composed of multiple HTML Elements)
					if (currentHeader.children.length > 0) {
						//set the id to the the inner text of the header, with underscores instead of "<", ">", and spaces (" "). Increment Id checks if the ID has been assigned yet, and if so, increments the Id with a number at the end of the id name
						id = processId($(aText).text().trim().replace(/ /g, "_").replace(/</g, "_").replace(/>/g, "_"))

						//if the header is simple
					} else {
						//the inner html of the header is just text, so use anchor text for the id directly
						id = processId(aText.replace(/ /g, "_").replace(/</g, "_").replace(/>/g, "_")) //replace all spaces with underscore, and special chars
					}

					//construct the URL from the base URL with a # and the ID for the Div
					url = pointTo + id; //url is baseurl#divId

					//set the element's id to the constructed ID
					//Need to do this because WYSIWYG does not set IDs on divs correctly. May be able to set flag to check if tiki editor, and mimic how it creates IDs, avoiding this step.
					currentHeader.setAttribute("id", id);

					//create the HTML anchor item with the text from the header and pointing to baseurl#divId
					itm = '
' + aText + '
';

					//*****use the information gathered to write the string of the HTML for the list of anchors*****//

					//The following code determines how indented the previous header was, and closes or opens sublists to match that indentation level.
					//Example:
					//h1
					//h2
					//h3
					//h1
					//Above, if it had just written the h3, and it went to write the h1, its initial indentLevel would be 1, while previous would be 3, so it would know it needs to close sublists equal to the difference beteen its indentation level and that of h3. So, it would need to close two levels from h3 to get to h1, and then write the item.
					//To do this, the code below either applies ++ or -- on the indentLevel variable while closing or opening sublists until the current indentLevel matches the previousHeaderLevel.

					indentLevel = headerLevel;

					//if header is a sub header (nested on a child level of the previous header), meaning its indent level is greater than that of the previousHeaderLevel
					if (indentLevel > previousHeaderLevel) {
						//open a new sublist for each level of difference

						//this is an alternative way to think about this
						//~ openCount = indentLevel - previousHeaderLevel
						//~ for i in range(openCount):
						//~ str +="
"
						//~ str+= item

						while (indentLevel > previousHeaderLevel) {
							//open sublist
							str += "
";
							//track header level vs previous header to tell when to stop
							indentLevel -= 1;
						}
						//when header levels match, write item
						str += itm;
					}
					//else, if header is higher up (parent level of list, but wont be a parent of the page because below)
					else if (indentLevel < previousHeaderLevel) {
						//close sublists for each level of difference
						while (indentLevel < previousHeaderLevel) {
							//close sublist
							str += "
";
							//track header level vs previous header to tell when to stop
							indentLevel += 1;
						}
						//when header levels match, write item
						str += itm;

						//elif header levels are same
					} else if (indentLevel == previousHeaderLevel) {
						//write item
						str += itm;
					}
				}
				//set current header level to previous header level for next iteration
				previousHeaderLevel = headerLevel;
			}

			//close list
			str += '
';

			//set target div's inner html to the list
			for (i = 0; i < tocDivs.length; i++) {
				document.getElementById(tocDivs[i]).innerHTML = str;
			}

			//**************Add special mouse over and scroll behavior to toc**************//
			//make it so the non-static toc shows up on top of the page when you scroll past the first static top which sits at the top of the page.
			//make it so that when you mouse over the non-static toc's outer container, the inner container shows up
			//make it so that when you scroll, the innner-toc, which holds the toc, hides, and you just see the outerToc container.

			//set onmouseover for outerToc
			document.getElementById("outerToc").onmouseover = function () {
				//closure, sets "scope/context" for function setVis on mouse over
				var inner = document.getElementById('innerToc');
				var outer = document.getElementById('outerToc');

				function setVis(inner, outer) {
					//make the outerToc container more opaque
					outer.style.opacity = 1;
					//show the inner toc div, displaying the toc
					inner.style.display = 'block';
				}

				return setVis(inner, outer);
			}

			//set the scroll functionality
			//determine where the "mobile toc" should start showing up
			static_toc = $('#outerToc-static');
			start = $(static_toc).offset().top + $(static_toc).height();

			//assign inner and outer vars for window scroll event
			var outer = $('#outerToc'); //$(div);
			var inner = $('#innerToc');

			//set on scroll event to do: on scroll, hide the floating toc if you would overlap with the toc on the top of the page, otherwise hide the inner toc
			$.event.add(window, "scroll", function () {
				//position
				var p = $(window).scrollTop();

				//if current position is below the other toc and start of the page
				if (p > start) {
					//set outertoc position to fixed at top of screen
					outer.css('display', 'block');
					//hide inner toc
					inner.css('display', 'none');
					//make outerToc slightly transparent
					var elem = document.getElementById('outerToc');
					if (elem) {
						elem.style.opacity = 0.5;
					}
				} else {
					//if current position overlaps with the static toc at top of page, hide non-static toc div
					outer.css('display', 'none');
				}
			});
		}
	}
};
$.buildAutoToc();<commit_msg>[bp/r48680][FIX] fixed auto toc link not working as per comment on ticket 4902. changed auto toc behavior to use innerText from headers instead of grabbing the HTML. Formatting is no longer carried into auto toc, but this prevents pulling in anchor headers and makes toc look more consistent overall<commit_after>// $Id$
/*
file: autoToc.js
author: Jobi Carter
contact: keacarterdev@gmail.com
license: WTFPL
purpose: Automatically generate two table of contents on tiki pages with more than one header. The tocs are composed of clickable links to headers. One toc stays on the top of the page, while the seconds scrolls along with the page (css necessary) and hides until moused over.
last updated: 08/09/13

This script can be implimented in Tiki Wiki via editing the tiki-setup.php file and adding the following to the end of it.
	$headerlib->add_jsfile('path/to/file/here');

Generated output / What this does
	This script will generate two divs which contain a clickable table of contents for the headers on the page. One sits at the top of the page, and the other appears when you scroll past the one on the top. When you scroll past the top toc, a tab appears on the top of the page. When you mouse over this tab, a table of contents will appear. Clicking an item in the toc, or scrolling the page, will make the tab close again. The tab will be semi-transparent until it is moused over, and the toc appears. You can also click the tab to be taken to the top of the page.

disabling the toc
		turn off this script by adding a div with the id "toc-off" to a page.
		this script can be turned off for a category by adding a module with the "toc-off" div, and setting that module to only appear on that category.
		the script can be set to apply only to specific headers via the largestHeader and smallestHeader variables
		the script can be completely disabled by removing the headerLib entry in tiki-setup.php

This script does the following:
		determine if wiki page
		determine if page has the "toc-off"
		determine if more than one header on page
		insert toc HTML
		iterate over page and set Ids for header elements
			This is necessary because WYSIWYG fails to set correct Ids consistently.
		While setting header Ids, generate a HTML indented list of header anchors which point to their respective headers via header id
		set mouseover and onScroll events for two tocs

The following CSS should be applied to your theme's css file for correct behavior. You may need to adjust margin (where the toc that scrolls with you shows up) based on your theme:

	div#outerToc {
	        position:fixed;
	        top:0px;
	        margin-left:25%;
	        background:#E0E0E0;
	        display:none;
	        padding:10px;
	        border-radius:0px 0px 5px 5px;
	        z-index:5;
	}

*/

$.buildAutoToc = function () {
	//if a wiki page, and if there is no toc-off div
	if (document.getElementById("page-data") && !document.getElementById("toc-off") && document.location.href.indexOf("tiki-print.php") == -1) {
		//get headers
		headers = $("div#page-data").find(":header");

		//if there are more than one headers on the page
		if (headers.length > 1) {

			//**************Add the Toc Divs to the Page**************//

			//set target div IDs. these divs will be populated with an autoToc
			//innerToc and toc-static are required. Additional divs may be specified.
			tocDivs = ["innerToc", "toc-static"];

			//string to add html for toc divs
			tocString = "
  Table of Contents, or Click for Page Top

";

			//create the div to hold the tocs, and populate its inner HTML with the tocString
			tocContainer = document.createElement("div");
			tocContainer.innerHTML = tocString;

			//get pageData div, as we want to add toc before that
			page = document.getElementById("page-data");

			//add the tocContainer as first child of page-data div
			firstChild = page.firstChild;
			page.insertBefore(tocContainer, firstChild);

			//Configure starting header and ending header
			largestHeader = 1;
			smallestHeader = 6;

			//**************Iterate over headers and create autoToc HTML List**************//

			//start from indentation level 0, with header one as base
			previousHeaderLevel = 1;

			//create object to store processed IDs.
			var processedId = new Object();

			//function to process header Id generation. If an ID which has been processed is generated again and passed in again, the id name will be incremented to id_[1*]
			function processId(id) {
				if (id in processedId) {
					//if processed before
					//iterate count for header with this ane
					processedId[id] += 1;
					//set the new id to id plus count for header
					newId = id + "_" + processedId[id];
				} else {
					//if not processed before
					//add to "dictionary' with count of 0
					processedId[id] = 0;
					//return id passed in
					newId = id;
				}
				return newId;
			}

			//open HTML list and initialize string which will store the HTML list
			var str = '
';

			//Iterate over the headers
			for (i = 0; i < headers.length; i++) {

				//get header level for header to see if this header should be processed or not
				headerLevel = parseInt(headers[i].tagName[1]);

				//If the header level in range of acceptable headers, begin processing it
				if (headerLevel >= largestHeader && headerLevel <= smallestHeader) {

					//store browser location to append the #[1*/header text] to
					browserLocation = document.location.href;

					//Make the base URL for the link as everything up to page name plus hash. multistructure pages might have issues with this, but we are avoiding multistruction pages to avoid multicategory pages, so should be fine. For links to headers (via ids) to work, you need to have a clean url like cewiki/pagename#id, with nothing after the id string: https://cewiki-dev.colorado.edu/es-Searching+for+a+Student+Record#Overview will work, but https://cewiki-dev.colorado.edu/es-Searching+for+a+Student+Record#Overview&structure=es-Enrollment+Services will not.
					pointTo = browserLocation.substring(0, browserLocation.indexOf('&')) + "#";

					//current header
					currentHeader = headers[i];		// header is a reserved word in IE

					//grab the whole header element to show as the anchor text
					// aText = $.trim(currentHeader.innerHTML); //trim incase inner html is text (if <hx>text</hx>)
					aText = $.trim(currentHeader.innerText);

					//generate and set id if necessary (if element does not already have an id, create one)
					if (!currentHeader.id){
						//set the id to the the inner text of the header, with underscores instead of "<", ">", and spaces (" "). Increment Id checks if the ID has been assigned yet, and if so, increments the Id with a number at the end of the id name
						id = processId(currentHeader.innerText.trim().replace(/ /g, "_").replace(/</g, "_").replace(/>/g, "_"));

						//set the element's id to the constructed ID
						currentHeader.setAttribute("id", id);
					} else {
						id = currentHeader.id;
					}
					//construct the URL from the base URL with a # and the ID for the Div
					url = pointTo + id; //url is baseurl#divId

					//create the HTML anchor item with the text from the header and pointing to baseurl#divId
					itm = '
' + aText + '
';

					//*****use the information gathered to write the string of the HTML for the list of anchors*****//

					//The following code determines how indented the previous header was, and closes or opens sublists to match that indentation level.
					//Example:
					//h1
					//h2
					//h3
					//h1
					//Above, if it had just written the h3, and it went to write the h1, its initial indentLevel would be 1, while previous would be 3, so it would know it needs to close sublists equal to the difference beteen its indentation level and that of h3. So, it would need to close two levels from h3 to get to h1, and then write the item.
					//To do this, the code below either applies ++ or -- on the indentLevel variable while closing or opening sublists until the current indentLevel matches the previousHeaderLevel.

					indentLevel = headerLevel;

					//if header is a sub header (nested on a child level of the previous header), meaning its indent level is greater than that of the previousHeaderLevel
					if (indentLevel > previousHeaderLevel) {
						//open a new sublist for each level of difference

						//this is an alternative way to think about this
						//~ openCount = indentLevel - previousHeaderLevel
						//~ for i in range(openCount):
						//~ str +="
"
						//~ str+= item

						while (indentLevel > previousHeaderLevel) {
							//open sublist
							str += "
";
							//track header level vs previous header to tell when to stop
							indentLevel -= 1;
						}
						//when header levels match, write item
						str += itm;
					}
					//else, if header is higher up (parent level of list, but wont be a parent of the page because below)
					else if (indentLevel < previousHeaderLevel) {
						//close sublists for each level of difference
						while (indentLevel < previousHeaderLevel) {
							//close sublist
							str += "
";
							//track header level vs previous header to tell when to stop
							indentLevel += 1;
						}
						//when header levels match, write item
						str += itm;

						//elif header levels are same
					} else if (indentLevel == previousHeaderLevel) {
						//write item
						str += itm;
					}
				}
				//set current header level to previous header level for next iteration
				previousHeaderLevel = headerLevel;
			}

			//close list
			str += '
';

			//set target div's inner html to the list
			for (i = 0; i < tocDivs.length; i++) {
				document.getElementById(tocDivs[i]).innerHTML = str;
			}

			//**************Add special mouse over and scroll behavior to toc**************//
			//make it so the non-static toc shows up on top of the page when you scroll past the first static top which sits at the top of the page.
			//make it so that when you mouse over the non-static toc's outer container, the inner container shows up
			//make it so that when you scroll, the innner-toc, which holds the toc, hides, and you just see the outerToc container.

			//set onmouseover for outerToc
			document.getElementById("outerToc").onmouseover = function () {
				//closure, sets "scope/context" for function setVis on mouse over
				var inner = document.getElementById('innerToc');
				var outer = document.getElementById('outerToc');

				function setVis(inner, outer) {
					//make the outerToc container more opaque
					outer.style.opacity = 1;
					//show the inner toc div, displaying the toc
					inner.style.display = 'block';
				}

				return setVis(inner, outer);
			}

			//set the scroll functionality
			//determine where the "mobile toc" should start showing up
			static_toc = $('#outerToc-static');
			start = $(static_toc).offset().top + $(static_toc).height();

			//assign inner and outer vars for window scroll event
			var outer = $('#outerToc'); //$(div);
			var inner = $('#innerToc');

			//set on scroll event to do: on scroll, hide the floating toc if you would overlap with the toc on the top of the page, otherwise hide the inner toc
			$.event.add(window, "scroll", function () {
				//position
				var p = $(window).scrollTop();

				//if current position is below the other toc and start of the page
				if (p > start) {
					//set outertoc position to fixed at top of screen
					outer.css('display', 'block');
					//hide inner toc
					inner.css('display', 'none');
					//make outerToc slightly transparent
					var elem = document.getElementById('outerToc');
					if (elem) {
						elem.style.opacity = 0.5;
					}
				} else {
					//if current position overlaps with the static toc at top of page, hide non-static toc div
					outer.css('display', 'none');
				}
			});
		}
	}
};
$.buildAutoToc();<|endoftext|>


<commit_before>const npmApi = require('../../datasource/npm');
const versions = require('./versions');
const { isValidSemver } = require('../../util/semver');
const nodeManager = require('../_helpers/node/package');

module.exports = {
  getPackageUpdates,
};

async function getPackageUpdates(config) {
  logger.trace({ config }, `npm.getPackageUpdates()`);
  if (config.depType === 'engines') {
    if (config.depName !== 'node') {
      logger.debug('Skipping non-node engine');
      return [];
    }
    return nodeManager.getPackageUpdates(config);
  }
  let results = [];
  if (config.currentVersion.startsWith('file:')) {
    logger.debug(
      { dependency: config.depName, currentVersion: config.currentVersion },
      'Skipping file: dependency'
    );
    return [];
  }
  if (!isValidSemver(config.currentVersion)) {
    results.push({
      depName: config.depName,
      type: 'warning',
      message: `Dependency uses tag "\`${
        config.currentVersion
      }\`" as its version so that will never be changed by Renovate`,
    });
    logger.debug(results[0].message);
    return results;
  }
  npmApi.setNpmrc(
    config.npmrc,
    config.global ? config.global.exposeEnv : false
  );
  const npmDep = await npmApi.getDependency(config.depName);
  if (npmDep) {
    results = await versions.determineUpgrades(npmDep, config);
    if (results.length > 0) {
      logger.info(
        { dependency: config.depName },
        `${results.length} result(s): ${results.map(
          upgrade => upgrade.newVersion
        )}`
      );
    }
  } else {
    // If dependency lookup fails then warn and return
    const result = {
      type: 'warning',
      message: `Failed to look up dependency ${config.depName}`,
    };
    results = [result];
    logger.info({ dependency: config.depName }, result.message);
  }
  for (const result of results) {
    result.repositoryUrl =
      npmDep && npmDep.repositoryUrl && npmDep.repositoryUrl.length
        ? npmDep.repositoryUrl
        : null;
    if (!result.repositoryUrl && config.depName.startsWith('@types/')) {
      logger.debug('Setting @types url manually');
      result.repositoryUrl = `https://github.com/DefinitelyTyped/DefinitelyTyped/tree/master/${config.depName.replace(
        '@',
        ''
      )}`;
    }
  }
  return results;
}
<commit_msg>logs: log packageFile when failing to look up dependency<commit_after>const npmApi = require('../../datasource/npm');
const versions = require('./versions');
const { isValidSemver } = require('../../util/semver');
const nodeManager = require('../_helpers/node/package');

module.exports = {
  getPackageUpdates,
};

async function getPackageUpdates(config) {
  logger.trace({ config }, `npm.getPackageUpdates()`);
  if (config.depType === 'engines') {
    if (config.depName !== 'node') {
      logger.debug('Skipping non-node engine');
      return [];
    }
    return nodeManager.getPackageUpdates(config);
  }
  let results = [];
  if (config.currentVersion.startsWith('file:')) {
    logger.debug(
      { dependency: config.depName, currentVersion: config.currentVersion },
      'Skipping file: dependency'
    );
    return [];
  }
  if (!isValidSemver(config.currentVersion)) {
    results.push({
      depName: config.depName,
      type: 'warning',
      message: `Dependency uses tag "\`${
        config.currentVersion
      }\`" as its version so that will never be changed by Renovate`,
    });
    logger.debug(results[0].message);
    return results;
  }
  npmApi.setNpmrc(
    config.npmrc,
    config.global ? config.global.exposeEnv : false
  );
  const npmDep = await npmApi.getDependency(config.depName);
  if (npmDep) {
    results = await versions.determineUpgrades(npmDep, config);
    if (results.length > 0) {
      logger.info(
        { dependency: config.depName },
        `${results.length} result(s): ${results.map(
          upgrade => upgrade.newVersion
        )}`
      );
    }
  } else {
    // If dependency lookup fails then warn and return
    const result = {
      type: 'warning',
      message: `Failed to look up dependency ${config.depName}`,
    };
    results = [result];
    logger.info(
      { dependency: config.depName, packageFile: config.packageFile },
      result.message
    );
  }
  for (const result of results) {
    result.repositoryUrl =
      npmDep && npmDep.repositoryUrl && npmDep.repositoryUrl.length
        ? npmDep.repositoryUrl
        : null;
    if (!result.repositoryUrl && config.depName.startsWith('@types/')) {
      logger.debug('Setting @types url manually');
      result.repositoryUrl = `https://github.com/DefinitelyTyped/DefinitelyTyped/tree/master/${config.depName.replace(
        '@',
        ''
      )}`;
    }
  }
  return results;
}
<|endoftext|>