#![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,
}
#[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 {
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<Self> {
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<Self> {
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<String>,
pub version: Option<String>,
pub description: Option<String>,
pub license: Option<String>,
pub compile_options: Option<Vec<String>>,
pub minimum_divina_version: Option<String>,
pub sources: Option<Vec<String>>,
pub config_type: ConfigType,
pub members: Option<Vec<Self>>,
pub package_type: Option<PackageType>,
pub path: Option<String>,
pub arch: Option<Arch>,
pub compiler: Option<String>,
pub visual_studio: Option<String>,
}
impl Config {
#[must_use]
pub fn new() -> Self { Self::default() }
#[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<String>,
(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<String>,
(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);
let members;
get_or_none!(
workspace_table,
"Workspace",
"members",
Vec<String>,
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,
}
}
}