use std::fmt::Write;
use once_cell::sync::Lazy;
use percent_encoding::{utf8_percent_encode, NON_ALPHANUMERIC};
use regex::Regex;
use crate::comment::strip_comment_markers;
use crate::{StrExt, StringExt};
fn preprocess_formula(formula: &str) -> String {
const ARG_REGEX: &str = r"\s*\{([^}]*?)\}";
static MACROS: Lazy<Vec<(Regex, &str)>> = Lazy::new(|| {
vec![
(
Regex::new(&format!("\\\\matTT{}", ARG_REGEX.repeat(9))).unwrap(),
"\\[ \\left|\\begin{array}{ccc} $1 & $2 & $3\\\\ $4 & $5 & $6\\\\ $7 & $8 & $9 \\end{array}\\right| \\]",
),
(
Regex::new(&format!("\\\\fork{}", ARG_REGEX.repeat(4))).unwrap(),
"\\left\\{ \\begin{array}{l l} $1 & \\mbox{$2}\\\\ $3 & \\mbox{$4}\\\\ \\end{array} \\right.",
),
(
Regex::new(&format!("\\\\forkthree{}", ARG_REGEX.repeat(6))).unwrap(),
"\\left\\{ \\begin{array}{l l} $1 & \\mbox{$2}\\\\ $3 & \\mbox{$4}\\\\ $5 & \\mbox{$6}\\\\ \\end{array} \\right.",
),
(
Regex::new(&format!("\\\\forkfour{}", ARG_REGEX.repeat(8))).unwrap(),
"\\left\\{ \\begin{array}{l l} $1 & \\mbox{$2}\\\\ $3 & \\mbox{$4}\\\\ $5 & \\mbox{$6}\\\\ $7 & \\mbox{$8}\\\\ \\end{array} \\right.",
),
(
Regex::new(&format!("\\\\vecthree{}", ARG_REGEX.repeat(3))).unwrap(),
"\\begin{bmatrix} $1\\\\ $2\\\\ $3 \\end{bmatrix}",
),
(
Regex::new(&format!("\\\\vecthreethree{}", ARG_REGEX.repeat(9))).unwrap(),
"\\begin{bmatrix} $1 & $2 & $3\\\\ $4 & $5 & $6\\\\ $7 & $8 & $9 \\end{bmatrix}",
),
(
Regex::new(&format!("\\\\hdotsfor{ARG_REGEX}")).unwrap(),
"\\dots",
),
(
Regex::new(&format!("\\\\mathbbm{ARG_REGEX}")).unwrap(),
"\\mathbb{$1}",
),
(
Regex::new(&format!("\\\\bordermatrix{}", ARG_REGEX.repeat(9))).unwrap(),
"\\matrix{$1}",
),
]
});
let mut out = formula.to_string();
for (re, repl) in &*MACROS {
out.replace_in_place_regex(re, repl);
}
out
}
pub fn render_doc_comment(doc_comment: &str, prefix: &str, opencv_version: &str) -> String {
render_doc_comment_with_processor(doc_comment, prefix, opencv_version, |_| {})
}
pub fn render_doc_comment_with_processor(
doc_comment: &str,
prefix: &str,
opencv_version: &str,
mut post_processor: impl FnMut(&mut String),
) -> String {
let mut out = strip_comment_markers(doc_comment);
out.replace_in_place("\r\n", "\n");
static MODULE_TITLE_1: Lazy<Regex> = Lazy::new(|| Regex::new(r#"(?m)\s*@[}{].*$"#).unwrap());
static MODULE_TITLE_2: Lazy<Regex> = Lazy::new(|| Regex::new(r#"@defgroup [^ ]+ (.*)"#).unwrap());
static MODULE_TITLE_3: Lazy<Regex> = Lazy::new(|| Regex::new(r#"(?m)^.*?@addtogroup\s+.+"#).unwrap());
out.replace_in_place_regex(&MODULE_TITLE_1, "");
out.replace_in_place_regex(&MODULE_TITLE_2, r#"# $1"#);
out.replace_in_place_regex(&MODULE_TITLE_3, "");
let trimmed = out.trim();
if trimmed.len() != out.len() {
out = trimmed.to_string()
}
static BRIEF: Lazy<Regex> = Lazy::new(|| Regex::new(r#"@brief[ :]*"#).unwrap());
out.replace_in_place_regex(&BRIEF, "");
out.replace_in_place("@note", "\nNote:");
static CODE: Lazy<Regex> = Lazy::new(|| Regex::new(r#"@code(?: ?\{.+?})?"#).unwrap());
out.replace_in_place_regex(&CODE, "```C++");
out.replace_in_place("@endcode", "```\n");
static SNIPPET: Lazy<Regex> = Lazy::new(|| Regex::new(r#"@snippet\s+([\w/.]+)\s+([\w-]+)"#).unwrap());
out.replace_in_place_regex_cb(&SNIPPET, |s, caps| {
let (path_start, path_end) = caps.get(1).expect("Impossible");
let path = &s[path_start..path_end];
let (name_start, name_end) = caps.get(2).expect("Impossible");
if path.starts_with("samples/") {
Some(
format!(
"[{name}](https://github.com/opencv/opencv_contrib/blob/{version}/modules/hdf/{path}#L1)",
name = &s[name_start..name_end],
version = opencv_version,
path = path,
)
.into(),
)
} else {
Some(
format!(
"[{name}](https://github.com/opencv/opencv/blob/{version}/samples/cpp/tutorial_code/{path}#L1)",
name = &s[name_start..name_end],
version = opencv_version,
path = path,
)
.into(),
)
}
});
out.replace_in_place("'fps'", r#""fps""#);
out.replace_in_place("'cv::Exception'", r#""cv::Exception""#);
static SEE_ALSO_BLOCK: Lazy<Regex> = Lazy::new(|| Regex::new(r#"(?m)^\s*@(sa|see)\s+"#).unwrap());
static SEE_ALSO_INLINE: Lazy<Regex> = Lazy::new(|| Regex::new(r#"@(sa|see)\s+"#).unwrap());
if out.replacen_in_place_regex(&SEE_ALSO_BLOCK, 1, "## See also\n") {
out.replace_in_place_regex(&SEE_ALSO_INLINE, "");
} else {
out.replace_in_place_regex(&SEE_ALSO_INLINE, "see also: ");
}
static CITE: Lazy<Regex> = Lazy::new(|| Regex::new(r#"@cite\s+([\w:]+)"#).unwrap());
out.replace_in_place_regex(
&CITE,
&format!(
"[$1](https://docs.opencv.org/{opencv_version}/d0/de3/citelist.html#CITEREF_$1)"
),
);
static REF: Lazy<Regex> = Lazy::new(|| Regex::new(r#"@ref\s+([\w:]+)"#).unwrap());
out.replace_in_place_regex(&REF, "[$1]");
static IMAGE: Lazy<Regex> = Lazy::new(|| Regex::new(r#"!\[(.*?)]\((?:.*/)?(.+)?\)"#).unwrap());
out.replace_in_place_regex(&IMAGE, &format!("![$1](https://docs.opencv.org/{opencv_version}/$2)"));
static RETURNS: Lazy<Regex> = Lazy::new(|| Regex::new(r#".*?@returns?\s*"#).unwrap());
out.replace_in_place_regex(&RETURNS, "## Returns\n");
static PARAM_HEADER: Lazy<Regex> = Lazy::new(|| Regex::new(r#"(?m)^(.*?@param)"#).unwrap());
static PARAM: Lazy<Regex> = Lazy::new(|| Regex::new(r#".*?@param\s*(?:\[in]|(\[out]))?\s+(\w+) *(.*)"#).unwrap());
out.replacen_in_place_regex(&PARAM_HEADER, 1, "## Parameters\n$1");
out.replace_in_place_regex(&PARAM, "* $2:$1 $3");
static DEPRECATED: Lazy<Regex> = Lazy::new(|| Regex::new(r#".*?@deprecated\s+(.+)"#).unwrap());
let mut deprecated = None;
out.replace_in_place_regex_cb(&DEPRECATED, |out, caps| {
let (cap_start, cap_end) = caps.get(1).expect("Impossible");
let deprecated_msg = out[cap_start..cap_end].to_string();
let out = format!("\n**Deprecated**: {deprecated_msg}");
deprecated = Some(deprecated_msg);
Some(out.into())
});
static LEADING_DASH: Lazy<Regex> = Lazy::new(|| Regex::new(r#"(?m)^(\s*)-(\s{2,})"#).unwrap());
out.replace_in_place_regex(&LEADING_DASH, "$1*$2");
static BLOCK_FORMULA: Lazy<Regex> = Lazy::new(|| Regex::new(r#"(?s)\\f\[(.*?)\\f]"#).unwrap());
static INLINE_FORMULA: Lazy<Regex> = Lazy::new(|| Regex::new(r#"(?s)\\f\$(.*?)\\f\$"#).unwrap());
out.replace_in_place_regex_cb(&BLOCK_FORMULA, |out, caps| {
let (cap_start, cap_end) = caps.get(1).expect("Impossible");
let formula = preprocess_formula(&out[cap_start..cap_end]);
let encoded = utf8_percent_encode(&formula, NON_ALPHANUMERIC);
Some(format!("![block formula](https://latex.codecogs.com/png.latex?{encoded})").into())
});
out.replace_in_place_regex_cb(&INLINE_FORMULA, |out, caps| {
let (cap_start, cap_end) = caps.get(1).expect("Impossible");
let formula = preprocess_formula(&out[cap_start..cap_end]);
let encoded = utf8_percent_encode(&formula, NON_ALPHANUMERIC);
Some(format!("![inline formula](https://latex.codecogs.com/png.latex?{encoded})").into())
});
static URL: Lazy<Regex> = Lazy::new(|| {
Regex::new(r#"([^<"/(]|[^]]\(|^)(https?://[-a-zA-Z0-9@:%._+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b(?:[-a-zA-Z0-9()@:%_+.~#?&/=]*[-a-zA-Z0-9@:%_+~#?&/=])?)"#).unwrap()
});
out.replace_in_place_regex(&URL, "$1<$2>");
static ESCAPE: Lazy<Regex> = Lazy::new(|| Regex::new(r#"(?m)\\n$"#).unwrap());
out.replace_in_place_regex(&ESCAPE, "\n");
static INDENTS: Lazy<Regex> = Lazy::new(|| Regex::new(r#"(?m)^(\s{3}|\s{7}|\s{11}|\s{15}|\s{19})\s(\S)"#).unwrap());
out.replace_in_place_regex(&INDENTS, "$1$2");
post_processor(&mut out);
let mut out = if out.is_empty() || prefix.is_empty() {
out
} else {
out.lines_with_nl().fold(
String::with_capacity(out.len() + (prefix.len() + 1) * 128),
|mut out_prefixed, line| {
out_prefixed.push_str(prefix);
out_prefixed.push(' ');
out_prefixed + line
},
)
};
if let Some(deprecated) = deprecated {
write!(out, "\n#[deprecated = \"{deprecated}\"]").expect("write! to String shouldn't fail");
}
out
}
#[test]
fn test_render_doc_comment() {
assert_eq!("test", &render_doc_comment("/** test */", "", "master"));
assert_eq!("/// test", &render_doc_comment("// test", "///", "master"));
assert_eq!("//! test", &render_doc_comment("/*test */", "//!", "master"));
{
let comment = "\
/**
* line1
* line2
*/
";
let res = "\
/// line1
/// line2";
assert_eq!(res, &render_doc_comment(comment, "///", "master"));
}
{
let comment = "\
/*
line1
line2
*/
";
let res = "\
/// line1
/// line2";
assert_eq!(res, &render_doc_comment(comment, "///", "master"));
}
{
let comment = "\
/** line1
* line2
* line3
";
let res = "\
/// line1
/// line2
/// line3";
assert_eq!(res, &render_doc_comment(comment, "///", "master"));
}
{
let comment = "\
/* line1
* line2
line3
* line4
*/
";
let res = "\
/// line1
/// * line2
/// line3
/// * line4";
assert_eq!(res, &render_doc_comment(comment, "///", "master"));
}
{
let comment = "\
/**
* line1
* line2
line3
*/
";
let res = "\
/// line1
/// line2
/// line3";
assert_eq!(res, &render_doc_comment(comment, "///", "master"));
}
{
let comment = "\
/** line1
line2
line3*/
";
let res = "\
/// line1
/// line2
/// line3";
assert_eq!(res, &render_doc_comment(comment, "///", "master"));
}
{
let comment = "\
//!< line1
//!< line2
";
let res = "\
/// line1
/// line2";
assert_eq!(res, &render_doc_comment(comment, "///", "master"));
}
{
let comment = "\
/** line1
line2*/
/** line3 */
";
let res = "\
/// line1
/// line2
/// line3";
assert_eq!(res, &render_doc_comment(comment, "///", "master"));
}
{
let comment = "\
/** line1
line2
*/
";
let res = "\
/// line1
///\x20
/// line2";
assert_eq!(res, &render_doc_comment(comment, "///", "master"));
}
{
let comment = "\
/**
line1
\x20
line2
*/
";
let res = "\
/// line1
///\x20
/// line2";
assert_eq!(res, &render_doc_comment(comment, "///", "master"));
}
{
let comment = "\
/** line1
line2
*/
";
let res = "\
/// line1
///\x20
/// line2";
assert_eq!(res, &render_doc_comment(comment, "///", "master"));
}
{
let comment = "@deprecated test\r\ntest\r\n";
assert!(comment.contains('\r'));
let res = "\
///\x20
/// **Deprecated**: test
/// test
#[deprecated = \"test\"]";
assert_eq!(res, &render_doc_comment(comment, "///", "master"));
}
}