mirror of
https://github.com/tenrok/BBob.git
synced 2026-05-15 11:59:37 +03:00
feat: typescript support (#185)
* feat: initial typescript support * feat: typescript support * feat(plugin-helper): move files to typescript * chore: update lock files * feat: preset types * fix: build * fix: benchmark * fix: remove pnpm cache * fix: bench action * fix: pnpm recursive install * fix: nx cache * fix: lock file * fix: workflows * fix: lerna support in pnpm * fix: pnpm workspace * fix: remove unused files * fix: pnpm lock file * fix: update lerna for support pnpm * fix: lerna bootstrap * fix: rollup build * fix: update nx * fix: build * fix: add nx dep target * fix: remove nx cache * fix: workflow run on push only for master * fix: test workflow run on push only for master * fix: remove parallel for gen types * fix: benchmark * fix: benchmark imports * fix: pnpm * fix: types errors and pnpm * fix: types * fix: types * refactor: parser * fix(parser): tests * fix: preset tests * fix: react types * fix: react type declarations * fix: pnpm lock file * fix: react preset types * fix: lock file * fix: vue2 types * feat: dev container support * fix: types * fix: types * refactor: rewrite pkg-task, add nx gen-types deps, fix react/render.ts * refactor: types * fix: types * fix: rename gen-types to types * fix: nx build order * fix: nx reset * fix: define nx deps explicit * fix: build * fix: nx * fix: nx order build * fix: nx deps * fix: bbob cli tests * fix: tests * fix: cli tests and import * fix: test cover * fix: cli cover
This commit is contained in:
@@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
"feat: typescript support": minor
|
||||||
|
---
|
||||||
|
|
||||||
|
Now BBob supports Typescript with typings
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
// For format details, see https://aka.ms/devcontainer.json. For config options, see the
|
||||||
|
// README at: https://github.com/devcontainers/templates/tree/main/src/typescript-node
|
||||||
|
{
|
||||||
|
"name": "Node.js & TypeScript",
|
||||||
|
// Or use a Dockerfile or Docker Compose file. More info: https://containers.dev/guide/dockerfile
|
||||||
|
"image": "mcr.microsoft.com/devcontainers/typescript-node:1-20-bullseye",
|
||||||
|
"features": {
|
||||||
|
"ghcr.io/devcontainers/features/node:1": {}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Features to add to the dev container. More info: https://containers.dev/features.
|
||||||
|
// "features": {},
|
||||||
|
|
||||||
|
// Use 'forwardPorts' to make a list of ports inside the container available locally.
|
||||||
|
// "forwardPorts": [],
|
||||||
|
|
||||||
|
// Use 'postCreateCommand' to run commands after the container is created.
|
||||||
|
// "postCreateCommand": "yarn install",
|
||||||
|
|
||||||
|
// Configure tool-specific properties.
|
||||||
|
// "customizations": {},
|
||||||
|
|
||||||
|
// Uncomment to connect as root instead. More info: https://aka.ms/dev-containers-non-root.
|
||||||
|
// "remoteUser": "root"
|
||||||
|
}
|
||||||
@@ -32,16 +32,16 @@ jobs:
|
|||||||
cache: 'pnpm'
|
cache: 'pnpm'
|
||||||
|
|
||||||
- name: Install
|
- name: Install
|
||||||
run: pnpm install --frozen-lockfile --strict-peer-dependencies
|
run: pnpm i --frozen-lockfile --strict-peer-dependencies
|
||||||
|
|
||||||
- name: Run the lint
|
- name: Run the lint
|
||||||
run: pnpm lint
|
run: pnpm run lint
|
||||||
|
|
||||||
- name: Install coverage
|
- name: Install coverage
|
||||||
run: pnpm install --global codecov
|
run: pnpm install --global codecov
|
||||||
|
|
||||||
- name: Run the coverage
|
- name: Run the coverage
|
||||||
run: pnpm cover
|
run: pnpm run cover
|
||||||
|
|
||||||
- name: Run the coverage
|
- name: Run the coverage
|
||||||
run: codecov
|
run: codecov
|
||||||
|
|||||||
@@ -64,3 +64,8 @@ typings/
|
|||||||
# benchmark test files
|
# benchmark test files
|
||||||
benchmark/html5
|
benchmark/html5
|
||||||
.nx
|
.nx
|
||||||
|
|
||||||
|
.nx
|
||||||
|
|
||||||
|
|
||||||
|
.nx/cache
|
||||||
|
|||||||
@@ -1 +1,2 @@
|
|||||||
registry=https://registry.npmjs.org/
|
registry=https://registry.npmjs.org/
|
||||||
|
link-workspace-packages=true
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
{
|
{
|
||||||
|
"$schema": "https://json.schemastore.org/swcrc",
|
||||||
"module": {
|
"module": {
|
||||||
"type": "commonjs",
|
"type": "commonjs",
|
||||||
|
|
||||||
@@ -9,14 +10,12 @@
|
|||||||
},
|
},
|
||||||
"jsc": {
|
"jsc": {
|
||||||
"transform": {
|
"transform": {
|
||||||
"optimizer": {
|
"optimizer": {}
|
||||||
"simplify": false
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
"loose": true,
|
"loose": true,
|
||||||
"parser": {
|
"parser": {
|
||||||
"syntax": "ecmascript",
|
"syntax": "typescript",
|
||||||
"jsx": true
|
"tsx": true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+4
-5
@@ -1,4 +1,5 @@
|
|||||||
{
|
{
|
||||||
|
"$schema": "https://json.schemastore.org/swcrc",
|
||||||
"module": {
|
"module": {
|
||||||
"type": "es6",
|
"type": "es6",
|
||||||
"strict": false,
|
"strict": false,
|
||||||
@@ -9,15 +10,13 @@
|
|||||||
"minify": false,
|
"minify": false,
|
||||||
"jsc": {
|
"jsc": {
|
||||||
"transform": {
|
"transform": {
|
||||||
"optimizer": {
|
"optimizer": {}
|
||||||
"simplify": false
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
"target": "es2018",
|
"target": "es2018",
|
||||||
"loose": true,
|
"loose": true,
|
||||||
"parser": {
|
"parser": {
|
||||||
"syntax": "ecmascript",
|
"syntax": "typescript",
|
||||||
"jsx": true
|
"tsx": true
|
||||||
},
|
},
|
||||||
"externalHelpers": false
|
"externalHelpers": false
|
||||||
}
|
}
|
||||||
|
|||||||
Vendored
+4
-4
@@ -48,17 +48,17 @@ suite
|
|||||||
});
|
});
|
||||||
})
|
})
|
||||||
.add('@bbob/parser lexer old', () => {
|
.add('@bbob/parser lexer old', () => {
|
||||||
const lexer1 = require('@bbob/parser/lib/lexer_old');
|
const lexer1 = require('./lexer_old');
|
||||||
|
|
||||||
return require('@bbob/parser/lib/index').parse(stub, {
|
return require('@bbob/parser').parse(stub, {
|
||||||
onlyAllowTags: ['ch'],
|
onlyAllowTags: ['ch'],
|
||||||
createTokenizer: lexer1.createLexer,
|
createTokenizer: lexer1.createLexer,
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
.add('@bbob/parser lexer', () => {
|
.add('@bbob/parser lexer', () => {
|
||||||
const lexer2 = require('@bbob/parser/lib/lexer');
|
const lexer2 = require('@bbob/parser');
|
||||||
|
|
||||||
return require('@bbob/parser/lib/index').parse(stub, {
|
return require('@bbob/parser').parse(stub, {
|
||||||
onlyAllowTags: ['ch'],
|
onlyAllowTags: ['ch'],
|
||||||
createTokenizer: lexer2.createLexer,
|
createTokenizer: lexer2.createLexer,
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/* eslint-disable no-plusplus,no-param-reassign */
|
/* eslint-disable no-plusplus,no-param-reassign */
|
||||||
import {
|
const {
|
||||||
OPEN_BRAKET,
|
OPEN_BRAKET,
|
||||||
CLOSE_BRAKET,
|
CLOSE_BRAKET,
|
||||||
QUOTEMARK,
|
QUOTEMARK,
|
||||||
@@ -9,12 +9,12 @@ import {
|
|||||||
TAB,
|
TAB,
|
||||||
EQ,
|
EQ,
|
||||||
N,
|
N,
|
||||||
} from '@bbob/plugin-helper';
|
} = require('@bbob/plugin-helper');
|
||||||
|
|
||||||
import {
|
const {
|
||||||
Token, TYPE_ATTR_NAME, TYPE_ATTR_VALUE, TYPE_NEW_LINE, TYPE_SPACE, TYPE_TAG, TYPE_WORD,
|
Token, TYPE_ATTR_NAME, TYPE_ATTR_VALUE, TYPE_NEW_LINE, TYPE_SPACE, TYPE_TAG, TYPE_WORD,
|
||||||
} from './Token';
|
} = require('@bbob/parser/Token');
|
||||||
import { createCharGrabber, trimChar, unquote } from './utils';
|
const { createCharGrabber, trimChar, unquote } = require('@bbob/parser/utils');
|
||||||
|
|
||||||
// for cases <!-- -->
|
// for cases <!-- -->
|
||||||
const EM = '!';
|
const EM = '!';
|
||||||
@@ -238,5 +238,4 @@ function createLexer(buffer, options = {}) {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export const createTokenOfType = createToken;
|
module.exports.createLexer = createLexer;
|
||||||
export { createLexer };
|
|
||||||
Vendored
+2
-1
@@ -13,7 +13,8 @@
|
|||||||
"url": "https://artkost.ru/"
|
"url": "https://artkost.ru/"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@bbob/parser": "workspace:*",
|
"@bbob/parser": "*",
|
||||||
|
"@bbob/plugin-helper": "*",
|
||||||
"benchmark": "2.1.4",
|
"benchmark": "2.1.4",
|
||||||
"picocolors": "1.0.0",
|
"picocolors": "1.0.0",
|
||||||
"xbbcode-parser": "0.1.2",
|
"xbbcode-parser": "0.1.2",
|
||||||
|
|||||||
+3
-1
@@ -27,5 +27,7 @@
|
|||||||
"message": "chore(release): publish %s"
|
"message": "chore(release): publish %s"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"useNx": true
|
"npmClient": "pnpm",
|
||||||
|
"useNx": true,
|
||||||
|
"useWorkspaces": true
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,12 +1,81 @@
|
|||||||
{
|
{
|
||||||
"tasksRunnerOptions": {
|
"targetDefaults": {
|
||||||
"default": {
|
"types": {
|
||||||
"runner": "nx/tasks-runners/default",
|
"dependsOn": [
|
||||||
"options": {
|
"^types"
|
||||||
"cacheableOperations": ["build"]
|
],
|
||||||
}
|
"outputs": ["{projectRoot}/types"],
|
||||||
|
"cache": true
|
||||||
|
},
|
||||||
|
"build": {
|
||||||
|
"dependsOn": [
|
||||||
|
"^types",
|
||||||
|
"^build:commonjs",
|
||||||
|
"^build:es",
|
||||||
|
"^build:umd"
|
||||||
|
],
|
||||||
|
"outputs": ["{projectRoot}/lib", "{projectRoot}/es", "{projectRoot}/dist"],
|
||||||
|
"cache": true
|
||||||
|
},
|
||||||
|
"test": {
|
||||||
|
"dependsOn": [
|
||||||
|
"^build",
|
||||||
|
"^test"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"cover": {
|
||||||
|
"dependsOn": [
|
||||||
|
"^build",
|
||||||
|
"^cover"
|
||||||
|
],
|
||||||
|
"outputs": ["{projectRoot}/coverage"]
|
||||||
|
},
|
||||||
|
"lint": {
|
||||||
|
"dependsOn": [
|
||||||
|
"^lint"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"build:commonjs": {
|
||||||
|
"dependsOn": [
|
||||||
|
"^build:es",
|
||||||
|
"^build:commonjs"
|
||||||
|
],
|
||||||
|
"outputs": ["{projectRoot}/lib"],
|
||||||
|
"cache": true
|
||||||
|
},
|
||||||
|
"build:es": {
|
||||||
|
"dependsOn": [
|
||||||
|
"^build:es"
|
||||||
|
],
|
||||||
|
"outputs": ["{projectRoot}/es"],
|
||||||
|
"cache": true
|
||||||
|
},
|
||||||
|
"build:umd": {
|
||||||
|
"dependsOn": [
|
||||||
|
"^build:es",
|
||||||
|
"^build:umd"
|
||||||
|
],
|
||||||
|
"outputs": ["{projectRoot}/dist"],
|
||||||
|
"cache": true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"targetDefaults": {},
|
"$schema": "./node_modules/nx/schemas/nx-schema.json",
|
||||||
"defaultBase": "master"
|
"defaultBase": "master",
|
||||||
|
"extends": "nx/presets/npm.json",
|
||||||
|
"plugins": [
|
||||||
|
{
|
||||||
|
"plugin": "@nx/eslint/plugin",
|
||||||
|
"options": {
|
||||||
|
"targetName": "eslint:lint",
|
||||||
|
"extensions": [
|
||||||
|
"ts",
|
||||||
|
"tsx",
|
||||||
|
"js",
|
||||||
|
"jsx",
|
||||||
|
"html",
|
||||||
|
"vue"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
+42
-26
@@ -1,7 +1,8 @@
|
|||||||
{
|
{
|
||||||
|
"name": "bbob",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"prepublishOnly": "npm run test",
|
"prepublishOnly": "npm run test",
|
||||||
"bootstrap": "lerna bootstrap --no-ci",
|
"bootstrap": "pnpm re",
|
||||||
"publish-ci": "npm run build && lerna publish from-package --yes --include-merged-tags --conventional-commits",
|
"publish-ci": "npm run build && lerna publish from-package --yes --include-merged-tags --conventional-commits",
|
||||||
"publish-canary": "npm run build && lerna publish --yes --include-merged-tags --conventional-commits --no-private --no-git-tag-version",
|
"publish-canary": "npm run build && lerna publish --yes --include-merged-tags --conventional-commits --no-private --no-git-tag-version",
|
||||||
"publish-from-packages": "npm run build && lerna publish from-package --yes --include-merged-tags --conventional-commits --create-release github",
|
"publish-from-packages": "npm run build && lerna publish from-package --yes --include-merged-tags --conventional-commits --create-release github",
|
||||||
@@ -10,11 +11,13 @@
|
|||||||
"publish-minor": "npm run build && lerna version minor --include-merged-tags --conventional-commits --no-git-tag-version --no-private",
|
"publish-minor": "npm run build && lerna version minor --include-merged-tags --conventional-commits --no-git-tag-version --no-private",
|
||||||
"size": "nx run-many --target=size",
|
"size": "nx run-many --target=size",
|
||||||
"bundlesize": "nx run-many --target=bundlesize",
|
"bundlesize": "nx run-many --target=bundlesize",
|
||||||
"test": "npm run build && nx run-many --target=link && nx run-many --target=test",
|
"test": "nx run-many --target=test",
|
||||||
"cover": "nx run-many --target=cover",
|
"cover": "nx run-many --target=cover",
|
||||||
"build": "nx run-many --target=build",
|
"build": "nx run-many --target=build",
|
||||||
|
"types": "nx run-many --target=types",
|
||||||
"release": "npm run build && changeset publish",
|
"release": "npm run build && changeset publish",
|
||||||
"lint": "nx run-many --target=link && npm run build && nx run-many --target=lint"
|
"lint": "nx run-many --target=lint",
|
||||||
|
"cleanup": "node scripts/cleanup"
|
||||||
},
|
},
|
||||||
"author": {
|
"author": {
|
||||||
"name": "Nikolay Kostyurin <jilizart@gmail.com>",
|
"name": "Nikolay Kostyurin <jilizart@gmail.com>",
|
||||||
@@ -26,14 +29,13 @@
|
|||||||
"@changesets/cli": "2.26.2",
|
"@changesets/cli": "2.26.2",
|
||||||
"@commitlint/cli": "13.2.1",
|
"@commitlint/cli": "13.2.1",
|
||||||
"@commitlint/config-conventional": "13.2.0",
|
"@commitlint/config-conventional": "13.2.0",
|
||||||
"@nrwl/cli": "15.3.3",
|
"@rollup/plugin-commonjs": "25.0.7",
|
||||||
"@rollup/plugin-commonjs": "23.0.2",
|
"@rollup/plugin-node-resolve": "15.2.3",
|
||||||
"@rollup/plugin-node-resolve": "15.0.1",
|
"@rollup/plugin-replace": "5.0.5",
|
||||||
"@rollup/plugin-replace": "5.0.1",
|
"@size-limit/preset-small-lib": "11.0.1",
|
||||||
"@size-limit/preset-small-lib": "6.0.1",
|
"@swc/cli": "0.3.10",
|
||||||
"@swc/cli": "0.1.57",
|
"@swc/core": "1.3.107",
|
||||||
"@swc/core": "1.3.16",
|
"@swc/jest": "0.2.36",
|
||||||
"@swc/jest": "0.2.23",
|
|
||||||
"@testing-library/dom": "9.3.1",
|
"@testing-library/dom": "9.3.1",
|
||||||
"@testing-library/jest-dom": "6.1.2",
|
"@testing-library/jest-dom": "6.1.2",
|
||||||
"bundlesize2": "0.0.31",
|
"bundlesize2": "0.0.31",
|
||||||
@@ -46,18 +48,26 @@
|
|||||||
"eslint-plugin-react": "7.26.1",
|
"eslint-plugin-react": "7.26.1",
|
||||||
"eslint-plugin-react-hooks": "4.6.0",
|
"eslint-plugin-react-hooks": "4.6.0",
|
||||||
"husky": "7.0.2",
|
"husky": "7.0.2",
|
||||||
"jest": "29.5.0",
|
"jest": "29.7.0",
|
||||||
"jest-environment-jsdom": "29.6.4",
|
"jest-environment-jsdom": "29.7.0",
|
||||||
"lerna": "6.0.3",
|
"lerna": "7.4.1",
|
||||||
"lint-staged": "11.2.3",
|
"lint-staged": "11.2.3",
|
||||||
"microtime": "3.0.0",
|
"microtime": "3.0.0",
|
||||||
"nx": "15.3.3",
|
"nx": "18.3.3",
|
||||||
"posthtml-render": "^3.0.0",
|
"posthtml-render": "3.0.0",
|
||||||
"rimraf": "^3.0.2",
|
"rimraf": "5.0.5",
|
||||||
"rollup": "3.3.0",
|
"rollup": "4.1.5",
|
||||||
"rollup-plugin-gzip": "3.1.0",
|
"rollup-plugin-gzip": "3.1.0",
|
||||||
"rollup-plugin-swc3": "0.7.0",
|
"rollup-plugin-swc3": "0.11.0",
|
||||||
"size-limit": "6.0.1"
|
"size-limit": "11.0.1",
|
||||||
|
"typescript": "5.1.6",
|
||||||
|
"@types/node": "20.4.5",
|
||||||
|
"@types/jest": "29.5.3",
|
||||||
|
"@types/react": "18.2.18",
|
||||||
|
"@nx/eslint": "18.3.3",
|
||||||
|
"@nx/rollup": "18.3.3",
|
||||||
|
"@nx/jest": "18.3.3",
|
||||||
|
"@bbob/scripts": "*"
|
||||||
},
|
},
|
||||||
"publishConfig": {
|
"publishConfig": {
|
||||||
"access": "public",
|
"access": "public",
|
||||||
@@ -69,15 +79,18 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"pkgTasks": {
|
"pkgTasks": {
|
||||||
"build-commonjs": "@/cross-env BABEL_ENV=commonjs NODE_ENV=production @/swc --config-file ../../.swcrc-commonjs.json --out-dir lib src",
|
"build:commonjs": "@/cross-env BABEL_ENV=commonjs NODE_ENV=production @/swc ./src/ --config-file ../../.swcrc-commonjs.json --out-dir lib --strip-leading-paths",
|
||||||
"build-es": "@/cross-env BABEL_ENV=es NODE_ENV=production @/swc --config-file ../../.swcrc.json --out-dir es src",
|
"build:es": "@/cross-env BABEL_ENV=es NODE_ENV=production @/swc ./src/ --config-file ../../.swcrc.json --out-dir es --strip-leading-paths",
|
||||||
"build-umd": "@/cross-env BABEL_ENV=rollup NODE_ENV=production @/rollup --config ../../rollup.config.mjs",
|
"build:umd": "@/cross-env BABEL_ENV=rollup NODE_ENV=production @/rollup --config ../../rollup.config.mjs",
|
||||||
|
"build": "npm run build:es && npm run build:commonjs && npm run build:umd",
|
||||||
"test": "@/jest",
|
"test": "@/jest",
|
||||||
"cover": "@/jest --config ../../jest.config.js --coverage .",
|
"cover": "@/jest --coverage .",
|
||||||
"lint": "@/eslint .",
|
"lint": "@/eslint .",
|
||||||
"bundlesize": "npm run build && @/cross-env NODE_ENV=production @/bundlesize ."
|
"types": "@/tsc",
|
||||||
|
"bundlesize": "npm run build && @/cross-env NODE_ENV=production @/bundlesize .",
|
||||||
|
"size": "npm run build && @/cross-env NODE_ENV=production @/size-limit ."
|
||||||
},
|
},
|
||||||
"packageManager": "pnpm@8.7.6",
|
"packageManager": "pnpm@8.15.1",
|
||||||
"pnpm": {
|
"pnpm": {
|
||||||
"overrides": {
|
"overrides": {
|
||||||
"semver@>=7.0.0 <7.5.2": ">=7.5.2",
|
"semver@>=7.0.0 <7.5.2": ">=7.5.2",
|
||||||
@@ -100,5 +113,8 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"nx": {
|
||||||
|
"includedScripts": []
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,2 +1,7 @@
|
|||||||
coverage
|
coverage
|
||||||
dist
|
dist
|
||||||
|
lib
|
||||||
|
es
|
||||||
|
types
|
||||||
|
test/*.d.ts
|
||||||
|
test/*.map
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
package-lock.json
|
pnpm-lock.yaml
|
||||||
coverage
|
coverage
|
||||||
src
|
src
|
||||||
dist
|
!dist
|
||||||
!lib
|
!lib
|
||||||
|
!es
|
||||||
|
*.test.js
|
||||||
|
|||||||
@@ -1,4 +1,15 @@
|
|||||||
#@bbob/cli
|
# @bbob/cli
|
||||||
|
[](https://packagephobia.now.sh/result?p=@bbob/html) [](https://snyk.io/test/github/JiLiZART/bbob?targetFile=packages%2Fbbob-cli%2Fpackage.json)
|
||||||
|
|
||||||
Convert BBCode to HTML
|
> Converts bbcode string to html
|
||||||
|
|
||||||
|
```shell
|
||||||
|
npm i -g @bbob/cli
|
||||||
|
```
|
||||||
|
|
||||||
|
```shell
|
||||||
|
echo "[i]Text[/i]" | bbob
|
||||||
|
|
||||||
|
# prints
|
||||||
|
<span style="font-style: italic;">Text</span>
|
||||||
|
```
|
||||||
|
|||||||
@@ -1,3 +0,0 @@
|
|||||||
#!/usr/bin/env node
|
|
||||||
|
|
||||||
require('../lib/cli');
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
#!/usr/bin/env node
|
|
||||||
|
|
||||||
require('../lib/cli');
|
|
||||||
@@ -1,25 +0,0 @@
|
|||||||
const fs = require('fs');
|
|
||||||
const program = require('commander');
|
|
||||||
const { version } = require('../package.json');
|
|
||||||
|
|
||||||
program
|
|
||||||
.version(version)
|
|
||||||
.parse(process.argv);
|
|
||||||
|
|
||||||
const options = {};
|
|
||||||
|
|
||||||
// eslint-disable-next-line no-unused-vars
|
|
||||||
function readFile(filename, encoding, callback) {
|
|
||||||
if (options.file === '-') {
|
|
||||||
// read from stdin
|
|
||||||
const chunks = [];
|
|
||||||
|
|
||||||
process.stdin.on('data', (chunk) => {
|
|
||||||
chunks.push(chunk);
|
|
||||||
});
|
|
||||||
|
|
||||||
process.stdin.on('end', () => callback(null, Buffer.concat(chunks).toString(encoding)));
|
|
||||||
} else {
|
|
||||||
fs.readFile(filename, encoding, callback);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -2,17 +2,30 @@
|
|||||||
"name": "@bbob/cli",
|
"name": "@bbob/cli",
|
||||||
"version": "3.0.2",
|
"version": "3.0.2",
|
||||||
"description": "Comand line bbcode parser",
|
"description": "Comand line bbcode parser",
|
||||||
"main": "lib/cli.js",
|
|
||||||
"bin": {
|
"bin": {
|
||||||
"bbob": "bin/bbob"
|
"bbob": "lib/cli.js"
|
||||||
},
|
},
|
||||||
"directories": {
|
"main": "lib/index.js",
|
||||||
"lib": "lib"
|
"module": "es/index.js",
|
||||||
|
"jsnext:main": "es/index.js",
|
||||||
|
"browser": "dist/index.js",
|
||||||
|
"browserName": "BbobCli",
|
||||||
|
"types": "types/index.d.ts",
|
||||||
|
"exports": {
|
||||||
|
".": {
|
||||||
|
"types": "./types/index.d.ts",
|
||||||
|
"import": "./es/index.js",
|
||||||
|
"require": "./lib/index.js",
|
||||||
|
"browser": "./dist/index.min.js",
|
||||||
|
"umd": "./dist/index.min.js"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@bbob/html": "workspace:*",
|
"@bbob/html": "*",
|
||||||
"@bbob/preset-html5": "workspace:*",
|
"@bbob/preset-html5": "*"
|
||||||
"commander": "^2.15.1"
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@types/node": "20.11.30"
|
||||||
},
|
},
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
@@ -29,9 +42,16 @@
|
|||||||
},
|
},
|
||||||
"homepage": "https://github.com/JiLiZART/bbob#readme",
|
"homepage": "https://github.com/JiLiZART/bbob#readme",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"test": "../../scripts/pkg-task test",
|
"build:commonjs": "pkg-task",
|
||||||
"cover": "../../scripts/pkg-task cover",
|
"build:es": "pkg-task",
|
||||||
"lint": "../../scripts/pkg-task lint"
|
"build:umd": "pkg-task",
|
||||||
|
"build": "pkg-task",
|
||||||
|
"test": "pkg-task",
|
||||||
|
"lint": "pkg-task",
|
||||||
|
"size": "pkg-task",
|
||||||
|
"bundlesize": "pkg-task",
|
||||||
|
"types": "pkg-task",
|
||||||
|
"prepublishOnly": "npm run build"
|
||||||
},
|
},
|
||||||
"author": "Nikolay Kostyurin <jilizart@gmail.com>",
|
"author": "Nikolay Kostyurin <jilizart@gmail.com>",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
|||||||
Executable
+4
@@ -0,0 +1,4 @@
|
|||||||
|
#!/usr/bin/env node
|
||||||
|
import { run } from './index';
|
||||||
|
|
||||||
|
run(process.stdin, process.stdout);
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
import html from '@bbob/html'
|
||||||
|
import presetHTML5 from '@bbob/preset-html5'
|
||||||
|
|
||||||
|
export function run(stdin = process.stdin, stdout = process.stdout) {
|
||||||
|
stdin.setEncoding('utf8');
|
||||||
|
|
||||||
|
let inputData = '';
|
||||||
|
|
||||||
|
// Read data from stdin
|
||||||
|
stdin.on('readable', () => {
|
||||||
|
const chunk = stdin.read();
|
||||||
|
|
||||||
|
if (chunk !== null) {
|
||||||
|
inputData += chunk;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Once there's no more data to read, reverse the input and write it to stdout
|
||||||
|
stdin.on('end', () => {
|
||||||
|
const reversedData = html(inputData, presetHTML5()).toString();
|
||||||
|
|
||||||
|
stdout.write(reversedData);
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
describe('CLI Interface', () => {
|
|
||||||
test('read from file', () => {
|
|
||||||
|
|
||||||
})
|
|
||||||
});
|
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
const { execSync } = require('child_process');
|
||||||
|
const path = require('path');
|
||||||
|
|
||||||
|
const pathToBin = path.resolve(__dirname, '../lib/cli.js')
|
||||||
|
|
||||||
|
describe('@bbob/cli', () => {
|
||||||
|
test('simple string', () => {
|
||||||
|
const stdout = execSync(`echo "hello" | ${pathToBin}`);
|
||||||
|
|
||||||
|
expect(String(stdout).trim()).toBe('hello');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('string with bbcodes', () => {
|
||||||
|
const stdout = execSync(`echo "[i]Text[/i]" | ${pathToBin}`);
|
||||||
|
|
||||||
|
expect(String(stdout).trim()).toBe('<span style="font-style: italic;">Text</span>');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('empty string', () => {
|
||||||
|
const stdout = execSync(`echo "" | ${pathToBin}`);
|
||||||
|
|
||||||
|
expect(String(stdout).trim()).toBe('');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('multiline string', () => {
|
||||||
|
const stdout = execSync(`echo "[i]Text[/i]\n[i]Text[/i]" | ${pathToBin}`);
|
||||||
|
|
||||||
|
expect(String(stdout).trim()).toBe('<span style="font-style: italic;">Text</span>\n<span style="font-style: italic;">Text</span>');
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
{
|
||||||
|
"extends": "../../tsconfig.json",
|
||||||
|
"compilerOptions": {
|
||||||
|
"baseUrl": ".",
|
||||||
|
"outDir": "./types"
|
||||||
|
},
|
||||||
|
"include": [
|
||||||
|
"./src/*"
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -2,3 +2,6 @@ coverage
|
|||||||
dist
|
dist
|
||||||
lib
|
lib
|
||||||
es
|
es
|
||||||
|
types
|
||||||
|
test/*.d.ts
|
||||||
|
test/*.map
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
package-lock.json
|
pnpm-lock.yaml
|
||||||
coverage
|
coverage
|
||||||
src
|
src
|
||||||
!dist
|
!dist
|
||||||
|
|||||||
@@ -20,13 +20,24 @@
|
|||||||
"core"
|
"core"
|
||||||
],
|
],
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@bbob/parser": "workspace:*"
|
"@bbob/parser": "*",
|
||||||
|
"@bbob/plugin-helper": "*"
|
||||||
},
|
},
|
||||||
"main": "lib/index.js",
|
"main": "lib/index.js",
|
||||||
"module": "es/index.js",
|
"module": "es/index.js",
|
||||||
"jsnext:main": "es/index.js",
|
"jsnext:main": "es/index.js",
|
||||||
"browser": "dist/index.js",
|
"browser": "dist/index.js",
|
||||||
"browserName": "BbobCore",
|
"browserName": "BbobCore",
|
||||||
|
"types": "types/index.d.ts",
|
||||||
|
"exports": {
|
||||||
|
".": {
|
||||||
|
"types": "./types/index.d.ts",
|
||||||
|
"import": "./es/index.js",
|
||||||
|
"require": "./lib/index.js",
|
||||||
|
"browser": "./dist/index.min.js",
|
||||||
|
"umd": "./dist/index.min.js"
|
||||||
|
}
|
||||||
|
},
|
||||||
"homepage": "https://github.com/JiLiZART/bbob",
|
"homepage": "https://github.com/JiLiZART/bbob",
|
||||||
"author": "Nikolay Kostyurin <jilizart@gmail.com>",
|
"author": "Nikolay Kostyurin <jilizart@gmail.com>",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
@@ -38,19 +49,22 @@
|
|||||||
"url": "git://github.com/JiLiZART/bbob.git"
|
"url": "git://github.com/JiLiZART/bbob.git"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build:commonjs": "../../scripts/pkg-task build-commonjs",
|
"build:commonjs": "pkg-task",
|
||||||
"build:es": "../../scripts/pkg-task build-es",
|
"build:es": "pkg-task",
|
||||||
"build:umd": "../../scripts/pkg-task build-umd",
|
"build:umd": "pkg-task",
|
||||||
"build": "npm run build:commonjs && npm run build:es && npm run build:umd",
|
"build": "pkg-task",
|
||||||
"test": "../../scripts/pkg-task test",
|
"test": "pkg-task",
|
||||||
"cover": "../../scripts/pkg-task cover",
|
"cover": "pkg-task",
|
||||||
"lint": "../../scripts/pkg-task lint",
|
"lint": "pkg-task",
|
||||||
"size": "../../scripts/pkg-task size",
|
"size": "pkg-task",
|
||||||
"bundlesize": "../../scripts/pkg-task bundlesize"
|
"bundlesize": "pkg-task",
|
||||||
|
"types": "pkg-task",
|
||||||
|
"prepublishOnly": "npm run build"
|
||||||
},
|
},
|
||||||
"size-limit": [
|
"size-limit": [
|
||||||
{
|
{
|
||||||
"path": "lib/index.js"
|
"path": "./dist/index.min.js",
|
||||||
|
"limit": "4.5 KB"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"bundlesize": [
|
"bundlesize": [
|
||||||
|
|||||||
Generated
+3
@@ -8,3 +8,6 @@ dependencies:
|
|||||||
'@bbob/parser':
|
'@bbob/parser':
|
||||||
specifier: workspace:*
|
specifier: workspace:*
|
||||||
version: link:../bbob-parser
|
version: link:../bbob-parser
|
||||||
|
'@bbob/plugin-helper':
|
||||||
|
specifier: workspace:*
|
||||||
|
version: link:../bbob-plugin-helper
|
||||||
|
|||||||
@@ -0,0 +1,10 @@
|
|||||||
|
|
||||||
|
let C1 = 'C1'
|
||||||
|
let C2 = 'C2'
|
||||||
|
|
||||||
|
if (process.env.NODE_ENV !== 'production') {
|
||||||
|
C1 = '"parser" is not a function, please pass to "process(input, { parser })" right function'
|
||||||
|
C2 = '"render" function not defined, please pass to "process(input, { render })"'
|
||||||
|
}
|
||||||
|
|
||||||
|
export { C1, C2 }
|
||||||
@@ -1,62 +0,0 @@
|
|||||||
import { parse } from '@bbob/parser';
|
|
||||||
import { iterate, match } from './utils';
|
|
||||||
|
|
||||||
function walk(cb) {
|
|
||||||
return iterate(this, cb);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function bbob(plugs) {
|
|
||||||
const plugins = typeof plugs === 'function' ? [plugs] : plugs || [];
|
|
||||||
|
|
||||||
let options = {
|
|
||||||
skipParse: false,
|
|
||||||
};
|
|
||||||
|
|
||||||
return {
|
|
||||||
process(input, opts) {
|
|
||||||
options = opts || {};
|
|
||||||
|
|
||||||
const parseFn = options.parser || parse;
|
|
||||||
const renderFn = options.render;
|
|
||||||
const data = options.data || null;
|
|
||||||
|
|
||||||
if (typeof parseFn !== 'function') {
|
|
||||||
throw new Error('"parser" is not a function, please pass to "process(input, { parser })" right function');
|
|
||||||
}
|
|
||||||
|
|
||||||
let tree = options.skipParse
|
|
||||||
? input || []
|
|
||||||
: parseFn(input, options);
|
|
||||||
|
|
||||||
// raw tree before modification with plugins
|
|
||||||
const raw = tree;
|
|
||||||
|
|
||||||
tree.messages = [];
|
|
||||||
tree.options = options;
|
|
||||||
tree.walk = walk;
|
|
||||||
tree.match = match;
|
|
||||||
|
|
||||||
plugins.forEach((plugin) => {
|
|
||||||
tree = plugin(tree, {
|
|
||||||
parse: parseFn,
|
|
||||||
render: renderFn,
|
|
||||||
iterate,
|
|
||||||
match,
|
|
||||||
data,
|
|
||||||
}) || tree;
|
|
||||||
});
|
|
||||||
|
|
||||||
return {
|
|
||||||
get html() {
|
|
||||||
if (typeof renderFn !== 'function') {
|
|
||||||
throw new Error('"render" function not defined, please pass to "process(input, { render })"');
|
|
||||||
}
|
|
||||||
return renderFn(tree, tree.options);
|
|
||||||
},
|
|
||||||
tree,
|
|
||||||
raw,
|
|
||||||
messages: tree.messages,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,76 @@
|
|||||||
|
import { parse } from '@bbob/parser';
|
||||||
|
import { iterate, match } from './utils';
|
||||||
|
import { C1, C2 } from './errors'
|
||||||
|
|
||||||
|
import type { IterateCallback } from './utils';
|
||||||
|
import type { NodeContent, PartialNodeContent } from "@bbob/plugin-helper";
|
||||||
|
import type { BBobCore, BBobCoreOptions, BBobCoreTagNodeTree, BBobPlugins } from "./types";
|
||||||
|
|
||||||
|
export * from './types'
|
||||||
|
|
||||||
|
export function createTree<Options extends BBobCoreOptions = BBobCoreOptions>(tree: NodeContent[], options: Options) {
|
||||||
|
const extendedTree = tree as BBobCoreTagNodeTree
|
||||||
|
|
||||||
|
extendedTree.messages = [...(extendedTree.messages || [])]
|
||||||
|
extendedTree.options = {...options, ...extendedTree.options}
|
||||||
|
extendedTree.walk = function walkNodes(cb: IterateCallback<NodeContent>) {
|
||||||
|
return iterate(this, cb);
|
||||||
|
}
|
||||||
|
extendedTree.match = function matchNodes(expr: PartialNodeContent | PartialNodeContent[], cb: IterateCallback<NodeContent>) {
|
||||||
|
return match(this, expr, cb)
|
||||||
|
}
|
||||||
|
|
||||||
|
return extendedTree
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function bbob<InputValue = string | NodeContent[], Options extends BBobCoreOptions = BBobCoreOptions>(
|
||||||
|
plugs?: BBobPlugins
|
||||||
|
): BBobCore<InputValue, Options> {
|
||||||
|
const plugins = typeof plugs === 'function' ? [plugs] : plugs || [];
|
||||||
|
const mockRender = () => ""
|
||||||
|
|
||||||
|
return {
|
||||||
|
process(input, opts) {
|
||||||
|
const options = opts || { skipParse: false, parser: parse, render: mockRender, data: null } as BBobCoreOptions
|
||||||
|
const parseFn = options.parser || parse;
|
||||||
|
const renderFn = options.render;
|
||||||
|
const data = options.data || null;
|
||||||
|
|
||||||
|
if (typeof parseFn !== 'function') {
|
||||||
|
throw new Error(C1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// raw tree before modification with plugins
|
||||||
|
const raw = options.skipParse && Array.isArray(input) ? input : parseFn(input as string, options);
|
||||||
|
let tree = options.skipParse && Array.isArray(input) ? createTree((input || []) as NodeContent[], options) : createTree(raw, options)
|
||||||
|
|
||||||
|
for (let idx = 0; idx < plugins.length; idx++) {
|
||||||
|
const plugin = plugins[idx]
|
||||||
|
|
||||||
|
if (typeof plugin === 'function' && renderFn) {
|
||||||
|
const newTree = plugin(tree, {
|
||||||
|
parse: parseFn,
|
||||||
|
render: renderFn,
|
||||||
|
iterate,
|
||||||
|
data,
|
||||||
|
})
|
||||||
|
|
||||||
|
tree = createTree(newTree || tree, options)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
get html() {
|
||||||
|
if (typeof renderFn !== 'function') {
|
||||||
|
throw new Error(C2);
|
||||||
|
}
|
||||||
|
|
||||||
|
return renderFn(tree, tree.options);
|
||||||
|
},
|
||||||
|
tree,
|
||||||
|
raw,
|
||||||
|
messages: tree.messages,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -0,0 +1,57 @@
|
|||||||
|
import type { ParseOptions, TagNode } from "@bbob/parser";
|
||||||
|
import type {
|
||||||
|
NodeContent,
|
||||||
|
PartialNodeContent,
|
||||||
|
TagNodeTree,
|
||||||
|
} from "@bbob/plugin-helper";
|
||||||
|
import type { IterateCallback, iterate } from "./utils";
|
||||||
|
|
||||||
|
export interface BBobCoreOptions<
|
||||||
|
Data = unknown | null,
|
||||||
|
Options extends ParseOptions = ParseOptions
|
||||||
|
> extends ParseOptions {
|
||||||
|
skipParse?: boolean;
|
||||||
|
parser?: (source: string, options?: Options) => TagNode[];
|
||||||
|
render?: (ast: TagNodeTree, options?: Options) => string;
|
||||||
|
data?: Data;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface BbobPluginOptions<
|
||||||
|
Options extends ParseOptions = ParseOptions
|
||||||
|
> {
|
||||||
|
parse: BBobCoreOptions["parser"];
|
||||||
|
render: (ast: TagNodeTree, options?: Options) => string;
|
||||||
|
iterate: typeof iterate;
|
||||||
|
data: unknown | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface BBobPluginFunction {
|
||||||
|
(tree: BBobCoreTagNodeTree, options: BbobPluginOptions): BBobCoreTagNodeTree;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface BBobCore<
|
||||||
|
InputValue = string | TagNode[],
|
||||||
|
Options extends BBobCoreOptions = BBobCoreOptions
|
||||||
|
> {
|
||||||
|
process(
|
||||||
|
input: InputValue,
|
||||||
|
opts?: Options
|
||||||
|
): {
|
||||||
|
readonly html: string;
|
||||||
|
tree: BBobCoreTagNodeTree;
|
||||||
|
raw: TagNode[] | string;
|
||||||
|
messages: unknown[];
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface BBobCoreTagNodeTree extends Array<NodeContent> {
|
||||||
|
messages: unknown[];
|
||||||
|
options: BBobCoreOptions;
|
||||||
|
walk: (cb: IterateCallback<NodeContent>) => BBobCoreTagNodeTree;
|
||||||
|
match: (
|
||||||
|
expression: PartialNodeContent | PartialNodeContent[],
|
||||||
|
cb: IterateCallback<NodeContent>
|
||||||
|
) => BBobCoreTagNodeTree;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type BBobPlugins = BBobPluginFunction | BBobPluginFunction[];
|
||||||
@@ -1,60 +0,0 @@
|
|||||||
/* eslint-disable no-plusplus */
|
|
||||||
const isObj = (value) => (typeof value === 'object');
|
|
||||||
const isBool = (value) => (typeof value === 'boolean');
|
|
||||||
|
|
||||||
export function iterate(t, cb) {
|
|
||||||
const tree = t;
|
|
||||||
|
|
||||||
if (Array.isArray(tree)) {
|
|
||||||
for (let idx = 0; idx < tree.length; idx++) {
|
|
||||||
tree[idx] = iterate(cb(tree[idx]), cb);
|
|
||||||
}
|
|
||||||
} else if (tree && isObj(tree) && tree.content) {
|
|
||||||
iterate(tree.content, cb);
|
|
||||||
}
|
|
||||||
|
|
||||||
return tree;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function same(expected, actual) {
|
|
||||||
if (typeof expected !== typeof actual) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!isObj(expected) || expected === null) {
|
|
||||||
return expected === actual;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Array.isArray(expected)) {
|
|
||||||
return expected.every((exp) => [].some.call(actual, (act) => same(exp, act)));
|
|
||||||
}
|
|
||||||
|
|
||||||
return Object.keys(expected).every((key) => {
|
|
||||||
const ao = actual[key];
|
|
||||||
const eo = expected[key];
|
|
||||||
|
|
||||||
if (isObj(eo) && eo !== null && ao !== null) {
|
|
||||||
return same(eo, ao);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isBool(eo)) {
|
|
||||||
return eo !== (ao === null);
|
|
||||||
}
|
|
||||||
|
|
||||||
return ao === eo;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export function match(expression, cb) {
|
|
||||||
return Array.isArray(expression)
|
|
||||||
? iterate(this, (node) => {
|
|
||||||
for (let idx = 0; idx < expression.length; idx++) {
|
|
||||||
if (same(expression[idx], node)) {
|
|
||||||
return cb(node);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return node;
|
|
||||||
})
|
|
||||||
: iterate(this, (node) => (same(expression, node) ? cb(node) : node));
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,72 @@
|
|||||||
|
/* eslint-disable no-plusplus */
|
||||||
|
const isObj = (value: unknown): value is Record<string, unknown> => (typeof value === 'object' && value !== null);
|
||||||
|
const isBool = (value: unknown): value is boolean => (typeof value === 'boolean');
|
||||||
|
|
||||||
|
export type IterateCallback<Content> = (node: Content) => Content
|
||||||
|
|
||||||
|
export function iterate<Content, Iterable = ArrayLike<Content> | Content>(t: Iterable, cb: IterateCallback<Content>): Iterable {
|
||||||
|
const tree = t;
|
||||||
|
|
||||||
|
if (Array.isArray(tree)) {
|
||||||
|
for (let idx = 0; idx < tree.length; idx++) {
|
||||||
|
tree[idx] = iterate(cb(tree[idx]), cb);
|
||||||
|
}
|
||||||
|
} else if (isObj(tree) && 'content' in tree) {
|
||||||
|
iterate(tree.content, cb);
|
||||||
|
}
|
||||||
|
|
||||||
|
return tree;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function same(expected: unknown, actual: unknown): boolean {
|
||||||
|
if (typeof expected !== typeof actual) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isObj(expected) || expected === null) {
|
||||||
|
return expected === actual;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Array.isArray(expected)) {
|
||||||
|
return expected.every((exp) => [].some.call(actual, (act) => same(exp, act)));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isObj(expected) && isObj(actual)) {
|
||||||
|
return Object.keys(expected).every((key) => {
|
||||||
|
const ao = actual[key];
|
||||||
|
const eo = expected[key];
|
||||||
|
|
||||||
|
if (isObj(eo) && isObj(ao)) {
|
||||||
|
return same(eo, ao);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isBool(eo)) {
|
||||||
|
return eo !== (ao === null);
|
||||||
|
}
|
||||||
|
|
||||||
|
return ao === eo;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
export function match<Content, Iterable = ArrayLike<Content>>(
|
||||||
|
t: Iterable,
|
||||||
|
expression: Content | ArrayLike<Content>,
|
||||||
|
cb: IterateCallback<Content>
|
||||||
|
) {
|
||||||
|
if (Array.isArray(expression)) {
|
||||||
|
return iterate<Content, Iterable>(t, (node) => {
|
||||||
|
for (let idx = 0; idx < expression.length; idx++) {
|
||||||
|
if (same(expression[idx], node)) {
|
||||||
|
return cb(node);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return node;
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return iterate<Content, Iterable>(t, (node) => (same(expression, node) ? cb(node) : node));
|
||||||
|
}
|
||||||
@@ -1,9 +1,10 @@
|
|||||||
import { TagNode } from '@bbob/parser'
|
import { TagNode } from '@bbob/parser'
|
||||||
import core from '../src'
|
import core, { BBobPluginFunction, BBobPlugins } from '../src'
|
||||||
|
import { isTagNode } from "@bbob/plugin-helper";
|
||||||
|
|
||||||
const stringify = val => JSON.stringify(val);
|
const stringify = (val: unknown) => JSON.stringify(val);
|
||||||
|
|
||||||
const process = (plugins, input) => core(plugins).process(input, { render: stringify });
|
const process = (plugins: BBobPlugins, input: string) => core(plugins).process(input, { render: stringify });
|
||||||
|
|
||||||
describe('@bbob/core', () => {
|
describe('@bbob/core', () => {
|
||||||
test('parse bbcode string to ast and html', () => {
|
test('parse bbcode string to ast and html', () => {
|
||||||
@@ -22,17 +23,27 @@ describe('@bbob/core', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test('plugin walk api node', () => {
|
test('plugin walk api node', () => {
|
||||||
const testPlugin = () => (tree) => tree.walk(node => {
|
const testPlugin = () => {
|
||||||
if (node.tag === 'mytag') {
|
|
||||||
node.attrs = {
|
|
||||||
pass: 1
|
|
||||||
};
|
|
||||||
|
|
||||||
node.content.push('Test');
|
const plugin: BBobPluginFunction = (tree) => tree.walk(node => {
|
||||||
}
|
if (isTagNode(node)) {
|
||||||
|
if (node?.tag === 'mytag') {
|
||||||
|
node.attrs = {
|
||||||
|
pass: 1
|
||||||
|
};
|
||||||
|
|
||||||
return node
|
if (Array.isArray(node.content)) {
|
||||||
});
|
node.content.push('Test');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return node
|
||||||
|
});
|
||||||
|
|
||||||
|
return plugin
|
||||||
|
}
|
||||||
|
|
||||||
const res = process([testPlugin()], '[mytag size="15px"]Large Text[/mytag]');
|
const res = process([testPlugin()], '[mytag size="15px"]Large Text[/mytag]');
|
||||||
const ast = res.tree;
|
const ast = res.tree;
|
||||||
@@ -56,13 +67,18 @@ describe('@bbob/core', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test('plugin walk api string', () => {
|
test('plugin walk api string', () => {
|
||||||
const testPlugin = () => (tree) => tree.walk(node => {
|
const testPlugin = () => {
|
||||||
if (node === ':)') {
|
|
||||||
return TagNode.create('test-tag')
|
|
||||||
}
|
|
||||||
|
|
||||||
return node
|
const plugin: BBobPluginFunction = (tree) => tree.walk(node => {
|
||||||
});
|
if (node === ':)') {
|
||||||
|
return TagNode.create('test-tag')
|
||||||
|
}
|
||||||
|
|
||||||
|
return node
|
||||||
|
})
|
||||||
|
|
||||||
|
return plugin
|
||||||
|
};
|
||||||
|
|
||||||
const res = process([testPlugin()], '[mytag]Large Text :)[/mytag]');
|
const res = process([testPlugin()], '[mytag]Large Text :)[/mytag]');
|
||||||
const ast = res.tree;
|
const ast = res.tree;
|
||||||
@@ -89,13 +105,18 @@ describe('@bbob/core', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test('plugin match api', () => {
|
test('plugin match api', () => {
|
||||||
const testPlugin = () => (tree) => tree.match([{ tag: 'mytag1' }, { tag: 'mytag2' }], node => {
|
const testPlugin = () => {
|
||||||
if (node.attrs) {
|
|
||||||
node.attrs['pass'] = 1
|
|
||||||
}
|
|
||||||
|
|
||||||
return node
|
const plugin: BBobPluginFunction = (tree) => tree.match([{ tag: 'mytag1' }, { tag: 'mytag2' }], node => {
|
||||||
});
|
if (isTagNode(node) && node.attrs) {
|
||||||
|
node.attrs['pass'] = 1
|
||||||
|
}
|
||||||
|
|
||||||
|
return node
|
||||||
|
})
|
||||||
|
|
||||||
|
return plugin
|
||||||
|
};
|
||||||
|
|
||||||
const res = process([testPlugin()], `[mytag1 size="15"]Tag1[/mytag1][mytag2 size="16"]Tag2[/mytag2][mytag3]Tag3[/mytag3]`);
|
const res = process([testPlugin()], `[mytag1 size="15"]Tag1[/mytag1][mytag2 size="16"]Tag2[/mytag2][mytag3]Tag3[/mytag3]`);
|
||||||
const ast = res.tree;
|
const ast = res.tree;
|
||||||
@@ -1,7 +1,5 @@
|
|||||||
import { iterate, match, same } from '../src/utils';
|
import { iterate, match, same } from '../src/utils';
|
||||||
|
import { isTagNode } from "@bbob/plugin-helper";
|
||||||
const stringify = val => JSON.stringify(val);
|
|
||||||
|
|
||||||
|
|
||||||
describe('@bbob/core utils', () => {
|
describe('@bbob/core utils', () => {
|
||||||
test('iterate', () => {
|
test('iterate', () => {
|
||||||
@@ -14,7 +12,12 @@ describe('@bbob/core utils', () => {
|
|||||||
}];
|
}];
|
||||||
|
|
||||||
const resultArr = iterate(testArr, node => {
|
const resultArr = iterate(testArr, node => {
|
||||||
node.pass = 1;
|
if (typeof node === 'object' && node !== null) {
|
||||||
|
return {
|
||||||
|
...node,
|
||||||
|
pass: 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return node;
|
return node;
|
||||||
});
|
});
|
||||||
@@ -31,7 +34,7 @@ describe('@bbob/core utils', () => {
|
|||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
expect(stringify(resultArr)).toEqual(stringify(expected));
|
expect(resultArr).toEqual(expected);
|
||||||
});
|
});
|
||||||
test('match', () => {
|
test('match', () => {
|
||||||
const testArr = [
|
const testArr = [
|
||||||
@@ -43,24 +46,25 @@ describe('@bbob/core utils', () => {
|
|||||||
{ tag: 'mytag6', six: 1 },
|
{ tag: 'mytag6', six: 1 },
|
||||||
];
|
];
|
||||||
|
|
||||||
testArr.match = match;
|
const resultArr = match(testArr, [{ tag: 'mytag1' }, { tag: 'mytag2' }], node => {
|
||||||
|
if (isTagNode(node)) {
|
||||||
const resultArr = testArr.match([{ tag: 'mytag1' }, { tag: 'mytag2' }], node => {
|
node.attrs = node.attrs || {}
|
||||||
node.pass = 1;
|
node.attrs.pass = 1
|
||||||
|
}
|
||||||
|
|
||||||
return node;
|
return node;
|
||||||
});
|
});
|
||||||
|
|
||||||
const expected = [
|
const expected = [
|
||||||
{ tag: 'mytag1', one: 1, pass: 1 },
|
{ tag: 'mytag1', one: 1, attrs: { pass: 1 } },
|
||||||
{ tag: 'mytag2', two: 1, pass: 1 },
|
{ tag: 'mytag2', two: 1, attrs: { pass: 1 } },
|
||||||
{ tag: 'mytag3', three: 1 },
|
{ tag: 'mytag3', three: 1 },
|
||||||
{ tag: 'mytag4', four: 1 },
|
{ tag: 'mytag4', four: 1 },
|
||||||
{ tag: 'mytag5', five: 1 },
|
{ tag: 'mytag5', five: 1 },
|
||||||
{ tag: 'mytag6', six: 1 },
|
{ tag: 'mytag6', six: 1 },
|
||||||
];
|
];
|
||||||
|
|
||||||
expect(stringify(resultArr)).toEqual(stringify(expected))
|
expect(resultArr).toEqual(expected)
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('same', () => {
|
describe('same', () => {
|
||||||
@@ -79,5 +83,8 @@ describe('@bbob/core utils', () => {
|
|||||||
test('same object', () => {
|
test('same object', () => {
|
||||||
expect(same({ foo: true, bar: 'test' }, { foo: true, bar: 'test', ext: true })).toBe(true)
|
expect(same({ foo: true, bar: 'test' }, { foo: true, bar: 'test', ext: true })).toBe(true)
|
||||||
})
|
})
|
||||||
|
test('same string', () => {
|
||||||
|
expect(same('bar', 'bar')).toBe(true)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
{
|
||||||
|
"extends": "../../tsconfig.json",
|
||||||
|
"compilerOptions": {
|
||||||
|
"baseUrl": ".",
|
||||||
|
"outDir": "./types"
|
||||||
|
},
|
||||||
|
"include": [
|
||||||
|
"./src/**/*"
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -2,3 +2,7 @@ coverage
|
|||||||
dist
|
dist
|
||||||
lib
|
lib
|
||||||
es
|
es
|
||||||
|
types
|
||||||
|
test/*.d.ts
|
||||||
|
test/*.map
|
||||||
|
tsconfig.tsbuildinfo
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
package-lock.json
|
pnpm-lock.yaml
|
||||||
coverage
|
coverage
|
||||||
src
|
src
|
||||||
!dist
|
!dist
|
||||||
|
|||||||
@@ -9,14 +9,24 @@
|
|||||||
"bbob"
|
"bbob"
|
||||||
],
|
],
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@bbob/core": "workspace:*",
|
"@bbob/core": "*",
|
||||||
"@bbob/plugin-helper": "workspace:*"
|
"@bbob/plugin-helper": "*"
|
||||||
},
|
},
|
||||||
"main": "lib/index.js",
|
"main": "lib/index.js",
|
||||||
"module": "es/index.js",
|
"module": "es/index.js",
|
||||||
"jsnext:main": "es/index.js",
|
"jsnext:main": "es/index.js",
|
||||||
"browser": "dist/index.js",
|
"browser": "dist/index.js",
|
||||||
"browserName": "BbobHtml",
|
"browserName": "BbobHtml",
|
||||||
|
"types": "types/index.d.ts",
|
||||||
|
"exports": {
|
||||||
|
".": {
|
||||||
|
"types": "./types/index.d.ts",
|
||||||
|
"import": "./es/index.js",
|
||||||
|
"require": "./lib/index.js",
|
||||||
|
"browser": "./dist/index.min.js",
|
||||||
|
"umd": "./dist/index.min.js"
|
||||||
|
}
|
||||||
|
},
|
||||||
"homepage": "https://github.com/JiLiZART/bbob",
|
"homepage": "https://github.com/JiLiZART/bbob",
|
||||||
"author": "Nikolay Kostyurin <jilizart@gmail.com>",
|
"author": "Nikolay Kostyurin <jilizart@gmail.com>",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
@@ -28,19 +38,22 @@
|
|||||||
"url": "git://github.com/JiLiZART/bbob.git"
|
"url": "git://github.com/JiLiZART/bbob.git"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build:commonjs": "../../scripts/pkg-task build-commonjs",
|
"build:commonjs": "pkg-task",
|
||||||
"build:es": "../../scripts/pkg-task build-es",
|
"build:es": "pkg-task",
|
||||||
"build:umd": "../../scripts/pkg-task build-umd",
|
"build:umd": "pkg-task",
|
||||||
"build": "npm run build:commonjs && npm run build:es && npm run build:umd",
|
"build": "pkg-task",
|
||||||
"test": "../../scripts/pkg-task test",
|
"test": "pkg-task",
|
||||||
"cover": "../../scripts/pkg-task cover",
|
"cover": "pkg-task",
|
||||||
"lint": "../../scripts/pkg-task lint",
|
"lint": "pkg-task",
|
||||||
"size": "../../scripts/pkg-task size",
|
"size": "pkg-task",
|
||||||
"bundlesize": "../../scripts/pkg-task bundlesize"
|
"bundlesize": "pkg-task",
|
||||||
|
"types": "pkg-task",
|
||||||
|
"prepublishOnly": "npm run build"
|
||||||
},
|
},
|
||||||
"size-limit": [
|
"size-limit": [
|
||||||
{
|
{
|
||||||
"path": "lib/index.js"
|
"path": "./dist/index.min.js",
|
||||||
|
"size": "4.6 KB"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"bundlesize": [
|
"bundlesize": [
|
||||||
|
|||||||
@@ -1,47 +0,0 @@
|
|||||||
import core from '@bbob/core';
|
|
||||||
import { attrsToString } from '@bbob/plugin-helper';
|
|
||||||
|
|
||||||
const SELFCLOSE_END_TAG = '/>';
|
|
||||||
const CLOSE_START_TAG = '</';
|
|
||||||
const START_TAG = '<';
|
|
||||||
const END_TAG = '>';
|
|
||||||
|
|
||||||
const renderNode = (node, { stripTags = false }) => {
|
|
||||||
if (!node) return '';
|
|
||||||
const type = typeof node;
|
|
||||||
|
|
||||||
if (type === 'string' || type === 'number') {
|
|
||||||
return node;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (type === 'object') {
|
|
||||||
if (stripTags === true) {
|
|
||||||
// eslint-disable-next-line no-use-before-define
|
|
||||||
return renderNodes(node.content, { stripTags });
|
|
||||||
}
|
|
||||||
|
|
||||||
if (node.content === null) {
|
|
||||||
return [START_TAG, node.tag, attrsToString(node.attrs), SELFCLOSE_END_TAG].join('');
|
|
||||||
}
|
|
||||||
|
|
||||||
// eslint-disable-next-line no-use-before-define
|
|
||||||
return [START_TAG, node.tag, attrsToString(node.attrs), END_TAG, renderNodes(node.content), CLOSE_START_TAG, node.tag, END_TAG].join('');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Array.isArray(node)) {
|
|
||||||
// eslint-disable-next-line no-use-before-define
|
|
||||||
return renderNodes(node, { stripTags });
|
|
||||||
}
|
|
||||||
|
|
||||||
return '';
|
|
||||||
};
|
|
||||||
|
|
||||||
const renderNodes = (nodes, { stripTags = false } = {}) => []
|
|
||||||
.concat(nodes)
|
|
||||||
.reduce((r, node) => r + renderNode(node, { stripTags }), '');
|
|
||||||
|
|
||||||
const toHTML = (source, plugins, options) => core(plugins)
|
|
||||||
.process(source, { ...options, render: renderNodes }).html;
|
|
||||||
|
|
||||||
export const render = renderNodes;
|
|
||||||
export default toHTML;
|
|
||||||
@@ -0,0 +1,61 @@
|
|||||||
|
import core, { BBobCoreOptions, BBobPlugins } from '@bbob/core';
|
||||||
|
import { attrsToString, isTagNode, TagNode, TagNodeTree } from '@bbob/plugin-helper';
|
||||||
|
|
||||||
|
const SELFCLOSE_END_TAG = '/>';
|
||||||
|
const CLOSE_START_TAG = '</';
|
||||||
|
const START_TAG = '<';
|
||||||
|
const END_TAG = '>';
|
||||||
|
|
||||||
|
export type BBobHTMLOptions = {
|
||||||
|
stripTags?: boolean
|
||||||
|
} & BBobCoreOptions
|
||||||
|
|
||||||
|
function renderNode(node?: TagNodeTree, options?: BBobHTMLOptions): string {
|
||||||
|
const { stripTags = false } = options || {}
|
||||||
|
|
||||||
|
if (typeof node === 'undefined' || node === null) {
|
||||||
|
return ''
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof node === 'string' || typeof node === 'number') {
|
||||||
|
return String(node);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Array.isArray(node)) {
|
||||||
|
return render(node, options);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isTagNode(node)) {
|
||||||
|
if (stripTags) {
|
||||||
|
return render(node.content, options);
|
||||||
|
}
|
||||||
|
|
||||||
|
const attrs = attrsToString(node.attrs)
|
||||||
|
|
||||||
|
if (node.content === null) {
|
||||||
|
return START_TAG + node.tag + attrs + SELFCLOSE_END_TAG
|
||||||
|
}
|
||||||
|
|
||||||
|
return START_TAG + node.tag + attrs + END_TAG + render(node.content, options) + CLOSE_START_TAG + node.tag + END_TAG;
|
||||||
|
}
|
||||||
|
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
export function render(nodes: TagNodeTree, options?: BBobHTMLOptions): string {
|
||||||
|
if (Array.isArray(nodes)) {
|
||||||
|
return nodes.reduce<string>((r, node) => r + renderNode(node, options), '')
|
||||||
|
}
|
||||||
|
|
||||||
|
if (nodes) {
|
||||||
|
return renderNode(nodes, options)
|
||||||
|
}
|
||||||
|
|
||||||
|
return ''
|
||||||
|
}
|
||||||
|
|
||||||
|
export function html<InputValue = string | TagNode[]>(source: InputValue, plugins: BBobPlugins, options?: BBobHTMLOptions) {
|
||||||
|
return core<InputValue, BBobHTMLOptions>(plugins).process(source, { ...options, render: render }).html
|
||||||
|
}
|
||||||
|
|
||||||
|
export default html;
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
import toHTML, {render} from '../src';
|
import toHTML, { BBobHTMLOptions, render } from '../src';
|
||||||
|
|
||||||
const process = (input, params) => toHTML(input, [], params);
|
const process = (input: string, params?: BBobHTMLOptions) => toHTML(input, [], params);
|
||||||
|
|
||||||
describe('@bbob/html', () => {
|
describe('@bbob/html', () => {
|
||||||
test('render bbcode tag with single param as html tag', () => {
|
test('render bbcode tag with single param as html tag', () => {
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
{
|
||||||
|
"extends": "../../tsconfig.json",
|
||||||
|
"compilerOptions": {
|
||||||
|
"baseUrl": ".",
|
||||||
|
"outDir": "./types"
|
||||||
|
},
|
||||||
|
"include": [
|
||||||
|
"./src/**/*"
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -2,3 +2,7 @@ coverage
|
|||||||
dist
|
dist
|
||||||
lib
|
lib
|
||||||
es
|
es
|
||||||
|
types
|
||||||
|
test/*.d.ts
|
||||||
|
test/*.map
|
||||||
|
tsconfig.tsbuildinfo
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
package-lock.json
|
pnpm-lock.yaml
|
||||||
coverage
|
coverage
|
||||||
src
|
src
|
||||||
!dist
|
!dist
|
||||||
|
|||||||
@@ -12,14 +12,59 @@
|
|||||||
"array",
|
"array",
|
||||||
"parse"
|
"parse"
|
||||||
],
|
],
|
||||||
|
"files": [
|
||||||
|
"dist",
|
||||||
|
"lib",
|
||||||
|
"src",
|
||||||
|
"es",
|
||||||
|
"types"
|
||||||
|
],
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@bbob/plugin-helper": "workspace:*"
|
"@bbob/plugin-helper": "*"
|
||||||
},
|
},
|
||||||
"main": "lib/index.js",
|
"main": "lib/index.js",
|
||||||
"module": "es/index.js",
|
"module": "es/index.js",
|
||||||
"jsnext:main": "es/index.js",
|
"jsnext:main": "es/index.js",
|
||||||
"browser": "dist/index.js",
|
"browser": "dist/index.js",
|
||||||
"browserName": "BbobParser",
|
"browserName": "BbobParser",
|
||||||
|
"types": "types/index.d.ts",
|
||||||
|
"exports": {
|
||||||
|
".": {
|
||||||
|
"types": "./types/index.d.ts",
|
||||||
|
"import": "./es/index.js",
|
||||||
|
"require": "./lib/index.js",
|
||||||
|
"browser": "./dist/index.min.js",
|
||||||
|
"umd": "./dist/index.min.js"
|
||||||
|
},
|
||||||
|
"./lexer": {
|
||||||
|
"types": "./types/lexer.d.ts",
|
||||||
|
"import": "./es/lexer.js",
|
||||||
|
"require": "./lib/lexer.js",
|
||||||
|
"browser": "./dist/index.min.js",
|
||||||
|
"umd": "./dist/index.min.js"
|
||||||
|
},
|
||||||
|
"./parse": {
|
||||||
|
"types": "./types/parse.d.ts",
|
||||||
|
"import": "./es/parse.js",
|
||||||
|
"require": "./lib/parse.js",
|
||||||
|
"browser": "./dist/index.min.js",
|
||||||
|
"umd": "./dist/index.min.js"
|
||||||
|
},
|
||||||
|
"./Token": {
|
||||||
|
"types": "./types/Token.d.ts",
|
||||||
|
"import": "./es/Token.js",
|
||||||
|
"require": "./lib/Token.js",
|
||||||
|
"browser": "./dist/index.min.js",
|
||||||
|
"umd": "./dist/index.min.js"
|
||||||
|
},
|
||||||
|
"./utils": {
|
||||||
|
"types": "./types/utils.d.ts",
|
||||||
|
"import": "./es/utils.js",
|
||||||
|
"require": "./lib/utils.js",
|
||||||
|
"browser": "./dist/index.min.js",
|
||||||
|
"umd": "./dist/index.min.js"
|
||||||
|
}
|
||||||
|
},
|
||||||
"homepage": "https://github.com/JiLiZART/bbob",
|
"homepage": "https://github.com/JiLiZART/bbob",
|
||||||
"author": "Nikolay Kostyurin <jilizart@gmail.com>",
|
"author": "Nikolay Kostyurin <jilizart@gmail.com>",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
@@ -31,20 +76,22 @@
|
|||||||
"url": "git://github.com/JiLiZART/bbob.git"
|
"url": "git://github.com/JiLiZART/bbob.git"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build:commonjs": "../../scripts/pkg-task build-commonjs",
|
"build:commonjs": "pkg-task",
|
||||||
"build:es": "../../scripts/pkg-task build-es",
|
"build:es": "pkg-task",
|
||||||
"build:umd": "../../scripts/pkg-task build-umd",
|
"build:umd": "pkg-task",
|
||||||
"build": "npm run build:commonjs && npm run build:es && npm run build:umd",
|
"build": "pkg-task",
|
||||||
"test": "../../scripts/pkg-task test",
|
"test": "pkg-task",
|
||||||
"cover": "../../scripts/pkg-task cover",
|
"cover": "pkg-task",
|
||||||
"lint": "../../scripts/pkg-task lint",
|
"lint": "pkg-task",
|
||||||
"size": "../../scripts/pkg-task size",
|
"size": "pkg-task",
|
||||||
"bundlesize": "../../scripts/pkg-task bundlesize",
|
"bundlesize": "pkg-task",
|
||||||
|
"types": "pkg-task",
|
||||||
"prepublishOnly": "npm run build"
|
"prepublishOnly": "npm run build"
|
||||||
},
|
},
|
||||||
"size-limit": [
|
"size-limit": [
|
||||||
{
|
{
|
||||||
"path": "lib/index.js"
|
"path": "./dist/index.min.js",
|
||||||
|
"size": "3 KB"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"bundlesize": [
|
"bundlesize": [
|
||||||
@@ -55,11 +102,5 @@
|
|||||||
],
|
],
|
||||||
"publishConfig": {
|
"publishConfig": {
|
||||||
"registry": "https://registry.npmjs.org/"
|
"registry": "https://registry.npmjs.org/"
|
||||||
},
|
}
|
||||||
"files": [
|
|
||||||
"dist",
|
|
||||||
"lib",
|
|
||||||
"src",
|
|
||||||
"es"
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,10 +5,11 @@ import {
|
|||||||
} from '@bbob/plugin-helper';
|
} from '@bbob/plugin-helper';
|
||||||
|
|
||||||
// type, value, line, row,
|
// type, value, line, row,
|
||||||
const TOKEN_TYPE_ID = 'type'; // 0;
|
|
||||||
const TOKEN_VALUE_ID = 'value'; // 1;
|
const TOKEN_TYPE_ID = 't'; // 0;
|
||||||
const TOKEN_COLUMN_ID = 'row'; // 2;
|
const TOKEN_VALUE_ID = 'v'; // 1;
|
||||||
const TOKEN_LINE_ID = 'line'; // 3;
|
const TOKEN_COLUMN_ID = 'r'; // 2;
|
||||||
|
const TOKEN_LINE_ID = 'l'; // 3;
|
||||||
|
|
||||||
const TOKEN_TYPE_WORD = 1; // 'word';
|
const TOKEN_TYPE_WORD = 1; // 'word';
|
||||||
const TOKEN_TYPE_TAG = 2; // 'tag';
|
const TOKEN_TYPE_TAG = 2; // 'tag';
|
||||||
@@ -17,29 +18,19 @@ const TOKEN_TYPE_ATTR_VALUE = 4; // 'attr-value';
|
|||||||
const TOKEN_TYPE_SPACE = 5; // 'space';
|
const TOKEN_TYPE_SPACE = 5; // 'space';
|
||||||
const TOKEN_TYPE_NEW_LINE = 6; // 'new-line';
|
const TOKEN_TYPE_NEW_LINE = 6; // 'new-line';
|
||||||
|
|
||||||
/**
|
const getTokenValue = (token: Token) => {
|
||||||
* @param {Token} token
|
|
||||||
* @returns {string}
|
|
||||||
*/
|
|
||||||
const getTokenValue = (token) => {
|
|
||||||
if (token && typeof token[TOKEN_VALUE_ID] !== 'undefined') {
|
if (token && typeof token[TOKEN_VALUE_ID] !== 'undefined') {
|
||||||
return token[TOKEN_VALUE_ID];
|
return token[TOKEN_VALUE_ID];
|
||||||
}
|
}
|
||||||
|
|
||||||
return '';
|
return '';
|
||||||
};
|
};
|
||||||
/**
|
|
||||||
* @param {Token}token
|
|
||||||
* @returns {number}
|
|
||||||
*/
|
|
||||||
const getTokenLine = (token) => (token && token[TOKEN_LINE_ID]) || 0;
|
|
||||||
const getTokenColumn = (token) => (token && token[TOKEN_COLUMN_ID]) || 0;
|
|
||||||
|
|
||||||
/**
|
const getTokenLine = (token: Token) => (token && token[TOKEN_LINE_ID]) || 0;
|
||||||
* @param {Token} token
|
|
||||||
* @returns {boolean}
|
const getTokenColumn = (token: Token) => (token && token[TOKEN_COLUMN_ID]) || 0;
|
||||||
*/
|
|
||||||
const isTextToken = (token) => {
|
const isTextToken = (token: Token) => {
|
||||||
if (token && typeof token[TOKEN_TYPE_ID] !== 'undefined') {
|
if (token && typeof token[TOKEN_TYPE_ID] !== 'undefined') {
|
||||||
return token[TOKEN_TYPE_ID] === TOKEN_TYPE_SPACE
|
return token[TOKEN_TYPE_ID] === TOKEN_TYPE_SPACE
|
||||||
|| token[TOKEN_TYPE_ID] === TOKEN_TYPE_NEW_LINE
|
|| token[TOKEN_TYPE_ID] === TOKEN_TYPE_NEW_LINE
|
||||||
@@ -49,20 +40,19 @@ const isTextToken = (token) => {
|
|||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
const isTagToken = (token: Token) => {
|
||||||
* @param {Token} token
|
|
||||||
* @returns {boolean}
|
|
||||||
*/
|
|
||||||
const isTagToken = (token) => {
|
|
||||||
if (token && typeof token[TOKEN_TYPE_ID] !== 'undefined') {
|
if (token && typeof token[TOKEN_TYPE_ID] !== 'undefined') {
|
||||||
return token[TOKEN_TYPE_ID] === TOKEN_TYPE_TAG;
|
return token[TOKEN_TYPE_ID] === TOKEN_TYPE_TAG;
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
const isTagEnd = (token) => getTokenValue(token).charCodeAt(0) === SLASH.charCodeAt(0);
|
|
||||||
const isTagStart = (token) => !isTagEnd(token);
|
const isTagEnd = (token: Token) => getTokenValue(token).charCodeAt(0) === SLASH.charCodeAt(0);
|
||||||
const isAttrNameToken = (token) => {
|
|
||||||
|
const isTagStart = (token: Token) => !isTagEnd(token);
|
||||||
|
|
||||||
|
const isAttrNameToken = (token: Token) => {
|
||||||
if (token && typeof token[TOKEN_TYPE_ID] !== 'undefined') {
|
if (token && typeof token[TOKEN_TYPE_ID] !== 'undefined') {
|
||||||
return token[TOKEN_TYPE_ID] === TOKEN_TYPE_ATTR_NAME;
|
return token[TOKEN_TYPE_ID] === TOKEN_TYPE_ATTR_NAME;
|
||||||
}
|
}
|
||||||
@@ -70,11 +60,7 @@ const isAttrNameToken = (token) => {
|
|||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
const isAttrValueToken = (token: Token) => {
|
||||||
* @param {Token} token
|
|
||||||
* @returns {boolean}
|
|
||||||
*/
|
|
||||||
const isAttrValueToken = (token) => {
|
|
||||||
if (token && typeof token[TOKEN_TYPE_ID] !== 'undefined') {
|
if (token && typeof token[TOKEN_TYPE_ID] !== 'undefined') {
|
||||||
return token[TOKEN_TYPE_ID] === TOKEN_TYPE_ATTR_VALUE;
|
return token[TOKEN_TYPE_ID] === TOKEN_TYPE_ATTR_VALUE;
|
||||||
}
|
}
|
||||||
@@ -82,13 +68,13 @@ const isAttrValueToken = (token) => {
|
|||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
|
|
||||||
const getTagName = (token) => {
|
const getTagName = (token: Token) => {
|
||||||
const value = getTokenValue(token);
|
const value = getTokenValue(token);
|
||||||
|
|
||||||
return isTagEnd(token) ? value.slice(1) : value;
|
return isTagEnd(token) ? value.slice(1) : value;
|
||||||
};
|
};
|
||||||
|
|
||||||
const convertTagToText = (token) => {
|
const tokenToText = (token: Token) => {
|
||||||
let text = OPEN_BRAKET;
|
let text = OPEN_BRAKET;
|
||||||
|
|
||||||
text += getTokenValue(token);
|
text += getTokenValue(token);
|
||||||
@@ -97,23 +83,29 @@ const convertTagToText = (token) => {
|
|||||||
return text;
|
return text;
|
||||||
};
|
};
|
||||||
|
|
||||||
class Token {
|
/**
|
||||||
/**
|
* @export
|
||||||
* @param {String} type
|
* @class Token
|
||||||
* @param {String} value
|
*/
|
||||||
* @param line
|
class Token<TokenValue = string> {
|
||||||
* @param row
|
private t: number // type
|
||||||
*/
|
private v: string // value
|
||||||
constructor(type, value, line, row) {
|
private l: number // line
|
||||||
this[TOKEN_TYPE_ID] = Number(type);
|
private r: number // row
|
||||||
|
|
||||||
|
constructor(type?: number, value?: TokenValue, row: number = 0, col: number = 0) {
|
||||||
|
this[TOKEN_LINE_ID] = row;
|
||||||
|
this[TOKEN_COLUMN_ID] = col;
|
||||||
|
this[TOKEN_TYPE_ID] = type || 0;
|
||||||
this[TOKEN_VALUE_ID] = String(value);
|
this[TOKEN_VALUE_ID] = String(value);
|
||||||
this[TOKEN_LINE_ID] = Number(line);
|
}
|
||||||
this[TOKEN_COLUMN_ID] = Number(row);
|
|
||||||
|
get type() {
|
||||||
|
return this[TOKEN_TYPE_ID]
|
||||||
}
|
}
|
||||||
|
|
||||||
isEmpty() {
|
isEmpty() {
|
||||||
// eslint-disable-next-line no-restricted-globals
|
return this[TOKEN_TYPE_ID] === 0 || isNaN(this[TOKEN_TYPE_ID]);
|
||||||
return isNaN(this[TOKEN_TYPE_ID]);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
isText() {
|
isText() {
|
||||||
@@ -157,7 +149,7 @@ class Token {
|
|||||||
}
|
}
|
||||||
|
|
||||||
toString() {
|
toString() {
|
||||||
return convertTagToText(this);
|
return tokenToText(this);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1,2 +0,0 @@
|
|||||||
export { TagNode } from '@bbob/plugin-helper';
|
|
||||||
export { default, parse } from './parse';
|
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
export { TagNode } from '@bbob/plugin-helper';
|
||||||
|
export { default } from './parse';
|
||||||
|
export * from './parse';
|
||||||
|
export * from './lexer'
|
||||||
|
export * from './types'
|
||||||
@@ -14,36 +14,17 @@ import {
|
|||||||
import {
|
import {
|
||||||
Token, TYPE_ATTR_NAME, TYPE_ATTR_VALUE, TYPE_NEW_LINE, TYPE_SPACE, TYPE_TAG, TYPE_WORD,
|
Token, TYPE_ATTR_NAME, TYPE_ATTR_VALUE, TYPE_NEW_LINE, TYPE_SPACE, TYPE_TAG, TYPE_WORD,
|
||||||
} from './Token';
|
} from './Token';
|
||||||
import { createCharGrabber, trimChar, unquote } from './utils';
|
import { CharGrabber, createCharGrabber, trimChar, unquote } from './utils';
|
||||||
|
import type { LexerOptions, LexerTokenizer } from "./types";
|
||||||
|
|
||||||
// for cases <!-- -->
|
// for cases <!-- -->
|
||||||
const EM = '!';
|
const EM = '!';
|
||||||
|
|
||||||
/**
|
export function createTokenOfType(type: number, value: string, r = 0, cl = 0) {
|
||||||
* Creates a Token entity class
|
return new Token(type, value, r, cl)
|
||||||
* @param {Number} type
|
}
|
||||||
* @param {String} value
|
|
||||||
* @param {Number} r line number
|
|
||||||
* @param {Number} cl char number in line
|
|
||||||
*/
|
|
||||||
const createToken = (type, value, r = 0, cl = 0) => new Token(type, value, r, cl);
|
|
||||||
|
|
||||||
/**
|
export function createLexer(buffer: string, options: LexerOptions = {}): LexerTokenizer {
|
||||||
* @typedef {Object} Lexer
|
|
||||||
* @property {Function} tokenize
|
|
||||||
* @property {Function} isTokenNested
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {String} buffer
|
|
||||||
* @param {Object} options
|
|
||||||
* @param {Function} options.onToken
|
|
||||||
* @param {String} options.openTag
|
|
||||||
* @param {String} options.closeTag
|
|
||||||
* @param {Boolean} options.enableEscapeTags
|
|
||||||
* @return {Lexer}
|
|
||||||
*/
|
|
||||||
function createLexer(buffer, options = {}) {
|
|
||||||
const STATE_WORD = 0;
|
const STATE_WORD = 0;
|
||||||
const STATE_TAG = 1;
|
const STATE_TAG = 1;
|
||||||
const STATE_TAG_ATTRS = 2;
|
const STATE_TAG_ATTRS = 2;
|
||||||
@@ -59,7 +40,7 @@ function createLexer(buffer, options = {}) {
|
|||||||
let stateMode = STATE_WORD;
|
let stateMode = STATE_WORD;
|
||||||
let tagMode = TAG_STATE_NAME;
|
let tagMode = TAG_STATE_NAME;
|
||||||
let contextFreeTag = '';
|
let contextFreeTag = '';
|
||||||
const tokens = new Array(Math.floor(buffer.length));
|
const tokens = new Array<Token<string>>(Math.floor(buffer.length));
|
||||||
const openTag = options.openTag || OPEN_BRAKET;
|
const openTag = options.openTag || OPEN_BRAKET;
|
||||||
const closeTag = options.closeTag || CLOSE_BRAKET;
|
const closeTag = options.closeTag || CLOSE_BRAKET;
|
||||||
const escapeTags = !!options.enableEscapeTags;
|
const escapeTags = !!options.enableEscapeTags;
|
||||||
@@ -76,20 +57,20 @@ function createLexer(buffer, options = {}) {
|
|||||||
const WHITESPACES = [SPACE, TAB];
|
const WHITESPACES = [SPACE, TAB];
|
||||||
const SPECIAL_CHARS = [EQ, SPACE, TAB];
|
const SPECIAL_CHARS = [EQ, SPACE, TAB];
|
||||||
|
|
||||||
const isCharReserved = (char) => (RESERVED_CHARS.indexOf(char) >= 0);
|
const isCharReserved = (char: string) => (RESERVED_CHARS.indexOf(char) >= 0);
|
||||||
const isNewLine = (char) => char === N;
|
const isNewLine = (char: string) => char === N;
|
||||||
const isWhiteSpace = (char) => (WHITESPACES.indexOf(char) >= 0);
|
const isWhiteSpace = (char: string) => (WHITESPACES.indexOf(char) >= 0);
|
||||||
const isCharToken = (char) => (NOT_CHAR_TOKENS.indexOf(char) === -1);
|
const isCharToken = (char: string) => (NOT_CHAR_TOKENS.indexOf(char) === -1);
|
||||||
const isSpecialChar = (char) => (SPECIAL_CHARS.indexOf(char) >= 0);
|
const isSpecialChar = (char: string) => (SPECIAL_CHARS.indexOf(char) >= 0);
|
||||||
const isEscapableChar = (char) => (char === openTag || char === closeTag || char === BACKSLASH);
|
const isEscapableChar = (char: string) => (char === openTag || char === closeTag || char === BACKSLASH);
|
||||||
const isEscapeChar = (char) => char === BACKSLASH;
|
const isEscapeChar = (char: string) => char === BACKSLASH;
|
||||||
const onSkip = () => {
|
const onSkip = () => {
|
||||||
col++;
|
col++;
|
||||||
};
|
};
|
||||||
|
|
||||||
const unq = (val) => unquote(trimChar(val, QUOTEMARK));
|
const unq = (val: string) => unquote(trimChar(val, QUOTEMARK));
|
||||||
|
|
||||||
const checkContextFreeMode = (name, isClosingTag) => {
|
const checkContextFreeMode = (name: string, isClosingTag?: boolean) => {
|
||||||
if (contextFreeTag !== '' && isClosingTag) {
|
if (contextFreeTag !== '' && isClosingTag) {
|
||||||
contextFreeTag = '';
|
contextFreeTag = '';
|
||||||
}
|
}
|
||||||
@@ -106,8 +87,8 @@ function createLexer(buffer, options = {}) {
|
|||||||
* @param {Number} type
|
* @param {Number} type
|
||||||
* @param {String} value
|
* @param {String} value
|
||||||
*/
|
*/
|
||||||
function emitToken(type, value) {
|
function emitToken(type: number, value: string) {
|
||||||
const token = createToken(type, value, row, col);
|
const token = createTokenOfType(type, value, row, col);
|
||||||
|
|
||||||
onToken(token);
|
onToken(token);
|
||||||
|
|
||||||
@@ -115,9 +96,9 @@ function createLexer(buffer, options = {}) {
|
|||||||
tokens[tokenIndex] = token;
|
tokens[tokenIndex] = token;
|
||||||
}
|
}
|
||||||
|
|
||||||
function nextTagState(tagChars, isSingleValueTag) {
|
function nextTagState(tagChars: CharGrabber, isSingleValueTag: boolean) {
|
||||||
if (tagMode === TAG_STATE_ATTR) {
|
if (tagMode === TAG_STATE_ATTR) {
|
||||||
const validAttrName = (char) => !(char === EQ || isWhiteSpace(char));
|
const validAttrName = (char: string) => !(char === EQ || isWhiteSpace(char));
|
||||||
const name = tagChars.grabWhile(validAttrName);
|
const name = tagChars.grabWhile(validAttrName);
|
||||||
const isEnd = tagChars.isLast();
|
const isEnd = tagChars.isLast();
|
||||||
const isValue = tagChars.getCurr() !== EQ;
|
const isValue = tagChars.getCurr() !== EQ;
|
||||||
@@ -143,7 +124,7 @@ function createLexer(buffer, options = {}) {
|
|||||||
if (tagMode === TAG_STATE_VALUE) {
|
if (tagMode === TAG_STATE_VALUE) {
|
||||||
let stateSpecial = false;
|
let stateSpecial = false;
|
||||||
|
|
||||||
const validAttrValue = (char) => {
|
const validAttrValue = (char: string) => {
|
||||||
// const isEQ = char === EQ;
|
// const isEQ = char === EQ;
|
||||||
const isQM = char === QUOTEMARK;
|
const isQM = char === QUOTEMARK;
|
||||||
const prevChar = tagChars.getPrev();
|
const prevChar = tagChars.getPrev();
|
||||||
@@ -152,7 +133,7 @@ function createLexer(buffer, options = {}) {
|
|||||||
const isNextEQ = nextChar === EQ;
|
const isNextEQ = nextChar === EQ;
|
||||||
const isWS = isWhiteSpace(char);
|
const isWS = isWhiteSpace(char);
|
||||||
// const isPrevWS = isWhiteSpace(prevChar);
|
// const isPrevWS = isWhiteSpace(prevChar);
|
||||||
const isNextWS = isWhiteSpace(nextChar);
|
const isNextWS = nextChar && isWhiteSpace(nextChar);
|
||||||
|
|
||||||
if (stateSpecial && isSpecialChar(char)) {
|
if (stateSpecial && isSpecialChar(char)) {
|
||||||
return true;
|
return true;
|
||||||
@@ -167,7 +148,7 @@ function createLexer(buffer, options = {}) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!isSingleValueTag) {
|
if (!isSingleValueTag) {
|
||||||
return isWS === false;
|
return !isWS;
|
||||||
// return (isEQ || isWS) === false;
|
// return (isEQ || isWS) === false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -186,7 +167,7 @@ function createLexer(buffer, options = {}) {
|
|||||||
return TAG_STATE_ATTR;
|
return TAG_STATE_ATTR;
|
||||||
}
|
}
|
||||||
|
|
||||||
const validName = (char) => !(char === EQ || isWhiteSpace(char) || tagChars.isLast());
|
const validName = (char: string) => !(char === EQ || isWhiteSpace(char) || tagChars.isLast());
|
||||||
const name = tagChars.grabWhile(validName);
|
const name = tagChars.grabWhile(validName);
|
||||||
|
|
||||||
emitToken(TYPE_TAG, name);
|
emitToken(TYPE_TAG, name);
|
||||||
@@ -214,7 +195,7 @@ function createLexer(buffer, options = {}) {
|
|||||||
const substr = chars.substrUntilChar(closeTag);
|
const substr = chars.substrUntilChar(closeTag);
|
||||||
const hasInvalidChars = substr.length === 0 || substr.indexOf(openTag) >= 0;
|
const hasInvalidChars = substr.length === 0 || substr.indexOf(openTag) >= 0;
|
||||||
|
|
||||||
if (isCharReserved(nextChar) || hasInvalidChars || chars.isLast()) {
|
if ((nextChar && isCharReserved(nextChar)) || hasInvalidChars || chars.isLast()) {
|
||||||
emitToken(TYPE_WORD, currChar);
|
emitToken(TYPE_WORD, currChar);
|
||||||
|
|
||||||
return STATE_WORD;
|
return STATE_WORD;
|
||||||
@@ -304,7 +285,7 @@ function createLexer(buffer, options = {}) {
|
|||||||
|
|
||||||
chars.skip(); // skip the \ without emitting anything
|
chars.skip(); // skip the \ without emitting anything
|
||||||
|
|
||||||
if (isEscapableChar(nextChar)) {
|
if (nextChar && isEscapableChar(nextChar)) {
|
||||||
chars.skip(); // skip past the [, ] or \ as well
|
chars.skip(); // skip past the [, ] or \ as well
|
||||||
|
|
||||||
emitToken(TYPE_WORD, nextChar);
|
emitToken(TYPE_WORD, nextChar);
|
||||||
@@ -317,7 +298,7 @@ function createLexer(buffer, options = {}) {
|
|||||||
return STATE_WORD;
|
return STATE_WORD;
|
||||||
}
|
}
|
||||||
|
|
||||||
const isChar = (char) => isCharToken(char) && !isEscapeChar(char);
|
const isChar = (char: string) => isCharToken(char) && !isEscapeChar(char);
|
||||||
|
|
||||||
const word = chars.grabWhile(isChar);
|
const word = chars.grabWhile(isChar);
|
||||||
|
|
||||||
@@ -356,7 +337,7 @@ function createLexer(buffer, options = {}) {
|
|||||||
return tokens;
|
return tokens;
|
||||||
}
|
}
|
||||||
|
|
||||||
function isTokenNested(token) {
|
function isTokenNested(token: Token) {
|
||||||
const value = openTag + SLASH + token.getValue();
|
const value = openTag + SLASH + token.getValue();
|
||||||
// potential bottleneck
|
// potential bottleneck
|
||||||
return buffer.indexOf(value) > -1;
|
return buffer.indexOf(value) > -1;
|
||||||
@@ -365,8 +346,5 @@ function createLexer(buffer, options = {}) {
|
|||||||
return {
|
return {
|
||||||
tokenize,
|
tokenize,
|
||||||
isTokenNested,
|
isTokenNested,
|
||||||
};
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const createTokenOfType = createToken;
|
|
||||||
export { createLexer };
|
|
||||||
@@ -1,303 +0,0 @@
|
|||||||
import {
|
|
||||||
TagNode, CLOSE_BRAKET, OPEN_BRAKET, isTagNode,
|
|
||||||
} from '@bbob/plugin-helper';
|
|
||||||
import { createLexer } from './lexer';
|
|
||||||
import { createList } from './utils';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @public
|
|
||||||
* @param {string} input
|
|
||||||
* @param {Object} opts
|
|
||||||
* @param {Function} opts.createTokenizer
|
|
||||||
* @param {Array<string>} opts.onlyAllowTags
|
|
||||||
* @param {Array<string>} opts.contextFreeTags
|
|
||||||
* @param {Boolean} opts.enableEscapeTags
|
|
||||||
* @param {string} opts.openTag
|
|
||||||
* @param {string} opts.closeTag
|
|
||||||
* @return {Array<string|TagNode>}
|
|
||||||
*/
|
|
||||||
const parse = (input, opts = {}) => {
|
|
||||||
const options = opts;
|
|
||||||
const openTag = options.openTag || OPEN_BRAKET;
|
|
||||||
const closeTag = options.closeTag || CLOSE_BRAKET;
|
|
||||||
const onlyAllowTags = (options.onlyAllowTags || [])
|
|
||||||
.filter(Boolean)
|
|
||||||
.map((tag) => tag.toLowerCase());
|
|
||||||
|
|
||||||
let tokenizer = null;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Result AST of nodes
|
|
||||||
* @private
|
|
||||||
* @type {NodeList}
|
|
||||||
*/
|
|
||||||
const nodes = createList();
|
|
||||||
/**
|
|
||||||
* Temp buffer of nodes that's nested to another node
|
|
||||||
* @private
|
|
||||||
* @type {NodeList}
|
|
||||||
*/
|
|
||||||
const nestedNodes = createList();
|
|
||||||
/**
|
|
||||||
* Temp buffer of nodes [tag..]...[/tag]
|
|
||||||
* @private
|
|
||||||
* @type {NodeList}
|
|
||||||
*/
|
|
||||||
const tagNodes = createList();
|
|
||||||
/**
|
|
||||||
* Temp buffer of tag attributes
|
|
||||||
* @private
|
|
||||||
* @type {NodeList}
|
|
||||||
*/
|
|
||||||
const tagNodesAttrName = createList();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Cache for nested tags checks
|
|
||||||
* @type Set<string>
|
|
||||||
*/
|
|
||||||
const nestedTagsMap = new Set();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {Token} token
|
|
||||||
* @returns {boolean}
|
|
||||||
*/
|
|
||||||
const isTokenNested = (token) => {
|
|
||||||
const value = token.getValue();
|
|
||||||
|
|
||||||
if (!nestedTagsMap.has(value) && tokenizer.isTokenNested && tokenizer.isTokenNested(token)) {
|
|
||||||
nestedTagsMap.add(value);
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return nestedTagsMap.has(value);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @private
|
|
||||||
* @param {string} tagName
|
|
||||||
* @returns {boolean}
|
|
||||||
*/
|
|
||||||
const isTagNested = (tagName) => Boolean(nestedTagsMap.has(tagName));
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @private
|
|
||||||
* @param {string} value
|
|
||||||
* @return {boolean}
|
|
||||||
*/
|
|
||||||
const isAllowedTag = (value) => {
|
|
||||||
if (onlyAllowTags.length) {
|
|
||||||
return onlyAllowTags.indexOf(value.toLowerCase()) >= 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Flushes temp tag nodes and its attributes buffers
|
|
||||||
* @private
|
|
||||||
* @return {Array}
|
|
||||||
*/
|
|
||||||
const flushTagNodes = () => {
|
|
||||||
if (tagNodes.flushLast()) {
|
|
||||||
tagNodesAttrName.flushLast();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @private
|
|
||||||
* @return {Array}
|
|
||||||
*/
|
|
||||||
const getNodes = () => {
|
|
||||||
const lastNestedNode = nestedNodes.getLast();
|
|
||||||
|
|
||||||
if (lastNestedNode && Array.isArray(lastNestedNode.content)) {
|
|
||||||
return lastNestedNode.content;
|
|
||||||
}
|
|
||||||
|
|
||||||
return nodes.toArray();
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @private
|
|
||||||
* @param {string|TagNode} node
|
|
||||||
* @param {boolean} isNested
|
|
||||||
*/
|
|
||||||
const appendNodeAsString = (node, isNested = true) => {
|
|
||||||
const items = getNodes();
|
|
||||||
|
|
||||||
if (Array.isArray(items)) {
|
|
||||||
items.push(node.toTagStart({ openTag, closeTag }));
|
|
||||||
|
|
||||||
if (node.content.length) {
|
|
||||||
node.content.forEach((item) => {
|
|
||||||
items.push(item);
|
|
||||||
});
|
|
||||||
|
|
||||||
if (isNested) {
|
|
||||||
items.push(node.toTagEnd({ openTag, closeTag }));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @private
|
|
||||||
* @param {string|TagNode} node
|
|
||||||
*/
|
|
||||||
const appendNodes = (node) => {
|
|
||||||
const items = getNodes();
|
|
||||||
|
|
||||||
if (Array.isArray(items)) {
|
|
||||||
if (isTagNode(node)) {
|
|
||||||
if (isAllowedTag(node.tag)) {
|
|
||||||
items.push(node.toTagNode());
|
|
||||||
} else {
|
|
||||||
appendNodeAsString(node);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
items.push(node);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @private
|
|
||||||
* @param {Token} token
|
|
||||||
*/
|
|
||||||
const handleTagStart = (token) => {
|
|
||||||
flushTagNodes();
|
|
||||||
|
|
||||||
const tagNode = TagNode.create(token.getValue());
|
|
||||||
const isNested = isTokenNested(token);
|
|
||||||
|
|
||||||
tagNodes.push(tagNode);
|
|
||||||
|
|
||||||
if (isNested) {
|
|
||||||
nestedNodes.push(tagNode);
|
|
||||||
} else {
|
|
||||||
appendNodes(tagNode, token);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @private
|
|
||||||
* @param {Token} token
|
|
||||||
*/
|
|
||||||
const handleTagEnd = (token) => {
|
|
||||||
flushTagNodes();
|
|
||||||
|
|
||||||
const lastNestedNode = nestedNodes.flushLast();
|
|
||||||
|
|
||||||
if (lastNestedNode) {
|
|
||||||
appendNodes(lastNestedNode, token);
|
|
||||||
} else if (typeof options.onError === 'function') {
|
|
||||||
const tag = token.getValue();
|
|
||||||
const line = token.getLine();
|
|
||||||
const column = token.getColumn();
|
|
||||||
|
|
||||||
options.onError({
|
|
||||||
message: `Inconsistent tag '${tag}' on line ${line} and column ${column}`,
|
|
||||||
tagName: tag,
|
|
||||||
lineNumber: line,
|
|
||||||
columnNumber: column,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @private
|
|
||||||
* @param {Token} token
|
|
||||||
*/
|
|
||||||
const handleTag = (token) => {
|
|
||||||
// [tag]
|
|
||||||
if (token.isStart()) {
|
|
||||||
handleTagStart(token);
|
|
||||||
}
|
|
||||||
|
|
||||||
// [/tag]
|
|
||||||
if (token.isEnd()) {
|
|
||||||
handleTagEnd(token);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @private
|
|
||||||
* @param {Token} token
|
|
||||||
*/
|
|
||||||
const handleNode = (token) => {
|
|
||||||
/**
|
|
||||||
* @type {TagNode}
|
|
||||||
*/
|
|
||||||
const lastTagNode = tagNodes.getLast();
|
|
||||||
const tokenValue = token.getValue();
|
|
||||||
const isNested = isTagNested(token);
|
|
||||||
|
|
||||||
if (lastTagNode) {
|
|
||||||
if (token.isAttrName()) {
|
|
||||||
tagNodesAttrName.push(tokenValue);
|
|
||||||
lastTagNode.attr(tagNodesAttrName.getLast(), '');
|
|
||||||
} else if (token.isAttrValue()) {
|
|
||||||
const attrName = tagNodesAttrName.getLast();
|
|
||||||
|
|
||||||
if (attrName) {
|
|
||||||
lastTagNode.attr(attrName, tokenValue);
|
|
||||||
tagNodesAttrName.flushLast();
|
|
||||||
} else {
|
|
||||||
lastTagNode.attr(tokenValue, tokenValue);
|
|
||||||
}
|
|
||||||
} else if (token.isText()) {
|
|
||||||
if (isNested) {
|
|
||||||
lastTagNode.append(tokenValue);
|
|
||||||
} else {
|
|
||||||
appendNodes(tokenValue);
|
|
||||||
}
|
|
||||||
} else if (token.isTag()) {
|
|
||||||
// if tag is not allowed, just past it as is
|
|
||||||
appendNodes(token.toString());
|
|
||||||
}
|
|
||||||
} else if (token.isText()) {
|
|
||||||
appendNodes(tokenValue);
|
|
||||||
} else if (token.isTag()) {
|
|
||||||
// if tag is not allowed, just past it as is
|
|
||||||
appendNodes(token.toString());
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @private
|
|
||||||
* @param {Token} token
|
|
||||||
*/
|
|
||||||
const onToken = (token) => {
|
|
||||||
if (token.isTag()) {
|
|
||||||
handleTag(token);
|
|
||||||
} else {
|
|
||||||
handleNode(token);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
tokenizer = (opts.createTokenizer ? opts.createTokenizer : createLexer)(input, {
|
|
||||||
onToken,
|
|
||||||
openTag,
|
|
||||||
closeTag,
|
|
||||||
onlyAllowTags: options.onlyAllowTags,
|
|
||||||
contextFreeTags: options.contextFreeTags,
|
|
||||||
enableEscapeTags: options.enableEscapeTags,
|
|
||||||
});
|
|
||||||
|
|
||||||
// eslint-disable-next-line no-unused-vars
|
|
||||||
const tokens = tokenizer.tokenize();
|
|
||||||
|
|
||||||
// handles situations where we open tag, but forgot close them
|
|
||||||
// for ex [q]test[/q][u]some[/u][q]some [u]some[/u] // forgot to close [/q]
|
|
||||||
// so we need to flush nested content to nodes array
|
|
||||||
const lastNestedNode = nestedNodes.flushLast();
|
|
||||||
if (lastNestedNode && isTagNested(lastNestedNode.tag)) {
|
|
||||||
appendNodeAsString(lastNestedNode, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
return nodes.toArray();
|
|
||||||
};
|
|
||||||
|
|
||||||
export { parse };
|
|
||||||
export default parse;
|
|
||||||
@@ -0,0 +1,349 @@
|
|||||||
|
import {
|
||||||
|
CLOSE_BRAKET,
|
||||||
|
OPEN_BRAKET,
|
||||||
|
TagNode,
|
||||||
|
isTagNode,
|
||||||
|
} from "@bbob/plugin-helper";
|
||||||
|
|
||||||
|
import { createLexer } from "./lexer";
|
||||||
|
|
||||||
|
import type { NodeContent, TagNodeTree } from "@bbob/plugin-helper";
|
||||||
|
import type { LexerTokenizer, LexerOptions } from "./types";
|
||||||
|
import type { Token } from "./Token";
|
||||||
|
|
||||||
|
type ParseError = {
|
||||||
|
tagName: string;
|
||||||
|
lineNumber: number;
|
||||||
|
columnNumber: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
export interface ParseOptions {
|
||||||
|
createTokenizer?: (input: string, options?: LexerOptions) => LexerTokenizer;
|
||||||
|
openTag?: string;
|
||||||
|
closeTag?: string;
|
||||||
|
onlyAllowTags?: string[];
|
||||||
|
contextFreeTags?: string[];
|
||||||
|
enableEscapeTags?: boolean;
|
||||||
|
onError?: (error: ParseError) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
class NodeList<Value> {
|
||||||
|
private n: Value[];
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
this.n = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
last() {
|
||||||
|
if (
|
||||||
|
Array.isArray(this.n) &&
|
||||||
|
this.n.length > 0 &&
|
||||||
|
typeof this.n[this.n.length - 1] !== "undefined"
|
||||||
|
) {
|
||||||
|
return this.n[this.n.length - 1];
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
flush() {
|
||||||
|
return this.n.length ? this.n.pop() : false;
|
||||||
|
}
|
||||||
|
|
||||||
|
push(value: Value) {
|
||||||
|
this.n.push(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
toArray() {
|
||||||
|
return this.n;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const createList = <Type>() => new NodeList<Type>();
|
||||||
|
|
||||||
|
function parse(input: string, opts: ParseOptions = {}) {
|
||||||
|
const options = opts;
|
||||||
|
const openTag = options.openTag || OPEN_BRAKET;
|
||||||
|
const closeTag = options.closeTag || CLOSE_BRAKET;
|
||||||
|
const onlyAllowTags = (options.onlyAllowTags || [])
|
||||||
|
.filter(Boolean)
|
||||||
|
.map((tag) => tag.toLowerCase());
|
||||||
|
|
||||||
|
let tokenizer: LexerTokenizer | null = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Result AST of nodes
|
||||||
|
* @private
|
||||||
|
* @type {NodeList}
|
||||||
|
*/
|
||||||
|
const nodes = createList<TagNode>();
|
||||||
|
/**
|
||||||
|
* Temp buffer of nodes that's nested to another node
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
const nestedNodes = createList<NodeContent>();
|
||||||
|
/**
|
||||||
|
* Temp buffer of nodes [tag..]...[/tag]
|
||||||
|
* @private
|
||||||
|
* @type {NodeList}
|
||||||
|
*/
|
||||||
|
const tagNodes = createList<TagNode>();
|
||||||
|
/**
|
||||||
|
* Temp buffer of tag attributes
|
||||||
|
* @private
|
||||||
|
* @type {NodeList}
|
||||||
|
*/
|
||||||
|
const tagNodesAttrName = createList<string>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cache for nested tags checks
|
||||||
|
*/
|
||||||
|
const nestedTagsMap = new Set<string>();
|
||||||
|
|
||||||
|
function isTokenNested(token: Token) {
|
||||||
|
const value = token.getValue();
|
||||||
|
const { isTokenNested } = tokenizer || {};
|
||||||
|
|
||||||
|
if (!nestedTagsMap.has(value) && isTokenNested && isTokenNested(token)) {
|
||||||
|
nestedTagsMap.add(value);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return nestedTagsMap.has(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
function isTagNested(tagName: string) {
|
||||||
|
return Boolean(nestedTagsMap.has(tagName));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
function isAllowedTag(value: string) {
|
||||||
|
if (onlyAllowTags.length) {
|
||||||
|
return onlyAllowTags.indexOf(value.toLowerCase()) >= 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Flushes temp tag nodes and its attributes buffers
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
function flushTagNodes() {
|
||||||
|
if (tagNodes.flush()) {
|
||||||
|
tagNodesAttrName.flush();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
function getNodes() {
|
||||||
|
const lastNestedNode = nestedNodes.last();
|
||||||
|
|
||||||
|
if (lastNestedNode && isTagNode(lastNestedNode)) {
|
||||||
|
return lastNestedNode.content;
|
||||||
|
}
|
||||||
|
|
||||||
|
return nodes.toArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
function appendNodeAsString(
|
||||||
|
nodes?: TagNodeTree,
|
||||||
|
node?: TagNode,
|
||||||
|
isNested = true
|
||||||
|
) {
|
||||||
|
if (Array.isArray(nodes) && typeof node !== "undefined") {
|
||||||
|
nodes.push(node.toTagStart({ openTag, closeTag }));
|
||||||
|
|
||||||
|
if (Array.isArray(node.content) && node.content.length) {
|
||||||
|
node.content.forEach((item) => {
|
||||||
|
nodes.push(item);
|
||||||
|
});
|
||||||
|
|
||||||
|
if (isNested) {
|
||||||
|
nodes.push(node.toTagEnd({ openTag, closeTag }));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
function appendNodes(nodes?: TagNodeTree, node?: NodeContent) {
|
||||||
|
if (Array.isArray(nodes) && typeof node !== "undefined") {
|
||||||
|
if (isTagNode(node)) {
|
||||||
|
if (isAllowedTag(node.tag)) {
|
||||||
|
nodes.push(node.toTagNode());
|
||||||
|
} else {
|
||||||
|
appendNodeAsString(nodes, node);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
nodes.push(node);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @private
|
||||||
|
* @param {Token} token
|
||||||
|
*/
|
||||||
|
function handleTagStart(token: Token) {
|
||||||
|
flushTagNodes();
|
||||||
|
|
||||||
|
const tagNode = TagNode.create(token.getValue());
|
||||||
|
const isNested = isTokenNested(token);
|
||||||
|
|
||||||
|
tagNodes.push(tagNode);
|
||||||
|
|
||||||
|
if (isNested) {
|
||||||
|
nestedNodes.push(tagNode);
|
||||||
|
} else {
|
||||||
|
const nodes = getNodes();
|
||||||
|
appendNodes(nodes, tagNode);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @private
|
||||||
|
* @param {Token} token
|
||||||
|
*/
|
||||||
|
function handleTagEnd(token: Token) {
|
||||||
|
flushTagNodes();
|
||||||
|
|
||||||
|
const lastNestedNode = nestedNodes.flush();
|
||||||
|
|
||||||
|
if (lastNestedNode) {
|
||||||
|
const nodes = getNodes();
|
||||||
|
appendNodes(nodes, lastNestedNode);
|
||||||
|
} else if (typeof options.onError === "function") {
|
||||||
|
const tag = token.getValue();
|
||||||
|
const line = token.getLine();
|
||||||
|
const column = token.getColumn();
|
||||||
|
|
||||||
|
options.onError({
|
||||||
|
tagName: tag,
|
||||||
|
lineNumber: line,
|
||||||
|
columnNumber: column,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @private
|
||||||
|
* @param {Token} token
|
||||||
|
*/
|
||||||
|
function handleTag(token: Token) {
|
||||||
|
// [tag]
|
||||||
|
if (token.isStart()) {
|
||||||
|
handleTagStart(token);
|
||||||
|
}
|
||||||
|
|
||||||
|
// [/tag]
|
||||||
|
if (token.isEnd()) {
|
||||||
|
handleTagEnd(token);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @private
|
||||||
|
* @param {Token} token
|
||||||
|
*/
|
||||||
|
function handleNode(token: Token) {
|
||||||
|
/**
|
||||||
|
* @type {TagNode}
|
||||||
|
*/
|
||||||
|
const activeTagNode = tagNodes.last();
|
||||||
|
const tokenValue = token.getValue();
|
||||||
|
const isNested = isTagNested(token.toString());
|
||||||
|
const nodes = getNodes();
|
||||||
|
|
||||||
|
if (activeTagNode !== null) {
|
||||||
|
if (token.isAttrName()) {
|
||||||
|
tagNodesAttrName.push(tokenValue);
|
||||||
|
const attrName = tagNodesAttrName.last();
|
||||||
|
|
||||||
|
if (attrName) {
|
||||||
|
activeTagNode.attr(attrName, "");
|
||||||
|
}
|
||||||
|
} else if (token.isAttrValue()) {
|
||||||
|
const attrName = tagNodesAttrName.last();
|
||||||
|
|
||||||
|
if (attrName) {
|
||||||
|
activeTagNode.attr(attrName, tokenValue);
|
||||||
|
tagNodesAttrName.flush();
|
||||||
|
} else {
|
||||||
|
activeTagNode.attr(tokenValue, tokenValue);
|
||||||
|
}
|
||||||
|
} else if (token.isText()) {
|
||||||
|
if (isNested) {
|
||||||
|
activeTagNode.append(tokenValue);
|
||||||
|
} else {
|
||||||
|
appendNodes(nodes, tokenValue);
|
||||||
|
}
|
||||||
|
} else if (token.isTag()) {
|
||||||
|
// if tag is not allowed, just pass it as is
|
||||||
|
appendNodes(nodes, token.toString());
|
||||||
|
}
|
||||||
|
} else if (token.isText()) {
|
||||||
|
appendNodes(nodes, tokenValue);
|
||||||
|
} else if (token.isTag()) {
|
||||||
|
// if tag is not allowed, just pass it as is
|
||||||
|
appendNodes(nodes, token.toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @private
|
||||||
|
* @param {Token} token
|
||||||
|
*/
|
||||||
|
function onToken(token: Token) {
|
||||||
|
if (token.isTag()) {
|
||||||
|
handleTag(token);
|
||||||
|
} else {
|
||||||
|
handleNode(token);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const lexer = opts.createTokenizer ? opts.createTokenizer : createLexer;
|
||||||
|
|
||||||
|
tokenizer = lexer(input, {
|
||||||
|
onToken,
|
||||||
|
openTag,
|
||||||
|
closeTag,
|
||||||
|
onlyAllowTags: options.onlyAllowTags,
|
||||||
|
contextFreeTags: options.contextFreeTags,
|
||||||
|
enableEscapeTags: options.enableEscapeTags,
|
||||||
|
});
|
||||||
|
|
||||||
|
// eslint-disable-next-line no-unused-vars
|
||||||
|
const tokens = tokenizer.tokenize();
|
||||||
|
|
||||||
|
// handles situations where we open tag, but forgot close them
|
||||||
|
// for ex [q]test[/q][u]some[/u][q]some [u]some[/u] // forgot to close [/q]
|
||||||
|
// so we need to flush nested content to nodes array
|
||||||
|
const lastNestedNode = nestedNodes.flush();
|
||||||
|
if (
|
||||||
|
lastNestedNode !== null &&
|
||||||
|
lastNestedNode &&
|
||||||
|
isTagNode(lastNestedNode) &&
|
||||||
|
isTagNested(lastNestedNode.tag)
|
||||||
|
) {
|
||||||
|
appendNodeAsString(getNodes(), lastNestedNode, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
return nodes.toArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
export { parse };
|
||||||
|
export default parse;
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
import type Token from "./Token";
|
||||||
|
|
||||||
|
export interface LexerTokenizer {
|
||||||
|
tokenize: () => Token<string>[];
|
||||||
|
isTokenNested?: (token: Token<string>) => boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type LexerOptions = {
|
||||||
|
openTag?: string;
|
||||||
|
closeTag?: string;
|
||||||
|
onlyAllowTags?: string[];
|
||||||
|
enableEscapeTags?: boolean;
|
||||||
|
contextFreeTags?: string[];
|
||||||
|
onToken?: (token?: Token<string>) => void;
|
||||||
|
};
|
||||||
@@ -1,165 +0,0 @@
|
|||||||
import {
|
|
||||||
QUOTEMARK,
|
|
||||||
BACKSLASH,
|
|
||||||
} from '@bbob/plugin-helper';
|
|
||||||
|
|
||||||
function CharGrabber(source, options) {
|
|
||||||
const cursor = {
|
|
||||||
pos: 0,
|
|
||||||
len: source.length,
|
|
||||||
};
|
|
||||||
|
|
||||||
const substrUntilChar = (char) => {
|
|
||||||
const { pos } = cursor;
|
|
||||||
const idx = source.indexOf(char, pos);
|
|
||||||
|
|
||||||
return idx >= 0 ? source.substring(pos, idx) : '';
|
|
||||||
};
|
|
||||||
const includes = (val) => source.indexOf(val, cursor.pos) >= 0;
|
|
||||||
const hasNext = () => cursor.len > cursor.pos;
|
|
||||||
const isLast = () => cursor.pos === cursor.len;
|
|
||||||
const skip = (num = 1, silent) => {
|
|
||||||
cursor.pos += num;
|
|
||||||
|
|
||||||
if (options && options.onSkip && !silent) {
|
|
||||||
options.onSkip();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
const rest = () => source.substring(cursor.pos);
|
|
||||||
const grabN = (num = 0) => source.substring(cursor.pos, cursor.pos + num);
|
|
||||||
const curr = () => source[cursor.pos];
|
|
||||||
const prev = () => {
|
|
||||||
const prevPos = cursor.pos - 1;
|
|
||||||
|
|
||||||
return typeof source[prevPos] !== 'undefined' ? source[prevPos] : null;
|
|
||||||
};
|
|
||||||
const next = () => {
|
|
||||||
const nextPos = cursor.pos + 1;
|
|
||||||
|
|
||||||
return nextPos <= (source.length - 1) ? source[nextPos] : null;
|
|
||||||
};
|
|
||||||
const grabWhile = (cond, silent) => {
|
|
||||||
let start = 0;
|
|
||||||
|
|
||||||
if (hasNext()) {
|
|
||||||
start = cursor.pos;
|
|
||||||
|
|
||||||
while (hasNext() && cond(curr())) {
|
|
||||||
skip(1, silent);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return source.substring(start, cursor.pos);
|
|
||||||
};
|
|
||||||
/**
|
|
||||||
* @type {skip}
|
|
||||||
*/
|
|
||||||
this.skip = skip;
|
|
||||||
/**
|
|
||||||
* @returns {Boolean}
|
|
||||||
*/
|
|
||||||
this.hasNext = hasNext;
|
|
||||||
/**
|
|
||||||
* @returns {String}
|
|
||||||
*/
|
|
||||||
this.getCurr = curr;
|
|
||||||
/**
|
|
||||||
* @returns {String}
|
|
||||||
*/
|
|
||||||
this.getRest = rest;
|
|
||||||
/**
|
|
||||||
* @returns {String}
|
|
||||||
*/
|
|
||||||
this.getNext = next;
|
|
||||||
/**
|
|
||||||
* @returns {String}
|
|
||||||
*/
|
|
||||||
this.getPrev = prev;
|
|
||||||
/**
|
|
||||||
* @returns {Boolean}
|
|
||||||
*/
|
|
||||||
this.isLast = isLast;
|
|
||||||
/**
|
|
||||||
* @returns {Boolean}
|
|
||||||
*/
|
|
||||||
this.includes = includes;
|
|
||||||
/**
|
|
||||||
* @param {Function} cond
|
|
||||||
* @param {Boolean} silent
|
|
||||||
* @return {String}
|
|
||||||
*/
|
|
||||||
this.grabWhile = grabWhile;
|
|
||||||
/**
|
|
||||||
* @param {Number} num
|
|
||||||
* @return {String}
|
|
||||||
*/
|
|
||||||
this.grabN = grabN;
|
|
||||||
/**
|
|
||||||
* Grabs rest of string until it find a char
|
|
||||||
* @param {String} char
|
|
||||||
* @return {String}
|
|
||||||
*/
|
|
||||||
this.substrUntilChar = substrUntilChar;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a grabber wrapper for source string, that helps to iterate over string char by char
|
|
||||||
* @param {String} source
|
|
||||||
* @param {Object} options
|
|
||||||
* @param {Function} options.onSkip
|
|
||||||
* @return CharGrabber
|
|
||||||
*/
|
|
||||||
export const createCharGrabber = (source, options) => new CharGrabber(source, options);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Trims string from start and end by char
|
|
||||||
* @example
|
|
||||||
* trimChar('*hello*', '*') ==> 'hello'
|
|
||||||
* @param {String} str
|
|
||||||
* @param {String} charToRemove
|
|
||||||
* @returns {String}
|
|
||||||
*/
|
|
||||||
export const trimChar = (str, charToRemove) => {
|
|
||||||
while (str.charAt(0) === charToRemove) {
|
|
||||||
// eslint-disable-next-line no-param-reassign
|
|
||||||
str = str.substring(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
while (str.charAt(str.length - 1) === charToRemove) {
|
|
||||||
// eslint-disable-next-line no-param-reassign
|
|
||||||
str = str.substring(0, str.length - 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
return str;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Unquotes \" to "
|
|
||||||
* @param str
|
|
||||||
* @return {String}
|
|
||||||
*/
|
|
||||||
export const unquote = (str) => str.replace(BACKSLASH + QUOTEMARK, QUOTEMARK);
|
|
||||||
|
|
||||||
function NodeList(values = []) {
|
|
||||||
const nodes = values;
|
|
||||||
|
|
||||||
const getLast = () => (
|
|
||||||
Array.isArray(nodes) && nodes.length > 0 && typeof nodes[nodes.length - 1] !== 'undefined'
|
|
||||||
? nodes[nodes.length - 1]
|
|
||||||
: null);
|
|
||||||
const flushLast = () => (nodes.length ? nodes.pop() : false);
|
|
||||||
const push = (value) => nodes.push(value);
|
|
||||||
const toArray = () => nodes;
|
|
||||||
|
|
||||||
this.push = push;
|
|
||||||
this.toArray = toArray;
|
|
||||||
this.getLast = getLast;
|
|
||||||
this.flushLast = flushLast;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @param values
|
|
||||||
* @return {NodeList}
|
|
||||||
*/
|
|
||||||
export const createList = (values = []) => new NodeList(values);
|
|
||||||
@@ -0,0 +1,121 @@
|
|||||||
|
import {
|
||||||
|
QUOTEMARK,
|
||||||
|
BACKSLASH,
|
||||||
|
} from '@bbob/plugin-helper';
|
||||||
|
|
||||||
|
export type CharGrabberOptions = {
|
||||||
|
onSkip?: () => void
|
||||||
|
}
|
||||||
|
|
||||||
|
export class CharGrabber {
|
||||||
|
private s: string;
|
||||||
|
private c: { len: number; pos: number };
|
||||||
|
private o: CharGrabberOptions;
|
||||||
|
|
||||||
|
constructor(source: string, options: CharGrabberOptions = {}) {
|
||||||
|
this.s = source
|
||||||
|
this.c = {
|
||||||
|
pos: 0,
|
||||||
|
len: source.length,
|
||||||
|
};
|
||||||
|
|
||||||
|
this.o = options
|
||||||
|
}
|
||||||
|
|
||||||
|
skip(num = 1, silent?: boolean) {
|
||||||
|
this.c.pos += num;
|
||||||
|
|
||||||
|
if (this.o && this.o.onSkip && !silent) {
|
||||||
|
this.o.onSkip();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
hasNext() {
|
||||||
|
return this.c.len > this.c.pos
|
||||||
|
}
|
||||||
|
|
||||||
|
getCurr() {
|
||||||
|
return this.s[this.c.pos]
|
||||||
|
}
|
||||||
|
|
||||||
|
getRest() {
|
||||||
|
return this.s.substring(this.c.pos)
|
||||||
|
}
|
||||||
|
|
||||||
|
getNext() {
|
||||||
|
const nextPos = this.c.pos + 1;
|
||||||
|
|
||||||
|
return nextPos <= (this.s.length - 1) ? this.s[nextPos] : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
getPrev() {
|
||||||
|
const prevPos = this.c.pos - 1;
|
||||||
|
|
||||||
|
return typeof this.s[prevPos] !== 'undefined' ? this.s[prevPos] : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
isLast() {
|
||||||
|
return this.c.pos === this.c.len
|
||||||
|
}
|
||||||
|
|
||||||
|
includes(val: string) {
|
||||||
|
return this.s.indexOf(val, this.c.pos) >= 0
|
||||||
|
}
|
||||||
|
|
||||||
|
grabWhile(condition: (curr: string) => boolean, silent?: boolean) {
|
||||||
|
let start = 0;
|
||||||
|
|
||||||
|
if (this.hasNext()) {
|
||||||
|
start = this.c.pos;
|
||||||
|
|
||||||
|
while (this.hasNext() && condition(this.getCurr())) {
|
||||||
|
this.skip(1, silent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.s.substring(start, this.c.pos);
|
||||||
|
}
|
||||||
|
|
||||||
|
grabN(num: number = 0) {
|
||||||
|
return this.s.substring(this.c.pos, this.c.pos + num)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Grabs rest of string until it find a char
|
||||||
|
*/
|
||||||
|
substrUntilChar(char: string) {
|
||||||
|
const { pos } = this.c;
|
||||||
|
const idx = this.s.indexOf(char, pos);
|
||||||
|
|
||||||
|
return idx >= 0 ? this.s.substring(pos, idx) : '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a grabber wrapper for source string, that helps to iterate over string char by char
|
||||||
|
*/
|
||||||
|
export const createCharGrabber = (source: string, options?: CharGrabberOptions) => new CharGrabber(source, options);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Trims string from start and end by char
|
||||||
|
* @example
|
||||||
|
* trimChar('*hello*', '*') ==> 'hello'
|
||||||
|
*/
|
||||||
|
export const trimChar = (str: string, charToRemove: string) => {
|
||||||
|
while (str.charAt(0) === charToRemove) {
|
||||||
|
// eslint-disable-next-line no-param-reassign
|
||||||
|
str = str.substring(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
while (str.charAt(str.length - 1) === charToRemove) {
|
||||||
|
// eslint-disable-next-line no-param-reassign
|
||||||
|
str = str.substring(0, str.length - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
return str;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unquotes \" to "
|
||||||
|
*/
|
||||||
|
export const unquote = (str: string) => str.replace(BACKSLASH + QUOTEMARK, QUOTEMARK);
|
||||||
@@ -2,7 +2,8 @@ import { TagNode } from "../src/index";
|
|||||||
|
|
||||||
describe('index', () => {
|
describe('index', () => {
|
||||||
test('tag with content and params', () => {
|
test('tag with content and params', () => {
|
||||||
const tagNode = TagNode.create('test', {test: 1}, ['Hello']);
|
const attrs = {test: 1}
|
||||||
|
const tagNode = TagNode.create('test', attrs, ['Hello']);
|
||||||
|
|
||||||
expect(String(tagNode)).toBe('[test test="1"]Hello[/test]');
|
expect(String(tagNode)).toBe('[test test="1"]Hello[/test]');
|
||||||
});
|
});
|
||||||
@@ -1,6 +1,14 @@
|
|||||||
import { TYPE_ID, VALUE_ID, TYPE_WORD, TYPE_TAG, TYPE_ATTR_NAME, TYPE_ATTR_VALUE, TYPE_SPACE, TYPE_NEW_LINE} from '../src/Token'
|
import { TYPE_ID, VALUE_ID, TYPE_WORD, TYPE_TAG, TYPE_ATTR_NAME, TYPE_ATTR_VALUE, TYPE_SPACE, TYPE_NEW_LINE} from '../src/Token'
|
||||||
import { createLexer } from '../src/lexer'
|
import { createLexer } from '../src/lexer'
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
namespace jest {
|
||||||
|
interface Matchers<R> {
|
||||||
|
toBeMantchOutput(expected: Array<unknown>): CustomMatcherResult;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const TYPE = {
|
const TYPE = {
|
||||||
WORD: TYPE_WORD,
|
WORD: TYPE_WORD,
|
||||||
TAG: TYPE_TAG,
|
TAG: TYPE_TAG,
|
||||||
@@ -10,13 +18,15 @@ const TYPE = {
|
|||||||
NEW_LINE: TYPE_NEW_LINE,
|
NEW_LINE: TYPE_NEW_LINE,
|
||||||
};
|
};
|
||||||
|
|
||||||
const TYPE_NAMES = Object.fromEntries(Object.keys(TYPE).map(key => [TYPE[key], key]));
|
const TYPE_NAMES = Object.fromEntries(Object.keys(TYPE).map((key: keyof typeof TYPE) => [TYPE[key], key]));
|
||||||
|
|
||||||
const tokenize = input => (createLexer(input).tokenize());
|
const tokenize = (input: string) => (createLexer(input).tokenize());
|
||||||
const tokenizeEscape = input => (createLexer(input, { enableEscapeTags: true }).tokenize());
|
const tokenizeEscape = (input: string) => (createLexer(input, { enableEscapeTags: true }).tokenize());
|
||||||
const tokenizeContextFreeTags = (input, tags = []) => (createLexer(input, { contextFreeTags: tags }).tokenize());
|
const tokenizeContextFreeTags = (input: string, tags: string[] = []) => (createLexer(input, { contextFreeTags: tags }).tokenize());
|
||||||
|
|
||||||
describe('lexer', () => {
|
describe('lexer', () => {
|
||||||
|
|
||||||
|
|
||||||
expect.extend({
|
expect.extend({
|
||||||
toBeMantchOutput(tokens, output) {
|
toBeMantchOutput(tokens, output) {
|
||||||
if (tokens.length !== output.length) {
|
if (tokens.length !== output.length) {
|
||||||
@@ -515,7 +525,7 @@ describe('lexer', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe('html', () => {
|
describe('html', () => {
|
||||||
const tokenizeHTML = input => createLexer(input, { openTag: '<', closeTag: '>' }).tokenize();
|
const tokenizeHTML = (input: string) => createLexer(input, { openTag: '<', closeTag: '>' }).tokenize();
|
||||||
|
|
||||||
test('normal attributes', () => {
|
test('normal attributes', () => {
|
||||||
const content = `<button id="test0" class="value0" title="value1">class="value0" title="value1"</button>`;
|
const content = `<button id="test0" class="value0" title="value1">class="value0" title="value1"</button>`;
|
||||||
@@ -593,9 +603,7 @@ input.buttonred{cursor:hand;font-family:verdana;background:#d12124;color:#fff;he
|
|||||||
-->
|
-->
|
||||||
</style>`
|
</style>`
|
||||||
const tokens = tokenizeHTML(content);
|
const tokens = tokenizeHTML(content);
|
||||||
const output = [];
|
expect(tokens).toBeMantchOutput([]);
|
||||||
|
|
||||||
expect(tokens).toBeMantchOutput(output);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test.skip('script tag', () => {
|
test.skip('script tag', () => {
|
||||||
@@ -606,9 +614,7 @@ input.buttonred{cursor:hand;font-family:verdana;background:#d12124;color:#fff;he
|
|||||||
//-->
|
//-->
|
||||||
</script>`;
|
</script>`;
|
||||||
const tokens = tokenizeHTML(content);
|
const tokens = tokenizeHTML(content);
|
||||||
const output = [];
|
expect(tokens).toBeMantchOutput([]);
|
||||||
|
|
||||||
expect(tokens).toBeMantchOutput(output);
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
@@ -1,7 +1,8 @@
|
|||||||
import { parse } from '../src'
|
import { parse } from '../src'
|
||||||
|
import type { TagNodeTree } from "@bbob/plugin-helper";
|
||||||
|
|
||||||
describe('Parser', () => {
|
describe('Parser', () => {
|
||||||
const expectOutput = (ast, output) => {
|
const expectOutput = (ast: TagNodeTree, output: Partial<TagNodeTree>) => {
|
||||||
expect(ast).toBeInstanceOf(Array);
|
expect(ast).toBeInstanceOf(Array);
|
||||||
expect(ast).toEqual(output);
|
expect(ast).toEqual(output);
|
||||||
};
|
};
|
||||||
@@ -440,7 +441,7 @@ sdfasdfasdf
|
|||||||
})
|
})
|
||||||
|
|
||||||
describe('html', () => {
|
describe('html', () => {
|
||||||
const parseHTML = input => parse(input, { openTag: '<', closeTag: '>' });
|
const parseHTML = (input: string) => parse(input, { openTag: '<', closeTag: '>' });
|
||||||
|
|
||||||
test('normal attributes', () => {
|
test('normal attributes', () => {
|
||||||
const content = `<button id="test0" class="value0" title="value1">class="value0" title="value1"</button>`;
|
const content = `<button id="test0" class="value0" title="value1">class="value0" title="value1"</button>`;
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
{
|
||||||
|
"extends": "../../tsconfig.json",
|
||||||
|
"compilerOptions": {
|
||||||
|
"baseUrl": ".",
|
||||||
|
"outDir": "./types"
|
||||||
|
},
|
||||||
|
"include": [
|
||||||
|
"./src/**/*"
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -2,3 +2,7 @@ coverage
|
|||||||
dist
|
dist
|
||||||
lib
|
lib
|
||||||
es
|
es
|
||||||
|
types
|
||||||
|
test/*.d.ts
|
||||||
|
test/*.map
|
||||||
|
tsconfig.tsbuildinfo
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
package-lock.json
|
pnpm-lock.yaml
|
||||||
coverage
|
coverage
|
||||||
src
|
src
|
||||||
!dist
|
!dist
|
||||||
|
|||||||
@@ -7,11 +7,49 @@
|
|||||||
"plugin",
|
"plugin",
|
||||||
"helper"
|
"helper"
|
||||||
],
|
],
|
||||||
|
"files": [
|
||||||
|
"dist",
|
||||||
|
"lib",
|
||||||
|
"src",
|
||||||
|
"es",
|
||||||
|
"types"
|
||||||
|
],
|
||||||
"main": "lib/index.js",
|
"main": "lib/index.js",
|
||||||
"module": "es/index.js",
|
"module": "es/index.js",
|
||||||
"jsnext:main": "es/index.js",
|
"jsnext:main": "es/index.js",
|
||||||
"browser": "dist/index.js",
|
"browser": "dist/index.js",
|
||||||
"browserName": "BbobPluginHelper",
|
"browserName": "BbobPluginHelper",
|
||||||
|
"types": "types/index.d.ts",
|
||||||
|
"exports": {
|
||||||
|
".": {
|
||||||
|
"types": "./types/index.d.ts",
|
||||||
|
"import": "./es/index.js",
|
||||||
|
"require": "./lib/index.js",
|
||||||
|
"browser": "./dist/index.min.js",
|
||||||
|
"umd": "./dist/index.min.js"
|
||||||
|
},
|
||||||
|
"./char": {
|
||||||
|
"types": "./types/char.d.ts",
|
||||||
|
"import": "./es/char.js",
|
||||||
|
"require": "./lib/char.js",
|
||||||
|
"browser": "./dist/index.min.js",
|
||||||
|
"umd": "./dist/index.min.js"
|
||||||
|
},
|
||||||
|
"./helpers": {
|
||||||
|
"types": "./types/helpers.d.ts",
|
||||||
|
"import": "./es/helpers.js",
|
||||||
|
"require": "./lib/helpers.js",
|
||||||
|
"browser": "./dist/index.min.js",
|
||||||
|
"umd": "./dist/index.min.js"
|
||||||
|
},
|
||||||
|
"./TagNode": {
|
||||||
|
"types": "./types/TagNode.d.ts",
|
||||||
|
"import": "./es/TagNode.js",
|
||||||
|
"require": "./lib/TagNode.js",
|
||||||
|
"browser": "./dist/index.min.js",
|
||||||
|
"umd": "./dist/index.min.js"
|
||||||
|
}
|
||||||
|
},
|
||||||
"homepage": "https://github.com/JiLiZART/bbob",
|
"homepage": "https://github.com/JiLiZART/bbob",
|
||||||
"author": "Nikolay Kostyurin <jilizart@gmail.com>",
|
"author": "Nikolay Kostyurin <jilizart@gmail.com>",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
@@ -23,35 +61,31 @@
|
|||||||
"url": "git://github.com/JiLiZART/bbob.git"
|
"url": "git://github.com/JiLiZART/bbob.git"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build:commonjs": "../../scripts/pkg-task build-commonjs",
|
"build:commonjs": "pkg-task",
|
||||||
"build:es": "../../scripts/pkg-task build-es",
|
"build:es": "pkg-task",
|
||||||
"build:umd": "../../scripts/pkg-task build-umd",
|
"build:umd": "pkg-task",
|
||||||
"build": "npm run build:commonjs && npm run build:es && npm run build:umd",
|
"build": "pkg-task",
|
||||||
"test": "../../scripts/pkg-task test",
|
"test": "pkg-task",
|
||||||
"cover": "../../scripts/pkg-task cover",
|
"cover": "pkg-task",
|
||||||
"lint": "../../scripts/pkg-task lint",
|
"lint": "pkg-task",
|
||||||
"size": "../../scripts/pkg-task size",
|
"size": "pkg-task",
|
||||||
"bundlesize": "../../scripts/pkg-task bundlesize",
|
"bundlesize": "pkg-task",
|
||||||
|
"types": "pkg-task",
|
||||||
"prepublishOnly": "npm run build"
|
"prepublishOnly": "npm run build"
|
||||||
},
|
},
|
||||||
"size-limit": [
|
"size-limit": [
|
||||||
{
|
{
|
||||||
"path": "lib/index.js"
|
"path": "./dist/index.min.js",
|
||||||
|
"size": "1 KB"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"bundlesize": [
|
"bundlesize": [
|
||||||
{
|
{
|
||||||
"path": "./dist/index.min.js",
|
"path": "./dist/index.min.js",
|
||||||
"maxSize": "1024 B"
|
"maxSize": "1 KB"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"publishConfig": {
|
"publishConfig": {
|
||||||
"registry": "https://registry.npmjs.org/"
|
"registry": "https://registry.npmjs.org/"
|
||||||
},
|
}
|
||||||
"files": [
|
|
||||||
"dist",
|
|
||||||
"lib",
|
|
||||||
"src",
|
|
||||||
"es"
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,77 +0,0 @@
|
|||||||
import { OPEN_BRAKET, CLOSE_BRAKET, SLASH } from './char';
|
|
||||||
import {
|
|
||||||
getNodeLength, appendToNode, attrsToString, attrValue, getUniqAttr,
|
|
||||||
} from './helpers';
|
|
||||||
|
|
||||||
const getTagAttrs = (tag, params) => {
|
|
||||||
const uniqAattr = getUniqAttr(params);
|
|
||||||
|
|
||||||
if (uniqAattr) {
|
|
||||||
const tagAttr = attrValue(tag, uniqAattr);
|
|
||||||
const attrs = { ...params };
|
|
||||||
|
|
||||||
delete attrs[uniqAattr];
|
|
||||||
|
|
||||||
const attrsStr = attrsToString(attrs);
|
|
||||||
|
|
||||||
return `${tagAttr}${attrsStr}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
return `${tag}${attrsToString(params)}`;
|
|
||||||
};
|
|
||||||
|
|
||||||
class TagNode {
|
|
||||||
constructor(tag, attrs, content) {
|
|
||||||
this.tag = tag;
|
|
||||||
this.attrs = attrs;
|
|
||||||
this.content = Array.isArray(content) ? content : [content];
|
|
||||||
}
|
|
||||||
|
|
||||||
attr(name, value) {
|
|
||||||
if (typeof value !== 'undefined') {
|
|
||||||
this.attrs[name] = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.attrs[name];
|
|
||||||
}
|
|
||||||
|
|
||||||
append(value) {
|
|
||||||
return appendToNode(this, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
get length() {
|
|
||||||
return getNodeLength(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
toTagStart({ openTag = OPEN_BRAKET, closeTag = CLOSE_BRAKET } = {}) {
|
|
||||||
const tagAttrs = getTagAttrs(this.tag, this.attrs);
|
|
||||||
|
|
||||||
return `${openTag}${tagAttrs}${closeTag}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
toTagEnd({ openTag = OPEN_BRAKET, closeTag = CLOSE_BRAKET } = {}) {
|
|
||||||
return `${openTag}${SLASH}${this.tag}${closeTag}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
toTagNode() {
|
|
||||||
return new TagNode(this.tag.toLowerCase(), this.attrs, this.content);
|
|
||||||
}
|
|
||||||
|
|
||||||
toString({ openTag = OPEN_BRAKET, closeTag = CLOSE_BRAKET } = {}) {
|
|
||||||
const isEmpty = this.content.length === 0;
|
|
||||||
const content = this.content.reduce((r, node) => r + node.toString({ openTag, closeTag }), '');
|
|
||||||
const tagStart = this.toTagStart({ openTag, closeTag });
|
|
||||||
|
|
||||||
if (isEmpty) {
|
|
||||||
return tagStart;
|
|
||||||
}
|
|
||||||
|
|
||||||
return `${tagStart}${content}${this.toTagEnd({ openTag, closeTag })}`;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
TagNode.create = (tag, attrs = {}, content = []) => new TagNode(tag, attrs, content);
|
|
||||||
TagNode.isOf = (node, type) => (node.tag === type);
|
|
||||||
|
|
||||||
export { TagNode };
|
|
||||||
export default TagNode;
|
|
||||||
@@ -0,0 +1,115 @@
|
|||||||
|
import { OPEN_BRAKET, CLOSE_BRAKET, SLASH } from './char';
|
||||||
|
import {
|
||||||
|
getUniqAttr,
|
||||||
|
getNodeLength,
|
||||||
|
appendToNode,
|
||||||
|
attrsToString,
|
||||||
|
attrValue,
|
||||||
|
isTagNode,
|
||||||
|
} from './helpers';
|
||||||
|
|
||||||
|
import type { NodeContent, TagNodeObject, TagNodeTree } from "./types";
|
||||||
|
|
||||||
|
const getTagAttrs = <AttrValue>(tag: string, params: Record<string, AttrValue>) => {
|
||||||
|
const uniqAttr = getUniqAttr(params);
|
||||||
|
|
||||||
|
if (uniqAttr) {
|
||||||
|
const tagAttr = attrValue(tag, uniqAttr);
|
||||||
|
const attrs = { ...params };
|
||||||
|
|
||||||
|
delete attrs[String(uniqAttr)];
|
||||||
|
|
||||||
|
const attrsStr = attrsToString(attrs);
|
||||||
|
|
||||||
|
return `${tagAttr}${attrsStr}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return `${tag}${attrsToString(params)}`;
|
||||||
|
};
|
||||||
|
|
||||||
|
const renderContent = (content: TagNodeTree, openTag: string, closeTag: string) => {
|
||||||
|
const toString = (node: NodeContent) => {
|
||||||
|
if (isTagNode(node)) {
|
||||||
|
return node.toString({ openTag, closeTag })
|
||||||
|
}
|
||||||
|
|
||||||
|
return String(node)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Array.isArray(content)) {
|
||||||
|
return content.reduce<string>((r, node) => {
|
||||||
|
if (node !== null) {
|
||||||
|
return r + toString(node)
|
||||||
|
}
|
||||||
|
|
||||||
|
return r
|
||||||
|
}, '')
|
||||||
|
}
|
||||||
|
|
||||||
|
if (content) {
|
||||||
|
return toString(content)
|
||||||
|
}
|
||||||
|
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
export class TagNode implements TagNodeObject {
|
||||||
|
public readonly tag: string
|
||||||
|
public attrs: Record<string, unknown>
|
||||||
|
public content: TagNodeTree
|
||||||
|
|
||||||
|
constructor(tag: string, attrs: Record<string, unknown>, content: TagNodeTree) {
|
||||||
|
this.tag = tag;
|
||||||
|
this.attrs = attrs;
|
||||||
|
this.content = content
|
||||||
|
}
|
||||||
|
|
||||||
|
attr(name: string, value?: unknown) {
|
||||||
|
if (typeof value !== 'undefined') {
|
||||||
|
this.attrs[name] = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.attrs[name];
|
||||||
|
}
|
||||||
|
|
||||||
|
append(value: string) {
|
||||||
|
return appendToNode(this, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
get length(): number {
|
||||||
|
return getNodeLength(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
toTagStart({ openTag = OPEN_BRAKET, closeTag = CLOSE_BRAKET } = {}) {
|
||||||
|
const tagAttrs = getTagAttrs(this.tag, this.attrs);
|
||||||
|
|
||||||
|
return `${openTag}${tagAttrs}${closeTag}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
toTagEnd({ openTag = OPEN_BRAKET, closeTag = CLOSE_BRAKET } = {}) {
|
||||||
|
return `${openTag}${SLASH}${this.tag}${closeTag}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
toTagNode() {
|
||||||
|
return new TagNode(this.tag.toLowerCase(), this.attrs, this.content);
|
||||||
|
}
|
||||||
|
|
||||||
|
toString({ openTag = OPEN_BRAKET, closeTag = CLOSE_BRAKET } = {}): string {
|
||||||
|
const content = this.content ? renderContent(this.content, openTag, closeTag) : ''
|
||||||
|
const tagStart = this.toTagStart({ openTag, closeTag });
|
||||||
|
|
||||||
|
if (this.content === null || Array.isArray(this.content) && this.content.length === 0) {
|
||||||
|
return tagStart;
|
||||||
|
}
|
||||||
|
|
||||||
|
return `${tagStart}${content}${this.toTagEnd({ openTag, closeTag })}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
static create(tag: string, attrs: Record<string, unknown> = {}, content: TagNodeTree = []) {
|
||||||
|
return new TagNode(tag, attrs, content)
|
||||||
|
}
|
||||||
|
|
||||||
|
static isOf(node: TagNode, type: string) {
|
||||||
|
return (node.tag === type)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,100 +0,0 @@
|
|||||||
import { N } from './char';
|
|
||||||
|
|
||||||
const isTagNode = (el) => typeof el === 'object' && !!el.tag;
|
|
||||||
const isStringNode = (el) => typeof el === 'string';
|
|
||||||
const isEOL = (el) => el === N;
|
|
||||||
|
|
||||||
const keysReduce = (obj, reduce, def) => Object.keys(obj).reduce(reduce, def);
|
|
||||||
|
|
||||||
const getNodeLength = (node) => {
|
|
||||||
if (isTagNode(node)) {
|
|
||||||
return node.content.reduce((count, contentNode) => count + getNodeLength(contentNode), 0);
|
|
||||||
} if (isStringNode(node)) {
|
|
||||||
return node.length;
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Appends value to Tag Node
|
|
||||||
* @param {TagNode} node
|
|
||||||
* @param value
|
|
||||||
*/
|
|
||||||
const appendToNode = (node, value) => {
|
|
||||||
node.content.push(value);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Replaces " to &qquot;
|
|
||||||
* @param {String} value
|
|
||||||
*/
|
|
||||||
const escapeHTML = (value) => value
|
|
||||||
.replace(/&/g, '&')
|
|
||||||
.replace(/</g, '<')
|
|
||||||
.replace(/>/g, '>')
|
|
||||||
.replace(/"/g, '"')
|
|
||||||
.replace(/'/g, ''')
|
|
||||||
// eslint-disable-next-line no-script-url
|
|
||||||
.replace(/(javascript|data|vbscript):/gi, '$1%3A');
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Acept name and value and return valid html5 attribute string
|
|
||||||
* @param {String} name
|
|
||||||
* @param {String} value
|
|
||||||
* @return {string}
|
|
||||||
*/
|
|
||||||
const attrValue = (name, value) => {
|
|
||||||
const type = typeof value;
|
|
||||||
|
|
||||||
const types = {
|
|
||||||
boolean: () => (value ? `${name}` : ''),
|
|
||||||
number: () => `${name}="${value}"`,
|
|
||||||
string: () => `${name}="${escapeHTML(value)}"`,
|
|
||||||
object: () => `${name}="${escapeHTML(JSON.stringify(value))}"`,
|
|
||||||
};
|
|
||||||
|
|
||||||
return types[type] ? types[type]() : '';
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Transforms attrs to html params string
|
|
||||||
* @param values
|
|
||||||
*/
|
|
||||||
const attrsToString = (values) => {
|
|
||||||
// To avoid some malformed attributes
|
|
||||||
if (values == null) {
|
|
||||||
return '';
|
|
||||||
}
|
|
||||||
|
|
||||||
return keysReduce(
|
|
||||||
values,
|
|
||||||
(arr, key) => [...arr, attrValue(key, values[key])],
|
|
||||||
[''],
|
|
||||||
).join(' ');
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets value from
|
|
||||||
* @example
|
|
||||||
* getUniqAttr({ 'foo': true, 'bar': bar' }) => 'bar'
|
|
||||||
* @param attrs
|
|
||||||
* @returns {string}
|
|
||||||
*/
|
|
||||||
const getUniqAttr = (attrs) => keysReduce(
|
|
||||||
attrs,
|
|
||||||
(res, key) => (attrs[key] === key ? attrs[key] : null),
|
|
||||||
null,
|
|
||||||
);
|
|
||||||
|
|
||||||
export {
|
|
||||||
attrsToString,
|
|
||||||
attrValue,
|
|
||||||
appendToNode,
|
|
||||||
escapeHTML,
|
|
||||||
getNodeLength,
|
|
||||||
getUniqAttr,
|
|
||||||
isTagNode,
|
|
||||||
isStringNode,
|
|
||||||
isEOL,
|
|
||||||
};
|
|
||||||
@@ -0,0 +1,125 @@
|
|||||||
|
import { N } from './char';
|
||||||
|
import type { TagNode } from "./TagNode";
|
||||||
|
import type { NodeContent, StringNode } from "./types";
|
||||||
|
|
||||||
|
function isTagNode(el: unknown): el is TagNode {
|
||||||
|
return typeof el === 'object' && el !== null && 'tag' in el;
|
||||||
|
}
|
||||||
|
|
||||||
|
function isStringNode(el: unknown): el is StringNode {
|
||||||
|
return typeof el === 'string';
|
||||||
|
}
|
||||||
|
|
||||||
|
// check string is end of line
|
||||||
|
function isEOL(el: string) {
|
||||||
|
return el === N
|
||||||
|
}
|
||||||
|
|
||||||
|
function keysReduce<Res, Def extends Res, T extends Record<string, unknown>>(obj: T, reduce: (acc: Def, key: keyof T) => Res, def: Def): Res {
|
||||||
|
const keys = Object.keys(obj)
|
||||||
|
|
||||||
|
return keys.reduce((acc, key) => reduce(acc, key), def)
|
||||||
|
}
|
||||||
|
|
||||||
|
function getNodeLength(node: NodeContent): number {
|
||||||
|
if (isTagNode(node) && Array.isArray(node.content)) {
|
||||||
|
return node.content.reduce<number>((count, contentNode) => {
|
||||||
|
return count + getNodeLength(contentNode)
|
||||||
|
}, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isStringNode(node)) {
|
||||||
|
return String(node).length;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
function appendToNode(node: TagNode, value: NodeContent) {
|
||||||
|
if (Array.isArray(node.content)) {
|
||||||
|
node.content.push(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Replaces " to &qquot;
|
||||||
|
* @param {string} value
|
||||||
|
*/
|
||||||
|
function escapeAttrValue(value: string) {
|
||||||
|
return value
|
||||||
|
.replace(/&/g, '&')
|
||||||
|
.replace(/</g, '<')
|
||||||
|
.replace(/>/g, '>')
|
||||||
|
.replace(/"/g, '"')
|
||||||
|
.replace(/'/g, ''')
|
||||||
|
// eslint-disable-next-line no-script-url
|
||||||
|
.replace(/(javascript|data|vbscript):/gi, '$1%3A');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @deprecated use escapeAttrValue
|
||||||
|
*/
|
||||||
|
const escapeHTML = escapeAttrValue
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Accept name and value and return valid html5 attribute string
|
||||||
|
*/
|
||||||
|
function attrValue<AttrValue = unknown>(name: string, value: AttrValue) {
|
||||||
|
// in case of performance
|
||||||
|
switch (typeof value) {
|
||||||
|
case 'boolean':
|
||||||
|
return value ? `${name}` : ''
|
||||||
|
case 'number':
|
||||||
|
return `${name}="${value}"`
|
||||||
|
case 'string':
|
||||||
|
return `${name}="${escapeAttrValue(value as string)}"`
|
||||||
|
case 'object':
|
||||||
|
return `${name}="${escapeAttrValue(JSON.stringify(value))}"`
|
||||||
|
default:
|
||||||
|
return ''
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Transforms attrs to html params string
|
||||||
|
* @example
|
||||||
|
* attrsToString({ 'foo': true, 'bar': bar' }) => 'foo="true" bar="bar"'
|
||||||
|
*/
|
||||||
|
function attrsToString<AttrValue = unknown>(values: Record<string, AttrValue> | null) {
|
||||||
|
// To avoid some malformed attributes
|
||||||
|
if (values == null) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
return keysReduce(
|
||||||
|
values,
|
||||||
|
(arr, key) => [...arr, attrValue(key, values[key])],
|
||||||
|
[''],
|
||||||
|
).join(' ');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets value from
|
||||||
|
* @example
|
||||||
|
* getUniqAttr({ 'foo': true, 'bar': bar' }) => 'bar'
|
||||||
|
*/
|
||||||
|
function getUniqAttr<Value>(attrs: Record<string, Value>) {
|
||||||
|
return keysReduce(
|
||||||
|
attrs,
|
||||||
|
(res, key) => (attrs[key] === key ? attrs[key] : null),
|
||||||
|
null,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export {
|
||||||
|
attrsToString,
|
||||||
|
attrValue,
|
||||||
|
appendToNode,
|
||||||
|
escapeHTML,
|
||||||
|
escapeAttrValue,
|
||||||
|
getNodeLength,
|
||||||
|
getUniqAttr,
|
||||||
|
isTagNode,
|
||||||
|
isStringNode,
|
||||||
|
isEOL,
|
||||||
|
};
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
export * from './helpers';
|
|
||||||
export * from './char';
|
|
||||||
export { TagNode } from './TagNode';
|
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
export * from "./helpers";
|
||||||
|
export * from "./char";
|
||||||
|
export * from "./TagNode";
|
||||||
|
export * from "./types";
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
export type StringNode = string | number
|
||||||
|
|
||||||
|
export interface TagNodeObject {
|
||||||
|
readonly tag: string
|
||||||
|
attrs: Record<string, unknown>
|
||||||
|
content: TagNodeTree
|
||||||
|
}
|
||||||
|
|
||||||
|
export type NodeContent = TagNodeObject | StringNode | null
|
||||||
|
|
||||||
|
export type PartialNodeContent = Partial<TagNodeObject> | StringNode | null
|
||||||
|
|
||||||
|
export type TagNodeTree = NodeContent | NodeContent[] | null
|
||||||
+31
@@ -13,6 +13,37 @@ describe('@bbob/plugin-helper/TagNode', () => {
|
|||||||
expect(TagNode.isOf(tagNode, 'test')).toBe(true);
|
expect(TagNode.isOf(tagNode, 'test')).toBe(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('attr', () => {
|
||||||
|
const tagNode = TagNode.create('test', {test: 1}, ['Hello']);
|
||||||
|
|
||||||
|
tagNode.attr('foo', 'bar')
|
||||||
|
|
||||||
|
expect(tagNode.attrs.foo).toBe('bar');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('append', () => {
|
||||||
|
const tagNode = TagNode.create('test', {test: 1}, ['Hello']);
|
||||||
|
|
||||||
|
tagNode.append('World')
|
||||||
|
|
||||||
|
expect(tagNode.content).toEqual(['Hello', 'World']);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('length', () => {
|
||||||
|
const tagNode = TagNode.create('test', {test: 1}, ['Hello', 'World']);
|
||||||
|
|
||||||
|
expect(tagNode.length).toEqual('HelloWorld'.length);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('toTagNode', () => {
|
||||||
|
const tagNode = TagNode.create('test', {test: 1}, ['Hello']);
|
||||||
|
const newTagNode = tagNode.toTagNode()
|
||||||
|
|
||||||
|
expect(newTagNode !== tagNode).toBe(true);
|
||||||
|
expect(newTagNode.tag).toEqual(tagNode.tag);
|
||||||
|
expect(newTagNode.content).toEqual(tagNode.content);
|
||||||
|
});
|
||||||
|
|
||||||
describe('toString', () => {
|
describe('toString', () => {
|
||||||
test('tag with content and params', () => {
|
test('tag with content and params', () => {
|
||||||
const tagNode = TagNode.create('test', {test: 1}, ['Hello']);
|
const tagNode = TagNode.create('test', {test: 1}, ['Hello']);
|
||||||
@@ -1,116 +0,0 @@
|
|||||||
import {
|
|
||||||
attrsToString,
|
|
||||||
attrValue,
|
|
||||||
appendToNode,
|
|
||||||
getNodeLength,
|
|
||||||
getUniqAttr,
|
|
||||||
isTagNode,
|
|
||||||
isStringNode,
|
|
||||||
isEOL,
|
|
||||||
} from '../src';
|
|
||||||
|
|
||||||
describe('@bbob/plugin-helper/helpers', () => {
|
|
||||||
test('appendToNode', () => {
|
|
||||||
const value = 'test';
|
|
||||||
const node = { content: [] };
|
|
||||||
|
|
||||||
appendToNode(node, value);
|
|
||||||
expect(node.content.pop()).toBe(value);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('getNodeLength', () => {
|
|
||||||
const node = {
|
|
||||||
tag: 'test',
|
|
||||||
content: [
|
|
||||||
'123',
|
|
||||||
{
|
|
||||||
tag: 'test2',
|
|
||||||
content: ['123']
|
|
||||||
}
|
|
||||||
]
|
|
||||||
};
|
|
||||||
|
|
||||||
expect(getNodeLength(node)).toBe(6)
|
|
||||||
});
|
|
||||||
|
|
||||||
test('isTagNode', () => {
|
|
||||||
const node = {
|
|
||||||
tag: 'test',
|
|
||||||
content: []
|
|
||||||
};
|
|
||||||
|
|
||||||
expect(isTagNode(node)).toBe(true)
|
|
||||||
});
|
|
||||||
|
|
||||||
test('isStringNode', () => {
|
|
||||||
const node = {
|
|
||||||
tag: 'test',
|
|
||||||
content: ['123']
|
|
||||||
};
|
|
||||||
|
|
||||||
expect(isStringNode(node.content[0])).toBe(true);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('attrValue boolean', () => {
|
|
||||||
expect(attrValue('test', true)).toBe('test');
|
|
||||||
});
|
|
||||||
|
|
||||||
test('attrValue number', () => {
|
|
||||||
expect(attrValue('test', 123)).toBe('test="123"');
|
|
||||||
});
|
|
||||||
|
|
||||||
test('attrValue string', () => {
|
|
||||||
expect(attrValue('test', 'hello')).toBe('test="hello"');
|
|
||||||
});
|
|
||||||
|
|
||||||
test('attrValue object', () => {
|
|
||||||
const attrs = { tag: 'test' };
|
|
||||||
|
|
||||||
expect(attrValue('test', attrs)).toBe('test="{"tag":"test"}"');
|
|
||||||
});
|
|
||||||
|
|
||||||
test('isEOL', () => {
|
|
||||||
expect(isEOL('\n')).toBe(true)
|
|
||||||
});
|
|
||||||
|
|
||||||
test('attrsToString', () => {
|
|
||||||
expect(attrsToString({
|
|
||||||
tag: 'test',
|
|
||||||
foo: 'bar',
|
|
||||||
disabled: true
|
|
||||||
})).toBe(` tag="test" foo="bar" disabled`)
|
|
||||||
});
|
|
||||||
|
|
||||||
test('attrsToString undefined', () => {
|
|
||||||
expect(attrsToString(undefined)).toBe('')
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('attrsToString escape', () => {
|
|
||||||
test(`javascript:alert("hello")`, () => {
|
|
||||||
expect(attrsToString({
|
|
||||||
onclick: `javascript:alert('hello')`,
|
|
||||||
href: `javascript:alert('hello')`,
|
|
||||||
})).toBe(` onclick="javascript%3Aalert('hello')" href="javascript%3Aalert('hello')"`)
|
|
||||||
});
|
|
||||||
test(`JAVASCRIPT:alert("hello")`, () => {
|
|
||||||
expect(attrsToString({
|
|
||||||
onclick: `JAVASCRIPT:alert('hello')`,
|
|
||||||
href: `JAVASCRIPT:alert('hello')`,
|
|
||||||
})).toBe(` onclick="JAVASCRIPT%3Aalert('hello')" href="JAVASCRIPT%3Aalert('hello')"`)
|
|
||||||
});
|
|
||||||
test(`<tag>`, () => {
|
|
||||||
expect(attrsToString({
|
|
||||||
onclick: `<tag>`,
|
|
||||||
href: `<tag>`,
|
|
||||||
})).toBe(` onclick="<tag>" href="<tag>"`)
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
test('getUniqAttr with unq attr', () => {
|
|
||||||
expect(getUniqAttr({foo: true, 'http://bar.com': 'http://bar.com'})).toBe('http://bar.com')
|
|
||||||
});
|
|
||||||
|
|
||||||
test('getUniqAttr without unq attr', () => {
|
|
||||||
expect(getUniqAttr({foo: true})).toBe(null)
|
|
||||||
})
|
|
||||||
});
|
|
||||||
@@ -0,0 +1,117 @@
|
|||||||
|
import {
|
||||||
|
attrsToString,
|
||||||
|
attrValue,
|
||||||
|
appendToNode,
|
||||||
|
getNodeLength,
|
||||||
|
getUniqAttr,
|
||||||
|
isTagNode,
|
||||||
|
isStringNode,
|
||||||
|
isEOL,
|
||||||
|
TagNode,
|
||||||
|
} from '../src';
|
||||||
|
|
||||||
|
describe('@bbob/plugin-helper/helpers', () => {
|
||||||
|
test('appendToNode', () => {
|
||||||
|
const value = 'test';
|
||||||
|
const node = {content: []} as TagNode;
|
||||||
|
|
||||||
|
appendToNode(node, value);
|
||||||
|
expect(node.content.pop()).toBe(value);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('getNodeLength', () => {
|
||||||
|
const node = {
|
||||||
|
tag: 'test',
|
||||||
|
content: [
|
||||||
|
'123',
|
||||||
|
{
|
||||||
|
tag: 'test2',
|
||||||
|
content: ['123']
|
||||||
|
}
|
||||||
|
]
|
||||||
|
} as TagNode;
|
||||||
|
|
||||||
|
expect(getNodeLength(node)).toBe(6)
|
||||||
|
});
|
||||||
|
|
||||||
|
test('isTagNode', () => {
|
||||||
|
const node = {
|
||||||
|
tag: 'test',
|
||||||
|
content: []
|
||||||
|
} as TagNode;
|
||||||
|
|
||||||
|
expect(isTagNode(node)).toBe(true)
|
||||||
|
});
|
||||||
|
|
||||||
|
test('isStringNode', () => {
|
||||||
|
const node = {
|
||||||
|
tag: 'test',
|
||||||
|
content: ['123']
|
||||||
|
};
|
||||||
|
|
||||||
|
expect(isStringNode(node.content[0])).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('attrValue boolean', () => {
|
||||||
|
expect(attrValue('test', true)).toBe('test');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('attrValue number', () => {
|
||||||
|
expect(attrValue('test', 123)).toBe('test="123"');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('attrValue string', () => {
|
||||||
|
expect(attrValue('test', 'hello')).toBe('test="hello"');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('attrValue object', () => {
|
||||||
|
const attrs = {tag: 'test'};
|
||||||
|
|
||||||
|
expect(attrValue('test', attrs)).toBe('test="{"tag":"test"}"');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('isEOL', () => {
|
||||||
|
expect(isEOL('\n')).toBe(true)
|
||||||
|
});
|
||||||
|
|
||||||
|
test('attrsToString', () => {
|
||||||
|
expect(attrsToString({
|
||||||
|
tag: 'test',
|
||||||
|
foo: 'bar',
|
||||||
|
disabled: true
|
||||||
|
})).toBe(` tag="test" foo="bar" disabled`)
|
||||||
|
});
|
||||||
|
|
||||||
|
test('attrsToString undefined', () => {
|
||||||
|
expect(attrsToString(undefined)).toBe('')
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('attrsToString escape', () => {
|
||||||
|
test(`javascript:alert("hello")`, () => {
|
||||||
|
expect(attrsToString({
|
||||||
|
onclick: `javascript:alert('hello')`,
|
||||||
|
href: `javascript:alert('hello')`,
|
||||||
|
})).toBe(` onclick="javascript%3Aalert('hello')" href="javascript%3Aalert('hello')"`)
|
||||||
|
});
|
||||||
|
test(`JAVASCRIPT:alert("hello")`, () => {
|
||||||
|
expect(attrsToString({
|
||||||
|
onclick: `JAVASCRIPT:alert('hello')`,
|
||||||
|
href: `JAVASCRIPT:alert('hello')`,
|
||||||
|
})).toBe(` onclick="JAVASCRIPT%3Aalert('hello')" href="JAVASCRIPT%3Aalert('hello')"`)
|
||||||
|
});
|
||||||
|
test(`<tag>`, () => {
|
||||||
|
expect(attrsToString({
|
||||||
|
onclick: `<tag>`,
|
||||||
|
href: `<tag>`,
|
||||||
|
})).toBe(` onclick="<tag>" href="<tag>"`)
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test('getUniqAttr with unq attr', () => {
|
||||||
|
expect(getUniqAttr({foo: true, 'http://bar.com': 'http://bar.com'})).toBe('http://bar.com')
|
||||||
|
});
|
||||||
|
|
||||||
|
test('getUniqAttr without unq attr', () => {
|
||||||
|
expect(getUniqAttr({foo: true})).toBe(null)
|
||||||
|
})
|
||||||
|
});
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
{
|
||||||
|
"extends": "../../tsconfig.json",
|
||||||
|
"compilerOptions": {
|
||||||
|
"baseUrl": ".",
|
||||||
|
"outDir": "./types"
|
||||||
|
},
|
||||||
|
"include": [
|
||||||
|
"./src/**/*"
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -2,3 +2,6 @@ coverage
|
|||||||
dist
|
dist
|
||||||
lib
|
lib
|
||||||
es
|
es
|
||||||
|
types
|
||||||
|
test/*.d.ts
|
||||||
|
test/*.map
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
package-lock.json
|
pnpm-lock.yaml
|
||||||
coverage
|
coverage
|
||||||
src
|
src
|
||||||
!dist
|
!dist
|
||||||
|
|||||||
@@ -8,17 +8,28 @@
|
|||||||
"bbob"
|
"bbob"
|
||||||
],
|
],
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@bbob/plugin-helper": "workspace:*",
|
"@bbob/plugin-helper": "*",
|
||||||
"@bbob/preset": "workspace:*"
|
"@bbob/preset": "*"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@bbob/html": "workspace:*"
|
"@bbob/core": "*",
|
||||||
|
"@bbob/html": "*"
|
||||||
},
|
},
|
||||||
"main": "lib/index.js",
|
"main": "lib/index.js",
|
||||||
"module": "es/index.js",
|
"module": "es/index.js",
|
||||||
"jsnext:main": "es/index.js",
|
"jsnext:main": "es/index.js",
|
||||||
"browser": "dist/index.js",
|
"browser": "dist/index.js",
|
||||||
"browserName": "BbobPresetHTML5",
|
"browserName": "BbobPresetHTML5",
|
||||||
|
"types": "types/index.d.ts",
|
||||||
|
"exports": {
|
||||||
|
".": {
|
||||||
|
"types": "./types/index.d.ts",
|
||||||
|
"import": "./es/index.js",
|
||||||
|
"require": "./lib/index.js",
|
||||||
|
"browser": "./dist/index.min.js",
|
||||||
|
"umd": "./dist/index.min.js"
|
||||||
|
}
|
||||||
|
},
|
||||||
"homepage": "https://github.com/JiLiZART/bbob",
|
"homepage": "https://github.com/JiLiZART/bbob",
|
||||||
"author": "Nikolay Kostyurin <jilizart@gmail.com>",
|
"author": "Nikolay Kostyurin <jilizart@gmail.com>",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
@@ -30,19 +41,22 @@
|
|||||||
"url": "git://github.com/JiLiZART/bbob.git"
|
"url": "git://github.com/JiLiZART/bbob.git"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build:commonjs": "../../scripts/pkg-task build-commonjs",
|
"build:commonjs": "pkg-task",
|
||||||
"build:es": "../../scripts/pkg-task build-es",
|
"build:es": "pkg-task",
|
||||||
"build:umd": "../../scripts/pkg-task build-umd",
|
"build:umd": "pkg-task",
|
||||||
"build": "npm run build:commonjs && npm run build:es && npm run build:umd",
|
"build": "pkg-task",
|
||||||
"test": "../../scripts/pkg-task test",
|
"test": "pkg-task",
|
||||||
"cover": "../../scripts/pkg-task cover",
|
"cover": "pkg-task",
|
||||||
"lint": "../../scripts/pkg-task lint",
|
"lint": "pkg-task",
|
||||||
"size": "../../scripts/pkg-task size",
|
"size": "pkg-task",
|
||||||
"bundlesize": "../../scripts/pkg-task bundlesize"
|
"bundlesize": "pkg-task",
|
||||||
|
"types": "pkg-task",
|
||||||
|
"prepublishOnly": "npm run build"
|
||||||
},
|
},
|
||||||
"size-limit": [
|
"size-limit": [
|
||||||
{
|
{
|
||||||
"path": "lib/index.js"
|
"path": "./dist/index.min.js",
|
||||||
|
"size": "2.6 KB"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"bundlesize": [
|
"bundlesize": [
|
||||||
|
|||||||
+3
@@ -13,6 +13,9 @@ dependencies:
|
|||||||
version: link:../bbob-preset
|
version: link:../bbob-preset
|
||||||
|
|
||||||
devDependencies:
|
devDependencies:
|
||||||
|
'@bbob/core':
|
||||||
|
specifier: workspace:*
|
||||||
|
version: link:../bbob-core
|
||||||
'@bbob/html':
|
'@bbob/html':
|
||||||
specifier: workspace:*
|
specifier: workspace:*
|
||||||
version: link:../bbob-html
|
version: link:../bbob-html
|
||||||
|
|||||||
@@ -1,91 +0,0 @@
|
|||||||
/* eslint-disable no-plusplus,no-lonely-if */
|
|
||||||
import {
|
|
||||||
getUniqAttr, isStringNode, isTagNode, TagNode,
|
|
||||||
} from '@bbob/plugin-helper';
|
|
||||||
|
|
||||||
const isStartsWith = (node, type) => (node[0] === type);
|
|
||||||
|
|
||||||
const styleMap = {
|
|
||||||
color: (val) => `color:${val};`,
|
|
||||||
size: (val) => `font-size:${val};`,
|
|
||||||
};
|
|
||||||
|
|
||||||
const getStyleFromAttrs = (attrs) => Object
|
|
||||||
.keys(attrs)
|
|
||||||
.reduce((acc, key) => (styleMap[key] ? acc.concat(styleMap[key](attrs[key])) : acc), [])
|
|
||||||
.join(' ');
|
|
||||||
|
|
||||||
const asListItems = (content) => {
|
|
||||||
let listIdx = 0;
|
|
||||||
const listItems = [];
|
|
||||||
|
|
||||||
const createItemNode = () => TagNode.create('li');
|
|
||||||
const ensureListItem = (val) => {
|
|
||||||
listItems[listIdx] = listItems[listIdx] || val;
|
|
||||||
};
|
|
||||||
const addItem = (val) => {
|
|
||||||
if (listItems[listIdx] && listItems[listIdx].content) {
|
|
||||||
listItems[listIdx].content = listItems[listIdx].content.concat(val);
|
|
||||||
} else {
|
|
||||||
listItems[listIdx] = listItems[listIdx].concat(val);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
content.forEach((el) => {
|
|
||||||
if (isStringNode(el) && isStartsWith(el, '*')) {
|
|
||||||
if (listItems[listIdx]) {
|
|
||||||
listIdx++;
|
|
||||||
}
|
|
||||||
ensureListItem(createItemNode());
|
|
||||||
addItem(el.substr(1));
|
|
||||||
} else if (isTagNode(el) && TagNode.isOf(el, '*')) {
|
|
||||||
if (listItems[listIdx]) {
|
|
||||||
listIdx++;
|
|
||||||
}
|
|
||||||
ensureListItem(createItemNode());
|
|
||||||
} else if (!isTagNode(listItems[listIdx])) {
|
|
||||||
listIdx++;
|
|
||||||
ensureListItem(el);
|
|
||||||
} else if (listItems[listIdx]) {
|
|
||||||
addItem(el);
|
|
||||||
} else {
|
|
||||||
ensureListItem(el);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return [].concat(listItems);
|
|
||||||
};
|
|
||||||
|
|
||||||
const renderUrl = (node, render) => (getUniqAttr(node.attrs)
|
|
||||||
? getUniqAttr(node.attrs)
|
|
||||||
: render(node.content));
|
|
||||||
|
|
||||||
const toNode = (tag, attrs, content) => ({
|
|
||||||
tag,
|
|
||||||
attrs,
|
|
||||||
content,
|
|
||||||
});
|
|
||||||
|
|
||||||
const toStyle = (style) => ({ style });
|
|
||||||
|
|
||||||
export default {
|
|
||||||
b: (node) => toNode('span', toStyle('font-weight: bold;'), node.content),
|
|
||||||
i: (node) => toNode('span', toStyle('font-style: italic;'), node.content),
|
|
||||||
u: (node) => toNode('span', toStyle('text-decoration: underline;'), node.content),
|
|
||||||
s: (node) => toNode('span', toStyle('text-decoration: line-through;'), node.content),
|
|
||||||
url: (node, { render }, options) => toNode('a', {
|
|
||||||
href: renderUrl(node, render, options),
|
|
||||||
}, node.content),
|
|
||||||
img: (node, { render }) => toNode('img', {
|
|
||||||
src: render(node.content),
|
|
||||||
}, null),
|
|
||||||
quote: (node) => toNode('blockquote', {}, [toNode('p', {}, node.content)]),
|
|
||||||
code: (node) => toNode('pre', {}, node.content),
|
|
||||||
style: (node) => toNode('span', toStyle(getStyleFromAttrs(node.attrs)), node.content),
|
|
||||||
list: (node) => {
|
|
||||||
const type = getUniqAttr(node.attrs);
|
|
||||||
|
|
||||||
return toNode(type ? 'ol' : 'ul', type ? { type } : {}, asListItems(node.content));
|
|
||||||
},
|
|
||||||
color: (node) => toNode('span', toStyle(`color: ${getUniqAttr(node.attrs)};`), node.content),
|
|
||||||
};
|
|
||||||
@@ -0,0 +1,146 @@
|
|||||||
|
/* eslint-disable no-plusplus,no-lonely-if */
|
||||||
|
import {
|
||||||
|
getUniqAttr,
|
||||||
|
isStringNode,
|
||||||
|
isTagNode,
|
||||||
|
TagNode,
|
||||||
|
TagNodeTree,
|
||||||
|
} from "@bbob/plugin-helper";
|
||||||
|
import type { NodeContent, TagNodeObject } from "@bbob/plugin-helper";
|
||||||
|
import type { PresetTagsDefinition } from "@bbob/preset";
|
||||||
|
import type { BbobPluginOptions } from "@bbob/core";
|
||||||
|
|
||||||
|
const isStartsWith = (node: string, type: string) => node[0] === type;
|
||||||
|
|
||||||
|
const getStyleFromAttrs = (attrs: Record<string, unknown>) => {
|
||||||
|
return Object.keys(attrs)
|
||||||
|
.reduce<string[]>((acc, key: "color" | "size") => {
|
||||||
|
const value = attrs[key];
|
||||||
|
|
||||||
|
if (typeof value === "string") {
|
||||||
|
if (key === "color") {
|
||||||
|
return acc.concat(`color:${value};`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (key === "size") {
|
||||||
|
return acc.concat(`font-size:${value};`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return acc;
|
||||||
|
}, [])
|
||||||
|
.join(" ");
|
||||||
|
};
|
||||||
|
|
||||||
|
const asListItems = (content: TagNodeTree): NodeContent[] => {
|
||||||
|
let listIdx = 0;
|
||||||
|
const listItems = [] as Array<NodeContent>;
|
||||||
|
|
||||||
|
const createItemNode = () => TagNode.create("li");
|
||||||
|
const ensureListItem = (val: NodeContent) => {
|
||||||
|
listItems[listIdx] = listItems[listIdx] || val;
|
||||||
|
};
|
||||||
|
const addItem = (val: NodeContent) => {
|
||||||
|
const listItem = listItems[listIdx];
|
||||||
|
|
||||||
|
if (listItem && isTagNode(listItem) && Array.isArray(listItem.content)) {
|
||||||
|
listItem.content = listItem.content.concat(val);
|
||||||
|
}
|
||||||
|
// else if (Array.isArray(listItem) && Array.isArray(listItems[listIdx])) {
|
||||||
|
// listItems[listIdx] = listItems[listIdx].concat(val);
|
||||||
|
// }
|
||||||
|
};
|
||||||
|
|
||||||
|
if (Array.isArray(content)) {
|
||||||
|
content.forEach((el) => {
|
||||||
|
if (isStringNode(el) && isStartsWith(String(el), "*")) {
|
||||||
|
if (listItems[listIdx]) {
|
||||||
|
listIdx++;
|
||||||
|
}
|
||||||
|
ensureListItem(createItemNode());
|
||||||
|
addItem(String(el).substr(1));
|
||||||
|
} else if (isTagNode(el) && TagNode.isOf(el, "*")) {
|
||||||
|
if (listItems[listIdx]) {
|
||||||
|
listIdx++;
|
||||||
|
}
|
||||||
|
ensureListItem(createItemNode());
|
||||||
|
} else if (!isTagNode(listItems[listIdx])) {
|
||||||
|
listIdx++;
|
||||||
|
ensureListItem(el);
|
||||||
|
} else if (listItems[listIdx]) {
|
||||||
|
addItem(el);
|
||||||
|
} else {
|
||||||
|
ensureListItem(el);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return listItems;
|
||||||
|
};
|
||||||
|
|
||||||
|
const renderUrl = (node: TagNodeObject, render: BbobPluginOptions["render"]) =>
|
||||||
|
getUniqAttr(node.attrs)
|
||||||
|
? getUniqAttr(node.attrs)
|
||||||
|
: render(node.content || []);
|
||||||
|
|
||||||
|
const toNode = (
|
||||||
|
tag: string,
|
||||||
|
attrs: Record<string, unknown>,
|
||||||
|
content: TagNodeTree
|
||||||
|
) => TagNode.create(tag, attrs, content);
|
||||||
|
|
||||||
|
const toStyle = (style: string) => ({ style });
|
||||||
|
|
||||||
|
const defaultTags: PresetTagsDefinition<
|
||||||
|
| "b"
|
||||||
|
| "i"
|
||||||
|
| "u"
|
||||||
|
| "s"
|
||||||
|
| "url"
|
||||||
|
| "img"
|
||||||
|
| "quote"
|
||||||
|
| "code"
|
||||||
|
| "style"
|
||||||
|
| "list"
|
||||||
|
| "color"
|
||||||
|
> = {
|
||||||
|
b: (node) => toNode("span", toStyle("font-weight: bold;"), node.content),
|
||||||
|
i: (node) => toNode("span", toStyle("font-style: italic;"), node.content),
|
||||||
|
u: (node) =>
|
||||||
|
toNode("span", toStyle("text-decoration: underline;"), node.content),
|
||||||
|
s: (node) =>
|
||||||
|
toNode("span", toStyle("text-decoration: line-through;"), node.content),
|
||||||
|
url: (node, { render }) =>
|
||||||
|
toNode(
|
||||||
|
"a",
|
||||||
|
{
|
||||||
|
href: renderUrl(node, render),
|
||||||
|
},
|
||||||
|
node.content
|
||||||
|
),
|
||||||
|
img: (node, { render }) =>
|
||||||
|
toNode(
|
||||||
|
"img",
|
||||||
|
{
|
||||||
|
src: render(node.content),
|
||||||
|
},
|
||||||
|
null
|
||||||
|
),
|
||||||
|
quote: (node) => toNode("blockquote", {}, [toNode("p", {}, node.content)]),
|
||||||
|
code: (node) => toNode("pre", {}, node.content),
|
||||||
|
style: (node) =>
|
||||||
|
toNode("span", toStyle(getStyleFromAttrs(node.attrs)), node.content),
|
||||||
|
list: (node) => {
|
||||||
|
const type = getUniqAttr(node.attrs);
|
||||||
|
|
||||||
|
return toNode(
|
||||||
|
type ? "ol" : "ul",
|
||||||
|
type ? { type } : {},
|
||||||
|
asListItems(node.content)
|
||||||
|
);
|
||||||
|
},
|
||||||
|
color: (node) =>
|
||||||
|
toNode("span", toStyle(`color: ${getUniqAttr(node.attrs)};`), node.content),
|
||||||
|
};
|
||||||
|
|
||||||
|
export default defaultTags;
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
/* eslint-disable indent */
|
|
||||||
import { createPreset } from '@bbob/preset';
|
|
||||||
import defaultTags from './defaultTags';
|
|
||||||
|
|
||||||
export default createPreset(defaultTags);
|
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
/* eslint-disable indent */
|
||||||
|
import { createPreset } from "@bbob/preset";
|
||||||
|
import defaultTags from "./defaultTags";
|
||||||
|
export type * from "@bbob/preset";
|
||||||
|
|
||||||
|
export default createPreset(defaultTags);
|
||||||
+8
-2
@@ -1,7 +1,13 @@
|
|||||||
import html from '@bbob/html'
|
import html, { render } from '@bbob/html'
|
||||||
|
import core from '@bbob/core'
|
||||||
|
|
||||||
import preset from '../src'
|
import preset from '../src'
|
||||||
|
|
||||||
const parse = input => html(input, preset());
|
const parse = (input: string) => {
|
||||||
|
const tree = core(preset()).process(input, { render })
|
||||||
|
|
||||||
|
return html(input, preset())
|
||||||
|
};
|
||||||
|
|
||||||
describe('@bbob/preset-html5', () => {
|
describe('@bbob/preset-html5', () => {
|
||||||
test('[b]bolded text[/b]', () => {
|
test('[b]bolded text[/b]', () => {
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
{
|
||||||
|
"extends": "../../tsconfig.json",
|
||||||
|
"compilerOptions": {
|
||||||
|
"baseUrl": ".",
|
||||||
|
"outDir": "./types"
|
||||||
|
},
|
||||||
|
"include": [
|
||||||
|
"./src/**/*"
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -2,3 +2,6 @@ coverage
|
|||||||
dist
|
dist
|
||||||
lib
|
lib
|
||||||
es
|
es
|
||||||
|
types
|
||||||
|
test/*.d.ts
|
||||||
|
test/*.map
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
package-lock.json
|
pnpm-lock.yaml
|
||||||
coverage
|
coverage
|
||||||
src
|
src
|
||||||
!dist
|
!dist
|
||||||
|
|||||||
@@ -8,16 +8,27 @@
|
|||||||
"react"
|
"react"
|
||||||
],
|
],
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@bbob/preset-html5": "workspace:*"
|
"@bbob/preset-html5": "*",
|
||||||
|
"@bbob/preset": "*"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@bbob/core": "workspace:*"
|
"@bbob/core": "*"
|
||||||
},
|
},
|
||||||
"main": "lib/index.js",
|
"main": "lib/index.js",
|
||||||
"module": "es/index.js",
|
"module": "es/index.js",
|
||||||
"jsnext:main": "es/index.js",
|
"jsnext:main": "es/index.js",
|
||||||
"browser": "dist/index.js",
|
"browser": "dist/index.js",
|
||||||
"browserName": "BbobPresetReact",
|
"browserName": "BbobPresetReact",
|
||||||
|
"types": "types/index.d.ts",
|
||||||
|
"exports": {
|
||||||
|
".": {
|
||||||
|
"types": "./types/index.d.ts",
|
||||||
|
"import": "./es/index.js",
|
||||||
|
"require": "./lib/index.js",
|
||||||
|
"browser": "./dist/index.min.js",
|
||||||
|
"umd": "./dist/index.min.js"
|
||||||
|
}
|
||||||
|
},
|
||||||
"homepage": "https://github.com/JiLiZART/bbob",
|
"homepage": "https://github.com/JiLiZART/bbob",
|
||||||
"author": "Nikolay Kostyurin <jilizart@gmail.com>",
|
"author": "Nikolay Kostyurin <jilizart@gmail.com>",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
@@ -29,19 +40,22 @@
|
|||||||
"url": "git://github.com/JiLiZART/bbob.git"
|
"url": "git://github.com/JiLiZART/bbob.git"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build:commonjs": "../../scripts/pkg-task build-commonjs",
|
"build:commonjs": "pkg-task",
|
||||||
"build:es": "../../scripts/pkg-task build-es",
|
"build:es": "pkg-task",
|
||||||
"build:umd": "../../scripts/pkg-task build-umd",
|
"build:umd": "pkg-task",
|
||||||
"build": "npm run build:commonjs && npm run build:es && npm run build:umd",
|
"build": "pkg-task",
|
||||||
"test": "../../scripts/pkg-task test",
|
"test": "pkg-task",
|
||||||
"cover": "../../scripts/pkg-task cover",
|
"cover": "pkg-task",
|
||||||
"lint": "../../scripts/pkg-task lint",
|
"lint": "pkg-task",
|
||||||
"size": "../../scripts/pkg-task size",
|
"size": "pkg-task",
|
||||||
"bundlesize": "../../scripts/pkg-task bundlesize"
|
"bundlesize": "pkg-task",
|
||||||
|
"types": "pkg-task",
|
||||||
|
"prepublishOnly": "npm run build"
|
||||||
},
|
},
|
||||||
"size-limit": [
|
"size-limit": [
|
||||||
{
|
{
|
||||||
"path": "lib/index.js"
|
"path": "./dist/index.min.js",
|
||||||
|
"size": "2.5 KB"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"bundlesize": [
|
"bundlesize": [
|
||||||
|
|||||||
@@ -1,12 +1,13 @@
|
|||||||
import presetHTML5 from '@bbob/preset-html5';
|
import presetHTML5 from '@bbob/preset-html5';
|
||||||
|
import type { PresetTagsDefinition } from '@bbob/preset';
|
||||||
|
|
||||||
const tagAttr = (style) => ({
|
const tagAttr = (style: Record<string, string>) => ({
|
||||||
attrs: {
|
attrs: {
|
||||||
style,
|
style,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
export default presetHTML5.extend((tags) => ({
|
export default presetHTML5.extend((tags: PresetTagsDefinition<'b' | 'i' | 'u' | 's'>) => ({
|
||||||
...tags,
|
...tags,
|
||||||
|
|
||||||
b: (...args) => ({
|
b: (...args) => ({
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
{
|
||||||
|
"extends": "../../tsconfig.json",
|
||||||
|
"compilerOptions": {
|
||||||
|
"baseUrl": ".",
|
||||||
|
"outDir": "./types"
|
||||||
|
},
|
||||||
|
"include": [
|
||||||
|
"./src/**/*"
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -2,3 +2,6 @@ coverage
|
|||||||
dist
|
dist
|
||||||
lib
|
lib
|
||||||
es
|
es
|
||||||
|
types
|
||||||
|
test/*.d.ts
|
||||||
|
test/*.map
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
package-lock.json
|
pnpm-lock.yaml
|
||||||
coverage
|
coverage
|
||||||
src
|
src
|
||||||
!dist
|
!dist
|
||||||
|
|||||||
@@ -8,16 +8,27 @@
|
|||||||
"vue"
|
"vue"
|
||||||
],
|
],
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@bbob/preset-html5": "workspace:*"
|
"@bbob/preset-html5": "*",
|
||||||
|
"@bbob/preset": "*"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@bbob/core": "workspace:*"
|
"@bbob/core": "*"
|
||||||
},
|
},
|
||||||
"main": "lib/index.js",
|
"main": "lib/index.js",
|
||||||
"module": "es/index.js",
|
"module": "es/index.js",
|
||||||
"jsnext:main": "es/index.js",
|
"jsnext:main": "es/index.js",
|
||||||
"browser": "dist/index.js",
|
"browser": "dist/index.js",
|
||||||
"browserName": "BbobPresetVue",
|
"browserName": "BbobPresetVue",
|
||||||
|
"types": "types/index.d.ts",
|
||||||
|
"exports": {
|
||||||
|
".": {
|
||||||
|
"types": "./types/index.d.ts",
|
||||||
|
"import": "./es/index.js",
|
||||||
|
"require": "./lib/index.js",
|
||||||
|
"browser": "./dist/index.min.js",
|
||||||
|
"umd": "./dist/index.min.js"
|
||||||
|
}
|
||||||
|
},
|
||||||
"homepage": "https://github.com/JiLiZART/bbob",
|
"homepage": "https://github.com/JiLiZART/bbob",
|
||||||
"author": "Nikolay Kostyurin <jilizart@gmail.com>",
|
"author": "Nikolay Kostyurin <jilizart@gmail.com>",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
@@ -29,19 +40,22 @@
|
|||||||
"url": "git://github.com/JiLiZART/bbob.git"
|
"url": "git://github.com/JiLiZART/bbob.git"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build:commonjs": "../../scripts/pkg-task build-commonjs",
|
"build:commonjs": "pkg-task",
|
||||||
"build:es": "../../scripts/pkg-task build-es",
|
"build:es": "pkg-task",
|
||||||
"build:umd": "../../scripts/pkg-task build-umd",
|
"build:umd": "pkg-task",
|
||||||
"build": "npm run build:commonjs && npm run build:es && npm run build:umd",
|
"build": "pkg-task",
|
||||||
"test": "../../scripts/pkg-task test",
|
"test": "pkg-task",
|
||||||
"cover": "../../scripts/pkg-task cover",
|
"cover": "pkg-task",
|
||||||
"lint": "../../scripts/pkg-task lint",
|
"lint": "pkg-task",
|
||||||
"size": "../../scripts/pkg-task size",
|
"size": "pkg-task",
|
||||||
"bundlesize": "../../scripts/pkg-task bundlesize"
|
"bundlesize": "pkg-task",
|
||||||
|
"types": "pkg-task",
|
||||||
|
"prepublishOnly": "npm run build"
|
||||||
},
|
},
|
||||||
"size-limit": [
|
"size-limit": [
|
||||||
{
|
{
|
||||||
"path": "lib/index.js"
|
"path": "./dist/index.min.js",
|
||||||
|
"size": "2.5 KB"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"bundlesize": [
|
"bundlesize": [
|
||||||
|
|||||||
@@ -1,12 +1,13 @@
|
|||||||
import presetHTML5 from '@bbob/preset-html5';
|
import presetHTML5 from '@bbob/preset-html5';
|
||||||
|
import type { PresetTagsDefinition } from '@bbob/preset';
|
||||||
|
|
||||||
export const tagAttr = (style) => ({
|
export const tagAttr = (style: Record<string, string>) => ({
|
||||||
attrs: {
|
attrs: {
|
||||||
style,
|
style,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
export const createTags = (tags) => ({
|
export const createTags = (tags: PresetTagsDefinition<'b' | 'i' | 'u' | 's'>) => ({
|
||||||
b: (...args) => ({
|
b: (...args) => ({
|
||||||
...tags.b(...args),
|
...tags.b(...args),
|
||||||
...tagAttr({ fontWeight: 'bold' }),
|
...tagAttr({ fontWeight: 'bold' }),
|
||||||
@@ -26,7 +27,7 @@ export const createTags = (tags) => ({
|
|||||||
...tags.s(...args),
|
...tags.s(...args),
|
||||||
...tagAttr({ textDecoration: 'line-through' }),
|
...tagAttr({ textDecoration: 'line-through' }),
|
||||||
}),
|
}),
|
||||||
});
|
} as PresetTagsDefinition);
|
||||||
|
|
||||||
export default presetHTML5.extend((tags) => ({
|
export default presetHTML5.extend((tags) => ({
|
||||||
...tags,
|
...tags,
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user