Skip to main content

libhaystack/haystack/encoding/zinc/
encode.rs

1// Copyright (C) 2020 - 2022, J2 Innovations
2
3//! Implement Zinc encoding
4
5use crate::haystack::val::{
6    Bool, Column, Coord, Date, DateTime, Dict, GRID_FORMAT_VERSION, Grid, List, Marker, Na, Number,
7    Ref, Remove, Str, Symbol, Time, Uri, Value, XStr,
8};
9use chrono::SecondsFormat;
10use std::fmt::Display;
11
12/// Zinc encoding trait implemented by scalar and collection types
13pub trait ToZinc {
14    fn to_zinc<W: std::io::Write>(&self, writer: &mut W) -> Result<()>;
15
16    /// Encodes this Haystack type as a Zinc string
17    ///
18    /// # Example
19    /// ```
20    /// use libhaystack::val::*;
21    /// use libhaystack::encoding::zinc::encode::*;
22    /// use libhaystack::units::get_unit_or_default;
23    /// let val = Number::make_with_unit(100.0, get_unit_or_default("s"));
24    /// assert_eq!(val.to_zinc_string(), Ok("100sec".to_string()));
25    /// ```
26    fn to_zinc_string(&self) -> Result<String> {
27        let mut output = Vec::new();
28        self.to_zinc(&mut output)?;
29        Ok(String::from_utf8(output)?)
30    }
31}
32
33/// Function that take a haystack  [Value](crate::val::Value)
34/// and returns its Zinc string encoding
35///
36/// # Example
37/// ```
38/// use libhaystack::val::*;
39/// use libhaystack::encoding::zinc::encode::*;
40/// let val = Value::make_true();
41/// assert_eq!(to_zinc_string(&val), Ok("T".to_string()));
42///
43pub fn to_zinc_string(value: &Value) -> Result<String> {
44    let mut output = Vec::new();
45    value.to_zinc(&mut output)?;
46    Ok(String::from_utf8(output)?)
47}
48
49#[derive(PartialEq, PartialOrd, Clone, Debug)]
50enum InnerGrid {
51    Yes,
52    No,
53}
54
55/// Specialized trait for encoding inner Grids
56trait ZincEncode: ToZinc {
57    fn zinc_encode<W: std::io::Write>(&self, writer: &mut W, in_grid: InnerGrid) -> Result<()>;
58}
59
60pub type Result<T> = std::result::Result<T, Error>;
61
62#[derive(Clone, Debug, PartialEq, Eq)]
63pub enum Error {
64    Message(String),
65}
66
67impl Display for Error {
68    fn fmt(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
69        match self {
70            Error::Message(msg) => formatter.write_str(msg),
71        }
72    }
73}
74
75impl std::error::Error for Error {}
76
77impl From<std::fmt::Error> for Error {
78    fn from(_: std::fmt::Error) -> Self {
79        Error::from("Format error.")
80    }
81}
82
83impl From<std::io::Error> for Error {
84    fn from(_: std::io::Error) -> Self {
85        Error::from("IO error.")
86    }
87}
88
89impl From<&str> for Error {
90    fn from(msg: &str) -> Self {
91        Error::Message(String::from(msg))
92    }
93}
94
95impl From<std::string::FromUtf8Error> for Error {
96    fn from(_: std::string::FromUtf8Error) -> Self {
97        Error::from("Utf8 encoding error.")
98    }
99}
100
101fn write_str<W: std::io::Write>(writer: &mut W, s: &str) -> Result<()> {
102    let bytes = s.as_bytes();
103    writer.write_all(bytes)?;
104    Ok(())
105}
106
107impl ToZinc for Marker {
108    fn to_zinc<W: std::io::Write>(&self, writer: &mut W) -> Result<()> {
109        writer.write_all(b"M")?;
110        Ok(())
111    }
112}
113
114impl ToZinc for Remove {
115    fn to_zinc<W: std::io::Write>(&self, writer: &mut W) -> Result<()> {
116        writer.write_all(b"R")?;
117        Ok(())
118    }
119}
120
121impl ToZinc for Na {
122    fn to_zinc<W: std::io::Write>(&self, writer: &mut W) -> Result<()> {
123        writer.write_all(b"NA")?;
124        Ok(())
125    }
126}
127
128impl ToZinc for Bool {
129    fn to_zinc<W: std::io::Write>(&self, writer: &mut W) -> Result<()> {
130        if self.value {
131            writer.write_all(b"T")?
132        } else {
133            writer.write_all(b"F")?
134        }
135        Ok(())
136    }
137}
138
139impl ToZinc for Number {
140    fn to_zinc<W: std::io::Write>(&self, writer: &mut W) -> Result<()> {
141        if self.value.is_nan() {
142            writer.write_all(b"NaN")?
143        } else if self.value.is_infinite() {
144            let sign = if self.value.is_sign_negative() {
145                "-"
146            } else {
147                ""
148            };
149            writer.write_fmt(format_args!("{sign}INF"))?
150        } else if let Some(unit) = &self.unit {
151            writer.write_fmt(format_args!("{value}{unit}", value = self.value))?
152        } else {
153            writer.write_fmt(format_args!("{}", self.value))?
154        }
155        Ok(())
156    }
157}
158
159impl ToZinc for Date {
160    fn to_zinc<W: std::io::Write>(&self, writer: &mut W) -> Result<()> {
161        write_str(writer, &self.to_string())?;
162        Ok(())
163    }
164}
165
166impl ToZinc for Time {
167    fn to_zinc<W: std::io::Write>(&self, writer: &mut W) -> Result<()> {
168        write_str(writer, &self.to_string())?;
169        Ok(())
170    }
171}
172
173impl ToZinc for DateTime {
174    fn to_zinc<W: std::io::Write>(&self, writer: &mut W) -> Result<()> {
175        if self.is_utc() {
176            write_str(writer, &self.to_rfc3339_opts(SecondsFormat::AutoSi, true))?;
177        } else {
178            writer.write_fmt(format_args!(
179                "{} {}",
180                &self.to_rfc3339_opts(SecondsFormat::AutoSi, true),
181                &self.timezone_short_name()
182            ))?
183        }
184        Ok(())
185    }
186}
187
188impl ToZinc for Str {
189    fn to_zinc<W: std::io::Write>(&self, writer: &mut W) -> Result<()> {
190        writer.write_all(b"\"")?;
191        let mut buf = [0; 4];
192        for c in self.value.chars() {
193            if c < ' ' || c == '"' || c == '\\' {
194                match c {
195                    '"' => writer.write_all(br#"\""#)?,
196                    '\t' => writer.write_all(br"\t")?,
197                    '\r' => writer.write_all(br"\r")?,
198                    '\n' => writer.write_all(br"\n")?,
199                    '\\' => writer.write_all(br"\\")?,
200                    _ => writer.write_fmt(format_args!("\\u{:04x}", c as u32))?,
201                }
202            } else if c == '$' {
203                writer.write_all(br"\$")?
204            } else {
205                let chunk = c.encode_utf8(&mut buf);
206                writer.write_fmt(format_args!("{chunk}"))?
207            }
208        }
209        writer.write_all(b"\"")?;
210        Ok(())
211    }
212}
213
214impl ToZinc for Ref {
215    fn to_zinc<W: std::io::Write>(&self, writer: &mut W) -> Result<()> {
216        if let Some(dis) = &self.dis {
217            writer.write_fmt(format_args!("@{} \"{}\"", self.value, dis))?
218        } else {
219            writer.write_fmt(format_args!("@{}", self.value))?
220        }
221        Ok(())
222    }
223}
224
225impl ToZinc for Symbol {
226    fn to_zinc<W: std::io::Write>(&self, writer: &mut W) -> Result<()> {
227        writer.write_fmt(format_args!("^{}", self.value))?;
228        Ok(())
229    }
230}
231
232impl ToZinc for Uri {
233    fn to_zinc<W: std::io::Write>(&self, writer: &mut W) -> Result<()> {
234        writer.write_all(b"`")?;
235        for c in self.value.chars() {
236            if c < ' ' {
237                continue;
238            }
239            match c {
240                '`' => writer.write_all(br"\`")?,
241                '\\' => writer.write_all(br"\\")?,
242                '\x20'..='\x7e' => writer.write_all(&[c as u8])?,
243                _ => writer.write_fmt(format_args!("\\u{:04x}", c as u32))?,
244            }
245        }
246        writer.write_all(b"`")?;
247        Ok(())
248    }
249}
250
251impl ToZinc for XStr {
252    fn to_zinc<W: std::io::Write>(&self, writer: &mut W) -> Result<()> {
253        writer.write_fmt(format_args!(
254            "{}{}(\"{}\")",
255            self.r#type[0..1].to_uppercase(),
256            &self.r#type[1..],
257            self.value
258        ))?;
259        Ok(())
260    }
261}
262
263impl ToZinc for Coord {
264    fn to_zinc<W: std::io::Write>(&self, writer: &mut W) -> Result<()> {
265        writer.write_fmt(format_args!("C({},{})", self.lat, self.long))?;
266        Ok(())
267    }
268}
269
270impl ToZinc for Grid {
271    fn to_zinc<W: std::io::Write>(&self, writer: &mut W) -> Result<()> {
272        self.zinc_encode(writer, InnerGrid::No)
273    }
274}
275
276impl ZincEncode for Grid {
277    fn zinc_encode<W: std::io::Write>(&self, writer: &mut W, in_grid: InnerGrid) -> Result<()> {
278        if in_grid == InnerGrid::Yes {
279            writer.write_all(b"<<\n")?;
280        }
281
282        writer.write_fmt(format_args!("ver:\"{GRID_FORMAT_VERSION}\"\n"))?;
283
284        // Grid meta
285        if let Some(meta) = &self.meta {
286            write_dict_tags(writer, meta, b" ")?;
287        }
288
289        if self.is_empty() {
290            // No rows to be written
291            writer.write_all(b"empty\n")?;
292        } else {
293            // Columns
294            for (i, col) in self.columns.iter().enumerate() {
295                col.to_zinc(writer)?;
296                if i < self.columns.len() - 1 {
297                    writer.write_all(b",")?;
298                }
299            }
300            writer.write_all(b"\n")?;
301            // Rows
302            for row in &self.rows {
303                // Tags
304                for (i, col) in self.columns.iter().enumerate() {
305                    if let Some(tag) = row.get(&col.name) {
306                        tag.zinc_encode(writer, InnerGrid::Yes)?;
307                    }
308                    if i < self.columns.len() - 1 {
309                        writer.write_all(b",")?;
310                    }
311                }
312                writer.write_all(b"\n")?;
313            }
314        }
315        if in_grid == InnerGrid::Yes {
316            writer.write_all(b">>")?;
317        } else {
318            writer.write_all(b"\n")?;
319        }
320        Ok(())
321    }
322}
323
324impl ToZinc for List {
325    fn to_zinc<W: std::io::Write>(&self, writer: &mut W) -> Result<()> {
326        writer.write_all(b"[")?;
327        for (i, el) in self.iter().enumerate() {
328            el.zinc_encode(writer, InnerGrid::Yes)?;
329            if i < self.len() - 1 {
330                writer.write_all(b",")?;
331            }
332        }
333        writer.write_all(b"]")?;
334        Ok(())
335    }
336}
337
338impl ToZinc for Dict {
339    fn to_zinc<W: std::io::Write>(&self, writer: &mut W) -> Result<()> {
340        writer.write_all(b"{")?;
341        write_dict_tags(writer, self, b",")?;
342        writer.write_all(b"}")?;
343        Ok(())
344    }
345}
346
347/// Implement the Zinc encoding for Haystack Value type
348impl ToZinc for Value {
349    fn to_zinc<W: std::io::Write>(&self, writer: &mut W) -> Result<()> {
350        self.zinc_encode(writer, InnerGrid::No)
351    }
352}
353
354/// Implements `ZincEncode` for the `Value` type
355/// This implementation deals with nested grids.
356impl ZincEncode for Value {
357    fn zinc_encode<W: std::io::Write>(&self, writer: &mut W, in_grid: InnerGrid) -> Result<()> {
358        match self {
359            Value::Null => writer.write_all(b"N")?,
360
361            Value::Remove => Remove.to_zinc(writer)?,
362
363            Value::Marker => Marker.to_zinc(writer)?,
364
365            Value::Bool(val) => val.to_zinc(writer)?,
366
367            Value::Na => Na.to_zinc(writer)?,
368
369            Value::Number(val) => val.to_zinc(writer)?,
370
371            Value::Str(val) => val.to_zinc(writer)?,
372
373            Value::Ref(val) => val.to_zinc(writer)?,
374
375            Value::Uri(val) => val.to_zinc(writer)?,
376
377            Value::Symbol(val) => val.to_zinc(writer)?,
378
379            Value::Date(val) => val.to_zinc(writer)?,
380
381            Value::Time(val) => val.to_zinc(writer)?,
382
383            Value::DateTime(val) => val.to_zinc(writer)?,
384
385            Value::Coord(val) => val.to_zinc(writer)?,
386
387            Value::XStr(val) => val.to_zinc(writer)?,
388
389            Value::List(val) => val.to_zinc(writer)?,
390
391            Value::Dict(val) => val.to_zinc(writer)?,
392
393            Value::Grid(val) => val.zinc_encode(writer, in_grid)?,
394        }
395        Ok(())
396    }
397}
398/// Serialize a Grid column
399impl ToZinc for Column {
400    fn to_zinc<W: std::io::Write>(&self, writer: &mut W) -> Result<()> {
401        write_str(writer, &self.name)?;
402        if let Some(meta) = &self.meta {
403            write_dict_tags(writer, meta, b" ")?;
404        }
405        Ok(())
406    }
407}
408
409fn write_dict_tags<W: std::io::Write>(
410    writer: &mut W,
411    dict: &Dict,
412    separator: &[u8; 1],
413) -> Result<()> {
414    for (pos, (k, v)) in dict.iter().enumerate() {
415        write_str(writer, k)?;
416        if !v.is_marker() {
417            writer.write_all(b":")?;
418            v.zinc_encode(writer, InnerGrid::Yes)?;
419        }
420        if pos < dict.len() - 1 {
421            writer.write_all(separator)?;
422        }
423    }
424    Ok(())
425}