r3bl_tui 0.7.2

TUI library to build modern apps inspired by React, Elm, with Flexbox, CSS, editor component, emoji support, and more
Documentation
/*
 *   Copyright (c) 2023-2025 R3BL LLC
 *   All rights reserved.
 *
 *   Licensed under the Apache License, Version 2.0 (the "License");
 *   you may not use this file except in compliance with the License.
 *   You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 *   Unless required by applicable law or agreed to in writing, software
 *   distributed under the License is distributed on an "AS IS" BASIS,
 *   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *   See the License for the specific language governing permissions and
 *   limitations under the License.
 */

//! # Markdown Parser Module
//!
//! This module provides a comprehensive markdown parser with support for standard markdown
//! features as well as R3BL-specific extensions.
//!
//! ## Module Organization
//!
//! The parser is organized into logical modules based on the scope and granularity of parsing:
//!
//! ```text
//! tui/src/tui/md_parser/
//! ├── block/            # Multi-line block parsers
//! │   ├── parse_fenced_code_block.rs
//! │   └── parse_smart_list_block.rs
//! ├── single_line/      # All single-line operations
//! │   ├── parse_heading_in_single_line.rs
//! │   ├── parse_markdown_text_in_single_line.rs
//! │   ├── take_text_in_single_line.rs
//! │   └── take_text_between_in_single_line.rs
//! ├── fragment/         # Fragment parsers within a line
//! └── extended/         # R3BL extensions
//! ```
//!
//! ### Module Descriptions
//!
//! - **`block/`**: Contains parsers for multi-line markdown constructs such as fenced code blocks
//!   and smart lists. These parsers handle content that spans multiple lines.
//!
//! - **`single_line/`**: Contains parsers and utilities that operate on single lines of text.
//!   This includes heading parsing, general markdown text parsing, and utility functions for
//!   extracting text within line boundaries.
//!
//! - **`fragment/`**: Contains parsers for inline markdown elements within a single line,
//!   such as bold, italic, links, and inline code.
//!
//! - **`extended/`**: Contains parsers for R3BL-specific markdown extensions like metadata
//!   key-value pairs and other custom syntax.
//!
//! ## Parser Naming Conventions
//!
//! - `parse_<what>_block` - Multi-line block structure parsers
//! - `parse_<what>_in_single_line` - Single-line parsers
//! - `take_<what>_in_single_line` - Single-line text extraction utilities
//! - `parse_<what>` - General parsers (context determines scope)
//!
//! ## Main Entry Point
//!
//! The main entry point (function) for this Markdown parsing module is
//! [`parse_markdown()`].
//! - It takes a string slice.
//! - And returns a vector of [`MdElement`]s.
//!
//! This module contains a fully functional Markdown parser. This parser supports standard
//! Markdown syntax as well as some extensions that are added to make it work w/
//! [R3BL](https://r3bl.com) products.
//!
//! Here are some entry points into the codebase.
//!
//! 1. The main function [`parse_markdown()`] that does the parsing of a string slice into
//!    a [`MdDocument`]. The tests are provided alongside the code itself. And you can
//!    follow along to see how other smaller parsers are used to build up this big one
//!    that parses the whole of the Markdown document.
//! 2. The [`mod@md_parser_types`] contain all the types that are used to represent the
//!    Markdown document model, such as [`MdDocument`], [`MdElement`], [`MdLineFragment`]
//!    and all the other intermediate types & enums required for parsing.
//! 3. All the parsers related to parsing metadata specific for [R3BL](https://r3bl.com)
//!    applications, which are not standard Markdown can be found in the [`extended`] module,
//!    specifically [`parse_unique_kv_opt_eol`] and [`parse_csv_opt_eol`].
//! 4. All the parsers that are related to parsing the main "blocks" of Markdown, such as
//!    ordered lists, unordered lists, code blocks, can be found in the [`block`] module.
//! 5. All the parsers that parse within a single line of text (headings, general text)
//!    can be found in the [`single_line`] module.
//! 6. All the parsers that are related to parsing inline fragments of Markdown text, such as
//!    links, bold, italic, etc. can be found in the [`fragment`] module.
//!
//! ## Video and blog post on this
//!
//! You can read all about this parser in [this blog post on
//! developerlife.com](https://developerlife.com/2024/06/28/md-parser-rust-from-r3bl-tui/).
//! You can watch a [video](https://youtu.be/SbwvSHZRb1E) about this parser on the `YouTube`
//! developerlife.com channel.
//!
//! ## Note on Parser Selection
//!
//! This is the primary and only markdown parser used in `r3bl_tui`. After extensive benchmarking
//! and analysis, this parser has proven to be the most reliable and performant solution.
//!
//! Two experimental parsers (NG parser and Simple parser) were developed but have been archived
//! to the `r3bl-open-core-archive` repository for historical reference. See
//! `docs/parser_strategy_analysis.md` for the detailed analysis that led to this decision.
//!
//! To learn about nom fundamentals, here are some resources:
//! - Tutorial on nom parsing on [developerlife.com](https://developerlife.com/2023/02/20/guide-to-nom-parsing/).
//! - Video on nom parsing on [YouTube developerlife.com channel](https://youtu.be/v3tMwr_ysPg).
//!
//! ## Architecture and parsing order
//!
//! This diagram showcases the order in which the parsers are called and how they are
//! composed together to parse a Markdown document.
//!
//! <!--
//! diagram:
//! https://asciiflow.com/#/share/eJzdlL9qwzAQxl%2Fl0JRChhLo0Gz9M3Rop2YUCNUWsYgsGfkcxxhD6dyhQwh9ltKnyZNUtus0hAYrJaXQQyAZf%2F6d7rN0JdE8FmSsM6WGRPFCWDImJSULSsbnZ6MhJYVbjZoVigW6B0oSK42VWMB6%2BbxePv7T8UKpBojkNAJwlT5Bwm0qWMztLDS5HpxACTsR8wTQAEYCAmOtCHBX0aIacgvdTDHXxengG3331U8LWb0B3IWXygQzmHMrucZ9e4DPGlGiEmzOVSZcmb0xqeV99W3YfJoyJVP0ITu2k%2Fd617F5hpGx3viLVu7HDjkeYAlcO7n3vh%2Fqn8MiwUOpp8wkyIRR%2B9PctMJD2Kk7tujjy30tvHU6f3ZgQj9PrpywPYfe7O62sbr5sFxixIxtZpMh0yJ3Nek6%2B8S9%2F8q0h%2B114vpii71679jVMcgde6u%2FLv%2B6C%2F7eeG1cVCY%2FjnVdUFKR6gNnN4sV)
//! -->
//!
//! ```text
//! priority ┌────────────────────────────────────────────────────────────────────────┐
//!   high   │ parse_markdown() {                map to the correct                   │
//!     │    │   many0(                         ───────────────────►  MdBlock variant │
//!     │    │     parse_title_value()                                  Title         │
//!     │    │     parse_tags_list()                                    Tags          │
//!     │    │     parse_authors_list()                                 Authors       │
//!     │    │     parse_date_value()                                   Date          │
//!     │    │     parse_heading_in_single_line()                       Heading       │
//!     │    │     parse_smart_list_block()                             SmartList     │
//!     │    │     parse_fenced_code_block()                            CodeBlock     │
//!     │    │     parse_block_markdown_text_with_or_without_new_line() Text          │
//!     │    │   )                                                                    │
//!     ▼    │ }                                                                      │
//! priority └────────────────────────────────────────────────────────────────────────┘
//!   low
//! ```
//! The parsing strategy in most cases is to parse the most specific thing first and then
//! parse the more general thing later. We often use the existence of `\n` (or `eol`) to
//! decide how far forwards we need to go into the input. And sometimes `\n` doesn't exist
//! and we simply use the entire input (or end of input or `eoi`). You might see functions
//! that have these suffixes in their names. Another term you might see is
//! `with_or_without_new_line` which makes the parsing strategy explicit in the name.
//!
//! The nature of `nom` parsers is to simply error out when they don't match. And leave
//! the `input` untouched, so that another parser have a go at it again. The nature of
//! these parsing functions is kind of recursive in nature. So it's important identify
//! edge and `request_shutdown` cases up front before diving into the parsing logic. You
//! will see this used in parsers which look for something more specific, if its not
//! found, they error out, and allow less specific parsers to have a go at it, and so on.
//!
//! ## The priority of parsers
//!
//! As we drill down into the implementation further, we see that the parsers are
//! prioritized in the order of their specificity. The most specific parsers are called
//! first and the least specific parsers are called last. This is done to ensure that the
//! most specific parsers get a chance to parse the input first. And if they fail, then
//! the less specific parsers get a chance to parse the input.
//!
//! <!--
//! diagram:
//! https://asciiflow.com/#/share/eJytlFFuwjAMhq8S5QkkHtD2MjhLJCsNBqK6CUpTUYaQpp2h4iB7RDtNT7I0sK1ABYNhVapdJ1%2F%2F2G7X3MgM%2BdgURANOcoWOj%2Fla8FLw8ejleSD4KnhPo2HwPJY%2BBIIvpMsRErIqhUy6dGKXBposLLWfg3XxbgsPBpdA2mCvz9bs3IQwjGXSrIa9juxtFlmM7bVp07wVpk7OMjQ%2Bh8J4TYCWGnVodRB0nRWsWVZX7%2F9VtvmNHkBrRXVV1dVbvd0xSf7OIh4TI3X7cSjkdwUh99KFOsYGF2aCLlfWIaBzYE27zx20cOILtMbv4LS05QtUWpJ%2BxclVWiJV6nUYzC5ipMXNLmdN3X6u7e4Kl3DqQWdy9pAzR1rY3CnzZpqao0oTW4ax9zZkXHu6u7r7%2BSfaMTaxlnr9SFPSq3kYODop4Sl1QVIffgzGnvf2ROO%2BL4cnFz%2FPiyb4hm%2B%2BAFpUbMk%3D)
//! -->
//!
//! ```text
//! parse_block_markdown_text_with_or_without_new_line() {
//!     many0(
//!       parse_inline_fragments_until_eol_or_eoi()
//!         )   │
//!   }         │                                                                 ──map to the correct──►
//!             └─► alt(                                                          MdLineFragment variant
//!                  ▲ parse_fragment_starts_with_underscore_err_on_new_line()      Italic
//!                  │ parse_fragment_starts_with_star_err_on_new_line()            Bold
//!     specialized  │ parse_fragment_starts_with_backtick_err_on_new_line()        InlineCode
//!     parsers ────►│ parse_fragment_starts_with_left_image_err_on_new_line()      Image
//!                  │ parse_fragment_starts_with_left_link_err_on_new_line()       Link
//!                  │ parse_fragment_starts_with_checkbox_into_str()               Plain
//!                  ▼ parse_fragment_starts_with_checkbox_checkbox_into_bool()     Checkbox
//!     catch all────► parse_fragment_plain_text_no_new_line()                      Plain
//!     parser       )
//! ```
//!
//! The last one on the list in the diagram above is
//! [`parse_block_markdown_text_with_or_without_new_line()`]. Let's zoom into this
//! function and see how it is composed.
//!
//! ## The "catch all" parser, which is the most complicated, and the lowest priority
//!
//! The most complicated parser is the "catch all" parser or the "plain text" parser. This
//! parser is the last one in the chain and it simply consumes the rest of the input and
//! turns it into a `MdBlock::Text`. This parser is the most complicated because it has to
//! deal with all the edge cases and `request_shutdown` cases that other parsers have not
//! dealt with. Such as special characters like `` ` ``, `*`, `_`, etc. They are all
//! listed here:
//!
//! - If the input does not start with a special char in this `get_sp_char_set_2()`, then
//!   this is the "Normal case". In this case the input is split at the first occurrence
//!   of a special char in `get_sp_char_set_3()`. The "before" part is
//!   [`MdLineFragment::Plain`] and the "after" part is parsed again by a more specific
//!   parser.
//! - If the input starts with a special char in this `get_sp_char_set_2()` and it is not
//!   in the `get_sp_char_set_1()` with only 1 occurrence, then the behavior is different
//!   "Edge case -> Normal case". Otherwise the behavior is "Edge case -> Special case".
//!   - "Edge case -> Normal case" takes all the characters until `\n` or end of input and
//!     turns it into a [`MdLineFragment::Plain`].
//!   - "Edge case -> Special case" splits the `input` before and after the special char.
//!     The "before" part is turned into a [`MdLineFragment::Plain`] and the "after" part
//!     is parsed again by a more specific parser.
//!
//! The reason this parser gets called repeatedly is because it is the last one in the
//! chain. Its the lowest priority parser called by
//! [`parse_inline_fragments_until_eol_or_eoi()`], which itself is called:
//! 1. Repeatedly in a loop by [`parse_block_markdown_text_with_or_without_new_line()`].
//! 2. And by [`parse_block_markdown_text_with_checkbox_policy_with_or_without_new_line()`].

// Attach.
pub mod block;
pub mod convert_to_plain_text;
pub mod extended;
pub mod fragment;
pub mod md_parser_types;
pub mod parse_markdown;
pub mod single_line;

// Test modules
#[cfg(test)]
pub mod conformance_test_data;
#[cfg(test)]
pub mod parser_snapshot_tests;
#[cfg(test)]
pub mod parser_bench_tests;

// Re-export.
pub use block::*;
pub use convert_to_plain_text::*;
pub use extended::*;
pub use fragment::*;
pub use md_parser_types::*;
pub use parse_markdown::*;
pub use single_line::*;