use {
base64,
clap::Parser,
core::panic,
std::{
fmt, fs,
fs::File,
io::{self, BufRead, Write},
path::{Path, PathBuf},
},
};
pub const COMMENT_MARKER: &str = "#";
pub const STD_OUTPUT_FMT: &str = "html";
pub const TAG_MARKER: &str = ".";
pub const SEPARATOR: &str = "---";
pub const TAG_FOOTER: &str = "footer";
pub const TAG_LOGO: &str = "logo";
pub const TAG_HEADING: &str = "heading";
pub const TAG_SUBHEADING: &str = "subheading";
pub const TAG_TABLE: &str = "table";
pub const TAG_ULIST: &str = "list";
pub const TAG_ORDLIST: &str = "ordlist";
pub const TAG_TEXT: &str = "text";
pub const TAG_MERMAID: &str = "mermaid";
pub const TAG_MERMAIDSCRIPT: &str = "mermaidscript";
pub const TAG_VIDEO: &str = "video";
pub const TAG_IMAGE: &str = "image";
pub const TAG_DRAFT: &str = "draft";
#[derive(Parser)]
pub struct Cli {
#[arg(short, long)]
input: PathBuf,
#[arg(short, long)]
output: Option<PathBuf>,
}
pub fn output(content: HTML, args: Cli) -> io::Result<()> {
let output_path = match args.output {
Some(mut output) => {
output.set_extension(STD_OUTPUT_FMT);
output
}
None => {
let mut output = args.input.clone();
output.set_extension(STD_OUTPUT_FMT);
output
}
};
let mut file = File::create(output_path)?;
writeln!(file, "{}", content)?;
Ok(())
}
pub fn input(args: &Cli) -> Result<Vec<String>, fmt::Error> {
if args.input.exists() && args.input.is_file() {
let file = match File::open(&args.input) {
Ok(file) => file,
Err(err) => panic!("Can't open file {}", err),
};
let reader = io::BufReader::new(file);
let lines: Result<Vec<String>, io::Error> = reader.lines().collect();
match lines {
Ok(lines) => Ok(lines),
Err(err) => {
eprintln!("Error reading lines {}", err);
Err(fmt::Error)
}
}
} else {
panic!("Error: no arguments passed.")
}
}
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
pub enum ElementNature {
Heading,
Subheading,
Text,
OrdList,
List,
Video,
Image,
Mermaid,
}
impl fmt::Display for ElementNature {
#![allow(unused)]
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(
f,
"\"{}\"",
match self {
Self::Heading => "heading",
Self::Text => "text",
Self::List => "list",
Self::OrdList => "ordered list",
Self::Image => "image",
Self::Mermaid => "mermaid",
Self::Subheading => "subheading",
Self::Video => "video",
}
);
Ok(())
}
}
pub struct Element {
pub nature: ElementNature,
pub content: String,
}
impl fmt::Display for Element {
#![allow(unused)]
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let nature = match self.nature {
ElementNature::Heading => "heading",
_ => "unknown",
};
write!(
f,
"content: \"{}\"... of nature:\"{}\"",
self.content[..20].to_string(),
nature
);
Ok(())
}
}
pub trait Organize {
fn organize(self) -> Self;
}
impl Organize for Vec<Element> {
fn organize(mut self) -> Self {
if self.len() < 1 {
self
} else {
self.sort_by_key(|e| e.nature.clone());
self
}
}
}
pub struct Slide {
pub content: Result<Vec<Element>, fmt::Error>,
pub draft: bool,
}
impl fmt::Display for Slide {
#![allow(unused)]
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let mut before = String::new();
match self.draft {
false => before = "<div class=\"slide\">".to_string(),
true => before = "<div class=\"slide, draft-slide\">".to_string(),
};
let mut fill = String::new();
match &self.content {
Ok(elements) => {
elements
.iter()
.for_each(|element| fill.push_str(&format!("{}", element.content)));
}
Err(_) => (),
};
write!(f, "{}{}</div>", before, fill);
Ok(())
}
}
pub trait IsComment {
fn is_comment(&self) -> bool;
}
impl IsComment for String {
fn is_comment(&self) -> bool {
if self.starts_with(COMMENT_MARKER) {
true
} else {
false
}
}
}
pub trait SplitOnTag {
fn split_on_tag(self) -> Vec<Vec<String>>;
}
impl SplitOnTag for Vec<String> {
fn split_on_tag(self) -> Vec<Vec<String>> {
let mut result = Vec::new();
let mut temp_group = Vec::new();
for s in self {
if s.starts_with(TAG_MARKER) {
if !temp_group.is_empty() {
result.push(temp_group);
}
temp_group = Vec::new();
}
temp_group.push(s);
}
if !temp_group.is_empty() {
result.push(temp_group);
}
result
}
}
pub trait CleanTag {
fn clean_tag(self) -> Self;
}
impl CleanTag for Vec<String> {
fn clean_tag(mut self) -> Self {
self[0] = self[0].replace(" ", "");
self
}
}
pub fn file_base64(file: String, tipo: &str) -> Result<String, fmt::Error> {
let file_data = fs::read(&file).expect("Media file passed to file_base64() not found.");
Ok(format!(
"data:{}/{};base64,{}",
tipo,
Path::new(&file)
.extension()
.expect(&format!("Error trying to set the filetype of {}", &file))
.to_str()
.ok_or(&format!("Error converting the path {} to string.", &file))
.expect(&format!("Error trying validate {} as a file path.", &file)),
base64::encode(&file_data)
))
}
pub fn is_element_ok(raw_element: &Vec<String>, reference: &str) -> Result<(), fmt::Error> {
if raw_element.len() < 2 {
eprintln!("A tag {} was not followed by its argument.", raw_element[0]); Err(fmt::Error)
} else if !(*raw_element[0] == format!("{}{}", TAG_MARKER, reference)) {
eprintln!("The tag \"{}\" is not valid.", raw_element[0]);
Err(fmt::Error)
} else {
Ok(())
}
}
pub fn text(raw_element: Vec<String>) -> Result<Element, fmt::Error> {
is_element_ok(&raw_element, TAG_TEXT)?;
let mut p = "<div class=\"element\"><p>".to_string();
p = p + &raw_element[1].to_string();
for raw_line in &raw_element[2..] {
p = p + &format!("<br>{}", raw_line);
}
Ok(Element {
nature: ElementNature::Text,
content: p + "</p></div>",
})
}
pub fn heading(raw_element: Vec<String>) -> Result<Element, fmt::Error> {
is_element_ok(&raw_element, TAG_HEADING)?;
let heading = format!(
"
<div class=\"element\"><h1>{}</h1></div>",
raw_element[1]
);
Ok(Element {
nature: ElementNature::Heading,
content: heading,
})
}
pub fn subheading(raw_element: Vec<String>) -> Result<Element, fmt::Error> {
is_element_ok(&raw_element, TAG_SUBHEADING)?;
let subheading = format!("<div class=\"element\"><h2>{}</h2></div>", raw_element[1]);
Ok(Element {
nature: ElementNature::Subheading,
content: subheading,
})
}
pub fn video(raw_element: Vec<String>) -> Result<Element, fmt::Error> {
is_element_ok(&raw_element, TAG_VIDEO)?;
let video_path: String = raw_element[1].clone();
let video = file_base64(video_path, "video");
let vid_content = format!("<video controls src=\"{}\"></video>", video?);
Ok(Element {
nature: ElementNature::Video,
content: vid_content,
})
}
pub fn table(raw_element: Vec<String>) -> Result<Element, fmt::Error> {
is_element_ok(&raw_element, TAG_TABLE)?;
let mut table = String::from("<div class=\"element\"><table>");
table += &format!(
"<thead><tr><th>{}</th></tr></thead>",
raw_element[1].replace("|", "</th><th>")
);
for line in &raw_element[2..] {
table += &format!(
"<tbody><tr><td>{}</td></tr></tbody>",
line.replace("|", "</td><td>")
);
}
table += "</table></div>";
Ok(Element {
nature: ElementNature::Video,
content: table,
})
}
pub fn footer(raw_element: Vec<String>) -> Result<String, fmt::Error> {
is_element_ok(&raw_element, TAG_FOOTER)?;
Ok(format!("<footer>{}</footer>", raw_element[1]))
}
pub fn logo(raw_element: Vec<String>) -> Result<String, fmt::Error> {
is_element_ok(&raw_element, TAG_LOGO)?;
let image = file_base64(raw_element[1].clone(), "image");
Ok(format!("<img class=\"logo\" src=\"{}\"></img>", image?))
}
pub fn image(raw_element: Vec<String>) -> Result<Element, fmt::Error> {
is_element_ok(&raw_element, TAG_IMAGE)?;
let image_path: String = raw_element[1].clone();
let image = file_base64(image_path, "image");
let mut _content = format!("<div class=\"element\"><img src=\"{}\">", image?);
if raw_element.len() > 2 {
let mut captions = String::from("<figcaption>");
captions = captions + &format!("{}", raw_element[2]);
for line in &raw_element[3..] {
captions = captions + &format!("<br>{}", line);
}
_content = _content + &captions + "</figcaption></img></div>";
} else {
_content = _content + "</img></div>";
};
Ok(Element {
nature: ElementNature::Image,
content: _content,
})
}
pub fn mermaid(raw_element: Vec<String>) -> Result<Element, fmt::Error> {
is_element_ok(&raw_element, TAG_MERMAID)?;
let mut content = String::new();
for raw_line in &raw_element[1..] {
content = content + &format!("{}", raw_line);
}
Ok(Element {
nature: ElementNature::Mermaid,
content: format!(
"<div class=\"element\"><pre class=\"mermaid\">{}</pre></div>",
&content
),
})
}
pub fn ulist(raw_element: Vec<String>) -> Result<Element, fmt::Error> {
is_element_ok(&raw_element, TAG_ULIST)?;
let mut out = String::from("<div class=\"element\"><ul>");
for raw_line in &raw_element[1..] {
out = out + &format!("<li>{}</li>", raw_line);
}
Ok(Element {
nature: ElementNature::List,
content: out + "</ul></div>",
})
}
pub fn ordlist(raw_element: Vec<String>) -> Result<Element, fmt::Error> {
is_element_ok(&raw_element, TAG_ORDLIST)?;
let mut out = "<div class=\"element\"><ol>".to_string();
for raw_line in &raw_element[1..] {
out = out + &format!("<li>{}</li>", raw_line);
}
Ok(Element {
nature: ElementNature::OrdList,
content: out + "</ol></div>",
})
}
pub struct HTML(pub String);
impl fmt::Display for HTML {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", &self.0)
}
}
pub fn render(
logo: Result<String, fmt::Error>,
footer: Result<String, fmt::Error>,
mermaid: bool,
slides: Vec<Slide>,
) -> Result<HTML, fmt::Error> {
let mut body: String = String::from("<body>");
if slides.len() < 1 {
panic!("Zero slides built.");
} else {
for slide in slides {
body = body + &format!("{}", slide);
}
};
body = body + "</body>";
let mut mermaid_script = String::new();
if mermaid {
mermaid_script = "<script type=\"module\">import mermaid from 'https://cdn.jsdelivr.net/npm/mermaid@11/dist/mermaid.esm.min.mjs';mermaid.initialize({ startOnLoad: true });</script>".to_string();
};
let foot = match footer {
Ok(foot) => foot,
_ => "".to_string(),
};
let logo_img = match logo {
Ok(logo) => logo,
_ => "".to_string(),
};
let script = "<script>".to_owned()
+ &String::from_utf8(include_bytes!("./script.js").to_vec())
.expect("Can't include \'script.js\' during compilation.")
+ "</script>";
let css = "<style>".to_owned()
+ &String::from_utf8(include_bytes!("./style.css").to_vec())
.expect("Can't include \'style.css\' during compilation.")
+ "</style>";
Ok(HTML(String::from(format!(
"<!DOCTYPE html>\n
<html>\n
<head>\n
{}{}{}{}
<div id=\"marcador\"></div>
<div id=\"popup\">
<p><span id=\"conteudo-popup\"></span></p>
</div></head>\n
{}{}
</html>",
&mermaid_script,
&css,
&foot,
&logo_img,
&body,
&script
))))
}