@yuhere/js-strings 1.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +179 -0
- package/lib/strings.d.ts +228 -0
- package/lib/strings.js +200 -0
- package/package.json +42 -0
- package/src/strings.ts +513 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
The MIT License (MIT)
|
|
2
|
+
|
|
3
|
+
Copyright (c) @yuhere
|
|
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.
|
package/README.md
ADDED
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
# String Utilities
|
|
2
|
+
|
|
3
|
+
A utility library aimed at providing common language-level helper functions (strings, dates, vars, etc.) in JavaScript/TypeScript projects.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
Install the package via npm:
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npm install @yuhere/js-strings
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Usage
|
|
14
|
+
|
|
15
|
+
Import the functions you need from the library:
|
|
16
|
+
|
|
17
|
+
```javascript
|
|
18
|
+
import {
|
|
19
|
+
s_pad,
|
|
20
|
+
s_left,
|
|
21
|
+
s_right,
|
|
22
|
+
s_ellipsis,
|
|
23
|
+
s_ellipsis_o,
|
|
24
|
+
s_xprint_extract_fmt,
|
|
25
|
+
s_xprint_fmts,
|
|
26
|
+
s_xprint,
|
|
27
|
+
s_glob
|
|
28
|
+
} from '@yuhere/js-strings';
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
### String Utilities
|
|
32
|
+
|
|
33
|
+
#### s_pad
|
|
34
|
+
|
|
35
|
+
Pad a string to a fixed width.
|
|
36
|
+
|
|
37
|
+
- `which`: padding direction, supports `'l'`, `'r'`, `'c'`
|
|
38
|
+
- `padchar`: padding character, default is space
|
|
39
|
+
- `is_trunc`: when `true`, truncates the string if it exceeds `width`
|
|
40
|
+
|
|
41
|
+
```javascript
|
|
42
|
+
import { s_pad } from '@yuhere/js-strings';
|
|
43
|
+
|
|
44
|
+
console.log(s_pad('hello', 10, 'l', '-')); // '-----hello'
|
|
45
|
+
console.log(s_pad('hello', 10, 'r', '-')); // 'hello-----'
|
|
46
|
+
console.log(s_pad('hello', 10, 'c')); // ' hello '
|
|
47
|
+
console.log(s_pad('hello world', 5, 'r', ' ', true)); // 'hello'
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
#### s_left
|
|
51
|
+
|
|
52
|
+
Get the leftmost characters of a string.
|
|
53
|
+
|
|
54
|
+
```javascript
|
|
55
|
+
import { s_left } from '@yuhere/js-strings';
|
|
56
|
+
|
|
57
|
+
console.log(s_left('hello world', 5)); // 'hello'
|
|
58
|
+
console.log(s_left('hello', 0)); // ''
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
#### s_right
|
|
62
|
+
|
|
63
|
+
Get the rightmost characters of a string.
|
|
64
|
+
|
|
65
|
+
```javascript
|
|
66
|
+
import { s_right } from '@yuhere/js-strings';
|
|
67
|
+
|
|
68
|
+
console.log(s_right('hello world', 5)); // 'world'
|
|
69
|
+
console.log(s_right('hello', 0)); // ''
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
#### s_ellipsis
|
|
73
|
+
|
|
74
|
+
Pad or truncate a string to a fixed width. When truncation happens, `...` is inserted according to the alignment.
|
|
75
|
+
|
|
76
|
+
- `'l'`: keep the left part and put `...` at the end
|
|
77
|
+
- `'r'`: keep the right part and put `...` at the beginning
|
|
78
|
+
- `'c'`: keep both ends and put `...` in the middle
|
|
79
|
+
|
|
80
|
+
```javascript
|
|
81
|
+
import { s_ellipsis } from '@yuhere/js-strings';
|
|
82
|
+
|
|
83
|
+
console.log(s_ellipsis('hello', 8)); // 'hello '
|
|
84
|
+
console.log(s_ellipsis('abcdefghijklmnopqrstuvwxyz', 10, 'l')); // 'abcdefg...'
|
|
85
|
+
console.log(s_ellipsis('abcdefghijklmnopqrstuvwxyz', 10, 'r')); // '...tuvwxyz'
|
|
86
|
+
console.log(s_ellipsis('abcdefghijklmnopqrstuvwxyz', 10, 'c')); // 'abcd...xyz'
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
#### s_ellipsis_o
|
|
90
|
+
|
|
91
|
+
Options-object form of `s_ellipsis`, suitable when parameters come from configuration.
|
|
92
|
+
|
|
93
|
+
```javascript
|
|
94
|
+
import { s_ellipsis_o } from '@yuhere/js-strings';
|
|
95
|
+
|
|
96
|
+
console.log(s_ellipsis_o('hello', { width: 8 })); // 'hello '
|
|
97
|
+
console.log(s_ellipsis_o('abcdefghijklmnopqrstuvwxyz', { width: 10, align: 'r' })); // '...tuvwxyz'
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
#### s_xprint_extract_fmt
|
|
101
|
+
|
|
102
|
+
Parse a column format definition used by `s_xprint`.
|
|
103
|
+
|
|
104
|
+
- string form: `"<width>[<align><padchar>]"`
|
|
105
|
+
- object form: `{ width, align, padchar }`
|
|
106
|
+
|
|
107
|
+
```javascript
|
|
108
|
+
import { s_xprint_extract_fmt } from '@yuhere/js-strings';
|
|
109
|
+
|
|
110
|
+
console.log(s_xprint_extract_fmt('8')); // { width: 8, align: 'l', padchar: undefined }
|
|
111
|
+
console.log(s_xprint_extract_fmt('8r0')); // { width: 8, align: 'r', padchar: '0' }
|
|
112
|
+
console.log(s_xprint_extract_fmt({ width: 12, align: 'l', padchar: '_' }));
|
|
113
|
+
// { width: 12, align: 'l', padchar: '_' }
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
#### s_xprint_fmts
|
|
117
|
+
|
|
118
|
+
Batch-convert multiple format definitions into normalized format objects.
|
|
119
|
+
|
|
120
|
+
```javascript
|
|
121
|
+
import { s_xprint_fmts } from '@yuhere/js-strings';
|
|
122
|
+
|
|
123
|
+
console.log(s_xprint_fmts(['8', '6r0', { width: 10, align: 'l', padchar: '_' }]));
|
|
124
|
+
// [
|
|
125
|
+
// { width: 8, align: 'l', padchar: undefined },
|
|
126
|
+
// { width: 6, align: 'r', padchar: '0' },
|
|
127
|
+
// { width: 10, align: 'l', padchar: '_' }
|
|
128
|
+
// ]
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
#### s_xprint
|
|
132
|
+
|
|
133
|
+
Format multiple columns into one line of fixed-width text. This is useful for CLI table rows, aligned logs, and report output.
|
|
134
|
+
|
|
135
|
+
- `columns`: values to output
|
|
136
|
+
- `formats`: per-column format definitions
|
|
137
|
+
- `sep`: separator inserted between columns
|
|
138
|
+
- `ellipsisEnd`: whether the last column should also be padded/truncated
|
|
139
|
+
|
|
140
|
+
```javascript
|
|
141
|
+
import { s_xprint } from '@yuhere/js-strings';
|
|
142
|
+
|
|
143
|
+
console.log(
|
|
144
|
+
s_xprint({
|
|
145
|
+
columns: ['id', 7, 'abcdefghijklmnopqrstuvwxyz'],
|
|
146
|
+
formats: ['6l ', '4r0', '10l '],
|
|
147
|
+
sep: ' | '
|
|
148
|
+
})
|
|
149
|
+
);
|
|
150
|
+
// 'id | 0007 | abcdefg...'
|
|
151
|
+
|
|
152
|
+
console.log(
|
|
153
|
+
s_xprint({
|
|
154
|
+
columns: ['INFO', 'startup ok', 'this part is not truncated'],
|
|
155
|
+
formats: ['6l ', '12l ', '8l '],
|
|
156
|
+
sep: ' ',
|
|
157
|
+
ellipsisEnd: false
|
|
158
|
+
})
|
|
159
|
+
);
|
|
160
|
+
// 'INFO startup ok this part is not truncated'
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
#### s_glob
|
|
164
|
+
|
|
165
|
+
Match a string against a simple glob pattern.
|
|
166
|
+
|
|
167
|
+
Current public wrapper behavior uses the default glob conversion rules, which are suitable for common `*` pattern matching.
|
|
168
|
+
|
|
169
|
+
```javascript
|
|
170
|
+
import { s_glob } from '@yuhere/js-strings';
|
|
171
|
+
|
|
172
|
+
console.log(s_glob('*.ts', 'index.ts')); // true
|
|
173
|
+
console.log(s_glob('*.ts', 'index.js')); // false
|
|
174
|
+
console.log(s_glob('src/*', 'src/strings.ts')); // true
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
## License
|
|
178
|
+
|
|
179
|
+
MIT
|
package/lib/strings.d.ts
ADDED
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
type SAlign = "l" | "r" | "c";
|
|
2
|
+
type SXPrintAlign = "l" | "r";
|
|
3
|
+
interface SEllipsisOptions {
|
|
4
|
+
width: number;
|
|
5
|
+
align?: SAlign;
|
|
6
|
+
padchar?: string;
|
|
7
|
+
}
|
|
8
|
+
interface SXPrintFormat {
|
|
9
|
+
width: number;
|
|
10
|
+
align?: SXPrintAlign;
|
|
11
|
+
padchar?: string;
|
|
12
|
+
}
|
|
13
|
+
interface SXPrintParams {
|
|
14
|
+
columns: Array<string | number | null | undefined>;
|
|
15
|
+
formats?: Array<string | SXPrintFormat>;
|
|
16
|
+
sep?: string;
|
|
17
|
+
ellipsisEnd?: boolean;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* String padding.
|
|
21
|
+
*
|
|
22
|
+
* @param text - Input text.
|
|
23
|
+
* @param width - Target width. If `undefined`, defaults to `text.length`.
|
|
24
|
+
* @param which - Padding side:
|
|
25
|
+
* - `"r"`: pad on the right (left-justified)
|
|
26
|
+
* - `"l"`: pad on the left (right-justified)
|
|
27
|
+
* - `"c"`: pad both sides (center)
|
|
28
|
+
* @param padchar - Padding character (single-column). Defaults to `" "`.
|
|
29
|
+
* @param is_trunc - If `true`, truncate `text` when it exceeds `width`.
|
|
30
|
+
* @returns Padded (or truncated) string with length `width`.
|
|
31
|
+
*/
|
|
32
|
+
export declare function s_pad(text: string, width?: number, which?: SAlign, padchar?: string, is_trunc?: boolean): string;
|
|
33
|
+
/**
|
|
34
|
+
* Returns a string containing the specified number of characters from the left side of `str`.
|
|
35
|
+
*
|
|
36
|
+
* @param str - The source string.
|
|
37
|
+
* @param length - Number of characters to return.
|
|
38
|
+
* - If `length <= 0`, returns an empty string (`""`).
|
|
39
|
+
* - If `length >= str.length`, returns the entire `str`.
|
|
40
|
+
* @returns The leftmost `length` characters of `str`.
|
|
41
|
+
*/
|
|
42
|
+
export declare function s_left(str: string, length: number): string;
|
|
43
|
+
/**
|
|
44
|
+
* Returns a string containing the specified number of characters from the right side of `str`.
|
|
45
|
+
*
|
|
46
|
+
* @param str - The source string.
|
|
47
|
+
* @param length - Number of characters to return.
|
|
48
|
+
* - If `length <= 0`, returns an empty string (`""`).
|
|
49
|
+
* - If `length >= str.length`, returns the entire `str`.
|
|
50
|
+
* @returns The rightmost `length` characters of `str`.
|
|
51
|
+
*/
|
|
52
|
+
export declare function s_right(str: string, length: number): string;
|
|
53
|
+
/**
|
|
54
|
+
* Truncate or pad a string to a fixed width, using an ellipsis (`"..."`) when truncation happens.
|
|
55
|
+
*
|
|
56
|
+
* - If `str.length < width`, pads with `padchar` according to `align`.
|
|
57
|
+
* - If `str.length > width`, truncates and inserts `"..."` based on `align`:
|
|
58
|
+
* - `"l"`: keep left part + `"..."` at end
|
|
59
|
+
* - `"r"`: `"..."` at start + keep right part
|
|
60
|
+
* - `"c"`: keep head and tail with `"..."` in the middle
|
|
61
|
+
*
|
|
62
|
+
* @param str - Input value to format. Non-string values are coerced to string.
|
|
63
|
+
* @param width - Target width (in characters).
|
|
64
|
+
* @param align - Alignment mode: `"l"` (left), `"r"` (right), `"c"` (center). Defaults to `"l"`.
|
|
65
|
+
* @param padchar - Padding character used when `str` is shorter than `width`. Defaults to `" "`.
|
|
66
|
+
* @returns The formatted string with exact length `width` (unless `width` is too small to hold content).
|
|
67
|
+
*/
|
|
68
|
+
export declare function s_ellipsis(str: string, width: number, align?: SAlign, padchar?: string): string;
|
|
69
|
+
/**
|
|
70
|
+
* Convenience wrapper around {@link s_ellipsis} that accepts an options object.
|
|
71
|
+
*
|
|
72
|
+
* @param str - Input value to format. Non-string values are coerced to string by {@link s_ellipsis}.
|
|
73
|
+
* @param opts - Formatting options.
|
|
74
|
+
* @param opts.width - Target width (in characters).
|
|
75
|
+
* @param opts.align - Alignment mode: `"l"` (left), `"r"` (right), `"c"` (center).
|
|
76
|
+
* @param opts.padchar - Padding character used when `str` is shorter than `width`.
|
|
77
|
+
* @returns The formatted string as produced by {@link s_ellipsis}.
|
|
78
|
+
*
|
|
79
|
+
* @example
|
|
80
|
+
* // Pad (left aligned) to width 8 with spaces
|
|
81
|
+
* s_ellipsis_o("abc", { width: 8, align: "l", padchar: " " })
|
|
82
|
+
* // => "abc "
|
|
83
|
+
*
|
|
84
|
+
* @example
|
|
85
|
+
* // Pad (right aligned) to width 8 with '0'
|
|
86
|
+
* s_ellipsis_o("abc", { width: 8, align: "r", padchar: "0" })
|
|
87
|
+
* // => "00000abc"
|
|
88
|
+
*
|
|
89
|
+
* @example
|
|
90
|
+
* // Truncate with ellipsis at end (left align behavior for truncation)
|
|
91
|
+
* s_ellipsis_o("abcdefghijklmnopqrstuvwxyz", { width: 10, align: "l", padchar: " " })
|
|
92
|
+
* // => "abcdefg..."
|
|
93
|
+
*
|
|
94
|
+
* @example
|
|
95
|
+
* // Truncate with ellipsis at start (right align behavior for truncation)
|
|
96
|
+
* s_ellipsis_o("abcdefghijklmnopqrstuvwxyz", { width: 10, align: "r", padchar: " " })
|
|
97
|
+
* // => "...tuvwxyz"
|
|
98
|
+
*
|
|
99
|
+
* @example
|
|
100
|
+
* // Truncate with ellipsis in the middle (center)
|
|
101
|
+
* s_ellipsis_o("abcdefghijklmnopqrstuvwxyz", { width: 10, align: "c", padchar: " " })
|
|
102
|
+
* // => "abcd...xyz"
|
|
103
|
+
*/
|
|
104
|
+
export declare function s_ellipsis_o(str: string, opts: SEllipsisOptions): string;
|
|
105
|
+
/**
|
|
106
|
+
* Parse an `s_xprint` format specifier into a normalized format object.
|
|
107
|
+
*
|
|
108
|
+
* Supports either:
|
|
109
|
+
* - A compact string spec: `"<width>[<align><padchar>]"`, where:
|
|
110
|
+
* - `width` is a positive integer (required)
|
|
111
|
+
* - `align` is optional: `"l"` (left) or `"r"` (right)
|
|
112
|
+
* - `padchar` is optional: a single character used for padding
|
|
113
|
+
* - An already-constructed format object, which is returned as-is.
|
|
114
|
+
*
|
|
115
|
+
* The string form is matched by: `/^(\\d+)(?:([lr])(.?))?$/`
|
|
116
|
+
*
|
|
117
|
+
* @param fmt - Format specifier as a string (e.g. `"10l_"`) or a format object
|
|
118
|
+
* (e.g. `{ width: 10, align: "l", padchar: "_" }`).
|
|
119
|
+
* @returns A format object `{ width, align, padchar }`.
|
|
120
|
+
* @throws {Error} If `fmt` is a string but does not match the expected format.
|
|
121
|
+
*
|
|
122
|
+
* @example
|
|
123
|
+
* // String form: width only
|
|
124
|
+
* s_xprint_extract_fmt("8")
|
|
125
|
+
* // => { width: 8, align: undefined, padchar: undefined }
|
|
126
|
+
*
|
|
127
|
+
* @example
|
|
128
|
+
* // String form: width + align
|
|
129
|
+
* s_xprint_extract_fmt("8l")
|
|
130
|
+
* // => { width: 8, align: "l", padchar: "" }
|
|
131
|
+
*
|
|
132
|
+
* @example
|
|
133
|
+
* // String form: width + align + padchar
|
|
134
|
+
* s_xprint_extract_fmt("8r0")
|
|
135
|
+
* // => { width: 8, align: "r", padchar: "0" }
|
|
136
|
+
*
|
|
137
|
+
* @example
|
|
138
|
+
* // Object form: returned unchanged
|
|
139
|
+
* s_xprint_extract_fmt({ width: 12, align: "l", padchar: " " })
|
|
140
|
+
* // => { width: 12, align: "l", padchar: " " }
|
|
141
|
+
*/
|
|
142
|
+
export declare function s_xprint_extract_fmt(fmt: string | SXPrintFormat): SXPrintFormat;
|
|
143
|
+
/**
|
|
144
|
+
* Normalize a list of `s_xprint` format specifiers into format objects.
|
|
145
|
+
*
|
|
146
|
+
* This is a convenience helper that applies {@link s_xprint_extract_fmt} to each
|
|
147
|
+
* element in `fmts`.
|
|
148
|
+
*
|
|
149
|
+
* Supported input items:
|
|
150
|
+
* - String form: `"<width>[<align><padchar>]"`, e.g. `"8"`, `"10l"`, `"6r0"`
|
|
151
|
+
* - Object form: `{ width, align, padchar }`
|
|
152
|
+
*
|
|
153
|
+
* @param fmts - An array of format specifiers (strings or format objects).
|
|
154
|
+
* @returns An array of normalized format objects: `{ width, align, padchar }`.
|
|
155
|
+
* @throws {Error} If any string format does not match the expected pattern.
|
|
156
|
+
*
|
|
157
|
+
* @example
|
|
158
|
+
* // String formats
|
|
159
|
+
* s_xprint_fmts(["8", "10l_", "6r0"])
|
|
160
|
+
* // => [
|
|
161
|
+
* // { width: 8, align: undefined, padchar: undefined },
|
|
162
|
+
* // { width: 10, align: "l", padchar: "_" },
|
|
163
|
+
* // { width: 6, align: "r", padchar: "0" }
|
|
164
|
+
* // ]
|
|
165
|
+
*
|
|
166
|
+
* @example
|
|
167
|
+
* // Mixed formats (object formats are returned as-is)
|
|
168
|
+
* s_xprint_fmts([{ width: 4, align: "l", padchar: " " }, "3r0"])
|
|
169
|
+
* // => [
|
|
170
|
+
* // { width: 4, align: "l", padchar: " " },
|
|
171
|
+
* // { width: 3, align: "r", padchar: "0" }
|
|
172
|
+
* // ]
|
|
173
|
+
*/
|
|
174
|
+
export declare function s_xprint_fmts(fmts: Array<string | SXPrintFormat>): SXPrintFormat[];
|
|
175
|
+
/**
|
|
176
|
+
* Format and join a list of columns into a single fixed-width string (table-like output).
|
|
177
|
+
*
|
|
178
|
+
* Each column is formatted using {@link s_ellipsis} with the corresponding `formats` entry.
|
|
179
|
+
* Columns are then joined by `sep`.
|
|
180
|
+
*
|
|
181
|
+
* - `formats` entries accept the same specifiers as {@link s_xprint_extract_fmt}:
|
|
182
|
+
* - String form: `"<width>[<align><padchar>]"`, e.g. `"10"`, `"8l_"`, `"6r0"`
|
|
183
|
+
* - Object form: `{ width, align, padchar }`
|
|
184
|
+
* - The last column can optionally skip ellipsis formatting when `ellipsisEnd === false`.
|
|
185
|
+
*
|
|
186
|
+
* @param params - Options object.
|
|
187
|
+
* @param params.columns - Column values to print. `null`/`undefined` become `""`.
|
|
188
|
+
* @param params.formats - Per-column format specifiers (same length as `columns` is recommended).
|
|
189
|
+
* @param params.sep - Separator string inserted between formatted columns. Defaults to `" "`.
|
|
190
|
+
* @param params.ellipsisEnd - Whether to apply ellipsis formatting to the last column.
|
|
191
|
+
* Defaults to `true`. When `false`, the last column is returned as-is (no padding/truncation).
|
|
192
|
+
* @returns A single formatted line string.
|
|
193
|
+
*
|
|
194
|
+
* @example
|
|
195
|
+
* // Basic usage with string formats (width + align + padchar)
|
|
196
|
+
* // - 1st: width 6, left align, pad with space
|
|
197
|
+
* // - 2nd: width 4, right align, pad with '0'
|
|
198
|
+
* // - 3rd: width 10, left align (will ellipsis if too long)
|
|
199
|
+
* s_xprint({
|
|
200
|
+
* columns: ["id", 7, "abcdefghijklmnopqrstuvwxyz"],
|
|
201
|
+
* formats: ["6l ", "4r0", "10l "],
|
|
202
|
+
* sep: " | "
|
|
203
|
+
* })
|
|
204
|
+
* // => "id | 0007 | abcdefg..."
|
|
205
|
+
*
|
|
206
|
+
* @example
|
|
207
|
+
* // Skip formatting for the last column (useful for "message" / "tail" fields)
|
|
208
|
+
* s_xprint({
|
|
209
|
+
* columns: ["INFO", "startup ok", "this part is not truncated"],
|
|
210
|
+
* formats: ["6l ", "12l ", "8l "],
|
|
211
|
+
* sep: " ",
|
|
212
|
+
* ellipsisEnd: false
|
|
213
|
+
* })
|
|
214
|
+
* // => "INFO startup ok this part is not truncated"
|
|
215
|
+
*/
|
|
216
|
+
export declare function s_xprint({ columns, formats, sep, ellipsisEnd }?: SXPrintParams): string;
|
|
217
|
+
/**
|
|
218
|
+
* Test whether a string matches a glob pattern.
|
|
219
|
+
*
|
|
220
|
+
* This is a small wrapper around {@link glob_to_regexp} that converts the given
|
|
221
|
+
* glob `pattern` to a {@link RegExp} and then checks it against `str`.
|
|
222
|
+
*
|
|
223
|
+
* @param pattern - Glob pattern e.g. `"*.ts"`.
|
|
224
|
+
* @param str - The input string to test.
|
|
225
|
+
* @returns `true` if `str` matches `pattern`; otherwise `false`.
|
|
226
|
+
*/
|
|
227
|
+
export declare function s_glob(pattern: string, str: string): boolean;
|
|
228
|
+
export {};
|
package/lib/strings.js
ADDED
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
function s_pad(text, width, which = "l", padchar = " ", is_trunc = false) {
|
|
2
|
+
if (typeof width !== "number") width = text.length;
|
|
3
|
+
let w = text.length;
|
|
4
|
+
if (is_trunc && w > width) {
|
|
5
|
+
text = text.substring(0, width);
|
|
6
|
+
w = width;
|
|
7
|
+
} else {
|
|
8
|
+
var r = Math.max(0, width - w);
|
|
9
|
+
if (which === "l") {
|
|
10
|
+
text = padchar.repeat(r) + text;
|
|
11
|
+
} else if (which === "c") {
|
|
12
|
+
let n = Math.floor(r / 2);
|
|
13
|
+
text = padchar.repeat(n) + text + padchar.repeat(r - n);
|
|
14
|
+
} else {
|
|
15
|
+
text = text + padchar.repeat(r);
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
return text;
|
|
19
|
+
}
|
|
20
|
+
function s_left(str, length) {
|
|
21
|
+
if (length <= 0) return "";
|
|
22
|
+
let str_length = str.length;
|
|
23
|
+
if (length > str_length) {
|
|
24
|
+
return str;
|
|
25
|
+
}
|
|
26
|
+
return str.substring(0, length);
|
|
27
|
+
}
|
|
28
|
+
function s_right(str, length) {
|
|
29
|
+
if (length <= 0) return "";
|
|
30
|
+
let str_length = str.length;
|
|
31
|
+
if (length > str_length) {
|
|
32
|
+
return str;
|
|
33
|
+
}
|
|
34
|
+
return str.substring(str_length - length);
|
|
35
|
+
}
|
|
36
|
+
function s_ellipsis(str, width, align = "l", padchar = " ") {
|
|
37
|
+
str = typeof str === "string" ? str : str + "";
|
|
38
|
+
width = Number.isFinite(width) ? Math.max(0, Math.trunc(width)) : 0;
|
|
39
|
+
if (width === 0) return "";
|
|
40
|
+
align = align != void 0 ? align : "l";
|
|
41
|
+
const _align = align.substring(0, 1);
|
|
42
|
+
align = _align === "l" || _align === "r" || _align === "c" ? _align : "l";
|
|
43
|
+
padchar = typeof padchar === "string" ? padchar ? padchar : " " : " ";
|
|
44
|
+
str = str.replace(/\r ?\n/g, "");
|
|
45
|
+
let length = str.length;
|
|
46
|
+
if (length === width) {
|
|
47
|
+
return str;
|
|
48
|
+
} else if (length < width) {
|
|
49
|
+
if (align === "r") {
|
|
50
|
+
str = s_pad(str, width, "l", padchar);
|
|
51
|
+
} else if (align === "l") {
|
|
52
|
+
str = s_pad(str, width, "r", padchar);
|
|
53
|
+
} else {
|
|
54
|
+
str = s_pad(str, width, "c", padchar);
|
|
55
|
+
}
|
|
56
|
+
} else {
|
|
57
|
+
if (width <= 3) {
|
|
58
|
+
return ".".repeat(width);
|
|
59
|
+
}
|
|
60
|
+
if (align === "r") {
|
|
61
|
+
str = "..." + s_right(str, width - 3);
|
|
62
|
+
} else if (align === "c") {
|
|
63
|
+
let m_odd = width % 2;
|
|
64
|
+
let m_mid_len = Math.floor(width / 2);
|
|
65
|
+
let head_str = s_left(str, m_mid_len - 1);
|
|
66
|
+
let tail_str = s_right(str, m_mid_len - (m_odd ? 1 : 2));
|
|
67
|
+
str = head_str + "..." + tail_str;
|
|
68
|
+
} else {
|
|
69
|
+
str = s_left(str, width - 3) + "...";
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
return str;
|
|
73
|
+
}
|
|
74
|
+
function s_ellipsis_o(str, opts) {
|
|
75
|
+
const { width, align, padchar } = opts;
|
|
76
|
+
return s_ellipsis(str, width, align, padchar);
|
|
77
|
+
}
|
|
78
|
+
function s_xprint_extract_fmt(fmt) {
|
|
79
|
+
if (typeof fmt === "string") {
|
|
80
|
+
const mmm = /^(\d+)(?:([lr])(.?))?$/.exec(fmt);
|
|
81
|
+
if (mmm == null) {
|
|
82
|
+
throw Error("wrong foramt of s_xprint [" + fmt + "]");
|
|
83
|
+
}
|
|
84
|
+
const _align = mmm[2] === "l" || mmm[2] === "r" ? mmm[2] : "l";
|
|
85
|
+
return {
|
|
86
|
+
width: parseInt(mmm[1], 10),
|
|
87
|
+
align: _align,
|
|
88
|
+
padchar: mmm[3]
|
|
89
|
+
};
|
|
90
|
+
} else {
|
|
91
|
+
return fmt;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
function s_xprint_fmts(fmts) {
|
|
95
|
+
return fmts.map(s_xprint_extract_fmt);
|
|
96
|
+
}
|
|
97
|
+
function s_xprint({ columns, formats = [], sep = " ", ellipsisEnd = true } = { columns: [], formats: [], sep: " ", ellipsisEnd: true }) {
|
|
98
|
+
const fmts = formats.map(s_xprint_extract_fmt);
|
|
99
|
+
return columns.map((column, idx) => {
|
|
100
|
+
const column_str = column == null ? "" : String(column);
|
|
101
|
+
const fmt = fmts[idx] ?? { width: column_str.length, align: "l", padchar: " " };
|
|
102
|
+
const { width, align, padchar } = fmt;
|
|
103
|
+
if (!ellipsisEnd && idx === columns.length - 1) {
|
|
104
|
+
return column_str;
|
|
105
|
+
} else {
|
|
106
|
+
return s_ellipsis(column_str, width, align, padchar);
|
|
107
|
+
}
|
|
108
|
+
}).join(sep);
|
|
109
|
+
}
|
|
110
|
+
function glob_to_regexp(glob, opts = {}) {
|
|
111
|
+
if (typeof glob !== "string") {
|
|
112
|
+
throw new TypeError("Expected a string");
|
|
113
|
+
}
|
|
114
|
+
var str = String(glob);
|
|
115
|
+
var reStr = "";
|
|
116
|
+
var extended = opts ? !!opts.extended : false;
|
|
117
|
+
var globstar = opts ? !!opts.globstar : false;
|
|
118
|
+
var inGroup = false;
|
|
119
|
+
var flags = opts && typeof opts.flags === "string" ? opts.flags : "";
|
|
120
|
+
var c;
|
|
121
|
+
for (var i = 0, len = str.length; i < len; i++) {
|
|
122
|
+
c = str[i];
|
|
123
|
+
switch (c) {
|
|
124
|
+
case "/":
|
|
125
|
+
case "$":
|
|
126
|
+
case "^":
|
|
127
|
+
case "+":
|
|
128
|
+
case ".":
|
|
129
|
+
case "(":
|
|
130
|
+
case ")":
|
|
131
|
+
case "=":
|
|
132
|
+
case "!":
|
|
133
|
+
case "|":
|
|
134
|
+
reStr += "\\" + c;
|
|
135
|
+
break;
|
|
136
|
+
case "?":
|
|
137
|
+
if (extended) {
|
|
138
|
+
reStr += ".";
|
|
139
|
+
break;
|
|
140
|
+
}
|
|
141
|
+
case "[":
|
|
142
|
+
case "]":
|
|
143
|
+
if (extended) {
|
|
144
|
+
reStr += c;
|
|
145
|
+
break;
|
|
146
|
+
}
|
|
147
|
+
case "{":
|
|
148
|
+
if (extended) {
|
|
149
|
+
inGroup = true;
|
|
150
|
+
reStr += "(";
|
|
151
|
+
break;
|
|
152
|
+
}
|
|
153
|
+
case "}":
|
|
154
|
+
if (extended) {
|
|
155
|
+
inGroup = false;
|
|
156
|
+
reStr += ")";
|
|
157
|
+
break;
|
|
158
|
+
}
|
|
159
|
+
case ",":
|
|
160
|
+
if (inGroup) {
|
|
161
|
+
reStr += "|";
|
|
162
|
+
break;
|
|
163
|
+
}
|
|
164
|
+
reStr += "\\" + c;
|
|
165
|
+
break;
|
|
166
|
+
case "*":
|
|
167
|
+
var prevChar = str[i - 1];
|
|
168
|
+
var starCount = 1;
|
|
169
|
+
while (str[i + 1] === "*") {
|
|
170
|
+
starCount++;
|
|
171
|
+
i++;
|
|
172
|
+
}
|
|
173
|
+
var nextChar = str[i + 1];
|
|
174
|
+
if (!globstar) {
|
|
175
|
+
reStr += ".*";
|
|
176
|
+
} else {
|
|
177
|
+
var isGlobstar = starCount > 1 && (prevChar === "/" || prevChar === void 0) && (nextChar === "/" || nextChar === void 0);
|
|
178
|
+
if (isGlobstar) {
|
|
179
|
+
reStr += "((?:[^/]*(?:/|$))*)";
|
|
180
|
+
i++;
|
|
181
|
+
} else {
|
|
182
|
+
reStr += "([^/]*)";
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
break;
|
|
186
|
+
default:
|
|
187
|
+
reStr += c;
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
if (!flags || !~flags.indexOf("g")) {
|
|
191
|
+
reStr = "^" + reStr + "$";
|
|
192
|
+
}
|
|
193
|
+
return new RegExp(reStr, flags);
|
|
194
|
+
}
|
|
195
|
+
function s_glob(pattern, str) {
|
|
196
|
+
const regex = glob_to_regexp(pattern, {});
|
|
197
|
+
return regex.test(str);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
export { s_ellipsis, s_ellipsis_o, s_glob, s_left, s_pad, s_right, s_xprint, s_xprint_extract_fmt, s_xprint_fmts };
|
package/package.json
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@yuhere/js-strings",
|
|
3
|
+
"type": "module",
|
|
4
|
+
"description": "utility library aimed at providing common helper functions for strings in JavaScript/TypeScript projects.",
|
|
5
|
+
"version": "1.0.2",
|
|
6
|
+
"publisher": "yuhere",
|
|
7
|
+
"repository": "https://github.com/yuhere/js-strings.git",
|
|
8
|
+
"license": "MIT",
|
|
9
|
+
"main": "lib/strings.js",
|
|
10
|
+
"module": "lib/strings.js",
|
|
11
|
+
"types": "lib/strings.d.ts",
|
|
12
|
+
"files": [
|
|
13
|
+
"bin/",
|
|
14
|
+
"lib/",
|
|
15
|
+
"src/",
|
|
16
|
+
"README.md",
|
|
17
|
+
"LICENSE"
|
|
18
|
+
],
|
|
19
|
+
"scripts": {
|
|
20
|
+
"build": "npm-run-all -p build:*",
|
|
21
|
+
"build:rollup": "rollup --config rollup.config.js",
|
|
22
|
+
"build:types": "tsc -p tsconfig.types.json",
|
|
23
|
+
"lint": "eslint src",
|
|
24
|
+
"test": "npm run build && npx c8 mocha"
|
|
25
|
+
},
|
|
26
|
+
"devDependencies": {
|
|
27
|
+
"@types/chai": "^5.2.3",
|
|
28
|
+
"@types/mocha": "^10.0.10",
|
|
29
|
+
"@types/node": "^24.6.1",
|
|
30
|
+
"@typescript-eslint/eslint-plugin": "^7.14.1",
|
|
31
|
+
"@typescript-eslint/parser": "^7.11.0",
|
|
32
|
+
"eslint": "^8.57.0",
|
|
33
|
+
"npm-run-all": "^4.1.5",
|
|
34
|
+
"rollup": "^4.53.3",
|
|
35
|
+
"c8": "^10.1.3",
|
|
36
|
+
"chai": "^4.3.7",
|
|
37
|
+
"mocha": "^10.2.0",
|
|
38
|
+
"typescript": "^5.9.3",
|
|
39
|
+
"tsx": "^4.21.0",
|
|
40
|
+
"rollup-plugin-esbuild": "^6.2.1"
|
|
41
|
+
}
|
|
42
|
+
}
|
package/src/strings.ts
ADDED
|
@@ -0,0 +1,513 @@
|
|
|
1
|
+
|
|
2
|
+
// ########################################
|
|
3
|
+
// # STRINGS
|
|
4
|
+
// ########################################
|
|
5
|
+
|
|
6
|
+
type SAlign = "l" | "r" | "c"
|
|
7
|
+
type SXPrintAlign = "l" | "r"
|
|
8
|
+
|
|
9
|
+
interface SEllipsisOptions {
|
|
10
|
+
width: number
|
|
11
|
+
align?: SAlign
|
|
12
|
+
padchar?: string
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
interface SXPrintFormat {
|
|
16
|
+
width: number
|
|
17
|
+
align?: SXPrintAlign
|
|
18
|
+
padchar?: string
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
interface SXPrintParams {
|
|
22
|
+
columns: Array<string | number | null | undefined>
|
|
23
|
+
formats?: Array<string | SXPrintFormat>
|
|
24
|
+
sep?: string
|
|
25
|
+
ellipsisEnd?: boolean
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
interface GlobToRegExpOptions {
|
|
29
|
+
extended?: boolean
|
|
30
|
+
globstar?: boolean
|
|
31
|
+
flags?: string
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* String padding.
|
|
36
|
+
*
|
|
37
|
+
* @param text - Input text.
|
|
38
|
+
* @param width - Target width. If `undefined`, defaults to `text.length`.
|
|
39
|
+
* @param which - Padding side:
|
|
40
|
+
* - `"r"`: pad on the right (left-justified)
|
|
41
|
+
* - `"l"`: pad on the left (right-justified)
|
|
42
|
+
* - `"c"`: pad both sides (center)
|
|
43
|
+
* @param padchar - Padding character (single-column). Defaults to `" "`.
|
|
44
|
+
* @param is_trunc - If `true`, truncate `text` when it exceeds `width`.
|
|
45
|
+
* @returns Padded (or truncated) string with length `width`.
|
|
46
|
+
*/
|
|
47
|
+
export function s_pad(text: string, width?: number, which: SAlign = "l", padchar: string = " ", is_trunc: boolean = false): string {
|
|
48
|
+
if (typeof (width) !== "number") width = text.length
|
|
49
|
+
// ####
|
|
50
|
+
let w = text.length;
|
|
51
|
+
if (is_trunc && w > width) {
|
|
52
|
+
text = text.substring(0, width)
|
|
53
|
+
w = width;
|
|
54
|
+
} else {
|
|
55
|
+
var r = Math.max(0, width - w)
|
|
56
|
+
if (which === 'l') {
|
|
57
|
+
text = padchar.repeat(r) + text
|
|
58
|
+
} else if (which === 'c') {
|
|
59
|
+
let n = Math.floor(r / 2)
|
|
60
|
+
text = padchar.repeat(n) + text + padchar.repeat(r - n)
|
|
61
|
+
} else {
|
|
62
|
+
text = text + (padchar.repeat(r));
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
// ####
|
|
66
|
+
return text
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Returns a string containing the specified number of characters from the left side of `str`.
|
|
71
|
+
*
|
|
72
|
+
* @param str - The source string.
|
|
73
|
+
* @param length - Number of characters to return.
|
|
74
|
+
* - If `length <= 0`, returns an empty string (`""`).
|
|
75
|
+
* - If `length >= str.length`, returns the entire `str`.
|
|
76
|
+
* @returns The leftmost `length` characters of `str`.
|
|
77
|
+
*/
|
|
78
|
+
export function s_left(str: string, length: number): string {
|
|
79
|
+
if (length <= 0) return ""
|
|
80
|
+
let str_length = str.length
|
|
81
|
+
if (length > str_length) {
|
|
82
|
+
return str;
|
|
83
|
+
}
|
|
84
|
+
return str.substring(0, length);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Returns a string containing the specified number of characters from the right side of `str`.
|
|
89
|
+
*
|
|
90
|
+
* @param str - The source string.
|
|
91
|
+
* @param length - Number of characters to return.
|
|
92
|
+
* - If `length <= 0`, returns an empty string (`""`).
|
|
93
|
+
* - If `length >= str.length`, returns the entire `str`.
|
|
94
|
+
* @returns The rightmost `length` characters of `str`.
|
|
95
|
+
*/
|
|
96
|
+
export function s_right(str: string, length: number): string {
|
|
97
|
+
if (length <= 0) return "";
|
|
98
|
+
let str_length = str.length;
|
|
99
|
+
if (length > str_length) {
|
|
100
|
+
return str;
|
|
101
|
+
}
|
|
102
|
+
return str.substring(str_length - length);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Truncate or pad a string to a fixed width, using an ellipsis (`"..."`) when truncation happens.
|
|
107
|
+
*
|
|
108
|
+
* - If `str.length < width`, pads with `padchar` according to `align`.
|
|
109
|
+
* - If `str.length > width`, truncates and inserts `"..."` based on `align`:
|
|
110
|
+
* - `"l"`: keep left part + `"..."` at end
|
|
111
|
+
* - `"r"`: `"..."` at start + keep right part
|
|
112
|
+
* - `"c"`: keep head and tail with `"..."` in the middle
|
|
113
|
+
*
|
|
114
|
+
* @param str - Input value to format. Non-string values are coerced to string.
|
|
115
|
+
* @param width - Target width (in characters).
|
|
116
|
+
* @param align - Alignment mode: `"l"` (left), `"r"` (right), `"c"` (center). Defaults to `"l"`.
|
|
117
|
+
* @param padchar - Padding character used when `str` is shorter than `width`. Defaults to `" "`.
|
|
118
|
+
* @returns The formatted string with exact length `width` (unless `width` is too small to hold content).
|
|
119
|
+
*/
|
|
120
|
+
export function s_ellipsis(str: string, width: number, align: SAlign = "l", padchar: string = " "): string {
|
|
121
|
+
str = typeof (str) === "string" ? str : str + ""
|
|
122
|
+
width = Number.isFinite(width) ? Math.max(0, Math.trunc(width)) : 0
|
|
123
|
+
if (width === 0) return ""
|
|
124
|
+
align = align != undefined ? align : "l";
|
|
125
|
+
const _align = align.substring(0, 1);
|
|
126
|
+
align = _align === "l" || _align === "r" || _align === "c" ? _align : "l";
|
|
127
|
+
// ##
|
|
128
|
+
padchar = typeof (padchar) === "string" ? (padchar ? padchar : " ") : " ";
|
|
129
|
+
// ##
|
|
130
|
+
// $str = ~s /\r ?\n//g;
|
|
131
|
+
str = str.replace(/\r ?\n/g, "")
|
|
132
|
+
// ##
|
|
133
|
+
let length = str.length
|
|
134
|
+
// #
|
|
135
|
+
if (length === width) {
|
|
136
|
+
return str;
|
|
137
|
+
} else if (length < width) {
|
|
138
|
+
if (align === "r") {
|
|
139
|
+
str = s_pad(str, width, "l", padchar)
|
|
140
|
+
} else if (align === "l") {
|
|
141
|
+
str = s_pad(str, width, "r", padchar)
|
|
142
|
+
} else { // "c"
|
|
143
|
+
str = s_pad(str, width, "c", padchar);
|
|
144
|
+
}
|
|
145
|
+
} else {
|
|
146
|
+
if (width <= 3) {
|
|
147
|
+
return ".".repeat(width)
|
|
148
|
+
}
|
|
149
|
+
if (align === "r") {
|
|
150
|
+
str = "..." + s_right(str, width - 3);
|
|
151
|
+
} else if (align === "c") {
|
|
152
|
+
let m_odd = width % 2;
|
|
153
|
+
let m_mid_len = Math.floor(width / 2);
|
|
154
|
+
let head_str = s_left(str, m_mid_len - 1);
|
|
155
|
+
let tail_str = s_right(str, m_mid_len - (m_odd ? 1 : 2));
|
|
156
|
+
str = head_str + "..." + tail_str;
|
|
157
|
+
} else {
|
|
158
|
+
str = s_left(str, width - 3) + "...";
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
return str;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Convenience wrapper around {@link s_ellipsis} that accepts an options object.
|
|
166
|
+
*
|
|
167
|
+
* @param str - Input value to format. Non-string values are coerced to string by {@link s_ellipsis}.
|
|
168
|
+
* @param opts - Formatting options.
|
|
169
|
+
* @param opts.width - Target width (in characters).
|
|
170
|
+
* @param opts.align - Alignment mode: `"l"` (left), `"r"` (right), `"c"` (center).
|
|
171
|
+
* @param opts.padchar - Padding character used when `str` is shorter than `width`.
|
|
172
|
+
* @returns The formatted string as produced by {@link s_ellipsis}.
|
|
173
|
+
*
|
|
174
|
+
* @example
|
|
175
|
+
* // Pad (left aligned) to width 8 with spaces
|
|
176
|
+
* s_ellipsis_o("abc", { width: 8, align: "l", padchar: " " })
|
|
177
|
+
* // => "abc "
|
|
178
|
+
*
|
|
179
|
+
* @example
|
|
180
|
+
* // Pad (right aligned) to width 8 with '0'
|
|
181
|
+
* s_ellipsis_o("abc", { width: 8, align: "r", padchar: "0" })
|
|
182
|
+
* // => "00000abc"
|
|
183
|
+
*
|
|
184
|
+
* @example
|
|
185
|
+
* // Truncate with ellipsis at end (left align behavior for truncation)
|
|
186
|
+
* s_ellipsis_o("abcdefghijklmnopqrstuvwxyz", { width: 10, align: "l", padchar: " " })
|
|
187
|
+
* // => "abcdefg..."
|
|
188
|
+
*
|
|
189
|
+
* @example
|
|
190
|
+
* // Truncate with ellipsis at start (right align behavior for truncation)
|
|
191
|
+
* s_ellipsis_o("abcdefghijklmnopqrstuvwxyz", { width: 10, align: "r", padchar: " " })
|
|
192
|
+
* // => "...tuvwxyz"
|
|
193
|
+
*
|
|
194
|
+
* @example
|
|
195
|
+
* // Truncate with ellipsis in the middle (center)
|
|
196
|
+
* s_ellipsis_o("abcdefghijklmnopqrstuvwxyz", { width: 10, align: "c", padchar: " " })
|
|
197
|
+
* // => "abcd...xyz"
|
|
198
|
+
*/
|
|
199
|
+
export function s_ellipsis_o(str: string, opts: SEllipsisOptions): string {
|
|
200
|
+
const { width, align, padchar } = opts
|
|
201
|
+
return s_ellipsis(str, width, align, padchar)
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* Parse an `s_xprint` format specifier into a normalized format object.
|
|
206
|
+
*
|
|
207
|
+
* Supports either:
|
|
208
|
+
* - A compact string spec: `"<width>[<align><padchar>]"`, where:
|
|
209
|
+
* - `width` is a positive integer (required)
|
|
210
|
+
* - `align` is optional: `"l"` (left) or `"r"` (right)
|
|
211
|
+
* - `padchar` is optional: a single character used for padding
|
|
212
|
+
* - An already-constructed format object, which is returned as-is.
|
|
213
|
+
*
|
|
214
|
+
* The string form is matched by: `/^(\\d+)(?:([lr])(.?))?$/`
|
|
215
|
+
*
|
|
216
|
+
* @param fmt - Format specifier as a string (e.g. `"10l_"`) or a format object
|
|
217
|
+
* (e.g. `{ width: 10, align: "l", padchar: "_" }`).
|
|
218
|
+
* @returns A format object `{ width, align, padchar }`.
|
|
219
|
+
* @throws {Error} If `fmt` is a string but does not match the expected format.
|
|
220
|
+
*
|
|
221
|
+
* @example
|
|
222
|
+
* // String form: width only
|
|
223
|
+
* s_xprint_extract_fmt("8")
|
|
224
|
+
* // => { width: 8, align: undefined, padchar: undefined }
|
|
225
|
+
*
|
|
226
|
+
* @example
|
|
227
|
+
* // String form: width + align
|
|
228
|
+
* s_xprint_extract_fmt("8l")
|
|
229
|
+
* // => { width: 8, align: "l", padchar: "" }
|
|
230
|
+
*
|
|
231
|
+
* @example
|
|
232
|
+
* // String form: width + align + padchar
|
|
233
|
+
* s_xprint_extract_fmt("8r0")
|
|
234
|
+
* // => { width: 8, align: "r", padchar: "0" }
|
|
235
|
+
*
|
|
236
|
+
* @example
|
|
237
|
+
* // Object form: returned unchanged
|
|
238
|
+
* s_xprint_extract_fmt({ width: 12, align: "l", padchar: " " })
|
|
239
|
+
* // => { width: 12, align: "l", padchar: " " }
|
|
240
|
+
*/
|
|
241
|
+
export function s_xprint_extract_fmt(fmt: string | SXPrintFormat): SXPrintFormat {
|
|
242
|
+
if (typeof (fmt) === "string") {
|
|
243
|
+
const mmm = /^(\d+)(?:([lr])(.?))?$/.exec(fmt)
|
|
244
|
+
if (mmm == null) {
|
|
245
|
+
throw Error("wrong foramt of s_xprint [" + fmt + "]")
|
|
246
|
+
}
|
|
247
|
+
const _align = mmm[2] === "l" || mmm[2] === "r" ? mmm[2] : "l";
|
|
248
|
+
return {
|
|
249
|
+
width: parseInt(mmm[1], 10),
|
|
250
|
+
align: _align,
|
|
251
|
+
padchar: mmm[3]
|
|
252
|
+
}
|
|
253
|
+
} else {
|
|
254
|
+
return fmt
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
/**
|
|
259
|
+
* Normalize a list of `s_xprint` format specifiers into format objects.
|
|
260
|
+
*
|
|
261
|
+
* This is a convenience helper that applies {@link s_xprint_extract_fmt} to each
|
|
262
|
+
* element in `fmts`.
|
|
263
|
+
*
|
|
264
|
+
* Supported input items:
|
|
265
|
+
* - String form: `"<width>[<align><padchar>]"`, e.g. `"8"`, `"10l"`, `"6r0"`
|
|
266
|
+
* - Object form: `{ width, align, padchar }`
|
|
267
|
+
*
|
|
268
|
+
* @param fmts - An array of format specifiers (strings or format objects).
|
|
269
|
+
* @returns An array of normalized format objects: `{ width, align, padchar }`.
|
|
270
|
+
* @throws {Error} If any string format does not match the expected pattern.
|
|
271
|
+
*
|
|
272
|
+
* @example
|
|
273
|
+
* // String formats
|
|
274
|
+
* s_xprint_fmts(["8", "10l_", "6r0"])
|
|
275
|
+
* // => [
|
|
276
|
+
* // { width: 8, align: undefined, padchar: undefined },
|
|
277
|
+
* // { width: 10, align: "l", padchar: "_" },
|
|
278
|
+
* // { width: 6, align: "r", padchar: "0" }
|
|
279
|
+
* // ]
|
|
280
|
+
*
|
|
281
|
+
* @example
|
|
282
|
+
* // Mixed formats (object formats are returned as-is)
|
|
283
|
+
* s_xprint_fmts([{ width: 4, align: "l", padchar: " " }, "3r0"])
|
|
284
|
+
* // => [
|
|
285
|
+
* // { width: 4, align: "l", padchar: " " },
|
|
286
|
+
* // { width: 3, align: "r", padchar: "0" }
|
|
287
|
+
* // ]
|
|
288
|
+
*/
|
|
289
|
+
export function s_xprint_fmts(fmts: Array<string | SXPrintFormat>): SXPrintFormat[] {
|
|
290
|
+
return fmts.map(s_xprint_extract_fmt)
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
/**
|
|
294
|
+
* Format and join a list of columns into a single fixed-width string (table-like output).
|
|
295
|
+
*
|
|
296
|
+
* Each column is formatted using {@link s_ellipsis} with the corresponding `formats` entry.
|
|
297
|
+
* Columns are then joined by `sep`.
|
|
298
|
+
*
|
|
299
|
+
* - `formats` entries accept the same specifiers as {@link s_xprint_extract_fmt}:
|
|
300
|
+
* - String form: `"<width>[<align><padchar>]"`, e.g. `"10"`, `"8l_"`, `"6r0"`
|
|
301
|
+
* - Object form: `{ width, align, padchar }`
|
|
302
|
+
* - The last column can optionally skip ellipsis formatting when `ellipsisEnd === false`.
|
|
303
|
+
*
|
|
304
|
+
* @param params - Options object.
|
|
305
|
+
* @param params.columns - Column values to print. `null`/`undefined` become `""`.
|
|
306
|
+
* @param params.formats - Per-column format specifiers (same length as `columns` is recommended).
|
|
307
|
+
* @param params.sep - Separator string inserted between formatted columns. Defaults to `" "`.
|
|
308
|
+
* @param params.ellipsisEnd - Whether to apply ellipsis formatting to the last column.
|
|
309
|
+
* Defaults to `true`. When `false`, the last column is returned as-is (no padding/truncation).
|
|
310
|
+
* @returns A single formatted line string.
|
|
311
|
+
*
|
|
312
|
+
* @example
|
|
313
|
+
* // Basic usage with string formats (width + align + padchar)
|
|
314
|
+
* // - 1st: width 6, left align, pad with space
|
|
315
|
+
* // - 2nd: width 4, right align, pad with '0'
|
|
316
|
+
* // - 3rd: width 10, left align (will ellipsis if too long)
|
|
317
|
+
* s_xprint({
|
|
318
|
+
* columns: ["id", 7, "abcdefghijklmnopqrstuvwxyz"],
|
|
319
|
+
* formats: ["6l ", "4r0", "10l "],
|
|
320
|
+
* sep: " | "
|
|
321
|
+
* })
|
|
322
|
+
* // => "id | 0007 | abcdefg..."
|
|
323
|
+
*
|
|
324
|
+
* @example
|
|
325
|
+
* // Skip formatting for the last column (useful for "message" / "tail" fields)
|
|
326
|
+
* s_xprint({
|
|
327
|
+
* columns: ["INFO", "startup ok", "this part is not truncated"],
|
|
328
|
+
* formats: ["6l ", "12l ", "8l "],
|
|
329
|
+
* sep: " ",
|
|
330
|
+
* ellipsisEnd: false
|
|
331
|
+
* })
|
|
332
|
+
* // => "INFO startup ok this part is not truncated"
|
|
333
|
+
*/
|
|
334
|
+
export function s_xprint({ columns, formats = [], sep = " ", ellipsisEnd = true }: SXPrintParams = { columns: [], formats: [], sep: " ", ellipsisEnd: true }): string {
|
|
335
|
+
const fmts = formats.map(s_xprint_extract_fmt)
|
|
336
|
+
return columns.map((column, idx) => {
|
|
337
|
+
const column_str = column == null ? "" : String(column);
|
|
338
|
+
const fmt = fmts[idx] ?? { width: column_str.length, align: "l" as SXPrintAlign, padchar: " " }
|
|
339
|
+
const { width, align, padchar } = fmt
|
|
340
|
+
if (!ellipsisEnd && idx === columns.length - 1) {
|
|
341
|
+
return column_str
|
|
342
|
+
} else {
|
|
343
|
+
return s_ellipsis(column_str, width, align, padchar)
|
|
344
|
+
}
|
|
345
|
+
}).join(sep)
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
/**
|
|
349
|
+
* Convert a glob pattern string to a {@link RegExp}.
|
|
350
|
+
*
|
|
351
|
+
* Supports basic glob syntax (`*`) by default, and optionally "extended" glob
|
|
352
|
+
* syntax (e.g. `?`, `[]`, `{a,b}`) and "globstar" (`**`) via {@link opts}.
|
|
353
|
+
*
|
|
354
|
+
* Notes:
|
|
355
|
+
* - When `opts.flags` does not include `g`, the generated regexp is wrapped with
|
|
356
|
+
* `^` and `$` to match the whole input.
|
|
357
|
+
* - When `opts.globstar` is enabled, `**` matches zero or more path segments.
|
|
358
|
+
*
|
|
359
|
+
* @param glob - Glob pattern to convert (must be a string).
|
|
360
|
+
* @param opts - Options controlling how the glob is translated.
|
|
361
|
+
* @param opts.extended - Enable extended glob features: `?`, character classes
|
|
362
|
+
* (`[a-z]`), and groups (`{a,b}`).
|
|
363
|
+
* @param opts.globstar - Enable globstar behavior for `**` (path segment aware).
|
|
364
|
+
* @param opts.flags - RegExp flags passed to the {@link RegExp} constructor
|
|
365
|
+
* (e.g. `"i"`, `"m"`, `"g"`).
|
|
366
|
+
* @returns A {@link RegExp} equivalent to the provided glob.
|
|
367
|
+
* @throws {TypeError} If `glob` is not a string.
|
|
368
|
+
*/
|
|
369
|
+
function glob_to_regexp(glob: string, opts: GlobToRegExpOptions = {}): RegExp {
|
|
370
|
+
if (typeof glob !== 'string') {
|
|
371
|
+
throw new TypeError('Expected a string');
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
var str = String(glob);
|
|
375
|
+
|
|
376
|
+
// The regexp we are building, as a string.
|
|
377
|
+
var reStr = "";
|
|
378
|
+
|
|
379
|
+
// Whether we are matching so called "extended" globs (like bash) and should
|
|
380
|
+
// support single character matching, matching ranges of characters, group
|
|
381
|
+
// matching, etc.
|
|
382
|
+
var extended = opts ? !!opts.extended : false;
|
|
383
|
+
|
|
384
|
+
// When globstar is _false_ (default), '/foo/*' is translated a regexp like
|
|
385
|
+
// '^\/foo\/.*$' which will match any string beginning with '/foo/'
|
|
386
|
+
// When globstar is _true_, '/foo/*' is translated to regexp like
|
|
387
|
+
// '^\/foo\/[^/]*$' which will match any string beginning with '/foo/' BUT
|
|
388
|
+
// which does not have a '/' to the right of it.
|
|
389
|
+
// E.g. with '/foo/*' these will match: '/foo/bar', '/foo/bar.txt' but
|
|
390
|
+
// these will not '/foo/bar/baz', '/foo/bar/baz.txt'
|
|
391
|
+
// Lastely, when globstar is _true_, '/foo/**' is equivelant to '/foo/*' when
|
|
392
|
+
// globstar is _false_
|
|
393
|
+
var globstar = opts ? !!opts.globstar : false;
|
|
394
|
+
|
|
395
|
+
// If we are doing extended matching, this boolean is true when we are inside
|
|
396
|
+
// a group (eg {*.html,*.js}), and false otherwise.
|
|
397
|
+
var inGroup = false;
|
|
398
|
+
|
|
399
|
+
// RegExp flags (eg "i" ) to pass in to RegExp constructor.
|
|
400
|
+
var flags = opts && typeof (opts.flags) === "string" ? opts.flags : "";
|
|
401
|
+
|
|
402
|
+
var c;
|
|
403
|
+
for (var i = 0, len = str.length; i < len; i++) {
|
|
404
|
+
c = str[i];
|
|
405
|
+
|
|
406
|
+
switch (c) {
|
|
407
|
+
case "/":
|
|
408
|
+
case "$":
|
|
409
|
+
case "^":
|
|
410
|
+
case "+":
|
|
411
|
+
case ".":
|
|
412
|
+
case "(":
|
|
413
|
+
case ")":
|
|
414
|
+
case "=":
|
|
415
|
+
case "!":
|
|
416
|
+
case "|":
|
|
417
|
+
reStr += "\\" + c;
|
|
418
|
+
break;
|
|
419
|
+
|
|
420
|
+
case "?":
|
|
421
|
+
if (extended) {
|
|
422
|
+
reStr += ".";
|
|
423
|
+
break;
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
case "[":
|
|
427
|
+
case "]":
|
|
428
|
+
if (extended) {
|
|
429
|
+
reStr += c;
|
|
430
|
+
break;
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
case "{":
|
|
434
|
+
if (extended) {
|
|
435
|
+
inGroup = true;
|
|
436
|
+
reStr += "(";
|
|
437
|
+
break;
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
case "}":
|
|
441
|
+
if (extended) {
|
|
442
|
+
inGroup = false;
|
|
443
|
+
reStr += ")";
|
|
444
|
+
break;
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
case ",":
|
|
448
|
+
if (inGroup) {
|
|
449
|
+
reStr += "|";
|
|
450
|
+
break;
|
|
451
|
+
}
|
|
452
|
+
reStr += "\\" + c;
|
|
453
|
+
break;
|
|
454
|
+
|
|
455
|
+
case "*":
|
|
456
|
+
// Move over all consecutive "*"'s.
|
|
457
|
+
// Also store the previous and next characters
|
|
458
|
+
var prevChar = str[i - 1];
|
|
459
|
+
var starCount = 1;
|
|
460
|
+
while (str[i + 1] === "*") {
|
|
461
|
+
starCount++;
|
|
462
|
+
i++;
|
|
463
|
+
}
|
|
464
|
+
var nextChar = str[i + 1];
|
|
465
|
+
|
|
466
|
+
if (!globstar) {
|
|
467
|
+
// globstar is disabled, so treat any number of "*" as one
|
|
468
|
+
reStr += ".*";
|
|
469
|
+
} else {
|
|
470
|
+
// globstar is enabled, so determine if this is a globstar segment
|
|
471
|
+
var isGlobstar = starCount > 1 // multiple "*"'s
|
|
472
|
+
&& (prevChar === "/" || prevChar === undefined) // from the start of the segment
|
|
473
|
+
&& (nextChar === "/" || nextChar === undefined) // to the end of the segment
|
|
474
|
+
|
|
475
|
+
if (isGlobstar) {
|
|
476
|
+
// it's a globstar, so match zero or more path segments
|
|
477
|
+
reStr += "((?:[^/]*(?:\/|$))*)";
|
|
478
|
+
i++; // move over the "/"
|
|
479
|
+
} else {
|
|
480
|
+
// it's not a globstar, so only match one path segment
|
|
481
|
+
reStr += "([^/]*)";
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
break;
|
|
485
|
+
|
|
486
|
+
default:
|
|
487
|
+
reStr += c;
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
// When regexp 'g' flag is specified don't
|
|
492
|
+
// constrain the regular expression with ^ & $
|
|
493
|
+
if (!flags || !~flags.indexOf('g')) {
|
|
494
|
+
reStr = "^" + reStr + "$";
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
return new RegExp(reStr, flags);
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
/**
|
|
501
|
+
* Test whether a string matches a glob pattern.
|
|
502
|
+
*
|
|
503
|
+
* This is a small wrapper around {@link glob_to_regexp} that converts the given
|
|
504
|
+
* glob `pattern` to a {@link RegExp} and then checks it against `str`.
|
|
505
|
+
*
|
|
506
|
+
* @param pattern - Glob pattern e.g. `"*.ts"`.
|
|
507
|
+
* @param str - The input string to test.
|
|
508
|
+
* @returns `true` if `str` matches `pattern`; otherwise `false`.
|
|
509
|
+
*/
|
|
510
|
+
export function s_glob(pattern: string, str: string): boolean {
|
|
511
|
+
const regex = glob_to_regexp(pattern, {})
|
|
512
|
+
return regex.test(str)
|
|
513
|
+
}
|