Compare commits

..

3 Commits

Author SHA1 Message Date
2d4f611de1 begun development for single file generation
All checks were successful
Test the running changes / Test (push) Successful in 40s
2025-11-26 19:31:21 +02:00
1c7504e3e0 added special character escaping to md
All checks were successful
Test the running changes / Test (push) Successful in 42s
2025-11-23 22:31:24 +02:00
7d602fffba added cargo-tarpaulin to flake, added some tests to cracked_md
All checks were successful
Test the running changes / Test (push) Successful in 40s
2025-11-23 20:52:05 +02:00
8 changed files with 104 additions and 18 deletions

1
.gitignore vendored
View File

@@ -3,3 +3,4 @@ site/
target/ target/
result result
tarpaulin-report.html

View File

@@ -16,15 +16,10 @@ pub enum Block {
language: Option<String>, language: Option<String>,
content: String, content: String,
}, },
List(Vec<ListItem>), List(Vec<Block>),
Quote(Vec<Block>), Quote(Vec<Block>),
} }
#[derive(Debug, Clone, PartialEq)]
pub struct ListItem {
pub blocks: Vec<Block>,
}
#[derive(Debug, Clone, PartialEq)] #[derive(Debug, Clone, PartialEq)]
pub enum Inline { pub enum Inline {
Text(String), Text(String),

View File

@@ -133,6 +133,20 @@ mod test {
); );
} }
#[test]
fn code_block_content_after_end() {
let md = "```\necho hello\n```abc";
let doc_res = parse(md);
assert!(doc_res.is_err());
}
#[test]
fn code_block_no_terminating() {
let md = "```\nabc\n";
let doc_res = parse(md);
assert!(doc_res.is_err());
}
#[test] #[test]
fn rust_code_block() { fn rust_code_block() {
let md = "```rust\nfn main() {\n\tprintln!(\"Hello world!\");\n}\n```"; let md = "```rust\nfn main() {\n\tprintln!(\"Hello world!\");\n}\n```";

View File

@@ -48,6 +48,11 @@ pub fn parse_blocks(input: &str) -> Result<Vec<Block>, MdParseError> {
} }
*/ */
// unordered list TODO
if line_chars.parse_str("- ") {
todo!()
}
// code // code
if line_chars.parse_str("```") { if line_chars.parse_str("```") {
let lang_line: String = line_chars.collect(); let lang_line: String = line_chars.collect();

View File

@@ -36,12 +36,18 @@ pub fn parse_inlines(input: &str) -> Result<Vec<Inline>, MdParseError> {
_ => { _ => {
let mut text = String::new(); let mut text = String::new();
text.push(c); text.push(c);
let mut escaped = false;
while let Some(&nc) = chars.peek() { while let Some(&nc) = chars.peek() {
if matches!(nc, '*' | '_' | '`' | '[') { if matches!(nc, '*' | '_' | '`' | '[') && !escaped {
break; break;
} }
let c = chars.next().ok_or(MdParseError::new("a character", ""))?; let next_c = chars.next().ok_or(MdParseError::new("a character", ""))?;
text.push(c); if next_c == '\\' && !escaped {
escaped = true;
} else {
escaped = false;
text.push(next_c);
}
} }
inlines.push(Inline::Text(text)); inlines.push(Inline::Text(text));
} }
@@ -69,7 +75,14 @@ fn collect_until<I: Iterator<Item = char>>(
mod test { mod test {
use crate::ast::Inline; use crate::ast::Inline;
use super::parse_inlines; use super::{collect_until, parse_inlines};
#[test]
fn collect_until_without_end() {
let mut s = "abcdef".chars().peekable();
let res = collect_until(&mut s, '.');
assert!(res.is_err());
}
#[test] #[test]
fn bold_text() { fn bold_text() {
@@ -128,4 +141,43 @@ mod test {
] ]
); );
} }
#[test]
fn single_hyperlink() {
let md = "a link to [my site](https://example.com)";
let inl = parse_inlines(md).unwrap();
assert_eq!(
inl,
vec![
Inline::Text("a link to ".to_string()),
Inline::Link {
text: vec![Inline::Text("my site".to_string())],
href: "https://example.com".to_string()
}
]
);
}
#[test]
fn hyperlink_without_link() {
let md = "[abc]";
let inl = parse_inlines(md);
assert!(inl.is_err());
}
#[test]
fn escape_brackets() {
let md = r"some \[text\]";
let inl = parse_inlines(md).unwrap();
assert_eq!(inl, vec![Inline::Text("some [text]".to_string())]);
}
#[test]
fn escape_escape() {
let md = r"backslash \\";
let inl = parse_inlines(md).unwrap();
assert_eq!(inl, vec![Inline::Text(r"backslash \".to_string())]);
}
} }

View File

@@ -25,6 +25,7 @@
cargo-nextest cargo-nextest
cargo-expand cargo-expand
cargo-watch cargo-watch
cargo-tarpaulin
]; ];
}; };
} }

View File

@@ -10,14 +10,17 @@ use std::net::Ipv4Addr;
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
pub enum Command { pub enum Command {
Generate { force: bool }, Generate { force: bool, single: bool },
Serve { addr: Ipv4Addr, port: u16 }, Serve { addr: Ipv4Addr, port: u16 },
Init, Init,
} }
impl Default for Command { impl Default for Command {
fn default() -> Self { fn default() -> Self {
Self::Generate { force: true } Self::Generate {
force: true,
single: false,
}
} }
} }
@@ -67,10 +70,18 @@ impl TryFrom<Args> for Command {
comm = Command::Init; comm = Command::Init;
} }
// `gravel` command // `gravel` command
else if let Some(a) = value.next() { for a in value {
Err(Error::CommandLineArgsParse(format!( match a.as_str() {
"Unexpected argument: `{a}`" "-s" => {
)))?; comm = Command::Generate {
force: true,
single: true,
}
}
_ => Err(Error::CommandLineArgsParse(format!(
"Unknown argument: `{a}`"
)))?,
}
} }
Ok(comm) Ok(comm)

View File

@@ -15,9 +15,16 @@ fn run() -> Result<(), Error> {
let conf = ProgramConfig::new("gravel.toml", std::env::args())?; let conf = ProgramConfig::new("gravel.toml", std::env::args())?;
match conf.command { match conf.command {
Command::Init => todo!("project init not implemented"), Command::Init => todo!("project init"),
Command::Serve { addr, port } => serve(addr, port, conf.outdir)?, Command::Serve { addr, port } => serve(addr, port, conf.outdir)?,
Command::Generate { force } => generate(&conf.indir, &conf.outdir, force)?, Command::Generate {
force,
single: false,
} => generate(&conf.indir, &conf.outdir, force)?,
Command::Generate {
force: _f,
single: true,
} => todo!("single file generation"),
} }
Ok(()) Ok(())
} }