diff --git a/src/config.rs b/src/config.rs deleted file mode 100644 index ef90d0e..0000000 --- a/src/config.rs +++ /dev/null @@ -1,47 +0,0 @@ -use toml::{Table, Value}; - -#[derive(Debug, Clone)] -pub struct Config { - pub build_dir: String, - pub compile_commands: bool, - pub compile_commands_target: String, -} - -impl Config { - pub fn get_default() -> Self { - Config { - build_dir: String::from("build"), - compile_commands: false, - compile_commands_target: String::from(""), - } - } - pub fn set(&mut self, table: &Table) { - for k in table.keys() { - match &*k.to_owned() { - "build_dir" => { - if let Value::String(s) = table.get(k).unwrap() { - self.build_dir = s.to_owned(); - } else { - panic!("error parsing config table: {k} invalid type, must be a string."); - } - } - "compile_commands" => { - if let Value::Boolean(b) = table.get(k).unwrap() { - //eprintln!("warn: config.compile_commands has no functionality (yet)"); - self.compile_commands = *b; - } else { - panic!("error parsing config table: {k} invalid type, must be a boolean."); - } - } - "compile_commands_target" => { - if let Value::String(s) = table.get(k).unwrap() { - self.compile_commands_target = s.to_owned(); - } else { - panic!("error parsing config table: {k} invalid type, must be a string."); - } - } - _ => panic!("error parsing config table: unrecognized key {k}."), - } - } - } -} diff --git a/src/config_file_parsing.rs b/src/config_file_parsing.rs new file mode 100644 index 0000000..ca4cb24 --- /dev/null +++ b/src/config_file_parsing.rs @@ -0,0 +1,106 @@ +use std::fmt::Display; + +use toml::{Table, Value}; + +#[derive(Debug)] +pub struct ConfigFileError<'a> { + pub key_path: String, + pub message: &'a str, +} + +impl Display for ConfigFileError<'_> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!( + f, + "config file error: key {}: {}.", + self.key_path, self.message + ) + } +} + +pub fn get_key_as_str<'a>( + table: &'a Table, + key: &str, + key_path: String, +) -> Result, ConfigFileError<'a>> { + if let Some(v) = table.get(key) { + if let Value::String(s) = v { + Ok(Some(s)) + } else { + Err(ConfigFileError { + key_path, + message: "invalid type, should be string", + }) + } + } else { + Ok(None) + } +} + +pub fn get_key_as_table<'a>( + table: &'a Table, + key: &str, + key_path: String, +) -> Result, ConfigFileError<'a>> { + if let Some(v) = table.get(key) { + if let Value::Table(s) = v { + Ok(Some(s)) + } else { + Err(ConfigFileError { + key_path, + message: "invalid type, should be a table of key-value pairs", + }) + } + } else { + Ok(None) + } +} + +pub fn get_key_as_bool<'a>( + table: &'a Table, + key: &str, + key_path: String, +) -> Result, ConfigFileError<'a>> { + if let Some(v) = table.get(key) { + if let Value::Boolean(s) = v { + Ok(Some(*s)) + } else { + Err(ConfigFileError { + key_path, + message: "invalid type, should be boolean", + }) + } + } else { + Ok(None) + } +} + +pub fn get_key_as_vec_str<'a>( + table: &'a Table, + key: &str, + key_path: String, +) -> Result>, ConfigFileError<'a>> { + if let Some(v) = table.get(key) { + if let Value::Array(a) = v { + let mut ret: Vec<&'a str> = vec![]; + for elem in a { + if let Value::String(s) = elem { + ret.push(&s); + } else { + return Err(ConfigFileError { + key_path: format!("{key_path} elemement '{elem}'"), + message: "invalid type, should be string", + }); + } + } + Ok(Some(ret)) + } else { + Err(ConfigFileError { + key_path, + message: "invalid type, should be array of strings", + }) + } + } else { + Ok(None) + } +} diff --git a/src/config_table.rs b/src/config_table.rs new file mode 100644 index 0000000..84e0dee --- /dev/null +++ b/src/config_table.rs @@ -0,0 +1,45 @@ +use toml::Table; + +use crate::config_file_parsing::*; + +#[derive(Debug, Clone)] +pub struct ConfigTable<'a> { + pub build_dir: &'a str, + pub compile_commands: bool, + pub compile_commands_target: &'a str, +} + +impl<'a> ConfigTable<'a> { + pub fn get_default() -> Self { + ConfigTable { + build_dir: "build", + compile_commands: false, + compile_commands_target: "main", + } + } + + pub fn from_table(table: &'a Table, key_path: String) -> Result> { + let mut ret = Self::get_default(); + + if let Some(v) = get_key_as_str(table, "build_dir", format!("{key_path}.build_dir"))? { + ret.build_dir = v; + } + + if let Some(v) = get_key_as_bool( + table, + "compile_commands", + format!("{key_path}.compile_commands"), + )? { + ret.compile_commands = v; + } + + if let Some(v) = get_key_as_str( + table, + "compile_commands_target", + format!("{key_path}.compile_commands_target"), + )? { + ret.compile_commands_target = v; + } + Ok(ret) + } +} diff --git a/src/main.rs b/src/main.rs index 9edb07d..6c72548 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,4 +1,6 @@ use clap::Parser; +use config_file_parsing::get_key_as_table; +use core::{panic, str}; use std::{ fs::{read_to_string, write}, path::Path, @@ -9,8 +11,11 @@ use toml::{Table, Value}; mod target; use target::*; -mod config; -use config::*; +mod config_table; +use config_table::*; + +mod config_file_parsing; +mod target_options; /// ngen is a build file generator for the ninja build system. #[derive(Parser)] @@ -125,109 +130,128 @@ fn main() { let config_file = read_to_string(config_file_name) .unwrap_or_else(|e| panic!("could not read config file {config_file_name}: {e}")); - let parsed_config = toml::from_str::(&config_file) + let toml_config = toml::from_str::
(&config_file) .unwrap_or_else(|e| panic!("error parsing toml file {config_file_name}: {e}")); // get config - let mut config = Config::get_default(); - if let Some(v) = parsed_config.get("config") { - if let Value::Table(t) = v { - config.set(&t); - } else { - panic!("error parsing config table: config invalid type, must be a table."); - } + let config_tbl: ConfigTable; + match get_key_as_table(&toml_config, "config", "".to_string()) { + Ok(v) => match v { + Some(c) => { + config_tbl = ConfigTable::from_table(&c, ".config".to_string()) + .unwrap_or_else(|e| panic!("{e}")) + } + None => config_tbl = ConfigTable::get_default(), + }, + Err(e) => panic!("{e}"), } + println!("{:?}", config_tbl); + //let mut config = Config::get_default(); + //if let Some(v) = parsed_config.get("config") { + // if let Value::Table(t) = v { + // config.set(&t); + // } else { + // panic!("error parsing config table: config invalid type, must be a table."); + // } + //} //---- // parse the main target first - let mut targets: Vec = Vec::new(); - let main_target = Target::new(&parsed_config, "main", None); - targets.push(main_target.clone()); - let mut target_for_comp_cmds = main_target; - let mut target_for_comp_cmds_changed = false; + //let mut targets: Vec = vec![]; - // parse other targets - for k in parsed_config.keys() { - if let Value::Table(t) = parsed_config.get(k).unwrap() { - if k == "config" { - continue; // ignore config - } - let target = Target::new(&t, k, Some(&targets[0])); - targets.push(target); - if let Some(_) = args.write_compile_commands { - if *k == config.compile_commands_target { - target_for_comp_cmds = targets[targets.len() - 1].clone(); - target_for_comp_cmds_changed = true; - } - } - } + match Target::from_table(&toml_config, "main", "".to_string()) { + Ok(v) => println!("{:?}", v), + Err(e) => panic!("{e}"), } - if let Some(file) = args.write_compile_commands { - if config.compile_commands_target == "" { - eprintln!( - "ngen: warn: no compile_commands target found, using 'main' target by default." - ); - } else if !target_for_comp_cmds_changed && config.compile_commands_target != "main" { - let c = &config.compile_commands_target; - eprintln!( - "ngen: warn: compile_commands target {c} not found, using 'main' target instead." - ); - } - write( - &file, - &gen_compile_commands(&target_for_comp_cmds, &config.build_dir), - ) - .unwrap_or_else(|e| panic!("fatal: could not write {file}: {e}")); - // no need to write ninja file - exit(0); - } + exit(10); - let build_dir = &config.build_dir; - let mut ninjafile = format!( - "\ -# Generated by ngen. Do not modify by hand. - -builddir = {build_dir} - -rule mkdir - command = mkdir -p $out - description = Creating directory $out - -rule regen_ninjafile - command = ngen -c $in -o $out - generator = 1 - description = Regenerating $out - -", - ); - - if config.compile_commands { - ninjafile.push_str(&format!( - "\ -rule regen_compile_commands - command = ngen -c $in --write-compile-commands $out - description = Regenerating $out - -build $builddir: mkdir - -build $builddir/compile_commands.json: regen_compile_commands {config_file_name} || $builddir - pool = console - -build {output_file}: regen_ninjafile {config_file_name} || $builddir/compile_commands.json - pool = console -" - )); - } else { - ninjafile.push_str(&format!( - "build {output_file}: regen_ninjafile {config_file_name}\n pool = console\n" - )); - } - - for t in targets { - ninjafile.push_str(&gen_ninja_code(&t)); - } - - write(output_file, &ninjafile) - .unwrap_or_else(|e| panic!("could not write ninja file {ninjafile}: {e}")); + // targets.push(main_target.clone()); + // let mut target_for_comp_cmds = main_target; + // let mut target_for_comp_cmds_changed = false; + // + // // parse other targets + // for k in parsed_config.keys() { + // if let Value::Table(t) = parsed_config.get(k).unwrap() { + // if k == "config" { + // continue; // ignore config + // } + // let target = Target::new(&t, k, Some(&targets[0])); + // targets.push(target); + // if let Some(_) = args.write_compile_commands { + // if *k == config.compile_commands_target { + // target_for_comp_cmds = targets[targets.len() - 1].clone(); + // target_for_comp_cmds_changed = true; + // } + // } + // } + // } + // + // if let Some(file) = args.write_compile_commands { + // if config.compile_commands_target == "" { + // eprintln!( + // "ngen: warn: no compile_commands target found, using 'main' target by default." + // ); + // } else if !target_for_comp_cmds_changed && config.compile_commands_target != "main" { + // let c = &config.compile_commands_target; + // eprintln!( + // "ngen: warn: compile_commands target {c} not found, using 'main' target instead." + // ); + // } + // write( + // &file, + // &gen_compile_commands(&target_for_comp_cmds, &config.build_dir), + // ) + // .unwrap_or_else(|e| panic!("fatal: could not write {file}: {e}")); + // // no need to write ninja file + // exit(0); + // } + // + // let build_dir = &config.build_dir; + // let mut ninjafile = format!( + // "\ + //# Generated by ngen. Do not modify by hand. + // + //builddir = {build_dir} + // + //rule mkdir + // command = mkdir -p $out + // description = Creating directory $out + // + //rule regen_ninjafile + // command = ngen -c $in -o $out + // generator = 1 + // description = Regenerating $out + // + //", + // ); + // + // if config.compile_commands { + // ninjafile.push_str(&format!( + // "\ + //rule regen_compile_commands + // command = ngen -c $in --write-compile-commands $out + // description = Regenerating $out + // + //build $builddir: mkdir + // + //build $builddir/compile_commands.json: regen_compile_commands {config_file_name} || $builddir + // pool = console + // + //build {output_file}: regen_ninjafile {config_file_name} || $builddir/compile_commands.json + // pool = console + //" + // )); + // } else { + // ninjafile.push_str(&format!( + // "build {output_file}: regen_ninjafile {config_file_name}\n pool = console\n" + // )); + // } + // + // for t in targets { + // ninjafile.push_str(&gen_ninja_code(&t)); + // } + // + // write(output_file, &ninjafile) + // .unwrap_or_else(|e| panic!("could not write ninja file {ninjafile}: {e}")); } diff --git a/src/target.rs b/src/target.rs index a42817d..e2cd0ef 100644 --- a/src/target.rs +++ b/src/target.rs @@ -1,145 +1,90 @@ -use toml::{Table, Value}; +use toml::Table; + +use crate::config_file_parsing::*; +use crate::target_options::*; #[derive(Debug, Clone)] -pub struct Target { - pub name: String, - pub outfile: String, - pub compiler: String, - pub compiler_flags: Vec, - pub linker: String, - pub linker_flags: Vec, - pub linker_libs: Vec, - pub sources: Vec, +pub struct Target<'a> { + pub name: &'a str, + pub outfile: &'a str, + pub compiler: &'a str, + pub compiler_flags: Vec<&'a str>, + pub linker: &'a str, + pub linker_flags: Vec<&'a str>, + pub linker_libs: Vec<&'a str>, + pub sources: Vec<&'a str>, pub is_default: bool, + pub opts: TargetOptions<'a>, } -impl Target { - fn get_default() -> Self { +impl<'a> Target<'a> { + pub fn get_default() -> Self { Target { - name: String::from(""), - outfile: String::from("a.out"), - compiler: String::from("cc"), - compiler_flags: Vec::new(), - linker: String::from(""), - linker_flags: Vec::new(), - linker_libs: Vec::new(), - sources: Vec::new(), + name: "", + outfile: "a.out", + compiler: "cc", + compiler_flags: vec![""], + linker: "cc", + linker_flags: vec![""], + linker_libs: vec![""], + sources: vec![""], is_default: false, + opts: TargetOptions::get_default(), } } - pub fn new(table: &Table, name: &str, inherit_from: Option<&Self>) -> Self { - let mut ret: Self; - let mut linker_set = false; + pub fn from_table( + table: &'a Table, + name: &'a str, + key_path: String, + ) -> Result> { + let mut ret = Self::get_default(); - if let Some(t) = inherit_from { - ret = t.clone(); - } else { - ret = Self::get_default(); + if let Some(v) = get_key_as_table(table, "opts", format!("{key_path}.opts"))? { + ret.opts = TargetOptions::from_table(&v, format!("{key_path}.opts"))?; } - ret.name = String::from(name); - ret.is_default = false; + ret.name = name; - for k in table.keys() { - match &*k.to_owned() { - "outfile" => { - if let Value::String(s) = table.get(k).unwrap() { - ret.outfile = s.to_owned(); - } else { - panic!("error parsing target {name}: {k} invalid type, must be a string."); - } - } - "compiler" => { - if let Value::String(s) = table.get(k).unwrap() { - ret.compiler = s.to_owned(); - } else { - panic!("error parsing target {name}: {k} invalid type, must be a string."); - } - } - "compiler_flags" => { - if let Value::Array(a) = table.get(k).unwrap() { - for element in a { - if let Value::String(s) = element { - ret.compiler_flags.push(s.to_owned()); - } else { - panic!("error parsing target {name}: {k} invalid type, must be a array of strings."); - } - } - } else { - panic!("error parsing target {name}: {k} invalid type, must be a array of strings."); - } - } - "linker" => { - if let Value::String(s) = table.get(k).unwrap() { - ret.linker = s.to_owned(); - linker_set = true; - } else { - panic!("error parsing target {name}: {k} invalid type, must be a string."); - } - } - "linker_flags" => { - if let Value::Array(a) = table.get(k).unwrap() { - for element in a { - if let Value::String(s) = element { - ret.linker_flags.push(s.to_owned()); - } else { - panic!("error parsing target {name}: {k} invalid type, must be a array of strings."); - } - } - } else { - panic!("error parsing target {name}: {k} invalid type, must be a array of strings."); - } - } - "linker_libs" => { - if let Value::Array(a) = table.get(k).unwrap() { - for element in a { - if let Value::String(s) = element { - ret.linker_libs.push(s.to_owned()); - } else { - panic!("error parsing target {name}: {k} invalid type, must be a array of strings."); - } - } - } else { - panic!("error parsing target {name}: {k} invalid type, must be a array of strings."); - } - } - "sources" => { - if let Value::Array(a) = table.get(k).unwrap() { - for element in a { - if let Value::String(s) = element { - ret.sources.push(s.to_owned()); - } else { - panic!("error parsing target {name}: {k} invalid type, must be a array of strings."); - } - } - } else { - panic!("error parsing target {name}: {k} invalid type, must be a array of strings."); - } - } - "default" => { - if let Value::Boolean(b) = table.get(k).unwrap() { - ret.is_default = *b; - } else { - panic!("error parsing target {name}: {k} invalid type, must be a boolean."); - } - } - _ => { - if let Value::Table(_) = table.get(k).unwrap() { - } else { - panic!("error parsing target {name}: unrecognized key {k}."); - } - } - } + if let Some(v) = get_key_as_str(table, "outfile", format!("{key_path}.outfile"))? { + ret.outfile = v; } - if !linker_set { - ret.linker = ret.compiler.clone(); + if let Some(v) = get_key_as_str(table, "compiler", format!("{key_path}.compiler"))? { + ret.compiler = v; } - if ret.sources == Vec::::new() { - panic!("error parsing target {name}: you MUST specify at least one source"); + if let Some(v) = get_key_as_vec_str( + table, + "compiler_flags", + format!("{key_path}.compiler_flags"), + )? { + ret.compiler_flags = v; } - ret + if let Some(v) = get_key_as_str(table, "linker", format!("{key_path}.linker"))? { + ret.linker = v; + } + + if let Some(v) = + get_key_as_vec_str(table, "linker_flags", format!("{key_path}.linker_flags"))? + { + ret.linker_flags = v; + } + + if let Some(v) = + get_key_as_vec_str(table, "linker_libs", format!("{key_path}.linker_libs"))? + { + ret.linker_libs = v; + } + + if let Some(v) = get_key_as_vec_str(table, "sources", format!("{key_path}.sources"))? { + ret.sources = v; + } + + if let Some(v) = get_key_as_bool(table, "is_default", format!("{key_path}.is_default"))? { + ret.is_default = v; + } + + Ok(ret) } } diff --git a/src/target_options.rs b/src/target_options.rs new file mode 100644 index 0000000..6a2c027 --- /dev/null +++ b/src/target_options.rs @@ -0,0 +1,30 @@ +use toml::{Table, Value}; + +use crate::config_file_parsing::*; + +#[derive(Debug, Clone)] +pub struct TargetOptions<'a> { + pub inherit: bool, + pub inherit_from: &'a str, +} + +impl<'a> TargetOptions<'a> { + pub fn get_default() -> Self { + TargetOptions { + inherit: true, + inherit_from: "main", + } + } + pub fn from_table(table: &'a Table, key_path: String) -> Result> { + let mut ret = Self::get_default(); + + if let Some(v) = get_key_as_bool(table, "inherit", format!("{key_path}.inherit"))? { + ret.inherit = v; + } + if let Some(v) = get_key_as_str(table, "inherit_from", format!("{key_path}.inherit_from"))? + { + ret.inherit_from = v; + } + Ok(ret) + } +}