use nom::{branch::alt,
bytes::complete::{is_not, tag, take_until},
combinator::{map, opt},
sequence::{preceded, terminated},
IResult,
Parser};
use crate::{md_parser::constants::{CODE_BLOCK_END, CODE_BLOCK_START_PARTIAL, NEW_LINE},
CodeBlockLine,
CodeBlockLineContent,
List};
#[rustfmt::skip]
pub fn parse_block_code(input: &str) -> IResult<&str, List<CodeBlockLine<'_>>> {
let (remainder, (lang, code)) = (
parse_code_block_lang_to_eol,
parse_code_block_body_to_code_block_end,
)
.parse(input)?;
let (remainder, _) = opt(tag(NEW_LINE)).parse(remainder)?;
let acc = split_by_new_line(code);
Ok((remainder, convert_into_code_block_lines(lang, acc)))
}
#[rustfmt::skip]
fn parse_code_block_lang_to_eol(input: &str) -> IResult<&str, Option<&str>> {
alt((
map(
preceded(
tag(CODE_BLOCK_START_PARTIAL),
terminated(
is_not(NEW_LINE),
tag(NEW_LINE),
),
),
Some,
),
map(
(tag(CODE_BLOCK_START_PARTIAL), tag(NEW_LINE)),
|_| None,
),
)).parse(input)
}
#[rustfmt::skip]
fn parse_code_block_body_to_code_block_end(input: &str) -> IResult<&str, &str> {
let (remainder, output) = terminated(
take_until(CODE_BLOCK_END),
tag(CODE_BLOCK_END),
).parse(input)?;
Ok((remainder, output))
}
pub fn split_by_new_line(input: &str) -> Vec<&str> {
let mut acc: Vec<&str> = input.split('\n').collect();
if let Some(last_item) = acc.last()
&& last_item.is_empty()
{
acc.pop();
}
acc
}
pub fn convert_into_code_block_lines<'input>(
lang: Option<&'input str>,
lines: Vec<&'input str>,
) -> List<CodeBlockLine<'input>> {
let mut acc = List::with_capacity(lines.len() + 2);
acc += CodeBlockLine {
language: lang,
content: CodeBlockLineContent::StartTag,
};
for line in lines {
acc += CodeBlockLine {
language: lang,
content: CodeBlockLineContent::Text(line),
};
}
acc += CodeBlockLine {
language: lang,
content: CodeBlockLineContent::EndTag,
};
acc
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{assert_eq2, list};
#[test]
fn test_parse_codeblock_trailing_extra() {
let input = "```bash\npip install foobar\n````";
let lang = "bash";
let code_lines = vec!["pip install foobar"];
let (remainder, code_block_lines) = parse_block_code(input).unwrap();
assert_eq2!(remainder, "`");
assert_eq2!(
code_block_lines,
convert_into_code_block_lines(Some(lang), code_lines)
);
}
#[test]
fn test_convert_from_code_block_into_lines() {
{
let language = Some("rust");
let lines = vec![];
let expected = list![
CodeBlockLine {
language: Some("rust"),
content: CodeBlockLineContent::StartTag,
},
CodeBlockLine {
language: Some("rust"),
content: CodeBlockLineContent::EndTag,
},
];
let output = convert_into_code_block_lines(language, lines);
assert_eq2!(output, expected);
}
{
let language = Some("rust");
let lines = vec![""];
let expected = list![
CodeBlockLine {
language: Some("rust"),
content: CodeBlockLineContent::StartTag,
},
CodeBlockLine {
language: Some("rust"),
content: CodeBlockLineContent::Text(""),
},
CodeBlockLine {
language: Some("rust"),
content: CodeBlockLineContent::EndTag,
},
];
let output = convert_into_code_block_lines(language, lines);
assert_eq2!(output, expected);
}
{
let language = Some("rust");
let lines = vec!["let x = 1;"];
let expected = list![
CodeBlockLine {
language: Some("rust"),
content: CodeBlockLineContent::StartTag,
},
CodeBlockLine {
language: Some("rust"),
content: CodeBlockLineContent::Text("let x = 1;"),
},
CodeBlockLine {
language: Some("rust"),
content: CodeBlockLineContent::EndTag,
},
];
let output = convert_into_code_block_lines(language, lines);
assert_eq2!(output, expected);
}
{
let language = Some("rust");
let lines = vec!["let x = 1;", "let y = 2;"];
let expected = list![
CodeBlockLine {
language: Some("rust"),
content: CodeBlockLineContent::StartTag,
},
CodeBlockLine {
language: Some("rust"),
content: CodeBlockLineContent::Text("let x = 1;"),
},
CodeBlockLine {
language: Some("rust"),
content: CodeBlockLineContent::Text("let y = 2;"),
},
CodeBlockLine {
language: Some("rust"),
content: CodeBlockLineContent::EndTag,
},
];
let output = convert_into_code_block_lines(language, lines);
assert_eq2!(output, expected);
}
}
#[test]
fn test_parse_codeblock_split_by_eol() {
assert_eq2!(split_by_new_line("foobar\n"), vec!["foobar"]);
assert_eq2!(split_by_new_line("\n"), vec![""]);
assert_eq2!(split_by_new_line(""), Vec::<&str>::new());
assert_eq2!(split_by_new_line("foo\nbar\n"), vec!["foo", "bar"]);
assert_eq2!(split_by_new_line("\nfoo\nbar\n"), vec!["", "foo", "bar"]);
}
#[test]
fn test_parse_codeblock() {
{
let lang = "bash";
let code_lines = vec!["pip install foobar"];
let input = ["```bash", "pip install foobar", "```", ""].join("\n");
println!("{:#?}", &input);
let (remainder, code_block_lines) = parse_block_code(&input).unwrap();
assert_eq2!(remainder, "");
assert_eq2!(
code_block_lines,
convert_into_code_block_lines(Some(lang), code_lines)
);
}
{
let lang = "bash";
let code_lines = vec![];
let input = ["```bash", "```", ""].join("\n");
let (remainder, code_block_lines) = parse_block_code(&input).unwrap();
assert_eq2!(remainder, "");
assert_eq2!(
code_block_lines,
convert_into_code_block_lines(Some(lang), code_lines)
);
}
{
let lang = "bash";
let code_lines = vec![""];
let input = ["```bash", "", "```", ""].join("\n");
let (remainder, code_block_lines) = parse_block_code(&input).unwrap();
assert_eq2!(remainder, "");
assert_eq2!(
code_block_lines,
convert_into_code_block_lines(Some(lang), code_lines)
);
}
{
let lang = "python";
let code_lines = vec![
"import foobar",
"",
"foobar.pluralize('word') # returns 'words'",
"foobar.pluralize('goose') # returns 'geese'",
"foobar.singularize('phenomena') # returns 'phenomenon'",
];
let input = [
"```python",
"import foobar",
"",
"foobar.pluralize('word') # returns 'words'",
"foobar.pluralize('goose') # returns 'geese'",
"foobar.singularize('phenomena') # returns 'phenomenon'",
"```",
"",
]
.join("\n");
let (remainder, code_block_lines) = parse_block_code(&input).unwrap();
assert_eq2!(remainder, "");
assert_eq2!(
code_block_lines,
convert_into_code_block_lines(Some(lang), code_lines)
);
}
}
#[test]
fn test_parse_codeblock_no_language() {
let lang = None;
let code_lines = vec!["pip install foobar"];
let input = ["```", "pip install foobar", "```", ""].join("\n");
let (remainder, code_block_lines) = parse_block_code(&input).unwrap();
assert_eq2!(remainder, "");
assert_eq2!(
code_block_lines,
convert_into_code_block_lines(lang, code_lines)
);
}
}