use std::collections::{HashMap, HashSet};
use std::fs;
use std::path::{Path, PathBuf};
use anyhow::{Context, Result, anyhow};
use path_clean::clean;
use regex::Regex;
#[derive(Clone)]
struct NixImport {
path: PathBuf,
_position: (usize, usize),
full_import: String,
}
#[derive(Clone)]
struct NixFile {
_path: PathBuf,
content: String,
imports: Vec<NixImport>,
}
pub fn bundle_nix_files(entry_point: &Path) -> Result<String> {
let mut processed_files = HashSet::new();
let mut file_contents = HashMap::new();
process_nix_file(entry_point, &mut processed_files, &mut file_contents)?;
let abs_entry_path = if entry_point.is_absolute() {
entry_point.to_path_buf()
} else {
std::env::current_dir()?.join(entry_point)
};
let clean_entry_path = PathBuf::from(clean(abs_entry_path.to_string_lossy().as_ref()));
if !file_contents.contains_key(&clean_entry_path) {
return Err(anyhow!(
"エントリーポイントの内容が見つかりません: {}",
clean_entry_path.display()
));
}
let bundled_content = inline_imports(&clean_entry_path, &file_contents)?;
Ok(bundled_content)
}
fn process_nix_file(
file_path: &Path,
processed_files: &mut HashSet<PathBuf>,
file_contents: &mut HashMap<PathBuf, NixFile>,
) -> Result<()> {
let abs_path = if file_path.is_absolute() {
file_path.to_path_buf()
} else {
std::env::current_dir()?.join(file_path)
};
let clean_path = PathBuf::from(clean(abs_path.to_string_lossy().as_ref()));
if processed_files.contains(&clean_path) {
return Ok(());
}
if !clean_path.exists() {
return Err(anyhow!("ファイルが存在しません: {}", clean_path.display()));
}
let content = fs::read_to_string(&clean_path)
.with_context(|| format!("ファイルの読み込みに失敗しました: {}", clean_path.display()))?;
let imports = parse_imports(&content, &clean_path)?;
let nix_file = NixFile {
_path: clean_path.clone(),
content: content.clone(),
imports: imports.clone(),
};
file_contents.insert(clean_path.clone(), nix_file);
processed_files.insert(clean_path.clone());
for import in imports {
let import_path = resolve_import_path(&import.path, &clean_path)?;
process_nix_file(&import_path, processed_files, file_contents)?;
}
Ok(())
}
fn resolve_import_path(import_path: &Path, current_file: &Path) -> Result<PathBuf> {
if import_path.is_absolute() {
return Ok(import_path.to_path_buf());
}
let parent_dir = current_file
.parent()
.ok_or_else(|| anyhow!("親ディレクトリが見つかりません: {}", current_file.display()))?;
let resolved_path = parent_dir.join(import_path);
let clean_resolved_path = PathBuf::from(clean(resolved_path.to_string_lossy().as_ref()));
Ok(clean_resolved_path)
}
fn parse_imports(content: &str, file_path: &Path) -> Result<Vec<NixImport>> {
let mut imports = Vec::new();
let import_regex = Regex::new(r#"import\s+(?:(?:"([^"]+)")|(?:'([^']+)')|([^\s;]+))"#)?;
for (line_idx, line) in content.lines().enumerate() {
for captures in import_regex.captures_iter(line) {
let path_str = captures
.get(1)
.or_else(|| captures.get(2))
.or_else(|| captures.get(3))
.ok_or_else(|| {
anyhow!(
"インポートパスが見つかりません: {}:{}",
file_path.display(),
line_idx + 1
)
})?
.as_str();
let full_import = captures.get(0).unwrap().as_str().to_string();
let column = captures.get(0).unwrap().start();
let import_path = PathBuf::from(path_str);
imports.push(NixImport {
path: import_path,
_position: (line_idx + 1, column),
full_import,
});
}
}
Ok(imports)
}
fn inline_imports(entry_point: &Path, file_contents: &HashMap<PathBuf, NixFile>) -> Result<String> {
let mut inlined_files = HashSet::new();
inline_file_recursive(entry_point, file_contents, &mut inlined_files)
}
fn inline_file_recursive(
file_path: &Path,
file_contents: &HashMap<PathBuf, NixFile>,
inlined_files: &mut HashSet<PathBuf>,
) -> Result<String> {
let abs_path = if file_path.is_absolute() {
file_path.to_path_buf()
} else {
std::env::current_dir()?.join(file_path)
};
let clean_path = PathBuf::from(clean(abs_path.to_string_lossy().as_ref()));
let nix_file = file_contents
.get(&clean_path)
.ok_or_else(|| anyhow!("ファイル情報が見つかりません: {}", clean_path.display()))?;
if inlined_files.contains(&clean_path) {
return Ok(String::new());
}
inlined_files.insert(clean_path.clone());
let mut result = nix_file.content.clone();
for import in nix_file.imports.iter().rev() {
let import_path = resolve_import_path(&import.path, &clean_path)?;
let inlined_content = inline_file_recursive(&import_path, file_contents, inlined_files)?;
result = result.replace(&import.full_import, &inlined_content);
}
inlined_files.remove(&clean_path);
Ok(result)
}