aboutsummaryrefslogtreecommitdiff
path: root/mingling_core/src/asset
diff options
context:
space:
mode:
Diffstat (limited to 'mingling_core/src/asset')
-rw-r--r--mingling_core/src/asset/dispatcher.rs554
-rw-r--r--mingling_core/src/asset/node.rs157
2 files changed, 3 insertions, 708 deletions
diff --git a/mingling_core/src/asset/dispatcher.rs b/mingling_core/src/asset/dispatcher.rs
index d700405..79ebee0 100644
--- a/mingling_core/src/asset/dispatcher.rs
+++ b/mingling_core/src/asset/dispatcher.rs
@@ -1,6 +1,4 @@
-use std::fmt::Display;
-
-use crate::{ChainProcess, Program, ProgramCollect, asset::node::Node};
+use crate::ChainProcess;
/// The entry logic of the Mingling program
///
@@ -14,7 +12,6 @@ use crate::{ChainProcess, Program, ProgramCollect, asset::node::Node};
/// # use mingling_core::Dispatcher;
/// # use mingling_core::Grouped;
/// # use mingling_core::Routable;
-/// # use mingling_core::Node;
/// # use mingling_core::MockProgramCollect as ThisProgram;
/// # unsafe impl Grouped<ThisProgram> for Foo {
/// # fn member_id() -> ThisProgram { ThisProgram::Foo }
@@ -25,54 +22,14 @@ use crate::{ChainProcess, Program, ProgramCollect, asset::node::Node};
/// }
///
/// impl Dispatcher<ThisProgram> for CMDGreet {
-/// fn node(&self) -> Node {
-/// Node::default().join("greet")
-/// }
-///
/// fn begin(&self, args: Vec<String>) -> ChainProcess<ThisProgram> {
/// Routable::to_chain(Foo { args })
/// }
-///
-/// fn clone_dispatcher(&self) -> Box<dyn Dispatcher<ThisProgram>> {
-/// Box::new(CMDGreet)
-/// }
/// }
/// ```
pub trait Dispatcher<C> {
- /// Get the node of this Dispatcher, used to tell the program loop which arguments should be handled by this Dispatcher
- ///
- /// Example:
- ///
- /// ```
- /// # use mingling_core::ChainProcess;
- /// # use mingling_core::Dispatcher;
- /// # use mingling_core::Grouped;
- /// # use mingling_core::Routable;
- /// # use mingling_core::Node;
- /// # use mingling_core::MockProgramCollect as ThisProgram;
- /// # unsafe impl Grouped<ThisProgram> for Foo {
- /// # fn member_id() -> ThisProgram { ThisProgram::Foo }
- /// # }
- /// # struct CMDGreet;
- /// # struct Foo {
- /// # args: Vec<String>
- /// # }
- /// # impl Dispatcher<ThisProgram> for CMDGreet {
- /// fn node(&self) -> Node {
- /// // Construct the node
- /// Node::default().join("greet")
- /// }
- /// # fn begin(&self, args: Vec<String>) -> ChainProcess<ThisProgram> {
- /// # Routable::to_chain(Foo { args })
- /// # }
- /// # fn clone_dispatcher(&self) -> Box<dyn Dispatcher<ThisProgram>> {
- /// # Box::new(CMDGreet)
- /// # }
- /// # }
- /// ```
- fn node(&self) -> Node;
-
- /// Begin logic, receives the remaining arguments after the prefix has been stripped
+ /// Begin logic, receives the remaining arguments after the command prefix
+ /// has been stripped
///
/// Example:
///
@@ -81,7 +38,6 @@ pub trait Dispatcher<C> {
/// # use mingling_core::Dispatcher;
/// # use mingling_core::Grouped;
/// # use mingling_core::Routable;
- /// # use mingling_core::Node;
/// # use mingling_core::MockProgramCollect as ThisProgram;
/// # unsafe impl Grouped<ThisProgram> for Foo {
/// # fn member_id() -> ThisProgram { ThisProgram::Foo }
@@ -91,515 +47,11 @@ pub trait Dispatcher<C> {
/// # args: Vec<String>
/// # }
/// # impl Dispatcher<ThisProgram> for CMDGreet {
- /// # fn node(&self) -> Node {
- /// # Node::default().join("greet")
- /// # }
/// fn begin(&self, args: Vec<String>) -> ChainProcess<ThisProgram> {
/// // Create Foo from args and route it to the next chain
/// Routable::to_chain(Foo { args })
/// }
- /// # fn clone_dispatcher(&self) -> Box<dyn Dispatcher<ThisProgram>> {
- /// # Box::new(CMDGreet)
- /// # }
/// # }
/// ```
fn begin(&self, args: Vec<String>) -> ChainProcess<C>;
-
- /// Clone the dispatcher's Box for dynamic dispatch
- ///
- /// Example:
- ///
- /// ```
- /// # use mingling_core::ChainProcess;
- /// # use mingling_core::Dispatcher;
- /// # use mingling_core::Grouped;
- /// # use mingling_core::Routable;
- /// # use mingling_core::Node;
- /// # use mingling_core::MockProgramCollect as ThisProgram;
- /// # unsafe impl Grouped<ThisProgram> for Foo {
- /// # fn member_id() -> ThisProgram { ThisProgram::Foo }
- /// # }
- /// # struct CMDGreet;
- /// # struct Foo {
- /// # args: Vec<String>
- /// # }
- /// # impl Dispatcher<ThisProgram> for CMDGreet {
- /// # fn node(&self) -> Node {
- /// # Node::default().join("greet")
- /// # }
- /// # fn begin(&self, args: Vec<String>) -> ChainProcess<ThisProgram> {
- /// # Routable::to_chain(Foo { args })
- /// # }
- /// fn clone_dispatcher(&self) -> Box<dyn Dispatcher<ThisProgram>> {
- /// // Create a new Box
- /// Box::new(CMDGreet)
- /// }
- /// # }
- /// ```
- fn clone_dispatcher(&self) -> Box<dyn Dispatcher<C>>;
-}
-
-impl<G> Clone for Box<dyn Dispatcher<G>>
-where
- G: Display,
-{
- fn clone(&self) -> Self {
- self.clone_dispatcher()
- }
-}
-
-impl<C> Program<C>
-where
- C: ProgramCollect<Enum = C>,
-{
- /// Add a Dispatcher to the program
- ///
- /// This dynamically registers a Dispatcher into the program, used for command matching at program startup
- ///
- /// ```
- /// # use mingling_core::Program;
- /// # use mingling_core::ChainProcess;
- /// # use mingling_core::Dispatcher;
- /// # use mingling_core::Grouped;
- /// # use mingling_core::Routable;
- /// # use mingling_core::Node;
- /// # use mingling_core::MockProgramCollect as ThisProgram;
- /// # unsafe impl Grouped<ThisProgram> for Foo {
- /// # fn member_id() -> ThisProgram { ThisProgram::Foo }
- /// # }
- /// # struct CMDGreet;
- /// # struct Foo {
- /// # args: Vec<String>
- /// # }
- /// # impl Dispatcher<ThisProgram> for CMDGreet {
- /// # fn node(&self) -> Node {
- /// # Node::default().join("greet")
- /// # }
- /// # fn begin(&self, args: Vec<String>) -> ChainProcess<ThisProgram> {
- /// # Routable::to_chain(Foo { args })
- /// # }
- /// # fn clone_dispatcher(&self) -> Box<dyn Dispatcher<ThisProgram>> {
- /// # Box::new(CMDGreet)
- /// # }
- /// # }
- /// let mut program = Program::<ThisProgram>::new();
- /// program.with_dispatcher(CMDGreet);
- /// ```
- #[cfg_attr(
- feature = "dispatch_tree",
- deprecated(
- note = "When the `dispatch_tree` feature is enabled, the `dispatcher` field no longer exists inside Program. All types are collected at compile time by the `gen_program!()` macro, so the `with_dispatcher` function is no longer needed"
- )
- )]
- pub fn with_dispatcher<Disp>(&mut self, dispatcher: Disp) -> &mut Self
- where
- Disp: Dispatcher<C> + Send + Sync + 'static,
- {
- #[cfg(not(feature = "dispatch_tree"))]
- {
- self.dispatcher.push(Box::new(dispatcher));
- }
- #[cfg(feature = "dispatch_tree")]
- {
- let _ = dispatcher;
- }
- self
- }
-
- /// Add a group of Dispatchers to the program
- ///
- /// This dynamically registers a group of Dispatchers into the program, used for command matching at program startup
- ///
- /// ```
- /// # use mingling_core::Program;
- /// # use mingling_core::ChainProcess;
- /// # use mingling_core::Dispatcher;
- /// # use mingling_core::Grouped;
- /// # use mingling_core::Routable;
- /// # use mingling_core::Node;
- /// # use mingling_core::MockProgramCollect as ThisProgram;
- /// # unsafe impl Grouped<ThisProgram> for Foo {
- /// # fn member_id() -> ThisProgram { ThisProgram::Foo }
- /// # }
- /// # struct CMDGreet;
- /// # struct Foo {
- /// # args: Vec<String>
- /// # }
- /// # impl Dispatcher<ThisProgram> for CMDGreet {
- /// # fn node(&self) -> Node {
- /// # Node::default().join("greet")
- /// # }
- /// # fn begin(&self, args: Vec<String>) -> ChainProcess<ThisProgram> {
- /// # Routable::to_chain(Foo { args })
- /// # }
- /// # fn clone_dispatcher(&self) -> Box<dyn Dispatcher<ThisProgram>> {
- /// # Box::new(CMDGreet)
- /// # }
- /// # }
- /// let mut program = Program::<ThisProgram>::new();
- /// program.with_dispatchers((CMDGreet, /* Other Dispatchers */));
- /// ```
- #[deprecated(
- note = "with_dispatchers is no longer the recommended way to register Dispatchers, please split into multiple with_dispatcher calls"
- )]
- #[allow(deprecated)]
- pub fn with_dispatchers<D>(&mut self, dispatchers: D) -> &mut Self
- where
- D: Into<Dispatchers<C>>,
- {
- #[cfg(not(feature = "dispatch_tree"))]
- {
- let dispatchers = dispatchers.into();
- self.dispatcher.extend(dispatchers.dispatcher);
- }
- #[cfg(feature = "dispatch_tree")]
- {
- let _ = dispatchers;
- }
- self
- }
-}
-
-/// Represents a group of Dispatchers
-///
-/// It records a group of Dispatchers and implements conversion from tuples `(Disp, ..)` for this type,
-/// allowing for simpler construction syntax when using `with_dispatchers`
-///
-/// # Limits
-///
-/// Dispatchers supports conversion from tuples of up to 7 Dispatchers
-#[deprecated(
- note = "with_dispatchers is no longer the recommended way to register Dispatchers, please split into multiple with_dispatcher calls"
-)]
-pub struct Dispatchers<G> {
- dispatcher: Vec<Box<dyn Dispatcher<G> + Send + Sync + 'static>>,
-}
-
-#[allow(deprecated)]
-impl<G> From<Vec<Box<dyn Dispatcher<G> + Send + Sync>>> for Dispatchers<G> {
- fn from(dispatcher: Vec<Box<dyn Dispatcher<G> + Send + Sync>>) -> Self {
- Self { dispatcher }
- }
-}
-
-#[allow(deprecated)]
-impl<G> From<Box<dyn Dispatcher<G> + Send + Sync>> for Dispatchers<G> {
- fn from(dispatcher: Box<dyn Dispatcher<G> + Send + Sync>) -> Self {
- Self {
- dispatcher: vec![dispatcher],
- }
- }
-}
-
-#[allow(deprecated)]
-impl<D, G> From<(D,)> for Dispatchers<G>
-where
- D: Dispatcher<G> + Send + Sync + 'static,
- G: Display,
-{
- fn from(dispatcher: (D,)) -> Self {
- Self {
- dispatcher: vec![Box::new(dispatcher.0)],
- }
- }
-}
-
-#[allow(deprecated)]
-impl<D1, D2, G> From<(D1, D2)> for Dispatchers<G>
-where
- D1: Dispatcher<G> + Send + Sync + 'static,
- D2: Dispatcher<G> + Send + Sync + 'static,
- G: Display,
-{
- fn from(dispatchers: (D1, D2)) -> Self {
- Self {
- dispatcher: vec![Box::new(dispatchers.0), Box::new(dispatchers.1)],
- }
- }
-}
-
-#[allow(deprecated)]
-impl<D1, D2, D3, G> From<(D1, D2, D3)> for Dispatchers<G>
-where
- D1: Dispatcher<G> + Send + Sync + 'static,
- D2: Dispatcher<G> + Send + Sync + 'static,
- D3: Dispatcher<G> + Send + Sync + 'static,
- G: Display,
-{
- fn from(dispatchers: (D1, D2, D3)) -> Self {
- Self {
- dispatcher: vec![
- Box::new(dispatchers.0),
- Box::new(dispatchers.1),
- Box::new(dispatchers.2),
- ],
- }
- }
-}
-
-#[allow(deprecated)]
-impl<D1, D2, D3, D4, G> From<(D1, D2, D3, D4)> for Dispatchers<G>
-where
- D1: Dispatcher<G> + Send + Sync + 'static,
- D2: Dispatcher<G> + Send + Sync + 'static,
- D3: Dispatcher<G> + Send + Sync + 'static,
- D4: Dispatcher<G> + Send + Sync + 'static,
- G: Display,
-{
- fn from(dispatchers: (D1, D2, D3, D4)) -> Self {
- Self {
- dispatcher: vec![
- Box::new(dispatchers.0),
- Box::new(dispatchers.1),
- Box::new(dispatchers.2),
- Box::new(dispatchers.3),
- ],
- }
- }
-}
-
-#[allow(deprecated)]
-impl<D1, D2, D3, D4, D5, G> From<(D1, D2, D3, D4, D5)> for Dispatchers<G>
-where
- D1: Dispatcher<G> + Send + Sync + 'static,
- D2: Dispatcher<G> + Send + Sync + 'static,
- D3: Dispatcher<G> + Send + Sync + 'static,
- D4: Dispatcher<G> + Send + Sync + 'static,
- D5: Dispatcher<G> + Send + Sync + 'static,
- G: Display,
-{
- fn from(dispatchers: (D1, D2, D3, D4, D5)) -> Self {
- Self {
- dispatcher: vec![
- Box::new(dispatchers.0),
- Box::new(dispatchers.1),
- Box::new(dispatchers.2),
- Box::new(dispatchers.3),
- Box::new(dispatchers.4),
- ],
- }
- }
-}
-
-#[allow(deprecated)]
-impl<D1, D2, D3, D4, D5, D6, G> From<(D1, D2, D3, D4, D5, D6)> for Dispatchers<G>
-where
- D1: Dispatcher<G> + Send + Sync + 'static,
- D2: Dispatcher<G> + Send + Sync + 'static,
- D3: Dispatcher<G> + Send + Sync + 'static,
- D4: Dispatcher<G> + Send + Sync + 'static,
- D5: Dispatcher<G> + Send + Sync + 'static,
- D6: Dispatcher<G> + Send + Sync + 'static,
- G: Display,
-{
- fn from(dispatchers: (D1, D2, D3, D4, D5, D6)) -> Self {
- Self {
- dispatcher: vec![
- Box::new(dispatchers.0),
- Box::new(dispatchers.1),
- Box::new(dispatchers.2),
- Box::new(dispatchers.3),
- Box::new(dispatchers.4),
- Box::new(dispatchers.5),
- ],
- }
- }
-}
-
-#[allow(deprecated)]
-impl<D1, D2, D3, D4, D5, D6, D7, G> From<(D1, D2, D3, D4, D5, D6, D7)> for Dispatchers<G>
-where
- D1: Dispatcher<G> + Send + Sync + 'static,
- D2: Dispatcher<G> + Send + Sync + 'static,
- D3: Dispatcher<G> + Send + Sync + 'static,
- D4: Dispatcher<G> + Send + Sync + 'static,
- D5: Dispatcher<G> + Send + Sync + 'static,
- D6: Dispatcher<G> + Send + Sync + 'static,
- D7: Dispatcher<G> + Send + Sync + 'static,
- G: Display,
-{
- fn from(dispatchers: (D1, D2, D3, D4, D5, D6, D7)) -> Self {
- Self {
- dispatcher: vec![
- Box::new(dispatchers.0),
- Box::new(dispatchers.1),
- Box::new(dispatchers.2),
- Box::new(dispatchers.3),
- Box::new(dispatchers.4),
- Box::new(dispatchers.5),
- Box::new(dispatchers.6),
- ],
- }
- }
-}
-
-#[allow(deprecated)]
-impl<G> std::ops::Deref for Dispatchers<G> {
- type Target = Vec<Box<dyn Dispatcher<G> + Send + Sync + 'static>>;
-
- fn deref(&self) -> &Self::Target {
- &self.dispatcher
- }
-}
-
-#[allow(deprecated)]
-impl<G> From<Dispatchers<G>> for Vec<Box<dyn Dispatcher<G> + Send + Sync + 'static>> {
- fn from(val: Dispatchers<G>) -> Self {
- val.dispatcher
- }
-}
-
-#[cfg(test)]
-mod tests {
- use super::*;
- use crate::ChainProcess;
- use std::fmt::Display;
- #[derive(Clone)]
- struct MockDispatcher {
- name: &'static str,
- }
-
- impl<C: Display> Dispatcher<C> for MockDispatcher {
- fn node(&self) -> crate::asset::node::Node {
- self.name.into()
- }
-
- fn begin(&self, _args: Vec<String>) -> ChainProcess<C> {
- unimplemented!("not used in these tests")
- }
-
- fn clone_dispatcher(&self) -> Box<dyn Dispatcher<C>> {
- Box::new(self.clone())
- }
- }
- #[derive(Debug, Clone, Copy, PartialEq, Eq)]
- #[allow(dead_code)]
- enum MockG {
- A,
- }
-
- impl Display for MockG {
- fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
- write!(f, "A")
- }
- }
-
- #[test]
- #[allow(deprecated)]
- fn test_dispatchers_from_single_tuple() {
- let disp = MockDispatcher { name: "foo" };
- let dispatchers: Dispatchers<MockG> = Dispatchers::from((disp,));
- assert_eq!(dispatchers.dispatcher.len(), 1);
- }
-
- #[test]
- #[allow(deprecated)]
- fn test_dispatchers_from_two_tuple() {
- let d1 = MockDispatcher { name: "a" };
- let d2 = MockDispatcher { name: "b" };
- let dispatchers: Dispatchers<MockG> = Dispatchers::from((d1, d2));
- assert_eq!(dispatchers.dispatcher.len(), 2);
- }
-
- #[test]
- #[allow(deprecated)]
- fn test_dispatchers_from_three_tuple() {
- let d1 = MockDispatcher { name: "x" };
- let d2 = MockDispatcher { name: "y" };
- let d3 = MockDispatcher { name: "z" };
- let dispatchers: Dispatchers<MockG> = Dispatchers::from((d1, d2, d3));
- assert_eq!(dispatchers.dispatcher.len(), 3);
- }
-
- #[test]
- #[allow(deprecated)]
- fn test_dispatchers_from_four_tuple() {
- let d1 = MockDispatcher { name: "1" };
- let d2 = MockDispatcher { name: "2" };
- let d3 = MockDispatcher { name: "3" };
- let d4 = MockDispatcher { name: "4" };
- let dispatchers: Dispatchers<MockG> = Dispatchers::from((d1, d2, d3, d4));
- assert_eq!(dispatchers.dispatcher.len(), 4);
- }
-
- #[test]
- #[allow(deprecated)]
- fn test_dispatchers_from_five_tuple() {
- let d1 = MockDispatcher { name: "a" };
- let d2 = MockDispatcher { name: "b" };
- let d3 = MockDispatcher { name: "c" };
- let d4 = MockDispatcher { name: "d" };
- let d5 = MockDispatcher { name: "e" };
- let dispatchers: Dispatchers<MockG> = Dispatchers::from((d1, d2, d3, d4, d5));
- assert_eq!(dispatchers.dispatcher.len(), 5);
- }
-
- #[test]
- #[allow(deprecated)]
- fn test_dispatchers_from_six_tuple() {
- let d1 = MockDispatcher { name: "a" };
- let d2 = MockDispatcher { name: "b" };
- let d3 = MockDispatcher { name: "c" };
- let d4 = MockDispatcher { name: "d" };
- let d5 = MockDispatcher { name: "e" };
- let d6 = MockDispatcher { name: "f" };
- let dispatchers: Dispatchers<MockG> = Dispatchers::from((d1, d2, d3, d4, d5, d6));
- assert_eq!(dispatchers.dispatcher.len(), 6);
- }
-
- #[test]
- #[allow(deprecated)]
- fn test_dispatchers_from_seven_tuple() {
- let d1 = MockDispatcher { name: "a" };
- let d2 = MockDispatcher { name: "b" };
- let d3 = MockDispatcher { name: "c" };
- let d4 = MockDispatcher { name: "d" };
- let d5 = MockDispatcher { name: "e" };
- let d6 = MockDispatcher { name: "f" };
- let d7 = MockDispatcher { name: "g" };
- let dispatchers: Dispatchers<MockG> = Dispatchers::from((d1, d2, d3, d4, d5, d6, d7));
- assert_eq!(dispatchers.dispatcher.len(), 7);
- }
-
- #[test]
- #[allow(deprecated)]
- fn test_dispatchers_from_vec_of_boxed() {
- let d1: Box<dyn Dispatcher<MockG> + Send + Sync> = Box::new(MockDispatcher { name: "a" });
- let d2: Box<dyn Dispatcher<MockG> + Send + Sync> = Box::new(MockDispatcher { name: "b" });
- let dispatchers: Dispatchers<MockG> = vec![d1, d2].into();
- assert_eq!(dispatchers.dispatcher.len(), 2);
- }
-
- #[test]
- #[allow(deprecated)]
- fn test_dispatchers_from_single_boxed() {
- let d: Box<dyn Dispatcher<MockG> + Send + Sync> = Box::new(MockDispatcher { name: "x" });
- let dispatchers: Dispatchers<MockG> = d.into();
- assert_eq!(dispatchers.dispatcher.len(), 1);
- }
-
- #[test]
- #[allow(deprecated)]
- fn test_dispatchers_deref() {
- let disp = MockDispatcher { name: "test" };
- let dispatchers: Dispatchers<MockG> = Dispatchers::from((disp,));
- let inner: &Vec<Box<dyn Dispatcher<MockG> + Send + Sync + 'static>> = &dispatchers;
- assert_eq!(inner.len(), 1);
- }
-
- #[test]
- #[allow(deprecated)]
- fn test_dispatchers_into_vec() {
- let disp = MockDispatcher { name: "foo" };
- let dispatchers: Dispatchers<MockG> = Dispatchers::from((disp,));
- let vec: Vec<Box<dyn Dispatcher<MockG> + Send + Sync + 'static>> = dispatchers.into();
- assert_eq!(vec.len(), 1);
- }
-
- #[test]
- fn test_box_clone_dispatcher() {
- let disp: Box<dyn Dispatcher<MockG>> = Box::new(MockDispatcher { name: "clonable" });
- let cloned = disp.clone_dispatcher();
- assert_eq!(cloned.node().to_string(), "clonable");
- }
}
diff --git a/mingling_core/src/asset/node.rs b/mingling_core/src/asset/node.rs
deleted file mode 100644
index ee28836..0000000
--- a/mingling_core/src/asset/node.rs
+++ /dev/null
@@ -1,157 +0,0 @@
-use just_fmt::kebab_case;
-
-/// Represents a command node used for matching user-input command paths.
-///
-/// The node consists of multiple segments, separated by dots (`.`), which are automatically
-/// converted to kebab-case. For example, the input string `"node.subnode"` would be converted
-/// to the node representation `["node", "subnode"]`.
-///
-/// # Examples
-///
-/// ```
-/// use mingling_core::Node;
-///
-/// // Create a node and append child segments
-/// let node = Node::from("base").join("sub").join("leaf");
-/// assert_eq!(node.to_string(), "base.sub.leaf");
-/// ```
-#[derive(Debug, Default)]
-pub struct Node {
- node: Vec<String>,
-}
-
-impl Node {
- /// Appends a new segment to the node path.
- ///
- /// This method consumes the current node and returns a new node with the new
- /// segment appended to the end of the path.
- ///
- /// # Examples
- ///
- /// ```
- /// use mingling_core::Node;
- ///
- /// let node = Node::from("base").join("sub");
- /// assert_eq!(node.to_string(), "base.sub");
- /// ```
- #[must_use]
- pub fn join(self, node: impl Into<String>) -> Self {
- let mut new_node = self.node;
- new_node.push(node.into());
- Self { node: new_node }
- }
-}
-
-impl From<&str> for Node {
- fn from(s: &str) -> Self {
- let node = s.split('.').map(|part| kebab_case!(part)).collect();
- Self { node }
- }
-}
-
-impl From<String> for Node {
- fn from(s: String) -> Self {
- let node = s.split('.').map(|part| kebab_case!(part)).collect();
- Self { node }
- }
-}
-
-impl PartialEq for Node {
- fn eq(&self, other: &Self) -> bool {
- self.node == other.node
- }
-}
-
-impl Eq for Node {}
-
-impl PartialOrd for Node {
- fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
- Some(self.cmp(other))
- }
-}
-
-impl Ord for Node {
- fn cmp(&self, other: &Self) -> std::cmp::Ordering {
- self.node.cmp(&other.node)
- }
-}
-
-impl std::fmt::Display for Node {
- fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
- write!(f, "{}", self.node.join("."))
- }
-}
-
-#[cfg(test)]
-mod tests {
- use super::*;
-
- #[test]
- fn test_node_from_single_str() {
- let node = Node::from("hello");
- assert_eq!(node.node, vec!["hello"]);
- assert_eq!(node.to_string(), "hello");
- }
-
- #[test]
- fn test_node_from_dotted_str() {
- let node = Node::from("a.b.c");
- assert_eq!(node.node, vec!["a", "b", "c"]);
- assert_eq!(node.to_string(), "a.b.c");
- }
-
- #[test]
- fn test_node_kebab_case_conversion() {
- let node = Node::from("HelloWorld.FooBar");
- assert_eq!(node.node, vec!["hello-world", "foo-bar"]);
- }
-
- #[test]
- fn test_node_from_string() {
- let s = String::from("x.y");
- let node = Node::from(s);
- assert_eq!(node.node, vec!["x", "y"]);
- }
-
- #[test]
- fn test_node_join() {
- let node = Node::from("base").join("sub");
- assert_eq!(node.node, vec!["base", "sub"]);
- }
-
- #[test]
- fn test_node_join_multiple() {
- let node = Node::from("a").join("b").join("c");
- assert_eq!(node.to_string(), "a.b.c");
- }
-
- #[test]
- fn test_node_default_empty() {
- let node = Node::default();
- assert!(node.node.is_empty());
- assert_eq!(node.to_string(), "");
- }
-
- #[test]
- fn test_node_partial_eq() {
- let a = Node::from("a.b");
- let b = Node::from("a.b");
- let c = Node::from("a.c");
- assert_eq!(a, b);
- assert_ne!(a, c);
- }
-
- #[test]
- fn test_node_ord() {
- let a = Node::from("a");
- let b = Node::from("b");
- assert!(a < b);
- }
-
- #[test]
- fn test_node_join_appends_part() {
- let node = Node::from("existing");
- let joined = node.join("new-part");
- assert_eq!(joined.to_string(), "existing.new-part");
- }
-}