diff --git a/.gitattributes b/.gitattributes index e9d29560f..cb1991458 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,4 +1,10 @@ package-lock.json -diff + +test/assert.js -diff +test/uvu.js -diff + +src/css.js -diff + data/icons/re.sonny.Workbench-symbolic.svg -diff data/icons/re.sonny.Workbench.Devel.svg -diff data/icons/re.sonny.Workbench.svg -diff diff --git a/package-lock.json b/package-lock.json index d21f7dd04..f9fddc64c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5,8 +5,14 @@ "packages": { "": { "devDependencies": { + "@rollup/plugin-commonjs": "^21.0.1", + "@rollup/plugin-node-resolve": "^13.1.3", + "css": "^3.0.0", "eslint": "^8.4.1", - "eslint-plugin-import": "^2.25.3" + "eslint-plugin-import": "^2.25.3", + "rollup": "^2.67.0", + "rollup-plugin-ignore": "^1.0.10", + "uvu": "^0.5.3" } }, "node_modules/@eslint/eslintrc": { @@ -49,12 +55,97 @@ "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==", "dev": true }, + "node_modules/@rollup/plugin-commonjs": { + "version": "21.0.1", + "resolved": "https://registry.npmjs.org/@rollup/plugin-commonjs/-/plugin-commonjs-21.0.1.tgz", + "integrity": "sha512-EA+g22lbNJ8p5kuZJUYyhhDK7WgJckW5g4pNN7n4mAFUM96VuwUnNT3xr2Db2iCZPI1pJPbGyfT5mS9T1dHfMg==", + "dev": true, + "dependencies": { + "@rollup/pluginutils": "^3.1.0", + "commondir": "^1.0.1", + "estree-walker": "^2.0.1", + "glob": "^7.1.6", + "is-reference": "^1.2.1", + "magic-string": "^0.25.7", + "resolve": "^1.17.0" + }, + "engines": { + "node": ">= 8.0.0" + }, + "peerDependencies": { + "rollup": "^2.38.3" + } + }, + "node_modules/@rollup/plugin-commonjs/node_modules/estree-walker": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", + "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", + "dev": true + }, + "node_modules/@rollup/plugin-node-resolve": { + "version": "13.1.3", + "resolved": "https://registry.npmjs.org/@rollup/plugin-node-resolve/-/plugin-node-resolve-13.1.3.tgz", + "integrity": "sha512-BdxNk+LtmElRo5d06MGY4zoepyrXX1tkzX2hrnPEZ53k78GuOMWLqmJDGIIOPwVRIFZrLQOo+Yr6KtCuLIA0AQ==", + "dev": true, + "dependencies": { + "@rollup/pluginutils": "^3.1.0", + "@types/resolve": "1.17.1", + "builtin-modules": "^3.1.0", + "deepmerge": "^4.2.2", + "is-module": "^1.0.0", + "resolve": "^1.19.0" + }, + "engines": { + "node": ">= 10.0.0" + }, + "peerDependencies": { + "rollup": "^2.42.0" + } + }, + "node_modules/@rollup/pluginutils": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-3.1.0.tgz", + "integrity": "sha512-GksZ6pr6TpIjHm8h9lSQ8pi8BE9VeubNT0OMJ3B5uZJ8pz73NPiqOtCog/x2/QzM1ENChPKxMDhiQuRHsqc+lg==", + "dev": true, + "dependencies": { + "@types/estree": "0.0.39", + "estree-walker": "^1.0.1", + "picomatch": "^2.2.2" + }, + "engines": { + "node": ">= 8.0.0" + }, + "peerDependencies": { + "rollup": "^1.20.0||^2.0.0" + } + }, + "node_modules/@types/estree": { + "version": "0.0.39", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.39.tgz", + "integrity": "sha512-EYNwp3bU+98cpU4lAWYYL7Zz+2gryWH1qbdDTidVd6hkiR6weksdbMadyXKXNPEkQFhXM+hVO9ZygomHXp+AIw==", + "dev": true + }, "node_modules/@types/json5": { "version": "0.0.29", "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", "integrity": "sha1-7ihweulOEdK4J7y+UnC86n8+ce4=", "dev": true }, + "node_modules/@types/node": { + "version": "17.0.14", + "resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.14.tgz", + "integrity": "sha512-SbjLmERksKOGzWzPNuW7fJM7fk3YXVTFiZWB/Hs99gwhk+/dnrQRPBQjPW9aO+fi1tAffi9PrwFvsmOKmDTyng==", + "dev": true + }, + "node_modules/@types/resolve": { + "version": "1.17.1", + "resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-1.17.1.tgz", + "integrity": "sha512-yy7HuzQhj0dhGpD8RLXSZWEkLsV9ibvxvi6EiJ3bkqLAO1RGo0WbkWQiwpRlSFymTJRz0d3k5LM3kkx8ArDbLw==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, "node_modules/acorn": { "version": "8.6.0", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.6.0.tgz", @@ -167,6 +258,18 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/atob": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz", + "integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==", + "dev": true, + "bin": { + "atob": "bin/atob.js" + }, + "engines": { + "node": ">= 4.5.0" + } + }, "node_modules/balanced-match": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", @@ -183,6 +286,18 @@ "concat-map": "0.0.1" } }, + "node_modules/builtin-modules": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-3.2.0.tgz", + "integrity": "sha512-lGzLKcioL90C7wMczpkY0n/oART3MbBa8R9OFGE1rJxoVI86u4WAGfEk8Wjv10eKSyTHVGkSo3bvBylCEtk7LA==", + "dev": true, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/call-bind": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", @@ -239,6 +354,12 @@ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true }, + "node_modules/commondir": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", + "integrity": "sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs=", + "dev": true + }, "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", @@ -259,6 +380,17 @@ "node": ">= 8" } }, + "node_modules/css": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/css/-/css-3.0.0.tgz", + "integrity": "sha512-DG9pFfwOrzc+hawpmqX/dHYHJG+Bsdb0klhyi1sDneOgGOXy9wQIC8hzyVp1e4NRYDBdxcylvywPkkXCHAzTyQ==", + "dev": true, + "dependencies": { + "inherits": "^2.0.4", + "source-map": "^0.6.1", + "source-map-resolve": "^0.6.0" + } + }, "node_modules/debug": { "version": "4.3.2", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.2.tgz", @@ -276,12 +408,30 @@ } } }, + "node_modules/decode-uri-component": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.0.tgz", + "integrity": "sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU=", + "dev": true, + "engines": { + "node": ">=0.10" + } + }, "node_modules/deep-is": { "version": "0.1.3", "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz", "integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=", "dev": true }, + "node_modules/deepmerge": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.2.2.tgz", + "integrity": "sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/define-properties": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz", @@ -294,6 +444,24 @@ "node": ">= 0.4" } }, + "node_modules/dequal": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.2.tgz", + "integrity": "sha512-q9K8BlJVxK7hQYqa6XISGmBZbtQQWVXSrRrWreHC94rMt1QL/Impruc+7p2CYSYuVIUr+YCt6hjrs1kkdJRTug==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/diff": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-5.0.0.tgz", + "integrity": "sha512-/VTCrvm5Z0JGty/BWHljh+BAiw3IK+2j87NGMu8Nwc/f48WoDAC395uomO9ZD117ZOBaHmkX1oyLvkVM/aIT3w==", + "dev": true, + "engines": { + "node": ">=0.3.1" + } + }, "node_modules/doctrine": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", @@ -628,6 +796,12 @@ "node": ">=4.0" } }, + "node_modules/estree-walker": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-1.0.1.tgz", + "integrity": "sha512-1fMXF3YP4pZZVozF8j/ZLfvnR8NSIljt56UhbZ5PeeDmmGHpgpdwQt7ITlGvYaQukCvuBRMLEiKiYC+oeIg4cg==", + "dev": true + }, "node_modules/esutils": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", @@ -704,6 +878,20 @@ "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", "dev": true }, + "node_modules/fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "dev": true, + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, "node_modules/function-bind": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", @@ -1002,6 +1190,12 @@ "node": ">=0.10.0" } }, + "node_modules/is-module": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-module/-/is-module-1.0.0.tgz", + "integrity": "sha1-Mlj7afeMFNW4FdZkM2tM/7ZEFZE=", + "dev": true + }, "node_modules/is-negative-zero": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.1.tgz", @@ -1029,6 +1223,15 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-reference": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/is-reference/-/is-reference-1.2.1.tgz", + "integrity": "sha512-U82MsXXiFIrjCK4otLT+o2NA2Cd2g5MLoOVXUZjIOhLurrRxpEXzI8O0KZHr3IjLvlAH1kTPYSuqer5T9ZVBKQ==", + "dev": true, + "dependencies": { + "@types/estree": "*" + } + }, "node_modules/is-regex": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz", @@ -1138,6 +1341,15 @@ "json5": "lib/cli.js" } }, + "node_modules/kleur": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/kleur/-/kleur-4.1.4.tgz", + "integrity": "sha512-8QADVssbrFjivHWQU7KkMgptGTl6WAcSdlbBPY4uNF+mWr6DGcKrvY2w4FQJoXch7+fKMjj0dRrL75vk3k23OA==", + "dev": true, + "engines": { + "node": ">=6" + } + }, "node_modules/levn": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", @@ -1182,6 +1394,15 @@ "node": ">=10" } }, + "node_modules/magic-string": { + "version": "0.25.7", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.25.7.tgz", + "integrity": "sha512-4CrMT5DOHTDk4HYDlzmwu4FVCcIYI8gauveasrdCu2IKIFOJ3f0v/8MDGJCDL9oD2ppz/Av1b0Nj345H9M+XIA==", + "dev": true, + "dependencies": { + "sourcemap-codec": "^1.4.4" + } + }, "node_modules/minimatch": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", @@ -1200,6 +1421,15 @@ "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==", "dev": true }, + "node_modules/mri": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/mri/-/mri-1.2.0.tgz", + "integrity": "sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==", + "dev": true, + "engines": { + "node": ">=4" + } + }, "node_modules/ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", @@ -1369,6 +1599,18 @@ "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", "dev": true }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, "node_modules/pkg-dir": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-2.0.0.tgz", @@ -1457,6 +1699,39 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/rollup": { + "version": "2.67.0", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-2.67.0.tgz", + "integrity": "sha512-W83AaERwvDiHwHEF/dfAfS3z1Be5wf7n+pO3ZAO5IQadCT2lBTr7WQ2MwZZe+nodbD+n3HtC4OCOAdsOPPcKZQ==", + "dev": true, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=10.0.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/rollup-plugin-ignore": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/rollup-plugin-ignore/-/rollup-plugin-ignore-1.0.10.tgz", + "integrity": "sha512-VsbnfwwaTv2Dxl2onubetX/3RnSnplNnjdix0hvF8y2YpqdzlZrjIq6zkcuVJ08XysS8zqW3gt3ORBndFDgsrg==", + "dev": true + }, + "node_modules/sade": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/sade/-/sade-1.8.1.tgz", + "integrity": "sha512-xal3CZX1Xlo/k4ApwCFrHVACi9fBqJ7V+mwhBsuf/1IOKbBy098Fex+Wa/5QMubw09pSZ/u8EY8PWgevJsXp1A==", + "dev": true, + "dependencies": { + "mri": "^1.1.0" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/semver": { "version": "7.3.5", "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", @@ -1507,6 +1782,32 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-resolve": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/source-map-resolve/-/source-map-resolve-0.6.0.tgz", + "integrity": "sha512-KXBr9d/fO/bWo97NXsPIAW1bFSBOuCnjbNTBMO7N59hsv5i9yzRDfcYwwt0l04+VqnKC+EwzvJZIP/qkuMgR/w==", + "deprecated": "See https://github.com/lydell/source-map-resolve#deprecated", + "dev": true, + "dependencies": { + "atob": "^2.1.2", + "decode-uri-component": "^0.2.0" + } + }, + "node_modules/sourcemap-codec": { + "version": "1.4.8", + "resolved": "https://registry.npmjs.org/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz", + "integrity": "sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==", + "dev": true + }, "node_modules/string.prototype.trimend": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.4.tgz", @@ -1644,6 +1945,24 @@ "punycode": "^2.1.0" } }, + "node_modules/uvu": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/uvu/-/uvu-0.5.3.tgz", + "integrity": "sha512-brFwqA3FXzilmtnIyJ+CxdkInkY/i4ErvP7uV0DnUVxQcQ55reuHphorpF+tZoVHK2MniZ/VJzI7zJQoc9T9Yw==", + "dev": true, + "dependencies": { + "dequal": "^2.0.0", + "diff": "^5.0.0", + "kleur": "^4.0.3", + "sade": "^1.7.3" + }, + "bin": { + "uvu": "bin.js" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/v8-compile-cache": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz", @@ -1738,12 +2057,81 @@ "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==", "dev": true }, + "@rollup/plugin-commonjs": { + "version": "21.0.1", + "resolved": "https://registry.npmjs.org/@rollup/plugin-commonjs/-/plugin-commonjs-21.0.1.tgz", + "integrity": "sha512-EA+g22lbNJ8p5kuZJUYyhhDK7WgJckW5g4pNN7n4mAFUM96VuwUnNT3xr2Db2iCZPI1pJPbGyfT5mS9T1dHfMg==", + "dev": true, + "requires": { + "@rollup/pluginutils": "^3.1.0", + "commondir": "^1.0.1", + "estree-walker": "^2.0.1", + "glob": "^7.1.6", + "is-reference": "^1.2.1", + "magic-string": "^0.25.7", + "resolve": "^1.17.0" + }, + "dependencies": { + "estree-walker": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", + "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", + "dev": true + } + } + }, + "@rollup/plugin-node-resolve": { + "version": "13.1.3", + "resolved": "https://registry.npmjs.org/@rollup/plugin-node-resolve/-/plugin-node-resolve-13.1.3.tgz", + "integrity": "sha512-BdxNk+LtmElRo5d06MGY4zoepyrXX1tkzX2hrnPEZ53k78GuOMWLqmJDGIIOPwVRIFZrLQOo+Yr6KtCuLIA0AQ==", + "dev": true, + "requires": { + "@rollup/pluginutils": "^3.1.0", + "@types/resolve": "1.17.1", + "builtin-modules": "^3.1.0", + "deepmerge": "^4.2.2", + "is-module": "^1.0.0", + "resolve": "^1.19.0" + } + }, + "@rollup/pluginutils": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-3.1.0.tgz", + "integrity": "sha512-GksZ6pr6TpIjHm8h9lSQ8pi8BE9VeubNT0OMJ3B5uZJ8pz73NPiqOtCog/x2/QzM1ENChPKxMDhiQuRHsqc+lg==", + "dev": true, + "requires": { + "@types/estree": "0.0.39", + "estree-walker": "^1.0.1", + "picomatch": "^2.2.2" + } + }, + "@types/estree": { + "version": "0.0.39", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.39.tgz", + "integrity": "sha512-EYNwp3bU+98cpU4lAWYYL7Zz+2gryWH1qbdDTidVd6hkiR6weksdbMadyXKXNPEkQFhXM+hVO9ZygomHXp+AIw==", + "dev": true + }, "@types/json5": { "version": "0.0.29", "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", "integrity": "sha1-7ihweulOEdK4J7y+UnC86n8+ce4=", "dev": true }, + "@types/node": { + "version": "17.0.14", + "resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.14.tgz", + "integrity": "sha512-SbjLmERksKOGzWzPNuW7fJM7fk3YXVTFiZWB/Hs99gwhk+/dnrQRPBQjPW9aO+fi1tAffi9PrwFvsmOKmDTyng==", + "dev": true + }, + "@types/resolve": { + "version": "1.17.1", + "resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-1.17.1.tgz", + "integrity": "sha512-yy7HuzQhj0dhGpD8RLXSZWEkLsV9ibvxvi6EiJ3bkqLAO1RGo0WbkWQiwpRlSFymTJRz0d3k5LM3kkx8ArDbLw==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, "acorn": { "version": "8.6.0", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.6.0.tgz", @@ -1820,6 +2208,12 @@ "es-abstract": "^1.19.0" } }, + "atob": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz", + "integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==", + "dev": true + }, "balanced-match": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", @@ -1836,6 +2230,12 @@ "concat-map": "0.0.1" } }, + "builtin-modules": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-3.2.0.tgz", + "integrity": "sha512-lGzLKcioL90C7wMczpkY0n/oART3MbBa8R9OFGE1rJxoVI86u4WAGfEk8Wjv10eKSyTHVGkSo3bvBylCEtk7LA==", + "dev": true + }, "call-bind": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", @@ -1877,6 +2277,12 @@ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true }, + "commondir": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", + "integrity": "sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs=", + "dev": true + }, "concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", @@ -1894,6 +2300,17 @@ "which": "^2.0.1" } }, + "css": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/css/-/css-3.0.0.tgz", + "integrity": "sha512-DG9pFfwOrzc+hawpmqX/dHYHJG+Bsdb0klhyi1sDneOgGOXy9wQIC8hzyVp1e4NRYDBdxcylvywPkkXCHAzTyQ==", + "dev": true, + "requires": { + "inherits": "^2.0.4", + "source-map": "^0.6.1", + "source-map-resolve": "^0.6.0" + } + }, "debug": { "version": "4.3.2", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.2.tgz", @@ -1903,12 +2320,24 @@ "ms": "2.1.2" } }, + "decode-uri-component": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.0.tgz", + "integrity": "sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU=", + "dev": true + }, "deep-is": { "version": "0.1.3", "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz", "integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=", "dev": true }, + "deepmerge": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.2.2.tgz", + "integrity": "sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg==", + "dev": true + }, "define-properties": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz", @@ -1918,6 +2347,18 @@ "object-keys": "^1.0.12" } }, + "dequal": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.2.tgz", + "integrity": "sha512-q9K8BlJVxK7hQYqa6XISGmBZbtQQWVXSrRrWreHC94rMt1QL/Impruc+7p2CYSYuVIUr+YCt6hjrs1kkdJRTug==", + "dev": true + }, + "diff": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-5.0.0.tgz", + "integrity": "sha512-/VTCrvm5Z0JGty/BWHljh+BAiw3IK+2j87NGMu8Nwc/f48WoDAC395uomO9ZD117ZOBaHmkX1oyLvkVM/aIT3w==", + "dev": true + }, "doctrine": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", @@ -2187,6 +2628,12 @@ "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", "dev": true }, + "estree-walker": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-1.0.1.tgz", + "integrity": "sha512-1fMXF3YP4pZZVozF8j/ZLfvnR8NSIljt56UhbZ5PeeDmmGHpgpdwQt7ITlGvYaQukCvuBRMLEiKiYC+oeIg4cg==", + "dev": true + }, "esutils": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", @@ -2251,6 +2698,13 @@ "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", "dev": true }, + "fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "dev": true, + "optional": true + }, "function-bind": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", @@ -2459,6 +2913,12 @@ "is-extglob": "^2.1.1" } }, + "is-module": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-module/-/is-module-1.0.0.tgz", + "integrity": "sha1-Mlj7afeMFNW4FdZkM2tM/7ZEFZE=", + "dev": true + }, "is-negative-zero": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.1.tgz", @@ -2474,6 +2934,15 @@ "has-tostringtag": "^1.0.0" } }, + "is-reference": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/is-reference/-/is-reference-1.2.1.tgz", + "integrity": "sha512-U82MsXXiFIrjCK4otLT+o2NA2Cd2g5MLoOVXUZjIOhLurrRxpEXzI8O0KZHr3IjLvlAH1kTPYSuqer5T9ZVBKQ==", + "dev": true, + "requires": { + "@types/estree": "*" + } + }, "is-regex": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz", @@ -2553,6 +3022,12 @@ "minimist": "^1.2.0" } }, + "kleur": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/kleur/-/kleur-4.1.4.tgz", + "integrity": "sha512-8QADVssbrFjivHWQU7KkMgptGTl6WAcSdlbBPY4uNF+mWr6DGcKrvY2w4FQJoXch7+fKMjj0dRrL75vk3k23OA==", + "dev": true + }, "levn": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", @@ -2588,6 +3063,15 @@ "yallist": "^4.0.0" } }, + "magic-string": { + "version": "0.25.7", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.25.7.tgz", + "integrity": "sha512-4CrMT5DOHTDk4HYDlzmwu4FVCcIYI8gauveasrdCu2IKIFOJ3f0v/8MDGJCDL9oD2ppz/Av1b0Nj345H9M+XIA==", + "dev": true, + "requires": { + "sourcemap-codec": "^1.4.4" + } + }, "minimatch": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", @@ -2603,6 +3087,12 @@ "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==", "dev": true }, + "mri": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/mri/-/mri-1.2.0.tgz", + "integrity": "sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==", + "dev": true + }, "ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", @@ -2730,6 +3220,12 @@ "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", "dev": true }, + "picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true + }, "pkg-dir": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-2.0.0.tgz", @@ -2788,6 +3284,30 @@ "glob": "^7.1.3" } }, + "rollup": { + "version": "2.67.0", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-2.67.0.tgz", + "integrity": "sha512-W83AaERwvDiHwHEF/dfAfS3z1Be5wf7n+pO3ZAO5IQadCT2lBTr7WQ2MwZZe+nodbD+n3HtC4OCOAdsOPPcKZQ==", + "dev": true, + "requires": { + "fsevents": "~2.3.2" + } + }, + "rollup-plugin-ignore": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/rollup-plugin-ignore/-/rollup-plugin-ignore-1.0.10.tgz", + "integrity": "sha512-VsbnfwwaTv2Dxl2onubetX/3RnSnplNnjdix0hvF8y2YpqdzlZrjIq6zkcuVJ08XysS8zqW3gt3ORBndFDgsrg==", + "dev": true + }, + "sade": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/sade/-/sade-1.8.1.tgz", + "integrity": "sha512-xal3CZX1Xlo/k4ApwCFrHVACi9fBqJ7V+mwhBsuf/1IOKbBy098Fex+Wa/5QMubw09pSZ/u8EY8PWgevJsXp1A==", + "dev": true, + "requires": { + "mri": "^1.1.0" + } + }, "semver": { "version": "7.3.5", "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", @@ -2823,6 +3343,28 @@ "object-inspect": "^1.9.0" } }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + }, + "source-map-resolve": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/source-map-resolve/-/source-map-resolve-0.6.0.tgz", + "integrity": "sha512-KXBr9d/fO/bWo97NXsPIAW1bFSBOuCnjbNTBMO7N59hsv5i9yzRDfcYwwt0l04+VqnKC+EwzvJZIP/qkuMgR/w==", + "dev": true, + "requires": { + "atob": "^2.1.2", + "decode-uri-component": "^0.2.0" + } + }, + "sourcemap-codec": { + "version": "1.4.8", + "resolved": "https://registry.npmjs.org/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz", + "integrity": "sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==", + "dev": true + }, "string.prototype.trimend": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.4.tgz", @@ -2927,6 +3469,18 @@ "punycode": "^2.1.0" } }, + "uvu": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/uvu/-/uvu-0.5.3.tgz", + "integrity": "sha512-brFwqA3FXzilmtnIyJ+CxdkInkY/i4ErvP7uV0DnUVxQcQ55reuHphorpF+tZoVHK2MniZ/VJzI7zJQoc9T9Yw==", + "dev": true, + "requires": { + "dequal": "^2.0.0", + "diff": "^5.0.0", + "kleur": "^4.0.3", + "sade": "^1.7.3" + } + }, "v8-compile-cache": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz", diff --git a/package.json b/package.json index f5f0b5bb8..88155e8b6 100644 --- a/package.json +++ b/package.json @@ -1,8 +1,14 @@ { "private": true, "devDependencies": { + "@rollup/plugin-commonjs": "^21.0.1", + "@rollup/plugin-node-resolve": "^13.1.3", + "css": "^3.0.0", "eslint": "^8.4.1", - "eslint-plugin-import": "^2.25.3" + "eslint-plugin-import": "^2.25.3", + "rollup": "^2.67.0", + "rollup-plugin-ignore": "^1.0.10", + "uvu": "^0.5.3" }, "type": "module" } diff --git a/rollup.config.js b/rollup.config.js new file mode 100644 index 000000000..51a5859ef --- /dev/null +++ b/rollup.config.js @@ -0,0 +1,29 @@ +import { nodeResolve } from "@rollup/plugin-node-resolve"; +import commonjs from '@rollup/plugin-commonjs'; +import ignore from "rollup-plugin-ignore" + +export default [ + { + input: "node_modules/uvu/dist/index.mjs", + output: { + file: "test/uvu.js", + }, + plugins: [nodeResolve()], + }, + + { + input: "node_modules/uvu/assert/index.mjs", + output: { + file: "test/assert.js", + }, + plugins: [nodeResolve()], + }, + + { + input: "node_modules/css/index.js", + output: { + file: "src/css.js", + }, + plugins: [ignore(["url", "path", "fs", 'source-map', "source-map-resolve"]), commonjs(), nodeResolve()] + } +]; diff --git a/src/css.js b/src/css.js new file mode 100644 index 000000000..9368c10fa --- /dev/null +++ b/src/css.js @@ -0,0 +1,1362 @@ +function getAugmentedNamespace(n) { + if (n.__esModule) return n; + var a = Object.defineProperty({}, '__esModule', {value: true}); + Object.keys(n).forEach(function (k) { + var d = Object.getOwnPropertyDescriptor(n, k); + Object.defineProperty(a, k, d.get ? d : { + enumerable: true, + get: function () { + return n[k]; + } + }); + }); + return a; +} + +var css = {}; + +// http://www.w3.org/TR/CSS21/grammar.html +// https://github.com/visionmedia/css-parse/pull/49#issuecomment-30088027 +var commentre = /\/\*[^*]*\*+([^/*][^*]*\*+)*\//g; + +var parse$1 = function(css, options){ + options = options || {}; + + /** + * Positional. + */ + + var lineno = 1; + var column = 1; + + /** + * Update lineno and column based on `str`. + */ + + function updatePosition(str) { + var lines = str.match(/\n/g); + if (lines) lineno += lines.length; + var i = str.lastIndexOf('\n'); + column = ~i ? str.length - i : column + str.length; + } + + /** + * Mark position and patch `node.position`. + */ + + function position() { + var start = { line: lineno, column: 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: column }; + this.source = options.source; + } + + /** + * Non-enumerable source string + */ + + Position.prototype.content = css; + + /** + * Error `msg`. + */ + + var errorsList = []; + + function error(msg) { + var 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() { + var rulesList = rules(); + + return { + type: 'stylesheet', + stylesheet: { + source: options.source, + rules: rulesList, + parsingErrors: errorsList + } + }; + } + + /** + * Opening brace. + */ + + function open() { + return match(/^{\s*/); + } + + /** + * Closing brace. + */ + + function close() { + return match(/^}/); + } + + /** + * Parse ruleset. + */ + + function rules() { + var node; + var 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) { + var m = re.exec(css); + if (!m) return; + var str = m[0]; + updatePosition(str); + css = css.slice(str.length); + return m; + } + + /** + * Parse whitespace. + */ + + function whitespace() { + match(/^\s*/); + } + + /** + * Parse comments; + */ + + function comments(rules) { + var c; + rules = rules || []; + while (c = comment()) { + if (c !== false) { + rules.push(c); + } + } + return rules; + } + + /** + * Parse comment. + */ + + function comment() { + var pos = position(); + if ('/' != css.charAt(0) || '*' != css.charAt(1)) return; + + var 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'); + } + + var 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() { + var 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() { + var pos = position(); + + // prop + var prop = match(/^(\*?[-#\/\*\\\w]+(\[[0-9a-z_-]+\])?)\s*/); + if (!prop) return; + prop = trim(prop[0]); + + // : + if (!match(/^:\s*/)) return error("property missing ':'"); + + // val + var val = match(/^((?:'(?:\\'|.)*?'|"(?:\\"|.)*?"|\([^\)]*?\)|[^};])+)/); + + var ret = pos({ + type: 'declaration', + property: prop.replace(commentre, ''), + value: val ? trim(val[0]).replace(commentre, '') : '' + }); + + // ; + match(/^[;\s]*/); + + return ret; + } + + /** + * Parse declarations. + */ + + function declarations() { + var decls = []; + + if (!open()) return error("missing '{'"); + comments(decls); + + // declarations + var decl; + while (decl = declaration()) { + if (decl !== false) { + decls.push(decl); + comments(decls); + } + } + + if (!close()) return error("missing '}'"); + return decls; + } + + /** + * Parse keyframe. + */ + + function keyframe() { + var m; + var vals = []; + var 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() { + var pos = position(); + var m = match(/^@([-\w]+)?keyframes\s*/); + + if (!m) return; + var vendor = m[1]; + + // identifier + var m = match(/^([-\w]+)\s*/); + if (!m) return error("@keyframes missing name"); + var name = m[1]; + + if (!open()) return error("@keyframes missing '{'"); + + var frame; + var frames = comments(); + while (frame = keyframe()) { + frames.push(frame); + frames = frames.concat(comments()); + } + + if (!close()) return error("@keyframes missing '}'"); + + return pos({ + type: 'keyframes', + name: name, + vendor: vendor, + keyframes: frames + }); + } + + /** + * Parse supports. + */ + + function atsupports() { + var pos = position(); + var m = match(/^@supports *([^{]+)/); + + if (!m) return; + var supports = trim(m[1]); + + if (!open()) return error("@supports missing '{'"); + + var style = comments().concat(rules()); + + if (!close()) return error("@supports missing '}'"); + + return pos({ + type: 'supports', + supports: supports, + rules: style + }); + } + + /** + * Parse host. + */ + + function athost() { + var pos = position(); + var m = match(/^@host\s*/); + + if (!m) return; + + if (!open()) return error("@host missing '{'"); + + var style = comments().concat(rules()); + + if (!close()) return error("@host missing '}'"); + + return pos({ + type: 'host', + rules: style + }); + } + + /** + * Parse media. + */ + + function atmedia() { + var pos = position(); + var m = match(/^@media *([^{]+)/); + + if (!m) return; + var media = trim(m[1]); + + if (!open()) return error("@media missing '{'"); + + var style = comments().concat(rules()); + + if (!close()) return error("@media missing '}'"); + + return pos({ + type: 'media', + media: media, + rules: style + }); + } + + + /** + * Parse custom-media. + */ + + function atcustommedia() { + var pos = position(); + var 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() { + var pos = position(); + var m = match(/^@page */); + if (!m) return; + + var sel = selector() || []; + + if (!open()) return error("@page missing '{'"); + var decls = comments(); + + // declarations + var 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() { + var pos = position(); + var m = match(/^@([-\w]+)?document *([^{]+)/); + if (!m) return; + + var vendor = trim(m[1]); + var doc = trim(m[2]); + + if (!open()) return error("@document missing '{'"); + + var style = comments().concat(rules()); + + if (!close()) return error("@document missing '}'"); + + return pos({ + type: 'document', + document: doc, + vendor: vendor, + rules: style + }); + } + + /** + * Parse font-face. + */ + + function atfontface() { + var pos = position(); + var m = match(/^@font-face\s*/); + if (!m) return; + + if (!open()) return error("@font-face missing '{'"); + var decls = comments(); + + // declarations + var 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 + */ + + var atimport = _compileAtrule('import'); + + /** + * Parse charset + */ + + var atcharset = _compileAtrule('charset'); + + /** + * Parse namespace + */ + + var atnamespace = _compileAtrule('namespace'); + + /** + * Parse non-block at-rules + */ + + + function _compileAtrule(name) { + var re = new RegExp('^@' + name + '\\s*([^;]+);'); + return function() { + var pos = position(); + var m = match(re); + if (!m) return; + var 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() { + var pos = position(); + var 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) { + var isNode = obj && typeof obj.type === 'string'; + var childParent = isNode ? obj : parent; + + for (var k in obj) { + var 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; +} + +/** + * Expose `Compiler`. + */ + +var compiler = Compiler$2; + +/** + * Initialize a compiler. + * + * @param {Type} name + * @return {Type} + * @api public + */ + +function Compiler$2(opts) { + this.options = opts || {}; +} + +/** + * Emit `str` + */ + +Compiler$2.prototype.emit = function(str) { + return str; +}; + +/** + * Visit `node`. + */ + +Compiler$2.prototype.visit = function(node){ + return this[node.type](node); +}; + +/** + * Map visit over array of `nodes`, optionally using a `delim` + */ + +Compiler$2.prototype.mapVisit = function(nodes, delim){ + var buf = ''; + delim = delim || ''; + + for (var i = 0, length = nodes.length; i < length; i++) { + buf += this.visit(nodes[i]); + if (delim && i < length - 1) buf += this.emit(delim); + } + + return buf; +}; + +var inherits$2 = {exports: {}}; + +var inherits_browser = {exports: {}}; + +if (typeof Object.create === 'function') { + // implementation from standard node.js 'util' module + inherits_browser.exports = function inherits(ctor, superCtor) { + if (superCtor) { + ctor.super_ = superCtor; + ctor.prototype = Object.create(superCtor.prototype, { + constructor: { + value: ctor, + enumerable: false, + writable: true, + configurable: true + } + }); + } + }; +} else { + // old school shim for old browsers + inherits_browser.exports = function inherits(ctor, superCtor) { + if (superCtor) { + ctor.super_ = superCtor; + var TempCtor = function () {}; + TempCtor.prototype = superCtor.prototype; + ctor.prototype = new TempCtor(); + ctor.prototype.constructor = ctor; + } + }; +} + +try { + var util = require('util'); + /* istanbul ignore next */ + if (typeof util.inherits !== 'function') throw ''; + inherits$2.exports = util.inherits; +} catch (e) { + /* istanbul ignore next */ + inherits$2.exports = inherits_browser.exports; +} + +/** + * Module dependencies. + */ + +var Base$1 = compiler; +var inherits$1 = inherits$2.exports; + +/** + * Expose compiler. + */ + +var compress = Compiler$1; + +/** + * Initialize a new `Compiler`. + */ + +function Compiler$1(options) { + Base$1.call(this, options); +} + +/** + * Inherit from `Base.prototype`. + */ + +inherits$1(Compiler$1, Base$1); + +/** + * Compile `node`. + */ + +Compiler$1.prototype.compile = function(node){ + return node.stylesheet + .rules.map(this.visit, this) + .join(''); +}; + +/** + * Visit comment node. + */ + +Compiler$1.prototype.comment = function(node){ + return this.emit('', node.position); +}; + +/** + * Visit import node. + */ + +Compiler$1.prototype.import = function(node){ + return this.emit('@import ' + node.import + ';', node.position); +}; + +/** + * Visit media node. + */ + +Compiler$1.prototype.media = function(node){ + return this.emit('@media ' + node.media, node.position) + + this.emit('{') + + this.mapVisit(node.rules) + + this.emit('}'); +}; + +/** + * Visit document node. + */ + +Compiler$1.prototype.document = function(node){ + var doc = '@' + (node.vendor || '') + 'document ' + node.document; + + return this.emit(doc, node.position) + + this.emit('{') + + this.mapVisit(node.rules) + + this.emit('}'); +}; + +/** + * Visit charset node. + */ + +Compiler$1.prototype.charset = function(node){ + return this.emit('@charset ' + node.charset + ';', node.position); +}; + +/** + * Visit namespace node. + */ + +Compiler$1.prototype.namespace = function(node){ + return this.emit('@namespace ' + node.namespace + ';', node.position); +}; + +/** + * Visit supports node. + */ + +Compiler$1.prototype.supports = function(node){ + return this.emit('@supports ' + node.supports, node.position) + + this.emit('{') + + this.mapVisit(node.rules) + + this.emit('}'); +}; + +/** + * Visit keyframes node. + */ + +Compiler$1.prototype.keyframes = function(node){ + return this.emit('@' + + (node.vendor || '') + + 'keyframes ' + + node.name, node.position) + + this.emit('{') + + this.mapVisit(node.keyframes) + + this.emit('}'); +}; + +/** + * Visit keyframe node. + */ + +Compiler$1.prototype.keyframe = function(node){ + var decls = node.declarations; + + return this.emit(node.values.join(','), node.position) + + this.emit('{') + + this.mapVisit(decls) + + this.emit('}'); +}; + +/** + * Visit page node. + */ + +Compiler$1.prototype.page = function(node){ + var sel = node.selectors.length + ? node.selectors.join(', ') + : ''; + + return this.emit('@page ' + sel, node.position) + + this.emit('{') + + this.mapVisit(node.declarations) + + this.emit('}'); +}; + +/** + * Visit font-face node. + */ + +Compiler$1.prototype['font-face'] = function(node){ + return this.emit('@font-face', node.position) + + this.emit('{') + + this.mapVisit(node.declarations) + + this.emit('}'); +}; + +/** + * Visit host node. + */ + +Compiler$1.prototype.host = function(node){ + return this.emit('@host', node.position) + + this.emit('{') + + this.mapVisit(node.rules) + + this.emit('}'); +}; + +/** + * Visit custom-media node. + */ + +Compiler$1.prototype['custom-media'] = function(node){ + return this.emit('@custom-media ' + node.name + ' ' + node.media + ';', node.position); +}; + +/** + * Visit rule node. + */ + +Compiler$1.prototype.rule = function(node){ + var decls = node.declarations; + if (!decls.length) return ''; + + return this.emit(node.selectors.join(','), node.position) + + this.emit('{') + + this.mapVisit(decls) + + this.emit('}'); +}; + +/** + * Visit declaration node. + */ + +Compiler$1.prototype.declaration = function(node){ + return this.emit(node.property + ':' + node.value, node.position) + this.emit(';'); +}; + +/** + * Module dependencies. + */ + +var Base = compiler; +var inherits = inherits$2.exports; + +/** + * Expose compiler. + */ + +var identity = Compiler; + +/** + * Initialize a new `Compiler`. + */ + +function Compiler(options) { + options = options || {}; + Base.call(this, options); + this.indentation = options.indent; +} + +/** + * Inherit from `Base.prototype`. + */ + +inherits(Compiler, Base); + +/** + * Compile `node`. + */ + +Compiler.prototype.compile = function(node){ + return this.stylesheet(node); +}; + +/** + * Visit stylesheet node. + */ + +Compiler.prototype.stylesheet = function(node){ + return this.mapVisit(node.stylesheet.rules, '\n\n'); +}; + +/** + * Visit comment node. + */ + +Compiler.prototype.comment = function(node){ + return this.emit(this.indent() + '/*' + node.comment + '*/', node.position); +}; + +/** + * Visit import node. + */ + +Compiler.prototype.import = function(node){ + return this.emit('@import ' + node.import + ';', node.position); +}; + +/** + * Visit media node. + */ + +Compiler.prototype.media = function(node){ + return this.emit('@media ' + node.media, node.position) + + this.emit( + ' {\n' + + this.indent(1)) + + this.mapVisit(node.rules, '\n\n') + + this.emit( + this.indent(-1) + + '\n}'); +}; + +/** + * Visit document node. + */ + +Compiler.prototype.document = function(node){ + var doc = '@' + (node.vendor || '') + 'document ' + node.document; + + return this.emit(doc, node.position) + + this.emit( + ' ' + + ' {\n' + + this.indent(1)) + + this.mapVisit(node.rules, '\n\n') + + this.emit( + this.indent(-1) + + '\n}'); +}; + +/** + * Visit charset node. + */ + +Compiler.prototype.charset = function(node){ + return this.emit('@charset ' + node.charset + ';', node.position); +}; + +/** + * Visit namespace node. + */ + +Compiler.prototype.namespace = function(node){ + return this.emit('@namespace ' + node.namespace + ';', node.position); +}; + +/** + * Visit supports node. + */ + +Compiler.prototype.supports = function(node){ + return this.emit('@supports ' + node.supports, node.position) + + this.emit( + ' {\n' + + this.indent(1)) + + this.mapVisit(node.rules, '\n\n') + + this.emit( + this.indent(-1) + + '\n}'); +}; + +/** + * Visit keyframes node. + */ + +Compiler.prototype.keyframes = function(node){ + return this.emit('@' + (node.vendor || '') + 'keyframes ' + node.name, node.position) + + this.emit( + ' {\n' + + this.indent(1)) + + this.mapVisit(node.keyframes, '\n') + + this.emit( + this.indent(-1) + + '}'); +}; + +/** + * Visit keyframe node. + */ + +Compiler.prototype.keyframe = function(node){ + var decls = node.declarations; + + return this.emit(this.indent()) + + this.emit(node.values.join(', '), node.position) + + this.emit( + ' {\n' + + this.indent(1)) + + this.mapVisit(decls, '\n') + + this.emit( + this.indent(-1) + + '\n' + + this.indent() + '}\n'); +}; + +/** + * Visit page node. + */ + +Compiler.prototype.page = function(node){ + var sel = node.selectors.length + ? node.selectors.join(', ') + ' ' + : ''; + + return this.emit('@page ' + sel, node.position) + + this.emit('{\n') + + this.emit(this.indent(1)) + + this.mapVisit(node.declarations, '\n') + + this.emit(this.indent(-1)) + + this.emit('\n}'); +}; + +/** + * Visit font-face node. + */ + +Compiler.prototype['font-face'] = function(node){ + return this.emit('@font-face ', node.position) + + this.emit('{\n') + + this.emit(this.indent(1)) + + this.mapVisit(node.declarations, '\n') + + this.emit(this.indent(-1)) + + this.emit('\n}'); +}; + +/** + * Visit host node. + */ + +Compiler.prototype.host = function(node){ + return this.emit('@host', node.position) + + this.emit( + ' {\n' + + this.indent(1)) + + this.mapVisit(node.rules, '\n\n') + + this.emit( + this.indent(-1) + + '\n}'); +}; + +/** + * Visit custom-media node. + */ + +Compiler.prototype['custom-media'] = function(node){ + return this.emit('@custom-media ' + node.name + ' ' + node.media + ';', node.position); +}; + +/** + * Visit rule node. + */ + +Compiler.prototype.rule = function(node){ + var indent = this.indent(); + var decls = node.declarations; + if (!decls.length) return ''; + + return this.emit(node.selectors.map(function(s){ return indent + s }).join(',\n'), node.position) + + this.emit(' {\n') + + this.emit(this.indent(1)) + + this.mapVisit(decls, '\n') + + this.emit(this.indent(-1)) + + this.emit('\n' + this.indent() + '}'); +}; + +/** + * Visit declaration node. + */ + +Compiler.prototype.declaration = function(node){ + return this.emit(this.indent()) + + this.emit(node.property + ': ' + node.value, node.position) + + this.emit(';'); +}; + +/** + * Increase, decrease or return current indentation. + */ + +Compiler.prototype.indent = function(level) { + this.level = this.level || 1; + + if (null != level) { + this.level += level; + return ''; + } + + return Array(this.level).join(this.indentation || ' '); +}; + +var sourceMapSupport = {exports: {}}; + +var _rollup_plugin_ignore_empty_module_placeholder = {}; + +var _rollup_plugin_ignore_empty_module_placeholder$1 = /*#__PURE__*/Object.freeze({ + __proto__: null, + 'default': _rollup_plugin_ignore_empty_module_placeholder +}); + +var require$$3 = /*@__PURE__*/getAugmentedNamespace(_rollup_plugin_ignore_empty_module_placeholder$1); + +(function (module, exports) { +/** + * Module dependencies. + */ + +var SourceMap = require$$3.SourceMapGenerator; +var SourceMapConsumer = require$$3.SourceMapConsumer; +var sourceMapResolve = require$$3; +var fs = require$$3; +var path = require$$3; + +/** + * Expose `mixin()`. + */ + +module.exports = mixin; + +/** + * Ensure Windows-style paths are formatted properly + */ + +const makeFriendlyPath = function(aPath) { + return path.sep === "\\" ? aPath.replace(/\\/g, "/").replace(/^[a-z]:\/?/i, "/") : aPath; +}; + +/** + * Mixin source map support into `compiler`. + * + * @param {Compiler} compiler + * @api public + */ + +function mixin(compiler) { + compiler._comment = compiler.comment; + compiler.map = new SourceMap(); + compiler.position = { line: 1, column: 1 }; + compiler.files = {}; + for (var k in exports) compiler[k] = exports[k]; +} + +/** + * Update position. + * + * @param {String} str + * @api private + */ + +exports.updatePosition = function(str) { + var lines = str.match(/\n/g); + if (lines) this.position.line += lines.length; + var i = str.lastIndexOf('\n'); + this.position.column = ~i ? str.length - i : this.position.column + str.length; +}; + +/** + * Emit `str`. + * + * @param {String} str + * @param {Object} [pos] + * @return {String} + * @api private + */ + +exports.emit = function(str, pos) { + if (pos) { + var sourceFile = makeFriendlyPath(pos.source || 'source.css'); + + this.map.addMapping({ + source: sourceFile, + generated: { + line: this.position.line, + column: Math.max(this.position.column - 1, 0) + }, + original: { + line: pos.start.line, + column: pos.start.column - 1 + } + }); + + this.addFile(sourceFile, pos); + } + + this.updatePosition(str); + + return str; +}; + +/** + * Adds a file to the source map output if it has not already been added + * @param {String} file + * @param {Object} pos + */ + +exports.addFile = function(file, pos) { + if (typeof pos.content !== 'string') return; + if (Object.prototype.hasOwnProperty.call(this.files, file)) return; + + this.files[file] = pos.content; +}; + +/** + * Applies any original source maps to the output and embeds the source file + * contents in the source map. + */ + +exports.applySourceMaps = function() { + Object.keys(this.files).forEach(function(file) { + var content = this.files[file]; + this.map.setSourceContent(file, content); + + if (this.options.inputSourcemaps !== false) { + var originalMap = sourceMapResolve.resolveSync( + content, file, fs.readFileSync); + if (originalMap) { + var map = new SourceMapConsumer(originalMap.map); + var relativeTo = originalMap.sourcesRelativeTo; + this.map.applySourceMap(map, file, makeFriendlyPath(path.dirname(relativeTo))); + } + } + }, this); +}; + +/** + * Process comments, drops sourceMap comments. + * @param {Object} node + */ + +exports.comment = function(node) { + if (/^# sourceMappingURL=/.test(node.comment)) + return this.emit('', node.position); + else + return this._comment(node); +}; +}(sourceMapSupport, sourceMapSupport.exports)); + +/** + * Module dependencies. + */ + +var Compressed = compress; +var Identity = identity; + +/** + * Stringfy the given AST `node`. + * + * Options: + * + * - `compress` space-optimized output + * - `sourcemap` return an object with `.code` and `.map` + * + * @param {Object} node + * @param {Object} [options] + * @return {String} + * @api public + */ + +var stringify$1 = function(node, options){ + options = options || {}; + + var compiler = options.compress + ? new Compressed(options) + : new Identity(options); + + // source maps + if (options.sourcemap) { + var sourcemaps = sourceMapSupport.exports; + sourcemaps(compiler); + + var code = compiler.compile(node); + compiler.applySourceMaps(); + + var map = options.sourcemap === 'generator' + ? compiler.map + : compiler.map.toJSON(); + + return { code: code, map: map }; + } + + var code = compiler.compile(node); + return code; +}; + +var parse = css.parse = parse$1; +var stringify = css.stringify = stringify$1; + +export { css as default, parse, stringify }; diff --git a/src/window.js b/src/window.js index 09a313e45..70fd94648 100644 --- a/src/window.js +++ b/src/window.js @@ -8,6 +8,7 @@ import GLib from 'gi://GLib' import { relativePath, settings } from "./util.js"; import Shortcuts from "./Shortcuts.js"; +import * as css from './css.js' Source.init(); @@ -162,8 +163,15 @@ export default function Window({ application, data }) { Gtk.StyleContext.remove_provider_for_display(output.get_display(), css_provider); css_provider = null; } - const style = source_view_css.buffer.text; + let style = source_view_css.buffer.text; if (!style) return; + + try { + style = scopeStylesheet(style); + } catch (err) { + // logError(err); + } + css_provider = new Gtk.CssProvider(); css_provider.load_from_data(style); // Unfortunally this styles the widget to which the style_context belongs to only @@ -183,12 +191,13 @@ export default function Window({ application, data }) { updatePreview(); function run() { + button_run.set_sensitive(false); + updatePreview(); const code = source_view_javascript.buffer.text; if (!code.trim()) return; - button_run.set_sensitive(false); // We have to create a new file each time // because gjs doesn't appear to use etag for module caching // ?foo=Date.now() also does not work as expected @@ -213,3 +222,15 @@ export default function Window({ application, data }) { return { window }; } + +function scopeStylesheet(style) { + const ast = css.parse(style); + + for (const {selectors} of ast.stylesheet.rules) { + for (let i = 0; i < selectors.length; i++) { + selectors[i] = ".workbench_output " + selectors[i] + } + } + + return css.stringify(ast); +} diff --git a/src/window.ui b/src/window.ui index a269601a9..e64e67770 100644 --- a/src/window.ui +++ b/src/window.ui @@ -145,7 +145,12 @@ - + + + > + diff --git a/test/assert.js b/test/assert.js new file mode 100644 index 000000000..27907aa23 --- /dev/null +++ b/test/assert.js @@ -0,0 +1,1034 @@ +var has = Object.prototype.hasOwnProperty; + +function find(iter, tar, key) { + for (key of iter.keys()) { + if (dequal(key, tar)) return key; + } +} + +function dequal(foo, bar) { + var ctor, len, tmp; + if (foo === bar) return true; + + if (foo && bar && (ctor=foo.constructor) === bar.constructor) { + if (ctor === Date) return foo.getTime() === bar.getTime(); + if (ctor === RegExp) return foo.toString() === bar.toString(); + + if (ctor === Array) { + if ((len=foo.length) === bar.length) { + while (len-- && dequal(foo[len], bar[len])); + } + return len === -1; + } + + if (ctor === Set) { + if (foo.size !== bar.size) { + return false; + } + for (len of foo) { + tmp = len; + if (tmp && typeof tmp === 'object') { + tmp = find(bar, tmp); + if (!tmp) return false; + } + if (!bar.has(tmp)) return false; + } + return true; + } + + if (ctor === Map) { + if (foo.size !== bar.size) { + return false; + } + for (len of foo) { + tmp = len[0]; + if (tmp && typeof tmp === 'object') { + tmp = find(bar, tmp); + if (!tmp) return false; + } + if (!dequal(len[1], bar.get(tmp))) { + return false; + } + } + return true; + } + + if (ctor === ArrayBuffer) { + foo = new Uint8Array(foo); + bar = new Uint8Array(bar); + } else if (ctor === DataView) { + if ((len=foo.byteLength) === bar.byteLength) { + while (len-- && foo.getInt8(len) === bar.getInt8(len)); + } + return len === -1; + } + + if (ArrayBuffer.isView(foo)) { + if ((len=foo.byteLength) === bar.byteLength) { + while (len-- && foo[len] === bar[len]); + } + return len === -1; + } + + if (!ctor || typeof foo === 'object') { + len = 0; + for (ctor in foo) { + if (has.call(foo, ctor) && ++len && !has.call(bar, ctor)) return false; + if (!(ctor in bar) || !dequal(foo[ctor], bar[ctor])) return false; + } + return Object.keys(bar).length === len; + } + } + + return foo !== foo && bar !== bar; +} + +let FORCE_COLOR, NODE_DISABLE_COLORS, NO_COLOR, TERM, isTTY=true; +if (typeof process !== 'undefined') { + ({ FORCE_COLOR, NODE_DISABLE_COLORS, NO_COLOR, TERM } = process.env); + isTTY = process.stdout && process.stdout.isTTY; +} + +const $ = { + enabled: !NODE_DISABLE_COLORS && NO_COLOR == null && TERM !== 'dumb' && ( + FORCE_COLOR != null && FORCE_COLOR !== '0' || isTTY + ), + + // modifiers + reset: init(0, 0), + bold: init(1, 22), + dim: init(2, 22), + italic: init(3, 23), + underline: init(4, 24), + inverse: init(7, 27), + hidden: init(8, 28), + strikethrough: init(9, 29), + + // colors + black: init(30, 39), + red: init(31, 39), + green: init(32, 39), + yellow: init(33, 39), + blue: init(34, 39), + magenta: init(35, 39), + cyan: init(36, 39), + white: init(37, 39), + gray: init(90, 39), + grey: init(90, 39), + + // background colors + bgBlack: init(40, 49), + bgRed: init(41, 49), + bgGreen: init(42, 49), + bgYellow: init(43, 49), + bgBlue: init(44, 49), + bgMagenta: init(45, 49), + bgCyan: init(46, 49), + bgWhite: init(47, 49) +}; + +function run(arr, str) { + let i=0, tmp, beg='', end=''; + for (; i < arr.length; i++) { + tmp = arr[i]; + beg += tmp.open; + end += tmp.close; + if (!!~str.indexOf(tmp.close)) { + str = str.replace(tmp.rgx, tmp.close + tmp.open); + } + } + return beg + str + end; +} + +function chain(has, keys) { + let ctx = { has, keys }; + + ctx.reset = $.reset.bind(ctx); + ctx.bold = $.bold.bind(ctx); + ctx.dim = $.dim.bind(ctx); + ctx.italic = $.italic.bind(ctx); + ctx.underline = $.underline.bind(ctx); + ctx.inverse = $.inverse.bind(ctx); + ctx.hidden = $.hidden.bind(ctx); + ctx.strikethrough = $.strikethrough.bind(ctx); + + ctx.black = $.black.bind(ctx); + ctx.red = $.red.bind(ctx); + ctx.green = $.green.bind(ctx); + ctx.yellow = $.yellow.bind(ctx); + ctx.blue = $.blue.bind(ctx); + ctx.magenta = $.magenta.bind(ctx); + ctx.cyan = $.cyan.bind(ctx); + ctx.white = $.white.bind(ctx); + ctx.gray = $.gray.bind(ctx); + ctx.grey = $.grey.bind(ctx); + + ctx.bgBlack = $.bgBlack.bind(ctx); + ctx.bgRed = $.bgRed.bind(ctx); + ctx.bgGreen = $.bgGreen.bind(ctx); + ctx.bgYellow = $.bgYellow.bind(ctx); + ctx.bgBlue = $.bgBlue.bind(ctx); + ctx.bgMagenta = $.bgMagenta.bind(ctx); + ctx.bgCyan = $.bgCyan.bind(ctx); + ctx.bgWhite = $.bgWhite.bind(ctx); + + return ctx; +} + +function init(open, close) { + let blk = { + open: `\x1b[${open}m`, + close: `\x1b[${close}m`, + rgx: new RegExp(`\\x1b\\[${close}m`, 'g') + }; + return function (txt) { + if (this !== void 0 && this.has !== void 0) { + !!~this.has.indexOf(open) || (this.has.push(open),this.keys.push(blk)); + return txt === void 0 ? this : $.enabled ? run(this.keys, txt+'') : txt+''; + } + return txt === void 0 ? chain([open], [blk]) : $.enabled ? run([blk], txt+'') : txt+''; + }; +} + +function Diff() {} +Diff.prototype = { + diff: function diff(oldString, newString) { + var options = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {}; + var callback = options.callback; + + if (typeof options === 'function') { + callback = options; + options = {}; + } + + this.options = options; + var self = this; + + function done(value) { + if (callback) { + setTimeout(function () { + callback(undefined, value); + }, 0); + return true; + } else { + return value; + } + } // Allow subclasses to massage the input prior to running + + + oldString = this.castInput(oldString); + newString = this.castInput(newString); + oldString = this.removeEmpty(this.tokenize(oldString)); + newString = this.removeEmpty(this.tokenize(newString)); + var newLen = newString.length, + oldLen = oldString.length; + var editLength = 1; + var maxEditLength = newLen + oldLen; + var bestPath = [{ + newPos: -1, + components: [] + }]; // Seed editLength = 0, i.e. the content starts with the same values + + var oldPos = this.extractCommon(bestPath[0], newString, oldString, 0); + + if (bestPath[0].newPos + 1 >= newLen && oldPos + 1 >= oldLen) { + // Identity per the equality and tokenizer + return done([{ + value: this.join(newString), + count: newString.length + }]); + } // Main worker method. checks all permutations of a given edit length for acceptance. + + + function execEditLength() { + for (var diagonalPath = -1 * editLength; diagonalPath <= editLength; diagonalPath += 2) { + var basePath = void 0; + + var addPath = bestPath[diagonalPath - 1], + removePath = bestPath[diagonalPath + 1], + _oldPos = (removePath ? removePath.newPos : 0) - diagonalPath; + + if (addPath) { + // No one else is going to attempt to use this value, clear it + bestPath[diagonalPath - 1] = undefined; + } + + var canAdd = addPath && addPath.newPos + 1 < newLen, + canRemove = removePath && 0 <= _oldPos && _oldPos < oldLen; + + if (!canAdd && !canRemove) { + // If this path is a terminal then prune + bestPath[diagonalPath] = undefined; + continue; + } // Select the diagonal that we want to branch from. We select the prior + // path whose position in the new string is the farthest from the origin + // and does not pass the bounds of the diff graph + + + if (!canAdd || canRemove && addPath.newPos < removePath.newPos) { + basePath = clonePath(removePath); + self.pushComponent(basePath.components, undefined, true); + } else { + basePath = addPath; // No need to clone, we've pulled it from the list + + basePath.newPos++; + self.pushComponent(basePath.components, true, undefined); + } + + _oldPos = self.extractCommon(basePath, newString, oldString, diagonalPath); // If we have hit the end of both strings, then we are done + + if (basePath.newPos + 1 >= newLen && _oldPos + 1 >= oldLen) { + return done(buildValues(self, basePath.components, newString, oldString, self.useLongestToken)); + } else { + // Otherwise track this path as a potential candidate and continue. + bestPath[diagonalPath] = basePath; + } + } + + editLength++; + } // Performs the length of edit iteration. Is a bit fugly as this has to support the + // sync and async mode which is never fun. Loops over execEditLength until a value + // is produced. + + + if (callback) { + (function exec() { + setTimeout(function () { + // This should not happen, but we want to be safe. + + /* istanbul ignore next */ + if (editLength > maxEditLength) { + return callback(); + } + + if (!execEditLength()) { + exec(); + } + }, 0); + })(); + } else { + while (editLength <= maxEditLength) { + var ret = execEditLength(); + + if (ret) { + return ret; + } + } + } + }, + pushComponent: function pushComponent(components, added, removed) { + var last = components[components.length - 1]; + + if (last && last.added === added && last.removed === removed) { + // We need to clone here as the component clone operation is just + // as shallow array clone + components[components.length - 1] = { + count: last.count + 1, + added: added, + removed: removed + }; + } else { + components.push({ + count: 1, + added: added, + removed: removed + }); + } + }, + extractCommon: function extractCommon(basePath, newString, oldString, diagonalPath) { + var newLen = newString.length, + oldLen = oldString.length, + newPos = basePath.newPos, + oldPos = newPos - diagonalPath, + commonCount = 0; + + while (newPos + 1 < newLen && oldPos + 1 < oldLen && this.equals(newString[newPos + 1], oldString[oldPos + 1])) { + newPos++; + oldPos++; + commonCount++; + } + + if (commonCount) { + basePath.components.push({ + count: commonCount + }); + } + + basePath.newPos = newPos; + return oldPos; + }, + equals: function equals(left, right) { + if (this.options.comparator) { + return this.options.comparator(left, right); + } else { + return left === right || this.options.ignoreCase && left.toLowerCase() === right.toLowerCase(); + } + }, + removeEmpty: function removeEmpty(array) { + var ret = []; + + for (var i = 0; i < array.length; i++) { + if (array[i]) { + ret.push(array[i]); + } + } + + return ret; + }, + castInput: function castInput(value) { + return value; + }, + tokenize: function tokenize(value) { + return value.split(''); + }, + join: function join(chars) { + return chars.join(''); + } +}; + +function buildValues(diff, components, newString, oldString, useLongestToken) { + var componentPos = 0, + componentLen = components.length, + newPos = 0, + oldPos = 0; + + for (; componentPos < componentLen; componentPos++) { + var component = components[componentPos]; + + if (!component.removed) { + if (!component.added && useLongestToken) { + var value = newString.slice(newPos, newPos + component.count); + value = value.map(function (value, i) { + var oldValue = oldString[oldPos + i]; + return oldValue.length > value.length ? oldValue : value; + }); + component.value = diff.join(value); + } else { + component.value = diff.join(newString.slice(newPos, newPos + component.count)); + } + + newPos += component.count; // Common case + + if (!component.added) { + oldPos += component.count; + } + } else { + component.value = diff.join(oldString.slice(oldPos, oldPos + component.count)); + oldPos += component.count; // Reverse add and remove so removes are output first to match common convention + // The diffing algorithm is tied to add then remove output and this is the simplest + // route to get the desired output with minimal overhead. + + if (componentPos && components[componentPos - 1].added) { + var tmp = components[componentPos - 1]; + components[componentPos - 1] = components[componentPos]; + components[componentPos] = tmp; + } + } + } // Special case handle for when one terminal is ignored (i.e. whitespace). + // For this case we merge the terminal into the prior string and drop the change. + // This is only available for string mode. + + + var lastComponent = components[componentLen - 1]; + + if (componentLen > 1 && typeof lastComponent.value === 'string' && (lastComponent.added || lastComponent.removed) && diff.equals('', lastComponent.value)) { + components[componentLen - 2].value += lastComponent.value; + components.pop(); + } + + return components; +} + +function clonePath(path) { + return { + newPos: path.newPos, + components: path.components.slice(0) + }; +} + +var characterDiff = new Diff(); +function diffChars(oldStr, newStr, options) { + return characterDiff.diff(oldStr, newStr, options); +} + +// +// Ranges and exceptions: +// Latin-1 Supplement, 0080–00FF +// - U+00D7 × Multiplication sign +// - U+00F7 ÷ Division sign +// Latin Extended-A, 0100–017F +// Latin Extended-B, 0180–024F +// IPA Extensions, 0250–02AF +// Spacing Modifier Letters, 02B0–02FF +// - U+02C7 ˇ ˇ Caron +// - U+02D8 ˘ ˘ Breve +// - U+02D9 ˙ ˙ Dot Above +// - U+02DA ˚ ˚ Ring Above +// - U+02DB ˛ ˛ Ogonek +// - U+02DC ˜ ˜ Small Tilde +// - U+02DD ˝ ˝ Double Acute Accent +// Latin Extended Additional, 1E00–1EFF + +var extendedWordChars = /^[A-Za-z\xC0-\u02C6\u02C8-\u02D7\u02DE-\u02FF\u1E00-\u1EFF]+$/; +var reWhitespace = /\S/; +var wordDiff = new Diff(); + +wordDiff.equals = function (left, right) { + if (this.options.ignoreCase) { + left = left.toLowerCase(); + right = right.toLowerCase(); + } + + return left === right || this.options.ignoreWhitespace && !reWhitespace.test(left) && !reWhitespace.test(right); +}; + +wordDiff.tokenize = function (value) { + // All whitespace symbols except newline group into one token, each newline - in separate token + var tokens = value.split(/([^\S\r\n]+|[()[\]{}'"\r\n]|\b)/); // Join the boundary splits that we do not consider to be boundaries. This is primarily the extended Latin character set. + + for (var i = 0; i < tokens.length - 1; i++) { + // If we have an empty string in the next field and we have only word chars before and after, merge + if (!tokens[i + 1] && tokens[i + 2] && extendedWordChars.test(tokens[i]) && extendedWordChars.test(tokens[i + 2])) { + tokens[i] += tokens[i + 2]; + tokens.splice(i + 1, 2); + i--; + } + } + + return tokens; +}; + +var lineDiff = new Diff(); + +lineDiff.tokenize = function (value) { + var retLines = [], + linesAndNewlines = value.split(/(\n|\r\n)/); // Ignore the final empty token that occurs if the string ends with a new line + + if (!linesAndNewlines[linesAndNewlines.length - 1]) { + linesAndNewlines.pop(); + } // Merge the content and line separators into single tokens + + + for (var i = 0; i < linesAndNewlines.length; i++) { + var line = linesAndNewlines[i]; + + if (i % 2 && !this.options.newlineIsToken) { + retLines[retLines.length - 1] += line; + } else { + if (this.options.ignoreWhitespace) { + line = line.trim(); + } + + retLines.push(line); + } + } + + return retLines; +}; + +function diffLines(oldStr, newStr, callback) { + return lineDiff.diff(oldStr, newStr, callback); +} + +var sentenceDiff = new Diff(); + +sentenceDiff.tokenize = function (value) { + return value.split(/(\S.+?[.!?])(?=\s+|$)/); +}; + +var cssDiff = new Diff(); + +cssDiff.tokenize = function (value) { + return value.split(/([{}:;,]|\s+)/); +}; + +function _typeof(obj) { + "@babel/helpers - typeof"; + + if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") { + _typeof = function (obj) { + return typeof obj; + }; + } else { + _typeof = function (obj) { + return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; + }; + } + + return _typeof(obj); +} + +var objectPrototypeToString = Object.prototype.toString; +var jsonDiff = new Diff(); // Discriminate between two lines of pretty-printed, serialized JSON where one of them has a +// dangling comma and the other doesn't. Turns out including the dangling comma yields the nicest output: + +jsonDiff.useLongestToken = true; +jsonDiff.tokenize = lineDiff.tokenize; + +jsonDiff.castInput = function (value) { + var _this$options = this.options, + undefinedReplacement = _this$options.undefinedReplacement, + _this$options$stringi = _this$options.stringifyReplacer, + stringifyReplacer = _this$options$stringi === void 0 ? function (k, v) { + return typeof v === 'undefined' ? undefinedReplacement : v; + } : _this$options$stringi; + return typeof value === 'string' ? value : JSON.stringify(canonicalize(value, null, null, stringifyReplacer), stringifyReplacer, ' '); +}; + +jsonDiff.equals = function (left, right) { + return Diff.prototype.equals.call(jsonDiff, left.replace(/,([\r\n])/g, '$1'), right.replace(/,([\r\n])/g, '$1')); +}; +// object that is already on the "stack" of items being processed. Accepts an optional replacer + +function canonicalize(obj, stack, replacementStack, replacer, key) { + stack = stack || []; + replacementStack = replacementStack || []; + + if (replacer) { + obj = replacer(key, obj); + } + + var i; + + for (i = 0; i < stack.length; i += 1) { + if (stack[i] === obj) { + return replacementStack[i]; + } + } + + var canonicalizedObj; + + if ('[object Array]' === objectPrototypeToString.call(obj)) { + stack.push(obj); + canonicalizedObj = new Array(obj.length); + replacementStack.push(canonicalizedObj); + + for (i = 0; i < obj.length; i += 1) { + canonicalizedObj[i] = canonicalize(obj[i], stack, replacementStack, replacer, key); + } + + stack.pop(); + replacementStack.pop(); + return canonicalizedObj; + } + + if (obj && obj.toJSON) { + obj = obj.toJSON(); + } + + if (_typeof(obj) === 'object' && obj !== null) { + stack.push(obj); + canonicalizedObj = {}; + replacementStack.push(canonicalizedObj); + + var sortedKeys = [], + _key; + + for (_key in obj) { + /* istanbul ignore else */ + if (obj.hasOwnProperty(_key)) { + sortedKeys.push(_key); + } + } + + sortedKeys.sort(); + + for (i = 0; i < sortedKeys.length; i += 1) { + _key = sortedKeys[i]; + canonicalizedObj[_key] = canonicalize(obj[_key], stack, replacementStack, replacer, _key); + } + + stack.pop(); + replacementStack.pop(); + } else { + canonicalizedObj = obj; + } + + return canonicalizedObj; +} + +var arrayDiff = new Diff(); + +arrayDiff.tokenize = function (value) { + return value.slice(); +}; + +arrayDiff.join = arrayDiff.removeEmpty = function (value) { + return value; +}; + +function diffArrays(oldArr, newArr, callback) { + return arrayDiff.diff(oldArr, newArr, callback); +} + +const colors = { + '--': $.red, + '··': $.grey, + '++': $.green, +}; + +const TITLE = $.dim().italic; +const TAB=$.dim('→'), SPACE=$.dim('·'), NL=$.dim('↵'); +const LOG = (sym, str) => colors[sym](sym + PRETTY(str)) + '\n'; +const LINE = (num, x) => $.dim('L' + String(num).padStart(x, '0') + ' '); +const PRETTY = str => str.replace(/[ ]/g, SPACE).replace(/\t/g, TAB).replace(/(\r?\n)/g, NL); + +function line(obj, prev, pad) { + let char = obj.removed ? '--' : obj.added ? '++' : '··'; + let arr = obj.value.replace(/\r?\n$/, '').split('\n'); + let i=0, tmp, out=''; + + if (obj.added) out += colors[char]().underline(TITLE('Expected:')) + '\n'; + else if (obj.removed) out += colors[char]().underline(TITLE('Actual:')) + '\n'; + + for (; i < arr.length; i++) { + tmp = arr[i]; + if (tmp != null) { + if (prev) out += LINE(prev + i, pad); + out += LOG(char, tmp || '\n'); + } + } + + return out; +} + +// TODO: want better diffing +//~> complex items bail outright +function arrays(input, expect) { + let arr = diffArrays(input, expect); + let i=0, j=0, k=0, tmp, val, char, isObj, str; + let out = LOG('··', '['); + + for (; i < arr.length; i++) { + char = (tmp = arr[i]).removed ? '--' : tmp.added ? '++' : '··'; + + if (tmp.added) { + out += colors[char]().underline(TITLE('Expected:')) + '\n'; + } else if (tmp.removed) { + out += colors[char]().underline(TITLE('Actual:')) + '\n'; + } + + for (j=0; j < tmp.value.length; j++) { + isObj = (tmp.value[j] && typeof tmp.value[j] === 'object'); + val = stringify(tmp.value[j]).split(/\r?\n/g); + for (k=0; k < val.length;) { + str = ' ' + val[k++] + (isObj ? '' : ','); + if (isObj && k === val.length && (j + 1) < tmp.value.length) str += ','; + out += LOG(char, str); + } + } + } + + return out + LOG('··', ']'); +} + +function lines(input, expect, linenum = 0) { + let i=0, tmp, output=''; + let arr = diffLines(input, expect); + let pad = String(expect.split(/\r?\n/g).length - linenum).length; + + for (; i < arr.length; i++) { + output += line(tmp = arr[i], linenum, pad); + if (linenum && !tmp.removed) linenum += tmp.count; + } + + return output; +} + +function chars(input, expect) { + let arr = diffChars(input, expect); + let i=0, output='', tmp; + + let l1 = input.length; + let l2 = expect.length; + + let p1 = PRETTY(input); + let p2 = PRETTY(expect); + + tmp = arr[i]; + + if (l1 === l2) ; else if (tmp.removed && arr[i + 1]) { + let del = tmp.count - arr[i + 1].count; + if (del == 0) ; else if (del > 0) { + expect = ' '.repeat(del) + expect; + p2 = ' '.repeat(del) + p2; + l2 += del; + } else if (del < 0) { + input = ' '.repeat(-del) + input; + p1 = ' '.repeat(-del) + p1; + l1 += -del; + } + } + + output += direct(p1, p2, l1, l2); + + if (l1 === l2) { + for (tmp=' '; i < l1; i++) { + tmp += input[i] === expect[i] ? ' ' : '^'; + } + } else { + for (tmp=' '; i < arr.length; i++) { + tmp += ((arr[i].added || arr[i].removed) ? '^' : ' ').repeat(Math.max(arr[i].count, 0)); + if (i + 1 < arr.length && ((arr[i].added && arr[i+1].removed) || (arr[i].removed && arr[i+1].added))) { + arr[i + 1].count -= arr[i].count; + } + } + } + + return output + $.red(tmp); +} + +function direct(input, expect, lenA = String(input).length, lenB = String(expect).length) { + let gutter = 4; + let lenC = Math.max(lenA, lenB); + let typeA=typeof input, typeB=typeof expect; + + if (typeA !== typeB) { + gutter = 2; + + let delA = gutter + lenC - lenA; + let delB = gutter + lenC - lenB; + + input += ' '.repeat(delA) + $.dim(`[${typeA}]`); + expect += ' '.repeat(delB) + $.dim(`[${typeB}]`); + + lenA += delA + typeA.length + 2; + lenB += delB + typeB.length + 2; + lenC = Math.max(lenA, lenB); + } + + let output = colors['++']('++' + expect + ' '.repeat(gutter + lenC - lenB) + TITLE('(Expected)')) + '\n'; + return output + colors['--']('--' + input + ' '.repeat(gutter + lenC - lenA) + TITLE('(Actual)')) + '\n'; +} + +function sort(input, expect) { + var k, i=0, tmp, isArr = Array.isArray(input); + var keys=[], out=isArr ? Array(input.length) : {}; + + if (isArr) { + for (i=0; i < out.length; i++) { + tmp = input[i]; + if (!tmp || typeof tmp !== 'object') out[i] = tmp; + else out[i] = sort(tmp, expect[i]); // might not be right + } + } else { + for (k in expect) + keys.push(k); + + for (; i < keys.length; i++) { + if (Object.prototype.hasOwnProperty.call(input, k = keys[i])) { + if (!(tmp = input[k]) || typeof tmp !== 'object') out[k] = tmp; + else out[k] = sort(tmp, expect[k]); + } + } + + for (k in input) { + if (!out.hasOwnProperty(k)) { + out[k] = input[k]; // expect didnt have + } + } + } + + return out; +} + +function circular() { + var cache = new Set; + return function print(key, val) { + if (val === void 0) return '[__VOID__]'; + if (typeof val === 'number' && val !== val) return '[__NAN__]'; + if (!val || typeof val !== 'object') return val; + if (cache.has(val)) return '[Circular]'; + cache.add(val); return val; + } +} + +function stringify(input) { + return JSON.stringify(input, circular(), 2).replace(/"\[__NAN__\]"/g, 'NaN').replace(/"\[__VOID__\]"/g, 'undefined'); +} + +function compare(input, expect) { + if (Array.isArray(expect) && Array.isArray(input)) return arrays(input, expect); + if (expect instanceof RegExp) return chars(''+input, ''+expect); + + let isA = input && typeof input == 'object'; + let isB = expect && typeof expect == 'object'; + + if (isA && isB) input = sort(input, expect); + if (isB) expect = stringify(expect); + if (isA) input = stringify(input); + + if (expect && typeof expect == 'object') { + input = stringify(sort(input, expect)); + expect = stringify(expect); + } + + isA = typeof input == 'string'; + isB = typeof expect == 'string'; + + if (isA && /\r?\n/.test(input)) return lines(input, ''+expect); + if (isB && /\r?\n/.test(expect)) return lines(''+input, expect); + if (isA && isB) return chars(input, expect); + + return direct(input, expect); +} + +function dedent(str) { + str = str.replace(/\r?\n/g, '\n'); + let arr = str.match(/^[ \t]*(?=\S)/gm); + let i = 0, min = 1/0, len = (arr||[]).length; + for (; i < len; i++) min = Math.min(min, arr[i].length); + return len && min ? str.replace(new RegExp(`^[ \\t]{${min}}`, 'gm'), '') : str; +} + +class Assertion extends Error { + constructor(opts={}) { + super(opts.message); + this.name = 'Assertion'; + this.code = 'ERR_ASSERTION'; + if (Error.captureStackTrace) { + Error.captureStackTrace(this, this.constructor); + } + this.details = opts.details || false; + this.generated = !!opts.generated; + this.operator = opts.operator; + this.expects = opts.expects; + this.actual = opts.actual; + } +} + +function assert(bool, actual, expects, operator, detailer, backup, msg) { + if (bool) return; + let message = msg || backup; + if (msg instanceof Error) throw msg; + let details = detailer && detailer(actual, expects); + throw new Assertion({ actual, expects, operator, message, details, generated: !msg }); +} + +function ok(val, msg) { + assert(!!val, false, true, 'ok', false, 'Expected value to be truthy', msg); +} + +function is(val, exp, msg) { + assert(val === exp, val, exp, 'is', compare, 'Expected values to be strictly equal:', msg); +} + +function equal(val, exp, msg) { + assert(dequal(val, exp), val, exp, 'equal', compare, 'Expected values to be deeply equal:', msg); +} + +function unreachable(msg) { + assert(false, true, false, 'unreachable', false, 'Expected not to be reached!', msg); +} + +function type(val, exp, msg) { + let tmp = typeof val; + assert(tmp === exp, tmp, exp, 'type', false, `Expected "${tmp}" to be "${exp}"`, msg); +} + +function instance(val, exp, msg) { + let name = '`' + (exp.name || exp.constructor.name) + '`'; + assert(val instanceof exp, val, exp, 'instance', false, `Expected value to be an instance of ${name}`, msg); +} + +function match(val, exp, msg) { + if (typeof exp === 'string') { + assert(val.includes(exp), val, exp, 'match', false, `Expected value to include "${exp}" substring`, msg); + } else { + assert(exp.test(val), val, exp, 'match', false, `Expected value to match \`${String(exp)}\` pattern`, msg); + } +} + +function snapshot(val, exp, msg) { + val=dedent(val); exp=dedent(exp); + assert(val === exp, val, exp, 'snapshot', lines, 'Expected value to match snapshot:', msg); +} + +const lineNums = (x, y) => lines(x, y, 1); +function fixture(val, exp, msg) { + val=dedent(val); exp=dedent(exp); + assert(val === exp, val, exp, 'fixture', lineNums, 'Expected value to match fixture:', msg); +} + +function throws(blk, exp, msg) { + if (!msg && typeof exp === 'string') { + msg = exp; exp = null; + } + + try { + blk(); + assert(false, false, true, 'throws', false, 'Expected function to throw', msg); + } catch (err) { + if (err instanceof Assertion) throw err; + + if (typeof exp === 'function') { + assert(exp(err), false, true, 'throws', false, 'Expected function to throw matching exception', msg); + } else if (exp instanceof RegExp) { + assert(exp.test(err.message), false, true, 'throws', false, `Expected function to throw exception matching \`${String(exp)}\` pattern`, msg); + } + } +} + +// --- + +function not(val, msg) { + assert(!val, true, false, 'not', false, 'Expected value to be falsey', msg); +} + +not.ok = not; + +is.not = function (val, exp, msg) { + assert(val !== exp, val, exp, 'is.not', false, 'Expected values not to be strictly equal', msg); +}; + +not.equal = function (val, exp, msg) { + assert(!dequal(val, exp), val, exp, 'not.equal', false, 'Expected values not to be deeply equal', msg); +}; + +not.type = function (val, exp, msg) { + let tmp = typeof val; + assert(tmp !== exp, tmp, exp, 'not.type', false, `Expected "${tmp}" not to be "${exp}"`, msg); +}; + +not.instance = function (val, exp, msg) { + let name = '`' + (exp.name || exp.constructor.name) + '`'; + assert(!(val instanceof exp), val, exp, 'not.instance', false, `Expected value not to be an instance of ${name}`, msg); +}; + +not.snapshot = function (val, exp, msg) { + val=dedent(val); exp=dedent(exp); + assert(val !== exp, val, exp, 'not.snapshot', false, 'Expected value not to match snapshot', msg); +}; + +not.fixture = function (val, exp, msg) { + val=dedent(val); exp=dedent(exp); + assert(val !== exp, val, exp, 'not.fixture', false, 'Expected value not to match fixture', msg); +}; + +not.match = function (val, exp, msg) { + if (typeof exp === 'string') { + assert(!val.includes(exp), val, exp, 'not.match', false, `Expected value not to include "${exp}" substring`, msg); + } else { + assert(!exp.test(val), val, exp, 'not.match', false, `Expected value not to match \`${String(exp)}\` pattern`, msg); + } +}; + +not.throws = function (blk, exp, msg) { + if (!msg && typeof exp === 'string') { + msg = exp; exp = null; + } + + try { + blk(); + } catch (err) { + if (typeof exp === 'function') { + assert(!exp(err), true, false, 'not.throws', false, 'Expected function not to throw matching exception', msg); + } else if (exp instanceof RegExp) { + assert(!exp.test(err.message), true, false, 'not.throws', false, `Expected function not to throw exception matching \`${String(exp)}\` pattern`, msg); + } else if (!exp) { + assert(false, true, false, 'not.throws', false, 'Expected function not to throw', msg); + } + } +}; + +export { Assertion, equal, fixture, instance, is, match, not, ok, snapshot, throws, type, unreachable }; diff --git a/test/uvu.js b/test/uvu.js new file mode 100644 index 000000000..f47367d7e --- /dev/null +++ b/test/uvu.js @@ -0,0 +1,942 @@ +let FORCE_COLOR, NODE_DISABLE_COLORS, NO_COLOR, TERM, isTTY=true; +if (typeof process !== 'undefined') { + ({ FORCE_COLOR, NODE_DISABLE_COLORS, NO_COLOR, TERM } = process.env); + isTTY = process.stdout && process.stdout.isTTY; +} + +const $ = { + enabled: !NODE_DISABLE_COLORS && NO_COLOR == null && TERM !== 'dumb' && ( + FORCE_COLOR != null && FORCE_COLOR !== '0' || isTTY + ), + + // modifiers + reset: init(0, 0), + bold: init(1, 22), + dim: init(2, 22), + italic: init(3, 23), + underline: init(4, 24), + inverse: init(7, 27), + hidden: init(8, 28), + strikethrough: init(9, 29), + + // colors + black: init(30, 39), + red: init(31, 39), + green: init(32, 39), + yellow: init(33, 39), + blue: init(34, 39), + magenta: init(35, 39), + cyan: init(36, 39), + white: init(37, 39), + gray: init(90, 39), + grey: init(90, 39), + + // background colors + bgBlack: init(40, 49), + bgRed: init(41, 49), + bgGreen: init(42, 49), + bgYellow: init(43, 49), + bgBlue: init(44, 49), + bgMagenta: init(45, 49), + bgCyan: init(46, 49), + bgWhite: init(47, 49) +}; + +function run(arr, str) { + let i=0, tmp, beg='', end=''; + for (; i < arr.length; i++) { + tmp = arr[i]; + beg += tmp.open; + end += tmp.close; + if (!!~str.indexOf(tmp.close)) { + str = str.replace(tmp.rgx, tmp.close + tmp.open); + } + } + return beg + str + end; +} + +function chain(has, keys) { + let ctx = { has, keys }; + + ctx.reset = $.reset.bind(ctx); + ctx.bold = $.bold.bind(ctx); + ctx.dim = $.dim.bind(ctx); + ctx.italic = $.italic.bind(ctx); + ctx.underline = $.underline.bind(ctx); + ctx.inverse = $.inverse.bind(ctx); + ctx.hidden = $.hidden.bind(ctx); + ctx.strikethrough = $.strikethrough.bind(ctx); + + ctx.black = $.black.bind(ctx); + ctx.red = $.red.bind(ctx); + ctx.green = $.green.bind(ctx); + ctx.yellow = $.yellow.bind(ctx); + ctx.blue = $.blue.bind(ctx); + ctx.magenta = $.magenta.bind(ctx); + ctx.cyan = $.cyan.bind(ctx); + ctx.white = $.white.bind(ctx); + ctx.gray = $.gray.bind(ctx); + ctx.grey = $.grey.bind(ctx); + + ctx.bgBlack = $.bgBlack.bind(ctx); + ctx.bgRed = $.bgRed.bind(ctx); + ctx.bgGreen = $.bgGreen.bind(ctx); + ctx.bgYellow = $.bgYellow.bind(ctx); + ctx.bgBlue = $.bgBlue.bind(ctx); + ctx.bgMagenta = $.bgMagenta.bind(ctx); + ctx.bgCyan = $.bgCyan.bind(ctx); + ctx.bgWhite = $.bgWhite.bind(ctx); + + return ctx; +} + +function init(open, close) { + let blk = { + open: `\x1b[${open}m`, + close: `\x1b[${close}m`, + rgx: new RegExp(`\\x1b\\[${close}m`, 'g') + }; + return function (txt) { + if (this !== void 0 && this.has !== void 0) { + !!~this.has.indexOf(open) || (this.has.push(open),this.keys.push(blk)); + return txt === void 0 ? this : $.enabled ? run(this.keys, txt+'') : txt+''; + } + return txt === void 0 ? chain([open], [blk]) : $.enabled ? run([blk], txt+'') : txt+''; + }; +} + +function Diff() {} +Diff.prototype = { + diff: function diff(oldString, newString) { + var options = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {}; + var callback = options.callback; + + if (typeof options === 'function') { + callback = options; + options = {}; + } + + this.options = options; + var self = this; + + function done(value) { + if (callback) { + setTimeout(function () { + callback(undefined, value); + }, 0); + return true; + } else { + return value; + } + } // Allow subclasses to massage the input prior to running + + + oldString = this.castInput(oldString); + newString = this.castInput(newString); + oldString = this.removeEmpty(this.tokenize(oldString)); + newString = this.removeEmpty(this.tokenize(newString)); + var newLen = newString.length, + oldLen = oldString.length; + var editLength = 1; + var maxEditLength = newLen + oldLen; + var bestPath = [{ + newPos: -1, + components: [] + }]; // Seed editLength = 0, i.e. the content starts with the same values + + var oldPos = this.extractCommon(bestPath[0], newString, oldString, 0); + + if (bestPath[0].newPos + 1 >= newLen && oldPos + 1 >= oldLen) { + // Identity per the equality and tokenizer + return done([{ + value: this.join(newString), + count: newString.length + }]); + } // Main worker method. checks all permutations of a given edit length for acceptance. + + + function execEditLength() { + for (var diagonalPath = -1 * editLength; diagonalPath <= editLength; diagonalPath += 2) { + var basePath = void 0; + + var addPath = bestPath[diagonalPath - 1], + removePath = bestPath[diagonalPath + 1], + _oldPos = (removePath ? removePath.newPos : 0) - diagonalPath; + + if (addPath) { + // No one else is going to attempt to use this value, clear it + bestPath[diagonalPath - 1] = undefined; + } + + var canAdd = addPath && addPath.newPos + 1 < newLen, + canRemove = removePath && 0 <= _oldPos && _oldPos < oldLen; + + if (!canAdd && !canRemove) { + // If this path is a terminal then prune + bestPath[diagonalPath] = undefined; + continue; + } // Select the diagonal that we want to branch from. We select the prior + // path whose position in the new string is the farthest from the origin + // and does not pass the bounds of the diff graph + + + if (!canAdd || canRemove && addPath.newPos < removePath.newPos) { + basePath = clonePath(removePath); + self.pushComponent(basePath.components, undefined, true); + } else { + basePath = addPath; // No need to clone, we've pulled it from the list + + basePath.newPos++; + self.pushComponent(basePath.components, true, undefined); + } + + _oldPos = self.extractCommon(basePath, newString, oldString, diagonalPath); // If we have hit the end of both strings, then we are done + + if (basePath.newPos + 1 >= newLen && _oldPos + 1 >= oldLen) { + return done(buildValues(self, basePath.components, newString, oldString, self.useLongestToken)); + } else { + // Otherwise track this path as a potential candidate and continue. + bestPath[diagonalPath] = basePath; + } + } + + editLength++; + } // Performs the length of edit iteration. Is a bit fugly as this has to support the + // sync and async mode which is never fun. Loops over execEditLength until a value + // is produced. + + + if (callback) { + (function exec() { + setTimeout(function () { + // This should not happen, but we want to be safe. + + /* istanbul ignore next */ + if (editLength > maxEditLength) { + return callback(); + } + + if (!execEditLength()) { + exec(); + } + }, 0); + })(); + } else { + while (editLength <= maxEditLength) { + var ret = execEditLength(); + + if (ret) { + return ret; + } + } + } + }, + pushComponent: function pushComponent(components, added, removed) { + var last = components[components.length - 1]; + + if (last && last.added === added && last.removed === removed) { + // We need to clone here as the component clone operation is just + // as shallow array clone + components[components.length - 1] = { + count: last.count + 1, + added: added, + removed: removed + }; + } else { + components.push({ + count: 1, + added: added, + removed: removed + }); + } + }, + extractCommon: function extractCommon(basePath, newString, oldString, diagonalPath) { + var newLen = newString.length, + oldLen = oldString.length, + newPos = basePath.newPos, + oldPos = newPos - diagonalPath, + commonCount = 0; + + while (newPos + 1 < newLen && oldPos + 1 < oldLen && this.equals(newString[newPos + 1], oldString[oldPos + 1])) { + newPos++; + oldPos++; + commonCount++; + } + + if (commonCount) { + basePath.components.push({ + count: commonCount + }); + } + + basePath.newPos = newPos; + return oldPos; + }, + equals: function equals(left, right) { + if (this.options.comparator) { + return this.options.comparator(left, right); + } else { + return left === right || this.options.ignoreCase && left.toLowerCase() === right.toLowerCase(); + } + }, + removeEmpty: function removeEmpty(array) { + var ret = []; + + for (var i = 0; i < array.length; i++) { + if (array[i]) { + ret.push(array[i]); + } + } + + return ret; + }, + castInput: function castInput(value) { + return value; + }, + tokenize: function tokenize(value) { + return value.split(''); + }, + join: function join(chars) { + return chars.join(''); + } +}; + +function buildValues(diff, components, newString, oldString, useLongestToken) { + var componentPos = 0, + componentLen = components.length, + newPos = 0, + oldPos = 0; + + for (; componentPos < componentLen; componentPos++) { + var component = components[componentPos]; + + if (!component.removed) { + if (!component.added && useLongestToken) { + var value = newString.slice(newPos, newPos + component.count); + value = value.map(function (value, i) { + var oldValue = oldString[oldPos + i]; + return oldValue.length > value.length ? oldValue : value; + }); + component.value = diff.join(value); + } else { + component.value = diff.join(newString.slice(newPos, newPos + component.count)); + } + + newPos += component.count; // Common case + + if (!component.added) { + oldPos += component.count; + } + } else { + component.value = diff.join(oldString.slice(oldPos, oldPos + component.count)); + oldPos += component.count; // Reverse add and remove so removes are output first to match common convention + // The diffing algorithm is tied to add then remove output and this is the simplest + // route to get the desired output with minimal overhead. + + if (componentPos && components[componentPos - 1].added) { + var tmp = components[componentPos - 1]; + components[componentPos - 1] = components[componentPos]; + components[componentPos] = tmp; + } + } + } // Special case handle for when one terminal is ignored (i.e. whitespace). + // For this case we merge the terminal into the prior string and drop the change. + // This is only available for string mode. + + + var lastComponent = components[componentLen - 1]; + + if (componentLen > 1 && typeof lastComponent.value === 'string' && (lastComponent.added || lastComponent.removed) && diff.equals('', lastComponent.value)) { + components[componentLen - 2].value += lastComponent.value; + components.pop(); + } + + return components; +} + +function clonePath(path) { + return { + newPos: path.newPos, + components: path.components.slice(0) + }; +} + +var characterDiff = new Diff(); +function diffChars(oldStr, newStr, options) { + return characterDiff.diff(oldStr, newStr, options); +} + +// +// Ranges and exceptions: +// Latin-1 Supplement, 0080–00FF +// - U+00D7 × Multiplication sign +// - U+00F7 ÷ Division sign +// Latin Extended-A, 0100–017F +// Latin Extended-B, 0180–024F +// IPA Extensions, 0250–02AF +// Spacing Modifier Letters, 02B0–02FF +// - U+02C7 ˇ ˇ Caron +// - U+02D8 ˘ ˘ Breve +// - U+02D9 ˙ ˙ Dot Above +// - U+02DA ˚ ˚ Ring Above +// - U+02DB ˛ ˛ Ogonek +// - U+02DC ˜ ˜ Small Tilde +// - U+02DD ˝ ˝ Double Acute Accent +// Latin Extended Additional, 1E00–1EFF + +var extendedWordChars = /^[A-Za-z\xC0-\u02C6\u02C8-\u02D7\u02DE-\u02FF\u1E00-\u1EFF]+$/; +var reWhitespace = /\S/; +var wordDiff = new Diff(); + +wordDiff.equals = function (left, right) { + if (this.options.ignoreCase) { + left = left.toLowerCase(); + right = right.toLowerCase(); + } + + return left === right || this.options.ignoreWhitespace && !reWhitespace.test(left) && !reWhitespace.test(right); +}; + +wordDiff.tokenize = function (value) { + // All whitespace symbols except newline group into one token, each newline - in separate token + var tokens = value.split(/([^\S\r\n]+|[()[\]{}'"\r\n]|\b)/); // Join the boundary splits that we do not consider to be boundaries. This is primarily the extended Latin character set. + + for (var i = 0; i < tokens.length - 1; i++) { + // If we have an empty string in the next field and we have only word chars before and after, merge + if (!tokens[i + 1] && tokens[i + 2] && extendedWordChars.test(tokens[i]) && extendedWordChars.test(tokens[i + 2])) { + tokens[i] += tokens[i + 2]; + tokens.splice(i + 1, 2); + i--; + } + } + + return tokens; +}; + +var lineDiff = new Diff(); + +lineDiff.tokenize = function (value) { + var retLines = [], + linesAndNewlines = value.split(/(\n|\r\n)/); // Ignore the final empty token that occurs if the string ends with a new line + + if (!linesAndNewlines[linesAndNewlines.length - 1]) { + linesAndNewlines.pop(); + } // Merge the content and line separators into single tokens + + + for (var i = 0; i < linesAndNewlines.length; i++) { + var line = linesAndNewlines[i]; + + if (i % 2 && !this.options.newlineIsToken) { + retLines[retLines.length - 1] += line; + } else { + if (this.options.ignoreWhitespace) { + line = line.trim(); + } + + retLines.push(line); + } + } + + return retLines; +}; + +function diffLines(oldStr, newStr, callback) { + return lineDiff.diff(oldStr, newStr, callback); +} + +var sentenceDiff = new Diff(); + +sentenceDiff.tokenize = function (value) { + return value.split(/(\S.+?[.!?])(?=\s+|$)/); +}; + +var cssDiff = new Diff(); + +cssDiff.tokenize = function (value) { + return value.split(/([{}:;,]|\s+)/); +}; + +function _typeof(obj) { + "@babel/helpers - typeof"; + + if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") { + _typeof = function (obj) { + return typeof obj; + }; + } else { + _typeof = function (obj) { + return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; + }; + } + + return _typeof(obj); +} + +var objectPrototypeToString = Object.prototype.toString; +var jsonDiff = new Diff(); // Discriminate between two lines of pretty-printed, serialized JSON where one of them has a +// dangling comma and the other doesn't. Turns out including the dangling comma yields the nicest output: + +jsonDiff.useLongestToken = true; +jsonDiff.tokenize = lineDiff.tokenize; + +jsonDiff.castInput = function (value) { + var _this$options = this.options, + undefinedReplacement = _this$options.undefinedReplacement, + _this$options$stringi = _this$options.stringifyReplacer, + stringifyReplacer = _this$options$stringi === void 0 ? function (k, v) { + return typeof v === 'undefined' ? undefinedReplacement : v; + } : _this$options$stringi; + return typeof value === 'string' ? value : JSON.stringify(canonicalize(value, null, null, stringifyReplacer), stringifyReplacer, ' '); +}; + +jsonDiff.equals = function (left, right) { + return Diff.prototype.equals.call(jsonDiff, left.replace(/,([\r\n])/g, '$1'), right.replace(/,([\r\n])/g, '$1')); +}; +// object that is already on the "stack" of items being processed. Accepts an optional replacer + +function canonicalize(obj, stack, replacementStack, replacer, key) { + stack = stack || []; + replacementStack = replacementStack || []; + + if (replacer) { + obj = replacer(key, obj); + } + + var i; + + for (i = 0; i < stack.length; i += 1) { + if (stack[i] === obj) { + return replacementStack[i]; + } + } + + var canonicalizedObj; + + if ('[object Array]' === objectPrototypeToString.call(obj)) { + stack.push(obj); + canonicalizedObj = new Array(obj.length); + replacementStack.push(canonicalizedObj); + + for (i = 0; i < obj.length; i += 1) { + canonicalizedObj[i] = canonicalize(obj[i], stack, replacementStack, replacer, key); + } + + stack.pop(); + replacementStack.pop(); + return canonicalizedObj; + } + + if (obj && obj.toJSON) { + obj = obj.toJSON(); + } + + if (_typeof(obj) === 'object' && obj !== null) { + stack.push(obj); + canonicalizedObj = {}; + replacementStack.push(canonicalizedObj); + + var sortedKeys = [], + _key; + + for (_key in obj) { + /* istanbul ignore else */ + if (obj.hasOwnProperty(_key)) { + sortedKeys.push(_key); + } + } + + sortedKeys.sort(); + + for (i = 0; i < sortedKeys.length; i += 1) { + _key = sortedKeys[i]; + canonicalizedObj[_key] = canonicalize(obj[_key], stack, replacementStack, replacer, _key); + } + + stack.pop(); + replacementStack.pop(); + } else { + canonicalizedObj = obj; + } + + return canonicalizedObj; +} + +var arrayDiff = new Diff(); + +arrayDiff.tokenize = function (value) { + return value.slice(); +}; + +arrayDiff.join = arrayDiff.removeEmpty = function (value) { + return value; +}; + +function diffArrays(oldArr, newArr, callback) { + return arrayDiff.diff(oldArr, newArr, callback); +} + +const colors = { + '--': $.red, + '··': $.grey, + '++': $.green, +}; + +const TITLE = $.dim().italic; +const TAB=$.dim('→'), SPACE=$.dim('·'), NL=$.dim('↵'); +const LOG = (sym, str) => colors[sym](sym + PRETTY(str)) + '\n'; +const LINE = (num, x) => $.dim('L' + String(num).padStart(x, '0') + ' '); +const PRETTY = str => str.replace(/[ ]/g, SPACE).replace(/\t/g, TAB).replace(/(\r?\n)/g, NL); + +function line(obj, prev, pad) { + let char = obj.removed ? '--' : obj.added ? '++' : '··'; + let arr = obj.value.replace(/\r?\n$/, '').split('\n'); + let i=0, tmp, out=''; + + if (obj.added) out += colors[char]().underline(TITLE('Expected:')) + '\n'; + else if (obj.removed) out += colors[char]().underline(TITLE('Actual:')) + '\n'; + + for (; i < arr.length; i++) { + tmp = arr[i]; + if (tmp != null) { + if (prev) out += LINE(prev + i, pad); + out += LOG(char, tmp || '\n'); + } + } + + return out; +} + +// TODO: want better diffing +//~> complex items bail outright +function arrays(input, expect) { + let arr = diffArrays(input, expect); + let i=0, j=0, k=0, tmp, val, char, isObj, str; + let out = LOG('··', '['); + + for (; i < arr.length; i++) { + char = (tmp = arr[i]).removed ? '--' : tmp.added ? '++' : '··'; + + if (tmp.added) { + out += colors[char]().underline(TITLE('Expected:')) + '\n'; + } else if (tmp.removed) { + out += colors[char]().underline(TITLE('Actual:')) + '\n'; + } + + for (j=0; j < tmp.value.length; j++) { + isObj = (tmp.value[j] && typeof tmp.value[j] === 'object'); + val = stringify(tmp.value[j]).split(/\r?\n/g); + for (k=0; k < val.length;) { + str = ' ' + val[k++] + (isObj ? '' : ','); + if (isObj && k === val.length && (j + 1) < tmp.value.length) str += ','; + out += LOG(char, str); + } + } + } + + return out + LOG('··', ']'); +} + +function lines(input, expect, linenum = 0) { + let i=0, tmp, output=''; + let arr = diffLines(input, expect); + let pad = String(expect.split(/\r?\n/g).length - linenum).length; + + for (; i < arr.length; i++) { + output += line(tmp = arr[i], linenum, pad); + if (linenum && !tmp.removed) linenum += tmp.count; + } + + return output; +} + +function chars(input, expect) { + let arr = diffChars(input, expect); + let i=0, output='', tmp; + + let l1 = input.length; + let l2 = expect.length; + + let p1 = PRETTY(input); + let p2 = PRETTY(expect); + + tmp = arr[i]; + + if (l1 === l2) ; else if (tmp.removed && arr[i + 1]) { + let del = tmp.count - arr[i + 1].count; + if (del == 0) ; else if (del > 0) { + expect = ' '.repeat(del) + expect; + p2 = ' '.repeat(del) + p2; + l2 += del; + } else if (del < 0) { + input = ' '.repeat(-del) + input; + p1 = ' '.repeat(-del) + p1; + l1 += -del; + } + } + + output += direct(p1, p2, l1, l2); + + if (l1 === l2) { + for (tmp=' '; i < l1; i++) { + tmp += input[i] === expect[i] ? ' ' : '^'; + } + } else { + for (tmp=' '; i < arr.length; i++) { + tmp += ((arr[i].added || arr[i].removed) ? '^' : ' ').repeat(Math.max(arr[i].count, 0)); + if (i + 1 < arr.length && ((arr[i].added && arr[i+1].removed) || (arr[i].removed && arr[i+1].added))) { + arr[i + 1].count -= arr[i].count; + } + } + } + + return output + $.red(tmp); +} + +function direct(input, expect, lenA = String(input).length, lenB = String(expect).length) { + let gutter = 4; + let lenC = Math.max(lenA, lenB); + let typeA=typeof input, typeB=typeof expect; + + if (typeA !== typeB) { + gutter = 2; + + let delA = gutter + lenC - lenA; + let delB = gutter + lenC - lenB; + + input += ' '.repeat(delA) + $.dim(`[${typeA}]`); + expect += ' '.repeat(delB) + $.dim(`[${typeB}]`); + + lenA += delA + typeA.length + 2; + lenB += delB + typeB.length + 2; + lenC = Math.max(lenA, lenB); + } + + let output = colors['++']('++' + expect + ' '.repeat(gutter + lenC - lenB) + TITLE('(Expected)')) + '\n'; + return output + colors['--']('--' + input + ' '.repeat(gutter + lenC - lenA) + TITLE('(Actual)')) + '\n'; +} + +function sort(input, expect) { + var k, i=0, tmp, isArr = Array.isArray(input); + var keys=[], out=isArr ? Array(input.length) : {}; + + if (isArr) { + for (i=0; i < out.length; i++) { + tmp = input[i]; + if (!tmp || typeof tmp !== 'object') out[i] = tmp; + else out[i] = sort(tmp, expect[i]); // might not be right + } + } else { + for (k in expect) + keys.push(k); + + for (; i < keys.length; i++) { + if (Object.prototype.hasOwnProperty.call(input, k = keys[i])) { + if (!(tmp = input[k]) || typeof tmp !== 'object') out[k] = tmp; + else out[k] = sort(tmp, expect[k]); + } + } + + for (k in input) { + if (!out.hasOwnProperty(k)) { + out[k] = input[k]; // expect didnt have + } + } + } + + return out; +} + +function circular() { + var cache = new Set; + return function print(key, val) { + if (val === void 0) return '[__VOID__]'; + if (typeof val === 'number' && val !== val) return '[__NAN__]'; + if (!val || typeof val !== 'object') return val; + if (cache.has(val)) return '[Circular]'; + cache.add(val); return val; + } +} + +function stringify(input) { + return JSON.stringify(input, circular(), 2).replace(/"\[__NAN__\]"/g, 'NaN').replace(/"\[__VOID__\]"/g, 'undefined'); +} + +function compare(input, expect) { + if (Array.isArray(expect) && Array.isArray(input)) return arrays(input, expect); + if (expect instanceof RegExp) return chars(''+input, ''+expect); + + let isA = input && typeof input == 'object'; + let isB = expect && typeof expect == 'object'; + + if (isA && isB) input = sort(input, expect); + if (isB) expect = stringify(expect); + if (isA) input = stringify(input); + + if (expect && typeof expect == 'object') { + input = stringify(sort(input, expect)); + expect = stringify(expect); + } + + isA = typeof input == 'string'; + isB = typeof expect == 'string'; + + if (isA && /\r?\n/.test(input)) return lines(input, ''+expect); + if (isB && /\r?\n/.test(expect)) return lines(''+input, expect); + if (isA && isB) return chars(input, expect); + + return direct(input, expect); +} + +let isCLI = false, isNode = false; +let hrtime = (now = Date.now()) => () => (Date.now() - now).toFixed(2) + 'ms'; +let write = console.log; + +const into = (ctx, key) => (name, handler) => ctx[key].push({ name, handler }); +const context = (state) => ({ tests:[], before:[], after:[], bEach:[], aEach:[], only:[], skips:0, state }); +const milli = arr => (arr[0]*1e3 + arr[1]/1e6).toFixed(2) + 'ms'; +const hook = (ctx, key) => handler => ctx[key].push(handler); + +if (isNode = typeof process < 'u' && typeof process.stdout < 'u') { + // globalThis polyfill; Node < 12 + if (typeof globalThis !== 'object') { + Object.defineProperty(global, 'globalThis', { + get: function () { return this } + }); + } + + let rgx = /(\.bin[\\+\/]uvu$|uvu[\\+\/]bin\.js)/i; + isCLI = process.argv.some(x => rgx.test(x)); + + // attach node-specific utils + write = x => process.stdout.write(x); + hrtime = (now = process.hrtime()) => () => milli(process.hrtime(now)); +} else if (typeof performance < 'u') { + hrtime = (now = performance.now()) => () => (performance.now() - now).toFixed(2) + 'ms'; +} + +globalThis.UVU_QUEUE = globalThis.UVU_QUEUE || []; +isCLI || UVU_QUEUE.push([null]); + +const QUOTE = $.dim('"'), GUTTER = '\n '; +const FAIL = $.red('✘ '), PASS = $.gray('• '); +const IGNORE = /^\s*at.*(?:\(|\s)(?:node|(internal\/[\w/]*))/; +const FAILURE = $.bold().bgRed(' FAIL '); +const FILE = $.bold().underline().white; +const SUITE = $.bgWhite().bold; + +function stack(stack, idx) { + let i=0, line, out=''; + let arr = stack.substring(idx).replace(/\\/g, '/').split('\n'); + for (; i < arr.length; i++) { + line = arr[i].trim(); + if (line.length && !IGNORE.test(line)) { + out += '\n ' + line; + } + } + return $.grey(out) + '\n'; +} + +function format(name, err, suite = '') { + let { details, operator='' } = err; + let idx = err.stack && err.stack.indexOf('\n'); + if (err.name.startsWith('AssertionError') && !operator.includes('not')) details = compare(err.actual, err.expected); // TODO? + let str = ' ' + FAILURE + (suite ? $.red(SUITE(` ${suite} `)) : '') + ' ' + QUOTE + $.red().bold(name) + QUOTE; + str += '\n ' + err.message + (operator ? $.italic().dim(` (${operator})`) : '') + '\n'; + if (details) str += GUTTER + details.split('\n').join(GUTTER); + if (!!~idx) str += stack(err.stack, idx); + return str + '\n'; +} + +async function runner(ctx, name) { + let { only, tests, before, after, bEach, aEach, state } = ctx; + let hook, test, arr = only.length ? only : tests; + let num=0, errors='', total=arr.length; + + try { + if (name) write(SUITE($.black(` ${name} `)) + ' '); + for (hook of before) await hook(state); + + for (test of arr) { + state.__test__ = test.name; + try { + for (hook of bEach) await hook(state); + await test.handler(state); + for (hook of aEach) await hook(state); + write(PASS); + num++; + } catch (err) { + for (hook of aEach) await hook(state); + if (errors.length) errors += '\n'; + errors += format(test.name, err, name); + write(FAIL); + } + } + } finally { + state.__test__ = ''; + for (hook of after) await hook(state); + let msg = ` (${num} / ${total})\n`; + let skipped = (only.length ? tests.length : 0) + ctx.skips; + write(errors.length ? $.red(msg) : $.green(msg)); + return [errors || true, num, skipped, total]; + } +} + +let timer; +function defer() { + clearTimeout(timer); + timer = setTimeout(exec); +} + +function setup(ctx, name = '') { + ctx.state.__test__ = ''; + ctx.state.__suite__ = name; + const test = into(ctx, 'tests'); + test.before = hook(ctx, 'before'); + test.before.each = hook(ctx, 'bEach'); + test.after = hook(ctx, 'after'); + test.after.each = hook(ctx, 'aEach'); + test.only = into(ctx, 'only'); + test.skip = () => { ctx.skips++; }; + test.run = () => { + let copy = { ...ctx }; + let run = runner.bind(0, copy, name); + Object.assign(ctx, context(copy.state)); + UVU_QUEUE[globalThis.UVU_INDEX || 0].push(run); + isCLI || defer(); + }; + return test; +} + +const suite = (name = '', state = {}) => setup(context(state), name); +const test = suite(); + +async function exec(bail) { + let timer = hrtime(); + let done=0, total=0, skips=0, code=0; + + for (let group of UVU_QUEUE) { + if (total) write('\n'); + + let name = group.shift(); + if (name != null) write(FILE(name) + '\n'); + + for (let test of group) { + let [errs, ran, skip, max] = await test(); + total += max; done += ran; skips += skip; + if (errs.length) { + write('\n' + errs + '\n'); code=1; + if (bail) return isNode && process.exit(1); + } + } + } + + write('\n Total: ' + total); + write((code ? $.red : $.green)('\n Passed: ' + done)); + write('\n Skipped: ' + (skips ? $.yellow(skips) : skips)); + write('\n Duration: ' + timer() + '\n\n'); + + if (isNode) process.exitCode = code; +} + +export { exec, suite, test };