rs_web/lua/
helpers.rs

1//! Shared helper functions for Lua API
2
3use std::path::{Path, PathBuf};
4
5/// Check if a path is within the project root (for sandbox enforcement)
6pub fn is_path_within_root(path: &Path, root: &Path) -> bool {
7    let resolved = if path.exists() {
8        path.canonicalize().ok()
9    } else {
10        // For non-existing paths, canonicalize the parent and append the filename
11        path.parent()
12            .map(|p| {
13                if p.as_os_str().is_empty() {
14                    PathBuf::from(".")
15                } else {
16                    p.to_path_buf()
17                }
18            })
19            .and_then(|p| p.canonicalize().ok())
20            .map(|p| p.join(path.file_name().unwrap_or_default()))
21    };
22
23    match resolved {
24        Some(abs_path) => abs_path.starts_with(root),
25        None => false,
26    }
27}
28
29/// Resolve a path relative to project root
30pub fn resolve_path(path: &str, root: &Path) -> PathBuf {
31    let p = Path::new(path);
32    if p.is_absolute() {
33        p.to_path_buf()
34    } else {
35        root.join(p)
36    }
37}
38
39/// Parse frontmatter from content string
40/// Supports YAML (---) and TOML (+++) delimiters
41/// Returns (frontmatter_value, content_without_frontmatter)
42pub fn parse_frontmatter_content(raw: &str) -> (Option<serde_json::Value>, String) {
43    let trimmed = raw.trim_start();
44
45    // Check for YAML frontmatter (---)
46    if trimmed.starts_with("---")
47        && let Some(end_idx) = trimmed[3..].find("\n---")
48    {
49        let fm_str = &trimmed[3..3 + end_idx].trim();
50        let content_start = 3 + end_idx + 4; // Skip past closing ---\n
51        let content = trimmed[content_start..].trim_start().to_string();
52
53        match serde_yaml::from_str::<serde_json::Value>(fm_str) {
54            Ok(v) => return (Some(v), content),
55            Err(_) => return (None, raw.to_string()),
56        }
57    }
58
59    // Check for TOML frontmatter (+++)
60    if trimmed.starts_with("+++")
61        && let Some(end_idx) = trimmed[3..].find("\n+++")
62    {
63        let fm_str = &trimmed[3..3 + end_idx].trim();
64        let content_start = 3 + end_idx + 4; // Skip past closing +++\n
65        let content = trimmed[content_start..].trim_start().to_string();
66
67        match toml::from_str::<toml::Value>(fm_str) {
68            Ok(v) => {
69                // Convert TOML to JSON
70                let json_str = serde_json::to_string(&v).unwrap_or_default();
71                match serde_json::from_str(&json_str) {
72                    Ok(jv) => return (Some(jv), content),
73                    Err(_) => return (None, raw.to_string()),
74                }
75            }
76            Err(_) => return (None, raw.to_string()),
77        }
78    }
79
80    // No frontmatter found
81    (None, raw.to_string())
82}