argyph_pack/render/
markdown.rs1use camino::Utf8PathBuf;
2
3pub fn render_markdown(files: &[(Utf8PathBuf, &str, bool, usize)], repo_name: &str) -> String {
9 let mut out = String::new();
10 out.push_str(&format!("# Repository: {repo_name}\n\n"));
11
12 for (path, content, truncated, token_count) in files {
13 let lang = lang_from_ext(path);
14 out.push_str(&format!(
15 "## File: {path} (tokens: {token_count}, truncated: {truncated})\n\n"
16 ));
17 out.push_str(&format!("```{lang}\n{content}\n```\n\n"));
18 }
19
20 out
21}
22
23fn lang_from_ext(path: &camino::Utf8Path) -> &str {
25 match path.extension().unwrap_or("") {
26 "rs" => "rust",
27 "ts" => "typescript",
28 "tsx" => "tsx",
29 "js" => "javascript",
30 "jsx" => "jsx",
31 "py" | "pyi" | "pyx" => "python",
32 "md" | "mdx" => "markdown",
33 "toml" => "toml",
34 "yaml" | "yml" => "yaml",
35 "json" => "json",
36 "html" | "htm" => "html",
37 "css" => "css",
38 "scss" | "sass" => "scss",
39 "sql" => "sql",
40 "sh" | "bash" | "zsh" => "bash",
41 "c" | "h" => "c",
42 "cpp" | "cc" | "cxx" | "hpp" | "hxx" => "cpp",
43 "go" => "go",
44 "java" => "java",
45 "kt" | "kts" => "kotlin",
46 "swift" => "swift",
47 "rb" => "ruby",
48 "php" => "php",
49 "cs" => "csharp",
50 "fs" | "fsx" => "fsharp",
51 "scala" | "sc" => "scala",
52 "xml" | "svg" => "xml",
53 "r" => "r",
54 "lua" => "lua",
55 "zig" => "zig",
56 "ex" | "exs" => "elixir",
57 "erl" | "hrl" => "erlang",
58 "hs" => "haskell",
59 "ml" | "mli" => "ocaml",
60 "dockerfile" => "dockerfile",
61 "makefile" | "mk" => "makefile",
62 "cmake" => "cmake",
63 "proto" => "protobuf",
64 "graphql" | "gql" => "graphql",
65 "vue" => "vue",
66 "svelte" => "svelte",
67 "tf" | "tfvars" => "terraform",
68 _ => "",
69 }
70}
71
72#[cfg(test)]
73#[allow(clippy::unwrap_used)]
74mod tests {
75 use super::*;
76 use camino::Utf8PathBuf;
77
78 fn p(s: &str) -> Utf8PathBuf {
79 Utf8PathBuf::from(s)
80 }
81
82 #[test]
83 fn empty_file_list_produces_only_header() {
84 let result = render_markdown(&[], "my-repo");
85 assert_eq!(result.trim(), "# Repository: my-repo");
86 }
87
88 #[test]
89 fn single_file_has_heading_and_code_block() {
90 let files = [(p("src/main.rs"), "fn main() {}", false, 4)];
91 let result = render_markdown(&files, "test");
92 assert!(result.contains("## File: src/main.rs (tokens: 4, truncated: false)"));
93 assert!(result.contains("```rust"));
94 assert!(result.contains("fn main() {}"));
95 assert!(result.contains("```"));
96 }
97
98 #[test]
99 fn truncated_flag_is_written() {
100 let files = [(p("lib.ts"), "export const x = 1;", true, 3)];
101 let result = render_markdown(&files, "repo");
102 assert!(result.contains("truncated: true"));
103 }
104
105 #[test]
106 fn multiple_files_in_order() {
107 let files = [(p("a.rs"), "// a", false, 1), (p("b.py"), "# b", false, 1)];
108 let result = render_markdown(&files, "repo");
109 let a_pos = result.find("a.rs").unwrap();
110 let b_pos = result.find("b.py").unwrap();
111 assert!(a_pos < b_pos);
112 }
113
114 #[test]
115 fn lang_detection_common_extensions() {
116 assert_eq!(lang_from_ext(camino::Utf8Path::new("foo.rs")), "rust");
117 assert_eq!(lang_from_ext(camino::Utf8Path::new("foo.ts")), "typescript");
118 assert_eq!(lang_from_ext(camino::Utf8Path::new("foo.py")), "python");
119 assert_eq!(lang_from_ext(camino::Utf8Path::new("foo.md")), "markdown");
120 assert_eq!(lang_from_ext(camino::Utf8Path::new("foo.toml")), "toml");
121 assert_eq!(lang_from_ext(camino::Utf8Path::new("foo.json")), "json");
122 }
123
124 #[test]
125 fn unknown_extension_returns_empty() {
126 assert_eq!(lang_from_ext(camino::Utf8Path::new("foo.unknown_ext")), "");
127 assert_eq!(lang_from_ext(camino::Utf8Path::new("no_extension")), "");
128 }
129}