<commit_before>import postcss from 'postcss'
import parser from 'postcss-selector-parser'

import { resolveMatches } from './generateRules'
import bigSign from '../util/bigSign'
import escapeClassName from '../util/escapeClassName'

/** @typedef {Map<string, [any, import('postcss').Rule[]]>} ApplyCache */

function extractClasses(node) {
  let classes = new Set()
  let container = postcss.root({ nodes: [node.clone()] })

  container.walkRules((rule) => {
    parser((selectors) => {
      selectors.walkClasses((classSelector) => {
        classes.add(classSelector.value)
      })
    }).processSync(rule.selector)
  })

  return Array.from(classes)
}

function extractBaseCandidates(candidates, separator) {
  let baseClasses = new Set()

  for (let candidate of candidates) {
    baseClasses.add(candidate.split(separator).pop())
  }

  return Array.from(baseClasses)
}

function prefix(context, selector) {
  let prefix = context.tailwindConfig.prefix
  return typeof prefix === 'function' ? prefix(selector) : prefix + selector
}

function* pathToRoot(node) {
  yield node
  while (node.parent) {
    yield node.parent
    node = node.parent
  }
}

/**
 * Only clone the node itself and not its children
 *
 * @param {*} node
 * @param {*} overrides
 * @returns
 */
function shallowClone(node, overrides = {}) {
  let children = node.nodes
  node.nodes = []

  let tmp = node.clone(overrides)

  node.nodes = children

  return tmp
}

/**
 * Clone just the nodes all the way to the top that are required to represent
 * this singular rule in the tree.
 *
 * For example, if we have CSS like this:
 * ```css
 * @media (min-width: 768px) {
 *   @supports (display: grid) {
 *     .foo {
 *       display: grid;
 *       grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
 *     }
 *   }
 *
 *   @supports (backdrop-filter: blur(1px)) {
 *     .bar {
 *       backdrop-filter: blur(1px);
 *     }
 *   }
 *
 *   .baz {
 *     color: orange;
 *   }
 * }
 * ```
 *
 * And we're cloning `.bar` it'll return a cloned version of what's required for just that single node:
 *
 * ```css
 * @media (min-width: 768px) {
 *   @supports (backdrop-filter: blur(1px)) {
 *     .bar {
 *       backdrop-filter: blur(1px);
 *     }
 *   }
 * }
 * ```
 *
 * @param {import('postcss').Node} node
 */
function nestedClone(node) {
  for (let parent of pathToRoot(node)) {
    if (node === parent) {
      continue
    }

    if (parent.type === 'root') {
      break
    }

    node = shallowClone(parent, {
      nodes: [node],
    })
  }

  return node
}

/**
 * @param {import('postcss').Root} root
 */
function buildLocalApplyCache(root, context) {
  /** @type {ApplyCache} */
  let cache = new Map()

  let highestOffset = context.layerOrder.user >> 4n

  root.walkRules((rule, idx) => {
    // Ignore rules generated by Tailwind
    for (let node of pathToRoot(rule)) {
      if (node.raws.tailwind?.layer !== undefined) {
        return
      }
    }

    // Clone what's required to represent this singular rule in the tree
    let container = nestedClone(rule)

    for (let className of extractClasses(rule)) {
      let list = cache.get(className) || []
      cache.set(className, list)

      list.push([
        {
          layer: 'user',
          sort: BigInt(idx) + highestOffset,
          important: false,
        },
        container,
      ])
    }
  })

  return cache
}

/**
 * @returns {ApplyCache}
 */
function buildApplyCache(applyCandidates, context) {
  for (let candidate of applyCandidates) {
    if (context.notClassCache.has(candidate) || context.applyClassCache.has(candidate)) {
      continue
    }

    if (context.classCache.has(candidate)) {
      context.applyClassCache.set(
        candidate,
        context.classCache.get(candidate).map(([meta, rule]) => [meta, rule.clone()])
      )
      continue
    }

    let matches = Array.from(resolveMatches(candidate, context))

    if (matches.length === 0) {
      context.notClassCache.add(candidate)
      continue
    }

    context.applyClassCache.set(candidate, matches)
  }

  return context.applyClassCache
}

/**
 * Build a cache only when it's first used
 *
 * @param {() => ApplyCache} buildCacheFn
 * @returns {ApplyCache}
 */
function lazyCache(buildCacheFn) {
  let cache = null

  return {
    get: (name) => {
      cache = cache || buildCacheFn()

      return cache.get(name)
    },
    has: (name) => {
      cache = cache || buildCacheFn()

      return cache.has(name)
    },
  }
}

/**
 * Take a series of multiple caches and merge
 * them so they act like one large cache
 *
 * @param {ApplyCache[]} caches
 * @returns {ApplyCache}
 */
function combineCaches(caches) {
  return {
    get: (name) => caches.flatMap((cache) => cache.get(name) || []),
    has: (name) => caches.some((cache) => cache.has(name)),
  }
}

function extractApplyCandidates(params) {
  let candidates = params.split(/[\s\t\n]+/g)

  if (candidates[candidates.length - 1] === '!important') {
    return [candidates.slice(0, -1), true]
  }

  return [candidates, false]
}

function processApply(root, context, localCache) {
  let applyCandidates = new Set()

  // Collect all @apply rules and candidates
  let applies = []
  root.walkAtRules('apply', (rule) => {
    let [candidates] = extractApplyCandidates(rule.params)

    for (let util of candidates) {
      applyCandidates.add(util)
    }

    applies.push(rule)
  })

  // Start the @apply process if we have rules with @apply in them
  if (applies.length === 0) {
    return
  }

  // Fill up some caches!
  let applyClassCache = combineCaches([localCache, buildApplyCache(applyCandidates, context)])

  /**
   * When we have an apply like this:
   *
   * .abc {
   *    @apply hover:font-bold;
   * }
   *
   * What we essentially will do is resolve to this:
   *
   * .abc {
   *    @apply .hover\:font-bold:hover {
   *      font-weight: 500;
   *    }
   * }
   *
   * Notice that the to-be-applied class is `.hover\:font-bold:hover` and that the utility candidate was `hover:font-bold`.
   * What happens in this function is that we prepend a `.` and escape the candidate.
   * This will result in `.hover\:font-bold`
   * Which means that we can replace `.hover\:font-bold` with `.abc` in `.hover\:font-bold:hover` resulting in `.abc:hover`
   */
  // TODO: Should we use postcss-selector-parser for this instead?
  function replaceSelector(selector, utilitySelectors, candidate) {
    let needle = `.${escapeClassName(candidate)}`
    let needles = [...new Set([needle, needle.replace(/\\2c /g, '\\,')])]
    let utilitySelectorsList = utilitySelectors.split(/\s*(?<!\\)\,(?![^(]*\))\s*/g)

    return selector
      .split(/\s*(?<!\\)\,(?![^(]*\))\s*/g)
      .map((s) => {
        let replaced = []

        for (let utilitySelector of utilitySelectorsList) {
          let replacedSelector = utilitySelector
          for (const needle of needles) {
            replacedSelector = replacedSelector.replace(needle, s)
          }
          if (replacedSelector === utilitySelector) {
            continue
          }
          replaced.push(replacedSelector)
        }
        return replaced.join(', ')
      })
      .join(', ')
  }

  let perParentApplies = new Map()

  // Collect all apply candidates and their rules
  for (let apply of applies) {
    let candidates = perParentApplies.get(apply.parent) || []

    perParentApplies.set(apply.parent, [candidates, apply.source])

    let [applyCandidates, important] = extractApplyCandidates(apply.params)

    if (apply.parent.type === 'atrule') {
      if (apply.parent.name === 'screen') {
        const screenType = apply.parent.params

        throw apply.error(
          `@apply is not supported within nested at-rules like @screen. We suggest you write this as @apply ${applyCandidates
            .map((c) => `${screenType}:${c}`)
            .join(' ')} instead.`
        )
      }

      throw apply.error(
        `@apply is not supported within nested at-rules like @${apply.parent.name}. You can fix this by un-nesting @${apply.parent.name}.`
      )
    }

    for (let applyCandidate of applyCandidates) {
      if ([prefix(context, 'group'), prefix(context, 'peer')].includes(applyCandidate)) {
        // TODO: Link to specific documentation page with error code.
        throw apply.error(`@apply should not be used with the '${applyCandidate}' utility`)
      }

      if (!applyClassCache.has(applyCandidate)) {
        throw apply.error(
          `The \`${applyCandidate}\` class does not exist. If \`${applyCandidate}\` is a custom class, make sure it is defined within a \`@layer\` directive.`
        )
      }

      let rules = applyClassCache.get(applyCandidate)

      candidates.push([applyCandidate, important, rules])
    }
  }

  for (const [parent, [candidates, atApplySource]] of perParentApplies) {
    let siblings = []

    for (let [applyCandidate, important, rules] of candidates) {
      for (let [meta, node] of rules) {
        let parentClasses = extractClasses(parent)
        let nodeClasses = extractClasses(node)

        // Add base utility classes from the @apply node to the list of
        // classes to check whether it intersects and therefore results in a
        // circular dependency or not.
        //
        // E.g.:
        // .foo {
        //   @apply hover:a; // This applies "a" but with a modifier
        // }
        //
        // We only have to do that with base classes of the `node`, not of the `parent`
        // E.g.:
        // .hover\:foo {
        //   @apply bar;
        // }
        // .bar {
        //   @apply foo;
        // }
        //
        // This should not result in a circular dependency because we are
        // just applying `.foo` and the rule above is `.hover\:foo` which is
        // unrelated. However, if we were to apply `hover:foo` then we _did_
        // have to include this one.
        nodeClasses = nodeClasses.concat(
          extractBaseCandidates(nodeClasses, context.tailwindConfig.separator)
        )

        let intersects = parentClasses.some((selector) => nodeClasses.includes(selector))
        if (intersects) {
          throw node.error(
            `You cannot \`@apply\` the \`${applyCandidate}\` utility here because it creates a circular dependency.`
          )
        }

        let root = postcss.root({ nodes: [node.clone()] })

        // Make sure every node in the entire tree points back at the @apply rule that generated it
        root.walk((node) => {
          node.source = atApplySource
        })

        let canRewriteSelector =
          node.type !== 'atrule' || (node.type === 'atrule' && node.name !== 'keyframes')

        if (canRewriteSelector) {
          root.walkRules((rule) => {
            // Let's imagine you have the following structure:
            //
            // .foo {
            //   @apply bar;
            // }
            //
            // @supports (a: b) {
            //   .bar {
            //     color: blue
            //   }
            //
            //   .something-unrelated {}
            // }
            //
            // In this case we want to apply `.bar` but it happens to be in
            // an atrule node. We clone that node instead of the nested one
            // because we still want that @supports rule to be there once we
            // applied everything.
            //
            // However it happens to be that the `.something-unrelated` is
            // also in that same shared @supports atrule. This is not good,
            // and this should not be there. The good part is that this is
            // a clone already and it can be safely removed. The question is
            // how do we know we can remove it. Basically what we can do is
            // match it against the applyCandidate that you want to apply. If
            // it doesn't match the we can safely delete it.
            //
            // If we didn't do this, then the `replaceSelector` function
            // would have replaced this with something that didn't exist and
            // therefore it removed the selector altogether. In this specific
            // case it would result in `{}` instead of `.something-unrelated {}`
            if (!extractClasses(rule).some((candidate) => candidate === applyCandidate)) {
              rule.remove()
              return
            }

            rule.selector = replaceSelector(parent.selector, rule.selector, applyCandidate)

            rule.walkDecls((d) => {
              d.important = meta.important || important
            })
          })
        }

        // Insert it
        siblings.push([
          // Ensure that when we are sorting, that we take the layer order into account
          { ...meta, sort: meta.sort | context.layerOrder[meta.layer] },
          root.nodes[0],
        ])
      }
    }

    // Inject the rules, sorted, correctly
    let nodes = siblings.sort(([a], [z]) => bigSign(a.sort - z.sort)).map((s) => s[1])

    // `parent` refers to the node at `.abc` in: .abc { @apply mt-2 }
    parent.after(nodes)
  }

  for (let apply of applies) {
    // If there are left-over declarations, just remove the @apply
    if (apply.parent.nodes.length > 1) {
      apply.remove()
    } else {
      // The node is empty, drop the full node
      apply.parent.remove()
    }
  }

  // Do it again, in case we have other `@apply` rules
  processApply(root, context, localCache)
}

export default function expandApplyAtRules(context) {
  return (root) => {
    // Build a cache of the user's CSS so we can use it to resolve classes used by @apply
    let localCache = lazyCache(() => buildLocalApplyCache(root, context))

    processApply(root, context, localCache)
  }
}
<commit_msg>Simplify apply handling<commit_after>import postcss from 'postcss'
import parser from 'postcss-selector-parser'

import { resolveMatches } from './generateRules'
import bigSign from '../util/bigSign'
import escapeClassName from '../util/escapeClassName'

/** @typedef {Map<string, [any, import('postcss').Rule[]]>} ApplyCache */

function extractClasses(node) {
  let classes = new Set()
  let container = postcss.root({ nodes: [node.clone()] })

  container.walkRules((rule) => {
    parser((selectors) => {
      selectors.walkClasses((classSelector) => {
        classes.add(classSelector.value)
      })
    }).processSync(rule.selector)
  })

  return Array.from(classes)
}

function extractBaseCandidates(candidates, separator) {
  let baseClasses = new Set()

  for (let candidate of candidates) {
    baseClasses.add(candidate.split(separator).pop())
  }

  return Array.from(baseClasses)
}

function prefix(context, selector) {
  let prefix = context.tailwindConfig.prefix
  return typeof prefix === 'function' ? prefix(selector) : prefix + selector
}

function* pathToRoot(node) {
  yield node
  while (node.parent) {
    yield node.parent
    node = node.parent
  }
}

/**
 * Only clone the node itself and not its children
 *
 * @param {*} node
 * @param {*} overrides
 * @returns
 */
function shallowClone(node, overrides = {}) {
  let children = node.nodes
  node.nodes = []

  let tmp = node.clone(overrides)

  node.nodes = children

  return tmp
}

/**
 * Clone just the nodes all the way to the top that are required to represent
 * this singular rule in the tree.
 *
 * For example, if we have CSS like this:
 * ```css
 * @media (min-width: 768px) {
 *   @supports (display: grid) {
 *     .foo {
 *       display: grid;
 *       grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
 *     }
 *   }
 *
 *   @supports (backdrop-filter: blur(1px)) {
 *     .bar {
 *       backdrop-filter: blur(1px);
 *     }
 *   }
 *
 *   .baz {
 *     color: orange;
 *   }
 * }
 * ```
 *
 * And we're cloning `.bar` it'll return a cloned version of what's required for just that single node:
 *
 * ```css
 * @media (min-width: 768px) {
 *   @supports (backdrop-filter: blur(1px)) {
 *     .bar {
 *       backdrop-filter: blur(1px);
 *     }
 *   }
 * }
 * ```
 *
 * @param {import('postcss').Node} node
 */
function nestedClone(node) {
  for (let parent of pathToRoot(node)) {
    if (node === parent) {
      continue
    }

    if (parent.type === 'root') {
      break
    }

    node = shallowClone(parent, {
      nodes: [node],
    })
  }

  return node
}

/**
 * @param {import('postcss').Root} root
 */
function buildLocalApplyCache(root, context) {
  /** @type {ApplyCache} */
  let cache = new Map()

  let highestOffset = context.layerOrder.user >> 4n

  root.walkRules((rule, idx) => {
    // Ignore rules generated by Tailwind
    for (let node of pathToRoot(rule)) {
      if (node.raws.tailwind?.layer !== undefined) {
        return
      }
    }

    // Clone what's required to represent this singular rule in the tree
    let container = nestedClone(rule)

    for (let className of extractClasses(rule)) {
      let list = cache.get(className) || []
      cache.set(className, list)

      list.push([
        {
          layer: 'user',
          sort: BigInt(idx) + highestOffset,
          important: false,
        },
        container,
      ])
    }
  })

  return cache
}

/**
 * @returns {ApplyCache}
 */
function buildApplyCache(applyCandidates, context) {
  for (let candidate of applyCandidates) {
    if (context.notClassCache.has(candidate) || context.applyClassCache.has(candidate)) {
      continue
    }

    if (context.classCache.has(candidate)) {
      context.applyClassCache.set(
        candidate,
        context.classCache.get(candidate).map(([meta, rule]) => [meta, rule.clone()])
      )
      continue
    }

    let matches = Array.from(resolveMatches(candidate, context))

    if (matches.length === 0) {
      context.notClassCache.add(candidate)
      continue
    }

    context.applyClassCache.set(candidate, matches)
  }

  return context.applyClassCache
}

/**
 * Build a cache only when it's first used
 *
 * @param {() => ApplyCache} buildCacheFn
 * @returns {ApplyCache}
 */
function lazyCache(buildCacheFn) {
  let cache = null

  return {
    get: (name) => {
      cache = cache || buildCacheFn()

      return cache.get(name)
    },
    has: (name) => {
      cache = cache || buildCacheFn()

      return cache.has(name)
    },
  }
}

/**
 * Take a series of multiple caches and merge
 * them so they act like one large cache
 *
 * @param {ApplyCache[]} caches
 * @returns {ApplyCache}
 */
function combineCaches(caches) {
  return {
    get: (name) => caches.flatMap((cache) => cache.get(name) || []),
    has: (name) => caches.some((cache) => cache.has(name)),
  }
}

function extractApplyCandidates(params) {
  let candidates = params.split(/[\s\t\n]+/g)

  if (candidates[candidates.length - 1] === '!important') {
    return [candidates.slice(0, -1), true]
  }

  return [candidates, false]
}

function processApply(root, context, localCache) {
  let applyCandidates = new Set()

  // Collect all @apply rules and candidates
  let applies = []
  root.walkAtRules('apply', (rule) => {
    let [candidates] = extractApplyCandidates(rule.params)

    for (let util of candidates) {
      applyCandidates.add(util)
    }

    applies.push(rule)
  })

  // Start the @apply process if we have rules with @apply in them
  if (applies.length === 0) {
    return
  }

  // Fill up some caches!
  let applyClassCache = combineCaches([localCache, buildApplyCache(applyCandidates, context)])

  /**
   * When we have an apply like this:
   *
   * .abc {
   *    @apply hover:font-bold;
   * }
   *
   * What we essentially will do is resolve to this:
   *
   * .abc {
   *    @apply .hover\:font-bold:hover {
   *      font-weight: 500;
   *    }
   * }
   *
   * Notice that the to-be-applied class is `.hover\:font-bold:hover` and that the utility candidate was `hover:font-bold`.
   * What happens in this function is that we prepend a `.` and escape the candidate.
   * This will result in `.hover\:font-bold`
   * Which means that we can replace `.hover\:font-bold` with `.abc` in `.hover\:font-bold:hover` resulting in `.abc:hover`
   */
  // TODO: Should we use postcss-selector-parser for this instead?
  function replaceSelector(selector, utilitySelectors, candidate) {
    let needle = `.${escapeClassName(candidate)}`
    let needles = [...new Set([needle, needle.replace(/\\2c /g, '\\,')])]
    let utilitySelectorsList = utilitySelectors.split(/\s*(?<!\\)\,(?![^(]*\))\s*/g)

    return selector
      .split(/\s*(?<!\\)\,(?![^(]*\))\s*/g)
      .map((s) => {
        let replaced = []

        for (let utilitySelector of utilitySelectorsList) {
          let replacedSelector = utilitySelector
          for (const needle of needles) {
            replacedSelector = replacedSelector.replace(needle, s)
          }
          if (replacedSelector === utilitySelector) {
            continue
          }
          replaced.push(replacedSelector)
        }
        return replaced.join(', ')
      })
      .join(', ')
  }

  let perParentApplies = new Map()

  // Collect all apply candidates and their rules
  for (let apply of applies) {
    let candidates = []

    perParentApplies.set(apply.parent, [candidates, apply.source])

    let [applyCandidates, important] = extractApplyCandidates(apply.params)

    if (apply.parent.type === 'atrule') {
      if (apply.parent.name === 'screen') {
        const screenType = apply.parent.params

        throw apply.error(
          `@apply is not supported within nested at-rules like @screen. We suggest you write this as @apply ${applyCandidates
            .map((c) => `${screenType}:${c}`)
            .join(' ')} instead.`
        )
      }

      throw apply.error(
        `@apply is not supported within nested at-rules like @${apply.parent.name}. You can fix this by un-nesting @${apply.parent.name}.`
      )
    }

    for (let applyCandidate of applyCandidates) {
      if ([prefix(context, 'group'), prefix(context, 'peer')].includes(applyCandidate)) {
        // TODO: Link to specific documentation page with error code.
        throw apply.error(`@apply should not be used with the '${applyCandidate}' utility`)
      }

      if (!applyClassCache.has(applyCandidate)) {
        throw apply.error(
          `The \`${applyCandidate}\` class does not exist. If \`${applyCandidate}\` is a custom class, make sure it is defined within a \`@layer\` directive.`
        )
      }

      let rules = applyClassCache.get(applyCandidate)

      candidates.push([applyCandidate, important, rules])
    }
  }

  for (const [parent, [candidates, atApplySource]] of perParentApplies) {
    let siblings = []

    for (let [applyCandidate, important, rules] of candidates) {
      for (let [meta, node] of rules) {
        let parentClasses = extractClasses(parent)
        let nodeClasses = extractClasses(node)

        // Add base utility classes from the @apply node to the list of
        // classes to check whether it intersects and therefore results in a
        // circular dependency or not.
        //
        // E.g.:
        // .foo {
        //   @apply hover:a; // This applies "a" but with a modifier
        // }
        //
        // We only have to do that with base classes of the `node`, not of the `parent`
        // E.g.:
        // .hover\:foo {
        //   @apply bar;
        // }
        // .bar {
        //   @apply foo;
        // }
        //
        // This should not result in a circular dependency because we are
        // just applying `.foo` and the rule above is `.hover\:foo` which is
        // unrelated. However, if we were to apply `hover:foo` then we _did_
        // have to include this one.
        nodeClasses = nodeClasses.concat(
          extractBaseCandidates(nodeClasses, context.tailwindConfig.separator)
        )

        let intersects = parentClasses.some((selector) => nodeClasses.includes(selector))
        if (intersects) {
          throw node.error(
            `You cannot \`@apply\` the \`${applyCandidate}\` utility here because it creates a circular dependency.`
          )
        }

        let root = postcss.root({ nodes: [node.clone()] })

        // Make sure every node in the entire tree points back at the @apply rule that generated it
        root.walk((node) => {
          node.source = atApplySource
        })

        let canRewriteSelector =
          node.type !== 'atrule' || (node.type === 'atrule' && node.name !== 'keyframes')

        if (canRewriteSelector) {
          root.walkRules((rule) => {
            // Let's imagine you have the following structure:
            //
            // .foo {
            //   @apply bar;
            // }
            //
            // @supports (a: b) {
            //   .bar {
            //     color: blue
            //   }
            //
            //   .something-unrelated {}
            // }
            //
            // In this case we want to apply `.bar` but it happens to be in
            // an atrule node. We clone that node instead of the nested one
            // because we still want that @supports rule to be there once we
            // applied everything.
            //
            // However it happens to be that the `.something-unrelated` is
            // also in that same shared @supports atrule. This is not good,
            // and this should not be there. The good part is that this is
            // a clone already and it can be safely removed. The question is
            // how do we know we can remove it. Basically what we can do is
            // match it against the applyCandidate that you want to apply. If
            // it doesn't match the we can safely delete it.
            //
            // If we didn't do this, then the `replaceSelector` function
            // would have replaced this with something that didn't exist and
            // therefore it removed the selector altogether. In this specific
            // case it would result in `{}` instead of `.something-unrelated {}`
            if (!extractClasses(rule).some((candidate) => candidate === applyCandidate)) {
              rule.remove()
              return
            }

            rule.selector = replaceSelector(parent.selector, rule.selector, applyCandidate)

            rule.walkDecls((d) => {
              d.important = meta.important || important
            })
          })
        }

        // Insert it
        siblings.push([
          // Ensure that when we are sorting, that we take the layer order into account
          { ...meta, sort: meta.sort | context.layerOrder[meta.layer] },
          root.nodes[0],
        ])
      }
    }

    // Inject the rules, sorted, correctly
    let nodes = siblings.sort(([a], [z]) => bigSign(a.sort - z.sort)).map((s) => s[1])

    // `parent` refers to the node at `.abc` in: .abc { @apply mt-2 }
    parent.after(nodes)
  }

  for (let apply of applies) {
    // If there are left-over declarations, just remove the @apply
    if (apply.parent.nodes.length > 1) {
      apply.remove()
    } else {
      // The node is empty, drop the full node
      apply.parent.remove()
    }
  }

  // Do it again, in case we have other `@apply` rules
  processApply(root, context, localCache)
}

export default function expandApplyAtRules(context) {
  return (root) => {
    // Build a cache of the user's CSS so we can use it to resolve classes used by @apply
    let localCache = lazyCache(() => buildLocalApplyCache(root, context))

    processApply(root, context, localCache)
  }
}
<|endoftext|>


<commit_before>// Meteor.subscribe('tags');
Meteor.subscribe('elements');

// Tags = new Meteor.Collection('tags');
Elements = new Meteor.Collection('elements');

Template.map.rendered = function() {
  var map = L.map('map').setView([45.500, 9.35], 10);

  // add an OpenStreetMap tile layer
  L.tileLayer('http://{s}.tile.osm.org/{z}/{x}/{y}.png', {
    attribution: '© OpenStreetMap contributors'
  }).addTo(map);

  var query = Elements.find();
  var graphicalMarkers = {};
  query.observeChanges({
    added: function (id, element) {
      graphicalMarker = L.marker([element.points[0][0], element.points[0][1]]);
      graphicalMarkers[id] = graphicalMarker;
      map.addLayer(graphicalMarker);
    },
    removed: function (id, pos) {
      map.removeLayer(graphicalMarkers[id]);
    }
  });
}

function showError(error) {
  switch(error.code) {
    case error.PERMISSION_DENIED:
      alert("Permesso negato")
      break;
    case error.POSITION_UNAVAILABLE:
      alert("Informazione non disponibile");
      break;
    case error.TIMEOUT:
      alert("Timeout");
      break;
    case error.UNKNOWN_ERROR:
      alert("Impossibile localizzarti");
      break;
  }
}

function sendLocation(position) {
  Elements.insert({
    type:'poi',
    userId:Meteor.userId(),
    points:[{
      latitude:position.coords.latitude,
      longitude:position.coords.longitude,
      accuracy:position.coords.accuracy,
      timestamp:position.timestamp
    }]
  });
}

if (navigator.geolocation) {
    navigator.geolocation.getCurrentPosition(sendLocation, showError);
} else {
    alert("Il tuo browser non supporta la geolocalizzazione");
}
<commit_msg>Marker code fixed<commit_after>// Meteor.subscribe('tags');
Meteor.subscribe('elements');

// Tags = new Meteor.Collection('tags');
Elements = new Meteor.Collection('elements');

Template.map.rendered = function() {
  var map = L.map('map').setView([45.500, 9.35], 10);

  // add an OpenStreetMap tile layer
  L.tileLayer('http://{s}.tile.osm.org/{z}/{x}/{y}.png', {
    attribution: '© OpenStreetMap contributors'
  }).addTo(map);

  var query = Elements.find();
  var graphicalMarkers = {};
  query.observeChanges({
    added: function (id, element) {
      graphicalMarker = L.marker([element.points[0].latitude, element.points[0].longitude]);
      graphicalMarkers[id] = graphicalMarker;
      map.addLayer(graphicalMarker);
    },
    removed: function (id, pos) {
      map.removeLayer(graphicalMarkers[id]);
    }
  });
}

function showError(error) {
  switch(error.code) {
    case error.PERMISSION_DENIED:
      alert("Permesso negato")
      break;
    case error.POSITION_UNAVAILABLE:
      alert("Informazione non disponibile");
      break;
    case error.TIMEOUT:
      alert("Timeout");
      break;
    case error.UNKNOWN_ERROR:
      alert("Impossibile localizzarti");
      break;
  }
}

function sendLocation(position) {
  Elements.insert({
    type:'poi',
    userId:Meteor.userId(),
    points:[{
      latitude:position.coords.latitude,
      longitude:position.coords.longitude,
      accuracy:position.coords.accuracy,
      timestamp:position.timestamp
    }]
  });
}

if (navigator.geolocation) {
    navigator.geolocation.getCurrentPosition(sendLocation, showError);
} else {
    alert("Il tuo browser non supporta la geolocalizzazione");
}
<|endoftext|>


<commit_before>#!/usr/bin/env node
var path = require('path')
var nopt = require('nopt')
var noptUsage = require('nopt-usage')
var shelljs = require('shelljs')
var fs = require('fs')
var express = require('express')
var app = express()
require('express-ws')(app)

var knownOpts = {
  'expression': String,
  'help': Boolean,
  'verbose': Boolean
}
var shortHands = {
  'e': ['--expression'],
  'h': ['--help'],
  'v': ['--verbose']
}
var description = {
  'expression': 'expression to evaluate instead of a file',
  'help': 'display this help',
  'verbose': 'show results from intermediary stages'
}
var parsed = nopt(knownOpts, shortHands)
var missingArgument = (parsed.argv.remain.length < 1 && !parsed.expression)
var firstArgumentIndex = parsed.expression ? 0 : 1

if (parsed.help || missingArgument) {
  var usage = 'usage: run [options] file [A [A ...]]\n\n' +
    'Unified interface for running javascript functions in a browser.\n\n' +
    'positional arguments:\n' +
    '  file\t\tJavaScript file to execute in the browser\n' +
    '  A\t\tpositional argument(s) to pass to the function\n\n' +
    'optional arguments: \n' +
    noptUsage(knownOpts, shortHands, description)
  console.log(usage)
  process.exit(1)
}

app.use('/page', express.static(path.join(__dirname, './public')))
app.use('/build', express.static(path.dirname(parsed.argv.remain[0]).toString()))
app.use('/input', express.static(path.join(__dirname, './input')))

function bashToJavaScript (a) {
  if (Number.parseInt(a, 10) || Number.parseFloat(a)) {
    return a.toString()
  }

  if (a.indexOf('@' >= 0)) {
    var tuple = a.split('@')
    var type = tuple[0].split('-')
    var inputPath = tuple[1]

    // Expected type
    if (type[0] !== 'array') {
      console.log("Invalid type '" + tuple[0] + "', expected an 'array' prefix")
      process.exit(1)
    }

    if (type[1] !== 'int32' &&
      type[1] !== 'float64') {
      console.log("Invalid array type '" + tuple[1] + "', expected an 'int32' or 'float64'")
      process.exit(1)
    }

    var size = type.slice(2).map(Number)

    // Input file
    var ext = path.extname(inputPath)

    var data = []
    var fileData
    if (ext === '.csv') {
      fileData = fs.readFileSync(inputPath).toString()
      fileData.split('\n').forEach(function (line) {
        if (line === '') return
        line.split(',').forEach(function (s) {
          var n = Number.parseInt(s, 10) || Number.parseFloat(s)
          if (!n) {
            console.log("Invalid number '" + s + "' in file " + inputPath)
            process.exit(1)
          }
          data.push(n)
        })
      })
    } else if (ext === '.pgm') {
      fileData = fs.readFileSync(inputPath).toString()
      fileData.split('\n').slice(3).forEach(function (line) {
        if (line === '') return
        line.split(' ').forEach(function (s) {
          if (s === '') return
          var n = Number.parseInt(s, 10) || Number.parseFloat(s)

          if (n === null) {
            console.log("Invalid null value for '" + s + "'")
          }
          if (!n) {
            console.log("Invalid number '" + s + "' in file " + inputPath)
            process.exit(1)
          }
          data.push(n)
        })
      })
    } else {
      console.log("Unsupported extension '" + ext + "'")
      process.exit(1)
    }

    var jsArrayType = ((type[1] === 'int32') ? 'Int32Array' : 'Float64Array')

    var array = '{\n' +
      "        type: 'array',\n" +
      "        'array-type': " + JSON.stringify(type[1]) + ',\n' +
      '        size: ' + JSON.stringify(size) + ',\n' +
      "        layout: 'row-major'" + ',\n' +
      '        data: ' + 'new ' + jsArrayType + '(' + JSON.stringify(data) + ')\n' +
      '    }'

    return array
  }

  return "'" + a.toString() + "'"
}
function indent (x) {
  return '    ' + x
}
app.ws('/socket', function (ws, req) {
  // Serialize arguments to the input args file
  var args = ['var args = []']
  parsed.argv.remain.slice(firstArgumentIndex).forEach(function (x, i) {
    args.push('args[' + i + '] = (' + bashToJavaScript(x) + ')')
  })
  args.push('return args')
  fs.writeFileSync(path.join(__dirname, 'input', 'args.js'),
    'define(function () {\n' +
    args.map(indent).join('\n') + '\n' +
    '})'
  )

  // Determine files to load
  var modules = ['/input/args.js']
  if (!parsed.expression) {
    modules.push(path.join('/build', path.basename(parsed.argv.remain[0])))
  }

  // Generate code to run. First load dependencies, then execute the code
  var code = 'require([' + modules.map(JSON.stringify).join(',') + '], function (args) {\n' +
    parsed.expression + ';\n' +
    "if (typeof run === 'function') {\n" +
    '  run.apply(null, args)\n' +
    '}\n' +
    '})'

  var cmd = JSON.stringify({ 'type': 'eval', 'code': code })
  if (parsed.verbose) {
    console.log('Sending: ' + cmd)
  }
  ws.send(cmd)

  ws.on('message', function (msg) {
    if (parsed.verbose) {
      console.log('Received: ' + msg)
    }

    try {
      var o = JSON.parse(msg)
      if (o.type === 'done') {
        ws.send(JSON.stringify({ 'type': 'close' }), function (error) {
          if (error) {
            process.stderr.write(error + '\n')
            process.exit(1)
          } else {
            process.exit(0)
          }
        })
      } else if (o.type === 'status') {
        if (parsed.verbose && o.status === 'connected') {
          console.log('Connection confirmed')
        }
      } else if (o.type === 'output') {
        console.log(o.output)
      } else if (o.type === 'load') {
        var data = JSON.stringify({
          'type': 'loadresult',
          'data': fs.readFileSync(o.path).toString()
        })
        ws.send(data)
      } else if (o.type === 'save') {
        fs.writeFileSync(o.path, o.data)
      } else if (o.type === 'error') {
        process.stderr.write(o.message + '\n')
        process.exit(1)
      } else {
        if (parsed.verbose) {
          console.log('Unknown message: ' + msg)
        }
      }
    } catch (e) {
      if (parsed.verbose) {
        console.log('Invalid JSON from message: ' + msg)
      }
    }
  })

  process.stdin.on('readable', function () {
    var chunk = process.stdin.read()
    if (chunk !== null) {
      ws.send(chunk.toString())
    }
  })
})
exports.start = function (startCmd) {
  app.listen(8080, function () {
    var cmd = startCmd + ' http://localhost:8080/page/run.html'
    var status = shelljs.exec(cmd, {silent: !parsed.verbose})
    if (status.code !== 0) {
      console.log("Execution error for '" + cmd + "':")
      console.log(status.output)
      process.exit(1)
    }
  })
}
<commit_msg>Fixed bug<commit_after>#!/usr/bin/env node
var path = require('path')
var nopt = require('nopt')
var noptUsage = require('nopt-usage')
var shelljs = require('shelljs')
var fs = require('fs')
var express = require('express')
var app = express()
require('express-ws')(app)

var knownOpts = {
  'expression': String,
  'help': Boolean,
  'verbose': Boolean
}
var shortHands = {
  'e': ['--expression'],
  'h': ['--help'],
  'v': ['--verbose']
}
var description = {
  'expression': 'expression to evaluate instead of a file',
  'help': 'display this help',
  'verbose': 'show results from intermediary stages'
}
var parsed = nopt(knownOpts, shortHands)
var missingArgument = (parsed.argv.remain.length < 1 && !parsed.expression)
var firstArgumentIndex = parsed.expression ? 0 : 1

if (parsed.help || missingArgument) {
  var usage = 'usage: run [options] file [A [A ...]]\n\n' +
    'Unified interface for running javascript functions in a browser.\n\n' +
    'positional arguments:\n' +
    '  file\t\tJavaScript file to execute in the browser\n' +
    '  A\t\tpositional argument(s) to pass to the function\n\n' +
    'optional arguments: \n' +
    noptUsage(knownOpts, shortHands, description)
  console.log(usage)
  process.exit(1)
}

app.use('/page', express.static(path.join(__dirname, './public')))
app.use('/build', express.static(path.dirname(parsed.argv.remain[0]).toString()))
app.use('/input', express.static(path.join(__dirname, './input')))

function bashToJavaScript (a) {
  if (Number.parseInt(a, 10) || Number.parseFloat(a)) {
    return a.toString()
  }

  if (a.indexOf('@') >= 0) {
    var tuple = a.split('@')
    var type = tuple[0].split('-')
    var inputPath = tuple[1]

    // Expected type
    if (type[0] !== 'array') {
      console.log("Invalid type '" + tuple[0] + "', expected an 'array' prefix from argument '" + a + "'")
      process.exit(1)
    }

    if (type[1] !== 'int32' &&
      type[1] !== 'float64') {
      console.log("Invalid array type '" + tuple[1] + "', expected an 'int32' or 'float64'")
      process.exit(1)
    }

    var size = type.slice(2).map(Number)

    // Input file
    var ext = path.extname(inputPath)

    var data = []
    var fileData
    if (ext === '.csv') {
      fileData = fs.readFileSync(inputPath).toString()
      fileData.split('\n').forEach(function (line) {
        if (line === '') return
        line.split(',').forEach(function (s) {
          var n = Number.parseInt(s, 10) || Number.parseFloat(s)
          if (!n) {
            console.log("Invalid number '" + s + "' in file " + inputPath)
            process.exit(1)
          }
          data.push(n)
        })
      })
    } else if (ext === '.pgm') {
      fileData = fs.readFileSync(inputPath).toString()
      fileData.split('\n').slice(3).forEach(function (line) {
        if (line === '') return
        line.split(' ').forEach(function (s) {
          if (s === '') return
          var n = Number.parseInt(s, 10) || Number.parseFloat(s)

          if (n === null) {
            console.log("Invalid null value for '" + s + "'")
          }
          if (!n) {
            console.log("Invalid number '" + s + "' in file " + inputPath)
            process.exit(1)
          }
          data.push(n)
        })
      })
    } else {
      console.log("Unsupported extension '" + ext + "'")
      process.exit(1)
    }

    var jsArrayType = ((type[1] === 'int32') ? 'Int32Array' : 'Float64Array')

    var array = '{\n' +
      "        type: 'array',\n" +
      "        'array-type': " + JSON.stringify(type[1]) + ',\n' +
      '        size: ' + JSON.stringify(size) + ',\n' +
      "        layout: 'row-major'" + ',\n' +
      '        data: ' + 'new ' + jsArrayType + '(' + JSON.stringify(data) + ')\n' +
      '    }'

    return array
  }

  return "'" + a.toString() + "'"
}
function indent (x) {
  return '    ' + x
}
app.ws('/socket', function (ws, req) {
  // Serialize arguments to the input args file
  var args = ['var args = []']
  parsed.argv.remain.slice(firstArgumentIndex).forEach(function (x, i) {
    args.push('args[' + i + '] = (' + bashToJavaScript(x) + ')')
  })
  args.push('return args')
  fs.writeFileSync(path.join(__dirname, 'input', 'args.js'),
    'define(function () {\n' +
    args.map(indent).join('\n') + '\n' +
    '})'
  )

  // Determine files to load
  var modules = ['/input/args.js']
  if (!parsed.expression) {
    modules.push(path.join('/build', path.basename(parsed.argv.remain[0])))
  }

  // Generate code to run. First load dependencies, then execute the code
  var code = 'require([' + modules.map(JSON.stringify).join(',') + '], function (args) {\n' +
    parsed.expression + ';\n' +
    "if (typeof run === 'function') {\n" +
    '  run.apply(null, args)\n' +
    '}\n' +
    '})'

  var cmd = JSON.stringify({ 'type': 'eval', 'code': code })
  if (parsed.verbose) {
    console.log('Sending: ' + cmd)
  }
  ws.send(cmd)

  ws.on('message', function (msg) {
    if (parsed.verbose) {
      console.log('Received: ' + msg)
    }

    try {
      var o = JSON.parse(msg)
      if (o.type === 'done') {
        ws.send(JSON.stringify({ 'type': 'close' }), function (error) {
          if (error) {
            process.stderr.write(error + '\n')
            process.exit(1)
          } else {
            process.exit(0)
          }
        })
      } else if (o.type === 'status') {
        if (parsed.verbose && o.status === 'connected') {
          console.log('Connection confirmed')
        }
      } else if (o.type === 'output') {
        console.log(o.output)
      } else if (o.type === 'load') {
        var data = JSON.stringify({
          'type': 'loadresult',
          'data': fs.readFileSync(o.path).toString()
        })
        ws.send(data)
      } else if (o.type === 'save') {
        fs.writeFileSync(o.path, o.data)
      } else if (o.type === 'error') {
        process.stderr.write(o.message + '\n')
        process.exit(1)
      } else {
        if (parsed.verbose) {
          console.log('Unknown message: ' + msg)
        }
      }
    } catch (e) {
      if (parsed.verbose) {
        console.log('Invalid JSON from message: ' + msg)
      }
    }
  })

  process.stdin.on('readable', function () {
    var chunk = process.stdin.read()
    if (chunk !== null) {
      ws.send(chunk.toString())
    }
  })
})
exports.start = function (startCmd) {
  app.listen(8080, function () {
    var cmd = startCmd + ' http://localhost:8080/page/run.html'
    var status = shelljs.exec(cmd, {silent: !parsed.verbose})
    if (status.code !== 0) {
      console.log("Execution error for '" + cmd + "':")
      console.log(status.output)
      process.exit(1)
    }
  })
}
<|endoftext|>


<commit_before>/* G. Hemingway Copyright @2014
 * Shape class for the CAD models
 */
"use strict";

import Shell from './shell';
import Annotation from './annotation';

/********************************* Shape Class ********************************/
export default class Shape extends THREE.EventDispatcher {
    constructor(shapeJSON,manager,id) {
        super();
        this._manager=manager;
        this.id = id;
        this._shells = [];
        this._annotations = [];
        this._geoms = new THREE.Group();
        this._geoms.userData.id = id;
        this._bbox = [0,0,0,0,0,0];
        this._transform = new THREE.Matrix4();
        this._inScene = false;
        this._isVisible = false;
        _.each(shapeJSON,(geom)=>{
           switch(geom.type){
               case 'mesh':
                   let shell = new Shell(geom.geom);
                   this._geoms.add(shell.getGeometry());
                   this._shells.push(shell);
                   break;
               case 'annotation':
                   let anno = new Annotation(geom.geom);
                   this._geoms.add(ann.getGeometry());
                   this._annotations.push();
                   break;
               case 'default':
                   console.log('unexpected item in bagging area');
                   break;
           }
        })
        return this;
    }
    toggleVisibility(){
        this._geoms.visible = !this._geoms.visible;
    }
    isVisible(){
        return this._geoms.visible;
    }
    getBoundingBox(){
        return new THREE.Box3().setFromObject(this._geoms);
    }
    getGeometry(){
        return this._geoms;
    }

    setManager(manager){
        this._manager = manager;
    }
    show(){
        this._geoms.visible = true;
    }

    hide(){
        this._geoms.visible = false;
    }

    addToScene(bbox,xform){
        let transform = new THREE.Matrix4();
        transform.set(
            xform[0],xform[4],xform[8],xform[12],
            xform[1],xform[5],xform[9],xform[13],
            xform[2],xform[6],xform[10],xform[14],
            xform[3],xform[7],xform[11],xform[15],
        )
        this._geoms.applyMatrix(transform);
        this._geoms.updateMatrixWorld();
        this._bbox = bbox;
        this._transform = transform;
        if(this._inScene) return;
        this._manager.addShape(this);
        this._inScene = true;
    };
    removeFromScene(){
        if(!this._inScene) return;
        this._manager.removeShape(this);
        this._inScene = false;
    };
};
<commit_msg>HOW DO I SWITCH STATEMENT?<commit_after>/* G. Hemingway Copyright @2014
 * Shape class for the CAD models
 */
"use strict";

import Shell from './shell';
import Annotation from './annotation';

/********************************* Shape Class ********************************/
export default class Shape extends THREE.EventDispatcher {
    constructor(shapeJSON,manager,id) {
        super();
        this._manager=manager;
        this.id = id;
        this._shells = [];
        this._annotations = [];
        this._geoms = new THREE.Group();
        this._geoms.userData.id = id;
        this._bbox = [0,0,0,0,0,0];
        this._transform = new THREE.Matrix4();
        this._inScene = false;
        this._isVisible = false;
        _.each(shapeJSON,(geom)=>{
           switch(geom.type){
               case 'mesh':
                   let shell = new Shell(geom.geom);
                   this._geoms.add(shell.getGeometry());
                   this._shells.push(shell);
                   break;
               case 'annotation':
                   let anno = new Annotation(geom.geom);
                   this._geoms.add(ann.getGeometry());
                   this._annotations.push();
                   break;
               default:
                   console.log('unexpected item in bagging area');
                   break;
           }
        })
        return this;
    }
    toggleVisibility(){
        this._geoms.visible = !this._geoms.visible;
    }
    isVisible(){
        return this._geoms.visible;
    }
    getBoundingBox(){
        return new THREE.Box3().setFromObject(this._geoms);
    }
    getGeometry(){
        return this._geoms;
    }

    setManager(manager){
        this._manager = manager;
    }
    show(){
        this._geoms.visible = true;
    }

    hide(){
        this._geoms.visible = false;
    }

    addToScene(bbox,xform){
        let transform = new THREE.Matrix4();
        transform.set(
            xform[0],xform[4],xform[8],xform[12],
            xform[1],xform[5],xform[9],xform[13],
            xform[2],xform[6],xform[10],xform[14],
            xform[3],xform[7],xform[11],xform[15],
        )
        this._geoms.applyMatrix(transform);
        this._geoms.updateMatrixWorld();
        this._bbox = bbox;
        this._transform = transform;
        if(this._inScene) return;
        this._manager.addShape(this);
        this._inScene = true;
    };
    removeFromScene(){
        if(!this._inScene) return;
        this._manager.removeShape(this);
        this._inScene = false;
    };
};
<|endoftext|>


<commit_before>/**
 * Sample React Native App
 * https://github.com/facebook/react-native
 */
import React, {
  AppRegistry,
  StyleSheet,
  Component
  Text,
  View,
} from 'react-native';
import Notification from 'react-native-notification';

class NotificationExample extends Component {
  render() {
    return (
      <View style={styles.container}>
        <Text style={styles.welcome}>
          Welcome to React Native!
        </Text>
        <Text style={styles.instructions}>
          To get started, edit index.ios.js
        </Text>
        <Text style={styles.instructions}>
          Press Cmd+R to reload,{'\n'}
          Cmd+D or shake for dev menu
        </Text>
      </View>
    );
  }
}

var styles = StyleSheet.create({
  container: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center',
    backgroundColor: '#F5FCFF',
  },
  welcome: {
    fontSize: 20,
    textAlign: 'center',
    margin: 10,
  },
  instructions: {
    textAlign: 'center',
    color: '#333333',
    marginBottom: 5,
  },
});

AppRegistry.registerComponent('NotificationExample', () => NotificationExample);
<commit_msg>Update example<commit_after>/**
 * Sample React Native App
 * https://github.com/facebook/react-native
 */
import React, {
  AppRegistry,
  StyleSheet,
  Component,
  Text,
  View,
  TouchableOpacity,
} from 'react-native';
import Notification from 'react-native-notification';

class NotificationExample extends Component {
  constructor(props) {
    super(props);

    this.state = {
      notificationVisible: false,
    }
  }

  onPress = () => {
    this.setState({
      notificationVisible: true,
    });

    setInterval(() => {
      this.setState({
        notificationVisible: false,
      });
    }, 3000);
  };

  render() {
    return (
      <View style={styles.container}>
        <TouchableOpacity onPress={this.onPress}>
          <Text style={styles.welcome}>
            Tap me!
          </Text>
        </TouchableOpacity>
        <Notification visible={this.state.notificationVisible}>
          Hi there!
        </Notification>
      </View>
    );
  }
}

var styles = StyleSheet.create({
  container: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center',
    backgroundColor: '#F5FCFF',
  },
  welcome: {
    fontSize: 20,
    textAlign: 'center',
    margin: 10,
  },
  instructions: {
    textAlign: 'center',
    color: '#333333',
    marginBottom: 5,
  },
});

AppRegistry.registerComponent('NotificationExample', () => NotificationExample);
<|endoftext|>


<commit_before>'use strict';

// Declare app level module which depends on filters, and services
angular.module('myApp.config', [])
   .constant('version', '0.1')
   //.constant('FBURL', 'https://INSTANCE.firebaseio.com');
   .constant('FBURL', 'https://angularfire-seed.firebaseio.com/');<commit_msg>completed test units, 100% passing (still needs e2e tests before release)<commit_after>'use strict';

// Declare app level module which depends on filters, and services
angular.module('myApp.config', [])
   .constant('version', '0.1')
   .constant('FBURL', 'https://INSTANCE.firebaseio.com');
<|endoftext|>


<commit_before>/**
 * utils.js
 *
 * Common helper functions.
 */
var fs = require('fs-extra')
var child_process = require('child_process')
var path = require('path')

/**
 * Uncompress the given src archive file to dst folder.
 *
 * Returns the name of the uncomressed file (caller is responsible
 * for cleaning it up eventually).
 */
module.exports.uncompress = function (src, dst, cb) {
  try {
    fs.ensureDirSync(dst)

    child_process.exec(
            'dtrx -q -n -f ' + src,
            { cwd: dst },
            function (err, stdout, stderr) {
              if (err) return cb(err)
              return cb(null, path.join(dst, path.basename(src).replace('.zip', '')))
            })
  } catch (err) {
    cb(err, undefined)
  }
}

/** Return max of the two dates. */
module.exports.datemax = function (a, b) {
  if (!b) return a
  if (!a) return b
  return (a.getTime() >= b.getTime() ? a : b)
}

/** Return max of the two dates. */
module.exports.datemin = function (a, b) {
  if (!b) return a
  if (!a) return b
  return (a.getTime() <= b.getTime() ? a : b)
}
<commit_msg>Fix utils errors<commit_after>/**
 * utils.js
 *
 * Common helper functions.
 */
var fs = require('fs-extra')
var childProcess = require('child_process')
var path = require('path')

/**
 * Uncompress the given src archive file to dst folder.
 *
 * Returns the name of the uncomressed file (caller is responsible
 * for cleaning it up eventually).
 */
module.exports.uncompress = function (src, dst, cb) {
  try {
    fs.ensureDirSync(dst)

    childProcess.exec(
            'dtrx -q -n -f ' + src,
            { cwd: dst },
            function (err, stdout, stderr) {
              if (err) return cb(err)
              return cb(null, path.join(dst, path.basename(src).replace('.zip', '')))
            })
  } catch (err) {
    cb(err, undefined)
  }
}

/** Return max of the two dates. */
module.exports.datemax = function (a, b) {
  if (!b) return a
  if (!a) return b
  return (a.getTime() >= b.getTime() ? a : b)
}

/** Return max of the two dates. */
module.exports.datemin = function (a, b) {
  if (!b) return a
  if (!a) return b
  return (a.getTime() <= b.getTime() ? a : b)
}
<|endoftext|>


<commit_before>// Monitoring function to be called every minute
var monitor = function() {
  // We need to get all monitoring jobs
  var jobs = Jobs.find( { isDeleted: false, isActive: true } );
  jobs.forEach(function(element, index, array) {
    var lastCursor = Events.find({ jobId : element._id},
                                 {sort: { etime : -1}, limit: 1});
    if (0 === lastCursor.count()) {
      triggerJob(element, null);
    } else {
      var last = lastCursor.fetch()[0];
      if (isDue(element, last)) {
        triggerJob(element, last);
      }
    }
  });

  Meteor.call("updateEventStats", function(err, data) {
    if (err) console.log("updateEventStats error: " + err);
  });
};
// Checks whether a job needs to run
var isDue = function(job, last) {
  var lastRun = last.etime;
  var due  = new Date(lastRun.setMinutes(lastRun.getMinutes() + job.freq));
  var current = new Date();
  return current >= due;
};

// Unleash a job
var triggerJob = function(job, last) {
  var lastUpdate;
  if (null === last) { //This will be true only the 1st time a job is run
    lastUpdate = "1970-01-01T00:00:00Z";
  } else {
    lastUpdate = moment(last.etime).format();
  }
  var startTime = new Date();
  job.lastRun = startTime;
  var params = {};
  if (job.deltas) {
    params.updatedAfter = lastUpdate;
  }

  //-- request options
  var options = {
    strictSSL: false,
    agentOptions: {
      secureProtocol: 'TLSv1_method'
      /*rejectUnauthorized: false*/
    },
    params: params,
    headers: {
      "User-Agent": "Heimdallr 1.0.0"
    }
  };

  if (proxy){
    options.proxy = proxy;
  }
  if (job.format === "sdmx-generic-2.1") {
    options.headers.Accept = "application/vnd.sdmx.genericdata+xml;version=2.1";
  } else if (job.format === "sdmx-compact-2.1") {
    options.headers.Accept = "application/vnd.sdmx.structurespecificdata+xml;version=2.1";
  } else if (job.format === "sdmx-json-1.0.0") {
    options.headers.Accept = "application/vnd.sdmx.data+json;version=1.0.0-wd";
  } else { // default/unknown is SDMX-JSON
    options.headers.Accept =  "application/vnd.sdmx.data+json;version=1.0.0-wd"
  }

  if (job.isCompressed) {
    //options.headers['Accept-Encoding'] = "gzip";
    options.gzip = true;
  }

  if (job.isIMS && null !== last) {
    options.headers["If-Modified-Since"] = new Date(last.etime).toUTCString();
  }

  //-- request
  HTTP.call("GET", job.url, options , function (error, result) {
    //console.log("result from HTTP.call(..., function(error, result)) for job (" + job.name + ") : ");
    //console.dir(result);
    var received = new Date();
    if (result) {

      var status = result.statusCode;
      var serieObs = {nSeries: 0, nObs: 0};
      if (200 === status) {
        switch(job.format){
          case "sdmx-generic-2.1":
            serieObs = parseGenericXML(result.content);
            break;
          case "sdmx-compact-2.1":
            serieObs = parseCompactXML(result.content);
            break;
          case "sdmx-json-1.0.0":
            serieObs = parseJSON(result.content);
            break;
          default:
            serieObs = parseJSON(result.content);
        }
      }
      Meteor.call("eventInsert", job, status, received - startTime, serieObs.nSeries, serieObs.nObs);
      return;
    } else {
      // alert should be raised?
      console.log(error);
    }
  });
};

var parseCompactXML = function(content) {
  var ret = {nSeries: 0, nObs: 0};
  xml2js.parseString(content, function (err, result) {
    result['message:StructureSpecificData']['message:DataSet'].forEach(function (dataset, index, array) {
      var dsSeries = dataset.Series;
      dsSeries.forEach(function (series, index, array) {
        ret.nSeries++;
        var obs = series.Obs;
        ret.nObs = ret.nObs + obs.length;
      });
    });
  });
  return ret;
};

var parseGenericXML = function(content) {
  var ret = {nSeries: 0, nObs: 0};
  xml2js.parseString(content, function (err, result) {
    result['message:GenericData']['message:DataSet'].forEach(function (dataset, index, array) {
      var dsSeries = dataset['generic:Series'];
      dsSeries.forEach(function (series, index, array) {
        ret.nSeries++;
        var obs = series['generic:Obs'];
        ret.nObs = ret.nObs + obs.length;
      });
    });
  });
  return ret;
};

var parseJSON = function(content) {
  var ret = {nSeries: 0, nObs: 0};
  var response = JSON.parse(content);
  response.dataSets.forEach(function (element, index, array) {
    var series = element.series;
    for (var property in series) {
      if (series.hasOwnProperty(property)) {
        ret.nSeries++;
        var obs = series[property]["observations"];
        for (var obsProp in obs) {
          if (obs.hasOwnProperty(obsProp)) {
            ret.nObs++;
          }
        }
      }
    }
  });
  return ret;
}
// Start cron
Meteor.setInterval(monitor, 60 * 1000);

var proxy = process.env.http_proxy;
if (proxy) {
  console.log("Using the env variable proxy: http_proxy");
}
<commit_msg>Minor changes to scheduler<commit_after>// Monitoring function to be called every minute
var monitor = function() {
  // We need to get all monitoring jobs
  var jobs = Jobs.find( { isDeleted: false, isActive: true } );
  jobs.forEach(function(element, index, array) {
    var lastCursor = Events.find({ jobId : element._id},
                                 {sort: { etime : -1}, limit: 1});
    if (0 === lastCursor.count()) {
      triggerJob(element, null);
    } else {
      var last = lastCursor.fetch()[0];
      if (isDue(element, last)) {
        triggerJob(element, last);
      }
    }
  });

  Meteor.call("updateEventStats", function(err, data) {
    if (err) console.log("updateEventStats error: " + err);
  });
};
// Checks whether a job needs to run
var isDue = function(job, last) {
  var lastRun = last.etime;
  var due  = new Date(lastRun.setMinutes(lastRun.getMinutes() + job.freq));
  var current = new Date();
  return current >= due;
};

// Unleash a job
var triggerJob = function(job, last) {
  var lastUpdate;
  if (null === last) { //This will be true only the 1st time a job is run
    lastUpdate = "1970-01-01T00:00:00Z";
  } else {
    lastUpdate = moment(last.etime).format();
  }

  var params = {};
  if (job.deltas) {
    params.updatedAfter = lastUpdate;
  }

  //-- request options
  var options = {
    strictSSL: false,
    agentOptions: {
      secureProtocol: 'TLSv1_method'
      /*rejectUnauthorized: false*/
    },
    params: params,
    headers: {
      "User-Agent": "Heimdallr 1.0.0"
    }
  };

  if (proxy){
    options.proxy = proxy;
  }
  if (job.format === "sdmx-generic-2.1") {
    options.headers.Accept = "application/vnd.sdmx.genericdata+xml;version=2.1";
  } else if (job.format === "sdmx-compact-2.1") {
    options.headers.Accept = "application/vnd.sdmx.structurespecificdata+xml;version=2.1";
  } else if (job.format === "sdmx-json-1.0.0") {
    options.headers.Accept = "application/vnd.sdmx.data+json;version=1.0.0-wd";
  } else { // default/unknown is SDMX-JSON
    options.headers.Accept =  "application/vnd.sdmx.data+json;version=1.0.0-wd"
  }

  if (job.isCompressed) {
    options.headers['Accept-Encoding'] = "gzip";
    options.gzip = true;
  }

  if (job.isIMS && null !== last) {
    options.headers["If-Modified-Since"] = new Date(last.etime).toUTCString();
  }
  var startTime = new Date();
  job.lastRun = startTime;
  //-- request
  HTTP.call("GET", job.url, options , function (error, result) {
    if (result) {
      processResults(result, job, startTime);
    } else {
      // alert should be raised?
      console.log(error);
    }
  });
};

var processResults = function(result, job, startTime) {
  var received = new Date();
  var status = result.statusCode;
  var serieObs = {nSeries: 0, nObs: 0};
  if (200 === status) {
    var content;
    if (job.isCompressed) {
      content = gunzipSync(result.content).toString('utf-8');
    } else {
      content = result.content;
    }
    switch(job.format){
      case "sdmx-generic-2.1":
        serieObs = parseGenericXML(content);
        break;
      case "sdmx-compact-2.1":
        serieObs = parseCompactXML(content);
        break;
      case "sdmx-json-1.0.0":
        serieObs = parseJSON(content);
        break;
      default:
        serieObs = parseJSON(content);
    }
  }
  Meteor.call("eventInsert", job, status, received - startTime, serieObs.nSeries, serieObs.nObs);
}

var parseCompactXML = function(content) {
  var ret = {nSeries: 0, nObs: 0};
  xml2js.parseString(content, function (err, result) {
    result['message:StructureSpecificData']['message:DataSet'].forEach(function (dataset, index, array) {
      var dsSeries = dataset.Series;
      dsSeries.forEach(function (series, index, array) {
        ret.nSeries++;
        var obs = series.Obs;
        ret.nObs = ret.nObs + obs.length;
      });
    });
  });
  return ret;
};

var parseGenericXML = function(content) {
  var ret = {nSeries: 0, nObs: 0};
  xml2js.parseString(content, function (err, result) {
    result['message:GenericData']['message:DataSet'].forEach(function (dataset, index, array) {
      var dsSeries = dataset['generic:Series'];
      dsSeries.forEach(function (series, index, array) {
        ret.nSeries++;
        var obs = series['generic:Obs'];
        ret.nObs = ret.nObs + obs.length;
      });
    });
  });
  return ret;
};

var parseJSON = function(content) {
  var ret = {nSeries: 0, nObs: 0};
  var response = JSON.parse(content);
  response.dataSets.forEach(function (element, index, array) {
    var series = element.series;
    for (var property in series) {
      if (series.hasOwnProperty(property)) {
        ret.nSeries++;
        var obs = series[property]["observations"];
        for (var obsProp in obs) {
          if (obs.hasOwnProperty(obsProp)) {
            ret.nObs++;
          }
        }
      }
    }
  });
  return ret;
}
// Start cron
Meteor.setInterval(monitor, 60 * 1000);

var proxy = process.env.http_proxy;
if (proxy) {
  console.log("Using the env variable proxy: http_proxy");
}
<|endoftext|>


<commit_before>var db = require('./wrapper'),

    explodeMessages = function (results) {
        var words = [];

        results.forEach(function (result) {
            words = words.concat(result.message.split(' '));
        });

        return words;
    };

exports.getTicketWords = function (req, res) {
    db.getAll('ticket').from('retrospectives')
        .then(function (result) {
            var words = explodeMessages(result);
            res.json({'results': words});
        })
        .fail(function (err) {
            console.log(err);
            res.json(err);
        });
};

exports.getTickets = function (req, res) {
    db.query('retroId:' + req.params.retroId).of('ticket').from('retrospectives')
        .then(function (result) {
            res.json({'results': result, 'total': result.total});
        })
        .fail(function (err) {
            console.log(err);
            res.json(err);
        });
};

exports.getTicket = function (req, res) {
    db.query('_id:' + req.params.ticketId).of('ticket').from('retrospectives')
        .then(function (result) {
            res.json(result);
        })
        .fail(function (err) {
            console.log(err);
            res.json(err);
        });
};

exports.deleteTicket = function (req, res) {
    db.delete('ticket').withId(req.params.ticketId).from('retrospectives')
        .then(function (result) {
            res.json(result);
        })
        .fail(function (err) {
            console.log(err);
            res.json(err);
        });
};

exports.getRetrospective = function (req, res) {
    db.query('_id:' + req.params.retroId).of('retrospective').from('retrospectives')
        .then(function (result) {
            res.json(result[0]);
        })
        .fail(function (err) {
            console.log(err);
            res.json(err);
        });
};

exports.getRetrospectives = function (req, res) {
    db.getAll('retrospective').from('retrospectives')
        .then(function (result) {
            res.json({'results': result, 'total': result.total});
        })
        .fail(function (err) {
            console.log(err);
            res.json(err);
        });
};

exports.postRetrospective = function (req, res) {
    db.post(req.body).ofType('retrospective').into('retrospectives')
        .then(function (result) {
            res.json(result);
        })
        .fail(function (err) {
            console.log(err);
            res.json(err);
        });
};

exports.putRetrospective = function (req, res) {
    db.put(req.body).ofType('retrospective').withId(req.params.retroId).into('retrospectives')
        .then(function (result) {
            res.json(result);
        })
        .fail(function (err) {
            console.log(err);
            res.json(err);
        });
};

exports.putTicket = function (req, res) {
    req.body.retroId = req.params.retroId;
    db.put(req.body).ofType('ticket').withId(req.params.ticketId).into('retrospectives')
        .then(function (result) {
            res.json(result);
        })
        .fail(function (err) {
            console.log(err);
            res.json(err);
        });
};

exports.postTicketToRetrospective = function (req, res) {
    // @TODO: perhaps move retroId into req.body
    //  from the frontend?
    req.body.retroId = req.params.retroId;
    db.post(req.body).ofType('ticket').into('retrospectives')
        .then(function (result) {
            res.json(result);
        })
        .fail(function (err) {
            console.log(err);
            res.json(err);
        });
};
<commit_msg>Added optional start param for getRetrospectives.<commit_after>var _ = require('lodash'),
    db = require('./wrapper'),

    explodeMessages = function (results) {
        var words = [];

        results.forEach(function (result) {
            words = words.concat(result.message.split(' '));
        });

        return words;
    };

exports.getTicketWords = function (req, res) {
    db.getAll('ticket').from('retrospectives')
        .then(function (result) {
            var words = explodeMessages(result);
            res.json({'results': words});
        })
        .fail(function (err) {
            console.log(err);
            res.json(err);
        });
};

exports.getTickets = function (req, res) {
    db.query('retroId:' + req.params.retroId).of('ticket').from('retrospectives')
        .then(function (result) {
            res.json({'results': result, 'total': result.total});
        })
        .fail(function (err) {
            console.log(err);
            res.json(err);
        });
};

exports.getTicket = function (req, res) {
    db.query('_id:' + req.params.ticketId).of('ticket').from('retrospectives')
        .then(function (result) {
            res.json(result);
        })
        .fail(function (err) {
            console.log(err);
            res.json(err);
        });
};

exports.deleteTicket = function (req, res) {
    db.delete('ticket').withId(req.params.ticketId).from('retrospectives')
        .then(function (result) {
            res.json(result);
        })
        .fail(function (err) {
            console.log(err);
            res.json(err);
        });
};

exports.getRetrospective = function (req, res) {
    db.query('_id:' + req.params.retroId).of('retrospective').from('retrospectives')
        .then(function (result) {
            res.json(result[0]);
        })
        .fail(function (err) {
            console.log(err);
            res.json(err);
        });
};

exports.getRetrospectives = function (req, res) {
    var start = 0;
    if (!_.isUndefined(req.params.start)) {
        start = req.params.start;
    }
    db.getAll('retrospective').start(start).from('retrospectives')
        .then(function (result) {
            res.json({'results': result, 'total': result.total});
        })
        .fail(function (err) {
            console.log(err);
            res.json(err);
        });
};

exports.postRetrospective = function (req, res) {
    db.post(req.body).ofType('retrospective').into('retrospectives')
        .then(function (result) {
            res.json(result);
        })
        .fail(function (err) {
            console.log(err);
            res.json(err);
        });
};

exports.putRetrospective = function (req, res) {
    db.put(req.body).ofType('retrospective').withId(req.params.retroId).into('retrospectives')
        .then(function (result) {
            res.json(result);
        })
        .fail(function (err) {
            console.log(err);
            res.json(err);
        });
};

exports.putTicket = function (req, res) {
    req.body.retroId = req.params.retroId;
    db.put(req.body).ofType('ticket').withId(req.params.ticketId).into('retrospectives')
        .then(function (result) {
            res.json(result);
        })
        .fail(function (err) {
            console.log(err);
            res.json(err);
        });
};

exports.postTicketToRetrospective = function (req, res) {
    // @TODO: perhaps move retroId into req.body
    //  from the frontend?
    req.body.retroId = req.params.retroId;
    db.post(req.body).ofType('ticket').into('retrospectives')
        .then(function (result) {
            res.json(result);
        })
        .fail(function (err) {
            console.log(err);
            res.json(err);
        });
};
<|endoftext|>


<commit_before>var apikeys = require('../apikeys');

var Promise       = require('bluebird'),
    request       = require('request'),
    //failed to promisify xml2js (not sure if by own error or by bug in either lib)
    parseXML      = require('xml2js').parseString;

var redis         = require("redis"),
    redisClient   = redis.createClient();

redisClient.on("error", function (err) {
    console.log("Redis Error: ", err);
});

var synonyms = {
    getSynonyms : function(word) {
        var self = this;
        return new Promise(function(resolve, reject) {

            var synonymsList = [];

            //check if synonym exists in cache
            var getCachedSynonymsPromise = self._getCachedSynonyms(word);

            //Handle cache repsonse
            var apiPromise = getCachedSynonymsPromise.then(function(result) {
                    if(!result) {
                        //No cached synonyms found so make api request
                        return self._makeApiRequest(word);
                    }
                    //Cached synonyms found
                    resolve(result);
                })
                .error(function(err) {
                    reject(err);
                });

            //Handle Api Response
            var setCachedSynonymsPromise = apiPromise.then(function(result) {
                    //check for result to see if apiRequest was even made. this fix works, but seems hacky
                    if(result) {
                        synonymsList = result;
                        return self._setCachedSynonyms(word, result);
                    }
                })
                .error(function(err) {
                    reject(err);
                });

            //Handle setting cache response
            setCachedSynonymsPromise.then(function(result) {
                    if(result) {
                        //console.log('success setting cached synonyms: ', result);
                    } else {
                        //console.log('results already cached');
                    }
                })
                .error(function(err) {
                    //console.log('error setting cached synonyms');
                })
                .finally(function() {
                    resolve(synonymsList);
                });

        });
    },

    /*
     * Utility Methods
     * These methods below are exposed for unit testing purposes only
     */

    _makeApiRequest : function(word) {

        var self = this;

        return new Promise(function(resolve, reject) {
            //word was not in cache
            var url =
                'http://www.dictionaryapi.com/api/v1/references/thesaurus/xml/' +
                word +
                '?key=' +
                apikeys.thesaurus;

            request(url, function(error, response, body) {
                if(error) {
                    reject(error);
                    return;
                }
                parseXML(body, function(error, result) {
                    if(error) {
                        reject(error);
                    } else {
                        if(result.suggestion || result.entry_list.suggestion) {
                            //reject({error: 'Synonyms not found. Please check your spelling.'});
                            resolve(false);
                        }
                        var parsedSynonyms = self._parseSynonymsXML(result);
                        if(parsedSynonyms.error) {
                            reject(parsedSynonyms);
                        }

                        var synonymsList = self._synonymsList(parsedSynonyms);
                        resolve(synonymsList);
                    }
                })
            });
        });
    },

    _getCachedSynonyms : function(word) {
        return new Promise(function(resolve, reject) {
            redisClient.get('synonyms:' + word, function(err, result) {
                if(err) {
                    reject(err);
                } else if(result) {
                    //results are stored in redis as a string. Need to blow it up into array
                    var resultArray = result.split(',');
                    resolve(resultArray);
                } else {
                    resolve(false);
                }
            });
        });
    },

    _setCachedSynonyms : function(word, synonyms) {
        return new Promise(function(resolve, reject) {
            //cache synonyms results
            redisClient.set('synonyms:' + word, synonyms, function(err, result) {
                if(err) {
                    reject(err);
                } else if(result) {
                    var secondsPerDay = 60 * 60 * 24;
                    var ttlDays = 180;
                    var ttl = ttlDays * secondsPerDay;
                    redisClient.expire('synonyms:' + word, ttl, function(err, res) {
                        //console.log('expire err:', err);
                        //console.log('expire result: ', res);
                    });
                    resolve(result);
                } else {
                    reject('Redis: Unkown error caching synonymsList');
                }
            });
        });
    },

    //this method returns an object of mapped data, or an object with an error property
    _parseSynonymsXML : function(rawSynonyms) {
        //check for needed properties
        if( !rawSynonyms ||
            !rawSynonyms.entry_list ||
            !rawSynonyms.entry_list.entry ||
            rawSynonyms.entry_list.entry.length == 0 ) {

                return {error: '_mapRawSynonyms: rawSynonyms fails validation'};
        }

        //an entry differentiates between words by type like noun or verb
        var entries = [];
        var entryCount = rawSynonyms.entry_list.entry.length;
        for(var i = 0; i < entryCount; i++) {
            var rawEntry = rawSynonyms.entry_list.entry[i];

            var entry = {};
            entry.wordType = rawEntry.fl;

            var senses = [];
            var senseCount  = rawEntry.sens.length;
            for(var j = 0; j < senseCount; j++) {
                var rawSense = rawEntry.sens[j];

                //Checking for valid Sense.
                //  The API sometimes returns invalid Senses mixed with valid ones (investigate why)
                if( !rawSense ||
                    !rawSense.mc ||
                    !rawSense.mc.length ||
                    !rawSense.rel ||
                    !rawSense.rel.length ||
                    !rawSense.syn ||
                    !rawSense.syn.length) {
                    //console.log('rawSense is null. senseCount: ', senseCount, ' j: ', j, ' i: ', i);
                    continue;
                }

                var sense = {};
                sense.meaning = rawSense.mc[0];

                var rel = rawSense.rel[0];
                if(rel._) {
                    rel = rel._;
                }
                var syn = rawSense.syn[0];
                if(syn._) {
                    syn = syn._;
                }
                var relWords = this._extractKeywords(rel);
                var synWords = this._extractKeywords(syn);
                sense.words = synWords.concat(relWords);

                senses.push(sense);
            }
            entry.senses = senses;

            entries.push(entry);
        }

        return entries;
    },

    _extractKeywords : function(str) {
        return str
            //remove whitespace
            .replace(/(\s)/g, '')
            //remove parentheses and words in them
            .replace(/(\(.*\))/g, '')
            //replace semicolons with commas
            .replace(/([;])/g, ',')
            //remove square brackets (are they empty? I forget the format that caused this check to be necessary)
            .replace(/(\[\])/g, '')
            //remove hyphens
            .replace(/(-)/g, '')
            //use commas to split words into an array
            .split(',');
    },

    //Takes in a synonyms array.
    /*
        synonyms[] {
            wordType : ...
            senses[] : {
                meaning : ...
                words[] : ...
            }
        }
    */
    _synonymsList : function(synonyms) {
        //storing domains as a hash
        var synonymsHash = {};

        //so many loops. caching lengths to squeeze performance a bit.
        var synonymsCount = synonyms.length;
        for(var i = 0; i < synonymsCount; i++) {
            var synonymSet = synonyms[i]
            var senseCount = synonymSet.senses.length;
            for(var j = 0; j < senseCount; j++) {
                var sense = synonymSet.senses[j];

                var wordCount = sense.words.length;
                for(var k = 0; k < wordCount; k++) {
                    var synonym = sense.words[k];
                    synonymsHash[synonym] = true;
                }
            }
        }

        var result = [], prop, i;

        for (prop in synonymsHash) {
            if (hasOwnProperty.call(synonymsHash, prop)) {
                result.push(prop);
            }
        }

        return result;
    }
};

module.exports = synonyms;<commit_msg>Implement basic Wordnik api request functionality and async fetching along with original data<commit_after>var apikeys = require('../apikeys');

var Promise       = require('bluebird'),
    request       = require('request'),
    //failed to promisify xml2js (not sure if by own error or by bug in either lib)
    parseXML      = require('xml2js').parseString;

var redis         = require("redis"),
    redisClient   = redis.createClient();

redisClient.on("error", function (err) {
    console.log("Redis Error: ", err);
});

var synonyms = {
    getSynonyms : function(word) {
        var self = this;
        return new Promise(function(resolve, reject) {

            var synonymsList = [];

            //check if synonym exists in cache
            var getCachedSynonymsPromise = self._getCachedSynonyms(word);

            //Handle cache repsonse
            var apiPromise = getCachedSynonymsPromise.then(function(result) {
                    if(!result) {
                        //No cached synonyms found so make api request
                        return Promise.all([self._makeApiRequest(word), self._makeWordnikApiRequest(word)]);
                    }
                    //Cached synonyms found
                    resolve(result);
                })
                .error(function(err) {
                    reject(err);
                })
                //handle the all or continue passing along the empty result
                .then(function(result) {
                    if(result) {
                        //console.log('wordnik api request', result);
                        var joinedSynonyms = result[0]
                            .concat(result[1])
                            .sort()
                            .filter(function(item, pos, arr) {
                                return !pos || item != arr[pos - 1];
                            });
                        return joinedSynonyms;
                    }
                }, function(err) {
                    console.log('error making api requests: ', err);
                });

            //Handle Api Response
            var setCachedSynonymsPromise = apiPromise.then(function(result) {
                    //check for result to see if apiRequest was even made. this fix works, but seems hacky
                    if(result) {
                        synonymsList = result;
                        return self._setCachedSynonyms(word, result);
                    }
                })
                .error(function(err) {
                    reject(err);
                });

            //Handle setting cache response
            setCachedSynonymsPromise.then(function(result) {
                    if(result) {
                        //console.log('success setting cached synonyms: ', result);
                    } else {
                        //console.log('results already cached');
                    }
                })
                .error(function(err) {
                    console.log('error setting cached synonyms: ', err);
                })
                .finally(function() {
                    resolve(synonymsList);
                });

        });
    },

    /*
     * Utility Methods
     * These methods below are exposed for unit testing purposes only
     */

    _makeApiRequest : function(word) {

        var self = this;

        return new Promise(function(resolve, reject) {
            //word was not in cache
            var url =
                'http://www.dictionaryapi.com/api/v1/references/thesaurus/xml/' +
                word +
                '?key=' +
                apikeys.thesaurus;

            request(url, function(error, response, body) {
                if(error) {
                    reject(error);
                    return;
                }
                parseXML(body, function(error, result) {
                    if(error) {
                        reject(error);
                    } else {
                        if(result.suggestion || result.entry_list.suggestion) {
                            //reject({error: 'Synonyms not found. Please check your spelling.'});
                            resolve(false);
                        }
                        var parsedSynonyms = self._parseSynonymsXML(result);
                        if(parsedSynonyms.error) {
                            reject(parsedSynonyms);
                        }

                        var synonymsList = self._synonymsList(parsedSynonyms);
                        resolve(synonymsList);
                    }
                })
            });
        });
    },

    _makeWordnikApiRequest : function(word) {

        var self = this;

        return new Promise(function(resolve, reject) {
            //word was not in cache
            var url =
                'http://api.wordnik.com:80/v4/word.json/' +
                word +
                '/relatedWords?useCanonical=false&limitPerRelationshipType=100&api_key=' +
                apikeys.wordnik;

            request(url, function(error, response, body) {
                if(error) {
                    reject(error);
                    return;
                }
                //parsing the json from wordnik is so much easier
                //console.log('Wordnik Response: ', body);
                resolve(self._parseWordnikResults(JSON.parse(body)));
            });
        });
    },

    _parseWordnikResults : function(results) {
        // hash table for fast lookups
        var wantedRelationshipTypes = {
            "verb-form": true,
            "hypernym": true,
            "etymologically-related-term": true,
            "variant": true,
            "same-context": true,
            "synonym": true
        };
        var words = [];
        for(var i = 0; i < results.length; i++) {
            if(wantedRelationshipTypes[results[i].relationshipType] === true) {
                words = words.concat(results[i].words);
            }
        }
        return words;
    },

    _getCachedSynonyms : function(word) {
        return new Promise(function(resolve, reject) {
            redisClient.get('synonyms:' + word, function(err, result) {
                if(err) {
                    reject(err);
                } else if(result) {
                    //results are stored in redis as a string. Need to blow it up into array
                    var resultArray = result.split(',');
                    resolve(resultArray);
                } else {
                    resolve(false);
                }
            });
        });
    },

    _setCachedSynonyms : function(word, synonyms) {
        return new Promise(function(resolve, reject) {
            //cache synonyms results
            redisClient.set('synonyms:' + word, synonyms, function(err, result) {
                if(err) {
                    reject(err);
                } else if(result) {
                    var secondsPerDay = 60 * 60 * 24;
                    var ttlDays = 180;
                    var ttl = ttlDays * secondsPerDay;
                    redisClient.expire('synonyms:' + word, ttl, function(err, res) {
                        //console.log('expire err:', err);
                        //console.log('expire result: ', res);
                    });
                    resolve(result);
                } else {
                    reject('Redis: Unkown error caching synonymsList');
                }
            });
        });
    },

    //this method returns an object of mapped data, or an object with an error property
    _parseSynonymsXML : function(rawSynonyms) {
        //check for needed properties
        if( !rawSynonyms ||
            !rawSynonyms.entry_list ||
            !rawSynonyms.entry_list.entry ||
            rawSynonyms.entry_list.entry.length == 0 ) {

                return {error: '_mapRawSynonyms: rawSynonyms fails validation'};
        }

        //an entry differentiates between words by type like noun or verb
        var entries = [];
        var entryCount = rawSynonyms.entry_list.entry.length;
        for(var i = 0; i < entryCount; i++) {
            var rawEntry = rawSynonyms.entry_list.entry[i];

            var entry = {};
            entry.wordType = rawEntry.fl;

            var senses = [];
            var senseCount  = rawEntry.sens.length;
            for(var j = 0; j < senseCount; j++) {
                var rawSense = rawEntry.sens[j];

                //Checking for valid Sense.
                //  The API sometimes returns invalid Senses mixed with valid ones (investigate why)
                if( !rawSense ||
                    !rawSense.mc ||
                    !rawSense.mc.length ||
                    !rawSense.rel ||
                    !rawSense.rel.length ||
                    !rawSense.syn ||
                    !rawSense.syn.length) {
                    //console.log('rawSense is null. senseCount: ', senseCount, ' j: ', j, ' i: ', i);
                    continue;
                }

                var sense = {};
                sense.meaning = rawSense.mc[0];

                var rel = rawSense.rel[0];
                if(rel._) {
                    rel = rel._;
                }
                var syn = rawSense.syn[0];
                if(syn._) {
                    syn = syn._;
                }
                var relWords = this._extractKeywords(rel);
                var synWords = this._extractKeywords(syn);
                sense.words = synWords.concat(relWords);

                senses.push(sense);
            }
            entry.senses = senses;

            entries.push(entry);
        }

        return entries;
    },

    _extractKeywords : function(str) {
        return str
            //remove whitespace
            .replace(/(\s)/g, '')
            //remove parentheses and words in them
            .replace(/(\(.*\))/g, '')
            //replace semicolons with commas
            .replace(/([;])/g, ',')
            //remove square brackets (are they empty? I forget the format that caused this check to be necessary)
            .replace(/(\[\])/g, '')
            //remove hyphens
            .replace(/(-)/g, '')
            //use commas to split words into an array
            .split(',');
    },

    //Takes in a synonyms array.
    /*
        synonyms[] {
            wordType : ...
            senses[] : {
                meaning : ...
                words[] : ...
            }
        }
    */
    _synonymsList : function(synonyms) {
        //storing domains as a hash
        var synonymsHash = {};

        //so many loops. caching lengths to squeeze performance a bit.
        var synonymsCount = synonyms.length;
        for(var i = 0; i < synonymsCount; i++) {
            var synonymSet = synonyms[i]
            var senseCount = synonymSet.senses.length;
            for(var j = 0; j < senseCount; j++) {
                var sense = synonymSet.senses[j];

                var wordCount = sense.words.length;
                for(var k = 0; k < wordCount; k++) {
                    var synonym = sense.words[k];
                    synonymsHash[synonym] = true;
                }
            }
        }

        var result = [], prop, i;

        for (prop in synonymsHash) {
            if (hasOwnProperty.call(synonymsHash, prop)) {
                result.push(prop);
            }
        }

        return result;
    }
};

module.exports = synonyms;<|endoftext|>


<commit_before>var keywords = ["hn", "more"];

var isValidCommand = function(command) {
  // Handle a string
  if (typeof command === "string") {
    if (keywords.indexOf(command) > -1) {
      return true;
    } else {
      return false;
    }
  }
  if (typeof command === "number") {
    return true;
  }
  return false;
};

var createListResponse = function(data) {
  var titles = [];
  for (var i in data) {
    titles.push(data[i].title);
  }
  return titles.join("\n");
};

module.exports = {
  isValidCommand: isValidCommand,
  createListResponse: createListResponse,
};
<commit_msg>Removed a false check<commit_after>var keywords = ["hn", "more"];

var isValidCommand = function(command) {
  // Handle a string
  if (typeof command === "string") {
    if (keywords.indexOf(command) > -1) {
      return true;
    } else {
      return false;
    }
  }
  return true;
};

var createListResponse = function(data) {
  var titles = [];
  for (var i in data) {
    titles.push(data[i].title);
  }
  return titles.join("\n");
};

module.exports = {
  isValidCommand: isValidCommand,
  createListResponse: createListResponse,
};
<|endoftext|>