effstring
v1.4.0
Published
Formatting of numbers and other values using a syntax similar to Python's f-strings
Readme
EffString
Table of Contents generated with DocToc
- EffString
EffString
EffString re-packages the great d3-format library to provide formatting for numerical values in JavaScript tagged templates.
Using EffString is simple: to get formatting for the default locale en-US, simply import the tag function
f and put it in front of a JavaScript template literal; then, after each interpolated value field of the
string, you can put a format specifier that is delineated by a leading colon : and a
trailing semicolon ;.
For other pre-defined locales
Simple Example
JavaScript:
const { f, } = require( 'effstring' );
console.log( f`${'Alice'}:*<15c; has ${1234}:_>$12,.00f; in their pocket.` );
console.log( f`${'Bob'}:*<15c; has ${45678.93}:_>$12,.00f; in their pocket.` );CoffeeScript:
{ f, } = require 'effstring'
console.log f"#{'Alice'}:*<15c; has #{1234}:_>$12,.00f; in their pocket."
console.log f"#{'Bob'}:*<15c; has #{45678.93}:_>$12,.00f; in their pocket."Result:
Alice********** has ___$1,234.00 in their pocket.
Bob************ has __$45,678.93 in their pocket.
┌────────────── ┌───────────
│ │ 12 characters right aligned
│ │ filled with underscores
│ │ currency, 2 decimals
│ │ thousands separator
│
│ 20 characters left aligned
│ filled with asterisksAPI
Calling require 'effstring' will return an object with these public members:
f"": The default tag function (formatter) which assumes anen-USlocale.new_ftag(): Method to create a new tag function with custom locale settings.Effstring_error: Abstract base class for errors thrown byeffstring; actual errors will be eitherEffstring_syntax_error,Effstring_lib_syntax_error,Effstring_syntax_fillwidth_error, orEffstring_validation_error(also exported).
The following are also exported but only meant for internal use such as testing:
_d3_format: The underlyingd3-formatlibrary that does the heavy lifting._default_locale: The default locale._hint_as_locale_cfg(): Given either an object or a BCP47 locale code such asen-GBorja-JP, return an object with the locale settings._locale_cfg_from_bcp47(): Given a BCP47 locale code such asen-GBorja-JP, return an object with the locale settings._locale_cfg_from_hints(): Given a sequence of locale hints (BCP47 codes or objects), resolve to a complete locale settings object with defaults filled out; settings coming later will overwriter ones that come earlier (usingObject.assign())._fmtspec_re: The regular expression used to spot the optional format specifier that may follow an EffString interpolated field.
Format Specifier
The general shape of a string literal with an EffString tag function and an interpolated value field is shown below.
f`${x}:[[fill]align][sign][symbol][zeros][width][thousands][.precision][~][type[/unit]];` (JS)
f"#{x}:[[fill]align][sign][symbol][zeros][width][thousands][.precision][~][type[/unit]];" (CoffeeScript)
┌─── ┌──── ┌─── ┌───── ┌───── ┌───── ┌───────── ┌────────── ┌─ ┌──── ┌────
│ │ │ │ │ │ │ │ │ │ │
│ ¤ │ < │ ␣ │ $ │ 0 │ ℕ │ , │ .ℕ │~ │ e │ /y
│ ^ │ + │ # │ f │ /z
│ > │ - │ g │ /a
│ = │ ( │ r │ /f
│ s │ /p
│ % │ /n
│ p │ /µ
* Symbols: │ b │ /m
¤: any single-width Unicode BMP character │ o │ /1
␣: U+0020, space character │ d │ /k
ℕ: /[1-9][0-9]*/, an integer number │ x │ /M
* other characters represent themselves; │ X │ /G
* all fields are optional; │ c │ /T
* a leading fill chr must always be followed by an alignment specifier │ /P
* a unit prefix can only be added to fixed format `f` (e.g. `f/µ` for micro) │ /E
│ /Z
│ /YFormat Specifier: Fill
The optional fill specifier, when present, must be a single-width character from the Unicode Basic
Multilingual Plane (BMP); this includes code points between U+0000 thru U+ffff with the exception of CJK
Ideographs (Hanzi, Kanji, Hanja) and any other fullwidth characters. When present, the fill character must
always be followed by one of the alignment specifiers <, ^, >, or =; thus, even characters like ;
and < are allowed fill specifiers as in :;>10; (fill ;, alignment >, width 10) and :<>10; (fill
<, alignment >, width 10).
Format Specifier: Align
>: right-aligned (default)<: left-aligned^: centered=: right-aligned but with any sign and symbol to the left of any padding
Format Specifier: Sign
-: positive numbers get nothing, negatives ones get a minus sign (default)+: positive numbers get a plus sign, negative ones get a minus sign(: positive numbers get nothing, negatives ones get parentheses␣(space) positive numbers get a space, negative numbers a minus sign
Format Specifier: Symbol
$: apply currency symbols per the locale definition#: for binary, octal, or hexadecimal notation, prefix by0b,0o, or0x, respectively.
Format Specifier: Zeros
The zeros option is indicated by a digit zero 0 right before the digits of the width field and
enables zero-padding (this implicitly sets fill to 0 and align to =).
Format Specifier: Width
The width defines the minimum field width; if not specified, then the width will be determined by the
content.
Format Specifier: Thousands
The thousands option (activated by the presence of a comma , right behind the digits indicating
the width) enables the use of a group separator, such as a comma for thousands.
Format Specifier: Precision
Depending on the value of the type specifier, the precision specifier either indicates the number of
digits that follow the decimal point (types f and %), or the number of significant digits (types e,
g, r, s and p). If the precision is not specified, it defaults to 6 for all types except for the
default type specifier, for which a precision of 12 is assumed.
Precision is ignored for integer formats (types b, o, d, x, and X) and character data (type c).
Format Specifier: Trailing
The trailing option is activated by a tilde ~ right in front of the type specifier. This is most
commonly used in conjunction with types r, e, s and %. Its effect is to trim insignificant trailing
zeros.
Format Specifier: Type
The available type specifiers are:
e: exponent notationf: fixed point notationg: either decimal or exponent notation, rounded to significant digitsr: decimal notation, rounded to significant digitss: decimal notation with an SI prefix, rounded to significant digitsn: shorthand for,g, that is, decimal or exponent notation with group separators%: multiply by 100, and then decimal notation with a percent signp: multiply by 100, round to significant digits, and then decimal notation with a percent signb: binary notation, rounded to integero: octal notation, rounded to integerd: decimal notation, rounded to integerx: hexadecimal notation, using lower-case letters, rounded to integerX: hexadecimal notation, using upper-case letters, rounded to integerc: character data, for a string of text
Leaving out the type specifier is treated as shorthand for ~g (with a default precision of 12 instead of
6). For the g, n and default types, decimal notation is used if the resulting string would have up to
the number of digits indicated by the precision specifier; otherwise, exponent notation is used.
Format Specifier: SI Unit Prefix
In case format type specifier f is explicitly given, it is possible to specify an SI unit prefix by adding
a slash / followed by one of the recognized unit symbols which are:
y: yocto, 10⁻²⁴z: zepto, 10⁻²¹a: atto, 10⁻¹⁸f: femto, 10⁻¹⁵p: pico, 10⁻¹²n: nano, 10⁻⁹µ: micro, 10⁻⁶m: milli, 10⁻³1: unit, 10⁰k: kilo, 10³M: mega, 10⁶G: giga, 10⁹T: tera, 10¹²P: peta, 10¹⁵E: exa, 10¹⁸Z: zetta, 10²¹Y: yotta, 10²⁴
As an example, consider we have four measurements given in meters: 123000m, 7000m, 500m, and 99m;
these should be printed but be represented in kilometers instead of meters; we choose fixed format f and
prefix /k; the slash can be thought of as a division by the magnitude that k represents, namely 10³:
f"#{ 123000 }:_>9,.3f/k;m" ————> __123.000km
f"#{ 7000 }:_>9,.3f/k;m" ————> ____7.000km
f"#{ 500 }:_>9,.3f/k;m" ————> ____0.500km
f"#{ 99 }:_>9,.3f/k;m" ————> ____0.099kmLocale Settings
_default_locale =
decimal: '.' # decimal point
thousands: ',' # group separator
grouping: [ 3, ] # array of group sizes, cycled as needed
currency: [ '$', '', ] # currency prefix and suffix
numerals: [ '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', ] # array of ten strings to replace digits 0-9
percent: '%' # percent sign
minus: '−' # U+2212 # minus sign
nan: 'NaN' # not-a-number value
fullwidth: true # may switch off fullwidth supportPre-Defined Locales
For the following locales, d3-format provides ready-made settings:
| Code | Description |
|----------|-----------------------------------------------------------------------------------|
| ar-001 | Arabic — World (global standard Arabic; "Modern Standard Arabic" typically used) |
| ar-AE | Arabic — United Arab Emirates (Gulf Arabic dialect) |
| ar-BH | Arabic — Bahrain (Gulf Arabic dialect, Bahraini variant) |
| ar-DJ | Arabic — Djibouti (East African Arabic) |
| ar-DZ | Arabic — Algeria (Maghrebi Arabic) |
| ar-EG | Arabic — Egypt (Egyptian Arabic, a widely influential dialect) |
| ar-EH | Arabic — Western Sahara (Hassaniya Arabic, Maghrebi influence) |
| ar-ER | Arabic — Eritrea (official language, used in government and some communities) |
| ar-IL | Arabic — Israel (Arabic spoken by Arab citizens of Israel) |
| ar-IQ | Arabic — Iraq (Mesopotamian Arabic dialects) |
| ar-JO | Arabic — Jordan (Levantine Arabic) |
| ar-KM | Arabic — Comoros (formal/official language; Comorian Arabic influence) |
| ar-KW | Arabic — Kuwait (Gulf Arabic dialect) |
| ar-LB | Arabic — Lebanon (Levantine Arabic) |
| ar-LY | Arabic — Libya (Maghrebi Arabic) |
| ar-MA | Arabic — Morocco (Maghrebi Arabic, Darija) |
| ar-MR | Arabic — Mauritania (Hassaniya Arabic) |
| ar-OM | Arabic — Oman (Gulf Arabic, with some unique Omani dialects) |
| ar-PS | Arabic — Palestine (Levantine Arabic) |
| ar-QA | Arabic — Qatar (Gulf Arabic) |
| ar-SA | Arabic — Saudi Arabia (Najdi, Hejazi, and Gulf Arabic variants) |
| ar-SD | Arabic — Sudan (Sudanese Arabic) |
| ar-SO | Arabic — Somalia (secondary language, limited usage) |
| ar-SS | Arabic — South Sudan (minority usage; Arabic-based "Juba Arabic" in some regions) |
| ar-SY | Arabic — Syria (Levantine Arabic) |
| ar-TD | Arabic — Chad (Chadian Arabic, lingua franca) |
| ar-TN | Arabic — Tunisia (Tunisian Arabic, Maghrebi influence) |
| ar-YE | Arabic — Yemen (Yemeni Arabic, with multiple regional dialects) |
| ca-ES | Catalan — Spain (Catalonia, Valencia, Balearic Islands) |
| cs-CZ | Czech — Czech Republic |
| da-DK | Danish — Denmark |
| de-CH | German — Switzerland (Swiss Standard German) |
| de-DE | German — Germany (Standard German) |
| en-CA | English — Canada |
| en-GB | English — United Kingdom |
| en-IE | English — Ireland |
| en-IN | English — India (Indian English variant) |
| en-US | English — United States |
| es-BO | Spanish — Bolivia |
| es-ES | Spanish — Spain (Castilian Spanish) |
| es-MX | Spanish — Mexico |
| fi-FI | Finnish — Finland |
| fr-CA | French — Canada (mainly Québec French) |
| fr-FR | French — France (Standard French) |
| he-IL | Hebrew — Israel |
| hu-HU | Hungarian — Hungary |
| it-IT | Italian — Italy |
| ja-JP | Japanese — Japan |
| ko-KR | Korean — South Korea |
| mk-MK | Macedonian — North Macedonia |
| nl-NL | Dutch — Netherlands |
| pl-PL | Polish — Poland |
| pt-BR | Portuguese — Brazil |
| pt-PT | Portuguese — Portugal |
| ru-RU | Russian — Russia |
| sl-SI | Slovenian — Slovenia |
| sv-SE | Swedish — Sweden |
| uk-UA | Ukrainian — Ukraine |
| zh-CN | Chinese (Simplified) — Mainland China |
Handling of 'Wide' Characters
EffString makes an effort to correctly handle so-called 'wide' or (Asian) 'fullwidth'
characters (d3-format per se lacks that
capability). As the below proof-of-concept demonstrates, it's possible to mix CJK Kanji and Kana with
'normal width' / 'halfwidth' characters and get adjusted fieldwidths (upper two blocks). In order to skip
fullwidth processing, pass in { fullwidth: false, } to new_ftag() (lower two blocks):
{ new_ftag, } = require 'effstring'
do =>
ja_jp_cfg = {
numerals: [ '〇', '一', '二', '三', '四', '五', '六', '七', '八', '九', ], }
f_en = new_ftag 'en-GB'
f_ja = new_ftag 'ja-JP', ja_jp_cfg
console.log f_en"#{'Alice'}:*<15c; is in #{'London'}:.^12c; and has #{1234}:_>$22,.2f; in their pocket."
console.log f_en"#{'Bob'}:*<15c; is in #{'London'}:.^12c; and has #{45678.93}:_>$22,.2f; in their pocket."
console.log f_ja"#{'アリスさん'}:*<15c; is in #{'倫敦'}:.^12c; and has #{1234}:_>$22,.2f; in their pocket."
console.log f_ja"#{'ボブさん'}:*<15c; is in #{'倫敦'}:.^12c; and has #{45678.93}:_>$22,.2f; in their pocket."
do =>
zh_tw_cfg =
currency: [ '新臺幣', '', ],
f_en = new_ftag 'en-GB'
f_zh = new_ftag 'zh-CN', zh_tw_cfg
console.log f_en"#{-98765.43}:·>$20,.2f;"
console.log f_zh"#{-98765.43}:·>$20,.2f;"
console.log f_en"#{-98765.43}:·=$20,.2f;"
console.log f_zh"#{-98765.43}:·=$20,.2f;"
do =>
ja_jp_cfg = {
numerals: [ '〇', '一', '二', '三', '四', '五', '六', '七', '八', '九', ], }
f_en = new_ftag 'en-GB', { fullwidth: false, }
f_ja = new_ftag 'ja-JP', ja_jp_cfg, { fullwidth: false, }
console.log f_en"#{'Alice'}:*<15c; is in #{'London'}:.^12c; and has #{1234}:_>$22,.2f; in their pocket."
console.log f_en"#{'Bob'}:*<15c; is in #{'London'}:.^12c; and has #{45678.93}:_>$22,.2f; in their pocket."
console.log f_ja"#{'アリスさん'}:*<15c; is in #{'倫敦'}:.^12c; and has #{1234}:_>$22,.2f; in their pocket."
console.log f_ja"#{'ボブさん'}:*<15c; is in #{'倫敦'}:.^12c; and has #{45678.93}:_>$22,.2f; in their pocket."
do =>
zh_tw_cfg = {
currency: [ '新臺幣', '', ],
# numerals: [ '〇', '一', '二', '三', '四', '五', '六', '七', '八', '九', ],
}
f_en = new_ftag 'en-GB', { fullwidth: false, }
f_zh = new_ftag 'zh-CN', zh_tw_cfg, { fullwidth: false, }
console.log f_en"#{-98765.43}:·>$20,.2f;"
console.log f_zh"#{-98765.43}:·>$20,.2f;"
console.log f_en"#{-98765.43}:·=$20,.2f;"
console.log f_zh"#{-98765.43}:·=$20,.2f;"This will produce neatly adjusted fields even if it doesn't quite show here which is down to GitHub's choice of fonts:
Alice********** is in ...London... and has _____________£1,234.00 in their pocket.
Bob************ is in ...London... and has ____________£45,678.93 in their pocket.
アリスさん***** is in ....倫敦.... and has ______一,二三四.〇〇円 in their pocket.
ボブさん******* is in ....倫敦.... and has ____四五,六七八.九三円 in their pocket.
·········−£98,765.43
····−新臺幣98,765.43
−£·········98,765.43
−新臺幣····98,765.43
Alice********** is in ...London... and has _____________£1,234.00 in their pocket.
Bob************ is in ...London... and has ____________£45,678.93 in their pocket.
アリスさん********** is in .....倫敦..... and has _____________一,二三四.〇〇円 in their pocket.
ボブさん*********** is in .....倫敦..... and has ____________四五,六七八.九三円 in their pocket.
·········−£98,765.43
·······−新臺幣98,765.43
−£·········98,765.43
−新臺幣·······98,765.43It looks more better in the terminal which is the primary use case for handling fullwidth anyway; notice how
in the upper two blocks everything lines up nicely whereas it doesn't in the lower two blocks which have
fullwidth set to false:

Demo
| Input | Output | Notes |
| --- | :--------- | :---------- |
| f'' | '' | empty string remains empty string. Yay! |
| f'helo' | 'helo' | any string without interpolation... |
| f'(#{123})' | '(#{123})' | ...just remains as-is |
| f"(#{123})" | '(123)' | |
| f"(#{123}:5;)" | '( 123)' | |
| f"(#{123}:>5;)" | '( 123)' | |
| f"(#{123}:<5;)" | '(123 )' | |
| f"(#{123.456}:>5.2;)" | '(1.2e+2)' | |
| f"(#{123.456}:>5.2f;)" | '(123.46)' | |
| f"(#{123.456}:>15.2f;)" | '( 123.46)' | |
| f"(#{1234.567}:>15.2f;)" | '( 1234.57)' | |
| f"(#{1234.567}:=>15.2f;)" | '(========1234.57)' | |
| f"(#{1234.567}:=>15,.2f;)" | '(=======1,234.57)' | |
| f"(#{123.456}:<15.2f;)" | '(123.46 )' | |
| f"(#{1234.567}:<15.2f;)" | '(1234.57 )' | |
| f"(#{1234.567}:=<15.2f;)" | '(1234.57========)' | |
| f"(#{1234.567}:=<15,.2f;)" | '(1,234.57=======)' | |
| f"#{0.123}:.0%;" | '12%' | rounded percentage |
| f"#{-3.5}:($.2f;" | '($3.50)' | localized fixed-point currency |
| f"#{-3.5}:($.2f;" | '(£3.50)' | localized fixed-point currency |
| f"#{42}:+20;" | ' +42' | space-filled and signed |
| f"#{42}:.^20;" | '.........42.........' | dot-filled and centered |
| f"#{42e6}:.2s;" | '42M' | SI-prefix with two significant digits |
| f"#{48879}:#x;" | '0xbeef' | prefixed lowercase hexadecimal |
| f"#{4223}:,.2r;" | '4,200' | grouped thousands with two significant digits |
| Input | Error | Notes |
| --- | :--------- | :---------- |
| f"(#{123}:)" | illegal format expression | |
| f"(#{123}:;)" | illegal format expression | |
Required NodeJS Version
EffString relies on sindresorhus/string-width to adjust
strings with fullwidth characters; since string-width is an ESM module but EffString uses CJS'
require(), EffString needs at least NodeJS v22 (with --experimental-require-module command line flag) or
v23 (without command line flag).
To Do
[—]cache intermediate values when handling fullwidth characters[—]allow to configure plus sign[—]in format specifier, make formattypefield mandatory and delete the passage above about it being "treated as shorthand for~g(with a default precision of 12 instead of 6)" because no user is ever going to grok and use it, and they have no reason to; they can still use:.12~g;if it's that what they want[—]consider to make other format specifier fields mandatory like thetypefield[—]re-design SI unit prefix handling; do not uselocale.formatPrefix(); instead, just multiply value with the appropriate scale and tack on the desired prefix[—]implement less-used SI prefixes such asc(cm),d(dm),h(hPa)
[—]should be able to useBigInts witheffstring[—]effstringfails silently in cases (1), (2), while case (3) mysteriously causesEffstring_lib_syntax_error(so raised byd3-format?):@eq ( Ωfstr_181 = -> urge 'Ωfstr_182', rpr f"d = #{"helo"}:60.40f/k;m" ), null ### (1) ### @eq ( Ωfstr_181 = -> urge 'Ωfstr_182', rpr f"d = #{true}:60.40f/k;m" ), null ### (2) ### @eq ( Ωfstr_187 = -> urge 'Ωfstr_188', rpr f"d = #{123456789n}:60.40f/k;m" ), null ### (3) ### 'd = NaNkm' 'd = 0.00100000000000000002km' Error: an unexpected error occurred when calling task 'si_units_format_specifiers_updated'; `expected a result but got an an error: "Ωfstr___4 (Effstring_lib_syntax_error) illegal format specifier '60.40f'"`this should be handled as follows:
[—]if, in a numerical field (i.e. all field types exceptc), a valuexappears that is not afloat(see remark below), look for settingfallbackin locale CFG- if
locale_cfg.fallbackis not set, throw anEffstring_value_error - if
locale_cfg.fallbackis set to afloator atext, use that value to replacex - if
locale_cfg.fallbackis set to a function, call that function and- use its return value but only if it's a
floator atext - throw an
Effstring_value_errorif return value isnull - otherwise, throw an
Effstring_value_error(which should point out bogus return value)
- use its return value but only if it's a
- prevent
locale_cfg.fallbackto be set to anything but afloat, atext, or afunction - must decide whether
NaN,Infinitycount asfloat
- if
[—]ensure that SI unit prefixes are outside of field width, decimal count; thusf"#{1234}#>7.3f/k:;m"should result in"##1.234km", not"#1.234km"(will probably obviate need forhas_si_unit_prefixandsi_unit_correction)[—]{c|sh}ould we extend EffString to handle dates (withIntl.DateTimeFormat)?[—]re-implement EffString usingIntl.NumberFormat? If not, write chapter "why not use / differences withIntl.NumberFormat"[—]whileIntl.NumberFormatas such is no drop-in replacement for EffString's /d3-format's intended use case—fixed-width formatting of numbers with free choice of grouping &c.—it does have superior number formatting capabilities as demonstrated inloveencounterflow/hengist-NG/dev/effstring/src/test-basics.coffee.When the number of significant digits is restricted to 16 (
new Intl.NumberFormat 'en-US', { useGrouping: false, minimumSignificantDigits: 16, maximumSignificantDigits: 16, }), appearances of 'ghost' or 'stray' digits is pared down greatly or even prevented (this being the limit of 64bit IEEE754 floating point format).Compared to what is achievable without too many acrobatics by 'classical' means—
.toFixed(),.toPrecision()and so on—Intl.NumberFormatdoes write out one and two hundred digits before and after the decimal point without switching to exponent notation.The remaining task is, then, to take the output and insert leading zeros and/or trailing zeros and/or group marks (thousands separators), adjust the decimal point, replace digits, and adjust the resulting width to the current field.
[—]incorporate or create new package 'GeeString' where user can define format using a pattern that closely resembles desired output, as in###,##,##.## ₹(call it a 'skeleton'?)- https://github.com/formatjs/formatjs/blob/main/packages/icu-skeleton-parser/number.ts
- https://unicode-org.github.io/icu/userguide/format_parse/numbers/skeletons.html
[—]we need ways to indicate whether a given field width is maximal (in which case contents will be abridged) or minimal (in which wider contents will overflow the given width)[—]user should be able to configure representation ofInfinity, e.g. as∞orℵ₀[—]when inserting group separators, will not use RegExes because (1) in JS they lack a straightforward way to recognize visual glyphs, something thatIntl.Segmentercan do best, and (2) while there are elegant expressions for inserting one separator at constant intervals, no generalization that does arbitrary separators at different intervals has been found[—]splitting method should be configurable and include[...new Intl.Segmenter().segment( text )].map(s => s.segment)as well asArray.from text(the former will almost always be more correct but the latter is like 20x faster) as well as custom methods. Also compare to'abcd'.split /(?=.)/.
[—]support scientific and general exponential format using numerical superscripts, either utilizing Unicode superscripts or HTML<sup>tags:const num = 1234567; const expStr = num.toExponential(3); // "1.235e+6" const [mantissa, exponent] = expStr.split('e'); const formattedMantissa = new Intl.NumberFormat('de-DE', { minimumFractionDigits: 3, maximumFractionDigits: 3 }).format(Number(mantissa)); console.log(`${formattedMantissa}e${exponent}`); // → e.g. "1,235e+6"[—]allow to declare replacements for characters (and RegEx patterns?) of a given (locale-dependent) numerical representation:replace: { 0: '𝟘', '.': ',', '.': ',' }. Note the example shows that ordering is (of course) relevant and that swapping of characters may occur which can only be done with an intermediate value when performing step-by-step replacements; thus, we should compile a replacement function that is triggered by a RegEx that matches on any of the relevant characters and returns the configured replacement (text.replace /[0.,]/vg, ( chr ) -> cfg.replace[ chr ])[—]allow to declare SI unit prefixes that are calculated but not shown[—]allow to declare numerical factors in place of SI unit prefixes (that will presumably never be shown?); ex.:[—]allow to bundle several independently configured formatters to be used by a single tag function such that they can be addressed by naming them in the fmt spec, ex.:de = new_ftag 'de-DE'; gb = new_ftag 'en-GB'; f = combine { de, gb, }; echo f"#{sum_eur}!de:.3f; #{sum_gbp}!gb:.3f;"; namecombineand format selector}!de:are preliminary[—]slevithan/regexallows this usgae pattern:( regex 'gm' )"^.+"where'gm'represents RegEx flags used to configure how to interpret the tagged string. This pattern could be useful for EffString.[—]offer option to draw Unicode table lines, headers, footers[—]BUG: the firstinfo()call hangs, the second one works:f = ( a, b, P... ) -> # cfg = Object.assign ( Object.create null ), { a, b, }, P... cfg = Object.assign {}, { a, b, }, P... # info 'Ω_154', f"#{GUY.trm.gold [ arguments..., ]}:>30c;", GUY.trm.blue { a, b, cfg, } ### HANGS ### info 'Ω_155', ( GUY.trm.gold [ arguments..., ] ), GUY.trm.blue { a, b, cfg, } f 1 f 1, 2, 3 f 1, 2, k: 'K' f 1, 2, k: 'K', 9, m: 'M'[—]fix handling of ANSI escapes (not properly counted into_width()/width_of()?)[+]for alignment<[+]for alignment>[—]for alignment^[—]for alignment=
[—]implement minimum, maximum field widths so fields are printed with at leastmor at mostncharacters of width; presentwidthacts likem: width, n: Infinity; needs ellipsis character
Is Done
[+]basic handling of fullwidth characters[+]useD3F.formatPrefix()to implement units format type specifier as inf"#{0.00042}:_<15,.3/µ;"[+]may want to use:...f/#{unit};as inf"#{0.00042}:_<15,.3f/µ;"; that way users are reminded that (1) SI-unit prefix format is based on fixed format (f) and (2) SI-unit prefix format is really in addition to an existing type specifier, not a replacement for it
Don't
[—]accept fullwidth characters as fills see next point[—]do not assume deletions can be performed per code unit in_to_width()becaused3-formatdoesn't accept codepoints outside the BMP and because we check for the fill character being present in the output offormat()we can actually assume a fill chr does take exactly a single string index position[—]consider to use arbitrary units so users can writef"distance: #{d}:10,.3f/km;"instead off"distance: #{d}:10,.3f/k;m"(???)
