use turbovault_core::{Callout, Frontmatter, Heading, Link, Tag, TaskItem};
use crate::engine::ParseEngine;
#[derive(Debug, Clone)]
pub struct ParseOptions {
pub parse_frontmatter: bool,
pub parse_wikilinks: bool,
pub parse_markdown_links: bool,
pub parse_headings: bool,
pub parse_tasks: bool,
pub parse_callouts: bool,
pub parse_tags: bool,
pub full_callouts: bool,
}
impl Default for ParseOptions {
fn default() -> Self {
Self::all()
}
}
impl ParseOptions {
pub fn all() -> Self {
Self {
parse_frontmatter: true,
parse_wikilinks: true,
parse_markdown_links: true,
parse_headings: true,
parse_tasks: true,
parse_callouts: true,
parse_tags: true,
full_callouts: false,
}
}
pub fn none() -> Self {
Self {
parse_frontmatter: false,
parse_wikilinks: false,
parse_markdown_links: false,
parse_headings: false,
parse_tasks: false,
parse_callouts: false,
parse_tags: false,
full_callouts: false,
}
}
pub fn treemd() -> Self {
Self {
parse_frontmatter: false,
parse_wikilinks: true,
parse_markdown_links: true,
parse_headings: true,
parse_tasks: false,
parse_callouts: true,
parse_tags: false,
full_callouts: true, }
}
pub fn links_only() -> Self {
Self {
parse_frontmatter: false,
parse_wikilinks: true,
parse_markdown_links: true,
parse_headings: false,
parse_tasks: false,
parse_callouts: false,
parse_tags: false,
full_callouts: false,
}
}
pub fn with_frontmatter(mut self) -> Self {
self.parse_frontmatter = true;
self
}
pub fn with_full_callouts(mut self) -> Self {
self.full_callouts = true;
self
}
}
#[derive(Debug, Clone, Default)]
pub struct ParsedContent {
pub frontmatter: Option<Frontmatter>,
pub headings: Vec<Heading>,
pub wikilinks: Vec<Link>,
pub embeds: Vec<Link>,
pub markdown_links: Vec<Link>,
pub tags: Vec<Tag>,
pub tasks: Vec<TaskItem>,
pub callouts: Vec<Callout>,
}
impl ParsedContent {
pub fn parse(content: &str) -> Self {
Self::parse_with_options(content, ParseOptions::all())
}
pub fn parse_with_options(content: &str, opts: ParseOptions) -> Self {
let engine = ParseEngine::new(content);
let result = engine.parse(&opts);
Self {
frontmatter: result.frontmatter,
headings: result.headings,
wikilinks: result.wikilinks,
embeds: result.embeds,
markdown_links: result.markdown_links,
tags: result.tags,
tasks: result.tasks,
callouts: result.callouts,
}
}
pub fn all_links(&self) -> impl Iterator<Item = &Link> {
self.wikilinks
.iter()
.chain(self.embeds.iter())
.chain(self.markdown_links.iter())
}
pub fn has_links(&self) -> bool {
!self.wikilinks.is_empty() || !self.embeds.is_empty() || !self.markdown_links.is_empty()
}
pub fn link_count(&self) -> usize {
self.wikilinks.len() + self.embeds.len() + self.markdown_links.len()
}
pub fn is_empty(&self) -> bool {
self.frontmatter.is_none()
&& self.headings.is_empty()
&& self.wikilinks.is_empty()
&& self.embeds.is_empty()
&& self.markdown_links.is_empty()
&& self.tags.is_empty()
&& self.tasks.is_empty()
&& self.callouts.is_empty()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_parse_complete() {
let content = r#"---
title: Test Note
tags: [test]
---
# Heading 1
This has [[WikiLink]] and [markdown](url).
## Heading 2
- [ ] Task 1
- [x] Task 2 #tag
> [!NOTE]
> Callout content
![[image.png]]
"#;
let parsed = ParsedContent::parse(content);
assert!(parsed.frontmatter.is_some());
assert_eq!(parsed.headings.len(), 2);
assert_eq!(parsed.wikilinks.len(), 1);
assert_eq!(parsed.markdown_links.len(), 1);
assert_eq!(parsed.tasks.len(), 2);
assert_eq!(parsed.tags.len(), 1);
assert_eq!(parsed.callouts.len(), 1);
assert_eq!(parsed.embeds.len(), 1);
}
#[test]
fn test_all_links() {
let content = "[[wiki]] and [md](url) and ![[embed]]";
let parsed = ParsedContent::parse(content);
assert_eq!(parsed.all_links().count(), 3);
assert_eq!(parsed.link_count(), 3);
assert!(parsed.has_links());
}
#[test]
fn test_empty_content() {
let parsed = ParsedContent::parse("");
assert!(parsed.frontmatter.is_none());
assert!(parsed.headings.is_empty());
assert!(!parsed.has_links());
assert!(parsed.is_empty());
}
#[test]
fn test_parse_options_none() {
let content = "# Title\n\n[[Link]] #tag";
let parsed = ParsedContent::parse_with_options(content, ParseOptions::none());
assert!(parsed.frontmatter.is_none());
assert!(parsed.headings.is_empty());
assert!(parsed.wikilinks.is_empty());
assert!(parsed.tags.is_empty());
}
#[test]
fn test_parse_options_links_only() {
let content = "# Title\n\n[[Link]] #tag";
let parsed = ParsedContent::parse_with_options(content, ParseOptions::links_only());
assert!(parsed.headings.is_empty()); assert_eq!(parsed.wikilinks.len(), 1); assert!(parsed.tags.is_empty()); }
#[test]
fn test_parse_options_treemd() {
let content = r#"# Title
[[Link]] #tag
> [!NOTE] Title
> Content here
"#;
let parsed = ParsedContent::parse_with_options(content, ParseOptions::treemd());
assert_eq!(parsed.headings.len(), 1); assert_eq!(parsed.wikilinks.len(), 1); assert!(parsed.tags.is_empty()); assert_eq!(parsed.callouts.len(), 1); assert_eq!(parsed.callouts[0].content, "Content here"); }
#[test]
fn test_full_callouts() {
let content = r#"> [!WARNING] Important
> Line 1
> Line 2"#;
let simple = ParsedContent::parse_with_options(content, ParseOptions::all());
let full =
ParsedContent::parse_with_options(content, ParseOptions::all().with_full_callouts());
assert!(simple.callouts[0].content.is_empty()); assert_eq!(full.callouts[0].content, "Line 1\nLine 2"); }
#[test]
fn test_frontmatter_parsing() {
let content = r#"---
title: Test
author: Alice
---
Content here"#;
let parsed = ParsedContent::parse(content);
let fm = parsed.frontmatter.unwrap();
assert_eq!(fm.data.get("title").and_then(|v| v.as_str()), Some("Test"));
assert_eq!(
fm.data.get("author").and_then(|v| v.as_str()),
Some("Alice")
);
}
#[test]
fn test_position_tracking() {
let content = "Line 1\n[[Link]] on line 2";
let parsed = ParsedContent::parse(content);
assert_eq!(parsed.wikilinks[0].position.line, 2);
assert_eq!(parsed.wikilinks[0].position.column, 1);
}
#[test]
fn test_code_block_awareness() {
let content = r#"
Normal [[Valid Link]] here.
```rust
// This is a code block
let link = "[[Fake Link Inside Code]]";
```
Also valid: [[Another Valid Link]]
"#;
let parsed = ParsedContent::parse(content);
assert_eq!(parsed.wikilinks.len(), 2);
assert_eq!(parsed.wikilinks[0].target, "Valid Link");
assert_eq!(parsed.wikilinks[1].target, "Another Valid Link");
}
}