2025-10-11 14:11:31 -07:00
|
|
|
use crate::actions::ActionDeclaration;
|
2025-10-20 18:17:29 -07:00
|
|
|
use crate::options::SproutOptions;
|
2025-10-30 02:36:14 -04:00
|
|
|
use crate::platform::timer::PlatformTimer;
|
2025-10-13 00:55:11 -07:00
|
|
|
use anyhow::anyhow;
|
2025-10-24 18:44:28 -07:00
|
|
|
use anyhow::{Result, bail};
|
2025-10-24 19:37:06 -07:00
|
|
|
use std::cmp::Reverse;
|
2025-10-04 23:12:01 -07:00
|
|
|
use std::collections::{BTreeMap, BTreeSet};
|
|
|
|
|
use std::rc::Rc;
|
2025-10-13 00:55:11 -07:00
|
|
|
use uefi::proto::device_path::DevicePath;
|
2025-10-04 23:12:01 -07:00
|
|
|
|
2025-10-24 18:44:28 -07:00
|
|
|
/// The maximum number of iterations that can be performed in [SproutContext::finalize].
|
|
|
|
|
const CONTEXT_FINALIZE_ITERATION_LIMIT: usize = 100;
|
|
|
|
|
|
2025-10-20 00:06:46 -07:00
|
|
|
/// Declares a root context for Sprout.
|
|
|
|
|
/// This contains data that needs to be shared across Sprout.
|
2025-10-04 23:12:01 -07:00
|
|
|
pub struct RootContext {
|
2025-10-20 00:06:46 -07:00
|
|
|
/// The actions that are available in Sprout.
|
2025-10-04 23:12:01 -07:00
|
|
|
actions: BTreeMap<String, ActionDeclaration>,
|
2025-10-20 00:06:46 -07:00
|
|
|
/// The device path of the loaded Sprout image.
|
2025-10-13 00:55:11 -07:00
|
|
|
loaded_image_path: Option<Box<DevicePath>>,
|
2025-10-30 02:36:14 -04:00
|
|
|
/// Platform timer started at the beginning of the boot process.
|
|
|
|
|
timer: PlatformTimer,
|
2025-10-20 18:17:29 -07:00
|
|
|
/// The global options of Sprout.
|
|
|
|
|
options: SproutOptions,
|
2025-10-04 23:12:01 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl RootContext {
|
2025-10-20 00:06:46 -07:00
|
|
|
/// Creates a new root context with the `loaded_image_device_path` which will be stored
|
2025-10-30 02:36:14 -04:00
|
|
|
/// in the context for easy access. We also provide a `timer` which is used to measure elapsed
|
|
|
|
|
/// time for the bootloader.
|
|
|
|
|
pub fn new(
|
|
|
|
|
loaded_image_device_path: Box<DevicePath>,
|
|
|
|
|
timer: PlatformTimer,
|
|
|
|
|
options: SproutOptions,
|
|
|
|
|
) -> Self {
|
2025-10-20 00:06:46 -07:00
|
|
|
Self {
|
2025-10-13 00:55:11 -07:00
|
|
|
actions: BTreeMap::new(),
|
2025-10-30 02:36:14 -04:00
|
|
|
timer,
|
2025-10-13 00:55:11 -07:00
|
|
|
loaded_image_path: Some(loaded_image_device_path),
|
2025-10-20 18:17:29 -07:00
|
|
|
options,
|
2025-10-13 00:55:11 -07:00
|
|
|
}
|
2025-10-04 23:12:01 -07:00
|
|
|
}
|
|
|
|
|
|
2025-10-20 00:06:46 -07:00
|
|
|
/// Access the actions configured inside Sprout.
|
2025-10-04 23:12:01 -07:00
|
|
|
pub fn actions(&self) -> &BTreeMap<String, ActionDeclaration> {
|
|
|
|
|
&self.actions
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-20 00:06:46 -07:00
|
|
|
/// Access the actions configured inside Sprout mutably for modification.
|
2025-10-04 23:12:01 -07:00
|
|
|
pub fn actions_mut(&mut self) -> &mut BTreeMap<String, ActionDeclaration> {
|
|
|
|
|
&mut self.actions
|
|
|
|
|
}
|
2025-10-13 00:55:11 -07:00
|
|
|
|
2025-10-30 02:36:14 -04:00
|
|
|
/// Access the platform timer that is started at the beginning of the boot process.
|
|
|
|
|
pub fn timer(&self) -> &PlatformTimer {
|
|
|
|
|
&self.timer
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-20 00:06:46 -07:00
|
|
|
/// Access the device path of the loaded Sprout image.
|
2025-10-13 00:55:11 -07:00
|
|
|
pub fn loaded_image_path(&self) -> Result<&DevicePath> {
|
|
|
|
|
self.loaded_image_path
|
|
|
|
|
.as_deref()
|
|
|
|
|
.ok_or_else(|| anyhow!("no loaded image path"))
|
|
|
|
|
}
|
2025-10-20 18:17:29 -07:00
|
|
|
|
|
|
|
|
/// Access the global Sprout options.
|
|
|
|
|
pub fn options(&self) -> &SproutOptions {
|
|
|
|
|
&self.options
|
|
|
|
|
}
|
2025-10-04 23:12:01 -07:00
|
|
|
}
|
|
|
|
|
|
2025-10-20 00:06:46 -07:00
|
|
|
/// A context of Sprout. This is passed around different parts of Sprout and represents
|
|
|
|
|
/// a [RootContext] which is data that is shared globally, and [SproutContext] which works
|
|
|
|
|
/// sort of like a tree of values. You can cheaply clone a [SproutContext] and modify it with
|
|
|
|
|
/// new values, which override the values of contexts above it.
|
|
|
|
|
///
|
|
|
|
|
/// This is a core part of the value mechanism in Sprout which makes templating possible.
|
2025-10-11 14:35:29 -07:00
|
|
|
pub struct SproutContext {
|
2025-10-04 23:12:01 -07:00
|
|
|
root: Rc<RootContext>,
|
2025-10-11 14:35:29 -07:00
|
|
|
parent: Option<Rc<SproutContext>>,
|
2025-10-04 23:12:01 -07:00
|
|
|
values: BTreeMap<String, String>,
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-11 14:35:29 -07:00
|
|
|
impl SproutContext {
|
2025-10-20 00:06:46 -07:00
|
|
|
/// Create a new [SproutContext] using `root` as the root context.
|
2025-10-04 23:12:01 -07:00
|
|
|
pub fn new(root: RootContext) -> Self {
|
|
|
|
|
Self {
|
|
|
|
|
root: Rc::new(root),
|
|
|
|
|
parent: None,
|
|
|
|
|
values: BTreeMap::new(),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-20 00:06:46 -07:00
|
|
|
/// Access the root context of this context.
|
2025-10-04 23:12:01 -07:00
|
|
|
pub fn root(&self) -> &RootContext {
|
|
|
|
|
self.root.as_ref()
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-27 03:37:09 -04:00
|
|
|
/// Access the root context to modify it, if possible.
|
|
|
|
|
pub fn root_mut(&mut self) -> Option<&mut RootContext> {
|
|
|
|
|
Rc::get_mut(&mut self.root)
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-20 00:06:46 -07:00
|
|
|
/// Retrieve the value specified by `key` from this context or its parents.
|
|
|
|
|
/// Returns `None` if the value is not found.
|
2025-10-04 23:12:01 -07:00
|
|
|
pub fn get(&self, key: impl AsRef<str>) -> Option<&String> {
|
|
|
|
|
self.values.get(key.as_ref()).or_else(|| {
|
|
|
|
|
self.parent
|
|
|
|
|
.as_ref()
|
|
|
|
|
.and_then(|parent| parent.get(key.as_ref()))
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-20 00:06:46 -07:00
|
|
|
/// Collects all keys that are present in this context or its parents.
|
|
|
|
|
/// This is useful for iterating over all keys in a context.
|
2025-10-04 23:12:01 -07:00
|
|
|
pub fn all_keys(&self) -> Vec<String> {
|
|
|
|
|
let mut keys = BTreeSet::new();
|
|
|
|
|
|
|
|
|
|
for key in self.values.keys() {
|
|
|
|
|
keys.insert(key.clone());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if let Some(parent) = &self.parent {
|
|
|
|
|
keys.extend(parent.all_keys());
|
|
|
|
|
}
|
|
|
|
|
keys.into_iter().collect()
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-20 00:06:46 -07:00
|
|
|
/// Collects all values that are present in this context or its parents.
|
|
|
|
|
/// This is useful for iterating over all values in a context.
|
2025-10-04 23:12:01 -07:00
|
|
|
pub fn all_values(&self) -> BTreeMap<String, String> {
|
|
|
|
|
let mut values = BTreeMap::new();
|
|
|
|
|
for key in self.all_keys() {
|
2025-10-27 16:16:09 -04:00
|
|
|
// Acquire the value from the context. Since retrieving all the keys will give us
|
|
|
|
|
// a full view of the context, we can be sure that the key exists.
|
|
|
|
|
let value = self.get(&key).cloned().unwrap_or_default();
|
|
|
|
|
values.insert(key.clone(), value);
|
2025-10-04 23:12:01 -07:00
|
|
|
}
|
|
|
|
|
values
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-20 00:06:46 -07:00
|
|
|
/// Sets the value `key` to the value specified by `value` in this context.
|
|
|
|
|
/// If the parent context has this key, this will override that key.
|
2025-10-04 23:12:01 -07:00
|
|
|
pub fn set(&mut self, key: impl AsRef<str>, value: impl ToString) {
|
|
|
|
|
self.values
|
|
|
|
|
.insert(key.as_ref().to_string(), value.to_string());
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-20 00:06:46 -07:00
|
|
|
/// Inserts all the specified `values` into this context.
|
|
|
|
|
/// These values will take precedence over its parent context.
|
2025-10-04 23:12:01 -07:00
|
|
|
pub fn insert(&mut self, values: &BTreeMap<String, String>) {
|
|
|
|
|
for (key, value) in values {
|
|
|
|
|
self.values.insert(key.clone(), value.clone());
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-20 00:06:46 -07:00
|
|
|
/// Forks this context as an owned [SproutContext]. This makes it possible
|
|
|
|
|
/// to cheaply modify a context without cloning the parent context map.
|
|
|
|
|
/// The parent of the returned context is [self].
|
2025-10-11 14:35:29 -07:00
|
|
|
pub fn fork(self: &Rc<SproutContext>) -> Self {
|
2025-10-04 23:12:01 -07:00
|
|
|
Self {
|
|
|
|
|
root: self.root.clone(),
|
|
|
|
|
parent: Some(self.clone()),
|
|
|
|
|
values: BTreeMap::new(),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-20 00:06:46 -07:00
|
|
|
/// Freezes this context into a [Rc] which makes it possible to cheaply clone
|
|
|
|
|
/// and makes it less easy to modify a context. This can be used to pass the context
|
|
|
|
|
/// to various other parts of Sprout and ensure it won't be modified. Instead, once
|
|
|
|
|
/// a context is frozen, it should be [self.fork]'d to be modified.
|
2025-10-11 14:35:29 -07:00
|
|
|
pub fn freeze(self) -> Rc<SproutContext> {
|
2025-10-04 23:12:01 -07:00
|
|
|
Rc::new(self)
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-20 00:06:46 -07:00
|
|
|
/// Finalizes a context by producing a context with no parent that contains all the values
|
|
|
|
|
/// of all parent contexts merged. This makes it possible to ensure [SproutContext] has no
|
|
|
|
|
/// inheritance with other [SproutContext]s. It will still contain a [RootContext] however.
|
2025-10-24 18:44:28 -07:00
|
|
|
pub fn finalize(&self) -> Result<SproutContext> {
|
2025-10-20 00:06:46 -07:00
|
|
|
// Collect all the values from the context and its parents.
|
2025-10-04 23:12:01 -07:00
|
|
|
let mut current_values = self.all_values();
|
|
|
|
|
|
2025-10-24 18:44:28 -07:00
|
|
|
// To ensure that there is no possible infinite loop, we need to check
|
2025-10-28 00:10:22 -04:00
|
|
|
// the number of iterations. If it exceeds CONTEXT_FINALIZE_ITERATION_LIMIT, we bail.
|
2025-10-24 18:44:28 -07:00
|
|
|
let mut iterations: usize = 0;
|
2025-10-04 23:12:01 -07:00
|
|
|
loop {
|
2025-10-24 18:44:28 -07:00
|
|
|
iterations += 1;
|
|
|
|
|
|
|
|
|
|
if iterations > CONTEXT_FINALIZE_ITERATION_LIMIT {
|
2025-10-28 00:10:22 -04:00
|
|
|
bail!("maximum number of replacement iterations reached while finalizing context");
|
2025-10-24 18:44:28 -07:00
|
|
|
}
|
|
|
|
|
|
2025-10-04 23:12:01 -07:00
|
|
|
let mut did_change = false;
|
|
|
|
|
let mut values = BTreeMap::new();
|
|
|
|
|
for (key, value) in ¤t_values {
|
|
|
|
|
let (changed, result) = Self::stamp_values(¤t_values, value);
|
|
|
|
|
if changed {
|
2025-10-20 00:06:46 -07:00
|
|
|
// If the value changed, we need to re-stamp it.
|
2025-10-04 23:12:01 -07:00
|
|
|
did_change = true;
|
|
|
|
|
}
|
2025-10-20 00:06:46 -07:00
|
|
|
// Insert the new value into the value map.
|
2025-10-04 23:12:01 -07:00
|
|
|
values.insert(key.clone(), result);
|
|
|
|
|
}
|
|
|
|
|
current_values = values;
|
|
|
|
|
|
2025-10-20 00:06:46 -07:00
|
|
|
// If the values did not change, we can stop.
|
2025-10-04 23:12:01 -07:00
|
|
|
if !did_change {
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
2025-10-20 00:06:46 -07:00
|
|
|
|
|
|
|
|
// Produce the final context.
|
2025-10-24 18:44:28 -07:00
|
|
|
Ok(Self {
|
2025-10-04 23:12:01 -07:00
|
|
|
root: self.root.clone(),
|
|
|
|
|
parent: None,
|
|
|
|
|
values: current_values,
|
2025-10-24 18:44:28 -07:00
|
|
|
})
|
2025-10-04 23:12:01 -07:00
|
|
|
}
|
|
|
|
|
|
2025-10-20 00:06:46 -07:00
|
|
|
/// Stamps the `text` value with the specified `values` map. The returned value indicates
|
|
|
|
|
/// whether the `text` has been changed and the value that was stamped and changed.
|
2025-10-04 23:12:01 -07:00
|
|
|
fn stamp_values(values: &BTreeMap<String, String>, text: impl AsRef<str>) -> (bool, String) {
|
|
|
|
|
let mut result = text.as_ref().to_string();
|
|
|
|
|
let mut did_change = false;
|
2025-10-24 19:24:29 -07:00
|
|
|
|
|
|
|
|
// Sort the keys by length. This is to ensure that we stamp the longest keys first.
|
|
|
|
|
// If we did not do this, "$abc" could be stamped by "$a" into an invalid result.
|
|
|
|
|
let mut keys = values.keys().collect::<Vec<_>>();
|
2025-10-24 19:37:06 -07:00
|
|
|
|
|
|
|
|
// Sort by key length, reversed. This results in the longest keys appearing first.
|
|
|
|
|
keys.sort_by_key(|key| Reverse(key.len()));
|
2025-10-24 19:24:29 -07:00
|
|
|
|
|
|
|
|
for key in keys {
|
2025-10-24 19:12:43 -07:00
|
|
|
// Empty keys are not supported.
|
|
|
|
|
if key.is_empty() {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
2025-10-24 19:24:29 -07:00
|
|
|
|
|
|
|
|
// We can fetch the value from the map. It is verifiable that the key exists.
|
|
|
|
|
let Some(value) = values.get(key) else {
|
|
|
|
|
unreachable!("keys iterated over is collected on a map that cannot be modified");
|
|
|
|
|
};
|
|
|
|
|
|
2025-10-04 23:12:01 -07:00
|
|
|
let next_result = result.replace(&format!("${key}"), value);
|
|
|
|
|
if result != next_result {
|
|
|
|
|
did_change = true;
|
|
|
|
|
}
|
|
|
|
|
result = next_result;
|
|
|
|
|
}
|
|
|
|
|
(did_change, result)
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-20 00:06:46 -07:00
|
|
|
/// Stamps the input `text` with all the values in this [SproutContext] and it's parents.
|
|
|
|
|
/// For example, if this context contains {"a":"b"}, and the text "hello\\$a", it will produce
|
|
|
|
|
/// "hello\\b" as an output string.
|
2025-10-04 23:12:01 -07:00
|
|
|
pub fn stamp(&self, text: impl AsRef<str>) -> String {
|
|
|
|
|
Self::stamp_values(&self.all_values(), text.as_ref()).1
|
|
|
|
|
}
|
2025-10-27 03:37:09 -04:00
|
|
|
|
|
|
|
|
/// Unloads a [SproutContext] back into an owned context. This
|
|
|
|
|
/// may not succeed if something else is holding onto the value.
|
|
|
|
|
pub fn unload(self: Rc<SproutContext>) -> Option<SproutContext> {
|
|
|
|
|
Rc::into_inner(self)
|
|
|
|
|
}
|
2025-10-04 23:12:01 -07:00
|
|
|
}
|