num_runtime_fmt/lib.rs
1//! Format numbers according to a runtime specification.
2//!
3//! ## Entry Points
4//!
5//! The core of this crate is the [`NumFmt`] type. You can [build][NumFmt::builder] it explicitly,
6//! or [parse][NumFmt::from_str] it from a format string with similar grammar to that of the
7//! standard library.
8//!
9//! Given an instance of `NumFmt`, you can call its [`fmt`][NumFmt::fmt] method to simply format
10//! a number, or its [`fmt_with`][NumFmt::fmt_with] method to apply dynamic parameters.
11//!
12//! ## Format String Grammar
13//!
14//! The gramar for the format string derives substantially from the standard library's:
15//!
16//! ```text
17//! format_spec := [[fill]align][sign]['#'][['0']width]['.' precision][format][separator[spacing]]
18//! fill := character
19//! align := '<' | '^' | '>' | 'v'
20//! sign := '+' | '-'
21//! width := integer not beginning with '0'
22//! precision := integer
23//! format := 'b' | 'o' | 'd' | 'x' | 'X'
24//! separator := '_', | ',' | ' '
25//! spacing := integer
26//! ```
27//!
28//! ### Note
29//!
30//! There is no special syntax for dynamic insertion of `with`, `precision` and `spacing`.
31//! Simply use [`NumFmt::fmt_with`]; the dynamic values provided there always override any
32//! values for those fields, whether set or not in the format string.
33//!
34//! ## `fill`
35//!
36//! Any single `char` which precedes an align specifier is construed as the fill
37//! character: when `width` is greater than the actual rendered width of the number,
38//! the excess is padded with this character.
39//!
40//! ### Note
41//! Wide characters are counted according to their quantity, not their bit width.
42//!
43//! ```rust
44//! # use num_runtime_fmt::NumFmt;
45//! let heart = '🖤';
46//! assert_eq!(heart.len_utf8(), 4);
47//! let fmt = NumFmt::builder().fill(heart).width(3).build();
48//! let formatted = fmt.fmt(1).unwrap();
49//! assert_eq!(formatted, "🖤🖤1");
50//! // Note that even though we requested a width of 3, the binary length is 9.
51//! assert_eq!(formatted.len(), 9);
52//! ```
53//!
54//! ## `align`ment
55//!
56//! - `>`: the output is right-aligned in `width` columns (default).
57//! - `^`: the output is centered in `width` columns.
58//! - `<`: the output is left-aligned in `width` columns.
59//! - `v`: attempt to align the decimal point at column index `width`. For integers,
60//! equivalent to `>`.
61//!
62//! ## `sign`
63//!
64//! - `-`: print a leading `-` for negative numbers, and nothing in particular for
65//! positive (default)
66//! - `+`: print a leading `+` for positive numbers
67//!
68//! ## `#`
69//!
70//! If a `#` character is present, print a base specification before the number
71//! according to its format (see `format` below).
72//!
73//! - binary: `0b`
74//! - octal: `0o`
75//! - decimal: `0d`
76//! - hex: `0x`
77//!
78//! This base specification counts toward the width of the number:
79//!
80//! ```rust
81//! # use num_runtime_fmt::NumFmt;
82//! assert_eq!(NumFmt::from_str("#04b").unwrap().fmt(2).unwrap(), "0b10");
83//! ```
84//!
85//! ## `0`
86//!
87//! Engage the zero handler.
88//!
89//! The zero handler overrides the padding specification to `0`, and
90//! treats pad characters as part of the number, in contrast
91//! to the default behavior which treats them as arbitrary spacing.
92//!
93//! ## Examples
94//!
95//! ```rust
96//! # use num_runtime_fmt::NumFmt;
97//! // sign handling
98//! assert_eq!(NumFmt::from_str("03").unwrap().fmt(-1).unwrap(), "-01");
99//! assert_eq!(NumFmt::from_str("0>3").unwrap().fmt(-1).unwrap(), "0-1");
100//! ```
101//!
102//! ```rust
103//! # use num_runtime_fmt::NumFmt;
104//! // separator handling
105//! assert_eq!(NumFmt::from_str("0>7,").unwrap().fmt(1).unwrap(), "0000001");
106//! assert_eq!(NumFmt::from_str("07,").unwrap().fmt(1).unwrap(), "000,001");
107//! ```
108//!
109//! ## `width`
110//!
111//! This is a parameter for the "minimum width" that the format should take up. If
112//! the value's string does not fill up this many characters, then the padding
113//! specified by fill/alignment will be used to take up the required space (see
114//! `fill` above).
115//!
116//! When using the `$` sigil instead of an explicit width, the width can be set
117//! dynamically:
118//!
119//! ```rust
120//! # use num_runtime_fmt::{NumFmt, Dynamic};
121//! assert_eq!(NumFmt::from_str("-^").unwrap().fmt_with(1, Dynamic::width(5)).unwrap(), "--1--");
122//! ```
123//!
124//! If an explicit width is not provided, defaults to 0.
125//!
126//! ## `precision`
127//!
128//! Precision will pad or truncate as required if set. If unset, passes through as many
129//! digits past the decimal as the underlying type naturally returns.
130//!
131//! ```rust
132//! # use num_runtime_fmt::{NumFmt, Dynamic};
133//! assert_eq!(NumFmt::from_str(".2").unwrap().fmt(3.14159).unwrap(), "3.14");
134//! assert_eq!(NumFmt::from_str(".7").unwrap().fmt(3.14159).unwrap(), "3.1415900");
135//! ```
136//!
137//! If the requested precision exceeds the native precision available to this number,
138//! the remainder is always filled with `'0'`, even if `fill` is specified:
139//!
140//! ```rust
141//! # use num_runtime_fmt::NumFmt;
142//! assert_eq!(NumFmt::from_str("-<6.2").unwrap().fmt(1.0_f32).unwrap(), "1.00--");
143//! ```
144//!
145//! ## `format`
146//!
147//! - `b`: Emit this number's binary representation
148//! - `o`: Emit this number's octal representation
149//! - `d`: Emit this number's decimal representation (default)
150//! - `x`: Emit this number's hexadecimal representation with lowercase letters
151//! - `X`: Emit this number's hexadecimal representation with uppercase letters
152//!
153//! ### Note
154//!
155//! This is one of a few areas where the standard library has
156//! capabilities this library does not: it supports some other numeric formats.
157//! Pull requests welcomed to bring this up to parity.
158//!
159//! ## `separator`
160//!
161//! A separator is a (typically non-numeric) character inserted between groups of digits to make
162//! it easier for humans to parse the number when reading. Different separators may
163//! be desirable in different contexts.
164//!
165//! - `_`: Separate numeric groups with an underscore
166//! - `,`: Separate numeric groups with a comma
167//! - ` ` (space char): Separate numeric groups with a space
168//!
169//! By default, numeric groups are not separated. It is not possible to explicitly
170//! specify that numeric groups are not separated when using a format string.
171//! However, this can be specified when building the formatter via builder.
172//!
173//! When using the builder to explicitly set formatter options, it is also possible
174//! to separate numeric groups with an arbitrary `char`. This can be desirable to
175//! i.e. support German number formats, which use a `.` to separate numeric groups
176//! and a `,` as a decimal separator.
177//!
178//! ## `spacing`
179//!
180//! Spacing determines the number of characters in each character group. It is only
181//! of interest when the separator is set. The default spacing is 3.
182
183mod align;
184mod base;
185mod builder;
186mod dynamic;
187mod num_fmt;
188pub mod numeric_trait;
189pub mod parse;
190mod sign;
191
192pub use align::Align;
193pub use base::Base;
194pub use builder::Builder;
195pub use dynamic::Dynamic;
196pub use num_fmt::{Error, NumFmt};
197pub use numeric_trait::Numeric;
198pub use sign::Sign;