#![forbid(unsafe_code)]
#![doc = include_str!("../README.md")]
use core::{fmt, str::FromStr};
use std::error::Error;
#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub struct PoseName(String);
impl PoseName {
pub fn new(value: impl AsRef<str>) -> Result<Self, PoseTextError> {
non_empty_pose_text(value).map(Self)
}
#[must_use]
pub fn as_str(&self) -> &str {
&self.0
}
#[must_use]
pub fn into_string(self) -> String {
self.0
}
}
impl AsRef<str> for PoseName {
fn as_ref(&self) -> &str {
self.as_str()
}
}
impl fmt::Display for PoseName {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter.write_str(self.as_str())
}
}
impl FromStr for PoseName {
type Err = PoseTextError;
fn from_str(value: &str) -> Result<Self, Self::Err> {
Self::new(value)
}
}
#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub enum PoseKind {
Home,
Ready,
Rest,
Tool,
Target,
Waypoint,
Calibration,
Unknown,
Custom(String),
}
impl fmt::Display for PoseKind {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter.write_str(match self {
Self::Home => "home",
Self::Ready => "ready",
Self::Rest => "rest",
Self::Tool => "tool",
Self::Target => "target",
Self::Waypoint => "waypoint",
Self::Calibration => "calibration",
Self::Unknown => "unknown",
Self::Custom(value) => value.as_str(),
})
}
}
impl FromStr for PoseKind {
type Err = PoseKindParseError;
fn from_str(value: &str) -> Result<Self, Self::Err> {
let trimmed = value.trim();
if trimmed.is_empty() {
return Err(PoseKindParseError::Empty);
}
match normalized_token(trimmed).as_str() {
"home" => Ok(Self::Home),
"ready" => Ok(Self::Ready),
"rest" => Ok(Self::Rest),
"tool" => Ok(Self::Tool),
"target" => Ok(Self::Target),
"waypoint" => Ok(Self::Waypoint),
"calibration" => Ok(Self::Calibration),
"unknown" => Ok(Self::Unknown),
_ => Ok(Self::Custom(trimmed.to_string())),
}
}
}
#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub struct Pose2Label(String);
impl Pose2Label {
pub fn new(value: impl AsRef<str>) -> Result<Self, PoseTextError> {
non_empty_pose_text(value).map(Self)
}
#[must_use]
pub fn as_str(&self) -> &str {
&self.0
}
#[must_use]
pub fn into_string(self) -> String {
self.0
}
}
impl AsRef<str> for Pose2Label {
fn as_ref(&self) -> &str {
self.as_str()
}
}
impl fmt::Display for Pose2Label {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter.write_str(self.as_str())
}
}
impl FromStr for Pose2Label {
type Err = PoseTextError;
fn from_str(value: &str) -> Result<Self, Self::Err> {
Self::new(value)
}
}
#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub struct Pose3Label(String);
impl Pose3Label {
pub fn new(value: impl AsRef<str>) -> Result<Self, PoseTextError> {
non_empty_pose_text(value).map(Self)
}
#[must_use]
pub fn as_str(&self) -> &str {
&self.0
}
#[must_use]
pub fn into_string(self) -> String {
self.0
}
}
impl AsRef<str> for Pose3Label {
fn as_ref(&self) -> &str {
self.as_str()
}
}
impl fmt::Display for Pose3Label {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter.write_str(self.as_str())
}
}
impl FromStr for Pose3Label {
type Err = PoseTextError;
fn from_str(value: &str) -> Result<Self, Self::Err> {
Self::new(value)
}
}
#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub enum OrientationKind {
Euler,
Quaternion,
AxisAngle,
RotationMatrix,
Unknown,
Custom(String),
}
impl fmt::Display for OrientationKind {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter.write_str(match self {
Self::Euler => "euler",
Self::Quaternion => "quaternion",
Self::AxisAngle => "axis-angle",
Self::RotationMatrix => "rotation-matrix",
Self::Unknown => "unknown",
Self::Custom(value) => value.as_str(),
})
}
}
impl FromStr for OrientationKind {
type Err = OrientationKindParseError;
fn from_str(value: &str) -> Result<Self, Self::Err> {
let trimmed = value.trim();
if trimmed.is_empty() {
return Err(OrientationKindParseError::Empty);
}
match normalized_token(trimmed).as_str() {
"euler" => Ok(Self::Euler),
"quaternion" => Ok(Self::Quaternion),
"axis-angle" => Ok(Self::AxisAngle),
"rotation-matrix" => Ok(Self::RotationMatrix),
"unknown" => Ok(Self::Unknown),
_ => Ok(Self::Custom(trimmed.to_string())),
}
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum PoseTextError {
Empty,
}
impl fmt::Display for PoseTextError {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Empty => formatter.write_str("pose text cannot be empty"),
}
}
}
impl Error for PoseTextError {}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum PoseKindParseError {
Empty,
}
impl fmt::Display for PoseKindParseError {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Empty => formatter.write_str("pose kind cannot be empty"),
}
}
}
impl Error for PoseKindParseError {}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum OrientationKindParseError {
Empty,
}
impl fmt::Display for OrientationKindParseError {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Empty => formatter.write_str("orientation kind cannot be empty"),
}
}
}
impl Error for OrientationKindParseError {}
fn non_empty_pose_text(value: impl AsRef<str>) -> Result<String, PoseTextError> {
let trimmed = value.as_ref().trim();
if trimmed.is_empty() {
Err(PoseTextError::Empty)
} else {
Ok(trimmed.to_string())
}
}
fn normalized_token(value: &str) -> String {
value
.trim()
.chars()
.map(|character| match character {
'_' | ' ' => '-',
other => other.to_ascii_lowercase(),
})
.collect()
}
#[cfg(test)]
mod tests {
use super::{
OrientationKind, OrientationKindParseError, PoseKind, PoseKindParseError, PoseName,
PoseTextError,
};
#[test]
fn constructs_valid_pose_name() -> Result<(), PoseTextError> {
let name = PoseName::new(" home ")?;
assert_eq!(name.as_str(), "home");
Ok(())
}
#[test]
fn rejects_empty_pose_name() {
assert_eq!(PoseName::new(""), Err(PoseTextError::Empty));
}
#[test]
fn displays_and_parses_pose_kind() -> Result<(), PoseKindParseError> {
assert_eq!("waypoint".parse::<PoseKind>()?, PoseKind::Waypoint);
assert_eq!(PoseKind::Calibration.to_string(), "calibration");
Ok(())
}
#[test]
fn displays_and_parses_orientation_kind() -> Result<(), OrientationKindParseError> {
assert_eq!(
"axis angle".parse::<OrientationKind>()?,
OrientationKind::AxisAngle
);
assert_eq!(
OrientationKind::RotationMatrix.to_string(),
"rotation-matrix"
);
Ok(())
}
#[test]
fn stores_custom_pose_kind() -> Result<(), PoseKindParseError> {
assert_eq!(
"inspection-hover".parse::<PoseKind>()?,
PoseKind::Custom("inspection-hover".to_string())
);
Ok(())
}
}