use std::{collections::HashMap, env, fs, path::PathBuf, process};
use anyhow::anyhow;
use clang::Clang;
use clap::Parser;
use inquire::{Select, Text};
use log::Level;
use crate::{
cli::{Arguments, Command},
config::Config,
};
mod build;
mod cli;
mod config;
mod exports;
mod header;
struct Saja {
pub clang: Clang,
pub args: Arguments,
pub profiles: HashMap<String, Vec<String>>,
pub config: Config,
}
impl Saja {
fn default_profiles() -> HashMap<String, Vec<String>> {
fn profile(common: &[&str], extra: &[&str]) -> Vec<String> {
common
.iter()
.chain(extra.iter())
.map(ToString::to_string)
.collect()
}
let common = vec![
"-Wall",
"-Wextra",
"-Werror",
"-Wno-unknown-pragmas",
r#"-Dpublic=__attribute__((annotate("public")))"#,
];
let cflags = env::var("CFLAGS").unwrap_or_default();
let common = [
common.as_slice(),
cflags.split(' ').collect::<Vec<_>>().as_slice(),
]
.concat();
HashMap::from([
("debug".into(), profile(&common, &["-g", "-O0", "-DDEBUG"])),
(
"release".into(),
profile(&common, &["-O3", "-march=native"]),
),
("syntax".into(), profile(&common, &["-fsyntax-only"])),
])
}
fn new() -> anyhow::Result<Self> {
let args = Arguments::parse();
let command = args.command;
let config = match Config::load(&env::current_dir()?) {
Ok(config) => config,
Err(_) => {
if command == Command::Init {
Self::init()?;
process::exit(0);
} else {
anyhow::bail!(
"could not find saja.toml in the current directory, did you initialise a project?"
);
}
}
};
Ok(Self {
clang: Clang::new().map_err(|e| anyhow!(e))?,
args: Arguments::parse(),
profiles: Self::default_profiles(),
config,
})
}
fn init() -> anyhow::Result<()> {
let username = process::Command::new("git")
.args(["config", "--global", "user.name"])
.output()
.ok()
.and_then(|o| String::from_utf8(o.stdout).ok())
.map(|s| s.trim().to_string());
let name = Text::new("project name").prompt()?;
let path = PathBuf::from(&name);
let author = Text::new("author")
.with_default(username.as_deref().unwrap_or("John Doe"))
.prompt()?;
let standard = Select::new(
"C standard version",
vec![
"c89", "c90", "c99", "c11", "c17", "c23", "gnu89", "gnu90", "gnu99", "gnu11",
"gnu17", "gnu23",
],
)
.prompt()?
.to_string();
let version = Text::new("version").with_default("0.1.0").prompt()?;
let config = Config {
name: name.clone(),
version,
standard,
author,
license: "NOASSERTION".into(),
};
let src = path.join("src");
fs::create_dir_all(&src)?;
config.store(&path)?;
fs::write(
src.join("main.c"),
r#"#include <stdio.h>
int main() {
printf("Hello, world!\n");
return 0;
}"#,
)?;
println!(
"welcome to saja!
next: `cd {name}`, `saja build`, and `.saja/{name}`"
);
Ok(())
}
}
fn main() -> anyhow::Result<()> {
let saja = Saja::new()?;
let level = if saja.args.verbose {
Level::Debug
} else {
Level::Info
};
clang_log::init(level, env!("CARGO_PKG_NAME"));
match &saja.args.command {
Command::Build {
profile,
release: _,
no_compile_commands,
} => saja.build(&env::current_dir()?, profile, no_compile_commands)?,
Command::Init => unreachable!(),
}
Ok(())
}