use crate::prelude::*;
use heck::ToKebabCase;
use std::path::Path;
use std::str::FromStr;
#[derive(Debug, Clone, Resource, Reflect)]
#[reflect(Resource)]
pub struct PackageConfig {
pub title: String,
pub binary_name: String,
pub version: String,
pub description: String,
pub homepage: String,
pub repository: Option<String>,
pub stage: String,
pub service_access: ServiceAccess,
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, Reflect)]
pub enum ServiceAccess {
Local,
Remote,
}
impl FromStr for ServiceAccess {
type Err = String;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s.to_lowercase().as_str() {
"local" => Ok(ServiceAccess::Local),
"remote" => Ok(ServiceAccess::Remote),
other => Err(format!(
"Invalid service access: {other}, expected 'local' or 'remote'"
)),
}
}
}
impl std::fmt::Display for ServiceAccess {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let s = match self {
ServiceAccess::Local => "local",
ServiceAccess::Remote => "remote",
};
write!(f, "{s}")
}
}
impl PackageConfig {
pub fn binary_name(&self) -> &str { &self.binary_name }
pub fn version(&self) -> &str { &self.version }
pub fn description(&self) -> &str { &self.description }
pub fn repository(&self) -> Option<&str> { self.repository.as_deref() }
pub fn stage(&self) -> &str { &self.stage }
pub fn router_lambda_name(&self) -> String { self.resource_name("router") }
pub fn html_bucket_name(&self) -> String { self.resource_name("html") }
pub fn assets_bucket_name(&self) -> String { self.resource_name("assets") }
pub fn analytics_bucket_name(&self) -> String {
self.resource_name("analytics")
}
#[rustfmt::skip]
pub fn envs(&self)->Vec<(String,String)>{
vec![
("BEET_STAGE".to_string(),self.stage().to_string()),
("BEET_SERVICE_ACCESS".to_string(),self.service_access.to_string()),
]
}
pub fn resource_name(&self, descriptor: &str) -> String {
let binary_name = self.binary_name.to_kebab_case();
let stage = self.stage.as_str();
format! {"{binary_name}--{stage}--{descriptor}"}
}
}
impl std::fmt::Display for PackageConfig {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
writeln!(f, "title: {}", self.title)?;
writeln!(f, "binary_name: {}", self.binary_name)?;
writeln!(f, "version: {}", self.version)?;
writeln!(f, "description: {}", self.description)?;
writeln!(f, "homepage: {}", self.homepage)?;
if let Some(repo) = &self.repository {
writeln!(f, "repository: {}", repo)?;
} else {
writeln!(f, "repository: None")?;
}
writeln!(f, "stage: {}", self.stage)?;
Ok(())
}
}
#[macro_export]
macro_rules! pkg_config {
() => {
$crate::prelude::PackageConfig {
title: env!("CARGO_PKG_NAME").to_string(),
binary_name: env!("CARGO_PKG_NAME").to_string(),
version: env!("CARGO_PKG_VERSION").to_string(),
description: env!("CARGO_PKG_DESCRIPTION").to_string(),
homepage: env!("CARGO_PKG_HOMEPAGE").to_string(),
repository: option_env!("CARGO_PKG_REPOSITORY")
.map(|s| s.to_string()),
stage: option_env!("BEET_STAGE").unwrap_or("dev").to_string(),
service_access: option_env!("BEET_SERVICE_ACCESS")
.map(|s| s.parse().unwrap_or(ServiceAccess::Local))
.unwrap_or(ServiceAccess::Local),
}
};
}
#[derive(Debug, Clone, Resource, Reflect)]
#[reflect(Resource)]
pub struct WorkspaceConfig {
pub snippet_filter: GlobFilter,
pub launch_filter: GlobFilter,
pub launch_file: WsPathBuf,
pub root_dir: WsPathBuf,
pub snippets_dir: WsPathBuf,
pub html_dir: WsPathBuf,
pub assets_dir: WsPathBuf,
pub analytics_dir: WsPathBuf,
pub client_islands_path: WsPathBuf,
}
impl Default for WorkspaceConfig {
fn default() -> Self {
Self {
snippet_filter: GlobFilter::default()
.with_exclude("*/target/*")
.with_exclude("*/codegen/*")
.with_exclude("*/.cache/*")
.with_exclude("*/node_modules/*"),
launch_filter: GlobFilter::default()
.with_include("*/launch/*")
.with_include("*/launch.rs"),
launch_file: WsPathBuf::new("launch.ron"),
root_dir: {
#[cfg(test)]
{
WsPathBuf::new("tests/test_site")
}
#[cfg(not(test))]
{
WsPathBuf::default()
}
},
snippets_dir: WsPathBuf::new("target/snippets"),
html_dir: WsPathBuf::new("target/client"),
assets_dir: WsPathBuf::new("assets"),
analytics_dir: WsPathBuf::new("target/analytics"),
client_islands_path: WsPathBuf::new("target/client_islands.ron"),
}
}
}
impl WorkspaceConfig {
pub fn test_site() -> Self {
let mut this = Self::default();
this.root_dir = WsPathBuf::new("tests/test_site");
this
}
pub fn snippets_dir(&self) -> &WsPathBuf { &self.snippets_dir }
pub fn rsx_snippet_path(
&self,
path: impl AsRef<Path>,
start_line: u32,
) -> WsPathBuf {
let mut path = path.as_ref().to_path_buf();
let file_stem = path.file_stem().unwrap_or_default().to_string_lossy();
let snippet_file_name = format!("{}:{}.rsx.ron", file_stem, start_line);
path.set_file_name(snippet_file_name);
self.snippets_dir.join(path)
}
pub fn lang_snippet_path(&self, path: &WsPathBuf, index: u64) -> WsPathBuf {
let mut path = path.clone();
let file_stem = path.file_stem().unwrap_or_default().to_string_lossy();
let snippet_file_name = format!("{}-{}.lang.ron", file_stem, index);
path.set_file_name(snippet_file_name);
self.snippets_dir.join(path)
}
pub fn passes(&self, path: impl AsRef<Path>) -> bool {
self.snippet_filter.passes(path)
}
pub fn get_files(&self) -> Result<Vec<AbsPathBuf>, FsError> {
ReadDir::files_recursive(&self.root_dir.into_abs())?
.into_iter()
.filter(|path| self.snippet_filter.passes(path))
.map(|path| AbsPathBuf::new(path))
.collect()
}
}
#[cfg(test)]
mod test {
use crate::prelude::*;
#[test]
fn works() {
pkg_config!()
.resource_name("lambda")
.xpect_eq("beet-core--dev--lambda");
}
}