1use std::fmt::Write as _;
34
35use crate::links_format::flatten_lino_value;
36
37use super::file::{formalize_repository_file, RepositoryFileFormalization};
38use super::{SummarizationConfig, SummarizationMode};
39
40#[derive(Debug, Clone, PartialEq, Eq)]
46pub enum RepositoryEntry {
47 File { path: String, content: String },
49 Directory { path: String, children: Vec<Self> },
51}
52
53impl RepositoryEntry {
54 #[must_use]
56 pub fn file(path: impl Into<String>, content: impl Into<String>) -> Self {
57 Self::File {
58 path: path.into(),
59 content: content.into(),
60 }
61 }
62
63 #[must_use]
65 pub fn directory(path: impl Into<String>, children: Vec<Self>) -> Self {
66 Self::Directory {
67 path: path.into(),
68 children,
69 }
70 }
71
72 #[must_use]
74 pub fn path(&self) -> &str {
75 match self {
76 Self::File { path, .. } | Self::Directory { path, .. } => path,
77 }
78 }
79}
80
81#[derive(Debug, Clone)]
83pub struct RepositoryDirectoryFormalization {
84 pub path: String,
85 pub direct_file_count: usize,
87 pub direct_directory_count: usize,
89 pub total_file_count: usize,
91 pub total_directory_count: usize,
93 pub total_line_count: usize,
95 pub total_byte_count: usize,
97 pub children: Vec<RepositoryResourceFormalization>,
99}
100
101#[derive(Debug, Clone)]
103pub enum RepositoryResourceFormalization {
104 File(RepositoryFileFormalization),
105 Directory(RepositoryDirectoryFormalization),
106}
107
108impl RepositoryResourceFormalization {
109 #[must_use]
111 pub fn path(&self) -> &str {
112 match self {
113 Self::File(file) => &file.path,
114 Self::Directory(directory) => &directory.path,
115 }
116 }
117
118 #[must_use]
120 pub const fn is_directory(&self) -> bool {
121 matches!(self, Self::Directory(_))
122 }
123
124 #[must_use]
130 pub fn summary(&self, config: &SummarizationConfig) -> String {
131 match self {
132 Self::File(file) => file.summary(config),
133 Self::Directory(directory) => directory.summary(config),
134 }
135 }
136
137 #[must_use]
139 pub fn links_notation(&self) -> String {
140 match self {
141 Self::File(file) => file.links_notation(),
142 Self::Directory(directory) => directory.links_notation(),
143 }
144 }
145}
146
147impl RepositoryDirectoryFormalization {
148 #[must_use]
151 pub fn summary(&self, config: &SummarizationConfig) -> String {
152 let mut parts = vec![self.identity_sentence()];
153
154 if config.mode == SummarizationMode::Topic {
155 return parts.remove(0);
158 }
159
160 let child_config = config.clone().with_mode(config.mode.one_step_shorter());
161 let cap = child_summary_cap(config.mode);
162 let mut child_summaries = Vec::new();
163 for child in self.children.iter().take(cap) {
164 let summary = child.summary(&child_config);
165 if !summary.is_empty() {
166 child_summaries.push(summary);
167 }
168 }
169 let hidden = self.children.len().saturating_sub(cap);
170
171 if !child_summaries.is_empty() {
172 parts.push(format!("Contents: {}", child_summaries.join(" ")));
173 }
174 if hidden > 0 {
175 parts.push(format!(
176 "{hidden} more {} omitted for brevity.",
177 pluralize(hidden, "entry", "entries")
178 ));
179 }
180 parts.join(" ")
181 }
182
183 fn identity_sentence(&self) -> String {
184 format!(
185 "{} is a repository directory with {} {} and {} {} ({} {} total across {} {}).",
186 self.path,
187 self.direct_file_count,
188 pluralize(self.direct_file_count, "file", "files"),
189 self.direct_directory_count,
190 pluralize(
191 self.direct_directory_count,
192 "subdirectory",
193 "subdirectories"
194 ),
195 self.total_line_count,
196 pluralize(self.total_line_count, "line", "lines"),
197 self.total_file_count,
198 pluralize(self.total_file_count, "file", "files"),
199 )
200 }
201
202 #[must_use]
204 pub fn links_notation(&self) -> String {
205 let mut out = String::from("repository_directory\n");
206 push_field(&mut out, 1, "path", &self.path);
207 push_field(
208 &mut out,
209 1,
210 "direct_file_count",
211 &self.direct_file_count.to_string(),
212 );
213 push_field(
214 &mut out,
215 1,
216 "direct_directory_count",
217 &self.direct_directory_count.to_string(),
218 );
219 push_field(
220 &mut out,
221 1,
222 "total_file_count",
223 &self.total_file_count.to_string(),
224 );
225 push_field(
226 &mut out,
227 1,
228 "total_directory_count",
229 &self.total_directory_count.to_string(),
230 );
231 push_field(
232 &mut out,
233 1,
234 "total_line_count",
235 &self.total_line_count.to_string(),
236 );
237 push_field(
238 &mut out,
239 1,
240 "total_byte_count",
241 &self.total_byte_count.to_string(),
242 );
243 for child in &self.children {
244 match child {
245 RepositoryResourceFormalization::File(file) => {
246 push_field(&mut out, 1, "file", &file.path);
247 }
248 RepositoryResourceFormalization::Directory(directory) => {
249 push_field(&mut out, 1, "directory", &directory.path);
250 }
251 }
252 }
253 out.trim_end().to_owned()
254 }
255}
256
257#[must_use]
259pub fn formalize_repository_resource(entry: &RepositoryEntry) -> RepositoryResourceFormalization {
260 match entry {
261 RepositoryEntry::File { path, content } => {
262 RepositoryResourceFormalization::File(formalize_repository_file(path, content))
263 }
264 RepositoryEntry::Directory { path, children } => {
265 RepositoryResourceFormalization::Directory(formalize_repository_directory(
266 path, children,
267 ))
268 }
269 }
270}
271
272#[must_use]
274pub fn formalize_repository_directory(
275 path: &str,
276 children: &[RepositoryEntry],
277) -> RepositoryDirectoryFormalization {
278 let formalized_children: Vec<RepositoryResourceFormalization> =
279 children.iter().map(formalize_repository_resource).collect();
280
281 let mut direct_file_count = 0;
282 let mut direct_directory_count = 0;
283 let mut total_file_count = 0;
284 let mut total_directory_count = 0;
285 let mut total_line_count = 0;
286 let mut total_byte_count = 0;
287
288 for child in &formalized_children {
289 match child {
290 RepositoryResourceFormalization::File(file) => {
291 direct_file_count += 1;
292 total_file_count += 1;
293 total_line_count += file.line_count;
294 total_byte_count += file.byte_count;
295 }
296 RepositoryResourceFormalization::Directory(directory) => {
297 direct_directory_count += 1;
298 total_directory_count += 1 + directory.total_directory_count;
299 total_file_count += directory.total_file_count;
300 total_line_count += directory.total_line_count;
301 total_byte_count += directory.total_byte_count;
302 }
303 }
304 }
305
306 RepositoryDirectoryFormalization {
307 path: path.to_owned(),
308 direct_file_count,
309 direct_directory_count,
310 total_file_count,
311 total_directory_count,
312 total_line_count,
313 total_byte_count,
314 children: formalized_children,
315 }
316}
317
318#[must_use]
322pub fn summarize_repository_resource(
323 entry: &RepositoryEntry,
324 config: &SummarizationConfig,
325) -> String {
326 formalize_repository_resource(entry).summary(config)
327}
328
329const fn child_summary_cap(mode: SummarizationMode) -> usize {
333 match mode {
334 SummarizationMode::Topic => 0,
335 SummarizationMode::Short => 2,
336 SummarizationMode::Standard => 4,
337 SummarizationMode::Full | SummarizationMode::Expand => usize::MAX,
338 }
339}
340
341const fn pluralize(count: usize, singular: &'static str, plural: &'static str) -> &'static str {
342 if count == 1 {
343 singular
344 } else {
345 plural
346 }
347}
348
349fn push_field(out: &mut String, indent: usize, name: &str, value: &str) {
350 for _ in 0..indent {
351 out.push_str(" ");
352 }
353 let _ = writeln!(out, "{name} {}", flatten_lino_value(value));
354}