From 6f9f5b6e7438dbd308051e52d37580d820a82bf2 Mon Sep 17 00:00:00 2001 From: VictorBo Date: Thu, 4 Jul 2024 14:56:52 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=A2=9E=E5=8A=A0@xpart-utils/string?= =?UTF-8?q?=E7=9A=84template=E6=96=B9=E6=B3=95=EF=BC=8C=E4=BB=A5=E6=94=AF?= =?UTF-8?q?=E6=92=91log=E5=87=BD=E6=95=B0=E5=AE=9E=E7=8E=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .npmrc | 3 + eslint.config.js | 7 ++ packages/is/__test__/isHttp.test.ts | 4 - packages/is/src/isEmptyString.ts | 2 +- packages/is/src/isHttp.ts | 1 - packages/log/__test__/log.test.ts | 14 +-- packages/log/package.json | 3 + packages/log/src/log.ts | 138 +++++++++++----------- packages/object/src/deepCopy.ts | 2 +- packages/string/__test__/template.test.ts | 16 +++ packages/string/package.json | 3 + packages/string/src/template.ts | 42 +++++++ pnpm-lock.yaml | 21 +++- 13 files changed, 172 insertions(+), 84 deletions(-) create mode 100644 .npmrc create mode 100644 packages/string/__test__/template.test.ts create mode 100644 packages/string/src/template.ts diff --git a/.npmrc b/.npmrc new file mode 100644 index 0000000..d8b8386 --- /dev/null +++ b/.npmrc @@ -0,0 +1,3 @@ +strict-peer-dependencies=false +auto-install-peers=true +shamefully-hoist=true diff --git a/eslint.config.js b/eslint.config.js index 80a9953..dde1af0 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -20,4 +20,11 @@ export default defineConfig( 'unicorn/error-message': 'off', }, }, + // @xparcai-utils/log 包取消禁用console + { + files: ['packages/log/**/*.*'], + rules: { + 'no-console': 'off', + }, + }, ) diff --git a/packages/is/__test__/isHttp.test.ts b/packages/is/__test__/isHttp.test.ts index e721b38..fc77258 100644 --- a/packages/is/__test__/isHttp.test.ts +++ b/packages/is/__test__/isHttp.test.ts @@ -12,9 +12,5 @@ describe('@xparcai-utils/is', () => { isHttp.setCondition('s') expect(isHttp('http://xparcai.com')).toBe(false) expect(isHttp('https://xparcai.com')).toBe(true) - - // 设置默认值调用 - expect(isHttp.setCondition('s')('http://xparcai.com')).toBe(false) - expect(isHttp.setCondition('s')('https://xparcai.com')).toBe(true) }) }) diff --git a/packages/is/src/isEmptyString.ts b/packages/is/src/isEmptyString.ts index 1aa26d5..e351941 100644 --- a/packages/is/src/isEmptyString.ts +++ b/packages/is/src/isEmptyString.ts @@ -5,6 +5,6 @@ import { isString } from './isString' * @param str 某个字符串 * @returns 是否为空字符串 */ -export function isEmptyString(str: unknown):boolean { +export function isEmptyString(str: unknown): boolean { return isString(str) && str !== '' } diff --git a/packages/is/src/isHttp.ts b/packages/is/src/isHttp.ts index 9053025..525d3c9 100644 --- a/packages/is/src/isHttp.ts +++ b/packages/is/src/isHttp.ts @@ -23,5 +23,4 @@ export function isHttp(url: string, condition: Condition = _condition): boolean */ isHttp.setCondition = function (condition: Condition = _condition) { _condition = condition - return isHttp } diff --git a/packages/log/__test__/log.test.ts b/packages/log/__test__/log.test.ts index 11f1044..c5ee60f 100644 --- a/packages/log/__test__/log.test.ts +++ b/packages/log/__test__/log.test.ts @@ -18,20 +18,20 @@ describe('@xparcai-utils/log', () => { const data = [ { id: 1, name: 'Alice', age: 25 }, { id: 2, name: 'Bob', age: 30 }, - { id: 3, name: 'Charlie', age: 35 } + { id: 3, name: 'Charlie', age: 35 }, ] const columns = [ { - title: 'ID', - key: 'id' + title: 'ID', + key: 'id', }, { - title: '名字', - key: 'name' + title: '名字', + key: 'name', }, { - title: '年龄', - key: 'age' + title: '年龄', + key: 'age', }, ] expect(log.table(data, columns)) diff --git a/packages/log/package.json b/packages/log/package.json index 7d69382..97f40b3 100644 --- a/packages/log/package.json +++ b/packages/log/package.json @@ -52,5 +52,8 @@ "clean": "run-s clean:*", "clean:dist": "rimraf dist", "clean:deps": "rimraf node_modules" + }, + "devDependencies": { + "@xparcai-utils/is": "workspace:*" } } diff --git a/packages/log/src/log.ts b/packages/log/src/log.ts index a8aafa3..e8d1f38 100644 --- a/packages/log/src/log.ts +++ b/packages/log/src/log.ts @@ -1,19 +1,20 @@ import { isEmptyArray, isEmptyString } from '@xparcai-utils/is' -type textOption = { - color?: string, - borderColor?: string, - borderStyle?: string, - borderWidth?: string, - fontSize?: string, - padding?: string, - borderRadius?: string, + +interface textOption { + color?: string + borderColor?: string + borderStyle?: string + borderWidth?: string + fontSize?: string + padding?: string + borderRadius?: string backgroundColor?: string } -type tableColum = { +interface tableColum { title: string key: string } -type tableOption = { +interface tableOption { color?: string backgroundColor?: string padding?: string @@ -24,12 +25,12 @@ function prettyLog() { `%c ${title} %c ${text} %c`, `background:${option.backgroundColor};border:${option.borderWidth} ${option.borderStyle} ${option.borderColor}; padding: ${option.padding}; border-radius: ${option.borderRadius}; color: #fff;`, `border:${option.borderWidth} ${option.borderStyle} ${option.borderColor}; padding: ${option.padding}; border-radius: ${option.borderRadius}; color: ${option.color};`, - 'background:transparent' - ); - }; + 'background:transparent', + ) + } const info = (textOrTitle: string, content: string = '', option: textOption = {}) => { - const title = isEmptyString(content) ? 'Info' : textOrTitle; - const text = isEmptyString(content) ? textOrTitle : content; + const title = isEmptyString(content) ? 'Info' : textOrTitle + const text = isEmptyString(content) ? textOrTitle : content const defaultOption = { color: '#909399', borderColor: '#909399', @@ -38,14 +39,14 @@ function prettyLog() { fontSize: '12px', padding: '1px', borderRadius: '2px 0 0 2px', - backgroundColor: '#909399' + backgroundColor: '#909399', } const textOption = Object.assign(defaultOption, option) - prettyPrint(title, text, textOption); - }; + prettyPrint(title, text, textOption) + } const error = (textOrTitle: string, content: string = '', option: textOption = {}) => { - const title = isEmptyString(content) ? 'Error' : textOrTitle; - const text = isEmptyString(content) ? textOrTitle : content; + const title = isEmptyString(content) ? 'Error' : textOrTitle + const text = isEmptyString(content) ? textOrTitle : content const defaultOption = { color: '#F56C6C', borderColor: '#F56C6C', @@ -54,14 +55,14 @@ function prettyLog() { fontSize: '12px', padding: '1px', borderRadius: '2px 0 0 2px', - backgroundColor: '#F56C6C' + backgroundColor: '#F56C6C', } const textOption = Object.assign(defaultOption, option) - prettyPrint(title, text, textOption); - }; - const warning = (textOrTitle: string, content:string = '', option: textOption = {}) => { - const title = isEmptyString(content) ? 'Warning' : textOrTitle; - const text = isEmptyString(content) ? textOrTitle : content; + prettyPrint(title, text, textOption) + } + const warning = (textOrTitle: string, content: string = '', option: textOption = {}) => { + const title = isEmptyString(content) ? 'Warning' : textOrTitle + const text = isEmptyString(content) ? textOrTitle : content const defaultOption = { color: '#E6A23C', borderColor: '#E6A23C', @@ -70,14 +71,14 @@ function prettyLog() { fontSize: '12px', padding: '1px', borderRadius: '2px 0 0 2px', - backgroundColor: '#E6A23C' + backgroundColor: '#E6A23C', } const textOption = Object.assign(defaultOption, option) - prettyPrint(title, text, textOption); - }; - const success = (textOrTitle: string, content:string = '', option: textOption = {}) => { - const title = isEmptyString(content) ? 'Success ' : textOrTitle; - const text = isEmptyString(content) ? textOrTitle : content; + prettyPrint(title, text, textOption) + } + const success = (textOrTitle: string, content: string = '', option: textOption = {}) => { + const title = isEmptyString(content) ? 'Success ' : textOrTitle + const text = isEmptyString(content) ? textOrTitle : content const defaultOption = { color: '#67C23A', borderColor: '#67C23A', @@ -86,11 +87,11 @@ function prettyLog() { fontSize: '12px', padding: '1px', borderRadius: '2px 0 0 2px', - backgroundColor: '#67C23A' + backgroundColor: '#67C23A', } const textOption = Object.assign(defaultOption, option) - prettyPrint(title, text, textOption); - }; + prettyPrint(title, text, textOption) + } const table = (tableData: any[], tableColumns: tableColum[], headerOption: tableOption = {}, bodyOption: tableOption = {}) => { let canUse = true let cantUseMsg = '' @@ -103,29 +104,30 @@ function prettyLog() { return } let theaderStr: string = '' - let tHeaderArr: string[] = [] - let tBodyArr: string[] = [] + const tHeaderArr: string[] = [] + const tBodyArr: string[] = [] const defaultHeaderOption: tableOption = { color: 'white', backgroundColor: 'black', - padding: '2px 10px' + padding: '2px 10px', } - const optionHeader : tableOption = Object.assign(defaultHeaderOption, headerOption) + const optionHeader: tableOption = Object.assign(defaultHeaderOption, headerOption) const defaultBodyOption: tableOption = { color: 'black', backgroundColor: 'lightgray', - padding: '2px 10px' + padding: '2px 10px', } const optionBody: tableOption = Object.assign(defaultBodyOption, bodyOption) - + for (let index = 0; index < tableColumns.length; index++) { - const item = tableColumns[index]; + const item = tableColumns[index] if (isEmptyString(item.title) || isEmptyString(item.key)) { cantUseMsg = '请传入正确的tableColumns数据' canUse = false break - } else { - theaderStr = theaderStr + `%c ${item.title}` + } + else { + theaderStr = `${theaderStr}%c ${item.title}` tHeaderArr.push(`color: ${optionHeader.color}; background-color: ${optionHeader.backgroundColor}; padding: ${optionHeader.padding};`) tBodyArr.push(`color: ${optionBody.color}; background-color: ${optionBody.backgroundColor}; padding: ${optionBody.padding};`) } @@ -135,29 +137,29 @@ function prettyLog() { return } // 输出头部 - console.log(theaderStr,...tHeaderArr); + console.log(theaderStr, ...tHeaderArr) // 循环输出body内容 tableData.forEach((row: any) => { let tBodyStr = '' - tableColumns.forEach(item => { - tBodyStr = tBodyStr + `%c ${row[item.key]}` + tableColumns.forEach((item) => { + tBodyStr = `${tBodyStr}%c ${row[item.key]}` }) - console.log(tBodyStr, ...tBodyArr); - }); - }; + console.log(tBodyStr, ...tBodyArr) + }) + } const picture = (url: string, scale = 1) => { - const img = new Image(); - img.crossOrigin = 'anonymous'; + const img = new Image() + img.crossOrigin = 'anonymous' img.onload = () => { - const c = document.createElement('canvas'); - const ctx = c.getContext('2d'); + const c = document.createElement('canvas') + const ctx = c.getContext('2d') if (ctx) { - c.width = img.width; - c.height = img.height; - ctx.fillStyle = 'red'; - ctx.fillRect(0, 0, c.width, c.height); - ctx.drawImage(img, 0, 0); - const dataUri = c.toDataURL('image/png'); + c.width = img.width + c.height = img.height + ctx.fillStyle = 'red' + ctx.fillRect(0, 0, c.width, c.height) + ctx.drawImage(img, 0, 0) + const dataUri = c.toDataURL('image/png') console.log( `%c sup?`, @@ -167,12 +169,12 @@ function prettyLog() { background-repeat: no-repeat; background-size: ${img.width * scale}px ${img.height * scale}px; color: transparent; - ` - ); + `, + ) } - }; - img.src = url; - }; + } + img.src = url + } return { info, @@ -180,8 +182,8 @@ function prettyLog() { warning, success, picture, - table - }; + table, + } } -export const log = prettyLog(); \ No newline at end of file +export const log = prettyLog() diff --git a/packages/object/src/deepCopy.ts b/packages/object/src/deepCopy.ts index 8449336..e51b6ee 100644 --- a/packages/object/src/deepCopy.ts +++ b/packages/object/src/deepCopy.ts @@ -16,7 +16,7 @@ export function deepCopy(data: T): T export function deepCopy(data: T, hash?: H): T /** - * 深拷贝实现 + * 实现函数 */ export function deepCopy(data: T, hash: any = new WeakMap()): T { // 日期对象直接返回一个新的日期对象 diff --git a/packages/string/__test__/template.test.ts b/packages/string/__test__/template.test.ts new file mode 100644 index 0000000..48e2d01 --- /dev/null +++ b/packages/string/__test__/template.test.ts @@ -0,0 +1,16 @@ +import { describe, expect, it } from 'vitest' +import { template } from '../src/template' + +describe('@xparcai-utils/string', () => { + const nmtp = 'hello {0}, {1}好!' + const jmtp = 'hello {name}, {time}好!' + + it('template', () => { + expect(template(nmtp, 'xparcai', '早上')).toBe('hello xparcai, 早上好!') + expect(template(jmtp, { name: 'xparcai', time: '早上' })).toBe('hello xparcai, 早上好!') + expect(template(jmtp, { name: 'xparcai' }, '晚上')).toBe('hello xparcai, 晚上好!') + expect(template(jmtp, key => (key !== 'time' ? { time: '中午' } : { name: 'xparcai' }))).toBe('hello name, time好!') + expect(template(jmtp, key => (key === 'time' ? { time: '中午' } : {}), '[xparcai]')).toBe('hello [xparcai], 中午好!') + expect(template(jmtp, () => ({}), 'xparcai')).toBe('hello xparcai, xparcai好!') + }) +}) diff --git a/packages/string/package.json b/packages/string/package.json index b1b44cc..a0eb2f4 100644 --- a/packages/string/package.json +++ b/packages/string/package.json @@ -52,5 +52,8 @@ "clean": "run-s clean:*", "clean:dist": "rimraf dist", "clean:deps": "rimraf node_modules" + }, + "devDependencies": { + "@xparcai-utils/is": "workspace:*" } } diff --git a/packages/string/src/template.ts b/packages/string/src/template.ts new file mode 100644 index 0000000..ab45697 --- /dev/null +++ b/packages/string/src/template.ts @@ -0,0 +1,42 @@ +import { isFunction, isObject } from '@xparcai-utils/is' + +type Value = string | number | bigint | undefined | null +type ValueObject = Record + +/** + * 匿名字符串模板替换 + * @param tp 字符串模板 + * @param args 占位值 + * @returns 替换后的字符串 + */ +export function template(tp: string, ...args: Value[]): string + +/** + * 混合字符串模板替换 + * @param tp 字符串模板 + * @param values 具名占位值集合,未提供值的统一使用 other 值填充 + * @param other 匿名占位值,支持字符串和函数 + * @returns 替换后的字符串 + */ +export function template(tp: string, values: ValueObject | ((key: string) => ValueObject), other?: string | ((key: string) => string)): string + +/** + * 实现函数 + */ +export function template(tp: string, ...args: any[]): string { + const [values, other] = args + if (isFunction(values) || isObject(values)) { + return tp.replace(/\{(\w+)\}/g, (_, key) => { + const _values = isFunction(values) ? values(key) : values + return _values[key] || ((isFunction(other) ? other(key) : other) ?? key) + }) + } + else { + return tp.replace(/\{(\d+)\}/g, (_, key) => { + const index = Number(key) + if (Number.isNaN(index)) + return key + return args[index] + }) + } +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 71e9163..b2eff0d 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -144,7 +144,11 @@ importers: specifier: workspace:* version: link:../tool - packages/log: {} + packages/log: + devDependencies: + '@xparcai-utils/is': + specifier: workspace:* + version: link:../is packages/object: devDependencies: @@ -152,7 +156,11 @@ importers: specifier: workspace:* version: link:../is - packages/string: {} + packages/string: + devDependencies: + '@xparcai-utils/is': + specifier: workspace:* + version: link:../is packages/tool: {} @@ -1228,46 +1236,55 @@ packages: resolution: {integrity: sha512-C/zbRYRXFjWvz9Z4haRxcTdnkPt1BtCkz+7RtBSuNmKzMzp3ZxdM28Mpccn6pt28/UWUCTXa+b0Mx1k3g6NOMA==} cpu: [arm] os: [linux] + libc: [glibc] '@rollup/rollup-linux-arm-musleabihf@4.18.0': resolution: {integrity: sha512-l3m9ewPgjQSXrUMHg93vt0hYCGnrMOcUpTz6FLtbwljo2HluS4zTXFy2571YQbisTnfTKPZ01u/ukJdQTLGh9A==} cpu: [arm] os: [linux] + libc: [musl] '@rollup/rollup-linux-arm64-gnu@4.18.0': resolution: {integrity: sha512-rJ5D47d8WD7J+7STKdCUAgmQk49xuFrRi9pZkWoRD1UeSMakbcepWXPF8ycChBoAqs1pb2wzvbY6Q33WmN2ftw==} cpu: [arm64] os: [linux] + libc: [glibc] '@rollup/rollup-linux-arm64-musl@4.18.0': resolution: {integrity: sha512-be6Yx37b24ZwxQ+wOQXXLZqpq4jTckJhtGlWGZs68TgdKXJgw54lUUoFYrg6Zs/kjzAQwEwYbp8JxZVzZLRepQ==} cpu: [arm64] os: [linux] + libc: [musl] '@rollup/rollup-linux-powerpc64le-gnu@4.18.0': resolution: {integrity: sha512-hNVMQK+qrA9Todu9+wqrXOHxFiD5YmdEi3paj6vP02Kx1hjd2LLYR2eaN7DsEshg09+9uzWi2W18MJDlG0cxJA==} cpu: [ppc64] os: [linux] + libc: [glibc] '@rollup/rollup-linux-riscv64-gnu@4.18.0': resolution: {integrity: sha512-ROCM7i+m1NfdrsmvwSzoxp9HFtmKGHEqu5NNDiZWQtXLA8S5HBCkVvKAxJ8U+CVctHwV2Gb5VUaK7UAkzhDjlg==} cpu: [riscv64] os: [linux] + libc: [glibc] '@rollup/rollup-linux-s390x-gnu@4.18.0': resolution: {integrity: sha512-0UyyRHyDN42QL+NbqevXIIUnKA47A+45WyasO+y2bGJ1mhQrfrtXUpTxCOrfxCR4esV3/RLYyucGVPiUsO8xjg==} cpu: [s390x] os: [linux] + libc: [glibc] '@rollup/rollup-linux-x64-gnu@4.18.0': resolution: {integrity: sha512-xuglR2rBVHA5UsI8h8UbX4VJ470PtGCf5Vpswh7p2ukaqBGFTnsfzxUBetoWBWymHMxbIG0Cmx7Y9qDZzr648w==} cpu: [x64] os: [linux] + libc: [glibc] '@rollup/rollup-linux-x64-musl@4.18.0': resolution: {integrity: sha512-LKaqQL9osY/ir2geuLVvRRs+utWUNilzdE90TpyoX0eNqPzWjRm14oMEE+YLve4k/NAqCdPkGYDaDF5Sw+xBfg==} cpu: [x64] os: [linux] + libc: [musl] '@rollup/rollup-win32-arm64-msvc@4.18.0': resolution: {integrity: sha512-7J6TkZQFGo9qBKH0pk2cEVSRhJbL6MtfWxth7Y5YmZs57Pi+4x6c2dStAUvaQkHQLnEQv1jzBUW43GvZW8OFqA==}