dircat 1.0.1

High-performance Rust utility that concatenates and displays directory contents, similar to the C++ DirCat.
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
//! Provides a trait and implementations for transforming file content.

use dyn_clone::DynClone;
use std::fmt;

mod comments;
mod empty_lines;

// Re-export the standalone functions
pub use comments::remove_comments;
pub use empty_lines::remove_empty_lines;

/// A trait for content transformation filters.
///
/// Filters are applied sequentially to the content of each text file.
///
/// # Examples
///
/// **Implementing a Custom Filter:**
///
/// ```
/// use dircat::processing::filters::ContentFilter;
///
/// #[derive(Clone)]
/// struct UppercaseFilter;
///
/// impl ContentFilter for UppercaseFilter {
///     fn apply(&self, content: &str) -> String {
///         content.to_uppercase()
///     }
///     fn name(&self) -> &'static str { "UppercaseFilter" }
/// }
///
/// let filter = UppercaseFilter;
/// let result = filter.apply("Hello World");
/// assert_eq!(result, "HELLO WORLD");
/// assert_eq!(filter.name(), "UppercaseFilter");
/// ```
///
/// **Using a Custom Filter with `ConfigBuilder`:**
///
/// ```
/// # use dircat::processing::filters::ContentFilter;
/// # #[derive(Clone)]
/// # struct UppercaseFilter;
/// # impl ContentFilter for UppercaseFilter {
/// #     fn apply(&self, content: &str) -> String { content.to_uppercase() }
/// #     fn name(&self) -> &'static str { "UppercaseFilter" }
/// # }
/// use dircat::config::ConfigBuilder;
///
/// let config = ConfigBuilder::new()
///     .content_filter(Box::new(UppercaseFilter))
///     .build()
///     .unwrap();
///
/// assert_eq!(
///     config.processing.content_filters[0].name(),
///     "UppercaseFilter"
/// );
/// ```
pub trait ContentFilter: DynClone + Send + Sync {
    /// Applies the filter to the given content string.
    fn apply(&self, content: &str) -> String;
    /// Returns a descriptive name for the filter.
    fn name(&self) -> &'static str;
}

dyn_clone::clone_trait_object!(ContentFilter);

// Implement Debug manually for Box<dyn ContentFilter> by using the name method.
impl fmt::Debug for Box<dyn ContentFilter> {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        f.debug_tuple("ContentFilter").field(&self.name()).finish()
    }
}

// --- Filter Implementations ---

/// A [`ContentFilter`] that removes C/C++ style comments.
///
/// This filter uses a state machine to correctly handle comments inside
/// string literals and other edge cases.
///
/// # Examples
///
/// ```
/// use dircat::processing::filters::{ContentFilter, RemoveCommentsFilter};
/// let filter = RemoveCommentsFilter;
/// assert_eq!(filter.apply("code // comment"), "code");
/// ```
#[derive(Debug, Clone)]
pub struct RemoveCommentsFilter;

impl ContentFilter for RemoveCommentsFilter {
    fn apply(&self, content: &str) -> String {
        comments::remove_comments(content)
    }
    fn name(&self) -> &'static str {
        "RemoveCommentsFilter"
    }
}

/// A [`ContentFilter`] that removes lines containing only whitespace.
///
/// This filter works by splitting the content into lines, filtering out any
/// line that becomes empty after trimming whitespace, and then rejoining the
/// remaining lines.
///
/// # Examples
///
/// ```
/// use dircat::processing::filters::{ContentFilter, RemoveEmptyLinesFilter};
/// let filter = RemoveEmptyLinesFilter;
/// let input = "line 1\n  \nline 3";
/// assert_eq!(filter.apply(input), "line 1\nline 3");
/// ```
#[derive(Debug, Clone)]
pub struct RemoveEmptyLinesFilter;

impl ContentFilter for RemoveEmptyLinesFilter {
    fn apply(&self, content: &str) -> String {
        empty_lines::remove_empty_lines(content)
    }
    fn name(&self) -> &'static str {
        "RemoveEmptyLinesFilter"
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    // --- Trait Implementation Tests ---

    #[test]
    fn test_remove_comments_filter_apply() {
        let filter = RemoveCommentsFilter;
        let input = "code // comment\nmore code";
        let expected = "code\nmore code";
        assert_eq!(filter.apply(input), expected);
    }

    #[test]
    fn test_remove_empty_lines_filter_apply() {
        let filter = RemoveEmptyLinesFilter;
        let input = "Line 1\n\n  \nLine 4\n";
        let expected = "Line 1\nLine 4";
        assert_eq!(filter.apply(input), expected);
    }

    // --- remove_empty_lines tests ---
    #[test]
    fn test_remove_empty() {
        let input = "Line 1\n\n  \nLine 4\n";
        let expected = "Line 1\nLine 4";
        assert_eq!(remove_empty_lines(input), expected);
    }

    #[test]
    fn test_remove_empty_no_empty_lines() {
        let input = "Line 1\nLine 2";
        let expected = "Line 1\nLine 2";
        assert_eq!(remove_empty_lines(input), expected);
    }

    #[test]
    fn test_remove_empty_all_empty() {
        let input = "\n  \n\t\n";
        let expected = "";
        assert_eq!(remove_empty_lines(input), expected);
    }

    #[test]
    fn test_remove_empty_trailing_newlines() {
        let input = "Line 1\nLine 2\n\n";
        let expected = "Line 1\nLine 2"; // Trailing empty lines removed
        assert_eq!(remove_empty_lines(input), expected);
    }

    #[test]
    fn test_remove_empty_leading_newlines() {
        let input = "\n\nLine 1\nLine 2";
        let expected = "Line 1\nLine 2"; // Leading empty lines removed
        assert_eq!(remove_empty_lines(input), expected);
    }

    #[test]
    fn test_remove_empty_windows_newlines() {
        // Note: .lines() handles \r\n, but join("\n") only uses \n
        let input = "Line 1\r\n\r\n  \r\nLine 4\r\n";
        let expected = "Line 1\nLine 4";
        assert_eq!(remove_empty_lines(input), expected);
    }

    // --- remove_comments tests ---
    #[test]
    fn test_remove_line_comment_simple() {
        let input = "code // comment\nmore code";
        let expected = "code\nmore code"; // Trailing space removed by trim_end
        assert_eq!(remove_comments(input), expected);
    }

    #[test]
    fn test_remove_line_comment_no_newline() {
        let input = "code // comment";
        let expected = "code"; // Trailing space removed by trim_end
        assert_eq!(remove_comments(input), expected);
    }

    #[test]
    fn test_remove_block_comment_simple() {
        let input = "code /* comment */ more code";
        let expected = "code  more code"; // Space where comment was remains
        assert_eq!(remove_comments(input), expected);
    }

    #[test]
    fn test_remove_block_comment_multiline() {
        let input = "code /* comment\n more comment */ more code";
        let expected = "code  more code";
        assert_eq!(remove_comments(input), expected);
    }

    #[test]
    fn test_remove_block_comment_with_stars() {
        let input = "code /**** comment ****/ more code";
        let expected = "code  more code";
        assert_eq!(remove_comments(input), expected);
    }

    #[test]
    fn test_remove_block_comment_at_end() {
        let input = "code /* comment */";
        let expected = "code"; // Trailing space removed by trim_end, final trim handles if only space remains
        assert_eq!(remove_comments(input), expected);
    }

    #[test]
    fn test_comment_markers_in_strings() {
        let input = r#"let s = "// not a comment"; /* also " not start */"#;
        let expected = r#"let s = "// not a comment";"#; // Trailing space removed by trim_end, final trim handles if only space remains
        assert_eq!(remove_comments(input), expected);
    }

    #[test]
    fn test_comment_markers_in_chars() {
        let input = r#"let c = '/'; // char comment"#;
        let expected = r#"let c = '/';"#; // Trailing space removed by trim_end
        assert_eq!(remove_comments(input), expected);

        let input2 = r#"let c = '*'; /* char comment */"#;
        let expected2 = r#"let c = '*';"#; // Trailing space removed by trim_end
        assert_eq!(remove_comments(input2), expected2);
    }

    #[test]
    fn test_escaped_quotes_in_strings() {
        let input = r#"let s = "string with \" quote"; // comment"#;
        let expected = r#"let s = "string with \" quote";"#; // Trailing space removed by trim_end
        assert_eq!(remove_comments(input), expected);
    }

    #[test]
    fn test_escaped_slash_before_comment() {
        let input = r#"let path = "\\\\server\\share"; // comment"#;
        let expected = r#"let path = "\\\\server\\share";"#; // Trailing space removed by trim_end
        assert_eq!(remove_comments(input), expected);
    }

    #[test]
    fn test_division_operator() {
        let input = "a = b / c; // divide\nx = y / *p; /* ptr divide */";
        let expected = "a = b / c;\nx = y / *p;"; // Trailing spaces removed by trim_end
        assert_eq!(remove_comments(input), expected);
    }

    #[test]
    fn test_empty_input() {
        let input = "";
        let expected = "";
        assert_eq!(remove_comments(input), expected);
    }

    #[test]
    fn test_only_line_comment() {
        let input = "// only comment";
        let expected = "";
        assert_eq!(remove_comments(input), expected);
    }

    #[test]
    fn test_only_line_comment_with_newline() {
        let input = "// only comment\n";
        let expected = ""; // Newline removed by final trim
        assert_eq!(remove_comments(input), expected);
    }

    #[test]
    fn test_only_block_comment() {
        let input = "/* only comment */";
        let expected = "";
        assert_eq!(remove_comments(input), expected);
    }

    #[test]
    fn test_only_block_comment_multiline() {
        let input = "/* only \n comment */";
        let expected = "";
        assert_eq!(remove_comments(input), expected);
    }

    #[test]
    fn test_block_comment_unterminated() {
        // Should consume until the end
        let input = "code /* comment";
        let expected = "code"; // Trailing space removed by trim_end, final trim handles if only space remains
        assert_eq!(remove_comments(input), expected);
    }

    #[test]
    fn test_ends_with_slash() {
        let input = "code /";
        let expected = "code /";
        assert_eq!(remove_comments(input), expected);
    }

    #[test]
    fn test_ends_with_star_in_block() {
        let input = "code /* comment *";
        let expected = "code"; // Trailing space removed by trim_end, final trim handles if only space remains
        assert_eq!(remove_comments(input), expected);
    }

    #[test]
    fn test_adjacent_block_comments() {
        let input = "code /* first */ /* second */";
        let expected = "code"; // Trailing space removed by trim_end, final trim handles if only space remains
        assert_eq!(remove_comments(input), expected);
    }

    #[test]
    fn test_adjacent_line_comments() {
        let input = "code // first \n// second\nend";
        let expected = "code\n\nend"; // Trailing spaces removed, newlines preserved
        assert_eq!(remove_comments(input), expected);
    }

    #[test]
    fn test_block_comment_inside_line_comment() {
        // Line comment takes precedence
        let input = "code // line /* block */ comment\nend";
        let expected = "code\nend"; // Trailing space removed by trim_end
        assert_eq!(remove_comments(input), expected);
    }

    #[test]
    fn test_line_comment_inside_block_comment() {
        // Block comment takes precedence
        let input = "code /* block // line \n comment */ end";
        let expected = "code  end";
        assert_eq!(remove_comments(input), expected);
    }

    #[test]
    fn test_string_with_comment_markers_and_escapes() {
        let input =
            r#"str = "/* not comment */ // also not comment \" escaped quote"; // real comment"#;
        let expected = r#"str = "/* not comment */ // also not comment \" escaped quote";"#; // Trailing space removed by trim_end
        assert_eq!(remove_comments(input), expected);
    }

    #[test]
    fn test_char_with_comment_markers_and_escapes() {
        let input = r#"
let c1 = '/'; // comment 1
let c2 = '*'; /* comment 2 */
let c3 = '\\'; // comment 3
let c4 = '\''; // comment 4
"#;
        // Expected: Keep code, remove comments, trim trailing spaces.
        // The leading/trailing newlines from the input string literal are handled by the final trim.
        let expected = "let c1 = '/';\nlet c2 = '*';\nlet c3 = '\\\\';\nlet c4 = '\\'';";
        assert_eq!(remove_comments(input), expected);
    }

    #[test]
    fn test_mixed_comments_and_code() {
        let input = r#"
        int main() { // start main
            /* block comment
               here */
            printf("Hello // World\n"); /* Print */
            // return 0;
            return 1; /* Success? */
        } // end main"#;
        // Expected: Code preserved, comments removed, trailing spaces removed.
        // Leading/trailing empty lines handled by final trim.
        // Lines that contained only comments become empty lines after trim_end(), resulting in \n\n.
        let expected = "        int main() {\n\n            printf(\"Hello // World\\n\");\n\n            return 1;\n        }";
        assert_eq!(remove_comments(input), expected);
    }

    #[test]
    fn test_tricky_slashes_and_stars() {
        let input = "a = b / *p; // divide by pointer value\n c = d */e; /* incorrect comment? */";
        // Expected: Comments removed, trailing spaces removed.
        let expected = "a = b / *p;\n c = d */e;"; // Treats */ as normal code here
        assert_eq!(remove_comments(input), expected);

        let input2 = "a = b / * p * / c; /* comment */"; // Pointer arithmetic, then comment
        let expected2 = "a = b / * p * / c;"; // Trailing space removed by trim_end
        assert_eq!(remove_comments(input2), expected2);
    }

    #[test]
    fn test_slash_then_quote() {
        let input = r#"x = y / "/"; // divide by string"#;
        let expected = r#"x = y / "/";"#; // Trailing space removed by trim_end
        assert_eq!(remove_comments(input), expected);
    }
}