use crate::cmd::run_cmd_directly;
use crate::errors::*;
use crate::parse::{InitOpts, NewOpts, Opts};
use std::fs;
use std::path::{Path, PathBuf};
fn create_file_if_not_present(
filename: &Path,
contents: &str,
name: &str,
) -> Result<(), InitError> {
let filename_str = filename.to_str().unwrap();
if fs::metadata(filename).is_ok() {
eprintln!("[WARNING]: Didn't create '{}', since it already exists. If you didn't mean for this to happen, you should remove this file and try again.", filename_str);
} else {
let contents = contents
.replace("%name", name)
.replace("%perseus_version", env!("CARGO_PKG_VERSION"));
fs::write(filename, contents).map_err(|err| InitError::CreateInitFileFailed {
source: err,
filename: filename_str.to_string(),
})?;
}
Ok(())
}
pub fn init(dir: PathBuf, opts: &InitOpts) -> Result<i32, InitError> {
fs::create_dir_all(dir.join("src/templates"))
.map_err(|err| InitError::CreateDirStructureFailed { source: err })?;
create_file_if_not_present(&dir.join("Cargo.toml"), DFLT_INIT_CARGO_TOML, &opts.name)?;
create_file_if_not_present(&dir.join(".gitignore"), DFLT_INIT_GITIGNORE, &opts.name)?;
create_file_if_not_present(&dir.join("src/main.rs"), DFLT_INIT_MAIN_RS, &opts.name)?;
create_file_if_not_present(
&dir.join("src/templates/mod.rs"),
DFLT_INIT_MOD_RS,
&opts.name,
)?;
create_file_if_not_present(
&dir.join("src/templates/index.rs"),
DFLT_INIT_INDEX_RS,
&opts.name,
)?;
println!("Your new app has been created! Run `perseus serve -w` to get to work! You can find more details, including about improving compilation speeds in the Perseus docs (https://framesurge.sh/perseus/en-US/docs/).");
Ok(0)
}
pub fn new(dir: PathBuf, opts: &NewOpts, global_opts: &Opts) -> Result<i32, NewError> {
let target = dir.join(opts.dir.as_ref().unwrap_or(&opts.name));
if let Some(url) = &opts.template {
let url_parts = url.split('@').collect::<Vec<&str>>();
let engine_url = url_parts[0];
let cmd = format!(
"{} clone --single-branch {branch} --depth 1 {repo} {output}",
global_opts.git_path,
branch = if let Some(branch) = url_parts.get(1) {
format!("--branch {}", branch)
} else {
String::new()
},
repo = engine_url,
output = target.to_string_lossy()
);
println!(
"Fetching custom initialization template with command: '{}'.",
&cmd
);
let exit_code = run_cmd_directly(
cmd,
&dir, vec![],
)
.map_err(|err| NewError::GetCustomInitFailed { source: err })?;
if exit_code != 0 {
return Err(NewError::GetCustomInitNonZeroExitCode { exit_code });
}
let git_target = target.join(".git");
if let Err(err) = fs::remove_dir_all(&git_target) {
return Err(NewError::RemoveCustomInitGitFailed {
target_dir: git_target.to_str().map(|s| s.to_string()),
source: err,
});
}
Ok(0)
} else {
fs::create_dir(&target).map_err(|err| NewError::CreateProjectDirFailed { source: err })?;
let exit_code = init(
target,
&InitOpts {
name: opts.name.to_string(),
},
)?;
Ok(exit_code)
}
}
static DFLT_INIT_CARGO_TOML: &str = r#"[package]
name = "%name"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
# Dependencies for the engine and the browser go here
[dependencies]
perseus = { version = "=%perseus_version", features = [ "hydrate" ] }
sycamore = "^0.8.1"
serde = { version = "1", features = [ "derive" ] }
serde_json = "1"
# Engine-only dependencies go here
[target.'cfg(engine)'.dependencies]
tokio = { version = "1", features = [ "macros", "rt", "rt-multi-thread" ] }
perseus-axum = { version = "=%perseus_version", features = [ "dflt-server" ] }
# Browser-only dependencies go here
[target.'cfg(client)'.dependencies]"#;
static DFLT_INIT_GITIGNORE: &str = r#"dist/"#;
static DFLT_INIT_MAIN_RS: &str = r#"mod templates;
use perseus::prelude::*;
#[perseus::main(perseus_axum::dflt_server)]
pub fn main<G: Html>() -> PerseusApp<G> {
PerseusApp::new()
.template(crate::templates::index::get_template())
}"#;
static DFLT_INIT_MOD_RS: &str = r#"pub mod index;"#;
static DFLT_INIT_INDEX_RS: &str = r#"use perseus::prelude::*;
use sycamore::prelude::*;
fn index_page<G: Html>(cx: Scope) -> View<G> {
view! { cx,
// Don't worry, there are much better ways of styling in Perseus!
div(style = "display: flex; flex-direction: column; justify-content: center; align-items: center; height: 95vh;") {
h1 { "Welcome to Perseus!" }
p {
"This is just an example app. Try changing some code inside "
code { "src/templates/index.rs" }
" and you'll be able to see the results here!"
}
}
}
}
#[engine_only_fn]
fn head(cx: Scope) -> View<SsrNode> {
view! { cx,
title { "Welcome to Perseus!" }
}
}
pub fn get_template<G: Html>() -> Template<G> {
Template::build("index").view(index_page).head(head).build()
}"#;