|
@@ -0,0 +1,153 @@
|
|
|
|
+import { flattenDeep } from 'lodash'
|
|
|
|
+
|
|
|
|
+const parseShadow = string => {
|
|
|
|
+ const modes = ['_full', 'inset', 'x', 'y', 'blur', 'spread', 'color', 'alpha']
|
|
|
|
+ const regexPrep = [
|
|
|
|
+ // inset keyword (optional)
|
|
|
|
+ '^(?:(inset)\\s+)?',
|
|
|
|
+ // x
|
|
|
|
+ '(?:(-?[0-9]+(?:\\.[0-9]+)?)\\s+)',
|
|
|
|
+ // y
|
|
|
|
+ '(?:(-?[0-9]+(?:\\.[0-9]+)?)\\s+)',
|
|
|
|
+ // blur (optional)
|
|
|
|
+ '(?:(-?[0-9]+(?:\\.[0-9]+)?)\\s+)?',
|
|
|
|
+ // spread (optional)
|
|
|
|
+ '(?:(-?[0-9]+(?:\\.[0-9]+)?)\\s+)?',
|
|
|
|
+ // either hex, variable or function
|
|
|
|
+ '(#[0-9a-f]{6}|--[a-z\\-_]+|\\$[a-z\\-()_]+)',
|
|
|
|
+ // opacity (optional)
|
|
|
|
+ '(?:\\s+\\/\\s+([0-9]+(?:\\.[0-9]+)?)\\s*)?$'
|
|
|
|
+ ].join('')
|
|
|
|
+ const regex = new RegExp(regexPrep, 'gis') // global, (stable) indices, single-string
|
|
|
|
+ const result = regex.exec(string)
|
|
|
|
+ if (result == null) {
|
|
|
|
+ return string
|
|
|
|
+ } else {
|
|
|
|
+ const numeric = new Set(['x', 'y', 'blur', 'spread', 'alpha'])
|
|
|
|
+ const { x, y, blur, spread, alpha, inset, color } = Object.fromEntries(modes.map((mode, i) => {
|
|
|
|
+ if (numeric.has(mode)) {
|
|
|
|
+ return [mode, Number(result[i])]
|
|
|
|
+ } else if (mode === 'inset') {
|
|
|
|
+ return [mode, !!result[i]]
|
|
|
|
+ } else {
|
|
|
|
+ return [mode, result[i]]
|
|
|
|
+ }
|
|
|
|
+ }).filter(([k, v]) => v !== false).slice(1))
|
|
|
|
+
|
|
|
|
+ return { x, y, blur, spread, color, alpha, inset }
|
|
|
|
+ }
|
|
|
|
+}
|
|
|
|
+// this works nearly the same as HTML tree converter
|
|
|
|
+const parseIss = (input) => {
|
|
|
|
+ const buffer = [{ selector: null, content: [] }]
|
|
|
|
+ let textBuffer = ''
|
|
|
|
+
|
|
|
|
+ const getCurrentBuffer = () => {
|
|
|
|
+ let current = buffer[buffer.length - 1]
|
|
|
|
+ if (current == null) {
|
|
|
|
+ current = { selector: null, content: [] }
|
|
|
|
+ }
|
|
|
|
+ return current
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // Processes current line buffer, adds it to output buffer and clears line buffer
|
|
|
|
+ const flushText = (kind) => {
|
|
|
|
+ if (textBuffer === '') return
|
|
|
|
+ if (kind === 'content') {
|
|
|
|
+ getCurrentBuffer().content.push(textBuffer.trim())
|
|
|
|
+ } else {
|
|
|
|
+ getCurrentBuffer().selector = textBuffer.trim()
|
|
|
|
+ }
|
|
|
|
+ textBuffer = ''
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ for (let i = 0; i < input.length; i++) {
|
|
|
|
+ const char = input[i]
|
|
|
|
+
|
|
|
|
+ if (char === ';') {
|
|
|
|
+ flushText('content')
|
|
|
|
+ } else if (char === '{') {
|
|
|
|
+ flushText('header')
|
|
|
|
+ } else if (char === '}') {
|
|
|
|
+ flushText('content')
|
|
|
|
+ buffer.push({ selector: null, content: [] })
|
|
|
|
+ textBuffer = ''
|
|
|
|
+ } else {
|
|
|
|
+ textBuffer += char
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ return buffer
|
|
|
|
+}
|
|
|
|
+export const deserialize = (input) => {
|
|
|
|
+ const ast = parseIss(input)
|
|
|
|
+ const finalResult = ast.filter(i => i.selector != null).map(item => {
|
|
|
|
+ const { selector, content } = item
|
|
|
|
+ let stateCount = 0
|
|
|
|
+ const selectors = selector.split(/,/g)
|
|
|
|
+ const result = selectors.map(selector => {
|
|
|
|
+ const output = { component: '' }
|
|
|
|
+ let currentDepth = null
|
|
|
|
+
|
|
|
|
+ selector.split(/ /g).reverse().forEach((fragment, index, arr) => {
|
|
|
|
+ const fragmentObject = { component: '' }
|
|
|
|
+
|
|
|
|
+ let mode = 'component'
|
|
|
|
+ for (let i = 0; i < fragment.length; i++) {
|
|
|
|
+ const char = fragment[i]
|
|
|
|
+ switch (char) {
|
|
|
|
+ case '.': {
|
|
|
|
+ mode = 'variant'
|
|
|
|
+ fragmentObject.variant = ''
|
|
|
|
+ break
|
|
|
|
+ }
|
|
|
|
+ case ':': {
|
|
|
|
+ mode = 'state'
|
|
|
|
+ fragmentObject.state = fragmentObject.state || []
|
|
|
|
+ stateCount++
|
|
|
|
+ break
|
|
|
|
+ }
|
|
|
|
+ default: {
|
|
|
|
+ if (mode === 'state') {
|
|
|
|
+ const currentState = fragmentObject.state[stateCount - 1]
|
|
|
|
+ if (currentState == null) {
|
|
|
|
+ fragmentObject.state.push('')
|
|
|
|
+ }
|
|
|
|
+ fragmentObject.state[stateCount - 1] += char
|
|
|
|
+ } else {
|
|
|
|
+ fragmentObject[mode] += char
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ if (currentDepth !== null) {
|
|
|
|
+ currentDepth.parent = { ...fragmentObject }
|
|
|
|
+ currentDepth = currentDepth.parent
|
|
|
|
+ } else {
|
|
|
|
+ Object.keys(fragmentObject).forEach(key => {
|
|
|
|
+ output[key] = fragmentObject[key]
|
|
|
|
+ })
|
|
|
|
+ if (index !== (arr.length - 1)) {
|
|
|
|
+ output.parent = { component: '' }
|
|
|
|
+ }
|
|
|
|
+ currentDepth = output
|
|
|
|
+ }
|
|
|
|
+ })
|
|
|
|
+
|
|
|
|
+ output.directives = Object.fromEntries(content.map(d => {
|
|
|
|
+ const [property, value] = d.split(':')
|
|
|
|
+ let realValue = value.trim()
|
|
|
|
+ if (property === 'shadow') {
|
|
|
|
+ realValue = value.split(',').map(v => parseShadow(v.trim()))
|
|
|
|
+ } if (!Number.isNaN(Number(value))) {
|
|
|
|
+ realValue = Number(value)
|
|
|
|
+ }
|
|
|
|
+ return [property, realValue]
|
|
|
|
+ }))
|
|
|
|
+
|
|
|
|
+ return output
|
|
|
|
+ })
|
|
|
|
+ return result
|
|
|
|
+ })
|
|
|
|
+ return flattenDeep(finalResult)
|
|
|
|
+}
|