nano-staged
Version:
Tiny tool to run commands for modified, staged, and committed git files.
265 lines (221 loc) • 5.45 kB
JavaScript
const CLOSE_PARENTHESES = ')'.charCodeAt(0)
const OPEN_PARENTHESES = '('.charCodeAt(0)
const CLOSE_SQUARE = ']'.charCodeAt(0)
const OPEN_SQUARE = '['.charCodeAt(0)
const CLOSE_CURLY = '}'.charCodeAt(0)
const OPEN_CURLY = '{'.charCodeAt(0)
const BACKSLASH = '\\'.charCodeAt(0)
const ASTERISK = '*'.charCodeAt(0)
const QUESTION = '?'.charCodeAt(0)
const DOLLAR = '$'.charCodeAt(0)
const EQUALS = '='.charCodeAt(0)
const CARRET = '^'.charCodeAt(0)
const SLASH = '/'.charCodeAt(0)
const COLON = ':'.charCodeAt(0)
const POINT = '.'.charCodeAt(0)
const PIPE = '|'.charCodeAt(0)
const PLUS = '+'.charCodeAt(0)
const BANG = '!'.charCodeAt(0)
const COMA = ','.charCodeAt(0)
const AT = '@'.charCodeAt(0)
const GLOBSTAR = `((?:[^/]*(?:/|$))*)`
const WILDCARD = `([^/]*)`
export function globToRegex(glob, opts = {}) {
let { extended = false, globstar = false, flags = '' } = opts
let inRange = false
let inGroup = false
let stack = []
let regex = ''
let pos = 0
let code, next
while (pos < glob.length) {
code = glob.charCodeAt(pos)
switch (code) {
case BACKSLASH:
case DOLLAR:
case CARRET:
case EQUALS:
case POINT: {
regex += `\\${glob[pos]}`
break
}
case SLASH: {
regex += `\\${glob[pos]}`
if (glob.charCodeAt(pos + 1) === SLASH) {
regex += '?'
}
break
}
case OPEN_PARENTHESES: {
if (stack.length) {
regex += glob[pos]
break
}
regex += `\\${glob[pos]}`
break
}
case CLOSE_PARENTHESES: {
if (stack.length) {
regex += glob[pos]
let type = stack.pop()
if (type === '@') {
regex += '{1}'
} else if (type === '!') {
regex += '([^/]*)'
} else {
regex += type
}
break
}
regex += `\\${glob[pos]}`
break
}
case PIPE: {
if (stack.length) {
regex += glob[pos]
break
}
regex += `\\${glob[pos]}`
break
}
case PLUS: {
if (glob.charCodeAt(pos + 1) === OPEN_PARENTHESES && extended) {
stack.push(glob[pos])
break
}
regex += `\\${glob[pos]}`
break
}
case AT: {
if (glob.charCodeAt(pos + 1) === OPEN_PARENTHESES && extended) {
stack.push(glob[pos])
break
}
}
case BANG: {
if (extended) {
if (inRange) {
regex += `^`
break
}
if (glob.charCodeAt(pos + 1) === OPEN_PARENTHESES) {
stack.push(glob[pos])
regex += `(?!`
pos++
break
}
regex += `\\${glob[pos]}`
break
}
regex += `\\${glob[pos]}`
break
}
case QUESTION: {
if (extended) {
if (glob.charCodeAt(pos + 1) === OPEN_PARENTHESES) {
stack.push(glob[pos])
} else {
regex += `.`
}
break
}
regex += `\\${glob[pos]}`
break
}
case OPEN_SQUARE: {
if (inRange && glob.charCodeAt(pos + 1) === COLON) {
next = glob.indexOf(':', pos + 2)
let value = glob.slice(pos + 2, next)
if (value === 'alnum') {
regex += `(\\w|\\d)`
} else if (value === 'space') {
regex += `\\s`
} else if (value === 'digit') {
regex += `\\d`
}
pos = next + 1
break
}
if (extended) {
inRange = true
regex += glob[pos]
break
}
regex += `\\${glob[pos]}`
break
}
case CLOSE_SQUARE: {
if (extended) {
inRange = false
regex += glob[pos]
break
}
regex += `\\${glob[pos]}`
break
}
case OPEN_CURLY: {
if (extended) {
inGroup = true
regex += `(`
break
}
regex += `\\${glob[pos]}`
break
}
case CLOSE_CURLY: {
if (extended) {
inGroup = false
regex += `)`
break
}
regex += `\\${glob[pos]}`
break
}
case COMA: {
if (inGroup) {
regex += `|`
break
}
regex += `\\${glob[pos]}`
break
}
case ASTERISK: {
if (glob.charCodeAt(pos + 1) === OPEN_PARENTHESES && extended) {
stack.push(glob[pos])
break
}
let prevChar = glob[pos - 1]
let starCount = 1
while (glob.charCodeAt(pos + 1) === ASTERISK) {
starCount++
pos++
}
let nextChar = glob[pos + 1]
if (!globstar) {
regex += `.*`
} else {
let isGlobstar =
starCount > 1 &&
(prevChar === '/' || prevChar === undefined) &&
(nextChar === '/' || nextChar === undefined)
if (isGlobstar) {
regex += GLOBSTAR
pos++
} else {
regex += WILDCARD
}
}
break
}
default: {
regex += glob[pos]
break
}
}
pos++
}
if (!flags.includes('g')) {
regex = `^${regex}$`
}
return { regex: new RegExp(regex, flags) }
}