databoard 0.3.1

Provides a hierarchical key-value-store
Documentation
// Copyright © 2025 Stephan Kunz
//! Implements [`databoard`][`RemappingList`] and helper functions handling the remapping rules.

use core::{
	ops::{Deref, DerefMut},
	str::FromStr,
};

use alloc::{string::String, vec::Vec};

use crate::{ConstString, RemappingTarget};

use super::error::{Error, Result};

/// An immutable remapping entry.
type RemappingEntry = (ConstString, RemappingTarget);

/// A mutable remapping list.
///
/// The following rules between `key`and `value` are valid:
/// - `key`and `value` are literals.
/// - The `key`s may not start with the characters `@` and `_`, these are reserved.
/// - The `key`s may not contain any of the following characters: [`:`, `"`, `'`].
/// - A `value` **NOT** wrapped in brackets is a constant,
///   or a `value` containing any of the following characters: [`:`, `"`, `'`]
///   is a constant assignment e.g. `literal` or `{x: 1, y: 2}` or `{"value"}`.
///   It does not access a [`Databoard`](crate::databoard).
///   It is helpful in combination with types that implement the trait [`FromStr`](core::str::FromStr) to create a distinct value.
/// - A `value` wrapped in brackets is a `remapped_key` to a parent [`Databoard`](crate::databoard), e.g. `{remapped_key}`.
///  - A `remapped_key` starting with `@` is a redirection to the top level [`Databoard`](crate::databoard), e.g. `{@remapped_key}`.
///  - A `remapped_key` starting with `_` is a restriction to the current level [`Databoard`](crate::databoard), e.g. `{_remapped_key}`.
/// - The `value` `{=}` is a shortcut for the redirection with the same name as in `key`, e.g. `{=}`.
#[derive(Clone, Default)]
#[repr(transparent)]
pub struct RemappingList(Vec<RemappingEntry>);

impl core::fmt::Debug for RemappingList {
	fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
		write!(f, "Remappings {{ ")?;
		write!(f, "{:?}", &self.0)?;
		write!(f, " }}")
	}
}

impl Deref for RemappingList {
	type Target = Vec<RemappingEntry>;

	fn deref(&self) -> &Self::Target {
		&self.0
	}
}

impl DerefMut for RemappingList {
	fn deref_mut(&mut self) -> &mut Self::Target {
		&mut self.0
	}
}

impl RemappingList {
	/// Adds an entry to the [`Remappings`] table.
	/// # Errors
	/// - [`Error::AlreadyRemapped`] if entry already exists
	/// - [`Error::CreateRemapping`] if target is not a valid [`RemappingTarget`]
	pub fn add(&mut self, key: impl Into<ConstString>, target: impl Into<ConstString>) -> Result<()> {
		let key = key.into();
		let target = target.into();
		let remap_to = if target.as_ref() == "{=}" {
			(String::from("{") + &key + "}").into()
		} else {
			target
		};
		for (original, _remapped) in &self.0 {
			if original == &key {
				return Err(Error::AlreadyRemapped {
					key,
					remapped: "todo!()".into(),
				});
			}
		}
		let target = RemappingTarget::from_str(&remap_to)?;
		self.0.push((key, target));
		Ok(())
	}

	/// Adds an entry to the [`Remappings`] table.
	/// Already existing values will be overwritten.
	/// # Errors
	/// - [`Error::CreateRemapping`] if target is not a valid [`RemappingTarget`]
	pub fn overwrite(&mut self, key: impl Into<ConstString>, target: impl Into<ConstString>) -> Result<()> {
		let key = key.into();
		let target = target.into();
		let remap_to = if target.as_ref() == "{=}" {
			(String::from("{") + &key + "}").into()
		} else {
			target
		};
		let target = RemappingTarget::from_str(&remap_to)?;
		for (original, old_value) in &mut self.0 {
			if original == &key {
				// replace value
				*old_value = target;
				return Ok(());
			}
		}
		// create if not existent
		self.0.push((key, target));
		Ok(())
	}

	/// Returns the remapped value for `key`.
	#[must_use]
	pub fn find(&self, key: &str) -> RemappingTarget {
		for (original, remapped) in &self.0 {
			if original.as_ref() == key {
				return remapped.clone();
			}
		}
		RemappingTarget::None(key.into())
	}

	/// Optimize for size
	pub fn shrink(&mut self) {
		self.0.shrink_to_fit();
	}
}

#[cfg(test)]
mod tests {
	use super::*;

	// check, that the auto traits are available
	const fn is_normal<T: Sized + Send + Sync>() {}

	#[test]
	const fn normal_types() {
		is_normal::<RemappingList>();
		is_normal::<RemappingEntry>();
	}
}