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
//! This utility helps in consolidating documentation directories across a project.
//! It scans a given root directory for all subdirectories named "docs" (case-insensitive),
//! copies their contents to a specified target directory, and reconstructs the path
//! structure, omitting the "docs" segment. This allows for a flattened or
//! reorganized documentation output.
use fs;
use ;
use ;
use Command;
use ;
/// The name of the directory to search for, case-insensitive.
const FIND_THIS_DIR: &str = "docs";
/// Directories that should be skipped entirely during the traversal.
/// These are common project-related or dependency directories that do not
/// typically contain relevant documentation.
const DEFAULT_IGNORE_PATTERNS: = ;
/// Normalizes a given path string by ensuring it ends with a trailing slash.
/// If the path already ends with a slash, it is returned as is.
/// Otherwise, a slash is appended.
///
/// # Arguments
///
/// * `path` - A string slice representing the path to normalize.
///
/// # Returns
///
/// A `String` containing the normalized path with a trailing slash.
///
/// # Examples
///
/// ```
/// assert_eq!(normalize_path("path/to/dir"), "path/to/dir/");
/// assert_eq!(normalize_path("path/to/dir/"), "path/to/dir/");
/// ```
/// Resolves a given path to its canonical, absolute form.
/// This function will panic if the path cannot be resolved.
///
/// # Arguments
///
/// * `p` - A string slice representing the path to resolve.
///
/// # Returns
///
/// A `String` containing the canonicalized path.
///
/// # Panics
///
/// Panics if the path cannot be canonicalized (e.g., if it does not exist).
///
/// # Examples
///
/// ```no_run
/// // Assuming "/tmp" exists
/// let resolved = resolve_path("/tmp/../tmp");
/// // On Unix, this might resolve to "/private/tmp" or "/tmp"
/// // assert!(resolved.ends_with("/tmp/"));
/// ```
/// Cleans up the target directory by recursively removing its contents if it exists,
/// and then recreating it. This ensures a clean slate for copying documentation.
///
/// # Arguments
///
/// * `path` - A string slice representing the path to the target directory.
///
/// # Errors
///
/// Returns an `io::Result` indicating whether the operation was successful.
/// An error is returned if directory removal or creation fails.
///
/// # Examples
///
/// ```no_run
/// use std::fs;
/// use std::io;
/// // Create a dummy directory for testing
/// let _ = fs::create_dir_all("test_target/subdir");
/// let _ = fs::write("test_target/subdir/file.txt", "content");
///
/// // Clean up the directory
/// cleanup_dir("test_target").unwrap();
///
/// // Assert that the directory exists but is empty
/// assert!(fs::metadata("test_target").unwrap().is_dir());
/// assert!(fs::read_dir("test_target").unwrap().next().is_none());
///
/// // Clean up the created directory
/// let _ = fs::remove_dir("test_target");
/// ```
/// Copies a directory from a source path to a destination path using the `cp -r` command.
///
/// # Arguments
///
/// * `src` - A string slice representing the source directory path.
/// * `dest` - A string slice representing the destination directory path.
///
/// # Errors
///
/// Returns an `io::Result` indicating whether the operation was successful.
/// An error is returned if the `cp` command fails or returns a non-zero exit status.
///
/// # Examples
///
/// ```no_run
/// use std::fs;
/// use std::io;
/// // Create dummy source and destination directories
/// let _ = fs::create_dir_all(\"source_dir/docs\");
/// let _ = fs::write(\"source_dir/docs/file.txt\", \"content\");
/// let _ = fs::create_dir_all(\"target_dir\");
///
/// // Copy the docs directory
/// copy_docs_dir(\"source_dir/docs\", \"target_dir/copied_docs\").unwrap();
///
/// assert!(fs::metadata(\"target_dir/copied_docs/file.txt\").unwrap().is_file());
///
/// // Clean up
/// let _ = fs::remove_dir_all(\"source_dir\");
/// let _ = fs::remove_dir_all(\"target_dir\");
/// ```
/// Determines whether a given directory entry should be traversed by the `WalkDir` iterator.
/// This function filters out non-directories, hidden directories (starting with '.'),
/// special directories (starting with '_'), and directories matching `DEFAULT_IGNORE_PATTERNS`.
///
/// # Arguments
///
/// * `entry` - A reference to a `DirEntry` to evaluate.
///
/// # Returns
///
/// `true` if the directory should be traversed, `false` otherwise.
///
/// # Examples
///
/// ```no_run
/// use walkdir::{DirEntry, WalkDir};
/// use std::path::PathBuf;
/// // Assume a DirEntry `entry` for a directory named "my_project"
/// // let entry: DirEntry = ...;
/// // assert_eq!(should_traverse(&entry), true);
///
/// // Assume a DirEntry `entry_hidden` for a directory named ".git"
/// // let entry_hidden: DirEntry = ...;
/// // assert_eq!(should_traverse(&entry_hidden), false);
/// ```
/// Filters `DirEntry` objects, returning true only for directories
/// that are named "docs" (case-insensitive).
///
/// # Arguments
///
/// * `entry` - A reference to a `DirEntry` to evaluate.
///
/// # Returns
///
/// `true` if the entry is a directory named "docs", `false` otherwise.
///
/// # Examples
///
/// ```no_run
/// use walkdir::{DirEntry, WalkDir};
/// use std::path::PathBuf;
/// // Assume a DirEntry `entry_docs` for a directory named "docs"
/// // let entry_docs: DirEntry = ...;
/// // assert_eq!(is_docs_dir(&entry_docs), true);
///
/// // Assume a DirEntry `entry_other` for a directory named "src"
/// // let entry_other: DirEntry = ...;
/// // assert_eq!(is_docs_dir(&entry_other), false);
/// ```
/// The main function of the documentation helper utility.
/// It takes two command-line arguments: a root directory to scan and a target directory
/// where the consolidated documentation will be placed.
///
/// It performs the following steps:
/// 1. Parses command-line arguments and validates their count.
/// 2. Resolves and normalizes the root and target paths.
/// 3. Cleans up the target directory.
/// 4. Walks the root directory, filtering for "docs" directories using `should_traverse`
/// and `is_docs_dir`.
/// 5. For each found "docs" directory, it constructs a new path in the target
/// directory, removing the "docs" segment from the relative path.
/// 6. Copies the contents of the "docs" directory to the newly constructed path.
/// 7. Prints progress and error messages during the copying process.
/// 8. On successful completion, prints a success message.
///
/// # Arguments
///
/// * `args` - Command line arguments: `[0]` - program name, `[1]` - root directory, `[2]` - target directory.
///
/// # Errors
///
/// Returns an `io::Result` indicating whether the overall operation was successful.
/// Errors can occur during path resolution, directory cleanup, directory creation,
/// or file copying.
///
/// # Examples
///
/// To run this utility, you would typically compile it first:
/// ```bash
/// cargo build --release
/// ```
/// Then, you can execute it from your project root, specifying a root directory
/// to scan for "docs" folders and a target directory for the consolidated output:
/// ```bash
/// # Assuming 'target/release/docs-helper' is your compiled binary
/// # and you want to scan the current directory ('.') for docs and output to './dist'
/// ./target/release/docs-helper . ./dist
/// ```
/// This would find all "docs" directories within the current directory and its subdirectories,
/// and copy their contents into the `./dist` folder, reconstructing the relative paths
/// but omitting the "docs" segment. For example, if you have `my_project/src/docs`
/// and `my_project/api/docs`, their contents would be copied to `dist/src` and `dist/api`
/// respectively.