aboutsummaryrefslogtreecommitdiffstats
path: root/src/util
diff options
context:
space:
mode:
Diffstat (limited to 'src/util')
-rw-r--r--src/util/line_ending.rs129
-rw-r--r--src/util/mdx.rs131
-rw-r--r--src/util/mod.rs2
3 files changed, 262 insertions, 0 deletions
diff --git a/src/util/line_ending.rs b/src/util/line_ending.rs
new file mode 100644
index 0000000..a018fe8
--- /dev/null
+++ b/src/util/line_ending.rs
@@ -0,0 +1,129 @@
+extern crate alloc;
+
+use alloc::{str::FromStr, string::String};
+
+/// Type of line endings in markdown.
+///
+/// Particularly when working with Windows, you might want to use
+/// `LineEnding::CarriageReturnLineFeed`.
+///
+/// ## Examples
+///
+/// ```
+/// use markdown::LineEnding;
+/// # fn main() {
+///
+/// // Use a CR + LF combination:
+/// let crlf = LineEnding::CarriageReturnLineFeed;
+/// # }
+/// ```
+#[derive(Clone, Debug, Default, Eq, PartialEq)]
+pub enum LineEnding {
+ /// Both a carriage return (`\r`) and a line feed (`\n`).
+ ///
+ /// ## Example
+ ///
+ /// ```markdown
+ /// a␍␊
+ /// b
+ /// ```
+ CarriageReturnLineFeed,
+ /// Sole carriage return (`\r`).
+ ///
+ /// ## Example
+ ///
+ /// ```markdown
+ /// a␍
+ /// b
+ /// ```
+ CarriageReturn,
+ /// Sole line feed (`\n`).
+ ///
+ /// ## Example
+ ///
+ /// ```markdown
+ /// a␊
+ /// b
+ /// ```
+ #[default]
+ LineFeed,
+}
+
+// xxxxxxxxxxxxxxx
+impl LineEnding {
+ /// Turn the line ending into a [str].
+ #[must_use]
+ pub fn as_str(&self) -> &str {
+ match self {
+ LineEnding::CarriageReturnLineFeed => "\r\n",
+ LineEnding::CarriageReturn => "\r",
+ LineEnding::LineFeed => "\n",
+ }
+ }
+}
+
+impl FromStr for LineEnding {
+ type Err = String;
+
+ /// Turn a string into a line ending.
+ ///
+ /// ## Panics
+ ///
+ /// Panics if `code` is not `\r\n`, `\r`, or `\n`.
+ fn from_str(s: &str) -> Result<Self, Self::Err> {
+ match s {
+ "\r\n" => Ok(LineEnding::CarriageReturnLineFeed),
+ "\r" => Ok(LineEnding::CarriageReturn),
+ "\n" => Ok(LineEnding::LineFeed),
+ _ => Err("Expected CR, LF, or CRLF".into()),
+ }
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ extern crate std;
+ use super::*;
+
+ #[test]
+ fn test_line_ending() {
+ assert_eq!(
+ "\r".parse(),
+ Ok(LineEnding::CarriageReturn),
+ "should support turning a string into a carriage return"
+ );
+ assert_eq!(
+ LineEnding::CarriageReturn.as_str(),
+ "\r",
+ "should support turning a carriage return into a string"
+ );
+
+ assert_eq!(
+ "\n".parse(),
+ Ok(LineEnding::LineFeed),
+ "should support turning a string into a line feed"
+ );
+ assert_eq!(
+ LineEnding::LineFeed.as_str(),
+ "\n",
+ "should support turning a line feed into a string"
+ );
+
+ assert_eq!(
+ "\r\n".parse(),
+ Ok(LineEnding::CarriageReturnLineFeed),
+ "should support turning a string into a carriage return + line feed"
+ );
+ assert_eq!(
+ LineEnding::CarriageReturnLineFeed.as_str(),
+ "\r\n",
+ "should support turning a carriage return + line feed into a string"
+ );
+
+ assert_eq!(
+ "aaa".parse::<LineEnding>(),
+ Err("Expected CR, LF, or CRLF".into()),
+ "should error when parsing a non-eol"
+ );
+ }
+}
diff --git a/src/util/mdx.rs b/src/util/mdx.rs
new file mode 100644
index 0000000..712b2c7
--- /dev/null
+++ b/src/util/mdx.rs
@@ -0,0 +1,131 @@
+extern crate alloc;
+
+use alloc::string::String;
+
+/// Signal used as feedback when parsing MDX ESM/expressions.
+#[derive(Clone, Debug)]
+pub enum Signal {
+ /// A syntax error.
+ ///
+ /// `markdown-rs` will crash with error message `String`, and convert the
+ /// `usize` (byte offset into `&str` passed to `MdxExpressionParse` or
+ /// `MdxEsmParse`) to where it happened in the whole document.
+ ///
+ /// ## Examples
+ ///
+ /// ```rust ignore
+ /// Signal::Error("Unexpected `\"`, expected identifier".into(), 1)
+ /// ```
+ Error(String, usize),
+ /// An error at the end of the (partial?) expression.
+ ///
+ /// `markdown-rs` will either crash with error message `String` if it
+ /// doesn’t have any more text, or it will try again later when more text
+ /// is available.
+ ///
+ /// ## Examples
+ ///
+ /// ```rust ignore
+ /// Signal::Eof("Unexpected end of file in string literal".into())
+ /// ```
+ Eof(String),
+ /// Done, successfully.
+ ///
+ /// `markdown-rs` knows that this is the end of a valid expression/esm and
+ /// continues with markdown.
+ ///
+ /// ## Examples
+ ///
+ /// ```rust ignore
+ /// Signal::Ok
+ /// ```
+ Ok,
+}
+
+/// Signature of a function that parses MDX ESM.
+///
+/// Can be passed as `mdx_esm_parse` in [`ParseOptions`][] to support
+/// ESM according to a certain grammar (typically, a programming language).
+pub type EsmParse = dyn Fn(&str) -> Signal;
+
+/// Expression kind.
+#[derive(Clone, Debug)]
+pub enum ExpressionKind {
+ /// Kind of expressions in prose.
+ ///
+ /// ```mdx
+ /// > | # {Math.PI}
+ /// ^^^^^^^^^
+ /// |
+ /// > | {Math.PI}
+ /// ^^^^^^^^^
+ /// ```
+ Expression,
+ /// Kind of expressions as attributes.
+ ///
+ /// ```mdx
+ /// > | <a {...b}>
+ /// ^^^^^^
+ /// ```
+ AttributeExpression,
+ /// Kind of expressions as attribute values.
+ ///
+ /// ```mdx
+ /// > | <a b={c}>
+ /// ^^^
+ /// ```
+ AttributeValueExpression,
+}
+
+/// Signature of a function that parses MDX expressions.
+///
+/// Can be passed as `mdx_expression_parse` in [`ParseOptions`][] to support
+/// expressions according to a certain grammar (typically, a programming
+/// language).
+///
+pub type ExpressionParse = dyn Fn(&str, &ExpressionKind) -> Signal;
+
+#[cfg(test)]
+mod tests {
+ extern crate std;
+ use super::*;
+ use alloc::boxed::Box;
+
+ #[test]
+ fn test_mdx_expression_parse() {
+ fn func(_value: &str, _kind: &ExpressionKind) -> Signal {
+ Signal::Ok
+ }
+
+ let func_accepting = |_a: Box<ExpressionParse>| true;
+
+ assert!(
+ matches!(func("a", &ExpressionKind::Expression), Signal::Ok),
+ "should expose an `ExpressionParse` type (1)"
+ );
+
+ assert!(
+ func_accepting(Box::new(func)),
+ "should expose an `ExpressionParse` type (2)"
+ );
+ }
+
+ #[test]
+ fn test_mdx_esm_parse() {
+ fn func(_value: &str) -> Signal {
+ Signal::Ok
+ }
+
+ let func_accepting = |_a: Box<EsmParse>| true;
+
+ assert!(
+ matches!(func("a"), Signal::Ok),
+ "should expose an `EsmParse` type (1)"
+ );
+
+ assert!(
+ func_accepting(Box::new(func)),
+ "should expose an `EsmParse` type (2)"
+ );
+ }
+}
diff --git a/src/util/mod.rs b/src/util/mod.rs
index f44e183..cb9a40b 100644
--- a/src/util/mod.rs
+++ b/src/util/mod.rs
@@ -8,7 +8,9 @@ pub mod encode;
pub mod gfm_tagfilter;
pub mod identifier;
pub mod infer;
+pub mod line_ending;
pub mod location;
+pub mod mdx;
pub mod mdx_collect;
pub mod normalize_identifier;
pub mod sanitize_uri;