use clap::crate_version;
use reqwest::blocking;
use serde::Deserialize;
use std::env;
use std::fs::{self, create_dir, File, OpenOptions};
use std::io::{self, prelude::*};
use std::path::{Path, PathBuf};
const GITHUB_TUONO_REPO_URL: &str =
"https://api.github.com/repos/Valerioageno/tuono/git/trees/main?recursive=1";
const GITHUB_RAW_CONTENT_URL: &str = "https://raw.githubusercontent.com/Valerioageno/tuono/main/";
#[derive(Deserialize, Debug)]
enum GithubFileType {
#[serde(rename = "blob")]
Blob,
#[serde(rename = "tree")]
Tree,
}
#[derive(Deserialize, Debug)]
struct GithubResponse<T> {
tree: Vec<T>,
}
#[derive(Deserialize, Debug)]
struct GithubFile {
path: String,
#[serde(rename(deserialize = "type"))]
element_type: GithubFileType,
}
fn create_file(path: PathBuf, content: String) -> std::io::Result<()> {
let mut file = File::create(path)?;
let _ = file.write_all(content.as_bytes());
Ok(())
}
pub fn create_new_project(folder_name: Option<String>) {
let folder = folder_name.unwrap_or(".".to_string());
if folder != "." {
create_dir(&folder).unwrap();
}
let client = blocking::Client::builder()
.user_agent("")
.build()
.expect("Failed to build reqwest client");
let res = client
.get(GITHUB_TUONO_REPO_URL)
.send()
.expect("Failed to call the folder github API")
.json::<GithubResponse<GithubFile>>()
.expect("Failed to parse the repo structure");
let new_project_files = res
.tree
.iter()
.filter(|GithubFile { path, .. }| {
if path.starts_with("examples/tuono/") {
return true;
}
false
})
.collect::<Vec<&GithubFile>>();
let folder_name = PathBuf::from(&folder);
let current_dir = env::current_dir().expect("Failed to get current working directory");
let folder_path = current_dir.join(folder_name);
create_directories(&new_project_files, &folder_path).expect("Failed to create directories");
for GithubFile {
element_type, path, ..
} in new_project_files.iter()
{
if let GithubFileType::Blob = element_type {
let file_content = client
.get(format!("{GITHUB_RAW_CONTENT_URL}{path}"))
.send()
.expect("Failed to call the folder github API")
.text()
.expect("Failed to parse the repo structure");
let path = PathBuf::from(&path.replace("examples/tuono/", ""));
let file_path = folder_path.join(&path);
create_file(file_path, file_content).expect("failed to create file");
}
}
update_package_json_version(&folder_path).expect("Failed to update package.json version");
update_cargo_toml_version(&folder_path).expect("Failed to update Cargo.toml version");
outro(folder);
}
fn create_directories(new_project_files: &Vec<&GithubFile>, folder_path: &Path) -> io::Result<()> {
for GithubFile {
element_type, path, ..
} in new_project_files.iter()
{
if let GithubFileType::Tree = element_type {
let path = PathBuf::from(&path.replace("examples/tuono/", ""));
let dir_path = folder_path.join(&path);
create_dir(&dir_path).unwrap();
}
}
Ok(())
}
fn update_package_json_version(folder_path: &Path) -> io::Result<()> {
let v = crate_version!();
let package_json_path = folder_path.join(PathBuf::from("package.json"));
let package_json = fs::read_to_string(&package_json_path)?;
let package_json = package_json.replace("link:../../packages/tuono", v);
let mut file = OpenOptions::new()
.write(true)
.truncate(true)
.open(package_json_path)?;
file.write_all(package_json.as_bytes())?;
Ok(())
}
fn update_cargo_toml_version(folder_path: &Path) -> io::Result<()> {
let v = crate_version!();
let cargo_toml_path = folder_path.join(PathBuf::from("Cargo.toml"));
let cargo_toml = fs::read_to_string(&cargo_toml_path)?;
let cargo_toml =
cargo_toml.replace("{ path = \"../../crates/tuono_lib/\"}", &format!("\"{v}\""));
let mut file = OpenOptions::new()
.write(true)
.truncate(true)
.open(cargo_toml_path)?;
file.write_all(cargo_toml.as_bytes())?;
Ok(())
}
fn outro(folder_name: String) {
println!("Success! 🎉");
if folder_name != "." {
println!("\nGo to the project directory:");
println!("cd {folder_name}/");
}
println!("\nInstall the dependencies:");
println!("pnpm install");
println!("\nRun the local environment:");
println!("tuono dev");
}