use super::{Formatter, PathDisplayMode};
use crate::scanner::{FileNode, TreeStats};
use anyhow::Result;
use marqant::Marqant as MarqantCore;
use std::collections::HashMap;
use std::io::Write;
use std::path::Path;
pub struct MarqantFormatter {
no_emoji: bool,
}
impl MarqantFormatter {
pub fn new(_path_mode: PathDisplayMode, no_emoji: bool) -> Self {
Self { no_emoji }
}
pub fn compress_markdown(content: &str) -> Result<String> {
MarqantCore::compress_markdown(content)
}
pub fn compress_markdown_with_flags(content: &str, flags: Option<&str>) -> Result<String> {
MarqantCore::compress_markdown_with_flags(content, flags)
}
#[allow(dead_code)]
fn add_section_tags(content: &str) -> String {
let mut result = String::new();
let mut in_code_block = false;
for line in content.lines() {
if line.trim_start().starts_with("```") {
in_code_block = !in_code_block;
}
if !in_code_block {
if let Some(stripped) = line.strip_prefix("# ") {
let section = stripped.trim();
result.push_str(&format!("::section:{}::\n", section));
} else if let Some(stripped) = line.strip_prefix("## ") {
let subsection = stripped.trim();
result.push_str(&format!("::section:{}::\n", subsection));
}
}
result.push_str(line);
result.push('\n');
}
result
}
pub fn tokenize_content(content: &str) -> (HashMap<String, String>, String) {
MarqantCore::tokenize_content(content)
}
pub fn decompress_marqant(compressed: &str) -> Result<String> {
MarqantCore::decompress_marqant(compressed)
}
}
impl Formatter for MarqantFormatter {
fn format(
&self,
writer: &mut dyn Write,
nodes: &[FileNode],
stats: &TreeStats,
root_path: &Path,
) -> Result<()> {
let mut markdown = String::new();
let project_name = root_path
.file_name()
.and_then(|n| n.to_str())
.unwrap_or("Directory");
markdown.push_str(&format!("# {} Structure\n\n", project_name));
markdown.push_str("## File Tree\n\n");
markdown.push_str("```\n");
for node in nodes {
let indent = " ".repeat(node.depth);
let name = node.path.file_name().and_then(|n| n.to_str()).unwrap_or("");
let suffix = if node.is_dir { "/" } else { "" };
let emoji = if !self.no_emoji {
if node.is_dir {
"📁 "
} else {
"📄 "
}
} else {
""
};
markdown.push_str(&format!("{}{}{}{}\n", indent, emoji, name, suffix));
}
markdown.push_str("```\n\n");
markdown.push_str("## Statistics\n\n");
markdown.push_str(&format!("- Total files: {}\n", stats.total_files));
markdown.push_str(&format!("- Total directories: {}\n", stats.total_dirs));
markdown.push_str(&format!(
"- Total size: {:.2} MB\n",
stats.total_size as f64 / 1_048_576.0
));
if !stats.file_types.is_empty() {
markdown.push_str("\n### File Types\n\n");
let mut types: Vec<_> = stats.file_types.iter().collect();
types.sort_by(|a, b| b.1.cmp(a.1));
for (ext, count) in types.iter().take(10) {
markdown.push_str(&format!("- .{}: {} files\n", ext, count));
}
}
let compressed = Self::compress_markdown(&markdown)?;
writer.write_all(compressed.as_bytes())?;
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_markdown_compression() {
let markdown = r#"# Test Document
## Section One
This is a test document. This is a test document.
### Subsection
- Item one
- Item two
- Item three
## Section Two
This is a test document.
```rust
fn main() {
println!("Hello, world!");
}
```
## Section Three
**Bold text** and *italic text*.
"#;
let compressed = MarqantFormatter::compress_markdown(markdown).unwrap();
assert!(
compressed.starts_with("MARQANT"),
"Compressed data should start with MARQANT header"
);
let decompressed = MarqantFormatter::decompress_marqant(&compressed).unwrap();
assert_eq!(decompressed.trim(), markdown.trim());
assert!(
compressed.starts_with("MARQANT"),
"Should have proper header"
);
assert!(compressed.len() > 20, "Should have header and content");
}
#[test]
fn test_token_assignment() {
let markdown_content = "## Section 1\n\n## Section 2\n\n## Section 3\n\n## Section 4\n\n## Section 5\n\nContent here.";
let (tokens, tokenized) = MarqantFormatter::tokenize_content(markdown_content);
assert!(
!tokens.is_empty() || tokenized != markdown_content,
"Tokenization should create tokens or modify content. Got tokens: {:?}, content modified: {}",
tokens.keys().collect::<Vec<_>>(),
tokenized != markdown_content
);
}
}