enumerated_latin 1.0.0

Encodes short strings as numeric values
Documentation
// SPDX-FileCopyrightText: 2025 Slatian
//
// SPDX-License-Identifier: LGPL-3.0-only

use crate::decoding_style;
use crate::decoding_style::DecodingStyle;
use crate::EncodingTarget;

/// Trait for implementing the number to letters part of enumerated latin.
///
/// This trait is implemented on all valid [EncodingTarget]s and can decode
/// to a string with the preferred letter casing.
///
/// All methods return `None` if given a number that can't be obtained through
/// the [enumerated_latin_encode][crate::EnumeratedLatinEncode::enumerated_latin_encode]
/// method.
pub trait EnumeratedLatinDecode {
	/// Decodes to a `lowercase` string.
	fn enumerated_latin_decode_lowercase(self) -> Option<String>;

	/// Decodes to an `UPPERCASE` string.
	fn enumerated_latin_decode_uppercase(self) -> Option<String>;

	/// Decodes to a `Titlecase` string.
	fn enumerated_latin_decode_titlecase(self) -> Option<String>;
}

impl<T: EncodingTarget> EnumeratedLatinDecode for T {
	fn enumerated_latin_decode_lowercase(self) -> Option<String> {
		enumerated_latin_decode::<T, decoding_style::AllLowercase>(self)
	}

	fn enumerated_latin_decode_uppercase(self) -> Option<String> {
		enumerated_latin_decode::<T, decoding_style::AllUppercase>(self)
	}

	fn enumerated_latin_decode_titlecase(self) -> Option<String> {
		enumerated_latin_decode::<T, decoding_style::Titlecase>(self)
	}
}

fn enumerated_latin_decode<T: EncodingTarget, S: DecodingStyle>(
	mut to_decode: T,
) -> Option<String> {
	if to_decode < 26.into() {
		if to_decode == 1.into() {
			return Some("".to_string());
		}
		return None;
	}
	let mut out = "".to_string();
	while to_decode > 0.into() {
		let n = to_decode % 26.into();
		to_decode = (to_decode - n) / 26.into();
		if to_decode > 0.into() {
			out.insert(
				0,
				S::number_to_letter(
					n.try_into().ok()?,
					to_decode == 1.into(), // If to_decode equals the end marker, that is the first character of the string
				)?,
			);
		} else if n != 1.into() {
			// Make sure that there is a terminating one
			// Otherwise the number probably got corrupted
			return None;
		}
	}
	Some(out)
}

#[cfg(test)]
mod test {
	use super::*;
	#[test]
	pub fn test_case_decoding() {
		assert_eq!(
			(8480578981 as u64).enumerated_latin_decode_lowercase(),
			Some("blubber".to_string())
		);
		assert_eq!(
			(8480578981 as u64).enumerated_latin_decode_uppercase(),
			Some("BLUBBER".to_string())
		);
		assert_eq!(
			(8480578981 as u64).enumerated_latin_decode_titlecase(),
			Some("Blubber".to_string())
		);
		assert_eq!(
			(26 as u32).enumerated_latin_decode_lowercase(),
			Some("a".to_string())
		);
		assert_eq!(
			(26 as u32).enumerated_latin_decode_uppercase(),
			Some("A".to_string())
		);
		assert_eq!(
			(26 as u32).enumerated_latin_decode_titlecase(),
			Some("A".to_string())
		);
	}

	#[test]
	pub fn test_decode_empty() {
		assert_eq!(
			(1 as u32).enumerated_latin_decode_lowercase(),
			Some("".to_string())
		);
	}

	#[test]
	pub fn test_out_of_range() {
		assert_eq!((0 as u16).enumerated_latin_decode_lowercase(), None);
		assert_eq!((2 as u16).enumerated_latin_decode_lowercase(), None);
		assert_eq!((25 as u16).enumerated_latin_decode_lowercase(), None);
		assert_eq!((-1 as i16).enumerated_latin_decode_lowercase(), None);
		assert_eq!(
			(51 as u8).enumerated_latin_decode_lowercase(),
			Some("z".to_string())
		);
		assert_eq!((52 as u8).enumerated_latin_decode_lowercase(), None);
		assert_eq!((255 as u8).enumerated_latin_decode_lowercase(), None);
	}
}