rsformat 1.2.1 → 1.4.0

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/README.md CHANGED
@@ -1,163 +1,228 @@
1
- # RSFormat
2
-
3
- RSFormat is a string formatting/printing library for JavaScript. It offers a minimal, yet powerful and flexible alternative to the string formatting and printing provided by `console.log`.
4
-
5
- ```js
6
- import { rs, println } from 'rsformat';
7
-
8
- let s = rs`${15} is ${15}:#X in hex`;
9
- // s == '15 is 0xF in hex'
10
-
11
- println(rs`${'a'}:^5`);
12
- // Prints ' a '
13
- ```
14
-
15
- ## Motivation
16
-
17
- `console.log` is an odd method: its output can be affected by functions called before/after it (such as `console.group`), or their order affected by what parameters there are. For example, when calling `console.log(string, number)`, number can come either after or inside `string` depending on the value of `string`.
18
-
19
- This behaviour has largely been superseded at a language level by template literals, which allow formatting of parameters directly inside the templates, causing these methods to have unnecessary overhead and undesired behaviour.
20
-
21
- RSFormat builds onto template literals by implementing Rust-style format specifiers and lightweight printing functions. Rust formatting includes a lot of convenient operators for formatting text, such as padding/alignment, printing numbers in a given base, specifying decimal precision, etc.
22
-
23
- ## Usage
24
-
25
- You can install RSFormat from [npm](https://www.npmjs.com/package/rsformat):
26
-
27
- ```sh
28
- npm install rsformat
29
- ```
30
-
31
- ### Basic formatting and printing to console
32
-
33
- The `rs` template tag can be used to enable rust-style formatting in a template.
34
-
35
- To reference a previous or following argument, use `rs.ref` with the argument number. This is useful if you want to reuse a complicated expression without having to declare it separately.
36
-
37
- ```js
38
- import { rs, println } from 'rsformat'; // ESM
39
- const { rs, println } = require('rsformat'); // CommonJS
40
-
41
- let number = 14;
42
-
43
- let info = rs`${number+1} is ${rs.ref(0)}:x in hex`; // info == '15 is f in hex'
44
- ```
45
-
46
- > NB: templates tagged with `rs` are instances of a special class `RsString` that extends `String`, rather than a primitive value. This is to enable colors for debug formatting inside the printing functions. This difference should not affect normal usage, but `rs.raw` can be used as an alternative tag to get a primitive `string`.
47
-
48
- The printing functions can be called with plain strings, instances of `String` or templates formatted with `rs`:
49
-
50
- ```ts
51
- println('Hello World');
52
- println(`This template did ${'Not'} need fancy formatting`);
53
- println(rs`...`);
54
- ```
55
-
56
- ### Format Specifiers
57
-
58
- Format specifiers can be used by adding `:` after the format argument, and will format the value differently inside the string. See the [rust format docs](https://doc.rust-lang.org/std/fmt/) for more detailed information on format specifiers.
59
-
60
- This implementation differs from the Rust one in a few ways:
61
-
62
- - Rather than escaping the braces using `{{` or `}}`, the formatting colon can be escaped using `::`.
63
- - Different parameters are referenced using `rs.ref(n)` rather than the number literal `n`.
64
- - To separate a formatting specifier from the rest of the string without adding a space, an extra closing colon must be added (eg. `:#?:foo` - specifier gets parsed as `:#?`)
65
- - The `-` sign (unused in Rust) will add a space if the number is positive to align it with negative numbers without showing a `+`.
66
- - Pointer format type `p` is unsupported.
67
- - Hexadecimal debug types `x?` and `X?` are unsupported.
68
- - Specifying precision dynamically with `*` is unsupported. Instead, precision and width can both be specified dynamically by using a separate number parameter in place of the number.
69
- - New format types have been added:
70
- - `N` for uppercase ordinal suffixing of numbers (rounded to integers)
71
- - `n` for lowercase ordinal suffixing of numbers (rounded to integers)
72
-
73
- #### Different formatting types
74
-
75
- The debug format specifier `?` uses `util.inspect` to stringify the parameter rather than `toString`.
76
-
77
- ```js
78
- let obj = { a: 1 };
79
- println(rs`${obj}`); // prints '[object Object]'
80
- println(rs`${obj}:?`); // prints '{ a: 1 }'
81
- ```
82
-
83
- The provided printing functions will display colors in the output of `util.inspect`, but otherwise it will be formatted without color.
84
-
85
- The specifiers `b`,`o`,`x`,`X`,`e`,`E`,`n`,`N` will convert a `number` or `bigint` parameter to:
86
- - `b`: binary
87
- - `o`: octal
88
- - `x`/`X`: lowercase/uppercase hexadecimal
89
- - `e`/`E`: lowercase/uppercase scientific notation
90
- - `n`/`N`: lowercase/uppercase ordinal suffixed string (rounded to integer)
91
-
92
- ```js
93
- let advancedInfo = (n) => rs`${n} is ${n}:x in hex, ${n}:b in binary and ${n}:o in octal`;
94
-
95
- advancedInfo(15); // '15 is f in hex, 1111 in binary and 17 in octal'
96
-
97
- let hugeNumber = 1000n;
98
- let science = rs`${hugeNumber}:E`; // '1E3'
99
- let ordinal = rs`${hugeNumber}:n`; // '1000th'
100
- ```
101
-
102
- #### Padding, Alignment
103
-
104
- Values can be aligned using any fill character (will default to a space ` `), and either left, center or right aligned with `<`, `^` or `>` respectively (will default to right alignment `>`). You will also have to specify a width with an integer after the alignment, or provide a separate number parameter.
105
-
106
- ```js
107
- /*
108
- Will print a pyramid of 'a's:
109
- ' a '
110
- ' aaa '
111
- 'aaaaa'
112
- */
113
- let pyramidLevels = ['a', 'aaa', 'aaaaa'];
114
- for(let value of pyramidLevels) {
115
- println(rs`${value}:^5`);
116
- }
117
- ```
118
-
119
- ```js
120
- rs`${[1,2]}:.>${7}` // '....1,2'
121
- ```
122
-
123
- #### Pretty Printing
124
-
125
- In some instances (namely debug, binary, octal and hexadecimal formatting), adding a `#` before the format specifier will use an alternative 'pretty' printing style. This amounts to using multiline `util.inspect` for debug printing (spanning multiple lines), and adding `0b`/`0o`/`0x` as a prefix for the numbers in the respective bases.
126
-
127
- ```js
128
- rs`${255}:#X` // '0xFF'
129
- ```
130
-
131
- #### Specific Number Formatting
132
-
133
- Specifically for `number` and `bigint` values, a `0` can be placed before the width to pad the number with zeroes instead. This will account for signs and possible formatting differences.
134
-
135
- ```js
136
- rs`${15}:#07x` // '0x0000F'
137
- ```
138
-
139
- Decimal precision can be specified for numbers by adding a `.` and specifying an integer for precision. An additional parameter can also be provided to do this dynamically.
140
-
141
- ```js
142
- rs`${1.23456789}:.3` // '1.234'
143
- rs`${-1}:.${3}` // '-1.000'
144
- ```
145
-
146
- Adding a `+` to the formatting specifier will print the sign regardless of whether the number is negative.
147
- Adding a `-` will instead add a space if the number is positive.
148
-
149
- ```js
150
- rs`${1}:+` // '+1'
151
- rs`${1}:-` // ' 1'
152
- ```
153
-
154
- ## Older versions of RSFormat
155
-
156
- Versions of RSFormat on npm prior to `1.0.0` provide formatting and printing functions that are more similar in syntax to Rust, using plain strings instead of tagged templates:
157
-
158
- ```js
159
- import { format } from 'rsformat';
160
- format('{} is {0:#x} in hex', 15); // '15 is 0xf in hex'
161
- ```
162
-
1
+ # RSFormat
2
+
3
+ RSFormat is a string formatting/printing library for JavaScript. It offers a minimal, yet powerful and flexible alternative to the string formatting and printing provided by `console.log`.
4
+
5
+ ```js
6
+ import { rs, println } from 'rsformat';
7
+
8
+ let s = rs`${15} is ${15}:#X in hex`;
9
+ // s == '15 is 0xF in hex'
10
+
11
+ println(rs`${'a'}:^5`);
12
+ // Prints ' a '
13
+ ```
14
+
15
+ ## Table of Contents
16
+
17
+ - [Motivation](#motivation)
18
+ - [Usage](#usage)
19
+ - [Basic formatting and printing to console](#basic-formatting-and-printing-to-console)
20
+ - [Decorating terminal output](#decorating-terminal-output)
21
+ - [Format specifiers](#format-specifiers)
22
+ - [Different formatting types](#different-formatting-types)
23
+ - [Padding, Alignment](#padding-alignment)
24
+ - [Pretty printing with `#`](#pretty-printing-with-)
25
+ - [Specific number formatting](#specific-number-formatting)
26
+ - [Specific string formatting](#specific-string-formatting)
27
+ - [Formatting without `rs`](#formatting-without-rs)
28
+ - [Older versions of RSFormat](#older-versions-of-rsformat)
29
+
30
+ ## Motivation
31
+
32
+ `console.log` is an odd method: its output can be affected by functions called before/after it (such as `console.group`), or their order affected by what parameters there are. For example, when calling `console.log(string, number)`, number can come either after or inside `string` depending on the value of `string`.
33
+
34
+ This behaviour has largely been superseded at a language level by template literals, which allow formatting of parameters directly inside the templates, causing these methods to have unnecessary overhead and undesired behaviour.
35
+
36
+ RSFormat builds onto template literals by implementing Rust-style format specifiers and lightweight printing functions. Rust formatting includes a lot of convenient operators for formatting text, such as padding/alignment, printing numbers in a given base, specifying decimal precision, etc.
37
+
38
+ ## Usage
39
+
40
+ You can install RSFormat from [npm](https://www.npmjs.com/package/rsformat):
41
+
42
+ ```sh
43
+ npm install rsformat
44
+ ```
45
+
46
+ ### Basic formatting and printing to console
47
+
48
+ The `rs` template tag can be used to enable rust-style formatting in a template.
49
+
50
+ To reference a previous or following argument, use `rs.ref` with the argument number. This is useful if you want to reuse a complicated expression without having to declare it separately.
51
+
52
+ ```js
53
+ import { rs, println } from 'rsformat'; // ESM
54
+ const { rs, println } = require('rsformat'); // CommonJS
55
+
56
+ let number = 14;
57
+
58
+ let info = rs`${number+1} is ${rs.ref(0)}:x in hex`; // info == '15 is f in hex'
59
+ ```
60
+
61
+ > NB: templates tagged with `rs` are instances of a special class `RsString` that extends `String`, rather than a primitive value. This is to enable colours for debug formatting inside the printing functions. This difference should not affect normal usage, but `rs.raw` can be used as an alternative tag to get a primitive `string`.
62
+
63
+ The printing functions can be called with plain strings, instances of `String` or templates formatted with `rs`:
64
+
65
+ ```ts
66
+ println('Hello World');
67
+ println(`This template did ${'Not'} need fancy formatting`);
68
+ println(rs`...`);
69
+ ```
70
+
71
+ ### Decorating terminal output
72
+
73
+ If you want to decorate text for terminal output, you can use `rs.style`, which will format a string using one (or more with an array) of [the modifiers provided by node's `util` module](https://nodejs.org/docs/latest-v22.x/api/util.html#modifiers).
74
+
75
+ This is a re-export of node's `util.styleText`, and is thus aware of whether the current stdout will support the provided styles.
76
+
77
+ ```ts
78
+ println(rs.style("red", "I am angry"));
79
+ println(rs.style(["red", "bold", "underline"], "I am very angry"));
80
+ ```
81
+
82
+ This also works if passed inside `rs` tagged templates.
83
+
84
+ ### Format Specifiers
85
+
86
+ Format specifiers can be used by adding a `:` after the format argument, and will format the value differently inside the string.
87
+
88
+ See [docs.md](./docs.md) for a detailed yet quick reference for format specifiers.
89
+
90
+ #### Different formatting types
91
+
92
+ The debug format specifier `?` uses `util.inspect` to stringify the parameter rather than `toString`.
93
+
94
+ ```js
95
+ let obj = { a: 1 };
96
+ println(rs`${obj}`); // prints '[object Object]'
97
+ println(rs`${obj}:?`); // prints '{ a: 1 }'
98
+ ```
99
+
100
+ The provided printing functions will display colours in the output of `util.inspect`, but otherwise it will be formatted without colour.
101
+
102
+ The specifiers `b`,`o`,`x`,`X`,`e`,`E`,`n`,`N` will convert a `number` or `bigint` parameter to:
103
+ - `b`: binary
104
+ - `o`: octal
105
+ - `x`/`X`: lowercase/uppercase hexadecimal
106
+ - `e`/`E`: lowercase/uppercase scientific notation
107
+ - `n`/`N`: lowercase/uppercase ordinal suffixed string (rounded to integer)
108
+
109
+ ```js
110
+ let advancedInfo = (n) => rs`${n} is ${n}:x in hex, ${n}:b in binary and ${n}:o in octal`;
111
+
112
+ advancedInfo(15); // '15 is f in hex, 1111 in binary and 17 in octal'
113
+
114
+ let hugeNumber = 1000n;
115
+ let science = rs`${hugeNumber}:E`; // '1E3'
116
+ let ordinal = rs`${hugeNumber}:n`; // '1000th'
117
+ ```
118
+
119
+ #### Padding, Alignment
120
+
121
+ Values can be aligned using any fill character (will default to a space ` `), and either left, center or right aligned with `<`, `^` or `>` respectively (will default to right alignment `>`). You will also have to specify a width with an integer after the alignment, or provide a separate number parameter.
122
+
123
+ ```js
124
+ /*
125
+ Will print a pyramid of 'a's:
126
+ ' a '
127
+ ' aaa '
128
+ 'aaaaa'
129
+ */
130
+ let pyramidLevels = ['a', 'aaa', 'aaaaa'];
131
+ for(let value of pyramidLevels) {
132
+ println(rs`${value}:^5`);
133
+ }
134
+
135
+ // More powerful equivalent:
136
+ const character = 'a';
137
+ const baseWidth = 5;
138
+ for(let width = 1; width <= baseWidth; width += 2) {
139
+ println(rs`${character.repeat(width)}:^${baseWidth}`);
140
+ }
141
+ ```
142
+
143
+ ```js
144
+ rs`${[1,2]}:.>7` // '....1,2'
145
+ ```
146
+
147
+ #### Pretty printing with `#`
148
+
149
+ In some instances (namely debug, binary, octal and hexadecimal formatting), adding a `#` before the format specifier will use an alternative 'pretty' printing style. This amounts to using multiline `util.inspect` for debug printing (spanning multiple lines), and adding `0b`/`0o`/`0x` as a prefix for the numbers in the respective bases.
150
+
151
+ ```js
152
+ rs`${255}:#X` // '0xFF'
153
+ ```
154
+
155
+ #### Specific number formatting
156
+
157
+ Specifically for `number` and `bigint` values, a `0` can be placed before the width to pad the number with zeroes instead. This will account for signs and possible formatting differences.
158
+
159
+ ```js
160
+ rs`${15}:#07x` // '0x0000f'
161
+ ```
162
+
163
+ Decimal precision can be specified for numbers by adding a `.` and specifying an integer for precision. An additional parameter can also be provided to do this dynamically.
164
+
165
+ ```js
166
+ rs`${1.23456789}:.3` // '1.235'
167
+ rs`${-1}:.${3}` // '-1.000'
168
+ ```
169
+
170
+ Adding a `+` to the formatting specifier will print the sign regardless of whether the number is negative.
171
+ Adding a `-` will instead add a space if the number is positive.
172
+
173
+ ```js
174
+ rs`${1}:+` // '+1'
175
+ rs`${1}:-` // ' 1'
176
+ ```
177
+
178
+ #### Specific string formatting
179
+
180
+ Adding a `+` or `-` to a formatting specifier of a string will instead convert it to uppercase or lowercase respectively.
181
+
182
+ ```js
183
+ let str = "Hello!"
184
+ let str_upper = rs`${str}:+` // 'HELLO!'
185
+ let str_lower = rs`${str}:-` // 'hello!'
186
+ ```
187
+
188
+ Specifying precision will truncate the string to the given length.
189
+
190
+ ```js
191
+ let str = "Hello!"
192
+ let str_truncated = rs`${str}:.3` // 'Hel'
193
+ ```
194
+
195
+ ## Formatting without `rs`
196
+
197
+ If you want to format a single value without using an `rs` template, you can use the `formatParam` function. It provides a more explicit, object‑based API and avoids parsing format specifiers.
198
+
199
+ > NB: formatParam returns an array with the raw and debug-colored string at indices `0` and `1` respectively.
200
+
201
+ ```ts
202
+ import { formatParam } from "rsformat/format";
203
+
204
+ // Equivalent to `${255}:+#09X` or `${255}:<+#09.0X`
205
+ let [ pretty255 ] = formatParam(255, {
206
+ fill: '',
207
+ align: '<',
208
+ force_sign: '+',
209
+ pretty: true,
210
+ pad_zeroes: true,
211
+ width: 9,
212
+ precision: 0,
213
+ type: "X"
214
+ });
215
+
216
+ // pretty255 == '+0x0000FF'
217
+ ```
218
+
219
+ ## Older versions of RSFormat
220
+
221
+ Versions of RSFormat on npm prior to `1.0.0` provide formatting and printing functions that are more similar in syntax to Rust, using plain strings instead of tagged templates:
222
+
223
+ ```js
224
+ import { format } from 'rsformat';
225
+ format('{} is {0:#x} in hex', 15); // '15 is 0xf in hex'
226
+ ```
227
+
163
228
  See the `old` branch for more detailed documentation. The last version to use this formatting was `0.2.5`.
package/docs.md ADDED
@@ -0,0 +1,59 @@
1
+ # rsformat Format Specifier Documentation
2
+
3
+ A rsformat format specifier is parsed as follows:
4
+
5
+ - `${value}::` Will escape the colon character and replace it with a single `:`. Stringification of `value` will occur as in normal format strings.
6
+
7
+ - `${value}:[fill][align][sign][#][0][width][.(precision)][format_type][:]` is an unescaped format specifier and will parse the string as follows:
8
+
9
+ | Name | Syntax | Purpose | Default value |
10
+ | ------------- | ---------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------- | ------------- |
11
+ | `fill` | any single character | The fill character for alignment | space (` `) |
12
+ | `align` | `<`, `^` or `>` | The alignment direction (left, center, right) | right (`>`) |
13
+ | `sign` | `+` or `-` | Pad a positive number with `+` or a space ` ` (or convert a string to uppercase/lowercase) | None |
14
+ | `#` | `#` | Pretty printing (Add prefixes `0b`, `0o` and `0x` to binary, octal and hex-formatted numbers, and use non-compact debug formatting) | None |
15
+ | `0` | `0` | Pads numbers with `0` characters instead of using fill/align | None |
16
+ | `width` | any positive integer (in string or as ${} parameter) | Minimum width for fill/alignment | 0 |
17
+ | `precision` | `.` + any positive integer (in string or as ${} parameter) | Minimum precision for non-integer numbers | 0 |
18
+ | `format_type` | `?`,`o`,`x`,`X`,`b`,`e`,`E`,`n` or `N` | Format type (see [Format types](#format-types)) | None |
19
+ | `:` | `:` | Add to the end of a format specifier to not have to insert a space after it | None |
20
+
21
+ Every single one of the above values is optional, but must be included in that order.
22
+
23
+ ## Format types
24
+
25
+ | Character | Purpose | Example |
26
+ | :-------: | ------------------------------------------------------------ | -------------------------- |
27
+ | `?` | Debug formatting (`util.inspect` rather than `toString`) | `[10]` -> `[10]` |
28
+ | `o` | Octal number formatting | `10` -> `12` or `0o12` |
29
+ | `x` | Hexadecimal number formatting | `10` -> `a` or `0xa` |
30
+ | `X` | Uppercase hexadecimal number formatting | `10` -> `A` or `0xA` |
31
+ | `b` | Binary number formatting | `10` -> `1010` or `0b1010` |
32
+ | `e` | Scientific notation number formatting | `10` -> `1e1` |
33
+ | `E` | Uppercase scientific notation number formatting | `10` -> `1E1` |
34
+ | `n` | Ordinal-suffixed number formatting | `10` -> `10th` |
35
+ | `N` | Uppercase ordinal-suffixed number formatting | `10` -> `10TH` |
36
+
37
+ ## Examples
38
+
39
+ `${"abc"}:+:!` Will capitalise `"abc"` with an exclamation mark right after it, ie. `ABC!`
40
+
41
+ `${15}:#08x` Will convert `15` to hexadecimal, add `0x` and pad it with 0s until it is 8 characters wide, ie. `0x00000F`
42
+
43
+ `${1.2345678}:,^10.${3}` will round `1.2345678` to 3 decimal places and center align it with `,` until it is 10 characters wide, ie. `,,1.234,,,`
44
+
45
+ ## rsformat specifiers for those familiar with Rust formatting
46
+
47
+ This implementation parses formatting very similarly to Rust, but differing in a few key ways:
48
+
49
+ - Rather than escaping the braces using `{{` or `}}`, the formatting colon can be escaped using `::`.
50
+ - Different parameters are referenced using `rs.ref(n)` rather than the number literal `n`.
51
+ - To separate a formatting specifier from the rest of the string without adding a space, an extra closing colon must be added (eg. `:#?:foo` - specifier gets parsed as `:#?`)
52
+ - The `-` sign (unused in Rust) will add a space if the number is positive to align it with negative numbers without showing a `+`.
53
+ - When used on strings, `+` and `-` sign specifiers will conver them to uppercase and lowercase respectively
54
+ - Pointer format type `p` is unsupported.
55
+ - Hexadecimal debug types `x?` and `X?` are unsupported.
56
+ - Specifying precision dynamically with `*` is unsupported. Instead, precision and width can both be specified dynamically by using a separate number parameter in place of the number.
57
+ - New format types have been added:
58
+ - `N` for uppercase ordinal suffixing of numbers (rounded to integers)
59
+ - `n` for lowercase ordinal suffixing of numbers (rounded to integers)
package/lib/format.d.ts CHANGED
@@ -1,3 +1,4 @@
1
+ import util from 'node:util';
1
2
  /**
2
3
  * Type representing a string formatted by `rs`.
3
4
  * An extension of `String`.
@@ -43,4 +44,6 @@ type FormatSpecifier = {
43
44
  * @returns `param` as a debug-colored and raw formatted string
44
45
  */
45
46
  export declare function formatParam(param: any, format: FormatSpecifier): [string, string];
47
+ /** Re-export of node's `util.styleText`. */
48
+ export declare const style: typeof util.styleText;
46
49
  export {};
package/lib/format.js CHANGED
@@ -3,7 +3,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
3
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.RsString = void 0;
6
+ exports.style = exports.RsString = void 0;
7
7
  exports.buildString = buildString;
8
8
  exports.formatParam = formatParam;
9
9
  const node_util_1 = __importDefault(require("node:util"));
@@ -103,7 +103,7 @@ function buildString(strings, params) {
103
103
  idx++;
104
104
  width = Number(string.substring(width_substring_start, idx));
105
105
  }
106
- else if (idx == string.length) {
106
+ else if (idx == string.length && i < params.length) {
107
107
  // Grab the next parameter and fuse the string with the next one
108
108
  width = params[i];
109
109
  if (typeof width != 'number')
@@ -116,7 +116,7 @@ function buildString(strings, params) {
116
116
  // Grab the next parameter and fuse the string with the next one
117
117
  precision = params[i];
118
118
  if (typeof precision != 'number')
119
- throw error(i - 1, idx, `Expected a number or number parameter for precision specifier after . (found ${string[idx] ? "'" + string[idx] + "'" : typeof width + ' parameter'}).\nIf the next parameter was not meant to be a precision number, add a : to the end of the formatting specifier.`);
119
+ throw error(i - 1, idx, `Expected a number or number parameter for precision specifier after . (found ${string[idx] ? "'" + string[idx] + "'" : typeof precision + ' parameter'}).\nIf the next parameter was not meant to be a precision number, add a : to the end of the formatting specifier.`);
120
120
  string += strings[++i];
121
121
  }
122
122
  else {
@@ -176,69 +176,80 @@ function buildString(strings, params) {
176
176
  */
177
177
  function formatParam(param, format) {
178
178
  let param_type = typeof param;
179
+ let base = 10;
180
+ switch (format.type) {
181
+ case 'o':
182
+ base = 8;
183
+ break;
184
+ case 'x':
185
+ case 'X':
186
+ base = 16;
187
+ break;
188
+ case 'b':
189
+ base = 2;
190
+ break;
191
+ }
192
+ let param_raw;
179
193
  let param_colored = "";
180
- // embed RsStrings directly
194
+ // embed Strings directly
181
195
  if (param instanceof String && format.type != '?') {
196
+ param_raw = param.toString(false);
182
197
  param_colored = param.toString(true);
183
- param = param.toString(false);
184
198
  }
185
199
  else
186
200
  switch (format.type) {
187
201
  // Process format type
188
202
  case 'o':
189
- param = param.toString(8);
190
- break;
191
203
  case 'x':
192
- param = param.toString(16);
193
- break;
194
204
  case 'X':
195
- param = param.toString(16).toUpperCase();
196
- break;
197
205
  case 'b':
198
- param = param.toString(2);
206
+ param_raw = roundInBase(param, base, format.precision);
207
+ if (format.type == "X")
208
+ param_raw = param_raw.toUpperCase();
199
209
  break;
200
210
  case 'e':
201
211
  case 'E':
202
212
  if (param_type != 'number' && param_type != 'bigint') {
203
- param = param.toString();
213
+ param_raw = param.toString();
204
214
  break;
205
215
  }
206
- param = param.toLocaleString('en-US', { notation: 'scientific', maximumFractionDigits: 20 });
216
+ param_raw = param.toLocaleString('en-US', { notation: 'scientific', maximumFractionDigits: 20 });
207
217
  if (format.type == 'e')
208
- param = param.toLowercase();
218
+ param_raw = param_raw.toLowerCase();
219
+ // Do not pad with zeroes when using scientific formatting
220
+ format.pad_zeroes = false;
209
221
  break;
210
222
  case 'n':
211
223
  case 'N':
212
224
  if (param_type != 'number' && param_type != 'bigint') {
213
- param = param.toString();
225
+ param_raw = param.toString();
214
226
  break;
215
227
  }
216
228
  // Round and add suffix
217
229
  if (param_type == 'number')
218
230
  param = Math.round(param);
219
- param = param.toString();
220
- let last_2_digits = param.substring(param.length - 2);
231
+ param_raw = param.toString();
232
+ let last_2_digits = param_raw.substring(param_raw.length - 2);
221
233
  if (last_2_digits == '11' || last_2_digits == '12' || last_2_digits == '13') {
222
- param = param + 'th';
234
+ param_raw += 'th';
223
235
  }
224
236
  else
225
237
  switch (last_2_digits[last_2_digits.length - 1]) {
226
238
  case '1':
227
- param = param + 'st';
239
+ param_raw += 'st';
228
240
  break;
229
241
  case '2':
230
- param = param + 'nd';
242
+ param_raw += 'nd';
231
243
  break;
232
244
  case '3':
233
- param = param + 'rd';
245
+ param_raw += 'rd';
234
246
  break;
235
- default: param = param + 'th';
247
+ default: param_raw += 'th';
236
248
  }
237
249
  if (format.type == 'N')
238
- param = param.toUpperCase();
239
- // Do not pad with zeroes or align to precision when using ordinal formatting
250
+ param_raw = param_raw.toUpperCase();
251
+ // Do not pad with zeroes when using ordinal formatting
240
252
  format.pad_zeroes = false;
241
- format.precision = -1;
242
253
  break;
243
254
  case '?':
244
255
  param_colored = node_util_1.default.inspect(param, {
@@ -246,32 +257,25 @@ function formatParam(param, format) {
246
257
  colors: true,
247
258
  compact: !format.pretty
248
259
  });
249
- param = node_util_1.default.stripVTControlCharacters(param_colored);
260
+ param_raw = node_util_1.default.stripVTControlCharacters(param_colored);
250
261
  // Do not force sign, pad with zeroes or align to precision when using debug formatting
251
262
  param_type = 'string';
263
+ // format.force_sign = '';
252
264
  break;
253
265
  default:
254
- param = param.toString();
266
+ param_raw = roundInBase(param, base, format.precision);
255
267
  break;
256
268
  }
257
269
  ;
258
- // Compute radix-point precision on numbers
259
- if (param_type == 'number' && format.precision != -1) {
260
- let [pre, post] = param.split('.');
261
- if (!format.precision) { // precision = 0, do not include radix point
262
- param = pre;
263
- }
264
- else {
265
- post = ((post || '') + '0'.repeat(format.precision)).slice(0, format.precision);
266
- param = pre + '.' + post;
267
- }
270
+ if (param_type == 'string' && format.force_sign != '') {
271
+ param_raw = format.force_sign == '+' ? param_raw.toUpperCase() : param_raw.toLowerCase();
268
272
  }
269
273
  // let filled = false;
270
274
  if ((param_type == 'number') || (param_type == 'bigint')) {
271
275
  // Compute parameter sign
272
- let maybe_sign = param.substring(0, 1);
276
+ let maybe_sign = param_raw.substring(0, 1);
273
277
  if (maybe_sign === '-') {
274
- param = param.substring(1, param.length);
278
+ param_raw = param_raw.substring(1, param_raw.length);
275
279
  }
276
280
  else if (format.force_sign == '+') {
277
281
  maybe_sign = '+';
@@ -300,19 +304,20 @@ function formatParam(param, format) {
300
304
  //pad with zeroes if specified
301
305
  if (format.pad_zeroes) {
302
306
  // filled = true;
303
- while (param.length < format.width - maybe_sign.length) {
304
- param = '0' + param;
307
+ while (param_raw.length < format.width - maybe_sign.length) {
308
+ param_raw = '0' + param_raw;
305
309
  }
306
310
  }
307
- param = maybe_sign + param;
311
+ param_raw = maybe_sign + param_raw;
308
312
  }
309
313
  if (param_colored == "")
310
- param_colored = param;
311
- if (format.width > param.length) {
314
+ param_colored = param_raw;
315
+ let visible_length = [...param_raw].length;
316
+ if (format.width > visible_length) {
312
317
  // Compute fill/align
313
318
  let left = '';
314
319
  let right = '';
315
- let diff = format.width - param.length;
320
+ let diff = format.width - visible_length;
316
321
  switch (format.align) {
317
322
  case '>':
318
323
  left = format.fill.repeat(diff);
@@ -326,8 +331,37 @@ function formatParam(param, format) {
326
331
  right = format.fill.repeat(diff / 2 + diff % 2);
327
332
  break;
328
333
  }
329
- param = left + param + right;
334
+ param_raw = left + param_raw + right;
330
335
  param_colored = left + param_colored + right;
331
336
  }
332
- return [param_colored, param];
337
+ return [param_colored, param_raw];
338
+ }
339
+ /**
340
+ * Helper function to round a number to a given precision in a given base.
341
+ * Will also truncate strings to the desired length.
342
+ */
343
+ function roundInBase(n, base, precision) {
344
+ if (typeof n == "string") {
345
+ if (precision == -1)
346
+ return n;
347
+ return n.slice(0, precision);
348
+ }
349
+ if (typeof n != "number" && typeof n != "bigint") {
350
+ return n.toString();
351
+ }
352
+ if (precision < 0) {
353
+ return n.toString(base);
354
+ }
355
+ if (precision == 0) {
356
+ return (typeof n == "bigint" ? n : Math.round(n)).toString(base);
357
+ }
358
+ const factor = base ** precision;
359
+ const rounded = typeof n == "bigint" ? n * BigInt(factor) : Math.round((n + Number.EPSILON) * factor);
360
+ const str = rounded.toString(base);
361
+ // Insert radix point from the right
362
+ const intPart = str.slice(0, -precision) || "0";
363
+ const fracPart = str.slice(-precision).padStart(precision, "0");
364
+ return intPart + "." + fracPart;
333
365
  }
366
+ /** Re-export of node's `util.styleText`. */
367
+ exports.style = node_util_1.default.styleText;
package/lib/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { RsString } from './format';
1
+ import { RsString, style } from './format';
2
2
  export { print, println, eprint, eprintln, dbg } from './print';
3
3
  type ParameterReference = {
4
4
  __rs_param_ref: number;
@@ -25,6 +25,11 @@ type rs = {
25
25
  * @returns A reference to the `n`th parameter
26
26
  */
27
27
  ref: (n: number) => ParameterReference;
28
+ /**
29
+ * Format a string using one or more of [the modifiers provided by node's `util` module](https://nodejs.org/docs/latest-v22.x/api/util.html#modifiers).
30
+ * This is a re-export of `util.styleText`.
31
+ */
32
+ style: typeof style;
28
33
  };
29
34
  /**
30
35
  * Tag to use Rust-style formatting in a template literal.
package/lib/index.js CHANGED
@@ -23,3 +23,4 @@ Object.defineProperty(exports, "dbg", { enumerable: true, get: function () { ret
23
23
  exports.rs = ((strings, ...params) => new format_1.RsString(strings, params));
24
24
  exports.rs.raw = (strings, ...params) => (0, format_1.buildString)(strings, params).raw;
25
25
  exports.rs.ref = (n) => ({ __rs_param_ref: n });
26
+ exports.rs.style = format_1.style;
package/lib/print.js CHANGED
@@ -72,6 +72,6 @@ function eprintln(string) {
72
72
  * @param value Value to debug print
73
73
  */
74
74
  function dbg(value) {
75
- eprintln((0, _1.rs) `${value}:?`);
75
+ eprintln((0, _1.rs) `${value}:#?`);
76
76
  return value;
77
77
  }
package/package.json CHANGED
@@ -1,10 +1,11 @@
1
1
  {
2
2
  "name": "rsformat",
3
- "version": "1.2.1",
3
+ "version": "1.4.0",
4
4
  "description": "Formatting/printing library for JavaScript that takes after rust's string formatting ",
5
5
  "files": [
6
6
  "lib",
7
- "README.MD"
7
+ "README.md",
8
+ "docs.md"
8
9
  ],
9
10
  "exports": {
10
11
  ".": {
@@ -30,11 +31,16 @@
30
31
  "author": "Alfio (https://github.com/p2js)",
31
32
  "license": "ISC",
32
33
  "devDependencies": {
33
- "@types/node": "^22.8.6"
34
+ "@types/jest": "^30.0.0",
35
+ "@types/node": "^22.8.6",
36
+ "jest": "^30.2.0",
37
+ "typescript": "^5.9.3"
34
38
  },
35
39
  "scripts": {
36
- "build": "tsc",
37
- "prepublish": "pnpm build",
38
- "test": "echo \"Error: no test specified\" && exit 1"
40
+ "build": "pnpm exec tsc",
41
+ "build:test": "pnpm exec tsc -p tsconfig.test.json",
42
+ "pretest": "pnpm build && pnpm build:test",
43
+ "test": "pnpm exec jest test-build",
44
+ "posttest": "rm -r test-build"
39
45
  }
40
46
  }