summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLaurenz <laurmaedje@gmail.com>2022-07-27 00:09:15 +0200
committerLaurenz <laurmaedje@gmail.com>2022-07-27 00:09:15 +0200
commit9362c279de362eac1b7ec76834dd76a0235c5dd2 (patch)
treefdcec0de57deac0f94c2b4521ff09e94b1c785ee
parentfc574b39454aec77cf2c33270566225917c7c823 (diff)
CSV reading
-rw-r--r--Cargo.lock51
-rw-r--r--Cargo.toml1
-rw-r--r--src/lib.rs3
-rw-r--r--src/library/mod.rs1
-rw-r--r--src/library/prelude.rs5
-rw-r--r--src/library/utility/data.rs30
-rw-r--r--src/library/utility/mod.rs2
-rw-r--r--tests/ref/utility/csv.pngbin0 -> 8603 bytes
-rw-r--r--tests/res/bad.csv4
-rw-r--r--tests/res/zoo.csv4
-rw-r--r--tests/typ/utility/csv.typ15
11 files changed, 113 insertions, 3 deletions
diff --git a/Cargo.lock b/Cargo.lock
index d9d959d3..9365ce7c 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -84,6 +84,18 @@ 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"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -127,6 +139,28 @@ dependencies = [
]
[[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"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -274,6 +308,12 @@ dependencies = [
[[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"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "112c678d4050afce233f4f2852bb2eb519230b3cf12f33585275537d7e41578d"
@@ -431,7 +471,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "249f9b33a3192626f2cd9f4b0cd66c1ec32d65968d58cf4d8239977feddddead"
dependencies = [
"bitflags",
- "itoa",
+ "itoa 1.0.2",
"ryu",
]
@@ -548,6 +588,12 @@ dependencies = [
]
[[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"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -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 db9fb5c7..99817716 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 1a39a14d..72c7fb51 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -79,6 +79,8 @@ pub fn typeset(ctx: &mut Context, id: SourceId) -> TypResult<Vec<Frame>> {
/// The core context which holds the configuration and stores.
pub struct Context {
+ /// The loader for fonts and files.
+ pub loader: Arc<dyn Loader>,
/// 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<dyn Loader>, 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 d78e38ca..bd34590a 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 c033b631..f55447c3 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 00000000..0f9e6bf0
--- /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<Value> {
+ let Spanned { v: path, span } =
+ args.expect::<Spanned<EcoString>>("path to csv file")?;
+
+ let path = vm.locate(&path).at(span)?;
+ let try_load = || -> io::Result<Value> {
+ 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 10aa7c7a..9c95e60c 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 00000000..69e0ae38
--- /dev/null
+++ b/tests/ref/utility/csv.png
Binary files differ
diff --git a/tests/res/bad.csv b/tests/res/bad.csv
new file mode 100644
index 00000000..2c2696e9
--- /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 00000000..42ff06c7
--- /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 00000000..ab955ab0
--- /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")