├── .changeset ├── README.md └── config.json ├── .gitee ├── ISSUE_TEMPLATE.zh-CN.md └── PULL_REQUEST_TEMPLATE.zh-CN.md ├── .github └── workflows │ ├── publish.yaml │ └── website.yaml ├── .gitignore ├── .husky └── commit-msg ├── .npmrc ├── .pnpm-debug.log ├── .vscode ├── launch.json └── settings.json ├── CHANGELOG.md ├── LICENSE ├── README.md ├── babel.config.js ├── commitlint.config.cjs ├── docs ├── .nojekyll ├── .vitepress │ ├── config.mts │ └── theme │ │ ├── index.ts │ │ └── style.css ├── advanced │ ├── colorized.md │ ├── config.md │ └── plugin.md ├── api.md ├── guide │ ├── banner.md │ ├── create.md │ ├── datatype.md │ ├── format.md │ ├── grouped-tasks.md │ ├── header.md │ ├── images │ │ ├── banner1.png │ │ ├── banner2.png │ │ ├── createTasks.png │ │ ├── group-tasks.png │ │ ├── grouped-tasks-line.png │ │ ├── header.png │ │ ├── list.png │ │ ├── list2.png │ │ ├── log.jpg │ │ ├── log10.jpg │ │ ├── log11.jpg │ │ ├── log2.jpg │ │ ├── log3.jpg │ │ ├── log4.jpg │ │ ├── log5.jpg │ │ ├── log6.jpg │ │ ├── log7.jpg │ │ ├── log8.jpg │ │ ├── log9.jpg │ │ ├── logvar.png │ │ ├── logvar2.png │ │ ├── progressbar.png │ │ ├── task-grouped.png │ │ ├── task.png │ │ ├── tasklist.demo.gif │ │ ├── tasklist.png │ │ ├── tasks.note.png │ │ ├── tree1.png │ │ └── tree2.png │ ├── index.md │ ├── list.md │ ├── log.md │ ├── progressbar.md │ ├── quick-tasklist.md │ ├── run-tasklist.md │ ├── separator.md │ ├── table.md │ ├── task.md │ ├── tasklist.md │ ├── template.md │ └── tree.md ├── index.md └── intro │ ├── about.md │ └── install.md ├── examples ├── banner.demo.js ├── colors.demo.js ├── createtasks.demo.js ├── demo.js ├── header.demo.js ├── list.demo.js ├── progressbar.demo.js ├── table.demo.js ├── task.demo.js ├── tasklist.demo.js ├── tasklist.demo2.js ├── tasklist.group.demo.js ├── tasks.demo.js ├── tasks.group.demo.js └── tree.demo.js ├── package.json ├── pnpm-lock.yaml ├── rollup.config.js └── src ├── banner.plugin.d.ts ├── banner.plugin.js ├── colorize.js ├── colors.d.ts ├── consts.js ├── header.plugin.js ├── index.d.ts ├── index.js ├── list.plugin.d.ts ├── list.plugin.js ├── progressbar.plugin.d.ts ├── progressbar.plugin.js ├── stringify.js ├── table.plugin.d.ts ├── table.plugin.js ├── tasklist.plugin.d.ts ├── tasklist.plugin.js ├── tree.plugin.d.ts ├── tree.plugin.js ├── utils.js └── utils └── index.ts /.changeset/README.md: -------------------------------------------------------------------------------- 1 | # Changesets 2 | 3 | Hello and welcome! This folder has been automatically generated by `@changesets/cli`, a build tool that works 4 | with multi-package repos, or single-package repos to help you version and publish your code. You can 5 | find the full documentation for it [in our repository](https://github.com/changesets/changesets) 6 | 7 | We have a quick list of common questions to get you started engaging with this project in 8 | [our documentation](https://github.com/changesets/changesets/blob/main/docs/common-questions.md) 9 | -------------------------------------------------------------------------------- /.changeset/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://unpkg.com/@changesets/config@3.0.4/schema.json", 3 | "changelog": "@changesets/cli/changelog", 4 | "commit": true, 5 | "fixed": [], 6 | "linked": [], 7 | "access": "public", 8 | "baseBranch": "master", 9 | "updateInternalDependencies": "patch", 10 | "ignore": [] 11 | } 12 | -------------------------------------------------------------------------------- /.gitee/ISSUE_TEMPLATE.zh-CN.md: -------------------------------------------------------------------------------- 1 | ### 该问题是怎么引起的? 2 | 3 | 4 | 5 | ### 重现步骤 6 | 7 | 8 | 9 | ### 报错信息 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /.gitee/PULL_REQUEST_TEMPLATE.zh-CN.md: -------------------------------------------------------------------------------- 1 | ### 相关的Issue 2 | 3 | 4 | ### 原因(目的、解决的问题等) 5 | 6 | 7 | ### 描述(做了什么,变更了什么) 8 | 9 | 10 | ### 测试用例(新增、改动、可能影响的功能) 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /.github/workflows/publish.yaml: -------------------------------------------------------------------------------- 1 | name: Publish Package 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | tags: 8 | - v* 9 | 10 | permissions: 11 | pull-requests: write 12 | contents: write 13 | id-token: write 14 | 15 | concurrency: ${{ github.workflow }}-${{ github.ref }} 16 | 17 | jobs: 18 | build-and-publish: 19 | runs-on: ubuntu-latest 20 | 21 | steps: 22 | - name: Checkout Repo 23 | uses: actions/checkout@v4 24 | 25 | - uses: pnpm/action-setup@v4 26 | 27 | - name: Setup Node.js environment 28 | uses: actions/setup-node@v4 29 | with: 30 | node-version: 20 31 | cache: pnpm 32 | 33 | - name: Install pnpm 34 | run: npm install -g pnpm 35 | # 安装依赖 36 | - name: Install dependencies 37 | run: pnpm install --registry=https://registry.npmjs.org/ --no-frozen-lockfile 38 | - name: Build package 39 | run: pnpm build 40 | - name: Create Release Pull Request or Publish to npm 41 | id: changesets 42 | uses: changesets/action@v1 43 | with: 44 | # This expects you to have a script called release which does a build for your packages and calls changeset publish 45 | publish: pnpm publish:all 46 | env: 47 | # this doesn't work but semantic-release works 48 | # see https://github.com/sonofmagic/npm-lib-rollup-template/blob/main/.github/workflows/release.yml#L46 49 | # NODE_AUTH_TOKEN: ${{secrets.NPM_TOKEN}} 50 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 51 | NPM_TOKEN: ${{ secrets.NPM_TOKEN }} 52 | npm_config_registry: https://registry.npmjs.org 53 | -------------------------------------------------------------------------------- /.github/workflows/website.yaml: -------------------------------------------------------------------------------- 1 | # Sample workflow for building and deploying a VitePress site to GitHub Pages 2 | # 3 | name: Deploy Logsets site to Pages 4 | 5 | on: 6 | # Runs on pushes targeting the `main` branch. Change this to `master` if you're 7 | # using the `master` branch as the default branch. 8 | push: 9 | branches: [master] 10 | 11 | # Allows you to run this workflow manually from the Actions tab 12 | workflow_dispatch: 13 | 14 | # Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages 15 | permissions: 16 | contents: read 17 | pages: write 18 | id-token: write 19 | 20 | # Allow only one concurrent deployment, skipping runs queued between the run in-progress and latest queued. 21 | # However, do NOT cancel in-progress runs as we want to allow these production deployments to complete. 22 | concurrency: 23 | group: pages 24 | cancel-in-progress: false 25 | 26 | jobs: 27 | # Build job 28 | build: 29 | runs-on: ubuntu-latest 30 | steps: 31 | - name: Checkout 32 | uses: actions/checkout@v3 33 | with: 34 | fetch-depth: 0 # Not needed if lastUpdated is not enabled 35 | # - uses: pnpm/action-setup@v2 # Uncomment this if you're using pnpm 36 | # - uses: oven-sh/setup-bun@v1 # Uncomment this if you're using Bun 37 | - name: Setup Node 38 | uses: actions/setup-node@v3 39 | with: 40 | node-version: 18 41 | - name: Setup Pages 42 | uses: actions/configure-pages@v3 43 | - name: Install pnpm 44 | run: 45 | npm install -g pnpm 46 | - name: Install dependencies 47 | run: pnpm install --registry=https://registry.npmjs.org/ --no-frozen-lockfile 48 | - name: pnpm docs:build 49 | run: | 50 | pnpm docs:build 51 | touch docs/.vitepress/dist/.nojekyll 52 | - name: Upload artifact 53 | uses: actions/upload-pages-artifact@v3 54 | with: 55 | path: docs/.vitepress/dist 56 | 57 | # Deployment job 58 | deploy: 59 | environment: 60 | name: github-pages 61 | url: ${{ steps.deployment.outputs.page_url }} 62 | needs: build 63 | runs-on: ubuntu-latest 64 | name: Deploy 65 | steps: 66 | - name: Deploy to GitHub Pages 67 | id: deployment 68 | uses: actions/deploy-pages@v4 -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules 2 | dist 3 | 4 | docs/.vitepress/cache 5 | docs/.vitepress/dist -------------------------------------------------------------------------------- /.husky/commit-msg: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | . "$(dirname -- "$0")/_/husky.sh" 3 | 4 | npx --no -- commitlint --edit ${1} 5 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | tag-version-prefix="" 2 | message="chore(release): %s :tada:" -------------------------------------------------------------------------------- /.pnpm-debug.log: -------------------------------------------------------------------------------- 1 | { 2 | "0 debug pnpm:scope": { 3 | "selected": 1 4 | }, 5 | "1 debug pnpm:package-manifest": { 6 | "initial": { 7 | "name": "logsets", 8 | "version": "1.0.6", 9 | "description": "", 10 | "main": "dist/index.cjs", 11 | "module": "dist/index.mjs", 12 | "type": "module", 13 | "scripts": { 14 | "test": "echo \"Error: no test specified\" && exit 1", 15 | "build": "rollup -c" 16 | }, 17 | "exports": { 18 | ".": { 19 | "import": "./dist/index.mjs", 20 | "require": "./dist/index.cjs" 21 | }, 22 | "./plugins/table": { 23 | "import": "./dist/table.plugin.mjs", 24 | "require": "./dist/table.plugin.cjs" 25 | } 26 | }, 27 | "author": "", 28 | "license": "ISC", 29 | "repository": { 30 | "type": "git", 31 | "url": "git+https://gitee.com/zhangfisher/logsets.git" 32 | }, 33 | "dependencies": { 34 | "@babel/runtime-corejs3": "^7.17.0", 35 | "ansicolor": "^1.1.100", 36 | "core-js": "^3.21.0", 37 | "deepmerge": "^4.2.2" 38 | }, 39 | "devDependencies": { 40 | "@babel/core": "^7.17.2", 41 | "@babel/cli ": "^7.17.0", 42 | "@babel/preset-env": "^7.16.11", 43 | "@babel/runtime": "^7.17.2", 44 | "@rollup/plugin-babel": "^5.3.0", 45 | "@rollup/plugin-commonjs": "^21.0.1", 46 | "rollup-plugin-clear": "^2.0.7", 47 | "rollup-plugin-uglify": "^6.0.4" 48 | } 49 | }, 50 | "prefix": "D:\\Code\\logsets" 51 | }, 52 | "2 debug pnpm:context": { 53 | "currentLockfileExists": false, 54 | "storeDir": "D:\\.pnpm-store\\v3", 55 | "virtualStoreDir": "D:\\Code\\logsets\\node_modules\\.pnpm" 56 | }, 57 | "3 debug pnpm:stage": { 58 | "prefix": "D:\\Code\\logsets", 59 | "stage": "resolution_started" 60 | }, 61 | "4 debug pnpm:_dependency_resolved": { 62 | "resolution": "registry.npmmirror.com/@babel/core/7.17.2", 63 | "wanted": { 64 | "dependentId": ".", 65 | "name": "@babel/core", 66 | "rawSpec": "^7.17.2" 67 | } 68 | }, 69 | "5 debug pnpm:_dependency_resolved": { 70 | "resolution": "registry.npmmirror.com/@babel/preset-env/7.16.11", 71 | "wanted": { 72 | "dependentId": ".", 73 | "name": "@babel/preset-env", 74 | "rawSpec": "^7.16.11" 75 | } 76 | }, 77 | "6 debug pnpm:_dependency_resolved": { 78 | "resolution": "registry.npmmirror.com/@babel/runtime/7.17.2", 79 | "wanted": { 80 | "dependentId": ".", 81 | "name": "@babel/runtime", 82 | "rawSpec": "^7.17.2" 83 | } 84 | }, 85 | "7 debug pnpm:_dependency_resolved": { 86 | "resolution": "registry.npmmirror.com/@rollup/plugin-babel/5.3.0", 87 | "wanted": { 88 | "dependentId": ".", 89 | "name": "@rollup/plugin-babel", 90 | "rawSpec": "^5.3.0" 91 | } 92 | }, 93 | "8 debug pnpm:_dependency_resolved": { 94 | "resolution": "registry.npmmirror.com/@rollup/plugin-commonjs/21.0.1", 95 | "wanted": { 96 | "dependentId": ".", 97 | "name": "@rollup/plugin-commonjs", 98 | "rawSpec": "^21.0.1" 99 | } 100 | }, 101 | "9 debug pnpm:_dependency_resolved": { 102 | "resolution": "registry.npmmirror.com/rollup-plugin-clear/2.0.7", 103 | "wanted": { 104 | "dependentId": ".", 105 | "name": "rollup-plugin-clear", 106 | "rawSpec": "^2.0.7" 107 | } 108 | }, 109 | "10 debug pnpm:_dependency_resolved": { 110 | "resolution": "registry.npmmirror.com/rollup-plugin-uglify/6.0.4", 111 | "wanted": { 112 | "dependentId": ".", 113 | "name": "rollup-plugin-uglify", 114 | "rawSpec": "^6.0.4" 115 | } 116 | }, 117 | "11 debug pnpm:_dependency_resolved": { 118 | "resolution": "registry.npmmirror.com/@babel/runtime-corejs3/7.17.0", 119 | "wanted": { 120 | "dependentId": ".", 121 | "name": "@babel/runtime-corejs3", 122 | "rawSpec": "^7.17.0" 123 | } 124 | }, 125 | "12 debug pnpm:_dependency_resolved": { 126 | "resolution": "registry.npmmirror.com/ansicolor/1.1.100", 127 | "wanted": { 128 | "dependentId": ".", 129 | "name": "ansicolor", 130 | "rawSpec": "^1.1.100" 131 | } 132 | }, 133 | "13 debug pnpm:_dependency_resolved": { 134 | "resolution": "registry.npmmirror.com/core-js/3.21.0", 135 | "wanted": { 136 | "dependentId": ".", 137 | "name": "core-js", 138 | "rawSpec": "^3.21.0" 139 | } 140 | }, 141 | "14 debug pnpm:_dependency_resolved": { 142 | "resolution": "registry.npmmirror.com/deepmerge/4.2.2", 143 | "wanted": { 144 | "dependentId": ".", 145 | "name": "deepmerge", 146 | "rawSpec": "^4.2.2" 147 | } 148 | }, 149 | "15 debug pnpm:progress": { 150 | "packageId": "registry.npmmirror.com/@babel/preset-env/7.16.11", 151 | "requester": "D:\\Code\\logsets", 152 | "status": "found_in_store" 153 | }, 154 | "16 debug pnpm:progress": { 155 | "packageId": "registry.npmmirror.com/@babel/preset-env/7.16.11", 156 | "requester": "D:\\Code\\logsets", 157 | "status": "resolved" 158 | }, 159 | "17 debug pnpm:progress": { 160 | "packageId": "registry.npmmirror.com/@babel/core/7.17.2", 161 | "requester": "D:\\Code\\logsets", 162 | "status": "found_in_store" 163 | }, 164 | "18 debug pnpm:progress": { 165 | "packageId": "registry.npmmirror.com/@babel/core/7.17.2", 166 | "requester": "D:\\Code\\logsets", 167 | "status": "resolved" 168 | }, 169 | "19 debug pnpm:progress": { 170 | "packageId": "registry.npmmirror.com/@rollup/plugin-babel/5.3.0", 171 | "requester": "D:\\Code\\logsets", 172 | "status": "found_in_store" 173 | }, 174 | "20 debug pnpm:progress": { 175 | "packageId": "registry.npmmirror.com/@rollup/plugin-babel/5.3.0", 176 | "requester": "D:\\Code\\logsets", 177 | "status": "resolved" 178 | }, 179 | "21 debug pnpm:progress": { 180 | "packageId": "registry.npmmirror.com/rollup-plugin-clear/2.0.7", 181 | "requester": "D:\\Code\\logsets", 182 | "status": "found_in_store" 183 | }, 184 | "22 debug pnpm:progress": { 185 | "packageId": "registry.npmmirror.com/rollup-plugin-clear/2.0.7", 186 | "requester": "D:\\Code\\logsets", 187 | "status": "resolved" 188 | }, 189 | "23 debug pnpm:progress": { 190 | "packageId": "registry.npmmirror.com/@rollup/plugin-commonjs/21.0.1", 191 | "requester": "D:\\Code\\logsets", 192 | "status": "found_in_store" 193 | }, 194 | "24 debug pnpm:progress": { 195 | "packageId": "registry.npmmirror.com/@rollup/plugin-commonjs/21.0.1", 196 | "requester": "D:\\Code\\logsets", 197 | "status": "resolved" 198 | }, 199 | "25 debug pnpm:progress": { 200 | "packageId": "registry.npmmirror.com/rollup-plugin-uglify/6.0.4", 201 | "requester": "D:\\Code\\logsets", 202 | "status": "found_in_store" 203 | }, 204 | "26 debug pnpm:progress": { 205 | "packageId": "registry.npmmirror.com/rollup-plugin-uglify/6.0.4", 206 | "requester": "D:\\Code\\logsets", 207 | "status": "resolved" 208 | }, 209 | "27 debug pnpm:progress": { 210 | "packageId": "registry.npmmirror.com/@babel/runtime/7.17.2", 211 | "requester": "D:\\Code\\logsets", 212 | "status": "found_in_store" 213 | }, 214 | "28 debug pnpm:progress": { 215 | "packageId": "registry.npmmirror.com/@babel/runtime/7.17.2", 216 | "requester": "D:\\Code\\logsets", 217 | "status": "resolved" 218 | }, 219 | "29 debug pnpm:progress": { 220 | "packageId": "registry.npmmirror.com/ansicolor/1.1.100", 221 | "requester": "D:\\Code\\logsets", 222 | "status": "found_in_store" 223 | }, 224 | "30 debug pnpm:progress": { 225 | "packageId": "registry.npmmirror.com/ansicolor/1.1.100", 226 | "requester": "D:\\Code\\logsets", 227 | "status": "resolved" 228 | }, 229 | "31 debug pnpm:progress": { 230 | "packageId": "registry.npmmirror.com/deepmerge/4.2.2", 231 | "requester": "D:\\Code\\logsets", 232 | "status": "found_in_store" 233 | }, 234 | "32 debug pnpm:progress": { 235 | "packageId": "registry.npmmirror.com/deepmerge/4.2.2", 236 | "requester": "D:\\Code\\logsets", 237 | "status": "resolved" 238 | }, 239 | "33 debug pnpm:progress": { 240 | "packageId": "registry.npmmirror.com/@babel/runtime-corejs3/7.17.0", 241 | "requester": "D:\\Code\\logsets", 242 | "status": "found_in_store" 243 | }, 244 | "34 debug pnpm:progress": { 245 | "packageId": "registry.npmmirror.com/@babel/runtime-corejs3/7.17.0", 246 | "requester": "D:\\Code\\logsets", 247 | "status": "resolved" 248 | }, 249 | "35 debug pnpm:fetching-progress": { 250 | "attempt": 1, 251 | "packageId": "registry.npmmirror.com/core-js/3.21.0", 252 | "size": 224454, 253 | "status": "started" 254 | }, 255 | "36 error pnpm": { 256 | "code": "ERR_PNPM_FETCH_404", 257 | "hint": "@babel/cli is not in the npm registry, or you have no permission to fetch it.\n\nNo authorization header was set for the request.", 258 | "request": { 259 | "url": "https://registry.npmjs.org/@babel%2Fcli%20" 260 | }, 261 | "response": { 262 | "size": 0 263 | }, 264 | "pkgName": "@babel/cli ", 265 | "pkgsStack": [], 266 | "err": { 267 | "name": "pnpm", 268 | "message": "GET https://registry.npmjs.org/@babel%2Fcli%20: Not Found - 404", 269 | "code": "ERR_PNPM_FETCH_404", 270 | "stack": "pnpm: GET https://registry.npmjs.org/@babel%2Fcli%20: Not Found - 404\n at RetryOperation._fn (C:\\Users\\Administrator\\AppData\\Roaming\\npm\\pnpm-global\\5\\node_modules\\.pnpm\\registry.npmmirror.com+pnpm@6.32.3\\node_modules\\pnpm\\dist\\pnpm.cjs:85709:19)\n at runMicrotasks ()\n at processTicksAndRejections (node:internal/process/task_queues:96:5)" 271 | } 272 | } 273 | } -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // 使用 IntelliSense 了解相关属性。 3 | // 悬停以查看现有属性的描述。 4 | // 欲了解更多信息,请访问: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "type": "node", 9 | "request": "launch", 10 | "name": "Demo", 11 | "skipFiles": [ 12 | "/**" 13 | ], 14 | "program": "${workspaceFolder}\\demo.js" 15 | },{ 16 | "type": "node", 17 | "request": "launch", 18 | "name": "Launch Program", 19 | "skipFiles": [ 20 | "/**" 21 | ], 22 | "program": "${file}" 23 | }, 24 | { 25 | "type": "node", 26 | "request": "launch", 27 | "name": "表格", 28 | "skipFiles": [ 29 | "/**" 30 | ], 31 | "program": "${workspaceFolder}\\table.demo.js" 32 | }, 33 | { 34 | "type": "node", 35 | "request": "launch", 36 | "name": "进度条", 37 | "skipFiles": [ 38 | "/**" 39 | ], 40 | "program": "${workspaceFolder}\\progressbar.demo.js" 41 | }, 42 | { 43 | "type": "node", 44 | "request": "launch", 45 | "name": "任务列表", 46 | "skipFiles": [ 47 | "/**" 48 | ], 49 | "program": "${workspaceFolder}\\tasklist.demo.js" 50 | }, 51 | { 52 | "type": "node", 53 | "request": "launch", 54 | "name": "树结构", 55 | "skipFiles": [ 56 | "/**" 57 | ], 58 | "program": "${workspaceFolder}\\tree.demo.js" 59 | }, 60 | { 61 | "type": "node", 62 | "request": "launch", 63 | "name": "横幅", 64 | "skipFiles": [ 65 | "/**" 66 | ], 67 | "program": "${workspaceFolder}\\banner.demo.js" 68 | } 69 | ] 70 | } -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "cSpell.words": [ 3 | "logsets", 4 | "progressbar" 5 | ] 6 | } -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # logsets 2 | 3 | ## 1.3.31 4 | 5 | ### Patch Changes 6 | 7 | - 3b9f3c0: 优化超长错误栈信息的显示 8 | 9 | ## 1.3.30 10 | 11 | ### Patch Changes 12 | 13 | - ed13298: fix tasks end flag 14 | 15 | ## 1.3.29 16 | 17 | ### Patch Changes 18 | 19 | - 311aa1d: fix addmemo last no display 20 | 21 | ## 1.3.28 22 | 23 | ### Patch Changes 24 | 25 | - 378c7b5: add done method for tasklist 26 | 27 | ## 1.3.27 28 | 29 | ### Patch Changes 30 | 31 | - 2b4d3d2: 优化任务列表的结束项显示 32 | 33 | ## 1.3.25 34 | 35 | ### Patch Changes 36 | 37 | - 59e58ca: 优化任务列表的结束项 38 | 39 | ## 1.3.24 40 | 41 | ### Patch Changes 42 | 43 | - 6071858: fix addMemo types 44 | 45 | ## 1.3.23 46 | 47 | ### Patch Changes 48 | 49 | - ed486fa: fix task indent 50 | 51 | ## 1.3.22 52 | 53 | ### Patch Changes 54 | 55 | - 0421777: 优化 task 错误显示 56 | 57 | ## 1.3.21 58 | 59 | ### Patch Changes 60 | 61 | - ee04ed6: add grouped support for tasklist 62 | 63 | ## 1.3.20 64 | 65 | ### Patch Changes 66 | 67 | - 536eec8: update tasklist 68 | - 9024faa: tasklist add grouped support 69 | 70 | ## 1.3.19 71 | 72 | ### Patch Changes 73 | 74 | - a28b695: tasks support function 75 | 76 | ## 1.3.18 77 | 78 | ### Patch Changes 79 | 80 | - 9ce0194: add grouped of list 81 | 82 | ## 1.3.17 83 | 84 | ### Patch Changes 85 | 86 | - efb127d: update taslkgroup symbol 87 | 88 | ## 1.3.16 89 | 90 | ### Patch Changes 91 | 92 | - b1c8057: feat: task.note 和 task.[status]支持插值字符串 93 | 94 | ## 1.3.15 95 | 96 | ### Patch Changes 97 | 98 | - 02384cd: 任务列表新支持分组 99 | 100 | ## 1.3.14 101 | 102 | ### Patch Changes 103 | 104 | - 93ad063: fix `logsets.colors`的类型提示 105 | 106 | ## 1.3.13 107 | 108 | ### Patch Changes 109 | 110 | - f849561: fix `logsets.colors`的类型提示 111 | - 8d11df6: add logsets.header output head 112 | 113 | ## 1.3.12 114 | 115 | ### Patch Changes 116 | 117 | - fd29216: fix ci 118 | - 7d09c9d: feat: 执行`logsets.task`出错时显示`error.stack`,而不是`error.message` 119 | - 975992f: fix ci workflow 120 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 fisher 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 为开发`nodejs`命令行程序提供丰富的表现输出样式,支持以下特性: 2 | 3 | - 支持按不同数据类型以不同的颜色显示,并且可以配置显示样式 4 | - 支持按`DEBUG`、`INFO`、`WARN `、`ERROR `、`FATAL`五个级别输出日志 5 | - 支持输出带颜色的模板字符串 6 | - 支持自动格式化显示`{}`和`[]`类型 7 | - 支持强大的表格输出 8 | - 支持输出任务列表、进度条、横幅和树等扩展 9 | - 正确处理中文与英文混排时的对齐问题 10 | - 提供任务执行列表显示 11 | 12 | 13 | # **开源推荐:** 14 | 15 | - **`VoerkaI18n`**: [基于Nodejs/React/Vue的一键国际化解决方案](https://zhangfisher.github.io/voerka-i18n/) 16 | - **`Logsets`**: [命令行应用增强输出库](https://zhangfisher.github.io/logsets/) 17 | - **`FlexDecorators`**: [JavaScript/TypeScript装饰器开发](https://zhangfisher.github.io/flex-decorators/) 18 | - **`FlexState`**: [有限状态机实现]](https://zhangfisher.github.io/flexstate/) 19 | - **`AutoPub`**: [基于pnpm/monorepo的自动发包工具](https://zhangfisher.github.io/autopub/) 20 | 21 | 22 | # 安装 23 | 24 | ```shell 25 | npm install logsets 26 | yarn add logsets 27 | pnpm add logsets 28 | ``` 29 | 30 | # 使用文档 31 | 32 | 访问[官方文档](https://zhangfisher.github.io/logsets/) 33 | 34 | # 输出效果 35 | 36 | - 模板字符串输出 37 | 38 | ![image](./docs/guide/images/log.jpg) 39 | 40 | - 按数据类型输出 41 | 42 | ![image](./docs/guide/images/log2.jpg) 43 | 44 | - 格式化输出对象 45 | 46 | ![image](./docs/guide/images/log3.jpg) 47 | ![image](./docs/guide/images/log5.jpg) 48 | 49 | - 输出日志级别 50 | 51 | ![image](./docs/guide/images/log11.jpg) 52 | 53 | - 表格输出 54 | 55 | ![image](./docs/guide/images/log6.jpg) 56 | 57 | ![image](./docs/guide/images/log7.jpg) 58 | 59 | ![image](./docs/guide/images/log8.jpg) 60 | 61 | - 进度条 62 | 63 | ![](./docs/guide/images/progressbar.png) 64 | 65 | - 横幅 66 | 67 | ![](./docs/guide/images/banner1.png) 68 | ![](./docs/guide/images/banner2.png) 69 | 70 | 71 | - 任务列表 72 | 73 | ![](./docs/guide/images/tasklist.demo.gif) 74 | ![](./docs/guide/images/createTasks.png) 75 | 76 | 77 | - 树 78 | 79 | ![](./docs/guide/images/tree1.png) 80 | ![](./docs/guide/images/tree2.png) 81 | 82 | - 列表 83 | 84 | ![](./docs/guide/images/list.png) 85 | 86 | -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | "presets": [ 3 | [ 4 | "@babel/preset-env", 5 | { 6 | "useBuiltIns": "usage", 7 | "debug": false, 8 | "modules": false, 9 | "corejs":{ 10 | "version":"3.21", 11 | "proposals": true 12 | } 13 | } 14 | ] 15 | ], 16 | "plugins": [ 17 | [ 18 | "@babel/plugin-transform-runtime", 19 | { 20 | "corejs":3, 21 | "proposals": true 22 | } 23 | ] 24 | ] 25 | } -------------------------------------------------------------------------------- /commitlint.config.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: ["@commitlint/config-conventional"], 3 | defaultIgnores: false, 4 | rules: { 5 | "scope-enum":[1,"always",["core","banner","progressbar","tree","table","tasklist","list"]] 6 | }, 7 | prompt: { 8 | settings:{ 9 | enableMultipleScopes:true, 10 | scopeEnumSeparator:"," 11 | }, 12 | messages: { 13 | skip: "跳过", 14 | max: "最大支持%d个字符", 15 | min: "不能少于%d个字符", 16 | emptyWarning: "不能为空", 17 | upperLimitWarning: "限制大写", 18 | lowerLimitWarning: "限制小写", 19 | }, 20 | questions: { 21 | type: { 22 | description: "选择你要提交的类型:", 23 | enum: { 24 | feat: { 25 | title: "特性", 26 | description: " 🚀 新增功能", 27 | emoji: "🚀", 28 | }, 29 | fix: { 30 | title: "修复", 31 | description: " 🐛 修复缺陷", 32 | emoji: "🐛", 33 | }, 34 | docs: { 35 | title: "文档", 36 | description: " 📚 文档变更", 37 | emoji: "📚", 38 | }, 39 | style: { 40 | title: "格式", 41 | description:" 🎨 代码格式(不影响功能,例如空格、分号等格式修正)", 42 | emoji: "🎨", 43 | }, 44 | refactor: { 45 | title: "重构", 46 | description:" 🛠 代码重构(不包括 bug 修复、功能新增)", 47 | emoji: "🛠", 48 | }, 49 | perf: { 50 | title: "性能", 51 | description: " ⚡️ 性能优化", 52 | emoji: "⚡️", 53 | }, 54 | test: { 55 | title: "测试", 56 | description: " ✅ 添加测试或已有测试改动", 57 | emoji: "✅", 58 | }, 59 | build: { 60 | title: "构建", 61 | description: 62 | " 📦 构建流程、外部依赖变更(如升级 npm 包、修改 webpack 配置等)", 63 | emoji: "📦", 64 | }, 65 | ci: { 66 | title: "集成", 67 | description: " 🎡 修改 CI 配置、脚本", 68 | emoji: "🎡", 69 | }, 70 | chore: { 71 | description: " ♻️ 除源代码文件和测试文件之外的改变", 72 | title: 'Chores', 73 | emoji: '♻️', 74 | }, 75 | revert: { 76 | title: "回退", 77 | description: " ⏪️ 回滚 commit", 78 | emoji: "⏪️", 79 | }, 80 | other: { 81 | title: "其他", 82 | description:" 🔨 对构建过程或辅助工具和库的更改(不影响源文件、测试用例)", 83 | emoji: "🔨", 84 | }, 85 | }, 86 | }, 87 | scope: { 88 | description: "本次提交涉及的模块或范围(可选):", 89 | }, 90 | subject: { 91 | description: "填写简短精炼的变更描述 :\n", 92 | }, 93 | body: { 94 | description: 95 | '填写更加详细的变更描述(可选)。使用 "|" 换行 :\n', 96 | }, 97 | breaking: { 98 | description: 99 | '列举非兼容性重大的变更(可选)。使用 "|" 换行 :\n', 100 | }, 101 | isBreaking: { 102 | description: '本次提交是一个不兼容的变更?', // Are there any breaking changes? 103 | }, 104 | issues: { 105 | description: "选择关联issue(可选):", 106 | }, 107 | isIssueAffected:{ 108 | description:"是否会影响任何未解决的issue问题?" 109 | }, 110 | issues: { 111 | description: "列举关联issue (可选) 例如: #31, #I3244 :\n", 112 | }, 113 | }, 114 | }, 115 | }; 116 | -------------------------------------------------------------------------------- /docs/.nojekyll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhangfisher/logsets/dc3a10c9b8038071bd5170198eeb50bb4f5b23f7/docs/.nojekyll -------------------------------------------------------------------------------- /docs/.vitepress/config.mts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vitepress' 2 | 3 | // https://vitepress.dev/reference/site-config 4 | export default defineConfig({ 5 | title: "Logsets", 6 | description: "Nodejs terminal application output colorized enhancement", 7 | base: '/logsets/', 8 | themeConfig: { 9 | outline: { 10 | label: "目录", 11 | level: [2, 5] 12 | }, 13 | // https://vitepress.dev/reference/default-theme-config 14 | nav: [ 15 | { text: '首页', link: '/' }, 16 | { text: '指南', link: '/guide/' }, 17 | { text: '开源推荐', link: 'https://zhangfisher.github.io/repos/' } 18 | ], 19 | 20 | sidebar: [ 21 | { 22 | text: '关于', 23 | collapsed:false, 24 | items: [ 25 | { text: '介绍', link: '/intro/about'}, 26 | { text: '安装', link: '/intro/install'} 27 | ] 28 | }, 29 | { 30 | text: '指南', 31 | collapsed:false, 32 | items: [ 33 | { text: "创建", link: "/guide/create" }, 34 | { text: "数据类型", link: "/guide/datatype" }, 35 | { text: "模板字符", link: "/guide/template" }, 36 | { text: "格式化对象", link: "/guide/format" }, 37 | { text: "横幅", link: "/guide/banner" }, 38 | { text: "列表", link: "/guide/list" }, 39 | { text: "日志", link: "/guide/log" }, 40 | { text: "进度条", link: "/guide/progressbar" }, 41 | { text: "分割条", link: "/guide/separator" }, 42 | { text: "标题", link: "/guide/header" }, 43 | { text: "表格", link: "/guide/table" }, 44 | { 45 | text: "任务列表", 46 | collapsed:false, 47 | items:[ 48 | { text: "创建任务列表", link: "/guide/tasklist" }, 49 | { text: "执行任务列表", link: "/guide/run-tasklist" }, 50 | { text: "快速任务列表", link: "/guide/quick-tasklist" }, 51 | { text: "分组任务列表", link: "/guide/grouped-tasks" }, 52 | { text: "执行单个任务", link: "/guide/task" }, 53 | ] 54 | }, 55 | { text: "树", link: "/guide/tree" } 56 | ] 57 | }, 58 | { 59 | text: '高级', 60 | collapsed:false, 61 | items: [ 62 | { text: "输出彩色内容", link: "/advanced/colorized" }, 63 | { text: "全局配置", link: "/advanced/config" }, 64 | { text: "开发插件", link: "/advanced/plugin" }, 65 | ] 66 | }, 67 | { 68 | text: 'API', 69 | link: '/api', 70 | }, 71 | ], 72 | 73 | socialLinks: [ 74 | { icon: 'github', link: 'https://github.com/zhangfisher/logsets' } 75 | ] 76 | } 77 | }) 78 | -------------------------------------------------------------------------------- /docs/.vitepress/theme/index.ts: -------------------------------------------------------------------------------- 1 | // https://vitepress.dev/guide/custom-theme 2 | import { h } from 'vue' 3 | import type { Theme } from 'vitepress' 4 | import DefaultTheme from 'vitepress/theme' 5 | import './style.css' 6 | 7 | export default { 8 | extends: DefaultTheme, 9 | Layout: () => { 10 | return h(DefaultTheme.Layout, null, { 11 | // https://vitepress.dev/guide/extending-default-theme#layout-slots 12 | }) 13 | }, 14 | enhanceApp({ app, router, siteData }) { 15 | // ... 16 | } 17 | } satisfies Theme 18 | -------------------------------------------------------------------------------- /docs/.vitepress/theme/style.css: -------------------------------------------------------------------------------- 1 | /** 2 | * Customize default theme styling by overriding CSS variables: 3 | * https://github.com/vuejs/vitepress/blob/main/src/client/theme-default/styles/vars.css 4 | */ 5 | 6 | /** 7 | * Colors 8 | * 9 | * Each colors have exact same color scale system with 3 levels of solid 10 | * colors with different brightness, and 1 soft color. 11 | * 12 | * - `XXX-1`: The most solid color used mainly for colored text. It must 13 | * satisfy the contrast ratio against when used on top of `XXX-soft`. 14 | * 15 | * - `XXX-2`: The color used mainly for hover state of the button. 16 | * 17 | * - `XXX-3`: The color for solid background, such as bg color of the button. 18 | * It must satisfy the contrast ratio with pure white (#ffffff) text on 19 | * top of it. 20 | * 21 | * - `XXX-soft`: The color used for subtle background such as custom container 22 | * or badges. It must satisfy the contrast ratio when putting `XXX-1` colors 23 | * on top of it. 24 | * 25 | * The soft color must be semi transparent alpha channel. This is crucial 26 | * because it allows adding multiple "soft" colors on top of each other 27 | * to create a accent, such as when having inline code block inside 28 | * custom containers. 29 | * 30 | * - `default`: The color used purely for subtle indication without any 31 | * special meanings attached to it such as bg color for menu hover state. 32 | * 33 | * - `brand`: Used for primary brand colors, such as link text, button with 34 | * brand theme, etc. 35 | * 36 | * - `tip`: Used to indicate useful information. The default theme uses the 37 | * brand color for this by default. 38 | * 39 | * - `warning`: Used to indicate warning to the users. Used in custom 40 | * container, badges, etc. 41 | * 42 | * - `danger`: Used to show error, or dangerous message to the users. Used 43 | * in custom container, badges, etc. 44 | * -------------------------------------------------------------------------- */ 45 | 46 | :root { 47 | --vp-c-default-1: var(--vp-c-gray-1); 48 | --vp-c-default-2: var(--vp-c-gray-2); 49 | --vp-c-default-3: var(--vp-c-gray-3); 50 | --vp-c-default-soft: var(--vp-c-gray-soft); 51 | 52 | --vp-c-brand-1: var(--vp-c-indigo-1); 53 | --vp-c-brand-2: var(--vp-c-indigo-2); 54 | --vp-c-brand-3: var(--vp-c-indigo-3); 55 | --vp-c-brand-soft: var(--vp-c-indigo-soft); 56 | 57 | --vp-c-tip-1: var(--vp-c-brand-1); 58 | --vp-c-tip-2: var(--vp-c-brand-2); 59 | --vp-c-tip-3: var(--vp-c-brand-3); 60 | --vp-c-tip-soft: var(--vp-c-brand-soft); 61 | 62 | --vp-c-warning-1: var(--vp-c-yellow-1); 63 | --vp-c-warning-2: var(--vp-c-yellow-2); 64 | --vp-c-warning-3: var(--vp-c-yellow-3); 65 | --vp-c-warning-soft: var(--vp-c-yellow-soft); 66 | 67 | --vp-c-danger-1: var(--vp-c-red-1); 68 | --vp-c-danger-2: var(--vp-c-red-2); 69 | --vp-c-danger-3: var(--vp-c-red-3); 70 | --vp-c-danger-soft: var(--vp-c-red-soft); 71 | } 72 | 73 | /** 74 | * Component: Button 75 | * -------------------------------------------------------------------------- */ 76 | 77 | :root { 78 | --vp-button-brand-border: transparent; 79 | --vp-button-brand-text: var(--vp-c-white); 80 | --vp-button-brand-bg: var(--vp-c-brand-3); 81 | --vp-button-brand-hover-border: transparent; 82 | --vp-button-brand-hover-text: var(--vp-c-white); 83 | --vp-button-brand-hover-bg: var(--vp-c-brand-2); 84 | --vp-button-brand-active-border: transparent; 85 | --vp-button-brand-active-text: var(--vp-c-white); 86 | --vp-button-brand-active-bg: var(--vp-c-brand-1); 87 | } 88 | 89 | /** 90 | * Component: Home 91 | * -------------------------------------------------------------------------- */ 92 | 93 | :root { 94 | --vp-home-hero-name-color: transparent; 95 | --vp-home-hero-name-background: -webkit-linear-gradient( 96 | 120deg, 97 | #bd34fe 30%, 98 | #41d1ff 99 | ); 100 | 101 | --vp-home-hero-image-background-image: linear-gradient( 102 | -45deg, 103 | #bd34fe 50%, 104 | #47caff 50% 105 | ); 106 | --vp-home-hero-image-filter: blur(44px); 107 | } 108 | 109 | @media (min-width: 640px) { 110 | :root { 111 | --vp-home-hero-image-filter: blur(56px); 112 | } 113 | } 114 | 115 | @media (min-width: 960px) { 116 | :root { 117 | --vp-home-hero-image-filter: blur(68px); 118 | } 119 | } 120 | 121 | /** 122 | * Component: Custom Block 123 | * -------------------------------------------------------------------------- */ 124 | 125 | :root { 126 | --vp-custom-block-tip-border: transparent; 127 | --vp-custom-block-tip-text: var(--vp-c-text-1); 128 | --vp-custom-block-tip-bg: var(--vp-c-brand-soft); 129 | --vp-custom-block-tip-code-bg: var(--vp-c-brand-soft); 130 | } 131 | 132 | /** 133 | * Component: Algolia 134 | * -------------------------------------------------------------------------- */ 135 | 136 | .DocSearch { 137 | --docsearch-primary-color: var(--vp-c-brand-1) !important; 138 | } 139 | 140 | -------------------------------------------------------------------------------- /docs/advanced/colorized.md: -------------------------------------------------------------------------------- 1 | # 输出彩色内容 2 | 3 | `logsets`依赖于`ansicolor`,并且将其挂在了`logsets.colors`下,因此也可以直接调用来生成彩色内容。 4 | 5 | ```javascript 6 | import logsets from "logsets" 7 | 8 | console.log(logsets.colors.red(text)) 9 | console.log(logsets.colors.green(text)) 10 | console.log(logsets.colors.yellow(text)) 11 | console.log(logsets.colors.blue(text)) 12 | console.log(logsets.colors.magenta(text)) 13 | console.log(logsets.colors.cyan(text)) 14 | console.log(logsets.colors.white(text)) 15 | console.log(logsets.colors.darkGray(text)) 16 | console.log(logsets.colors.black()) 17 | ``` 18 | 19 | 支持的方法如下: 20 | 21 | ```javascript 22 | export interface ColorizedMethods{ 23 | default (text:string):string 24 | white (text:string):string 25 | black (text:string):string 26 | red (text:string):string 27 | green (text:string):string 28 | yellow (text:string):string 29 | blue (text:string):string 30 | magenta (text:string):string 31 | cyan (text:string):string 32 | 33 | darkGray (text:string):string 34 | lightGray (text:string):string 35 | lightRed (text:string):string 36 | lightGreen (text:string):string 37 | lightYellow (text:string):string 38 | lightBlue (text:string):string 39 | lightMagenta (text:string):string 40 | lightCyan (text:string):string 41 | 42 | bright (text:string):string 43 | dim (text:string):string 44 | italic (text:string):string 45 | underline (text:string):string 46 | inverse (text:string):string 47 | 48 | bgDefault (text:string):string 49 | bgWhite (text:string):string 50 | bgBlack (text:string):string 51 | bgRed (text:string):string 52 | bgGreen (text:string):string 53 | bgYellow (text:string):string 54 | bgBlue (text:string):string 55 | bgMagenta (text:string):string 56 | bgCyan (text:string):string 57 | 58 | bgDarkGray (text:string):string 59 | bgLightGray (text:string):string 60 | bgLightRed (text:string):string 61 | bgLightGreen (text:string):string 62 | bgLightYellow (text:string):string 63 | bgLightBlue (text:string):string 64 | bgLightMagenta (text:string):string 65 | bgLightCyan (text:string):string 66 | } 67 | ``` 68 | ` 69 | -------------------------------------------------------------------------------- /docs/advanced/config.md: -------------------------------------------------------------------------------- 1 | # 全局配置 2 | 3 | 绝大多数情况下,`logsets`是开箱即用的,不需要进行配置。 4 | 5 | 但是`logsets`也支持丰富的配置参数,可以自定义输出样式。 6 | 7 | ## 默认配置 8 | 9 | 完整配置如下: 10 | 11 | ```javascript 12 | import createLogger from "logsets" 13 | const log = createLogger({ 14 | indent: " ", // 缩进 15 | singleQuotes: false, // 显示单引号 16 | template: "[{level}] {datetime} - {message}", // 模板 17 | compact:false, // 是否采用紧凑模式输出 18 | Array:{ 19 | compact : true, // 是否采用紧凑模式输出 20 | maxItems: 100, // 数组最大长度,超过则显示省略号 21 | memo : (value)=> darkGray("(共"+value.length+"项)") // 当数组数量超过maxItems时,显示共几项的备注 22 | }, 23 | Object:{ 24 | compact:true, // 是否采用紧凑模式输出 25 | maxItems:100, // 成员数量,超过则显示省略号 26 | align:true, // 是否自动对齐 27 | memo:(value)=> darkGray("(共"+value.length+"项)"), 28 | }, 29 | Function : { 30 | style:"lightCyan", 31 | format:value=>value.name ? `[Function ${value.name}]` : "()=>{...}" 32 | }, 33 | AsyncFunction: { 34 | style:"lightCyan", 35 | format:value=>value.name ? `[AsyncFunction ${value.name}]` : "async ()=>{...}" 36 | }, 37 | Error : { 38 | style:"red", 39 | format:e=>`${e.name.firstUpper()}('${e.message}')` 40 | }, 41 | Null : "darkGray", 42 | Undefined: "darkGray", 43 | Boolean : "cyan", 44 | Number : "yellow", 45 | String : "green", 46 | Class : { 47 | style:"lightCyan", 48 | format:value=>`[Class ${value.name}]` 49 | }, 50 | Instance : { 51 | style:"lightBlue", 52 | format:value=>`` 53 | }, 54 | Date : { 55 | style:"lightBlue", 56 | format:(value)=>`${value.getFullYear()}-${value.getMonth()+1}-${value.getDate()} ${value.getHours()}:${value.getMinutes()}:${value.getSeconds()}` 57 | }, 58 | Symbol : "blue", 59 | RegExp : { 60 | style:"magenta", 61 | format:(value)=>`${value.toString()}` 62 | }, 63 | levels :{ 64 | align: true, // 是否自动对齐消息 65 | maxLineChars : 90, // 每行最大字符数 66 | memo : "darkGray", 67 | debug : "lightGray", 68 | info : "dim", 69 | warn : "yellow", 70 | error : "red", 71 | fatal : "red" 72 | } 73 | }) 74 | 75 | ``` 76 | 77 | 以上是默认的配置,一般情况不需要进行修改覆盖。 78 | 79 | ## 数据类型显示样式 80 | 81 | 各种数据类型均可以配置显示样式,支持配置`Array`、`Object`、`Function`、`AsyncFunction`、`Error`、`Date`、`Null`、`Undefuned`、`Boolean`、`Number`、`String`、`Class`、`Instance`,`Symbol`、`Regexp`。 82 | 83 | 配置数据类型的样式可以按如下方式: 84 | 85 | ```javascript 86 | // 1. 简单指定类型的样式名称 87 | { 88 | [数据类型名称]:"<样式名称>,<样式名称>,...,<样式名称>" 89 | } 90 | // 2. 指定类型的样式名称,同时指一个格式化函数来用内容进行格式化 91 | { 92 | [数据类型名称]:{ 93 | style:"<样式名称>,<样式名称>,...,<样式名称>", 94 | format:(value)=>{...<返回格式化后的内容>...} 95 | } 96 | } 97 | ``` 98 | 99 | 100 | ## 颜色样式 101 | 102 | 显示**样式名称**支持设置一个或多个,同时使用多个时采用`,`分开。 103 | 104 | 样式名称用来指定以何种前景颜色、背景颜色或修饰样式,支持如下值: 105 | 106 | - **普通前景色:**`red`,`green`,`yellow`,`blue`,`magenta`,`cyan`,`white`,`darkGray`,`black` 107 | - **加亮前景色:**`lightRed`,`lightGreen`,`lightYellow`,`lightBlue`,`lightMagenta`,lightCyan ,`lightGray` 108 | - **普通背景色:**`bgRed`,`bgGreen`,`bgYellow`,`bgBlue`,`bgMagenta`,`bgCyan`,`bgWhite`,`bgDarkGray`,`bgBlack` 109 | - **加亮背景色:**`bgLightRed`,`bgLightGreen`,`bgLightYellow`,`bgLightBlue`,`bgLightMagenta`,`bgLightCyan`,`bgLightGray` 110 | - **修饰样式:**`bright`,`dim`,`italic`,`underline`,`inverse` 111 | 112 | 所有可以定制显示颜色样式的参数均支持`TypeScript`提示 113 | 114 | **举例如下:** 115 | 116 | ```javascript 117 | logsets.config({ 118 | Boolean:"bgLightRed,white" 119 | }) 120 | ``` 121 | 122 | 代表对`Boolean`类型数据采用**亮红色背景**,**白色文本**方式显示。 123 | 124 | **注**: 样式可能受不平操作系统平台差异,部分样式不支持。 125 | 126 | ## 文本样式 127 | 128 | 在上述所有功能中均会使用一个`style`参数来配置自定义的显示样式。 129 | 130 | `style`参数是一个使用`,`分割的字符串,可以同时应用`1-N`个字符串色彩和样式。比如`style="bgLightRed,white"`代表采用**亮红色背景**,**白色文本**方式显示。 131 | -------------------------------------------------------------------------------- /docs/advanced/plugin.md: -------------------------------------------------------------------------------- 1 | # 开发插件 2 | 3 | `logsets`支持开发扩展插件,步骤如下: 4 | 5 | 6 | ## 第一步:定义插件函数 7 | `logsets`插件是一个普通的函数,其传入参数: 8 | 9 | - **logsets**: 当前`logsets`实例,一般可以直接在其上面挂载插件函数 10 | - **options**: 当前`logsets`实例的配置参数,==`logsets.options` 11 | 12 | ```javascript 13 | 14 | export default function(logsets,options){ 15 | logsets.myplugin = (opts={})=>{ 16 | // ....插件逻辑 17 | } 18 | } 19 | 20 | ``` 21 | ## 第二步:编写插件逻辑 22 | 23 | `logsets`插件主要使用当前`logsets`实例的`colorize`,`getColorizer`,`getColorizedTemplate`这三个函数来控制终端输出的颜色。 24 | 25 | - `colorize` 26 | 对输入参数按数据类型进行着色,返回着色后的字符串。例:`colorize(1)`,`colorize(true)`,`colorize("logsets")`返回的是根据配置中的数据类型的样式着色后的字符串。 27 | - `getColorizer` 28 | 根据颜色字符串返回一个着色函数,如`red,dim`返回能着红色的函数。 29 | 比如要输出`红色,高亮`的内容,可以`logsets.getColorizer("red,bright")("hello logsets")` 30 | - `getColorizedTemplate` 31 | 根据模板字符串插值,输出着色后的内容。例:`getColorizedTemplate("{}+{}={}",1,1,2)`返回的就是经过着色后的字符串。可以直接使用`console.log(getColorizedTemplate("{}+{}={}",1,1,2))` 32 | 33 | `logsets`插件也可以直接调用当前插件实例方法(如`print`,`format`,`log)`来显示内容。 34 | 需要注意,`colorize`,`getColorizer`,`getColorizedTemplate`这三个函数并不会在控制台输出,仅仅是返回着色后的字符串。 35 | 36 | `logsets`也提供了一些工具函数来简化常见的功能。 37 | 38 | ```javascript 39 | import { 40 | // 显示/隐藏光标 41 | hideCursor, 42 | showCursor, 43 | // 字符串居中/左/右填充,主要是将中文按2个字符处理 44 | paddingCenter, 45 | paddingStart, 46 | paddingEnd, 47 | // 获取字符串长度,中文按2个字符表示,多行字符串取其中最长的一行 48 | // 如果是着色过的会自动去掉着色再计算 49 | getStringWidth, 50 | // 截取字符串,超过显示省略号,支持中文 51 | cutstr, 52 | //截断字符串为多行字符串\ 53 | // truncateString("123456789",3,"*") == "123*456*789" 54 | // truncateString("123456789",3) == 第一行:"123 第二行:456 第三行:*789 55 | truncateString 56 | // 输出新行,等效\n 57 | newline 58 | } from 'logsets/utils' 59 | 60 | ``` 61 | 62 | ## 第三步:注册插件 63 | 64 | ```javascript 65 | import logsets from 'logsets' 66 | import MyPlugin from 'my-plugin' 67 | logsets.use(MyPlugin) 68 | ``` 69 | -------------------------------------------------------------------------------- /docs/api.md: -------------------------------------------------------------------------------- 1 | # API 2 | 3 | `logsets`实例`API`如下: 4 | 5 | | 名称 | 说明 | 6 | | :--- | :--- | 7 | | `log(message,...args)` | 对模板字符串进行插值后输出着色后的字符串 | 8 | | `print(...args)` | 根据输入参数的参数类型进行着色输出,比如可以输出JSON ,同`console.log`,差别就在于输出内容的着色| 9 | | `format(value,options)` | 输出带缩进格式和着色过的对象 | 10 | | `debug(...args)` | 输出DEBUG日志,并会对插值变量进行着色。 | 11 | | `info(...args)` |输出INFO日志,并会对插值变量进行着色。 | 12 | | `warn(...args)` | 输出WARN日志,并会对插值变量进行着色。 | 13 | | `error(...args)` | 输出ERROR日志,并会对插值变量进行着色。 | 14 | | `fatal(...args)` | 输出FATAL日志,并会对插值变量进行着色。 | 15 | | `use(plugin)` | 安装插件 | 16 | | `colorize(arg)` | 对输入参数按数据类型进行着色,返回着色后的字符串 | 17 | | `getColorizer(colors)` | 根据颜色字符串返回一个着色函数,如`red,dim`返回能着红色的函数。 | 18 | | `getColorizedTemplate(template, ...args)` |根据模板字符串插值,输出着色后的内容 | 19 | | `separator(width, char= "─")` | 输出一个分割线 | 20 | | `options` | 返回当前配置参数 | 21 | | `colors` | 返回[ansicolor](https://xpl.github.io/ansicolor/)实例 | 22 | | `config(options={})` | 配置 | 23 | -------------------------------------------------------------------------------- /docs/guide/banner.md: -------------------------------------------------------------------------------- 1 | # 横幅 2 | 3 | 显示一个广告横幅 4 | 5 | ### 基本用法 6 | 7 | ```javascript 8 | import logsets from "./index.js" 9 | 10 | let banner = logsets.banner({ }) 11 | 12 | banner.add("Logsets Utility Toolkit") 13 | banner.add("Output color elements at the terminal") 14 | banner.add("Version: ",1) 15 | banner.add("Release: ","2022-01-01") 16 | banner.render() 17 | ``` 18 | 19 | 输出效果如下: 20 | 21 | ![](./images/banner1.png) 22 | 23 | ```javascript 24 | banner = logsets.banner({ 25 | width:60 26 | }) 27 | banner.add("Logsets工具库") 28 | banner.add("在终端命令行输出彩色文本",{style:"darkGray"}) 29 | banner.add() // 输出空行 30 | banner.add("版本: ",1) 31 | banner.add("网站: ","http://www.logsets.com",{align: 'left',style:["","lightBlue"]}) 32 | banner.add("发布日期: ","2022-01-01",{align: 'right',style:["","lightMagenta"]}) 33 | banner.add("作者: ","fisher",{align: 'right',style:["","lightCyan"]}) 34 | banner.add("许可证: ","MIT ","GPL ","Apache 2.0",{style:["","red"]}) 35 | banner.render() 36 | ``` 37 | 38 | ![](./images/banner2.png) 39 | 40 | ## 配置参数 41 | 42 | ```javascript 43 | { 44 | indent : " ", // 横幅整体缩进 45 | border : { 46 | style : "lightGray", // 边框颜色 47 | width : 1 // 边框宽度,0-不显示,1-单线框,2-双线框 48 | }, 49 | // 第一行自动作为标题行 50 | title : { 51 | align : "center", // 标题对齐方式 52 | style : ["","","green,bright","",""], // 标题样式颜色 53 | wrapper : "☆ ☆ ☆" // 标题包裹符号,用来装饰 54 | }, 55 | align : "center", // 横幅行默认对齐方式,默认居中 56 | paddingLeft : 4, // 左右空白宽度,以字符为单位 57 | paddingRight : 4, 58 | paddingTop : 1, // 顶部和底部空白行 59 | paddingBottom: 1 60 | } 61 | ``` 62 | 63 | 64 | ## API 65 | 66 | - **add(arg1,arg2,...,{options})** 67 | 68 | 增加行,支持多个输出参数,每个参数均会按照logger的数据类型的配色进行输出。 69 | 70 | 如果最后一个参数是`{}`,则支持配置额外的样式和参数。 71 | 72 | ```javascript 73 | banner.add(arg1,arg2,...,{ 74 | align:"center | left | right", // 整体居中、居左、居右 75 | // 指定该行整行的色彩 76 | style:"<色彩样式>", 77 | // 可以为每一个参数指定颜色。 78 | style:[ 79 | "<第1个参数的色彩样式>", 80 | "<第2个参数的色彩样式>", 81 | "<第3个参数的色彩样式>", 82 | ..., 83 | "<第n个参数的色彩样式>" 84 | ] 85 | //当参数个数与style数组长度不匹配时,会取最后一个style[style.length-1] 86 | }) 87 | 88 | // text1显示红色,text2/text3/text4显示黄色 89 | banner.add("text1","text2","text3","text4",{style:["red","yellow"]}) 90 | 91 | ``` 92 | -------------------------------------------------------------------------------- /docs/guide/create.md: -------------------------------------------------------------------------------- 1 | # 创建实例 2 | 3 | `logsets`默认自动创建一个实例,可以直接引入使用。 4 | 5 | ```javascript 6 | import logsets from "logsets" 7 | logsets.config({...}) 8 | ``` 9 | 10 | 也可以创建多个实例: 11 | 12 | ```javascript 13 | import createLogger from "logsets" 14 | const logsets = createLogger({...}) 15 | ``` -------------------------------------------------------------------------------- /docs/guide/datatype.md: -------------------------------------------------------------------------------- 1 | # 按数据类型输出 2 | 3 | 提供`print`方法,用来连续输出多个经过着色的参数。 4 | ```javascript 5 | print(arg1,arg2,arg3,.....) 6 | print(arg1,arg2,arg3,.....,{end:"\n",append:" "}) // 增加可选的输出参数 7 | ``` 8 | 9 | 10 | ## 配置参数 11 | 12 | 同`log`方法. 13 | 14 | ## 示例 15 | 16 | ```javascript 17 | import logsets from "logsets" 18 | 19 | logsets.print("String",true,100,()=>{},[1,2,3]) 20 | logsets.print(null,undefined) 21 | logsets.print(/^colored$/g) 22 | logsets.print(new Error("Value Error")) 23 | logsets.print(new Date()) 24 | logsets.print(class A{}) 25 | logsets.print(new (class X{})()) 26 | logsets.print({name:"tom",age:100,admin:true,posts:["a","b"],values:[1,2,3]},()=>"hello") 27 | ``` 28 | 29 | 输出效果如下: 30 | 31 | ![image](./images/log2.jpg) 32 | -------------------------------------------------------------------------------- /docs/guide/format.md: -------------------------------------------------------------------------------- 1 | # 格式化输出 2 | 3 | 提供`format`方法,用来带缩进格式和着色过的对象 4 | 5 | ## 基本用法 6 | 7 | ```javascript 8 | import logsets from "logsets" 9 | 10 | logsets.format({ 11 | name:"tom", 12 | age:11, 13 | admin:true, 14 | posts:["经理","主任"], 15 | address:{ 16 | company:"中华人民共和国北京市二环路", 17 | family:"福建省泉州市惠安路1512号" 18 | } 19 | }) 20 | 21 | ``` 22 | 输出效果如下: 23 | 24 | ![image](./images/log3.jpg) 25 | 26 | ## 优化数组和对象输出 27 | 28 | 对数组或对象成员数量当超过指定值时,显示省略号并备注总数量。 29 | 30 | ```javascript 31 | import logsets from "logsets" 32 | 33 | logsets.format({ 34 | values:new Array(10).fill(0).map((v,i)=>i+1), 35 | users:{ 36 | tom:{name:"tom",age:21,sex:true}, 37 | jack:{name:"jack",age:21,sex:false}, 38 | jack1:{name:"jack",age:21,sex:false}, 39 | jack2:{name:"jack",age:21,sex:false}, 40 | jack3:{name:"jack",age:21,sex:false}, 41 | jack4:{name:"jack",age:21,sex:false}, 42 | jack5:{name:"jack",age:21,sex:false}, 43 | jack6:{name:"jack",age:21,sex:false}, 44 | jack7:{name:"jack",age:21,sex:false}, 45 | jack8:{name:"jack",age:21,sex:false}, 46 | jack9:{name:"jack",age:21,sex:false}, 47 | jack10:{name:"jack",age:21,sex:false}, 48 | jack11:{name:"jack",age:21,sex:false}, 49 | jack12:{name:"jack",age:21,sex:false}, 50 | } 51 | },{Array:{maxItems:5},Object:{maxItems:5}}) 52 | 53 | ``` 54 | `maxItems`参数用来指定只显示多少项,超出显示省略号并备注总数量。 55 | 56 | 57 | 输出效果如下: 58 | 59 | ![image](./images/log5.jpg) 60 | 61 | ## 紧凑模式输出 62 | 63 | 可以配置紧凑模式输出。 64 | 65 | ```javascript 66 | import logsets from "logsets" 67 | 68 | logsets.format({ 69 | values:new Array(10).fill(0).map((v,i)=>i+1), 70 | users:{ 71 | tom:{name:"tom",age:21,sex:true}, 72 | ..., 73 | jack12:{name:"jack",age:21,sex:false}, 74 | } 75 | },{compact:true, Array:{maxItems:5},Object:{maxItems:5}}) 76 | 77 | ``` 78 | `compact`参数用来指示采用紧凑模式输出 79 | 80 | 输出效果如下: 81 | 82 | ![image](./images/log4.jpg) 83 | 84 | 也可以单独控制Array和Object类型是否采用紧凑模式输出。 85 | ```javascript 86 | logsets.format({...},{ 87 | compact:true, 88 | Array:{ 89 | maxItems:5, 90 | compact:false 91 | }, 92 | Object:{ 93 | maxItems:5 94 | } 95 | }) 96 | ``` 97 | 98 | `logsets.format`支持指定一个`{title:"标题"}`参数,用来输出一个带标题的对象。 99 | -------------------------------------------------------------------------------- /docs/guide/grouped-tasks.md: -------------------------------------------------------------------------------- 1 | # 分组任务列表 2 | 3 | 使用`logsets.createTasks`可以创建一个分组任务列表,然后通过`logsets.run`来执行任务列表。 4 | 5 | ## 创建分组任务列表 6 | 7 | 创建任务列表时,可以使用`string`或`string[]`来创建一个任务分组标题。 8 | 9 | 10 | ```javascript 11 | const taskList =[ 12 | "准备阶段", //组标题 // [!code ++] 13 | { title: "开始扫描文件", 14 | execute: async function () {return 'error'}, 15 | },{ title: "准备对文件进行预处理", 16 | execute: async function () {return 'skip'}, 17 | }, 18 | { title: "正在下载文件", 19 | execute: async function ({task}) {....}, 20 | }, 21 | "编译项目阶段", // 组标题 // [!code ++] 22 | { title: "任务处理被停止", 23 | execute: async function () {...}, 24 | },{ title: "任务处理被停止", 25 | execute: async function () {...}, 26 | },{ title: "任务处理被停止", 27 | execute: async function () {...}, 28 | }, 29 | "执行单元测试项目", // 组标题 // [!code ++] 30 | { 31 | title: "任务执行失败", 32 | execute: async function () { 33 | await delay(); 34 | }, 35 | }, 36 | { 37 | title: "任务待办状态", 38 | execute: async function () { 39 | await delay(); 40 | }, 41 | }, 42 | ["构建{}项目","vue"], // 组标题,支持插值变量着色 // [!code ++] 43 | { 44 | title: ["下载文件:{},大小:{}, 已下载{}", "package.json", 122, 344], 45 | execute: async function () { 46 | await delay(); 47 | }, 48 | }, 49 | { 50 | title: ["下载文件:{},大小:{}, 已下载{}", ["package.json", 122, 344]], 51 | execute: async function () { 52 | await delay(); 53 | return 'cancel' 54 | }, 55 | }, 56 | ] 57 | let tasks = logsets.createTasks(taskList); 58 | ``` 59 | 60 | 输出效果如下: 61 | 62 | ![](./images/group-tasks.png) 63 | 64 | 65 | ## 显示分组线 66 | 67 | 可以通过`grouped`参数来显示分组线。 68 | 69 | ```javascript 70 | const taskList =[ 71 | // .... 72 | ] 73 | let tasks = logsets.createTasks(taskList,{ 74 | grouped: true // 显示分组线 // [!code ++] 75 | }); 76 | ``` 77 | 78 | ![](./images/task-grouped.png) -------------------------------------------------------------------------------- /docs/guide/header.md: -------------------------------------------------------------------------------- 1 | # 标题 2 | 3 | `logsets.header(message,...args)`可以输出高亮标题. 4 | 5 | ```js 6 | logsets.header("{#red a}+{b}={c}",{a:1,b:1,c:2}) 7 | // 位置插值变量 8 | logsets.header("My name is {}","tom") 9 | logsets.header("{a}+{b}={#bgGreen c}",1,1,2) 10 | 11 | logsets.header("My name is {#red tom}") 12 | logsets.h("{#blue Voerkai18n}是一个非常不错的{#red,dim 多语言}解决方案!") 13 | 14 | ``` 15 | 16 | **效果如下:** 17 | 18 | ![](./images/header.png) 19 | 20 | :::warning 21 | `logsets.h`是`logsets.header`的简写 22 | ::: -------------------------------------------------------------------------------- /docs/guide/images/banner1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhangfisher/logsets/dc3a10c9b8038071bd5170198eeb50bb4f5b23f7/docs/guide/images/banner1.png -------------------------------------------------------------------------------- /docs/guide/images/banner2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhangfisher/logsets/dc3a10c9b8038071bd5170198eeb50bb4f5b23f7/docs/guide/images/banner2.png -------------------------------------------------------------------------------- /docs/guide/images/createTasks.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhangfisher/logsets/dc3a10c9b8038071bd5170198eeb50bb4f5b23f7/docs/guide/images/createTasks.png -------------------------------------------------------------------------------- /docs/guide/images/group-tasks.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhangfisher/logsets/dc3a10c9b8038071bd5170198eeb50bb4f5b23f7/docs/guide/images/group-tasks.png -------------------------------------------------------------------------------- /docs/guide/images/grouped-tasks-line.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhangfisher/logsets/dc3a10c9b8038071bd5170198eeb50bb4f5b23f7/docs/guide/images/grouped-tasks-line.png -------------------------------------------------------------------------------- /docs/guide/images/header.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhangfisher/logsets/dc3a10c9b8038071bd5170198eeb50bb4f5b23f7/docs/guide/images/header.png -------------------------------------------------------------------------------- /docs/guide/images/list.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhangfisher/logsets/dc3a10c9b8038071bd5170198eeb50bb4f5b23f7/docs/guide/images/list.png -------------------------------------------------------------------------------- /docs/guide/images/list2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhangfisher/logsets/dc3a10c9b8038071bd5170198eeb50bb4f5b23f7/docs/guide/images/list2.png -------------------------------------------------------------------------------- /docs/guide/images/log.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhangfisher/logsets/dc3a10c9b8038071bd5170198eeb50bb4f5b23f7/docs/guide/images/log.jpg -------------------------------------------------------------------------------- /docs/guide/images/log10.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhangfisher/logsets/dc3a10c9b8038071bd5170198eeb50bb4f5b23f7/docs/guide/images/log10.jpg -------------------------------------------------------------------------------- /docs/guide/images/log11.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhangfisher/logsets/dc3a10c9b8038071bd5170198eeb50bb4f5b23f7/docs/guide/images/log11.jpg -------------------------------------------------------------------------------- /docs/guide/images/log2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhangfisher/logsets/dc3a10c9b8038071bd5170198eeb50bb4f5b23f7/docs/guide/images/log2.jpg -------------------------------------------------------------------------------- /docs/guide/images/log3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhangfisher/logsets/dc3a10c9b8038071bd5170198eeb50bb4f5b23f7/docs/guide/images/log3.jpg -------------------------------------------------------------------------------- /docs/guide/images/log4.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhangfisher/logsets/dc3a10c9b8038071bd5170198eeb50bb4f5b23f7/docs/guide/images/log4.jpg -------------------------------------------------------------------------------- /docs/guide/images/log5.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhangfisher/logsets/dc3a10c9b8038071bd5170198eeb50bb4f5b23f7/docs/guide/images/log5.jpg -------------------------------------------------------------------------------- /docs/guide/images/log6.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhangfisher/logsets/dc3a10c9b8038071bd5170198eeb50bb4f5b23f7/docs/guide/images/log6.jpg -------------------------------------------------------------------------------- /docs/guide/images/log7.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhangfisher/logsets/dc3a10c9b8038071bd5170198eeb50bb4f5b23f7/docs/guide/images/log7.jpg -------------------------------------------------------------------------------- /docs/guide/images/log8.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhangfisher/logsets/dc3a10c9b8038071bd5170198eeb50bb4f5b23f7/docs/guide/images/log8.jpg -------------------------------------------------------------------------------- /docs/guide/images/log9.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhangfisher/logsets/dc3a10c9b8038071bd5170198eeb50bb4f5b23f7/docs/guide/images/log9.jpg -------------------------------------------------------------------------------- /docs/guide/images/logvar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhangfisher/logsets/dc3a10c9b8038071bd5170198eeb50bb4f5b23f7/docs/guide/images/logvar.png -------------------------------------------------------------------------------- /docs/guide/images/logvar2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhangfisher/logsets/dc3a10c9b8038071bd5170198eeb50bb4f5b23f7/docs/guide/images/logvar2.png -------------------------------------------------------------------------------- /docs/guide/images/progressbar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhangfisher/logsets/dc3a10c9b8038071bd5170198eeb50bb4f5b23f7/docs/guide/images/progressbar.png -------------------------------------------------------------------------------- /docs/guide/images/task-grouped.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhangfisher/logsets/dc3a10c9b8038071bd5170198eeb50bb4f5b23f7/docs/guide/images/task-grouped.png -------------------------------------------------------------------------------- /docs/guide/images/task.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhangfisher/logsets/dc3a10c9b8038071bd5170198eeb50bb4f5b23f7/docs/guide/images/task.png -------------------------------------------------------------------------------- /docs/guide/images/tasklist.demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhangfisher/logsets/dc3a10c9b8038071bd5170198eeb50bb4f5b23f7/docs/guide/images/tasklist.demo.gif -------------------------------------------------------------------------------- /docs/guide/images/tasklist.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhangfisher/logsets/dc3a10c9b8038071bd5170198eeb50bb4f5b23f7/docs/guide/images/tasklist.png -------------------------------------------------------------------------------- /docs/guide/images/tasks.note.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhangfisher/logsets/dc3a10c9b8038071bd5170198eeb50bb4f5b23f7/docs/guide/images/tasks.note.png -------------------------------------------------------------------------------- /docs/guide/images/tree1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhangfisher/logsets/dc3a10c9b8038071bd5170198eeb50bb4f5b23f7/docs/guide/images/tree1.png -------------------------------------------------------------------------------- /docs/guide/images/tree2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhangfisher/logsets/dc3a10c9b8038071bd5170198eeb50bb4f5b23f7/docs/guide/images/tree2.png -------------------------------------------------------------------------------- /docs/guide/index.md: -------------------------------------------------------------------------------- 1 | # 关于 2 | 3 | 为开发`nodejs`命令行程序提供丰富的表现输出样式,支持以下特性: 4 | 5 | - 支持按不同数据类型以不同的颜色显示,并且可以配置显示样式 6 | - 支持按`DEBUG`、`INFO`、`WARN `、`ERROR `、`FATAL`五个级别输出日志 7 | - 支持输出带颜色的模板字符串 8 | - 支持自动格式化显示`{}`和`[]`类型 9 | - 支持强大的表格输出 10 | - 支持输出任务列表、进度条、横幅和树等扩展 11 | - 正确处理中文与英文混排时的对齐问题 12 | - 支持扩展插件机制 13 | - `TypeScript`类型支持 -------------------------------------------------------------------------------- /docs/guide/list.md: -------------------------------------------------------------------------------- 1 | # 列表 2 | 3 | 显示信息列表 4 | 5 | ## 基本用法 6 | 7 | ```javascript 8 | import logsets from "logsets" 9 | logsets.list(["欢迎使用{}国际化解决方案",'VoerkaI18n'], [ 10 | { 11 | title: "全流程支持", 12 | description:"从文本提取/自动翻译/编译/动态切换的全流程工程化支持,适用于大型项目", 13 | }, 14 | { 15 | title: "集成自动翻译", 16 | type:"×", 17 | description:["调用{}支持对提取的文本进行自动翻译,大幅度提高工程效率","在线翻译API"], 18 | }, 19 | { 20 | title: "符合直觉", 21 | type:['○','yellow'], 22 | style:"red", 23 | description:"在源码中直接使用符合直觉的翻译形式,不需要绞尽脑汁想种种key", 24 | }, 25 | { 26 | title: ["支持{}","TypeScript"], 27 | description: "内置支持TypeScript类型以及生成TypeScript源码", 28 | }, 29 | { 30 | title: "自动提取文本", 31 | description: "提供扫描提取工具对源码文件中需要翻译的文本进行提取", 32 | }, 33 | { 34 | title: "适用性", 35 | description: 36 | "支持任意Javascript应用,包括Nodejs/Vue/React/ReactNative等。", 37 | }, 38 | { 39 | title: "多库联动", description: "支持多包工程下多库进行语言切换的联动" }, 40 | { 41 | title: "工具链", 42 | description: "提供Vue/React/Babel等扩展插件,简化各种应用开发", 43 | }, 44 | { 45 | title: "插值变量", 46 | description: 47 | "强大的插值变量机制,能扩展支持复数、日期、货币等灵活强大的多语言特性", 48 | }, 49 | { title: "语言补丁", description: "在应用上线后发现错误时可以在线修复" }, 50 | { title: "动态增加语种", description: "可以在应用上线后动态增加语种支持" }, 51 | { title: ["测试覆盖率{}","90%+"], description: "核心运行时超过90%的测试覆盖率" } 52 | ] 53 | ``` 54 | 55 | 输出效果如下: 56 | 57 | ![](./images/list2.png) 58 | 59 | 60 | ## 定义列表项 61 | 62 | ```javascript 63 | interface ListItem{ 64 | title:string | [string,string] 65 | description?:string 66 | type?:string | [string,string] 67 | style?:string | [string,string] 68 | 69 | } 70 | logsets.list(title,[ 71 | // 只提供标题 72 | "工具链:提供Vue/React/Babel等扩展插件,简化各种应用开发", 73 | // 完整列表项 74 | { 75 | title:"工具链", 76 | // 允许指定插值变量,对变量进行着色 77 | title:["支持的框架:{}/{}/{}","React","Vue","Babel"], 78 | // 描述信息,支持多行 79 | description:"描述信息\n描述信息\n描述信息" 80 | // 描述信息也支持对变量进行着色 81 | description:["描述{},{}",1,2] 82 | // 默认的列表图标为√,可以指定其他图标 83 | type:"×", 84 | // 也可以指定图标的颜色 85 | type:["×","red"] 86 | // 指定列表标题的颜色 87 | style:"red", 88 | // 指定列表标题和描述的颜色 89 | style:["red","yellow"] 90 | } 91 | ]) 92 | 93 | ``` 94 | 95 | ## 配置参数 96 | 97 | ```javascript 98 | 99 | logsets.list(title,items:ListItem[],options:ListPluginOptions) 100 | 101 | interface ListPluginOptions { 102 | grouped? : boolean // 是否分组显示 103 | groupSymbol? : string // 分组符号,默认为"♦" 104 | indent? : string // 整体缩进 105 | showOrderNumber?:boolean // 是否显示序号 106 | title? : { 107 | emoji? : string 108 | style? : string, // 标题样式 109 | }, 110 | item?:{ 111 | indent? :string // 缩进 112 | type? : [string,string] // [列表图标字符,颜色样式] 113 | style? : [string,string] // [标题颜色样式,描述颜色样式] 114 | } 115 | } 116 | ``` 117 | -------------------------------------------------------------------------------- /docs/guide/log.md: -------------------------------------------------------------------------------- 1 | # 日志输出 2 | 3 | 按指定级别输出日志,并会对插值变量进行着色。 4 | 5 | ```javascript 6 | logsets.debug("<模块字符串>",[位置插值变量列表] || {插值变量列表},"备注信息") 7 | logsets.info("<模块字符串>",[位置插值变量列表] || {插值变量列表},"备注信息") 8 | logsets.warn("<模块字符串>",[位置插值变量列表] || {插值变量列表},"备注信息") 9 | logsets.error("<模块字符串>",[位置插值变量列表] || {插值变量列表},"备注信息") 10 | logsets.fatal("<模块字符串>",[位置插值变量列表] || {插值变量列表},"备注信息") 11 | ``` 12 | 13 | 示例如下: 14 | 15 | ```javascript 16 | import logsets from "logsets" 17 | logsets.debug("正在执行程序{},还需要{}秒...",["logs",9]) 18 | logsets.info("正在执行程序{app},还需要{time}秒...",{app:"logs",time:9}) 19 | logsets.warn("正在执行程序{app},还需要{time}秒...",{app:"logs",time:9},"Line:123") 20 | logsets.warn("程序执行可能出错\n变量没有定义") 21 | logsets.error("程序执行可能出错\n变量没有定义") 22 | logsets.fatal("正在执行程序{a} + {b} , {sex} {name}...",{a:1,b:1,sex:true,name:"voerka"}) 23 | 24 | ``` 25 | 输出效果如下: 26 | 27 | ![image](./images/log11.jpg) 28 | 29 | 第二个参数也可以是一个返回`[]`或`{}`插值变量列表的函数. 30 | 31 | ```javascript 32 | logsets.warn("My name is {name}, age is {age}",()=> ({name:"Voerka",age:1})) 33 | ``` 34 | 35 | 输出样式可以通过`template`参数配置模块字符串。 36 | 37 | ```javascript 38 | logsets.config({ 39 | template:"[{level}] {datetime} - {message}" 40 | }) 41 | ``` 42 | 43 | `template`支持以下插值变量: 44 | 45 | - **level**:日志级别 46 | - **datetime**:当前日期时间 47 | - **date**:当前日期 48 | - **time**:当前时间 49 | - **message**:文本信息 50 | -------------------------------------------------------------------------------- /docs/guide/progressbar.md: -------------------------------------------------------------------------------- 1 | # 进度条 2 | 3 | 显示一个滚动的进度条。 4 | 5 | ## 基本用法 6 | 7 | ```javascript 8 | import logsets from "logsets" 9 | 10 | const pbar = logsets.progressbar({ 11 | title : "下载进度", 12 | //...其他配置参数... 13 | }) 14 | 15 | progressbar.begin() // 开始启动进度条 16 | for(let i = 0 ; i <= 60; i++){ 17 | await delay() 18 | progressbar.value(i) // 更新进度条 19 | } 20 | progressbar.end() // 结束进度条 21 | 22 | ``` 23 | 24 | `progressbar.demo.js`输出效果如下: 25 | 26 | ![](./images/progressbar.png) 27 | 28 | ## 配置参数 29 | 30 | `progressbar`支持以下配置参数: 31 | 32 | ```javascript 33 | { 34 | title:"<显示标题>" 35 | theme : "", // 可选主题色,内置支持default,red,green 36 | max : 100, // 进度最大值 37 | min : 0, // 进度最小值 38 | value : 0, // 当前值 39 | // 显示在最后的备注字符串,支持插值变量{value} {percent} {max} {min} 40 | dispaly : "{percent}%", 41 | width : 60, // 进度条宽度 42 | background: { // 进度条样式 43 | show : true, // 是否显示背景,默认显示,不显示时只显示进度条滑块 44 | style : "bgDarkGray", // 进度条样式 45 | char : " " 46 | }, 47 | slider : { // 滑块字符 48 | style : "bgWhite", // 进度条样式 49 | char : " ", // 50 | } 51 | } 52 | ``` 53 | 54 | - 所有参数均是可选的,大部份情况下只需要配置`max`、`min`参数即可。 55 | - `dispaly`参数用来控制当进度条正在执行时显示在右侧的信息,支持插值变量`{value}` `{percent}`、` {max} `、`{min}`,比如`{percent}%`显示百分比,`{value}/{max}`显示当前进度值与最大值。 56 | - `width`用来指定进度条的宽度,默认是`60`个字符。 57 | - `background`用来控制进度条的背景,默认是暗灰色空格。 58 | - `slider`用来控制进度值,默认是白色空格。 59 | 60 | ## API 61 | 62 | ### begin() 63 | 64 | 开始一个进度条,开始时会隐藏光标 65 | 66 | ### value(n) 67 | 68 | 更新进度 69 | 70 | ### end(note) 71 | 72 | 结束进度条,结束后换行 73 | 74 | ### stop(note) 75 | 76 | 停止进度条,`note`参数会显示在进度条右侧。 77 | 78 | ### error(note) 79 | 80 | 进度条出错,`note`参数会显示在进度条右侧。 -------------------------------------------------------------------------------- /docs/guide/quick-tasklist.md: -------------------------------------------------------------------------------- 1 | # 快速任务列表 2 | 3 | 提供了一个快速执行任务列表的方法,可以通过`logsets.run`来执行任务列表。 4 | 5 | `logsets.run`的参数与`createTasks`的参数一样,只是`logsets.run`会自动创建一个任务列表,然后执行。 6 | 7 | ```javascript 8 | const logsets = require("logsets") 9 | 10 | await logsets.run("正在下载文件",[{...},{...}]) 11 | await logsets.run(["正在下载{}文件",6],[{...},{...}]) // 标题支持插值变量着色 12 | 13 | 14 | // 等效于 15 | 16 | const tasks = logsets.createTasks([{...},{...}]) 17 | await tasks.run("正在下载文件") 18 | await tasks.run(["正在下载{}文件",6]) 19 | 20 | ``` 21 | 22 | 23 | -------------------------------------------------------------------------------- /docs/guide/run-tasklist.md: -------------------------------------------------------------------------------- 1 | # # 执行任务列表 2 | 3 | ## 基本用法 4 | 5 | 当需要执行多个任务时使用`tasklist`来进行任务指示,需要写很多的样板代码,如: 6 | 7 | ```javascript 8 | const logsets = require("logsets") 9 | 10 | const tasks = logsets.tasklist() 11 | 12 | try{ 13 | tasks.add("任务1") 14 | await task1() 15 | tasks.complete() 16 | }catch(e){ 17 | tasks.error(e) 18 | } 19 | //.... 20 | 21 | try{ 22 | tasks.add("任务n") 23 | await taskn() 24 | tasks.complete() 25 | }catch(e){ 26 | tasks.error(e) 27 | } 28 | 29 | ``` 30 | 31 | 我们提供了一个`createTasks`方法,可以简化上述代码,如下: 32 | 33 | ```javascript 34 | const tasks = logsets.createTasks([ 35 | { 36 | title:"任务处理被停止", 37 | execute:async ()=>{ 38 | await delay(100) 39 | return "abort" 40 | } 41 | }, 42 | { 43 | title:"开始扫描文件", 44 | execute:async ()=>{await delay(100);return 1} 45 | }, 46 | { title:"准备对文件进行预处理", 47 | execute:async ()=>{throw new Error("已安装")}, 48 | }, 49 | { title:"准备对文件进行预处理", 50 | execute:async ()=>{ 51 | await delay(100) 52 | return "已完成" 53 | } 54 | }, 55 | { title:"执行过程中显示进度", 56 | execute:async ({task})=>{ 57 | for(let i=0;i<100;i++){ 58 | await delay(100) 59 | task.note(i+"%") 60 | } 61 | } 62 | }, 63 | { 64 | title:"读取文件并编译成exe文件", 65 | execute:async ()=>{ 66 | await delay(100) 67 | return ['stop',"不干了"] 68 | } 69 | }, 70 | { 71 | title:"任务处理被停止", 72 | execute:async ()=>{ 73 | await delay(100) 74 | return ["abort",'真的不干了'] 75 | } 76 | }, 77 | "-", 78 | { 79 | title:"任务执行失败", 80 | execute:async ()=>{throw new Error("TimeOut")}, 81 | error:["ignore","忽略:{message}"] 82 | }, 83 | { 84 | title:"任务待办状态", 85 | execute:async ()=>{throw new Error("TimeOut")}, 86 | error:"出错了" 87 | }, 88 | "出错处理", 89 | { 90 | title:["下载文件:{},大小:{}, 已下载{}","package.json",122,344], 91 | execute:async ()=>{throw new Error("TimeOut")}, 92 | error:"出错了:{message}" 93 | }, 94 | { 95 | title:["下载文件:{},大小:{}, 已下载{}",["package.json",122,344]], 96 | execute:async ()=>{throw new Error("TimeOut")}, 97 | error:()=>"X" 98 | }, 99 | { 100 | title:["下载文件:{},大小:{}, 已下载{}",["package.json",122,344]], 101 | execute:async ()=>{throw new Error("TimeOut")}, 102 | error:()=>"skip" 103 | }, 104 | ],{ignoreErrors:true}) 105 | 106 | 107 | try{ 108 | let results = await tasks.run(["开始执行{}任务",5]) 109 | console.log(results) 110 | }catch(e){ 111 | console.error(e) 112 | } 113 | 114 | ``` 115 | 116 | 117 | 运行后的效果如下: 118 | 119 | ![](./images/createTasks.png) 120 | 121 | 122 | ## 声明任务列表 123 | 124 | `createTasks`方法接受两个参数,第一个参数是任务列表,第二个参数是配置参数。 125 | 126 | ```javascript 127 | createTasks( 128 | tasks:CreateTaskDefine[], 129 | options?:CreateTasksOptions 130 | ):TaskRunner 131 | ``` 132 | 133 | `CreateTaskDefine`是一个对象,用来声明任务,包含以下属性: 134 | 135 | 每一个任务均指定一个`execute`函数,根据该函数的返回值来决定任务状态提示信息。 136 | 137 | ```javascript 138 | { 139 | // 任务标题,可以是字符串或者数组,数组中的字符串可以包含插值变量 140 | title:"任务标题", 141 | // 任务标题可以是字符串数组,对插值变量进行自动着色 142 | title:["任务标题{},{},{}",1,2,3], 143 | execute:async (context)=>{ 144 | // context是run方法传入的上下文对象,可以在任务执行函数中使用,可以在此保存任务执行的中间结果 145 | // 返回值可以是字符串、函数 146 | // 字符串: 显示完成提示信息 147 | return "<内置任务状态>" //'ignore' | 'running' | 'complete' | 'error' | 'abort' | 'fail' | 'cancel' | 'skip' | 'stop' | 'todo' 148 | return ["<内置任务状态>","提示信息"] // 第二个参数是提示信息 149 | return "abort" // 代表该任务被跳过,等效于task.abort()并中断后续任务 150 | return "skip" // 代表该任务被跳过,等效于task.skip() 151 | return ["skip","跳过操作"]// 代表该任务被跳过,等效于task.skip("跳过操作") 152 | return "ignore" // 代表该任务ignore,等效于task.ignore() 153 | return "其他任意字符串" // 代表该任务完成,等效于task.complete("其他任意字符串" ) 154 | }, 155 | // 当任务执行失败时的状态,可以是字符串、函数 156 | error:"<内置的任务状态>",//'ignore' | 'running' | 'complete' | 'error' | 'abort' | 'fail' | 'cancel' | 'skip' | 'stop' | 'todo' 157 | error:["<内置的任务状态>","提示信息"], // 第二个参数是提示信息 158 | error:"skip" // 代表该任务被跳过,等效于task.skip() 159 | error:["skip","跳过"] // 代表该任务被跳过,等效于task.skip("跳过") 160 | error:"abort" // 代表该任务被终止,等效于task.abort() 161 | error:"ignore" // 代表该任务ignore,等效于task.ignore() 162 | error:"任务失败提示", // 代表该任务失败,等效于task.error("任务失败提示" ) 163 | error:"任务出错:{message}", // 代表该任务失败,等效于task.error(`任务失败提示${error.message}` ) 164 | error:"任务出错:{code}", // 代表该任务失败,等效于task.error(`任务失败提示${error.code}` ) 165 | error:"任务出错:{stack}", // 代表该任务失败,等效于task.error(`任务失败提示${error.stack}` ) 166 | error:({error})=>{ 167 | 168 | } 169 | } 170 | 171 | ``` 172 | 173 | 174 | - `execute`参数用来指定一个异步任务执行函数,注意必须是异步函数. 175 | - `execute`可以返回以下值: 176 | - `void | undefined`:代表任务执行成功,等效于`task.complete()` 177 | - `内置的任务状态`: 取值是`'ignore' | 'running' | 'complete' | 'error' | 'fail' | 'cancel' | 'skip' | 'stop' | 'todo'`,代表任务执行的状态,等效于`task[status]()` 178 | - `["内置的任务状态","提示信息"]`: 数组类型,第一个参数是`'ignore' | 'running' | 'complete' | 'error' | 'fail' | 'cancel' | 'skip' | 'stop' | 'todo'`,代表任务执行的状态,等效于`task[status](提示信息)` 179 | - `string`: 任意字符串,代表任务执行成功,等效于`task.complete(任意字符串)` 180 | 181 | - 默认情况下,当执行任务函数`execute`出错时,`error`用来配置当`execute`函数执行出错后的的行为: 182 | - `error='ignore' | 'running' | 'complete' | 'error' | 'fail' | 'cancel' | 'skip' | 'stop' | 'todo' `显示对应的状态文本。 183 | - `error='abort'`:终止后续任务的执行。 184 | - `error=['abort','提示信息']`:终止后续任务的执行。 185 | - `error="内置的任务状态"`: 等效于`task[status]()` 186 | - `error=["内置的任务状态","提示信息"]`: 等效于`task[status](提示信息)` 187 | - `error=(error)=>{}`:函数返回值可以是以上任意值。 188 | - 如果没有指定`error`参数,则默认是执行等效于`task.error(error.message)`,继续执行后续任务。如果指定了`abortOnError=true`则停止后续任务的执行。 189 | - 当指定`error`是一个字符串时,可以指定`{message}`,`{code}`,`{stack}`这三个插值变量,如`error="出错了:{message}"` 190 | 191 | 192 | - 在满足以下条件时停止后续任务的执行: 193 | - 在`execute`显式返回`abort`或`["abort",""]` 194 | - 当`execute`函数出错时,显式指定`error=abort`或`error=["abort",""]`或`error=(error)=>{返回'abort'或["abort",""]}` 195 | - 指定`abortOnError=true`时,则无论`error`指定任何值均会停止后续任务的执行 196 | - 如果`abortOnError=false`时,则也可以通过指定`execute`或`error`返回`abort`或`["abort",""]`来停止任务 197 | - 如果`ignoreErrors=true`,则以上规则失效,所有任务均会得到执行,该选项用于调试。 198 | 199 | - 当任务出错时,如果选择了继续执行后续任务,则`tasks.run`不会触发错误,而可以通过`tasks.run`的返回值来获取错误信息。比如执行了10个任务有6个出错,但是均选择了`skip`,则此时`tasks.run().errors==[Error,Error,....]` 200 | 201 | ## 任务上下文 202 | 203 | 执行任务列表时可以传入一个上下对象参数,该参数将会传递给每个任务的`execute`函数,同时会在上下文中注入一个`task`对象,可以在任务执行过程中调用`task.note`来修改任务的执行信息。 204 | 205 | ```typescript 206 | 207 | const tasks = logsets.createTasks([ 208 | { 209 | title:"正在下载文件", 210 | execute: async function (context) { 211 | const {a,task} = context // task是自动注入的任务对象 212 | await delay(); 213 | for(let i=0;i<10;i++){ 214 | await delay(100) 215 | task.note(`${i}%`) // 修改任务的执行信息 216 | } 217 | }, 218 | } 219 | ]) 220 | 221 | await tasks.run("开始执行",{a:1}) // 可选地传入一个上下文对象 222 | ``` 223 | 224 | 225 | 226 | ## 配置参数 227 | 228 | ```javascript 229 | createTasks(tasks:CreateTaskDefine[],options?:CreateTasksOptions):TaskRunner 230 | 231 | interface CreateTasksOptions{ 232 | abortOnError?:boolean 233 | ignoreErrors?:boolean 234 | } 235 | ``` 236 | 237 | - `ignoreErrors`忽略所有错误,所有任务均会得到执行,该选项用于调试。 238 | - `abortOnError`当任务出错时,是否停止后续任务的执行,默认为`true`。如果`=false`,也可以通过指定`execute`或`error`返回`abort`或`["abort",""]`来停止后续任务的执行。 239 | 240 | -------------------------------------------------------------------------------- /docs/guide/separator.md: -------------------------------------------------------------------------------- 1 | # 显示分割条 2 | 3 | `logsets.separator(width)`可以输出一条水平分割线, `width`参数是可选的,默认是`60`。 -------------------------------------------------------------------------------- /docs/guide/table.md: -------------------------------------------------------------------------------- 1 | # 表格 2 | 3 | `logsets`内置支持`table`插件用来输出表格 4 | 5 | ## 基本用法 6 | 7 | ```javascript 8 | import logsets from "logsets" 9 | 10 | 11 | const table = logsets.table({ 12 | colorize:1, // 是否需要颜色化 0-禁用着色,1-简单着色 2-对表单元里面的对象和数组进行着色,需要额外的计算 13 | grid:2, // 表格线样式,0=不显示表格线,1=只显示垂直表格线,2=显示完整表格线 14 | maxColWidth:32, // 最大列宽,超过会显示省略号 15 | colPadding:" ", // 列额外的空格 16 | header:{ 17 | style:"bright" // 表头颜色样式,默认高亮 18 | }, 19 | footer:{ 20 | style:"darkGray", // 表尾颜色样式 21 | merge:true // 是否合并行 22 | align:"right", // 当合并时对齐方式 23 | }, 24 | summary:{ // 默认汇总行配置 25 | style:"yellow,bright", // 汇总颜色样式 26 | align:"right", // 汇总对齐方式 27 | }, 28 | }) 29 | // 输出表头,只支持一个表头 30 | table.addHeader("序号","文件名","大小","下载进度","完成","<备注") 31 | // 输出行,一个参数对应一列 32 | table.addRow(1,"readme.md",58713,100,true,"自述文件") 33 | table.addRow(2,"index.js",1222,100,true,"源代码文件") 34 | table.addRow(3,"consts.js",45981,100,true,"常量定义\n包含默认的配置文件") 35 | table.addRow(4,"table.plugin.js",434,100,true,"表格插件\n可选,用来输出表格") 36 | table.addRow(5,"rollup.config.js",123,100,true,"构建配置文件") 37 | // 输出汇总行 38 | table.addSummary(["已下载",5,"个文件\n累计耗时",56,"秒"],{align:"right"}) 39 | table.addRow(6,"colorize.js",6542,60,false,"实现对变量或对象进行着色") 40 | table.addRow(7,"stringify.js",5546,34,false,"格式化JSON") 41 | table.addRow(8,"utils.js",6456,66,false,"一个工具函数") 42 | // 输出表尾 43 | table.addFooter(["共",8,"个文件"]) 44 | // 渲染输出 45 | table.render() 46 | ``` 47 | 48 | 输出效果如下: 49 | 50 | ![image](./images/log6.jpg) 51 | 52 | ## 控制表格线样式 53 | 54 | 当`grid=1`时,输出效果如下: 55 | 56 | ![image](./images/log7.jpg) 57 | 58 | 59 | 当`grid=2`时,输出效果如下: 60 | 61 | ![image](./images/log8.jpg) 62 | 63 | ## 单元格着色 64 | 65 | `table.addRow`进行增加行时,会根据`logsets` 全局配置按不同的数据类型显示不同的颜色。 66 | 67 | ## 单元格里显示着色对象 68 | 69 | 默认情况下,在单元格里面显示`{...}`或`[...]`时会将之转化为字符串进行显示,而不是像`format`方法一样进行格式化关色后输出。需要额外配置`colorize=2`才会进行着色输出。 70 | 71 | ```javascript 72 | table = logsets.table({ 73 | colorize:2, 74 | }) 75 | ``` 76 | 77 | `colorize`参数用来控制是否对单元格内容进行着色。 78 | 79 | - **0 :** 禁用着色输出 80 | - **1 :** 对简单数据类型进行着色,默认值 81 | - **2** :对表单元里面的对象和数组进行着色,需要额外的计算 82 | 83 | ## API 84 | 85 | table实例具有以下方法: 86 | 87 | ### addHeader 88 | 89 | 增加表头,一个表格只能指定一个表头,并且表头不支持多行标题。 90 | 91 | ```javascript 92 | addHeader("列标题","列标题",......,"列标题") 93 | ``` 94 | 95 | 列标题**默认居中显示**,指定列标题时可以通过第一个字符来指定该列的显示对齐方式。如: 96 | 97 | ```javascript 98 | addHeader("序号","名称",">地址") // 地址列右对齐 99 | addHeader("序号","名称","<地址") // 地址列左对齐 100 | ``` 101 | 102 | ### addRow 103 | 104 | 表格支持添加任意多的普通表行。 105 | 106 | ```javascript 107 | addRow(<单元格内容>,<单元格内容>,...,<单元格内容>) 108 | ``` 109 | 110 | 一般情况下,单元格的数量应该与`addHeader`中列数量一致。如果`addRow`的参数个数大于`addHeader`的参数个数,会自动扩展列,取最大的列数量进行显示。 111 | 112 | ```javascript 113 | table = logsets.table({ 114 | grid:2, 115 | maxColWidth:12, 116 | }) 117 | // 表头定义了4列 118 | table.addHeader("名称","性别","出生日期","<居住地址") 119 | // 该行提供了5个单元格参数 120 | table.addRow("令狐冲","男","1653/12/2","思过崖","华山派") 121 | table.addRow("东方不败","男","1603/6/3","日月神教无敌峰藏经阁") 122 | table.addRow("任盈盈","女","1651/2/8","") 123 | table.render() 124 | ``` 125 | 126 | - 渲染单元格时会按照配置中指定的样式,分别对不同的数据类型显示不同的颜色。 127 | 128 | - 表格行每一列会根据内容自适应宽度显示,但是其最大值受配置参数中的`maxColWidth`约束,当单元格内容字符宽度超过`maxColWidth`时会显示省略号。效果如下: 129 | 130 | ![](./images/log9.jpg) 131 | 132 | ### addSummary 133 | 134 | 增加汇总行,汇总行用来合并所有单元格并显示内容。 135 | 136 | ```javascript 137 | addSummary( 138 | content, // 单元格内容 139 | { 140 | style:"yellow,bright", // 汇总颜色样式 141 | align:"right", // 汇总对齐方式,取值:left,auto,center,right 142 | }) 143 | ``` 144 | 145 | - `content`参数可以是一个字符串,其显示颜色样式由style指定,默认值是`yellow,bright` 146 | 147 | - `content`参数也可以是一个Array,其显示颜色样式会根据数组成员的数据类型进行着色。 148 | 149 | 效果图参阅上文。 150 | 151 | ### addFooter 152 | 153 | 增加表尾,一个表格只能显示一个表尾。 154 | 155 | 156 | ```javascript 157 | addFooter(content, 158 | { 159 | style:"darkGray", // 表尾颜色样式 ,当merge=false时生效 160 | merge:, // 是否合并行显示,默认true 161 | align:"left", // 对齐方式,取值:left,auto,center,right 162 | style:"" 163 | }) 164 | ``` 165 | 166 | - `content`参数用法与`addSummary` 一样。 167 | 168 | - 表尾支持可以通过`{merge:}`来配置是否合并显示或者分列显示。如`addFooter([1,2,3,4],{merge:false})` 169 | 170 | ![](./images/log10.jpg) 171 | 172 | - 默认情况下`merge=true`,即`addFooter("内容")===addFooter("内容",{merge:true})`。 173 | 174 | ### addSeparator 175 | 176 | 当`grid=0`或`grid=1`即不显示网格线时用来增加一条分割线。 177 | 178 | ```javascript 179 | addSeparator() // 无参数 180 | ``` 181 | -------------------------------------------------------------------------------- /docs/guide/task.md: -------------------------------------------------------------------------------- 1 | # 任务 2 | 3 | ## 创建单个任务 4 | 5 | 显示正在执行的单个任务,输出效果与`tasklist`一样,差别在于`task`只显示一项任务,并且没有缩进。 6 | 7 | ```javascript 8 | let task = logsets.task("下载文件{#yellow voerki18n.zip},大小{#red size}",{size:12354}) 9 | task.complete() 10 | task = logsets.task("下载文件{#yellow voerki18n.zip},大小{#red size}",{size:12354}) 11 | task.error() 12 | task = logsets.task("下载文件{#yellow voerki18n.zip},大小{#red size}",{size:12354}) 13 | task.fail() 14 | task = logsets.task("下载文件{#yellow voerki18n.zip},大小{#red size}",{size:12354}) 15 | task.todo() 16 | task = logsets.task("下载文件{#yellow voerki18n.zip},大小{#red size}",{size:12354}) 17 | task.skip() 18 | task = logsets.task("下载文件{#yellow voerki18n.zip},大小{#red size}",{size:12354}) 19 | task.ignore("<可选备注>") 20 | task = logsets.task("下载文件{#yellow voerki18n.zip},大小{#red size}",{size:12354}) 21 | task.cancel("取消") 22 | // 也可以采用插值变量,对变量进行着色输出 23 | let task = logsets.task("下载文件:{},大小:{}, 已下载{}","package.json",122,344) 24 | 25 | let task 26 | try{ 27 | task = logsets.task("下载文件{#yellow voerki18n.zip},大小{#red size}",{size:12354}) 28 | task.complete() 29 | }catch(e){ 30 | task.error(e.message) 31 | } 32 | 33 | 34 | ``` 35 | 输出效果如下: 36 | 37 | ![](./images/task.png) 38 | 39 | 40 | ## 直接执行任务 41 | 42 | 更方便的使用方式如下: 43 | 44 | 45 | ```js 46 | await logsets.task("正常执行任务",async ()=>{ 47 | await delay(1000) 48 | }) 49 | await logsets.task("正常任务出错",async ()=>{ 50 | await delay(1000) 51 | throw new Error("任务出错") 52 | }) 53 | await logsets.task("正常任务跳过",async ()=>{ 54 | await delay(1000) 55 | return "skip" 56 | }) 57 | await logsets.task("正常任务取消",async ()=>{ 58 | await delay(1000) 59 | return "cancel" 60 | }) 61 | await logsets.task("正常任务停止",async ()=>{ 62 | await delay(1000) 63 | return ["stop","任务停止"] 64 | }) 65 | await logsets.task(["正常执行任务:{}","voerkai18n"],async ()=>{ 66 | await delay(1000) 67 | }) 68 | // 传入一个task 69 | await logsets.task(["正常执行任务:{}","voerkai18n"],async (task)=>{ 70 | for(let i=0;i<10;i++){ 71 | task.note(i+1) 72 | await delay(100) 73 | } 74 | }) 75 | ``` 76 | 77 | - 通过`task.note(text)`可以更新任务备注信息 -------------------------------------------------------------------------------- /docs/guide/tasklist.md: -------------------------------------------------------------------------------- 1 | # 任务列表 2 | 3 | 显示正在进行的任务列表,能显示任务的状态。 4 | 5 | ![](./images/tasklist.demo.gif) 6 | 7 | ## 基本用法 8 | 9 | ```javascript 10 | import logsets from "logsets" 11 | 12 | // 创建一个任务列表 13 | let tasks = logsets.tasklist({ 14 | title:"指定任务列表标题" // 高亮显示 15 | // 标题支持插值变量着色 16 | title:"所有任务:{#yellow 8}个", 17 | title:["所有任务:{}个",8] 18 | title:["所有任务:{count}个",{count:100}] 19 | }) 20 | // let tasks = logsets.tasklist("指定列表标题") 只指定标题 21 | 22 | // 新增一个任务列表项 23 | tasks.add("开始扫描文件") 24 | // 支持插值变量着色 25 | //tasks.add("开始扫描文件") 26 | //tasks.add("开始扫描文件,数量{#yellow 100}") 27 | // 增加后,任务列表项会处于运行状态,需要分别调用complete/error/stop/skip/todo等结束运行状态 28 | tasks.complete("OK") 29 | 30 | tasks.add("准备对文件进行预处理") 31 | tasks.error("ERROR:文件没有找到") 32 | 33 | tasks.add("读取文件并编译成exe文件") 34 | tasks.skip("SKIP") 35 | 36 | tasks.add("任务处理被停止") 37 | tasks.stop("STOP") 38 | 39 | tasks.add("任务待办状态") 40 | tasks.todo("TODO") 41 | 42 | // 任务描述还支持对插值变量按数据类型进行着色显示 43 | tasks.add("下载文件:{},大小:{}, 已下载{}","package.json",122,344) 44 | 45 | // 任务描述着色显示 46 | tasks.add("下载文件:{#red },大小:{}, 已下载{}",["package.json",122,344]) 47 | 48 | 49 | // 可以在任务列表之间插入一个分割线 50 | tasks.separator() 51 | 52 | // 增加缩进,每次调用增加一个缩进,可多次调用 53 | tasks.indent() 54 | // 减少缩进 55 | tasks.outdent() 56 | // 任务分组标题 57 | tasks.addGroup("分组标题") 58 | 59 | // 一般用法 60 | 61 | try{ 62 | tasks.add("正在下载文件{}","file.zip") 63 | await http.download(url) 64 | tasks.complete() // 任务完成后调用 65 | }catch(e){ 66 | tasks.error(e) // 出错时调用 67 | } 68 | 69 | // 如果有多个任务则需要多个重复上述代码,因此提供了一个快捷方法 70 | 71 | tasks.run("正在下载文件{}","file.zip",async () => { 72 | await http.download(url) 73 | // 未返回任意值 等效于 task.complete() 74 | // return "字符串" 等效于 task.complete("字符串") 75 | // return 'ignore' | 'running' | 'complete' | 'error' | 'abort' | 'fail' | 'cancel' | 'skip' | 'stop' | 'todo' | 'ignore' 76 | // 返回以上字符串 等效于 task.fail() task.running() , 77 | // 返回[以上字符串,"xxx"] 等效于 task.fail("xxx") task.running("xxx") , 78 | },{ 79 | catchError:true // 是否捕获错误,=false则执行出错时会抛出错误,导致后续任务不能执行 80 | showErrorStack:false // 出错时是否显示错误信息 81 | }) 82 | 83 | const result = await tasks.run("正在下载文件{}","a.zip", ()=>{ 84 | delay(1000) 85 | return "skip" 86 | }) 87 | 88 | // result === "skip" 89 | 90 | await tasks.run("正在下载文件{}","a.zip", ()=>{ 91 | delay(1000) 92 | return ["abort","用户中止"] 93 | }) 94 | 95 | // result === ["abort","用户中止"] 96 | 97 | 98 | 99 | ``` 100 | 101 | 运行后的效果如下: 102 | 103 | ![](./images/tasklist.demo.gif) 104 | 105 | ## 配置参数 106 | 107 | 以下所有配置参数均为可选,仅当您对默认样式不满意时进行定制。 108 | 109 | ```javascript 110 | { 111 | indent : " ", // 列表项缩进字符 112 | style : "bright", // 标题样式,可以用red,bgYellow等组合,参阅输出样式,默认是高亮 113 | width : 60, // 列表项总宽度 114 | refInterval:200, // 列表项渲染间隔,以ms为单位 115 | progressbar:{ 116 | style:"darkGray", // 进度条样式,默认是深灰色 117 | char:".", // 进度条字符,当任务处于运行状态时会动态显示 118 | }, 119 | // 当新任务项后会自动running,后续可以调用方法结束任务 120 | status:{ 121 | running:{ 122 | style:"white", 123 | symbol:"-", 124 | note:"" 125 | }, 126 | complete:{ 127 | style:"green", 128 | symbol:"√", 129 | note:"OK" 130 | }, 131 | error:{ 132 | style:"red", 133 | symbol:"×", 134 | note:"ERROR" 135 | }, 136 | fail:{ 137 | style:"red", 138 | symbol:"×", 139 | note:"FAIL" 140 | }, 141 | skip:{ 142 | style:"yellow", 143 | symbol:"○", 144 | note:"SKIP" 145 | }, 146 | stop:{ 147 | style:"red", 148 | symbol:"●", 149 | note:"STOP" 150 | }, 151 | todo:{ 152 | style:"lightCyan", 153 | symbol:"□", 154 | note:"TODO" 155 | }, 156 | ignore:{ 157 | style:"blue", 158 | symbol:"~", 159 | note:"IGNORE" 160 | }, 161 | cancel:{ 162 | style:"red", 163 | symbol:"×", 164 | note:"CANCEL" 165 | } 166 | } 167 | } 168 | ``` 169 | 170 | 除以上`running`、`complete`、`error`、`fail`、`skip`、`stop`、`todo`任务状态外,还支持自定义状态。 171 | 172 | ```javascript 173 | let tasks = logsets.tasklist({ 174 | title:"任务标题", 175 | status:{ 176 | connected:{ 177 | symbol:"*", 178 | style:"green" 179 | } 180 | } 181 | }) 182 | tasks.add("正在连接") 183 | tasks.connected() 184 | ``` 185 | 186 | ## API 187 | 188 | - **add(title)** 189 | 190 | 新增加一个任务,增加后会自动进入运行状态,如果上一个任务还在进行中会自动完成。 191 | 如果`title`是数组,则内部会调用`log`方法输出。因此,任务标题也支持对插值变量进行着色后输出。 192 | 193 | - **<状态名称>(note)** 194 | 195 | 使当前正在进行的任务结束并进入指定的状态,传入的可选的`note`参数显示在最右侧。 196 | 197 | - **indent** 198 | 199 | 增加缩进,每次调用增加一个缩进,可多次调用 200 | 201 | - **outdent** 202 | 203 | 减少缩进 204 | 205 | 206 | -------------------------------------------------------------------------------- /docs/guide/template.md: -------------------------------------------------------------------------------- 1 | # 模板字符串输出 2 | 3 | ## 基本用法 4 | 5 | 对模板字符串进行插值后输出着色后的字符串。 6 | 7 | ```javascript 8 | import logsets from "logsets" 9 | logsets.log("<模板字符串>",<变量1>,<变量1>,...,{end:"\n",append:" "}) 10 | logsets.log("<模板字符串>",<变量1>,<变量1>,...) 11 | logsets.log("<模板字符串>",{<变量1>:<值>,<变量1>:<值>},) 12 | logsets.log("<模板字符串>",{<变量1>:<值>,<变量1>:<值>},{end:"\n",append:" "}) 13 | ``` 14 | 15 | ### 示例 16 | 17 | **示例如下:** 18 | 19 | ```javascript 20 | import logsets from "logsets" 21 | // 命名插值变量 22 | logsets.log("{a}+{b}={c}",{a:1,b:1,c:2}) 23 | // 位置插值变量 24 | logsets.log("My name is {}","tom") 25 | logsets.log("{a}+{b}={c}",1,1,2) 26 | ``` 27 | 28 | ### 输出效果 29 | 30 | **输出效果如下:** 31 | 32 | ![image](./images/log.jpg) 33 | 34 | 35 | 默认情况下,每次执行`log`方法完成后均会导致换行输出。`log`方法还支持配置输出参数: 36 | ```javascript 37 | for(let i =0 ; i<=100; i++){ 38 | logsets.log("正在下载:{}",i,{end:"\r"}) // 每行输出时最后打印\r回车符,回到行头,从而可以实现下载进度的更新。 39 | } 40 | logsets.log() // 换行 41 | ``` 42 | ## 自定义插值变量颜色 43 | 44 | 默认情况下`logsets.log`会根据插值变量的数据类型分别着色后显示,但也支持自定义颜色显示. 45 | 46 | !> 在插值变量占位符中使用`{#<颜色样式> <变量名称>}`的形式来指定颜色 47 | 48 | 49 | ```javascript 50 | import logsets from "logsets" 51 | // 命名插值变量 52 | logsets.log("{#red a}+{b}={c}",{a:1,b:1,c:2}) 53 | // 位置插值变量 54 | logsets.log("My name is {}","tom") 55 | logsets.log("{a}+{b}={#bgGreen c}",1,1,2) 56 | logsets.log("{a}+{b}={#bgGreen,dim c}",1,1,2) // 多个颜色组合用,分开 57 | ``` 58 | 59 | 输出如下: 60 | 61 | ![image](./images/logvar.png) 62 | 63 | 64 | ## 对字符串进行局部着色 65 | 66 | `logsets.log`在没有提供配套的插值变量时会原样输出变量,复用此特性就可以实现对字符串进行局部着色. 67 | 68 | 69 | ```javascript 70 | import logsets from "logsets" 71 | // 命名插值变量 72 | logsets.log("My name is {#red tom}") 73 | logsets.log("{#blue Voerkai18n}是一个非常不错的{#red,dim 多语言}解决方案!") 74 | ``` 75 | 76 | 输出如下: 77 | 78 | ![image](./images/logvar2.png) 79 | 80 | 81 | ## 配置参数 82 | 83 | 当`log`的参数`大于=2`个并且最后一个参数是`{}`时,将最后一个参数视为是输出配置参数。 84 | 85 | ```javascript 86 | { 87 | // 行结束字符,默认是换行会导致打印下一行,如\r则不会换行而只是回到行首 88 | end:"\n", 89 | // 每个输出参数自动追加的字符,默认是一个空格 90 | append:" " 91 | } 92 | ``` -------------------------------------------------------------------------------- /docs/guide/tree.md: -------------------------------------------------------------------------------- 1 | # 树 2 | 3 | 输出树结构,比如文件夹等。 4 | 5 | ## 基本用法 6 | 7 | ```javascript 8 | import logsets from "./index.js" 9 | 10 | let tree = logsets.tree({ 11 | root:"文件结构" 12 | }) 13 | tree.addNode("readme.md") 14 | tree.addNode("package.json") 15 | tree.addNode("个人简历.doc",{note:logsets.colors.green("√")}) 16 | tree.addNode("网络组网方案.docx") 17 | tree.addNode("工资清单.xlsx") 18 | tree.addNode("",{style:"yellow"}) 19 | tree.beginChildren() 20 | tree.addNode("readme.md") 21 | tree.addNode("package.json") 22 | tree.addNode("个人简历.doc") 23 | tree.beginChildren() 24 | tree.addNode("readme.md") 25 | tree.addNode("package.json") 26 | tree.addNode("个人简历.doc") 27 | tree.addNode("网络组网方案.docx") 28 | tree.addNode("工资清单.xlsx",{last:true}) 29 | tree.endChildren() 30 | tree.addNode("网络组网方案.docx") 31 | tree.addNode("工资清单.xlsx",{last:true}) 32 | tree.endChildren() 33 | tree.addNode("网络组网方案.docx") 34 | tree.addNode("工资清单.xlsx",{last:true}) 35 | ``` 36 | 37 | 输出效果如下: 38 | 39 | ![](./images/tree1.png) 40 | 41 | 树还可以配置为每一个节点输出备注信息。 42 | 43 | ```javascript 44 | let tree = logsets.tree({ 45 | root:"文件结构", 46 | note:{ 47 | enable:true 48 | } 49 | }) 50 | tree.addNode("readme.md",{note:logsets.colors.green("√")}) 51 | tree.addNode("package.json",{note:logsets.colors.green("√")}) 52 | tree.addNode("个人简历.doc",{note:logsets.colors.green("√")}) 53 | tree.addNode("网络组网方案.docx",{note:logsets.colors.green("√")}) 54 | tree.addNode("工资清单.xlsx",{note:logsets.colors.green("√")}) 55 | tree.addNode("",{style:"yellow",note:logsets.colors.red("×")}) 56 | tree.beginChildren() 57 | tree.addNode("readme.md") 58 | tree.addNode("package.json") 59 | tree.addNode("个人简历.doc") 60 | tree.beginChildren() 61 | tree.addNode("readme.md") 62 | tree.addNode("package.json",{note:logsets.colors.red("×")}) 63 | tree.addNode("个人简历.doc",{note:"已审核"}) 64 | tree.addNode("网络组网方案.docx") 65 | tree.addNode("工资清单.xlsx",{last:true}) 66 | tree.endChildren() 67 | tree.addNode("网络组网方案.docx") 68 | tree.addNode("工资清单.xlsx",{last:true}) 69 | tree.endChildren() 70 | tree.addNode("网络组网方案.docx") 71 | tree.addNode("工资清单.xlsx",{last:true}) 72 | ``` 73 | 74 | 输出效果如下: 75 | 76 | ![](./images/tree2.png) 77 | 78 | ## 配置参数 79 | 80 | ```javascript 81 | { 82 | root: "Root", // 根节点显示内容 83 | width: 60, // 当显示备注信息时,树的总宽度 84 | indent:" ", // 整体缩进字符 85 | node:{ 86 | style:"", // 默认节点样式 87 | }, 88 | note:{ // 节点备注 89 | enable:false, // 是否显示备注信息 90 | style:"darkGrey", // 备注信息的默认样式 91 | char:".", // 备注指示线 92 | } 93 | } 94 | ``` 95 | 96 | ## API 97 | 98 | - **addNode(text,{style,note,last})** 99 | 100 | 增加一个树节点 101 | 102 | `style`用来配置节点的显示颜色样式,如`style="red"`时,节节点文本显示为红色。 103 | 104 | `note`用来提供备注信息; 105 | 106 | `last`用来提示这是当前最后一个节点,当添加节点时,树线默认显示`├── `,当`last=true`时,则显示`└── `,这样就可以确保最后一个子节点显示正确。 107 | 108 | - `beginChildren()`、`endChildren()` 109 | 110 | 开始和结束一个子节点集。 -------------------------------------------------------------------------------- /docs/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: home 3 | 4 | hero: 5 | name: "Logsets" 6 | text: "终端输出增强组件" 7 | tagline: "nodejs命令行程序提供丰富的增强表现组件" 8 | actions: 9 | - theme: brand 10 | text: 指南 11 | link: /guide 12 | - theme: alt 13 | text: GitHub 14 | link: https://github.com/zhangfisher/logsets 15 | 16 | features: 17 | - title: 简单实用 18 | details: 所有API都经过精心设计,简单易用 19 | - title: 丰富的组件 20 | details: 提供树、表格、进度条、横幅等丰富的组件 21 | - title: 类型安全 22 | details: 完全基于TypeScript构建 23 | --- 24 | 25 | -------------------------------------------------------------------------------- /docs/intro/about.md: -------------------------------------------------------------------------------- 1 | # 关于 2 | 3 | 为开发`nodejs`命令行程序提供丰富的表现输出样式,支持以下特性: 4 | 5 | - 支持按不同数据类型以不同的颜色显示,并且可以配置显示样式 6 | - 支持按`DEBUG`、`INFO`、`WARN `、`ERROR `、`FATAL`五个级别输出日志 7 | - 支持输出带颜色的模板字符串 8 | - 支持自动格式化显示`{}`和`[]`类型 9 | - 支持强大的表格输出 10 | - 支持输出任务列表、进度条、横幅和树等扩展 11 | - 正确处理中文与英文混排时的对齐问题 12 | - 支持扩展插件机制 13 | - `TypeScript`类型支持 -------------------------------------------------------------------------------- /docs/intro/install.md: -------------------------------------------------------------------------------- 1 | # 安装 2 | 3 | ::: code-group 4 | 5 | ```shell [npm] 6 | npm install logsets 7 | ``` 8 | 9 | ```shell [yarn] 10 | yarn add logsets 11 | ``` 12 | 13 | ```shell [pnpm] 14 | pnpm add logsets 15 | ``` 16 | ::: -------------------------------------------------------------------------------- /examples/banner.demo.js: -------------------------------------------------------------------------------- 1 | const logsets = require("../src") 2 | 3 | 4 | let banner = logsets.banner({ 5 | }) 6 | 7 | banner.add("Logsets Utility Toolkit") 8 | // banner.add("Output color elements at the terminal") 9 | // banner.add("Version:{} ",1) 10 | banner.add("Help:{} ","https://zhangfisher.github.io/flexci/") 11 | // banner.add("Release:{#red} ","2022-01-01") 12 | banner.render() 13 | 14 | 15 | banner = logsets.banner({ 16 | width:60 17 | }) 18 | 19 | banner.add("Logsets工具库") 20 | .add("在终端命令行输出彩色文本") 21 | .add() 22 | .add("版本: {}",2) 23 | .add("网站: {}","http://www.logsets.com",{align: 'left'}) 24 | .add("发布日期:{}","2022-01-01",{align: 'right'}) 25 | .add("作者:{author}",{author:"fisher"},{align: 'right'}) 26 | .add("许可证: {#lightYellow} {} {}",["MIT","GPL","Apache 2.0"]) 27 | .add("Run <{#yellow}> to upgrade","npm upgrade -g @voerkai18n/cli") 28 | 29 | .render() 30 | 31 | 32 | 33 | const banner1 = logsets.banner() 34 | banner1.add("VoerkaI18n") 35 | banner1.add("VoerkaI18n command line interactive tools",{style:"darkGray"}) 36 | banner1.add() 37 | banner1.add("installed: {} latest: {}",[1,2]) 38 | banner1.add("Run <{#yellow}> to upgrade","npm upgrade -g @voerkai18n/cli") 39 | banner1.render() 40 | 41 | -------------------------------------------------------------------------------- /examples/colors.demo.js: -------------------------------------------------------------------------------- 1 | const logsets = require("../src/") 2 | 3 | 4 | 5 | const text = "Hello World" 6 | console.log(text) 7 | console.log(logsets.colors.red(text)) 8 | console.log(logsets.colors.green(text)) 9 | console.log(logsets.colors.yellow(text)) 10 | console.log(logsets.colors.blue(text)) 11 | console.log(logsets.colors.magenta(text)) 12 | console.log(logsets.colors.cyan(text)) 13 | console.log(logsets.colors.white(text)) 14 | console.log(logsets.colors.darkGray(text)) 15 | console.log(logsets.colors.black(text)) 16 | console.log(logsets.colors.dim(text)) 17 | console.log(logsets.colors.bright(text)) 18 | 19 | console.log(logsets.colors.bgYellow(text)) 20 | -------------------------------------------------------------------------------- /examples/createtasks.demo.js: -------------------------------------------------------------------------------- 1 | const logsets = require("../src/") 2 | 3 | async function delay(n=10){ 4 | return new Promise(resolve=>setTimeout(resolve,n)) 5 | } 6 | (async ()=>{ 7 | const tasks = logsets.createTasks([ 8 | { 9 | title:"任务处理被停止", 10 | execute:async ()=>{ 11 | await delay(100) 12 | throw new Error("没法干了") 13 | return "abort" 14 | }, 15 | error:"abort" 16 | }, 17 | { 18 | title:"开始扫描文件", 19 | execute:async ()=>{await delay(100);return 1} 20 | }, 21 | { title:"准备对文件进行预处理", 22 | execute:async ()=>{throw new Error("已安装")}, 23 | }, 24 | { title:"准备对文件进行预处理", 25 | execute:async ()=>{ 26 | await delay(100) 27 | return "已完成" 28 | } 29 | }, 30 | { 31 | title:"读取文件并编译成exe文件", 32 | execute:async ()=>{ 33 | await delay(100) 34 | return ['stop',"不干了"] 35 | } 36 | }, 37 | { 38 | title:"任务处理被停止", 39 | execute:async ()=>{ 40 | await delay(100) 41 | return ["abort",'真的不干了'] 42 | } 43 | }, 44 | "-", 45 | { 46 | title:"任务执行失败", 47 | execute:async ()=>{throw new Error("TimeOut")}, 48 | error:["ignore","忽略:{message}"] 49 | }, 50 | { 51 | title:"任务待办状态", 52 | execute:async ()=>{throw new Error("TimeOut")}, 53 | error:"出错了" 54 | }, 55 | "出错处理", 56 | { 57 | title:["下载文件:{},大小:{}, 已下载{}","package.json",122,344], 58 | execute:async ()=>{throw new Error("TimeOut")}, 59 | error:"出错了:{message}" 60 | }, 61 | { 62 | title:["下载文件:{},大小:{}, 已下载{}",["package.json",122,344]], 63 | execute:async ()=>{throw new Error("TimeOut")}, 64 | error:()=>"X" 65 | }, 66 | { 67 | title:["下载文件:{},大小:{}, 已下载{}",["package.json",122,344]], 68 | execute:async ()=>{throw new Error("TimeOut")}, 69 | error:()=>"skip" 70 | }, 71 | ],{ignoreErrors:false}) 72 | 73 | 74 | try{ 75 | let results = await tasks.run(["开始执行{}任务",5]) 76 | console.log(results) 77 | }catch(e){ 78 | console.error(e) 79 | } 80 | 81 | })() -------------------------------------------------------------------------------- /examples/demo.js: -------------------------------------------------------------------------------- 1 | const logsets = require("../src/") 2 | 3 | logsets.print(/^colored$/g) 4 | 5 | logsets.colors.red("hello") 6 | 7 | // logsets.print("I am very cool!",true,3,[1,2,3,4],"Voerka",{a:1,b:2},null,undefined,[{a:1}]) 8 | 9 | // logsets.log("I am {}, age is {}","voerka",1,true,null) 10 | // logsets.log("{a} + {b} == 3 {a} ",{a:1111,b:2}) 11 | const t= new Date() 12 | function getUserInfo(){ 13 | console.logsets.log("getUserInfo") 14 | } 15 | class MyError extends Error { 16 | constructor(message) { 17 | super(message) 18 | } 19 | } 20 | async function delay(n=50){ 21 | return new Promise(resolve=>{ 22 | setTimeout(resolve,n) 23 | }) 24 | } 25 | 26 | 27 | logsets.log("------------") 28 | for(let i = 0 ; i < 100; i=i+10){ 29 | //await delay(10) 30 | logsets.print("已下载",i,"%",{end:"\r"}) 31 | } 32 | logsets.print("") 33 | logsets.log("------------") 34 | 35 | // logsets.print({posts:["a","b"],values:[1,2,3],z:()=>{}}) 36 | logsets.print({z:()=>{}}) 37 | 38 | logsets.log("{a}+{b}={c}",{a:1,b:1,c:2}) 39 | logsets.log("My name is {}","tom") 40 | logsets.log("{a}+{b}={c}",{a:1,b:1,c:2}) 41 | logsets.log("位置参数插值:{a}+{b}={c}",1,2,3) 42 | logsets.log("指定颜色{#red a}+{#bgGreen,bgLightYellow b}={c} {x}",{a:1000,b:12222,c:2,x:"HELLO"}) 43 | logsets.log("Welcome to {#red VoerkaI18n}") 44 | 45 | 46 | logsets.print("String",true,100,()=>{},[1,2,3]) 47 | logsets.print(null,undefined) 48 | logsets.print(/^colored$/g) 49 | logsets.print(new Error("Value Error")) 50 | logsets.print(new Date()) 51 | logsets.print(class A{}) 52 | logsets.print(new (class User{})()) 53 | logsets.print({name:"tom",age:100,admin:true,posts:["a","b"],values:[1,2,3]},()=>"hello") 54 | 55 | logsets.format("{a}+{b}={c}",{a:1,b:1,c:2}) 56 | 57 | logsets.log("My name is {}", "Voerka") 58 | logsets.log("My name is {}, age is {}", "Voerka", 1) 59 | logsets.log("{a}+{b}={c}", {a:1,b:2,c:3}) 60 | logsets.print({numbers:[1,2,3,4,5,6,7,1,1,1,1,1,1,1,1,1,1,1,1,1,5,6,7,1,1,1,1,1,1,1,1,1,1,1,1], fooc: true, barops: 42,seescvx:"mass",x:null,y:undefined,z:()=>{},reg:/^12323$/,obj:{ 61 | x:1,y:2,z:[1,2,3,4,5,6,7,1,1,1,1,1,1,1,1,1,1,1,1,1,5,6,7,1,1,1,1,1,1,1,1,1,1,1,1] 62 | }}) 63 | 64 | 65 | logsets.format({ 66 | values:new Array(10).fill(0).map((v,i)=>i+1), 67 | users:{ 68 | tom:{name:"tom",age:21,sex:true}, 69 | jack:{name:"jack",age:21,sex:false}, 70 | jack1:{name:"jack",age:21,sex:false}, 71 | jack2:{name:"jack",age:21,sex:false}, 72 | jack3:{name:"jack",age:21,sex:false}, 73 | jack4:{name:"jack",age:21,sex:false}, 74 | jack5:{name:"jack",age:21,sex:false}, 75 | jack6:{name:"jack",age:21,sex:false}, 76 | jack7:{name:"jack",age:21,sex:false}, 77 | jack8:{name:"jack",age:21,sex:false}, 78 | jack9:{name:"jack",age:21,sex:false}, 79 | jack10:{name:"jack",age:21,sex:false}, 80 | jack11:{name:"jack",age:21,sex:false}, 81 | jack12:{name:"jack",age:21,sex:false}, 82 | } 83 | },{Array:{maxItems:5},Object:{maxItems:5},title:"产品列表: "}) 84 | 85 | 86 | 87 | logsets.format({ 88 | name:"tom", 89 | age:11, 90 | admin:true, 91 | posts:["经理","主任"], 92 | addtrss:{ 93 | company:"中华人民共和国北京市二环路", 94 | family:"福建省泉州市惠安路1512号" 95 | } 96 | },{title:"产品经理: "}) 97 | 98 | // const o1={asks:{ price: "2000", amt: 10 } , 99 | // bids: [ { price: "500", amt: 10 }, 100 | // { price: "100", amt: 10 } ] , 101 | // time:t ,error:new MyError("BCXCCC"),callback:getUserInfo } 102 | 103 | // logsets.log(o1) 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | logsets.config({ 112 | Object:{maxItems:5,compact:true} 113 | }).format({ 114 | users:{ 115 | tom:{name:"tom",age:21,sex:true}, 116 | jack:{name:"jack",age:21,sex:false}, 117 | jack1:{name:"jack",age:21,sex:false}, 118 | jack2:{name:"jack",age:21,sex:false}, 119 | jack3:{name:"jack",age:21,sex:false}, 120 | jack4:{name:"jack",age:21,sex:false}, 121 | jack5:{name:"jack",age:21,sex:false}, 122 | jack6:{name:"jack",age:21,sex:false}, 123 | jack7:{name:"jack",age:21,sex:false}, 124 | jack8:{name:"jack",age:21,sex:false}, 125 | jack9:{name:"jack",age:21,sex:false}, 126 | jack10:{name:"jack",age:21,sex:false}, 127 | jack11:{name:"jack",age:21,sex:false}, 128 | jack12:{name:"jack",age:21,sex:false}, 129 | }, 130 | number:100, 131 | bool:true, 132 | nil:null, 133 | empty:undefined, 134 | string:new Array(109).fill("hello").map((v,i)=>(i+1)).join("="), 135 | array:new Array(109).fill(0).map((v,i)=>(i+1)), 136 | error:new MyError("File does not exists") 137 | }) 138 | 139 | 140 | logsets.debug("正在执行程序{},还需要{}秒...",["logs",9]) 141 | logsets.info("正在执行程序{app},还需要{time}秒...",{app:"logs",time:9}) 142 | logsets.warn("正在执行程序{app},还需要{time}秒...",{app:"logs",time:9},"Line:123") 143 | logsets.warn("程序执行可能出错\n变量没有定义") 144 | logsets.error("程序执行可能出错\n变量没有定义") 145 | logsets.fatal("执行程序{a} + {b}发生致命错误",{a:1,b:1}) 146 | 147 | logsets.warn("My name is {name}, age is {age}",()=> ({name:"Voerka",age:1})) 148 | 149 | let table = logsets.table({ 150 | colorize:1, 151 | grid:1, 152 | }) 153 | table.addHeader("序号","文件名","大小","下载进度","完成","<备注") 154 | table.addRow(1,"readme.md",58713,100,true,"自述文件") 155 | table.addRow(2,"index.js",1222,100,true,"源代码文件") 156 | table.addSeparator() 157 | table.addRow(3,"consts.js",45981,100,true,"常量定义\n包含默认的配置文件") 158 | table.addRow(4,"table.plugin.js",434,100,true,"表格插件\n可选,用来输出表格") 159 | table.addRow(5,"rollup.config.js",123,100,true,"构建配置文件") 160 | table.addSummary(["已下载",5,"个文件\n累计耗时",56,"秒"],{align:"right"}) 161 | table.addRow(6,"colorize.js",6542,60,false,"实现对变量或对象进行着色") 162 | table.addRow(7,"stringify.js",5546,34,false,"格式化JSON") 163 | table.addRow(8,"utils.js",6456,66,false,"一个工具函数") 164 | table.addFooter(["共",8,"个文件"]) 165 | table.render() 166 | 167 | 168 | 169 | table = logsets.table({ 170 | colorize:1, 171 | grid:2, 172 | maxColWidth:12, 173 | }) 174 | table.addHeader("名称","性别","出生日期","<居住地址") 175 | table.addRow("令狐冲","男","1653/12/2","思过崖","华山派") 176 | table.addRow("东方不败","男","1603/6/3","日月神教无敌峰藏经阁") 177 | table.addRow("任盈盈","女","1651/2/8","") 178 | table.addFooter([1,2,3,4],{merge:false}) 179 | table.render() 180 | 181 | 182 | 183 | const tree = { 184 | "arrays.js": { 185 | lasModifyTime: 1650854060803, 186 | buildTime: 1650873807693, 187 | }, 188 | "asyncSignal.js": { 189 | lasModifyTime: 1642384454095, 190 | buildTime: 1650873808220, 191 | }, 192 | "asyncStateMachine.js": { 193 | lasModifyTime: 1650853827230, 194 | buildTime: 1650873808755, 195 | }, 196 | "buffers.js": { 197 | lasModifyTime: 1642384454370, 198 | buildTime: 1650873809044, 199 | }, 200 | "classes.js": { 201 | lasModifyTime: 1650447265223, 202 | buildTime: 1650873809255, 203 | }, 204 | "dataConsumer.js": { 205 | lasModifyTime: 1650867353119, 206 | buildTime: 1650873809564, 207 | }, 208 | "dataqueue.js": { 209 | lasModifyTime: 1642384454530, 210 | buildTime: 1650873809748, 211 | }, 212 | "deviceInfo.android.js": { 213 | lasModifyTime: 1642384454604, 214 | buildTime: 1650873809863, 215 | }, 216 | "deviceInfo.js": { 217 | lasModifyTime: 1642384454677, 218 | buildTime: 1650873810012, 219 | }, 220 | "dicts.js": { 221 | lasModifyTime: 1650356961062, 222 | buildTime: 1650873810283, 223 | }, 224 | "enum.js": { 225 | lasModifyTime: 1642384454844, 226 | buildTime: 1650873810388, 227 | }, 228 | "envs.js": { 229 | lasModifyTime: 1650773039369, 230 | buildTime: 1650873810475, 231 | }, 232 | "flexArgs.js": { 233 | lasModifyTime: 1642384454943, 234 | buildTime: 1650873810566, 235 | }, 236 | "funcs.js": { 237 | lasModifyTime: 1650854008034, 238 | buildTime: 1650873810714, 239 | }, 240 | "hook.js": { 241 | lasModifyTime: 1642384455098, 242 | buildTime: 1650873810784, 243 | }, 244 | "index.js": { 245 | lasModifyTime: 1642384455158, 246 | buildTime: 1650873810863, 247 | }, 248 | "iterator.js": { 249 | lasModifyTime: 1642384455319, 250 | buildTime: 1650873810925, 251 | }, 252 | "multiplex.js": { 253 | lasModifyTime: 1650854079018, 254 | buildTime: 1650873811116, 255 | }, 256 | "schema.js": { 257 | lasModifyTime: 1642384455695, 258 | buildTime: 1650873811207, 259 | }, 260 | "session.js": { 261 | lasModifyTime: 1642384455761, 262 | buildTime: 1650873811350, 263 | }, 264 | "snowflake.js": { 265 | lasModifyTime: 1642384455818, 266 | buildTime: 1650873811412, 267 | }, 268 | "statedPromise.js": { 269 | lasModifyTime: 1642384455884, 270 | buildTime: 1650873811512, 271 | }, 272 | "timer.js": { 273 | lasModifyTime: 1650436530905, 274 | buildTime: 1650873811601, 275 | }, 276 | "tree.js": { 277 | lasModifyTime: 1642384456218, 278 | buildTime: 1650873811706, 279 | }, 280 | "typeCheck.js": { 281 | lasModifyTime: 1650856845095, 282 | buildTime: 1650873811785, 283 | }, 284 | "a\\index.js": { 285 | lasModifyTime: 1650856983600, 286 | buildTime: 1650873811865, 287 | }, 288 | } 289 | 290 | 291 | logsets.format(tree) 292 | 293 | logsets.log("{#red a}+{b}={c}",{a:1,b:1,c:2}) 294 | // 位置插值变量 295 | logsets.log("My name is {}","tom") 296 | logsets.log("{a}+{b}={#bgGreen c}",1,1,2) 297 | 298 | logsets.log("My name is {#red tom}") 299 | logsets.log("{#blue Voerkai18n}是一个非常不错的{#red,dim 多语言}解决方案!") 300 | 301 | 302 | console.log(logsets.getColorizedTemplate("My name is {}","zhang")) 303 | logsets.config({ 304 | String:"yellow", 305 | levels:{ 306 | memo : "red", 307 | debug : "red", 308 | info : "red", 309 | warn : "red", 310 | error : "red", 311 | fatal : "red" 312 | } 313 | }) 314 | console.log(logsets.getColorizedTemplate("My name is {}","zhang")) 315 | 316 | 317 | 318 | logsets.debug("正在执行程序{},还需要{}秒...",["logs",9]) 319 | logsets.info("正在执行程序{app},还需要{time}秒...",{app:"logs",time:9}) 320 | logsets.warn("正在执行程序{app},还需要{time}秒...",{app:"logs",time:9},"Line:123") 321 | logsets.warn("程序执行可能出错\n变量没有定义") 322 | logsets.error("程序执行可能出错\n变量没有定义") 323 | logsets.fatal("执行程序{a} + {b}发生致命错误",{a:1,b:1}) 324 | -------------------------------------------------------------------------------- /examples/header.demo.js: -------------------------------------------------------------------------------- 1 | const logsets = require("../src") 2 | 3 | logsets.header("{#red a}+{b}={c}",{a:1,b:1,c:2}) 4 | // 位置插值变量 5 | logsets.header("My name is {}","tom") 6 | logsets.header("{a}+{b}={#bgGreen c}",1,1,2) 7 | 8 | logsets.header("My name is {#red tom}") 9 | logsets.h("{#blue Voerkai18n}是一个非常不错的{#red,dim 多语言}解决方案!") 10 | 11 | -------------------------------------------------------------------------------- /examples/list.demo.js: -------------------------------------------------------------------------------- 1 | 2 | const logsets = require("../src/") 3 | const delay = async (n = 100) => new Promise((resolve) => setTimeout(resolve, n)); 4 | 5 | const items = [ 6 | { 7 | title: "全流程支持", 8 | description:"从文本提取/自动翻译/编译/动态切换的全流程工程化支持,适用于大型项目", 9 | }, 10 | { 11 | title: "集成自动翻译", 12 | type:"×", 13 | description:["调用{}支持对提取的文本进行自动翻译,大幅度提高\n工程效率","在线翻译API"], 14 | }, 15 | { 16 | title: "符合直觉", 17 | type:['○','yellow'], 18 | style:"red", 19 | description:"在源码中直接使用符合直觉的翻译形式,不需要绞尽脑汁想种种key", 20 | }, 21 | { 22 | title: ["支持{}","TypeScript"], 23 | description: "内置支持TypeScript类型以及生成TypeScript源码", 24 | }, 25 | { 26 | title: "自动提取文本", 27 | description: "提供扫描提取工具对源码文件中需要翻译的文本进行提取", 28 | }, 29 | "构建流程", 30 | { 31 | title: "适用性", 32 | description: 33 | "支持任意Javascript应用,包括Nodejs/Vue/React/ReactNative等。", 34 | }, 35 | { 36 | title: "多库联动", description: "支持多包工程下多库进行语言切换的联动" , 37 | type:['👉'], 38 | }, 39 | { 40 | title: "工具链:提供Vue/React/Babel等扩展插件,简化各种应用开发" 41 | }, 42 | { 43 | title: "插值变量", 44 | indent:"-------", 45 | description: "强大的插值变量机制,能扩展支持复数、日期、货币等灵活强大的多语言特性", 46 | }, 47 | ["配置{}工作流",'VoerkaI18n'], 48 | { title: "语言补丁", description: "在应用上线后发现错误时可以在线修复" }, 49 | { title: "动态增加语种", description: "可以在应用上线后动态增加语种支持" }, 50 | { title: ["测试覆盖率{}","90%+"], description: "核心运行时超过90%的测试覆盖率" } 51 | ] 52 | 53 | 54 | logsets.list(["欢迎使用{}国际化解决方案",'VoerkaI18n'],items,{ 55 | showOrderNumber:false, 56 | title:{ 57 | emoji:"🔥", 58 | } 59 | }).then(async ()=>{ 60 | await logsets.list(["欢迎使用{}国际化解决方案",'VoerkaI18n'],async (index,end)=>{ 61 | await delay() 62 | if(index===items.length-1) end() 63 | return items[index] 64 | },{ 65 | showOrderNumber:false, 66 | title:{ 67 | emoji:"💎", 68 | } 69 | }).then(()=>{ 70 | console.log("完成") 71 | }) 72 | }) 73 | 74 | 75 | // logsets.list(["欢迎使用{}国际化解决方案",'VoerkaI18n'],items,{ 76 | // showOrderNumber:false, 77 | // grouped:false, 78 | // title:{ 79 | // emoji:"💎", 80 | // } 81 | // }) 82 | -------------------------------------------------------------------------------- /examples/progressbar.demo.js: -------------------------------------------------------------------------------- 1 | const logsets = require("../src") 2 | 3 | const run = async ()=>{ 4 | const progressbar = logsets.progressbar({ 5 | title:"下载进度:", 6 | slider:{ 7 | style:"bgDarkGray,red", 8 | char:"=" 9 | } 10 | }) 11 | 12 | async function delay(n=10){ 13 | return new Promise(resolve=>setTimeout(resolve,n)) 14 | } 15 | progressbar.begin() 16 | for(let i = 0 ; i <= 60; i++){ 17 | await delay() 18 | progressbar.value(i) 19 | } 20 | progressbar.end() 21 | logsets.log("") 22 | 23 | progressbar.begin() 24 | progressbar.value(82) 25 | progressbar.stop(logsets.colors.red("下载停止")) 26 | logsets.log("") 27 | 28 | progressbar.begin() 29 | progressbar.value(36) 30 | progressbar.error(logsets.colors.yellow("下载失败")) 31 | logsets.log("") 32 | 33 | 34 | 35 | logsets.log("File download complate!") 36 | 37 | } 38 | 39 | run() -------------------------------------------------------------------------------- /examples/table.demo.js: -------------------------------------------------------------------------------- 1 | const logsets = require("../src/") 2 | 3 | let table = logsets.table({ 4 | colorize: 1, 5 | grid: 1, 6 | }); 7 | table 8 | .addHeader("序号", "文件名", "大小", "下载进度", "完成", "<备注") 9 | .addRow(1, "readme.md", 58713, 100, true, "自述文件") 10 | .addRow(2, "index.js", 1222, 100, true, "源代码文件") 11 | .addSeparator() 12 | .addRow(3, "consts.js", 45981, 100, true, "常量定义\n包含默认的配置文件") 13 | .addRow(4, "table.plugin.js", 434, 100, true, "表格插件\n可选,用来输出表格") 14 | .addRow(5, "rollup.config.js", 123, 100, true, "构建配置文件") 15 | .addSummary(["已下载", 5, "个文件\n累计耗时", 56, "秒"], { align: "right" }) 16 | .addRow(6, "colorize.js", 6542, 60, false, "实现对变量或对象进行着色") 17 | .addRow(7, "stringify.js", 5546, 34, false, "格式化JSON") 18 | .addRow(8, "utils.js", 6456, 66, false, "一个工具函数") 19 | .addSeparator() 20 | .addFooter(["共", 8, "个文件"]) 21 | .render(); 22 | 23 | table = logsets.table({ 24 | colorize: 1, 25 | grid: 2, 26 | maxColWidth: 12, 27 | }); 28 | table 29 | .addHeader("名称", "性别", "出生日期", "<居住地址") 30 | .addRow("令狐冲", "男", "1653/12/2", "思过崖", "华山派") 31 | .addRow("东方不败", "男", "1603/6/3", "日月神教无敌峰藏经阁") 32 | .addRow("任盈盈", "女", "1651/2/8", "") 33 | .addFooter([1, 2, 3, 4], { merge: false }) 34 | .render(); 35 | -------------------------------------------------------------------------------- /examples/task.demo.js: -------------------------------------------------------------------------------- 1 | const logsets = require("../src") 2 | 3 | 4 | async function delay(n = 100) { 5 | return new Promise((resolve) => setTimeout(resolve, n)); 6 | } 7 | 8 | async function run(){ 9 | await logsets.task("正常执行任务",async ()=>{ 10 | await delay(1000) 11 | }) 12 | await logsets.task("正常任务出错",async ()=>{ 13 | await delay(1000) 14 | throw new Error("任务出错") 15 | }) 16 | await logsets.task("正常任务跳过",async ()=>{ 17 | await delay(1000) 18 | return "skip" 19 | }) 20 | await logsets.task("正常任务取消",async ()=>{ 21 | await delay(1000) 22 | return "cancel" 23 | }) 24 | await logsets.task("正常任务停止",async ()=>{ 25 | await delay(1000) 26 | return ["stop","任务停止"] 27 | }) 28 | await logsets.task(["正常执行任务:{}","voerkai18n"],async ()=>{ 29 | await delay(1000) 30 | }) 31 | await logsets.task(["正常执行任务:{}","voerkai18n"],async (task)=>{ 32 | await delay(1000) 33 | for(let i=0;i<10;i++){ 34 | task.note(i+1) 35 | await delay(100) 36 | } 37 | }) 38 | } 39 | 40 | run() -------------------------------------------------------------------------------- /examples/tasklist.demo.js: -------------------------------------------------------------------------------- 1 | const logsets = require("../src/") 2 | 3 | const run = async ()=>{ 4 | let tasks = logsets.tasklist({ 5 | title:["所有任务:{count}个",{count:100}], 6 | status:{ 7 | connected:{ 8 | style:"green", 9 | symbol:"C", 10 | note:"连接成功" 11 | } 12 | } 13 | }) 14 | 15 | async function delay(n=10){ 16 | return new Promise(resolve=>setTimeout(resolve,n)) 17 | } 18 | 19 | const taskData = [ 20 | {title:"开始扫描文件",result:"complete",note:"OK"}, 21 | {title:"准备对文件进行预处理",result:"error",note:new Error("文件没有找到")}, 22 | {title:"读取文件并编译成exe文件",result:"skip",note:"SKIP"}, 23 | "-", 24 | {title:"任务处理被停止",result:"stop",note:"STOP"}, 25 | {title:"任务执行失败",result:"fail",note:"FAIL"}, 26 | {title:"任务待办状态",result:"todo",note:"TODO"}, 27 | {title:["下载文件:{},大小:{}, 已下载{}","package.json",122,344],result:"todo",note:"TODO"}, 28 | {title:["下载文件:{},大小:{}, 已下载{}",["package.json",122,344]],result:"todo",note:"TODO"}, 29 | 30 | ] 31 | tasks.addMemo("任务列表") 32 | 33 | for(let task of taskData){ 34 | if(task=="-") { 35 | tasks.separator() 36 | continue 37 | } 38 | let t = tasks.add(...Array.isArray(task.title) ? task.title:[task.title]) 39 | let n = 1 40 | let tm = setInterval(() => { 41 | t.note(`任务${n++}备注`) 42 | },10) 43 | await delay(500) 44 | clearInterval(tm) 45 | tasks[task.result](task.note) 46 | } 47 | tasks.add("正在连接") 48 | tasks.connected() 49 | 50 | // logsets.separator() 51 | 52 | // for(let task of taskData){ 53 | // let taskObj = tasks.add(task.title) 54 | // await delay(1000) 55 | // taskObj[task.result](task.note) 56 | // } 57 | 58 | logsets.separator() 59 | let task = logsets.task("正在构建文件") 60 | await delay(100) 61 | task.complete() 62 | task = logsets.task("1. 下载文件:{#bgCyan},大小:{}, 已下载{}",["package.json",122,344]) 63 | await delay(1000) 64 | task.error("文件不存在") 65 | 66 | 67 | tasks.add("下载文件:{#red },大小:{}, 已下载{}",["package.json",1,2]) 68 | tasks.complete() 69 | 70 | 71 | tasks.add("2. 下载文件:{#red },大小:{}, 已下载{}",["package.json",122,344]) 72 | tasks.complete(["下载成功:{}", 1299]) 73 | tasks.add("3. 下载文件:{#red },大小:{}, 已下载{}",["package.json",122,344]) 74 | tasks.error() 75 | tasks.add("4. 下载文件:{#red },大小:{}, 已下载{}",["package.json",122,344]) 76 | tasks.fail() 77 | // tasks.indent() 78 | tasks.add("5. 下载文件:{#red },大小:{}, 已下载{}",["package.json",122,344]) 79 | tasks.cancel() 80 | tasks.add("6. 下载文件:{#red },大小:{}, 已下载{}",["package.json",122,344]) 81 | tasks.stop() 82 | tasks.add("7. 下载文件:{#red },大小:{}, 已下载{}",["package.json",122,344]) 83 | tasks.todo() 84 | // tasks.outdent() 85 | tasks.add("8. 下载文件:{#red },大小:{}, 已下载{}",["package.json",122,344]) 86 | tasks.skip() 87 | tasks.add("9. 下载文件:{#red },大小:{}, 已下载{}",["package.json",122,344]) 88 | tasks.ignore() 89 | 90 | 91 | task = logsets.task("10. 下载文件{#yellow voerki18n.zip},大小{#red size}",{size:12354}) 92 | task.complete() 93 | task = logsets.task("11. 下载文件{#yellow voerki18n.zip},大小{#red size}",{size:12354}) 94 | task.error() 95 | task = logsets.task("12. 下载文件{#yellow voerki18n.zip},大小{#red size}",{size:12354}) 96 | task.fail() 97 | task = logsets.task("13. 下载文件{#yellow voerki18n.zip},大小{#red size}",{size:12354}) 98 | task.todo() 99 | task = logsets.task("14. 下载文件{#yellow voerki18n.zip},大小{#red size}",{size:12354}) 100 | task.skip() 101 | task = logsets.task("15. 下载文件{#yellow voerki18n.zip},大小{#red size}",{size:12354}) 102 | task.ignore("<可选备注>") 103 | task = logsets.task("16. 下载文件{#yellow voerki18n.zip},大小{#red size}",{size:12354}) 104 | task.cancel("取消") 105 | 106 | logsets.separator() 107 | 108 | await tasks.run("17. 正在下载文件{}","a.zip", ()=>{ 109 | delay(1000) 110 | return "skip" 111 | }) 112 | 113 | await tasks.run("18. 正在下载文件{}","a.zip", ()=>{ 114 | delay(1000) 115 | return ["abort","用户中止"] 116 | }) 117 | 118 | await tasks.run("19. 正在下载文件{}","b.zip", ()=>{ 119 | delay(1000) 120 | throw new TypeError("下载失败") 121 | }) 122 | await tasks.run("20. 正在下载文件{}","b.zip", ()=>{ 123 | delay(2000) 124 | }) 125 | 126 | 127 | } 128 | 129 | run() -------------------------------------------------------------------------------- /examples/tasklist.demo2.js: -------------------------------------------------------------------------------- 1 | const logsets = require("../src") 2 | const summary = logsets.tasklist({grouped:true}) 3 | summary.addGroup("配置信息:") 4 | summary.addMemo("语言配置文件: {}","settingsRelFile") 5 | summary.addMemo("拟支持的语言: {}",['zh-CN','en-US']) 6 | summary.addMemo("已安装运行时: {}",'@voerkai18n/runtime') 7 | summary.addMemo("工作模式 : {}","库模式") 8 | summary.addGroup("初始化成功,下一步:") 9 | summary.addMemo("修改{}文件编辑语言参数",`languages/settings.json`) 10 | summary.addMemo("运行<{}>扫描提取要翻译的文本","voerkai18n extract") 11 | summary.addMemo("运行<{}>在线自动翻译","voerkai18n translate") 12 | summary.addMemo("运行<{}>编译语言包","voerkai18n compile") 13 | summary.done("完成{}%",100) -------------------------------------------------------------------------------- /examples/tasklist.group.demo.js: -------------------------------------------------------------------------------- 1 | const logsets = require("../src") 2 | 3 | const run = async ()=>{ 4 | let tasks = logsets.tasklist({ 5 | title:["所有任务:{count}个",{count:100}], 6 | grouped:true, 7 | status:{ 8 | connected:{ 9 | style:"green", 10 | symbol:"C", 11 | note:"连接成功" 12 | } 13 | } 14 | }) 15 | 16 | async function delay(n=10){ 17 | return new Promise(resolve=>setTimeout(resolve,n)) 18 | } 19 | 20 | const taskData = [ 21 | {title:"开始扫描文件",result:"complete",note:"OK"}, 22 | "预处理阶段", 23 | {title:"准备对文件进行预处理",result:"error",note:new Error("文件没有找到")}, 24 | {title:"读取文件并编译成exe文件",result:"skip",note:"SKIP"}, 25 | "-", 26 | {title:"任务处理被停止",result:"stop",note:"STOP"}, 27 | {title:"任务执行失败",result:"fail",note:"FAIL"}, 28 | {title:"任务待办状态",result:"todo",note:"TODO"}, 29 | ["下载{}文件","voerkai18n"], 30 | {title:["下载文件:{},大小:{}, 已下载{}","package.json",122,344],result:"todo",note:"TODO"}, 31 | {title:["下载文件:{},大小:{}, 已下载{}",["package.json",122,344]],result:"todo",note:"TODO"}, 32 | 33 | ] 34 | 35 | 36 | for(let task of taskData){ 37 | if(task=="-") { 38 | tasks.separator() 39 | }else if(typeof task === "string"){ 40 | tasks.addGroup(task) 41 | }else if(Array.isArray(task)){ 42 | tasks.addGroup(...task) 43 | }else{ 44 | let t = tasks.add(...Array.isArray(task.title) ? task.title:[task.title]) 45 | let n = 1 46 | let tm = setInterval(() => { 47 | t.note(`任务${n++}备注`) 48 | },10) 49 | await delay(500) 50 | clearInterval(tm) 51 | tasks[task.result](task.note) 52 | } 53 | } 54 | 55 | let task 56 | try{ 57 | task = tasks.add("正在连接") 58 | throw new Error("连接失败") 59 | }catch(e){ 60 | task.error(e) 61 | } 62 | 63 | 64 | 65 | } 66 | 67 | run() -------------------------------------------------------------------------------- /examples/tasks.demo.js: -------------------------------------------------------------------------------- 1 | const logsets = require("../src/") 2 | 3 | 4 | async function delay(n = 100) { 5 | return new Promise((resolve) => setTimeout(resolve, n)); 6 | } 7 | 8 | const run = async ()=>{ 9 | const taskList =[ 10 | { 11 | title: "开始扫描文件", 12 | execute: async function () { 13 | await delay(); 14 | }, 15 | }, 16 | { 17 | title: "准备对文件进行预处理", 18 | execute: async function () { 19 | await delay(); 20 | }, 21 | }, 22 | { 23 | title: "正在下载文件", 24 | execute: async function ({task}) { 25 | 26 | await delay(); 27 | for(let i=0;i<100;i++){ 28 | await delay(5) 29 | task.note(`${i}%`) 30 | } 31 | }, 32 | }, 33 | "-", 34 | { 35 | title: "任务处理被停止", 36 | execute: async function () { 37 | await delay(); 38 | }, 39 | }, 40 | "构建流程", 41 | { 42 | title: ["编码读取文件并编译成{}文件","exe"], 43 | }, 44 | { 45 | title: "读取文件并编译成exe文件", 46 | }, 47 | { 48 | title: "读取文件并编译成exe文件", 49 | }, 50 | { 51 | title: "读取文件并编译成exe文件", 52 | }, 53 | { 54 | title: "读取文件并编译成exe文件", 55 | }, 56 | "任务执行", 57 | { 58 | title: "任务执行失败", 59 | execute: async function () { 60 | await delay(); 61 | throw new Error("任务执行失败"); 62 | }, 63 | }, 64 | ["asdsdsd","asdsdsd"], 65 | { 66 | title: "任务待办状态", 67 | execute: async function () { 68 | await delay(); 69 | }, 70 | }, 71 | { 72 | title: ["下载文件:{},大小:{}, 已下载{}", "package.json", 122, 344], 73 | execute: async function () { 74 | await delay(); 75 | }, 76 | }, 77 | { 78 | title: ["下载文件:{},大小:{}, 已下载{}", ["package.json", 122, 344]], 79 | end:false, 80 | execute: async function () { 81 | await delay(); 82 | }, 83 | }, 84 | ] 85 | let tasks = logsets.createTasks(taskList,{ 86 | ignoreErrors:true, 87 | grouped:true 88 | }); 89 | 90 | tasks.run("开始执行任务").then(() => { 91 | // logsets.run(["开始执行{}个任务",5],taskList).then(() => { 92 | // console.log("done"); 93 | // }); 94 | let fntasks = logsets.createTasks((index,end)=>{ 95 | if(index { 107 | console.log("done"); 108 | }); 109 | },{ 110 | grouped:true 111 | }); 112 | 113 | 114 | } 115 | 116 | 117 | run() -------------------------------------------------------------------------------- /examples/tasks.group.demo.js: -------------------------------------------------------------------------------- 1 | const logsets = require("../src") 2 | 3 | 4 | async function delay(n = 100) { 5 | return new Promise((resolve) => setTimeout(resolve, n)); 6 | } 7 | 8 | const run = async ()=>{ 9 | const taskList =[ 10 | "准备阶段", 11 | { 12 | title: "开始扫描文件", 13 | execute: async function () { 14 | await delay(); 15 | return 'error' 16 | }, 17 | }, 18 | { 19 | title: "准备对文件进行预处理", 20 | execute: async function () { 21 | await delay(); 22 | return 'skip' 23 | }, 24 | }, 25 | { 26 | title: "正在下载文件", 27 | execute: async function ({task}) { 28 | await delay(); 29 | for(let i=0;i<100;i++){ 30 | await delay(5) 31 | task.note(`${i}%`) 32 | } 33 | }, 34 | }, 35 | "编译项目阶段", 36 | { 37 | title: "任务处理被停止", 38 | execute: async function () { 39 | await delay(); 40 | }, 41 | }, 42 | { 43 | title: "任务处理被停止", 44 | execute: async function () { 45 | await delay(); 46 | return 'stop' 47 | }, 48 | }, 49 | { 50 | title: "任务处理被停止", 51 | execute: async function () { 52 | await delay(); 53 | }, 54 | }, 55 | "执行单元测试项目", 56 | { 57 | title: "任务执行失败", 58 | execute: async function () { 59 | await delay(); 60 | }, 61 | }, 62 | { 63 | title: "任务待办状态", 64 | execute: async function () { 65 | await delay(); 66 | }, 67 | }, 68 | ["构建{}项目","vue"], 69 | { 70 | title: ["下载文件:{},大小:{}, 已下载{}", "package.json", 122, 344], 71 | execute: async function () { 72 | await delay(); 73 | }, 74 | }, 75 | { 76 | title: ["下载文件:{},大小:{}, 已下载{}", ["package.json", 122, 344]], 77 | execute: async function () { 78 | await delay(); 79 | return 'cancel' 80 | }, 81 | end:true 82 | }, 83 | ] 84 | let tasks = logsets.createTasks(taskList,{grouped:true}); 85 | 86 | tasks.run().then(() => { 87 | console.log("done"); 88 | logsets.run(["开始执行{}个任务",5],taskList).then(() => { 89 | console.log("done"); 90 | }); 91 | 92 | }); 93 | 94 | 95 | } 96 | 97 | 98 | run() -------------------------------------------------------------------------------- /examples/tree.demo.js: -------------------------------------------------------------------------------- 1 | const logsets = require("../src/") 2 | 3 | 4 | const tree = logsets.tree({ 5 | root:"文件结构", 6 | note:{ 7 | enable:true 8 | } 9 | }) 10 | 11 | tree.addNode("readme.md",{note:logsets.colors.green("√")}) 12 | .addNode("package.json",{note:logsets.colors.green("√")}) 13 | .addNode("个人简历.doc",{note:logsets.colors.green("√")}) 14 | .addNode("网络组网方案.docx",{note:logsets.colors.green("√")}) 15 | .addNode("工资清单.xlsx",{note:logsets.colors.green("√")}) 16 | .addNode("",{style:"yellow",note:logsets.colors.red("×")}) 17 | .beginChildren() 18 | .addNode("readme.md") 19 | .addNode("package.json") 20 | .addNode("个人简历.doc") 21 | .beginChildren() 22 | .addNode("readme.md") 23 | .addNode("package.json",{note:logsets.colors.red("×")}) 24 | .addNode("个人简历.doc",{note:"已审核"}) 25 | .addNode("网络组网{#red 方案}.docx") 26 | .addNode("工资清单.xlsx",{last:true}) 27 | .endChildren() 28 | .addNode("网络组网方案.docx") 29 | .addNode("工资清单.xlsx",{last:true}) 30 | .endChildren() 31 | .addNode("网络组网方案.docx") 32 | .addNode("工资清单.xlsx",{last:true}) 33 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "logsets", 3 | "version": "1.3.31", 4 | "description": "Nodejs terminal application output colorized enhancement component", 5 | "main": "dist/index.cjs", 6 | "module": "dist/index.mjs", 7 | "homepage": "https://zhangfisher.github.io/logsets/", 8 | "types": "./src/index.d.ts", 9 | "scripts": { 10 | "test": "echo \"Error: no test specified\" && exit 1", 11 | "build": "rollup -c", 12 | "release": "nrm use npm && npm version patch --commit-hooks=false && npm run build && npm publish", 13 | "dev:docs": "docsify serve docs", 14 | "prepare": "husky install", 15 | "commit": "git add . && git cz", 16 | "version": "conventional-changelog -p angular -i ./docs/CHANGELOG.md -s -r 0 && git add ./docs/CHANGELOG.md", 17 | "docs:dev": "vitepress dev docs", 18 | "docs:build": "vitepress build docs", 19 | "docs:preview": "vitepress preview docs", 20 | "publish:all": "changeset version && changeset publish" 21 | }, 22 | "exports": { 23 | ".": { 24 | "import": "./dist/index.mjs", 25 | "require": "./dist/index.cjs" 26 | }, 27 | "./utils": { 28 | "import": "./dist/utils.mjs", 29 | "require": "./dist/utils.cjs" 30 | } 31 | }, 32 | "author": "", 33 | "license": "ISC", 34 | "repository": { 35 | "type": "git", 36 | "url": "git+https://gitee.com/zhangfisher/logsets.git" 37 | }, 38 | "dependencies": { 39 | "@babel/runtime-corejs3": "^7.17.0", 40 | "ansicolor": "^1.1.100", 41 | "core-js": "^3.21.0", 42 | "flex-tools": "1.5.9" 43 | }, 44 | "devDependencies": { 45 | "@babel/cli": "^7.17.0", 46 | "@babel/core": "^7.17.2", 47 | "@babel/plugin-transform-runtime": "^7.17.0", 48 | "@babel/preset-env": "^7.16.11", 49 | "@babel/runtime": "^7.17.2", 50 | "@changesets/cli": "^2.27.10", 51 | "@commitlint/cli": "^17.4.4", 52 | "@commitlint/config-conventional": "^17.4.4", 53 | "@commitlint/cz-commitlint": "^17.4.4", 54 | "@rollup/plugin-babel": "^5.3.0", 55 | "@rollup/plugin-commonjs": "^21.0.1", 56 | "commitizen": "^4.3.0", 57 | "conventional-changelog-cli": "^2.2.2", 58 | "husky": "^8.0.3", 59 | "rollup": "^2.70.0", 60 | "rollup-plugin-clear": "^2.0.7", 61 | "rollup-plugin-terser": "^7.0.2", 62 | "ts-essentials": "^9.3.0", 63 | "type-fest": "^3.5.3", 64 | "vitepress": "^1.5.0" 65 | }, 66 | "config": { 67 | "commitizen": { 68 | "path": "@commitlint/cz-commitlint" 69 | } 70 | }, 71 | "pnpm": { 72 | "enable-pre-post-scripts": true 73 | }, 74 | "packageManager": "pnpm@9.0.0" 75 | } -------------------------------------------------------------------------------- /rollup.config.js: -------------------------------------------------------------------------------- 1 | 2 | import clear from 'rollup-plugin-clear' 3 | import { babel } from '@rollup/plugin-babel'; 4 | import commonjs from '@rollup/plugin-commonjs'; 5 | import { terser } from "rollup-plugin-terser"; 6 | 7 | // import resolve from "@rollup/plugin-node-resolve" 8 | const plugins = [ 9 | //resolve(), 10 | commonjs(), 11 | babel({ 12 | babelHelpers:"runtime", 13 | exclude: 'node_modules/**' 14 | }), 15 | clear({targets:["dist"]}), 16 | terser() 17 | ] 18 | export default [ 19 | { 20 | input: './src/index.js', 21 | output: [ 22 | { 23 | file: 'dist/index.mjs', 24 | format:"es" 25 | }, 26 | { 27 | file: 'dist/index.cjs', 28 | exports:"default", 29 | format:"cjs" 30 | } 31 | ], 32 | plugins, 33 | external:["@babel/runtime"] 34 | }, 35 | { 36 | input: './src/utils.js', 37 | output: [ 38 | { 39 | file: 'dist/utils.mjs', 40 | format:"es" 41 | }, 42 | { 43 | file: 'dist/utils.cjs', 44 | format:"cjs" 45 | } 46 | ], 47 | plugins, 48 | external:["@babel/runtime"] 49 | } 50 | // { 51 | // input: './table.plugin.js', 52 | // output: [ 53 | // { 54 | // file: 'dist/table.plugin.mjs', 55 | // format:"es" 56 | // }, 57 | // { 58 | // file: 'dist/table.plugin.cjs', 59 | // exports:"default", 60 | // format:"cjs" 61 | // } 62 | // ], 63 | // plugins, 64 | // external:["@babel/runtime"] 65 | // }, 66 | // { 67 | // input: './progressbar.plugin.js', 68 | // output: [ 69 | // { 70 | // file: 'dist/progressbar.plugin.mjs', 71 | // format:"es" 72 | // }, 73 | // { 74 | // file: 'dist/progressbar.plugin.cjs', 75 | // exports:"default", 76 | // format:"cjs" 77 | // } 78 | // ], 79 | // plugins, 80 | // external:["@babel/runtime"] 81 | // }, 82 | // { 83 | // input: './tasklist.plugin.js', 84 | // output: [ 85 | // { 86 | // file: 'dist/tasklist.plugin.mjs', 87 | // format:"es" 88 | // }, 89 | // { 90 | // file: 'dist/tasklist.plugin.cjs', 91 | // exports:"default", 92 | // format:"cjs" 93 | // } 94 | // ], 95 | // plugins, 96 | // external:["@babel/runtime"] 97 | // }, 98 | // { 99 | // input: './banner.plugin.js', 100 | // output: [ 101 | // { 102 | // file: 'dist/banner.plugin.mjs', 103 | // format:"es" 104 | // }, 105 | // { 106 | // file: 'dist/banner.plugin.cjs', 107 | // exports:"default", 108 | // format:"cjs" 109 | // } 110 | // ], 111 | // plugins, 112 | // external:["@babel/runtime"] 113 | // }, 114 | // { 115 | // input: './tree.plugin.js', 116 | // output: [ 117 | // { 118 | // file: 'dist/tree.plugin.mjs', 119 | // format:"es" 120 | // }, 121 | // { 122 | // file: 'dist/tree.plugin.cjs', 123 | // exports:"default", 124 | // format:"cjs" 125 | // } 126 | // ], 127 | // plugins, 128 | // external:["@babel/runtime"] 129 | // } 130 | 131 | ] -------------------------------------------------------------------------------- /src/banner.plugin.d.ts: -------------------------------------------------------------------------------- 1 | import type { NamedColorStyles } from "./colors" 2 | import type { Logsets } from "./" 3 | import type { DeepRequired } from "ts-essentials" 4 | 5 | export interface BannerPluginOptions { 6 | indent? : string // 横幅缩进 7 | border? : { 8 | style? : NamedColorStyles // 边框颜色 9 | width? : 0 | 1 | 2 // 边框宽度,0-不显示,1-单线框,2-双线框 10 | }, 11 | title? : { 12 | align? : 'left' | 'center' | 'right' // 标题对齐方式 13 | style? : NamedColorStyles // 标题颜色 14 | wrapper? : string // 原本是使用☆ ☆ ☆标题包裹符号比较好看,但是 15 | }, 16 | align? : 'left' | 'center' | 'right' // 横幅行默认对齐方式,默认居中 17 | paddingLeft? : number // 左右空白宽度 18 | paddingRight? : number 19 | paddingTop? : number // 顶部空白行 20 | paddingBottom?: number 21 | } 22 | 23 | export interface Banner{ 24 | add(text:string,vars?:any[] | Record,options?:BannerPluginOptions):Banner 25 | render():void 26 | } 27 | 28 | export declare const BannerPlugin: { 29 | (logsets:Logsets,options:DeepRequired):Banner 30 | } 31 | 32 | export default BannerPlugin 33 | -------------------------------------------------------------------------------- /src/banner.plugin.js: -------------------------------------------------------------------------------- 1 | /** 2 | 3 | 用来显示一个横幅内容 4 | 5 | ╭──────────────────────────────────────────────────────────────────╮ 6 | | logsets │ 7 | │ https://www.xxx.com │ 8 | │ │ 9 | │ │ 10 | ╰──────────────────────────────────────────────────────────────────╯ 11 | 12 | const banner = logger.banner({}) 13 | 14 | banner.add("logsets",{align:"center",style:"green"}) 15 | banner.add("终端打印彩色输出工具",{align:"center",style:"green"}) 16 | banner.add("https://www.xxx.com") 17 | banner.add("Version","1.0.0") 18 | bammern.add("Copyright",2019) 19 | banner.render() 20 | 21 | ═ ║ ╒ ╓ ╔ ╕ ╖ ╗ ╘ ╙ ╚ ╛ ╜ ╝ ╞ ╟ ╠ ╡ ╢ ╣ ╤ ╥ ╦ ╧ ╩ ╨ ╪ ╫ ╬ ╭ ╯ ╰ 22 | 23 | ★☆ 24 | 25 | */ 26 | const utils = require("./utils") 27 | const { consoleOutput, getLeftRepeatChars, getRightRepeatChars, getStringWidth, paddingCenter,paddingStart, paddingEnd } = utils 28 | const { deepMerge } = require('flex-tools/object/deepMerge') 29 | 30 | const DefaultBannersOptions = { 31 | indent : " ", // 横幅缩进 32 | border : { 33 | style : "lightGray", // 边框颜色 34 | width : 1 // 边框宽度,0-不显示,1-单线框,2-双线框 35 | }, 36 | title : { 37 | align : "center", // 标题对齐方式 38 | style : "green,bright", // 标题颜色 39 | wrapper : "--==" // 原本是使用☆ ☆ ☆标题包裹符号比较好看,但是 40 | }, 41 | align : "center", // 横幅行默认对齐方式,默认居中 42 | paddingLeft : 4, // 左右空白宽度 43 | paddingRight : 4, 44 | paddingTop : 0, // 顶部空白行 45 | paddingBottom: 1 46 | } 47 | 48 | function createBanner(context,options){ 49 | const logger = this 50 | let opts = deepMerge(DefaultBannersOptions,options) 51 | let lines = [] // [{text,style,align}] 52 | function renderBanner(){ 53 | // 计算最大行宽度 54 | const paddingLeft = new Array(opts.paddingLeft).fill(" ").join("") 55 | const paddingRight = new Array(opts.paddingRight).fill(" ").join("") 56 | // 计算最大值 57 | let totalWidth = lines.reduce((width,line,index)=>{ 58 | if(index===0){ // 第一行视为标题行 59 | line.text = `${opts.title.wrapper} ${logger.colorizeString(line.text,opts.title.style)} ${opts.title.wrapper.reverse()}` 60 | } 61 | // 需要将插值变量里面的#号替换掉颜色 62 | return Math.max(width,getStringWidth((line.text||'').replace(/\{\#/g ,"{").params(line.vars))) 63 | },0) + opts.paddingLeft + opts.paddingRight 64 | if(typeof(opts.width)==="number" && opts.width > totalWidth){ 65 | totalWidth = opts.width 66 | } 67 | 68 | // ╔═══════════╗ 69 | consoleOutput("╔"+new Array(totalWidth).fill("═").join("")+"╗") 70 | // 留出空白行 71 | new Array(opts.paddingTop).fill("").forEach(()=>{ 72 | consoleOutput("║"+new Array(totalWidth).fill(" ").join("")+"║") 73 | }) 74 | // 75 | // ║ ║ line = {text:[],vars,align} 76 | 77 | lines.map((line,index)=>{ 78 | let lineText = (line.text || "").trim() 79 | let coloredLineText = lineText 80 | let t = "" 81 | let leftSpace="",rightSpace="" 82 | if(lineText.trim()===""){ 83 | leftSpace=new Array(totalWidth).fill(" ").join("") 84 | }else{ 85 | coloredLineText = logger.getColorizedTemplate(lineText,line.vars) 86 | if(line.align==="left"){ 87 | t= paddingEnd(coloredLineText,totalWidth-opts.paddingLeft-opts.paddingRight) 88 | }else if(line.align==="right"){ 89 | t= paddingStart(coloredLineText,totalWidth-opts.paddingLeft-opts.paddingRight) 90 | }else{ 91 | t= paddingCenter(coloredLineText,totalWidth-opts.paddingLeft-opts.paddingRight) 92 | } 93 | leftSpace = paddingLeft + getLeftRepeatChars(t) + (index==0 ? "" : "") 94 | rightSpace= paddingRight + getRightRepeatChars(t) + (index==0 ? "" : "") 95 | } 96 | 97 | 98 | 99 | logger.print("║",leftSpace,coloredLineText,rightSpace,"║",{append:""}) 100 | // 在标题栏下增加一行分割线╟ ─ ╢ 101 | if(index===0){ 102 | //consoleOutput("║"+new Array(totalWidth).fill(" ").join("")+"║") 103 | consoleOutput("╟"+new Array(totalWidth).fill("─").join("")+"╢") 104 | } 105 | }) 106 | 107 | // 留出空白行 108 | new Array(opts.paddingBottom).fill("").forEach(()=>{ 109 | consoleOutput("║"+new Array(totalWidth).fill(" ").join("")+"║") 110 | }) 111 | // ╚═══════════╝ 112 | consoleOutput("╚"+new Array(totalWidth).fill("═").join("")+"╝") 113 | } 114 | return { 115 | add(text,vars={},options={}){ 116 | lines.push({ 117 | text, 118 | vars, 119 | ...options 120 | }) 121 | return this 122 | }, 123 | render:()=>{ 124 | renderBanner() 125 | } 126 | } 127 | 128 | } 129 | 130 | 131 | 132 | 133 | 134 | /** 135 | * 136 | * @param {*} logger 137 | * @param {*} context 当前上下文配置参数 138 | */ 139 | module.exports = function(logger,context){ 140 | logger.banner = (opts={})=>createBanner.call(logger,context,opts) 141 | } -------------------------------------------------------------------------------- /src/colorize.js: -------------------------------------------------------------------------------- 1 | 2 | const stringifyObject = require('./stringify'); 3 | const ansicolor = require('ansicolor'); 4 | const { getDataType } = require('./utils'); 5 | const { isPlainObject } = require('flex-tools/typecheck/isPlainObject'); 6 | 7 | 8 | Object.assign(ansicolor.rgb,{ 9 | bgDarkGray: [13,213,33] 10 | }) 11 | /** 12 | 'foreground colors' 13 | .red.green.yellow.blue.magenta.cyan.white.darkGray.black 14 | 'light foreground colors' 15 | .lightRed.lightGreen.lightYellow.lightBlue.lightMagenta.lightCyan.lightGray 16 | 'background colors' 17 | .bgRed.bgGreen.bgYellow.bgBlue.bgMagenta.bgCyan.bgWhite.bgDarkGray.bgBlack 18 | 'light background colors' 19 | .bgLightRed.bgLightGreen.bgLightYellow.bgLightBlue.bgLightMagenta.bgLightCyan.bgLightGray 20 | 'styles' 21 | .bright.dim.italic.underline.inverse // your platform should support italic 22 | */ 23 | 24 | 25 | 26 | /** 27 | * 根据red,green,yellow形式的描述字符串返回ansicolor着色函数 28 | * 29 | * 支持允许多种颜色混合使用 30 | * 31 | * getColorizeFunction("dim,red") === dim(red(...)) 32 | * 33 | * @param {*} style 34 | * @returns 35 | */ 36 | function getColorizeFunction(style){ 37 | let func 38 | if(isPlainObject(style)) style = style.style || "" 39 | try{ 40 | let styles = style.split(",") 41 | for(let i=0;i typeof(options.format) === "function" ? options.format(v) : 70 | (typeof(options.format) === "string" ? options.format.params(v) : v) 71 | value = formatter(value) 72 | } 73 | const colorize = getColorizeFunction(options.style) 74 | return colorize(value) 75 | } 76 | 77 | /** 78 | * 79 | * 返回着色后的字符串 80 | * 81 | * 不同的数据类型按不同的颜色进行显示 82 | * 83 | * @param {*} obj 84 | * @param {*} opts 85 | * @returns 86 | */ 87 | function colorize(obj, opts={}) { 88 | let options = opts 89 | 90 | const objType = getDataType(obj); 91 | if((objType in options) && options[objType] && !isPlainObject(obj) && !Array.isArray(obj)){ 92 | return colorizeBaseType(obj,options[objType]) 93 | } 94 | 95 | const { title='' } = opts 96 | 97 | return title+ stringifyObject(obj, { 98 | ...options, 99 | transform: (obj, key, originalResult) => { // 获取值类型 100 | if(obj == null){ 101 | //const valueType = getDataType(originalResult); 102 | return colorizeBaseType(originalResult,options[objType]) 103 | }else{ 104 | const value = obj[key]; 105 | const valueType = getDataType(value); 106 | if((valueType in options) && options[valueType] && !isPlainObject(value) && !Array.isArray(value)){ 107 | try{ 108 | return colorizeBaseType(originalResult,options[valueType]) 109 | }catch{ 110 | return originalResult; 111 | } 112 | } 113 | return originalResult; 114 | } 115 | 116 | }, 117 | }); 118 | } 119 | 120 | 121 | module.exports = { 122 | colorize, 123 | getColorizeFunction, 124 | colorizeString 125 | } -------------------------------------------------------------------------------- /src/colors.d.ts: -------------------------------------------------------------------------------- 1 | /** 2 | 'foreground colors' 3 | .red.green.yellow.blue.magenta.cyan.white.darkGray.black 4 | 'light foreground colors' 5 | .lightRed.lightGreen.lightYellow.lightBlue.lightMagenta.lightCyan.lightGray 6 | 'background colors' 7 | .bgRed.bgGreen.bgYellow.bgBlue.bgMagenta.bgCyan.bgWhite.bgDarkGray.bgBlack 8 | 'light background colors' 9 | .bgLightRed.bgLightGreen.bgLightYellow.bgLightBlue.bgLightMagenta.bgLightCyan.bgLightGray 10 | 'styles' 11 | .bright.dim.italic.underline.inverse // your platform should support italic 12 | */ 13 | 14 | 15 | export type ForegroundColors = 'red'|'green'|'yellow'|'blue'|'magenta'|'cyan'|'white'|'darkGray'|'black'|'lightRed'|'lightGreen'|'lightYellow'|'lightBlue'|'lightMagenta'|'lightCyan'|'lightGray' 16 | export type BackgroundColors ='bgRed'|'bgGreen'|'bgYellow'|'bgBlue'|'bgMagenta'|'bgCyan'|'bgWhite'|'bgDarkGray'|'bgBlack'|'bgLightRed'|'bgLightGreen'|'bgLightYellow'|'bgLightBlue'|'bgLightMagenta'|'bgLightCyan'|'bgLightGray' 17 | 18 | export type FontStyles='bright'|'dim'|'italic'|'underline'|'inverse' 19 | 20 | 21 | export type NamedColorStyles = ForegroundColors | BackgroundColors | FontStyles 22 | |`${ForegroundColors},${BackgroundColors}` 23 | |`${ForegroundColors},${FontStyles}` 24 | |`${BackgroundColors},${ForegroundColors}` 25 | |`${BackgroundColors},${FontStyles}` 26 | |`${FontStyles},${ForegroundColors}` 27 | |`${FontStyles},${FontStyles}` 28 | |`${ForegroundColors},${BackgroundColors},${FontStyles}` 29 | |`${ForegroundColors},${FontStyles},${BackgroundColors}` 30 | |`${BackgroundColors},${ForegroundColors},${FontStyles}` 31 | |`${BackgroundColors},${FontStyles},${ForegroundColors}` 32 | |`${FontStyles},${BackgroundColors},${FontStyles}` 33 | |`${FontStyles},${FontStyles},${BackgroundColors}` 34 | 35 | 36 | export interface ColorizedMethods{ 37 | default (text:string):string 38 | white (text:string):string 39 | black (text:string):string 40 | red (text:string):string 41 | green (text:string):string 42 | yellow (text:string):string 43 | blue (text:string):string 44 | magenta (text:string):string 45 | cyan (text:string):string 46 | 47 | darkGray (text:string):string 48 | lightGray (text:string):string 49 | lightRed (text:string):string 50 | lightGreen (text:string):string 51 | lightYellow (text:string):string 52 | lightBlue (text:string):string 53 | lightMagenta (text:string):string 54 | lightCyan (text:string):string 55 | 56 | bright (text:string):string 57 | dim (text:string):string 58 | italic (text:string):string 59 | underline (text:string):string 60 | inverse (text:string):string 61 | 62 | bgDefault (text:string):string 63 | bgWhite (text:string):string 64 | bgBlack (text:string):string 65 | bgRed (text:string):string 66 | bgGreen (text:string):string 67 | bgYellow (text:string):string 68 | bgBlue (text:string):string 69 | bgMagenta (text:string):string 70 | bgCyan (text:string):string 71 | 72 | bgDarkGray (text:string):string 73 | bgLightGray (text:string):string 74 | bgLightRed (text:string):string 75 | bgLightGreen (text:string):string 76 | bgLightYellow (text:string):string 77 | bgLightBlue (text:string):string 78 | bgLightMagenta (text:string):string 79 | bgLightCyan (text:string):string 80 | } -------------------------------------------------------------------------------- /src/consts.js: -------------------------------------------------------------------------------- 1 | const { darkGray } = require("ansicolor") 2 | 3 | // 'foreground colors' 4 | // .red.green.yellow.blue.magenta.cyan.white.darkGray.black 5 | // 'light foreground colors' 6 | // .lightRed.lightGreen.lightYellow.lightBlue.lightMagenta.lightCyan.lightGray 7 | // 'background colors' 8 | // .bgRed.bgGreen.bgYellow.bgBlue.bgMagenta.bgCyan.bgWhite.bgDarkGray.bgBlack 9 | // 'light background colors' 10 | // .bgLightRed.bgLightGreen.bgLightYellow.bgLightBlue.bgLightMagenta.bgLightCyan.bgLightGray 11 | // 'styles' 12 | // .bright.dim.italic.underline.inverse // your platform should support italic 13 | 14 | const DefaultOptions ={ 15 | indent: " ", // 缩进 16 | singleQuotes: false, // 显示单引号 17 | template: "[{level}] {datetime} - {message}", // 模板 18 | compact:false, // 是否采用紧凑模式输出 19 | Array:{ 20 | maxItems: 100, // 数组最大长度,超过则显示省略号 21 | memo : (value)=> darkGray("(共"+value.length+"项)") // 当数组数量超过maxItems时,显示共几项的备注 22 | }, 23 | Object:{ 24 | maxItems:100, // 成员数量,超过则显示省略号 25 | align:true, // 是否自动对齐 26 | memo:(value)=> darkGray("(共"+value.length+"项)"), 27 | }, 28 | Function : { 29 | style:"lightCyan", 30 | format:value=>value.name ? `[Function ${value.name}]` : "()=>{...}" 31 | }, 32 | AsyncFunction: { 33 | style:"lightCyan", 34 | format:value=>value.name ? `[AsyncFunction ${value.name}]` : "async ()=>{...}" 35 | }, 36 | Error : { 37 | style:"red", 38 | format:e=>`${e.name.firstUpper()}('${e.message}')` 39 | }, 40 | Null : "darkGray", 41 | Undefined: "darkGray", 42 | Boolean : "cyan", 43 | Number : "yellow", 44 | String : "green", 45 | Class : { 46 | style:"lightCyan", 47 | format:value=>`[Class ${value.name}]` 48 | }, 49 | Instance : { 50 | style:"lightBlue", 51 | format:value=>`<${value.constructor.name.firstUpper()}()>` 52 | }, 53 | Date : { 54 | style:"lightBlue", 55 | format:(value)=>`${value.getFullYear()}-${value.getMonth()+1}-${value.getDate()} ${value.getHours()}:${value.getMinutes()}:${value.getSeconds()}` 56 | }, 57 | Symbol : "blue", 58 | RegExp : { 59 | style:"magenta", 60 | format:(value)=>`${value.toString()}` 61 | }, 62 | levels :{ 63 | align: true, // 是否自动对齐消息 64 | maxLineChars : 90, // 每行最大字符数 65 | memo : "darkGray", 66 | debug : "lightGray", 67 | info : "dim", 68 | warn : "yellow", 69 | error : "red", 70 | fatal : "red" 71 | } 72 | } 73 | 74 | module.exports = { 75 | DefaultOptions 76 | } -------------------------------------------------------------------------------- /src/header.plugin.js: -------------------------------------------------------------------------------- 1 | /** 2 | 3 | 用来显示一个横幅内容 4 | 5 | ╭──────────────────────────────────────────────────────────────────╮ 6 | | logsets │ 7 | │ https://www.xxx.com │ 8 | │ │ 9 | │ │ 10 | ╰──────────────────────────────────────────────────────────────────╯ 11 | 12 | const banner = logger.banner({}) 13 | 14 | 15 | 16 | */ 17 | /** 18 | * 19 | * @param {*} logger 20 | * @param {*} context 当前上下文配置参数 21 | */ 22 | module.exports = function(logger,context){ 23 | logger.h = logger.header = (message,...args)=>{ 24 | logger.log(logger.colorizeString(message,"bright,lightCyan"),...args) 25 | } 26 | } -------------------------------------------------------------------------------- /src/index.d.ts: -------------------------------------------------------------------------------- 1 | import type { Banner,BannerPluginOptions } from "./banner.plugin" 2 | import type { Task,TaskList,TaskListPluginOptions,CreateTaskDefine,CreateTasksOptions,TaskRunner, RunTasksOptions,TaskWrokerResult } from "./tasklist.plugin" 3 | import type { Progressbar,ProgressbarPluginOptions } from "./progressbar.plugin" 4 | import type { Tree,TreePluginOptions } from "./tree.plugin" 5 | import type { Table,TablePluginOptions} from "./table.plugin" 6 | import type { NamedColorStyles,ColorizedMethods } from "./colors" 7 | import type { ListPluginOptions,ListItem} from './list.plugin'; 8 | 9 | export type Class = new (...args: any[]) => any 10 | 11 | export type DataTypeColorizeOptions = string | { 12 | style?:NamedColorStyles 13 | format?:(value:any) => string 14 | } 15 | 16 | export interface LogsetsOptions{ 17 | indent?:string // 缩进 18 | singleQuotes?: boolean // 显示单引号 19 | template?:string // 模板 20 | compact?:boolean // 是否采用紧凑模式输出 21 | Array?:{ 22 | maxItems?:number // 数组最大长度,超过则显示省略号 23 | memo?: (value:any[]) => string // 当数组数量超过maxItems时,显示共几项的备注 24 | }, 25 | Object?:{ 26 | maxItems?:number // 成员数量,超过则显示省略号 27 | align?:boolean // 是否自动对齐 28 | memo?:(value:any[]) => string 29 | }, 30 | Null? : DataTypeColorizeOptions 31 | Undefined?: DataTypeColorizeOptions 32 | Boolean? : DataTypeColorizeOptions 33 | Number? : DataTypeColorizeOptions 34 | String? : DataTypeColorizeOptions 35 | Symbol? : DataTypeColorizeOptions 36 | AsyncFunction?: DataTypeColorizeOptions & { format?:(value:Function)=>string } 37 | Function? : DataTypeColorizeOptions & { format?:(value:Function)=>string } 38 | Error? : DataTypeColorizeOptions & { format?:(value:Error)=>string } 39 | Class? : DataTypeColorizeOptions & { format?:(value:Class) => string} 40 | Instance? : DataTypeColorizeOptions & { format:(value:object)=>string } 41 | Date? : DataTypeColorizeOptions & { format:(value:Date)=>string } 42 | RegExp? : DataTypeColorizeOptions & { format:(value:RegExp)=>string } 43 | 44 | levels? :{ 45 | align?: boolean // 是否自动对齐消息 46 | maxLineChars? : number // 每行最大字符数 47 | memo? : NamedColorStyles 48 | debug? : NamedColorStyles 49 | info? : NamedColorStyles 50 | warn? : NamedColorStyles 51 | error? : NamedColorStyles 52 | fatal? : NamedColorStyles 53 | } 54 | } 55 | 56 | export interface Logsets{ 57 | // 注册插件 58 | use(plugin:LogsetsPlugin):void 59 | 60 | //模板字符串着色输出 61 | log(message: string):void 62 | log(message: string,vars?:any[]):void 63 | log(message: string,vars?:Record):void 64 | log(message: string,...vars:any[]):void 65 | 66 | // 根据数据类型的不同进行着色后输出 67 | print(...args: any[]):void 68 | print(arg:any,options?:{end?:string,append?:string}):void 69 | print(arg1:any,arg2:any,options?:{end?:string,append?:string}):void 70 | print(arg1:any,arg2:any,arg3:any,options?:{end?:string,append?:string}):void 71 | 72 | // 带缩进格式和着色过的对象 73 | format(obj:Record | any[],options?:LogsetsOptions & {title?:string}):void 74 | 75 | // 日志输出 76 | debug(message:string,args?:any[] | Record,memo?:string):void 77 | info(message:string,args?:any[] | Record,memo?:string):void 78 | warn(message:string,args?:any[] | Record,memo?:string):void 79 | error(message:string,args?:any[] | Record,memo?:string):void 80 | fatal(message:string,args?:any[] | Record,memo?:string):void 81 | 82 | // 返回对JSON对象进行着色 83 | colorizeObject(arg:object | any[]):void 84 | 85 | // 返回对字符串进行着色 86 | colorizeString(arg:string,style?:string):void 87 | 88 | // 返回着色后的字符串 89 | getColorizer(style:string) : Colorizer 90 | 91 | // 对插值变量进行插值返回着色后的字符串 92 | getColorizedTemplate(template:string,vars?:any[] | Record) : string 93 | getColorizedTemplate(template:string,...vars:any[]) : string 94 | 95 | // 输出分割线 96 | separator(length?:number,char?:string):void 97 | 98 | options:LogsetsOptions 99 | 100 | colors:ColorizedMethods 101 | 102 | config(options:LogsetsOptions):Logsets 103 | 104 | banner(options?:BannerPluginOptions):Banner 105 | tasklist(options?:TaskListPluginOptions | string):TaskList['status']> 106 | createTasks(tasks:CreateTaskDefine[] | ((index:number,end:()=>void)=>CreateTaskDefine | Promise),options?:CreateTasksOptions):TaskRunner 107 | run(title:string | [string,...any],tasks:CreateTaskDefine[],options?:RunTasksOptions):Promise 108 | task(title:string,vars?:any[] | Record):Task 109 | task(title:string,...vars:any[]):Task 110 | task(title:string | [string,...any],worker:(task:Task)=>Promise):Promise 111 | progressbar(options?:ProgressbarPluginOptions):Progressbar 112 | tree(options?:TreePluginOptions):Tree 113 | table(options?:TablePluginOptions):Table 114 | list(title:string | [string,...any] ,items:(string|ListItem)[] | ((index:number)=>string|ListItem),options?:ListPluginOptions):Promise 115 | 116 | header(message: string):void 117 | header(message: string,vars?:any[]):void 118 | header(message: string,vars?:Record):void 119 | header(message: string,...vars:any[]):void 120 | h(message: string):void 121 | h(message: string,vars?:any[]):void 122 | h(message: string,vars?:Record):void 123 | h(message: string,...vars:any[]):void 124 | } 125 | 126 | 127 | 128 | // 返回对字符串进行着色 129 | export type Colorizer = (text:string)=>string 130 | export type LogsetsPlugin = (log:Logsets,options:Record)=>void; 131 | 132 | export declare var createLogsets: { 133 | (options?:LogsetsOptions):Logsets 134 | } & Logsets 135 | 136 | export default createLogsets 137 | 138 | 139 | export * from "./banner.plugin" 140 | export * from "./tasklist.plugin" 141 | export * from "./progressbar.plugin" 142 | export * from "./tree.plugin" 143 | export * from "./table.plugin" 144 | export * from "./colors" 145 | 146 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | const flexToolsString = require("flex-tools/string"); 2 | const { colorize,getColorizeFunction,colorizeString } = require("./colorize"); 3 | const { DefaultOptions } = require("./consts"); 4 | const { paddingCenter, isPlainFunction, consoleOutput, forEachInterpolateVars } = require("./utils"); 5 | const ansicolor = require("ansicolor"); 6 | const bannerPlugin = require("./banner.plugin"); 7 | const progressbarPlugin = require("./progressbar.plugin"); 8 | const tablePlugin = require("./table.plugin"); 9 | const tasklistPlugin = require("./tasklist.plugin"); 10 | const treePlugin = require("./tree.plugin"); 11 | const listPlugin = require("./list.plugin"); 12 | const headerPlugin = require("./header.plugin"); 13 | const { deepMerge } = require("flex-tools/object/deepMerge"); 14 | const { isPlainObject } = require("flex-tools/typecheck/isPlainObject"); 15 | 16 | const DEBUG = 'DEBUG' 17 | const INFO = 'INFO' 18 | const WARN = 'WARN' 19 | const ERROR = 'ERROR' 20 | const FATAL = 'FATAL' 21 | 22 | 23 | // 'foreground colors' 24 | // .red.green.yellow.blue.magenta.cyan.white.darkGray.black 25 | // 'light foreground colors' 26 | // .lightRed.lightGreen.lightYellow.lightBlue.lightMagenta.lightCyan.lightGray 27 | // 'background colors' 28 | // .bgRed.bgGreen.bgYellow.bgBlue.bgMagenta.bgCyan.bgWhite.bgDarkGray.bgBlack 29 | // 'light background colors' 30 | // .bgLightRed.bgLightGreen.bgLightYellow.bgLightBlue.bgLightMagenta.bgLightCyan.bgLightGray 31 | // 'styles' 32 | // .bright.dim.italic.underline.inverse // your platform should support italic 33 | 34 | 35 | 36 | /** 37 | * 根据模板字符串,输出着色后的内容 38 | * getColorizedTemplate("{a}+{b}={}") 39 | * getColorizedTemplate("{a}+{b}={}",[a,b,1]) 40 | * getColorizedTemplate("{a}+{b}={c}",{a:1,b:1,c:2}) 41 | * getColorizedTemplate("{#red a}+{b}={c}",{a:1,b:1,c:2}) #指定颜色 42 | * 43 | * getColorizedTemplate("{#red a} +{b}") == a+b 其中a使用红色 44 | * 45 | * 46 | * @param {*} template 模板字符串,如"this is {a}+{b}" 或者 "this is {}+{}" 47 | * @param {Array | object} vars 48 | * @returns 49 | */ 50 | function getColorizedTemplate(template,...args) { 51 | if(!template) return "" 52 | let logOptions = this 53 | let vars 54 | if(arguments.length===1 && Array.isArray(arguments[0])){ 55 | template = arguments[0][0] 56 | args= [...arguments][0].slice(1) 57 | } 58 | if (args.length === 1){ 59 | vars = typeof args[0] === 'function' ? args[0]() : 60 | (isPlainObject(args[0]) ? args[0] : (Array.isArray(args[0]) ? args[0] : [args[0]])) 61 | }else{ 62 | vars = args.map(arg=>{ 63 | if(typeof arg === 'function') { try{arg = arg()}catch{}} 64 | if(arg===undefined || arg===null){ 65 | return "" 66 | }else if(typeof(arg)=='object'){ 67 | try{return JSON.stringify(arg)}catch{return String(arg)} 68 | }else{ 69 | return arg 70 | } 71 | }) 72 | } 73 | 74 | if(Array.isArray(vars)){ 75 | let index = 0 76 | return forEachInterpolateVars(template,(word)=>{ 77 | let [color,varname] = word.startsWith("#") ? word.split(" ") : ["",word] 78 | varname = (varname || '').trim() 79 | color=color.substring(1) 80 | if(color){// 指定了颜色 81 | const shader = getColorizeFunction(color) 82 | return shader(index{ 89 | let [color,varname] = word.startsWith("#") ? word.split(" ") : ["",word] 90 | varname = (varname || '').trim() 91 | color=color.substring(1) 92 | if(color){// 指定了颜色 93 | const shader = getColorizeFunction(color) 94 | return shader(varname in vars ? vars[varname] : varname) 95 | }else{// 未指定颜色时根据数据类型的不同着色 96 | return colorize(varname in vars ? vars[varname] : varname,logOptions) 97 | } 98 | }) 99 | }else{ 100 | return template 101 | } 102 | } 103 | 104 | /** 105 | * 106 | * logOutput("DEBUG","this is a debug message",{a:1,b:2}) 变量插值 只有两个参数 107 | * logOutput("DEBUG","this is a debug message",[1,2,3]) 位置插值 108 | * 109 | * @param {*} level 110 | * @param {} args 111 | */ 112 | function logOutput(level, message, args, memo) { 113 | const now = new Date() 114 | const time = `${now.getHours().toString().padStart(2, "0")}:${now.getMinutes().toString().padStart(2, "0")}:${now.getSeconds().toString().padStart(2, "0")} ${now.getMilliseconds().toString().padStart(3, "0")}`.padEnd(12) 115 | const date = `${now.getFullYear()}/${(now.getMonth() + 1).toString().padStart(2, "0")}/${now.getDate().toString().padStart(2, "0")}` 116 | // 超过指定的字符后进行截断换行 117 | if (this.levels.maxLineChars > 0 && message.length > this.levels.maxLineChars) { 118 | //message=message.substr(0,this.levels.maxLineChars) 119 | } 120 | // 121 | if (this.levels.align) { 122 | message = message.replaceAll('\n', '\n' + new Array(33).fill(' ').join('')) 123 | } 124 | if (!Array.isArray(args)) args = [args] 125 | message = getColorizedTemplate(message, ...args) 126 | 127 | // 变量插值 128 | let result = this.template.params({ 129 | level: paddingCenter(level, 5), 130 | message, 131 | datetime: date + ' ' + time, 132 | time, 133 | date, 134 | ...isPlainObject(args[0]) ? {} : args[0] // 额外的上下插值变量 135 | }) 136 | let colorize = getColorizeFunction(this.levels[level.toLowerCase()]) 137 | let output = colorize(result) 138 | if (memo) { 139 | output += getColorizeFunction(this.levels.memo)(`(${memo})`) 140 | } 141 | consoleOutput(output) 142 | } 143 | 144 | 145 | 146 | 147 | /** 148 | * 149 | * logger.options({}).print(...) 150 | * logger.options({}).print(...) 151 | * 152 | * logger.styles({}).print(...) 153 | * 154 | * 在控制台打印输出着色后的内容 155 | * 使用方法同console.log,差别就在于输出内容的着色 156 | * this=logger.options 157 | **/ 158 | function print() { 159 | let args = [...arguments] 160 | let options = { end: "\n", append: " " } 161 | if (arguments.length >= 2 && isPlainObject(arguments[arguments.length - 1])) { 162 | args = args.slice(0, arguments.length - 1) 163 | Object.assign(options, arguments[arguments.length - 1]) 164 | } 165 | 166 | 167 | consoleOutput( 168 | ...Array.from(args).map(arg => { 169 | if (isPlainFunction(arg)) { 170 | try { 171 | arg = arg() 172 | } catch { 173 | arg = "ERROR" 174 | } 175 | } 176 | if (typeof (arg) === "string") { 177 | return arg 178 | } else { 179 | return colorize(arg, this) 180 | } 181 | }), 182 | options 183 | ) 184 | } 185 | 186 | function format(value, options = {}) { 187 | if (typeof value === 'function') value = value() 188 | const opts = Object.assign({ 189 | title:'' 190 | }, options) 191 | consoleOutput(colorize(value,deepMerge(this, opts))) 192 | } 193 | 194 | /** 195 | * this=logger.options 196 | * @param {*} template 197 | * @param {...any} args 198 | */ 199 | function printTemplate(message, ...args) { 200 | consoleOutput(getColorizedTemplate.call(this, message, ...args)) 201 | } 202 | 203 | /** 204 | * 205 | * const logger = createLogsets({}) 206 | * logger.log("{a}+{b}",{a:1,b:2}) // 变量插值输出 207 | * logger.log("{}+{}",1,2) // 位置参数插值输出 208 | * logger.print(...) 209 | * logger.config({compact:true}).print(...) // 设置全局配置 210 | * logger.set().print(...) // 设置临时配置 211 | * logger.log("this is {a}+{b}",{a:1,b:2}) 212 | * logger.info("this is {a}+{b}",{a:1,b:2}) 213 | * logger.warn("this is {a}+{b}",1,2) 214 | * 215 | * 216 | * @param {*} opts 217 | */ 218 | 219 | function createLogsets(opts = {}) { 220 | let options = deepMerge(DefaultOptions, opts) 221 | let log = {} 222 | log.log = (message, ...args) => printTemplate.call(options, message, ...args) 223 | log.print = (...args) => print.call(options, ...args) 224 | log.format = (...args) => format.call(options, ...args) 225 | log.debug = (...args) => logOutput.call(options, DEBUG, ...args) 226 | log.info = (...args) => logOutput.call(options, INFO, ...args) 227 | log.warn = (...args) => logOutput.call(options, WARN, ...args) 228 | log.error = (...args) => logOutput.call(options, ERROR, ...args) 229 | log.fatal = (...args) => logOutput.call(options, FATAL, ...args) 230 | log.use = (plugin) => plugin(log, options) 231 | log.colorizeObject = (arg) => colorize(arg, options) 232 | log.colorizeString = (text,style) => colorizeString(text,style) 233 | log.getColorizer = getColorizeFunction.bind(options) 234 | log.getColorizedTemplate = getColorizedTemplate.bind(options) 235 | log.separator = (n = 80, char = "─") => { consoleOutput(new Array(n).fill(char).join('')) } 236 | log.options = options 237 | log.colors = ansicolor 238 | log.config = opts => { 239 | if (isPlainObject(opts)) { 240 | Object.assign(options,deepMerge(options, opts)) 241 | } 242 | return log 243 | } 244 | // 引入所有内置插件 245 | log.use(bannerPlugin) 246 | log.use(progressbarPlugin) 247 | log.use(tablePlugin) 248 | log.use(tasklistPlugin) 249 | log.use(treePlugin) 250 | log.use(listPlugin) 251 | log.use(headerPlugin) 252 | return log 253 | } 254 | 255 | const defaultLogger = createLogsets() 256 | 257 | Object.assign(createLogsets,defaultLogger) 258 | 259 | module.exports = createLogsets -------------------------------------------------------------------------------- /src/list.plugin.d.ts: -------------------------------------------------------------------------------- 1 | import type { Logsets } from "." 2 | import type { DeepRequired } from "ts-essentials" 3 | 4 | export interface ListPluginOptions { 5 | indent? : string // 整体缩进 6 | showOrderNumber?:boolean // 是否显示序号 7 | title? : { 8 | emoji? : string // 默认的总结项图标 9 | style? : string, // 标题颜色 10 | }, 11 | item?:{ 12 | indent? :string 13 | type? : [string,string] 14 | style? : [string,string] 15 | } 16 | } 17 | 18 | export interface ListItem{ 19 | title:string | [string,string] 20 | description?:string 21 | type?:string | [string,string] 22 | style?:string | [string,string] 23 | 24 | } 25 | 26 | export interface List{ 27 | show(title:string | [string,...any],items:(string|ListItem)[],options?:ListPluginOptions):void 28 | } 29 | 30 | export declare const ListPlugin: { 31 | (logsets:Logsets,options:DeepRequired):List 32 | } 33 | 34 | export default ListPlugin 35 | -------------------------------------------------------------------------------- /src/list.plugin.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * 信息列表 4 | * 5 | * 🚀 接下来需要干的事:📃 6 | * | dsdsdsdsdsdd 7 | * | dsdsdsdsdsddds 8 | * | dsdsdsdsdsdd 9 | * └─ dsdsdsdsdsdd 10 | * 11 | * 12 | * 13 | */ 14 | 15 | const consoleOutput = require("./utils").consoleOutput; 16 | const {deepMerge} = require('flex-tools/object/deepMerge'); 17 | 18 | const DefaultListOptions = { 19 | grouped: true, // 是否启用分组 20 | groupSymbol : "♦", // 分组符号 21 | indent : "", // 整体缩进 22 | showOrderNumber: false, // 是否显示序号 23 | title : { 24 | emoji : "", // 默认的总结项图标 25 | style : "bright", // 标题颜色 26 | }, 27 | item:{ 28 | indent : " ", 29 | type : [ "√", "green" ], 30 | style : [ "", "darkGray" ], 31 | } 32 | } 33 | 34 | function createList(){ 35 | const logsets = this 36 | return { 37 | async show(title,items=[],options={}){ 38 | const opts = deepMerge({},DefaultListOptions,options) 39 | 40 | const getItem = typeof(items)=='function' ? items : (index)=>items[index] 41 | 42 | // 显示标题 43 | const colorizedTitle = logsets.getColorizer(opts.title.style)(logsets.getColorizedTemplate(title)) 44 | consoleOutput(`${opts.indent} ${opts.title.emoji} ${colorizedTitle}`) 45 | let index =0 46 | let isEndItem = false 47 | const setEnd = ()=>isEndItem = true 48 | while(true){ 49 | const item = await getItem(index++,setEnd) 50 | if(item == undefined) break 51 | if(typeof(item)=='string' || Array.isArray(item)){ 52 | const [message,...args] = Array.isArray(item) ? item : [item] 53 | logsets.log( 54 | opts.indent 55 | + opts.item.indent 56 | + (opts.grouped ? logsets.colors.darkGray(`${opts.groupSymbol}── `) : '') 57 | + logsets.colorizeString(message,"bright") 58 | ,...args) 59 | }else{ 60 | const itemTitle = [opts.indent,opts.item.indent] 61 | const itemData = Object.assign({ 62 | title: "", 63 | style: opts.item.style, 64 | type : opts.item.type, 65 | emoji: opts.item.emoji, 66 | },typeof(item)=='string' ? { title:item }: (Array.isArray(item) ? {title:item} : item )) 67 | 68 | if(!Array.isArray(itemData.style)) itemData.style = [itemData.style,opts.item.style[1]] 69 | if(itemData.style.length==1) itemData.style.push(opts.item.style[1]) 70 | if(!Array.isArray(itemData.type)) itemData.type = opts.item.type 71 | if(itemData.type.length==1) itemData.type.push(opts.item.type[1]) 72 | 73 | if(!itemData) return 74 | // 显示分组线 75 | if(opts.grouped){ 76 | if(isEndItem && !item.description){ 77 | itemTitle.push(logsets.colors.darkGray(`└─ `)) 78 | }else{ 79 | itemTitle.push(logsets.colors.darkGray(`│ `)) 80 | } 81 | } 82 | // 显示序号 83 | let order = `${String(index+1)}.`.padEnd(3) 84 | if(opts.showOrderNumber) itemTitle.push(logsets.getColorizer(itemData.style[0])(order)+" ") 85 | // 显示图标 86 | itemTitle.push(logsets.getColorizer(itemData.type[1])(itemData.type[0] + " ")) 87 | // 显示标题 88 | itemTitle.push(logsets.getColorizer(itemData.style[0])(logsets.getColorizedTemplate(itemData.title))) 89 | 90 | consoleOutput(itemTitle.join("")) 91 | 92 | // 显示描述信息 93 | if(item.description){ 94 | const [memo,...args] = Array.isArray(item.description) ? item.description : [item.description] 95 | const indent = " ".repeat(itemData.type[0].length + 1 + (opts.showOrderNumber ? order.length + 1 : 0)) 96 | memo.split("\n").forEach((line)=>{ 97 | consoleOutput( 98 | opts.indent 99 | + opts.item.indent 100 | + (opts.grouped ? 101 | logsets.colors.darkGray(isEndItem ? `└── ` : `│ `) 102 | : '' 103 | ) 104 | + indent 105 | + logsets.getColorizedTemplate(logsets.getColorizer(itemData.style[1])(line),...args)) 106 | }) 107 | } 108 | } 109 | 110 | } 111 | } 112 | } 113 | 114 | } 115 | 116 | 117 | 118 | 119 | 120 | /** 121 | * 122 | * @param {*} logsets 123 | * @param {*} context 当前上下文配置参数 124 | */ 125 | module.exports = function(logsets,context){ 126 | logsets.list = async (title,items,options)=>await createList.call(logsets,context).show(title,items,options) 127 | } -------------------------------------------------------------------------------- /src/progressbar.plugin.d.ts: -------------------------------------------------------------------------------- 1 | 2 | import type { NamedColorStyles } from "./colors" 3 | import type { Logsets } from "./" 4 | import type { DeepRequired } from "ts-essentials" 5 | 6 | export type PresetThemes = 'default' | 'red' | 'green' | 'blue' | 'yellow' | 'magenta' | 'cyan' 7 | 8 | export interface ProgressbarPluginOptions { 9 | title? : string // 显示标题 10 | theme? : PresetThemes | undefined // 一些预设好的主题配色, 11 | max? : number // 最大值 12 | min? : number // 最小值 13 | value? : number // 当前值 14 | dispaly? : string // 备注字符串,支持插值变量{value} {percent} {max} {min} 15 | width? : number // 进度条宽度 16 | background?: { // 进度条样式 17 | show? : boolean // 是否显示背景,默认显示,不显示时只显示进度条滑块 18 | style? : NamedColorStyles // 进度条样式 19 | char? : string 20 | }, 21 | slider? : { // 滑块字符 22 | style? : NamedColorStyles // 进度条样式 23 | char? : string 24 | } 25 | } 26 | 27 | export interface Progressbar { 28 | begin():void 29 | value(n:number):void 30 | end(note?:string):void 31 | stop(note?:string):void 32 | error(note?:string):void 33 | } 34 | 35 | export declare const ProgressbarPlugin: { 36 | (logsets:Logsets,options:DeepRequired):Progressbar 37 | } 38 | 39 | export default ProgressbarPlugin 40 | -------------------------------------------------------------------------------- /src/progressbar.plugin.js: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | import createLogger from "coloredLogger" 4 | import progressbar from "logsets/plugins/progressbar"" 5 | const logger = createLogger({...}) 6 | logger.use(progressbar) 7 | 8 | const progressbar = logger.progressbar({ 9 | max: 100, 10 | min: 0, 11 | dispaly:"", // 显 12 | width: 80, 13 | }) 14 | 15 | progressbar.next(1) == 向前移动指定值 16 | progressbar.begin() 17 | progressbar.value() 18 | progressbar.end() 19 | "✋✔️❌ 20 | 21 | */ 22 | 23 | const {deepMerge} = require('flex-tools/object/deepMerge'); 24 | const { hideCursor, showCursor } = require('./utils'); 25 | 26 | 27 | // 预设的主题样式 28 | const PresetThemes = { 29 | default:["bgWhite","bgDarkGray"], 30 | red:["bgRed","bgDarkGray"], 31 | green:["bgGreen","bgDarkGray"], 32 | blue:["bgBlue","bgDarkGray"], 33 | yellow:["bgYellow","bgDarkGray"], 34 | magenta:["bgMagenta","bgDarkGray"], 35 | cyan:["bgCyan","bgDarkGray"] 36 | } 37 | 38 | const DefaultProgressbarOptions = { 39 | title : "", // 显示标题 40 | theme : undefined, // 一些预设好的主题配色, 41 | max : 100, // 最大值 42 | min : 0, // 最小值 43 | value : 0, // 当前值 44 | dispaly : "{percent}%", // 备注字符串,支持插值变量{value} {percent} {max} {min} 45 | width : 60, // 进度条宽度 46 | background: { // 进度条样式 47 | show : true, // 是否显示背景,默认显示,不显示时只显示进度条滑块 48 | style : "bgDarkGray", // 进度条样式 49 | char : " " 50 | }, 51 | slider : { // 滑块字符 52 | style : "bgWhite", // 进度条样式 53 | char : " ", 54 | } 55 | } 56 | 57 | 58 | function getPercent(value,max){ 59 | return Math.round(value/max*10000)/100 60 | } 61 | 62 | function createProgressbar(context,options){ 63 | const logger = this 64 | let themeStyle = {},opts={} 65 | 66 | if(options.theme in PresetThemes){ 67 | themeStyle = { 68 | slider:{style:PresetThemes[options.theme][0]}, 69 | background:{style:PresetThemes[options.theme][1]} 70 | } 71 | } 72 | opts = deepMerge(DefaultProgressbarOptions,themeStyle) 73 | opts = deepMerge(opts,options) 74 | 75 | const sliderColorizer = logger.getColorizer(opts.slider.style) 76 | const bgColorizer = logger.getColorizer(opts.background.style) 77 | 78 | // 常规进度条,进度由value,min,max控制 79 | function renderProgressbar(value,note){ 80 | opts.value = value 81 | // 计算滑块大小,当value>min时显示为1,当value opts.min && value === sliderLenght) sliderLenght = 1 84 | if(value == opts.max && opts.max === sliderLenght ) sliderLenght = opts.width - 1 85 | let slider =new Array(sliderLenght).fill(opts.slider.char).join("") 86 | // 背景条 87 | let bg =new Array(opts.width - sliderLenght).fill(opts.background.char).join("") 88 | let emptyBg = new Array(opts.width).fill(opts.background.char).join("") 89 | 90 | // 显示文本 91 | let noteText = "" 92 | if(note){ 93 | noteText = note 94 | }else{ 95 | noteText = opts.dispaly.params({value,percent:getPercent(value,opts.max),max:opts.max,min:opts.min}) 96 | } 97 | // 清除进度条 98 | logger.print(`${opts.title}${bgColorizer(emptyBg)} ${noteText}`,{end:"\r"}) 99 | // 绘制进度条 100 | logger.print(`${opts.title}${sliderColorizer(slider)}${bgColorizer(bg)} ${noteText}`,{end:"\r"}) 101 | } 102 | 103 | return { 104 | begin(){ 105 | hideCursor() 106 | opts.value = opts.min 107 | }, 108 | value(n){ 109 | if(n>opts.max) n = opts.max 110 | if(ncreateProgressbar.call(logger,context,opts) 141 | } 142 | -------------------------------------------------------------------------------- /src/stringify.js: -------------------------------------------------------------------------------- 1 | const isRegexp = require("./utils").isRegexp; 2 | const getOwnEnumPropSymbols = require("./utils").getOwnEnumPropSymbols; 3 | const { isPlainObject } = require('flex-tools/typecheck/isPlainObject'); 4 | 5 | 6 | /** 7 | * 获取对象的键名对齐的长度 8 | */ 9 | function getObjectKeysMaxLength(keys){ 10 | return keys.reduce((preLen,key)=>{ 11 | return Math.max(preLen,key.length); 12 | },0) 13 | } 14 | 15 | module.exports = function stringifyObject(input, options, pad) { 16 | const seen = []; 17 | return (function stringify(input, options = {}, pad = '') { 18 | const indent = options.indent || '\t'; 19 | 20 | let tokens; 21 | if (options.inlineCharacterLimit === undefined) { 22 | tokens = { 23 | newline: '\n', 24 | newlineOrSpace: '\n', 25 | pad, 26 | indent: pad + indent, 27 | }; 28 | } else { 29 | tokens = { 30 | newline: '@@__STRINGIFY_OBJECT_NEW_LINE__@@', 31 | newlineOrSpace: '@@__STRINGIFY_OBJECT_NEW_LINE_OR_SPACE__@@', 32 | pad: '@@__STRINGIFY_OBJECT_PAD__@@', 33 | indent: '@@__STRINGIFY_OBJECT_INDENT__@@', 34 | }; 35 | } 36 | 37 | const expandWhiteSpace = string => { 38 | if (options.inlineCharacterLimit === undefined) { 39 | return string; 40 | } 41 | 42 | const oneLined = string 43 | .replace(new RegExp(tokens.newline, 'g'), '') 44 | .replace(new RegExp(tokens.newlineOrSpace, 'g'), ' ') 45 | .replace(new RegExp(tokens.pad + '|' + tokens.indent, 'g'), ''); 46 | 47 | if (oneLined.length <= options.inlineCharacterLimit) { 48 | return oneLined; 49 | } 50 | 51 | return string 52 | .replace(new RegExp(tokens.newline + '|' + tokens.newlineOrSpace, 'g'), '\n') 53 | .replace(new RegExp(tokens.pad, 'g'), pad) 54 | .replace(new RegExp(tokens.indent, 'g'), pad + indent); 55 | }; 56 | 57 | if (seen.includes(input)) { 58 | return '"[Circular]"'; 59 | } 60 | 61 | if ( 62 | input === null 63 | || input === undefined 64 | || typeof input === 'number' 65 | || typeof input === 'boolean' 66 | || typeof input === 'symbol' 67 | ) { 68 | return String(input); 69 | }else if(isRegexp(input)){ 70 | return options.transform(null, null , input) 71 | }else if(typeof input === 'function'){ 72 | return options.transform(null, null , input) 73 | } 74 | 75 | if (Array.isArray(input)) { 76 | if (input.length === 0) { 77 | return '[]'; 78 | } 79 | seen.push(input); 80 | let returnValue 81 | let addOverflowTag = options.Array.maxItems>0 && input.length > options.Array.maxItems 82 | if(options.Array.compact===true || (options.compact===true && options.Array.compact!==false)){ 83 | returnValue = '[ '+ input.map((element, i) => { 84 | if(addOverflowTag && i===options.Array.maxItems){ 85 | return "..."; 86 | }else if(i>options.Array.maxItems){ 87 | return "" 88 | } 89 | const eol = input.length - 1 === i ? "" : ', ' 90 | let value = stringify(element, options, pad ); 91 | if (options.transform) { 92 | value = options.transform(input, i, value); 93 | } 94 | return value + eol; 95 | }).join('') + ' ]'; 96 | }else{ 97 | returnValue = '[' + tokens.newline + input.map((element, i) => { 98 | let eol = input.length - 1 === i ? tokens.newline : ',' + tokens.newlineOrSpace; 99 | 100 | if(addOverflowTag && i===options.Array.maxItems){ 101 | return tokens.indent + "..."+ tokens.newline 102 | }else if(i>options.Array.maxItems){ 103 | return "" 104 | } 105 | 106 | let value = stringify(element, options, pad + indent); 107 | if (options.transform) { 108 | value = options.transform(input, i, value); 109 | } 110 | return tokens.indent + value + eol; 111 | }).join('') + tokens.pad + ']'; 112 | } 113 | if(addOverflowTag && typeof(options.Array.memo)==="function"){ 114 | returnValue+=options.Array.memo(input) 115 | } 116 | seen.pop(); 117 | 118 | return expandWhiteSpace(returnValue); 119 | } 120 | 121 | if (isPlainObject(input)) { 122 | let objectKeys = [ 123 | ...Object.keys(input), 124 | ...getOwnEnumPropSymbols(input), 125 | ]; 126 | 127 | if (options.filter) { 128 | // eslint-disable-next-line unicorn/no-array-callback-reference, unicorn/no-array-method-this-argument 129 | objectKeys = objectKeys.filter(element => options.filter(input, element)); 130 | } 131 | 132 | if (objectKeys.length === 0) { 133 | return '{}'; 134 | } 135 | 136 | seen.push(input); 137 | let returnValue 138 | let addOverflowTag = options.Object.maxItems>0 && objectKeys.length > options.Object.maxItems 139 | if(options.Object.compact===true || (options.compact===true && options.Object.compact!==false)){ 140 | returnValue = '{' + objectKeys.map((element, i) => { 141 | if(addOverflowTag && i===options.Object.maxItems){ 142 | return "..." 143 | }else if(i>options.Object.maxItems){ 144 | return "" 145 | } 146 | const eol = objectKeys.length - 1 === i ? " " : ', ' ; 147 | const isSymbol = typeof element === 'symbol'; 148 | const isClassic = !isSymbol && /^[a-z$_][$\w]*$/i.test(element); 149 | const key = isSymbol || isClassic ? element : stringify(element, options); 150 | 151 | let value = stringify(input[element], options, pad ); 152 | if (options.transform) { 153 | value = options.transform(input, element, value); 154 | } 155 | return String(key) + ': ' + value + eol; 156 | }).join('') + '}'; 157 | }else{ 158 | let keyPadding = getObjectKeysMaxLength(objectKeys)+2 159 | returnValue = '{' + tokens.newline + objectKeys.map((element, i) => { 160 | const eol = objectKeys.length - 1 === i ? tokens.newline : ',' + tokens.newlineOrSpace; 161 | 162 | if(addOverflowTag && i===options.Object.maxItems){ 163 | return tokens.indent + "..." + tokens.newline 164 | }else if(i>options.Object.maxItems){ 165 | return "" 166 | } 167 | const isSymbol = typeof element === 'symbol'; 168 | const isClassic = !isSymbol && /^[a-z$_][$\w]*$/i.test(element); 169 | const key =String(isSymbol || isClassic ? element : stringify(element, options)) 170 | 171 | let value = stringify(input[element], options, pad + indent); 172 | if (options.transform) { 173 | value = options.transform(input, element, value); 174 | } 175 | // 对齐填充 176 | try{ 177 | const itemPadding =options.Object.align ? new Array(keyPadding - key.length).fill(" ").join("") : "" 178 | return tokens.indent + key + itemPadding + ' : ' + value + eol; 179 | 180 | }catch(e){ 181 | console.log(e) 182 | } 183 | }).join('') + tokens.pad + '}'; 184 | } 185 | if(addOverflowTag && typeof(options.Array.memo)==="function"){ 186 | returnValue+=options.Object.memo(objectKeys) 187 | } 188 | seen.pop(); 189 | 190 | return expandWhiteSpace(returnValue); 191 | } 192 | 193 | input = String(input).replace(/[\r\n]/g, x => x === '\n' ? '\\n' : '\\r'); 194 | 195 | if (options.singleQuotes === false) { 196 | input = input.replace(/"/g, '\\"'); 197 | return `"${input}"`; 198 | } 199 | 200 | input = input.replace(/\\?'/g, '\\\''); 201 | return `'${input}'`; 202 | })(input, options, pad); 203 | } 204 | -------------------------------------------------------------------------------- /src/table.plugin.d.ts: -------------------------------------------------------------------------------- 1 | import type { NamedColorStyles } from "./colors" 2 | import type { Logsets } from "./" 3 | import type { DeepRequired } from "ts-essentials" 4 | 5 | export interface TableColumnAttrs { 6 | align?:"left" | "center" | "right" 7 | width?:"auto" | number 8 | color?:NamedColorStyles 9 | } 10 | export enum TableRowType{ 11 | ROW = 0, 12 | SEPARATOR = 1, 13 | SUMMARY = 2 14 | } 15 | export interface TablePluginOptions { 16 | colorize?:number, // 是否需要颜色化 0-禁用着色,1-简单着色 2-对表单元里面的对象和数组进行着色,需要额外的计算 17 | grid?:number, // 是否显示网络线,0-不显示,1-显示垂直线,2-同时显示垂直和水平线 18 | maxColWidth?:number // 最大列宽,超过会显示省略号 19 | colPadding?:string // 列间距 20 | header?:{ 21 | style?:NamedColorStyles // 表头颜色样式,默认高亮 22 | }, 23 | footer?:{ 24 | style?:NamedColorStyles // 表尾颜色样式 25 | merge?:true // 合并行 26 | align?:"left" | "center" | "right" // 当合并时对齐方式 27 | }, 28 | summary?:{ // 默认汇总行配置 29 | style?:NamedColorStyles // 汇总颜色样式 30 | align?:"left" | "center" | "right" // 汇总对齐方式 31 | }, 32 | cols?:TableColumnAttrs[] // 列定义 = [{align:"center",width:"auto",color:"auto"},...] 33 | 34 | } 35 | export interface TableColDefine{ 36 | title?:string 37 | style?:NamedColorStyles 38 | align?:"left" | "center" | "right" 39 | } 40 | export interface Table { 41 | addRow(...cells: any[]): Table; 42 | addHeader(...cells: string[]): Table; 43 | addFooter(cells: string[], options?: { merge?: boolean }): Table; 44 | addSeparator(): Table; 45 | addSummary(cells: any[], options?: { merge?: boolean }): Table; 46 | render(): void; 47 | } 48 | 49 | export declare const TablePlugin: { 50 | (logsets:Logsets,options:DeepRequired):Table 51 | } 52 | 53 | export default TablePlugin 54 | -------------------------------------------------------------------------------- /src/tasklist.plugin.d.ts: -------------------------------------------------------------------------------- 1 | import type { NamedColorStyles } from "./colors" 2 | import type { Logsets } from "./" 3 | import type { DeepRequired } from "ts-essentials" 4 | 5 | export interface TaskListStatus{ 6 | style?:NamedColorStyles 7 | symbol?:string 8 | note?:string 9 | } 10 | 11 | export interface TaskListPluginOptions{ 12 | indent? : string // 列表缩进字符 13 | style? : NamedColorStyles // 标题样式 14 | width? : number // 列表总宽度 15 | refInterval?:number // 列表项渲染间隔 16 | progressbar?:{ 17 | style?:NamedColorStyles // 进度条样式 18 | char?:string // 进度条字符 19 | }, 20 | status?:Record 21 | } 22 | export type CustomTaskList = { 23 | [key in keyof CUSTOM_STATUS]: (note?:string)=>void 24 | } 25 | export interface Task { 26 | running():boolean 27 | start():void 28 | end():void 29 | note(info:string | string[]):void 30 | running(note?:string | string[]):void 31 | complete(note?:string | string[]):void 32 | error(note?:string | string[]):void 33 | fail(note?:string | string[]):void 34 | skip(note?:string | string[]):void 35 | stop(note?:string | string[]):void 36 | todo(note?:string | string[]):void 37 | ignore(note?:string | string[]):void 38 | cancel(note?:string | string[]):void 39 | } 40 | 41 | export type InlineTaskStatus = 'ignore' | 'running' | 'complete' | 'error' | 'abort' | 'fail' | 'cancel' | 'skip' | 'stop' | 'todo' 42 | 43 | export type TaskWrokerResult = void | string | InlineTaskStatus | Uppercase | [InlineTaskStatus,string] | [Uppercase,string] 44 | 45 | export type TaskList = { 46 | add(title:string,vars?:any[] | Record):Task 47 | add(title:string,...vars:any[]):Task 48 | addGroup(title:string,...vars:any[]):void 49 | addMemo(title:string,...vars:any[]):void 50 | done(title:string,...vars:any[]):void 51 | // 运行函数任务 52 | run(title:string,vars:any[] | Record,worker:()=>Promise,options?:{catchError?:boolean,showErrorStack?:boolean}):TaskWrokerResult 53 | run(title:string,worker:()=>Promise,options?:{catchError?:boolean,showErrorStack?:boolean}):TaskWrokerResult 54 | separator(char?:string):void 55 | 56 | } & Task & CustomTaskList 57 | 58 | 59 | 60 | export declare const TaskListPlugin: { 61 | (logsets:Logsets,options:DeepRequired):TaskList 62 | } 63 | 64 | 65 | export type ExecuteContext = { 66 | task:Task 67 | } & Record 68 | 69 | 70 | // createTasks类型 71 | export type CreateTaskDefine = { 72 | title:string | [string,...rest:any[]] 73 | execute?:(context?:ExecuteContext)=>Promise, 74 | error?:string | (( 75 | {error}:{error?:Error} 76 | )=>Awaited) 77 | } 78 | export interface CreateTasksOptions{ 79 | abortOnError?:boolean 80 | ignoreErrors?:boolean 81 | } 82 | 83 | export type TaskRunner={ 84 | run(title?:string | any[],context?:ExecuteContext):any 85 | } 86 | 87 | 88 | export type RunTasksOptions = CreateTasksOptions & { 89 | context?:Parameters[1], 90 | } 91 | 92 | export default TaskListPlugin 93 | -------------------------------------------------------------------------------- /src/tree.plugin.d.ts: -------------------------------------------------------------------------------- 1 | 2 | import type { NamedColorStyles } from "./colors" 3 | import type { Logsets } from "./" 4 | import type { DeepRequired } from "ts-essentials" 5 | 6 | export interface TreePluginOptions { 7 | root?:string 8 | width?: number // 当显示备注信息时,树的总宽度 9 | indent?:string // 缩进字符 10 | node?:{ 11 | style?:NamedColorStyles // 默认节点样式 12 | }, 13 | note?:{ // 节点备注 14 | enable?:boolean 15 | style?:NamedColorStyles // 文本样式 16 | char?:string 17 | } 18 | } 19 | 20 | export interface Tree { 21 | addNode(text:string, options?:{style?:string; boolean?:boolean,note?:string}):Tree 22 | beginChildren():Tree 23 | endChildren():Tree 24 | 25 | } 26 | 27 | export declare const TreePlugin: { 28 | (logsets:Logsets,options:DeepRequired):Tree 29 | } 30 | 31 | export default TreePlugin 32 | -------------------------------------------------------------------------------- /src/tree.plugin.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 显示列表 3 | * 4 | * import createLogger from "logsets" 5 | * import treePlugin from "logsets/plugin/tree" 6 | * const logger = createLogger({...}) 7 | * logger.use(treePlugin) 8 | * 9 | * tree = logger.tree({ 10 | * root: "根节点", * 11 | * symbol:"-", // 节点符号 12 | * lineStyle:true, // 是否显示树形结构线,0-不显示,1-显示 13 | * render:"immediate", // 渲染模式,immediate=实时渲染,manual=手动调用render渲染 14 | * note:{ 15 | * style:"" // 备注样式 16 | * } 17 | * ", 18 | * }) 19 | * tree.addNode("item1",{style:"",note:""}) // 添加节点备注 20 | * tree.addNode("item1","123Bytes") 21 | * tree.addNode("item1") 22 | * tree.beginChildren() // 增加子节点 23 | * tree.addNode("item2") 24 | * tree.addNode("item2") 25 | * tree.endChildren() 26 | * 27 | * tree.render() // 当options.render="once"时,只会渲染一次 28 | * 29 | * ROOT 30 | * ├── base.json........................................备注 31 | * ├── package.json.....................................124Bytes 32 | * ├── README.md........................................124Bytes 33 | * | ├── base.json....................................124Bytes 34 | * | ├── package.json 35 | * | ├── README.md 36 | * | | ├── README.md 37 | * | | ├── base.json 38 | * | | ├── package.json 39 | * | | ├── README.md 40 | * 41 | 42 | 43 | */ 44 | 45 | const { consoleOutput, getStringWidth } = require('./utils') 46 | const {deepMerge} = require('flex-tools/object/deepMerge'); 47 | 48 | const DefaultTreeOptions = { 49 | root: "Root", 50 | width: 60, // 当显示备注信息时,树的总宽度 51 | indent:" ", // 缩进字符 52 | node:{ 53 | style:"white", // 默认节点样式 54 | }, 55 | note:{ // 节点备注 56 | enable:false, 57 | style:"darkGrey", // 文本样式 58 | char:".", 59 | } 60 | } 61 | 62 | 63 | //const symbols = "√×●" 64 | 65 | function createTree(context,options){ 66 | const logger = this 67 | let opts = deepMerge(DefaultTreeOptions,options) 68 | const colorizer = logger.getColorizer 69 | let curLevel = 0 // 当前层级 70 | 71 | function renderRoot(){ 72 | consoleOutput(colorizer("bright")(`${opts.indent}${opts.root}`)) 73 | curLevel++ 74 | } 75 | function renderNode(text,options={}){ 76 | const {style,last} =Object.assign({},opts.node,options) 77 | const levelsIndent = new Array(curLevel-1).fill("│ ").join("") 78 | const treeLine =last ? "└── " : "├── " 79 | let note = options.note || "" 80 | const coloredText = logger.getColorizedTemplate(text) 81 | if(opts.note.enable){ 82 | const noteOffset = opts.width - levelsIndent.length - treeLine.length - getStringWidth(coloredText) 83 | note = colorizer("darkGray")(new Array(noteOffset).fill(opts.note.char).join("")) + colorizer(opts.note.style)(note) 84 | }else{ 85 | note="" 86 | } 87 | consoleOutput(`${opts.indent}${levelsIndent}${treeLine}${colorizer(style)(coloredText)}${note}`) 88 | } 89 | renderRoot() 90 | return { 91 | addNode(text,options={}){ 92 | renderNode(text,options) 93 | return this 94 | }, 95 | beginChildren(){ 96 | curLevel++ 97 | return this 98 | }, 99 | endChildren(){ 100 | curLevel-- 101 | if(curLevel===0) curLevel=0 102 | return this 103 | } 104 | } 105 | 106 | 107 | 108 | } 109 | /** 110 | * 111 | * @param {*} log 112 | * @param {*} context 当前上下文配置参数 113 | */ 114 | module.exports = function(logger,context){ 115 | logger.tree = (opts={})=>createTree.call(logger,context,opts) 116 | } -------------------------------------------------------------------------------- /src/utils.js: -------------------------------------------------------------------------------- 1 | const ansicolor =require("ansicolor"); 2 | const { isEscaped,strip } = ansicolor 3 | const { isPlainObject,} = require('flex-tools/typecheck/isPlainObject') 4 | const { isAsyncFunction } = require('flex-tools/typecheck/isAsyncFunction') 5 | 6 | function isClass(cls){ 7 | let result = false 8 | if (typeof(cls) === 'function' && cls.prototype) { 9 | try { 10 | cls.arguments && cls.caller; 11 | } catch(e) { 12 | result=true 13 | } 14 | } 15 | return result; 16 | } 17 | function isClassInstance(obj){ 18 | return obj.constructor && obj.constructor.toString().startsWith("class") 19 | } 20 | 21 | 22 | // 获取所有给定对象所有自有的Symbol值的可枚举属性的数组 23 | function getOwnEnumPropSymbols(obj){ 24 | return Object 25 | .getOwnPropertySymbols(obj) 26 | .filter((keySymbol) => Object.prototype.propertyIsEnumerable.call(obj, keySymbol)); 27 | } 28 | 29 | /** 30 | * 当参数大于2个,并且最后一个参数是一个{}时,视为控制配置参数 31 | * 32 | * consoleOutput(1,{ 33 | * append:" ", // 默认每一个参数后面添加的字符串,默认空格 34 | * end: "\n" // 结束字符 35 | * }) 36 | * 37 | * @param {...any} texts 38 | */ 39 | function consoleOutput(...texts){ 40 | let options = { 41 | append:" ", 42 | end:"\n" //换行符 43 | } 44 | if(texts.length>=2 && isPlainObject(texts[texts.length-1])){ 45 | Object.assign(options,texts.pop()) 46 | } 47 | texts.forEach(text=>{ 48 | process.stdout.write(text+options.append) 49 | }) 50 | if(options.end){ 51 | process.stdout.write(options.end) 52 | } 53 | } 54 | function newline(){ 55 | process.stdout.write("\n") 56 | } 57 | function hideCursor(){ 58 | process.stdout.write(`${"\x1b"}[?25l`) 59 | } 60 | function showCursor(){ 61 | process.stdout.write(`${"\x1b"}[?25h`) 62 | } 63 | 64 | /** 65 | * 字符串居中填充 66 | * paddingCenter("a",5,"*") == "**a**" 67 | * 68 | * @param {*} s 69 | * @param {*} width 70 | * @param {*} fillChar 71 | * @returns 72 | */ 73 | function paddingCenter(s,width,fillChar=" ") { 74 | let len = getStringWidth(String(s)) 75 | let llength=parseInt((width-len)/2) 76 | // if(llength<=0) return 77 | try{ 78 | return new Array(llength).fill(fillChar).join("")+s+new Array(width-len-llength).fill(fillChar).join("") 79 | }catch(e){ 80 | return s 81 | } 82 | } 83 | function paddingStart(s,width,fillChar=" ") { 84 | let len = getStringWidth(String(s)) 85 | // if(len>=width) return s 86 | try{ 87 | return new Array(width-len).fill(fillChar).join("") + s 88 | }catch(e){ 89 | return s 90 | } 91 | 92 | } 93 | function paddingEnd(s,width,fillChar=" ") { 94 | let len = getStringWidth(String(s)) 95 | // if(len>=width) return s 96 | try{ 97 | return s + new Array(width-len).fill(fillChar).join("") 98 | }catch{ 99 | return s 100 | } 101 | } 102 | // 获取左边连续的字符串 103 | function getLeftRepeatChars(s,char=" "){ 104 | let r = [] 105 | for(let i=0;i<=s.length;i++){ 106 | if(s[i]!==char) break 107 | r.push(char) 108 | } 109 | return r.join("") 110 | } 111 | function getRightRepeatChars(s,char=" "){ 112 | let r = [] 113 | for(let i=s.length-1;i>=0;i--){ 114 | if(s[i]!==char) break 115 | r.push(char) 116 | } 117 | return r.join("") 118 | } 119 | 120 | function repeatChars(count,char){ 121 | return new Array(count).fill(char).join("") 122 | } 123 | 124 | function getSignalLineWidth(s){ 125 | let str = String(s) 126 | var realLength = 0, len = str.length, charCode = -1; 127 | for (var i = 0; i < len; i++) { 128 | charCode = str.charCodeAt(i); 129 | if (charCode >= 0 && charCode <= 128) realLength += 1; 130 | else realLength += 2; 131 | } 132 | return realLength; 133 | } 134 | 135 | // 获取字符串长度,中文按2个字符表示,多行字符串取其中最长的一行 136 | // 如果是着色过的会自动去掉着色再计算 137 | function getStringWidth(str){ 138 | if(isPlainObject(str) || Array.isArray(str)) str = JSON.stringify(str) 139 | if(typeof(str)!=="string") str = String(str) 140 | if(isEscaped(str)) str = strip(str) 141 | return Math.max(...String(str).split("\n").map(s=>getSignalLineWidth(s)) ) 142 | } 143 | 144 | // 返回指定内空是否是基本的数据类型 145 | function isBaseDataType(value){ 146 | return !(Array.isArray(value) || isPlainObject(value)) 147 | } 148 | 149 | // 截取字符串,超过显示省略号,支持中文 150 | function cutstr(str, len) { 151 | if(getStringWidth(str)<=len) return str 152 | if(len<4) len = 4 153 | var str_length = 0; 154 | var str_len = 0; 155 | let str_cut = new String(); 156 | str_len = str.length; 157 | for (var i = 0; i < str_len; i++) { 158 | let a = str.charAt(i); 159 | str_length++; 160 | if (escape(a).length > 4) { 161 | str_length++;//中文字符的长度经编码之后大于4 162 | } 163 | str_cut = str_cut.concat(a); 164 | if (str_length >= len-3) { 165 | str_cut = str_cut.concat("..."); 166 | if(getSignalLineWidth(str_cut)>len) str_cut = str_cut.substr(0,str_cut.length-1) 167 | return str_cut; 168 | } 169 | } 170 | //如果给定字符串小于指定长度,则返回源字符串; 171 | if (str_length < len) { 172 | return str; 173 | } 174 | } 175 | /** 176 | * 截断字符串 177 | * truncateString("123456789",3,"*") == "123*456*789" 178 | * truncateString("123456789",3) == 第一行:"123 第二行:456 第三行:*789 179 | * @param {*} s 180 | * @param {*} width 181 | * @param {*} fillChar 182 | */ 183 | function truncateString(s,width=80,fillChar="\n"){ 184 | if(s.length<=width) return s 185 | let lines=[] , index = 0 186 | while(index{ 223 | if(typeof(callback)=="function"){ 224 | return callback(word,match) 225 | }else{ 226 | return word 227 | } 228 | }) 229 | }else{ 230 | return text 231 | } 232 | } 233 | 234 | 235 | 236 | module.exports = { 237 | isClass, 238 | isClassInstance, 239 | getOwnEnumPropSymbols, 240 | consoleOutput, 241 | newline, 242 | hideCursor, 243 | showCursor, 244 | paddingCenter, 245 | paddingStart, 246 | paddingEnd, 247 | getLeftRepeatChars, 248 | getRightRepeatChars, 249 | repeatChars, 250 | getSignalLineWidth, 251 | getStringWidth, 252 | isBaseDataType, 253 | cutstr, 254 | truncateString, 255 | getDataType, 256 | isPlainFunction, 257 | isRegexp, 258 | forEachInterpolateVars 259 | 260 | 261 | 262 | } -------------------------------------------------------------------------------- /src/utils/index.ts: -------------------------------------------------------------------------------- 1 | export {} --------------------------------------------------------------------------------