Skip to content

Commit c96fcfe

Browse files
committed
feat: add *_combined.txt to gitignore and file combiner ignore patterns
1 parent 7abb17b commit c96fcfe

4 files changed

Lines changed: 177 additions & 22 deletions

File tree

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ Cargo.lock
2424
# Project specific
2525
found_files.txt
2626
safetensor_keys.txt
27+
*_combined.txt
2728

2829
# Reference code and assets
2930
References/

README.md

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -109,8 +109,12 @@ cargo run -p bitnet-app -- --help
109109
```sh
110110
cargo run -p bitnet-app -- --help
111111
```
112-
- **Combine files (tools):**
112+
- **File Combiner Tools:**
113113
```sh
114+
# Run the GUI version
115+
cargo run --release -p file_combiner_gui
116+
117+
# Run the CLI version
114118
cargo run -p bitnet-tools --bin combine_files -- --help
115119
```
116120

crates/bitnet-tools/gui_combiner/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,3 +12,4 @@ eframe = "0.27"
1212
egui = "0.27"
1313
rfd = "0.14"
1414
bitnet-tools = { path = "../" }
15+
ignore = "0.4"

crates/bitnet-tools/gui_combiner/src/main.rs

Lines changed: 170 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,51 @@ use std::fs;
66
use std::io::Write;
77
use std::path::{Path, PathBuf};
88
use bitnet_tools::combine::{is_combined_file, file_matches_filter, combine_files_to_path};
9+
use ignore::WalkBuilder;
10+
11+
// Common binary and non-text file extensions to ignore
12+
const IGNORED_EXTENSIONS: &[&str] = &[
13+
// Binary formats
14+
".bin", ".exe", ".dll", ".so", ".dylib", ".pdb",
15+
".safetensors", ".onnx", ".pt", ".pth", ".h5", ".ckpt",
16+
// Image formats
17+
".jpg", ".jpeg", ".png", ".gif", ".bmp", ".tiff", ".webp",
18+
".ico", ".svg",
19+
// Audio/Video
20+
".mp3", ".wav", ".mp4", ".avi", ".mov",
21+
// Archives
22+
".zip", ".tar", ".gz", ".7z", ".rar",
23+
// Other binary formats
24+
".pdf", ".doc", ".docx", ".xls", ".xlsx",
25+
// Cache and build artifacts
26+
".pyc", ".pyo", ".rlib", ".rmeta", ".d",
27+
// Database files
28+
".db", ".sqlite", ".sqlite3",
29+
// Rust specific
30+
".rs.bk", ".lock",
31+
];
32+
33+
// Common directories to ignore (in addition to .gitignore patterns)
34+
const IGNORED_DIRECTORIES: &[&str] = &[
35+
// Version control
36+
".git", ".github", ".gitignore", ".gitattributes", ".gitmodules",
37+
// IDE and editor
38+
".vscode", ".idea", ".vs", ".settings",
39+
// Build and cache
40+
"target", "node_modules", "__pycache__", ".cache",
41+
// Environment and config
42+
".env", ".config", ".local",
43+
// Logs and temporary files
44+
"logs", "temp", "tmp",
45+
// Dependencies
46+
"vendor", "packages", "deps",
47+
// Documentation build
48+
"docs/_build", "site", "public",
49+
// Test coverage
50+
"coverage", ".coverage", "htmlcov",
51+
// Project specific (from your .gitignore)
52+
"References", "models", "Original", "Converted",
53+
];
954

1055
#[derive(PartialEq)]
1156
enum Tab {
@@ -509,39 +554,143 @@ fn checkbox_tristate(ui: &mut egui::Ui, state: &mut CheckState) -> bool {
509554
}
510555
}
511556

557+
/// Returns true if the directory should be ignored
558+
fn should_ignore_directory(path: &Path) -> bool {
559+
if let Some(dir_name) = path.file_name().and_then(|n| n.to_str()) {
560+
IGNORED_DIRECTORIES.iter().any(|ignored| {
561+
dir_name.eq_ignore_ascii_case(ignored) ||
562+
// Handle nested cases like "docs/_build"
563+
ignored.split('/').all(|part| dir_name.eq_ignore_ascii_case(part))
564+
})
565+
} else {
566+
false
567+
}
568+
}
569+
570+
/// Returns true if the file should be ignored based on extension
571+
fn should_ignore_file(path: &Path) -> bool {
572+
// Check file extensions
573+
if let Some(ext) = path.extension().and_then(|e| e.to_str()) {
574+
let ext = format!(".{}", ext.to_lowercase());
575+
if IGNORED_EXTENSIONS.contains(&ext.as_str()) {
576+
return true;
577+
}
578+
}
579+
580+
// Check if the file itself is in the ignored list (like .gitignore)
581+
if let Some(file_name) = path.file_name().and_then(|n| n.to_str()) {
582+
if IGNORED_DIRECTORIES.contains(&file_name) {
583+
return true;
584+
}
585+
}
586+
587+
is_combined_file(path) // Also ignore *_combined.txt files
588+
}
589+
512590
fn build_tree_with_filter(path: &Path, filter_exts: &[String]) -> std::io::Result<DirEntryNode> {
513-
let is_dir = fs::metadata(path)?.is_dir();
514-
let mut node = DirEntryNode::new(path.to_path_buf(), is_dir);
515-
516-
if is_dir {
517-
for entry in fs::read_dir(path)? {
518-
let entry = entry?;
519-
let child_path = entry.path();
520-
if entry.file_type()?.is_dir() {
521-
if let Ok(child_node) = build_tree_with_filter(&child_path, filter_exts) {
522-
if !child_node.children.is_empty() {
523-
node.children.push(child_node);
591+
let mut root = DirEntryNode::new(path.to_path_buf(), true);
592+
593+
// Use WalkBuilder to respect .gitignore
594+
let walker = WalkBuilder::new(path)
595+
.hidden(true) // Skip hidden files
596+
.git_ignore(true) // Respect .gitignore
597+
.build();
598+
599+
let mut dirs: std::collections::HashMap<PathBuf, Vec<DirEntryNode>> = std::collections::HashMap::new();
600+
601+
for entry in walker.filter_map(Result::ok) {
602+
let path = entry.path().to_owned();
603+
if path == root.path {
604+
continue;
605+
}
606+
607+
// Skip ignored directories
608+
if entry.file_type().map(|ft| ft.is_dir()).unwrap_or(false) {
609+
if should_ignore_directory(&path) {
610+
continue;
611+
}
612+
let node = DirEntryNode::new(path.clone(), true);
613+
if let Some(parent) = path.parent() {
614+
dirs.entry(parent.to_owned())
615+
.or_default()
616+
.push(node);
617+
}
618+
} else if entry.file_type().map(|ft| ft.is_file()).unwrap_or(false) {
619+
// Skip files we want to ignore
620+
if should_ignore_file(&path) {
621+
continue;
622+
}
623+
624+
// Apply user's extension filter
625+
if !filter_exts.is_empty() && !file_matches_filter(&path, filter_exts) {
626+
continue;
627+
}
628+
629+
let node = DirEntryNode::new(path.clone(), false);
630+
if let Some(parent) = path.parent() {
631+
dirs.entry(parent.to_owned())
632+
.or_default()
633+
.push(node);
634+
}
635+
}
636+
}
637+
638+
// Build the tree from bottom up
639+
fn build_tree_recursive(
640+
path: &Path,
641+
dirs: &mut std::collections::HashMap<PathBuf, Vec<DirEntryNode>>,
642+
) -> Vec<DirEntryNode> {
643+
if let Some(children) = dirs.remove(path) {
644+
let mut result = Vec::new();
645+
for mut child in children {
646+
if child.is_dir {
647+
child.children = build_tree_recursive(&child.path, dirs);
648+
if !child.children.is_empty() {
649+
result.push(child);
524650
}
651+
} else {
652+
result.push(child);
525653
}
526-
} else if (filter_exts.is_empty() || file_matches_filter(&child_path, filter_exts))
527-
&& !is_combined_file(&child_path) // Exclude *_combined.txt
528-
{
529-
node.children.push(DirEntryNode::new(child_path, false));
530654
}
655+
result.sort_by(|a, b| {
656+
// Directories first, then files
657+
if a.is_dir == b.is_dir {
658+
a.path.file_name().cmp(&b.path.file_name())
659+
} else {
660+
b.is_dir.cmp(&a.is_dir)
661+
}
662+
});
663+
result
664+
} else {
665+
Vec::new()
531666
}
532667
}
533-
Ok(node)
668+
669+
root.children = build_tree_recursive(path, &mut dirs);
670+
Ok(root)
534671
}
535672

536-
fn main() -> eframe::Result<()> {
673+
fn main() {
674+
eprintln!("Starting Universal File Combiner...");
675+
537676
let options = eframe::NativeOptions {
538-
viewport: egui::ViewportBuilder::default().with_inner_size([900.0, 700.0]),
677+
viewport: egui::ViewportBuilder::default()
678+
.with_inner_size([900.0, 700.0])
679+
.with_title("Universal File Combiner"),
539680
..Default::default()
540681
};
541682

542-
eframe::run_native(
683+
eprintln!("Initializing GUI...");
684+
685+
match eframe::run_native(
543686
"Universal File Combiner",
544687
options,
545-
Box::new(|_cc| Box::new(FileCombinerApp::default())),
546-
)
688+
Box::new(|_cc| {
689+
eprintln!("Creating application instance...");
690+
Box::new(FileCombinerApp::default())
691+
}),
692+
) {
693+
Ok(_) => eprintln!("GUI closed successfully"),
694+
Err(e) => eprintln!("Error running GUI: {}", e),
695+
}
547696
}

0 commit comments

Comments
 (0)