Created
March 20, 2026 23:02
-
-
Save stefanv/00cdc4e815b06668d55cce2ee60fef9c to your computer and use it in GitHub Desktop.
si2 role for myst
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| const UNITS = { | |
| // SI base units | |
| ampere: 'A', | |
| candela: 'cd', | |
| kelvin: 'K', | |
| kilogram: 'kg', | |
| metre: 'm', | |
| meter: 'm', | |
| mole: 'mol', | |
| second: 's', | |
| // SI derived units | |
| becquerel: 'Bq', | |
| degreeCelsius: '°C', | |
| coulomb: 'C', | |
| farad: 'F', | |
| gray: 'Gy', | |
| hertz: 'Hz', | |
| henry: 'H', | |
| joule: 'J', | |
| lumen: 'lm', | |
| katal: 'kat', | |
| lux: 'lx', | |
| newton: 'N', | |
| ohm: 'Ω', | |
| pascal: 'Pa', | |
| radian: 'rad', | |
| siemens: 'S', | |
| sievert: 'Sv', | |
| steradian: 'sr', | |
| tesla: 'T', | |
| volt: 'V', | |
| watt: 'W', | |
| weber: 'Wb', | |
| // Non-SI units accepted for use with SI | |
| astronomicalunit: 'au', | |
| bel: 'B', | |
| dalton: 'Da', | |
| day: 'd', | |
| decibel: 'dB', | |
| degree: '°', | |
| electronvolt: 'eV', | |
| hectare: 'ha', | |
| hour: 'h', | |
| litre: 'L', | |
| liter: 'L', | |
| arcminute: '′', // minute (plane angle) U+2032 | |
| minute: 'min', // minute (time) | |
| arcsecond: '″', // second (plane angle) U+2033 | |
| neper: 'Np', | |
| tonne: 't', | |
| // SI prefixes | |
| yocto: 'y', // -24 | |
| zepto: 'z', // -21 | |
| atto: 'a', // -18 | |
| femto: 'f', // -15 | |
| pico: 'p', // -12 | |
| nano: 'n', // -9 | |
| micro: 'µ', // -6 | |
| milli: 'm', // -3 | |
| centi: 'c', // -2 | |
| deci: 'd', // -1 | |
| deca: 'da', // 1 | |
| hecto: 'h', // 2 | |
| kilo: 'k', // 3 | |
| mega: 'M', // 6 | |
| giga: 'G', // 9 | |
| tera: 'T', // 12 | |
| peta: 'P', // 15 | |
| exa: 'E', // 18 | |
| zetta: 'Z', // 21 | |
| yotta: 'Y', // 24 | |
| // Special | |
| angstrom: 'Å', | |
| }; | |
| /** Format a number string as LaTeX: commas → {,}, eN → \times 10^{N} */ | |
| function formatNumber(num) { | |
| if (!num) return ''; | |
| const eMatch = num.match(/^(-?[\d,.]+)e(\d+)$/); | |
| if (eMatch) { | |
| const base = eMatch[1].replace(/,/g, '{,}'); | |
| return `${base} \\times 10^{${eMatch[2]}}`; | |
| } | |
| return num.replace(/,/g, '{,}'); | |
| } | |
| const PREFIXES = [ | |
| 'yocto', 'zepto', 'atto', 'femto', 'pico', 'nano', 'micro', 'milli', | |
| 'centi', 'deci', 'deca', 'hecto', 'kilo', 'mega', 'giga', 'tera', | |
| 'peta', 'exa', 'zetta', 'yotta', | |
| ]; | |
| /** Resolve a unit name to its symbol, handling combined prefix+unit names like 'millimeter' */ | |
| function resolveSymbol(name) { | |
| if (name in UNITS) return UNITS[name]; | |
| for (const prefix of PREFIXES) { | |
| if (name.startsWith(prefix)) { | |
| const remainder = name.slice(prefix.length); | |
| if (remainder in UNITS) { | |
| return (UNITS[prefix] ?? prefix) + (UNITS[remainder] ?? remainder); | |
| } | |
| } | |
| } | |
| return name; | |
| } | |
| /** Convert a unit name to a \mathrm{} LaTeX token */ | |
| function unitToLatex(name) { | |
| return `\\mathrm{${resolveSymbol(name)}}`; | |
| } | |
| const si2Role = { | |
| name: 'si2', | |
| doc: 'Format a number with SI units, e.g. {si2}`1.5e3 <\\kilo\\hertz>`', | |
| body: { | |
| type: String, | |
| required: true, | |
| }, | |
| run(data) { | |
| const value = data.body; | |
| // Supports: optional sign, comma-grouped integer, optional decimal, optional exponent | |
| const match = value.match( | |
| /^(-?(\d+(,\d+)*(\.\d+)?)?(e\d+)?)\s?<([\\a-zA-Z\s]+)>$/, | |
| ); | |
| if (!match) { | |
| return [{ type: 'inlineMath', value }]; | |
| } | |
| const number = match[1]; | |
| const commands = match[match.length - 1]; | |
| const unitNames = [...commands.matchAll(/\\([a-zA-Z]+)/g)].map(([, c]) => c); | |
| // Split on \per: units before → numerator, units after → denominator (^{-1}) | |
| const perIdx = unitNames.indexOf('per'); | |
| const numerator = perIdx === -1 ? unitNames : unitNames.slice(0, perIdx); | |
| const denominator = perIdx === -1 ? [] : unitNames.slice(perIdx + 1); | |
| const numLatex = numerator.map(unitToLatex).join('\\,'); | |
| const denLatex = denominator.map((u) => `${unitToLatex(u)}^{-1}`).join('\\,'); | |
| const unitLatex = [numLatex, denLatex].filter(Boolean).join('\\,'); | |
| const latex = `${formatNumber(number)}\\,${unitLatex}`; | |
| return [{ type: 'inlineMath', value: latex }]; | |
| }, | |
| }; | |
| const plugin = { name: 'SI Units', roles: [si2Role] }; | |
| export default plugin; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment