use serde::Deserialize;
use serde::Serialize;
use crate::SPACE;
use crate::TAB;
#[derive(thiserror::Error, Debug)]
pub enum IndentError {
#[error("indentation with spaces must have a number of spaces")]
InvalidSpaceConfiguration,
#[error("indentation with tabs cannot have a number of spaces")]
InvalidTabConfiguration,
#[error("`{0}` is more than the maximum allowed number of spaces ({max})", max = MAX_SPACE_INDENT)]
TooManySpaces(usize),
}
const DEFAULT_SPACE_INDENT: usize = 4;
pub const DEFAULT_INDENT: Indent = Indent::Spaces(DEFAULT_SPACE_INDENT);
pub const MAX_SPACE_INDENT: usize = 16;
#[derive(Clone, Copy, Debug)]
pub enum Indent {
Tabs,
Spaces(usize),
}
impl Default for Indent {
fn default() -> Self {
DEFAULT_INDENT
}
}
impl Indent {
pub fn try_new(tab: bool, num_spaces: Option<usize>) -> Result<Self, IndentError> {
match (tab, num_spaces) {
(true, None) => Ok(Indent::Tabs),
(true, Some(_)) => Err(IndentError::InvalidTabConfiguration),
(false, Some(n)) => {
if n > MAX_SPACE_INDENT {
Err(IndentError::TooManySpaces(n))
} else {
Ok(Indent::Spaces(n))
}
}
(false, None) => Err(IndentError::InvalidSpaceConfiguration),
}
}
pub fn num(&self) -> usize {
match self {
Indent::Tabs => 1,
Indent::Spaces(n) => *n,
}
}
pub fn character(&self) -> &str {
match self {
Indent::Tabs => TAB,
Indent::Spaces(_) => SPACE,
}
}
pub fn string(&self) -> String {
match self {
Indent::Tabs => self.character().to_string(),
Indent::Spaces(n) => self.character().repeat(*n),
}
}
}
impl Serialize for Indent {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
match self {
Indent::Tabs => "tabs".serialize(serializer),
Indent::Spaces(n) => n.serialize(serializer),
}
}
}
impl<'de> Deserialize<'de> for Indent {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
#[derive(Deserialize)]
#[serde(untagged)]
enum Value {
Num(usize),
Str(String),
}
match Value::deserialize(deserializer)? {
Value::Num(n) => Indent::try_new(false, Some(n)).map_err(serde::de::Error::custom),
Value::Str(s) if s == "tabs" => Ok(Indent::Tabs),
Value::Str(s) => Err(serde::de::Error::custom(format!(
"expected a number or \"tabs\", got \"{s}\""
))),
}
}
}