ccboard_core/parsers/
rules.rs1use anyhow::Result;
6use std::fs;
7use std::path::{Path, PathBuf};
8
9#[derive(Debug, Clone, Default)]
11pub struct Rules {
12 pub global: Option<RulesFile>,
14
15 pub project: Option<RulesFile>,
17}
18
19#[derive(Debug, Clone)]
21pub struct RulesFile {
22 pub path: PathBuf,
24
25 pub content: String,
27
28 pub size: u64,
30}
31
32impl Rules {
33 pub fn load(claude_home: &Path, project: Option<&Path>) -> Result<Self> {
35 let global = Self::load_file(&claude_home.join("CLAUDE.md"));
36
37 let project = project.and_then(|p| Self::load_file(&p.join(".claude/CLAUDE.md")));
38
39 Ok(Rules { global, project })
40 }
41
42 fn load_file(path: &Path) -> Option<RulesFile> {
44 if !path.exists() {
45 return None;
46 }
47
48 let content = fs::read_to_string(path).ok()?;
49 let size = fs::metadata(path).ok()?.len();
50
51 Some(RulesFile {
52 path: path.to_path_buf(),
53 content,
54 size,
55 })
56 }
57
58 pub fn preview(file: &RulesFile, max_lines: usize) -> Vec<String> {
60 file.content
61 .lines()
62 .take(max_lines)
63 .map(|s| s.to_string())
64 .collect()
65 }
66}
67
68#[cfg(test)]
69mod tests {
70 use super::*;
71 use std::fs;
72 use tempfile::TempDir;
73
74 #[test]
75 fn test_load_global_only() {
76 let temp = TempDir::new().unwrap();
77 let claude_home = temp.path();
78
79 fs::write(
80 claude_home.join("CLAUDE.md"),
81 "# Global Rules\n\nBe helpful.",
82 )
83 .unwrap();
84
85 let rules = Rules::load(claude_home, None).unwrap();
86 assert!(rules.global.is_some());
87 assert!(rules.project.is_none());
88
89 let global = rules.global.unwrap();
90 assert_eq!(global.size, 27);
91 assert!(global.content.contains("Be helpful"));
92 }
93
94 #[test]
95 fn test_load_with_project() {
96 let temp = TempDir::new().unwrap();
97 let claude_home = temp.path();
98 let project_dir = temp.path().join("myproject");
99
100 fs::create_dir_all(project_dir.join(".claude")).unwrap();
101 fs::write(claude_home.join("CLAUDE.md"), "# Global\n").unwrap();
102 fs::write(
103 project_dir.join(".claude/CLAUDE.md"),
104 "# Project Rules\n\nUse TypeScript.",
105 )
106 .unwrap();
107
108 let rules = Rules::load(claude_home, Some(&project_dir)).unwrap();
109 assert!(rules.global.is_some());
110 assert!(rules.project.is_some());
111
112 let project = rules.project.unwrap();
113 assert!(project.content.contains("TypeScript"));
114 }
115
116 #[test]
117 fn test_load_missing_files() {
118 let temp = TempDir::new().unwrap();
119 let rules = Rules::load(temp.path(), None).unwrap();
120 assert!(rules.global.is_none());
121 assert!(rules.project.is_none());
122 }
123
124 #[test]
125 fn test_preview() {
126 let file = RulesFile {
127 path: PathBuf::from("/fake/path"),
128 content: "Line 1\nLine 2\nLine 3\nLine 4".to_string(),
129 size: 28,
130 };
131
132 let preview = Rules::preview(&file, 2);
133 assert_eq!(preview.len(), 2);
134 assert_eq!(preview[0], "Line 1");
135 assert_eq!(preview[1], "Line 2");
136 }
137}