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(""); let clang = Value::String(String::from("clang")); let blank = Value::String(String::from("")); 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 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} cflags_{name} = {compiler_flags} linker_flags_{name} = {linker_flags} linker_libs_{name} = {linker_libs} rule cc_{name} deps = gcc depfile = $out.d command = {compiler} $cflags_{name} -MD -MF $out.d -o $out -c $in description = Building [{name}] object $out rule link_{name} command = {linker} $linker_flags_{name} -o $out $in $linker_libs_{name} description = Linking [{name}] $out build $builddir/{name}: mkdir "# )); let sources = config_table .get("sources") .unwrap_or_else(|| panic!("fatal: must provide sources")); let mut source_list: Vec = Vec::new(); match sources { Value::Array(a) => { for elem in a { match elem { Value::String(s) => { let obj_name = format!("$builddir/{name}/{s}.o"); ret.push_str(&format!( "build {obj_name}: cc_{name} {s} | build.ninja || $builddir/{name}\n" )); source_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}/{name}: link_{name} ")); ret.push_str(&source_list.join(" ")); ret.push_str(&format!(" \nbuild {name}: phony $builddir/{name}/{name}\n")); ret.push_str(&format!("# END TARGET {name}\n\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}")); }