1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
//! Render Markdown or reStructuredText with syntax highlighting and image filtering similar to GitHub's
//!
//! ```rust
//! use render_readme::*;
//! let deadline = std::time::Instant::now() + std::time::Duration::from_secs(3);
//! let links_settings = LinksContext {
//!     base_url: None,
//!     nofollow: Links::Trusted,
//!     own_crate_name: None,
//!     link_own_crate_to_crates_io: true,
//!     link_fixer: None,
//! };
//! let r = Renderer::new(Some(Highlighter::new()));
//! let _ = r.markdown_str("# Hello", false, &links_settings, deadline);
//! let _ = r.page(&Markup::Markdown("# Hello".to_string()), &links_settings, true, deadline);
//! ```
//!
//! Markdown is implemented natively in Rust. RST rendering requires `rst2html` installed.
//!
#![allow(clippy::map_unwrap_or)]
#![allow(clippy::missing_errors_doc)]
#![allow(clippy::missing_panics_doc)]
#![allow(clippy::needless_raw_string_hashes)]
#![allow(clippy::redundant_closure_for_method_calls)]
#![allow(clippy::unused_self)]
#![allow(clippy::wildcard_imports)]

#[macro_use] extern crate html5ever;

mod filter;
mod highlight;
mod markup;
mod parser;

pub use crate::filter::*;
pub use crate::markup::*;
use std::path::Path;

pub use crate::highlight::Highlighter;
use serde::{Deserialize, Serialize};

/// Describe format/syntax used in the string
#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)]
pub enum Markup {
    Markdown(String),
    Rst(String),
    AsciiDoc(String),
    Html(String),
}

/// Readme page from a repository
#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)]
pub struct Readme {
    pub markup: Markup,
    pub base_url: Option<String>,
    pub base_image_url: Option<String>,
}

impl Readme {
    #[must_use] pub fn new(markup: Markup, base_url: Option<String>, base_image_url: Option<String>) -> Self {
        Self { markup, base_url, base_image_url }
    }

    #[must_use]
    pub fn base_urls(&self) -> Option<(&str, &str)> {
        match (self.base_url.as_deref(), self.base_image_url.as_deref()) {
            (Some(l), Some(i)) => Some((l, i)),
            (Some(l), None) | (None, Some(l)) => Some((l, l)),
            _ => None,
        }
    }

    #[must_use]
    pub fn raw_text(&self) -> &str {
        match &self.markup {
            Markup::Markdown(t) | Markup::Rst(t) | Markup::AsciiDoc(t) | Markup::Html(t) => t,
        }
    }
}

#[must_use]
pub fn is_readme_filename(path: &Path) -> bool {
    path.to_str().is_some_and(|s| {
        // that's not great; there are readme-windows, readme.ja.md and more
        let readme_filenames = &[
            "readme_en.md", "readme.en.md", // i18n attempt
            "readme.md", "readme.markdown", "readme.mdown", "readme", "readme.rst", "readme.adoc", "readme.asciidoc", "readme.txt", "readme.rest"];
        readme_filenames.iter().any(|f| f.eq_ignore_ascii_case(s.as_ref()))
    })
}