libsdbootconf/
config.rs

1//! Configuration of the installed systemd-boot environment.
2//!
3//! Create a Config by using either `Config::new()`, or `ConfigBuilder` to build a `Config` from
4//! scratch.
5//!
6//! # Examples
7//!
8//! ```
9//! use libsdbootconf::config::{Config, ConfigBuilder};
10//!
11//! let config = Config::new(Some("5.12.0-aosc-main"), Some(5u32));
12//! let built = ConfigBuilder::new()
13//!     .default("5.12.0-aosc-main")
14//!     .timeout(5u32)
15//!     .build();
16//!
17//! assert_eq!(config.to_string(), built.to_string());
18//! ```
19
20use std::{fs, ops::Not, path::Path, str::FromStr};
21
22use crate::{generate_builder_method, Entry, LibSDBootConfError};
23
24/// A systemd-boot loader configuration.
25#[derive(Default, Debug, PartialEq)]
26pub struct Config {
27    /// Pattern to select the default entry in the list of entries.
28    pub default: Option<String>,
29    /// Timeout in seconds for how long to show the menu.
30    pub timeout: Option<u32>,
31}
32
33impl FromStr for Config {
34    type Err = LibSDBootConfError;
35
36    fn from_str(s: &str) -> Result<Self, Self::Err> {
37        let mut config = Self::default();
38        let lines = s.lines();
39
40        for line in lines {
41            if line.starts_with('#') || line.is_empty() {
42                continue;
43            }
44
45            let mut parts = line.splitn(2, ' ');
46            let key = parts.next().ok_or(LibSDBootConfError::ConfigParseError)?;
47            let value = parts.next().ok_or(LibSDBootConfError::ConfigParseError)?;
48
49            match key {
50                "default" => config.default = Some(value.to_string()),
51                "timeout" => config.timeout = Some(value.parse().unwrap_or_default()),
52                _ => continue,
53            }
54        }
55
56        Ok(config)
57    }
58}
59
60impl ToString for Config {
61    fn to_string(&self) -> String {
62        let mut buffer = String::new();
63
64        if let Some(default) = &self.default {
65            buffer.push_str(&format!("default {}\n", default));
66        }
67
68        if let Some(timeout) = &self.timeout {
69            buffer.push_str(&format!("timeout {}\n", timeout));
70        }
71
72        buffer
73    }
74}
75
76impl Config {
77    /// Create a new `Config`.
78    ///
79    /// # Examples
80    ///
81    /// ```
82    /// use libsdbootconf::config::Config;
83    ///
84    /// let config = Config::new(Some("5.12.0-aosc-main"), Some(5u32));
85    ///
86    /// assert_eq!(config.default, Some("5.12.0-aosc-main".to_owned()));
87    /// assert_eq!(config.timeout, Some(5u32));
88    /// ```
89    pub fn new<S, U>(default: Option<S>, timeout: Option<U>) -> Config
90    where
91        S: Into<String>,
92        U: Into<u32>,
93    {
94        Config {
95            default: default.map(|s| s.into()),
96            timeout: timeout.map(|u| u.into()),
97        }
98    }
99
100    /// Load an existing config file.
101    ///
102    /// # Examples
103    ///
104    /// ```no_run
105    /// use libsdbootconf::config::Config;
106    ///
107    /// let config = Config::load("/path/to/config").unwrap();
108    /// ```
109    pub fn load<P: AsRef<Path>>(path: P) -> Result<Config, LibSDBootConfError> {
110        Config::from_str(&fs::read_to_string(path.as_ref())?)
111    }
112
113    /// Save the config to a file.
114    ///
115    /// # Examples
116    ///
117    /// ```no_run
118    /// use libsdbootconf::config::Config;
119    ///
120    /// let config = Config::new(Some("5.12.0-aosc-main"), Some(5u32));
121    /// config.write("/path/to/config").unwrap();
122    /// ```
123    pub fn write<P: AsRef<Path>>(&self, path: P) -> Result<(), LibSDBootConfError> {
124        fs::write(path.as_ref(), self.to_string())?;
125
126        Ok(())
127    }
128
129    /// Set an Entry as the default boot entry.
130    ///
131    /// # Examples
132    ///
133    /// ```
134    /// use libsdbootconf::{Entry, Config};
135    ///
136    /// let mut config = Config::default();
137    /// let entry = Entry::new("5.12.0-aosc-main", Vec::new());
138    ///
139    /// config.set_default(&entry);
140    ///
141    /// assert_eq!(config.default, Some("5.12.0-aosc-main.conf".to_owned()));
142    /// ```
143    pub fn set_default(&mut self, default: &Entry) {
144        self.default = Some(
145            default.id.clone()
146                + default
147                    .id
148                    .ends_with(".conf")
149                    .not()
150                    .then_some(".conf")
151                    .unwrap_or_default(),
152        );
153    }
154
155    /// Try to load the default entry as an Entry object.
156    ///
157    /// Returns `None` if the config does not contain a `default` field.
158    ///
159    /// # Examples
160    ///
161    /// ```no_run
162    /// use libsdbootconf::{Config, Entry};
163    ///
164    /// let config = Config::new(Some("5.12.0-aosc-main.conf"), Some(5u32));
165    ///
166    /// assert_ne!(None, config.default_entry("/efi/loader/entries/").unwrap())
167    /// ```
168    pub fn default_entry<P: AsRef<Path>>(
169        &self,
170        directory: P,
171    ) -> Result<Option<Entry>, LibSDBootConfError> {
172        self.default
173            .as_ref()
174            .map(|default| Entry::load(directory.as_ref().join(default)))
175            .transpose()
176    }
177}
178
179/// Builder for `Config`.
180#[derive(Default, Debug)]
181pub struct ConfigBuilder {
182    inner: Config,
183}
184
185impl ConfigBuilder {
186    /// Create an empty `ConfigBuilder`.
187    pub fn new() -> Self {
188        Self {
189            inner: Config::default(),
190        }
191    }
192
193    generate_builder_method!(
194        /// Set the default entry with a `String`.
195        option INNER(inner) default(S: String)
196    );
197    generate_builder_method!(
198        /// Set the timeout.
199        option INNER(inner) timeout(U: u32)
200    );
201
202    /// Set the default entry with an `Entry`.
203    pub fn default_entry(mut self, entry: &Entry) -> Self {
204        self.inner.set_default(entry);
205
206        self
207    }
208
209    /// Build the `Config`.
210    pub fn build(self) -> Config {
211        self.inner
212    }
213}