use crate::args::Args;
use crate::db::LuauLocaleDb;
use crate::language::Language;
use clap::Parser;
use log::{LevelFilter, error, warn};
use serde_json::Value;
use simple_logger::SimpleLogger;
use std::collections::HashMap;
use std::ffi::OsStr;
use std::io::ErrorKind;
use std::path::{Path, PathBuf};
use std::{fs, iter};
mod args;
mod db;
mod language;
fn main() {
let args = Args::parse();
let mut db = LuauLocaleDb::default();
SimpleLogger::new()
.with_level(LevelFilter::Debug)
.init()
.expect("Failed to install logger");
for language in Language::FROSTBITE_LANGUAGES {
let language_files = files_for_langauge(&args.root, language);
if language_files.is_empty() {
warn!(
"No valid entries found for {} (expect any of {:?})",
language.name, language.additional_codes
);
continue;
}
for (path, contents) in language_files {
let map = serde_json::from_str::<HashMap<String, Value>>(&contents)
.unwrap_or_else(|e| panic!("Failed to deserialize {path:?}: {e}"));
for (key, value) in map
.into_iter()
.flat_map(|(key, value)| extract_key_values(&path, key, value))
{
db.insert_mapping(language, key, value)
}
}
}
fs::write(args.output, format!("{db}")).expect("Failed to write output file")
}
fn extract_key_values(
path: &Path,
key: String,
value: Value,
) -> Box<dyn Iterator<Item = (String, String)> + '_> {
match value {
Value::String(str) => Box::new(iter::once((key, str))),
Value::Object(object) => {
Box::new(
object
.into_iter()
.flat_map(move |(inner_key, inner_value)| {
extract_key_values(path, format!("{key}.{inner_key}"), inner_value)
}),
)
}
v => panic!("Unexpected JSON value {v} in {path:?}"),
}
}
fn files_for_langauge(root: &Path, language: &Language) -> Vec<(PathBuf, String)> {
for language_code in iter::once(&language.fb_code).chain(language.additional_codes.iter()) {
let base_path = root.join(language_code);
let direct_path = base_path.join(".json");
if let Some(contents) = read_file(&direct_path) {
return vec![(direct_path, contents)];
}
match fs::read_dir(&base_path) {
Ok(directory) => {
return directory
.filter_map(move |entry| {
let path = match entry {
Ok(entry) => entry.path(),
Err(err) => {
error!("Failed reading an entry in {base_path:?}: {err:?}");
return None;
}
};
if !path.is_file() || path.extension() != Some(OsStr::new("json")) {
warn!("Ignoring {path:?} because it is not a JSON file");
return None;
}
read_file(&path).map(|contents| (path, contents))
})
.collect();
}
Err(err) if err.kind() != ErrorKind::NotFound => {
error!("Unexpected error trying to read {base_path:?} as a directory: {err}");
}
_ => {}
}
}
vec![]
}
fn read_file(path: &Path) -> Option<String> {
match fs::read_to_string(path) {
Ok(contents) => Some(contents),
Err(err) => {
if err.kind() != ErrorKind::NotFound {
error!("Unexpected error trying to read {path:?}: {err}");
}
None
}
}
}