summaryrefslogtreecommitdiff
path: root/library/src/utility
diff options
context:
space:
mode:
authorLaurenz <laurmaedje@gmail.com>2022-11-03 16:50:26 +0100
committerLaurenz <laurmaedje@gmail.com>2022-11-03 16:50:26 +0100
commit33928a00dc58250e24da1dae4e5db17e7b598d70 (patch)
tree451083aa64f57b442359875b0415541463cb1a0c /library/src/utility
parent46921a8c283718402322d4d09c0bd1d9194278b1 (diff)
Tidy up library
Diffstat (limited to 'library/src/utility')
-rw-r--r--library/src/utility/color.rs66
-rw-r--r--library/src/utility/data.rs132
-rw-r--r--library/src/utility/math.rs144
-rw-r--r--library/src/utility/mod.rs54
-rw-r--r--library/src/utility/string.rs141
5 files changed, 0 insertions, 537 deletions
diff --git a/library/src/utility/color.rs b/library/src/utility/color.rs
deleted file mode 100644
index 8bb12334..00000000
--- a/library/src/utility/color.rs
+++ /dev/null
@@ -1,66 +0,0 @@
-use std::str::FromStr;
-
-use crate::prelude::*;
-
-/// Create a grayscale color.
-pub fn luma(_: &mut Vm, args: &mut Args) -> SourceResult<Value> {
- let Component(luma) = args.expect("gray component")?;
- Ok(Value::Color(LumaColor::new(luma).into()))
-}
-
-/// Create an RGB(A) color.
-pub fn rgb(_: &mut Vm, args: &mut Args) -> SourceResult<Value> {
- Ok(Value::Color(
- if let Some(string) = args.find::<Spanned<EcoString>>()? {
- match RgbaColor::from_str(&string.v) {
- Ok(color) => color.into(),
- Err(msg) => bail!(string.span, msg),
- }
- } else {
- let Component(r) = args.expect("red component")?;
- let Component(g) = args.expect("green component")?;
- let Component(b) = args.expect("blue component")?;
- let Component(a) = args.eat()?.unwrap_or(Component(255));
- RgbaColor::new(r, g, b, a).into()
- },
- ))
-}
-
-/// Create a CMYK color.
-pub fn cmyk(_: &mut Vm, args: &mut Args) -> SourceResult<Value> {
- let RatioComponent(c) = args.expect("cyan component")?;
- let RatioComponent(m) = args.expect("magenta component")?;
- let RatioComponent(y) = args.expect("yellow component")?;
- let RatioComponent(k) = args.expect("key component")?;
- Ok(Value::Color(CmykColor::new(c, m, y, k).into()))
-}
-
-/// An integer or ratio component.
-struct Component(u8);
-
-castable! {
- Component,
- Expected: "integer or ratio",
- Value::Int(v) => match v {
- 0 ..= 255 => Self(v as u8),
- _ => Err("must be between 0 and 255")?,
- },
- Value::Ratio(v) => if (0.0 ..= 1.0).contains(&v.get()) {
- Self((v.get() * 255.0).round() as u8)
- } else {
- Err("must be between 0% and 100%")?
- },
-}
-
-/// A component that must be a ratio.
-struct RatioComponent(u8);
-
-castable! {
- RatioComponent,
- Expected: "ratio",
- Value::Ratio(v) => if (0.0 ..= 1.0).contains(&v.get()) {
- Self((v.get() * 255.0).round() as u8)
- } else {
- Err("must be between 0% and 100%")?
- },
-}
diff --git a/library/src/utility/data.rs b/library/src/utility/data.rs
deleted file mode 100644
index 3edade55..00000000
--- a/library/src/utility/data.rs
+++ /dev/null
@@ -1,132 +0,0 @@
-use std::fmt::Write;
-
-use typst::diag::format_xml_like_error;
-
-use crate::prelude::*;
-
-/// Read structured data from a CSV file.
-pub fn csv(vm: &mut Vm, args: &mut Args) -> SourceResult<Value> {
- let Spanned { v: path, span } =
- args.expect::<Spanned<EcoString>>("path to csv file")?;
-
- let path = vm.locate(&path).at(span)?;
- let data = vm.world.file(&path).at(span)?;
-
- let mut builder = csv::ReaderBuilder::new();
- builder.has_headers(false);
-
- let mut reader = builder.from_reader(data.as_slice());
- let mut vec = vec![];
-
- for result in reader.records() {
- let row = result.map_err(format_csv_error).at(span)?;
- let array = row.iter().map(|field| Value::Str(field.into())).collect();
- vec.push(Value::Array(array))
- }
-
- Ok(Value::Array(Array::from_vec(vec)))
-}
-
-/// Format the user-facing CSV error message.
-fn format_csv_error(error: csv::Error) -> String {
- match error.kind() {
- csv::ErrorKind::Utf8 { .. } => "file is not valid utf-8".into(),
- csv::ErrorKind::UnequalLengths { pos, expected_len, len } => {
- let mut msg = format!(
- "failed to parse csv file: found {len} instead of {expected_len} fields"
- );
- if let Some(pos) = pos {
- write!(msg, " in line {}", pos.line()).unwrap();
- }
- msg
- }
- _ => "failed to parse csv file".into(),
- }
-}
-
-/// Read structured data from a JSON file.
-pub fn json(vm: &mut Vm, args: &mut Args) -> SourceResult<Value> {
- let Spanned { v: path, span } =
- args.expect::<Spanned<EcoString>>("path to json file")?;
-
- let path = vm.locate(&path).at(span)?;
- let data = vm.world.file(&path).at(span)?;
- let value: serde_json::Value =
- serde_json::from_slice(&data).map_err(format_json_error).at(span)?;
-
- Ok(convert_json(value))
-}
-
-/// Convert a JSON value to a Typst value.
-fn convert_json(value: serde_json::Value) -> Value {
- match value {
- serde_json::Value::Null => Value::None,
- serde_json::Value::Bool(v) => Value::Bool(v),
- serde_json::Value::Number(v) => match v.as_i64() {
- Some(int) => Value::Int(int),
- None => Value::Float(v.as_f64().unwrap_or(f64::NAN)),
- },
- serde_json::Value::String(v) => Value::Str(v.into()),
- serde_json::Value::Array(v) => {
- Value::Array(v.into_iter().map(convert_json).collect())
- }
- serde_json::Value::Object(v) => Value::Dict(
- v.into_iter()
- .map(|(key, value)| (key.into(), convert_json(value)))
- .collect(),
- ),
- }
-}
-
-/// Format the user-facing JSON error message.
-fn format_json_error(error: serde_json::Error) -> String {
- assert!(error.is_syntax() || error.is_eof());
- format!(
- "failed to parse json file: syntax error in line {}",
- error.line()
- )
-}
-
-/// Read structured data from an XML file.
-pub fn xml(vm: &mut Vm, args: &mut Args) -> SourceResult<Value> {
- let Spanned { v: path, span } =
- args.expect::<Spanned<EcoString>>("path to xml file")?;
-
- let path = vm.locate(&path).at(span)?;
- let data = vm.world.file(&path).at(span)?;
- let text = std::str::from_utf8(&data).map_err(FileError::from).at(span)?;
-
- let document = roxmltree::Document::parse(text).map_err(format_xml_error).at(span)?;
-
- Ok(convert_xml(document.root()))
-}
-
-/// Convert an XML node to a Typst value.
-fn convert_xml(node: roxmltree::Node) -> Value {
- if node.is_text() {
- return Value::Str(node.text().unwrap_or_default().into());
- }
-
- let children: Array = node.children().map(convert_xml).collect();
- if node.is_root() {
- return Value::Array(children);
- }
-
- let tag: Str = node.tag_name().name().into();
- let attrs: Dict = node
- .attributes()
- .iter()
- .map(|attr| (attr.name().into(), attr.value().into()))
- .collect();
-
- Value::Dict(dict! {
- "tag" => tag,
- "attrs" => attrs,
- "children" => children,
- })
-}
-
-/// Format the user-facing XML error message.
-fn format_xml_error(error: roxmltree::Error) -> String {
- format_xml_like_error("xml file", error)
-}
diff --git a/library/src/utility/math.rs b/library/src/utility/math.rs
deleted file mode 100644
index dd37e8e7..00000000
--- a/library/src/utility/math.rs
+++ /dev/null
@@ -1,144 +0,0 @@
-use std::cmp::Ordering;
-
-use crate::prelude::*;
-
-/// Convert a value to an integer.
-pub fn int(_: &mut Vm, args: &mut Args) -> SourceResult<Value> {
- let Spanned { v, span } = args.expect("value")?;
- Ok(Value::Int(match v {
- Value::Bool(v) => v as i64,
- Value::Int(v) => v,
- Value::Float(v) => v as i64,
- Value::Str(v) => match v.parse() {
- Ok(v) => v,
- Err(_) => bail!(span, "invalid integer"),
- },
- v => bail!(span, "cannot convert {} to integer", v.type_name()),
- }))
-}
-
-/// Convert a value to a float.
-pub fn float(_: &mut Vm, args: &mut Args) -> SourceResult<Value> {
- let Spanned { v, span } = args.expect("value")?;
- Ok(Value::Float(match v {
- Value::Int(v) => v as f64,
- Value::Float(v) => v,
- Value::Str(v) => match v.parse() {
- Ok(v) => v,
- Err(_) => bail!(span, "invalid float"),
- },
- v => bail!(span, "cannot convert {} to float", v.type_name()),
- }))
-}
-
-/// The absolute value of a numeric value.
-pub fn abs(_: &mut Vm, args: &mut Args) -> SourceResult<Value> {
- let Spanned { v, span } = args.expect("numeric value")?;
- Ok(match v {
- Value::Int(v) => Value::Int(v.abs()),
- Value::Float(v) => Value::Float(v.abs()),
- Value::Angle(v) => Value::Angle(v.abs()),
- Value::Ratio(v) => Value::Ratio(v.abs()),
- Value::Fraction(v) => Value::Fraction(v.abs()),
- Value::Length(_) | Value::Relative(_) => {
- bail!(span, "cannot take absolute value of a length")
- }
- v => bail!(span, "expected numeric value, found {}", v.type_name()),
- })
-}
-
-/// The minimum of a sequence of values.
-pub fn min(_: &mut Vm, args: &mut Args) -> SourceResult<Value> {
- minmax(args, Ordering::Less)
-}
-
-/// The maximum of a sequence of values.
-pub fn max(_: &mut Vm, args: &mut Args) -> SourceResult<Value> {
- minmax(args, Ordering::Greater)
-}
-
-/// Find the minimum or maximum of a sequence of values.
-fn minmax(args: &mut Args, goal: Ordering) -> SourceResult<Value> {
- let mut extremum = args.expect::<Value>("value")?;
- for Spanned { v, span } in args.all::<Spanned<Value>>()? {
- match v.partial_cmp(&extremum) {
- Some(ordering) => {
- if ordering == goal {
- extremum = v;
- }
- }
- None => bail!(
- span,
- "cannot compare {} and {}",
- extremum.type_name(),
- v.type_name(),
- ),
- }
- }
- Ok(extremum)
-}
-
-/// Whether an integer is even.
-pub fn even(_: &mut Vm, args: &mut Args) -> SourceResult<Value> {
- Ok(Value::Bool(args.expect::<i64>("integer")? % 2 == 0))
-}
-
-/// Whether an integer is odd.
-pub fn odd(_: &mut Vm, args: &mut Args) -> SourceResult<Value> {
- Ok(Value::Bool(args.expect::<i64>("integer")? % 2 != 0))
-}
-
-/// The modulo of two numbers.
-pub fn mod_(_: &mut Vm, args: &mut Args) -> SourceResult<Value> {
- let Spanned { v: v1, span: span1 } = args.expect("integer or float")?;
- let Spanned { v: v2, span: span2 } = args.expect("integer or float")?;
-
- let (a, b) = match (v1, v2) {
- (Value::Int(a), Value::Int(b)) => match a.checked_rem(b) {
- Some(res) => return Ok(Value::Int(res)),
- None => bail!(span2, "divisor must not be zero"),
- },
- (Value::Int(a), Value::Float(b)) => (a as f64, b),
- (Value::Float(a), Value::Int(b)) => (a, b as f64),
- (Value::Float(a), Value::Float(b)) => (a, b),
- (Value::Int(_), b) | (Value::Float(_), b) => bail!(
- span2,
- format!("expected integer or float, found {}", b.type_name())
- ),
- (a, _) => bail!(
- span1,
- format!("expected integer or float, found {}", a.type_name())
- ),
- };
-
- if b == 0.0 {
- bail!(span2, "divisor must not be zero");
- }
-
- Ok(Value::Float(a % b))
-}
-
-/// Create a sequence of numbers.
-pub fn range(_: &mut Vm, args: &mut Args) -> SourceResult<Value> {
- let first = args.expect::<i64>("end")?;
- let (start, end) = match args.eat::<i64>()? {
- Some(second) => (first, second),
- None => (0, first),
- };
-
- let step: i64 = match args.named("step")? {
- Some(Spanned { v: 0, span }) => bail!(span, "step must not be zero"),
- Some(Spanned { v, .. }) => v,
- None => 1,
- };
-
- let mut x = start;
- let mut seq = vec![];
-
- while x.cmp(&end) == 0.cmp(&step) {
- seq.push(Value::Int(x));
- x += step;
- }
-
- Ok(Value::Array(Array::from_vec(seq)))
-}
diff --git a/library/src/utility/mod.rs b/library/src/utility/mod.rs
deleted file mode 100644
index 402944cd..00000000
--- a/library/src/utility/mod.rs
+++ /dev/null
@@ -1,54 +0,0 @@
-//! Computational utility functions.
-
-mod color;
-mod data;
-mod math;
-mod string;
-
-pub use color::*;
-pub use data::*;
-pub use math::*;
-pub use string::*;
-
-use comemo::Track;
-use typst::model::{Eval, Route, Scopes, Vm};
-use typst::syntax::Source;
-
-use crate::prelude::*;
-
-/// The name of a value's type.
-pub fn type_(_: &mut Vm, args: &mut Args) -> SourceResult<Value> {
- Ok(args.expect::<Value>("value")?.type_name().into())
-}
-
-/// Ensure that a condition is fulfilled.
-pub fn assert(_: &mut Vm, args: &mut Args) -> SourceResult<Value> {
- let Spanned { v, span } = args.expect::<Spanned<bool>>("condition")?;
- if !v {
- bail!(span, "assertion failed");
- }
- Ok(Value::None)
-}
-
-/// Evaluate a string as Typst markup.
-pub fn eval(vm: &mut Vm, args: &mut Args) -> SourceResult<Value> {
- let Spanned { v: text, span } = args.expect::<Spanned<String>>("source")?;
-
- // Parse the source and set a synthetic span for all nodes.
- let source = Source::synthesized(text, span);
- let ast = source.ast()?;
-
- // Evaluate the source.
- let std = &vm.world.config().scope;
- let scopes = Scopes::new(Some(std));
- let route = Route::default();
- let mut sub = Vm::new(vm.world, route.track(), None, scopes);
- let result = ast.eval(&mut sub);
-
- // Handle control flow.
- if let Some(flow) = sub.flow {
- bail!(flow.forbidden());
- }
-
- Ok(Value::Content(result?))
-}
diff --git a/library/src/utility/string.rs b/library/src/utility/string.rs
deleted file mode 100644
index ed444d35..00000000
--- a/library/src/utility/string.rs
+++ /dev/null
@@ -1,141 +0,0 @@
-use typst::model::Regex;
-
-use crate::prelude::*;
-
-/// The string representation of a value.
-pub fn repr(_: &mut Vm, args: &mut Args) -> SourceResult<Value> {
- Ok(args.expect::<Value>("value")?.repr().into())
-}
-
-/// Convert a value to a string.
-pub fn str(_: &mut Vm, args: &mut Args) -> SourceResult<Value> {
- let Spanned { v, span } = args.expect("value")?;
- Ok(Value::Str(match v {
- Value::Int(v) => format_str!("{}", v),
- Value::Float(v) => format_str!("{}", v),
- Value::Str(v) => v,
- v => bail!(span, "cannot convert {} to string", v.type_name()),
- }))
-}
-
-/// Create blind text.
-pub fn lorem(_: &mut Vm, args: &mut Args) -> SourceResult<Value> {
- let words: usize = args.expect("number of words")?;
- Ok(Value::Str(lipsum::lipsum(words).into()))
-}
-
-/// Create a regular expression.
-pub fn regex(_: &mut Vm, args: &mut Args) -> SourceResult<Value> {
- let Spanned { v, span } = args.expect::<Spanned<EcoString>>("regular expression")?;
- Ok(Regex::new(&v).at(span)?.into())
-}
-
-/// Converts an integer into one or multiple letters.
-pub fn letter(_: &mut Vm, args: &mut Args) -> SourceResult<Value> {
- numbered(Numbering::Letter, args)
-}
-
-/// Converts an integer into a roman numeral.
-pub fn roman(_: &mut Vm, args: &mut Args) -> SourceResult<Value> {
- numbered(Numbering::Roman, args)
-}
-
-/// Convert a number into a symbol.
-pub fn symbol(_: &mut Vm, args: &mut Args) -> SourceResult<Value> {
- numbered(Numbering::Symbol, args)
-}
-
-fn numbered(numbering: Numbering, args: &mut Args) -> SourceResult<Value> {
- let n = args.expect::<usize>("non-negative integer")?;
- Ok(Value::Str(numbering.apply(n).into()))
-}
-
-/// Allows to convert a number into letters, roman numerals and symbols.
-#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
-pub enum Numbering {
- Arabic,
- Letter,
- Roman,
- Symbol,
-}
-
-impl Numbering {
- /// Apply the numbering to the given number.
- pub fn apply(self, mut n: usize) -> EcoString {
- match self {
- Self::Arabic => {
- format_eco!("{}", n)
- }
- Self::Letter => {
- if n == 0 {
- return '-'.into();
- }
-
- n -= 1;
-
- let mut letters = vec![];
- loop {
- letters.push(b'a' + (n % 26) as u8);
- n /= 26;
- if n == 0 {
- break;
- }
- }
-
- letters.reverse();
- String::from_utf8(letters).unwrap().into()
- }
- Self::Roman => {
- if n == 0 {
- return 'N'.into();
- }
-
- // Adapted from Yann Villessuzanne's roman.rs under the Unlicense, at
- // https://github.com/linfir/roman.rs/
- let mut fmt = EcoString::new();
- for &(name, value) in ROMANS {
- while n >= value {
- n -= value;
- fmt.push_str(name);
- }
- }
-
- fmt
- }
- Self::Symbol => {
- if n == 0 {
- return '-'.into();
- }
-
- let symbol = SYMBOLS[(n - 1) % SYMBOLS.len()];
- let amount = ((n - 1) / SYMBOLS.len()) + 1;
- std::iter::repeat(symbol).take(amount).collect()
- }
- }
- }
-}
-
-const ROMANS: &[(&str, usize)] = &[
- ("M̅", 1000000),
- ("D̅", 500000),
- ("C̅", 100000),
- ("L̅", 50000),
- ("X̅", 10000),
- ("V̅", 5000),
- ("I̅V̅", 4000),
- ("M", 1000),
- ("CM", 900),
- ("D", 500),
- ("CD", 400),
- ("C", 100),
- ("XC", 90),
- ("L", 50),
- ("XL", 40),
- ("X", 10),
- ("IX", 9),
- ("V", 5),
- ("IV", 4),
- ("I", 1),
-];
-
-const SYMBOLS: &[char] = &['*', '†', '‡', '§', '‖', '¶'];