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
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
//! Easily import Glade-generated UI files into Rust code.
//!
//! ```
//! use gtk::prelude::*;
//! use gladis::Gladis;
//!
//! const GLADE_SRC: &str = r#"
//! <?xml version="1.0" encoding="UTF-8"?>
//! <!-- Generated with glade 3.22.2 -->
//! <interface>
//!   <requires lib="gtk+" version="3.20"/>
//!   <object class="GtkApplicationWindow" id="window">
//!     <property name="can_focus">False</property>
//!     <child type="titlebar">
//!       <placeholder/>
//!     </child>
//!     <child>
//!       <object class="GtkLabel" id="label">
//!         <property name="visible">True</property>
//!         <property name="can_focus">False</property>
//!         <property name="label" translatable="yes">label</property>
//!       </object>
//!     </child>
//!   </object>
//! </interface>"#;
//!
//! #[derive(Gladis, Clone)]
//! pub struct Window {
//!     pub window: gtk::ApplicationWindow,
//!     pub label: gtk::Label,
//! }
//!
//! gtk::init().unwrap();
//! let _ui = Window::from_string(GLADE_SRC).unwrap();
//! ```

use std::{error::Error, fmt::Display};

#[derive(Debug, Clone)]
pub struct NotFoundError {
    pub identifier: String,
    pub typ: String,
}

impl Display for NotFoundError {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(
            f,
            "identifier {} of type {} was not found",
            self.identifier, self.typ
        )
    }
}

impl Error for NotFoundError {}

#[derive(Debug, Clone)]
pub enum GladisError {
    NotFound(NotFoundError),
}

impl Display for GladisError {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            GladisError::NotFound(e) => write!(f, "not found error: {}", e),
        }
    }
}

impl Error for GladisError {
    fn source(&self) -> Option<&(dyn Error + 'static)> {
        match self {
            GladisError::NotFound(e) => Some(e),
        }
    }
}

impl GladisError {
    pub fn not_found<T>(identifier: T, typ: T) -> Self
    where
        T: ToString,
    {
        let identifier = identifier.to_string();
        let typ = typ.to_string();
        GladisError::NotFound(NotFoundError { identifier, typ })
    }
}

pub type Result<T> = std::result::Result<T, GladisError>;

pub trait Gladis {
    //! A trait to load a struct from a builder.
    //!
    //! # Automatic implementation
    //!
    //! This trait wakes little sense alone, but truly show its power when used
    //! with the [gladis_proc_macro](https://docs.rs/gladis_proc_macro) crate
    //! and its `#[derive(Gladis)]` macro.
    //!
    //! ```
    //! use gtk::prelude::*;
    //! use gladis::Gladis;
    //!
    //! #[derive(Gladis, Clone)]
    //! pub struct Window {
    //!     pub window: gtk::ApplicationWindow,
    //!     pub label: gtk::Label,
    //! }
    //! ```
    //!
    //! # Manual implementation
    //!
    //! Below is an example of manual implementation of the trait.
    //!
    //! ```
    //! use gtk::prelude::*;
    //! use gladis::{Gladis, Result, GladisError};
    //!
    //! pub struct Window {
    //!     pub window: gtk::ApplicationWindow,
    //!     pub label: gtk::Label,
    //! }
    //!
    //! impl Gladis for Window {
    //!     fn from_builder(builder: gtk::Builder) -> Result<Self> {
    //!         let window: gtk::ApplicationWindow = builder
    //!             .object("window")
    //!             .ok_or(GladisError::not_found("window", "gtk::ApplicationWindow"))?;
    //!
    //!         let label: gtk::Label = builder
    //!             .object("label")
    //!             .ok_or(GladisError::not_found("label", "gtk::Label"))?;
    //!
    //!         Ok(Self { window, label })
    //!     }
    //! }
    //! ```

    /// Populate struct from a builder.
    ///
    /// This method should not be called directly but is used as a common
    /// function for the `from_string` and `from_resource` functions to
    /// share the same code.
    fn from_builder(builder: gtk::Builder) -> Result<Self>
    where
        Self: std::marker::Sized;

    /// Populate struct from a Glade document.
    fn from_string(src: &str) -> Result<Self>
    where
        Self: std::marker::Sized,
    {
        let builder = gtk::Builder::from_string(src);
        Gladis::from_builder(builder)
    }

    /// Populate struct from a Glade document as a resource.
    fn from_resource(resource_path: &str) -> Result<Self>
    where
        Self: std::marker::Sized,
    {
        let builder = gtk::Builder::from_resource(resource_path);
        Gladis::from_builder(builder)
    }
}

// Re-export #[derive(Gladis)].
#[cfg(feature = "derive")]
#[doc(hidden)]
pub use gladis_proc_macro::Gladis;

#[cfg(test)]
mod tests {
    use crate::{GladisError, NotFoundError};

    #[test]
    fn fmt_not_found_error() {
        let err = NotFoundError {
            identifier: "foo".to_string(),
            typ: "bar".to_string(),
        };
        assert_eq!(err.to_string(), "identifier foo of type bar was not found");
    }

    #[test]
    fn fmt_gladis_error() {
        let err = GladisError::NotFound(NotFoundError {
            identifier: "foo".to_string(),
            typ: "bar".to_string(),
        });
        assert_eq!(
            err.to_string(),
            "not found error: identifier foo of type bar was not found"
        );
    }
}