mod cli;
mod config;
mod dev_dependency_installer;
mod file_manager;
mod github_actions;
mod licenses;
mod package_version;
mod project_generator;
mod project_info;
mod python_files;
mod rust_files;
mod utils;
#[cfg(feature = "fastapi")]
mod fastapi;
use std::{fs::remove_dir_all, process::exit, time::Duration};
use anyhow::{Error, Result};
use clap::Parser;
use cli::ApplicationOrLib;
use colored::*;
use indicatif::{ProgressBar, ProgressStyle};
use crate::{
cli::{Args, BooleanChoice, Command, Param},
config::Config,
dev_dependency_installer::{install_dev_dependencies, install_prek_hooks, update_prek_hooks},
project_generator::generate_project,
project_info::{get_project_info, ProjectInfo, ProjectManager},
rust_files::cargo_add_pyo3,
};
#[cfg(feature = "fastapi")]
use crate::fastapi::{
fastapi_files::generate_fastapi, fastapi_installer::install_fastapi_dependencies,
};
fn create(project_info: &ProjectInfo) -> Result<()> {
generate_project(project_info)?;
std::process::Command::new("git")
.args(["init", &project_info.project_slug])
.output()
.expect("Failed to initialize git");
if matches!(project_info.project_manager, ProjectManager::Maturin) {
cargo_add_pyo3(project_info)?;
}
install_dev_dependencies(project_info)?;
update_prek_hooks(project_info)?;
install_prek_hooks(project_info)?;
#[cfg(feature = "fastapi")]
if project_info.is_fastapi_project {
install_fastapi_dependencies(project_info)?;
generate_fastapi(project_info)?;
}
Ok(())
}
fn print_error(err: Error) {
eprintln!("\n{}", err.to_string().red());
}
fn delete_slug(project_info: &ProjectInfo) -> Result<()> {
let dir = &project_info.base_dir();
if dir.exists() {
remove_dir_all(dir)?;
}
Ok(())
}
fn main() {
let args = Args::parse();
match args.command {
Command::Create { default } => {
let project_info = match get_project_info(default) {
Ok(pi) => pi,
Err(e) => {
print_error(e);
exit(1);
}
};
let create_result: Result<()>;
if let Ok(progress_style) = ProgressStyle::with_template("{spinner:.green} {msg}") {
let pb = ProgressBar::new_spinner();
pb.enable_steady_tick(Duration::from_millis(80));
pb.set_style(
progress_style.tick_strings(&["⣷", "⣯", "⣟", "⡿", "⢿", "⣻", "⣽", "⣾"]),
);
pb.set_message("Generating Project...");
create_result = create(&project_info);
pb.finish_and_clear();
} else {
create_result = create(&project_info);
}
match create_result {
Ok(_) => {
let success_message = format!(
"\nProject created in the {} directory",
project_info.project_slug
);
println!("{}", success_message.green());
}
Err(e) => {
print_error(e);
if let Err(e) = delete_slug(&project_info) {
print_error(e);
};
exit(1);
}
};
}
Command::Config(config) => match config.param {
Param::Creator { value } => {
if let Err(e) = Config::default().save_creator(value) {
print_error(e);
exit(1);
}
}
Param::ResetCreator => {
if let Err(e) = Config::default().reset_creator() {
print_error(e);
exit(1);
}
}
Param::CreatorEmail { value } => {
if let Err(e) = Config::default().save_creator_email(value) {
print_error(e);
exit(1);
}
}
Param::ResetCreatorEmail => {
if let Err(e) = Config::default().reset_creator_email() {
print_error(e);
exit(1);
}
}
Param::License { value } => {
if let Err(e) = Config::default().save_license(value) {
print_error(e);
exit(1);
}
}
Param::ResetLicense => {
if let Err(e) = Config::default().reset_license() {
print_error(e);
exit(1);
}
}
Param::PythonVersion { value } => {
if let Err(e) = Config::default().save_python_version(value) {
print_error(e);
exit(1);
}
}
Param::ResetPythonVersion => {
if let Err(e) = Config::default().reset_python_version() {
print_error(e);
exit(1);
}
}
Param::MinPythonVersion { value } => {
if let Err(e) = Config::default().save_min_python_version(value) {
print_error(e);
exit(1);
}
}
Param::ResetMinPythonVersion => {
if let Err(e) = Config::default().reset_min_python_version() {
print_error(e);
exit(1);
}
}
Param::ProjectManager { value } => {
if let Err(e) = Config::default().save_project_manager(value) {
print_error(e);
exit(1);
}
}
Param::ResetProjectManager => {
if let Err(e) = Config::default().reset_project_manager() {
print_error(e);
exit(1);
}
}
Param::Pyo3PythonManager { value } => {
if let Err(e) = Config::default().save_pyo3_python_manager(value) {
print_error(e);
exit(1);
}
}
Param::ResetPyo3PythonManager => {
if let Err(e) = Config::default().reset_pyo3_python_manager() {
print_error(e);
exit(1);
}
}
Param::ApplicationOrLibrary { value } => match value {
ApplicationOrLib::Application => {
if let Err(e) = Config::default().save_is_application(true) {
print_error(e);
exit(1);
}
}
ApplicationOrLib::Lib => {
if let Err(e) = Config::default().save_is_application(false) {
print_error(e);
exit(1);
}
}
},
Param::ResetApplicationOrLibrary => {
if let Err(e) = Config::default().reset_is_application() {
print_error(e);
exit(1);
}
}
Param::IsAsyncProject { value } => match value {
BooleanChoice::True => {
if let Err(e) = Config::default().save_is_async_project(true) {
print_error(e);
exit(1);
}
}
BooleanChoice::False => {
if let Err(e) = Config::default().save_is_async_project(false) {
print_error(e);
exit(1);
}
}
},
Param::ResetIsAsyncProject => {
if let Err(e) = Config::default().reset_is_async_project() {
print_error(e);
exit(1);
}
}
Param::GithubActionPythonTestVersions { value } => {
if let Err(e) = Config::default().save_github_actions_python_test_versions(value) {
print_error(e);
exit(1);
}
}
Param::ResetGithubActionPythonTestVersions => {
if let Err(e) = Config::default().reset_github_actions_python_test_versions() {
print_error(e);
exit(1);
}
}
Param::MaxLineLength { value } => {
if let Err(e) = Config::default().save_max_line_length(value) {
print_error(e);
exit(1);
}
}
Param::ResetMaxLineLength => {
if let Err(e) = Config::default().reset_max_line_length() {
print_error(e);
exit(1);
}
}
Param::UseDependabot { value } => match value {
BooleanChoice::True => {
if let Err(e) = Config::default().save_use_dependabot(true) {
print_error(e);
exit(1);
}
}
BooleanChoice::False => {
if let Err(e) = Config::default().save_use_dependabot(false) {
print_error(e);
exit(1);
}
}
},
Param::ResetUseDependabot => {
if let Err(e) = Config::default().reset_use_dependabot() {
print_error(e);
exit(1);
}
}
Param::DependabotSchedule { value } => {
if let Err(e) = Config::default().save_dependabot_schedule(value) {
print_error(e);
exit(1);
}
}
Param::ResetDependabotSchedule => {
if let Err(e) = Config::default().reset_dependabot_schedule() {
print_error(e);
exit(1);
}
}
Param::DependabotDay { value } => {
if let Err(e) = Config::default().save_dependabot_day(value) {
print_error(e);
exit(1);
}
}
Param::ResetDependabotDay => {
if let Err(e) = Config::default().reset_dependabot_day() {
print_error(e);
exit(1);
}
}
Param::UseContinuousDeployment { value } => match value {
BooleanChoice::True => {
if let Err(e) = Config::default().save_use_continuous_deployment(true) {
print_error(e);
exit(1);
}
}
BooleanChoice::False => {
if let Err(e) = Config::default().save_use_continuous_deployment(false) {
print_error(e);
exit(1);
}
}
},
Param::ResetUseContinuousDeployment => {
if let Err(e) = Config::default().reset_use_continuous_deployment() {
print_error(e);
exit(1);
}
}
Param::UseReleaseDrafter { value } => match value {
BooleanChoice::True => {
if let Err(e) = Config::default().save_use_release_drafter(true) {
print_error(e);
exit(1);
}
}
BooleanChoice::False => {
if let Err(e) = Config::default().save_use_release_drafter(false) {
print_error(e);
exit(1);
}
}
},
Param::ResetUseReleaseDrafter => {
if let Err(e) = Config::default().reset_use_release_drafter() {
print_error(e);
exit(1);
}
}
Param::UseMultiOsCi { value } => match value {
BooleanChoice::True => {
if let Err(e) = Config::default().save_use_multi_os_ci(true) {
print_error(e);
exit(1);
}
}
BooleanChoice::False => {
if let Err(e) = Config::default().save_use_multi_os_ci(false) {
print_error(e);
exit(1);
}
}
},
Param::ResetUseMultiOsCi => {
if let Err(e) = Config::default().reset_use_multi_os_ci() {
print_error(e);
exit(1);
}
}
Param::IncludeDocs { value } => match value {
BooleanChoice::True => {
if let Err(e) = Config::default().save_include_docs(true) {
print_error(e);
exit(1);
}
}
BooleanChoice::False => {
if let Err(e) = Config::default().save_include_docs(false) {
print_error(e);
exit(1);
}
}
},
Param::ResetIncludeDocs => {
if let Err(e) = Config::default().reset_include_docs() {
print_error(e);
exit(1);
}
}
#[cfg(feature = "fastapi")]
Param::IsFastapiProject { value } => match value {
BooleanChoice::True => {
if let Err(e) = Config::default().save_is_fastapi_project(true) {
print_error(e);
exit(1);
}
}
BooleanChoice::False => {
if let Err(e) = Config::default().save_is_fastapi_project(false) {
print_error(e);
exit(1);
}
}
},
#[cfg(feature = "fastapi")]
Param::ResetIsFastapiProject => {
if let Err(e) = Config::default().reset_is_fastapi_project() {
print_error(e);
exit(1);
}
}
Param::Reset => {
if Config::reset().is_err() {
let message = "Error resetting config.";
eprintln!("{}", message.red());
exit(1);
}
}
Param::Show => Config::default().show(),
},
}
}
#[cfg(test)]
mod tests {
use super::project_info::{LicenseType, ProjectManager};
use super::*;
use std::fs::create_dir_all;
use tmp_path::tmp_path;
#[test]
#[tmp_path]
fn test_delete_slug() {
let project_slug = "test-project";
let slug_dir = tmp_path.join(project_slug);
let project_info = ProjectInfo {
project_name: "My project".to_string(),
project_slug: project_slug.to_string(),
source_dir: "my_project".to_string(),
project_description: "This is a test".to_string(),
creator: "Arthur Dent".to_string(),
creator_email: "authur@heartofgold.com".to_string(),
license: LicenseType::Mit,
copyright_year: Some("2023".to_string()),
version: "0.1.0".to_string(),
python_version: "3.12".to_string(),
min_python_version: "3.10".to_string(),
project_manager: ProjectManager::Poetry,
pyo3_python_manager: None,
is_application: true,
is_async_project: false,
github_actions_python_test_versions: vec![
"3.10".to_string(),
"3.11".to_string(),
"3.12".to_string(),
"3.13".to_string(),
"3.14".to_string(),
],
max_line_length: 100,
use_dependabot: true,
dependabot_schedule: None,
dependabot_day: None,
use_continuous_deployment: true,
use_release_drafter: true,
use_multi_os_ci: true,
include_docs: false,
docs_info: None,
project_root_dir: Some(tmp_path),
#[cfg(feature = "fastapi")]
is_fastapi_project: false,
#[cfg(feature = "fastapi")]
database_manager: None,
};
create_dir_all(&slug_dir).unwrap();
assert!(slug_dir.exists());
delete_slug(&project_info).unwrap();
assert!(!slug_dir.exists());
}
}