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}