Why Gemfury? Push, build, and install  RubyGems npm packages Python packages Maven artifacts PHP packages Go Modules Debian packages RPM packages NuGet packages

Repository URL to install this package:

Details    
Size: Mime:
// http://www.w3.org/TR/CSS21/grammar.html
// https://github.com/visionmedia/css-parse/pull/49#issuecomment-30088027
let commentre = /\/\*[^*]*\*+([^/*][^*]*\*+)*\//g

module.exports = function(css, options) {
  options = options || {}

  /**
   * Positional.
   */

  let lineno = 1
  let column = 1

  /**
   * Update lineno and column based on `str`.
   */

  function updatePosition(str) {
    let lines = str.match(/\n/g)
    if (lines) {
      lineno += lines.length
    }
    let i = str.lastIndexOf('\n')
    column = ~i ? str.length - i : column + str.length
  }

  /**
   * Mark position and patch `node.position`.
   */

  function position() {
    let start = { line: lineno, column }
    return function(node) {
      node.position = new Position(start)
      whitespace()
      return node
    }
  }

  /**
   * Store position information for a node
   */

  function Position(start) {
    this.start = start
    this.end = { line: lineno, column }
    this.source = options.source
  }

  /**
   * Non-enumerable source string
   */

  Position.prototype.content = css

  /**
   * Error `msg`.
   */

  let errorsList = []

  function error(msg) {
    let err = new Error(options.source + ':' + lineno + ':' + column + ': ' + msg)
    err.reason = msg
    err.filename = options.source
    err.line = lineno
    err.column = column
    err.source = css

    if (options.silent) {
      errorsList.push(err)
    } else {
      throw err
    }
  }

  /**
   * Parse stylesheet.
   */

  function stylesheet() {
    let rulesList = rules()

    return {
      type: 'stylesheet',
      stylesheet: {
        rules: rulesList,
        parsingErrors: errorsList,
      },
    }
  }

  /**
   * Opening brace.
   */

  function open() {
    return match(/^{\s*/)
  }

  /**
   * Closing brace.
   */

  function close() {
    return match(/^}/)
  }

  /**
   * Parse ruleset.
   */

  function rules() {
    let node
    let rules = []
    whitespace()
    comments(rules)
    while (css.length && css.charAt(0) != '}' && (node = atrule() || rule())) {
      if (node !== false) {
        rules.push(node)
        comments(rules)
      }
    }
    return rules
  }

  /**
   * Match `re` and return captures.
   */

  function match(re) {
    let m = re.exec(css)
    if (!m) {
      return
    }
    let str = m[0]
    updatePosition(str)
    css = css.slice(str.length)
    return m
  }

  /**
   * Parse whitespace.
   */

  function whitespace() {
    match(/^\s*/)
  }

  /**
   * Parse comments;
   */

  function comments(rules) {
    let c
    rules = rules || []
    while (c = comment()) {
      if (c !== false) {
        rules.push(c)
      }
    }
    return rules
  }

  /**
   * Parse comment.
   */

  function comment() {
    let pos = position()
    if (css.charAt(0) != '/' || css.charAt(1) != '*') {
      return
    }

    let i = 2
    while (css.charAt(i) != '' && (css.charAt(i) != '*' || css.charAt(i + 1) != '/')) {
      ++i
    }
    i += 2

    if (css.charAt(i - 1) === '') {
      return error('End of comment missing')
    }

    let str = css.slice(2, i - 2)
    column += 2
    updatePosition(str)
    css = css.slice(i)
    column += 2

    return pos({
      type: 'comment',
      comment: str,
    })
  }

  /**
   * Parse selector.
   */

  function selector() {
    let m = match(/^([^{]+)/)
    if (!m) {
      return
    }
    /* @fix Remove all comments from selectors
     * http://ostermiller.org/findcomment.html */
    return trim(m[0])
      .replace(/\/\*([^*]|[\r\n]|(\*+([^*/]|[\r\n])))*\*\/+/g, '')
      .replace(/"(?:\\"|[^"])*"|'(?:\\'|[^'])*'/g, function(m) {
        return m.replace(/,/g, '\u200C')
      })
      .split(/\s*(?![^(]*\)),\s*/)
      .map(function(s) {
        return s.replace(/\u200C/g, ',')
      })
  }

  /**
   * Parse declaration.
   */

  function declaration() {
    let pos = position()

    // prop
    let prop = match(/^(\*?[-#\/\*\\\w]+(\[[0-9a-z_-]+\])?)\s*/)
    if (!prop) {
      return
    }
    prop = trim(prop[0])

    // :
    if (!match(/^:\s*/)) {
      return error("property missing ':'")
    }

    // val
    let val = match(/^((?:'(?:\\'|.)*?'|"(?:\\"|.)*?"|\([^\)]*?\)|[^};])+)/)

    let ret = pos({
      type: 'declaration',
      property: prop.replace(commentre, ''),
      value: val ? trim(val[0]).replace(commentre, '') : '',
    })

    // ;
    match(/^[;\s]*/)

    return ret
  }

  /**
   * Parse declarations.
   */

  function declarations() {
    let decls = []

    if (!open()) {
      return error("missing '{'")
    }
    comments(decls)

    // declarations
    let decl
    while (decl = declaration()) {
      if (decl !== false) {
        decls.push(decl)
        comments(decls)
      }
    }

    if (!close()) {
      return error("missing '}'")
    }
    return decls
  }

  /**
   * Parse keyframe.
   */

  function keyframe() {
    let m
    let vals = []
    let pos = position()

    while (m = match(/^((\d+\.\d+|\.\d+|\d+)%?|[a-z]+)\s*/)) {
      vals.push(m[1])
      match(/^,\s*/)
    }

    if (!vals.length) {
      return
    }

    return pos({
      type: 'keyframe',
      values: vals,
      declarations: declarations(),
    })
  }

  /**
   * Parse keyframes.
   */

  function atkeyframes() {
    let pos = position()
    var m = match(/^@([-\w]+)?keyframes\s*/)

    if (!m) {
      return
    }
    let vendor = m[1]

    // identifier
    var m = match(/^([-\w]+)\s*/)
    if (!m) {
      return error('@keyframes missing name')
    }
    let name = m[1]

    if (!open()) {
      return error("@keyframes missing '{'")
    }

    let frame
    let frames = comments()
    while (frame = keyframe()) {
      frames.push(frame)
      frames = frames.concat(comments())
    }

    if (!close()) {
      return error("@keyframes missing '}'")
    }

    return pos({
      type: 'keyframes',
      name,
      vendor,
      keyframes: frames,
    })
  }

  /**
   * Parse supports.
   */

  function atsupports() {
    let pos = position()
    let m = match(/^@supports *([^{]+)/)

    if (!m) {
      return
    }
    let supports = trim(m[1])

    if (!open()) {
      return error("@supports missing '{'")
    }

    let style = comments().concat(rules())

    if (!close()) {
      return error("@supports missing '}'")
    }

    return pos({
      type: 'supports',
      supports,
      rules: style,
    })
  }

  /**
   * Parse host.
   */

  function athost() {
    let pos = position()
    let m = match(/^@host\s*/)

    if (!m) {
      return
    }

    if (!open()) {
      return error("@host missing '{'")
    }

    let style = comments().concat(rules())

    if (!close()) {
      return error("@host missing '}'")
    }

    return pos({
      type: 'host',
      rules: style,
    })
  }

  /**
   * Parse media.
   */

  function atmedia() {
    let pos = position()
    let m = match(/^@media *([^{]+)/)

    if (!m) {
      return
    }
    let media = trim(m[1])

    if (!open()) {
      return error("@media missing '{'")
    }

    let style = comments().concat(rules())

    if (!close()) {
      return error("@media missing '}'")
    }

    return pos({
      type: 'media',
      media,
      rules: style,
    })
  }


  /**
   * Parse custom-media.
   */

  function atcustommedia() {
    let pos = position()
    let m = match(/^@custom-media\s+(--[^\s]+)\s*([^{;]+);/)
    if (!m) {
      return
    }

    return pos({
      type: 'custom-media',
      name: trim(m[1]),
      media: trim(m[2]),
    })
  }

  /**
   * Parse paged media.
   */

  function atpage() {
    let pos = position()
    let m = match(/^@page */)
    if (!m) {
      return
    }

    let sel = selector() || []

    if (!open()) {
      return error("@page missing '{'")
    }
    let decls = comments()

    // declarations
    let decl
    while (decl = declaration()) {
      decls.push(decl)
      decls = decls.concat(comments())
    }

    if (!close()) {
      return error("@page missing '}'")
    }

    return pos({
      type: 'page',
      selectors: sel,
      declarations: decls,
    })
  }

  /**
   * Parse document.
   */

  function atdocument() {
    let pos = position()
    let m = match(/^@([-\w]+)?document *([^{]+)/)
    if (!m) {
      return
    }

    let vendor = trim(m[1])
    let doc = trim(m[2])

    if (!open()) {
      return error("@document missing '{'")
    }

    let style = comments().concat(rules())

    if (!close()) {
      return error("@document missing '}'")
    }

    return pos({
      type: 'document',
      document: doc,
      vendor,
      rules: style,
    })
  }

  /**
   * Parse font-face.
   */

  function atfontface() {
    let pos = position()
    let m = match(/^@font-face\s*/)
    if (!m) {
      return
    }

    if (!open()) {
      return error("@font-face missing '{'")
    }
    let decls = comments()

    // declarations
    let decl
    while (decl = declaration()) {
      decls.push(decl)
      decls = decls.concat(comments())
    }

    if (!close()) {
      return error("@font-face missing '}'")
    }

    return pos({
      type: 'font-face',
      declarations: decls,
    })
  }

  /**
   * Parse import
   */

  let atimport = _compileAtrule('import')

  /**
   * Parse charset
   */

  let atcharset = _compileAtrule('charset')

  /**
   * Parse namespace
   */

  let atnamespace = _compileAtrule('namespace')

  /**
   * Parse non-block at-rules
   */


  function _compileAtrule(name) {
    let re = new RegExp('^@' + name + '\\s*([^;]+);')
    return function() {
      let pos = position()
      let m = match(re)
      if (!m) {
        return
      }
      let ret = { type: name }
      ret[name] = m[1].trim()
      return pos(ret)
    }
  }

  /**
   * Parse at rule.
   */

  function atrule() {
    if (css[0] != '@') {
      return
    }

    return atkeyframes()
      || atmedia()
      || atcustommedia()
      || atsupports()
      || atimport()
      || atcharset()
      || atnamespace()
      || atdocument()
      || atpage()
      || athost()
      || atfontface()
  }

  /**
   * Parse rule.
   */

  function rule() {
    let pos = position()
    let sel = selector()

    if (!sel) {
      return error('selector missing')
    }
    comments()

    return pos({
      type: 'rule',
      selectors: sel,
      declarations: declarations(),
    })
  }

  return addParent(stylesheet())
}

/**
 * Trim `str`.
 */

function trim(str) {
  return str ? str.replace(/^\s+|\s+$/g, '') : ''
}

/**
 * Adds non-enumerable parent node reference to each node.
 */

function addParent(obj, parent) {
  let isNode = obj && typeof obj.type === 'string'
  let childParent = isNode ? obj : parent

  for (let k in obj) {
    let value = obj[k]
    if (Array.isArray(value)) {
      value.forEach(function(v) {
        addParent(v, childParent)
      })
    } else if (value && typeof value === 'object') {
      addParent(value, childParent)
    }
  }

  if (isNode) {
    Object.defineProperty(obj, 'parent', {
      configurable: true,
      writable: true,
      enumerable: false,
      value: parent || null,
    })
  }

  return obj
}