1#![cfg_attr(not(feature = "std"), no_std)]
25#![cfg(not(feature = "std"))]
26extern crate alloc;
27
28#[cfg(not(feature = "std"))]
29use alloc::format;
30#[cfg(not(feature = "std"))]
31use alloc::string::String;
32#[cfg(not(feature = "std"))]
33use alloc::vec::Vec;
34
35#[cfg(not(feature = "std"))]
36use alloc::str::FromStr;
37#[cfg(feature = "std")]
38use std::str::FromStr;
39
40#[derive(Debug, PartialEq)]
42pub enum BLSValue {
43 Value(String),
45 ValueWithComment(String, String),
47}
48
49#[derive(Debug, PartialEq)]
52pub enum ValueSetPolicy {
53 ReplaceAll,
55 Append,
57 Prepend,
59 InsertAt(usize),
61}
62
63#[derive(Debug, PartialEq)]
64pub enum BLSKey {
65 Title,
66 Version,
67 MachineId,
68 SortKey,
69 Linux,
70 Efi,
71 Initrd,
72 Options,
73 Devicetree,
74 DevicetreeOverlay,
75 Architecture,
76 GrubHotkey,
77 GrubUsers,
78 GrubClass,
79 GrubArg,
80}
81
82impl FromStr for BLSKey {
83 type Err = String;
84
85 fn from_str(key: &str) -> Result<Self, Self::Err> {
86 match key {
87 "linux" => Ok(BLSKey::Linux),
88 "title" => Ok(BLSKey::Title),
89 "version" => Ok(BLSKey::Version),
90 "machine_id" => Ok(BLSKey::MachineId),
91 "sort_key" => Ok(BLSKey::SortKey),
92 "efi" => Ok(BLSKey::Efi),
93 "initrd" => Ok(BLSKey::Initrd),
94 "options" => Ok(BLSKey::Options),
95 "devicetree" => Ok(BLSKey::Devicetree),
96 "devicetree_overlay" => Ok(BLSKey::DevicetreeOverlay),
97 "architecture" => Ok(BLSKey::Architecture),
98 "grub_hotkey" => Ok(BLSKey::GrubHotkey),
99 "grub_users" => Ok(BLSKey::GrubUsers),
100 "grub_class" => Ok(BLSKey::GrubClass),
101 "grub_arg" => Ok(BLSKey::GrubArg),
102 _ => Err(format!("Invalid key {}", key)),
103 }
104 }
105}
106
107#[derive(Debug)]
109pub struct BLSEntry {
110 pub title: Option<BLSValue>,
111 pub version: Option<BLSValue>,
112 pub machine_id: Option<BLSValue>,
113 pub sort_key: Option<BLSValue>,
114 pub linux: BLSValue,
115 pub efi: Option<BLSValue>,
116 pub initrd: Vec<BLSValue>,
117 pub options: Vec<BLSValue>,
118 pub devicetree: Option<BLSValue>,
119 pub devicetree_overlay: Option<BLSValue>,
120 pub architecture: Option<BLSValue>,
121 pub grub_hotkey: Option<BLSValue>,
122 pub grub_users: Option<BLSValue>,
123 pub grub_class: Vec<BLSValue>,
124 pub grub_arg: Option<BLSValue>,
125 pub comments: Vec<String>,
127}
128
129impl BLSEntry {
130 pub fn new() -> BLSEntry {
132 BLSEntry {
133 title: None,
134 version: None,
135 machine_id: None,
136 sort_key: None,
137 linux: BLSValue::Value(String::new()),
138 efi: None,
139 initrd: Vec::new(),
140 options: Vec::new(),
141 devicetree: None,
142 devicetree_overlay: None,
143 architecture: None,
144 grub_hotkey: None,
145 grub_users: None,
146 grub_class: Vec::new(),
147 grub_arg: None,
148 comments: Vec::new(),
149 }
150 }
151
152 pub fn parse(buffer: &str) -> Result<BLSEntry, String> {
156 let mut entry = BLSEntry::new();
157 let mut has_linux = false;
158
159 for line in buffer.lines() {
160 let mut comment = None;
161 let line = if line.contains("#") {
163 let split: Vec<_> = line.splitn(2, "#").collect();
164 comment = Some(String::from(split[1]));
165 split[0]
166 } else {
167 line
168 };
169
170 if line.trim().contains(" ") {
172 let key_value: Vec<&str> = line.trim().splitn(2, " ").collect();
173
174 let key = BLSKey::from_str(key_value[0])?;
175 if key == BLSKey::Linux {
176 has_linux = true;
177 }
178 entry.set(
179 key,
180 String::from(key_value[1]),
181 comment,
182 ValueSetPolicy::Append,
183 );
184 } else {
185 match comment {
186 Some(comment) => {
187 entry.comments.push(comment);
188 }
189 None => {}
190 }
191 }
192 }
193
194 if has_linux {
195 Ok(entry)
196 } else {
197 Err(String::from("No 'linux' command found."))
198 }
199 }
200
201 pub fn render(&self) -> String {
203 let mut content = String::new();
204
205 fn render_value(content: &mut String, key: &str, value: &BLSValue) {
206 content.push_str(key);
207 content.push(' ');
208 match value {
209 BLSValue::Value(value) => content.push_str(&value),
210 BLSValue::ValueWithComment(value, comment) => {
211 content.push_str(&value);
212 content.push_str(" #");
213 content.push_str(&comment);
214 }
215 }
216 }
217
218 fn render_single_value(content: &mut String, key: &str, value: &Option<BLSValue>) {
219 if let Some(value) = value {
220 render_value(content, key, &value)
221 }
222 }
223
224 fn render_multiple_values(content: &mut String, key: &str, values: &Vec<BLSValue>) {
225 for val in values {
226 render_value(content, key, &val)
227 }
228 }
229
230 for comment in &self.comments {
232 content.push_str("#");
233 content.push_str(&comment)
234 }
235
236 render_value(&mut content, "linux", &self.linux);
238
239 render_single_value(&mut content, "title", &self.title);
241 render_single_value(&mut content, "version", &self.version);
242 render_single_value(&mut content, "machine-id", &self.machine_id);
243 render_single_value(&mut content, "sort-key", &self.sort_key);
244 render_single_value(&mut content, "efi", &self.efi);
245 render_single_value(&mut content, "devicetree", &self.devicetree);
246 render_single_value(&mut content, "devicetree-overlay", &self.devicetree_overlay);
247 render_single_value(&mut content, "architecture", &self.architecture);
248 render_single_value(&mut content, "grub_hotkey", &self.devicetree_overlay);
249 render_single_value(&mut content, "grub_users", &self.devicetree_overlay);
250 render_single_value(&mut content, "grub_arg", &self.devicetree_overlay);
251
252 render_multiple_values(&mut content, "initrd", &self.initrd);
254 render_multiple_values(&mut content, "options", &self.options);
255 render_multiple_values(&mut content, "grub_class", &self.grub_class);
256
257 content
258 }
259
260 pub fn set(
272 &mut self,
273 key: BLSKey,
274 value: String,
275 comment: Option<String>,
276 set_policy: ValueSetPolicy,
277 ) {
278 fn value_generator(value: String, comment: Option<String>) -> BLSValue {
279 match comment {
280 Some(comment) => BLSValue::ValueWithComment(value, comment),
281 None => BLSValue::Value(value),
282 }
283 }
284
285 fn push_value(values: &mut Vec<BLSValue>, val: BLSValue, policy: ValueSetPolicy) {
286 match policy {
287 ValueSetPolicy::Append => values.push(val),
288 ValueSetPolicy::InsertAt(i) => values.insert(i, val),
289 ValueSetPolicy::Prepend => values.insert(0, val),
290 ValueSetPolicy::ReplaceAll => {
291 values.clear();
292 values.push(val);
293 }
294 }
295 }
296
297 match key {
298 BLSKey::Title => self.title = Some(value_generator(value, comment)),
299 BLSKey::Version => self.version = Some(value_generator(value, comment)),
300 BLSKey::MachineId => self.machine_id = Some(value_generator(value, comment)),
301 BLSKey::SortKey => self.sort_key = Some(value_generator(value, comment)),
302 BLSKey::Linux => self.linux = value_generator(value, comment),
303 BLSKey::Efi => self.efi = Some(value_generator(value, comment)),
304 BLSKey::Devicetree => self.devicetree = Some(value_generator(value, comment)),
305 BLSKey::DevicetreeOverlay => {
306 self.devicetree_overlay = Some(value_generator(value, comment))
307 }
308 BLSKey::Architecture => self.architecture = Some(value_generator(value, comment)),
309 BLSKey::GrubHotkey => self.grub_hotkey = Some(value_generator(value, comment)),
310 BLSKey::GrubUsers => self.grub_users = Some(value_generator(value, comment)),
311 BLSKey::GrubArg => self.grub_arg = Some(value_generator(value, comment)),
312
313 BLSKey::Initrd => push_value(
314 &mut self.initrd,
315 value_generator(value, comment),
316 set_policy,
317 ),
318 BLSKey::Options => push_value(
319 &mut self.options,
320 value_generator(value, comment),
321 set_policy,
322 ),
323 BLSKey::GrubClass => push_value(
324 &mut self.grub_class,
325 value_generator(value, comment),
326 set_policy,
327 ),
328 }
329 }
330
331 pub fn clear(&mut self, key: BLSKey) {
336 match key {
337 BLSKey::Linux => self.linux = BLSValue::Value(String::from("")),
338 BLSKey::Title => self.title = None,
339 BLSKey::Version => self.version = None,
340 BLSKey::MachineId => self.machine_id = None,
341 BLSKey::SortKey => self.sort_key = None,
342 BLSKey::Efi => self.efi = None,
343 BLSKey::Devicetree => self.devicetree = None,
344 BLSKey::DevicetreeOverlay => self.devicetree_overlay = None,
345 BLSKey::Architecture => self.architecture = None,
346 BLSKey::GrubHotkey => self.grub_hotkey = None,
347 BLSKey::GrubUsers => self.grub_users = None,
348 BLSKey::GrubArg => self.grub_arg = None,
349
350 BLSKey::Initrd => self.initrd.clear(),
351 BLSKey::Options => self.options.clear(),
352 BLSKey::GrubClass => self.grub_class.clear(),
353 }
354 }
355}
356
357#[cfg(test)]
358mod bls_tests {
359 use super::String;
360
361 #[cfg(not(feature = "std"))]
362 use alloc::vec;
363
364 use super::BLSEntry;
365 use super::BLSKey;
366 use super::BLSValue;
367 use super::ValueSetPolicy;
368
369 #[test]
370 fn new_entry() {
371 let entry = BLSEntry::new();
372 match entry.linux {
373 BLSValue::Value(linux) => {
374 assert_eq!(linux, "");
375 }
376 _ => {
377 panic!("Invalid 'linux' value {:?}", entry.linux);
378 }
379 }
380 assert_eq!(entry.initrd.len(), 0);
381 }
382
383 #[test]
384 fn parse_entry() {
385 let entry_txt = "#Comment\n\
386 linux foobar-2.4\n\
387 options foo=bar #Another Comment";
388 let entry = BLSEntry::parse(entry_txt);
389
390 assert!(entry.is_ok());
391 let entry = entry.unwrap();
392 assert_eq!(entry.comments.len(), 1);
393 assert_eq!(entry.comments[0], "Comment");
394
395 if let BLSValue::Value(linux) = entry.linux {
396 assert_eq!(linux, "foobar-2.4");
397 }
398
399 assert_eq!(entry.options.len(), 1);
400 match &entry.options[0] {
401 BLSValue::ValueWithComment(option, comment) => {
402 assert_eq!(option, "foo=bar");
403 assert_eq!(comment, "Another Comment");
404 }
405 _ => {
406 panic!("Invalid 'options' value {:?}", entry.options[0])
407 }
408 }
409 }
410
411 #[test]
412 fn parse_errors() {
413 let entry_txt = "options foo=bar";
415 let entry = BLSEntry::parse(entry_txt);
416 assert!(entry.is_err());
417
418 let entry_txt = "linux asdasdasdas\n\
420 invalid_command foo=bar";
421 let entry = BLSEntry::parse(entry_txt);
422 assert!(entry.is_err());
423 }
424
425 #[test]
426 fn set_value_policies() {
427 let mut entry = BLSEntry::new();
429 let _ = entry.set(
430 BLSKey::Options,
431 String::from("foo"),
432 None,
433 ValueSetPolicy::Append,
434 );
435 let _ = entry.set(
436 BLSKey::Options,
437 String::from("bar"),
438 None,
439 ValueSetPolicy::Append,
440 );
441 let _ = entry.set(
442 BLSKey::Options,
443 String::from("baz"),
444 None,
445 ValueSetPolicy::Append,
446 );
447
448 assert_eq!(
449 entry.options,
450 vec![
451 BLSValue::Value(String::from("foo")),
452 BLSValue::Value(String::from("bar")),
453 BLSValue::Value(String::from("baz"))
454 ]
455 );
456
457 let _ = entry.set(
459 BLSKey::Options,
460 String::from("lol"),
461 None,
462 ValueSetPolicy::InsertAt(1),
463 );
464 assert_eq!(
465 entry.options,
466 vec![
467 BLSValue::Value(String::from("foo")),
468 BLSValue::Value(String::from("lol")),
469 BLSValue::Value(String::from("bar")),
470 BLSValue::Value(String::from("baz"))
471 ]
472 );
473
474 let _ = entry.set(
476 BLSKey::Options,
477 String::from("wtf"),
478 None,
479 ValueSetPolicy::ReplaceAll,
480 );
481 assert_eq!(entry.options, vec![BLSValue::Value(String::from("wtf"))]);
482
483 let _ = entry.set(
485 BLSKey::Options,
486 String::from("uwu"),
487 None,
488 ValueSetPolicy::Prepend,
489 );
490 assert_eq!(
491 entry.options,
492 vec![
493 BLSValue::Value(String::from("uwu")),
494 BLSValue::Value(String::from("wtf"))
495 ]
496 );
497
498 entry.clear(BLSKey::Options);
500 assert_eq!(entry.options, vec![]);
501
502 entry.set(
503 BLSKey::Title,
504 String::from("foobar"),
505 None,
506 ValueSetPolicy::Append,
507 );
508 assert_eq!(entry.title, Some(BLSValue::Value(String::from("foobar"))));
509
510 entry.clear(BLSKey::Title);
511 assert_eq!(entry.title, None);
512 }
513}