<commit_before>/***
*
* styleguide.js
*
* Parses CSS/LESS/SASS files with KSS notation and generates a styleguide
*
* Heavily influenced by node-kss and gulp-kss projects
*
***/

'use strict';

var path = require('path'),
  jade = require('jade'),
  through = require('through2'),
  gulp = require('gulp'),
  rename = require('gulp-rename'),
  mustache = require('gulp-mustache'),
  postcss = require('gulp-postcss'),
  replace = require('gulp-replace'),
  minimatch = require('minimatch'),
  fs = require('fs-extra'),
  File = require('vinyl'),
  LocalCache = require('node-localcache'),
  crypto = require('crypto'),
  Q = require('q'),
  _ = require('lodash'),
  markdown = require('./modules/markdown'),
  atRules = require('./modules/at-rules'),
  variableParser = require('./modules/variable-parser'),
  kssParser = require('./modules/kss-parser'),
  angularFiles = require('./modules/angular-files'),
  pseudoSelectors = require('./modules/pseudo-selectors'),
  wrapperMarkup = require('./modules/wrapper-markup'),
  sectionReferences = require('./modules/section-references'),
  common = require('./modules/common'),
  addSection = require('./modules/helpers/add-section'),
  sgServer = require('./server'),
  distPath = path.join(__dirname, 'dist'),
  fileHashes = {},
  jadeCache,
  serverInstance;

function socketIsOpen() {
  return serverInstance && serverInstance.io;
}

function emitProgressStart() {
  if (socketIsOpen()) {
    serverInstance.io.emitProgressStart();
  }
}

function emitProgressEnd() {
  if (socketIsOpen()) {
    serverInstance.io.emitProgressEnd();
  }
}

function emitCompileError(error) {
  if (socketIsOpen()) {
    serverInstance.io.emitCompileError(error);
  }
}

function emitCompileSuccess() {
  if (socketIsOpen()) {
    serverInstance.io.emitCompileSuccess();
  }
}

function groupModuleFiles(allModules) {
  // Group modules by module name
  var namedModules = _.groupBy(allModules, function(module) {
    return module.name;
  });

  // Commbile files from every module that has the same name
  return _.map(namedModules, function(modules, moduleName) {
    var files = _.uniq(_.reduce(modules, function(files, singleModule) {
      return files.concat(singleModule.files);
    }, []));
    return {
      name: moduleName,
      files: files
    };
  });
}

function generateJadeMarkup(json, options) {
  jadeCache = new LocalCache(path.resolve(options.rootPath, 'jadecache.json'));

  // path to bemto
  var bemtoinclude = 'include ./node_modules/bemto.jade/bemto.jade\n',
      jadeOptions = {
        filename: __dirname,
        basedir: process.cwd(),
        compileDebug: true,
        pretty: true
      };

  _.each(json.sections, convertToJade);

  var parentref;
  function convertToJade(section) {
    var cacheKey, jadestring, md5;

    if (!section.markup) { return; }
    if (/<[a-z][\s\S]*>/i.test(section.markup)) { return; }

    section.markupJade = section.markup;

    if (section.reference) {
      parentref = section.reference;
      cacheKey = section.reference;
    } else {
      cacheKey = parentref + ' - ' + section.name;
    }

    jadestring = bemtoinclude + section.markup;
    md5 = crypto.createHash('md5').update(jadestring).digest('hex');

    if (jadeCache.getItem(cacheKey) && jadeCache.getItem(cacheKey).key === md5) {
      section.markup = jadeCache.getItem(cacheKey).value;
    } else {
      section.markup = jade.render(jadestring, jadeOptions);
      jadeCache.setItem(cacheKey, {
        key : md5,
        value : section.markup
      });
    }

    if (section.modifiers) {
      _.each(section.modifiers, convertToJade);
    }
  }
}

function generateSectionWrapperMarkup(json) {
  json.section = wrapperMarkup.generateSectionWrapperMarkup(json.sections);
}

function replaceSectionReferences(json) {
  json.sections = sectionReferences.replace(json.sections);
}

function copyUsedOptionsToJsonConfig(opt, json) {
  var used = ['appRoot', 'extraHead', 'beforeBody', 'afterBody', 'commonClass', 'title', 'disableEncapsulation', 'disableHtml5Mode', 'readOnly'];
  json.config = {};
  used.forEach(function(prop) {
    json.config[prop] = _.cloneDeep(opt[prop]);
  });
  return json;
}

function copyUsedOptionsToInlineJsonConfig(opt, json) {
  var used = ['title', 'disableEncapsulation', 'disableHtml5Mode'];
  json.config = {};
  used.forEach(function(prop) {
    json.config[prop] = _.cloneDeep(opt[prop]);
  });
  return json;
}

function addFileHashesAndReplaceAbsolutePaths(json) {
  if (!json) {
    return;
  }

  if (json.variables) {
    json.variables.forEach(function(variable) {
      variable.fileHash = fileHashes[variable.file];
      variable.file = basenameOf(variable.file);
    });
  }

  if (json.variables) {
    json.sections.forEach(function(section) {
      section.fileHash = fileHashes[section.file];
      section.file = basenameOf(section.file);
    });
  }
}

function basenameOf(p) {
  return path.basename(p);
}

function appendUsedVariablesToEachBlock(opt, styleguide) {
  // Go trough every styleguide style block and find used variables
  styleguide.sections.forEach(function(section) {
    // It is possible that the CSS is not valid anymore after it is splitted to sections
    // We need to catch possible parsing errors
    try {
      if (section.css) {
        section.variables = variableParser.findVariables(section.css, section.syntax, opt);
      }
    } catch (e) {
      console.error('Could not parse used variables from section', section.reference + ':');
      console.error(e.toString());
    }

    variableParser.findModifierVariables(section.modifiers).forEach(function(varName) {
      if (!section.variables || section.variables.indexOf(varName) === -1) {
        section.variables = section.variables || [];
        section.variables.push(varName);
      }
    });
    return section;
  });
}

function filterFiles(files, filter) {
  var filtered = {};
  Object.keys(files).forEach(function(filePath) {
    if (minimatch(filePath, filter)) {
      filtered[filePath] = files[filePath];
    }
  });
  return filtered;
}

module.exports.generate = function(options) {
  var opt = common.sanitizeOptions(options),
    filesBuffer = {},
    throughOpts = {
      objectMode: true,
      allowHalfOpen: false
    },
    styleguideProcessors = _.extend(
        {
          10: replaceSectionReferences,
          20: generateSectionWrapperMarkup
        },
        opt.styleguideProcessors
    );

  function bufferFileContents(file, enc, done) {
    if (file.isNull()) {
      return;
    }
    if (file.isStream()) {
      return console.error('Styleguide does not support streams!');
    }

    // Exclude empty files
    if (file.contents.toString('utf8') !== '') {
      filesBuffer[file.path] = file.contents.toString('utf8');
      var hash = crypto.createHash('md5').update(file.path).digest('hex');
      fileHashes[file.path] = hash;
      fileHashes[hash] = file.path;
    }

    // Make sure file goes through the next gulp plugin
    // jshint -W040
    this.push(file);
    // jshint +W040
    done();
  }

  emitProgressStart();

  // A stream through which each file will pass
  return through(throughOpts, bufferFileContents, function(callback) {
      var _this = this,
        // Styleguide object to be built
        styleguide = {},
        // Parse KSS sections
        parseKSSPromise = kssParser.parseKssSections(filesBuffer, opt.kssOpt, opt.parsers),
        // Filter variable files
        // File paths are full absolute paths so we need to add wildcard prefix
        // Also empty wildcard should return all files
        variableFiles = opt.styleVariables ? filterFiles(filesBuffer, '**/' + opt.styleVariables) : filesBuffer,
        // Parse variable decarations from files
        parseVariablesPromise = variableParser.parseVariableDeclarationsFromFiles(variableFiles, opt);

      Q.all([parseKSSPromise, parseVariablesPromise]).spread(function(sections, variables) {
        styleguide.sections = sections;
        styleguide.variables = variables;

        // Extend config with Angular directives declared in KSS
        opt.filesConfig = angularFiles.add(opt.filesConfig, sections);

        function pushAllFiles() {
          return through.obj(function(file, enc, cb) {
            _this.push(file);
            cb();
          });
        }

        function processOverviewMarkdown(opt) {
          return Q.Promise(function(resolve) {
            if (!opt.overviewPath) {
              resolve();
            }
            markdown.getStream(opt.overviewPath)
              .pipe(rename(function(path) {
                path.basename = 'overview';
                path.extname = '.html';
              }))
              .pipe(pushAllFiles())
              .on('finish', resolve);
          });
        }

        options.enableJade && generateJadeMarkup(styleguide, options);

        _.each(styleguideProcessors, function(processor) {
          processor(styleguide);
        });

        copyUsedOptionsToJsonConfig(opt, styleguide);
        appendUsedVariablesToEachBlock(opt, styleguide);
        addFileHashesAndReplaceAbsolutePaths(styleguide);

        // Create JSON containing KSS data
        _this.push(new File({
          path: 'styleguide.json',
          contents: new Buffer(JSON.stringify(styleguide))
        }));

        var stylesCompiled,
          overviewProcessed,
          filesCopied,
          indexHtmlProcessed;

        overviewProcessed = processOverviewMarkdown(opt);

        var cssSrc = [distPath + '/css/styleguide-app.css', distPath + '/css/styleguide_helper_elements.css'];

        // Copy all files (except index.html) from dist from to output stream
        filesCopied = Q.Promise(function(resolve) {
          //gulp.src([distPath + '/**', '!' + distPath + '/index.html', + '!' + distPath + '**/*.css'])
          var copySrc = [distPath + '/**', '!' + distPath + '/index.html'];

          copySrc = copySrc.concat(cssSrc.map(function(item){
            return '!' + item;
          }));

          gulp.src(copySrc)
            .pipe(pushAllFiles())
            .on('finish', resolve);
        });

        // Compile UI styles
        stylesCompiled = Q.Promise(function(resolve) {

          // If we have custom colors defined. Overwrite default empty file
          if (opt.customColors) {
            try {
              fs.copySync(opt.customColors, distPath + '/css/_styleguide_custom_variables.css');
              // Add directory of custom colors file to SASS includePaths
              // This allows to including external files in custom variable file
            } catch (err) {
              console.log('ERROR: customColors option is defined but the file is not copyable. Using default colors.');
            }
          }
          gulp.src(cssSrc)
            .pipe(replace('{{{appRoot}}}', opt.appRoot))
            .pipe(postcss([
              require('postcss-partial-import'),
              require('postcss-mixins'),
              require('cssnext'),
              require('postcss-advanced-variables'),
              require('postcss-conditionals'),
              require('postcss-color-function'),
              require('postcss-color-alpha'),
              require('postcss-nested'),
              require('postcss-custom-media'),
              require('autoprefixer'),
              require('postcss-inline-comment')
            ]))
            // Strings hack
            .pipe(replace(/url\((.*)\)/g, function(replacement, parsedPath) {
              return 'url(\'' + parsedPath.replace(/'/g, '') + '\')';
            }))
            .pipe(pushAllFiles())
            .on('finish', resolve);
        });

        // Process index.html
        indexHtmlProcessed = Q.Promise(function(resolve) {
          gulp.src([distPath + '/index.html'])
            .pipe(mustache({
              title: opt.title,
              extraHead: opt.extraHead,
              beforeBody: opt.beforeBody,
              afterBody: opt.afterBody,
              styleguideConfig: JSON.stringify(copyUsedOptionsToInlineJsonConfig(opt, {}).config),
              appRoot: opt.appRoot,
              socketIo: opt.server,
              filesConfig: JSON.stringify(groupModuleFiles(opt.filesConfig))
            }))
            .pipe(pushAllFiles())
            .on('finish', resolve);
        });

        Q.all([stylesCompiled, overviewProcessed, filesCopied, indexHtmlProcessed])
          .then(function() {
            if (opt.server) {
              opt.fileHashes = fileHashes;
              startServer(opt);
            }
            emitCompileSuccess();
          })
          .catch(function(error) {
            console.error(error.stack);
            emitCompileError(error);
          })
          .finally(callback);
      }).catch(function(error) {
        console.error(error.stack);
        emitCompileError(error);
        callback();
      });
    }
  ).on('error', console.error.bind(console)).on('end', function() {
    emitProgressEnd();
  });
};

/* Build pseudo styles and ay rules from preprocessed CSS styles */

module.exports.applyStyles = function() {
  var throughOpts = {
    objectMode: true,
    allowHalfOpen: false
  }, stylesBuffer = '',
    pseudoStylesBuffer = '',
    atRulesBuffer = '';

  function bufferFileContents(file, enc, done) {
    var pseudoStylesPromise,
      atRulesPromise;

    // Make sure file goes through the next gulp plugin
    // jshint -W040
    this.push(file);
    // jshint +W040

    // Process only CSS files
    // For example gulp-ruby-sass generatsd sourcemaps to the stream that we do not want to include
    if (path.extname(file.path) !== '.css') {
      done();
      return;
    }

    // Add styles to common stylesheet
    stylesBuffer += file.contents.toString();

    // Create stylesheet that contains pseudo styles
    pseudoStylesPromise = Q.Promise(function(resolve) {
      pseudoStylesBuffer += pseudoSelectors.stylesFromString(file.contents.toString(), {
        source: file.path
      });
      resolve();
    });

    // Create stylesheet that contains at-rules
    atRulesPromise = Q.Promise(function(resolve) {
      atRulesBuffer += atRules.stylesFromString(file.contents.toString(), {
        source: file.path
      });
      resolve();
    });

    Q.all([pseudoStylesPromise, atRulesPromise]).then(function() {
      done();
    });
  }

  return through(throughOpts, bufferFileContents, function(cb) {
    this.push(new File({
      path: 'styleguide.css',
      contents: new Buffer(stylesBuffer)
    }));

    this.push(new File({
      path: 'styleguide_pseudo_styles.css',
      contents: new Buffer(pseudoStylesBuffer)
    }));

    this.push(new File({
      path: 'styleguide_at_rules.css',
      contents: new Buffer(atRulesBuffer)
    }));

    cb();
  }).on('end', function() {
    if (socketIsOpen()) {
      serverInstance.io.emitStylesChanged();
    }
  });
};

/* Built-in server */

module.exports.server = function(options) {
  startServer(options);
};

function startServer(options) {
  var port = options.port;
  // Ignore start server if we already have instance running
  if (!serverInstance) {
    serverInstance = sgServer(options);
    serverInstance.app.set('port', port);
    serverInstance.server.listen(serverInstance.app.get('port'), function() {
      console.log('Express server listening on port ' + serverInstance.server.address().port);
    }).on('error', function(error) {
      if (error.code === 'EADDRINUSE') {
        console.error('Port:' + port + ' is already in use.');
        console.error('Please provide port using --port <port>');
      }
    });
  }
  return serverInstance;
}

module.exports.addSection = addSection;
<commit_msg>Update styleguide.js<commit_after>/***
*
* styleguide.js
*
* Parses CSS/LESS/SASS files with KSS notation and generates a styleguide
*
* Heavily influenced by node-kss and gulp-kss projects
*
***/

'use strict';

var path = require('path'),
  jade = require('jade'),
  through = require('through2'),
  gulp = require('gulp'),
  rename = require('gulp-rename'),
  mustache = require('gulp-mustache'),
  postcss = require('gulp-postcss'),
  replace = require('gulp-replace'),
  minimatch = require('minimatch'),
  fs = require('fs-extra'),
  File = require('vinyl'),
  LocalCache = require('node-localcache'),
  crypto = require('crypto'),
  Q = require('q'),
  _ = require('lodash'),
  markdown = require('./modules/markdown'),
  atRules = require('./modules/at-rules'),
  variableParser = require('./modules/variable-parser'),
  kssParser = require('./modules/kss-parser'),
  angularFiles = require('./modules/angular-files'),
  pseudoSelectors = require('./modules/pseudo-selectors'),
  wrapperMarkup = require('./modules/wrapper-markup'),
  sectionReferences = require('./modules/section-references'),
  common = require('./modules/common'),
  addSection = require('./modules/helpers/add-section'),
  sgServer = require('./server'),
  distPath = path.join(__dirname, 'dist'),
  fileHashes = {},
  jadeCache,
  serverInstance;

function socketIsOpen() {
  return serverInstance && serverInstance.io;
}

function emitProgressStart() {
  if (socketIsOpen()) {
    serverInstance.io.emitProgressStart();
  }
}

function emitProgressEnd() {
  if (socketIsOpen()) {
    serverInstance.io.emitProgressEnd();
  }
}

function emitCompileError(error) {
  if (socketIsOpen()) {
    serverInstance.io.emitCompileError(error);
  }
}

function emitCompileSuccess() {
  if (socketIsOpen()) {
    serverInstance.io.emitCompileSuccess();
  }
}

function groupModuleFiles(allModules) {
  // Group modules by module name
  var namedModules = _.groupBy(allModules, function(module) {
    return module.name;
  });

  // Commbile files from every module that has the same name
  return _.map(namedModules, function(modules, moduleName) {
    var files = _.uniq(_.reduce(modules, function(files, singleModule) {
      return files.concat(singleModule.files);
    }, []));
    return {
      name: moduleName,
      files: files
    };
  });
}

function generateJadeMarkup(json, options) {
  jadeCache = new LocalCache(path.resolve(options.rootPath, 'jadecache.json'));

  // path to bemto
  var bemtoinclude = 'include ./node_modules/bemto.jade/bemto.jade\n',
      jadeOptions = {
        filename: __dirname,
        basedir: process.cwd(),
        compileDebug: true,
        pretty: true
      };

  _.each(json.sections, convertToJade);

  var parentref;
  function convertToJade(section) {
    var cacheKey, jadestring, md5;

    if (!section.markup) { return; }
    if (/<[a-z][\s\S]*>/i.test(section.markup)) { return; }

    section.markupJade = section.markup;

    if (section.reference) {
      parentref = section.reference;
      cacheKey = section.reference;
    } else {
      cacheKey = parentref + ' - ' + section.name;
    }

    jadestring = bemtoinclude + section.markup;
    md5 = crypto.createHash('md5').update(jadestring).digest('hex');

    if (jadeCache.getItem(cacheKey) && jadeCache.getItem(cacheKey).key === md5) {
      section.markup = jadeCache.getItem(cacheKey).value;
    } else {
      section.markup = jade.render(jadestring, jadeOptions);
      jadeCache.setItem(cacheKey, {
        key : md5,
        value : section.markup
      });
    }

    if (section.modifiers) {
      _.each(section.modifiers, convertToJade);
    }
  }
}

function generateSectionWrapperMarkup(json) {
  json.section = wrapperMarkup.generateSectionWrapperMarkup(json.sections);
}

function replaceSectionReferences(json) {
  json.sections = sectionReferences.replace(json.sections);
}

function copyUsedOptionsToJsonConfig(opt, json) {
  var used = ['appRoot', 'extraHead', 'beforeBody', 'afterBody', 'commonClass', 'title', 'disableEncapsulation', 'disableHtml5Mode', 'readOnly'];
  json.config = {};
  used.forEach(function(prop) {
    json.config[prop] = _.cloneDeep(opt[prop]);
  });
  return json;
}

function copyUsedOptionsToInlineJsonConfig(opt, json) {
  var used = ['title', 'disableEncapsulation', 'disableHtml5Mode'];
  json.config = {};
  used.forEach(function(prop) {
    json.config[prop] = _.cloneDeep(opt[prop]);
  });
  return json;
}

function addFileHashesAndReplaceAbsolutePaths(json) {
  if (!json) {
    return;
  }

  if (json.variables) {
    json.variables.forEach(function(variable) {
      variable.fileHash = fileHashes[variable.file];
      variable.file = basenameOf(variable.file);
    });
  }

  if (json.variables) {
    json.sections.forEach(function(section) {
      section.fileHash = fileHashes[section.file];
      section.file = basenameOf(section.file);
    });
  }
}

function basenameOf(p) {
  return path.basename(p);
}

function appendUsedVariablesToEachBlock(opt, styleguide) {
  // Go trough every styleguide style block and find used variables
  styleguide.sections.forEach(function(section) {
    // It is possible that the CSS is not valid anymore after it is splitted to sections
    // We need to catch possible parsing errors
    try {
      if (section.css) {
        section.variables = variableParser.findVariables(section.css, section.syntax, opt);
      }
    } catch (e) {
      console.error('Could not parse used variables from section', section.reference + ':');
      console.error(e.toString());
    }

    variableParser.findModifierVariables(section.modifiers).forEach(function(varName) {
      if (!section.variables || section.variables.indexOf(varName) === -1) {
        section.variables = section.variables || [];
        section.variables.push(varName);
      }
    });
    return section;
  });
}

function filterFiles(files, filter) {
  var filtered = {};
  Object.keys(files).forEach(function(filePath) {
    if (minimatch(filePath, filter)) {
      filtered[filePath] = files[filePath];
    }
  });
  return filtered;
}

module.exports.generate = function(options) {
  var opt = common.sanitizeOptions(options),
    filesBuffer = {},
    throughOpts = {
      objectMode: true,
      allowHalfOpen: false
    },
    styleguideProcessors = _.extend(
        {
          10: replaceSectionReferences,
          20: generateSectionWrapperMarkup
        },
        opt.styleguideProcessors
    );

  function bufferFileContents(file, enc, done) {
    if (file.isNull()) {
      return;
    }
    if (file.isStream()) {
      return console.error('Styleguide does not support streams!');
    }

    // Exclude empty files
    if (file.contents.toString('utf8') !== '') {
      filesBuffer[file.path] = file.contents.toString('utf8');
      var hash = crypto.createHash('md5').update(file.path).digest('hex');
      fileHashes[file.path] = hash;
      fileHashes[hash] = file.path;
    }

    // Make sure file goes through the next gulp plugin
    // jshint -W040
    this.push(file);
    // jshint +W040
    done();
  }

  emitProgressStart();

  // A stream through which each file will pass
  return through(throughOpts, bufferFileContents, function(callback) {
      var _this = this,
        // Styleguide object to be built
        styleguide = {},
        // Parse KSS sections
        parseKSSPromise = kssParser.parseKssSections(filesBuffer, opt.kssOpt, opt.parsers),
        // Filter variable files
        // File paths are full absolute paths so we need to add wildcard prefix
        // Also empty wildcard should return all files
        variableFiles = opt.styleVariables ? filterFiles(filesBuffer, '**/' + opt.styleVariables) : filesBuffer,
        // Parse variable decarations from files
        parseVariablesPromise = variableParser.parseVariableDeclarationsFromFiles(variableFiles, opt);

      Q.all([parseKSSPromise, parseVariablesPromise]).spread(function(sections, variables) {
        styleguide.sections = sections;
        styleguide.variables = variables;

        // Extend config with Angular directives declared in KSS
        opt.filesConfig = angularFiles.add(opt.filesConfig, sections);

        function pushAllFiles() {
          return through.obj(function(file, enc, cb) {
            _this.push(file);
            cb();
          });
        }

        function processOverviewMarkdown(opt) {
          return Q.Promise(function(resolve) {
            if (!opt.overviewPath) {
              resolve();
            }
            markdown.getStream(opt.overviewPath)
              .pipe(rename(function(path) {
                path.basename = 'overview';
                path.extname = '.html';
              }))
              .pipe(pushAllFiles())
              .on('finish', resolve);
          });
        }

        options.enableJade && generateJadeMarkup(styleguide, options);

        _.each(styleguideProcessors, function(processor) {
          processor(styleguide);
        });

        copyUsedOptionsToJsonConfig(opt, styleguide);
        appendUsedVariablesToEachBlock(opt, styleguide);
        addFileHashesAndReplaceAbsolutePaths(styleguide);

        // Create JSON containing KSS data
        _this.push(new File({
          path: 'styleguide.json',
          contents: new Buffer(JSON.stringify(styleguide))
        }));

        var stylesCompiled,
          overviewProcessed,
          filesCopied,
          indexHtmlProcessed;

        overviewProcessed = processOverviewMarkdown(opt);

        var cssSrc = [distPath + '/css/styleguide-app.css', distPath + '/css/styleguide_helper_elements.css'];

        // Copy all files (except index.html) from dist from to output stream
        filesCopied = Q.Promise(function(resolve) {
          //gulp.src([distPath + '/**', '!' + distPath + '/index.html', + '!' + distPath + '**/*.css'])
          var copySrc = [distPath + '/**', '!' + distPath + '/index.html'];

          copySrc = copySrc.concat(cssSrc.map(function(item){
            return '!' + item;
          }));

          gulp.src(copySrc)
            .pipe(pushAllFiles())
            .on('finish', resolve);
        });

        // Compile UI styles
        stylesCompiled = Q.Promise(function(resolve) {

          // If we have custom colors defined. Overwrite default empty file
          if (opt.customColors) {
            try {
              fs.copySync(opt.customColors, distPath + '/css/_styleguide_custom_variables.css');
              // Add directory of custom colors file to SASS includePaths
              // This allows to including external files in custom variable file
            } catch (err) {
              console.log('ERROR: customColors option is defined but the file is not copyable. Using default colors.');
            }
          }
          gulp.src(cssSrc)
            .pipe(replace('{{{appRoot}}}', opt.appRoot))
            .pipe(postcss([
              require('postcss-partial-import'),
              require('postcss-mixins'),
              require('cssnext'),
              require('postcss-advanced-variables'),
              require('postcss-conditionals'),
              require('postcss-color-function'),
              require('postcss-color-alpha'),
              require('postcss-nested'),
              require('postcss-custom-media'),
              require('autoprefixer'),
              require('postcss-inline-comment')
            ]))
            // Strings hack
            .pipe(replace(/url\((.*)\)/g, function(replacement, parsedPath) {
              return 'url(\'' + parsedPath.replace(/'/g, '') + '\')';
            }))
            .pipe(pushAllFiles())
            .on('finish', resolve);
        });

        // Process index.html
        indexHtmlProcessed = Q.Promise(function(resolve) {
          gulp.src([distPath + '/index.html'])
            .pipe(mustache({
              title: opt.title,
              extraHead: opt.extraHead,
              beforeBody: opt.beforeBody,
              afterBody: opt.afterBody,
              styleguideConfig: JSON.stringify(copyUsedOptionsToInlineJsonConfig(opt, {}).config),
              appRoot: opt.appRoot,
              socketIo: opt.server,
              filesConfig: JSON.stringify(groupModuleFiles(opt.filesConfig))
            }))
            .pipe(pushAllFiles())
            .on('finish', resolve);
        });

        Q.all([stylesCompiled, overviewProcessed, filesCopied, indexHtmlProcessed])
          .then(function() {
            if (opt.server) {
              opt.fileHashes = fileHashes;
              startServer(opt);
            }
            emitCompileSuccess();
          })
          .catch(function(error) {
            console.error(error.stack || error);
            emitCompileError(error);
          })
          .finally(callback);
      }).catch(function(error) {
        console.error(error.stack || error);
        emitCompileError(error);
        callback();
      });
    }
  ).on('error', console.error.bind(console)).on('end', function() {
    emitProgressEnd();
  });
};

/* Build pseudo styles and ay rules from preprocessed CSS styles */

module.exports.applyStyles = function() {
  var throughOpts = {
    objectMode: true,
    allowHalfOpen: false
  }, stylesBuffer = '',
    pseudoStylesBuffer = '',
    atRulesBuffer = '';

  function bufferFileContents(file, enc, done) {
    var pseudoStylesPromise,
      atRulesPromise;

    // Make sure file goes through the next gulp plugin
    // jshint -W040
    this.push(file);
    // jshint +W040

    // Process only CSS files
    // For example gulp-ruby-sass generatsd sourcemaps to the stream that we do not want to include
    if (path.extname(file.path) !== '.css') {
      done();
      return;
    }

    // Add styles to common stylesheet
    stylesBuffer += file.contents.toString();

    // Create stylesheet that contains pseudo styles
    pseudoStylesPromise = Q.Promise(function(resolve) {
      pseudoStylesBuffer += pseudoSelectors.stylesFromString(file.contents.toString(), {
        source: file.path
      });
      resolve();
    });

    // Create stylesheet that contains at-rules
    atRulesPromise = Q.Promise(function(resolve) {
      atRulesBuffer += atRules.stylesFromString(file.contents.toString(), {
        source: file.path
      });
      resolve();
    });

    Q.all([pseudoStylesPromise, atRulesPromise]).then(function() {
      done();
    });
  }

  return through(throughOpts, bufferFileContents, function(cb) {
    this.push(new File({
      path: 'styleguide.css',
      contents: new Buffer(stylesBuffer)
    }));

    this.push(new File({
      path: 'styleguide_pseudo_styles.css',
      contents: new Buffer(pseudoStylesBuffer)
    }));

    this.push(new File({
      path: 'styleguide_at_rules.css',
      contents: new Buffer(atRulesBuffer)
    }));

    cb();
  }).on('end', function() {
    if (socketIsOpen()) {
      serverInstance.io.emitStylesChanged();
    }
  });
};

/* Built-in server */

module.exports.server = function(options) {
  startServer(options);
};

function startServer(options) {
  var port = options.port;
  // Ignore start server if we already have instance running
  if (!serverInstance) {
    serverInstance = sgServer(options);
    serverInstance.app.set('port', port);
    serverInstance.server.listen(serverInstance.app.get('port'), function() {
      console.log('Express server listening on port ' + serverInstance.server.address().port);
    }).on('error', function(error) {
      if (error.code === 'EADDRINUSE') {
        console.error('Port:' + port + ' is already in use.');
        console.error('Please provide port using --port <port>');
      }
    });
  }
  return serverInstance;
}

module.exports.addSection = addSection;
<|endoftext|>


<commit_before>/**
 * @license
 * Copyright 2016 Google Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

goog.provide('shaka.media.VideoWrapper');

goog.require('goog.asserts');
goog.require('shaka.log');
goog.require('shaka.util.EventManager');
goog.require('shaka.util.IReleasable');
goog.require('shaka.util.Timer');

/**
 * Creates a new VideoWrapper that manages setting current time and playback
 * rate.  This handles seeks before content is loaded and ensuring the video
 * time is set properly.  This doesn't handle repositioning within the
 * presentation window.
 *
 * @param {!HTMLMediaElement} video
 * @param {function()} onSeek Called when the video seeks.
 * @param {number} startTime The time to start at.
 *
 * @constructor
 * @struct
 * @implements {shaka.util.IReleasable}
 */
shaka.media.VideoWrapper = function(video, onSeek, startTime) {
  /** @private {HTMLMediaElement} */
  this.video_ = video;

  /** @private {shaka.util.Timer} */
  this.seekTimer_ = new shaka.util.Timer(onSeek);

  /** @private {number} */
  this.startTime_ = startTime;

  /** @private {shaka.util.EventManager} */
  this.eventManager_ = new shaka.util.EventManager();

  /** @private {number} */
  this.playbackRate_ = 1;

  /** @private {boolean} */
  this.buffering_ = false;

  /** @private {shaka.util.Timer} */
  this.trickPlayTimer_ = null;

  /** @private {number} */
  this.remainingMoveAttempts_ = 0;

  /** @private {number} */
  this.oldTime_ = 0;
  /** @private {number} */
  this.newTime_ = 0;

  /**
   * The move playhead timer is used to force the playhead to move. Sometimes,
   * IE and Edge ignore re-seeks.  Check every 100ms and try again if need be,
   * up to |remainingMoveAttempts_|.
   *
   * Delay stats over 100 runs of a re-seeking integration test:
   * IE     -   0ms -  47%
   * IE     - 100ms -  63%
   * Edge   -   0ms -   2%
   * Edge   - 100ms -  40%
   * Edge   - 200ms -  32%
   * Edge   - 300ms -  24%
   * Edge   - 400ms -   2%
   * Chrome -   0ms - 100%
   *
   * TODO: File a bug on IE/Edge about this.
   *
   * @private {shaka.util.Timer}
   * */
  this.movePlayheadTimer_ = new shaka.util.Timer(() => {
    if (this.remainingMoveAttempts_ <= 0) {
      this.movePlayheadTimer_.stop();
      return;
    }

    if (this.video_.currentTime != this.oldTime_) {
      this.movePlayheadTimer_.stop();
      return;
    }

    // Sigh... Try again...
    this.video_.currentTime = this.newTime_;
    this.remainingMoveAttempts_--;
  });

  // Check if the video has already loaded some metadata.
  if (video.readyState > 0) {
    this.onLoadedMetadata_();
  } else {
    this.eventManager_.listenOnce(
        video, 'loadedmetadata', this.onLoadedMetadata_.bind(this));
  }

  this.eventManager_.listen(video, 'ratechange', this.onRateChange_.bind(this));
};

/** @override */
shaka.media.VideoWrapper.prototype.release = function() {
  if (this.eventManager_) {
    this.eventManager_.release();
    this.eventManager_ = null;
  }

  if (this.trickPlayTimer_ != null) {
    this.trickPlayTimer_.stop();
    this.trickPlayTimer_ = null;
  }

  if (this.movePlayheadTimer_ != null) {
    this.movePlayheadTimer_.stop();
    this.movePlayheadTimer_ = null;
  }

  if (this.seekTimer_ != null) {
    this.seekTimer_.stop();
    this.seekTimer_ = null;
  }

  this.video_ = null;
};

/**
 * Gets the video's current (logical) position.
 *
 * @return {number}
 */
shaka.media.VideoWrapper.prototype.getTime = function() {
  if (this.video_.readyState > 0) {
    return this.video_.currentTime;
  } else {
    return this.startTime_;
  }
};

/**
 * Sets the current time of the video.
 *
 * @param {number} time
 */
shaka.media.VideoWrapper.prototype.setTime = function(time) {
  if (this.video_.readyState > 0) {
    this.movePlayhead_(this.video_.currentTime, time);
  } else {
    // The video element is not ready, try again by seeking next interpreter
    // cycle.
    this.startTime_ = time;
    this.seekTimer_.tickAfter(/* seconds= */ 0);
  }
};

/**
 * Gets the current effective playback rate.  This may be negative even if the
 * browser does not directly support rewinding.
 * @return {number}
 */
shaka.media.VideoWrapper.prototype.getPlaybackRate = function() {
  return this.playbackRate_;
};

/**
 * Sets the playback rate.
 * @param {number} rate
 */
shaka.media.VideoWrapper.prototype.setPlaybackRate = function(rate) {
  if (this.trickPlayTimer_ != null) {
    this.trickPlayTimer_.stop();
    this.trickPlayTimer_ = null;
  }

  this.playbackRate_ = rate;
  // All major browsers support playback rates above zero.  Only need fake
  // trick play for negative rates.
  this.video_.playbackRate = (this.buffering_ || rate < 0) ? 0 : rate;

  if (!this.buffering_ && rate < 0) {
    // Defer creating the timer until we stop buffering.  This function will be
    // called again from setBuffering().
    this.trickPlayTimer_ = new shaka.util.Timer(() => {
      this.video_.currentTime += rate / 4;
    }).tickEvery(/* seconds= */ 0.25);
  }
};

/**
 * Stops the playhead for buffering, or resumes the playhead after buffering.
 *
 * @param {boolean} buffering True to stop the playhead; false to allow it to
 *   continue.
 */
shaka.media.VideoWrapper.prototype.setBuffering = function(buffering) {
  if (buffering != this.buffering_) {
    this.buffering_ = buffering;
    this.setPlaybackRate(this.playbackRate_);
  }
};

/**
 * Handles a 'ratechange' event.
 *
 * @private
 */
shaka.media.VideoWrapper.prototype.onRateChange_ = function() {
  // NOTE: This will not allow explicitly setting the playback rate to 0 while
  // the playback rate is negative.  Pause will still work.
  let expectedRate =
      this.buffering_ || this.playbackRate_ < 0 ? 0 : this.playbackRate_;

  // Native controls in Edge trigger a change to playbackRate and set it to 0
  // when seeking.  If we don't exclude 0 from this check, we will force the
  // rate to stay at 0 after a seek with Edge native controls.
  // https://github.com/google/shaka-player/issues/951
  if (this.video_.playbackRate && this.video_.playbackRate != expectedRate) {
    shaka.log.debug('Video playback rate changed to', this.video_.playbackRate);
    this.setPlaybackRate(this.video_.playbackRate);
  }
};

/**
 * Handles a 'loadedmetadata' event.
 *
 * @private
 */
shaka.media.VideoWrapper.prototype.onLoadedMetadata_ = function() {
  if (Math.abs(this.video_.currentTime - this.startTime_) < 0.001) {
    this.onSeekingToStartTime_();
  } else {
    this.eventManager_.listenOnce(
        this.video_, 'seeking', this.onSeekingToStartTime_.bind(this));
    // If the currentTime != 0, it indicates that the user has seeked after
    // calling load(), so it is intended to start from a specific timestamp
    // when playback, and should not be overriden by the startTime.
    if (this.video_.currentTime == 0) {
      this.video_.currentTime = this.startTime_;
    } else {
      // This is a workaround solution. If the currentTime is not set again, the
      // video is stuck and could not be played.
      // TODO: Need further investigation why it happens. Before and after
      // setting the current time, video.readyState is 1, video.paused is true,
      // and video.buffered's TimeRanges length is 0.
      // See: https://github.com/google/shaka-player/issues/1298
      this.video_.currentTime = this.video_.currentTime;
    }
  }
};

/**
 * Handles the 'seeking' event from the initial jump to the start time (if
 * there is one).
 *
 * @private
 */
shaka.media.VideoWrapper.prototype.onSeekingToStartTime_ = function() {
  goog.asserts.assert(this.video_.readyState > 0,
                      'readyState should be greater than 0');
  this.eventManager_.listen(
      this.video_, 'seeking', () => this.seekTimer_.tickNow());
};

/**
 * Moves the playhead to the target time, triggering a call to onSeeking_().
 *
 * @param {number} currentTime
 * @param {number} targetTime
 * @private
 */
shaka.media.VideoWrapper.prototype.movePlayhead_ = function(
    currentTime, targetTime) {
  shaka.log.debug('Moving playhead...',
                  'currentTime=' + currentTime,
                  'targetTime=' + targetTime);
  this.video_.currentTime = targetTime;

  // Reset the values that the timer will use to force the playhead to
  // |targetTime|.
  this.oldTime_ = currentTime;
  this.newTime_ = targetTime;
  this.remainingMoveAttempts_ = 10;

  this.movePlayheadTimer_.tickEvery(/* seconds= */ 0.1);
};
<commit_msg>Isolate Move Playhead Position<commit_after>/**
 * @license
 * Copyright 2016 Google Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

goog.provide('shaka.media.VideoWrapper');
goog.provide('shaka.media.VideoWrapper.PlayheadMover');

goog.require('goog.asserts');
goog.require('shaka.log');
goog.require('shaka.util.EventManager');
goog.require('shaka.util.IReleasable');
goog.require('shaka.util.Timer');

/**
 * Creates a new VideoWrapper that manages setting current time and playback
 * rate.  This handles seeks before content is loaded and ensuring the video
 * time is set properly.  This doesn't handle repositioning within the
 * presentation window.
 *
 * @param {!HTMLMediaElement} video
 * @param {function()} onSeek Called when the video seeks.
 * @param {number} startTime The time to start at.
 *
 * @constructor
 * @struct
 * @implements {shaka.util.IReleasable}
 */
shaka.media.VideoWrapper = function(video, onSeek, startTime) {
  /** @private {HTMLMediaElement} */
  this.video_ = video;

  /** @private {function()} */
  this.onSeek_ = onSeek;

  /** @private {number} */
  this.startTime_ = startTime;

  /** @private {shaka.util.EventManager} */
  this.eventManager_ = new shaka.util.EventManager();

  /** @private {number} */
  this.playbackRate_ = 1;

  /** @private {boolean} */
  this.buffering_ = false;

  /** @private {shaka.util.Timer} */
  this.trickPlayTimer_ = null;

  /** @private {shaka.media.VideoWrapper.PlayheadMover} */
  this.mover_ = new shaka.media.VideoWrapper.PlayheadMover(
      /* mediaElement= */ video,
      /* maxAttempts= */ 10);

  // Before we can set the start time, we must check if the video element is
  // ready. If the video element is not ready, we cannot set the time. To work
  // around this, we will wait for the "loadedmetadata" event which tells us
  // that the media element is now ready.
  if (video.readyState > 0) {
    this.setStartTime_(startTime);
  } else {
    this.delaySetStartTime_(startTime);
  }

  this.eventManager_.listen(video, 'ratechange', this.onRateChange_.bind(this));
};

/** @override */
shaka.media.VideoWrapper.prototype.release = function() {
  if (this.eventManager_) {
    this.eventManager_.release();
    this.eventManager_ = null;
  }

  if (this.trickPlayTimer_ != null) {
    this.trickPlayTimer_.stop();
    this.trickPlayTimer_ = null;
  }

  if (this.mover_ != null) {
    this.mover_.release();
    this.mover_ = null;
  }

  this.onSeek_ = () => {};
  this.video_ = null;
};

/**
 * Gets the video's current (logical) position.
 *
 * @return {number}
 */
shaka.media.VideoWrapper.prototype.getTime = function() {
  return this.video_.readyState > 0 ?
         this.video_.currentTime :
         this.startTime_;
};

/**
 * Sets the current time of the video.
 *
 * @param {number} time
 */
shaka.media.VideoWrapper.prototype.setTime = function(time) {
  if (this.video_.readyState > 0) {
    this.mover_.moveTo(time);
  } else {
    this.delaySetStartTime_(time);
  }
};

/**
 * Gets the current effective playback rate.  This may be negative even if the
 * browser does not directly support rewinding.
 * @return {number}
 */
shaka.media.VideoWrapper.prototype.getPlaybackRate = function() {
  return this.playbackRate_;
};

/**
 * Sets the playback rate.
 * @param {number} rate
 */
shaka.media.VideoWrapper.prototype.setPlaybackRate = function(rate) {
  if (this.trickPlayTimer_ != null) {
    this.trickPlayTimer_.stop();
    this.trickPlayTimer_ = null;
  }

  this.playbackRate_ = rate;
  // All major browsers support playback rates above zero.  Only need fake
  // trick play for negative rates.
  this.video_.playbackRate = (this.buffering_ || rate < 0) ? 0 : rate;

  if (!this.buffering_ && rate < 0) {
    // Defer creating the timer until we stop buffering.  This function will be
    // called again from setBuffering().
    this.trickPlayTimer_ = new shaka.util.Timer(() => {
      this.video_.currentTime += rate / 4;
    }).tickEvery(/* seconds= */ 0.25);
  }
};

/**
 * Stops the playhead for buffering, or resumes the playhead after buffering.
 *
 * @param {boolean} buffering True to stop the playhead; false to allow it to
 *   continue.
 */
shaka.media.VideoWrapper.prototype.setBuffering = function(buffering) {
  if (buffering != this.buffering_) {
    this.buffering_ = buffering;
    this.setPlaybackRate(this.playbackRate_);
  }
};

/**
 * Handles a 'ratechange' event.
 *
 * @private
 */
shaka.media.VideoWrapper.prototype.onRateChange_ = function() {
  // NOTE: This will not allow explicitly setting the playback rate to 0 while
  // the playback rate is negative.  Pause will still work.
  let expectedRate =
      this.buffering_ || this.playbackRate_ < 0 ? 0 : this.playbackRate_;

  // Native controls in Edge trigger a change to playbackRate and set it to 0
  // when seeking.  If we don't exclude 0 from this check, we will force the
  // rate to stay at 0 after a seek with Edge native controls.
  // https://github.com/google/shaka-player/issues/951
  if (this.video_.playbackRate && this.video_.playbackRate != expectedRate) {
    shaka.log.debug('Video playback rate changed to', this.video_.playbackRate);
    this.setPlaybackRate(this.video_.playbackRate);
  }
};

/**
 * If the media element is not ready, we can't set |currentTime|. To work around
 * this we will listen for the "loadedmetadata" event so that we can set the
 * start time once the element is ready.
 *
 * @param {number} startTime
 */
shaka.media.VideoWrapper.prototype.delaySetStartTime_ = function(startTime) {
  const readyEvent = 'loadedmetadata';

  // Since we are going to override what the start time should be, we need to
  // save it so that |getTime| can return the most accurate start time possible.
  this.startTime_ = startTime;

  // The media element is not ready to accept changes to current time. We need
  // to cache them and then execute them once the media element is ready.
  this.eventManager_.unlisten(this.video_, readyEvent);

  this.eventManager_.listenOnce(this.video_, readyEvent, () => {
    this.setStartTime_(startTime);
  });
};

/**
 * Set the start time for the content. The given start time will be ignored if
 * the content does not start at 0.
 *
 * @param {number} startTime
 * @private
 */
shaka.media.VideoWrapper.prototype.setStartTime_ = function(startTime) {
  // If we start close enough to our intended start time, then we won't do
  // anything special.
  if (Math.abs(this.video_.currentTime - startTime) < 0.001) {
    this.startListeningToSeeks_();
    return;
  }

  // We will need to delay adding our normal seeking listener until we have
  // seen the first seek event. We will force the first seek event later in this
  // method.
  this.eventManager_.listenOnce(this.video_, 'seeking', () => {
    this.startListeningToSeeks_();
  });

  // If the currentTime != 0, it indicates that the user has seeked after
  // calling |Player.load|, meaning that |currentTime| is more meaningful than
  // |startTime|.
  //
  // Seeking to the current time is a work around for Issue 1298. If we don't
  // do this, the video may get stuck and not play.
  //
  // TODO: Need further investigation why it happens. Before and after
  // setting the current time, video.readyState is 1, video.paused is true,
  // and video.buffered's TimeRanges length is 0.
  // See: https://github.com/google/shaka-player/issues/1298
  this.mover_.moveTo(
      this.video_.currentTime == 0 ?
      startTime :
      this.video_.currentTime);
};

/**
 * Add the listener for seek-events. This will call the externally-provided
 * |onSeek| callback whenever the media element seeks.
 *
 * @private
 */
shaka.media.VideoWrapper.prototype.startListeningToSeeks_ = function() {
  goog.asserts.assert(
      this.video_.readyState > 0,
      'The media element should be ready before we listen for seeking.');

  this.eventManager_.listen(this.video_, 'seeking', () => this.onSeek_());
};

/**
 * A class used to move the playhead away from its current time.  Sometimes, IE
 * and Edge ignore re-seeks. After changing the current time, check every 100ms,
 * retrying if the change was not accepted.
 *
 * Delay stats over 100 runs of a re-seeking integration test:
 *   IE     -   0ms -  47%
 *   IE     - 100ms -  63%
 *   Edge   -   0ms -   2%
 *   Edge   - 100ms -  40%
 *   Edge   - 200ms -  32%
 *   Edge   - 300ms -  24%
 *   Edge   - 400ms -   2%
 *   Chrome -   0ms - 100%
 *
 * TODO: File a bug on IE/Edge about this.
 *
 * @implements {shaka.util.IReleasable}
 * @final
 */
shaka.media.VideoWrapper.PlayheadMover = class {
  /**
   * @param {!HTMLMediaElement} mediaElement
   *    The media element that the mover can manipulate.
   *
   * @param {number} maxAttempts
   *    To prevent us from infinitely trying to change the current time, the
   *    mover accepts a max attempts value. At most, the mover will check if the
   *    video moved |maxAttempts| times. If this is zero of negative, no
   *    attempts will be made.
   */
  constructor(mediaElement, maxAttempts) {
    /** @private {HTMLMediaElement} */
    this.mediaElement_ = mediaElement;

    /** @private {number} */
    this.maxAttempts_ = maxAttempts;

    /** @private {number} */
    this.remainingAttempts_ = 0;

    /** @private {number} */
    this.originTime_ = 0;

    /** @private {number} */
    this.targetTime_ = 0;

    /** @private {shaka.util.Timer} */
    this.timer_ = new shaka.util.Timer(() => this.onTick_());
  }

  /** @override */
  release() {
    if (this.timer_) {
      this.timer_.stop();
      this.timer_ = null;
    }

    this.mediaElement_ = null;
  }

  /**
   * Try forcing the media element to move to |timeInSeconds|. If a previous
   * call to |moveTo| is still in progress, this will override it.
   *
   * @param {number} timeInSeconds
   */
  moveTo(timeInSeconds) {
    this.originTime_ = this.mediaElement_.currentTime;
    this.targetTime_ = timeInSeconds;

    this.remainingAttempts_ = this.maxAttempts_;

    // Set the time and then start the timer. The timer will check if the set
    // was successful, and retry if not.
    this.mediaElement_.currentTime = timeInSeconds;
    this.timer_.tickEvery(/* seconds= */ 0.1);
  }

  /**
   * @private
   */
  onTick_() {
    // Sigh... We ran out of retries...
    if (this.remainingAttempts_ <= 0) {
      shaka.log.warning([
        'Failed to move playhead from', this.originTime_,
        'to', this.targetTime_,
      ].join(' '));

      this.timer_.stop();
      return;
    }

    // Yay! We were successful.
    if (this.mediaElement_.currentTime != this.originTime_) {
      this.timer_.stop();
      return;
    }

    // Sigh... Try again...
    this.mediaElement_.currentTime = this.targetTime_;
    this.remainingAttempts_--;
  }
};
<|endoftext|>


<commit_before>var Handsontable = require('./../bower_components/handsontable/dist/handsontable.full.js');
var loader = require('../renderer/loader.js');

var initialise = function(container) {

  var hot = new Handsontable(container, {
    colHeaders: true,
    rowHeaders: true,
    columnSorting: true,
    contextMenu: false,
    autoRowSize: true,
    enterBeginsEditing: false,
    tabMoves: function(event) {
      if (!event.shiftKey) {
        var selection = hot.getSelected();
        next = hot.getCell(selection[0], selection[1] + 1);
        if (next === null) {
         hot.alter('insert_col', selection[1] + 1);
        }
      }
      return {row: 0, col: 1};
    },
    afterInit: function() {
      loader.showLoader('Loading...');
    },
    afterLoadData: function() {
      loader.hideLoader();
    },
    enterMoves: function(event) {
      if (!event.shiftKey) {
        var selection = hot.getSelected();
        next = hot.getCell(selection[0] + 1, selection[1]);
        if (next === null) {
         hot.alter('insert_row', selection[0] + 1);
         return {row: 1, col: 0 - selection[1]};
       } else {
         return {row: 1, col: 0};
       }
     } else {
       return {row: 1, col: 0};
     }
    }
  });
  return hot;
};

var insertRowAbove = function(deselect) {
  hot.getActiveEditor().finishEditing(true);
  var range = hot.getSelectedRange();
  if (typeof range === 'undefined') { return; }
  var start = Math.min(range.from.row, range.to.row);
  hot.alter('insert_row', start);
  if (deselect) {
    hot.deselectCell();
  }
};

var insertRowBelow = function(deselect) {
  hot.getActiveEditor().finishEditing(true);
  var range = hot.getSelectedRange();
  if (typeof range === 'undefined') { return; }
  var end = Math.max(range.from.row, range.to.row);
  hot.alter('insert_row', (end + 1));
  if (deselect) {
    hot.deselectCell();
  }
};

var insertColumnLeft = function(deselect) {
  hot.getActiveEditor().finishEditing(true);
  var range = hot.getSelectedRange();
  if (typeof range === 'undefined') { return; }
  var start = Math.min(range.from.col, range.to.col);
  hot.alter('insert_col', start);
  if (deselect) {
    hot.deselectCell();
  }
};

var insertColumnRight = function(deselect) {
  hot.getActiveEditor().finishEditing(true);
  var range = hot.getSelectedRange();
  if (typeof range === 'undefined') { return; }
  var end = Math.max(range.from.col, range.to.col);
  hot.alter('insert_col', (end + 1));
  if (deselect) {
    hot.deselectCell();
  }
};

var removeRows = function() {
  var range = hot.getSelectedRange();
  if (typeof range === 'undefined') { return; }

  var start = Math.min(range.from.row, range.to.row);
  var end   = Math.max(range.from.row, range.to.row);

  for (var row = start; row <= end; row++) {
    // rows are re-indexed after each remove
    // so always remove 'start'
    hot.alter('remove_row', start);
  }

  hot.deselectCell();
};

var removeColumns = function() {
  var range = hot.getSelectedRange();
  if (typeof range === 'undefined') { return; }

  var start = Math.min(range.from.col, range.to.col);
  var end   = Math.max(range.from.col, range.to.col);

  for (var col = start; col <= end; col++) {
    // cols are re-indexed after each remove
    // so always remove 'start'
    hot.alter('remove_col', start);
  }

  hot.deselectCell();
};

module.exports = {
  insertRowAbove: insertRowAbove,
  insertRowBelow: insertRowBelow,
  insertColumnLeft: insertColumnLeft,
  insertColumnRight: insertColumnRight,
  removeRows: removeRows,
  removeColumns: removeColumns,
  create: initialise,
  // returns the HoT object
};
<commit_msg>fix tab shortcut<commit_after>var Handsontable = require('./../bower_components/handsontable/dist/handsontable.full.js');
var loader = require('../renderer/loader.js');

var initialise = function(container) {

  var hot = new Handsontable(container, {
    colHeaders: true,
    rowHeaders: true,
    columnSorting: true,
    contextMenu: false,
    autoRowSize: true,
    enterBeginsEditing: false,
    tabMoves: function(event) {
      if (!event.shiftKey) {
        var selection = hot.getSelected();
        next = hot.getCell(selection[0], selection[1] + 1);
        if (next === undefined) {
         hot.alter('insert_col', selection[1] + 1);
        }
      }
      return {row: 0, col: 1};
    },
    afterInit: function() {
      loader.showLoader('Loading...');
    },
    afterLoadData: function() {
      loader.hideLoader();
    },
    enterMoves: function(event) {
      if (!event.shiftKey) {
        var selection = hot.getSelected();
        next = hot.getCell(selection[0] + 1, selection[1]);
        if (next === null) {
         hot.alter('insert_row', selection[0] + 1);
         return {row: 1, col: 0 - selection[1]};
       } else {
         return {row: 1, col: 0};
       }
     } else {
       return {row: 1, col: 0};
     }
    }
  });
  return hot;
};

var insertRowAbove = function(deselect) {
  hot.getActiveEditor().finishEditing(true);
  var range = hot.getSelectedRange();
  if (typeof range === 'undefined') { return; }
  var start = Math.min(range.from.row, range.to.row);
  hot.alter('insert_row', start);
  if (deselect) {
    hot.deselectCell();
  }
};

var insertRowBelow = function(deselect) {
  hot.getActiveEditor().finishEditing(true);
  var range = hot.getSelectedRange();
  if (typeof range === 'undefined') { return; }
  var end = Math.max(range.from.row, range.to.row);
  hot.alter('insert_row', (end + 1));
  if (deselect) {
    hot.deselectCell();
  }
};

var insertColumnLeft = function(deselect) {
  hot.getActiveEditor().finishEditing(true);
  var range = hot.getSelectedRange();
  if (typeof range === 'undefined') { return; }
  var start = Math.min(range.from.col, range.to.col);
  hot.alter('insert_col', start);
  if (deselect) {
    hot.deselectCell();
  }
};

var insertColumnRight = function(deselect) {
  hot.getActiveEditor().finishEditing(true);
  var range = hot.getSelectedRange();
  if (typeof range === 'undefined') { return; }
  var end = Math.max(range.from.col, range.to.col);
  hot.alter('insert_col', (end + 1));
  if (deselect) {
    hot.deselectCell();
  }
};

var removeRows = function() {
  var range = hot.getSelectedRange();
  if (typeof range === 'undefined') { return; }

  var start = Math.min(range.from.row, range.to.row);
  var end   = Math.max(range.from.row, range.to.row);

  for (var row = start; row <= end; row++) {
    // rows are re-indexed after each remove
    // so always remove 'start'
    hot.alter('remove_row', start);
  }

  hot.deselectCell();
};

var removeColumns = function() {
  var range = hot.getSelectedRange();
  if (typeof range === 'undefined') { return; }

  var start = Math.min(range.from.col, range.to.col);
  var end   = Math.max(range.from.col, range.to.col);

  for (var col = start; col <= end; col++) {
    // cols are re-indexed after each remove
    // so always remove 'start'
    hot.alter('remove_col', start);
  }

  hot.deselectCell();
};

module.exports = {
  insertRowAbove: insertRowAbove,
  insertRowBelow: insertRowBelow,
  insertColumnLeft: insertColumnLeft,
  insertColumnRight: insertColumnRight,
  removeRows: removeRows,
  removeColumns: removeColumns,
  create: initialise,
  // returns the HoT object
};
<|endoftext|>


<commit_before>/*
 *
 * Papyruss Framework
 * Created by Joseph Gengarella
 *
 */

/* global jQuery, window */

/*
 * Make sure jQuery is included
 */
if (typeof jQuery == 'undefined') {
   throw new Error('Papyruss needs jQuery to run correcly, sorry :(');
}

/*
 * This is the basic function structure
 *
 * (function ($) {
 *    'use strict';
 * })(jQuery);
 */

/*
 * All logic behind the navbar toggle and it's responsiveness
 */

(function ($) {
   'use strict';

   var prev_size = $(window).width();
   var current_width = $(window).width();
   var sidebar = $('.nav-sidebar');
   var nav_toggle_link = $('.toggle-wrapper');
   var nav_toggle = $('.nav-toggle');
   var navbar = $('#navbar');

   if (sidebar.length) {
      navbar.show();
   }

   nav_toggle_link.on('click', function (e) {
      e.preventDefault();

      nav_toggle.toggleClass('opened');

      if (!sidebar.length) {
         navbar.slideToggle();
      } else if (sidebar.length) {
         sidebar.toggleClass('sidebar-active');

         if (sidebar.hasClass('sidebar-active')) {
            sidebar.animate({
               'margin-left': 0
            });
         } else {
            sidebar.animate({
               'margin-left': -250
            });
         }

      }
   });

   // Currntly not working...
   $(window).resize(function (e) {
      e.preventDefault();

      console.log("we");
      if ($(this).width() > 767) {
         navbar.show();
      }

      if ($(this).width() > 767 && prev_size < 767) {
         if (sidebar.length) {
            sidebar.css({
               'margin-left': 0
            });
            nav_toggle.removeClass('opened');
            sidebar.removeClass('sidebar-active');
         }
         prev_size = $(this).width();
      } else if ($(this).width() < 768 && prev_size > 768) {
         if (sidebar.length) {
            navbar.show();
            sidebar.css({
               'margin-left': -250
            });
         }
         if (!sidebar.length) {
            nav_toggle.removeClass('opened');
            navbar.hide();
         }
         prev_size = $(this).width();
      }
      current_width = $(this).width();
   });
})(jQuery);

/*
 * Adds padding on top/bot of screen for fixed top/bot nav bar
 */

(function ($) {
   'use strict';

   var page_content = $('#page-content');
   var top_is_fixed = $('.nav-top-fixed').length;
   var bot_is_fixed = $('.nav-bot-fixed').length;

   if (top_is_fixed) {
      page_content.css({
         'margin-top': $('nav').height() + 20
      });
   }
   if (bot_is_fixed) {
      page_content.css({
         'margin-bottom': $('nav').height() + 20
      });
   }
})(jQuery);

/*
 * Allows for scrolling to link locations
 */

(function ($) {
   'use strict';

   var scroll_el = $('.scroll');
   var nav = $('nav');
   var top_offset = 20;
   var window = $(window);
   var top_nav = $('.nav-top').length;

   if (top_nav) {
      top_offset += nav.height();
   }

   scroll_el.on('click', function (e) {
      e.preventDefault();

      $('html, body').animate({
         scrollTop: $(this.hash).offset().top - top_offset
      }, 500);
   });
})(jQuery);

/*
 * Scroll spy for inline links
 */

(function ($) {
   'use strict';

   var top_offset = 20;
   var sidebar = $('.nav-sidebar').length;
   var nav = $('nav');
   if (!sidebar) {
      top_offset += nav.height();
   }

   var prev_id;
   var first = $('#navbar').find('a')
               .attr('href').replace('#', '');
   var navbar = $('#navbar');
   var nav_links = navbar.find('a');
   var nav_list =
      nav_links.map(function () {
         var item = $($(this).attr('href'));
         if (item.length) {
            return item;
         }
      });

   $(window).scroll(function () {
      var distance_top = $(this).scrollTop() + top_offset;

      var cur = nav_list.map(function () {
         if ($(this).offset().top < distance_top) {
            return this;
         }
      });
      cur = cur[cur.length - 1];
      var id = cur && cur.length ? cur[0].id : first;

      if (prev_id !== id) {
         prev_id = id;
         console.log(id);
         nav_links
            .removeClass('nav-active')
            .filter('[href="#' + id + '"]').addClass('nav-active');
      }
   });
})(jQuery);
<commit_msg>Updated for more correct linting<commit_after>/*
 *
 * Papyruss Framework
 * Created by Joseph Gengarella
 *
 */

/* global jQuery, window */

/*
 * Make sure jQuery is included
 */

if (typeof jQuery == 'undefined') {
   throw new Error('Papyruss needs jQuery to run correcly, sorry :(');
}

/*
 * This is the basic function structure
 *
 * (function ($) {
 *    'use strict';
 * })(jQuery);
 */

/*
 * All logic behind the navbar toggle and it's responsiveness
 */

(function ($) {
   'use strict';

   var prev_size = $(window).width(),
      current_width = $(window).width(),
      sidebar = $('.nav-sidebar'),
      nav_toggle_link = $('.toggle-wrapper'),
      nav_toggle = $('.nav-toggle'),
      navbar = $('#navbar');

   if (sidebar.length) {
      navbar.show();
   }

   nav_toggle_link.on('click', function (e) {
      e.preventDefault();

      nav_toggle.toggleClass('opened');

      if (!sidebar.length) {
         navbar.slideToggle();
      } else if (sidebar.length) {
         sidebar.toggleClass('sidebar-active');

         if (sidebar.hasClass('sidebar-active')) {
            sidebar.animate({
               'margin-left': 0
            });
         } else {
            sidebar.animate({
               'margin-left': -250
            });
         }

      }
   });

   $(window).resize(function (e) {
      e.preventDefault();

      if ($(this).width() > 767) {
         navbar.show();
      }

      if ($(this).width() > 767 && prev_size < 767) {
         if (sidebar.length) {
            sidebar.css({
               'margin-left': 0
            });
            nav_toggle.removeClass('opened');
            sidebar.removeClass('sidebar-active');
         }
         prev_size = $(this).width();
      } else if ($(this).width() < 768 && prev_size > 768) {
         if (sidebar.length) {
            navbar.show();
            sidebar.css({
               'margin-left': -250
            });
         }
         if (!sidebar.length) {
            nav_toggle.removeClass('opened');
            navbar.hide();
         }
         prev_size = $(this).width();
      }
      current_width = $(this).width();
   });
})(jQuery);

/*
 * Adds padding on top/bot of screen for fixed top/bot nav bar
 */

(function ($) {
   'use strict';

   var page_content = $('#page-content'),
      top_is_fixed = $('.nav-top-fixed').length,
      bot_is_fixed = $('.nav-bot-fixed').length;

   if (top_is_fixed) {
      page_content.css({
         'margin-top': $('nav').height() + 20
      });
   }
   if (bot_is_fixed) {
      page_content.css({
         'margin-bottom': $('nav').height() + 20
      });
   }
})(jQuery);

/*
 * Allows for scrolling to link locations
 */

(function ($) {
   'use strict';

   var scroll_el = $('.scroll'),
      nav = $('nav'),
      top_offset = 20,
      window = $(window),
      top_nav = $('.nav-top').length;

   if (top_nav) {
      top_offset += nav.height();
   }

   scroll_el.on('click', function (e) {
      e.preventDefault();

      $('html, body').animate({
         scrollTop: $(this.hash).offset().top - top_offset
      }, 500);
   });
})(jQuery);

/*
 * Scroll spy for inline links
 */

(function ($) {
   'use strict';

   var top_offset = 21,
      sidebar = $('.nav-sidebar').length,
      nav = $('nav');
   if (!sidebar) {
      top_offset += nav.height();
   }

   var prev_id,
      first = $('#navbar').find('a')
      .attr('href').replace('#', ''),
      navbar = $('#navbar'),
      nav_links = navbar.find('a'),
      nav_list =
      nav_links.map(function () {
         var item = $($(this).attr('href'));
         if (item.length) {
            return item;
         }
      });

   $(window).scroll(function () {
      var distance_top = $(this).scrollTop() + top_offset,
         cur = nav_list.map(function () {
            if ($(this).offset().top < distance_top) {
               return this;
            }
         });

      cur = cur[cur.length - 1];
      var id = cur && cur.length ? cur[0].id : first;

      if (prev_id !== id) {
         prev_id = id;
         nav_links
            .removeClass('nav-active')
            .filter('[href="#' + id + '"]')
            .addClass('nav-active');
      }
   });
})(jQuery);
<|endoftext|>


<commit_before>var util = require("util");
var fs = require("fs");
var spawn = require("child_process").spawn;
var path = require("path");
var fileExtensionPattern;
var startChildProcess;
var noRestartOn = null;
var debug = true;
var verbose = false;
var ignoredPaths = {};

exports.run = run;

function run (args) {
  var arg, next, watch, ignore, program, extensions, executor, poll_interval, debugFlag, debugBrkFlag;
  while (arg = args.shift()) {
    if (arg === "--help" || arg === "-h" || arg === "-?") {
      return help();
    } else if (arg === "--quiet" || arg === "-q") {
      debug = false;
      util.debug = function(){};
      util.puts = function(){};
    } else if (arg === "--verbose" || arg === "-V") {
      verbose = true;
    } else if (arg === "--watch" || arg === "-w") {
      watch = args.shift();
    } else if (arg === "--ignore" || arg === "-i") {
      ignore = args.shift();
    } else if (arg === "--poll-interval" || arg === "-p") {
      poll_interval = parseInt(args.shift());
    } else if (arg === "--extensions" || arg === "-e") {
      extensions = args.shift();
    } else if (arg === "--exec" || arg === "-x") {
      executor = args.shift();
    } else if (arg === "--no-restart-on" || arg === "-n") {
      noRestartOn = args.shift();
    } else if (arg === "--debug") {
      debugFlag = true;
    } else if (arg === "--debug-brk") {
      debugBrkFlag = true;
    } else if (arg === "--") {
      program = args;
      break;
    } else if (arg.indexOf("-") && !args.length) {
      // Assume last arg is the program
      program = [arg];
    }
  }
  if (!program) {
    return help();
  }
  if (!watch) {
    watch = ".";
  }
  if (!poll_interval) {
    poll_interval = 100;
  }

  var programExt = program.join(" ").match(/.*\.(\S*)/);
  programExt = programExt && programExt[1];

  if (!extensions) {
    // If no extensions passed try to guess from the program
    extensions = "node|js";
    if (programExt && extensions.indexOf(programExt) == -1)
      extensions += "|" + programExt;
  }
  extensions = extensions.replace(/,/g, "|");
  fileExtensionPattern = new RegExp("^.*\.(" + extensions + ")$");

  if (!executor) {
    executor = (programExt === "coffee") ? "coffee" : "node";
  }

  if (debugFlag) {
    program.unshift("--debug");
  }
  if (debugBrkFlag) {
    program.unshift("--debug-brk");
  }

  try {
    // Pass kill signals through to child
    [ "SIGTERM", "SIGINT", "SIGHUP", "SIGQUIT" ].forEach( function(signal) {
      process.on(signal, function () {
        var child = exports.child;
        if (child) {
          util.debug("Sending "+signal+" to child...");
          child.kill(signal);
        }
        process.exit();
      });
    });
  } catch(e) {
    // Windows doesn't support signals yet, so they simply don't get this handling.
    // https://github.com/joyent/node/issues/1553
  }

  util.puts("")
  util.debug("Running node-supervisor with");
  util.debug("  program '" + program.join(" ") + "'");
  util.debug("  --watch '" + watch + "'");
  util.debug("  --ignore '" + ignore + "'");
  util.debug("  --extensions '" + extensions + "'");
  util.debug("  --exec '" + executor + "'");
  util.puts("");

  // store the call to startProgramm in startChildProcess
  // in order to call it later
  startChildProcess = function() { startProgram(program, executor); };

  // if we have a program, then run it, and restart when it crashes.
  // if we have a watch folder, then watch the folder for changes and restart the prog
  startChildProcess();

  if (ignore) {
    var ignoreItems = ignore.split(',');
    ignoreItems.forEach(function (ignoreItem) {
      ignoreItem = path.resolve(ignoreItem);
      ignoredPaths[ignoreItem] = true;
      util.debug("Ignoring directory '" + ignoreItem + "'.");
    });
  }

  var watchItems = watch.split(',');
  watchItems.forEach(function (watchItem) {
    watchItem = path.resolve(watchItem);
    util.debug("Watching directory '" + watchItem + "' for changes.");
    findAllWatchFiles(watchItem, function(f) {
      watchGivenFile( f, poll_interval );
    });
  });
};

function print (m, n) { util.print(m+(!n?"\n":"")); return print; }

function help () {
  print
    ("")
    ("Node Supervisor is used to restart programs when they crash.")
    ("It can also be used to restart programs when a *.js file changes.")
    ("")
    ("Usage:")
    ("  supervisor [options] <program>")
    ("  supervisor [options] -- <program> [args ...]")
    ("")
    ("Required:")
    ("  <program>")
    ("    The program to run.")
    ("")
    ("Options:")
    ("  -w|--watch <watchItems>")
    ("    A comma-delimited list of folders or js files to watch for changes.")
    ("    When a change to a js file occurs, reload the program")
    ("    Default is '.'")
    ("")
    ("  -i|--ignore <ignoreItems>")
    ("    A comma-delimited list of folders to ignore for changes.")
    ("    No default")
    ("")
    ("  -p|--poll-interval <milliseconds>")
    ("    How often to poll watched files for changes.")
    ("    Defaults to Node default.")
    ("")
    ("  -e|--extensions <extensions>")
    ("    Specific file extensions to watch in addition to defaults.")
    ("    Used when --watch option includes folders")
    ("    Default is 'node|js'")
    ("")
    ("  -x|--exec <executable>")
    ("    The executable that runs the specified program.")
    ("    Default is 'node'")
    ("")
    ("  --debug")
    ("    Start node with --debug flag.")
    ("")
    ("  --debug-brk")
    ("    Start node with --debug-brk flag.")
    ("")
    ("  -n|--no-restart-on error|exit")
    ("    Don't automatically restart the supervised program if it ends.")
    ("    Supervisor will wait for a change in the source files.")
    ("    If \"error\", an exit code of 0 will still restart.")
    ("    If \"exit\", no restart regardless of exit code.")
    ("")
    ("  -h|--help|-?")
    ("    Display these usage instructions.")
    ("")
    ("  -q|--quiet")
    ("    Suppress DEBUG messages")
    ("")
    ("  -V|--verbose")
    ("    Show extra DEBUG messages")
    ("")
    ("Examples:")
    ("  supervisor myapp.js")
    ("  supervisor myapp.coffee")
    ("  supervisor -w scripts -e myext -x myrunner myapp")
    ("  supervisor -- server.js -h host -p port")
    ("");
};

function startProgram (prog, exec) {
  util.debug("Starting child process with '" + exec + " " + prog.join(" ") + "'");
  crash_queued = false;
  var child = exports.child = spawn(exec, prog, {stdio: 'inherit'});
  if (child.stdout) {
    // node < 0.8 doesn't understand the 'inherit' option, so pass through manually
    child.stdout.addListener("data", function (chunk) { chunk && util.print(chunk); });
    child.stderr.addListener("data", function (chunk) { chunk && util.debug(chunk); });
  }
  child.addListener("exit", function (code) {
    if (!crash_queued) {
      util.debug("Program " + exec + " " + prog.join(" ") + " exited with code " + code + "\n");
      exports.child = null;
      if (noRestartOn == "exit" || noRestartOn == "error" && code !== 0) return;
    }
    startProgram(prog, exec);
  });
}

var timer = null, mtime = null; crash_queued = false;
function crash () {

  if (crash_queued)
    return;

  crash_queued = true;
  var child = exports.child;
  setTimeout(function() {
    if (child) {
      util.debug("crashing child");
      process.kill(child.pid);
    } else {
      util.debug("restarting child");
      startChildProcess();
    }
  }, 50);
}

function crashWin (event, filename) {
  var shouldCrash = true;
  if( event === 'change' ) {
    if (filename) {
      filename = path.resolve(filename);
      Object.keys(ignoredPaths).forEach(function (ignorePath) {
        if ( filename.indexOf(ignorePath + '\\') === 0 ) {
          shouldCrash = false;
        }
      });
    }
    if (shouldCrash)
      crash();
  }
}

function crashOther (oldStat, newStat) {
  // we only care about modification time, not access time.
  if ( newStat.mtime.getTime() !== oldStat.mtime.getTime() )
    crash();
}

var nodeVersion = process.version.split(".");
var isWindowsWithoutWatchFile = process.platform === 'win32' && parseInt(nodeVersion[1]) <= 6;
function watchGivenFile (watch, poll_interval) {
  if (isWindowsWithoutWatchFile)
    fs.watch(watch, { persistent: true, interval: poll_interval }, crashWin);
  else
    fs.watchFile(watch, { persistent: true, interval: poll_interval }, crashOther);
  if (verbose)
    util.debug("watching file '" + watch + "'");
}

var findAllWatchFiles = function(dir, callback) {
  dir = path.resolve(dir);
  if (ignoredPaths[dir])
    return;
  fs.stat(dir, function(err, stats){
    if (err) {
      util.error('Error retrieving stats for file: ' + dir);
    } else {
      if (stats.isDirectory()) {
        if (isWindowsWithoutWatchFile) callback(dir);
        fs.readdir(dir, function(err, fileNames) {
          if(err) {
            util.error('Error reading path: ' + dir);
          }
          else {
            fileNames.forEach(function (fileName) {
              findAllWatchFiles(path.join(dir, fileName), callback);
            });
          }
        });
      } else {
        if (!isWindowsWithoutWatchFile && dir.match(fileExtensionPattern)) {
          callback(dir);
        }
      }
    }
  });
};
<commit_msg>coffeescript doesn't understand debug or debugBrk<commit_after>var util = require("util");
var fs = require("fs");
var spawn = require("child_process").spawn;
var path = require("path");
var fileExtensionPattern;
var startChildProcess;
var noRestartOn = null;
var debug = true;
var verbose = false;
var ignoredPaths = {};

exports.run = run;

function run (args) {
  var arg, next, watch, ignore, program, extensions, executor, poll_interval, debugFlag, debugBrkFlag;
  while (arg = args.shift()) {
    if (arg === "--help" || arg === "-h" || arg === "-?") {
      return help();
    } else if (arg === "--quiet" || arg === "-q") {
      debug = false;
      util.debug = function(){};
      util.puts = function(){};
    } else if (arg === "--verbose" || arg === "-V") {
      verbose = true;
    } else if (arg === "--watch" || arg === "-w") {
      watch = args.shift();
    } else if (arg === "--ignore" || arg === "-i") {
      ignore = args.shift();
    } else if (arg === "--poll-interval" || arg === "-p") {
      poll_interval = parseInt(args.shift());
    } else if (arg === "--extensions" || arg === "-e") {
      extensions = args.shift();
    } else if (arg === "--exec" || arg === "-x") {
      executor = args.shift();
    } else if (arg === "--no-restart-on" || arg === "-n") {
      noRestartOn = args.shift();
    } else if (arg === "--debug") {
      debugFlag = true;
    } else if (arg === "--debug-brk") {
      debugBrkFlag = true;
    } else if (arg === "--") {
      program = args;
      break;
    } else if (arg.indexOf("-") && !args.length) {
      // Assume last arg is the program
      program = [arg];
    }
  }
  if (!program) {
    return help();
  }
  if (!watch) {
    watch = ".";
  }
  if (!poll_interval) {
    poll_interval = 100;
  }

  var programExt = program.join(" ").match(/.*\.(\S*)/);
  programExt = programExt && programExt[1];

  if (!extensions) {
    // If no extensions passed try to guess from the program
    extensions = "node|js";
    if (programExt && extensions.indexOf(programExt) == -1)
      extensions += "|" + programExt;
  }
  extensions = extensions.replace(/,/g, "|");
  fileExtensionPattern = new RegExp("^.*\.(" + extensions + ")$");

  if (!executor) {
    executor = (programExt === "coffee") ? "coffee" : "node";
  }

  if (debugFlag) {
    program.unshift("--debug");
  }
  if (debugBrkFlag) {
    program.unshift("--debug-brk");
  }

  if (executor === "coffee" && (debugFlag || debugBrkFlag)) {
    // coffee does not understand debug or debug-brk, make coffee pass options to node
    program.unshift("--nodejs")
  }

  try {
    // Pass kill signals through to child
    [ "SIGTERM", "SIGINT", "SIGHUP", "SIGQUIT" ].forEach( function(signal) {
      process.on(signal, function () {
        var child = exports.child;
        if (child) {
          util.debug("Sending "+signal+" to child...");
          child.kill(signal);
        }
        process.exit();
      });
    });
  } catch(e) {
    // Windows doesn't support signals yet, so they simply don't get this handling.
    // https://github.com/joyent/node/issues/1553
  }

  util.puts("")
  util.debug("Running node-supervisor with");
  util.debug("  program '" + program.join(" ") + "'");
  util.debug("  --watch '" + watch + "'");
  util.debug("  --ignore '" + ignore + "'");
  util.debug("  --extensions '" + extensions + "'");
  util.debug("  --exec '" + executor + "'");
  util.puts("");

  // store the call to startProgramm in startChildProcess
  // in order to call it later
  startChildProcess = function() { startProgram(program, executor); };

  // if we have a program, then run it, and restart when it crashes.
  // if we have a watch folder, then watch the folder for changes and restart the prog
  startChildProcess();

  if (ignore) {
    var ignoreItems = ignore.split(',');
    ignoreItems.forEach(function (ignoreItem) {
      ignoreItem = path.resolve(ignoreItem);
      ignoredPaths[ignoreItem] = true;
      util.debug("Ignoring directory '" + ignoreItem + "'.");
    });
  }

  var watchItems = watch.split(',');
  watchItems.forEach(function (watchItem) {
    watchItem = path.resolve(watchItem);
    util.debug("Watching directory '" + watchItem + "' for changes.");
    findAllWatchFiles(watchItem, function(f) {
      watchGivenFile( f, poll_interval );
    });
  });
};

function print (m, n) { util.print(m+(!n?"\n":"")); return print; }

function help () {
  print
    ("")
    ("Node Supervisor is used to restart programs when they crash.")
    ("It can also be used to restart programs when a *.js file changes.")
    ("")
    ("Usage:")
    ("  supervisor [options] <program>")
    ("  supervisor [options] -- <program> [args ...]")
    ("")
    ("Required:")
    ("  <program>")
    ("    The program to run.")
    ("")
    ("Options:")
    ("  -w|--watch <watchItems>")
    ("    A comma-delimited list of folders or js files to watch for changes.")
    ("    When a change to a js file occurs, reload the program")
    ("    Default is '.'")
    ("")
    ("  -i|--ignore <ignoreItems>")
    ("    A comma-delimited list of folders to ignore for changes.")
    ("    No default")
    ("")
    ("  -p|--poll-interval <milliseconds>")
    ("    How often to poll watched files for changes.")
    ("    Defaults to Node default.")
    ("")
    ("  -e|--extensions <extensions>")
    ("    Specific file extensions to watch in addition to defaults.")
    ("    Used when --watch option includes folders")
    ("    Default is 'node|js'")
    ("")
    ("  -x|--exec <executable>")
    ("    The executable that runs the specified program.")
    ("    Default is 'node'")
    ("")
    ("  --debug")
    ("    Start node with --debug flag.")
    ("")
    ("  --debug-brk")
    ("    Start node with --debug-brk flag.")
    ("")
    ("  -n|--no-restart-on error|exit")
    ("    Don't automatically restart the supervised program if it ends.")
    ("    Supervisor will wait for a change in the source files.")
    ("    If \"error\", an exit code of 0 will still restart.")
    ("    If \"exit\", no restart regardless of exit code.")
    ("")
    ("  -h|--help|-?")
    ("    Display these usage instructions.")
    ("")
    ("  -q|--quiet")
    ("    Suppress DEBUG messages")
    ("")
    ("  -V|--verbose")
    ("    Show extra DEBUG messages")
    ("")
    ("Examples:")
    ("  supervisor myapp.js")
    ("  supervisor myapp.coffee")
    ("  supervisor -w scripts -e myext -x myrunner myapp")
    ("  supervisor -- server.js -h host -p port")
    ("");
};

function startProgram (prog, exec) {
  util.debug("Starting child process with '" + exec + " " + prog.join(" ") + "'");
  crash_queued = false;
  var child = exports.child = spawn(exec, prog, {stdio: 'inherit'});
  if (child.stdout) {
    // node < 0.8 doesn't understand the 'inherit' option, so pass through manually
    child.stdout.addListener("data", function (chunk) { chunk && util.print(chunk); });
    child.stderr.addListener("data", function (chunk) { chunk && util.debug(chunk); });
  }
  child.addListener("exit", function (code) {
    if (!crash_queued) {
      util.debug("Program " + exec + " " + prog.join(" ") + " exited with code " + code + "\n");
      exports.child = null;
      if (noRestartOn == "exit" || noRestartOn == "error" && code !== 0) return;
    }
    startProgram(prog, exec);
  });
}

var timer = null, mtime = null; crash_queued = false;
function crash () {

  if (crash_queued)
    return;

  crash_queued = true;
  var child = exports.child;
  setTimeout(function() {
    if (child) {
      util.debug("crashing child");
      process.kill(child.pid);
    } else {
      util.debug("restarting child");
      startChildProcess();
    }
  }, 50);
}

function crashWin (event, filename) {
  var shouldCrash = true;
  if( event === 'change' ) {
    if (filename) {
      filename = path.resolve(filename);
      Object.keys(ignoredPaths).forEach(function (ignorePath) {
        if ( filename.indexOf(ignorePath + '\\') === 0 ) {
          shouldCrash = false;
        }
      });
    }
    if (shouldCrash)
      crash();
  }
}

function crashOther (oldStat, newStat) {
  // we only care about modification time, not access time.
  if ( newStat.mtime.getTime() !== oldStat.mtime.getTime() )
    crash();
}

var nodeVersion = process.version.split(".");
var isWindowsWithoutWatchFile = process.platform === 'win32' && parseInt(nodeVersion[1]) <= 6;
function watchGivenFile (watch, poll_interval) {
  if (isWindowsWithoutWatchFile)
    fs.watch(watch, { persistent: true, interval: poll_interval }, crashWin);
  else
    fs.watchFile(watch, { persistent: true, interval: poll_interval }, crashOther);
  if (verbose)
    util.debug("watching file '" + watch + "'");
}

var findAllWatchFiles = function(dir, callback) {
  dir = path.resolve(dir);
  if (ignoredPaths[dir])
    return;
  fs.stat(dir, function(err, stats){
    if (err) {
      util.error('Error retrieving stats for file: ' + dir);
    } else {
      if (stats.isDirectory()) {
        if (isWindowsWithoutWatchFile) callback(dir);
        fs.readdir(dir, function(err, fileNames) {
          if(err) {
            util.error('Error reading path: ' + dir);
          }
          else {
            fileNames.forEach(function (fileName) {
              findAllWatchFiles(path.join(dir, fileName), callback);
            });
          }
        });
      } else {
        if (!isWindowsWithoutWatchFile && dir.match(fileExtensionPattern)) {
          callback(dir);
        }
      }
    }
  });
};
<|endoftext|>


<commit_before>/*
Note: the calls to the API are done in an asynchronious way

Structure of courseData object:

    $scope.courseData = [{
            faculty: 'Faculty of Engineering',
            subjects: [{
                subject: 'ECE',
                courses: [{course-object>}...]
            }, {
                subject: 'MEC E',
                courses: [{<course-object>},...]
            }]
     }];
*/

winstonApp.factory('courseDataMaker', ['courseFactory', '$window', 'currentTerm', 'localStorageService', 'courseCache', '$q', function(courseFactory, $window, currentTerm, localStorageService, courseCache, $q){

    function FacultyObject(faculty, subjects) {
        this.faculty = faculty;
        this.subjects = subjects;
    }

    function saveFacultyObjectToTree(facultyObject) {
        var inserted = false;

        // Case 1: Insert into already existing faculty
        factory.treeCourses.forEach(function(faculty) {
            if (faculty.faculty === facultyObject.faculty) {
                facultyObject.subjects.forEach(function(Ssubject) {
                    foundSubject = false;
                    faculty.subjects.forEach(function(subject) {
                        if (subject.subject === Ssubject.subject) {
                            foundSubject = true;
                            var courseLevelNumber = new RegExp('[0-9]+').exec(Ssubject.courses[0].asString).join();
                            if (courseLevelNumber < subject.courses[0].number) {
                                // concat at front
                                subject.courses = Ssubject.courses.concat(subject.courses);
                            } else {
                                // concat at end
                                subject.courses = subject.courses.concat(Ssubject.courses);
                            }
                        }
                    })
                    if (!foundSubject) {
                        faculty.subjects.push(Ssubject);
                    }
                });
                inserted = true;
            }
        });

        // Case 2: create new faculty and insert
        if (!inserted) {
            var s = [];
            facultyObject.subjects.forEach(function (subject) {
                s.push(subject);
            });
            factory.treeCourses.push(new FacultyObject(facultyObject.faculty, s));
        }
    }

    function savePage(pageListing) {
        pageListing.objects.forEach(function(facultyObject) {
            saveFacultyObjectToTree(facultyObject);
        });
    }

    var factory = {};

    factory.getFlattenedCourses = function() {
        return _.chain(factory.treeCourses)
            .map(function(faculty) {
                return _.map(faculty.subjects, function(subject) {
                    return _.map(subject.courses, function(course) {
                        course['number'] = new RegExp('[0-9]+').exec(course.asString).join();
                        return _.extend(
                            _.pick(faculty, 'faculty'),
                            _.pick(subject, 'subject', 'subjectTitle'),
                            _.pick(course, 'course', 'asString', 'courseTitle', 'number'));
                    });
                });
            })
            .flatten()
            .value();
    }

    factory.getFlattenedSubjects = function() {
        return _.chain(factory.treeCourses)
            .map(function(faculty) {
                return _.map(faculty.subjects, function(subject) {
                    return _.values(_.extend(
                        _.pick(subject, 'subject')));
                })
            })
            .flatten()
            .value();
    }

    factory.treeCourses = [];

    var total_pages = null;

    function getFirstPage() {
        return courseFactory.getCoursesPage(1, currentTerm.termId)
                    .success(function(data) {
                        var pageListing = angular.fromJson(data);
                        total_pages = pageListing.total_pages;
                        savePage(pageListing);
                    })
                    .error(function() {
                        $window.alert("Server not responding.  All we can say is, try again later.");
                    })
    }

    var total_pages = null;

    factory.coursesDataPromise = function() {
        return courseFactory.getCoursesPage(1, currentTerm.termId)
            .success(function(data) {
                var pageListing = angular.fromJson(data);
                total_pages = pageListing.total_pages;
                savePage(pageListing);
            })
            .error(function() {

            });
    }

    factory.getRemainingPromises = function() {
        page = 2;
        var promises = [];
        while (page <= total_pages) {
            var promise = courseFactory.getCoursesPage(page, currentTerm.termId)
                .success(function(data) {
                    savePage(angular.fromJson(data));
                })
                .error(function(data) {
                    $window.alert("Server not responding.  All we can say is, try again later.");
                })
            promises.push(promise);
            page = page + 1;
        }

        return $q.all(promises).then(function() {
            $window.alert('assigning');
            courseCache.data[currentTerm.termId] = factory.treeCourses;
            localStorageService.set('courseCache.data', courseCache.data);
            console.dir(courseCache.data);
        });
    }

	return factory;

}]);

<commit_msg>localstorage: remove some debugging messages<commit_after>/*
Note: the calls to the API are done in an asynchronious way

Structure of courseData object:

    $scope.courseData = [{
            faculty: 'Faculty of Engineering',
            subjects: [{
                subject: 'ECE',
                courses: [{course-object>}...]
            }, {
                subject: 'MEC E',
                courses: [{<course-object>},...]
            }]
     }];
*/

winstonApp.factory('courseDataMaker', ['courseFactory', '$window', 'currentTerm', 'localStorageService', 'courseCache', '$q', function(courseFactory, $window, currentTerm, localStorageService, courseCache, $q){

    function FacultyObject(faculty, subjects) {
        this.faculty = faculty;
        this.subjects = subjects;
    }

    function saveFacultyObjectToTree(facultyObject) {
        var inserted = false;

        // Case 1: Insert into already existing faculty
        factory.treeCourses.forEach(function(faculty) {
            if (faculty.faculty === facultyObject.faculty) {
                facultyObject.subjects.forEach(function(Ssubject) {
                    foundSubject = false;
                    faculty.subjects.forEach(function(subject) {
                        if (subject.subject === Ssubject.subject) {
                            foundSubject = true;
                            var courseLevelNumber = new RegExp('[0-9]+').exec(Ssubject.courses[0].asString).join();
                            if (courseLevelNumber < subject.courses[0].number) {
                                // concat at front
                                subject.courses = Ssubject.courses.concat(subject.courses);
                            } else {
                                // concat at end
                                subject.courses = subject.courses.concat(Ssubject.courses);
                            }
                        }
                    })
                    if (!foundSubject) {
                        faculty.subjects.push(Ssubject);
                    }
                });
                inserted = true;
            }
        });

        // Case 2: create new faculty and insert
        if (!inserted) {
            var s = [];
            facultyObject.subjects.forEach(function (subject) {
                s.push(subject);
            });
            factory.treeCourses.push(new FacultyObject(facultyObject.faculty, s));
        }
    }

    function savePage(pageListing) {
        pageListing.objects.forEach(function(facultyObject) {
            saveFacultyObjectToTree(facultyObject);
        });
    }

    var factory = {};

    factory.getFlattenedCourses = function() {
        return _.chain(factory.treeCourses)
            .map(function(faculty) {
                return _.map(faculty.subjects, function(subject) {
                    return _.map(subject.courses, function(course) {
                        course['number'] = new RegExp('[0-9]+').exec(course.asString).join();
                        return _.extend(
                            _.pick(faculty, 'faculty'),
                            _.pick(subject, 'subject', 'subjectTitle'),
                            _.pick(course, 'course', 'asString', 'courseTitle', 'number'));
                    });
                });
            })
            .flatten()
            .value();
    }

    factory.getFlattenedSubjects = function() {
        return _.chain(factory.treeCourses)
            .map(function(faculty) {
                return _.map(faculty.subjects, function(subject) {
                    return _.values(_.extend(
                        _.pick(subject, 'subject')));
                })
            })
            .flatten()
            .value();
    }

    factory.treeCourses = [];

    var total_pages = null;

    function getFirstPage() {
        return courseFactory.getCoursesPage(1, currentTerm.termId)
            .success(function(data) {
                var pageListing = angular.fromJson(data);
                total_pages = pageListing.total_pages;
                savePage(pageListing);
            })
            .error(function() {
                $window.alert("Server not responding.  All we can say is, try again later.");
            })
    }

    var total_pages = null;

    factory.coursesDataPromise = function() {
        return courseFactory.getCoursesPage(1, currentTerm.termId)
            .success(function(data) {
                var pageListing = angular.fromJson(data);
                total_pages = pageListing.total_pages;
                savePage(pageListing);
            })
            .error(function() {

            });
    }

    factory.getRemainingPromises = function() {
        page = 2;
        var promises = [];
        while (page <= total_pages) {
            var promise = courseFactory.getCoursesPage(page, currentTerm.termId)
                .success(function(data) {
                    savePage(angular.fromJson(data));
                })
                .error(function(data) {
                    $window.alert("Server not responding.  All we can say is, try again later.");
                })
            promises.push(promise);
            page = page + 1;
        }

        return $q.all(promises).then(function() {
            courseCache.data[currentTerm.termId] = factory.treeCourses;
            localStorageService.set('courseCache.data', courseCache.data);
        });
    }

	return factory;

}]);

<|endoftext|>