mirror of
https://github.com/tuono-labs/tuono
synced 2026-07-25 12:52:47 -07:00
Refactor application struct representation (#15)
* feat: add ssg module and CLI argument * refactor: tuono lib.rs structure * refactor: create App struct * feat: extend file extensions to read in src/routes/ * feat: load all routes in the app.route_map * refactor: creaded AxumInfo struct
This commit is contained in:
@@ -0,0 +1,228 @@
|
||||
use glob::glob;
|
||||
use glob::GlobError;
|
||||
use std::collections::{hash_map::Entry, HashMap};
|
||||
use std::path::PathBuf;
|
||||
|
||||
use crate::route::Route;
|
||||
|
||||
const IGNORE_EXTENSIONS: [&str; 3] = ["css", "scss", "sass"];
|
||||
const IGNORE_FILES: [&str; 1] = ["__root"];
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct App {
|
||||
pub route_map: HashMap<String, Route>,
|
||||
pub base_path: PathBuf,
|
||||
}
|
||||
|
||||
impl App {
|
||||
pub fn new() -> Self {
|
||||
let base_path = std::env::current_dir().unwrap();
|
||||
|
||||
App {
|
||||
route_map: HashMap::new(),
|
||||
base_path,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn collect_routes(&mut self) {
|
||||
glob(self.base_path.join("src/routes/**/*.*").to_str().unwrap())
|
||||
.expect("Failed to read glob pattern")
|
||||
.for_each(|entry| {
|
||||
if self.should_collect_route(&entry) {
|
||||
self.collect_route(entry)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn should_collect_route(&self, entry: &Result<PathBuf, GlobError>) -> bool {
|
||||
let file_extension = entry.as_ref().unwrap().extension().unwrap();
|
||||
let file_name = entry.as_ref().unwrap().file_stem().unwrap();
|
||||
|
||||
if IGNORE_EXTENSIONS.iter().any(|val| val == &file_extension) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if IGNORE_FILES.iter().any(|val| val == &file_name) {
|
||||
return false;
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
fn collect_route(&mut self, path_buf: Result<PathBuf, GlobError>) {
|
||||
let entry = path_buf.unwrap();
|
||||
let base_path_str = self.base_path.to_str().unwrap();
|
||||
let path = entry
|
||||
.to_str()
|
||||
.unwrap()
|
||||
.replace(&format!("{base_path_str}/src/routes"), "")
|
||||
.replace(".rs", "")
|
||||
.replace(".tsx", "");
|
||||
|
||||
if entry.extension().unwrap() == "rs" {
|
||||
if let Entry::Vacant(route_map) = self.route_map.entry(path.clone()) {
|
||||
let mut route = Route::new(path);
|
||||
route.update_axum_info();
|
||||
route_map.insert(route);
|
||||
} else {
|
||||
let route = self.route_map.get_mut(&path).unwrap();
|
||||
route.update_axum_info();
|
||||
}
|
||||
return;
|
||||
}
|
||||
if let Entry::Vacant(route_map) = self.route_map.entry(path.clone()) {
|
||||
let route = Route::new(path);
|
||||
route_map.insert(route);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn should_collect_routes() {
|
||||
let mut app = App::new();
|
||||
app.base_path = "/home/user/Documents/tuono".into();
|
||||
|
||||
let routes = [
|
||||
"/home/user/Documents/tuono/src/routes/about.rs",
|
||||
"/home/user/Documents/tuono/src/routes/index.rs",
|
||||
"/home/user/Documents/tuono/src/routes/posts/index.rs",
|
||||
"/home/user/Documents/tuono/src/routes/posts/[post].rs",
|
||||
"/home/user/Documents/tuono/src/routes/posts/UPPERCASE.rs",
|
||||
];
|
||||
|
||||
routes
|
||||
.into_iter()
|
||||
.for_each(|route| app.collect_route(Ok(PathBuf::from(route))));
|
||||
|
||||
let results = [
|
||||
("/index", "index"),
|
||||
("/about", "about"),
|
||||
("/posts/index", "posts_index"),
|
||||
("/posts/[post]", "posts_dyn_post"),
|
||||
("/posts/UPPERCASE", "posts_uppercase"),
|
||||
];
|
||||
|
||||
results.into_iter().for_each(|(path, module_import)| {
|
||||
assert_eq!(
|
||||
app.route_map
|
||||
.get(path)
|
||||
.unwrap()
|
||||
.axum_info
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.module_import,
|
||||
String::from(module_import)
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn should_create_multi_level_axum_paths() {
|
||||
let mut app = App::new();
|
||||
app.base_path = "/home/user/Documents/tuono".into();
|
||||
|
||||
let routes = [
|
||||
"/home/user/Documents/tuono/src/routes/about.rs",
|
||||
"/home/user/Documents/tuono/src/routes/index.rs",
|
||||
"/home/user/Documents/tuono/src/routes/posts/index.rs",
|
||||
"/home/user/Documents/tuono/src/routes/posts/any-post.rs",
|
||||
"/home/user/Documents/tuono/src/routes/posts/[post].rs",
|
||||
];
|
||||
|
||||
routes
|
||||
.into_iter()
|
||||
.for_each(|route| app.collect_route(Ok(PathBuf::from(route))));
|
||||
|
||||
let results = [
|
||||
("/index", "/"),
|
||||
("/about", "/about"),
|
||||
("/posts/index", "/posts"),
|
||||
("/posts/any-post", "/posts/any-post"),
|
||||
("/posts/[post]", "/posts/:post"),
|
||||
];
|
||||
|
||||
results.into_iter().for_each(|(path, expected_path)| {
|
||||
assert_eq!(
|
||||
app.route_map
|
||||
.get(path)
|
||||
.unwrap()
|
||||
.axum_info
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.axum_route,
|
||||
String::from(expected_path)
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn should_ignore_whitelisted_extensions() {
|
||||
let mut app = App::new();
|
||||
app.base_path = "/home/user/Documents/tuono".into();
|
||||
|
||||
let routes = [
|
||||
"/home/user/Documents/tuono/src/routes/about.css",
|
||||
"/home/user/Documents/tuono/src/routes/index.scss",
|
||||
"/home/user/Documents/tuono/src/routes/posts/index.sass",
|
||||
];
|
||||
|
||||
routes.into_iter().for_each(|route| {
|
||||
if app.should_collect_route(&Ok(PathBuf::from(route))) {
|
||||
app.collect_route(Ok(PathBuf::from(route)))
|
||||
}
|
||||
});
|
||||
|
||||
assert!(app.route_map.is_empty())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn should_ignore_whitelisted_files() {
|
||||
let mut app = App::new();
|
||||
app.base_path = "/home/user/Documents/tuono".into();
|
||||
|
||||
let routes = [
|
||||
"/home/user/Documents/tuono/src/routes/__root.tsx",
|
||||
"/home/user/Documents/tuono/src/routes/posts/__root.tsx",
|
||||
];
|
||||
|
||||
routes.into_iter().for_each(|route| {
|
||||
if app.should_collect_route(&Ok(PathBuf::from(route))) {
|
||||
app.collect_route(Ok(PathBuf::from(route)))
|
||||
}
|
||||
});
|
||||
|
||||
assert!(app.route_map.is_empty())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn should_correctly_parse_routes_with_server_handler() {
|
||||
let mut app = App::new();
|
||||
app.base_path = "/home/user/Documents/tuono".into();
|
||||
|
||||
let routes = [
|
||||
"/home/user/Documents/tuono/src/routes/about.rs",
|
||||
"/home/user/Documents/tuono/src/routes/about.tsx",
|
||||
"/home/user/Documents/tuono/src/routes/index.tsx",
|
||||
];
|
||||
|
||||
routes
|
||||
.into_iter()
|
||||
.for_each(|route| app.collect_route(Ok(PathBuf::from(route))));
|
||||
|
||||
let results = [("/about", true), ("/index", false)];
|
||||
|
||||
results
|
||||
.into_iter()
|
||||
.for_each(|(path, expected_has_server_handler)| {
|
||||
if expected_has_server_handler {
|
||||
assert!(app.route_map.get(path).unwrap().axum_info.is_some())
|
||||
} else {
|
||||
assert!(app.route_map.get(path).unwrap().axum_info.is_none())
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
use clap::{Parser, Subcommand};
|
||||
use std::process::Command;
|
||||
|
||||
use crate::app::App;
|
||||
use crate::mode::Mode;
|
||||
use crate::scaffold_project;
|
||||
use crate::source_builder::{bundle_axum_source, check_tuono_folder, create_client_entry_files};
|
||||
use crate::watch;
|
||||
|
||||
#[derive(Subcommand, Debug)]
|
||||
enum Actions {
|
||||
/// Start the development environment
|
||||
Dev,
|
||||
/// Build the production assets
|
||||
Build {
|
||||
#[arg(short, long = "static")]
|
||||
/// Statically generate the website HTML
|
||||
ssg: bool,
|
||||
},
|
||||
/// Scaffold a new project
|
||||
New {
|
||||
/// The folder in which load the project. Default is the current directory.
|
||||
folder_name: Option<String>,
|
||||
/// The template to use to scaffold the project. The template should match one of the tuono
|
||||
/// examples
|
||||
#[arg(short, long)]
|
||||
template: Option<String>,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Parser, Debug)]
|
||||
#[command(version, about = "The react/rust fullstack framework")]
|
||||
struct Args {
|
||||
#[command(subcommand)]
|
||||
action: Actions,
|
||||
}
|
||||
|
||||
fn init_tuono_folder(mode: Mode) -> std::io::Result<()> {
|
||||
check_tuono_folder()?;
|
||||
bundle_axum_source(mode)?;
|
||||
create_client_entry_files()?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn app() -> std::io::Result<()> {
|
||||
let args = Args::parse();
|
||||
|
||||
match args.action {
|
||||
Actions::Dev => {
|
||||
init_tuono_folder(Mode::Dev)?;
|
||||
watch::watch().unwrap();
|
||||
}
|
||||
Actions::Build { ssg } => {
|
||||
init_tuono_folder(Mode::Prod)?;
|
||||
let mut vite_build = Command::new("./node_modules/.bin/tuono-build-prod");
|
||||
let _ = &vite_build.output()?;
|
||||
|
||||
if ssg {
|
||||
println!("SSG: generation started");
|
||||
let mut app = App::new();
|
||||
app.collect_routes();
|
||||
dbg!(app);
|
||||
}
|
||||
}
|
||||
Actions::New {
|
||||
folder_name,
|
||||
template,
|
||||
} => {
|
||||
scaffold_project::create_new_project(folder_name, template);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
+5
-64
@@ -1,66 +1,7 @@
|
||||
use clap::{Parser, Subcommand};
|
||||
use std::process::Command;
|
||||
|
||||
mod source_builder;
|
||||
use source_builder::{bundle_axum_source, create_client_entry_files};
|
||||
|
||||
use crate::source_builder::{check_tuono_folder, Mode};
|
||||
|
||||
mod app;
|
||||
pub mod cli;
|
||||
mod mode;
|
||||
mod route;
|
||||
mod scaffold_project;
|
||||
mod source_builder;
|
||||
mod watch;
|
||||
|
||||
#[derive(Subcommand, Debug)]
|
||||
enum Actions {
|
||||
/// Start the development environment
|
||||
Dev,
|
||||
/// Build the production assets
|
||||
Build,
|
||||
/// Scaffold a new project
|
||||
New {
|
||||
/// The folder in which load the project. Default is the current directory.
|
||||
folder_name: Option<String>,
|
||||
/// The template to use to scaffold the project. The template should match one of the tuono
|
||||
/// examples
|
||||
#[arg(short, long)]
|
||||
template: Option<String>,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Parser, Debug)]
|
||||
#[command(version, about = "The react/rust fullstack framework")]
|
||||
struct Args {
|
||||
#[command(subcommand)]
|
||||
action: Actions,
|
||||
}
|
||||
|
||||
fn init_tuono_folder(mode: Mode) -> std::io::Result<()> {
|
||||
check_tuono_folder()?;
|
||||
bundle_axum_source(mode)?;
|
||||
create_client_entry_files()?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn cli() -> std::io::Result<()> {
|
||||
let args = Args::parse();
|
||||
|
||||
match args.action {
|
||||
Actions::Dev => {
|
||||
init_tuono_folder(Mode::Dev)?;
|
||||
watch::watch().unwrap();
|
||||
}
|
||||
Actions::Build => {
|
||||
init_tuono_folder(Mode::Prod)?;
|
||||
let mut vite_build = Command::new("./node_modules/.bin/tuono-build-prod");
|
||||
let _ = &vite_build.output()?;
|
||||
}
|
||||
Actions::New {
|
||||
folder_name,
|
||||
template,
|
||||
} => {
|
||||
scaffold_project::create_new_project(folder_name, template);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use tuono::cli;
|
||||
use tuono::cli::app;
|
||||
|
||||
fn main() {
|
||||
cli().unwrap();
|
||||
app().expect("Failed to start the CLI")
|
||||
}
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
#[derive(PartialEq, Eq)]
|
||||
pub enum Mode {
|
||||
Prod,
|
||||
Dev,
|
||||
}
|
||||
|
||||
impl Mode {
|
||||
pub fn as_str<'a>(&self) -> &'a str {
|
||||
if *self == Mode::Dev {
|
||||
return "Mode::Dev";
|
||||
}
|
||||
"Mode::Prod"
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn should_correctly_print_the_mode_as_str() {
|
||||
let dev = Mode::Dev.as_str();
|
||||
let prod = Mode::Prod.as_str();
|
||||
assert_eq!(dev, "Mode::Dev");
|
||||
assert_eq!(prod, "Mode::Prod");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,108 @@
|
||||
use regex::Regex;
|
||||
|
||||
fn has_dynamic_path(route: &str) -> bool {
|
||||
let regex = Regex::new(r"\[(.*?)\]").expect("Failed to create the regex");
|
||||
regex.is_match(route)
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub struct AxumInfo {
|
||||
// Path for importing the module
|
||||
pub module_import: String,
|
||||
// path for the the axum router
|
||||
pub axum_route: String,
|
||||
}
|
||||
|
||||
impl AxumInfo {
|
||||
pub fn new(path: String) -> Self {
|
||||
// Remove first slash
|
||||
let mut module = path.chars();
|
||||
module.next();
|
||||
|
||||
let axum_route = path.replace("/index", "");
|
||||
|
||||
if axum_route.is_empty() {
|
||||
return AxumInfo {
|
||||
module_import: module.as_str().to_string().replace('/', "_"),
|
||||
axum_route: "/".to_string(),
|
||||
};
|
||||
}
|
||||
|
||||
if has_dynamic_path(&path) {
|
||||
return AxumInfo {
|
||||
module_import: module
|
||||
.as_str()
|
||||
.to_string()
|
||||
.replace('/', "_")
|
||||
.replace('[', "dyn_")
|
||||
.replace(']', ""),
|
||||
axum_route: axum_route.replace('[', ":").replace(']', ""),
|
||||
};
|
||||
}
|
||||
|
||||
AxumInfo {
|
||||
module_import: module.as_str().to_string().replace('/', "_").to_lowercase(),
|
||||
axum_route,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub struct Route {
|
||||
path: String,
|
||||
pub is_dynamic: bool,
|
||||
pub axum_info: Option<AxumInfo>,
|
||||
}
|
||||
|
||||
impl Route {
|
||||
pub fn new(cleaned_path: String) -> Self {
|
||||
Route {
|
||||
path: cleaned_path.clone(),
|
||||
axum_info: None,
|
||||
is_dynamic: has_dynamic_path(&cleaned_path),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn update_axum_info(&mut self) {
|
||||
self.axum_info = Some(AxumInfo::new(self.path.clone()))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn should_find_dynamic_paths() {
|
||||
let routes = [
|
||||
("/home/user/Documents/tuono/src/routes/about.rs", false),
|
||||
("/home/user/Documents/tuono/src/routes/index.rs", false),
|
||||
(
|
||||
"/home/user/Documents/tuono/src/routes/posts/index.rs",
|
||||
false,
|
||||
),
|
||||
(
|
||||
"/home/user/Documents/tuono/src/routes/posts/[post].rs",
|
||||
true,
|
||||
),
|
||||
];
|
||||
|
||||
routes
|
||||
.into_iter()
|
||||
.for_each(|route| assert_eq!(has_dynamic_path(route.0), route.1));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn should_correctly_create_the_axum_infos() {
|
||||
let info = AxumInfo::new("/index".to_string());
|
||||
|
||||
assert_eq!(info.axum_route, "/");
|
||||
assert_eq!(info.module_import, "index");
|
||||
|
||||
let dyn_info = AxumInfo::new("/[posts]".to_string());
|
||||
|
||||
assert_eq!(dyn_info.axum_route, "/:posts");
|
||||
assert_eq!(dyn_info.module_import, "dyn_posts");
|
||||
}
|
||||
}
|
||||
@@ -1,27 +1,13 @@
|
||||
use glob::glob;
|
||||
use glob::GlobError;
|
||||
use regex::Regex;
|
||||
use std::collections::HashMap;
|
||||
use std::fs;
|
||||
use std::io;
|
||||
use std::io::prelude::*;
|
||||
use std::path::Path;
|
||||
use std::path::PathBuf;
|
||||
|
||||
#[derive(PartialEq, Eq)]
|
||||
pub enum Mode {
|
||||
Prod,
|
||||
Dev,
|
||||
}
|
||||
|
||||
impl Mode {
|
||||
pub fn as_str<'a>(&self) -> &'a str {
|
||||
if *self == Mode::Dev {
|
||||
return "Mode::Dev";
|
||||
}
|
||||
"Mode::Prod"
|
||||
}
|
||||
}
|
||||
use crate::app::App;
|
||||
use crate::mode::Mode;
|
||||
use crate::route::AxumInfo;
|
||||
use crate::route::Route;
|
||||
|
||||
pub const SERVER_ENTRY_DATA: &str = "// File automatically generated by tuono
|
||||
// Do not manually update this file
|
||||
@@ -63,91 +49,9 @@ async fn main() {
|
||||
}
|
||||
"##;
|
||||
|
||||
const ROOT_FOLDER: &str = "src/routes";
|
||||
const ROUTE_FOLDER: &str = "src/routes";
|
||||
const DEV_FOLDER: &str = ".tuono";
|
||||
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
struct Route {
|
||||
/// Every module import is the path with a _ instead of /
|
||||
pub module_import: String,
|
||||
pub axum_route: String,
|
||||
}
|
||||
|
||||
fn has_dynamic_path(route: &str) -> bool {
|
||||
let regex = Regex::new(r"\[(.*?)\]").expect("Failed to create the regex");
|
||||
regex.is_match(route)
|
||||
}
|
||||
|
||||
impl Route {
|
||||
pub fn new(path: &str) -> Self {
|
||||
let route_name = path.replace(".rs", "");
|
||||
// Remove first slash
|
||||
let mut module = route_name.as_str().chars();
|
||||
module.next();
|
||||
|
||||
let axum_route = path.replace("/index.rs", "").replace(".rs", "");
|
||||
|
||||
if axum_route.is_empty() {
|
||||
return Route {
|
||||
module_import: module.as_str().to_string().replace('/', "_"),
|
||||
axum_route: "/".to_string(),
|
||||
};
|
||||
}
|
||||
|
||||
if has_dynamic_path(&route_name) {
|
||||
return Route {
|
||||
module_import: module
|
||||
.as_str()
|
||||
.to_string()
|
||||
.replace('/', "_")
|
||||
.replace('[', "dyn_")
|
||||
.replace(']', ""),
|
||||
axum_route: axum_route.replace('[', ":").replace(']', ""),
|
||||
};
|
||||
}
|
||||
|
||||
Route {
|
||||
module_import: module.as_str().to_string().replace('/', "_").to_lowercase(),
|
||||
axum_route,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct SourceBuilder {
|
||||
route_map: HashMap<PathBuf, Route>,
|
||||
base_path: PathBuf,
|
||||
}
|
||||
|
||||
impl SourceBuilder {
|
||||
pub fn new() -> Self {
|
||||
let base_path = std::env::current_dir().unwrap();
|
||||
|
||||
SourceBuilder {
|
||||
route_map: HashMap::new(),
|
||||
base_path,
|
||||
}
|
||||
}
|
||||
|
||||
fn collect_routes(&mut self) {
|
||||
glob(self.base_path.join("src/routes/**/*.rs").to_str().unwrap())
|
||||
.unwrap()
|
||||
.for_each(|entry| self.collect_route(entry))
|
||||
}
|
||||
|
||||
fn collect_route(&mut self, path_buf: Result<PathBuf, GlobError>) {
|
||||
let entry = path_buf.unwrap();
|
||||
let base_path_str = self.base_path.to_str().unwrap();
|
||||
let path = entry
|
||||
.to_str()
|
||||
.unwrap()
|
||||
.replace(&format!("{base_path_str}/src/routes"), "");
|
||||
|
||||
let route = Route::new(&path);
|
||||
|
||||
self.route_map.insert(PathBuf::from(path), route);
|
||||
}
|
||||
}
|
||||
|
||||
fn create_main_file(base_path: &Path, bundled_file: &String) {
|
||||
let mut data_file =
|
||||
fs::File::create(base_path.join(".tuono/main.rs")).expect("creation failed");
|
||||
@@ -157,37 +61,43 @@ fn create_main_file(base_path: &Path, bundled_file: &String) {
|
||||
.expect("write failed");
|
||||
}
|
||||
|
||||
fn create_routes_declaration(routes: &HashMap<PathBuf, Route>) -> String {
|
||||
fn create_routes_declaration(routes: &HashMap<String, Route>) -> String {
|
||||
let mut route_declarations = String::from("// ROUTE_BUILDER\n");
|
||||
|
||||
for (_, route) in routes.iter() {
|
||||
let Route {
|
||||
axum_route,
|
||||
module_import,
|
||||
} = &route;
|
||||
let Route { axum_info, .. } = &route;
|
||||
|
||||
route_declarations.push_str(&format!(
|
||||
r#".route("{axum_route}", get({module_import}::route))"#
|
||||
));
|
||||
route_declarations.push_str(&format!(
|
||||
r#".route("/__tuono/data{axum_route}", get({module_import}::api))"#
|
||||
));
|
||||
if axum_info.is_some() {
|
||||
let AxumInfo {
|
||||
axum_route,
|
||||
module_import,
|
||||
} = axum_info.as_ref().unwrap();
|
||||
|
||||
route_declarations.push_str(&format!(
|
||||
r#".route("{axum_route}", get({module_import}::route))"#
|
||||
));
|
||||
route_declarations.push_str(&format!(
|
||||
r#".route("/__tuono/data{axum_route}", get({module_import}::api))"#
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
route_declarations
|
||||
}
|
||||
|
||||
fn create_modules_declaration(routes: &HashMap<PathBuf, Route>) -> String {
|
||||
fn create_modules_declaration(routes: &HashMap<String, Route>) -> String {
|
||||
let mut route_declarations = String::from("// MODULE_IMPORTS\n");
|
||||
|
||||
for (path, route) in routes.iter() {
|
||||
let module_name = &route.module_import;
|
||||
let path_str = path.to_str().unwrap();
|
||||
route_declarations.push_str(&format!(
|
||||
r#"#[path="../{ROOT_FOLDER}{path_str}"]
|
||||
mod {module_name};
|
||||
"#
|
||||
))
|
||||
if route.axum_info.is_some() {
|
||||
let AxumInfo { module_import, .. } = route.axum_info.as_ref().unwrap();
|
||||
|
||||
route_declarations.push_str(&format!(
|
||||
r#"#[path="../{ROUTE_FOLDER}{path}.rs"]
|
||||
mod {module_import};
|
||||
"#
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
route_declarations
|
||||
@@ -196,10 +106,11 @@ mod {module_name};
|
||||
pub fn bundle_axum_source(mode: Mode) -> io::Result<()> {
|
||||
let base_path = std::env::current_dir().unwrap();
|
||||
|
||||
let mut source_builder = SourceBuilder::new();
|
||||
let mut source_builder = App::new();
|
||||
|
||||
source_builder.collect_routes();
|
||||
|
||||
dbg!(&source_builder);
|
||||
let bundled_file = generate_axum_source(&source_builder, mode);
|
||||
|
||||
create_main_file(&base_path, &bundled_file);
|
||||
@@ -207,7 +118,7 @@ pub fn bundle_axum_source(mode: Mode) -> io::Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn generate_axum_source(source_builder: &SourceBuilder, mode: Mode) -> String {
|
||||
fn generate_axum_source(source_builder: &App, mode: Mode) -> String {
|
||||
AXUM_ENTRY_POINT
|
||||
.replace(
|
||||
"// ROUTE_BUILDER\n",
|
||||
@@ -246,103 +157,9 @@ mod tests {
|
||||
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn should_find_dynamic_paths() {
|
||||
let routes = [
|
||||
("/home/user/Documents/tuono/src/routes/about.rs", false),
|
||||
("/home/user/Documents/tuono/src/routes/index.rs", false),
|
||||
(
|
||||
"/home/user/Documents/tuono/src/routes/posts/index.rs",
|
||||
false,
|
||||
),
|
||||
(
|
||||
"/home/user/Documents/tuono/src/routes/posts/[post].rs",
|
||||
true,
|
||||
),
|
||||
];
|
||||
|
||||
routes
|
||||
.into_iter()
|
||||
.for_each(|route| assert_eq!(has_dynamic_path(route.0), route.1));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn should_collect_routes() {
|
||||
let mut source_builder = SourceBuilder::new();
|
||||
source_builder.base_path = "/home/user/Documents/tuono".into();
|
||||
|
||||
let routes = [
|
||||
"/home/user/Documents/tuono/src/routes/about.rs",
|
||||
"/home/user/Documents/tuono/src/routes/index.rs",
|
||||
"/home/user/Documents/tuono/src/routes/posts/index.rs",
|
||||
"/home/user/Documents/tuono/src/routes/posts/[post].rs",
|
||||
"/home/user/Documents/tuono/src/routes/posts/UPPERCASE.rs",
|
||||
];
|
||||
|
||||
routes
|
||||
.into_iter()
|
||||
.for_each(|route| source_builder.collect_route(Ok(PathBuf::from(route))));
|
||||
|
||||
let results = [
|
||||
("/index.rs", "index"),
|
||||
("/about.rs", "about"),
|
||||
("/posts/index.rs", "posts_index"),
|
||||
("/posts/[post].rs", "posts_dyn_post"),
|
||||
("/posts/UPPERCASE.rs", "posts_uppercase"),
|
||||
];
|
||||
|
||||
results.into_iter().for_each(|(path, module_import)| {
|
||||
assert_eq!(
|
||||
source_builder
|
||||
.route_map
|
||||
.get(&PathBuf::from(path))
|
||||
.unwrap()
|
||||
.module_import,
|
||||
String::from(module_import)
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn should_create_multi_level_axum_paths() {
|
||||
let mut source_builder = SourceBuilder::new();
|
||||
source_builder.base_path = "/home/user/Documents/tuono".into();
|
||||
|
||||
let routes = [
|
||||
"/home/user/Documents/tuono/src/routes/about.rs",
|
||||
"/home/user/Documents/tuono/src/routes/index.rs",
|
||||
"/home/user/Documents/tuono/src/routes/posts/index.rs",
|
||||
"/home/user/Documents/tuono/src/routes/posts/any-post.rs",
|
||||
"/home/user/Documents/tuono/src/routes/posts/[post].rs",
|
||||
];
|
||||
|
||||
routes
|
||||
.into_iter()
|
||||
.for_each(|route| source_builder.collect_route(Ok(PathBuf::from(route))));
|
||||
|
||||
let results = [
|
||||
("/index.rs", "/"),
|
||||
("/about.rs", "/about"),
|
||||
("/posts/index.rs", "/posts"),
|
||||
("/posts/any-post.rs", "/posts/any-post"),
|
||||
("/posts/[post].rs", "/posts/:post"),
|
||||
];
|
||||
|
||||
results.into_iter().for_each(|(path, expected_path)| {
|
||||
assert_eq!(
|
||||
source_builder
|
||||
.route_map
|
||||
.get(&PathBuf::from(path))
|
||||
.unwrap()
|
||||
.axum_route,
|
||||
String::from(expected_path)
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn should_set_the_correct_mode() {
|
||||
let source_builder = SourceBuilder::new();
|
||||
let source_builder = App::new();
|
||||
|
||||
let dev_bundle = generate_axum_source(&source_builder, Mode::Dev);
|
||||
assert!(dev_bundle.contains("const MODE: Mode = Mode::Dev;"));
|
||||
@@ -351,12 +168,4 @@ mod tests {
|
||||
|
||||
assert!(prod_bundle.contains("const MODE: Mode = Mode::Prod;"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn should_correctly_print_the_mode_as_str() {
|
||||
let dev = Mode::Dev.as_str();
|
||||
let prod = Mode::Prod.as_str();
|
||||
assert_eq!(dev, "Mode::Dev");
|
||||
assert_eq!(prod, "Mode::Prod");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,7 +6,8 @@ use watchexec::Watchexec;
|
||||
use watchexec_signals::Signal;
|
||||
use watchexec_supervisor::job::{start_job, Job};
|
||||
|
||||
use crate::source_builder::{bundle_axum_source, Mode};
|
||||
use crate::mode::Mode;
|
||||
use crate::source_builder::bundle_axum_source;
|
||||
|
||||
fn watch_react_src() -> Job {
|
||||
start_job(Arc::new(Command {
|
||||
|
||||
Reference in New Issue
Block a user