| const hasOwnProperty = Object.prototype.hasOwnProperty; |
| const shape = { |
| generic: true, |
| types: appendOrAssign, |
| atrules: { |
| prelude: appendOrAssignOrNull, |
| descriptors: appendOrAssignOrNull |
| }, |
| properties: appendOrAssign, |
| parseContext: assign, |
| scope: deepAssign, |
| atrule: ['parse'], |
| pseudo: ['parse'], |
| node: ['name', 'structure', 'parse', 'generate', 'walkContext'] |
| }; |
| |
| function isObject(value) { |
| return value && value.constructor === Object; |
| } |
| |
| function copy(value) { |
| return isObject(value) |
| ? Object.assign({}, value) |
| : value; |
| } |
| |
| function assign(dest, src) { |
| return Object.assign(dest, src); |
| } |
| |
| function deepAssign(dest, src) { |
| for (const key in src) { |
| if (hasOwnProperty.call(src, key)) { |
| if (isObject(dest[key])) { |
| deepAssign(dest[key], copy(src[key])); |
| } else { |
| dest[key] = copy(src[key]); |
| } |
| } |
| } |
| |
| return dest; |
| } |
| |
| function append(a, b) { |
| if (typeof b === 'string' && /^\s*\|/.test(b)) { |
| return typeof a === 'string' |
| ? a + b |
| : b.replace(/^\s*\|\s*/, ''); |
| } |
| |
| return b || null; |
| } |
| |
| function appendOrAssign(a, b) { |
| if (typeof b === 'string') { |
| return append(a, b); |
| } |
| |
| const result = Object.assign({}, a); |
| for (let key in b) { |
| if (hasOwnProperty.call(b, key)) { |
| result[key] = append(hasOwnProperty.call(a, key) ? a[key] : undefined, b[key]); |
| } |
| } |
| |
| return result; |
| } |
| |
| function appendOrAssignOrNull(a, b) { |
| const result = appendOrAssign(a, b); |
| |
| return !isObject(result) || Object.keys(result).length |
| ? result |
| : null; |
| } |
| |
| function mix(dest, src, shape) { |
| for (const key in shape) { |
| if (hasOwnProperty.call(shape, key) === false) { |
| continue; |
| } |
| |
| if (shape[key] === true) { |
| if (key in src) { |
| if (hasOwnProperty.call(src, key)) { |
| dest[key] = copy(src[key]); |
| } |
| } |
| } else if (shape[key]) { |
| if (typeof shape[key] === 'function') { |
| const fn = shape[key]; |
| dest[key] = fn({}, dest[key]); |
| dest[key] = fn(dest[key] || {}, src[key]); |
| } else if (isObject(shape[key])) { |
| const result = {}; |
| |
| for (let name in dest[key]) { |
| result[name] = mix({}, dest[key][name], shape[key]); |
| } |
| |
| for (let name in src[key]) { |
| result[name] = mix(result[name] || {}, src[key][name], shape[key]); |
| } |
| |
| dest[key] = result; |
| } else if (Array.isArray(shape[key])) { |
| const res = {}; |
| const innerShape = shape[key].reduce(function(s, k) { |
| s[k] = true; |
| return s; |
| }, {}); |
| |
| for (const [name, value] of Object.entries(dest[key] || {})) { |
| res[name] = {}; |
| if (value) { |
| mix(res[name], value, innerShape); |
| } |
| } |
| |
| for (const name in src[key]) { |
| if (hasOwnProperty.call(src[key], name)) { |
| if (!res[name]) { |
| res[name] = {}; |
| } |
| |
| if (src[key] && src[key][name]) { |
| mix(res[name], src[key][name], innerShape); |
| } |
| } |
| } |
| |
| dest[key] = res; |
| } |
| } |
| } |
| return dest; |
| } |
| |
| module.exports = (dest, src) => mix(dest, src, shape); |