nigig-org/nimanyatta/copy_code.rs
andodeki fd8b0632ca
Some checks failed
repo hygiene / hygiene (push) Has been cancelled
Include nimanyatta as normal tree (not embedded git)
2026-09-26 09:29:36 +03:00

221 lines
No EOL
6.8 KiB
Rust

use std::fs::{self, File};
use std::io::{self, Read, Write};
use std::path::{Path, PathBuf};
use std::process::Command;
/// Directories to always skip, regardless of .gitignore
const IGNORED_DIRS: &[&str] = &[
"target",
"node_modules",
".git",
"dist",
"build",
".next",
".nuxt",
".cache",
".turbo",
"out",
"pkg",
"vendor",
"coverage",
"__pycache__",
];
/// Files to always skip
const IGNORED_FILES: &[&str] = &[
"package-lock.json",
"yarn.lock",
"pnpm-lock.yaml",
"Cargo.lock",
"concatenated_code.txt",
];
fn is_ignored_dir(path: &Path) -> bool {
path.file_name()
.and_then(|name| name.to_str())
.map(|name| IGNORED_DIRS.contains(&name))
.unwrap_or(false)
}
fn is_ignored_file(path: &Path) -> bool {
path.file_name()
.and_then(|name| name.to_str())
.map(|name| IGNORED_FILES.contains(&name))
.unwrap_or(false)
}
fn is_hidden(path: &Path) -> bool {
path.file_name()
.and_then(|name| name.to_str())
.map(|name| name.starts_with('.'))
.unwrap_or(false)
}
fn is_git_ignored(root: &Path, path: &Path) -> bool {
let relative_path = path.strip_prefix(root).unwrap_or(path);
let relative_path_str = match relative_path.to_str() {
Some(s) => s,
None => return false,
};
let output = Command::new("git")
.args(["check-ignore", "-q", relative_path_str])
.current_dir(root)
.output();
match output {
Ok(output) => output.status.success(),
Err(_) => false, // git not available — don't skip
}
}
fn should_skip(root: &Path, path: &Path) -> bool {
if is_hidden(path) {
println!(" skip (hidden): {}", path.display());
return true;
}
if path.is_dir() && is_ignored_dir(path) {
println!(" skip (ignored dir): {}", path.display());
return true;
}
if path.is_file() && is_ignored_file(path) {
println!(" skip (ignored file): {}", path.display());
return true;
}
if is_git_ignored(root, path) {
println!(" skip (gitignored): {}", path.display());
return true;
}
false
}
const VALID_EXTENSIONS: &[&str] = &[
"rs", "toml", "mjs", "md", "txt", "js", "ts", "tsx", "html", "css", "json", "yaml", "yml",
"env", "sh", "sql", "graphql", "proto", "dockerfile",
];
fn has_valid_extension(path: &Path) -> bool {
// Also allow files with no extension but known names
let known_extensionless = &["Makefile", "Dockerfile", "Justfile", ".env", ".env.example"];
if let Some(name) = path.file_name().and_then(|n| n.to_str()) {
if known_extensionless.contains(&name) {
return true;
}
}
path.extension()
.and_then(|ext| ext.to_str())
.map(|ext| VALID_EXTENSIONS.contains(&ext.to_lowercase().as_str()))
.unwrap_or(false)
}
fn copy_files_to_single_file(
source_dir: &Path,
output_file: &mut File,
root: &Path,
output_file_abs_path: &Path,
) -> Result<(), io::Error> {
// Read and sort entries for deterministic output
let mut entries: Vec<_> = fs::read_dir(source_dir)?
.filter_map(|e| e.ok())
.collect();
entries.sort_by_key(|e| e.path());
for entry in entries {
let path = entry.path();
if should_skip(root, &path) {
continue;
}
if path.is_dir() {
copy_files_to_single_file(&path, output_file, root, output_file_abs_path)?;
} else if path.is_file() {
// Skip the output file itself
let file_abs_path = fs::canonicalize(&path)?;
if file_abs_path == output_file_abs_path {
println!(" skip (output file): {}", path.display());
continue;
}
if !has_valid_extension(&path) {
println!(" skip (extension): {}", path.display());
continue;
}
let relative_path = path.strip_prefix(root).unwrap_or(&path);
writeln!(output_file, "// File: {}\n", relative_path.display())?;
let mut file = File::open(&path)?;
let mut contents = String::new();
file.read_to_string(&mut contents)?;
writeln!(output_file, "{}\n", contents)?;
println!(" ✓ included: {}", relative_path.display());
}
}
Ok(())
}
fn main() -> Result<(), io::Error> {
// let project_root = PathBuf::from("./src/gateway/io_backend");
// let project_root = PathBuf::from("./src/gateway/optimized");
// let project_root = PathBuf::from("./src/gateway/inbox");
// let project_root = PathBuf::from("./src/gateway/inbox");
// let project_root = PathBuf::from("./crates/kra-etims-sdk");
// let project_root = PathBuf::from("./src/observability");
// let project_root = PathBuf::from("./src/monitoring");
let project_root = PathBuf::from("./src/routes");
// let project_root = PathBuf::from("./src/proxy");
// let project_root = PathBuf::from("./src");
let output_file_path = project_root.join("concatenated_code.txt");
// let output_file_abs_path = fs::canonicalize(&output_file_path).unwrap_or_else(|_| {
// std::env::current_dir()
// .unwrap()
// .join("concatenated_code.txt")
// });/Users/aok/Projects/rustdev/CratesCode/nimanyatta/src/gateway/optimized
// Ensure the output file exists so canonicalize works
if !output_file_path.exists() {
File::create(&output_file_path)?;
}
let output_file_abs_path = fs::canonicalize(&output_file_path)?;
let mut output_file = File::create(&output_file_path)?;
println!("╔══════════════════════════════════════════╗");
println!("║ Code Concatenation Tool ║");
println!("╚══════════════════════════════════════════╝");
println!();
println!("Root: {}", project_root.display());
println!("Output: {}", output_file_abs_path.display());
println!();
println!("Ignored dirs: {}", IGNORED_DIRS.join(", "));
println!("Ignored files: {}", IGNORED_FILES.join(", "));
println!("Valid exts: {}", VALID_EXTENSIONS.join(", "));
println!();
copy_files_to_single_file(
&project_root,
&mut output_file,
&project_root,
&output_file_abs_path,
)?;
// Print summary
let output_size = fs::metadata(&output_file_path)?.len();
println!();
println!("════════════════════════════════════════════");
println!(
"Done. Output: {} ({} bytes)",
output_file_path.display(),
output_size
);
Ok(())
}