feat: initial (slop) commit
This commit is contained in:
@@ -0,0 +1,235 @@
|
||||
# `units`: A Quarto Extension for Standardized Scientific Unit Notation
|
||||
|
||||
|
||||
This extension provides a centralized, extensible mechanism for the consistent rendering of physical quantities across Quarto-based scientific websites and publications.
|
||||
It is designed principally for use in computational chemistry, biochemistry, and computer-aided drug design (CADD), disciplines in which a single document routinely reports energies, distances, timescales, concentrations, electrostatic potentials, and temperatures spanning many orders of magnitude and multiple unit systems.
|
||||
|
||||
## Motivation
|
||||
|
||||
The correct typographic and semantic treatment of physical quantities is governed by well-established conventions.
|
||||
Most notably those of the International Bureau of Weights and Measures (BIPM) as codified in the SI Brochure, and their discipline-specific elaborations in the IUPAC *Quantities, Units and Symbols in Physical Chemistry* ("Green Book") and NIST Special Publication 811.
|
||||
These conventions specify, among other things: upright (non-italic) typesetting of unit symbols; a non-breaking space between a numerical value and its associated unit; the use of negative exponents rather than stacked solidi for compound units; and unambiguous conversion factors between coherent and non-coherent units within a given physical dimension.
|
||||
|
||||
In practice, manuscripts and websites authored under time constraints tend to depart from these conventions inconsistently: for example, a bond length may be rendered as `1.54 Å` in one section and `1.54Å` (with an ordinary, breakable space) in another or an energy value may be transcribed from kJ·mol⁻¹ to kcal·mol⁻¹ by hand.
|
||||
|
||||
## Installation
|
||||
|
||||
You can install this Quarto extension with the following command:
|
||||
|
||||
|
||||
```bash
|
||||
quarto add https://git.scient.ing/infra/units-quarto-extension/archive/main.zip
|
||||
```
|
||||
|
||||
Following installation, confirm the presence of the extension within the project:
|
||||
|
||||
```text
|
||||
_extensions/
|
||||
└── units/
|
||||
├── _extension.yml
|
||||
└── units.lua
|
||||
```
|
||||
|
||||
A minimal document using the extension should render without error:
|
||||
|
||||
```markdown
|
||||
---
|
||||
title: "Installation Check"
|
||||
---
|
||||
|
||||
The bond length is {{< distance 1.54 angstrom >}}.
|
||||
```
|
||||
|
||||
## Reference: Shortcodes
|
||||
|
||||
| Shortcode | Signature | Description |
|
||||
|---|---|---|
|
||||
| `unit` | `{{< unit <unit-key> >}}` | Renders the typeset symbol for a single unit, with no numerical value or conversion. |
|
||||
| `sci` | `{{< sci <value> >}}` | Renders a bare numerical value in scientific notation, independent of any unit. |
|
||||
| `energy` | `{{< energy <value> <unit> [<target-unit>] >}}` | Bulk (non-molar) energy: J, kJ, cal, kcal. |
|
||||
| `molarenergy` | `{{< molarenergy <value> <unit> [<target-unit>] >}}` | Molar energy: J/mol, kJ/mol, cal/mol, kcal/mol, eV, hartree, cm⁻¹. |
|
||||
| `distance` | `{{< distance <value> <unit> [<target-unit>] >}}` | Distance: angstrom, nanometer, picometer, bohr, meter. |
|
||||
| `time` | `{{< time <value> <unit> [<target-unit>] >}}` | Time: second and its SI-prefixed subdivisions, plus minute, hour, day. |
|
||||
| `concentration` | `{{< concentration <value> <unit> [<target-unit>] >}}` | Molar concentration: M, mM, μM, nM, pM, fM. |
|
||||
| `voltage` | `{{< voltage <value> <unit> [<target-unit>] >}}` | Electrical potential: kV, V, mV, μV. |
|
||||
| `temperature` | `{{< temperature <value> <unit> [<target-unit>] >}}` | Temperature: kelvin, celsius, fahrenheit (affine conversion). |
|
||||
| `massconc` | `{{< massconc <value> <mass-unit> <molar-mass> <target-unit> >}}` | Mass-per-volume concentration converted to molar concentration given an explicit molar mass. |
|
||||
|
||||
Each quantity shortcode accepts an optional keyword argument, `notation=sci` or `notation=plain`, which overrides the automatic determination of numerical formatting described in Section 6.
|
||||
|
||||
## Supported Unit Categories
|
||||
|
||||
### Bulk Energy (base unit: joule)
|
||||
|
||||
| Unit key | Symbol | Notes |
|
||||
|---|---|---|
|
||||
| `J` | J | SI base representation |
|
||||
| `kJ` | kJ | |
|
||||
| `cal` | cal | Thermochemical calorie (1 cal = 4.184 J) |
|
||||
| `kcal` | kcal | |
|
||||
|
||||
### Molar Energy (base unit: kJ·mol⁻¹)
|
||||
|
||||
| Unit key | Symbol | Notes |
|
||||
|---|---|---|
|
||||
| `J/mol` | J·mol⁻¹ | |
|
||||
| `kJ/mol` | kJ·mol⁻¹ | |
|
||||
| `cal/mol` | cal·mol⁻¹ | |
|
||||
| `kcal/mol` | kcal·mol⁻¹ | |
|
||||
| `eV` | eV | 1 eV = 96.485 kJ·mol⁻¹ |
|
||||
| `hartree` | *E*ₕ | Atomic unit of energy; 1 hartree = 2625.5 kJ·mol⁻¹ |
|
||||
| `cm-1` | cm⁻¹ | Wavenumber, as conventionally reported in vibrational spectroscopy |
|
||||
|
||||
### Distance (base unit: ångström)
|
||||
|
||||
| Unit key | Symbol | Notes |
|
||||
|---|---|---|
|
||||
| `angstrom` | Å | Rendered using U+00C5, the canonical (NFKC-normalized) form |
|
||||
| `nanometer` | nm | |
|
||||
| `picometer` | pm | |
|
||||
| `bohr` | *a*₀ | Atomic unit of length |
|
||||
| `meter` | m | |
|
||||
|
||||
### Time (base unit: second)
|
||||
|
||||
| Unit key | Symbol |
|
||||
|---|---|
|
||||
| `second` | s |
|
||||
| `millisecond` | ms |
|
||||
| `microsecond` | μs |
|
||||
| `nanosecond` | ns |
|
||||
| `picosecond` | ps |
|
||||
| `femtosecond` | fs |
|
||||
| `minute` | min |
|
||||
| `hour` | h |
|
||||
| `day` | d |
|
||||
|
||||
### Molar Concentration (base unit: molar)
|
||||
|
||||
| Unit key | Symbol |
|
||||
|---|---|
|
||||
| `molar` | M |
|
||||
| `millimolar` | mM |
|
||||
| `micromolar` | μM |
|
||||
| `nanomolar` | nM |
|
||||
| `picomolar` | pM |
|
||||
| `femtomolar` | fM |
|
||||
|
||||
### Mass Concentration (base unit: mg·mL⁻¹)
|
||||
|
||||
| Unit key | Symbol |
|
||||
|---|---|
|
||||
| `mg/mL` | mg·mL⁻¹ |
|
||||
| `μg/mL` | μg·mL⁻¹ |
|
||||
| `ng/mL` | ng·mL⁻¹ |
|
||||
|
||||
This category is consumed exclusively by the `massconc` shortcode, which additionally requires a molar mass argument to compute the corresponding molar concentration.
|
||||
|
||||
### Voltage (base unit: volt)
|
||||
|
||||
| Unit key | Symbol |
|
||||
|---|---|
|
||||
| `kilovolt` | kV |
|
||||
| `volt` | V |
|
||||
| `millivolt` | mV |
|
||||
| `microvolt` | μV |
|
||||
|
||||
### Temperature
|
||||
|
||||
| Unit key | Symbol | Conversion basis |
|
||||
|---|---|---|
|
||||
| `kelvin` | K | Identity (SI base unit; no degree sign, per convention) |
|
||||
| `celsius` | °C | *T*(K) = *T*(°C) + 273.15 |
|
||||
| `fahrenheit` | °F | *T*(K) = [*T*(°F) + 459.67] × 5/9 |
|
||||
|
||||
## Numerical Formatting
|
||||
|
||||
The rendered representation of a numerical value is determined automatically unless overridden by the `notation` keyword argument.
|
||||
|
||||
By default, a value is formatted to three significant figures using fixed decimal notation.
|
||||
Should this formatting internally require exponential notation, the extension instead renders the value in explicit scientific notation, with the mantissa and a true superscript exponent joined by a non-breaking multiplication sign:
|
||||
|
||||
```markdown
|
||||
{{< sci 6.02214076e23 >}}
|
||||
```
|
||||
|
||||
renders as 6.022 × 10²³, rather than the unprocessed string `6.02e+23`.
|
||||
|
||||
## Worked Examples
|
||||
|
||||
### Distance
|
||||
|
||||
```markdown
|
||||
The O–H bond length is {{< distance 0.96 angstrom >}}.
|
||||
Converted to nanometers: {{< distance 1.54 angstrom nanometer >}}.
|
||||
```
|
||||
> The O–H bond length is 0.96 Å.
|
||||
> Converted to nanometers: 1.54 Å (0.154 nm).
|
||||
|
||||
### Bulk Energy
|
||||
|
||||
```markdown
|
||||
Combustion releases {{< energy 4.18 J >}} of heat under these conditions.
|
||||
```
|
||||
> Combustion releases 4.18 J of heat under these conditions.
|
||||
|
||||
### Molar Energy
|
||||
|
||||
```markdown
|
||||
The activation barrier was {{< molarenergy 45.2 kJ/mol kcal/mol >}}.
|
||||
A single-point DFT energy of {{< molarenergy 0.0421 hartree kcal/mol >}} was obtained.
|
||||
```
|
||||
> The activation barrier was 45.2 kJ·mol⁻¹ (10.8 kcal·mol⁻¹).
|
||||
> A single-point DFT energy of 0.0421 *E*ₕ (110 kcal·mol⁻¹) was obtained.
|
||||
|
||||
### Time
|
||||
|
||||
```markdown
|
||||
The production molecular dynamics simulation was extended to
|
||||
{{< time 500 nanosecond microsecond >}}, using an integration timestep of
|
||||
{{< time 2e-15 second femtosecond >}}.
|
||||
```
|
||||
> The production molecular dynamics simulation was extended to 500 ns
|
||||
> (0.5 μs), using an integration timestep of 2 × 10⁻¹⁵ s (2 fs).
|
||||
|
||||
### Molar Concentration
|
||||
|
||||
```markdown
|
||||
The compound exhibited a Kᵢ of {{< concentration 12 nanomolar >}}
|
||||
against the target enzyme.
|
||||
```
|
||||
> The compound exhibited a Kᵢ of 12 nM against the target enzyme.
|
||||
|
||||
### Voltage
|
||||
|
||||
```markdown
|
||||
Resting membrane potential was recorded at {{< voltage -70 millivolt >}}.
|
||||
```
|
||||
> Resting membrane potential was recorded at -70 mV.
|
||||
|
||||
### Temperature
|
||||
|
||||
```markdown
|
||||
Assays were conducted at {{< temperature 25 celsius kelvin >}}, approximating
|
||||
physiological temperature of {{< temperature 310.15 kelvin celsius >}}.
|
||||
```
|
||||
> Assays were conducted at 25 °C (298 K), approximating physiological
|
||||
> temperature of 310.15 K (37 °C).
|
||||
|
||||
### Scientific Notation and Rate Constants
|
||||
|
||||
```markdown
|
||||
The observed second-order rate constant was
|
||||
{{< concentration 2.1e6 molar notation=sci >}}⁻¹·{{< unit second >}}⁻¹.
|
||||
```
|
||||
> The observed second-order rate constant was 2.1 × 10⁶ M⁻¹·s⁻¹.
|
||||
|
||||
### Mass-to-Molar Concentration Conversion
|
||||
|
||||
```markdown
|
||||
A stock solution prepared at {{< massconc 0.5 mg/mL 350.4 micromolar >}}
|
||||
was used for the binding assay.
|
||||
```
|
||||
> A stock solution prepared at 0.5 mg·mL⁻¹ (1.43 μM, MW 350.4 g/mol) was
|
||||
> used for the binding assay.
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
|
||||
title: Units
|
||||
author: Alex Maldonado
|
||||
version: 0.1.0
|
||||
quarto-required: ">=1.2.0"
|
||||
contributes:
|
||||
shortcodes:
|
||||
- units.lua
|
||||
@@ -0,0 +1,293 @@
|
||||
local NBSP = "\u{00A0}"
|
||||
local function nbsp() return pandoc.Str(NBSP) end
|
||||
|
||||
-- render a value already known to be in scientific-notation range
|
||||
-- returns a list of inlines: mantissa <NBSP> × <NBSP> 10^exp^
|
||||
local function formatSci(value)
|
||||
local s = string.format("%.3e", value) -- e.g. "6.020e+23" or "1.500e-06"
|
||||
local mantissa, sign, exp = s:match("^(-?%d+%.?%d*)e([+-])(%d+)$")
|
||||
if not mantissa then
|
||||
-- fallback, shouldn't happen with %.3e, but never crash on bad input
|
||||
return { pandoc.Str(s) }
|
||||
end
|
||||
-- trim trailing zeros (and a trailing bare decimal point) from mantissa
|
||||
mantissa = mantissa:gsub("0+$", ""):gsub("%.$", "")
|
||||
local expNum = tonumber(sign .. exp) -- keeps sign, drops leading zeros (e.g. "+23" -> 23, "-06" -> -6)
|
||||
|
||||
local out = { pandoc.Str(mantissa) }
|
||||
if expNum ~= 0 then
|
||||
table.insert(out, nbsp())
|
||||
table.insert(out, pandoc.Str("×"))
|
||||
table.insert(out, nbsp())
|
||||
table.insert(out, pandoc.Str("10"))
|
||||
table.insert(out, pandoc.Superscript({ pandoc.Str(tostring(expNum)) }))
|
||||
end
|
||||
return out
|
||||
end
|
||||
|
||||
-- render a value in plain decimal notation, 3 significant figures.
|
||||
-- IMPORTANT: C's %g spec has its own internal rule for switching to
|
||||
-- exponential form ("use %e if exponent < -4 or exponent >= precision").
|
||||
-- With precision 3, that means %.3g silently flips to "1e+03"-style
|
||||
-- output for anything >= 1000 -- BEFORE any magnitude threshold of ours
|
||||
-- gets a say. Rather than duplicating that rule with a second threshold
|
||||
-- we let %g decide, then intercept its exponential output and re-render it through
|
||||
-- formatSci() instead of ever emitting the raw "1e+03" string.
|
||||
local function formatPlain(value)
|
||||
local s = string.format("%.3g", value)
|
||||
if s:find("[eE]") then
|
||||
return formatSci(value)
|
||||
end
|
||||
return { pandoc.Str(s) }
|
||||
end
|
||||
|
||||
-- decide plain vs. scientific, unless overridden.
|
||||
-- mode: nil/"auto" (let %g decide, see formatPlain), "sci", or "plain"
|
||||
local function formatNumber(value, mode)
|
||||
if value == 0 then return { pandoc.Str("0") } end
|
||||
if mode == "sci" then return formatSci(value) end
|
||||
return formatPlain(value) -- handles both "auto" and explicit "plain"
|
||||
end
|
||||
|
||||
local function appendAll(dst, list)
|
||||
for _, inl in ipairs(list) do table.insert(dst, inl) end
|
||||
end
|
||||
|
||||
local ENERGY_UNITS = {
|
||||
["J"] = { md = "J", per_base = 1 },
|
||||
["kJ"] = { md = "kJ", per_base = 1e-3 },
|
||||
["cal"] = { md = "cal", per_base = 1 / 4.184 },
|
||||
["kcal"] = { md = "kcal", per_base = 1 / 4184 },
|
||||
}
|
||||
|
||||
local MOLAR_ENERGY_UNITS = {
|
||||
["J/mol"] = { md = "J·mol^-1^", per_base = 1000 },
|
||||
["kJ/mol"] = { md = "kJ·mol^-1^", per_base = 1 },
|
||||
["cal/mol"] = { md = "cal·mol^-1^", per_base = 1000 / 4.184 },
|
||||
["kcal/mol"] = { md = "kcal·mol^-1^", per_base = 1 / 4.184 },
|
||||
["eV"] = { md = "eV", per_base = 1 / 96.485 },
|
||||
["hartree"] = { md = "*E*~h~", per_base = 1 / 2625.5 },
|
||||
["cm-1"] = { md = "cm^-1^", per_base = 1 / 0.0119627 },
|
||||
}
|
||||
|
||||
local DISTANCE_UNITS = {
|
||||
["angstrom"] = { md = "Å", per_base = 1 },
|
||||
["nanometer"] = { md = "nm", per_base = 0.1 },
|
||||
["picometer"] = { md = "pm", per_base = 100 },
|
||||
["bohr"] = { md = "*a*~0~", per_base = 1.8897259886 },
|
||||
["meter"] = { md = "m", per_base = 1e-10 },
|
||||
}
|
||||
|
||||
local TIME_UNITS = {
|
||||
["second"] = { md = "s", per_base = 1 },
|
||||
["millisecond"] = { md = "ms", per_base = 1e3 },
|
||||
["microsecond"] = { md = "μs", per_base = 1e6 },
|
||||
["nanosecond"] = { md = "ns", per_base = 1e9 },
|
||||
["picosecond"] = { md = "ps", per_base = 1e12 },
|
||||
["femtosecond"] = { md = "fs", per_base = 1e15 },
|
||||
["minute"] = { md = "min", per_base = 1 / 60 },
|
||||
["hour"] = { md = "h", per_base = 1 / 3600 },
|
||||
["day"] = { md = "d", per_base = 1 / 86400 },
|
||||
}
|
||||
|
||||
local CONCENTRATION_UNITS = {
|
||||
["molar"] = { md = "M", per_base = 1 },
|
||||
["millimolar"] = { md = "mM", per_base = 1e3 },
|
||||
["micromolar"] = { md = "μM", per_base = 1e6 },
|
||||
["nanomolar"] = { md = "nM", per_base = 1e9 },
|
||||
["picomolar"] = { md = "pM", per_base = 1e12 },
|
||||
["femtomolar"] = { md = "fM", per_base = 1e15 },
|
||||
}
|
||||
|
||||
local VOLTAGE_UNITS = {
|
||||
["kilovolt"] = { md = "kV", per_base = 1e-3 },
|
||||
["volt"] = { md = "V", per_base = 1 },
|
||||
["millivolt"] = { md = "mV", per_base = 1e3 },
|
||||
["microvolt"] = { md = "μV", per_base = 1e6 },
|
||||
}
|
||||
|
||||
local TEMPERATURE_UNITS = {
|
||||
["kelvin"] = {
|
||||
md = "K",
|
||||
toKelvin = function(v) return v end,
|
||||
fromKelvin = function(k) return k end,
|
||||
},
|
||||
["celsius"] = {
|
||||
md = "°C",
|
||||
toKelvin = function(v) return v + 273.15 end,
|
||||
fromKelvin = function(k) return k - 273.15 end,
|
||||
},
|
||||
["fahrenheit"] = {
|
||||
md = "°F",
|
||||
toKelvin = function(v) return (v + 459.67) * 5 / 9 end,
|
||||
fromKelvin = function(k) return k * 9 / 5 - 459.67 end,
|
||||
},
|
||||
}
|
||||
|
||||
local MASS_CONC_UNITS = {
|
||||
["mg/mL"] = { md = "mg·mL^-1^", per_base = 1 },
|
||||
["μg/mL"] = { md = "μg·mL^-1^", per_base = 1e3 },
|
||||
["ng/mL"] = { md = "ng·mL^-1^", per_base = 1e6 },
|
||||
}
|
||||
|
||||
local SYMBOLS = {}
|
||||
for _, tbl in ipairs({
|
||||
ENERGY_UNITS, MOLAR_ENERGY_UNITS, DISTANCE_UNITS,
|
||||
TIME_UNITS, CONCENTRATION_UNITS, VOLTAGE_UNITS, TEMPERATURE_UNITS,
|
||||
}) do
|
||||
for k, v in pairs(tbl) do SYMBOLS[k] = v end
|
||||
end
|
||||
|
||||
local function md_to_inlines(md)
|
||||
return pandoc.read(md, "markdown").blocks[1].content
|
||||
end
|
||||
|
||||
-- {{< unit nanomolar >}} -> just the formatted symbol, any category
|
||||
local function unit(args)
|
||||
local key = pandoc.utils.stringify(args[1])
|
||||
local u = SYMBOLS[key]
|
||||
if not u then
|
||||
io.stderr:write("[units] unknown unit '" .. key .. "'\n")
|
||||
return pandoc.Str(key)
|
||||
end
|
||||
return md_to_inlines(u.md)
|
||||
end
|
||||
|
||||
-- {{< sci 6.02e23 >}} -> 6.02 × 10²³ (always scientific, no unit)
|
||||
local function sci(args)
|
||||
local value = tonumber(pandoc.utils.stringify(args[1]))
|
||||
if not value then
|
||||
io.stderr:write("[units] bad sci() arg: '" .. pandoc.utils.stringify(args[1]) .. "'\n")
|
||||
return pandoc.Str("[sci error]")
|
||||
end
|
||||
return formatSci(value)
|
||||
end
|
||||
|
||||
-- generic converter, parameterized by which table to use.
|
||||
-- optional kwargs.notation = "sci" | "plain" forces that format
|
||||
-- for both the source value and (if present) the converted value.
|
||||
local function makeQuantityShortcode(TABLE, label)
|
||||
return function(args, kwargs)
|
||||
local value = tonumber(pandoc.utils.stringify(args[1]))
|
||||
local fromKey = pandoc.utils.stringify(args[2])
|
||||
local toKey = args[3] and pandoc.utils.stringify(args[3]) or nil
|
||||
local from = TABLE[fromKey]
|
||||
local mode = kwargs and kwargs["notation"] and pandoc.utils.stringify(kwargs["notation"]) or nil
|
||||
|
||||
if not value or not from then
|
||||
io.stderr:write("[units] bad " .. label .. "() args, unit='" ..
|
||||
tostring(fromKey) .. "'\n")
|
||||
return pandoc.Str("[" .. label .. " error]")
|
||||
end
|
||||
|
||||
local out = {}
|
||||
appendAll(out, formatNumber(value, mode))
|
||||
table.insert(out, nbsp())
|
||||
appendAll(out, md_to_inlines(from.md))
|
||||
|
||||
if toKey then
|
||||
local to = TABLE[toKey]
|
||||
if to then
|
||||
local baseVal = value / from.per_base
|
||||
local converted = baseVal * to.per_base
|
||||
table.insert(out, nbsp())
|
||||
table.insert(out, pandoc.Str("("))
|
||||
appendAll(out, formatNumber(converted, mode))
|
||||
table.insert(out, nbsp())
|
||||
appendAll(out, md_to_inlines(to.md))
|
||||
table.insert(out, pandoc.Str(")"))
|
||||
else
|
||||
io.stderr:write("[units] unknown target unit '" .. toKey .. "' for " .. label .. "\n")
|
||||
end
|
||||
end
|
||||
return out
|
||||
end
|
||||
end
|
||||
|
||||
-- {{< temperature 25 celsius kelvin >}}
|
||||
-- Uses toKelvin/fromKelvin round-trip instead of a per_base ratio,
|
||||
-- since C/F/K aren't related by a pure multiplicative factor.
|
||||
local function temperature(args, kwargs)
|
||||
local value = tonumber(pandoc.utils.stringify(args[1]))
|
||||
local fromKey = pandoc.utils.stringify(args[2])
|
||||
local toKey = args[3] and pandoc.utils.stringify(args[3]) or nil
|
||||
local from = TEMPERATURE_UNITS[fromKey]
|
||||
local mode = kwargs and kwargs["notation"] and pandoc.utils.stringify(kwargs["notation"]) or nil
|
||||
|
||||
if not value or not from then
|
||||
io.stderr:write("[units] bad temperature() args, unit='" .. tostring(fromKey) .. "'\n")
|
||||
return pandoc.Str("[temperature error]")
|
||||
end
|
||||
|
||||
local kelvin = from.toKelvin(value)
|
||||
if kelvin < 0 then
|
||||
io.stderr:write("[units] warning: temperature() computed " ..
|
||||
string.format("%.2f", kelvin) .. " K, below absolute zero -- check input\n")
|
||||
end
|
||||
|
||||
local out = {}
|
||||
appendAll(out, formatNumber(value, mode))
|
||||
table.insert(out, nbsp())
|
||||
appendAll(out, md_to_inlines(from.md))
|
||||
|
||||
if toKey then
|
||||
local to = TEMPERATURE_UNITS[toKey]
|
||||
if to then
|
||||
local converted = to.fromKelvin(kelvin)
|
||||
table.insert(out, nbsp())
|
||||
table.insert(out, pandoc.Str("("))
|
||||
appendAll(out, formatNumber(converted, mode))
|
||||
table.insert(out, nbsp())
|
||||
appendAll(out, md_to_inlines(to.md))
|
||||
table.insert(out, pandoc.Str(")"))
|
||||
else
|
||||
io.stderr:write("[units] unknown target unit '" .. toKey .. "' for temperature\n")
|
||||
end
|
||||
end
|
||||
return out
|
||||
end
|
||||
|
||||
-- {{< massconc 0.5 mg/mL 350.4 micromolar >}}
|
||||
local function massconc(args, kwargs)
|
||||
local value = tonumber(pandoc.utils.stringify(args[1]))
|
||||
local fromKey = pandoc.utils.stringify(args[2])
|
||||
local mw = tonumber(pandoc.utils.stringify(args[3]))
|
||||
local toKey = pandoc.utils.stringify(args[4])
|
||||
local from = MASS_CONC_UNITS[fromKey]
|
||||
local to = CONCENTRATION_UNITS[toKey]
|
||||
local mode = kwargs and kwargs["notation"] and pandoc.utils.stringify(kwargs["notation"]) or nil
|
||||
|
||||
if not (value and from and mw and to) then
|
||||
io.stderr:write("[units] bad massconc() args\n")
|
||||
return pandoc.Str("[massconc error]")
|
||||
end
|
||||
|
||||
local gPerL = value / from.per_base
|
||||
local molPerL = gPerL / mw
|
||||
local converted = molPerL * to.per_base
|
||||
|
||||
local out = {}
|
||||
appendAll(out, formatNumber(value, mode))
|
||||
table.insert(out, nbsp())
|
||||
appendAll(out, md_to_inlines(from.md))
|
||||
table.insert(out, nbsp())
|
||||
table.insert(out, pandoc.Str("("))
|
||||
appendAll(out, formatNumber(converted, mode))
|
||||
table.insert(out, nbsp())
|
||||
appendAll(out, md_to_inlines(to.md))
|
||||
table.insert(out, pandoc.Str(", MW " .. tostring(mw) .. NBSP .. "g/mol)"))
|
||||
return out
|
||||
end
|
||||
|
||||
return {
|
||||
["unit"] = unit,
|
||||
["sci"] = sci,
|
||||
["energy"] = makeQuantityShortcode(ENERGY_UNITS, "energy"),
|
||||
["molarenergy"] = makeQuantityShortcode(MOLAR_ENERGY_UNITS, "molarenergy"),
|
||||
["distance"] = makeQuantityShortcode(DISTANCE_UNITS, "distance"),
|
||||
["time"] = makeQuantityShortcode(TIME_UNITS, "time"),
|
||||
["concentration"] = makeQuantityShortcode(CONCENTRATION_UNITS, "concentration"),
|
||||
["voltage"] = makeQuantityShortcode(VOLTAGE_UNITS, "voltage"),
|
||||
["temperature"] = temperature,
|
||||
["massconc"] = massconc,
|
||||
}
|
||||
Reference in New Issue
Block a user