From 9362c279de362eac1b7ec76834dd76a0235c5dd2 Mon Sep 17 00:00:00 2001 From: Laurenz Date: Wed, 27 Jul 2022 00:09:15 +0200 Subject: [PATCH] CSV reading --- Cargo.lock | 51 ++++++++++++++++++++++++++++++++++-- Cargo.toml | 1 + src/lib.rs | 3 +++ src/library/mod.rs | 1 + src/library/prelude.rs | 5 +++- src/library/utility/data.rs | 30 +++++++++++++++++++++ src/library/utility/mod.rs | 2 ++ tests/ref/utility/csv.png | Bin 0 -> 8603 bytes tests/res/bad.csv | 4 +++ tests/res/zoo.csv | 4 +++ tests/typ/utility/csv.typ | 15 +++++++++++ 11 files changed, 113 insertions(+), 3 deletions(-) create mode 100644 src/library/utility/data.rs create mode 100644 tests/ref/utility/csv.png create mode 100644 tests/res/bad.csv create mode 100644 tests/res/zoo.csv create mode 100644 tests/typ/utility/csv.typ diff --git a/Cargo.lock b/Cargo.lock index d9d959d3a..9365ce7ca 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -83,6 +83,18 @@ version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" +[[package]] +name = "bstr" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba3569f383e8f1598449f1a423e72e99569137b47740b1da11ef19af3d5c3223" +dependencies = [ + "lazy_static", + "memchr", + "regex-automata", + "serde", +] + [[package]] name = "bytemuck" version = "1.9.1" @@ -126,6 +138,28 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "csv" +version = "1.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22813a6dc45b335f9bade10bf7271dc477e81113e89eb251a0bc2a8a81c536e1" +dependencies = [ + "bstr", + "csv-core", + "itoa 0.4.8", + "ryu", + "serde", +] + +[[package]] +name = "csv-core" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b2466559f260f48ad25fe6317b3c8dac77b5bdb5763ac7d9d6103530663bc90" +dependencies = [ + "memchr", +] + [[package]] name = "data-url" version = "0.1.1" @@ -272,6 +306,12 @@ dependencies = [ "either", ] +[[package]] +name = "itoa" +version = "0.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b71991ff56294aa922b450139ee08b3bfc70982c6b2c7562771375cf73542dd4" + [[package]] name = "itoa" version = "1.0.2" @@ -431,7 +471,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "249f9b33a3192626f2cd9f4b0cd66c1ec32d65968d58cf4d8239977feddddead" dependencies = [ "bitflags", - "itoa", + "itoa 1.0.2", "ryu", ] @@ -547,6 +587,12 @@ dependencies = [ "regex-syntax", ] +[[package]] +name = "regex-automata" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" + [[package]] name = "regex-syntax" version = "0.6.26" @@ -665,7 +711,7 @@ version = "1.0.81" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b7ce2b32a1aed03c558dc61a5cd328f15aff2dbc17daad8fb8af04d2100e15c" dependencies = [ - "itoa", + "itoa 1.0.2", "ryu", "serde", ] @@ -816,6 +862,7 @@ version = "0.1.0" dependencies = [ "bytemuck", "codespan-reporting", + "csv", "dirs", "flate2", "fxhash", diff --git a/Cargo.toml b/Cargo.toml index db9fb5c7e..998177163 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -41,6 +41,7 @@ usvg = { version = "0.22", default-features = false } # External implementation of user-facing features syntect = { version = "5", default-features = false, features = ["default-syntaxes", "regex-fancy"] } rex = { git = "https://github.com/laurmaedje/ReX" } +csv = "1" # PDF export miniz_oxide = "0.5" diff --git a/src/lib.rs b/src/lib.rs index 1a39a14d4..72c7fb51e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -79,6 +79,8 @@ pub fn typeset(ctx: &mut Context, id: SourceId) -> TypResult> { /// The core context which holds the configuration and stores. pub struct Context { + /// The loader for fonts and files. + pub loader: Arc, /// Stores loaded source files. pub sources: SourceStore, /// Stores parsed font faces. @@ -97,6 +99,7 @@ impl Context { /// Create a new context. pub fn new(loader: Arc, config: Config) -> Self { Self { + loader: Arc::clone(&loader), sources: SourceStore::new(Arc::clone(&loader)), fonts: FontStore::new(Arc::clone(&loader)), images: ImageStore::new(loader), diff --git a/src/library/mod.rs b/src/library/mod.rs index d78e38caa..bd34590a4 100644 --- a/src/library/mod.rs +++ b/src/library/mod.rs @@ -97,6 +97,7 @@ pub fn new() -> Scope { std.def_fn("roman", utility::roman); std.def_fn("symbol", utility::symbol); std.def_fn("lorem", utility::lorem); + std.def_fn("csv", utility::csv); // Predefined colors. std.define("black", Color::BLACK); diff --git a/src/library/prelude.rs b/src/library/prelude.rs index c033b6316..f55447c38 100644 --- a/src/library/prelude.rs +++ b/src/library/prelude.rs @@ -2,12 +2,15 @@ pub use std::fmt::{self, Debug, Formatter}; pub use std::hash::Hash; +pub use std::io; pub use std::num::NonZeroUsize; pub use std::sync::Arc; pub use typst_macros::node; -pub use crate::diag::{with_alternative, At, Error, StrResult, TypError, TypResult}; +pub use crate::diag::{ + failed_to_load, with_alternative, At, Error, StrResult, TypError, TypResult, +}; pub use crate::eval::{ Arg, Args, Array, Cast, Dict, Dynamic, Func, Machine, Node, RawAlign, RawLength, RawStroke, Scope, Smart, Value, diff --git a/src/library/utility/data.rs b/src/library/utility/data.rs new file mode 100644 index 000000000..0f9e6bf09 --- /dev/null +++ b/src/library/utility/data.rs @@ -0,0 +1,30 @@ +use crate::library::prelude::*; + +/// Read structured data from a CSV file. +pub fn csv(vm: &mut Machine, args: &mut Args) -> TypResult { + let Spanned { v: path, span } = + args.expect::>("path to csv file")?; + + let path = vm.locate(&path).at(span)?; + let try_load = || -> io::Result { + let data = vm.ctx.loader.load(&path)?; + + 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() { + vec.push(Value::Array( + result?.iter().map(|field| Value::Str(field.into())).collect(), + )) + } + + Ok(Value::Array(Array::from_vec(vec))) + }; + + try_load() + .map_err(|err| failed_to_load("csv file", &path, err)) + .at(span) +} diff --git a/src/library/utility/mod.rs b/src/library/utility/mod.rs index 10aa7c7a1..9c95e60cd 100644 --- a/src/library/utility/mod.rs +++ b/src/library/utility/mod.rs @@ -1,10 +1,12 @@ //! Computational utility functions. mod color; +mod data; mod math; mod string; pub use color::*; +pub use data::*; pub use math::*; pub use string::*; diff --git a/tests/ref/utility/csv.png b/tests/ref/utility/csv.png new file mode 100644 index 0000000000000000000000000000000000000000..69e0ae38d175250d950544e344d2c3f1ae85bee3 GIT binary patch literal 8603 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 literal 0 HcmV?d00001 diff --git a/tests/res/bad.csv b/tests/res/bad.csv new file mode 100644 index 000000000..2c2696e96 --- /dev/null +++ b/tests/res/bad.csv @@ -0,0 +1,4 @@ +A,B +1,2 +3,4,5 +6,7 diff --git a/tests/res/zoo.csv b/tests/res/zoo.csv new file mode 100644 index 000000000..42ff06c7d --- /dev/null +++ b/tests/res/zoo.csv @@ -0,0 +1,4 @@ +Name,Species,Weight,Length +Debby,Rhinoceros,1900kg,390cm +Fluffy,Tiger,115kg,310cm +Sleepy,Dolphin,150kg,180cm diff --git a/tests/typ/utility/csv.typ b/tests/typ/utility/csv.typ new file mode 100644 index 000000000..ab955ab07 --- /dev/null +++ b/tests/typ/utility/csv.typ @@ -0,0 +1,15 @@ +// Test reading structured CSV data. + +--- +#set page(width: auto) +#let data = csv("/res/zoo.csv") +#let cells = data(0).map(strong) + data.slice(1).flatten() +#table(columns: data(0).len(), ..cells) + +--- +// Error: 6-16 file not found (searched at typ/utility/nope.csv) +#csv("nope.csv") + +--- +// Error: 6-20 failed to load csv file (CSV error: record 2 (line: 3, byte: 8): found record with 3 fields, but the previous record has 2 fields) +#csv("/res/bad.csv")