uniui_build 0.0.6

Builds uniui applicatins for different targets
Documentation
use std::{
	collections::HashMap,
	env,
	fs::File,
	io::Write,
	process::Command,
};

use wasm_bindgen_cli_support::{
	Bindgen,
	EncodeInto,
};

mod template;
mod template_rocket;
mod template_tide;

struct Context {
	pub out_dir: String,
	pub target_wasm_dir: String,
	pub manifest_dir: String,
	pub file: File,
}

pub fn build_module_to_wasm(
	target_wasm_dir: &String,
	module_name: &str,
	path_to_manifest: &str,
	js_file_name: &str,
	wasm_file_name: &str,
	html_file_name: &str,
	theme: &str,
) {
	let mut command = Command::new("cargo");
	command.arg("build");

	let profile;

	match std::env::var("PROFILE") == Ok("release".to_owned()) {
		true => {
			command.arg("--release");
			profile = "release";
		},
		false => {
			profile = "debug";
		},
	}

	command
		.arg("-p")
		.arg(module_name.clone())
		.arg("--manifest-path")
		.arg(path_to_manifest)
		.arg("--target")
		.arg("wasm32-unknown-unknown")
		.env("RUSTFLAGS", "-D warnings")
		.env("CARGO_TARGET_DIR", target_wasm_dir);


	let status = command.status().expect("cargo failed to compile");

	if !status.success() {
		panic!("cargo build failed:{}", status);
	}

	let input_path = format!(
		"{}/wasm32-unknown-unknown/{}/{}.wasm",
		target_wasm_dir, profile, module_name
	);

	Bindgen::new()
		.input_path(input_path)
		.no_modules(true)
		.unwrap()
		.debug(false)
		.demangle(true)
		.keep_debug(false)
		.remove_name_section(false)
		.remove_producers_section(false)
		.typescript(false)
		.out_name("index")
		.encode_into(EncodeInto::Always)
		.generate(target_wasm_dir.clone())
		.expect("bindgen can't process input");

	let old_js = format!("{}/index.js", target_wasm_dir);
	std::fs::copy(old_js.clone(), &js_file_name).expect(&format!(
		"Couldn't copy js file {}/index.js to {}",
		target_wasm_dir, js_file_name
	));
	std::fs::remove_file(old_js.clone()).expect(&format!("Couldn't remove {}", old_js));

	let old_wasm = format!("{}/index_bg.wasm", target_wasm_dir);
	std::fs::copy(old_wasm.clone(), &wasm_file_name).expect(&format!(
		"Couldn't copy wasm file {}/index_gb.wasm to {}",
		target_wasm_dir, wasm_file_name
	));
	std::fs::remove_file(old_wasm.clone())
		.expect(&format!("Couldn't remove {}", old_wasm));

	let html_content = template::HTML
		.to_string()
		.replace(template::JS_FILE_NAME_PLACEHOLDER, ".js")
		.replace(template::WASM_FILE_NAME_PLACEHOLDER, "_bg.wasm")
		.replace(template::CUSTOM_THEME_PLACEHOLDER, theme)
		.replace("BODY_THEME_CLASS_NAME", crate::BODY_THEME_CLASS_NAME);

	let mut file = File::create(html_file_name.clone())
		.expect(&format!("Created html file {}", html_file_name));
	file.write_all(html_content.as_bytes()).expect("html file write");
}

fn build_module(
	module_name: &str,
	path_to_manifest: &str,
	module_template: &str,
	context: &mut Context,
	theme: &str,
) {
	let html_file_name = format!("{}/{}.html", context.out_dir, module_name);
	let wasm_file_name = format!("{}/{}.wasm", context.out_dir, module_name);
	let js_file_name = format!("{}/{}.js", context.out_dir, module_name);

	build_module_to_wasm(
		&context.target_wasm_dir,
		module_name,
		path_to_manifest,
		&js_file_name,
		&wasm_file_name,
		&html_file_name,
		theme,
	);

	let file_content = module_template
		.to_string()
		.replace(template::WASM_FILE_NAME_PLACEHOLDER, &wasm_file_name)
		.replace(template::JS_FILE_NAME_PLACEHOLDER, &js_file_name)
		.replace(template::MODULE_NAME_PLACEHOLDER, module_name)
		.replace(template::HTML_FILE_NAME_PLACEHOLDER, &html_file_name);

	context.file.write_all(file_content.as_bytes()).expect("file content written");
}

pub(crate) fn build_all_inner(
	pages: &[crate::SimpleUiPage],
	framework: crate::Framework,
	target_wasm_dir: String,
	theme: &str,
) {
	let prolog = match framework {
		crate::Framework::Tide => "",
		crate::Framework::Rocket => template_rocket::PROLOG,
	};

	let module_template = match framework {
		crate::Framework::Tide => template_tide::MODULE,
		crate::Framework::Rocket => template_rocket::MODULE,
	};

	let out_dir = env::var("OUT_DIR").expect("OUT_DIR have to be setted up");
	let file_name = format!("{}/uni_build_generated.rs", out_dir);

	let mut file = File::create(file_name).expect("file opened");
	file.write_all(prolog.as_bytes()).expect("file content written");

	let mut context = Context {
		file,
		out_dir,
		target_wasm_dir,

		manifest_dir: env::var("CARGO_MANIFEST_DIR")
			.expect("$CARGO_MANIFEST_DIR not exist"),
	};

	let mut output = HashMap::new();

	for page in pages {
		build_module(
			page.module_name(),
			page.module_path(),
			module_template,
			&mut context,
			theme,
		);

		if output.contains_key(page.path()) {
			panic!("Two pages for the same path found. The path is `{}`", page.path());
		}
		output.insert(page.path().to_owned(), page.module_name().to_owned());
	}

	let (pre, module, post) = match framework {
		crate::Framework::Rocket => {
			(
				template_rocket::GLOBAL_PRE,
				template_rocket::GLOBAL_MODULE,
				template_rocket::GLOBAL_POST,
			)
		},
		crate::Framework::Tide => {
			(
				template_tide::GLOBAL_PRE,
				template_tide::GLOBAL_MODULE,
				template_tide::GLOBAL_POST,
			)
		},
	};

	context.file.write_all(pre.as_bytes()).expect("File write error");
	for (path, name) in output.iter() {
		let s = module
			.to_string()
			.replace(template::MODULE_NAME_PLACEHOLDER, &name)
			.replace(template::MODULE_PAGE_PATH_PLACEHOLDER, &path);
		context.file.write_all(s.as_bytes()).expect("File write error");
	}
	context.file.write_all(post.as_bytes()).expect("File write error");
}