<commit_before>'use strict';

const log = require('intel').getLogger('sitespeedio.plugin.s3');
const s3 = require('s3');
const Promise = require('bluebird');
const throwIfMissing = require('../../support/util').throwIfMissing;
const fs = require('fs-extra');
const pick = require('lodash.pick');

Promise.promisifyAll(fs);

function createS3Client(s3Options) {
  const clientOptions = pick(s3Options,
    ['maxAsyncS3', 's3RetryCount', 's3RetryDelay', 'multipartUploadThreshold', 'multipartUploadSize']);

  clientOptions.s3Options = {
    accessKeyId: s3Options.key,
    secretAccessKey: s3Options.secret,
    region: s3Options.region
  };

  return s3.createClient(clientOptions);
}

module.exports = {
  open(context, options) {
    const s3Options = options.s3;

    throwIfMissing(s3Options, ['bucketname'], 's3');
    if (s3Options.key || s3Options.secret) {
      throwIfMissing(s3Options, ['key', 'secret'], 's3');
    }
    this.storageManager = context.storageManager;
  },

  postClose(options) {
    const s3Options = options.s3;
    const client = createS3Client(s3Options);

    const baseDir = this.storageManager.getBaseDir();

    const params = {
      localDir: baseDir,
      s3Params: {
        Bucket: s3Options.bucketname,
        Prefix: s3Options.path || this.storageManager.getStoragePrefix()
        // other options supported by putObject, except Body and ContentLength.
        // See: http://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/S3.html#putObject-property
      }
    };

    return new Promise((resolve, reject) => {
      log.info(`Uploading ${baseDir} to S3 bucket ${s3Options.bucketname}, this can take a while ...`);

      const uploader = client.uploadDir(params);

      uploader.on('error', err => {
        log.error('Could not upload to S3', err);
        reject(err);
      });
      uploader.on('end', () => {
        if (s3Options.removeLocalResult) {
          fs.removeAsync(baseDir)
            .then(() => {
              log.info(`Removed local files and directory ${baseDir}`);
              resolve();
            })
            .catch((e) => reject(e));
        } else {
          resolve();
        }
      });
    });
  }
};
<commit_msg>Added support for --s3.storageClass option for user to be able to specify the default storage class of the objects getting put into s3. Valid options are STANDARD | REDUCED_REDUNDANCY | STANDARD_IA (#1623)<commit_after>'use strict';

const log = require('intel').getLogger('sitespeedio.plugin.s3');
const s3 = require('s3');
const Promise = require('bluebird');
const throwIfMissing = require('../../support/util').throwIfMissing;
const fs = require('fs-extra');
const pick = require('lodash.pick');

Promise.promisifyAll(fs);

function createS3Client(s3Options) {
  const clientOptions = pick(s3Options,
    ['maxAsyncS3', 's3RetryCount', 's3RetryDelay', 'multipartUploadThreshold', 'multipartUploadSize']);

  clientOptions.s3Options = {
    accessKeyId: s3Options.key,
    secretAccessKey: s3Options.secret,
    region: s3Options.region
  };

  return s3.createClient(clientOptions);
}

module.exports = {
  open(context, options) {
    const s3Options = options.s3;

    throwIfMissing(s3Options, ['bucketname'], 's3');
    if (s3Options.key || s3Options.secret) {
      throwIfMissing(s3Options, ['key', 'secret'], 's3');
    }
    this.storageManager = context.storageManager;
  },

  postClose(options) {
    const s3Options = options.s3;
    const client = createS3Client(s3Options);

    const baseDir = this.storageManager.getBaseDir();

    const params = {
      localDir: baseDir,
      s3Params: {
        Bucket: s3Options.bucketname,
        Prefix: s3Options.path || this.storageManager.getStoragePrefix(),

        // Allow user to set default StorageClass on objects and default to STANDARD if not set
        // Possible options [STANDARD | REDUCED_REDUNDANCY | STANDARD_IA]
        StorageClass: s3Options.storageClass || 'STANDARD'

        // other options supported by putObject, except Body and ContentLength.
        // See: http://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/S3.html#putObject-property
      }
    };

    return new Promise((resolve, reject) => {
      log.info(`Uploading ${baseDir} to S3 bucket ${s3Options.bucketname}, this can take a while ...`);

      const uploader = client.uploadDir(params);

      uploader.on('error', err => {
        log.error('Could not upload to S3', err);
        reject(err);
      });
      uploader.on('end', () => {
        if (s3Options.removeLocalResult) {
          fs.removeAsync(baseDir)
            .then(() => {
              log.info(`Removed local files and directory ${baseDir}`);
              resolve();
            })
            .catch((e) => reject(e));
        } else {
          resolve();
        }
      });
    });
  }
};
<|endoftext|>


<commit_before>import PropTypes from 'prop-types';
import React from 'react';
import { injectIntl } from 'react-intl';
import { connect } from 'react-redux';
import { push } from 'react-router-redux';
import Select from '@material-ui/core/Select';
import MenuItem from '@material-ui/core/MenuItem';
import { fetchTopicSearchResults } from '../../../actions/topicActions';
import LoadingSpinner from '../../common/LoadingSpinner';
import { SearchButton } from '../../common/IconButton';
import { FETCH_ONGOING } from '../../../lib/fetchConstants';

const MAX_SUGGESTION_CHARS = 60;

const DELAY_BEFORE_SEARCH_MS = 500; // wait this long after a keypress to fire a search

const localMessages = {
  searchHint: { id: 'topics.search.hint', defaultMessage: 'Search by topic name' },
};

class TopicSearchContainer extends React.Component {

  constructor(props) {
    super(props);
    this.state = {
      lastSearchString: '',
      lastKeypress: 0,
      searchTimeout: null,
    };
  }

  handleClick = (item) => {
    const { onTopicSelected } = this.props;
    if (item) {
      if (onTopicSelected) {
        onTopicSelected(item);
      }
    }
  }

  fireSearchIfNeeded = () => {
    const { search } = this.props;
    const shouldSearch = this.state.lastSearchString.length > 3;
    if (shouldSearch) {
      search(this.state.lastSearchString);
    }
  }

  handleUpdateInput = (searchString) => {
    clearTimeout(this.state.searchTimeout); // cancel any pending searches
    this.setState({
      lastSearchString: searchString,
      searchTimeout: setTimeout(this.fireSearchIfNeeded, DELAY_BEFORE_SEARCH_MS),  // schedule a search for when they stop typing
    });
  }

  handleMenuItemKeyDown = (item, event) => {
    switch (event.key) {
      case 'Enter':
        this.handleClick(item);
        break;
      default: break;
    }
  }

  resetIfRequested = () => {
    const { topicResults } = this.props;
    let results = [];
    results = results.concat(topicResults);
    const resultsAsComponents = results.map(item => ({
      text: item.name,
      value: (
        <MenuItem
          onClick={() => this.handleClick(item)}
          onKeyDown={this.handleMenuItemKeyDown.bind(this, item)}
          primaryText={(item.name.length > MAX_SUGGESTION_CHARS) ? `${item.name.substr(0, MAX_SUGGESTION_CHARS)}...` : item.name}
        />
      ),
    }));
    return resultsAsComponents;
  }

  render() {
    const { fetchStatus } = this.props;
    const { formatMessage } = this.props.intl;
    // const resultsAsComponents = this.resetIfRequested();
    const isFetching = fetchStatus === FETCH_ONGOING;
    const fetchingStatus = (isFetching) ? <LoadingSpinner size={15} /> : null;
    return (

        <SearchButton />

{fetchingStatus}

        <Select
          label={formatMessage(localMessages.searchHint)}
          onClick={this.resetIfRequested}
          // TODO update this
          // options={topicResults}
          onChange={this.handleUpdateInput}
          // onNewRequest={this.handleNewRequest}
        />

    );
  }
}

TopicSearchContainer.propTypes = {
  // from context
  intl: PropTypes.object.isRequired,
  // from state
  fetchStatus: PropTypes.string.isRequired,
  topicResults: PropTypes.array.isRequired,
  // from dispatch
  onTopicSelected: PropTypes.func.isRequired,
  search: PropTypes.func.isRequired,
};

const mapStateToProps = state => ({
  fetchStatus: state.topics.search.fetchStatus,
  topicResults: state.topics.search.topics,
});

const mapDispatchToProps = dispatch => ({
  onTopicSelected: (item) => {
    dispatch(push(`/topics/${item.id}/summary`));
  },
  search: (searchString) => {
    dispatch(fetchTopicSearchResults(searchString));
  },
});

export default
  injectIntl(
    connect(mapStateToProps, mapDispatchToProps)(
      TopicSearchContainer
    )
  );
<commit_msg>#1234 autocomplete fix to TopicApp bar search component<commit_after>import PropTypes from 'prop-types';
import React from 'react';
import { injectIntl } from 'react-intl';
import { connect } from 'react-redux';
import { push } from 'react-router-redux';
import AutoComplete from 'material-ui/AutoComplete';
import MenuItem from '@material-ui/core/MenuItem';
import { fetchTopicSearchResults } from '../../../actions/topicActions';
import LoadingSpinner from '../../common/LoadingSpinner';
import { SearchButton } from '../../common/IconButton';
import { FETCH_ONGOING } from '../../../lib/fetchConstants';

const MAX_SUGGESTION_CHARS = 60;
const DEFAULT_MAX_TOPICS_TO_SHOW = 5;

const DELAY_BEFORE_SEARCH_MS = 500; // wait this long after a keypress to fire a search

const localMessages = {
  searchHint: { id: 'topics.search.hint', defaultMessage: 'Search by topic name' },
};

class TopicSearchContainer extends React.Component {

  constructor(props) {
    super(props);
    this.state = {
      lastSearchString: '',
      lastKeypress: 0,
      searchTimeout: null,
    };
  }

  getMaxTopicsToShow = () => {
    const { maxTopics } = this.props;
    return maxTopics || DEFAULT_MAX_TOPICS_TO_SHOW;
  }

  handleClick = (item) => {
    const { onTopicSelected } = this.props;
    if (item) {
      onTopicSelected(item.item);
    }
  }

  fireSearchIfNeeded = () => {
    const { search } = this.props;
    const shouldSearch = this.state.lastSearchString.length > 3;
    if (shouldSearch) {
      search(this.state.lastSearchString);
    }
  }

  handleUpdateInput = (searchString) => {
    clearTimeout(this.state.searchTimeout); // cancel any pending searches
    this.setState({
      lastSearchString: searchString,
      searchTimeout: setTimeout(this.fireSearchIfNeeded, DELAY_BEFORE_SEARCH_MS),  // schedule a search for when they stop typing
    });
  }

  handleMenuItemKeyDown = (item, event) => {
    switch (event.key) {
      case 'Enter':
        this.handleClick(item);
        break;
      default: break;
    }
  }

  handleNewRequest = (item, index) => {
    const { search } = this.props;
    if (index === -1) { // they pressed enter in the text field
      search(item.text);
      return;
    }
    // we want to send the user to the topic media url. The handleClick is no longer triggered in new/old material-ui setup
    this.handleClick(item);
  }

  resetIfRequested = () => {
    const { topicResults } = this.props;
    let results = [];
    results = results.concat(topicResults);
    const resultsAsComponents = results.map(item => ({
      text: item.name,
      value: (
        <MenuItem
          onClick={() => this.handleClick(item)}
          onKeyDown={this.handleMenuItemKeyDown.bind(this, item)}
          primaryText={(item.name.length > MAX_SUGGESTION_CHARS) ? `${item.name.substr(0, MAX_SUGGESTION_CHARS)}...` : item.name}
        />
      ),
      item,
    }));
    return resultsAsComponents;
  }

  render() {
    const { fetchStatus } = this.props;
    const { formatMessage } = this.props.intl;
    const resultsAsComponents = this.resetIfRequested();
    const isFetching = fetchStatus === FETCH_ONGOING;
    const fetchingStatus = (isFetching) ? <LoadingSpinner size={15} /> : null;
    return (

        <SearchButton />

{fetchingStatus}

        <AutoComplete
          label={formatMessage(localMessages.searchHint)}
          fullWidth
          openOnFocus
          searchText={this.state.lastSearchString}
          onClick={this.resetIfRequested}
          dataSource={resultsAsComponents}
          onUpdateInput={this.handleUpdateInput}
          onNewRequest={this.handleNewRequest}
          maxSearchResults={this.getMaxTopicsToShow()}
          filter={() => true}
        />

    );
  }
}

TopicSearchContainer.propTypes = {
  // from context
  intl: PropTypes.object.isRequired,
  // from state
  fetchStatus: PropTypes.string.isRequired,
  topicResults: PropTypes.array.isRequired,
  maxTopics: PropTypes.number,
  // from dispatch
  onTopicSelected: PropTypes.func.isRequired,
  search: PropTypes.func.isRequired,
};

const mapStateToProps = state => ({
  fetchStatus: state.topics.search.fetchStatus,
  topicResults: state.topics.search.topics,
});

const mapDispatchToProps = dispatch => ({
  onTopicSelected: (item) => {
    dispatch(push(`/topics/${item.id}/summary`));
  },
  search: (searchString) => {
    dispatch(fetchTopicSearchResults(searchString));
  },
});

export default
  injectIntl(
    connect(mapStateToProps, mapDispatchToProps)(
      TopicSearchContainer
    )
  );
<|endoftext|>


<commit_before>/**
 * Template Plugin : Includes templates associated with a given file when said file is imported.
 *
 * Config:
 *    root:
 *      templates:
 *        Key value hash mapping file names to arrays of templates to include
 *
 *        Special Values:
 *          auto-include: Key value pair mapping a regular expression key to a series of values
 *              to insert. Matching groups in the regular expression may be replaced using $i notation.
 *
 *              Example: 'js/views/(.*)\\.js': ['templates/$1.handlebars']
 *
 * Mixins:
 *  The template plugin will mixin auto-include mappings per item, giving priority to the local version.
 *  File mappings will be mixed in but are executed within the scope of the mixin only. I.e. foo.js
 *  in the local file will not match file mappings for foo.js in a mixin.
 *
 */
var _ = require('underscore'),
    build = require('../build'),
    path = require('path');

module.exports = {
  mode: 'scripts',
  priority: 50,

  loadMixin: function(context, next, complete) {
    var mixinTemplates = context.loadedMixin.templates;
    if (mixinTemplates) {
      var templates = context.mixins.originalConfig.templates || {},
          configTemplates = _.clone(context.config.attributes.templates || templates),
          assigned = false;

      if (context.mixins.mergeHash('auto-include', templates, mixinTemplates, configTemplates)) {
        assigned = true;
      }

      if (assigned) {
        context.config.attributes.templates = configTemplates;
      }
    }
    next(complete);
  },

  resourceList: function(context, next, complete) {
    var mixin = context.resource.mixin,
        attr = (mixin && mixin.parent) || context.config.attributes;

    next(function(err, ret) {
      if (err || !ret) {
        return complete(err);
      }

      function pushTemplates(templates) {
        _.each(templates, function(template) {
          ret.push({
            src: context.mixins.resolvePath(template, context.resource.mixin),
            name: template,
            template: true
          });
        });
      }

      var views = attr.templates || attr.views || {},
          resource = context.resource.originalSrc || context.resource.src || context.resource,
          mixinRoot = (context.resource.mixin && context.resource.mixin.root) || '';
      if (_.isString(resource) && resource.indexOf(mixinRoot) === 0) {
        resource = resource.substring(mixinRoot.length);
      }

      var deferComplete;
      if (build.filterResource(context.resource, context)) {
        pushTemplates(views[resource]);

        if (views['auto-include']) {
          var config = context.configCache['template-auto-include'];
          if (!config) {
            config = module.exports.generateMappings(views['auto-include']);
            context.configCache['template-auto-include'] = config;
          }

          var autoIncludes = [];
          _.each(config, function(mapping) {
            var remap = module.exports.remapFile(mapping, resource);
            if (remap) {
              autoIncludes.push.apply(autoIncludes, remap);
            }
          });
          if (autoIncludes.length) {
            deferComplete = true;

            context.fileUtil.fileList(autoIncludes, function(err, autoIncludes) {
              if (err) {
                return complete(err);
              }

              var watchDirs = [];
              autoIncludes = _.filter(autoIncludes, function(file) {
                if (file.enoent) {
                  watchDirs.push({watch: path.dirname(file.src)});
                } else {
                  return true;
                }
              });
              autoIncludes = _.map(autoIncludes, function(include) { return include.src || include; });

              pushTemplates(autoIncludes);
              ret.push.apply(ret, watchDirs);

              complete(undefined, ret);
            });
          }
        }
      }
      if (!deferComplete) {
        complete(undefined, ret);
      }
    });
  },

  resource: function(context, next, complete) {
    var resource = context.resource;

    if (resource.watch) {
      function generator(buildContext, callback) {
        // Ensure that the directory actually exists
        var path = context.fileUtil.resolvePath(resource.watch);
        context.fileUtil.stat(path, function(err, stat) {
          // Ignore any errors here
          var inputs = [];
          if (stat && stat.isDirectory()) {
            inputs.push(path);
          }
          callback(undefined, {inputs: inputs, data: '', noSeparator: true});
        });
      }
      complete(undefined, generator);
    } else {
      next(complete);
    }
  },

  generateMappings: function(autoInclude) {
    return _.map(autoInclude, function(templates, source) {
      if (!_.isArray(templates)) {
        templates = [templates];
      }
      return {regex: new RegExp(source), templates: templates};
    });
  },
  remapFile: function(mapping, resource) {
    var match;
    if (match = mapping.regex.exec(resource)) {
      return _.map(mapping.templates, function(template) {
        // Work in reverse so $10 takes priority over $1
        var i = match.length;
        while (i--) {
          template = template.replace('$' + i, match[i]);
        }
        return template;
      });
    }
  }
};
<commit_msg>Include mixin info with templates<commit_after>/**
 * Template Plugin : Includes templates associated with a given file when said file is imported.
 *
 * Config:
 *    root:
 *      templates:
 *        Key value hash mapping file names to arrays of templates to include
 *
 *        Special Values:
 *          auto-include: Key value pair mapping a regular expression key to a series of values
 *              to insert. Matching groups in the regular expression may be replaced using $i notation.
 *
 *              Example: 'js/views/(.*)\\.js': ['templates/$1.handlebars']
 *
 * Mixins:
 *  The template plugin will mixin auto-include mappings per item, giving priority to the local version.
 *  File mappings will be mixed in but are executed within the scope of the mixin only. I.e. foo.js
 *  in the local file will not match file mappings for foo.js in a mixin.
 *
 */
var _ = require('underscore'),
    build = require('../build'),
    path = require('path');

module.exports = {
  mode: 'scripts',
  priority: 50,

  loadMixin: function(context, next, complete) {
    var mixinTemplates = context.loadedMixin.templates;
    if (mixinTemplates) {
      var templates = context.mixins.originalConfig.templates || {},
          configTemplates = _.clone(context.config.attributes.templates || templates),
          assigned = false;

      if (context.mixins.mergeHash('auto-include', templates, mixinTemplates, configTemplates)) {
        assigned = true;
      }

      if (assigned) {
        context.config.attributes.templates = configTemplates;
      }
    }
    next(complete);
  },

  resourceList: function(context, next, complete) {
    var mixin = context.resource.mixin,
        attr = (mixin && mixin.parent) || context.config.attributes;

    next(function(err, ret) {
      if (err || !ret) {
        return complete(err);
      }

      function pushTemplates(templates) {
        _.each(templates, function(template) {
          ret.push({
            src: context.mixins.resolvePath(template, context.resource.mixin),
            name: template,
            mixin: mixin,
            template: true
          });
        });
      }

      var views = attr.templates || attr.views || {},
          resource = context.resource.originalSrc || context.resource.src || context.resource,
          mixinRoot = (context.resource.mixin && context.resource.mixin.root) || '';
      if (_.isString(resource) && resource.indexOf(mixinRoot) === 0) {
        resource = resource.substring(mixinRoot.length);
      }

      var deferComplete;
      if (build.filterResource(context.resource, context)) {
        pushTemplates(views[resource]);

        if (views['auto-include']) {
          var config = context.configCache['template-auto-include'];
          if (!config) {
            config = module.exports.generateMappings(views['auto-include']);
            context.configCache['template-auto-include'] = config;
          }

          var autoIncludes = [];
          _.each(config, function(mapping) {
            var remap = module.exports.remapFile(mapping, resource);
            if (remap) {
              autoIncludes.push.apply(autoIncludes, remap);
            }
          });
          if (autoIncludes.length) {
            deferComplete = true;

            context.fileUtil.fileList(autoIncludes, function(err, autoIncludes) {
              if (err) {
                return complete(err);
              }

              var watchDirs = [];
              autoIncludes = _.filter(autoIncludes, function(file) {
                if (file.enoent) {
                  watchDirs.push({watch: path.dirname(file.src)});
                } else {
                  return true;
                }
              });
              autoIncludes = _.map(autoIncludes, function(include) { return include.src || include; });

              pushTemplates(autoIncludes);
              ret.push.apply(ret, watchDirs);

              complete(undefined, ret);
            });
          }
        }
      }
      if (!deferComplete) {
        complete(undefined, ret);
      }
    });
  },

  resource: function(context, next, complete) {
    var resource = context.resource;

    if (resource.watch) {
      function generator(buildContext, callback) {
        // Ensure that the directory actually exists
        var path = context.fileUtil.resolvePath(resource.watch);
        context.fileUtil.stat(path, function(err, stat) {
          // Ignore any errors here
          var inputs = [];
          if (stat && stat.isDirectory()) {
            inputs.push(path);
          }
          callback(undefined, {inputs: inputs, data: '', noSeparator: true});
        });
      }
      complete(undefined, generator);
    } else {
      next(complete);
    }
  },

  generateMappings: function(autoInclude) {
    return _.map(autoInclude, function(templates, source) {
      if (!_.isArray(templates)) {
        templates = [templates];
      }
      return {regex: new RegExp(source), templates: templates};
    });
  },
  remapFile: function(mapping, resource) {
    var match;
    if (match = mapping.regex.exec(resource)) {
      return _.map(mapping.templates, function(template) {
        // Work in reverse so $10 takes priority over $1
        var i = match.length;
        while (i--) {
          template = template.replace('$' + i, match[i]);
        }
        return template;
      });
    }
  }
};
<|endoftext|>


<commit_before>/**
 * `password` type prompt
 */

var util = require('util');
var chalk = require('chalk');
var Base = require('./base');
var observe = require('../utils/events');

function mask(input) {
  input = String(input);
  if (input.length === 0) {
    return '';
  }

  return new Array(input.length + 1).join('*');
}

/**
 * Module exports
 */

module.exports = Prompt;

/**
 * Constructor
 */

function Prompt() {
  return Base.apply(this, arguments);
}
util.inherits(Prompt, Base);

/**
 * Start the Inquiry session
 * @param  {Function} cb      Callback when prompt is done
 * @return {this}
 */

Prompt.prototype._run = function (cb) {
  this.done = cb;

  var events = observe(this.rl);

  // Once user confirm (enter key)
  var submit = events.line.map(this.filterInput.bind(this));

  var validation = this.handleSubmitEvents(submit);
  validation.success.forEach(this.onEnd.bind(this));
  validation.error.forEach(this.onError.bind(this));

  events.keypress.takeUntil(validation.success).forEach(this.onKeypress.bind(this));

  // Init
  this.render();

  return this;
};

/**
 * Render the prompt to screen
 * @return {Prompt} self
 */

Prompt.prototype.render = function (error) {
  var message = this.getQuestion();
  var bottomContent = '';

  if (this.status === 'answered') {
    message += chalk.cyan(mask(this.answer));
  } else {
    message += mask(this.rl.line || '');
  }

  if (error) {
    bottomContent = '\n' + chalk.red('>> ') + error;
  }

  this.screen.render(message, bottomContent);
};

/**
 * When user press `enter` key
 */

Prompt.prototype.filterInput = function (input) {
  if (!input) {
    return this.opt.default == null ? '' : this.opt.default;
  }
  return input;
};

Prompt.prototype.onEnd = function (state) {
  this.status = 'answered';
  this.answer = state.value;

  // Re-render prompt
  this.render();

  this.screen.done();
  this.done(state.value);
};

Prompt.prototype.onError = function (state) {
  this.render(state.isValid);
  this.rl.output.unmute();
};

/**
 * When user type
 */

Prompt.prototype.onKeypress = function () {
  this.render();
};
<commit_msg>Password prompt should display no characters<commit_after>/**
 * `password` type prompt
 */

var util = require('util');
var chalk = require('chalk');
var Base = require('./base');
var observe = require('../utils/events');

/**
 * Module exports
 */

module.exports = Prompt;

/**
 * Constructor
 */

function Prompt() {
  return Base.apply(this, arguments);
}
util.inherits(Prompt, Base);

/**
 * Start the Inquiry session
 * @param  {Function} cb      Callback when prompt is done
 * @return {this}
 */

Prompt.prototype._run = function (cb) {
  this.done = cb;

  var events = observe(this.rl);

  // Once user confirm (enter key)
  var submit = events.line.map(this.filterInput.bind(this));

  var validation = this.handleSubmitEvents(submit);
  validation.success.forEach(this.onEnd.bind(this));
  validation.error.forEach(this.onError.bind(this));

  // Init
  this.render();

  return this;
};

/**
 * Render the prompt to screen
 * @return {Prompt} self
 */

Prompt.prototype.render = function (error) {
  var message = this.getQuestion();
  var bottomContent = '';

  if (this.status === 'answered') {
    message += chalk.cyan('[hidden]');
  }

  if (error) {
    bottomContent = '\n' + chalk.red('>> ') + error;
  }

  this.screen.render(message, bottomContent);
};

/**
 * When user press `enter` key
 */

Prompt.prototype.filterInput = function (input) {
  if (!input) {
    return this.opt.default == null ? '' : this.opt.default;
  }
  return input;
};

Prompt.prototype.onEnd = function (state) {
  this.status = 'answered';
  this.answer = state.value;

  // Re-render prompt
  this.render();

  this.screen.done();
  this.done(state.value);
};

Prompt.prototype.onError = function (state) {
  this.render(state.isValid);
  this.rl.output.unmute();
};
<|endoftext|>


<commit_before>/*
 *
 * Copyright (c) 2009, Felix Bruns <felixbruns@web.de>
 *
 */

function Cache(name){
	/* Set default values for parameters. */
	this.name = name || 'default';

	/* Create cache if it doesn't exist. */
	if(typeof(localStorage[this.name]) == 'undefined'){
		localStorage[this.name] = '{}';
	}
}

/* Clear. */
Cache.prototype.clear = function(){
	localStorage[this.name] = '{}';
};

/* Contains. */
Cache.prototype.contains = function(hash){
	var cache = JSON.parse(localStorage[this.name]);

	return typeof(cache[hash]) != 'undefined';
};

/* Load. */
Cache.prototype.load = function(hash){
	var cache = JSON.parse(localStorage[this.name]);

	return cache[hash];
};

/* Load all. */
Cache.prototype.loadAll = function(){
	return JSON.parse(localStorage[this.name]);
};

/* Remove. */
Cache.prototype.remove = function(hash){
	var cache = JSON.parse(localStorage[this.name]);

	delete cache[hash];

	localStorage[this.name] = JSON.stringify(cache);
};

/* Store. */
Cache.prototype.store = function(hash, data){
	var cache = JSON.parse(localStorage[this.name]);

	cache[hash] = data;

	localStorage[this.name] = JSON.stringify(cache);
};
<commit_msg>Fix bug in gateway cache.js<commit_after>/*
 *
 * Copyright (c) 2009, Felix Bruns <felixbruns@web.de>
 *
 */

function Cache(name){
	/* Set default values for parameters. */
	this.name = name || 'default';

	/* Create cache if it doesn't exist. */
	if(typeof(localStorage[this.name]) == 'undefined' || localStorage[this.name] == null){
		localStorage[this.name] = '{}';
	}
}

/* Clear. */
Cache.prototype.clear = function(){
	localStorage[this.name] = '{}';
};

/* Contains. */
Cache.prototype.contains = function(hash){
	var cache = JSON.parse(localStorage[this.name]);

	return typeof(cache[hash]) != 'undefined';
};

/* Load. */
Cache.prototype.load = function(hash){
	var cache = JSON.parse(localStorage[this.name]);

	return cache[hash];
};

/* Load all. */
Cache.prototype.loadAll = function(){
	return JSON.parse(localStorage[this.name]);
};

/* Remove. */
Cache.prototype.remove = function(hash){
	var cache = JSON.parse(localStorage[this.name]);

	delete cache[hash];

	localStorage[this.name] = JSON.stringify(cache);
};

/* Store. */
Cache.prototype.store = function(hash, data){
	var cache = JSON.parse(localStorage[this.name]);

	cache[hash] = data;

	localStorage[this.name] = JSON.stringify(cache);
};
<|endoftext|>


<commit_before>// open the split
var splitH = Split(['#aside', '#main'], {
  sizes: [20, 80],
  gutterSize: 3,
});
var splitV = Split(['#body', '#footer'], {
  sizes: [70, 30],
  direction: 'vertical',
  gutterSize: 3,
});

var form = document.getElementById("qform");
/*
// fill form
var url = new URL(window.location.href);
var q = url.searchParams.get("q");
form['q'].value = q;
if (q) {
  form.submit();
  dispatch(form);
}
*/

var tabs = document.getElementById('tabs').getElementsByTagName('a');
for (var i = 0; i < tabs.length; i++) {
  var name = url4name(tabs[i]);
  // do not change target her, desk.jsp knows better where to send link
  tabs[i].id = name;
  tabs[i].className = name;
  tabs[i].onclick = function(e) {
    form.action = this.href;
    form.submit();
    return false;
  }
}

var chrono = document.getElementById("chrono");
var panel = document.getElementById("panel");
function dispatch(form)
{
  var q = form['q'].value;
  console.log(q);
  parTop("q", q); // update URL
  // get frame as a window object
  if (chrono.offsetHeight > 10 && chrono.offsetWidth > 10) {
    chrono.src = "chrono.jsp?q=" + q;
  }
  if (panel.offsetHeight > 10 && panel.offsetWidth > 10) {
    var url = new URL(panel.src);
    panel.src = url.pathname+"?q="+q;
  }
  return true;
}
<commit_msg>target bug on compare tab<commit_after>// open the split
var splitH = Split(['#aside', '#main'], {
  sizes: [20, 80],
  gutterSize: 3,
});
var splitV = Split(['#body', '#footer'], {
  sizes: [70, 30],
  direction: 'vertical',
  gutterSize: 3,
});

var form = document.getElementById("qform");
/*
// fill form
var url = new URL(window.location.href);
var q = url.searchParams.get("q");
form['q'].value = q;
if (q) {
  form.submit();
  dispatch(form);
}
*/

var tabs = document.getElementById('tabs').getElementsByTagName('a');
for (var i = 0; i < tabs.length; i++) {
  var name = url4name(tabs[i]);
  tabs[i].id = name;
  tabs[i].className = name;
  // do not add an event to a tab that will not go in frame
  if (!tabs[i].target) continue;
  tabs[i].onclick = function(e) {
    form.action = this.href;
    form.submit();
    return false;
  }
}

var chrono = document.getElementById("chrono");
var panel = document.getElementById("panel");
function dispatch(form)
{
  var q = form['q'].value;
  console.log(q);
  parTop("q", q); // update URL
  // get frame as a window object
  if (chrono.offsetHeight > 10 && chrono.offsetWidth > 10) {
    chrono.src = "chrono.jsp?q=" + q;
  }
  if (panel.offsetHeight > 10 && panel.offsetWidth > 10) {
    var url = new URL(panel.src);
    panel.src = url.pathname+"?q="+q;
  }
  return true;
}
<|endoftext|>


<commit_before>setInterval(function(){
   var numdevices = 5;
   $.ajax({
      url: "/poll",
      global: false,
      type: "POST",
      cache: false,
      success: function(response){
         console.log(response);
      }});
},3000);
<commit_msg>web: finished poller<commit_after>setInterval(function(){
   var numdevices = 5;
   var state;
   $.ajax({
      url: "/poll",
      global: false,
      type: "POST",
      cache: false,
      success: function(response){
         for (var key in response) {
            if (response.hasOwnProperty(key)) {
               switch_button = $("li[lid='" + key + "']").find('input');
               if (response[key] == '0'){
                  state = false;
               }else if (response[key] == '1'){
                  state = true;
               }
               console.log(state);
               switch_button.bootstrapSwitch('state',state);
               state = null;
            }
         }
      }});
},3000);
<|endoftext|>


<commit_before>/*!
 * Nodeunit
 * Copyright (c) 2010 Caolan McMahon
 * MIT Licensed
 */

/**
 * Module dependencies
 */

var nodeunit = require('../nodeunit'),
    utils = require('../utils'),
    fs = require('fs'),
    track = require('../track'),
    path = require('path'),
    AssertionError = require('../assert').AssertionError;

/**
 * Reporter info string
 */

exports.info = "Nested test reporter";

/**
 * Run all tests within each module, reporting the results to the command-line.
 *
 * @param {Array} files
 * @api public
 */

exports.run = function (files, options) {

    if (!options) {
        // load default options
        var content = fs.readFileSync(
            __dirname + '/../../bin/nodeunit.json',
            'utf8'
        );
        options = JSON.parse(content);
    }

    var error = function (str) {
        return options.error_prefix + str + options.error_suffix;
    };
    var ok    = function (str) {
        return options.ok_prefix + str + options.ok_suffix;
    };
    var bold  = function (str) {
        return options.bold_prefix + str + options.bold_suffix;
    };
    var assertion_message = function (str) {
        return options.assertion_prefix + str + options.assertion_suffix;
    };

    var start = new Date().getTime();
    var paths = files.map(function (p) {
        return path.join(process.cwd(), p);
    });
    var tracker = track.createTracker(function (tracker) {
        var i, names;
        if (tracker.unfinished()) {
            console.log('');
            console.log(error(bold(
                'FAILURES: Undone tests (or their setups/teardowns): '
            )));
            names = tracker.names();
            for (i = 0; i < names.length; i += 1) {
                console.log('- ' + names[i]);
            }
            console.log('');
            console.log('To fix this, make sure all tests call test.done()');
            process.reallyExit(tracker.unfinished());
        }
    });

    // Object to hold status of each 'part' of the testCase/name array,
    // i.e., whether this part has been printed yet.
    tracker.already_printed = {};

    var print_status = function (status, name_arr) {
        var txt   = '';
        var space = "    ";
        var part, i, k;
        for (i = 0; i < name_arr.length; i++) {
            part = name_arr.slice(0, i + 1).join(",");
            // Only print the part if it hasn't already been printed.
            if (!tracker.already_printed[part]) {
                // Indent once for each nested level
                for (k = 0; k < i; k++) {
                    txt += space;
                }
                if (i === name_arr.length - 1) {
                    // Print the actual test name in bold/ok green.
                    if (status === 'pass') {
                        txt += bold(ok(name_arr[i] + " (pass)"));
                    } else {
                        txt += bold(error(name_arr[i] + " (fail) ✖ "));
                    }
                } else {
                    // This is a testCase, just print the testCase name
                    // and a newline.
                    txt += name_arr[i];
                    txt += "\n";
                }
                tracker.already_printed[part] = true;
            }
        }
        console.log(txt);
    };

    nodeunit.runFiles(paths, {
        testspec: options.testspec,
        moduleStart: function (name) {
            console.log('\n' + bold(name));
        },
        testDone: function (name, assertions) {
            tracker.remove(name);

            if (!assertions.failures()) {
                print_status('pass', name);
            } else {
                print_status('fail', name);
                assertions.forEach(function (a) {
                    if (a.failed()) {
                        a = utils.betterErrors(a);
                        if (a.error instanceof AssertionError && a.message) {
                            console.log(
                                'Assertion Message: ' +
                                    assertion_message(a.message)
                            );
                        }
                        console.log(a.error.stack + '\n');
                    }
                });
            }
        },
        done: function (assertions, end) {
            end = end || new Date().getTime();
            var duration = end - start;
            if (assertions.failures()) {
                console.log(
                    '\n' + bold(error('FAILURES: ')) + assertions.failures() +
                        '/' + assertions.length + ' assertions failed (' +
                        assertions.duration + 'ms)'
                );
            } else {
                console.log(
                    '\n' + bold(ok('OK: ')) + assertions.length +
                        ' assertions (' + assertions.duration + 'ms)'
                );
            }
        },
        testStart: function (name) {
            tracker.put(name);
        }
    });
};
<commit_msg>refactoring nested reporter<commit_after>/*!
 * Nodeunit
 * Copyright (c) 2010 Caolan McMahon
 * MIT Licensed
 */

/**
 * Module dependencies
 */

var nodeunit = require('../nodeunit'),
    utils = require('../utils'),
    fs = require('fs'),
    track = require('../track'),
    path = require('path'),
    AssertionError = require('../assert').AssertionError;

/**
 * Reporter info string
 */

exports.info = "Nested test reporter";

/**
 * Run all tests within each module, reporting the results to the command-line.
 *
 * @param {Array} files
 * @api public
 */

exports.run = function (files, options) {

    if (!options) {
        // load default options
        var content = fs.readFileSync(
            __dirname + '/../../bin/nodeunit.json',
            'utf8'
        );
        options = JSON.parse(content);
    }

    var error = function (str) {
        return options.error_prefix + str + options.error_suffix;
    };
    var ok    = function (str) {
        return options.ok_prefix + str + options.ok_suffix;
    };
    var bold  = function (str) {
        return options.bold_prefix + str + options.bold_suffix;
    };
    var assertion_message = function (str) {
        return options.assertion_prefix + str + options.assertion_suffix;
    };

    var start = new Date().getTime();
    var paths = files.map(function (p) {
        return path.join(process.cwd(), p);
    });
    var tracker = track.createTracker(function (tracker) {
        var i, names;
        if (tracker.unfinished()) {
            console.log('');
            console.log(error(bold(
                'FAILURES: Undone tests (or their setups/teardowns): '
            )));
            names = tracker.names();
            for (i = 0; i < names.length; i += 1) {
                console.log('- ' + names[i]);
            }
            console.log('');
            console.log('To fix this, make sure all tests call test.done()');
            process.reallyExit(tracker.unfinished());
        }
    });

    // Object to hold status of each 'part' of the testCase/name array,
    // i.e., whether this part has been printed yet.
    tracker.already_printed = {};

    var pass_text = function(txt) {
        // Print in bold green.
        return bold(ok(txt + " (pass)"));
    };

    var fail_text = function(txt) {
        return bold(error(txt + " (fail) ✖ "));
    };

    var status_text = function(status, txt) {
        if (status === 'pass') {
            return pass_text(txt);
        } else {
            return fail_text(txt);
        }
    };

    var print_status = function (name_arr, status) {
        var txt   = '';
        var space = "    ";
        var part, i, k;
        for (i = 0; i < name_arr.length; i++) {
            part = name_arr.slice(0, i + 1).join(",");
            // Only print the part if it hasn't already been printed.
            if (!tracker.already_printed[part]) {
                // Indent once for each nested level
                for (k = 0; k < i; k++) {
                    txt += space;
                }
                if (i === name_arr.length - 1) {
                    txt += status_text(status, name_arr[i]);
                } else {
                    // This is a testCase, just print the testCase name
                    // and a newline.
                    txt += name_arr[i];
                    txt += "\n";
                }
                tracker.already_printed[part] = true;
            }
        }
        console.log(txt);
    };

    nodeunit.runFiles(paths, {
        testspec: options.testspec,
        moduleStart: function (name) {
            console.log('\n' + bold(name));
        },
        testDone: function (name, assertions) {
            tracker.remove(name);

            if (!assertions.failures()) {
                print_status(name, 'pass');
            } else {
                print_status(name, 'fail');
                assertions.forEach(function (a) {
                    if (a.failed()) {
                        a = utils.betterErrors(a);
                        if (a.error instanceof AssertionError && a.message) {
                            console.log(
                                'Assertion Message: ' +
                                    assertion_message(a.message)
                            );
                        }
                        console.log(a.error.stack + '\n');
                    }
                });
            }
        },
        done: function (assertions, end) {
            end = end || new Date().getTime();
            var duration = end - start;
            if (assertions.failures()) {
                console.log(
                    '\n' + bold(error('FAILURES: ')) + assertions.failures() +
                        '/' + assertions.length + ' assertions failed (' +
                        assertions.duration + 'ms)'
                );
            } else {
                console.log(
                    '\n' + bold(ok('OK: ')) + assertions.length +
                        ' assertions (' + assertions.duration + 'ms)'
                );
            }
        },
        testStart: function (name) {
            tracker.put(name);
        }
    });
};
<|endoftext|>


<commit_before>const { WEBPACK_WEB_PORT, LBRY_WEB_API } = require('../config.js');
const path = require('path');
const fs = require('fs');
const merge = require('webpack-merge');
const baseConfig = require('../webpack.base.config.js');
const CopyWebpackPlugin = require('copy-webpack-plugin');
const WriteFilePlugin = require('write-file-webpack-plugin');
const { DefinePlugin, ProvidePlugin } = require('webpack');
const SentryWebpackPlugin = require('@sentry/webpack-plugin');
const { getJsBundleId } = require('./bundle-id.js');
const { insertToHead, buildHead } = require('./src/html');
const { insertVariableXml, getOpenSearchXml } = require('./src/xml');

const CUSTOM_ROOT = path.resolve(__dirname, '../custom/');
const STATIC_ROOT = path.resolve(__dirname, '../static/');
const UI_ROOT = path.resolve(__dirname, '../ui/');
const DIST_ROOT = path.resolve(__dirname, 'dist/');
const WEB_STATIC_ROOT = path.resolve(__dirname, 'static/');
const WEB_PLATFORM_ROOT = __dirname;
const isProduction = process.env.NODE_ENV === 'production';
const hasSentryToken = process.env.SENTRY_AUTH_TOKEN !== undefined;
const jsBundleId = getJsBundleId();

// copy static files to dist folder
const copyWebpackCommands = [
  {
    from: `${STATIC_ROOT}/index-web.html`,
    to: `${DIST_ROOT}/index.html`,
    // add javascript script to index.html, generate/insert metatags
    transform(content, path) {
      return insertToHead(content.toString(), buildHead());
    },
    force: true,
  },
  {
    from: `${STATIC_ROOT}/opensearch.xml`,
    to: `${DIST_ROOT}/opensearch.xml`,
    transform(content, path) {
      return insertVariableXml(content.toString(), getOpenSearchXml());
    },
    force: true,
  },
  {
    from: `${STATIC_ROOT}/robots.txt`,
    to: `${DIST_ROOT}/robots.txt`,
    force: true,
  },
  {
    from: `${STATIC_ROOT}/img/favicon.png`,
    to: `${DIST_ROOT}/public/favicon.png`,
    force: true,
  },
  {
    from: `${STATIC_ROOT}/img/v2-og.png`,
    to: `${DIST_ROOT}/public/v2-og.png`,
  },
  {
    from: `${STATIC_ROOT}/font/`,
    to: `${DIST_ROOT}/public/font/`,
  },
  {
    from: `${WEB_STATIC_ROOT}/pwa/`,
    to: `${DIST_ROOT}/public/pwa/`,
  },
  {
    from: `${WEB_STATIC_ROOT}/pwa/serviceWorker.js`,
    to: `${DIST_ROOT}/`,
  },
];

const CUSTOM_OG_PATH = `${CUSTOM_ROOT}/v2-og.png`;
if (fs.existsSync(CUSTOM_OG_PATH)) {
  copyWebpackCommands.push({
    from: CUSTOM_OG_PATH,
    to: `${DIST_ROOT}/public/v2-og.png`,
    force: true,
  });
}

const ROBOTS_TXT_PATH = `${CUSTOM_ROOT}/robots.txt`;
if (fs.existsSync(ROBOTS_TXT_PATH)) {
  copyWebpackCommands.push({
    from: ROBOTS_TXT_PATH,
    to: `${DIST_ROOT}/robots.txt`,
    force: true,
  });
}

let plugins = [
  new WriteFilePlugin(),
  new CopyWebpackPlugin(copyWebpackCommands),
  new DefinePlugin({
    IS_WEB: JSON.stringify(true),
    'process.env.SDK_API_URL': JSON.stringify(process.env.SDK_API_URL || LBRY_WEB_API),
  }),
  new ProvidePlugin({
    __: ['i18n.js', '__'],
  }),
];

if (isProduction && hasSentryToken) {
  plugins.push(
    new SentryWebpackPlugin({
      include: './dist',
      ignoreFile: '.sentrycliignore',
      ignore: ['node_modules', 'webpack.config.js'],
      configFile: 'sentry.properties',
    })
  );
}

const webConfig = {
  target: 'web',
  entry: {
    [`ui-${jsBundleId}`]: '../ui/index.jsx',
  },
  output: {
    filename: '[name].js',
    path: path.join(__dirname, 'dist/public/'),
    publicPath: '/public/',
    chunkFilename: '[name]-[chunkhash].js',
  },
  devServer: {
    port: WEBPACK_WEB_PORT,
    contentBase: path.join(__dirname, 'dist'),
    disableHostCheck: true, // to allow debugging with ngrok
  },
  module: {
    rules: [
      {
        loader: 'babel-loader',
        test: /\.jsx?$/,
        options: {
          presets: ['@babel/env', '@babel/react', '@babel/flow'],
          plugins: ['@babel/plugin-proposal-object-rest-spread', '@babel/plugin-proposal-class-properties'],
        },
      },
      {
        loader: 'preprocess-loader',
        test: /\.jsx?$/,
        exclude: /node_modules/,
        options: {
          TARGET: 'web',
          ppOptions: {
            type: 'js',
          },
        },
      },
    ],
  },
  resolve: {
    modules: [UI_ROOT, __dirname],

    alias: {
      lbryinc: 'lbryinc/dist/bundle.es.js',
      electron: `${WEB_PLATFORM_ROOT}/stubs/electron.js`,
      fs: `${WEB_PLATFORM_ROOT}/stubs/fs.js`,
    },
  },
  plugins,
};

module.exports = merge(baseConfig, webConfig);
<commit_msg>clear stale js files on nonprod environments<commit_after>const { WEBPACK_WEB_PORT, LBRY_WEB_API } = require('../config.js');
const path = require('path');
const fs = require('fs');
const merge = require('webpack-merge');
const baseConfig = require('../webpack.base.config.js');
const CopyWebpackPlugin = require('copy-webpack-plugin');
const WriteFilePlugin = require('write-file-webpack-plugin');
const { DefinePlugin, ProvidePlugin } = require('webpack');
const SentryWebpackPlugin = require('@sentry/webpack-plugin');
const { getJsBundleId } = require('./bundle-id.js');
const { insertToHead, buildHead } = require('./src/html');
const { insertVariableXml, getOpenSearchXml } = require('./src/xml');

const CUSTOM_ROOT = path.resolve(__dirname, '../custom/');
const STATIC_ROOT = path.resolve(__dirname, '../static/');
const UI_ROOT = path.resolve(__dirname, '../ui/');
const DIST_ROOT = path.resolve(__dirname, 'dist/');
const WEB_STATIC_ROOT = path.resolve(__dirname, 'static/');
const WEB_PLATFORM_ROOT = __dirname;
const isProduction = process.env.NODE_ENV === 'production';
const hasSentryToken = process.env.SENTRY_AUTH_TOKEN !== undefined;
const jsBundleId = getJsBundleId();

// copy static files to dist folder
const copyWebpackCommands = [
  {
    from: `${STATIC_ROOT}/index-web.html`,
    to: `${DIST_ROOT}/index.html`,
    // add javascript script to index.html, generate/insert metatags
    transform(content, path) {
      return insertToHead(content.toString(), buildHead());
    },
    force: true,
  },
  {
    from: `${STATIC_ROOT}/opensearch.xml`,
    to: `${DIST_ROOT}/opensearch.xml`,
    transform(content, path) {
      return insertVariableXml(content.toString(), getOpenSearchXml());
    },
    force: true,
  },
  {
    from: `${STATIC_ROOT}/robots.txt`,
    to: `${DIST_ROOT}/robots.txt`,
    force: true,
  },
  {
    from: `${STATIC_ROOT}/img/favicon.png`,
    to: `${DIST_ROOT}/public/favicon.png`,
    force: true,
  },
  {
    from: `${STATIC_ROOT}/img/v2-og.png`,
    to: `${DIST_ROOT}/public/v2-og.png`,
  },
  {
    from: `${STATIC_ROOT}/font/`,
    to: `${DIST_ROOT}/public/font/`,
  },
  {
    from: `${WEB_STATIC_ROOT}/pwa/`,
    to: `${DIST_ROOT}/public/pwa/`,
  },
  {
    from: `${WEB_STATIC_ROOT}/pwa/serviceWorker.js`,
    to: `${DIST_ROOT}/`,
  },
];

const CUSTOM_OG_PATH = `${CUSTOM_ROOT}/v2-og.png`;
if (fs.existsSync(CUSTOM_OG_PATH)) {
  copyWebpackCommands.push({
    from: CUSTOM_OG_PATH,
    to: `${DIST_ROOT}/public/v2-og.png`,
    force: true,
  });
}

// clear the dist folder of existing js files before compilation
let regex = /^.*\.(json|js|map)$/;
// only run on nonprod environments to avoid side effects on prod
if (!isProduction) {
  fs.readdirSync(`${DIST_ROOT}/public/`)
    .filter(f => regex.test(f))
    .map(f => fs.unlinkSync(`${DIST_ROOT}/public/` + f));
}

const ROBOTS_TXT_PATH = `${CUSTOM_ROOT}/robots.txt`;
if (fs.existsSync(ROBOTS_TXT_PATH)) {
  copyWebpackCommands.push({
    from: ROBOTS_TXT_PATH,
    to: `${DIST_ROOT}/robots.txt`,
    force: true,
  });
}

let plugins = [
  new WriteFilePlugin(),
  new CopyWebpackPlugin(copyWebpackCommands),
  new DefinePlugin({
    IS_WEB: JSON.stringify(true),
    'process.env.SDK_API_URL': JSON.stringify(process.env.SDK_API_URL || LBRY_WEB_API),
  }),
  new ProvidePlugin({
    __: ['i18n.js', '__'],
  }),
];

if (isProduction && hasSentryToken) {
  plugins.push(
    new SentryWebpackPlugin({
      include: './dist',
      ignoreFile: '.sentrycliignore',
      ignore: ['node_modules', 'webpack.config.js'],
      configFile: 'sentry.properties',
    })
  );
}

const webConfig = {
  target: 'web',
  entry: {
    [`ui-${jsBundleId}`]: '../ui/index.jsx',
  },
  output: {
    filename: '[name].js',
    path: path.join(__dirname, 'dist/public/'),
    publicPath: '/public/',
    chunkFilename: '[name]-[chunkhash].js',
  },
  devServer: {
    port: WEBPACK_WEB_PORT,
    contentBase: path.join(__dirname, 'dist'),
    disableHostCheck: true, // to allow debugging with ngrok
  },
  module: {
    rules: [
      {
        loader: 'babel-loader',
        test: /\.jsx?$/,
        options: {
          presets: ['@babel/env', '@babel/react', '@babel/flow'],
          plugins: ['@babel/plugin-proposal-object-rest-spread', '@babel/plugin-proposal-class-properties'],
        },
      },
      {
        loader: 'preprocess-loader',
        test: /\.jsx?$/,
        exclude: /node_modules/,
        options: {
          TARGET: 'web',
          ppOptions: {
            type: 'js',
          },
        },
      },
    ],
  },
  resolve: {
    modules: [UI_ROOT, __dirname],

    alias: {
      lbryinc: 'lbryinc/dist/bundle.es.js',
      electron: `${WEB_PLATFORM_ROOT}/stubs/electron.js`,
      fs: `${WEB_PLATFORM_ROOT}/stubs/fs.js`,
    },
  },
  plugins,
};

module.exports = merge(baseConfig, webConfig);
<|endoftext|>


<commit_before>#!/usr/bin/env phantomjs

var page = require('webpage').create(),
    system = require('system'),
    fs = require('fs'),
    setCookie = require('./setcookie'),
    logger = require('./logger').setMethod(system.stderr.writeLine),
    log = logger(),
    url = require('./url'),

    customHeaders,

    GET_UNMODIFIED = false,
    WAIT = 0,

    blacklist = {
        set: function(str) {
            // try to parse the string as JSON and assign the
            // blacklist key to the hosts array
            try {
                this.hosts = JSON.parse(str).blacklist;

                log('DEBUG', 'Blacklist loaded with ' + this.hosts.length +
                                ' host' + (this.hosts.length == 1 ? '' : 's'));
            } catch (ex) {
                log('WARN', 'Unable to parse the blacklist: ' + ex);
            }
        },
        has: function(host) {
            if (!this.hosts || !this.hosts.length)
                return false;

            return this.hosts.some(function(h) {
                var re = new RegExp(h.replace(/([\-\.])/g, '\\$1') + '$', 'i');

                return re.test(host);
            });
        }
    },

    response = {
        setCookie: function(name, data) {
            log('DEBUG', 'Setting cookie ' + name);
            this.cookies = this.cookies || {};

            if (this.cookies[name]) {
                this.cookies[name].value = data.value;
            } else {
                this.cookies[name] = data;
            }

            this.checkReady();
        },
        setHeader: function(name, value) {
            log('DEBUG', 'Setting HTTP header ' + name);
            this.headers = this.headers || {};

            this.headers[name] = value;

            this.checkReady();
        },
        setBody: function(value) {
            log('DEBUG', 'Setting body content');
            this.body = unescape(encodeURIComponent(value));

            this.checkReady();
        },
        setStatus: function(value) {
            log('DEBUG', 'Setting HTTP status');
            this.httpStatus = value;

            this.checkReady();
        },
        toString: function() {
            var args = [
                {
                    url: this.url,
                    httpStatus: this.httpStatus,
                    cookies: this.cookies,
                    headers: this.headers,
                    body: this.body
                }
            ];

            log.LOG_LEVEL >= logger.DEBUG && args.push(null, 2);

            return JSON.stringify.apply(JSON, args);
        },
        checkReady: function() {
            if (this._sendWhenReady
                && this.headers
                && this.body
                && this.httpStatus) {
                log('DEBUG', 'Sending output');
                system.stdout.writeLine(this);
                phantom.exit();
            }
        },
        sendWhenReady: function() {
            log('DEBUG', 'Will send when ready');
            this._sendWhenReady = true;

            this.checkReady();
        }
    };

system.args.forEach(function(arg, i) {
    if (i == 0) return;

    var pair = /^\-\-?(\w)[\w\-]*=?(.*)$/.exec(arg);

    if (!pair) {
        // TODO: display usage
        return;
    }

    pair.shift();

    switch (pair[0]) {
        case 'l': //log level
            var level = parseInt(pair[1], 10);

            if (isNaN(level)) {
                level = logger[pair[1].toUpperCase()];

                if (typeof level != 'number')
                    level = logger.ERROR;
            }

            log = logger(level);

            break;
        case 'a': //address
            // ensure the resource has a trailing slash if necessary
            response.url = url.format(url.parse(pair[1]));

            break;
        case 'h': //headers
            try {
                var headers = JSON.parse(pair[1]);

                customHeaders = customHeaders || {};

                for (var h in headers) {
                    customHeaders[h] = headers[h];
                }
            } catch (ex) {
                log('ERROR', "Couldn't parse headers: invalid JSON");
                phantom.exit(1);
            }
            break;
        case 'b': //blacklisted hosts
            if (fs.exists(pair[1]) && fs.isFile(pair[1])) {
                blacklist.set(fs.read(pair[1]));
            } else {
                log('WARN', "Couldn't find blacklist file " + pair[1]);
            }
            break;
        case 'u': //unmodified content
            GET_UNMODIFIED = true
            break;
        case 'w': //wait
            WAIT = pair[1] || 0;
            break;
    }
});

// effectively the same one in the API documentation
phantom.onError = function(msg, trace) {
    var stack = [msg];
    if (trace && trace.length) {
        stack.push("\tStack trace:");
        trace.forEach(function(t) {
            stack.push([
                "\t\t",
                t.file || t.sourceURL,
                ":",
                t.line,
                t.function ? ' (in ' + t.function + ')' : ''
            ].join(''));
        });
    }
    log('ERROR', stack.join("\n"));
    phantom.exit(1);
};

customHeaders && (page.customHeaders = customHeaders);

page.onResourceRequested = function(request, network) {
    log('DEBUG', 'Requesting resource: ' + request.url);

    var host = url.parse(request.url).host;

    if (blacklist.has(host)) {
        network.abort();
        log('DEBUG', 'Aborting load of ' + request.url);
    }
};

page.onResourceReceived = function(res) {
    log('DEBUG', 'Recieved resource: ' + res.url);
    var isRequestedResource = (res.url == response.url);

    isRequestedResource && response.setStatus(res.status);

    res.headers && res.headers.forEach(function(h) {
        if (/^set\-cookie$/i.test(h.name)) {
            setCookie.parse(h.value).forEach(function(c) {
                response.setCookie(c.name, c);
            });
        } else if (isRequestedResource) {
            if (!/^content\-encoding$/i.test(h.name))
                response.setHeader(h.name, h.value);
        }
    });
};

page.open(response.url, function(status) {
    log('DEBUG', 'Opening ' + response.url);
    log('DEBUG', 'Status: ' + status);

    if (status != 'success') {
        log('ERROR', 'Unable to load ' + response.url);
        phantom.exit(1);
    } else {
        setTimeout(function() {
            page.cookies.forEach(function(c) {
                response.setCookie(c.name, c);
            });

            if (GET_UNMODIFIED) {
                log('DEBUG', 'Requesting unmodified version');

                var cachedPage = require('webpage').create();

                cachedPage.customHeaders = customHeaders;
                cachedPage.settings = {
                    javascriptEnabled: false,
                    loadImages: false
                };

                cachedPage.open(response.url, function(status) {
                    if (status != 'success') {
                        log('ERROR', 'Error fetching cached copy of ' + response.url);
                        phantom.exit(1);
                    } else {
                        response.setBody(cachedPage.content);
                        response.sendWhenReady();
                    }
                });

            } else {
                response.setBody(page.content);
                response.sendWhenReady();
            }
        }, WAIT);
    }
});
<commit_msg>Add getHeader function; make setHeader case-insensitive<commit_after>#!/usr/bin/env phantomjs

var page = require('webpage').create(),
    system = require('system'),
    fs = require('fs'),
    setCookie = require('./setcookie'),
    logger = require('./logger').setMethod(system.stderr.writeLine),
    log = logger(),
    url = require('./url'),

    customHeaders,

    GET_UNMODIFIED = false,
    WAIT = 0,

    blacklist = {
        set: function(str) {
            // try to parse the string as JSON and assign the
            // blacklist key to the hosts array
            try {
                this.hosts = JSON.parse(str).blacklist;

                log('DEBUG', 'Blacklist loaded with ' + this.hosts.length +
                                ' host' + (this.hosts.length == 1 ? '' : 's'));
            } catch (ex) {
                log('WARN', 'Unable to parse the blacklist: ' + ex);
            }
        },
        has: function(host) {
            if (!this.hosts || !this.hosts.length)
                return false;

            return this.hosts.some(function(h) {
                var re = new RegExp(h.replace(/([\-\.])/g, '\\$1') + '$', 'i');

                return re.test(host);
            });
        }
    },

    response = {
        setCookie: function(name, data) {
            log('DEBUG', 'Setting cookie ' + name);
            this.cookies = this.cookies || {};

            if (this.cookies[name]) {
                this.cookies[name].value = data.value;
            } else {
                this.cookies[name] = data;
            }

            this.checkReady();
        },
        setHeader: function(name, value) {
            log('DEBUG', 'Setting HTTP header ' + name);
            this.headers = this.headers || {};

            var key = this.getHeader(name).name || name;

            this.headers[key] = value;

            this.checkReady();
        },
        getHeader: function(name) {
            for (var h in this.headers) {
                if (name.toLowerCase() == h.toLowerCase())
                    return {
                        name: h,
                        value: this.headers[h]
                    };
            }

            return {};
        },
        setBody: function(value) {
            log('DEBUG', 'Setting body content');
            this.body = unescape(encodeURIComponent(value));

            this.checkReady();
        },
        setStatus: function(value) {
            log('DEBUG', 'Setting HTTP status');
            this.httpStatus = value;

            this.checkReady();
        },
        toString: function() {
            var args = [
                {
                    url: this.url,
                    httpStatus: this.httpStatus,
                    cookies: this.cookies,
                    headers: this.headers,
                    body: this.body
                }
            ];

            log.LOG_LEVEL >= logger.DEBUG && args.push(null, 2);

            return JSON.stringify.apply(JSON, args);
        },
        checkReady: function() {
            if (this._sendWhenReady
                && this.headers
                && this.body
                && this.httpStatus) {
                log('DEBUG', 'Sending output');
                system.stdout.writeLine(this);
                phantom.exit();
            }
        },
        sendWhenReady: function() {
            log('DEBUG', 'Will send when ready');
            this._sendWhenReady = true;

            this.checkReady();
        }
    };

system.args.forEach(function(arg, i) {
    if (i == 0) return;

    var pair = /^\-\-?(\w)[\w\-]*=?(.*)$/.exec(arg);

    if (!pair) {
        // TODO: display usage
        return;
    }

    pair.shift();

    switch (pair[0]) {
        case 'l': //log level
            var level = parseInt(pair[1], 10);

            if (isNaN(level)) {
                level = logger[pair[1].toUpperCase()];

                if (typeof level != 'number')
                    level = logger.ERROR;
            }

            log = logger(level);

            break;
        case 'a': //address
            // ensure the resource has a trailing slash if necessary
            response.url = url.format(url.parse(pair[1]));

            break;
        case 'h': //headers
            try {
                var headers = JSON.parse(pair[1]);

                customHeaders = customHeaders || {};

                for (var h in headers) {
                    customHeaders[h] = headers[h];
                }
            } catch (ex) {
                log('ERROR', "Couldn't parse headers: invalid JSON");
                phantom.exit(1);
            }
            break;
        case 'b': //blacklisted hosts
            if (fs.exists(pair[1]) && fs.isFile(pair[1])) {
                blacklist.set(fs.read(pair[1]));
            } else {
                log('WARN', "Couldn't find blacklist file " + pair[1]);
            }
            break;
        case 'u': //unmodified content
            GET_UNMODIFIED = true
            break;
        case 'w': //wait
            WAIT = pair[1] || 0;
            break;
    }
});

// effectively the same one in the API documentation
phantom.onError = function(msg, trace) {
    var stack = [msg];
    if (trace && trace.length) {
        stack.push("\tStack trace:");
        trace.forEach(function(t) {
            stack.push([
                "\t\t",
                t.file || t.sourceURL,
                ":",
                t.line,
                t.function ? ' (in ' + t.function + ')' : ''
            ].join(''));
        });
    }
    log('ERROR', stack.join("\n"));
    phantom.exit(1);
};

customHeaders && (page.customHeaders = customHeaders);

page.onResourceRequested = function(request, network) {
    log('DEBUG', 'Requesting resource: ' + request.url);

    var host = url.parse(request.url).host;

    if (blacklist.has(host)) {
        network.abort();
        log('DEBUG', 'Aborting load of ' + request.url);
    }
};

page.onResourceReceived = function(res) {
    log('DEBUG', 'Recieved resource: ' + res.url);
    var isRequestedResource = (res.url == response.url);

    isRequestedResource && response.setStatus(res.status);

    res.headers && res.headers.forEach(function(h) {
        if (/^set\-cookie$/i.test(h.name)) {
            setCookie.parse(h.value).forEach(function(c) {
                response.setCookie(c.name, c);
            });
        } else if (isRequestedResource) {
            if (!/^content\-encoding$/i.test(h.name))
                response.setHeader(h.name, h.value);
        }
    });
};

page.open(response.url, function(status) {
    log('DEBUG', 'Opening ' + response.url);
    log('DEBUG', 'Status: ' + status);

    if (status != 'success') {
        log('ERROR', 'Unable to load ' + response.url);
        phantom.exit(1);
    } else {
        setTimeout(function() {
            page.cookies.forEach(function(c) {
                response.setCookie(c.name, c);
            });

            if (GET_UNMODIFIED) {
                log('DEBUG', 'Requesting unmodified version');

                var cachedPage = require('webpage').create();

                cachedPage.customHeaders = customHeaders;
                cachedPage.settings = {
                    javascriptEnabled: false,
                    loadImages: false
                };

                cachedPage.open(response.url, function(status) {
                    if (status != 'success') {
                        log('ERROR', 'Error fetching cached copy of ' + response.url);
                        phantom.exit(1);
                    } else {
                        response.setBody(cachedPage.content);
                        response.sendWhenReady();
                    }
                });

            } else {
                response.setBody(page.content);
                response.sendWhenReady();
            }
        }, WAIT);
    }
});
<|endoftext|>


<commit_before>// Load in library dependencies
var assert = require('assert');
var mustache = require('mustache');
var deepClone = require('clone');

// TODO: We should still probably draw out an automata diagram
// But... let's re-invent regular expressions with booleans

// TODO: We probably want one of these http://en.wikipedia.org/wiki/Backtracking

// This says depth first traversal. Let's do that and aim for the first working solution.

// TODO: Since mustache is ambiguous, we should require a schema to get our feet off the ground. It will significantly help with nested info
// DEV: If we were to use regular expressions, we would lose accuracy of re-using variable values

// TODO: Can we break each of these recursive items into its own function? I ask because the function is getting large.

// Reverse HTML escapes
// https://github.com/janl/mustache.js/blob/0.8.1/mustache.js#L53-L66
var reverseEntityMap = {
  '&': '&',
  '<': '<',
  '>': '>',
  '"': '"',
  ''': '\'',
  '/': '/'
};
var reverseEntityRegExp = /(&|<|>|"|'|/)/g;
function unescapeHtml(string) {
  return String(string).replace(reverseEntityRegExp, function (s) {
    return reverseEntityMap[s];
  });
}

// TODO: Instead of deep clone, consider prototypal lookup for tokens
function Context(content) {
  // Set up content and token internals
  this.originalContent = content;
  this.remainingContent = content || '';
  this.completedContent = '';
  this.tokensByName = {};
}
Context.prototype = {
  // Add a new string to the context
  addStr: function (str) {
    // Assert we are not adding in content that is unwanted
    var expectedStr = this.remainingContent.slice(0, str.length);
    assert.strictEqual(str, expectedStr, '`Context#addStr` received bad match for `remainingContent`. Expected: "' + expectedStr + '", Actual: "' + str + '"');

    // Update remaining and completed content
    this.remainingContent = this.remainingContent.slice(str.length);
    this.completedContent += str;
  },

  // Update a token on the context
  setToken: function (key, val) {
    // Assert the token is not yet set
    // DEV: Even if they are the same, we are missing an optimization by reusing that knowledge
    assert.strictEqual(this.tokensByName.hasOwnProperty(key), false, '`Context#setToken` expected `tokensByName["' + key + '"]` to not be defined but it was.');

    // Set the value
    this.tokensByName[key] = val;
  },

  // Duplicate context (useful for exploring potential subcases)
  clone: function () {
    var context = new Context(this.originalContent);
    context.remainingContent = this.remainingContent;
    context.completedContent = this.completedContent;
    context.tokensByName = deepClone(this.tokensByName);
    return context;
  },

  // Add results from another context (e.g. subcases)
  addContext: function (context) {
    // Assert the context is not null
    assert(context, '`Context#addContext` received `null`, this is probably due to a bad check of `result` being truthy for inheritance.');

    // Add the content
    this.addStr(context.completedContent);

    // Copy over tokens
    var tokenNames = Object.getOwnPropertyNames(context.tokensByName);
    var i = 0;
    var len = tokenNames.length;
    for (; i < len; i++) {
      var tokenName = tokenNames[i];
      this.setToken(tokenName, context.tokensByName[tokenName]);
    }
  }
};

// TODO: Should this be a new method of a mustache template?
function reverseMustache(params) {
  // Make assertions about parameters
  assert(params, '`reverseMustache` expects `params` to be provided');
  assert(params.template, '`reverseMustache` expects `params.template` to be provided');
  assert(params.content, '`reverseMustache` expects `params.content` to be provided');

  // Interpret and localize parameters
  var ast = mustache.parse(params.template);
  var content = params.content;

  // Define recursive function to perform depth-first traversal
  function dfs(content, tokens) {
    var token = tokens[0];

    // TODO: We should be re-using tokensByName from top-levels (via deep clone)
    // TODO: Figure out how the hell to deal with subpaths (maybe getval, setval)
    // TODO: We are going to need to use sub-tokensByName as with mustache. Maybe re-use their lookup tooling?
    var context = new Context(content);

    // If there are no tokens left, return context
    // TODO: Is this the right thing to do?
    if (!token) {
      return context;
    }

    // Depending on the type of the token
    var type = token[0];
    switch (type) {
      case '#': // If/loop
        // Match internal content as many times as possible
        var subtokens = token[4];
        var loopContext = context.clone();
        var subresultArr = [];
        while (true) {
          // Attempt to match the content again
          // TODO: Don't forget that this runs in its own sub-object context (yey)
          console.log(loopContext.remainingContent);
          var subresult = dfs(loopContext.remainingContent, subtokens);

          // If we could not match, stop looping
          if (!subresult) {
            break;
          }

          // Otherwise, save the loop context result
          loopContext.addContext(subresult);
          subresultArr.push(loopContext);

          // Break the context reference to avoid alteration to stored result
          loopContext = loopContext.clone();
        }

        // If the content matched, attempt to save our boolean as true
        // DEV: We make this as an attempt because future content could be invalid
        if (subresultArr.length) {
          // If there was one item, attempt to save as `true`
          // TODO: This could shift based on any `object` references to `name`
          console.log('hai', subresultArr.length);
          if (subresultArr.length === 1) {
            // Set up exploratory case for conditional being true
            // TODO: The value could still be an object, not necessarily `true`.
            var proposedContext = context.clone();
            proposedContext.setToken(token[1], true);
            proposedContext.addStr(subresult[0].completedContent);

            // Explore the tokens within our conditional
            var result = dfs(proposedContext.remainingContent, tokens.slice(1));

            // If we matched
            if (result) {
              // Accept proposed context, add our sub result, and return
              context = proposedContext;
              context.addContext(result);
              return context;
            }
          // Otherwise, assume it was an array
          } else {
            // Attempt to use the entire array but progressively backoff
            var i = subresultArr.length;
            for (; i >= 0; i -= 1) {
              // TODO: We are currently creating a placeholder array of `true` values. Please stop doing this.
              var proposedContext = context.clone();
              proposedContext.setToken(token[1], subresultArr.slice(0, i).map(function () { return true; }));
              proposedContext.addStr(subresultArr[i - 1].completedContent);

              // Explore the tokens within our conditional
              var result = dfs(proposedContext.remainingContent, tokens.slice(1));

              // If we matched
              if (result) {
                // Accept proposed context, add our sub result, and return
                context = proposedContext;
                context.addContext(result);
                return context;
              }
            }
          }

          // Otherwise, continue to false
        }

        // Mark the boolean as false
        // DEV: This will fail on future steps if it is not `false` either
        context.setToken(token[1], false);
        var result = dfs(context.remainingContent, tokens.slice(1));

        // If there was a result, inherit and return
        // TODO: We need to do this for all of our inheritance, is there a pattern for this?
        if (result) {
          context.addContext(result);
          return context;
        // Otherwise, return null
        } else {
          return null;
        }
      case '&': // Unescaped variable
      case 'name': // Escaped variable
        // Looping from the entire remaining string to the first character (varying width, fixed at the first character)
        // DEV: This is a greedy regular expression. yey.
        var remainingContent = context.remainingContent;
        var i = remainingContent.length;
        for (; i >= 0; i -= 1) {
          // Attempt to use the substring as a match
          // TODO: There can probably be optimizations for trimming left content (a la quicksort) or even better regexp engines
          var proposedContext = context.clone();
          var proposedStr = remainingContent.slice(0, i);

          // Unescape any escaped variabled
          // https://github.com/janl/mustache.js/blob/0.8.1/mustache.js#L506-L509
          var proposedTokenVal = proposedStr;
          if (type === 'name') {
            proposedTokenVal = reverseMustache.unescapeHtml(proposedStr);
          }
          proposedContext.setToken(token[1], proposedTokenVal);

          // Continue with the match attempt
          proposedContext.addStr(proposedStr);
          var proposedResult = dfs(proposedContext.remainingContent, tokens.slice(1));

          // If we matched successfully, use the proposed result and context
          // DEV: If we ever do a `matchAll` method, this will make every other action run `n` times (i.e. n^m runtime, n = characters, m = decisions)
          if (proposedResult) {
            context = proposedContext;
            context.addContext(proposedResult);
            return context;
          // Otherwise, continue
          } else {
            continue;
          }
        }
        break;
      case 'text': // Text
        // Slice the next snippet of text
        var expectedText = token[1];
        var actualText = context.remainingContent.slice(0, expectedText.length);

        // If it does not match, reject it
        if (actualText !== expectedText) {
          // TODO: This shouldn't be a return but walk back in `for` loop for previous decisions (e.g. `true` over `false`)
          return null;
        } else {
          context.addStr(expectedText);

          var result = dfs(context.remainingContent, tokens.slice(1));
          if (result) {
            context.addContext(result);
            return context;
          } else {
            return null;
          }
        }
        break;
      default:
        throw new Error('`reverseMustache` did not recognize type "' + type + '" for token ' + JSON.stringify(token));
    }
  }

  // Run our depth first traversal
  var result = dfs(content, ast);

  // If there was a result and there is remaining content, return negatively
  if (result && result.remainingContent !== '') {
    return null;
  // Otherwise, return the result
  } else {
    return result;
  }
}

// Export `reverseMustache` and `unescapeHtml`
reverseMustache.unescapeHtml = unescapeHtml;
module.exports = reverseMustache;
<commit_msg>Removed dev console.logs<commit_after>// Load in library dependencies
var assert = require('assert');
var mustache = require('mustache');
var deepClone = require('clone');

// TODO: We should still probably draw out an automata diagram
// But... let's re-invent regular expressions with booleans

// TODO: We probably want one of these http://en.wikipedia.org/wiki/Backtracking

// This says depth first traversal. Let's do that and aim for the first working solution.

// TODO: Since mustache is ambiguous, we should require a schema to get our feet off the ground. It will significantly help with nested info
// DEV: If we were to use regular expressions, we would lose accuracy of re-using variable values

// TODO: Can we break each of these recursive items into its own function? I ask because the function is getting large.

// Reverse HTML escapes
// https://github.com/janl/mustache.js/blob/0.8.1/mustache.js#L53-L66
var reverseEntityMap = {
  '&': '&',
  '<': '<',
  '>': '>',
  '"': '"',
  ''': '\'',
  '/': '/'
};
var reverseEntityRegExp = /(&|<|>|"|'|/)/g;
function unescapeHtml(string) {
  return String(string).replace(reverseEntityRegExp, function (s) {
    return reverseEntityMap[s];
  });
}

// TODO: Instead of deep clone, consider prototypal lookup for tokens
function Context(content) {
  // Set up content and token internals
  this.originalContent = content;
  this.remainingContent = content || '';
  this.completedContent = '';
  this.tokensByName = {};
}
Context.prototype = {
  // Add a new string to the context
  addStr: function (str) {
    // Assert we are not adding in content that is unwanted
    var expectedStr = this.remainingContent.slice(0, str.length);
    assert.strictEqual(str, expectedStr, '`Context#addStr` received bad match for `remainingContent`. Expected: "' + expectedStr + '", Actual: "' + str + '"');

    // Update remaining and completed content
    this.remainingContent = this.remainingContent.slice(str.length);
    this.completedContent += str;
  },

  // Update a token on the context
  setToken: function (key, val) {
    // Assert the token is not yet set
    // DEV: Even if they are the same, we are missing an optimization by reusing that knowledge
    assert.strictEqual(this.tokensByName.hasOwnProperty(key), false, '`Context#setToken` expected `tokensByName["' + key + '"]` to not be defined but it was.');

    // Set the value
    this.tokensByName[key] = val;
  },

  // Duplicate context (useful for exploring potential subcases)
  clone: function () {
    var context = new Context(this.originalContent);
    context.remainingContent = this.remainingContent;
    context.completedContent = this.completedContent;
    context.tokensByName = deepClone(this.tokensByName);
    return context;
  },

  // Add results from another context (e.g. subcases)
  addContext: function (context) {
    // Assert the context is not null
    assert(context, '`Context#addContext` received `null`, this is probably due to a bad check of `result` being truthy for inheritance.');

    // Add the content
    this.addStr(context.completedContent);

    // Copy over tokens
    var tokenNames = Object.getOwnPropertyNames(context.tokensByName);
    var i = 0;
    var len = tokenNames.length;
    for (; i < len; i++) {
      var tokenName = tokenNames[i];
      this.setToken(tokenName, context.tokensByName[tokenName]);
    }
  }
};

// TODO: Should this be a new method of a mustache template?
function reverseMustache(params) {
  // Make assertions about parameters
  assert(params, '`reverseMustache` expects `params` to be provided');
  assert(params.template, '`reverseMustache` expects `params.template` to be provided');
  assert(params.content, '`reverseMustache` expects `params.content` to be provided');

  // Interpret and localize parameters
  var ast = mustache.parse(params.template);
  var content = params.content;

  // Define recursive function to perform depth-first traversal
  function dfs(content, tokens) {
    var token = tokens[0];

    // TODO: We should be re-using tokensByName from top-levels (via deep clone)
    // TODO: Figure out how the hell to deal with subpaths (maybe getval, setval)
    // TODO: We are going to need to use sub-tokensByName as with mustache. Maybe re-use their lookup tooling?
    var context = new Context(content);

    // If there are no tokens left, return context
    // TODO: Is this the right thing to do?
    if (!token) {
      return context;
    }

    // Depending on the type of the token
    var type = token[0];
    switch (type) {
      case '#': // If/loop
        // Match internal content as many times as possible
        var subtokens = token[4];
        var loopContext = context.clone();
        var subresultArr = [];
        while (true) {
          // Attempt to match the content again
          // TODO: Don't forget that this runs in its own sub-object context (yey)
          var subresult = dfs(loopContext.remainingContent, subtokens);

          // If we could not match, stop looping
          if (!subresult) {
            break;
          }

          // Otherwise, save the loop context result
          loopContext.addContext(subresult);
          subresultArr.push(loopContext);

          // Break the context reference to avoid alteration to stored result
          loopContext = loopContext.clone();
        }

        // If the content matched, attempt to save our boolean as true
        // DEV: We make this as an attempt because future content could be invalid
        if (subresultArr.length) {
          // If there was one item, attempt to save as `true`
          // TODO: This could shift based on any `object` references to `name`
          if (subresultArr.length === 1) {
            // Set up exploratory case for conditional being true
            // TODO: The value could still be an object, not necessarily `true`.
            var proposedContext = context.clone();
            proposedContext.setToken(token[1], true);
            proposedContext.addStr(subresult[0].completedContent);

            // Explore the tokens within our conditional
            var result = dfs(proposedContext.remainingContent, tokens.slice(1));

            // If we matched
            if (result) {
              // Accept proposed context, add our sub result, and return
              context = proposedContext;
              context.addContext(result);
              return context;
            }
          // Otherwise, assume it was an array
          } else {
            // Attempt to use the entire array but progressively backoff
            var i = subresultArr.length;
            for (; i >= 0; i -= 1) {
              // TODO: We are currently creating a placeholder array of `true` values. Please stop doing this.
              var proposedContext = context.clone();
              proposedContext.setToken(token[1], subresultArr.slice(0, i).map(function () { return true; }));
              proposedContext.addStr(subresultArr[i - 1].completedContent);

              // Explore the tokens within our conditional
              var result = dfs(proposedContext.remainingContent, tokens.slice(1));

              // If we matched
              if (result) {
                // Accept proposed context, add our sub result, and return
                context = proposedContext;
                context.addContext(result);
                return context;
              }
            }
          }

          // Otherwise, continue to false
        }

        // Mark the boolean as false
        // DEV: This will fail on future steps if it is not `false` either
        context.setToken(token[1], false);
        var result = dfs(context.remainingContent, tokens.slice(1));

        // If there was a result, inherit and return
        // TODO: We need to do this for all of our inheritance, is there a pattern for this?
        if (result) {
          context.addContext(result);
          return context;
        // Otherwise, return null
        } else {
          return null;
        }
      case '&': // Unescaped variable
      case 'name': // Escaped variable
        // Looping from the entire remaining string to the first character (varying width, fixed at the first character)
        // DEV: This is a greedy regular expression. yey.
        var remainingContent = context.remainingContent;
        var i = remainingContent.length;
        for (; i >= 0; i -= 1) {
          // Attempt to use the substring as a match
          // TODO: There can probably be optimizations for trimming left content (a la quicksort) or even better regexp engines
          var proposedContext = context.clone();
          var proposedStr = remainingContent.slice(0, i);

          // Unescape any escaped variabled
          // https://github.com/janl/mustache.js/blob/0.8.1/mustache.js#L506-L509
          var proposedTokenVal = proposedStr;
          if (type === 'name') {
            proposedTokenVal = reverseMustache.unescapeHtml(proposedStr);
          }
          proposedContext.setToken(token[1], proposedTokenVal);

          // Continue with the match attempt
          proposedContext.addStr(proposedStr);
          var proposedResult = dfs(proposedContext.remainingContent, tokens.slice(1));

          // If we matched successfully, use the proposed result and context
          // DEV: If we ever do a `matchAll` method, this will make every other action run `n` times (i.e. n^m runtime, n = characters, m = decisions)
          if (proposedResult) {
            context = proposedContext;
            context.addContext(proposedResult);
            return context;
          // Otherwise, continue
          } else {
            continue;
          }
        }
        break;
      case 'text': // Text
        // Slice the next snippet of text
        var expectedText = token[1];
        var actualText = context.remainingContent.slice(0, expectedText.length);

        // If it does not match, reject it
        if (actualText !== expectedText) {
          // TODO: This shouldn't be a return but walk back in `for` loop for previous decisions (e.g. `true` over `false`)
          return null;
        } else {
          context.addStr(expectedText);

          var result = dfs(context.remainingContent, tokens.slice(1));
          if (result) {
            context.addContext(result);
            return context;
          } else {
            return null;
          }
        }
        break;
      default:
        throw new Error('`reverseMustache` did not recognize type "' + type + '" for token ' + JSON.stringify(token));
    }
  }

  // Run our depth first traversal
  var result = dfs(content, ast);

  // If there was a result and there is remaining content, return negatively
  if (result && result.remainingContent !== '') {
    return null;
  // Otherwise, return the result
  } else {
    return result;
  }
}

// Export `reverseMustache` and `unescapeHtml`
reverseMustache.unescapeHtml = unescapeHtml;
module.exports = reverseMustache;
<|endoftext|>


<commit_before>/* eslint-env jquery */

/*eslint-disable no-unused-vars*/
var addHeadingAnchors = {
  /*eslint-enable no-unused-vars*/
  TIME_TO_FADE: 1000,

  init: function (selector, popoverContainer) {
    this.popoverContainer = popoverContainer || "body";
    this.target = document.querySelector(selector);
    if (this.target) {
      this.addAnchorsToHeadings();
      this.registerClipboardHandlers();
    }
  },

  addAnchorsToHeadings: function () {
    var selectorString = "h1[id], h2[id], h3[id], h4[id], h5[id], h6[id]";
    var headings = this.target.querySelectorAll(selectorString);

    Array.prototype.forEach.call(headings, function (heading) {
      var id = heading.id;
      var anchor = this.createAnchor(id);
      heading.appendChild(anchor);
      this.createPopover(anchor, id);
    }.bind(this));
  },

  createAnchor: function (id) {
    var anchor = document.createElement("a");
    var icon = document.createElement("i");
    var attributes = {
      href: "#" + id,
      class: "headerLink"
    };

    icon.setAttribute("class", "icon-share");

    anchor.appendChild(icon);

    for (var attr in attributes) {
      anchor.setAttribute(attr, attributes[attr]);
    }

    // there might be a browser quirk here
    anchor.setAttribute("data-clipboard-text", anchor.href);

    return anchor;
  },

  createPopover: function (anchor, headingId) {
    var popover;
    var inputId = "popover-" + headingId;
    var timeToFade = this.TIME_TO_FADE;
    popover = $(anchor).popover({
      container: this.popoverContainer,
      title: "Share a link to this section",
      content: function () {
        return "<input readonly id='" + inputId + "' value='" + anchor.href + "'>";
      },
      html: true,
      trigger: "manual" // this disables it for clicks
    });

    popover.on("shown", function () {
      $("a.headerLink").not(this).popover("hide");

      // the contents of popover are lazy-created, so this unfortunately needs to go here.
      var input = document.getElementById(inputId);
      var blurHandler = function () {
        setTimeout(function () {
          // if the input hasn't been re-selected
          if (document.activeElement !== input) {
            $(anchor).popover("hide");
          }
        }, timeToFade);
      };
      input.addEventListener("blur", blurHandler);
      input.focus();
      input.select();
    });
  },

  registerClipboardHandlers: function () {
    var clipboardErrorHandler = function (e) {
      $(e.trigger).popover("show");
    };

    var ensureSuccessHandler = function (e) {
      var originalText = e.text;
      var clipboardContent = window.clipboardData.getData("Text");
      if (originalText !== clipboardContent) {
        // actually, this was a failure.
        clipboardErrorHandler(e);
      }
    };

    if (!this.handler) {
      this.handler = new Clipboard("a.headerLink");
      this.handler.on("error", clipboardErrorHandler);
      this.handler.on("success", ensureSuccessHandler);
    }
  }
};
<commit_msg>better, but this click scroll thing sucks<commit_after>/* eslint-env jquery */

/*eslint-disable no-unused-vars*/
var addHeadingAnchors = {
  /*eslint-enable no-unused-vars*/

  init: function (selector, popoverContainer) {
    this.popoverContainer = popoverContainer || "body";
    this.target = document.querySelector(selector);
    if (this.target) {
      this.addAnchorsToHeadings();
      this.registerClipboardHandlers();
      this.registerDismissPopoverHandler()
    }
  },

  addAnchorsToHeadings: function () {
    var selectorString = "h1[id], h2[id], h3[id], h4[id], h5[id], h6[id]";
    var headings = this.target.querySelectorAll(selectorString);

    Array.prototype.forEach.call(headings, function (heading) {
      var id = heading.id;
      var anchor = this.createAnchor(id);
      heading.appendChild(anchor);
      this.createPopover(anchor, id);
    }.bind(this));
  },

  createAnchor: function (id) {
    var anchor = document.createElement("a");
    var icon = document.createElement("i");
    var attributes = {
      href: "#" + id,
      class: "headerLink"
    };

    icon.setAttribute("class", "icon-share");

    anchor.appendChild(icon);

    for (var attr in attributes) {
      anchor.setAttribute(attr, attributes[attr]);
    }

    // there might be a browser quirk here
    anchor.setAttribute("data-clipboard-text", anchor.href);

    return anchor;
  },

  createPopover: function (anchor, headingId) {
    var popover;
    var inputId = "popover-" + headingId;
    popover = $(anchor).popover({
      container: this.popoverContainer,
      title: "Share a link to this section",
      content: function () {
        return "<input readonly id='" + inputId + "' value='" + anchor.href + "'>";
      },
      html: true,
      trigger: "manual" // this disables it for clicks
    });

    popover.on("shown", function () {
      $("a.headerLink").not(this).popover("hide");
      // the contents of popover are lazy-created, so this unfortunately needs to go here.
      var input = document.getElementById(inputId);
      input.focus();
      input.select();
    });
  },

  registerClipboardHandlers: function () {
    var clipboardErrorHandler = function (e) {
      $(e.trigger).popover("show");
    };

    var ensureSuccessHandler = function (e) {
      var originalText = e.text;
      var clipboardContent = window.clipboardData.getData("Text");
      if (originalText !== clipboardContent) {
        // actually, this was a failure.
        clipboardErrorHandler(e);
      }
    };

    if (!this.handler) {
      this.handler = new Clipboard("a.headerLink");
      this.handler.on("error", clipboardErrorHandler);
      this.handler.on("success", ensureSuccessHandler);
    }
  },

  registerDismissPopoverHandler: function () {
    $(this.target).click(function (e) {
      if ($('a.headerLink').has(e.target).length === 0) {
        $("a.headerLink").popover("hide");
      }
    });
  }
};
<|endoftext|>