<commit_before>
const log = console.debug; // in dev
// const log = () => {}; // in production

/** turn array into Map, for O(1) containment check */
const arrayToMap = (array) => {
  return array.reduce((acc, v) => {
      acc.set(v, true);
      return acc;
    }, new Map());
};

/** Language specific special conditions that, when met by a node whose parent
  starts on the previous row, will cause and indentation. Example else if:
  if (test)
    more()
  else if (test2)
    andmore()

  TODO: it would be nice to further reify this, i.e., make it declarative.
 */
const previousRowConditions = {

  javascript: (node) => (
    (node.parent.type == 'while_statement' && node.type != 'statement_block')
      || (node.parent.type == 'jsx_self_closing_element' && node.type != '/')
      || (node.parent.type == 'if_statement'
        && (!(node.type == 'if_statement'
            && node.previousSibling.type == 'else'
            && node.previousSibling.startPosition.row == node.startPosition.row)
          /* not an else-if */
        )
        && node.type != 'statement_block'
        && node.type != 'else')
  ),

  python: (node) => (
    ( node.parent.type == 'if_statement'
      && node.type != 'elif_clause'
      && node.type != 'else_clause')

      || ( node.parent.type == 'while_statement'
        && node.type != 'else_clause')

      || (node.parent.type == 'try_statement'
        && node.type != 'except_clause'
        && node.type != 'finally_clause'
        && node.type != 'else_clause')
  ),

  cpp: (node) => (
    (node.parent.type == 'while_statement' && node.type != 'compound_statement')
      || (node.parent.type == 'if_statement'
        && (!(node.type == 'if_statement'
            && node.previousSibling.type == 'else'
            && node.previousSibling.startPosition.row == node.startPosition.row)
          /* not an else-if */
        )
        && node.type != 'compound_statement'
        && node.type != 'else')
  ),
};

previousRowConditions.c = previousRowConditions.cpp;

/** Walk up the tree. Everytime we meet a scope type, check whether we
  are coming from the first (resp. last) child. If so, we are opening
  (resp. closing) that scope, i.e., do not count it. Otherwise, add 1.

  This is the core function.

  It might make more sense to reverse the direction of this walk, i.e.,
  go from root to leaf instead.
*/
const treeWalk = (node, scopes, previousRowConditions, lastScope = null) => {
  if (node == null || node.parent == null) {
    return 0;
  } else {

    let increment = 0;

    const notFirstOrLastSibling =
      (node.previousSibling != null && node.nextSibling != null);

    const isScope = scopes.indent.get(node.parent.type);
    (notFirstOrLastSibling && isScope && increment++);

    const isScope2 = scopes.indentExceptFirst.get(node.parent.type);
    (!increment && isScope2 && node.previousSibling != null && increment++);

    // check whether the last (lower) indentation happend due to a scope that
    // started on the same row and ends directly before this.
    // TODO: this currently only works for scopes that have a single-character
    // closing delimiter (like statement_blocks, but not HTML, for instance).
    if (lastScope && increment > 0
      && // previous scope was a two-sided scope, reduce if starts on same row
      // and ends right before
      node.parent.startPosition.row == lastScope.node.startPosition.row
      && node.parent.endIndex <= lastScope.node.endIndex + 1) {

      log('ignoring repeat', node.parent.type, lastScope);
      increment = 0;
    }

    // Adjusting based on node parent
    if (previousRowConditions
      && node.parent.startPosition.row < node.startPosition.row
      && previousRowConditions(node)) {
      log(`node adjustment -- previous row condition met`);
      increment += 1;
    }

    log('treewalk', {node, notFirstOrLastSibling, type: node.parent.type, increment});
    const newLastScope = (isScope || isScope2 ? {node: node.parent} : lastScope);
    return treeWalk(node.parent, scopes, previousRowConditions, newLastScope) + increment;
  }
};

const suggestedIndentForBufferRow = (e, scopes, previousRowConditions = null) => {

  log({e, scopes});

  // Given a position, walk up the syntax tree, to find the highest level
  // node that still starts here. This is to identify the column where this
  // node (e.g., an HTML closing tag) ends.
  const getHighestSyntaxNodeAtPosition = (row, column = null) => {
    if (column == null) {
      // Find the first character on the row that is not whitespace + 1
      column = e.lineTextForBufferRow(row).search(/\S/) + 1;
    }

    let syntaxNode;
    if (column >= 0) {
      syntaxNode = e.languageMode.getSyntaxNodeAtPosition({row, column});
      while (syntaxNode && syntaxNode.parent
        && syntaxNode.parent.startPosition.row == syntaxNode.startPosition.row
        && syntaxNode.parent.endPosition.row == syntaxNode.startPosition.row
        && syntaxNode.parent.startPosition.column == syntaxNode.startPosition.column
      ) {
        syntaxNode = syntaxNode.parent;
      }
      return syntaxNode;
    }
  }

  // -----------------------------------------------------------

  /* This approach is simpler and robust against parse errors */
  return (row, tabLength, options) => {

    const previousRow = Math.max(row - 1, 0);
    const previousIndentation = e.indentationForBufferRow(previousRow);
    const currentIndentation = e.indentationForBufferRow(row);

    const syntaxNode = getHighestSyntaxNodeAtPosition(row);
    if (!syntaxNode) {
      return 0;
    }
    let indentation = treeWalk(syntaxNode, scopes, previousRowConditions);

    // apply current row, single line, type-based rules, e.g., 'else' or 'private:'
    scopes.types.indent.get(syntaxNode.type) && indentation++;
    scopes.types.outdent.get(syntaxNode.type) && indentation--;

    // Special case for comments
    if (syntaxNode.type == 'comment'
      && syntaxNode.startPosition.row < row
      && syntaxNode.endPosition.row > row) {
      indentation += 1;
    }

    if (options && options.preserveLeadingWhitespace) {
      indentation -= currentIndentation;
    }

    return indentation;
  };
}

// --------------------------------------------------------

const SaneIndentation = {

  config: {
    javascript: {
      type: 'object',
      default: {
        scopes: {
          indent: [
            'array',
            'object',
            'arguments',
            'statement_block',
            'class_body',
            'parenthesized_expression',
            'jsx_element',
            'jsx_opening_element',
            'jsx_expression',
            'switch_body',
            'comment',
          ],
          indentExceptFirst: [
            'member_expression',
            'assignment_expression',
            'expression_statement',
            'variable_declarator',
            'lexical_declaration',
            'binary_expression',
          ]
        }
      }
    },

    html: {
      type: 'object',
      default: {
        scopes: {
          indent: [
            'element',
            'start_tag',
            'end_tag',
            'self_closing_tag',
          ]
        }
      }
    },

    python: {
      type: 'object',
      default: {
        scopes: {
          indent: [
            'argument_list',
            'dictionary',
            'list',
            'list_comprehension',
          ],
          indentExceptFirst: [
            'function_definition',
            'for_statement',
            'class_definition',
            'elif_clause',
            'else_clause',
            'expression_list',
            'binary_operator',
            'except_clause',
          ]
        }
      }
    },

    cpp: {
      type: 'object',
      default: {
        scopes: {
          indent: [
            'compound_statement',
            'argument_list',
            'field_declaration_list',
            'enumerator_list',
            'parameter_list',
            'initializer_list',
          ],
          indentExceptFirst: [
            'for_statement',
            'return_statement',
            'shift_expression',
            'call_expression',
            'field_expression',
            'logical_expression',
            'math_expression',
            'relational_expression',
            'assignment_expression',
            'lambda_expression',
            'field_initializer_list',
            'template_argument_list',
            'init_declarator',
          ],
        },
        types: { // for current node types, current line only
          indent: [],
          outdent: [
            'access_specifier'
          ]
        }
      }
    },

    c: {
      type: 'object',
      default: {
        scopes: {
          indent: [
            'compound_statement',
            'argument_list',
            'field_declaration_list',
            'enumerator_list',
            'parameter_list',
            'initializer_list',
          ],
          indentExceptFirst: [
            'for_statement',
            'return_statement',
            'shift_expression',
            'call_expression',
            'field_expression',
            'logical_expression',
            'math_expression',
            'relational_expression',
            'assignment_expression',
            'field_initializer_list',
            'init_declarator',
          ],
        }
      }
    },
  },

  activate: function(state) {
    log('activating sanity');
    atom.config.set('core.useTreeSitterParsers', true);

    // watch editors; if new ones get added, monkey-patch them as well
    atom.workspace.observeTextEditors((e) => {

      const language = e.languageMode.grammar && e.languageMode.grammar.id;
      log('language', language);
      const config = atom.config.get(`sane-indentation.${language}`);
      if (config) {
        // turn into Maps
        const scopes = {
          indent: arrayToMap(config.scopes.indent),
          indentExceptFirst: arrayToMap(config.scopes.indentExceptFirst || []),
          types: {
            indent: arrayToMap(config.types && config.types.indent || []),
            outdent: arrayToMap(config.types && config.types.outdent || [])
          }
        }

        e.languageMode.suggestedIndentForBufferRow =
          suggestedIndentForBufferRow(e, scopes, previousRowConditions[language]);
      } else {
        log(`no grammar defined for ${language}, leaving atom.io's indentation logic in place`);
      }

    });
  },

  deactivate: function() {
    log('back to insanity');
  },

};

module.exports = SaneIndentation;
<commit_msg>Adjust to renamed scope names in 1.32?<commit_after>
const log = console.debug; // in dev
// const log = () => {}; // in production

/** turn array into Map, for O(1) containment check */
const arrayToMap = (array) => {
  return array.reduce((acc, v) => {
      acc.set(v, true);
      return acc;
    }, new Map());
};

/** Language specific special conditions that, when met by a node whose parent
  starts on the previous row, will cause an indentation. Example else-if:
  if (test)
    more()
  else if (test2)
    andmore()

  TODO: it would be nice to further reify this, i.e., make it declarative.
 */
const previousRowConditions = {

  'source.js': (node) => (
    (node.parent.type == 'while_statement' && node.type != 'statement_block')
      || (node.parent.type == 'jsx_self_closing_element' && node.type != '/')
      || (node.parent.type == 'if_statement'
        && (!(node.type == 'if_statement'
            && node.previousSibling.type == 'else'
            && node.previousSibling.startPosition.row == node.startPosition.row)
          /* not an else-if */
        )
        && node.type != 'statement_block'
        && node.type != 'else')
  ),

  'source.python': (node) => (
    ( node.parent.type == 'if_statement'
      && node.type != 'elif_clause'
      && node.type != 'else_clause')

      || ( node.parent.type == 'while_statement'
        && node.type != 'else_clause')

      || (node.parent.type == 'try_statement'
        && node.type != 'except_clause'
        && node.type != 'finally_clause'
        && node.type != 'else_clause')
  ),

  'source.cpp': (node) => (
    (node.parent.type == 'while_statement' && node.type != 'compound_statement')
      || (node.parent.type == 'if_statement'
        && (!(node.type == 'if_statement'
            && node.previousSibling.type == 'else'
            && node.previousSibling.startPosition.row == node.startPosition.row)
          /* not an else-if */
        )
        && node.type != 'compound_statement'
        && node.type != 'else')
  ),
};

previousRowConditions['source.c'] =
  previousRowConditions['source.cpp'];

/** Walk up the tree. Everytime we meet a scope type, check whether we
  are coming from the first (resp. last) child. If so, we are opening
  (resp. closing) that scope, i.e., do not count it. Otherwise, add 1.

  This is the core function.

  It might make more sense to reverse the direction of this walk, i.e.,
  go from root to leaf instead.
*/
const treeWalk = (node, scopes, previousRowConditions, lastScope = null) => {
  if (node == null || node.parent == null) {
    return 0;
  } else {

    let increment = 0;

    const notFirstOrLastSibling =
      (node.previousSibling != null && node.nextSibling != null);

    const isScope = scopes.indent.get(node.parent.type);
    (notFirstOrLastSibling && isScope && increment++);

    const isScope2 = scopes.indentExceptFirst.get(node.parent.type);
    (!increment && isScope2 && node.previousSibling != null && increment++);

    // check whether the last (lower) indentation happend due to a scope that
    // started on the same row and ends directly before this.
    // TODO: this currently only works for scopes that have a single-character
    // closing delimiter (like statement_blocks, but not HTML, for instance).
    if (lastScope && increment > 0
      && // previous scope was a two-sided scope, reduce if starts on same row
      // and ends right before
      node.parent.startPosition.row == lastScope.node.startPosition.row
      && node.parent.endIndex <= lastScope.node.endIndex + 1) {

      log('ignoring repeat', node.parent.type, lastScope);
      increment = 0;
    }

    // Adjusting based on node parent
    if (previousRowConditions
      && node.parent.startPosition.row < node.startPosition.row
      && previousRowConditions(node)) {
      log(`node adjustment -- previous row condition met`);
      increment += 1;
    }

    log('treewalk', {node, notFirstOrLastSibling, type: node.parent.type, increment});
    const newLastScope = (isScope || isScope2 ? {node: node.parent} : lastScope);
    return treeWalk(node.parent, scopes, previousRowConditions, newLastScope) + increment;
  }
};

const suggestedIndentForBufferRow = (e, scopes, previousRowConditions = null) => {

  log({e, scopes});

  // Given a position, walk up the syntax tree, to find the highest level
  // node that still starts here. This is to identify the column where this
  // node (e.g., an HTML closing tag) ends.
  const getHighestSyntaxNodeAtPosition = (row, column = null) => {
    if (column == null) {
      // Find the first character on the row that is not whitespace + 1
      column = e.lineTextForBufferRow(row).search(/\S/) + 1;
    }

    let syntaxNode;
    if (column >= 0) {
      syntaxNode = e.languageMode.getSyntaxNodeAtPosition({row, column});
      while (syntaxNode && syntaxNode.parent
        && syntaxNode.parent.startPosition.row == syntaxNode.startPosition.row
        && syntaxNode.parent.endPosition.row == syntaxNode.startPosition.row
        && syntaxNode.parent.startPosition.column == syntaxNode.startPosition.column
      ) {
        syntaxNode = syntaxNode.parent;
      }
      return syntaxNode;
    }
  }

  // -----------------------------------------------------------

  /* This approach is simpler and robust against parse errors */
  return (row, tabLength, options) => {

    const previousRow = Math.max(row - 1, 0);
    const previousIndentation = e.indentationForBufferRow(previousRow);
    const currentIndentation = e.indentationForBufferRow(row);

    const syntaxNode = getHighestSyntaxNodeAtPosition(row);
    if (!syntaxNode) {
      return 0;
    }
    let indentation = treeWalk(syntaxNode, scopes, previousRowConditions);

    // apply current row, single line, type-based rules, e.g., 'else' or 'private:'
    scopes.types.indent.get(syntaxNode.type) && indentation++;
    scopes.types.outdent.get(syntaxNode.type) && indentation--;

    // Special case for comments
    if (syntaxNode.type == 'comment'
      && syntaxNode.startPosition.row < row
      && syntaxNode.endPosition.row > row) {
      indentation += 1;
    }

    if (options && options.preserveLeadingWhitespace) {
      indentation -= currentIndentation;
    }

    return indentation;
  };
}

// --------------------------------------------------------

const SaneIndentation = {

  config: {
    'source.js': {
      type: 'object',
      default: {
        scopes: {
          indent: [
            'array',
            'object',
            'arguments',
            'statement_block',
            'class_body',
            'parenthesized_expression',
            'jsx_element',
            'jsx_opening_element',
            'jsx_expression',
            'switch_body',
            'comment',
          ],
          indentExceptFirst: [
            'member_expression',
            'assignment_expression',
            'expression_statement',
            'variable_declarator',
            'lexical_declaration',
            'binary_expression',
          ]
        }
      }
    },

    'text.html.basic': {
      type: 'object',
      default: {
        scopes: {
          indent: [
            'element',
            'start_tag',
            'end_tag',
            'self_closing_tag',
          ]
        }
      }
    },

    'source.python': {
      type: 'object',
      default: {
        scopes: {
          indent: [
            'argument_list',
            'dictionary',
            'list',
            'list_comprehension',
          ],
          indentExceptFirst: [
            'function_definition',
            'for_statement',
            'class_definition',
            'elif_clause',
            'else_clause',
            'expression_list',
            'binary_operator',
            'except_clause',
          ]
        }
      }
    },

    'source.cpp': {
      type: 'object',
      default: {
        scopes: {
          indent: [
            'compound_statement',
            'argument_list',
            'field_declaration_list',
            'enumerator_list',
            'parameter_list',
            'initializer_list',
          ],
          indentExceptFirst: [
            'for_statement',
            'return_statement',
            'shift_expression',
            'call_expression',
            'field_expression',
            'logical_expression',
            'math_expression',
            'relational_expression',
            'assignment_expression',
            'lambda_expression',
            'field_initializer_list',
            'template_argument_list',
            'init_declarator',
          ],
        },
        types: { // for current node types, current line only
          indent: [],
          outdent: [
            'access_specifier'
          ]
        }
      }
    },

    'source.c': {
      type: 'object',
      default: {
        scopes: {
          indent: [
            'compound_statement',
            'argument_list',
            'field_declaration_list',
            'enumerator_list',
            'parameter_list',
            'initializer_list',
          ],
          indentExceptFirst: [
            'for_statement',
            'return_statement',
            'shift_expression',
            'call_expression',
            'field_expression',
            'logical_expression',
            'math_expression',
            'relational_expression',
            'assignment_expression',
            'field_initializer_list',
            'init_declarator',
          ],
        }
      }
    },
  },

  activate: function(state) {
    log('activating sanity');
    atom.config.set('core.useTreeSitterParsers', true);

    // watch editors; if new ones get added, monkey-patch them as well
    atom.workspace.observeTextEditors((e) => {

      const language = e.languageMode.grammar && e.languageMode.grammar.id;
      log('language', language);
      const config = atom.config.get(`sane-indentation.${language}`);
      if (config) {
        // turn into Maps
        const scopes = {
          indent: arrayToMap(config.scopes.indent),
          indentExceptFirst: arrayToMap(config.scopes.indentExceptFirst || []),
          types: {
            indent: arrayToMap(config.types && config.types.indent || []),
            outdent: arrayToMap(config.types && config.types.outdent || [])
          }
        }

        e.languageMode.suggestedIndentForBufferRow =
          suggestedIndentForBufferRow(e, scopes, previousRowConditions[language]);
      } else {
        log(`no grammar defined for ${language}, leaving atom.io's indentation logic in place`);
      }

    });
  },

  deactivate: function() {
    log('back to insanity');
  },

};

module.exports = SaneIndentation;
<|endoftext|>


<commit_before>import relateUrl from 'relateurl';

// Adopts from https://github.com/kangax/html-minifier/blob/51ce10f4daedb1de483ffbcccecc41be1c873da2/src/htmlminifier.js#L209-L221
const tagsHaveUriValuesForAttributes = new Set([
    'a',
    'area',
    'link',
    'base',
    'img',
    'object',
    'q',
    'blockquote',
    'ins',
    'form',
    'input',
    'head',
    'script'
]);

const tagsHasHrefAttributes = new Set([
    'a',
    'area',
    'link',
    'base'
]);

const attributesOfImgTagHasUriValues = new Set([
    'src',
    'longdesc',
    'usemap'
]);

const attributesOfObjectTagHasUriValues = new Set([
    'classid',
    'codebase',
    'data',
    'usemap'
]);

const isUriTypeAttribute = (tag, attr) => {
    return (
        tagsHasHrefAttributes.has(tag) && attr === 'href' ||
        tag === 'img' && attributesOfImgTagHasUriValues.has(attr) ||
        tag === 'object' && attributesOfObjectTagHasUriValues.has(attr) ||
        tag === 'q' && attr === 'cite' ||
        tag === 'blockquote' && attr === 'cite' ||
        (tag === 'ins' || tag === 'del') && attr === 'cite' ||
        tag === 'form' && attr === 'action' ||
        tag === 'input' && (attr === 'src' || attr === 'usemap') ||
        tag === 'head' && attr === 'profile' ||
        tag === 'script' && (attr === 'src' || attr === 'for')
    );
};

const processModuleOptions = options => {
    // FIXME!
    // relateurl@1.0.0-alpha only supports URL while stable version (0.2.7) only supports string
    // should convert input into URL instance after relateurl@1 is stable
    if (options instanceof URL) return options.toString();

    if (typeof options === 'string') return options;

    return false;
};

const isLinkRelCanonical = ({ tag, attrs }) => {
    // Return false early for non-"link" tag
    if (tag !== 'link') return false;

    for (const [attrName, attrValue] of Object.entries(attrs)) {
        if (attrName.toLowerCase() === 'rel' && attrValue === 'canonical') return true;
    }

    return false;
};

/** Convert absolute url into relative url */
export default function minifyUrls(tree, options, moduleOptions) {
    const urlBase = processModuleOptions(moduleOptions);

    // Invalid configuration, return tree directly
    if (!urlBase) return tree;

    tree.walk(node => {
        if (!node.attrs) return node;

        // Return early for better performance
        if (!tagsHaveUriValuesForAttributes.has(node.tag)) return node;

        // Prevent link[rel=canonical] being processed
        // Can't be excluded by isUriTypeAttribute()
        if (isLinkRelCanonical(node)) return node;

        for (const [attrName, attrValue] of Object.entries(node.attrs)) {
            const attrNameLower = attrName.toLowerCase();

            if (!isUriTypeAttribute(node.tag, attrNameLower)) continue;

            // FIXME!
            // relateurl@1.0.0-alpha only supports URL while stable version (0.2.7) only supports string
            // the WHATWG URL API is very strict while attrValue might not be a valid URL
            // new URL should be used, and relateUrl#relate should be wrapped in try...catch after relateurl@1 is stable
            node.attrs[attrName] = relateUrl.relate(urlBase, attrValue);
        }

        return node;
    });

    return tree;
}
<commit_msg>Feat: make RelateUrl instance reusable<commit_after>import RelateUrl from 'relateurl';

// Adopts from https://github.com/kangax/html-minifier/blob/51ce10f4daedb1de483ffbcccecc41be1c873da2/src/htmlminifier.js#L209-L221
const tagsHaveUriValuesForAttributes = new Set([
    'a',
    'area',
    'link',
    'base',
    'img',
    'object',
    'q',
    'blockquote',
    'ins',
    'form',
    'input',
    'head',
    'script'
]);

const tagsHasHrefAttributes = new Set([
    'a',
    'area',
    'link',
    'base'
]);

const attributesOfImgTagHasUriValues = new Set([
    'src',
    'longdesc',
    'usemap'
]);

const attributesOfObjectTagHasUriValues = new Set([
    'classid',
    'codebase',
    'data',
    'usemap'
]);

const isUriTypeAttribute = (tag, attr) => {
    return (
        tagsHasHrefAttributes.has(tag) && attr === 'href' ||
        tag === 'img' && attributesOfImgTagHasUriValues.has(attr) ||
        tag === 'object' && attributesOfObjectTagHasUriValues.has(attr) ||
        tag === 'q' && attr === 'cite' ||
        tag === 'blockquote' && attr === 'cite' ||
        (tag === 'ins' || tag === 'del') && attr === 'cite' ||
        tag === 'form' && attr === 'action' ||
        tag === 'input' && (attr === 'src' || attr === 'usemap') ||
        tag === 'head' && attr === 'profile' ||
        tag === 'script' && (attr === 'src' || attr === 'for')
    );
};

const processModuleOptions = options => {
    // FIXME!
    // relateurl@1.0.0-alpha only supports URL while stable version (0.2.7) only supports string
    // should convert input into URL instance after relateurl@1 is stable
    if (typeof options === 'string') return options;
    if (options instanceof URL) return options.toString();

    return false;
};

const isLinkRelCanonical = ({ tag, attrs }) => {
    // Return false early for non-"link" tag
    if (tag !== 'link') return false;

    for (const [attrName, attrValue] of Object.entries(attrs)) {
        if (attrName.toLowerCase() === 'rel' && attrValue === 'canonical') return true;
    }

    return false;
};

let relateUrlInstance;
let STORED_URL_BASE;

/** Convert absolute url into relative url */
export default function minifyUrls(tree, options, moduleOptions) {
    const urlBase = processModuleOptions(moduleOptions);

    // Invalid configuration, return tree directly
    if (!urlBase) return tree;

    /** Bring up a reusable RelateUrl instances (only once)
     *
     * STORED_URL_BASE is used to invalidate RelateUrl instances,
     * avoiding require.cache acrossing multiple htmlnano instance with different configuration,
     * e.g. unit tests cases.
     */
    if (!relateUrlInstance || STORED_URL_BASE !== urlBase) {
        relateUrlInstance = new RelateUrl(urlBase);
        STORED_URL_BASE = urlBase;
    }

    tree.walk(node => {
        if (!node.attrs) return node;

        // Return early for better performance
        if (!tagsHaveUriValuesForAttributes.has(node.tag)) return node;

        // Prevent link[rel=canonical] being processed
        // Can't be excluded by isUriTypeAttribute()
        if (isLinkRelCanonical(node)) return node;

        for (const [attrName, attrValue] of Object.entries(node.attrs)) {
            const attrNameLower = attrName.toLowerCase();

            if (!isUriTypeAttribute(node.tag, attrNameLower)) continue;

            // FIXME!
            // relateurl@1.0.0-alpha only supports URL while stable version (0.2.7) only supports string
            // the WHATWG URL API is very strict while attrValue might not be a valid URL
            // new URL should be used, and relateUrl#relate should be wrapped in try...catch after relateurl@1 is stable
            node.attrs[attrName] = relateUrlInstance.relate(attrValue);
        }

        return node;
    });

    return tree;
}
<|endoftext|>


<commit_before>/* @flow */
import React from 'react';
import Box from 'grommet/components/Box';
import Form from 'grommet/components/Form';
import FormFields from 'grommet/components/FormFields';
import FormField from 'grommet/components/FormField';
import Select from 'grommet/components/Select';
import Button from 'grommet/components/Button';
import Footer from 'grommet/components/Footer';
import RadioButton from 'grommet/components/RadioButton';
import validation from './validation';
import type { OnChangeEvent } from '../../types';
import iconMap from './iconMap';

export type AssetType = 'path' | 'href';
export type ButtonType = 'Button' | 'Anchor';
export type Primary = 'True' | 'False';
export type IconType =
  'primary' |
  'email' |
  'chat' |
  'share' |
  'attachment' |
  'word' |
  'zip' |
  'document download' |
  'download' |
  'play' |
  'print';

type State = {
  label: ?string,
  primary: ?Primary,
  path: ?string,
  href: ?string,
  buttonType: ?string,
  assetType: AssetType,
  icon?: IconType,
  error: {
    label: ?string,
    path: ?string,
    href: ?string,
  }
};

// Weird issue with unwrapping props.data || props, showing unused props.
// Disable for now.
/* eslint-disable react/no-unused-prop-types */
type Props = {
  pad?: boolean,
  onSubmit?: Function,
  onChange?: Function,
  label?: string,
  children?: React$Element<any>, // eslint-disable-line
  primary?: Primary,
  buttonType?: ButtonType,
  icon?: IconType,
  href?: string,
  assetType?: AssetType,
  path?: string,
  asset?: {
    path?: string,
  },
  data?: Object
};
/* eslint-enable react/no-unused-prop-types */

export default class BlockButtonForm extends React.Component {
  static defaultProps = {
    pad: false,
  }

  constructor(props: Props) {
    super(props);
    this.onChange = this.onChange.bind(this);
    this.onSubmit = this.onSubmit.bind(this);
    this.formIsValid = this.formIsValid.bind(this);
    this.onChangeAssetType = this.onChangeAssetType.bind(this);
    const { path, primary, label, buttonType, href, assetType, icon } = props.data || props;
    const labelInput = label || '';
    const primaryInput = primary || 'True';
    const buttonTypeInput = buttonType || 'Button';
    const iconInput = icon || 'primary';
    const pathInput = path || '';
    const hrefInput = href || '';
    const assetTypeInput = assetType || 'path';
    this.state = {
      label: labelInput,
      primary: primaryInput,
      path: pathInput,
      buttonType: buttonTypeInput,
      icon: iconInput,
      href: hrefInput,
      assetType: assetTypeInput,
      error: {
        label: null,
        path: null,
        href: null,
      },
    };
  }

  state: State;

  componentWillReceiveProps({ asset, data }: Props) {
    if (asset && asset.path !== this.state.path) {
      this.setState({
        path: asset.path,
      });
      if (this.props.onChange) {
        this.props.onChange({
          target: {
            id: 'path',
            value: asset.path,
          },
        });
      }
    }
    if (data) {
      this.setState({ ...data });
    }
  }

  onChange: (e: OnChangeEvent) => void;
  onChange(e: OnChangeEvent) {
    const { target, option } = e;
    if (option) {
      this.setState({
        [`${target.id}`]: option,
      });
    } else {
      this.setState({
        [`${target.id}`]: target.value,
      });
    }
    if (this.props.onChange) {
      this.props.onChange(e);
    }
  }

  onChangeAssetType: () => void;
  onChangeAssetType() {
    const { assetType } = this.state;
    let newType = 'href';
    if (assetType === 'href') {
      newType = 'path';
    }
    const event = {
      target: {
        id: 'assetType',
        value: newType,
      },
    };
    if (this.props.onChange) {
      this.props.onChange(event);
    }
    this.setState({
      assetType: newType,
    });
  }

  onSubmit: (event: SyntheticInputEvent) => void;
  onSubmit(event: SyntheticInputEvent) {
    event.preventDefault();
    if (this.formIsValid() && this.props.onSubmit) {
      if (this.props.onSubmit) {
        this.props.onSubmit(event);
      }
    }
  }

  props: Props;

  formIsValid: () => boolean;
  formIsValid() {
    const { path, label, href, assetType } = this.state;
    const hrefError = validation.validUrl(href) === true
      ? null
      : 'Please enter a valid url, containing a protocol such as http://';
    const pathError = validation.validPath(path)
      ? null
      : 'Please enter a valid path';
    const labelError = validation.validLength(label)
      ? null
      : 'Please enter a valid label';
    this.setState({
      error: {
        href: hrefError,
        path: pathError,
        label: labelError,
      },
    });
    if (assetType === 'path') {
      return pathError === null && labelError === null;
    }
    return hrefError === null && labelError === null;
  }

  render() {
    const { children, pad } = this.props;
    const {
      label,
      primary,
      path,
      buttonType,
      icon,
      assetType,
      href,
      error,
    } = this.state;
    return (
      <Box colorIndex="light-2" pad={pad ? 'medium' : 'none'}>
        <Form>
          <FormFields>
            <fieldset>
              <FormField
                label="Label"
                error={error.label}
                help="Enter a label that will appear on the button"
                htmlFor="label"
              >
                <input
                  id="label"
                  name="label"
                  type="text"
                  value={label}
                  onChange={this.onChange}
                />
              </FormField>
              <FormField
                error={assetType === 'path' ? error.path : error.href}
                help={assetType === 'path'
                  ? 'Enter a path to an internal asset, such as /uploads/image.png'
                  : 'Enter a url to an external site, such as http://google.com/image.png'}
                label="Link"
                htmlFor={assetType === 'path' ? 'path' : 'href'}
              >
                {assetType === 'path' ?
                  <input
                    id="path"
                    name="path"
                    type="text"
                    value={path}
                    onChange={this.onChange}
                  />
                :
                  <input
                    id="href"
                    name="href"
                    type="text"
                    value={href}
                    onChange={this.onChange}
                  />
                }
              </FormField>
              {primary && buttonType === 'Anchor' &&
                <FormField
                  label="CTA Icon"
                  htmlFor="icon"
                  help="Add an optional icon to the CTA."
                >
                  <Select
                    onChange={this.onChange}
                    value={icon || 'primary'}
                    options={Object.keys(iconMap)}
                    name="icon"
                    id="icon"
                  />
                </FormField>
              }
              <FormField
                label="Asset Link Type"
                help="What type of asset should the button link to?"
              >
                <Box direction="row" pad={{ vertical: 'small', horizontal: 'medium' }}>
                  <RadioButton
                    id="internal"
                    label="Internal Asset"
                    checked={(assetType === 'path')}
                    onChange={this.onChangeAssetType}
                    name="internal"
                  />
                  <RadioButton
                    id="external"
                    checked={(assetType !== 'path')}
                    label="External URL"
                    onChange={this.onChangeAssetType}
                    name="external"
                  />
                </Box>
              </FormField>
              <FormField
                label="Button Type"
                htmlFor="buttonType"
                help="What type of button would you like to use?  Button or Anchor?"
              >
                <Select
                  onChange={this.onChange}
                  value={buttonType || ''}
                  options={['Button', 'Anchor']}
                  name="buttonType"
                  id="buttonType"
                />
              </FormField>
              <FormField
                label="Primary"
                htmlFor="primary"
                help={buttonType === 'Anchor'
                  ? 'Should the anchor have the arrow icon in front of it?'
                  : 'Should the button be filled in with the brand color?'
                }
              >
                <Select
                  onChange={this.onChange}
                  value={primary || ''}
                  options={['True', 'False']}
                  name="primary"
                  id="primary"
                />
              </FormField>
            </fieldset>
            {assetType === 'path' && children && children}
          </FormFields>
          <Footer pad="medium">
            <Button
              onClick={this.onSubmit}
              type="submit"
              label="Done"
            />
          </Footer>
        </Form>
      </Box>
    );
  }
}

<commit_msg>Fix: issue where the cta icon would persist even when primary was false<commit_after>/* @flow */
import React from 'react';
import Box from 'grommet/components/Box';
import Form from 'grommet/components/Form';
import FormFields from 'grommet/components/FormFields';
import FormField from 'grommet/components/FormField';
import Select from 'grommet/components/Select';
import Button from 'grommet/components/Button';
import Footer from 'grommet/components/Footer';
import RadioButton from 'grommet/components/RadioButton';
import validation from './validation';
import type { OnChangeEvent } from '../../types';
import iconMap from './iconMap';

export type AssetType = 'path' | 'href';
export type ButtonType = 'Button' | 'Anchor';
export type Primary = 'True' | 'False';
export type IconType =
  'primary' |
  'email' |
  'chat' |
  'share' |
  'attachment' |
  'word' |
  'zip' |
  'document download' |
  'download' |
  'play' |
  'print';

type State = {
  label: ?string,
  primary: ?Primary,
  path: ?string,
  href: ?string,
  buttonType: ?string,
  assetType: AssetType,
  icon?: IconType,
  error: {
    label: ?string,
    path: ?string,
    href: ?string,
  }
};

// Weird issue with unwrapping props.data || props, showing unused props.
// Disable for now.
/* eslint-disable react/no-unused-prop-types */
type Props = {
  pad?: boolean,
  onSubmit?: Function,
  onChange?: Function,
  label?: string,
  children?: React$Element<any>, // eslint-disable-line
  primary?: Primary,
  buttonType?: ButtonType,
  icon?: IconType,
  href?: string,
  assetType?: AssetType,
  path?: string,
  asset?: {
    path?: string,
  },
  data?: Object
};
/* eslint-enable react/no-unused-prop-types */

export default class BlockButtonForm extends React.Component {
  static defaultProps = {
    pad: false,
  }

  constructor(props: Props) {
    super(props);
    this.onChange = this.onChange.bind(this);
    this.onSubmit = this.onSubmit.bind(this);
    this.formIsValid = this.formIsValid.bind(this);
    this.onChangeAssetType = this.onChangeAssetType.bind(this);
    const { path, primary, label, buttonType, href, assetType, icon } = props.data || props;
    const labelInput = label || '';
    const primaryInput = primary || 'True';
    const buttonTypeInput = buttonType || 'Button';
    const iconInput = icon || 'primary';
    const pathInput = path || '';
    const hrefInput = href || '';
    const assetTypeInput = assetType || 'path';
    this.state = {
      label: labelInput,
      primary: primaryInput,
      path: pathInput,
      buttonType: buttonTypeInput,
      icon: iconInput,
      href: hrefInput,
      assetType: assetTypeInput,
      error: {
        label: null,
        path: null,
        href: null,
      },
    };
  }

  state: State;

  componentWillReceiveProps({ asset, data }: Props) {
    if (asset && asset.path !== this.state.path) {
      this.setState({
        path: asset.path,
      });
      if (this.props.onChange) {
        this.props.onChange({
          target: {
            id: 'path',
            value: asset.path,
          },
        });
      }
    }
    if (data) {
      this.setState({ ...data });
    }
  }

  onChange: (e: OnChangeEvent) => void;
  onChange(e: OnChangeEvent) {
    const { target, option } = e;
    if (option) {
      this.setState({
        [`${target.id}`]: option,
      });
    } else {
      this.setState({
        [`${target.id}`]: target.value,
      });
    }
    if (this.props.onChange) {
      this.props.onChange(e);
    }
  }

  onChangeAssetType: () => void;
  onChangeAssetType() {
    const { assetType } = this.state;
    let newType = 'href';
    if (assetType === 'href') {
      newType = 'path';
    }
    const event = {
      target: {
        id: 'assetType',
        value: newType,
      },
    };
    if (this.props.onChange) {
      this.props.onChange(event);
    }
    this.setState({
      assetType: newType,
    });
  }

  onSubmit: (event: SyntheticInputEvent) => void;
  onSubmit(event: SyntheticInputEvent) {
    event.preventDefault();
    if (this.formIsValid() && this.props.onSubmit) {
      if (this.props.onSubmit) {
        this.props.onSubmit(event);
      }
    }
  }

  props: Props;

  formIsValid: () => boolean;
  formIsValid() {
    const { path, label, href, assetType } = this.state;
    const hrefError = validation.validUrl(href) === true
      ? null
      : 'Please enter a valid url, containing a protocol such as http://';
    const pathError = validation.validPath(path)
      ? null
      : 'Please enter a valid path';
    const labelError = validation.validLength(label)
      ? null
      : 'Please enter a valid label';
    this.setState({
      error: {
        href: hrefError,
        path: pathError,
        label: labelError,
      },
    });
    if (assetType === 'path') {
      return pathError === null && labelError === null;
    }
    return hrefError === null && labelError === null;
  }

  render() {
    const { children, pad } = this.props;
    const {
      label,
      primary,
      path,
      buttonType,
      icon,
      assetType,
      href,
      error,
    } = this.state;
    return (
      <Box colorIndex="light-2" pad={pad ? 'medium' : 'none'}>
        <Form>
          <FormFields>
            <fieldset>
              <FormField
                label="Label"
                error={error.label}
                help="Enter a label that will appear on the button"
                htmlFor="label"
              >
                <input
                  id="label"
                  name="label"
                  type="text"
                  value={label}
                  onChange={this.onChange}
                />
              </FormField>
              <FormField
                error={assetType === 'path' ? error.path : error.href}
                help={assetType === 'path'
                  ? 'Enter a path to an internal asset, such as /uploads/image.png'
                  : 'Enter a url to an external site, such as http://google.com/image.png'}
                label="Link"
                htmlFor={assetType === 'path' ? 'path' : 'href'}
              >
                {assetType === 'path' ?
                  <input
                    id="path"
                    name="path"
                    type="text"
                    value={path}
                    onChange={this.onChange}
                  />
                :
                  <input
                    id="href"
                    name="href"
                    type="text"
                    value={href}
                    onChange={this.onChange}
                  />
                }
              </FormField>
              {primary === 'True' && buttonType === 'Anchor' &&
                <FormField
                  label="CTA Icon"
                  htmlFor="icon"
                  help="Add an optional icon to the CTA."
                >
                  <Select
                    onChange={this.onChange}
                    value={icon || 'primary'}
                    options={Object.keys(iconMap)}
                    name="icon"
                    id="icon"
                  />
                </FormField>
              }
              <FormField
                label="Asset Link Type"
                help="What type of asset should the button link to?"
              >
                <Box direction="row" pad={{ vertical: 'small', horizontal: 'medium' }}>
                  <RadioButton
                    id="internal"
                    label="Internal Asset"
                    checked={(assetType === 'path')}
                    onChange={this.onChangeAssetType}
                    name="internal"
                  />
                  <RadioButton
                    id="external"
                    checked={(assetType !== 'path')}
                    label="External URL"
                    onChange={this.onChangeAssetType}
                    name="external"
                  />
                </Box>
              </FormField>
              <FormField
                label="Button Type"
                htmlFor="buttonType"
                help="What type of button would you like to use?  Button or Anchor?"
              >
                <Select
                  onChange={this.onChange}
                  value={buttonType || ''}
                  options={['Button', 'Anchor']}
                  name="buttonType"
                  id="buttonType"
                />
              </FormField>
              <FormField
                label="Primary"
                htmlFor="primary"
                help={buttonType === 'Anchor'
                  ? 'Should the anchor have the arrow icon in front of it?'
                  : 'Should the button be filled in with the brand color?'
                }
              >
                <Select
                  onChange={this.onChange}
                  value={primary || ''}
                  options={['True', 'False']}
                  name="primary"
                  id="primary"
                />
              </FormField>
            </fieldset>
            {assetType === 'path' && children && children}
          </FormFields>
          <Footer pad="medium">
            <Button
              onClick={this.onSubmit}
              type="submit"
              label="Done"
            />
          </Footer>
        </Form>
      </Box>
    );
  }
}

<|endoftext|>


<commit_before><commit_msg>fix standard nits<commit_after><|endoftext|>


<commit_before>// Ractive-Backbone tests
// ======================
//
// This loads in the render.json sample file and checks that each compiled
// template, in combination with the sample data, produces the expected
// HTML.
//
// TODO: add moar samples

(function () {

	var fixture;

	test( 'Ractive.adaptors.Backbone exists and is an object', function ( t ) {
		t.ok( typeof Ractive.adaptors.Backbone === 'object' );
	});

}());<commit_msg>Add a simple assertion for the existence of Backbone<commit_after>// Ractive-Backbone tests
// ======================
//
// This loads in the render.json sample file and checks that each compiled
// template, in combination with the sample data, produces the expected
// HTML.
//
// TODO: add moar samples

(function () {

	var fixture;

	var Adaptor = Ractive.adaptors.Backbone;

	test( 'Ractive.adaptors.Backbone exists and is an object', function ( t ) {
		t.ok( typeof Ractive.adaptors.Backbone === 'object' );
	});

	test( 'Backbone exists', function ( t ) {
		t.equal( typeof Backbone, 'object' );
		t.equal( typeof Backbone.Model, 'function' );
	});

}());
<|endoftext|>


<commit_before>$(function() {
"use strict";

function vercmp(a, b) {
  var re = /^([0-9]*)([^0-9]*)(.*)$/;
  var abits, bbits;
  var anum, bnum;
  while (a.length && b.length) {
    abits = re.exec(a);
    bbits = re.exec(b);
    if ((abits[1] === '') != (bbits[1] === '')) {
      return abits[1] ? -1 : 1;
    }
    if (abits[1] !== '') {
      anum = parseInt(abits[1]);
      bnum = parseInt(bbits[1])
      if (anum !== bnum)
        return anum - bnum;
    }

    if (abits[2] !== bbits[2]) {
      return abits[2] < bbits[2] ? -1 : 1
    }

    a = abits[3];
    b = bbits[3];
  }

  return a.length - b.length;
}

function shorten(ref) {
  var match = /^refs\/(tags|branches)\/(.*)/.exec(ref);
  if (match)
    return match[2];
  match = /^([0-9a-f]{8})[0-9a-f]+$/.exec(ref);
  if (match)
    return match[1];
  return ref;
}

var Match = Backbone.Model.extend({
  initialize: function() {
    this.get('contexts').forEach(function (ctx) {
        ctx.paths.sort(function (a,b) {return vercmp(a.ref, b.ref);})
    });
    this.get('contexts').sort(function (a,b) {
        return vercmp(a.paths[0].ref, b.paths[0].ref);
    });
    this.set({
               context: this.get('contexts')[0],
               path: this.get('contexts')[0].paths[0]
             });
  },
  url: function() {
    var tree = this.get('path').ref;
    var pieces, name = '', ref;
    pieces = tree.split(":");
    if (pieces.length == 1) {
      ref = tree;
    } else {
      name = pieces[0];
      ref = pieces[1];
    }
    return ""
    return "https://github.com/" + CodesearchUI.github_repos[this.get('backend')][name] +
      "/blob/" + shorten(ref) + "/" + this.get('path').path +
      "#L" + this.get('context').lno;
  }
});

var MatchView = Backbone.View.extend({
  tagName: 'div',
  initialize: function() {
    this.model.on('change', this.render, this);
  },
  render: function() {
    var div = this._render();
    this.$el.empty();
    this.$el.append(div);
    return this;
  },
  _render: function() {
    var h = new HTMLFactory();
    var ctx = this.model.get('context');
    var i;
    var ctx_before = [], ctx_after = [];
    for (i = 0; i < ctx.context_before.length; i ++) {
      ctx_before.unshift(h.div([
                                 h.span({cls: 'lno'}, [ctx.lno - i - 1, ":"]),
                                 ctx.context_before[i]
                               ]));
    }
    for (i = 0; i < ctx.context_after.length; i ++) {
      ctx_after.push(h.div([
                             h.span({cls: 'lno'}, [ctx.lno + i + 1, ":"]),
                             ctx.context_after[i]
                           ]));
    }
    var line = this.model.get('line');
    var bounds = this.model.get('bounds');
    var pieces = [line.substring(0, bounds[0]),
                  line.substring(bounds[0], bounds[1]),
                  line.substring(bounds[1])];

    return h.div({cls: 'match'}, [
        h.div({}, [
          h.span({cls: 'label'}, [
            h.a({href: this.model.url()}, [
                  shorten(this.model.get('path').ref),
                  ":",
                  this.model.get('path').path])])]),
        h.div({cls: 'contents'}, [
                ctx_before,
                h.div({cls: 'matchline'}, [
                  h.span({cls: 'lno'}, [ctx.lno + ":"]),
                  pieces[0],
                  h.span({cls: 'matchstr'}, [pieces[1]]),
                  pieces[2]
                ]),
                ctx_after]),
        this.render_contexts(h)]);
  },
  render_contexts: function(h) {
    var self = this;
    if (this.model.get('contexts').length == 1 &&
        this.model.get('context').paths.length == 1)
      return [];
    return h.div({cls: 'contexts'}, [
          h.span({cls: 'label'}, ["Also in:"]),
          h.ul({},
          this.model.get('contexts').map(function (ctx) {
            return h.li(ctx === self.model.get('context') ? {cls: 'selected'} : {}, [
                h.a({href: "#",
                     click: _.bind(self.switch_context, self, ctx)}, [
                shorten(ctx.paths[0].ref)]),
                ctx.paths.length > 1 ? (" +" + (ctx.paths.length - 1) + " identical") : [],
            ]);
          }))])
  },
  switch_context: function(ctx) {
    this.model.set({
                     context: ctx,
                     path: ctx.paths[0]
                   });
    return false;
  }
});

var MatchSet = Backbone.Collection.extend({
  model: Match
});

var SearchState = Backbone.Model.extend({
  defaults: function() {
    return {
      displaying: null,
      error: null,
      time: null,
      why: null
    };
  },

  initialize: function() {
    this.search_map = {};
    this.matches = new MatchSet();
    this.search_id = 0;
    this.on('change:displaying', this.new_search, this);
  },

  new_search: function() {
    this.set({
        error: null,
        time: null,
        why: null
    });
    this.matches.reset();
    for (var k in this.search_map) {
      if (parseInt(k) < this.get('displaying'))
        delete this.search_map[k];
    }
  },

  next_id: function() {
    return ++this.search_id;
  },

  dispatch: function (search) {
    var id = this.next_id();
    search.id = id;
    this.search_map[id] = {
      q: search.line,
      file: search.file,
      backend: search.backend,
      repo: search.repo
    };
    if (!search.line.length)
      this.set('displaying', id);
  },

  url: function() {
    var q = {};
    var current = this.search_map[this.get('displaying')];
    if (!current)
      return '/search';
    var base = '/search';

    ['q','file', 'repo'].forEach(function (key) {
      if(current[key])
        q[key] = current[key];
    });

    if (q.backend) {
      base += "/" + q.backend
    } else if (CodesearchUI.input_backend) {
      base += "/" + CodesearchUI.input_backend.val();
    }
    var qs = $.param(q);
    return base + (qs ? "?" + qs : "");
  },

  handle_error: function (search, error) {
    if (search === this.search_id) {
      this.set('displaying', search);
      this.set('error', error);
    }
  },

  handle_match: function (search, match) {
    if (search < this.get('displaying'))
      return false;
    this.set('displaying', search);
    this.matches.add({
                       backend: this.search_map[search].backend,
                       line: match.line,
                       bounds: match.bounds,
                       contexts: match.contexts
                     });
  },
  handle_done: function (search, time, why) {
    this.set('displaying', search);
    this.set({time: time, why: why});
  }
});

var MatchesView = Backbone.View.extend({
  el: $('#results'),
  initialize: function() {
    this.model.matches.bind('add', this.add_one, this);
    this.model.matches.bind('reset', this.add_all, this);
  },
  add_one: function(model) {
    var view = new MatchView({model: model});
    this.$el.append(view.render().el);
  },
  add_all: function() {
    this.$el.empty();
    this.model.matches.each(this.add_one);
  }
});

var ResultView = Backbone.View.extend({
  el: $('#resultarea'),
  initialize: function() {
    this.matches_view = new MatchesView({model: this.model});
    this.permalink = this.$('#permalink');
    this.results   = this.$('#numresults');
    this.errorbox  = $('#regex-error');
    this.time      = this.$('#searchtime');
    this.last_url  = null;

    this.model.on('all', this.render, this);
    this.model.matches.on('all', this.render, this);
  },

  render: function() {
    if (this.model.get('error')) {
      this.errorbox.find('#errortext').text(this.model.get('error'));
      this.errorbox.show();
    } else {
      this.errorbox.hide()
    }

    var url = this.model.url();
    if (this.last_url !== url ) {
      this.permalink.attr('href', url);
      if (history.replaceState) {
        history.replaceState(null, '', url);
      }
      this.last_url = url;
    }

    if (this.model.search_map[this.model.get('displaying')].q === '' ||
       this.model.get('error')) {
      this.$el.hide();
      return this;
    }

    this.$el.show();

    if (this.model.get('time')) {
      this.$('#searchtimebox').show();
      var time = this.model.get('time');
      this.time.text((time/1000) + "s")
    } else {
      this.$('#searchtimebox').hide();
    }

    var results = '' + this.model.matches.size();
    if (this.model.get('why') === 'limit')
      results = results + '+';
    this.results.text(results);

    return this;
  }
});

var CodesearchUI = function() {
  return {
    state: new SearchState(),
    view: null,
    onload: function() {
      if (CodesearchUI.input)
        return;

      CodesearchUI.view = new ResultView({model: CodesearchUI.state});

      CodesearchUI.input      = $('#searchbox');
      CodesearchUI.input_file = $('#filebox');
      CodesearchUI.input_repo = $('#repobox');
      CodesearchUI.input_backend = $('#backend');
      if (CodesearchUI.input_backend.length == 0)
        CodesearchUI.input_backend = null;
      CodesearchUI.parse_url();

      CodesearchUI.input.keydown(CodesearchUI.keypress);
      CodesearchUI.input_file.keydown(CodesearchUI.keypress);
      CodesearchUI.input_repo.keydown(CodesearchUI.keypress);
      CodesearchUI.input.bind('paste', CodesearchUI.keypress);
      CodesearchUI.input_file.bind('paste', CodesearchUI.keypress);
      CodesearchUI.input_repo.bind('paste', CodesearchUI.keypress);
      CodesearchUI.input.focus();
      if (CodesearchUI.input_backend)
        CodesearchUI.input_backend.change(CodesearchUI.select_backend);

      Codesearch.connect(CodesearchUI);
    },
    parse_url: function() {
      var parms = CodesearchUI.parse_query_params();
      if (parms.q)
        CodesearchUI.input.val(parms.q);
      if (parms.file)
        CodesearchUI.input_file.val(parms.file);
      if (parms.repo)
        CodesearchUI.input_repo.val(parms.repo);
      var backend = null;
      if (parms.backend)
        backend = parms.backend;
      var m;
      if (m = (new RegExp("/search/(\\w+)/?").exec(window.location.pathname))) {
        backend = m[1];
      }
      if (backend && CodesearchUI.input_backend)
        CodesearchUI.input_backend.val(backend);
      setTimeout(CodesearchUI.select_backend, 0);
    },
    parse_query_params: function() {
      var urlParams = {};
      var e,
          a = /\+/g,
          r = /([^&=]+)=?([^&]*)/g,
          d = function (s) { return decodeURIComponent(s.replace(a, " ")); },
          q = window.location.search.substring(1);

      while (e = r.exec(q))
        urlParams[d(e[1])] = d(e[2]);
      return urlParams;
    },
    on_connect: function() {
      CodesearchUI.newsearch();
    },
    select_backend: function() {
      if (!CodesearchUI.input_backend)
        return;
      var backend = CodesearchUI.input_backend.val();
      if (Object.keys(CodesearchUI.github_repos[backend]).length == 1) {
        CodesearchUI.input_repo.val('');
        $('#reposel').hide();
      } else {
        $('#reposel').show();
      }
      CodesearchUI.keypress();
    },
    keypress: function() {
      setTimeout(CodesearchUI.newsearch, 0);
    },
    newsearch: function() {
      var search = {
        line: CodesearchUI.input.val(),
        file: CodesearchUI.input_file.val(),
        repo: CodesearchUI.input_repo.val(),
      };
      if(CodesearchUI.input_backend)
        search.backend = CodesearchUI.input_backend.val();
      CodesearchUI.state.dispatch(search);
      if (search.line.length > 0)
        Codesearch.new_search(search);
    },
    error: function(search, error) {
      CodesearchUI.state.handle_error(search, error);
    },
    match: function(search, match) {
      CodesearchUI.state.handle_match(search, match);
    },
    search_done: function(search, time, why) {
      CodesearchUI.state.handle_done(search, time, why);
    },
    github_repos: {}
  };
}();
CodesearchUI.onload();
window.CodesearchUI = CodesearchUI;
});
<commit_msg>Fix github links in the single-backend case.<commit_after>$(function() {
"use strict";

function vercmp(a, b) {
  var re = /^([0-9]*)([^0-9]*)(.*)$/;
  var abits, bbits;
  var anum, bnum;
  while (a.length && b.length) {
    abits = re.exec(a);
    bbits = re.exec(b);
    if ((abits[1] === '') != (bbits[1] === '')) {
      return abits[1] ? -1 : 1;
    }
    if (abits[1] !== '') {
      anum = parseInt(abits[1]);
      bnum = parseInt(bbits[1])
      if (anum !== bnum)
        return anum - bnum;
    }

    if (abits[2] !== bbits[2]) {
      return abits[2] < bbits[2] ? -1 : 1
    }

    a = abits[3];
    b = bbits[3];
  }

  return a.length - b.length;
}

function shorten(ref) {
  var match = /^refs\/(tags|branches)\/(.*)/.exec(ref);
  if (match)
    return match[2];
  match = /^([0-9a-f]{8})[0-9a-f]+$/.exec(ref);
  if (match)
    return match[1];
  return ref;
}

var Match = Backbone.Model.extend({
  initialize: function() {
    this.get('contexts').forEach(function (ctx) {
        ctx.paths.sort(function (a,b) {return vercmp(a.ref, b.ref);})
    });
    this.get('contexts').sort(function (a,b) {
        return vercmp(a.paths[0].ref, b.paths[0].ref);
    });
    this.set({
               context: this.get('contexts')[0],
               path: this.get('contexts')[0].paths[0]
             });
  },
  url: function() {
    var tree = this.get('path').ref;
    var pieces, name = '', ref;
    pieces = tree.split(":");
    if (pieces.length == 1) {
      ref = tree;
    } else {
      name = pieces[0];
      ref = pieces[1];
    }
    var repo_map;
    if (this.get('backend'))
      repo_map = CodesearchUI.github_repos[this.get('backend')]
    else
      repo_map = CodesearchUI.github_repos[Object.keys(CodesearchUI.github_repos)[0]]
    return "https://github.com/" + repo_map[name] +
      "/blob/" + shorten(ref) + "/" + this.get('path').path +
      "#L" + this.get('context').lno;
  }
});

var MatchView = Backbone.View.extend({
  tagName: 'div',
  initialize: function() {
    this.model.on('change', this.render, this);
  },
  render: function() {
    var div = this._render();
    this.$el.empty();
    this.$el.append(div);
    return this;
  },
  _render: function() {
    var h = new HTMLFactory();
    var ctx = this.model.get('context');
    var i;
    var ctx_before = [], ctx_after = [];
    for (i = 0; i < ctx.context_before.length; i ++) {
      ctx_before.unshift(h.div([
                                 h.span({cls: 'lno'}, [ctx.lno - i - 1, ":"]),
                                 ctx.context_before[i]
                               ]));
    }
    for (i = 0; i < ctx.context_after.length; i ++) {
      ctx_after.push(h.div([
                             h.span({cls: 'lno'}, [ctx.lno + i + 1, ":"]),
                             ctx.context_after[i]
                           ]));
    }
    var line = this.model.get('line');
    var bounds = this.model.get('bounds');
    var pieces = [line.substring(0, bounds[0]),
                  line.substring(bounds[0], bounds[1]),
                  line.substring(bounds[1])];

    return h.div({cls: 'match'}, [
        h.div({}, [
          h.span({cls: 'label'}, [
            h.a({href: this.model.url()}, [
                  shorten(this.model.get('path').ref),
                  ":",
                  this.model.get('path').path])])]),
        h.div({cls: 'contents'}, [
                ctx_before,
                h.div({cls: 'matchline'}, [
                  h.span({cls: 'lno'}, [ctx.lno + ":"]),
                  pieces[0],
                  h.span({cls: 'matchstr'}, [pieces[1]]),
                  pieces[2]
                ]),
                ctx_after]),
        this.render_contexts(h)]);
  },
  render_contexts: function(h) {
    var self = this;
    if (this.model.get('contexts').length == 1 &&
        this.model.get('context').paths.length == 1)
      return [];
    return h.div({cls: 'contexts'}, [
          h.span({cls: 'label'}, ["Also in:"]),
          h.ul({},
          this.model.get('contexts').map(function (ctx) {
            return h.li(ctx === self.model.get('context') ? {cls: 'selected'} : {}, [
                h.a({href: "#",
                     click: _.bind(self.switch_context, self, ctx)}, [
                shorten(ctx.paths[0].ref)]),
                ctx.paths.length > 1 ? (" +" + (ctx.paths.length - 1) + " identical") : [],
            ]);
          }))])
  },
  switch_context: function(ctx) {
    this.model.set({
                     context: ctx,
                     path: ctx.paths[0]
                   });
    return false;
  }
});

var MatchSet = Backbone.Collection.extend({
  model: Match
});

var SearchState = Backbone.Model.extend({
  defaults: function() {
    return {
      displaying: null,
      error: null,
      time: null,
      why: null
    };
  },

  initialize: function() {
    this.search_map = {};
    this.matches = new MatchSet();
    this.search_id = 0;
    this.on('change:displaying', this.new_search, this);
  },

  new_search: function() {
    this.set({
        error: null,
        time: null,
        why: null
    });
    this.matches.reset();
    for (var k in this.search_map) {
      if (parseInt(k) < this.get('displaying'))
        delete this.search_map[k];
    }
  },

  next_id: function() {
    return ++this.search_id;
  },

  dispatch: function (search) {
    var id = this.next_id();
    search.id = id;
    this.search_map[id] = {
      q: search.line,
      file: search.file,
      backend: search.backend,
      repo: search.repo
    };
    if (!search.line.length)
      this.set('displaying', id);
  },

  url: function() {
    var q = {};
    var current = this.search_map[this.get('displaying')];
    if (!current)
      return '/search';
    var base = '/search';

    ['q','file', 'repo'].forEach(function (key) {
      if(current[key])
        q[key] = current[key];
    });

    if (q.backend) {
      base += "/" + q.backend
    } else if (CodesearchUI.input_backend) {
      base += "/" + CodesearchUI.input_backend.val();
    }
    var qs = $.param(q);
    return base + (qs ? "?" + qs : "");
  },

  handle_error: function (search, error) {
    if (search === this.search_id) {
      this.set('displaying', search);
      this.set('error', error);
    }
  },

  handle_match: function (search, match) {
    if (search < this.get('displaying'))
      return false;
    this.set('displaying', search);
    this.matches.add({
                       backend: this.search_map[search].backend,
                       line: match.line,
                       bounds: match.bounds,
                       contexts: match.contexts
                     });
  },
  handle_done: function (search, time, why) {
    this.set('displaying', search);
    this.set({time: time, why: why});
  }
});

var MatchesView = Backbone.View.extend({
  el: $('#results'),
  initialize: function() {
    this.model.matches.bind('add', this.add_one, this);
    this.model.matches.bind('reset', this.add_all, this);
  },
  add_one: function(model) {
    var view = new MatchView({model: model});
    this.$el.append(view.render().el);
  },
  add_all: function() {
    this.$el.empty();
    this.model.matches.each(this.add_one);
  }
});

var ResultView = Backbone.View.extend({
  el: $('#resultarea'),
  initialize: function() {
    this.matches_view = new MatchesView({model: this.model});
    this.permalink = this.$('#permalink');
    this.results   = this.$('#numresults');
    this.errorbox  = $('#regex-error');
    this.time      = this.$('#searchtime');
    this.last_url  = null;

    this.model.on('all', this.render, this);
    this.model.matches.on('all', this.render, this);
  },

  render: function() {
    if (this.model.get('error')) {
      this.errorbox.find('#errortext').text(this.model.get('error'));
      this.errorbox.show();
    } else {
      this.errorbox.hide()
    }

    var url = this.model.url();
    if (this.last_url !== url ) {
      this.permalink.attr('href', url);
      if (history.replaceState) {
        history.replaceState(null, '', url);
      }
      this.last_url = url;
    }

    if (this.model.search_map[this.model.get('displaying')].q === '' ||
       this.model.get('error')) {
      this.$el.hide();
      return this;
    }

    this.$el.show();

    if (this.model.get('time')) {
      this.$('#searchtimebox').show();
      var time = this.model.get('time');
      this.time.text((time/1000) + "s")
    } else {
      this.$('#searchtimebox').hide();
    }

    var results = '' + this.model.matches.size();
    if (this.model.get('why') === 'limit')
      results = results + '+';
    this.results.text(results);

    return this;
  }
});

var CodesearchUI = function() {
  return {
    state: new SearchState(),
    view: null,
    onload: function() {
      if (CodesearchUI.input)
        return;

      CodesearchUI.view = new ResultView({model: CodesearchUI.state});

      CodesearchUI.input      = $('#searchbox');
      CodesearchUI.input_file = $('#filebox');
      CodesearchUI.input_repo = $('#repobox');
      CodesearchUI.input_backend = $('#backend');
      if (CodesearchUI.input_backend.length == 0)
        CodesearchUI.input_backend = null;
      CodesearchUI.parse_url();

      CodesearchUI.input.keydown(CodesearchUI.keypress);
      CodesearchUI.input_file.keydown(CodesearchUI.keypress);
      CodesearchUI.input_repo.keydown(CodesearchUI.keypress);
      CodesearchUI.input.bind('paste', CodesearchUI.keypress);
      CodesearchUI.input_file.bind('paste', CodesearchUI.keypress);
      CodesearchUI.input_repo.bind('paste', CodesearchUI.keypress);
      CodesearchUI.input.focus();
      if (CodesearchUI.input_backend)
        CodesearchUI.input_backend.change(CodesearchUI.select_backend);

      Codesearch.connect(CodesearchUI);
    },
    parse_url: function() {
      var parms = CodesearchUI.parse_query_params();
      if (parms.q)
        CodesearchUI.input.val(parms.q);
      if (parms.file)
        CodesearchUI.input_file.val(parms.file);
      if (parms.repo)
        CodesearchUI.input_repo.val(parms.repo);
      var backend = null;
      if (parms.backend)
        backend = parms.backend;
      var m;
      if (m = (new RegExp("/search/(\\w+)/?").exec(window.location.pathname))) {
        backend = m[1];
      }
      if (backend && CodesearchUI.input_backend)
        CodesearchUI.input_backend.val(backend);
      setTimeout(CodesearchUI.select_backend, 0);
    },
    parse_query_params: function() {
      var urlParams = {};
      var e,
          a = /\+/g,
          r = /([^&=]+)=?([^&]*)/g,
          d = function (s) { return decodeURIComponent(s.replace(a, " ")); },
          q = window.location.search.substring(1);

      while (e = r.exec(q))
        urlParams[d(e[1])] = d(e[2]);
      return urlParams;
    },
    on_connect: function() {
      CodesearchUI.newsearch();
    },
    select_backend: function() {
      if (!CodesearchUI.input_backend)
        return;
      var backend = CodesearchUI.input_backend.val();
      if (Object.keys(CodesearchUI.github_repos[backend]).length == 1) {
        CodesearchUI.input_repo.val('');
        $('#reposel').hide();
      } else {
        $('#reposel').show();
      }
      CodesearchUI.keypress();
    },
    keypress: function() {
      setTimeout(CodesearchUI.newsearch, 0);
    },
    newsearch: function() {
      var search = {
        line: CodesearchUI.input.val(),
        file: CodesearchUI.input_file.val(),
        repo: CodesearchUI.input_repo.val(),
      };
      if (CodesearchUI.input_backend)
        search.backend = CodesearchUI.input_backend.val();
      CodesearchUI.state.dispatch(search);
      if (search.line.length > 0)
        Codesearch.new_search(search);
    },
    error: function(search, error) {
      CodesearchUI.state.handle_error(search, error);
    },
    match: function(search, match) {
      CodesearchUI.state.handle_match(search, match);
    },
    search_done: function(search, time, why) {
      CodesearchUI.state.handle_done(search, time, why);
    },
    github_repos: {}
  };
}();
CodesearchUI.onload();
window.CodesearchUI = CodesearchUI;
});
<|endoftext|>


<commit_before>$(document).ready(function() {
  // Initialize datetimepickers.
  $('#datetimepicker-beginning').datetimepicker();
  $('#datetimepicker-end').datetimepicker({
    useCurrent: false
  });

  // Link the two datetimepickers together to ensure that no illegal
  // date ranges can be selected.
  $('#datetimepicker-beginning').on('dp.change', function(e) {
    $('#datetimepicker-end').data('DateTimePicker').minDate(e.date);
  });
  $('#datetimepicker-end').on('dp.change', function(e) {
    $('#datetimepicker-beginning').data('DateTimePicker').maxDate(e.date);
  });

  // Remove filter that the clicked remove button is associated with,
  // and re-enable the dropdown menu item for that filter.
  $('.remove-filter').click(function() {
    $(this).parent().toggle();
    var parentId = $(this).parent().attr('id');
    $(parentId).find('input').prop('disabled', true);
    $(parentId).find('select').prop('disabled', true);
    var dropdownId = '#' + parentId.replace('control', 'menu');
    $(dropdownId).removeClass('disabled');
    $(dropdownId).removeClass('not-active');
    $(dropdownId).prop('disabled', false);

    // If all filters are now disabled, disable the generate button.
    if ($('.filter-control:enabled').length == 0) {
      $('#generate-button').prop('disabled', true);
    }
  });

  // Disable the dropdown menu item for the selected filter, and toggle
  // the visibility of the filter div to show the filter.
  $('.filter-menu-item').click(function() {
    $(this).prop('disabled', true);
    $(this).addClass('disabled');
    $(this).addClass('not-active');
    var itemId = $(this).attr('id');
    var controlId = '#' + itemId.replace('menu', 'control');
    $(controlId).toggle();
    $(controlId).find('input').prop('disabled', false);
    $(controlId).find('select').prop('disabled', false);

    // Enable generate button.
    $('#generate-button').prop('disabled', false);

    // Check if enabled filter is a search query. If so, the generate button
    // needs to be disabled until text is entered.
    if ($(this).attr('id') == 'search-query-menu') {
      $('#generate-button').prop('disabled', true);
    }
  });

  // Disable generate button until text is entered into search box (if enabled).
  $('#search-query').on('keyup', function() {
    if ($('#search-query').val().length > 0) {
      $('#generate-button').prop('disabled', false);
    } else {
      $('#generate-button').prop('disabled', true);
    }
  });
});
<commit_msg>Added minimum date to beginning datetimepicker and maximum date to end datetimepicker<commit_after>$(document).ready(function() {
  // Initialize datetimepickers.
  $('#datetimepicker-beginning').datetimepicker();
  $('#datetimepicker-end').datetimepicker({
    useCurrent: false
  });

  // Set minimum datetime of beginning time picker to the date of the
  // first ever YouTube video (April 23, 2005).
  // https://youtube.com/watch?v=jNQXAC9IVRw
  $('#datetimepicker-beginning').data('DateTimePicker').minDate(new Date('April 23, 2005'));

  // Set maximum datetime of ending time picker to the current datetime.
  $('#datetimepicker-end').data('DateTimePicker').maxDate(new Date());

  // Link the two datetimepickers together to ensure that no illegal
  // date ranges can be selected.
  $('#datetimepicker-beginning').on('dp.change', function(e) {
    $('#datetimepicker-end').data('DateTimePicker').minDate(e.date);
  });
  $('#datetimepicker-end').on('dp.change', function(e) {
    $('#datetimepicker-beginning').data('DateTimePicker').maxDate(e.date);
  });

  // Remove filter that the clicked remove button is associated with,
  // and re-enable the dropdown menu item for that filter.
  $('.remove-filter').click(function() {
    $(this).parent().toggle();
    var parentId = $(this).parent().attr('id');
    $(parentId).find('input').prop('disabled', true);
    $(parentId).find('select').prop('disabled', true);
    var dropdownId = '#' + parentId.replace('control', 'menu');
    $(dropdownId).removeClass('disabled');
    $(dropdownId).removeClass('not-active');
    $(dropdownId).prop('disabled', false);

    // If all filters are now disabled, disable the generate button.
    if ($('.filter-control:enabled').length == 0) {
      $('#generate-button').prop('disabled', true);
    }
  });

  // Disable the dropdown menu item for the selected filter, and toggle
  // the visibility of the filter div to show the filter.
  $('.filter-menu-item').click(function() {
    $(this).prop('disabled', true);
    $(this).addClass('disabled');
    $(this).addClass('not-active');
    var itemId = $(this).attr('id');
    var controlId = '#' + itemId.replace('menu', 'control');
    $(controlId).toggle();
    $(controlId).find('input').prop('disabled', false);
    $(controlId).find('select').prop('disabled', false);

    // Enable generate button.
    $('#generate-button').prop('disabled', false);

    // Check if enabled filter is a search query. If so, the generate button
    // needs to be disabled until text is entered.
    if ($(this).attr('id') == 'search-query-menu') {
      $('#generate-button').prop('disabled', true);
    }
  });

  // Disable generate button until text is entered into search box (if enabled).
  $('#search-query').on('keyup', function() {
    if ($('#search-query').val().length > 0) {
      $('#generate-button').prop('disabled', false);
    } else {
      $('#generate-button').prop('disabled', true);
    }
  });
});
<|endoftext|>


<commit_before>const ERR = require('async-stacktrace');
const async = require('async');
const fs = require('fs-extra');
const path = require('path');
const Ajv = require('ajv');

const globalLogger = require('./logger');
const config = require('./config').config;
const sqldb = require('./sqldb');
const sql = require('./sql-loader').loadSqlEquiv(__filename);

let messageSchema = null;

module.exports = function(sqs, queueUrl, receiveCallback, doneCallback) {
    let parsedMessage, jobCanceled, receiptHandle;
    async.series([
        (callback) => {
            globalLogger.info('Waiting for next job...');
            async.doUntil((done) => {
                const params = {
                    MaxNumberOfMessages: 1,
                    QueueUrl: queueUrl,
                    WaitTimeSeconds: 20
                };
                sqs.receiveMessage(params, (err, data) => {
                    if (ERR(err, done)) return;
                    if (!data.Messages) {
                        return done(null, null);
                    }
                    globalLogger.info('Received job!');
                    try {
                        parsedMessage = JSON.parse(data.Messages[0].Body);
                        receiptHandle = data.Messages[0].ReceiptHandle;
                    } catch (e) {
                        return done(e);
                    }
                    return done(null, parsedMessage);
                });
            }, (result) => {
                return !!result;
            }, (err) => {
                if (ERR(err, callback)) return;
                callback(null);
            });
        },
        (callback) => {
            if (!messageSchema) {
                fs.readJson(path.join(__dirname, 'messageSchema.json'), (err, data) => {
                    if (ERR(err, (err) => globalLogger.error(err))) {
                        globalLogger.error('Failed to read message schema; exiting process.');
                        process.exit(1);
                    }
                    const ajv = new Ajv();
                    messageSchema = ajv.compile(data);
                    return callback(null);
                });
            } else {
                return callback (null);
            }
        },
        (callback) => {
            const valid = messageSchema(parsedMessage);
            if (!valid) {
                globalLogger.error(messageSchema.errors);
                return callback(new Error('Message did not match schema.'));
            } else {
                return callback(null);
            }
        },
        (callback) => {
            const timeout = parsedMessage.timeout || config.defaultTimeout;
            // Add additional time to account for work that PG has to do:
            // downloading/uploading files, etc. This wasn't scientifically
            // chosen at all.
            const newTimeout = timeout + 10;
            const visibilityParams = {
                QueueUrl: queueUrl,
                ReceiptHandle: receiptHandle,
                VisibilityTimeout: newTimeout,
            };
            sqs.changeMessageVisibility(visibilityParams, (err) => {
                if (ERR(err, callback)) return;
                return callback(null);
            });
        },
        (callback) => {
            // If we're configured to use the database, ensure that this job
            // wasn't canceled in the time since job submission
            if (!config.useDatabase) return callback(null);

            const params = {
                grading_job_id: parsedMessage.jobId,
            };
            sqldb.queryOneRow(sql.check_job_cancelation, params, (err, result) => {
                if (ERR(err, callback)) return;
                jobCanceled = result.rows[0].canceled;
                callback(null);
            });
        },
        (callback) => {
            // Don't execute the job if it was canceled
            if (jobCanceled) {
                globalLogger.info(`Job ${parsedMessage.jobId} was canceled; skipping job.`);
                return callback(null);
            }

            receiveCallback(parsedMessage, (err) => {
                globalLogger.info(`Job ${parsedMessage.jobId} finished successfully.`);
                callback(err);
            }, () => {
                globalLogger.info(`Job ${parsedMessage.jobId} errored.`);
                callback(null);
            });
        },
        (callback) => {
            const deleteParams = {
                QueueUrl: queueUrl,
                ReceiptHandle: receiptHandle
            };
            sqs.deleteMessage(deleteParams, (err) => {
                if (ERR(err, callback)) return;
                return callback(null);
            });
        }
    ], (err) => {
        if (ERR(err, doneCallback)) return;
        doneCallback(null);
    });
};
<commit_msg>fix error reporting<commit_after>const ERR = require('async-stacktrace');
const async = require('async');
const fs = require('fs-extra');
const path = require('path');
const Ajv = require('ajv');

const globalLogger = require('./logger');
const config = require('./config').config;
const sqldb = require('./sqldb');
const sql = require('./sql-loader').loadSqlEquiv(__filename);

let messageSchema = null;

module.exports = function(sqs, queueUrl, receiveCallback, doneCallback) {
    let parsedMessage, jobCanceled, receiptHandle;
    async.series([
        (callback) => {
            globalLogger.info('Waiting for next job...');
            async.doUntil((done) => {
                const params = {
                    MaxNumberOfMessages: 1,
                    QueueUrl: queueUrl,
                    WaitTimeSeconds: 20
                };
                sqs.receiveMessage(params, (err, data) => {
                    if (ERR(err, done)) return;
                    if (!data.Messages) {
                        return done(null, null);
                    }
                    globalLogger.info('Received job!');
                    try {
                        parsedMessage = JSON.parse(data.Messages[0].Body);
                        receiptHandle = data.Messages[0].ReceiptHandle;
                    } catch (e) {
                        return done(e);
                    }
                    return done(null, parsedMessage);
                });
            }, (result) => {
                return !!result;
            }, (err) => {
                if (ERR(err, callback)) return;
                callback(null);
            });
        },
        (callback) => {
            if (!messageSchema) {
                fs.readJson(path.join(__dirname, 'messageSchema.json'), (err, data) => {
                    if (ERR(err, (err) => globalLogger.error(err))) {
                        globalLogger.error('Failed to read message schema; exiting process.');
                        process.exit(1);
                    }
                    const ajv = new Ajv();
                    messageSchema = ajv.compile(data);
                    return callback(null);
                });
            } else {
                return callback (null);
            }
        },
        (callback) => {
            const valid = messageSchema(parsedMessage);
            if (!valid) {
                globalLogger.error(messageSchema.errors);
                return callback(new Error('Message did not match schema.'));
            } else {
                return callback(null);
            }
        },
        (callback) => {
            const timeout = parsedMessage.timeout || config.defaultTimeout;
            // Add additional time to account for work that PG has to do:
            // downloading/uploading files, etc. This wasn't scientifically
            // chosen at all.
            const newTimeout = timeout + 10;
            const visibilityParams = {
                QueueUrl: queueUrl,
                ReceiptHandle: receiptHandle,
                VisibilityTimeout: newTimeout,
            };
            sqs.changeMessageVisibility(visibilityParams, (err) => {
                if (ERR(err, callback)) return;
                return callback(null);
            });
        },
        (callback) => {
            // If we're configured to use the database, ensure that this job
            // wasn't canceled in the time since job submission
            if (!config.useDatabase) return callback(null);

            const params = {
                grading_job_id: parsedMessage.jobId,
            };
            sqldb.queryOneRow(sql.check_job_cancelation, params, (err, result) => {
                if (ERR(err, callback)) return;
                jobCanceled = result.rows[0].canceled;
                callback(null);
            });
        },
        (callback) => {
            // Don't execute the job if it was canceled
            if (jobCanceled) {
                globalLogger.info(`Job ${parsedMessage.jobId} was canceled; skipping job.`);
                return callback(null);
            }

            receiveCallback(parsedMessage, (err) => {
                globalLogger.info(`Job ${parsedMessage.jobId} errored.`);
                callback(err);
            }, () => {
                globalLogger.info(`Job ${parsedMessage.jobId} finished successfully.`);
                callback(null);
            });
        },
        (callback) => {
            const deleteParams = {
                QueueUrl: queueUrl,
                ReceiptHandle: receiptHandle
            };
            sqs.deleteMessage(deleteParams, (err) => {
                if (ERR(err, callback)) return;
                return callback(null);
            });
        }
    ], (err) => {
        if (ERR(err, doneCallback)) return;
        doneCallback(null);
    });
};
<|endoftext|>


<commit_before>import { combineReducers } from 'redux';
import { routerReducer } from 'react-router-redux';
import { reducer as formReducer } from 'redux-form';
import auth from './userSignUp';
import login from './login';
import book from './book';
import favorites from './favorites';
import { books, mostUpvotedBooks } from './books';

const rootReducer = combineReducers({
  form: formReducer,
  auth,
  login,
  books,
  book,
  mostUpvotedBooks,
  favorites,
  routing: routerReducer,
});

export default rootReducer;
<commit_msg>Add profile reducer<commit_after>import { combineReducers } from 'redux';
import { routerReducer } from 'react-router-redux';
import { reducer as formReducer } from 'redux-form';
import auth from './userSignUp';
import login from './login';
import book from './book';
import favorites from './favorites';
import profile from './profile';
import { books, mostUpvotedBooks } from './books';

const rootReducer = combineReducers({
  form: formReducer,
  auth,
  login,
  books,
  book,
  mostUpvotedBooks,
  favorites,
  profile,
  routing: routerReducer,
});

export default rootReducer;
<|endoftext|>


<commit_before>import PropTypes from "prop-types";
import React, {Component} from 'react';
import Convert from 'ansi-to-html';

import { Col, Row,  TabContent, TabPane, Nav, NavItem, NavLink } from 'reactstrap';
import classnames from 'classnames';
import {AsyncTypeahead} from 'react-bootstrap-typeahead';
import ReactJson from 'react-json-view';

import Logs from "./Logs";
import "./Console.css";
import {EMBARK_PROCESS_NAME} from '../constants';

const convert = new Convert({newline: true, escapeXML: true});

class Console extends Component {
  constructor(props) {
    super(props);
    this.state = {value: '', isLoading: true, options: [], activeTab: EMBARK_PROCESS_NAME};
  }

  handleSubmit(event) {
    event.preventDefault();
    this.props.postCommand(this.state.value);
    this.setState({value: ''});
    this.typeahead.getInstance().clear();
  }

  handleChange(value, cb) {
    this.setState({value: value}, cb);
  }

  toggle(tab) {
    if (this.state.activeTab !== tab) {
      this.setState({
        activeTab: tab
      });
      this.props.updateTab(tab);
    }
  }

  renderNav() {
    return (

        {this.props.processes.map((process) => (
          <NavItem key={process.name}>
            <NavLink
              className={classnames({ active: this.state.activeTab === process.name })}
              onClick={() => { this.toggle(process.name); }}
          >
            {process.name}
          </NavLink>
        </NavItem>
        ))}

    );
  }

  isJsonObject(msg) {
    try {
      return typeof (JSON.parse(msg)) === 'object';
    } catch(_err) {
      return false;
    }
  }

  logClassName(item) {
    return classnames('m-0', {
      'text-success': item.logLevel === 'info',
      'text-info': item.logLevel === 'debug',
      'text-danger': item.logLevel === 'error',
      'text-warning': item.logLevel === 'warning'
    });
  }

  renderTabs() {
    const {processLogs, processes} = this.props;

    return (
      <TabContent activeTab={this.state.activeTab}>
        {processes.map(process => (
          <TabPane key={process.name} tabId={process.name}>
            <Logs>
              {processLogs
                .filter((item) => item.name === process.name)
                .reverse()
                .map((item, i) => {
                  const msg = item.result || item.msg;

                  if (this.isJsonObject(msg)) {
                    return(
                      <div key={`message-${i}`}>

                        <ReactJson src={JSON.parse(msg)} theme="monokai" sortKeys={true} collapsed={1} />

                    );
                  }
                  return (

                  );
                })
              }
            </Logs>
            {process.name === "embark" &&
              <AsyncTypeahead
                autoFocus={true}
                emptyLabel={false}
                labelKey="value"
                multiple={false}
                maxResults={20}
                isLoading={this.state.isLoading}
                onInputChange={(text) => this.handleChange(text)}
                onChange={(text) => {
                  if (text && text[0]) {
                    this.handleChange(text[0].value);
                  }
                }}
                ref={(typeahead) => this.typeahead = typeahead}
                searchText={false}
                onKeyDown={(e) => {
                  if (e.keyCode === 13) {
                    this.handleChange(e.target.value, () => {
                      this.handleSubmit(e);
                    });
                  }
                }}
                onSearch={(value) => {
                  this.props.postCommandSuggestions(value);
                }}
                filterBy={['value', 'description']}
                maxHeight="200px"
                placeholder="Type a command (e.g help)"
                options={this.props.commandSuggestions}
                renderMenuItemChildren={(option) => (

                    {option.value}

                      {option.command_type} - {option.description}

                )}
              />
            }

          </TabPane>
        ))}
      </TabContent>
    );
  }

  render() {
    return (
      <Row>
        <Col>
          {this.renderNav()}
          {this.renderTabs()}
        </Col>
      </Row>
    );
  }
}

Console.propTypes = {
  postCommand: PropTypes.func,
  postCommandSuggestions: PropTypes.func,
  isEmbark: PropTypes.func,
  processes: PropTypes.arrayOf(PropTypes.object).isRequired,
  commandSuggestions: PropTypes.arrayOf(PropTypes.object),
  processLogs: PropTypes.arrayOf(PropTypes.object).isRequired,
  updateTab: PropTypes.func
};

export default Console;
<commit_msg>fix: pressing enter on "Display additional results" does the expected thing<commit_after>import PropTypes from "prop-types";
import React, {Component} from 'react';
import Convert from 'ansi-to-html';

import { Col, Row,  TabContent, TabPane, Nav, NavItem, NavLink } from 'reactstrap';
import classnames from 'classnames';
import {AsyncTypeahead} from 'react-bootstrap-typeahead';
import ReactJson from 'react-json-view';

import Logs from "./Logs";
import "./Console.css";
import {EMBARK_PROCESS_NAME} from '../constants';

const convert = new Convert({newline: true, escapeXML: true});

class Console extends Component {
  constructor(props) {
    super(props);
    this.state = {value: '', isLoading: true, options: [], activeTab: EMBARK_PROCESS_NAME};
  }

  handleSubmit(event) {
    const instance = this.typeahead.getInstance();
    if(instance.state.selected.length === 0) {
      return;
    }

    event.preventDefault();
    this.props.postCommand(this.state.value);
    this.setState({value: ''});
    instance.clear();
  }

  handleChange(value, cb) {
    this.setState({value: value}, cb);
  }

  toggle(tab) {
    if (this.state.activeTab !== tab) {
      this.setState({
        activeTab: tab
      });
      this.props.updateTab(tab);
    }
  }

  renderNav() {
    return (

        {this.props.processes.map((process) => (
          <NavItem key={process.name}>
            <NavLink
              className={classnames({ active: this.state.activeTab === process.name })}
              onClick={() => { this.toggle(process.name); }}
          >
            {process.name}
          </NavLink>
        </NavItem>
        ))}

    );
  }

  isJsonObject(msg) {
    try {
      return typeof (JSON.parse(msg)) === 'object';
    } catch(_err) {
      return false;
    }
  }

  logClassName(item) {
    return classnames('m-0', {
      'text-success': item.logLevel === 'info',
      'text-info': item.logLevel === 'debug',
      'text-danger': item.logLevel === 'error',
      'text-warning': item.logLevel === 'warning'
    });
  }

  renderTabs() {
    const {processLogs, processes} = this.props;

    return (
      <TabContent activeTab={this.state.activeTab}>
        {processes.map(process => (
          <TabPane key={process.name} tabId={process.name}>
            <Logs>
              {processLogs
                .filter((item) => item.name === process.name)
                .reverse()
                .map((item, i) => {
                  const msg = item.result || item.msg;

                  if (this.isJsonObject(msg)) {
                    return(
                      <div key={`message-${i}`}>

                        <ReactJson src={JSON.parse(msg)} theme="monokai" sortKeys={true} collapsed={1} />

                    );
                  }
                  return (

                  );
                })
              }
            </Logs>
            {process.name === "embark" &&
              <AsyncTypeahead
                autoFocus={true}
                emptyLabel={false}
                labelKey="value"
                multiple={false}
                maxResults={20}
                isLoading={this.state.isLoading}
                onInputChange={(text) => this.handleChange(text)}
                onChange={(text) => {
                  if (text && text[0]) {
                    this.handleChange(text[0].value);
                  }
                }}
                ref={(typeahead) => this.typeahead = typeahead}
                searchText={false}
                onKeyDown={(e) => {
                  if (e.keyCode === 13) {
                    this.handleChange(e.target.value, () => {
                      this.handleSubmit(e);
                    });
                  }
                }}
                onSearch={(value) => {
                  this.props.postCommandSuggestions(value);
                }}
                filterBy={['value', 'description']}
                maxHeight="200px"
                placeholder="Type a command (e.g help)"
                options={this.props.commandSuggestions}
                renderMenuItemChildren={(option) => (

                    {option.value}

                      {option.command_type} - {option.description}

                )}
              />
            }

          </TabPane>
        ))}
      </TabContent>
    );
  }

  render() {
    return (
      <Row>
        <Col>
          {this.renderNav()}
          {this.renderTabs()}
        </Col>
      </Row>
    );
  }
}

Console.propTypes = {
  postCommand: PropTypes.func,
  postCommandSuggestions: PropTypes.func,
  isEmbark: PropTypes.func,
  processes: PropTypes.arrayOf(PropTypes.object).isRequired,
  commandSuggestions: PropTypes.arrayOf(PropTypes.object),
  processLogs: PropTypes.arrayOf(PropTypes.object).isRequired,
  updateTab: PropTypes.func
};

export default Console;
<|endoftext|>