335 lines
11 KiB
Rust
335 lines
11 KiB
Rust
|
use std::{collections::HashMap, fmt::Display, fs, io::Write, path, str::FromStr};
|
||
|
|
||
|
use anyhow::Context;
|
||
|
use rust_embed::RustEmbed;
|
||
|
|
||
|
use crate::{colors::*, manifest::Manifest, package_manager::PackageManager};
|
||
|
|
||
|
#[derive(RustEmbed)]
|
||
|
#[folder = "fragments"]
|
||
|
#[allow(clippy::upper_case_acronyms)]
|
||
|
struct FRAGMENTS;
|
||
|
|
||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||
|
#[non_exhaustive]
|
||
|
pub enum Template {
|
||
|
MakepadCounter,
|
||
|
MakepadStackNavigation,
|
||
|
MakepadLogin,
|
||
|
Unknown,
|
||
|
}
|
||
|
|
||
|
impl Default for Template {
|
||
|
fn default() -> Self {
|
||
|
Template::MakepadCounter
|
||
|
}
|
||
|
}
|
||
|
|
||
|
impl Template {
|
||
|
pub const fn select_text<'a>(&self) -> &'a str {
|
||
|
match self {
|
||
|
Template::MakepadCounter => "Counter",
|
||
|
Template::MakepadStackNavigation => "SimpleStackNavigation",
|
||
|
Template::MakepadLogin => "SimpleLogin",
|
||
|
Template::Unknown => "Unknown",
|
||
|
_ => unreachable!(),
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
impl Display for Template {
|
||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||
|
match self {
|
||
|
Template::MakepadCounter => write!(f, "counter"),
|
||
|
Template::MakepadStackNavigation => write!(f, "simplestacknavigation"),
|
||
|
Template::MakepadLogin => write!(f, "simplelogin"),
|
||
|
Template::Unknown => write!(f, "unknown"),
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
impl FromStr for Template {
|
||
|
type Err = String;
|
||
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||
|
match s {
|
||
|
"counter" => Ok(Template::MakepadCounter),
|
||
|
"simplestacknavigation" => Ok(Template::MakepadStackNavigation),
|
||
|
"simplelogin" => Ok(Template::MakepadLogin),
|
||
|
_ => Err(format!(
|
||
|
"{YELLOW}{s}{RESET} is not a valid template. Valid templates are [{}]",
|
||
|
Template::ALL
|
||
|
.iter()
|
||
|
.map(|e| format!("{GREEN}{e}{RESET}"))
|
||
|
.collect::<Vec<_>>()
|
||
|
.join(", ")
|
||
|
)),
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
impl<'a> Template {
|
||
|
pub const ALL: &'a [Template] = &[
|
||
|
Template::MakepadCounter,
|
||
|
Template::MakepadStackNavigation,
|
||
|
Template::MakepadLogin,
|
||
|
];
|
||
|
|
||
|
pub fn flavors<'b>(&self, pkg_manager: PackageManager) -> Option<&'b [Flavor]> {
|
||
|
match self {
|
||
|
Template::MakepadCounter => {
|
||
|
if pkg_manager == PackageManager::Cargo {
|
||
|
None
|
||
|
} else {
|
||
|
Some(&[Flavor::Unknown])
|
||
|
}
|
||
|
}
|
||
|
Template::MakepadStackNavigation => {
|
||
|
if pkg_manager == PackageManager::Cargo {
|
||
|
None
|
||
|
} else {
|
||
|
Some(&[Flavor::Unknown])
|
||
|
}
|
||
|
}
|
||
|
_ => None,
|
||
|
}
|
||
|
}
|
||
|
|
||
|
pub fn from_flavor(&self, flavor: Flavor) -> Self {
|
||
|
match (self, flavor) {
|
||
|
(Template::Unknown, Flavor::Unknown) => Template::Unknown,
|
||
|
_ => *self,
|
||
|
}
|
||
|
}
|
||
|
|
||
|
pub fn without_flavor(&self) -> Self {
|
||
|
match self {
|
||
|
Template::Unknown => Template::Unknown,
|
||
|
_ => *self,
|
||
|
}
|
||
|
}
|
||
|
|
||
|
pub const fn possible_package_managers(&self) -> &[PackageManager] {
|
||
|
match self {
|
||
|
Template::MakepadCounter => &[PackageManager::Cargo,],
|
||
|
Template::MakepadStackNavigation => &[PackageManager::Cargo],
|
||
|
Template::MakepadLogin => &[PackageManager::Cargo],
|
||
|
Template::Unknown => &[],
|
||
|
}
|
||
|
}
|
||
|
|
||
|
pub const fn needs_trunk(&self) -> bool {
|
||
|
matches!(self, Template::MakepadCounter | Template::MakepadStackNavigation | Template::MakepadLogin)
|
||
|
}
|
||
|
|
||
|
pub const fn needs_makepad_cli(&self) -> bool {
|
||
|
matches!(
|
||
|
self,
|
||
|
Template::MakepadCounter | Template::MakepadStackNavigation | Template::MakepadLogin
|
||
|
)
|
||
|
}
|
||
|
|
||
|
pub const fn needs_wasm32_target(&self) -> bool {
|
||
|
matches!(self, Template::MakepadCounter | Template::MakepadStackNavigation | Template::MakepadLogin)
|
||
|
}
|
||
|
|
||
|
pub fn render(
|
||
|
&self,
|
||
|
target_dir: &path::Path,
|
||
|
pkg_manager: PackageManager,
|
||
|
project_name: &str,
|
||
|
package_name: &str,
|
||
|
alpha: bool,
|
||
|
mobile: bool,
|
||
|
) -> anyhow::Result<()> {
|
||
|
let manifest_bytes = FRAGMENTS::get(&format!("fragment-{self}/_cta_manifest_"))
|
||
|
.with_context(|| "Failed to get manifest bytes")?
|
||
|
.data;
|
||
|
let manifest_str = String::from_utf8(manifest_bytes.to_vec())?;
|
||
|
let manifest = Manifest::parse(&manifest_str, mobile)?;
|
||
|
|
||
|
let lib_name = format!("{}_lib", package_name.replace('-', "_"));
|
||
|
|
||
|
let manifest_template_data: HashMap<&str, &str> = [
|
||
|
("pkg_manager_run_command", pkg_manager.run_cmd()),
|
||
|
("lib_name", &lib_name),
|
||
|
("project_name", project_name),
|
||
|
("package_name", package_name),
|
||
|
(
|
||
|
"double_dash_with_space",
|
||
|
if pkg_manager == PackageManager::Unknown {
|
||
|
"-- "
|
||
|
} else {
|
||
|
""
|
||
|
},
|
||
|
),
|
||
|
]
|
||
|
.into();
|
||
|
|
||
|
let template_data: HashMap<&str, String> = [
|
||
|
("stable", (!alpha).to_string()),
|
||
|
("alpha", alpha.to_string()),
|
||
|
("mobile", mobile.to_string()),
|
||
|
("project_name", project_name.to_string()),
|
||
|
("package_name", package_name.to_string()),
|
||
|
(
|
||
|
"before_dev_command",
|
||
|
crate::lte::render(
|
||
|
manifest.before_dev_command.unwrap_or_default(),
|
||
|
&manifest_template_data,
|
||
|
)?,
|
||
|
),
|
||
|
(
|
||
|
"before_build_command",
|
||
|
crate::lte::render(
|
||
|
manifest.before_build_command.unwrap_or_default(),
|
||
|
&manifest_template_data,
|
||
|
)?,
|
||
|
),
|
||
|
(
|
||
|
"dev_path",
|
||
|
crate::lte::render(
|
||
|
manifest.dev_path.unwrap_or_default(),
|
||
|
&manifest_template_data,
|
||
|
)?,
|
||
|
),
|
||
|
(
|
||
|
"dist_dir",
|
||
|
crate::lte::render(
|
||
|
manifest.dist_dir.unwrap_or_default(),
|
||
|
&manifest_template_data,
|
||
|
)?,
|
||
|
),
|
||
|
(
|
||
|
"with_global_makepad",
|
||
|
manifest.with_global_makepad.unwrap_or_default().to_string(),
|
||
|
),
|
||
|
("lib_name", lib_name),
|
||
|
]
|
||
|
.into();
|
||
|
|
||
|
let write_file = |file: &str, template_data| -> anyhow::Result<()> {
|
||
|
// remove the first component, which is certainly the fragment directory they were in before getting embeded into the binary
|
||
|
let p = path::PathBuf::from(file)
|
||
|
.components()
|
||
|
.skip(1)
|
||
|
.collect::<Vec<_>>()
|
||
|
.iter()
|
||
|
.collect::<path::PathBuf>();
|
||
|
|
||
|
let p = target_dir.join(p);
|
||
|
let file_name = p.file_name().unwrap().to_string_lossy();
|
||
|
|
||
|
let file_name = match &*file_name {
|
||
|
"_gitignore" => ".gitignore",
|
||
|
// skip manifest
|
||
|
"_cta_manifest_" => return Ok(()),
|
||
|
// conditional files:
|
||
|
// are files that start with a special syntax
|
||
|
// "%(<list of flags separated by `-`>%)<file_name>"
|
||
|
// flags are supported package managers, stable, alpha and mobile.
|
||
|
// example: "%(pnpm-npm-yarn-stable-alpha)%package.json"
|
||
|
name if name.starts_with("%(") && name[1..].contains(")%") => {
|
||
|
let mut s = name.strip_prefix("%(").unwrap().split(")%");
|
||
|
let (mut flags, name) = (
|
||
|
s.next().unwrap().split('-').collect::<Vec<_>>(),
|
||
|
s.next().unwrap(),
|
||
|
);
|
||
|
|
||
|
let for_stable = flags.contains(&"stable");
|
||
|
let for_alpha = flags.contains(&"alpha");
|
||
|
let for_mobile = flags.contains(&"mobile");
|
||
|
|
||
|
// remove these flags to only keep package managers flags
|
||
|
flags.retain(|e| !["stable", "alpha", "mobile"].contains(e));
|
||
|
|
||
|
if ((for_stable && !alpha)
|
||
|
|| (for_alpha && alpha && !mobile)
|
||
|
|| (for_mobile && alpha && mobile)
|
||
|
|| (!for_stable && !for_alpha && !for_mobile))
|
||
|
&& (flags.contains(&pkg_manager.to_string().as_str()) || flags.is_empty())
|
||
|
{
|
||
|
name
|
||
|
} else {
|
||
|
// skip writing this file
|
||
|
return Ok(());
|
||
|
}
|
||
|
}
|
||
|
name => name,
|
||
|
};
|
||
|
|
||
|
// Only modify files that need to use the template engine
|
||
|
let (file_data, file_name) = if let Some(file_name) = file_name.strip_suffix(".lts") {
|
||
|
let file_data = FRAGMENTS::get(file).unwrap().data.to_vec();
|
||
|
let file_data_as_str = std::str::from_utf8(&file_data)?;
|
||
|
(
|
||
|
crate::lte::render(file_data_as_str, template_data)?.into_bytes(),
|
||
|
file_name,
|
||
|
)
|
||
|
} else {
|
||
|
(FRAGMENTS::get(file).unwrap().data.to_vec(), file_name)
|
||
|
};
|
||
|
|
||
|
let parent = p.parent().unwrap();
|
||
|
fs::create_dir_all(parent)?;
|
||
|
fs::write(parent.join(file_name), file_data)?;
|
||
|
Ok(())
|
||
|
};
|
||
|
|
||
|
// 1. write base files
|
||
|
for file in FRAGMENTS::iter().filter(|e| {
|
||
|
path::PathBuf::from(e.to_string())
|
||
|
.components()
|
||
|
.next()
|
||
|
.unwrap()
|
||
|
.as_os_str()
|
||
|
== "_base_"
|
||
|
}) {
|
||
|
write_file(&file, &template_data)?;
|
||
|
}
|
||
|
|
||
|
// 2. write template files which can override files from base
|
||
|
for file in FRAGMENTS::iter().filter(|e| {
|
||
|
path::PathBuf::from(e.to_string())
|
||
|
.components()
|
||
|
.next()
|
||
|
.unwrap()
|
||
|
.as_os_str()
|
||
|
== path::PathBuf::from(format!("fragment-{self}"))
|
||
|
}) {
|
||
|
write_file(&file, &template_data)?;
|
||
|
}
|
||
|
|
||
|
// 3. write extra files specified in the fragment manifest
|
||
|
for (src, dest) in manifest.files {
|
||
|
let data = FRAGMENTS::get(&format!("_assets_/{src}"))
|
||
|
.with_context(|| format!("Failed to get asset file bytes: {src}"))?
|
||
|
.data;
|
||
|
let dest = target_dir.join(dest);
|
||
|
let parent = dest.parent().unwrap();
|
||
|
fs::create_dir_all(parent)?;
|
||
|
let mut file = fs::OpenOptions::new()
|
||
|
.append(true)
|
||
|
.create(true)
|
||
|
.open(dest)?;
|
||
|
file.write_all(&data)?;
|
||
|
}
|
||
|
|
||
|
Ok(())
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||
|
#[non_exhaustive]
|
||
|
pub enum Flavor {
|
||
|
Unknown,
|
||
|
}
|
||
|
|
||
|
impl Display for Flavor {
|
||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||
|
match self {
|
||
|
Flavor::Unknown => write!(f, "Unknown"),
|
||
|
}
|
||
|
}
|
||
|
}
|