use anyhow::{Context, Result};
use glob::glob;
use std::path::PathBuf;
use crate::compile;
use crate::formatters::Formatter;
pub fn compile_folder(
folder: &PathBuf,
out_folder: &PathBuf,
format: Option<Formatter>,
ast: bool,
skip_errors: bool,
pseudo_locale: Option<compile::PseudoLocale>,
ignore_tag: bool,
) -> Result<()> {
use crate::compile::compile;
if !folder.exists() {
anyhow::bail!("Source folder does not exist: {}", folder.display());
}
if !folder.is_dir() {
anyhow::bail!("Source path is not a directory: {}", folder.display());
}
std::fs::create_dir_all(out_folder).with_context(|| {
format!(
"Failed to create output directory: {}",
out_folder.display()
)
})?;
let pattern = folder.join("**/*.json");
let pattern_str = pattern
.to_str()
.context("Folder path contains invalid UTF-8")?;
let json_files: Vec<PathBuf> = match glob(pattern_str) {
Ok(paths) => paths.filter_map(Result::ok).collect(),
Err(e) => {
anyhow::bail!("Failed to read folder pattern '{}': {}", pattern_str, e);
}
};
if json_files.is_empty() {
eprintln!(
"Warning: No .json files found in folder {}",
folder.display()
);
return Ok(());
}
eprintln!("Found {} JSON files to compile", json_files.len());
let mut success_count = 0;
let mut error_count = 0;
for json_file in &json_files {
let relative_path = json_file
.strip_prefix(folder)
.with_context(|| format!("Failed to get relative path for {}", json_file.display()))?;
let out_file = out_folder.join(relative_path);
if let Some(parent) = out_file.parent() {
std::fs::create_dir_all(parent)
.with_context(|| format!("Failed to create directory: {}", parent.display()))?;
}
eprintln!("Compiling {} ...", relative_path.display());
match compile(
&[json_file.clone()],
format,
Some(&out_file),
ast,
skip_errors,
pseudo_locale,
ignore_tag,
) {
Ok(_) => {
success_count += 1;
}
Err(e) => {
error_count += 1;
eprintln!(" Error: {}", e);
}
}
}
eprintln!(
"\n✓ Folder compilation complete: {} succeeded, {} failed",
success_count, error_count
);
if error_count > 0 {
anyhow::bail!("{} file(s) failed to compile", error_count);
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
use serde_json::json;
use std::fs;
use tempfile::tempdir;
#[test]
fn test_compile_folder_simple() {
let src_dir = tempdir().unwrap();
let out_dir = tempdir().unwrap();
fs::write(
src_dir.path().join("en.json"),
json!({"greeting": {"defaultMessage": "Hello!"}}).to_string(),
)
.unwrap();
fs::write(
src_dir.path().join("fr.json"),
json!({"greeting": {"defaultMessage": "Bonjour!"}}).to_string(),
)
.unwrap();
compile_folder(
&src_dir.path().to_path_buf(),
&out_dir.path().to_path_buf(),
None,
false,
false,
None,
false,
)
.unwrap();
assert!(out_dir.path().join("en.json").exists());
assert!(out_dir.path().join("fr.json").exists());
let en_content = fs::read_to_string(out_dir.path().join("en.json")).unwrap();
let en_json: serde_json::Value = serde_json::from_str(&en_content).unwrap();
assert_eq!(en_json["greeting"], "Hello!");
let fr_content = fs::read_to_string(out_dir.path().join("fr.json")).unwrap();
let fr_json: serde_json::Value = serde_json::from_str(&fr_content).unwrap();
assert_eq!(fr_json["greeting"], "Bonjour!");
}
#[test]
fn test_compile_folder_preserves_structure() {
let src_dir = tempdir().unwrap();
let out_dir = tempdir().unwrap();
fs::create_dir(src_dir.path().join("locales")).unwrap();
fs::create_dir(src_dir.path().join("locales/en")).unwrap();
fs::create_dir(src_dir.path().join("locales/fr")).unwrap();
fs::write(
src_dir.path().join("locales/en/messages.json"),
json!({"greeting": {"defaultMessage": "Hello!"}}).to_string(),
)
.unwrap();
fs::write(
src_dir.path().join("locales/fr/messages.json"),
json!({"greeting": {"defaultMessage": "Bonjour!"}}).to_string(),
)
.unwrap();
compile_folder(
&src_dir.path().to_path_buf(),
&out_dir.path().to_path_buf(),
None,
false,
false,
None,
false,
)
.unwrap();
assert!(out_dir.path().join("locales/en/messages.json").exists());
assert!(out_dir.path().join("locales/fr/messages.json").exists());
let en_content =
fs::read_to_string(out_dir.path().join("locales/en/messages.json")).unwrap();
let en_json: serde_json::Value = serde_json::from_str(&en_content).unwrap();
assert_eq!(en_json["greeting"], "Hello!");
}
#[test]
fn test_compile_folder_with_formatter() {
let src_dir = tempdir().unwrap();
let out_dir = tempdir().unwrap();
fs::write(
src_dir.path().join("messages.json"),
json!({
"greeting": {
"defaultMessage": "Hello {name}!",
"description": "Greeting"
}
})
.to_string(),
)
.unwrap();
compile_folder(
&src_dir.path().to_path_buf(),
&out_dir.path().to_path_buf(),
Some(Formatter::Default),
false,
false,
None,
false,
)
.unwrap();
let content = fs::read_to_string(out_dir.path().join("messages.json")).unwrap();
let json: serde_json::Value = serde_json::from_str(&content).unwrap();
assert_eq!(json["greeting"], "Hello {name}!");
}
#[test]
fn test_compile_folder_to_ast() {
let src_dir = tempdir().unwrap();
let out_dir = tempdir().unwrap();
fs::write(
src_dir.path().join("messages.json"),
json!({"greeting": {"defaultMessage": "Hello {name}!"}}).to_string(),
)
.unwrap();
compile_folder(
&src_dir.path().to_path_buf(),
&out_dir.path().to_path_buf(),
None,
true, false,
None,
false,
)
.unwrap();
let content = fs::read_to_string(out_dir.path().join("messages.json")).unwrap();
let json: serde_json::Value = serde_json::from_str(&content).unwrap();
assert!(json["greeting"].is_array());
}
#[test]
fn test_compile_folder_source_not_exists() {
let out_dir = tempdir().unwrap();
let nonexistent = PathBuf::from("/nonexistent/path");
let result = compile_folder(
&nonexistent,
&out_dir.path().to_path_buf(),
None,
false,
false,
None,
false,
);
assert!(result.is_err());
assert!(result.unwrap_err().to_string().contains("does not exist"));
}
#[test]
fn test_compile_folder_source_not_directory() {
let dir = tempdir().unwrap();
let file = dir.path().join("file.txt");
let out_dir = tempdir().unwrap();
fs::write(&file, "content").unwrap();
let result = compile_folder(
&file,
&out_dir.path().to_path_buf(),
None,
false,
false,
None,
false,
);
assert!(result.is_err());
assert!(result.unwrap_err().to_string().contains("not a directory"));
}
#[test]
fn test_compile_folder_no_json_files() {
let src_dir = tempdir().unwrap();
let out_dir = tempdir().unwrap();
fs::write(src_dir.path().join("readme.txt"), "text").unwrap();
let result = compile_folder(
&src_dir.path().to_path_buf(),
&out_dir.path().to_path_buf(),
None,
false,
false,
None,
false,
);
assert!(result.is_ok());
}
#[test]
fn test_compile_folder_partial_failure() {
let src_dir = tempdir().unwrap();
let out_dir = tempdir().unwrap();
fs::write(
src_dir.path().join("valid.json"),
json!({"greeting": {"defaultMessage": "Hello!"}}).to_string(),
)
.unwrap();
fs::write(
src_dir.path().join("invalid.json"),
json!({"greeting": {"defaultMessage": "Hello {name"}}).to_string(), )
.unwrap();
let result = compile_folder(
&src_dir.path().to_path_buf(),
&out_dir.path().to_path_buf(),
None,
false,
false,
None,
false,
);
assert!(result.is_err());
}
#[test]
fn test_compile_folder_multiple_files() {
let src_dir = tempdir().unwrap();
let out_dir = tempdir().unwrap();
for i in 0..5 {
fs::write(
src_dir.path().join(format!("msg{}.json", i)),
json!({format!("msg{}", i): format!("Message {}", i)}).to_string(),
)
.unwrap();
}
compile_folder(
&src_dir.path().to_path_buf(),
&out_dir.path().to_path_buf(),
None,
false,
false,
None,
false,
)
.unwrap();
for i in 0..5 {
assert!(out_dir.path().join(format!("msg{}.json", i)).exists());
}
}
#[test]
fn test_compile_folder_creates_output_dir() {
let src_dir = tempdir().unwrap();
let parent_dir = tempdir().unwrap();
let out_dir = parent_dir.path().join("nested/output/dir");
fs::write(
src_dir.path().join("messages.json"),
json!({"greeting": "Hello!"}).to_string(),
)
.unwrap();
compile_folder(
&src_dir.path().to_path_buf(),
&out_dir,
None,
false,
false,
None,
false,
)
.unwrap();
assert!(out_dir.exists());
assert!(out_dir.join("messages.json").exists());
}
}