#![forbid(unsafe_code)]
#![doc = include_str!("../README.md")]
#[derive(Debug, Clone, PartialEq, Eq, Hash, Ord, PartialOrd)]
pub struct TokenName(String);
impl TokenName {
pub fn new(value: impl Into<String>) -> Self {
Self(value.into())
}
pub fn as_str(&self) -> &str {
&self.0
}
}
impl AsRef<str> for TokenName {
fn as_ref(&self) -> &str {
self.as_str()
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct TokenPath(Vec<TokenName>);
impl TokenPath {
pub fn new(segments: Vec<TokenName>) -> Self {
Self(segments)
}
pub fn from_segments<I, S>(segments: I) -> Self
where
I: IntoIterator<Item = S>,
S: Into<String>,
{
Self(segments.into_iter().map(TokenName::new).collect())
}
pub fn segments(&self) -> &[TokenName] {
&self.0
}
pub fn is_empty(&self) -> bool {
self.0.is_empty()
}
pub fn len(&self) -> usize {
self.0.len()
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Ord, PartialOrd)]
pub enum TokenCategory {
Color,
Typography,
Spacing,
Radius,
Shadow,
Motion,
Breakpoint,
Layer,
Component,
Semantic,
}
impl TokenCategory {
pub fn as_str(self) -> &'static str {
match self {
Self::Color => "color",
Self::Typography => "typography",
Self::Spacing => "spacing",
Self::Radius => "radius",
Self::Shadow => "shadow",
Self::Motion => "motion",
Self::Breakpoint => "breakpoint",
Self::Layer => "layer",
Self::Component => "component",
Self::Semantic => "semantic",
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct TokenReference {
path: TokenPath,
}
impl TokenReference {
pub fn new(path: TokenPath) -> Self {
Self { path }
}
pub fn path(&self) -> &TokenPath {
&self.path
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum TokenValue {
Text(String),
Integer(i64),
Boolean(bool),
Reference(TokenReference),
}
impl TokenValue {
pub fn text(value: impl Into<String>) -> Self {
Self::Text(value.into())
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct DesignToken {
name: TokenName,
category: TokenCategory,
value: TokenValue,
path: Option<TokenPath>,
}
impl DesignToken {
pub fn new(name: TokenName, category: TokenCategory, value: TokenValue) -> Self {
Self {
name,
category,
value,
path: None,
}
}
pub fn with_path(mut self, path: TokenPath) -> Self {
self.path = Some(path);
self
}
pub fn name(&self) -> &TokenName {
&self.name
}
pub fn category(&self) -> TokenCategory {
self.category
}
pub fn value(&self) -> &TokenValue {
&self.value
}
pub fn path(&self) -> Option<&TokenPath> {
self.path.as_ref()
}
}
#[cfg(test)]
mod tests {
use super::{DesignToken, TokenCategory, TokenName, TokenPath, TokenReference, TokenValue};
#[test]
fn creates_token_categories_and_values() {
let token = DesignToken::new(
TokenName::new("primary"),
TokenCategory::Color,
TokenValue::text("#3366cc"),
);
assert_eq!(token.name().as_str(), "primary");
assert_eq!(token.category().as_str(), "color");
assert_eq!(token.value(), &TokenValue::Text(String::from("#3366cc")));
}
#[test]
fn creates_paths_and_references() {
let path = TokenPath::from_segments(["color", "background", "primary"]);
let reference = TokenReference::new(path.clone());
let token = DesignToken::new(
TokenName::new("surface"),
TokenCategory::Semantic,
TokenValue::Reference(reference),
)
.with_path(path);
assert_eq!(token.path().map(TokenPath::len), Some(3));
assert!(!token.path().is_some_and(TokenPath::is_empty));
}
}