<commit_before>// @flow
const {
  GraphQLObjectType,
  GraphQLBoolean,
  GraphQLString,
  GraphQLFloat,
  GraphQLInt,
  GraphQLList,
  GraphQLUnionType,
} = require(`graphql`)
const _ = require(`lodash`)
const invariant = require(`invariant`)
const moment = require(`moment`)
const mime = require(`mime`)
const isRelative = require(`is-relative`)
const isRelativeUrl = require(`is-relative-url`)
const normalize = require(`normalize-path`)
const systemPath = require(`path`)
const { oneLine } = require(`common-tags`)

const { store, getNode, getNodes } = require(`../redux`)
const { joinPath } = require(`../utils/path`)
const { createPageDependency } = require(`../redux/actions/add-page-dependency`)
const createTypeName = require(`./create-type-name`)
const createKey = require(`./create-key`)
const {
  extractFieldExamples,
  isEmptyObjectOrArray,
} = require(`./data-tree-utils`)

import type { GraphQLOutputType } from "graphql"
import type {
  GraphQLFieldConfig,
  GraphQLFieldConfigMap,
} from "graphql/type/definition"

export type ProcessedNodeType = {
  name: string,
  nodes: any[],
  node: GraphQLFieldConfig<*, *>,
  fieldsFromPlugins: any,
  nodeObjectType: GraphQLOutputType,
}

const ISO_8601_FORMAT = [
  `YYYY`,
  `YYYY-MM`,
  `YYYY-MM-DD`,
  `YYYYMMDD`,
  `YYYY-MM-DDTHHZ`,
  `YYYY-MM-DDTHH:mmZ`,
  `YYYY-MM-DDTHHmmZ`,
  `YYYY-MM-DDTHH:mm:ssZ`,
  `YYYY-MM-DDTHHmmssZ`,
  `YYYY-MM-DDTHH:mm:ss.SSSZ`,
  `YYYY-MM-DDTHHmmss.SSSZ`,
  `YYYY-[W]WW`,
  `YYYY[W]WW`,
  `YYYY-[W]WW-E`,
  `YYYY[W]WWE`,
  `YYYY-DDDD`,
  `YYYYDDDD`,
]

function inferGraphQLType({
  exampleValue,
  selector,
  ...otherArgs
}): ?GraphQLFieldConfig<*, *> {
  if (exampleValue == null || isEmptyObjectOrArray(exampleValue)) return
  let fieldName = selector.split(`.`).pop()

  if (Array.isArray(exampleValue)) {
    exampleValue = exampleValue[0]

    if (exampleValue == null) return

    let headType
    // If the array contains objects, than treat them as "nodes"
    // and create an object type.
    if (_.isObject(exampleValue)) {
      headType = new GraphQLObjectType({
        name: createTypeName(fieldName),
        fields: inferObjectStructureFromNodes({
          ...otherArgs,
          exampleValue,
          selector,
        }),
      })
      // Else if the values are simple values, just infer their type.
    } else {
      let inferredType = inferGraphQLType({
        ...otherArgs,
        exampleValue,
        selector,
      })
      invariant(
        inferredType,
        `Could not infer graphQL type for value: ${exampleValue}`
      )

      headType = inferredType.type
    }
    return { type: new GraphQLList(headType) }
  }

  // Check if this is a date.
  // All the allowed ISO 8601 date-time formats used.
  const momentDate = moment.utc(exampleValue, ISO_8601_FORMAT, true)
  if (momentDate.isValid()) {
    return {
      type: GraphQLString,
      args: {
        formatString: {
          type: GraphQLString,
        },
        fromNow: {
          type: GraphQLBoolean,
          description: oneLine`
            Returns a string generated with Moment.js' fromNow function`,
        },
        difference: {
          type: GraphQLString,
          description: oneLine`
            Returns the difference between this date and the current time.
            Defaults to miliseconds but you can also pass in as the
            measurement years, months, weeks, days, hours, minutes,
            and seconds.`,
        },
      },
      resolve(object, { fromNow, difference, formatString }) {
        const date = JSON.parse(JSON.stringify(object[fieldName]))
        if (formatString) {
          return moment.utc(date, ISO_8601_FORMAT, true).format(formatString)
        } else if (fromNow) {
          return moment.utc(date, ISO_8601_FORMAT, true).fromNow()
        } else if (difference) {
          return moment().diff(
            moment.utc(date, ISO_8601_FORMAT, true),
            difference
          )
        } else {
          return date
        }
      },
    }
  }

  switch (typeof exampleValue) {
    case `boolean`:
      return { type: GraphQLBoolean }
    case `string`:
      return { type: GraphQLString }
    case `object`:
      return {
        type: new GraphQLObjectType({
          name: createTypeName(fieldName),
          fields: inferObjectStructureFromNodes({
            ...otherArgs,
            exampleValue,
            selector,
          }),
        }),
      }
    case `number`:
      return _.isInteger(exampleValue)
        ? { type: GraphQLInt }
        : { type: GraphQLFloat }
    default:
      return null
  }
}

function inferFromMapping(
  value,
  mapping,
  fieldSelector,
  types
): ?GraphQLFieldConfig<*, *> {
  const matchedTypes = types.filter(
    type => type.name === mapping[fieldSelector]
  )
  if (_.isEmpty(matchedTypes)) {
    console.log(`Couldn't find a matching node type for "${fieldSelector}"`)
    return
  }

  const findNode = (fieldValue, path) => {
    const linkedType = mapping[fieldSelector]
    const linkedNode = _.find(
      getNodes(),
      n => n.internal.type === linkedType && n.id === fieldValue
    )
    if (linkedNode) {
      createPageDependency({ path, nodeId: linkedNode.id })
      return linkedNode
    }
  }

  if (_.isArray(value)) {
    return {
      type: new GraphQLList(matchedTypes[0].nodeObjectType),
      resolve: (node, a, b, { fieldName }) => {
        const fieldValue = node[fieldName]

        if (fieldValue) {
          return fieldValue.map(value => findNode(value, b.path))
        } else {
          return null
        }
      },
    }
  }

  return {
    type: matchedTypes[0].nodeObjectType,
    resolve: (node, a, b, { fieldName }) => {
      const fieldValue = node[fieldName]

      if (fieldValue) {
        return findNode(fieldValue, b.path)
      } else {
        return null
      }
    },
  }
}

function findLinkedNode(value, linkedField, path) {
  let linkedNode
  // If the field doesn't link to the id, use that for searching.
  if (linkedField) {
    linkedNode = getNodes().find(n => n[linkedField] === value)
    // Else the field is linking to the node's id, the default.
  } else {
    linkedNode = getNode(value)
  }

  if (linkedNode) {
    if (path) {
      createPageDependency({ path, nodeId: linkedNode.id })
    }
    return linkedNode
  }
}

function inferFromFieldName(value, selector, types): GraphQLFieldConfig<*, *> {
  let isArray = false
  if (_.isArray(value)) {
    isArray = true
    // Reduce values to nodes with unique types.
    value = _.uniqBy(value, v => getNode(v).internal.type)
  }

  const key = selector.split(`.`).pop()
  const [, , linkedField] = key.split(`___`)

  const validateLinkedNode = linkedNode => {
    invariant(
      linkedNode,
      oneLine`
        Encountered an error trying to infer a GraphQL type for: "${selector}".
        There is no corresponding node with the ${linkedField || `id`}
        field matching: "${value}"
      `
    )
  }
  const validateField = (linkedNode, field) => {
    invariant(
      field,
      oneLine`
        Encountered an error trying to infer a GraphQL type for: "${selector}".
        There is no corresponding GraphQL type "${linkedNode.internal
          .type}" available
        to link to this node.
      `
    )
  }

  const findNodeType = node =>
    types.find(type => type.name === node.internal.type)

  if (isArray) {
    const linkedNodes = value.map(v => findLinkedNode(v))
    linkedNodes.forEach(node => validateLinkedNode(node))
    const fields = linkedNodes.map(node => findNodeType(node))
    fields.forEach((field, i) => validateField(linkedNodes[i], field))

    let type
    // If there's more than one type, we'll create a union type.
    if (fields.length > 1) {
      type = new GraphQLUnionType({
        name: `Union_${key}_${fields.map(f => f.name).join(`__`)}`,
        description: `Union interface for the field "${key}" for types [${fields
          .map(f => f.name)
          .join(`, `)}]`,
        types: fields.map(f => f.nodeObjectType),
        resolveType: data =>
          fields.find(f => f.name == data.internal.type).nodeObjectType,
      })
    } else {
      type = fields[0].nodeObjectType
    }

    return {
      type: new GraphQLList(type),
      resolve: (node, a, b = {}) => {
        let fieldValue = node[key]
        if (fieldValue) {
          return fieldValue.map(value =>
            findLinkedNode(value, linkedField, b.path)
          )
        } else {
          return null
        }
      },
    }
  }

  const linkedNode = findLinkedNode(value, linkedField)
  validateLinkedNode(linkedNode)
  const field = findNodeType(linkedNode)
  validateField(linkedNode, field)
  return {
    type: field.nodeObjectType,
    resolve: (node, a, b = {}) => {
      let fieldValue = node[key]
      if (fieldValue) {
        const result = findLinkedNode(fieldValue, linkedField, b.path)
        return result
      } else {
        return null
      }
    },
  }
}

function findRootNode(node) {
  // Find the root node.
  let rootNode = node
  let whileCount = 0
  while (
    rootNode.parent &&
    getNode(rootNode.parent) !== undefined &&
    whileCount < 101
  ) {
    rootNode = getNode(rootNode.parent)
    whileCount += 1
    if (whileCount > 100) {
      console.log(
        `It looks like you have a node that's set its parent as itself`,
        rootNode
      )
    }
  }

  return rootNode
}

function shouldInferFile(nodes, key, value) {
  // Find the node used for this example.
  const node = nodes.find(n => _.get(n, key) === value)

  if (!node) {
    return false
  }

  const looksLikeFile =
    _.isString(value) &&
    mime.lookup(value) !== `application/octet-stream` &&
    // domains ending with .com
    mime.lookup(value) !== `application/x-msdownload` &&
    isRelative(value) &&
    isRelativeUrl(value)

  if (!looksLikeFile) {
    return false
  }

  const rootNode = findRootNode(node)

  // Only nodes transformed (ultimately) from a File
  // can link to another File.
  if (rootNode.internal.type !== `File`) {
    return false
  }

  const pathToOtherNode = normalize(joinPath(rootNode.dir, value))
  const otherFileExists = getNodes().some(
    n => n.absolutePath === pathToOtherNode
  )
  return otherFileExists
}

// Look for fields that are pointing at a file — if the field has a known
// extension then assume it should be a file field.
function inferFromUri(key, types) {
  const fileField = types.find(type => type.name === `File`)

  if (!fileField) return

  return {
    type: fileField.nodeObjectType,
    resolve: (node, a, { path }) => {
      const fieldValue = node[key]

      if (!fieldValue) {
        return null
      }

      // Find File node for this node (we assume the node is something
      // like markdown which would be a child node of a File node).
      const parentFileNode = findRootNode(node)

      // Use the parent File node to create the absolute path to
      // the linked file.
      const fileLinkPath = normalize(
        systemPath.resolve(parentFileNode.dir, fieldValue)
      )

      // Use that path to find the linked File node.
      const linkedFileNode = _.find(
        getNodes(),
        n => n.internal.type === `File` && n.absolutePath === fileLinkPath
      )

      if (linkedFileNode) {
        createPageDependency({
          path,
          nodeId: linkedFileNode.id,
        })
        return linkedFileNode
      } else {
        return null
      }
    },
  }
}

type inferTypeOptions = {
  nodes: Object[],
  types: ProcessedNodeType[],
  selector?: string,
  exampleValue?: Object,
}

const EXCLUDE_KEYS = {
  id: 1,
  parent: 1,
  children: 1,
}

// Call this for the top level node + recursively for each sub-object.
// E.g. This gets called for Markdown and then for its frontmatter subobject.
export function inferObjectStructureFromNodes({
  nodes,
  types,
  selector,
  exampleValue = extractFieldExamples(nodes),
}: inferTypeOptions): GraphQLFieldConfigMap<*, *> {
  const config = store.getState().config
  const isRoot = !selector
  const mapping = config && config.mapping

  // Ensure nodes have internal key with object.
  nodes = nodes.map(n => (n.internal ? n : { ...n, internal: {} }))

  const inferredFields = {}
  _.each(exampleValue, (value, key) => {
    // Remove fields common to the top-level of all nodes.  We add these
    // elsewhere so don't need to infer their type.
    if (isRoot && EXCLUDE_KEYS[key]) return

    // Several checks to see if a field is pointing to custom type
    // before we try automatic inference.
    const nextSelector = selector ? `${selector}.${key}` : key
    const fieldSelector = `${nodes[0].internal.type}.${nextSelector}`

    let fieldName = key
    let inferredField

    // First check for manual field => type mappings in the site's
    // gatsby-config.js
    if (mapping && _.includes(Object.keys(mapping), fieldSelector)) {
      inferredField = inferFromMapping(value, mapping, fieldSelector, types)

      // Second if the field has a suffix of ___node. We use then the value
      // (a node id) to find the node and use that node's type as the field
    } else if (_.includes(key, `___NODE`)) {
      ;[fieldName] = key.split(`___`)
      inferredField = inferFromFieldName(value, nextSelector, types)

      // Third if the field is pointing to a file (from another file).
    } else if (
      nodes[0].internal.type !== `File` &&
      shouldInferFile(nodes, nextSelector, value)
    ) {
      inferredField = inferFromUri(key, types)
    }

    // Finally our automatic inference of field value type.
    if (!inferredField) {
      inferredField = inferGraphQLType({
        nodes,
        types,
        exampleValue: value,
        selector: selector ? `${selector}.${key}` : key,
      })
    }

    if (!inferredField) return

    // Replace unsupported values
    inferredFields[createKey(fieldName)] = inferredField
  })

  return inferredFields
}
<commit_msg>Check a date exists before trying to parse an undefined field (#2066)<commit_after>// @flow
const {
  GraphQLObjectType,
  GraphQLBoolean,
  GraphQLString,
  GraphQLFloat,
  GraphQLInt,
  GraphQLList,
  GraphQLUnionType,
} = require(`graphql`)
const _ = require(`lodash`)
const invariant = require(`invariant`)
const moment = require(`moment`)
const mime = require(`mime`)
const isRelative = require(`is-relative`)
const isRelativeUrl = require(`is-relative-url`)
const normalize = require(`normalize-path`)
const systemPath = require(`path`)
const { oneLine } = require(`common-tags`)

const { store, getNode, getNodes } = require(`../redux`)
const { joinPath } = require(`../utils/path`)
const { createPageDependency } = require(`../redux/actions/add-page-dependency`)
const createTypeName = require(`./create-type-name`)
const createKey = require(`./create-key`)
const {
  extractFieldExamples,
  isEmptyObjectOrArray,
} = require(`./data-tree-utils`)

import type { GraphQLOutputType } from "graphql"
import type {
  GraphQLFieldConfig,
  GraphQLFieldConfigMap,
} from "graphql/type/definition"

export type ProcessedNodeType = {
  name: string,
  nodes: any[],
  node: GraphQLFieldConfig<*, *>,
  fieldsFromPlugins: any,
  nodeObjectType: GraphQLOutputType,
}

const ISO_8601_FORMAT = [
  `YYYY`,
  `YYYY-MM`,
  `YYYY-MM-DD`,
  `YYYYMMDD`,
  `YYYY-MM-DDTHHZ`,
  `YYYY-MM-DDTHH:mmZ`,
  `YYYY-MM-DDTHHmmZ`,
  `YYYY-MM-DDTHH:mm:ssZ`,
  `YYYY-MM-DDTHHmmssZ`,
  `YYYY-MM-DDTHH:mm:ss.SSSZ`,
  `YYYY-MM-DDTHHmmss.SSSZ`,
  `YYYY-[W]WW`,
  `YYYY[W]WW`,
  `YYYY-[W]WW-E`,
  `YYYY[W]WWE`,
  `YYYY-DDDD`,
  `YYYYDDDD`,
]

function inferGraphQLType({
  exampleValue,
  selector,
  ...otherArgs
}): ?GraphQLFieldConfig<*, *> {
  if (exampleValue == null || isEmptyObjectOrArray(exampleValue)) return
  let fieldName = selector.split(`.`).pop()

  if (Array.isArray(exampleValue)) {
    exampleValue = exampleValue[0]

    if (exampleValue == null) return

    let headType
    // If the array contains objects, than treat them as "nodes"
    // and create an object type.
    if (_.isObject(exampleValue)) {
      headType = new GraphQLObjectType({
        name: createTypeName(fieldName),
        fields: inferObjectStructureFromNodes({
          ...otherArgs,
          exampleValue,
          selector,
        }),
      })
      // Else if the values are simple values, just infer their type.
    } else {
      let inferredType = inferGraphQLType({
        ...otherArgs,
        exampleValue,
        selector,
      })
      invariant(
        inferredType,
        `Could not infer graphQL type for value: ${exampleValue}`
      )

      headType = inferredType.type
    }
    return { type: new GraphQLList(headType) }
  }

  // Check if this is a date.
  // All the allowed ISO 8601 date-time formats used.
  const momentDate = moment.utc(exampleValue, ISO_8601_FORMAT, true)
  if (momentDate.isValid()) {
    return {
      type: GraphQLString,
      args: {
        formatString: {
          type: GraphQLString,
        },
        fromNow: {
          type: GraphQLBoolean,
          description: oneLine`
            Returns a string generated with Moment.js' fromNow function`,
        },
        difference: {
          type: GraphQLString,
          description: oneLine`
            Returns the difference between this date and the current time.
            Defaults to miliseconds but you can also pass in as the
            measurement years, months, weeks, days, hours, minutes,
            and seconds.`,
        },
      },
      resolve(object, { fromNow, difference, formatString }) {
        let date
        if (object[fieldName]) {
          date = JSON.parse(JSON.stringify(object[fieldName]))
        } else {
          return
        }
        if (formatString) {
          return moment.utc(date, ISO_8601_FORMAT, true).format(formatString)
        } else if (fromNow) {
          return moment.utc(date, ISO_8601_FORMAT, true).fromNow()
        } else if (difference) {
          return moment().diff(
            moment.utc(date, ISO_8601_FORMAT, true),
            difference
          )
        } else {
          return date
        }
      },
    }
  }

  switch (typeof exampleValue) {
    case `boolean`:
      return { type: GraphQLBoolean }
    case `string`:
      return { type: GraphQLString }
    case `object`:
      return {
        type: new GraphQLObjectType({
          name: createTypeName(fieldName),
          fields: inferObjectStructureFromNodes({
            ...otherArgs,
            exampleValue,
            selector,
          }),
        }),
      }
    case `number`:
      return _.isInteger(exampleValue)
        ? { type: GraphQLInt }
        : { type: GraphQLFloat }
    default:
      return null
  }
}

function inferFromMapping(
  value,
  mapping,
  fieldSelector,
  types
): ?GraphQLFieldConfig<*, *> {
  const matchedTypes = types.filter(
    type => type.name === mapping[fieldSelector]
  )
  if (_.isEmpty(matchedTypes)) {
    console.log(`Couldn't find a matching node type for "${fieldSelector}"`)
    return
  }

  const findNode = (fieldValue, path) => {
    const linkedType = mapping[fieldSelector]
    const linkedNode = _.find(
      getNodes(),
      n => n.internal.type === linkedType && n.id === fieldValue
    )
    if (linkedNode) {
      createPageDependency({ path, nodeId: linkedNode.id })
      return linkedNode
    }
  }

  if (_.isArray(value)) {
    return {
      type: new GraphQLList(matchedTypes[0].nodeObjectType),
      resolve: (node, a, b, { fieldName }) => {
        const fieldValue = node[fieldName]

        if (fieldValue) {
          return fieldValue.map(value => findNode(value, b.path))
        } else {
          return null
        }
      },
    }
  }

  return {
    type: matchedTypes[0].nodeObjectType,
    resolve: (node, a, b, { fieldName }) => {
      const fieldValue = node[fieldName]

      if (fieldValue) {
        return findNode(fieldValue, b.path)
      } else {
        return null
      }
    },
  }
}

function findLinkedNode(value, linkedField, path) {
  let linkedNode
  // If the field doesn't link to the id, use that for searching.
  if (linkedField) {
    linkedNode = getNodes().find(n => n[linkedField] === value)
    // Else the field is linking to the node's id, the default.
  } else {
    linkedNode = getNode(value)
  }

  if (linkedNode) {
    if (path) {
      createPageDependency({ path, nodeId: linkedNode.id })
    }
    return linkedNode
  }
}

function inferFromFieldName(value, selector, types): GraphQLFieldConfig<*, *> {
  let isArray = false
  if (_.isArray(value)) {
    isArray = true
    // Reduce values to nodes with unique types.
    value = _.uniqBy(value, v => getNode(v).internal.type)
  }

  const key = selector.split(`.`).pop()
  const [, , linkedField] = key.split(`___`)

  const validateLinkedNode = linkedNode => {
    invariant(
      linkedNode,
      oneLine`
        Encountered an error trying to infer a GraphQL type for: "${selector}".
        There is no corresponding node with the ${linkedField || `id`}
        field matching: "${value}"
      `
    )
  }
  const validateField = (linkedNode, field) => {
    invariant(
      field,
      oneLine`
        Encountered an error trying to infer a GraphQL type for: "${selector}".
        There is no corresponding GraphQL type "${linkedNode.internal
          .type}" available
        to link to this node.
      `
    )
  }

  const findNodeType = node =>
    types.find(type => type.name === node.internal.type)

  if (isArray) {
    const linkedNodes = value.map(v => findLinkedNode(v))
    linkedNodes.forEach(node => validateLinkedNode(node))
    const fields = linkedNodes.map(node => findNodeType(node))
    fields.forEach((field, i) => validateField(linkedNodes[i], field))

    let type
    // If there's more than one type, we'll create a union type.
    if (fields.length > 1) {
      type = new GraphQLUnionType({
        name: `Union_${key}_${fields.map(f => f.name).join(`__`)}`,
        description: `Union interface for the field "${key}" for types [${fields
          .map(f => f.name)
          .join(`, `)}]`,
        types: fields.map(f => f.nodeObjectType),
        resolveType: data =>
          fields.find(f => f.name == data.internal.type).nodeObjectType,
      })
    } else {
      type = fields[0].nodeObjectType
    }

    return {
      type: new GraphQLList(type),
      resolve: (node, a, b = {}) => {
        let fieldValue = node[key]
        if (fieldValue) {
          return fieldValue.map(value =>
            findLinkedNode(value, linkedField, b.path)
          )
        } else {
          return null
        }
      },
    }
  }

  const linkedNode = findLinkedNode(value, linkedField)
  validateLinkedNode(linkedNode)
  const field = findNodeType(linkedNode)
  validateField(linkedNode, field)
  return {
    type: field.nodeObjectType,
    resolve: (node, a, b = {}) => {
      let fieldValue = node[key]
      if (fieldValue) {
        const result = findLinkedNode(fieldValue, linkedField, b.path)
        return result
      } else {
        return null
      }
    },
  }
}

function findRootNode(node) {
  // Find the root node.
  let rootNode = node
  let whileCount = 0
  while (
    rootNode.parent &&
    getNode(rootNode.parent) !== undefined &&
    whileCount < 101
  ) {
    rootNode = getNode(rootNode.parent)
    whileCount += 1
    if (whileCount > 100) {
      console.log(
        `It looks like you have a node that's set its parent as itself`,
        rootNode
      )
    }
  }

  return rootNode
}

function shouldInferFile(nodes, key, value) {
  // Find the node used for this example.
  const node = nodes.find(n => _.get(n, key) === value)

  if (!node) {
    return false
  }

  const looksLikeFile =
    _.isString(value) &&
    mime.lookup(value) !== `application/octet-stream` &&
    // domains ending with .com
    mime.lookup(value) !== `application/x-msdownload` &&
    isRelative(value) &&
    isRelativeUrl(value)

  if (!looksLikeFile) {
    return false
  }

  const rootNode = findRootNode(node)

  // Only nodes transformed (ultimately) from a File
  // can link to another File.
  if (rootNode.internal.type !== `File`) {
    return false
  }

  const pathToOtherNode = normalize(joinPath(rootNode.dir, value))
  const otherFileExists = getNodes().some(
    n => n.absolutePath === pathToOtherNode
  )
  return otherFileExists
}

// Look for fields that are pointing at a file — if the field has a known
// extension then assume it should be a file field.
function inferFromUri(key, types) {
  const fileField = types.find(type => type.name === `File`)

  if (!fileField) return

  return {
    type: fileField.nodeObjectType,
    resolve: (node, a, { path }) => {
      const fieldValue = node[key]

      if (!fieldValue) {
        return null
      }

      // Find File node for this node (we assume the node is something
      // like markdown which would be a child node of a File node).
      const parentFileNode = findRootNode(node)

      // Use the parent File node to create the absolute path to
      // the linked file.
      const fileLinkPath = normalize(
        systemPath.resolve(parentFileNode.dir, fieldValue)
      )

      // Use that path to find the linked File node.
      const linkedFileNode = _.find(
        getNodes(),
        n => n.internal.type === `File` && n.absolutePath === fileLinkPath
      )

      if (linkedFileNode) {
        createPageDependency({
          path,
          nodeId: linkedFileNode.id,
        })
        return linkedFileNode
      } else {
        return null
      }
    },
  }
}

type inferTypeOptions = {
  nodes: Object[],
  types: ProcessedNodeType[],
  selector?: string,
  exampleValue?: Object,
}

const EXCLUDE_KEYS = {
  id: 1,
  parent: 1,
  children: 1,
}

// Call this for the top level node + recursively for each sub-object.
// E.g. This gets called for Markdown and then for its frontmatter subobject.
export function inferObjectStructureFromNodes({
  nodes,
  types,
  selector,
  exampleValue = extractFieldExamples(nodes),
}: inferTypeOptions): GraphQLFieldConfigMap<*, *> {
  const config = store.getState().config
  const isRoot = !selector
  const mapping = config && config.mapping

  // Ensure nodes have internal key with object.
  nodes = nodes.map(n => (n.internal ? n : { ...n, internal: {} }))

  const inferredFields = {}
  _.each(exampleValue, (value, key) => {
    // Remove fields common to the top-level of all nodes.  We add these
    // elsewhere so don't need to infer their type.
    if (isRoot && EXCLUDE_KEYS[key]) return

    // Several checks to see if a field is pointing to custom type
    // before we try automatic inference.
    const nextSelector = selector ? `${selector}.${key}` : key
    const fieldSelector = `${nodes[0].internal.type}.${nextSelector}`

    let fieldName = key
    let inferredField

    // First check for manual field => type mappings in the site's
    // gatsby-config.js
    if (mapping && _.includes(Object.keys(mapping), fieldSelector)) {
      inferredField = inferFromMapping(value, mapping, fieldSelector, types)

      // Second if the field has a suffix of ___node. We use then the value
      // (a node id) to find the node and use that node's type as the field
    } else if (_.includes(key, `___NODE`)) {
      ;[fieldName] = key.split(`___`)
      inferredField = inferFromFieldName(value, nextSelector, types)

      // Third if the field is pointing to a file (from another file).
    } else if (
      nodes[0].internal.type !== `File` &&
      shouldInferFile(nodes, nextSelector, value)
    ) {
      inferredField = inferFromUri(key, types)
    }

    // Finally our automatic inference of field value type.
    if (!inferredField) {
      inferredField = inferGraphQLType({
        nodes,
        types,
        exampleValue: value,
        selector: selector ? `${selector}.${key}` : key,
      })
    }

    if (!inferredField) return

    // Replace unsupported values
    inferredFields[createKey(fieldName)] = inferredField
  })

  return inferredFields
}
<|endoftext|>


<commit_before>define(['itemObjectModel', 'mac/roman'], function(itemOM, macRoman) {

  'use strict';

  function findNullOffset(bytes, pos) {
    var offset = 0;
    while (bytes[pos + offset]) offset++;
    return offset;
  }

  function versionString(bytes, pos) {
    if (!bytes[pos + 3]) return null;
    var versionString = bytes[pos] + '.' + (bytes[pos + 1] >> 4);
    if (bytes[pos + 1] & 7) versionString += '.' + (bytes[pos + 1] & 7);
    switch(bytes[pos + 2]) {
      case 0x20: versionString += 'dev'; break;
      case 0x40: versionString += 'a'; break;
      case 0x60: versionString += 'b'; break;
      case 0x80:
        if (bytes[pos + 3]) versionString += 'rel';
        break;
      default: versionString += '[u' + bytes[pos+2] + ']'; break;
    }
    if (bytes[pos + 3]) versionString += bytes[pos + 3];
    return versionString;
  }

  function open(item) {
    function onBlock(item, byteSource) {
      return byteSource.slice(0, 12).getBytes().then(function(headerBytes) {
        var dv = new DataView(
          headerBytes.buffer, headerBytes.byteOffset, headerBytes.byteLength);
        var length = dv.getUint32(0, false);
        var name = macRoman(headerBytes, 4, 4);
        var id = dv.getInt32(8, false);
        var blockItem = itemOM.createItem(name + " " + id);
        item.addItem(blockItem);
        if (length > 8) {
          blockItem.byteSource = byteSource.slice(12, length);
          switch (name) {
            case 'STAK':
              blockItem.startAddingItems();
              blockItem.notifyPopulating(blockItem.getBytes().then(function(bytes) {
                var stack = new StackView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
                blockItem.setDataObject(stack);
                for (var i = 0; i < stack.patternCount; i++) {
                  var patternItem = itemOM.createItem('pattern ' + i);
                  var patternData = stack.getRawPattern(i);
                  patternItem.withPixels(8, 8, function(pixelData) {
                    for (var y = 0; y < 8; y++) {
                      for (var x = 0; x < 8; x++) {
                        if (patternData[y] & (0x80 >> x)) {
                          pixelData[4 * (y*8 + x) + 3] = 0xff;
                        }
                      }
                    }
                  });
                  blockItem.addItem(patternItem);
                }
                var scriptItem = itemOM.createItem('script');
                scriptItem.setDataObject(stack.getScript());
                blockItem.addItem(scriptItem);
              }));
              break;
            /*
            case 'CARD': case 'BKGD':
              blockItem.startAddingItems();
              blockItem.notifyPopulating(blockItem.getBytes().then(function(bytes) {
                var card = new CardView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
                blockItem.setDataObject(card);
              }));
              break;
            */
          }
        }
        if (byteSource.byteLength >= (length + 12)) {
          return onBlock(item, byteSource.slice(length));
        }
      });
    }
    return onBlock(item, item.byteSource);
  }

  function StackView(buffer, byteOffset, byteLength) {
    this.dataView = new DataView(buffer, byteOffset, byteLength);
    this.bytes = new Uint8Array(buffer, byteOffset, byteLength);
  }
  StackView.prototype = {
    toJSON: function() {
      return {
        version1: this.version1,
        version2: this.version2,
        version3: this.version3,
        version4: this.version4,
        cardCount: this.cardCount,
        listBlockID: this.listBlockID,
        userLevelSetting: this.userLevelSetting,
        canPeek: this.canPeek,
        canAbort: this.canAbort,
        hasPrivateAccess: this.hasPrivateAccess,
        canDelete: this.canDelete,
        canModify: this.canModify,
        screenHeight: this.screenHeight,
        screenWidth: this.screenWidth,
      };
    },
    // unknown_0x00: 0x20 bytes
    get cardCount() {
      return this.dataView.getUint32(0x20, false);
    },
    // unknown_0x24: card ID for unknown reasons
    get listBlockID() {
      return this.dataView.getInt32(0x28, false);
    },
    // unknown_0x2C: 0x10 bytes
    get userLevelSetting() {
      return this.dataView.getUint16(0x3C, false);
    },
    // unknown_0x3E: 0x02 bytes
    get flags() {
      return this.dataView.getUint16(0x40, false);
    },
    get canPeek() {
      return !(this.flags & (1 << 10));
    },
    get canAbort() {
      return !(this.flags & (1 << 11));
    },
    get hasPrivateAccess() {
      return !!(this.flags & (1 << 13));
    },
    get canDelete() {
      return !(this.flags & (1 << 14));
    },
    get canModify() {
      return !(this.flags & (1 << 15));
    },
    // unknown_0x42: 0x12 bytes
    get version1() {
      return versionString(this.bytes, 0x54);
    },
    get version2() {
      return versionString(this.bytes, 0x58);
    },
    get version3() {
      return versionString(this.bytes, 0x5C);
    },
    get version4() {
      return versionString(this.bytes, 0x60);
    },
    // unknown_0x64: 0x148 bytes
    get screenHeight() {
      return this.dataView.getUint16(0x1ac, false);
    },
    get screenWidth() {
      return this.dataView.getUint16(0x1ae, false);
    },
    // unknown_0x1AE: 0x106 bytes
    get patternCount() {
      return 40;
    },
    getRawPattern: function(n) {
      return this.bytes.subarray(0x2B4 + n * 8, 0x2B4 + (n + 1) * 8);
    },
    // unknown_0x3F4: 0x200 bytes
    getScript: function() {
      return macRoman(this.bytes, 0x5F4).replace(/\0.*/, '');
    },
  };

  function CardView(buffer, byteOffset, byteLength) {
    this.dataView = new DataView(buffer, byteOffset, byteLength);
    this.bytes = new Uint8Array(buffer, byteOffset, byteLength);
  }
  CardView.prototype = {
    toJSON: function() {
      return {
        pictureBitmapID: this.pictureBitmapID,
        flags: this.flags,
        backgroundID: this.backgroundID,
        scriptType: this.scriptType,
        parts: this.parts,
        partContents: this.partContents,
        cardName: this.cardName,
        cardScript: this.cardScript,
        osaScript: this.osaScript,
      };
    },
    // unknown_0x0000: 4 bytes
    get pictureBitmapID() {
      return this.dataView.getInt32(0x4, false); // 0 = transparent
    },
    get flags() {
      return this.dataView.getUint16(0x8, false);
    },
    get canDelete() {
      return !(this.flags & (1 << 14));
    },
    get hideCardPicture() {
      return !!(this.flags & (1 << 13));
    },
    get dontSearch() {
      return !!(this.flags & (1 << 11));
    },
    // unknown_0x0A: 0xE bytes
    get backgroundID() {
      return this.dataView.getInt32(0x18, false);
    },
    get partCount() {
      return this.dataView.getUint16(0x1C, false);
    },
    // unknown_0x1E: 0x6 bytes
    get partContentCount() {
      return this.dataView.getUint16(0x24, false);
    },
    get scriptType() {
      return this.dataView.getUint32(0x24, false);
    },
    get parts() {
      var parts = new Array(this.partCount);
      var pos = 0x2A;
      for (var i = 0; i < parts.length; i++) {
        var len = this.dataView.getUint16(pos, false);
        parts[i] = new PartView(this.bytes.buffer, this.bytes.byteOffset + pos, len);
        pos += len + len%2;
      }
      parts.afterPos = pos;
      Object.defineProperty(this, 'parts', {value:parts});
      return parts;
    },
    get partContents() {
      var contents = new Array(this.partContentCount);
      var pos = this.parts.afterPos;
      for (var i = 0; i < contents.length; i++) {
        var len = this.dataView.getUint16(pos + 2, false);
        contents[i] = new ContentsView(this.bytes.buffer, this.bytes.byteOffset + pos, 4 + len);
        pos += 4 + len + len%2;
      }
      contents.afterPos = pos;
      Object.defineProperty(this, 'partContents', {value:contents});
      return contents;
    },
    get cardNamePos() {
      return this.partContents.afterPos;
    },
    get cardName() {
      var len = findNullOffset(this.bytes, this.cardNamePos);
      var val = macRoman(this.bytes, this.partContents.afterPos, len);
      Object.defineProperty(this, 'cardName', {value:val});
      return val;
    },
    get cardNameAfterPos() {
      return this.cardNamePos + this.cardName.length + 1;
    },
    get cardScriptPos() {
      return this.cardNameAfterPos;
    },
    get cardScript() {
      var len = findNullOffset(this.bytes, this.partContents.afterPos + this.cardName.length + 1);
      var val = macRoman(this.bytes, this.partContents.afterPos, len);
      Object.defineProperty(this, 'cardScript', {value:val});
      return val;
    },
    get cardScriptAfterPos() {
      return this.cardScriptPos + this.cardScript.length + 1;
    },
    get osaScript() {
      var pos = this.cardScriptAfterPos;
      if (pos >= this.bytes.length) return null;
      var offset = this.dataView.getUint16(pos, false);
      var len = this.dataView.getUint16(pos + 2, false);
      if (len === 0) return null;
      pos = pos + 2 + offset;
      return macRoman(this.bytes, pos, len);
    },
  };

  function PartView(buffer, byteOffset, byteLength) {

  }

  function ContentsView(buffer, byteOffset, byteLength) {

  }

  return open;

});
<commit_msg>Fix CARD/BKGD offset logic<commit_after>define(['itemObjectModel', 'mac/roman'], function(itemOM, macRoman) {

  'use strict';

  function findNullOffset(bytes, pos) {
    var offset = 0;
    while (bytes[pos + offset]) offset++;
    return offset;
  }

  function versionString(bytes, pos) {
    if (!bytes[pos + 3]) return null;
    var versionString = bytes[pos] + '.' + (bytes[pos + 1] >> 4);
    if (bytes[pos + 1] & 7) versionString += '.' + (bytes[pos + 1] & 7);
    switch(bytes[pos + 2]) {
      case 0x20: versionString += 'dev'; break;
      case 0x40: versionString += 'a'; break;
      case 0x60: versionString += 'b'; break;
      case 0x80:
        if (bytes[pos + 3]) versionString += 'rel';
        break;
      default: versionString += '[u' + bytes[pos+2] + ']'; break;
    }
    if (bytes[pos + 3]) versionString += bytes[pos + 3];
    return versionString;
  }

  function open(item) {
    function onBlock(item, byteSource) {
      return byteSource.slice(0, 12).getBytes().then(function(headerBytes) {
        var dv = new DataView(
          headerBytes.buffer, headerBytes.byteOffset, headerBytes.byteLength);
        var length = dv.getUint32(0, false);
        var name = macRoman(headerBytes, 4, 4);
        var id = dv.getInt32(8, false);
        var blockItem = itemOM.createItem(name + " " + id);
        item.addItem(blockItem);
        if (length > 8) {
          blockItem.byteSource = byteSource.slice(12, length);
          switch (name) {
            case 'STAK':
              blockItem.startAddingItems();
              blockItem.notifyPopulating(blockItem.getBytes().then(function(bytes) {
                var stack = new StackView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
                blockItem.setDataObject(stack);
                for (var i = 0; i < stack.patternCount; i++) {
                  var patternItem = itemOM.createItem('pattern ' + i);
                  var patternData = stack.getRawPattern(i);
                  patternItem.withPixels(8, 8, function(pixelData) {
                    for (var y = 0; y < 8; y++) {
                      for (var x = 0; x < 8; x++) {
                        if (patternData[y] & (0x80 >> x)) {
                          pixelData[4 * (y*8 + x) + 3] = 0xff;
                        }
                      }
                    }
                  });
                  blockItem.addItem(patternItem);
                }
                var scriptItem = itemOM.createItem('script');
                scriptItem.setDataObject(stack.getScript());
                blockItem.addItem(scriptItem);
              }));
              break;
            case 'CARD': case 'BKGD':
              blockItem.startAddingItems();
              blockItem.notifyPopulating(blockItem.getBytes().then(function(bytes) {
                var card = new CardView(name === 'BKGD', bytes.buffer, bytes.byteOffset, bytes.byteLength);
                blockItem.setDataObject(card);
              }));
              break;
          }
        }
        if (byteSource.byteLength >= (length + 12)) {
          return onBlock(item, byteSource.slice(length));
        }
      });
    }
    return onBlock(item, item.byteSource);
  }

  function StackView(buffer, byteOffset, byteLength) {
    this.dataView = new DataView(buffer, byteOffset, byteLength);
    this.bytes = new Uint8Array(buffer, byteOffset, byteLength);
  }
  StackView.prototype = {
    toJSON: function() {
      return {
        version1: this.version1,
        version2: this.version2,
        version3: this.version3,
        version4: this.version4,
        cardCount: this.cardCount,
        listBlockID: this.listBlockID,
        userLevelSetting: this.userLevelSetting,
        canPeek: this.canPeek,
        canAbort: this.canAbort,
        hasPrivateAccess: this.hasPrivateAccess,
        canDelete: this.canDelete,
        canModify: this.canModify,
        screenHeight: this.screenHeight,
        screenWidth: this.screenWidth,
      };
    },
    // unknown_0x00: 0x20 bytes
    get cardCount() {
      return this.dataView.getUint32(0x20, false);
    },
    // unknown_0x24: card ID for unknown reasons
    get listBlockID() {
      return this.dataView.getInt32(0x28, false);
    },
    // unknown_0x2C: 0x10 bytes
    get userLevelSetting() {
      return this.dataView.getUint16(0x3C, false);
    },
    // unknown_0x3E: 0x02 bytes
    get flags() {
      return this.dataView.getUint16(0x40, false);
    },
    get canPeek() {
      return !(this.flags & (1 << 10));
    },
    get canAbort() {
      return !(this.flags & (1 << 11));
    },
    get hasPrivateAccess() {
      return !!(this.flags & (1 << 13));
    },
    get canDelete() {
      return !(this.flags & (1 << 14));
    },
    get canModify() {
      return !(this.flags & (1 << 15));
    },
    // unknown_0x42: 0x12 bytes
    get version1() {
      return versionString(this.bytes, 0x54);
    },
    get version2() {
      return versionString(this.bytes, 0x58);
    },
    get version3() {
      return versionString(this.bytes, 0x5C);
    },
    get version4() {
      return versionString(this.bytes, 0x60);
    },
    // unknown_0x64: 0x148 bytes
    get screenHeight() {
      return this.dataView.getUint16(0x1ac, false);
    },
    get screenWidth() {
      return this.dataView.getUint16(0x1ae, false);
    },
    // unknown_0x1AE: 0x106 bytes
    get patternCount() {
      return 40;
    },
    getRawPattern: function(n) {
      return this.bytes.subarray(0x2B4 + n * 8, 0x2B4 + (n + 1) * 8);
    },
    // unknown_0x3F4: 0x200 bytes
    getScript: function() {
      return macRoman(this.bytes, 0x5F4).replace(/\0.*/, '');
    },
  };

  function CardView(isBackground, buffer, byteOffset, byteLength) {
    this.isBackground = isBackground;
    this.dataView = new DataView(buffer, byteOffset, byteLength);
    this.bytes = new Uint8Array(buffer, byteOffset, byteLength);
  }
  CardView.prototype = {
    toJSON: function() {
      return {
        pictureBitmapID: this.pictureBitmapID,
        flags: this.flags,
        backgroundID: this.backgroundID,
        scriptType: this.scriptType,
        parts: this.parts,
        partContents: this.partContents,
        cardName: this.cardName,
        cardScript: this.cardScript,
        osaScript: this.osaScript,
      };
    },
    // unknown_0x0000: 4 bytes
    get pictureBitmapID() {
      return this.dataView.getInt32(0x4, false); // 0 = transparent
    },
    get flags() {
      return this.dataView.getUint16(0x8, false);
    },
    get canDelete() {
      return !(this.flags & (1 << 14));
    },
    get hideCardPicture() {
      return !!(this.flags & (1 << 13));
    },
    get dontSearch() {
      return !!(this.flags & (1 << 11));
    },
    // unknown_0x0A: 0xE bytes
    get backgroundID() {
      return this.isBackground ? null : this.dataView.getInt32(0x18, false);
    },
    get offsetForBackground(v) {
      return this.isBackground ? v - 4 : v;
    },
    get partCount() {
      return this.dataView.getUint16(this.offsetForBackground(0x1C), false);
    },
    // unknown_0x1E: 0x6 bytes
    get partContentCount() {
      return this.dataView.getUint16(this.offsetForBackground(0x24), false);
    },
    get scriptType() {
      return this.dataView.getUint32(this.offsetForBackground(0x24), false);
    },
    get parts() {
      var parts = new Array(this.partCount);
      var pos = this.offsetForBackground(0x2A);
      for (var i = 0; i < parts.length; i++) {
        var len = this.dataView.getUint16(pos, false);
        parts[i] = new PartView(this.bytes.buffer, this.bytes.byteOffset + pos, len);
        pos += len + len%2;
      }
      parts.afterPos = pos;
      Object.defineProperty(this, 'parts', {value:parts});
      return parts;
    },
    get partContents() {
      var contents = new Array(this.partContentCount);
      var pos = this.parts.afterPos;
      for (var i = 0; i < contents.length; i++) {
        var len = this.dataView.getUint16(pos + 2, false);
        contents[i] = new ContentsView(this.bytes.buffer, this.bytes.byteOffset + pos, 4 + len);
        pos += 4 + len + len%2;
      }
      contents.afterPos = pos;
      Object.defineProperty(this, 'partContents', {value:contents});
      return contents;
    },
    get cardNamePos() {
      return this.partContents.afterPos;
    },
    get cardName() {
      var len = findNullOffset(this.bytes, this.cardNamePos);
      var val = macRoman(this.bytes, this.partContents.afterPos, len);
      Object.defineProperty(this, 'cardName', {value:val});
      return val;
    },
    get cardNameAfterPos() {
      return this.cardNamePos + this.cardName.length + 1;
    },
    get cardScriptPos() {
      return this.cardNameAfterPos;
    },
    get cardScript() {
      var len = findNullOffset(this.bytes, this.partContents.afterPos + this.cardName.length + 1);
      var val = macRoman(this.bytes, this.partContents.afterPos, len);
      Object.defineProperty(this, 'cardScript', {value:val});
      return val;
    },
    get cardScriptAfterPos() {
      return this.cardScriptPos + this.cardScript.length + 1;
    },
    get osaScript() {
      var pos = this.cardScriptAfterPos;
      if (pos >= this.bytes.length) return null;
      var offset = this.dataView.getUint16(pos, false);
      var len = this.dataView.getUint16(pos + 2, false);
      if (len === 0) return null;
      pos = pos + 2 + offset;
      return macRoman(this.bytes, pos, len);
    },
  };

  function PartView(buffer, byteOffset, byteLength) {

  }

  function ContentsView(buffer, byteOffset, byteLength) {

  }

  return open;

});
<|endoftext|>


<commit_before>var debug = true;

//create custom splice method. From user113716 @ http://stackoverflow.com/questions/4313841/javascript-how-can-i-insert-a-string-at-a-specific-index
String.prototype.splice = function (idx, rem, s) {
    return (this.slice(0, idx) + s + this.slice(idx + Math.abs(rem)));
};

//handle document load
$(document).ready(function () {
    paper.install(window);

    paper.setup('board');
    setCanvasSize();
    drawCanvasGrid();

    //set animation

});

//get the grid setup
function drawCanvasGrid() {
    //get canvas width and height
    var canvas = document.getElementById("board");
    var canvasWidth = canvas.width;
    var canvasHeight = canvas.height;

    for (var i = 0; i < 10; i++) {
        //vertical lines
        var path = new Path();
        path.strokeColor = 'black';
        path.moveTo(canvas.width * (i / 10), 0);
        path.lineTo(canvas.width * (i / 10), canvasHeight);
        view.draw();

        //horizontal lines
        var path2 = new Path();
        path2.strokeColor = 'black';
        path2.moveTo(0, canvas.height * (i / 10));
        path2.lineTo(canvasWidth, canvas.height * (i / 10));
        view.draw()
    }

}

//refresh the canvas grid when the browser resizes
$(window).resize(function () {
    setCanvasSize();
    drawCanvasGrid();
});
//keep canvas size consistent to prevent blurring
function setCanvasSize() {
    /*var canvas = document.getElementById('board');
    var canvasWidth = $(window).width() * .45;
    var canvasHeight = $(window).width() * .45;
    canvas.width = canvasWidth;
    canvas.height = canvasHeight;*/
}
//from Tim Down @ http://stackoverflow.com/questions/3410464/how-to-find-all-occurrences-of-one-string-in-another-in-javascript
function getIndicesOf(searchStr, str) {
    var startIndex = 0,
        searchStrLen = searchStr.length;
    var index, indices = [];
    while ((index = str.indexOf(searchStr, startIndex)) > -1) {
        indices.push(index);
        startIndex = index + searchStrLen;
    }
    return indices;
}

//check for likely new statements
function isNewStatementCharacter(character) {
    if (code.charAt(innerCounter - 1) == ' ' || code.charAt(innerCounter - 1) == ';' || code.charAt(innerCounter - 1) == ')' || code.charAt(innerCounter - 1) == '}' || code.charAt(innerCounter - 1) == '\n' || code.charAt(innerCounter - 1) == '\t')
        return true;
    else
        return false;
}

//compile code to avoid async results conflicting
function compileCode(code) {
    var imageIndices = getIndicesOf(code, ".setImage(");
    for (var i = 0; i < imageIndices.length; i++) {
        var innerCounter = imageIndices[i];
        while (!(isNewStatementCharacter(code.charAt(innerCounter - 1)))) {
            innerCounter--;
        }
        code.splice(innerCounter, 0, "delay" + i + "().then(function(){");
        innerCounter = code.indexOf(")", innerCounter);
        code.splice(innerCounter, 0, "});");
    }
    return code;
}

//compileCode('var sprite = Sprite.create();sprite.setImage("http://media.giphy.com/media/cqqY4tX61jof6/giphy.gif");sprite.move();');

//run student code
function runCode() {
    project.clear();
    drawCanvasGrid();
    var code = "";
    if (debug) {
        code = myCodeMirror.getValue();
    } else {
        code = "try{" + myCodeMirror.getValue() + "}catch(err) {alert(err.message)}";
    }
    window.eval(code);
}

//STUDENT ACCESSIBLE METHODS

//persitent data
var currentSpriteID = 0;
var Sprite = {
        create: function () {
            var temp = {
                image: new Raster(),
                setImage: function (URL) {
                    return new Promise(function (resolve, reject) {
                        image = null;
                        console.log("outer");
                        image = new Raster(URL);
                        //alert(image.width);
                        image.onLoad = function () {
                            setImageSize(image);
                            console.log("inner");
                            resolve(1);
                        };
                    });

                    //alert(image.width);
                },
                xCoord: 0,
                yCoord: 0,
                id: 0,
                move: function () {}
            }

            temp.move = function (direction, numSpaces, speed) {
                animate(temp);
            };
            temp.id = currentSpriteID;
            currentSpriteID++;
            return temp;
        }

    }
    //appropriately size image for canvas
function setImageSize(image) {
    //get canvas width and height
    var canvas = document.getElementById("board");
    var canvasWidth = canvas.width;
    var canvasHeight = canvas.height;

    var tileWidth = canvasWidth / 10;
    var tileHeight = canvasHeight / 10;
    //get image width and height
    var imageWidth = image.width;
    var imageHeight = image.height;

    if (tileWidth - 5 < imageWidth || tileHeight - 5 < imageHeight) {
        if (imageWidth > imageHeight) {
            var scaleFactor = (tileWidth - 5) / imageWidth;
            image.width = tileWidth - 5;
            image.height = image.height * scaleFactor;
        } else if (imageWidth < imageHeight) {
            var scaleFactor = (tileHeight - 5) / imageHeight;
            image.width = image.width * scaleFactor;
            image.height = tileHeight - 5;
        } else {
            image.width = tileWidth - 5;
            image.height = tileHeight - 5;
        }
    }

}

function animate(sprite, direction, speed) {
    console.log("test");
    view.onFrame = function (event) {
        sprite.image.rotate(3);
    };
}<commit_msg>Fix function call errors in student class<commit_after>var debug = true;

//create custom splice method. From user113716 @ http://stackoverflow.com/questions/4313841/javascript-how-can-i-insert-a-string-at-a-specific-index
String.prototype.splice = function (idx, rem, s) {
    return (this.slice(0, idx) + s + this.slice(idx + Math.abs(rem)));
};

//handle document load
$(document).ready(function () {
    paper.install(window);

    paper.setup('board');
    setCanvasSize();
    drawCanvasGrid();

    //set animation

});

//get the grid setup
function drawCanvasGrid() {
    //get canvas width and height
    var canvas = document.getElementById("board");
    var canvasWidth = canvas.width;
    var canvasHeight = canvas.height;

    for (var i = 0; i < 10; i++) {
        //vertical lines
        var path = new Path();
        path.strokeColor = 'black';
        path.moveTo(canvas.width * (i / 10), 0);
        path.lineTo(canvas.width * (i / 10), canvasHeight);
        view.draw();

        //horizontal lines
        var path2 = new Path();
        path2.strokeColor = 'black';
        path2.moveTo(0, canvas.height * (i / 10));
        path2.lineTo(canvasWidth, canvas.height * (i / 10));
        view.draw()
    }

}

//refresh the canvas grid when the browser resizes
$(window).resize(function () {
    setCanvasSize();
    drawCanvasGrid();
});
//keep canvas size consistent to prevent blurring
function setCanvasSize() {
    /*var canvas = document.getElementById('board');
    var canvasWidth = $(window).width() * .45;
    var canvasHeight = $(window).width() * .45;
    canvas.width = canvasWidth;
    canvas.height = canvasHeight;*/
}
//from Tim Down @ http://stackoverflow.com/questions/3410464/how-to-find-all-occurrences-of-one-string-in-another-in-javascript
function getIndicesOf(searchStr, str) {
    var startIndex = 0,
        searchStrLen = searchStr.length;
    var index, indices = [];
    while ((index = str.indexOf(searchStr, startIndex)) > -1) {
        indices.push(index);
        startIndex = index + searchStrLen;
    }
    return indices;
}

//check for likely new statements
function isNewStatementCharacter(character) {
    if (character == ' ' || character == ';' || character == ')' || character == '}' || character == '\n' || character == '\t')
        return true;
    else
        return false;
}

//compile code to avoid async results conflicting
function compileCode(code) {
    var imageIndices = getIndicesOf(".setImage(", code, false);
    for (var i = 0; i < imageIndices.length; i++) {
        var innerCounter = imageIndices[i];
        while (!(isNewStatementCharacter(code.charAt(innerCounter - 1)))) {
            innerCounter--;
        }
        code.splice(innerCounter, 0, "delay" + i + "().then(function(){");
        innerCounter = code.indexOf(")", innerCounter);
        code.splice(innerCounter, 0, "});");
    }
    return code;
}

//compileCode('var sprite = Sprite.create();sprite.setImage("http://media.giphy.com/media/cqqY4tX61jof6/giphy.gif");sprite.move();');

//run student code
function runCode() {
    project.clear();
    drawCanvasGrid();
    var code = "";
    if (debug) {
        code = myCodeMirror.getValue();
    } else {
        code = "try{" + myCodeMirror.getValue() + "}catch(err) {alert(err.message)}";
    }
    window.eval(code);
}

//STUDENT ACCESSIBLE METHODS

//persitent data
var currentSpriteID = 0;
var Sprite = {
        create: function () {
            var temp = {
                image: new Raster(),
                setImage: function (URL) {
                    return new Promise(function (resolve, reject) {
                        image = null;
                        console.log("outer");
                        image = new Raster(URL);
                        //alert(image.width);
                        image.onLoad = function () {
                            setImageSize(image);
                            console.log("inner");
                            resolve(1);
                        };
                    });

                    //alert(image.width);
                },
                xCoord: 0,
                yCoord: 0,
                id: 0,
                move: function () {}
            }

            temp.move = function (direction, numSpaces, speed) {
                animate(temp);
            };
            temp.id = currentSpriteID;
            currentSpriteID++;
            return temp;
        }

    }
    //appropriately size image for canvas
function setImageSize(image) {
    //get canvas width and height
    var canvas = document.getElementById("board");
    var canvasWidth = canvas.width;
    var canvasHeight = canvas.height;

    var tileWidth = canvasWidth / 10;
    var tileHeight = canvasHeight / 10;
    //get image width and height
    var imageWidth = image.width;
    var imageHeight = image.height;

    if (tileWidth - 5 < imageWidth || tileHeight - 5 < imageHeight) {
        if (imageWidth > imageHeight) {
            var scaleFactor = (tileWidth - 5) / imageWidth;
            image.width = tileWidth - 5;
            image.height = image.height * scaleFactor;
        } else if (imageWidth < imageHeight) {
            var scaleFactor = (tileHeight - 5) / imageHeight;
            image.width = image.width * scaleFactor;
            image.height = tileHeight - 5;
        } else {
            image.width = tileWidth - 5;
            image.height = tileHeight - 5;
        }
    }

}

function animate(sprite, direction, speed) {
    console.log("test");
    view.onFrame = function (event) {
        sprite.image.rotate(3);
    };
}<|endoftext|>


<commit_before>var _ = require("underscore")
  , inflection = require("./inflection")

var config = {}
CollectionFactory.configure = function(conf) {
  config = conf
}

function CollectionFactory(name, selector, polymorphicKey) {
  this.name = name
  this.selector = selector
  this.polymorphicKey = polymorphicKey
  this.TemplateFactories = {}
}

CollectionFactory.prototype.setTemplateFactory = function(TemplateFactory) {
  this.TemplateFactory = TemplateFactory
  return TemplateFactory
}

CollectionFactory.prototype.mapView = function(key) {
  this.viewClassName = key
}

CollectionFactory.prototype.generate = function(parent, parentView) {
  var that = this

  function Collection(models, opts) {
    opts = opts || {}
    this.length = 0
    this.Template = that.TemplateFactory.generate()
    this.templates = []
    this.container = parent.contents().andSelf().filter(that.selector)

    if(that.viewClassName) {
      this.View = require(config.viewDirectory + "/" + inflection.underscore(that.viewClassName))
    }

    if(models) {
      this.bind(models, opts.parentView)
    }
  }

  Collection.prototype.bind = function(collection, parentView) {
    if(this.View) {
      opts = { parent: parentView, el: this.container, template: this, collection: collection }
      this.view = new this.View(opts)
    }
    if(_.isArray(collection)) {
      this.push(collection)
    } else {
      this.push(collection.models)

      collection.on("reset", function(collection, opts) {
        this.remove(this.templates)
        this.add(collection.models)
      }, this)

      collection.on("add", function(model, collection, opts) {
        this.add(model, { at: opts.index })
      }, this)

      collection.on("remove", function(model, collection, opts) {
        var index = opts.index
        this.remove(this.at(index))
      }, this)
    }
  }

  Collection.prototype.remove = function(template) {
    if(_.isArray(template)) {
      return _(template).each(this.remove, this)
    }
    var index = _(this.templates).indexOf(template)
    if(index < 0) {
      throw new Error("Cannot remove a template that's not in collection")
    }
    this.templates.splice(index, 1)
    this.container.children().eq(index).remove()

    this.length = this.templates.length
  }

  Collection.prototype.at = function(index) {
    return this.templates[index]
  }

  Collection.prototype.add = function(models, opts) {
    opts = opts || {}
    opts.at = opts.at || this.templates.length
    if(!_.isArray(models)) {
      models = [models]
    }
    var templates = _.map(models, function(model) {
      return new this.Template(model, { parentView: this.view })
    }, this)

    ;[].splice.apply(this.templates, [opts.at, 0].concat(templates))

    var doms = _.map(templates, function(template) {
       return template.template[0]
    })
    var children = this.container.children()
    if(children.length)
      $(doms).insertAfter(children[opts.at - 1])
    else
      this.container.append($(doms))

    this.length = templates.length
  }

  Collection.prototype.push = function(data) {
    if(_.isArray(data)) {
      return _(data).each(this.push, this)
    }
    var template = new this.Template(data, { parentView: this.view })

    this.templates.push(template)
    this.container.append(template.template)
    this.length = this.templates.length
  }

  return Collection
}

module.exports = CollectionFactory
<commit_msg>store a templateFactory for each polymorpic type on a collection<commit_after>var _ = require("underscore")
  , inflection = require("./inflection")

var config = {}
CollectionFactory.configure = function(conf) {
  config = conf
}

function CollectionFactory(name, selector, polymorphicKey) {
  this.name = name
  this.selector = selector
  this.polymorphicKey = polymorphicKey
  this.TemplateFactories = {}
}

CollectionFactory.prototype.setTemplateFactory = function(polymorphicValue, TemplateFactory) {
  this.TemplateFactories[polymorphicValue] = TemplateFactory
  return TemplateFactory
}

CollectionFactory.prototype.mapView = function(key) {
  this.viewClassName = key
}

CollectionFactory.prototype.generate = function(parent, parentView) {
  var that = this

  function Collection(models, opts) {
    opts = opts || {}
    this.length = 0
    this.Template = that.TemplateFactory.generate()
    this.templates = []
    this.container = parent.contents().andSelf().filter(that.selector)

    if(that.viewClassName) {
      this.View = require(config.viewDirectory + "/" + inflection.underscore(that.viewClassName))
    }

    if(models) {
      this.bind(models, opts.parentView)
    }
  }

  Collection.prototype.bind = function(collection, parentView) {
    if(this.View) {
      opts = { parent: parentView, el: this.container, template: this, collection: collection }
      this.view = new this.View(opts)
    }
    if(_.isArray(collection)) {
      this.push(collection)
    } else {
      this.push(collection.models)

      collection.on("reset", function(collection, opts) {
        this.remove(this.templates)
        this.add(collection.models)
      }, this)

      collection.on("add", function(model, collection, opts) {
        this.add(model, { at: opts.index })
      }, this)

      collection.on("remove", function(model, collection, opts) {
        var index = opts.index
        this.remove(this.at(index))
      }, this)
    }
  }

  Collection.prototype.remove = function(template) {
    if(_.isArray(template)) {
      return _(template).each(this.remove, this)
    }
    var index = _(this.templates).indexOf(template)
    if(index < 0) {
      throw new Error("Cannot remove a template that's not in collection")
    }
    this.templates.splice(index, 1)
    this.container.children().eq(index).remove()

    this.length = this.templates.length
  }

  Collection.prototype.at = function(index) {
    return this.templates[index]
  }

  Collection.prototype.add = function(models, opts) {
    opts = opts || {}
    opts.at = opts.at || this.templates.length
    if(!_.isArray(models)) {
      models = [models]
    }
    var templates = _.map(models, function(model) {
      return new this.Template(model, { parentView: this.view })
    }, this)

    ;[].splice.apply(this.templates, [opts.at, 0].concat(templates))

    var doms = _.map(templates, function(template) {
       return template.template[0]
    })
    var children = this.container.children()
    if(children.length)
      $(doms).insertAfter(children[opts.at - 1])
    else
      this.container.append($(doms))

    this.length = templates.length
  }

  Collection.prototype.push = function(data) {
    if(_.isArray(data)) {
      return _(data).each(this.push, this)
    }
    var template = new this.Template(data, { parentView: this.view })

    this.templates.push(template)
    this.container.append(template.template)
    this.length = this.templates.length
  }

  return Collection
}

module.exports = CollectionFactory
<|endoftext|>


<commit_before>/**
*
* @file Combine MQ Main
* @version 0.1.0
* @author {@link http://github.com/furzeface Daniel Furze}
* @link https://github.com/buildingblocks/node-combine-mq
*
* Copyright (c) 2014 Building Blocks
* Licensed under the MIT license.
*
*/

'use strict';

// Require all of the things.
var fs = require('fs'),
chalk = require('chalk'),
mkdirp = require('mkdirp'),
parseCss = require('css-parse'),
superb = require('superb'),
dammit = require('dammit');

/**
 * Task methods.
 * @namespace CombineMq
 */
 module.exports = {
  /**
  * Initialises task.
  * @function init
  * @param {object} options
  * @memberof CombineMq
  * @todo Add option to log out combined queries.
  */
  init: function(options) {
		var self = this; // Scope resolution

		// Set file attributes
		self.src = options.src;
		self.dest = options.dest;

		// Read file in
		fs.readFile(self.src, 'utf8', function(err, fileContents) {
			if(err) {
				// Write error messages
				console.log(chalk.red(dammit({NSFW: false})));
				console.log(chalk.red('(Could not open file: "' + self.src + '")'));
				// Exit out
				process.exit(1);
			}
			else {
				console.log(chalk.bold('\nCombining file: ' + self.src + '...'));
				// Moving on...
				self.parseCssString(fileContents);
			}
		});
	},
  /**
  * Parses the input CSS file.
  * @function parseCssString
	* @param {string} contents - CSS string.
  * @memberof CombineMq
  */
  parseCssString: function (contents, options) {
		var self = this; // Scope resolution

		self.options = options;

    // Helpers

    // Process media queries
    var processImportURL = function(importURL) {
    	var strCss = '';
    	strCss += '@import ' + importURL.import + ';';
    	return strCss;
    };

    // Process comments
    var processComment = function(comment) {
    	var strCss = '/*' + comment.comment + '*/';
    	return strCss;
    };

    // Process declaration
    var processDeclaration = function(declaration) {
    	var strCss = declaration.property + ': ' + declaration.value + ';';
    	return strCss;
    };

    // Check declarations type
    var commentOrDeclaration = function(declarations) {
    	var strCss = '';
    	if(declarations.type === 'declaration'){
    		strCss += '\n\t' + processDeclaration(declarations);
    	} else if(declarations.type === 'comment'){
    		strCss += ' ' + processComment(declarations);
    	}
    	return strCss;
    };

    // Process normal CSS rule
    var processRule = function(rule) {
    	var strCss = '';
    	strCss += rule.selectors.join(',\n') + ' {';
    	rule.declarations.forEach(function (rules) {
    		strCss += commentOrDeclaration(rules);
    	});
    	strCss += '\n}\n\n';
    	return strCss;
    };

    // Check rule type
    var commentOrRule = function(rule) {
    	var strCss = '';
      //log(rule.type);
      if (rule.type === 'rule') {
      	strCss += processRule(rule);
      } else if (rule.type === 'comment') {
      	strCss += processComment(rule) + '\n\n';
      } else if (rule.type === 'media') {
      	strCss += processMedia(rule);
      } else if (rule.type === 'keyframes') {
      	strCss += processKeyframes(rule);
      }
      return strCss;
    };

    // Check keyframe type
    var commentOrKeyframe = function(frame){
    	var strCss = '';
    	if (frame.type === 'keyframe'){
    		strCss += frame.values.join(',') + ' {';
    		frame.declarations.forEach(function (declaration) {
    			strCss += commentOrDeclaration(declaration);
    		});
    		strCss += '\n}\n\n';
    	} else if (frame.type === 'comment'){
    		strCss += processComment(frame) + '\n\n';
    	}
    	return strCss;
    };

    // Process media queries
    var processMedia = function(media) {
    	var strCss = '',
    	query = media.rule;
      //log(JSON.stringify(media)+'\n\n');
      if(!query && media.media) {
      	query = media.media;
      }
      if(query) {
        //log(JSON.stringify(media)+'\n\n');
        strCss += '@media ' + query + ' {\n\n';
        media.rules.forEach(function (rule) {
        	strCss += commentOrRule(rule);
        });
        strCss += '}\n\n';
        console.log('@media ' + query);
      }
      return strCss;
    };

    // Process keyframes
    var processKeyframes = function(key) {
    	var strCss = '';
    	strCss += '@' + (typeof key.vendor !=='undefined'? key.vendor: '') + 'keyframes ' + key.name + ' {\n\n';
    	key.keyframes.forEach(function (keyframe) {
    		strCss += commentOrKeyframe(keyframe);
    	});
    	strCss += '}\n\n';

    	return strCss;
    };

    // Process document
    var processDocument = function(doc) {
    	var strCss = '';
    	strCss += '@' + (typeof doc.vendor !=='undefined'? doc.vendor: '') + 'document ' + doc.document + ' {\n\n';
    	console.log('@' + (typeof doc.vendor !=='undefined'? doc.vendor: '') + 'document ' + doc.document);
    	doc.rules.forEach(function (rule) {
    		strCss += commentOrRule(rule);
    	});
    	strCss += '}\n\n';
    	return strCss;
    };

    // Process charset
    var processCharset = function (charset) {
    	console.log('@charset ' + charset.charset);
    	return '@charset ' + charset.charset + ';\n\n';
    };

    // Do the processing
    var cssJson = parseCss(contents);
    var strStyles = '';
    var processedCSS = {};
    processedCSS.importURL = [];
    processedCSS.base = [];
    processedCSS.media = [];
    processedCSS.media.all = [];
    processedCSS.media.minWidth = [];
    processedCSS.media.maxWidth = [];
    processedCSS.media.minHeight = [];
    processedCSS.media.maxHeight = [];
    processedCSS.media.print = [];
    processedCSS.media.blank = [];
    processedCSS.keyframes = [];
    processedCSS.charset = [];
    processedCSS.document = [];

    // For every rule in the stylesheet...
    cssJson.stylesheet.rules.forEach( function (rule) {
    // if the rule is a media query...
    if (rule.type === 'media') {

    // Create 'id' based on the query (stripped from spaces and dashes etc.)
    var strMedia = rule.media.replace(/[^A-Za-z0-9]/ig,'');

    // Create an array with all the media queries with the same 'id'
    var item = processedCSS.media.filter(function (element) {
    	return (element.val === strMedia);
    });

    // If there are no media queries in the array, define details
    if (item.length < 1) {
    	var mediaObj = {};
    	mediaObj.sortVal = parseFloat(rule.media.match( /\d+/g ));
    	mediaObj.rule = rule.media;
    	mediaObj.val = strMedia;
    	mediaObj.rules = [];

    	processedCSS.media.push(mediaObj);
    }

    // Compare the query to other queries
    var i = 0, matched = false;
    processedCSS.media.forEach(function (elm) {
    	if (elm.val === strMedia) {
    		matched = true;
    	}
    	if (!matched) {i++;}
    });

    // Push every merged query
    rule.rules.forEach(function (mediaRule) {
    	if (mediaRule.type === 'rule' || 'comment' ) {
    		processedCSS.media[i].rules.push(mediaRule);
    	}
    });

  } else if (rule.type === 'keyframes') {
  	processedCSS.keyframes.push(rule);

  } else if (rule.type === 'import') {
  	processedCSS.importURL.push(rule);

  } else if (rule.type === 'rule' || 'comment') {
  	processedCSS.base.push(rule);

  }
});

        // Sort media queries by kind, this is needed to output them in the right order
        processedCSS.media.forEach(function (item) {
        	if (item.rule.match( /min-width/ )){
        		processedCSS.media.minWidth.push(item);
        	} else if (item.rule.match( /min-height/ )){
        		processedCSS.media.minHeight.push(item);
        	} else if (item.rule.match( /max-width/ )){
        		processedCSS.media.maxWidth.push(item);
        	} else if (item.rule.match( /max-height/ )){
        		processedCSS.media.maxHeight.push(item);
        	} else if (item.rule.match( /print/ )){
        		processedCSS.media.print.push(item);
        	} else if (item.rule.match( /all/ )){
        		processedCSS.media.all.push(item);
        	} else {
        		processedCSS.media.blank.push(item);
        	}
        });

        // Function to determine sort order
        var determineSortOrder = function(a, b, isMax) {
        	var sortValA = a.sortVal,
        	sortValB = b.sortVal;
        	isMax = typeof isMax !== 'undefined' ? isMax : false;

          // consider print for sorting if sortVals are equal
          if (sortValA === sortValB) {
          	if (a.rule.match( /print/ )) {
              // a contains print and should be sorted after b
              return 1;
            }
            if (b.rule.match( /print/ )) {
              // b contains print and should be sorted after a
              return -1;
            }
          }

          // return descending sort order for max-(width|height) media queries
          if (isMax) { return sortValB-sortValA; }

          // return ascending sort order
          return sortValA-sortValB;
        };

        // Sort media.all queries ascending
        processedCSS.media.all.sort(function(a,b){
        	return determineSortOrder(a, b);
        });

        // Sort media.minWidth queries ascending
        processedCSS.media.minWidth.sort(function(a,b){
        	return determineSortOrder(a, b);
        });

        // Sort media.minHeight queries ascending
        processedCSS.media.minHeight.sort(function(a,b){
        	return determineSortOrder(a, b);
        });

        // Sort media.maxWidth queries descending
        processedCSS.media.maxWidth.sort(function(a,b){
        	return determineSortOrder(a, b, true);
        });

        // Sort media.maxHeight queries descending
        processedCSS.media.maxHeight.sort(function(a,b){
        	return determineSortOrder(a, b, true);
        });

        // Function to output base CSS
        var outputBase = function(base){
        	base.forEach(function (rule) {
        		strStyles += commentOrRule(rule);
        	});
        };

        // Function to import URL
        var outputImportURL = function(importURL){
        	importURL.forEach(function(item){
        		strStyles += processImportURL(item);
        	});
        };

        // Function to output media queries
        var outputMedia = function(media){
        	media.forEach(function(item){
        		strStyles += processMedia(item);
        	});
        };

        // Function to output keyframes
        var outputKeyFrames = function(keyframes){
        	keyframes.forEach(function (keyframe) {
        		strStyles += processKeyframes(keyframe);
        	});
        };

        // Function to output document
        var outputDocument = function (doc) {
        	doc.forEach(function (doc) {
        		strStyles += processDocument(doc);
        	});
        };

        // Function to output charset
        var outputCharset = function(charset){
        	charset.forEach(function (charset) {
        		strStyles += processCharset(charset);
        	});
        };

        // Check if import URL was processed
        if (processedCSS.importURL.length !== 0){
        	outputImportURL(processedCSS.importURL);
        }

        // Check if base CSS was processed and print them
        if (processedCSS.base.length !== 0){
        	outputBase(processedCSS.base);
        }

        // Check if charset was set
        if (processedCSS.charset.length !== 0){
        	outputCharset(processedCSS.charset);
        }

       // Check if document was processed
       if (processedCSS.document.length !== 0){
       	console.log('Processed documents:');
       	outputDocument(processedCSS.document);
       	console.log('\n');
       }

        // Check if media queries were processed and print them in order
        if (processedCSS.media.length !== 0){
        	console.log('\nProcessed media queries:');
        	outputMedia(processedCSS.media.blank);
        	outputMedia(processedCSS.media.all);
        	outputMedia(processedCSS.media.minWidth);
        	outputMedia(processedCSS.media.minHeight);
        	outputMedia(processedCSS.media.maxWidth);
        	outputMedia(processedCSS.media.maxHeight);
        	outputMedia(processedCSS.media.print);
        	console.log('');
        }

        // Check if keyframes were processed and print them
        if (processedCSS.keyframes.length !== 0){
        	outputKeyFrames(processedCSS.keyframes);
        }

    	// Moving on...
    	self.writeFile(strStyles);
    },
    /**
  	* Write combined CSS to a specified file, in a specified directory.
  	* @function writeFile
		* @param {string} fileContents - combined CSS string.
  	* @memberof CombineMq
  	* @todo Strip file extension if it's in dest.
  	* @todo Extract file path from dest.
		*/
		writeFile: function (fileContents) {
			var self = this;

      // Create a dir
      mkdirp('tmp', function(err) {
      	if(err) {
      		throw err;
      	}
      	else {
      		process.chdir('tmp');
      		fs.writeFile(self.dest + '.css', fileContents, function (err) {
      			if (err) {
      				throw err;
      			}

      			// Write a success message
      			var sup = superb();
      			sup = sup.charAt(0).toUpperCase() + sup.slice(1) + '!';
      			console.log(chalk.green(sup + ' CSS combined!\n'));
      		});
      	}
      });
    }

  };
<commit_msg>feat(Task): Returns string if options passed through<commit_after>/**
*
* @file Combine MQ Main
* @version 0.1.0
* @author {@link http://github.com/furzeface Daniel Furze}
* @link https://github.com/buildingblocks/node-combine-mq
*
* Copyright (c) 2014 Building Blocks
* Licensed under the MIT license.
*
*/

'use strict';

// Require all of the things.
var fs = require('fs'),
chalk = require('chalk'),
mkdirp = require('mkdirp'),
parseCss = require('css-parse'),
superb = require('superb'),
dammit = require('dammit');

/**
 * Task methods.
 * @namespace CombineMq
 */
 module.exports = {
  /**
  * Initialises task.
  * @function init
  * @param {object} options
  * @memberof CombineMq
  * @todo Add option to log out combined queries.
  */
  init: function(options) {
		var self = this; // Scope resolution

		// Set file attributes
		self.src = options.src;
		self.dest = options.dest;

		// Read file in
		fs.readFile(self.src, 'utf8', function(err, fileContents) {
			if(err) {
				// Write error messages
				console.log(chalk.red(dammit({NSFW: false})));
				console.log(chalk.red('(Could not open file: "' + self.src + '")'));
				// Exit out
				process.exit(1);
			}
			else {
				console.log(chalk.bold('\nCombining file: ' + self.src + '...'));
				// Moving on...
				self.parseCssString(fileContents);
			}
		});
	},
  /**
  * Parses the input CSS file.
  * @function parseCssString
	* @param {string} contents - CSS string.
  * @memberof CombineMq
  */
  parseCssString: function (contents, options) {
		var self = this; // Scope resolution

		self.options = options;

    // Helpers

    // Process media queries
    var processImportURL = function(importURL) {
    	var strCss = '';
    	strCss += '@import ' + importURL.import + ';';
    	return strCss;
    };

    // Process comments
    var processComment = function(comment) {
    	var strCss = '/*' + comment.comment + '*/';
    	return strCss;
    };

    // Process declaration
    var processDeclaration = function(declaration) {
    	var strCss = declaration.property + ': ' + declaration.value + ';';
    	return strCss;
    };

    // Check declarations type
    var commentOrDeclaration = function(declarations) {
    	var strCss = '';
    	if(declarations.type === 'declaration'){
    		strCss += '\n\t' + processDeclaration(declarations);
    	} else if(declarations.type === 'comment'){
    		strCss += ' ' + processComment(declarations);
    	}
    	return strCss;
    };

    // Process normal CSS rule
    var processRule = function(rule) {
    	var strCss = '';
    	strCss += rule.selectors.join(',\n') + ' {';
    	rule.declarations.forEach(function (rules) {
    		strCss += commentOrDeclaration(rules);
    	});
    	strCss += '\n}\n\n';
    	return strCss;
    };

    // Check rule type
    var commentOrRule = function(rule) {
    	var strCss = '';
      //log(rule.type);
      if (rule.type === 'rule') {
      	strCss += processRule(rule);
      } else if (rule.type === 'comment') {
      	strCss += processComment(rule) + '\n\n';
      } else if (rule.type === 'media') {
      	strCss += processMedia(rule);
      } else if (rule.type === 'keyframes') {
      	strCss += processKeyframes(rule);
      }
      return strCss;
    };

    // Check keyframe type
    var commentOrKeyframe = function(frame){
    	var strCss = '';
    	if (frame.type === 'keyframe'){
    		strCss += frame.values.join(',') + ' {';
    		frame.declarations.forEach(function (declaration) {
    			strCss += commentOrDeclaration(declaration);
    		});
    		strCss += '\n}\n\n';
    	} else if (frame.type === 'comment'){
    		strCss += processComment(frame) + '\n\n';
    	}
    	return strCss;
    };

    // Process media queries
    var processMedia = function(media) {
    	var strCss = '',
    	query = media.rule;
      //log(JSON.stringify(media)+'\n\n');
      if(!query && media.media) {
      	query = media.media;
      }
      if(query) {
        //log(JSON.stringify(media)+'\n\n');
        strCss += '@media ' + query + ' {\n\n';
        media.rules.forEach(function (rule) {
        	strCss += commentOrRule(rule);
        });
        strCss += '}\n\n';
        console.log('@media ' + query);
      }
      return strCss;
    };

    // Process keyframes
    var processKeyframes = function(key) {
    	var strCss = '';
    	strCss += '@' + (typeof key.vendor !=='undefined'? key.vendor: '') + 'keyframes ' + key.name + ' {\n\n';
    	key.keyframes.forEach(function (keyframe) {
    		strCss += commentOrKeyframe(keyframe);
    	});
    	strCss += '}\n\n';

    	return strCss;
    };

    // Process document
    var processDocument = function(doc) {
    	var strCss = '';
    	strCss += '@' + (typeof doc.vendor !=='undefined'? doc.vendor: '') + 'document ' + doc.document + ' {\n\n';
    	console.log('@' + (typeof doc.vendor !=='undefined'? doc.vendor: '') + 'document ' + doc.document);
    	doc.rules.forEach(function (rule) {
    		strCss += commentOrRule(rule);
    	});
    	strCss += '}\n\n';
    	return strCss;
    };

    // Process charset
    var processCharset = function (charset) {
    	console.log('@charset ' + charset.charset);
    	return '@charset ' + charset.charset + ';\n\n';
    };

    // Do the processing
    var cssJson = parseCss(contents);
    var strStyles = '';
    var processedCSS = {};
    processedCSS.importURL = [];
    processedCSS.base = [];
    processedCSS.media = [];
    processedCSS.media.all = [];
    processedCSS.media.minWidth = [];
    processedCSS.media.maxWidth = [];
    processedCSS.media.minHeight = [];
    processedCSS.media.maxHeight = [];
    processedCSS.media.print = [];
    processedCSS.media.blank = [];
    processedCSS.keyframes = [];
    processedCSS.charset = [];
    processedCSS.document = [];

    // For every rule in the stylesheet...
    cssJson.stylesheet.rules.forEach( function (rule) {
    // if the rule is a media query...
    if (rule.type === 'media') {

    // Create 'id' based on the query (stripped from spaces and dashes etc.)
    var strMedia = rule.media.replace(/[^A-Za-z0-9]/ig,'');

    // Create an array with all the media queries with the same 'id'
    var item = processedCSS.media.filter(function (element) {
    	return (element.val === strMedia);
    });

    // If there are no media queries in the array, define details
    if (item.length < 1) {
    	var mediaObj = {};
    	mediaObj.sortVal = parseFloat(rule.media.match( /\d+/g ));
    	mediaObj.rule = rule.media;
    	mediaObj.val = strMedia;
    	mediaObj.rules = [];

    	processedCSS.media.push(mediaObj);
    }

    // Compare the query to other queries
    var i = 0, matched = false;
    processedCSS.media.forEach(function (elm) {
    	if (elm.val === strMedia) {
    		matched = true;
    	}
    	if (!matched) {i++;}
    });

    // Push every merged query
    rule.rules.forEach(function (mediaRule) {
    	if (mediaRule.type === 'rule' || 'comment' ) {
    		processedCSS.media[i].rules.push(mediaRule);
    	}
    });

  } else if (rule.type === 'keyframes') {
  	processedCSS.keyframes.push(rule);

  } else if (rule.type === 'import') {
  	processedCSS.importURL.push(rule);

  } else if (rule.type === 'rule' || 'comment') {
  	processedCSS.base.push(rule);

  }
});

        // Sort media queries by kind, this is needed to output them in the right order
        processedCSS.media.forEach(function (item) {
        	if (item.rule.match( /min-width/ )){
        		processedCSS.media.minWidth.push(item);
        	} else if (item.rule.match( /min-height/ )){
        		processedCSS.media.minHeight.push(item);
        	} else if (item.rule.match( /max-width/ )){
        		processedCSS.media.maxWidth.push(item);
        	} else if (item.rule.match( /max-height/ )){
        		processedCSS.media.maxHeight.push(item);
        	} else if (item.rule.match( /print/ )){
        		processedCSS.media.print.push(item);
        	} else if (item.rule.match( /all/ )){
        		processedCSS.media.all.push(item);
        	} else {
        		processedCSS.media.blank.push(item);
        	}
        });

        // Function to determine sort order
        var determineSortOrder = function(a, b, isMax) {
        	var sortValA = a.sortVal,
        	sortValB = b.sortVal;
        	isMax = typeof isMax !== 'undefined' ? isMax : false;

          // consider print for sorting if sortVals are equal
          if (sortValA === sortValB) {
          	if (a.rule.match( /print/ )) {
              // a contains print and should be sorted after b
              return 1;
            }
            if (b.rule.match( /print/ )) {
              // b contains print and should be sorted after a
              return -1;
            }
          }

          // return descending sort order for max-(width|height) media queries
          if (isMax) { return sortValB-sortValA; }

          // return ascending sort order
          return sortValA-sortValB;
        };

        // Sort media.all queries ascending
        processedCSS.media.all.sort(function(a,b){
        	return determineSortOrder(a, b);
        });

        // Sort media.minWidth queries ascending
        processedCSS.media.minWidth.sort(function(a,b){
        	return determineSortOrder(a, b);
        });

        // Sort media.minHeight queries ascending
        processedCSS.media.minHeight.sort(function(a,b){
        	return determineSortOrder(a, b);
        });

        // Sort media.maxWidth queries descending
        processedCSS.media.maxWidth.sort(function(a,b){
        	return determineSortOrder(a, b, true);
        });

        // Sort media.maxHeight queries descending
        processedCSS.media.maxHeight.sort(function(a,b){
        	return determineSortOrder(a, b, true);
        });

        // Function to output base CSS
        var outputBase = function(base){
        	base.forEach(function (rule) {
        		strStyles += commentOrRule(rule);
        	});
        };

        // Function to import URL
        var outputImportURL = function(importURL){
        	importURL.forEach(function(item){
        		strStyles += processImportURL(item);
        	});
        };

        // Function to output media queries
        var outputMedia = function(media){
        	media.forEach(function(item){
        		strStyles += processMedia(item);
        	});
        };

        // Function to output keyframes
        var outputKeyFrames = function(keyframes){
        	keyframes.forEach(function (keyframe) {
        		strStyles += processKeyframes(keyframe);
        	});
        };

        // Function to output document
        var outputDocument = function (doc) {
        	doc.forEach(function (doc) {
        		strStyles += processDocument(doc);
        	});
        };

        // Function to output charset
        var outputCharset = function(charset){
        	charset.forEach(function (charset) {
        		strStyles += processCharset(charset);
        	});
        };

        // Check if import URL was processed
        if (processedCSS.importURL.length !== 0){
        	outputImportURL(processedCSS.importURL);
        }

        // Check if base CSS was processed and print them
        if (processedCSS.base.length !== 0){
        	outputBase(processedCSS.base);
        }

        // Check if charset was set
        if (processedCSS.charset.length !== 0){
        	outputCharset(processedCSS.charset);
        }

       // Check if document was processed
       if (processedCSS.document.length !== 0){
       	console.log('Processed documents:');
       	outputDocument(processedCSS.document);
       	console.log('\n');
       }

        // Check if media queries were processed and print them in order
        if (processedCSS.media.length !== 0){
        	console.log('\nProcessed media queries:');
        	outputMedia(processedCSS.media.blank);
        	outputMedia(processedCSS.media.all);
        	outputMedia(processedCSS.media.minWidth);
        	outputMedia(processedCSS.media.minHeight);
        	outputMedia(processedCSS.media.maxWidth);
        	outputMedia(processedCSS.media.maxHeight);
        	outputMedia(processedCSS.media.print);
        	console.log('');
        }

        // Check if keyframes were processed and print them
        if (processedCSS.keyframes.length !== 0){
        	outputKeyFrames(processedCSS.keyframes);
        }

    	// Moving on...
    	if(!options) {
    		self.writeFile(strStyles);
    	} else {
    		return strStyles;
    	}
    },
    /**
  	* Write combined CSS to a specified file, in a specified directory.
  	* @function writeFile
		* @param {string} fileContents - combined CSS string.
  	* @memberof CombineMq
  	* @todo Strip file extension if it's in dest.
  	* @todo Extract file path from dest.
		*/
		writeFile: function (fileContents) {
			var self = this;

      // Create a dir
      mkdirp('tmp', function(err) {
      	if(err) {
      		throw err;
      	}
      	else {
      		process.chdir('tmp');
      		fs.writeFile(self.dest + '.css', fileContents, function (err) {
      			if (err) {
      				throw err;
      			}

      			// Write a success message
      			var sup = superb();
      			sup = sup.charAt(0).toUpperCase() + sup.slice(1) + '!';
      			console.log(chalk.green(sup + ' CSS combined!\n'));
      		});
      	}
      });
    }

  };
<|endoftext|>