use crate::fs::config::get_root_dir;
use anyhow::{anyhow, Result};
use std::path::{Path, PathBuf};
use url::Url;
pub fn url_to_path(url_str: &str) -> Result<PathBuf> {
let mut normalized = url_str.replace('\\', "/");
if !normalized.contains("://") {
if normalized.starts_with("braid.org") || normalized.starts_with("braidfs") {
normalized = format!("https://{}", normalized);
} else if normalized.starts_with("localhost") || normalized.starts_with("127.0.0.1") {
normalized = format!("http://{}", normalized);
}
}
let url = Url::parse(&normalized)?;
let host = url.host_str().ok_or_else(|| anyhow!("URL missing host"))?;
let port = url.port();
let mut domain_dir = host.to_string();
if let Some(p) = port {
domain_dir.push_str(&format!("+{}", p));
}
let root = get_root_dir()?;
let mut path = root.join(domain_dir);
for segment in url.path_segments().unwrap_or_else(|| "".split('/')) {
path.push(segment);
}
if url.path().ends_with('/') {
path.push("index");
}
Ok(path)
}
pub fn path_to_url(path: &Path) -> Result<String> {
let root = get_root_dir()?;
let root_abs = std::fs::canonicalize(&root).unwrap_or_else(|_| root.clone());
let path_abs = std::fs::canonicalize(path).unwrap_or_else(|_| path.to_path_buf());
let relative = path_abs.strip_prefix(&root_abs).map_err(|_| {
anyhow!(
"Path {:?} is not within BraidFS root {:?}",
path_abs,
root_abs
)
})?;
let mut components = relative.components();
let domain_comp = components.next().ok_or_else(|| anyhow!("Path too short"))?;
let domain_str = domain_comp.as_os_str().to_string_lossy();
if domain_str.starts_with('.') {
return Err(anyhow!("Ignoring dotfile/directory"));
}
let (host, port) = if let Some((h, p)) = domain_str.rsplit_once('+') {
(h, Some(p.parse::<u16>()?))
} else {
(domain_str.as_ref(), None)
};
let scheme = if host == "localhost" || host == "127.0.0.1" {
"http"
} else {
"https"
};
let mut url = Url::parse(&format!("{}://{}", scheme, host))?;
if let Some(p) = port {
url.set_port(Some(p)).map_err(|_| anyhow!("Invalid port"))?;
}
let mut path_segments = Vec::new();
for comp in components {
path_segments.push(comp.as_os_str().to_string_lossy().to_string());
}
if let Some(last) = path_segments.last() {
if last == "index" {
path_segments.pop();
}
}
url.path_segments_mut()
.map_err(|_| anyhow!("Cannot be base"))?
.extend(path_segments);
Ok(url.to_string())
}
pub fn path_join(parent: &str, name: &str) -> String {
if parent == "/" {
format!("/{}", name)
} else {
format!("{}/{}", parent, name)
}
}
pub fn extract_markdown(content: &str) -> String {
let trimmed = content.trim();
if trimmed.starts_with("<!DOCTYPE") || trimmed.starts_with("<html") {
let mut candidates = Vec::new();
let mut current_pos = 0;
while let Some(start_idx) = trimmed[current_pos..].find("<script type=\"statebus\">") {
let actual_start = current_pos + start_idx + "<script type=\"statebus\">".len();
if let Some(end_idx) = trimmed[actual_start..].find("</script>") {
let script_content = trimmed[actual_start..actual_start + end_idx].trim();
candidates.push(script_content.to_string());
current_pos = actual_start + end_idx + "</script>".len();
} else {
break;
}
}
if !candidates.is_empty() {
candidates.sort_by_key(|c| std::cmp::Reverse(c.len()));
for candidate in &candidates {
if candidate.contains("# ")
|| candidate.contains("\n- ")
|| candidate.contains("](")
|| candidate.contains("\n## ")
{
return candidate.clone();
}
}
return candidates[0].clone();
}
}
content.to_string()
}
pub fn wrap_markdown(original_content: &str, new_markdown: &str) -> String {
let trimmed = original_content.trim();
if trimmed.starts_with("<!DOCTYPE") || trimmed.starts_with("<html") {
if let Some(start_idx) = trimmed.find("<script type=\"statebus\">") {
let prefix = &trimmed[..start_idx + "<script type=\"statebus\">".len()];
let after_script = &trimmed[start_idx..];
if let Some(end_idx) = after_script.find("</script>") {
let suffix = &after_script[end_idx..];
return format!("{}\n{}\n{}", prefix, new_markdown, suffix);
}
}
}
new_markdown.to_string()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_extract_markdown() {
let html = "<html><script type=\"statebus\">hello world</script></html>";
assert_eq!(extract_markdown(html), "hello world");
}
#[test]
fn test_wrap_markdown() {
let html = "<html><script type=\"statebus\">hello world</script></html>";
let wrapped = wrap_markdown(html, "new content");
assert!(wrapped.contains("<script type=\"statebus\">"));
assert!(wrapped.contains("new content"));
assert!(wrapped.contains("</script>"));
}
#[test]
fn test_url_to_path() {
}
#[test]
fn test_path_join() {
assert_eq!(path_join("/", "foo"), "/foo");
assert_eq!(path_join("/bar", "baz"), "/bar/baz");
}
}