use colored::{Color, ColoredString, Colorize};
use markdown::{mdast::*, to_mdast};
use memchr::memchr2;
pub trait ToLines {
fn to_lines(&self) -> Vec<ColoredString>;
}
struct CodeLine<'a> {
pub line: &'a str,
pub is_comment: bool,
pub is_comment_start: bool,
pub other: Option<&'a str>,
}
impl<'a> CodeLine<'a> {
pub fn new(line: &'a str) -> Self {
if line.starts_with("//") || line.starts_with("#") {
return Self::start_comment(line);
} else if line.contains("//") || line.contains("#") {
return Self::contain_comment(line);
} else {
return Self::code(line);
}
}
fn code(line: &'a str) -> Self {
Self {
line,
is_comment: false,
is_comment_start: false,
other: None,
}
}
fn start_comment(line: &'a str) -> Self {
Self {
line,
is_comment: true,
is_comment_start: true,
other: None,
}
}
fn contain_comment(line: &'a str) -> Self {
if line.contains("//") {
let offset = memchr2(b'/', b'/', line.as_bytes()).unwrap();
let (line, other) = line.split_at(offset);
return Self {
line,
is_comment: true,
is_comment_start: false,
other: Some(other),
};
} else if line.contains("#") {
let offset = memchr2(b'#', b'#', line.as_bytes()).unwrap();
let (line, other) = line.split_at(offset);
return Self {
line,
is_comment: true,
is_comment_start: false,
other: Some(other),
};
}
return Self {
line,
is_comment: false,
is_comment_start: false,
other: None,
};
}
}
impl<'a> ToLines for CodeLine<'a> {
fn to_lines(&self) -> Vec<ColoredString> {
if self.is_comment && self.is_comment_start {
return vec![self.line.dimmed().italic(), "\n".into()];
}
if self.is_comment && !self.is_comment_start {
return vec![
self.line.cyan(),
self.other.unwrap().italic().dimmed(),
"\n".into(),
];
}
return vec![self.line.cyan(), "\n".into()];
}
}
fn process_code<'a>(code: &'a str) -> Vec<ColoredString> {
let lines: Vec<&'a str> = code.split("\n").collect();
let mut code_lines = vec![];
for line in lines {
let line = line.trim();
code_lines.push(CodeLine::new(line));
}
code_lines.iter().map(|x| x.to_lines()).flatten().collect()
}
impl ToLines for Node {
fn to_lines(&self) -> Vec<ColoredString> {
let mut lines = match self {
Node::Paragraph(paragraphs) => {
let lines = paragraphs
.children
.iter()
.flat_map(|x| x.to_lines())
.collect();
lines
}
Node::Heading(h) => {
let mut lines: Vec<_> = h
.children
.iter()
.flat_map(|x| {
x.to_lines()
.iter()
.map(|x| x.clone().green().bold())
.collect::<Vec<_>>()
})
.collect();
let level = h.depth as usize;
if level > 0 {
lines.insert(0, "#".repeat(level).green().bold());
lines.insert(1, " ".into());
}
lines
}
Node::Code(code) => {
let mut lines = vec![];
let mut have_filename = 0;
let language = &code.lang;
if let Some(lang) = language {
let pos = lang.find("title=");
if pos.is_some() {
let filename = lang.split("title=").nth(1);
if let Some(filename) = filename {
let filename = filename.trim();
let filename = filename.trim_matches('"');
let filename = filename.trim_matches('\'');
lines.push(filename.cyan().bold());
lines.push("\n".into());
lines.push("-".repeat(filename.len()).cyan());
lines.push("\n".into());
have_filename = filename.len();
}
}
}
let code_lines = process_code(&code.value);
lines.extend(code_lines);
if have_filename > 0 {
lines.push("-".repeat(have_filename).cyan());
}
lines
}
Node::Blockquote(b) => {
let mut lines: Vec<_> = b
.children
.iter()
.flat_map(|x| {
let mut plines = x.to_lines();
plines.pop();
plines
.iter()
.map(|x| x.clone().dimmed().italic())
.collect::<Vec<_>>()
})
.collect();
lines.insert(0, "> ".dimmed());
lines
}
Node::Text(t) => vec![t.value.color(Color::Blue)],
Node::List(items) if !items.ordered => {
let mut lines = vec![];
for item in &items.children {
lines.push("* ".blue());
lines.extend(item.to_lines());
lines.push("\n".into());
}
lines.pop();
lines
}
Node::Break(_) => vec![String::from("---").color(Color::Blue)],
Node::List(items) if items.ordered => {
let mut lines = vec![];
for (index, item) in items.children.iter().enumerate() {
lines.push(format!("{:}. ", index + 1).blue());
lines.extend(item.to_lines());
lines.push("\n".into());
}
lines.pop();
lines
}
Node::ListItem(item) => {
let mut lines = vec![];
for line in &item.children {
lines.extend(line.to_lines());
}
lines
}
Node::Strong(s) => {
let lines = s
.children
.iter()
.flat_map(|x| x.to_lines())
.collect::<Vec<_>>();
let lines = lines.iter().map(|x| x.clone().bold()).collect::<Vec<_>>();
lines
}
Node::Emphasis(e) => {
let lines = e
.children
.iter()
.flat_map(|x| x.to_lines())
.collect::<Vec<_>>();
let lines = lines.iter().map(|x| x.clone().italic()).collect::<Vec<_>>();
lines
}
Node::Link(link) => {
let name = &link.title;
let url = &link.url;
let name = if name.is_none() {
url.clone()
} else {
name.clone().unwrap()
};
let mut lines = vec![];
lines.push(name.color(Color::Blue).bold());
lines.push(format!(" ({})", url).dimmed());
lines
}
Node::Image(i) => {
let mut lines = vec![];
lines.push(i.alt.color(Color::Blue).bold());
lines.push(format!(" ({})", i.url).dimmed());
lines
}
Node::InlineCode(c) => {
let mut lines = vec![];
lines.push(c.value.color(Color::Blue).bold());
lines
}
_ => {
let lines = vec![];
lines
}
};
lines.push("\n\n".into());
lines
}
}
pub fn parse(markdown: &str) -> Vec<ColoredString> {
let options = markdown::ParseOptions::default();
let blocks = to_mdast(markdown, &options);
let mut lines: Vec<_> = blocks.into_iter().flat_map(|b| b.to_lines()).collect();
lines.pop();
lines
}