Skip to content

Commit a169fa3

Browse files
committed
feat: dynamic columns
Signed-off-by: Lexus Drumgold <[email protected]>
1 parent 3fd5751 commit a169fa3

33 files changed

+649
-446
lines changed

README.md

Lines changed: 57 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@ Wrap a string
2525
- [`lines(thing, config[, options])`](#linesthing-config-options)
2626
- [`wrap(thing, config[, options])`](#wrapthing-config-options)
2727
- [Types](#types)
28+
- [`ColumnsFunction<[T]>`](#columnsfunctiont)
29+
- [`Columns`](#columns)
2830
- [`Config`](#config)
2931
- [`LinesInfo`](#linesinfo)
3032
- [`Options`](#options)
@@ -41,6 +43,7 @@ This is a small, but useful package for word-wrapping a string to a specified co
4143
### Features
4244

4345
\:rainbow: [ansi][] support\
46+
\:triangular\_ruler: configure columns dynamically\
4447
\:unicorn: emoji and fullwidth character support\
4548
\:arrow\_right: indent and pad lines
4649

@@ -133,15 +136,16 @@ Get info about the lines of a wrapped string.
133136

134137
#### Overloads
135138

136-
- `lines(thing: unknown, config: number | string, options?: Options | null | undefined): LinesInfo`
137-
- `lines(thing: unknown, config: Config | number | string): LinesInfo`
139+
- `lines(thing: unknown, config: Columns, options?: Options | null | undefined): LinesInfo`
140+
- `lines(thing: unknown, config: Columns | Config): LinesInfo`
138141

139142
##### Parameters
140143

141144
- `thing` (`unknown`)
142145
— the thing to wrap. non-string values will be converted to strings
143-
- `config` ([`Config`](#config) | `number` | `string`)
144-
— the wrap configuration or the number of columns to wrap the string to
146+
- `config` ([`Columns`](#columns) | [`Config`](#config))
147+
— the wrap configuration, the number of columns to wrap the string to,
148+
or a function that returns the maximum number of columns per line
145149
- `options` ([`Options`](#options) | `null` | `undefined`, `optional`)
146150
— options for wrapping
147151

@@ -155,15 +159,16 @@ Wrap a string to the specified column width.
155159

156160
#### Overloads
157161

158-
- `wrap(thing: unknown, config: number | string, options?: Options | null | undefined): string`
159-
- `wrap(thing: unknown, config: Config | number | string): string`
162+
- `wrap(thing: unknown, config: Columns, options?: Options | null | undefined): string`
163+
- `wrap(thing: unknown, config: Columns | Config): string`
160164

161165
##### Parameters
162166

163167
- `thing` (`unknown`)
164168
— the thing to wrap. non-string values will be converted to strings
165-
- `config` ([`Config`](#config) | `number` | `string`)
166-
— the wrap configuration or the number of columns to wrap the string to
169+
- `config` ([`Columns`](#columns) | [`Config`](#config))
170+
— the wrap configuration, the number of columns to wrap the string to,
171+
or a function that returns the maximum number of columns per line
167172
- `options` ([`Options`](#options) | `null` | `undefined`, `optional`)
168173
— options for wrapping
169174

@@ -175,6 +180,45 @@ Wrap a string to the specified column width.
175180

176181
This package is fully typed with [TypeScript][].
177182

183+
### `ColumnsFunction<[T]>`
184+
185+
Get the maximum number of columns for the line at `index` (`type`).
186+
187+
> 👉 **Note**: The total number of available columns is calculated from the
188+
> maximum number of columns, indentation, and padding.
189+
190+
```ts
191+
type ColumnsFunction<T extends number | string = number | string> = (
192+
this: void,
193+
index: number,
194+
lines?: readonly string[] | null | undefined
195+
) => T
196+
```
197+
198+
#### Type Parameters
199+
200+
- `T` (`number` | `string`, optional)
201+
— the maximum number of columns
202+
203+
#### Parameters
204+
205+
- `index` (`number`)
206+
— the index of the current line, or `-1` on init
207+
- `lines` (`readonly string[]` | `null` | `undefined`, optional)
208+
— the current list of lines
209+
210+
#### Returns
211+
212+
(`T`) The maximum number of columns
213+
214+
### `Columns`
215+
216+
Union of column configurations (`type`).
217+
218+
```ts
219+
type Columns = ColumnsFunction | number | string
220+
```
221+
178222
### `Config`
179223
180224
String wrapping configuration (`interface`).
@@ -185,15 +229,17 @@ String wrapping configuration (`interface`).
185229
186230
#### Properties
187231
188-
- `columns` (`number` | `string`)
189-
— the number of columns to wrap the string to
232+
- `columns` ([`Columns`](#columns))
233+
— the number of columns to wrap the string to, or a function that returns the maximum number of columns per line
190234
191235
### `LinesInfo`
192236
193237
Info about the lines of a wrapped string (`interface`).
194238
195239
#### Properties
196240
241+
- `columns` ([`ColumnsFunction<number>`](#columnsfunctiont))
242+
— get the maximum number of columns per line
197243
- `eol` (`string`)
198244
— the character, or characters, used to mark the end of a line
199245
- `indent` ([`SpacerFunction<string>`](#spacerfunctiont))
@@ -270,7 +316,7 @@ type SpacerFunction<
270316
#### Parameters
271317
272318
- `index` (`number`)
273-
— the index of the current line
319+
— the index of the current line, or `-1` on init
274320
- `lines` (`readonly string[]` | `null` | `undefined`, optional)
275321
— the current list of lines
276322

__tests__/plugins/chai/satisfy-columns.mts

Lines changed: 51 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,9 @@
44
*/
55

66
import gs from '#internal/gs'
7-
import satisfyColumns from '#tests/utils/satisfy-columns'
7+
import type { ColumnsFunction } from '@flex-development/string-wrap'
88
import stripAnsi from '@flex-development/strip-ansi'
9+
import { ok } from 'devlop'
910

1011
export default plugin
1112

@@ -29,27 +30,62 @@ function plugin(chai: Chai.ChaiStatic, utils: Chai.ChaiUtils): undefined {
2930
/**
3031
* @this {Chai.Assertion}
3132
*
32-
* @param {number | string} columns
33-
* The maximum (inclusive) number of columns
33+
* @param {ColumnsFunction<number>} columns
34+
* A function that returns the maximum number of columns per line
3435
* @return {undefined}
3536
*/
3637
function satisfy(
3738
this: Chai.Assertion,
38-
columns: number | string
39+
columns: ColumnsFunction<number>
3940
): undefined {
4041
/**
41-
* The line to check.
42+
* The lines to check.
4243
*
43-
* @const {string} line
44+
* @const {string[]} lines
4445
*/
45-
const line: string = utils.flag(this, 'object')
46-
47-
return void this.assert(
48-
satisfyColumns(columns)(line),
49-
'expected length of #{this} to be in range [0,#{exp}] but got #{act}',
50-
'expected length of #{this} to not be in range [0,#{exp}] but got #{act}',
51-
+columns,
52-
gs.countGraphemes(stripAnsi(line))
53-
)
46+
const lines: string[] = utils.flag(this, 'object')
47+
48+
/**
49+
* The index of the current line.
50+
*
51+
* @var {number} i
52+
*/
53+
let i: number = -1
54+
55+
// check size of each line.
56+
while (++i < lines.length) {
57+
/**
58+
* The current line.
59+
*
60+
* @const {string | undefined} line
61+
*/
62+
const line: string | undefined = lines[i]
63+
64+
ok(typeof line === 'string', 'expected `line[index]`')
65+
66+
/**
67+
* The maximum number of allowed columns.
68+
*
69+
* @const {number} max
70+
*/
71+
const max: number = columns(i, lines.slice(0, i))
72+
73+
/**
74+
* The number of graphemes in the line.
75+
*
76+
* @const {number} size
77+
*/
78+
const size: number = gs.countGraphemes(stripAnsi(line))
79+
80+
void this.assert(
81+
size <= max,
82+
`expected line[${i}] size to be in range [0,#{exp}] but got #{act}`,
83+
`expected line[${i}] size to not be in range [0,#{exp}] but got #{act}`,
84+
max,
85+
size
86+
)
87+
}
88+
89+
return void columns
5490
}
5591
}

__tests__/utils/satisfy-columns.mts

Lines changed: 0 additions & 42 deletions
This file was deleted.

src/constructs/sequence.mts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ function resolveSequence(this: void, events: Event[]): Event[] {
6262

6363
// fill columns completely,
6464
// or break long sequences that don't fit on the current line.
65-
if (self.fill || (seq > self.cols && self.hard)) {
65+
if (self.fill || (seq > self.ac && self.hard)) {
6666
/**
6767
* The current sequence.
6868
*
@@ -78,7 +78,7 @@ function resolveSequence(this: void, events: Event[]): Event[] {
7878
*
7979
* @const {number} space
8080
*/
81-
const space: number = self.cols - width(self.line)
81+
const space: number = self.ac - width(self.line)
8282

8383
/**
8484
* The longest prefix of {@linkcode sequence} that fits into the
@@ -97,7 +97,7 @@ function resolveSequence(this: void, events: Event[]): Event[] {
9797
if (take) sequence = sequence.slice(take.length)
9898

9999
// start new line if there is no more space.
100-
if (width(self.line) === self.cols) self.flush()
100+
if (width(self.line) === self.ac) self.flush()
101101
}
102102
} else {
103103
/**
@@ -108,7 +108,7 @@ function resolveSequence(this: void, events: Event[]): Event[] {
108108
const columns: number = width(self.line)
109109

110110
// start new line if sequence cannot fit.
111-
if (columns && columns + seq > self.cols) self.flush()
111+
if (columns && columns + seq > self.ac) self.flush()
112112

113113
// sequence fits on line normally, or soft wrap is enabled
114114
// and long words may extend past the configured column width.

src/constructs/space.mts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ function resolveSpace(this: void, events: Event[]): Event[] {
5353
// start a new line if a space cannot fit,
5454
// or add the space to the current line if the line already has content.
5555
// if the line has no content, but should not be trimmed, also add the space.
56-
if (width(self.line) + token.value.length > self.cols) {
56+
if (width(self.line) + token.value.length > self.ac) {
5757
self.flush()
5858
if (!self.trim) self.line += token.value
5959
} else if (width(self.line) || !self.trim) {

src/interfaces/__tests__/config.spec-d.mts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,16 +4,16 @@
44
*/
55

66
import type TestSubject from '#interfaces/config'
7-
import type { Options } from '@flex-development/string-wrap'
7+
import type { Columns, Options } from '@flex-development/string-wrap'
88

99
describe('unit-d:interfaces/Config', () => {
1010
it('should extend Options', () => {
1111
expectTypeOf<TestSubject>().toExtend<Options>()
1212
})
1313

14-
it('should match [columns: number | string]', () => {
14+
it('should match [columns: Columns]', () => {
1515
expectTypeOf<TestSubject>()
1616
.toHaveProperty('columns')
17-
.toEqualTypeOf<number | string>()
17+
.toEqualTypeOf<Columns>()
1818
})
1919
})

src/interfaces/__tests__/lines-info.spec-d.mts

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,18 @@
44
*/
55

66
import type TestSubject from '#interfaces/lines-info'
7-
import type { SpacerFunction } from '@flex-development/string-wrap'
7+
import type {
8+
ColumnsFunction,
9+
SpacerFunction
10+
} from '@flex-development/string-wrap'
811

912
describe('unit-d:interfaces/LinesInfo', () => {
13+
it('should match [columns: ColumnsFunction<number>]', () => {
14+
expectTypeOf<TestSubject>()
15+
.toHaveProperty('columns')
16+
.toEqualTypeOf<ColumnsFunction<number>>()
17+
})
18+
1019
it('should match [eol: string]', () => {
1120
expectTypeOf<TestSubject>().toHaveProperty('eol').toEqualTypeOf<string>()
1221
})

src/interfaces/config.mts

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
* @module string-wrap/interfaces/Config
44
*/
55

6-
import type { Options } from '@flex-development/string-wrap'
6+
import type { Columns, Options } from '@flex-development/string-wrap'
77

88
/**
99
* String wrapping configuration.
@@ -14,9 +14,12 @@ import type { Options } from '@flex-development/string-wrap'
1414
*/
1515
interface Config extends Options {
1616
/**
17-
* The number of columns to wrap the string to.
17+
* The number of columns to wrap the string to,
18+
* or a function that returns the maximum number of columns per line.
19+
*
20+
* @see {@linkcode Columns}
1821
*/
19-
columns: number | string
22+
columns: Columns
2023
}
2124

2225
export type { Config as default }

src/interfaces/lines-info.mts

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,22 @@
33
* @module string-wrap/interfaces/LinesInfo
44
*/
55

6-
import type { SpacerFunction } from '@flex-development/string-wrap'
6+
import type {
7+
ColumnsFunction,
8+
SpacerFunction
9+
} from '@flex-development/string-wrap'
710

811
/**
912
* Info about the lines of a wrapped string.
1013
*/
1114
interface LinesInfo {
15+
/**
16+
* Get the maximum number of columns per line.
17+
*
18+
* @see {ColumnsFunction}
19+
*/
20+
columns: ColumnsFunction<number>
21+
1222
/**
1323
* The character, or characters, used to mark the end of a line.
1424
*/

0 commit comments

Comments
 (0)