Skip to main content

cgx_engine/docs/
wiki_link.rs

1//! Wiki-link / markdown-link formatting + safe filename conversion.
2
3use super::WikiLinkStyle;
4
5/// Format a link to a note path (without `.md`) with a display alias.
6///
7/// Obsidian renders `[[path|alias]]`; markdown viewers render `[alias](path.md)`.
8pub fn format_link(target: &str, alias: &str, style: WikiLinkStyle) -> String {
9    match style {
10        WikiLinkStyle::Obsidian => format!("[[{}|{}]]", target, alias),
11        WikiLinkStyle::Markdown => format!("[{}]({}.md)", alias, target),
12    }
13}
14
15/// Convert an arbitrary string (community label, file path, symbol name) into a
16/// filesystem-safe note name. Preserves directory separators in paths but replaces
17/// other unsafe chars with `-`.
18pub fn safe_filename(raw: &str) -> String {
19    let mut out = String::with_capacity(raw.len());
20    let mut last_dash = false;
21    for c in raw.chars() {
22        match c {
23            '/' | '\\' => {
24                out.push('/');
25                last_dash = false;
26            }
27            'a'..='z' | 'A'..='Z' | '0'..='9' | '_' | '.' => {
28                out.push(c);
29                last_dash = false;
30            }
31            '-' => {
32                if !last_dash {
33                    out.push('-');
34                    last_dash = true;
35                }
36            }
37            _ => {
38                if !last_dash {
39                    out.push('-');
40                    last_dash = true;
41                }
42            }
43        }
44    }
45    out.trim_matches('-').trim_start_matches('/').to_string()
46}
47
48/// Build the canonical module-note path (without `.md`) for a source file.
49pub fn module_note_target(community_slug: &str, file_path: &str) -> String {
50    format!("30-Modules/{}/{}", community_slug, safe_filename(file_path))
51}
52
53/// Group a source file by its meaningful containing directory.
54///
55/// Examples:
56/// - `crates/cgx-engine/src/graph.rs`           → `cgx-engine`
57/// - `crates/cgx-engine/src/parsers/rust.rs`    → `cgx-engine/parsers`
58/// - `packages/web-ui/src/components/App.tsx`   → `web-ui/components`
59/// - `tests/integration/auth.test.ts`           → `tests/integration`
60/// - `README.md`                                → `root`
61pub fn file_group(file_path: &str) -> String {
62    let parent = file_path.rsplit_once('/').map(|(p, _)| p).unwrap_or("");
63    if parent.is_empty() {
64        return "root".to_string();
65    }
66    // Drop noisy roots and then collapse `src` segments.
67    let trimmed = parent
68        .trim_start_matches("./")
69        .trim_start_matches("crates/")
70        .trim_start_matches("packages/")
71        .trim_start_matches("apps/")
72        .trim_start_matches("services/");
73    let parts: Vec<&str> = trimmed
74        .split('/')
75        .filter(|s| !s.is_empty() && *s != "src")
76        .collect();
77    if parts.is_empty() {
78        return "root".to_string();
79    }
80    // Keep at most 2 segments so paths stay readable.
81    let take = parts.len().min(2);
82    let joined: String = parts[..take].join("/");
83    safe_filename(&joined)
84}
85
86/// Slug form of [`file_group`] suitable as a single path segment.
87pub fn file_group_slug(file_path: &str) -> String {
88    let g = file_group(file_path);
89    g.replace('/', "-")
90}
91
92/// Build the canonical Public-API note path for a community.
93pub fn public_api_target(community_slug: &str) -> String {
94    format!("10-PublicAPI/{}", community_slug)
95}
96
97/// Slug a community label for use as a folder/filename. Slashes are flattened to `-`
98/// since this is a single path segment, not a tree.
99pub fn community_slug(label: &str, id: i64) -> String {
100    let slug = safe_filename(label).to_lowercase().replace('/', "-");
101    let slug = slug.trim_matches('-').to_string();
102    if slug.is_empty() {
103        format!("community-{}", id)
104    } else {
105        slug
106    }
107}