punktf-lib 2.0.0

Library for punktf, a cross-platform multi-target dotfiles manager
Documentation
//! Basic block and tokens a [template](`super::Template`) is created from.

use std::fmt;

use super::span::{ByteSpan, Spanned};

/// A parsed instruction from a template.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum BlockHint {
	/// Starts a `Text` block
	Text,
	/// Starts a `Comment` block
	Comment,
	/// Starts an escaped block
	Escaped,
	/// Starts a `Variable` block
	Var,
	/// Starts a `Print` block
	Print,
	/// Starts a `If` block
	IfStart,
	/// Continues an `If` block with an `ElIf` block
	ElIf,
	/// Continues an `If` block with an `Else` block
	Else,
	/// End an `If` block
	IfEnd,
}

impl BlockHint {
	/// Whether this instruction is part of a block.
	pub fn is_if_subblock(&self) -> bool {
		self == &Self::ElIf || self == &Self::Else || self == &Self::IfEnd
	}
}

/// A instruction that opens a new block.
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum BlockKind {
	/// A `Text` block, that contains text that is copied to the output.
	Text,
	/// A `Comment` block, that contains text that is ignored.
	Comment,
	/// An escaped block, that contains escaped text that is copied to the output.
	Escaped(ByteSpan),
	/// A `Variable` block, that contains a variable name that is replaced with its value.
	Var(Var),
	/// A `Print` block, that contains text that is printed to the log.
	Print(ByteSpan),
	/// An `If` block, that contains a condition that is evaluated and compiles the block conditionally.
	If(If),
}

impl BlockKind {
	/// Returns the corresponding hint for this block.
	pub const fn as_hint(&self) -> BlockHint {
		match self {
			BlockKind::Text => BlockHint::Text,
			BlockKind::Comment => BlockHint::Comment,
			BlockKind::Escaped(_) => BlockHint::Escaped,
			BlockKind::Var(_) => BlockHint::Var,
			BlockKind::Print(_) => BlockHint::Print,
			BlockKind::If(_) => BlockHint::IfEnd,
		}
	}
}

/// A block can be a single construction or open up a multi-line block that contains sub-blocks.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Block {
	/// The span of this block.
	pub span: ByteSpan,
	/// The type of this block.
	pub kind: BlockKind,
}

impl Block {
	/// Creates a new block.
	pub const fn new(span: ByteSpan, kind: BlockKind) -> Self {
		Self { span, kind }
	}

	/// Returns the span of the block.
	pub const fn span(&self) -> &ByteSpan {
		&self.span
	}

	/// Returns the type of this block.
	pub const fn kind(&self) -> &BlockKind {
		&self.kind
	}
}

/// The different types of sources for variables values
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum VarEnv {
	/// A variable that is defined by the system's environment.
	Environment,
	/// A variable that is defined in the profile.
	Profile,
	/// A variable that is defined for a specific dotfile.
	Dotfile,
}

impl fmt::Display for VarEnv {
	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
		fmt::Debug::fmt(&self, f)
	}
}

/// Defines a set of variables sources that can be used to resolve variables.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct VarEnvSet(pub [Option<VarEnv>; 3]);

impl VarEnvSet {
	/// Creates an empty `VarEnvSet`.
	pub const fn empty() -> Self {
		Self([None; 3])
	}

	/// Adds a new variable to the set if it is not already present.
	pub fn add(&mut self, value: VarEnv) -> bool {
		if self.0.contains(&Some(value)) {
			false
		} else if let Some(slot) = self.0.iter_mut().find(|x| x.is_none()) {
			*slot = Some(value);
			true
		} else {
			false
		}
	}

	/// Returns the set of `VarEnv`s that are defined.
	pub fn envs(&self) -> impl Iterator<Item = &VarEnv> {
		self.0.iter().filter_map(|x| x.as_ref())
	}

	/// Returns the number of environments that are defined.
	pub fn len(&self) -> usize {
		self.envs().count()
	}

	/// Returns the capacity of the set.
	pub const fn capacity(&self) -> usize {
		self.0.len()
	}
}

impl Default for VarEnvSet {
	fn default() -> Self {
		Self([Some(VarEnv::Dotfile), Some(VarEnv::Profile), None])
	}
}

impl fmt::Display for VarEnvSet {
	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
		f.debug_list().entries(self.envs()).finish()
	}
}

/// A variable that is defined in the template.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct Var {
	/// The `VarEnvSet` for the variable
	pub envs: VarEnvSet,
	/// The `ByteSpan` of the variable
	pub name: ByteSpan,
}

/// Defines an if block.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct If {
	/// The head of an if statement.
	///
	/// `{{@if {{VAR}}}}`
	pub head: (Spanned<IfExpr>, Vec<Block>),

	/// All elif statements of the if.
	///
	/// `{{@elif {{VAR}}}}`
	pub elifs: Vec<(Spanned<IfExpr>, Vec<Block>)>,

	/// The else statement of the if.
	///
	/// `{{@else}}`
	pub els: Option<(ByteSpan, Vec<Block>)>,

	/// The closing fi statement.
	///
	/// `{{@fi}}`
	pub end: ByteSpan,
}

/// The different types of if expression operations.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum IfOp {
	/// Operand to check for equality.
	Eq,

	/// Operand to check for inequality.
	NotEq,
}

impl IfOp {
	/// Evaluates an if expression.
	pub fn eval(&self, lhs: &str, rhs: &str) -> bool {
		match self {
			Self::Eq => lhs == rhs,
			Self::NotEq => lhs != rhs,
		}
	}
}

/// The different if expression types.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum IfExpr {
	/// An if expression that compares two values.
	Compare {
		/// Left hand side of the compare operation.
		var: Var,

		/// Compare operand.
		op: IfOp,

		/// Right hand side of the compare operation.
		other: ByteSpan,
	},

	/// An if expression that checks if a value is defined.
	Exists {
		/// Variable to check existence for.
		var: Var,
	},

	/// An if expression that checks if a value is not defined.
	NotExists {
		/// Variable to check not existence for.
		var: Var,
	},
}