Skip to main content

socketry_markdown/util/
line_ending.rs

1// Released under the MIT License.
2// Copyright, 2022, by Bernhard Berger.
3// Copyright, 2022-2025, by Titus Wormer.
4// Copyright, 2026, by Samuel Williams.
5
6use alloc::{str::FromStr, string::String};
7
8/// Type of line endings in markdown.
9///
10/// Particularly when working with Windows, you might want to use
11/// `LineEnding::CarriageReturnLineFeed`.
12///
13/// ## Examples
14///
15/// ```
16/// use socketry_markdown::LineEnding;
17/// # fn main() {
18///
19/// // Use a CR + LF combination:
20/// let crlf = LineEnding::CarriageReturnLineFeed;
21/// # }
22/// ```
23#[derive(Clone, Debug, Default, Eq, PartialEq)]
24#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
25pub enum LineEnding {
26    /// Both a carriage return (`\r`) and a line feed (`\n`).
27    ///
28    /// ## Example
29    ///
30    /// ```markdown
31    /// a␍␊
32    /// b
33    /// ```
34    #[cfg_attr(feature = "serde", serde(rename = "\r\n"))]
35    CarriageReturnLineFeed,
36    /// Sole carriage return (`\r`).
37    ///
38    /// ## Example
39    ///
40    /// ```markdown
41    /// a␍
42    /// b
43    /// ```
44    #[cfg_attr(feature = "serde", serde(rename = "\r"))]
45    CarriageReturn,
46    /// Sole line feed (`\n`).
47    ///
48    /// ## Example
49    ///
50    /// ```markdown
51    /// a␊
52    /// b
53    /// ```
54    #[default]
55    #[cfg_attr(feature = "serde", serde(rename = "\n"))]
56    LineFeed,
57}
58
59// xxxxxxxxxxxxxxx
60impl LineEnding {
61    /// Turn the line ending into a [str].
62    #[must_use]
63    pub fn as_str(&self) -> &str {
64        match self {
65            LineEnding::CarriageReturnLineFeed => "\r\n",
66            LineEnding::CarriageReturn => "\r",
67            LineEnding::LineFeed => "\n",
68        }
69    }
70}
71
72impl FromStr for LineEnding {
73    type Err = String;
74
75    /// Turn a string into a line ending.
76    ///
77    /// ## Panics
78    ///
79    /// Panics if `code` is not `\r\n`, `\r`, or `\n`.
80    fn from_str(s: &str) -> Result<Self, Self::Err> {
81        match s {
82            "\r\n" => Ok(LineEnding::CarriageReturnLineFeed),
83            "\r" => Ok(LineEnding::CarriageReturn),
84            "\n" => Ok(LineEnding::LineFeed),
85            _ => Err("Expected CR, LF, or CRLF".into()),
86        }
87    }
88}
89
90#[cfg(test)]
91mod tests {
92    use super::*;
93
94    #[test]
95    fn test_line_ending() {
96        assert_eq!(
97            "\r".parse(),
98            Ok(LineEnding::CarriageReturn),
99            "should support turning a string into a carriage return"
100        );
101        assert_eq!(
102            LineEnding::CarriageReturn.as_str(),
103            "\r",
104            "should support turning a carriage return into a string"
105        );
106
107        assert_eq!(
108            "\n".parse(),
109            Ok(LineEnding::LineFeed),
110            "should support turning a string into a line feed"
111        );
112        assert_eq!(
113            LineEnding::LineFeed.as_str(),
114            "\n",
115            "should support turning a line feed into a string"
116        );
117
118        assert_eq!(
119            "\r\n".parse(),
120            Ok(LineEnding::CarriageReturnLineFeed),
121            "should support turning a string into a carriage return + line feed"
122        );
123        assert_eq!(
124            LineEnding::CarriageReturnLineFeed.as_str(),
125            "\r\n",
126            "should support turning a carriage return + line feed into a string"
127        );
128
129        assert_eq!(
130            "aaa".parse::<LineEnding>(),
131            Err("Expected CR, LF, or CRLF".into()),
132            "should error when parsing a non-eol"
133        );
134    }
135}