graphix_package_xls/
lib.rs1#![doc(
2 html_logo_url = "https://graphix-lang.github.io/graphix/graphix-icon.svg",
3 html_favicon_url = "https://graphix-lang.github.io/graphix/graphix-icon.svg"
4)]
5use arcstr::ArcStr;
6use bytes::Bytes;
7use calamine::{open_workbook_auto_from_rs, Data, Reader};
8use graphix_compiler::errf;
9use graphix_package_core::{CachedArgsAsync, CachedVals, EvalCachedAsync};
10use graphix_package_sys::{get_stream, StreamKind};
11use netidx_value::{ValArray, Value};
12use poolshark::local::LPooled;
13use std::io::Cursor;
14use std::sync::Arc;
15use tokio::{io::AsyncReadExt, sync::Mutex};
16use triomphe::Arc as TArc;
17
18fn data_to_value(cell: &Data) -> Value {
21 match cell {
22 Data::Int(i) => Value::I64(*i),
23 Data::Float(f) => Value::F64(*f),
24 Data::String(s) => Value::String(ArcStr::from(s.as_str())),
25 Data::Bool(b) => Value::Bool(*b),
26 Data::DateTime(edt) => match edt.as_datetime() {
27 Some(ndt) => Value::DateTime(TArc::new(ndt.and_utc())),
28 None => Value::F64(edt.as_f64()),
29 },
30 Data::DateTimeIso(s) => match chrono::DateTime::parse_from_rfc3339(s) {
31 Ok(dt) => Value::DateTime(TArc::new(dt.with_timezone(&chrono::Utc))),
32 Err(_) => match chrono::NaiveDateTime::parse_from_str(s, "%Y-%m-%dT%H:%M:%S")
33 {
34 Ok(ndt) => Value::DateTime(TArc::new(ndt.and_utc())),
35 Err(_) => Value::String(ArcStr::from(s.as_str())),
36 },
37 },
38 Data::DurationIso(s) => Value::String(ArcStr::from(s.as_str())),
39 Data::Empty => Value::Null,
40 Data::Error(e) => Value::String(ArcStr::from(format!("{e:?}").as_str())),
41 }
42}
43
44fn parse_sheets<RS: std::io::Read + std::io::Seek + Clone>(rs: RS) -> Value {
47 let wb = match open_workbook_auto_from_rs(rs) {
48 Ok(wb) => wb,
49 Err(e) => return errf!("XlsErr", "{e}"),
50 };
51 let names = wb.sheet_names();
52 let mut vals: LPooled<Vec<Value>> =
53 names.iter().map(|n| Value::String(ArcStr::from(n.as_str()))).collect();
54 Value::Array(ValArray::from_iter_exact(vals.drain(..)))
55}
56
57fn parse_sheet<RS: std::io::Read + std::io::Seek + Clone>(rs: RS, sheet: &str) -> Value {
58 let mut wb = match open_workbook_auto_from_rs(rs) {
59 Ok(wb) => wb,
60 Err(e) => return errf!("XlsErr", "{e}"),
61 };
62 let range = match wb.worksheet_range(sheet) {
63 Ok(r) => r,
64 Err(e) => return errf!("XlsErr", "{e}"),
65 };
66 let mut rows: LPooled<Vec<Value>> = LPooled::take();
67 for row in range.rows() {
68 let mut cells: LPooled<Vec<Value>> = row.iter().map(data_to_value).collect();
69 rows.push(Value::Array(ValArray::from_iter_exact(cells.drain(..))));
70 }
71 Value::Array(ValArray::from_iter_exact(rows.drain(..)))
72}
73
74#[derive(Debug)]
77enum ReadInput {
78 Bytes(Bytes),
79 Stream(Arc<Mutex<Option<StreamKind>>>),
80}
81
82#[derive(Debug, Default)]
85struct XlsSheetsEv;
86
87impl EvalCachedAsync for XlsSheetsEv {
88 const NAME: &str = "xls_sheets";
89 const NEEDS_CALLSITE: bool = false;
90 type Args = ReadInput;
91
92 fn prepare_args(&mut self, cached: &CachedVals) -> Option<Self::Args> {
93 let v = cached.0.first()?.as_ref()?;
94 match v {
95 Value::Bytes(b) => Some(ReadInput::Bytes((**b).clone())),
96 Value::Abstract(_) => Some(ReadInput::Stream(get_stream(cached, 0)?)),
97 _ => None,
98 }
99 }
100
101 fn eval(input: Self::Args) -> impl Future<Output = Value> + Send {
102 async move {
103 match input {
104 ReadInput::Bytes(b) => {
105 let cursor = Cursor::new(b);
106 parse_sheets(cursor)
107 }
108 ReadInput::Stream(stream) => {
109 let mut guard = stream.lock().await;
110 let s = match guard.as_mut() {
111 Some(s) => s,
112 None => return errf!("IOErr", "stream unavailable"),
113 };
114 let mut buf = Vec::new();
115 if let Err(e) = s.read_to_end(&mut buf).await {
116 return errf!("IOErr", "read failed: {e}");
117 }
118 parse_sheets(Cursor::new(buf))
119 }
120 }
121 }
122 }
123}
124
125type XlsSheets = CachedArgsAsync<XlsSheetsEv>;
126
127#[derive(Debug, Default)]
130struct XlsReadEv;
131
132impl EvalCachedAsync for XlsReadEv {
133 const NAME: &str = "xls_read";
134 const NEEDS_CALLSITE: bool = false;
135 type Args = (ReadInput, ArcStr);
136
137 fn prepare_args(&mut self, cached: &CachedVals) -> Option<Self::Args> {
138 let v = cached.0.first()?.as_ref()?;
139 let input = match v {
140 Value::Bytes(b) => ReadInput::Bytes((**b).clone()),
141 Value::Abstract(_) => ReadInput::Stream(get_stream(cached, 0)?),
142 _ => return None,
143 };
144 let sheet = cached.get::<ArcStr>(1)?;
145 Some((input, sheet))
146 }
147
148 fn eval((input, sheet): Self::Args) -> impl Future<Output = Value> + Send {
149 async move {
150 match input {
151 ReadInput::Bytes(b) => {
152 let cursor = Cursor::new(b);
153 parse_sheet(cursor, &sheet)
154 }
155 ReadInput::Stream(stream) => {
156 let mut guard = stream.lock().await;
157 let s = match guard.as_mut() {
158 Some(s) => s,
159 None => return errf!("IOErr", "stream unavailable"),
160 };
161 let mut buf = Vec::new();
162 if let Err(e) = s.read_to_end(&mut buf).await {
163 return errf!("IOErr", "read failed: {e}");
164 }
165 parse_sheet(Cursor::new(buf), &sheet)
166 }
167 }
168 }
169 }
170}
171
172type XlsRead = CachedArgsAsync<XlsReadEv>;
173
174graphix_derive::defpackage! {
177 builtins => [
178 XlsSheets,
179 XlsRead,
180 ],
181}