use crate::prelude::*;
use beet_core::prelude::*;
use proc_macro2::Span;
use std::path::PathBuf;
use syn::Attribute;
use syn::Ident;
use syn::ItemMod;
use syn::parse_quote;
#[derive(Debug, Component)]
pub struct RouteSourceFile {
pub mod_path: PathBuf,
pub source_file_collection_rel: PathBuf,
pub route_path: RoutePath,
}
impl RouteSourceFile {
pub fn mod_ident(&self) -> syn::Ident {
let path = path_to_ident(&self.route_path.to_string_lossy());
Ident::new(&path, Span::call_site())
}
pub fn item_mod(&self, category: RouteCollectionCategory) -> ItemMod {
let ident = self.mod_ident();
let path = &self.mod_path.to_string_lossy();
let cfg: Option<Attribute> = match category {
RouteCollectionCategory::Pages => None,
RouteCollectionCategory::Actions => Some(parse_quote! {
#[cfg(feature = "server")]
}),
};
syn::parse_quote! {
#[path = #path]
#cfg
pub mod #ident;
}
}
}
pub fn reset_codegen_files(
changed_exprs: Populated<Entity, Changed<FileExprHash>>,
mut parent_codegen: Query<&mut CodegenFile>,
parents: Query<&ChildOf>,
) {
for file in changed_exprs.iter() {
for parent in parents.iter_ancestors(file) {
if let Ok(mut codegen) = parent_codegen.get_mut(parent) {
trace!("Resetting changed codegen: {}", codegen.output());
codegen.set_added();
codegen.clear_items();
}
}
}
}
pub(super) fn create_route_files(
mut commands: Commands,
query: Populated<
(Entity, &SourceFile),
(Added<SourceFile>, Without<RouteSourceFile>),
>,
collections: Query<(&RouteFileCollection, &CodegenFile)>,
parents: Query<&ChildOf>,
) -> Result {
let mut items = query.iter().collect::<Vec<_>>();
items.sort_by_key(|(_, file)| (*file).clone());
for (entity, file) in items.into_iter() {
let Some((collection, codegen)) = parents
.iter_ancestors(entity)
.find_map(|en| collections.get(en).ok())
else {
continue;
};
let mod_path =
path_ext::create_relative(&codegen.output_dir()?, &file)?;
let route_path = path_ext::create_relative(&collection.src, &file)?
.xmap(RoutePath::from_file_path)?;
let source_file_collection_rel =
path_ext::create_relative(&collection.src, &file)?;
debug!("Creating new RouteSourceFile: {}", file.path());
commands.entity(entity).insert(RouteSourceFile {
source_file_collection_rel,
mod_path,
route_path,
});
}
Ok(())
}
fn path_to_ident(path: &str) -> String {
let mut ident = String::new();
let mut chars = path.chars();
if let Some(first) = chars.next() {
if first.is_ascii_alphabetic() || first == '_' {
ident.push(first);
} else {
ident.push('_');
if first.is_ascii_digit() {
ident.push(first);
}
}
}
for ch in chars {
if ch.is_ascii_alphanumeric() || ch == '_' {
ident.push(ch);
} else {
ident.push('_');
}
}
if ident.is_empty() {
"index".to_string()
} else if ident == "_" {
"_index".to_string()
} else {
ident.replace("__", "_")
}
}
#[cfg(test)]
mod test {
use crate::prelude::*;
use beet_core::prelude::*;
use quote::ToTokens;
use std::ops::Deref;
use std::path::PathBuf;
#[test]
fn works() {
let mut world = BuildPlugin::world();
let group = world.spawn(RouteFileCollection::test_site_pages()).id();
world.run_schedule(ParseSourceFiles);
let source_file_entity =
world.entity(group).get::<Children>().unwrap()[0];
let route_file = world
.entity(source_file_entity)
.get::<RouteSourceFile>()
.unwrap();
route_file
.mod_path
.xref()
.xpect_eq(PathBuf::from("../pages/docs/index.rs"));
route_file
.route_path
.xref()
.deref()
.xpect_eq(PathBuf::from("/docs"));
route_file
.item_mod(RouteCollectionCategory::Pages)
.to_token_stream()
.xpect_snapshot();
}
}