Compare commits
3 commits
5abea8dec9
...
839fd164aa
Author | SHA1 | Date | |
---|---|---|---|
839fd164aa | |||
07daad708b | |||
b89934d3ac |
3 changed files with 162 additions and 150 deletions
146
src/convert.rs
Normal file
146
src/convert.rs
Normal file
|
@ -0,0 +1,146 @@
|
||||||
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
|
enum S {
|
||||||
|
None,
|
||||||
|
P,
|
||||||
|
Code,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn convert_document(markdown: &str) -> String {
|
||||||
|
let mut html = String::new();
|
||||||
|
let mut state = S::None;
|
||||||
|
|
||||||
|
for line in markdown.lines() {
|
||||||
|
if line.starts_with("```") {
|
||||||
|
if state == S::Code {
|
||||||
|
html += "</pre>\n";
|
||||||
|
state = S::None;
|
||||||
|
} else {
|
||||||
|
if state == S::P {
|
||||||
|
html += "</p>\n";
|
||||||
|
}
|
||||||
|
state = S::Code;
|
||||||
|
html += "<pre>\n";
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if state == S::Code {
|
||||||
|
html += line;
|
||||||
|
html += "\n";
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some((start, header)) = line.split_once(' ') {
|
||||||
|
let level = start.len();
|
||||||
|
if (1..=6).contains(&level) && start.chars().all(|c| c == '#') {
|
||||||
|
if state == S::P {
|
||||||
|
state = S::None;
|
||||||
|
html += "</p>\n";
|
||||||
|
}
|
||||||
|
let header = &convert_line(header);
|
||||||
|
html += &format!("<h{level}>{header}</h{level}>\n");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if state == S::P && line.is_empty() {
|
||||||
|
state = S::None;
|
||||||
|
html += "</p>\n";
|
||||||
|
} else if !line.is_empty() {
|
||||||
|
if state == S::None {
|
||||||
|
state = S::P;
|
||||||
|
html += "<p>\n";
|
||||||
|
}
|
||||||
|
html += &convert_line(line);
|
||||||
|
html += "<br>\n";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
html
|
||||||
|
}
|
||||||
|
|
||||||
|
fn convert_line(source: &str) -> String {
|
||||||
|
let mut out = String::new();
|
||||||
|
let mut is_em = false;
|
||||||
|
let mut is_b = false;
|
||||||
|
let mut is_code = false;
|
||||||
|
let mut is_ul = false;
|
||||||
|
let toggle = |state: bool, tag: &str| {
|
||||||
|
if state {
|
||||||
|
format!("<{tag}>")
|
||||||
|
} else {
|
||||||
|
format!("</{tag}>")
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut link: Option<(String, Option<String>)> = None;
|
||||||
|
|
||||||
|
let mut chars = source.chars().peekable();
|
||||||
|
while let Some(c) = chars.next() {
|
||||||
|
if let Some(link_c) = &mut link {
|
||||||
|
match link_c {
|
||||||
|
(link_text, None) => {
|
||||||
|
if c == ']' {
|
||||||
|
if chars.peek() == Some(&'(') {
|
||||||
|
_ = chars.next();
|
||||||
|
link_c.1 = Some(String::new());
|
||||||
|
} else {
|
||||||
|
out += &format!("[{link_text}]");
|
||||||
|
link = None;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
link_text.push(c);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
(link_text, Some(href)) => {
|
||||||
|
if c == ')' {
|
||||||
|
out += &format!("<a href=\"{href}\">{link_text}</a>");
|
||||||
|
link = None;
|
||||||
|
} else {
|
||||||
|
href.push(c);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if c == '[' {
|
||||||
|
link = Some((String::new(), None));
|
||||||
|
} else if c == '*' {
|
||||||
|
if chars.peek() == Some(&'*') {
|
||||||
|
_ = chars.next();
|
||||||
|
is_b = !is_b;
|
||||||
|
out += &toggle(is_b, "strong");
|
||||||
|
} else {
|
||||||
|
is_em = !is_em;
|
||||||
|
out += &toggle(is_em, "em");
|
||||||
|
}
|
||||||
|
} else if c == '`' {
|
||||||
|
is_code = !is_code;
|
||||||
|
out += &toggle(is_code, "code");
|
||||||
|
} else if c == '_' {
|
||||||
|
is_ul = !is_ul;
|
||||||
|
out += &toggle(is_ul, "u");
|
||||||
|
} else {
|
||||||
|
out.push(c);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let Some((link_text, href)) = link {
|
||||||
|
out += &format!("[{link_text}");
|
||||||
|
if let Some(href) = href {
|
||||||
|
out += &format!("]({href}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if is_em {
|
||||||
|
out += "</em>";
|
||||||
|
}
|
||||||
|
if is_b {
|
||||||
|
out += "</strong>";
|
||||||
|
}
|
||||||
|
if is_code {
|
||||||
|
out += "</code>";
|
||||||
|
}
|
||||||
|
if is_ul {
|
||||||
|
out += "</u>";
|
||||||
|
}
|
||||||
|
out
|
||||||
|
}
|
162
src/main.rs
162
src/main.rs
|
@ -6,9 +6,13 @@ use std::{
|
||||||
path::{Path, PathBuf},
|
path::{Path, PathBuf},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
mod convert;
|
||||||
|
use convert::convert_document;
|
||||||
|
|
||||||
const SRC_DIR: &str = "write";
|
const SRC_DIR: &str = "write";
|
||||||
const OUT_DIR: &str = "site";
|
const OUT_DIR: &str = "site";
|
||||||
const CONTENT_MARKER: &str = "CONTENT HERE";
|
const CONTENT_MARKER: &str = "{CONTENT}";
|
||||||
|
const FILENAME_MARKER: &str = "{FILENAME}";
|
||||||
|
|
||||||
const TEMPLATE_FILE: &str = "template.html";
|
const TEMPLATE_FILE: &str = "template.html";
|
||||||
const DEFAULT_TEMPLATE: &[u8] = include_bytes!("../template.html");
|
const DEFAULT_TEMPLATE: &[u8] = include_bytes!("../template.html");
|
||||||
|
@ -49,74 +53,23 @@ fn build_file(path: &Path) -> Result {
|
||||||
if path.extension().and_then(OsStr::to_str) == Some("md") {
|
if path.extension().and_then(OsStr::to_str) == Some("md") {
|
||||||
convert_file(path)
|
convert_file(path)
|
||||||
} else {
|
} else {
|
||||||
todo!("move file")
|
println!("file ignored: {}", path.display());
|
||||||
|
// TODO copy non dotfiles
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq)]
|
|
||||||
enum S {
|
|
||||||
None,
|
|
||||||
P,
|
|
||||||
Code,
|
|
||||||
}
|
|
||||||
|
|
||||||
fn convert_file(path: &Path) -> Result {
|
fn convert_file(path: &Path) -> Result {
|
||||||
let markdown = read_to_string(path)?;
|
|
||||||
let out_path = PathBuf::from(OUT_DIR).join(path.strip_prefix(SRC_DIR)?);
|
let out_path = PathBuf::from(OUT_DIR).join(path.strip_prefix(SRC_DIR)?);
|
||||||
let out_path = out_path.with_extension("html");
|
let out_path = out_path.with_extension("html");
|
||||||
|
|
||||||
let mut html = String::new();
|
let markdown = read_to_string(path)?;
|
||||||
let mut state = S::None;
|
let content = convert_document(&markdown);
|
||||||
|
|
||||||
for line in markdown.lines() {
|
|
||||||
if line.starts_with("```") {
|
|
||||||
if state == S::Code {
|
|
||||||
html += "</pre>\n";
|
|
||||||
state = S::None;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if state == S::P {
|
|
||||||
html += "</p>\n";
|
|
||||||
}
|
|
||||||
state = S::Code;
|
|
||||||
html += "<pre>\n";
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if state == S::Code {
|
|
||||||
html += line;
|
|
||||||
html += "\n";
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some((start, header)) = line.split_once(' ') {
|
|
||||||
let level = start.len();
|
|
||||||
if (1..=6).contains(&level) && start.chars().all(|c| c == '#') {
|
|
||||||
if state == S::P {
|
|
||||||
state = S::None;
|
|
||||||
html += "</p>\n";
|
|
||||||
}
|
|
||||||
let header = &convert_line(header);
|
|
||||||
html += &format!("<h{level}>{header}</h{level}>\n");
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if state == S::P && line.is_empty() {
|
|
||||||
state = S::None;
|
|
||||||
html += "</p>\n";
|
|
||||||
} else if !line.is_empty() {
|
|
||||||
if state == S::None {
|
|
||||||
state = S::P;
|
|
||||||
html += "<p>\n";
|
|
||||||
}
|
|
||||||
html += &convert_line(line);
|
|
||||||
html += "<br>\n";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let template = read_to_string(TEMPLATE_FILE)?;
|
let template = read_to_string(TEMPLATE_FILE)?;
|
||||||
let html = template.replacen(CONTENT_MARKER, &html, 1);
|
let filename = path.file_stem().unwrap().to_string_lossy().to_string();
|
||||||
|
let html = template
|
||||||
|
.replacen(CONTENT_MARKER, &content, 1)
|
||||||
|
.replace(FILENAME_MARKER, &filename);
|
||||||
|
|
||||||
DirBuilder::new()
|
DirBuilder::new()
|
||||||
.recursive(true)
|
.recursive(true)
|
||||||
|
@ -126,90 +79,3 @@ fn convert_file(path: &Path) -> Result {
|
||||||
println!("built {}", out_path.display());
|
println!("built {}", out_path.display());
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn convert_line(source: &str) -> String {
|
|
||||||
let mut out = String::new();
|
|
||||||
let mut is_em = false;
|
|
||||||
let mut is_b = false;
|
|
||||||
let mut is_code = false;
|
|
||||||
let mut is_ul = false;
|
|
||||||
let toggle = |state: bool, tag: &str| {
|
|
||||||
if state {
|
|
||||||
format!("<{tag}>")
|
|
||||||
} else {
|
|
||||||
format!("</{tag}>")
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
let mut link: Option<(String, Option<String>)> = None;
|
|
||||||
|
|
||||||
let mut chars = source.chars().peekable();
|
|
||||||
while let Some(c) = chars.next() {
|
|
||||||
if let Some(link_c) = &mut link {
|
|
||||||
match link_c {
|
|
||||||
(link_text, None) => {
|
|
||||||
if c == ']' {
|
|
||||||
if chars.peek() == Some(&'(') {
|
|
||||||
_ = chars.next();
|
|
||||||
link_c.1 = Some(String::new());
|
|
||||||
} else {
|
|
||||||
out += &format!("[{link_text}]");
|
|
||||||
link = None;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
link_text.push(c);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
(link_text, Some(href)) => {
|
|
||||||
if c == ')' {
|
|
||||||
out += &format!("<a href=\"{href}\">{link_text}</a>");
|
|
||||||
link = None;
|
|
||||||
} else {
|
|
||||||
href.push(c);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if c == '[' {
|
|
||||||
link = Some((String::new(), None));
|
|
||||||
} else if c == '*' {
|
|
||||||
if chars.peek() == Some(&'*') {
|
|
||||||
_ = chars.next();
|
|
||||||
is_b = !is_b;
|
|
||||||
out += &toggle(is_b, "strong");
|
|
||||||
} else {
|
|
||||||
is_em = !is_em;
|
|
||||||
out += &toggle(is_em, "em");
|
|
||||||
}
|
|
||||||
} else if c == '`' {
|
|
||||||
is_code = !is_code;
|
|
||||||
out += &toggle(is_code, "code");
|
|
||||||
} else if c == '_' {
|
|
||||||
is_ul = !is_ul;
|
|
||||||
out += &toggle(is_ul, "u");
|
|
||||||
} else {
|
|
||||||
out.push(c);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if let Some((link_text, href)) = link {
|
|
||||||
out += &format!("[{link_text}");
|
|
||||||
if let Some(href) = href {
|
|
||||||
out += &format!("]({href}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if is_em {
|
|
||||||
out += &toggle(false, "em");
|
|
||||||
}
|
|
||||||
if is_b {
|
|
||||||
out += &toggle(false, "b");
|
|
||||||
}
|
|
||||||
if is_code {
|
|
||||||
out += &toggle(false, "code");
|
|
||||||
}
|
|
||||||
if is_ul {
|
|
||||||
out += &toggle(false, "u");
|
|
||||||
}
|
|
||||||
out
|
|
||||||
}
|
|
||||||
|
|
|
@ -4,11 +4,11 @@
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
<title>TITLE HERE</title>
|
<title>Title - {FILENAME}</title>
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
CONTENT HERE
|
{CONTENT}
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
</html>
|
</html>
|
Loading…
Reference in a new issue