use core::panic; use std::fs::{read, write}; use toml::{Table, Value}; fn print_tabs(num: usize) { for _ in 0..num { print!(" "); } } fn print_table(table: &Table, depth: usize) { for k in table.keys() { match table.get(k) { Some(v) => match v { Value::Table(t) => { print_tabs(depth); println!("{k}:"); print_table(&t, depth + 1) } Value::String(s) => { print_tabs(depth); println!("{k}: {s}"); } Value::Array(a) => { print_tabs(depth); println!("(array){k}:"); for element in a { match element { Value::String(s) => println!(" {}", s), _ => todo!(), } } } _ => continue, }, None => continue, } } } fn target_generator(name: &str, config_table: &Table) -> String { let mut ret = String::from("\n"); let clang = Value::String(String::from("clang")); let blank = Value::String(String::from("")); let aout = Value::String(String::from("a.out")); let compiler = match config_table.get("compiler").unwrap_or(&clang) { Value::String(s) => s, _ => panic!("fatal: {name}.compiler: invalid type (must be string)"), }; let compiler_flags = match config_table.get("compiler_flags").unwrap_or(&blank) { Value::String(s) => s, _ => panic!("fatal: {name}.compiler_flags: invalid type (must be string)"), }; let linker_flags = match config_table.get("linker_flags").unwrap_or(&blank) { Value::String(s) => s, _ => panic!("fatal: {name}.linker_flags: invalid type (must be string)"), }; let linker_libs = match config_table.get("linker_libs").unwrap_or(&blank) { Value::String(s) => s, _ => panic!("fatal: {name}.linker_libs: invalid type (must be string)"), }; let filename = match config_table.get("filename").unwrap_or(&aout) { Value::String(s) => s, _ => panic!("fatal: {name}.filename: invalid type (must be string)"), }; let compiler_linker = Value::String(compiler.to_string()); let linker = match config_table.get("linker").unwrap_or(&compiler_linker) { Value::String(s) => s, _ => panic!("fatal: {name}.linker: invalid type (must be string)"), }; ret.push_str(&format!( r#"# BEGIN TARGET {name} rule cc_{name} deps = gcc depfile = $dep command = {compiler} {compiler_flags} -MD -MF $dep -o $out -c $in description = Building object $out rule link_{name} command = {linker} {linker_flags} -o $out $in {linker_libs} description = Linking $out build $builddir/{name}/obj: mkdir build $builddir/{name}/dep: mkdir "# )); let sources = config_table .get("sources") .unwrap_or_else(|| panic!("fatal: must provide sources")); let mut object_list: Vec = Vec::new(); match sources { Value::Array(a) => { for elem in a { match elem { Value::String(s) => { let new_file = s.replace("/", "-"); let obj_name = format!("$builddir/{name}/obj/{new_file}.o"); let dep_name = format!("$builddir/{name}/dep/{new_file}.o.d"); ret.push_str(&format!( "build {obj_name}: cc_{name} {s} | build.ninja || $builddir/{name}/obj $builddir/{name}/dep\n dep = {dep_name}\n" )); object_list.push(obj_name); } _ => panic!( "fatal: element in {name}.sources: invalid source type (must be a string)" ), } } } _ => panic!("fatal: {name}.sources: invalid sources type (must be a list of strings)"), }; ret.push_str(&format!( "\nbuild $builddir/{name}/{filename}: link_{name} " )); ret.push_str(&object_list.join(" ")); ret.push_str(&format!( " \nbuild {name}: phony $builddir/{name}/{filename}\n" )); ret.push_str(&format!("# END TARGET {name}\n")); ret } fn main() { let conf_file_name = "ngen.toml"; let conf_file_bytes = read(conf_file_name).unwrap_or_else(|e| panic!("fatal: could not read file: {e}")); let conf_file = String::from_utf8_lossy(&conf_file_bytes); let conf = toml::from_str::(&conf_file) .unwrap_or_else(|e| panic!("fatal: could not parse config file: {e}")); let mut ninjafile = String::from( r#"builddir = build rule mkdir command = mkdir -p $out description = Creating directory $out rule regen command = ngen -f $in generator = 1 description = Regenerating build.ninja build build.ninja: regen ngen.toml pool = console "#, ); for k in conf.keys() { match conf.get(k) { Some(v) => match v { Value::Table(t) => { //print_table(&t, 0); ninjafile.push_str(&target_generator(&k, &t)); } Value::String(s) => { println!("{k}: {s}"); } _ => continue, }, None => continue, } } write("build.ninja", ninjafile) .unwrap_or_else(|e| panic!("fatal: could not write ninja file: {e}")); }