extendr_api/wrapper/
dataframe.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
//! This provides an abstraction for R's `data.frame`-constructor in Rust.
//! For a given `struct` say `CustomRow`, one may implement or derive [`IntoDataFrameRow`],
//! thus being able to convert `Vec<CustomRow>` to an instance of `Dataframe<CustomRow>`,
//! see [`Dataframe`].
//!
//!
//! [`IntoDataFrameRow`]: ::extendr_macros::IntoDataFrameRow
//!
use super::*;

/// A trait to convert a collection of `IntoDataFrameRow` into
/// [`Dataframe`]. Typical usage involves using the derive-macro [`IntoDataFrameRow`]
/// on a struct, which would generate `impl IntoDataframe<T> for Vec<T>`.
///
/// [`IntoDataFrameRow`]: ::extendr_macros::IntoDataFrameRow
pub trait IntoDataFrameRow<T> {
    fn into_dataframe(self) -> Result<Dataframe<T>>;
}

/// A representation of a typed `data.frame`
///
/// A `data.frame` can be created from Rust by using the [`IntoDataFrameRow`] trait
/// which can be derived for a single `struct` that represents a single row.
/// The type of the row is captured by the marker `T`.
///
/// Note that at present, you can create a `Dataframe<T>` but you cannot extract
/// `T` from the object. `<T>` is purely a marker that indicates the struct that
/// was used to create its rows.
///
/// As a result, using `Dataframe<T>` as a function argument _will not_ perform
/// any type checking on the type.
#[derive(PartialEq, Clone)]
pub struct Dataframe<T> {
    pub(crate) robj: Robj,
    _marker: std::marker::PhantomData<T>,
}

impl<T> From<Dataframe<T>> for Robj {
    fn from(value: Dataframe<T>) -> Self {
        value.robj
    }
}

impl<T> std::convert::TryFrom<&Robj> for Dataframe<T> {
    type Error = Error;
    fn try_from(robj: &Robj) -> Result<Self> {
        // TODO: check type using derived trait.
        if !(robj.is_list() && robj.inherits("data.frame")) {
            return Err(Error::ExpectedDataframe(robj.clone()));
        }
        Ok(Dataframe {
            robj: robj.clone(),
            _marker: std::marker::PhantomData,
        })
    }
}

impl<T> std::convert::TryFrom<Robj> for Dataframe<T> {
    type Error = Error;
    fn try_from(robj: Robj) -> Result<Self> {
        (&robj).try_into()
    }
}

impl<T> Dataframe<T> {
    /// Use `#[derive(IntoDataFrameRow)]` to use this.
    pub fn try_from_values<I: IntoDataFrameRow<T>>(iter: I) -> Result<Self> {
        iter.into_dataframe()
    }
}

impl<T> Attributes for Dataframe<T> {}

impl<T> std::fmt::Debug for Dataframe<T>
where
    T: std::fmt::Debug,
{
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(
            f,
            "dataframe!({})",
            self.as_list()
                .unwrap()
                .iter()
                .map(|(k, v)| if !k.is_empty() {
                    format!("{}={:?}", k, v)
                } else {
                    format!("{:?}", v)
                })
                .collect::<Vec<_>>()
                .join(", ")
        )
    }
}

impl<T> From<Option<Dataframe<T>>> for Robj {
    fn from(value: Option<Dataframe<T>>) -> Self {
        match value {
            None => nil_value(),
            Some(value) => value.into(),
        }
    }
}