Repository URL to install this package:
|
Version:
1.1.16 ▾
|
// const { get } = require('lodash')
// const log = require('fliplog')
const { toUniverseView, dot } = require('./modules/chain-able')
const set = dot.set
/* eslint-disable brace-style */
// https://mdn.mozillademos.org/files/3665/css%20syntax%20-%20declaration.png
class Selector {
// & ~ + * [=""] : ,
}
// lowercase-or-numbers
Selector.className = /^([a-z][a-z0-9]*)(-[a-z0-9]+)*$/
// same as above, but starts with `.` ends with `{`
Selector.match = /^.([a-z][a-z0-9]*)(-[a-z0-9]+)*/
const isStarOrDoubleSlash = line => line.startsWith('/*') || line.startsWith('//')
// const isStarOrDoubleSlash = char => char === '*' || char === '//'
// const isComment = line => line.charAt(0) === '/' && isStarOrDoubleSlash(line.charAt(1))
const isComment = line => line.startsWith('/') && isStarOrDoubleSlash(line)
const not = fn => x => !fn(x)
const isNotComment = not(isComment)
const trim = x => x.trim()
const isNotEmpty = line => line !== ''
const removeFirst = current => {
current = current.split('')
current.shift()
current = current.join('')
return current
}
const removeLast = (current, num = 1) => {
current = current.split('')
while (num > 0) {
num -= 1
current.pop()
}
current = current.join('')
return current
}
const reverse = x => x.split('').reverse().join('')
const removeIndentWhile = str => {
let current = str.replace(/\t\r\n/gmi, '')
while (current.charAt(0) === ' ') {
current = removeFirst(current)
}
return current
}
const removeIndents = str => {
str = removeIndentWhile(str)
str = reverse(str)
str = removeIndentWhile(str)
str = reverse(str)
return str
}
const isSelector = line => Selector.match.test(line)
// isSelector(line) &&
const isMultiLineSelector = line => line.endsWith(',')
// isSelector(line) &&
const isSelectorEnd = line => line.endsWith('{')
const isRuleEnd = line => line.endsWith('}')
function toPath(line) {
line = line
// esc
// .replace(/\./gmi, '\\.')
// .split(' ').join('.')
.split('.').join('\\.')
// .split('\\.\\.').join('\\.')
// .map(l => l === '.')
// ---
// .replace(/\.\s/gim, '')
// .replace(/\./gim, ' ')
// .replace(/\s+/gim, '.')
// .replace(/ /gmi, '')
// .replace(/(\s|\t|\n|\r)+/gmi, '')
.trim()
// return removeLast(line)
if (line.endsWith('\\.')) {
return removeLast(line, 2)
}
// replace first dot if it exists
return line.charAt(0) === '.'
? line.replace('.', '')
: line
}
class Declaration {
constructor(property, value) {
this.property = property
this.value = value
}
}
class Parser {
constructor(css) {
this.css = css
// this.level = 0
this.store = new Map()
this.obj = {}
this.selectorPath = []
this.selectorGroupStack = false
// this.history = []
// this.stack = []
// this.path = []
this.mapLine = this.mapLine.bind(this)
}
get selector() {
return this.selectorPath.join(' ')
}
get(selector) {
return this.store.get(selector)
}
set(selector) {
if (this.store.has(selector)) {
return
}
const line = {
selectors: [selector],
rules: [],
}
this.store.set(selector, line)
// this.selectorPath.push(selector)
}
last() {
return this.selectorPath[this.selectorPath.length - 1]
}
lastWasMultiLineSelector() {
let last = this.last()
if (!last) return false
return isMultiLineSelector(last)
}
add(line) {
line = line.replace(/\&/gmi, '').trim()
this.selectorPath.push(line)
this.set(this.selector)
const path = toPath(this.selector)
set(this.obj, path, this.get(this.selector).rules)
toUniverseView(this.obj)
}
pop(line) {
this.selectorPath.pop()
}
mapLine(line) {
if (isMultiLineSelector(line)) {
line = removeLast(line)
if (this.selectorGroupStack) {
this.selectorGroupStack.push(line)
}
else {
this.selectorGroupStack = [line]
}
}
if (isSelectorEnd(line)) {
line = removeLast(line)
// line = line.trim()
// this is the annoying part...
if (this.selectorGroupStack) {
const selector = this.selector
const selectorGroupStack = this.selectorGroupStack.concat([line])
this.selectorGroupStack = false
selectorGroupStack.forEach((nestedSelector, index) => {
const isLast = index === selectorGroupStack.length - 1
let nested = nestedSelector
if (isLast) {
nested = selector + nestedSelector
nested += ','
}
else {
nested = selector + nestedSelector
}
this.add(nested)
})
}
else {
this.add(line)
}
}
else if (isRuleEnd(line)) {
this.pop()
}
else {
// console.log(this.selector, line)
const group = this.get(this.selector)
// console.log(group, this.selector)
group.rules.push(line)
}
// console.log(this.selectorPath)
}
parse(css) {
const mapped = css
.split('\n')
.map(removeIndents)
.filter(isNotComment)
// .filter(x => isComment(x))
.filter(isNotEmpty)
.map(this.mapLine)
Array.from(this.store.keys()).forEach(name => {
console.log('\n')
const value = this.get(name)
// log.fmtobj({ [name]: value.rules }).echo()
// log.bold(name).echo()
// log.blue('selectors').data(value.selectors).echo()
// log.yellow('rules').data(value.rules).echo()
// console.log({ [name]: value })
console.log('\n')
})
// log.fmtobj(this.obj.toFlat(false)).echo()
// log.quick((this.obj))
// log.quick(toUniverseView(this.obj, true))
// console.log(this.selector)
return css
}
}
function parse(css) {
return new Parser(css).parse(css)
}
module.exports = parse