// Copyright (C) 2022-2022 Fuwn // SPDX-License-Identifier: GPL-3.0-only #![deny( warnings, nonstandard_style, unused, future_incompatible, rust_2018_idioms, unsafe_code )] #![deny(clippy::all, clippy::nursery, clippy::pedantic)] #![recursion_limit = "128"] #![doc( html_logo_url = "https://emojipedia-us.s3.dualstack.us-west-1.amazonaws.com/thumbs/160/twitter/282/ribbon_1f380.png", html_favicon_url = "https://emojipedia-us.s3.dualstack.us-west-1.amazonaws.com/thumbs/160/twitter/282/ribbon_1f380.png" )] use std::{fmt, fmt::Formatter, io::Read}; use rlua::{Lua, Table}; const VERSION: &str = env!("CARGO_PKG_VERSION"); #[allow(unused)] #[derive(PartialEq)] enum GetRequired { Yes, No, } /// Get and store a variable from Lua context, if it doesn't exist; `None`. /// /// With help from: /// /// - /// - /// - /// - #[macro_export] macro_rules! get_or_none { ($table:ident, $key:expr, $key_type:ty, $assign_to:tt, $required:expr) => { $assign_to = if $required == GetRequired::No { match $table.get::<_, $key_type>($key) { Ok(some_value) => Some(some_value), Err(_) => None, } } else { Some($table.get::<_, $key_type>($key).expect(&format!( "could not access required global `{}`, perhaps you've forgotten to define it ?", $key, ))) } }; ($table:ident, $from_table:expr, $key:expr, $key_type:ty, $assign_to:tt, $required:expr) => { $assign_to = if $required == GetRequired::No { match $table.get::<_, $key_type>($key) { Ok(some_value) => Some(some_value), Err(_) => None, } } else { Some($table.get::<_, $key_type>($key).expect(&format!( "could not access required global `{}.{}`, perhaps you've forgotten to define it ?", $from_table, $key, ))) } }; } #[macro_export] macro_rules! get_table { ($assign_to:ident, $key:expr, $globals:ident) => { let $assign_to = $globals.get::<_, Table<'_>>($key).expect(&format!( "could not get `{}`, perhaps you've made an error ?", $key )); }; } #[macro_export] macro_rules! get_enum_or_none { ($table:ident, $key:expr, $assign_to:tt, $from_u8:tt, $error_message:expr, $required:expr) => { $assign_to = if $required == GetRequired::No { match $table.get::<_, u8>($key) { Ok(some_value) => Some($from_u8(some_value).expect($error_message)), Err(_) => None, } } else { Some( $from_u8($table.get::<_, u8>($key).expect(&format!( "could not access required global `{}`, perhaps you've forgotten to define it ?", $key, ))) .expect($error_message), ) } }; ( $table:ident, $from_table:expr, $key:expr, $assign_to:tt, $from_u8:tt, $error_message:expr, $required:expr ) => { $assign_to = if $required == GetRequired::No { match $table.get::<_, u8>($key) { Ok(some_value) => Some($from_u8(some_value).expect($error_message)), Err(_) => None, } } else { Some( $from_u8($table.get::<_, u8>($key).expect(&format!( "could not access required global `{}.{}`, perhaps you've forgotten to define it ?", $from_table, $key, ))) .expect($error_message), ) } }; } #[derive(Debug, PartialEq, Clone)] pub enum ConfigType { Package, Workspace, } impl fmt::Display for ConfigType { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { // write!(f, "{:?}", self) fmt::Debug::fmt(self, f) } } #[derive(Debug, Clone)] pub enum PackageType { Bin = 1, Lib = 2, } impl PackageType { #[must_use] pub const fn from_u8(n: u8) -> Option { match n { 1 => Some(Self::Bin), 2 => Some(Self::Lib), _ => None, } } } #[derive(Debug, Clone, PartialEq)] pub enum Arch { X86 = 1, X64 = 2, } impl Arch { #[must_use] pub const fn from_u8(n: u8) -> Option { match n { 1 => Some(Self::X86), 2 => Some(Self::X64), _ => None, } } #[must_use] pub fn from_string(arch: &Self) -> String { match arch { Arch::X86 => "32".to_string(), Arch::X64 => "64".to_string(), } } } #[derive(Debug, Clone)] pub struct Config { pub name: Option, pub version: Option, pub description: Option, pub license: Option, pub compile_options: Option>, pub minimum_divina_version: Option, pub sources: Option>, pub config_type: ConfigType, pub members: Option>, pub package_type: Option, pub path: Option, pub arch: Option, pub compiler: Option, pub visual_studio: Option, } impl Config { /// Create a new `Config` #[must_use] pub fn new() -> Self { Self::default() } /// Grab configuration values from `Divina.lua` and set /// /// # Panics /// if there are any errors #[allow(clippy::too_many_lines)] pub fn configure(&mut self, file: &str) { let mut script = std::fs::File::open(file) .unwrap_or_else(|_| panic!("!! could not locate `{}`, perhaps it doesn't exist ?", file)); let mut contents = String::new(); script.read_to_string(&mut contents).unwrap_or_else(|_| { panic!( "!! could not read `{}`, perhaps there's an encoding error ?", file ) }); let lua = Lua::new(); #[allow(clippy::cognitive_complexity)] lua.context(|ctx| { let globals = ctx.globals(); let test_function = ctx .create_function(|_, ()| { println!("test"); Ok(()) }) .expect("!! could not create function `test`, this *shouldn't* be possible"); let divina_table = ctx .create_table() .expect("!! could not create table `Divina`, this *shouldn't* be possible"); let type_table = ctx .create_table() .expect("!! could not create table `Divina.Type`, this *shouldn't* be possible"); let arch_table = ctx .create_table() .expect("!! could not create table `Divina.Arch`, this *shouldn't* be possible"); type_table .set("Bin", 1) .expect("!! could not set field `Divina.Type.Bin`, this *shouldn't* be possible"); type_table .set("Lib", 2) .expect("!! could not set field `Divina.Type.Lib`, this *shouldn't* be possible"); arch_table .set("x86", 1) .expect("!! could not set field `Divina.Type.x86`, this *shouldn't* be possible"); arch_table .set("x64", 2) .expect("!! could not set field `Divina.Type.x64`, this *shouldn't* be possible"); divina_table .set("version", VERSION) .expect("!! could not set field `Divina.version`, this *shouldn't* be possible"); divina_table .set("Type", type_table) .expect("!! could not set field `Divina.Type`, this *shouldn't* be possible"); divina_table .set("Arch", arch_table) .expect("!! could not set field `Divina.Arch`, this *shouldn't* be possible"); globals .set("Divina", divina_table) .expect("!! could not set table `Divina`, this *shouldn't* be possible"); globals .set("test", test_function) .expect("!! could not set function `test`, this *shouldn't* be possible"); ctx.load(contents.as_bytes()).exec().unwrap_or_else(|_| { panic!( "!! could not execute `{}`, perhaps you've made a syntax error ?", file ) }); self.config_type = if globals.get::<_, Table<'_>>("Workspace").is_ok() { ConfigType::Workspace } else if globals.get::<_, Table<'_>>("Package").is_ok() { ConfigType::Package } else { divina_util::exit_with!( 1, "!! '{}' is neither `Workspace` nor `Package`, perhaps you've forgotten to assign to it \ ?", file ); }; if self.config_type == ConfigType::Package { get_table!(config_table, "Package", globals); get_or_none!( config_table, "Package", "name", String, (self.name), GetRequired::Yes ); get_or_none!( config_table, "Package", "version", String, (self.version), GetRequired::Yes ); get_or_none!( config_table, "Package", "description", String, (self.description), GetRequired::No ); get_or_none!( config_table, "Package", "license", String, (self.license), GetRequired::No ); get_or_none!( config_table, "Package", "compile_options", Vec, (self.compile_options), GetRequired::No ); get_or_none!( config_table, "Package", "minimum_divina_version", String, (self.minimum_divina_version), GetRequired::Yes ); get_or_none!( config_table, "Package", "sources", Vec, (self.sources), GetRequired::No ); get_enum_or_none!( config_table, "Package", "type", (self.package_type), (PackageType::from_u8), "!! could not access `Package.type`, perhaps you've forgotten to assign it ?", GetRequired::Yes ); get_enum_or_none!( config_table, "Package", "arch", (self.arch), (Arch::from_u8), "!! could not access `Package.arch`, perhaps you've forgotten to assign it ?", GetRequired::Yes ); get_or_none!( config_table, "Package", "compiler", String, (self.compiler), GetRequired::No ); get_or_none!( config_table, "Package", "visual_studio", String, (self.visual_studio), GetRequired::No ); } else { get_table!(workspace_table, "Workspace", globals); // This sequence of BS actually took about two hours to complete. // // With help from: // // - let members; get_or_none!( workspace_table, "Workspace", "members", Vec, members, GetRequired::No ); if let Some(tables) = members { self.members = Some(Vec::new()); for path in tables { let mut config = Self::new(); config.configure(&format!("{}/Divina.lua", path)); config.path = Some(path); self .members .as_mut() .expect("!! could not access 'Config.members', this *shouldn't* be possible") .push(config); } } } }); } } impl Default for Config { fn default() -> Self { Self { name: None, version: None, description: None, license: None, compile_options: None, minimum_divina_version: None, sources: None, config_type: ConfigType::Package, members: None, package_type: None, path: None, arch: None, compiler: None, visual_studio: None, } } }