use std::{
env,
fs::{self, File},
path::{Path, PathBuf},
};
use anyhow::{anyhow, bail, Context, Result};
use clap::Args;
use facti_lib::{version::Version, FactorioVersion, ModInfo};
use tracing::{debug, info, warn};
use url::Url;
use crate::{
config::Config,
vcs::{self, Vcs},
};
#[derive(Args, Debug)]
pub struct NewArgs {
#[arg(value_name = "MOD_PATH")]
pub path: Option<PathBuf>,
#[arg(long)]
pub name: Option<String>,
#[arg(long, default_value_t = Version::new(0, 1, 0))]
pub mod_version: Version,
#[arg(long)]
pub title: Option<String>,
#[arg(long)]
pub author: Option<String>,
#[arg(long)]
pub contact: Option<String>,
#[arg(long)]
pub homepage: Option<Url>,
#[arg(long)]
pub description: Option<String>,
#[arg(long)]
pub factorio_version: Option<FactorioVersion>,
}
impl NewArgs {
pub fn run(&self, config: &Config) -> Result<()> {
let path = self.path.to_owned().unwrap_or(env::current_dir()?);
let name = path
.file_name()
.context("Failed to get file_name (basename) of mod path")?
.to_str()
.ok_or(anyhow!("Mod folder name is not valid unicode"))?;
init_dir(&path)?;
info!("Creating new mod {} at {}", name, path.display());
vcs::git::Git::init(&path)?;
let src_path = path.join("src");
fs::create_dir(&src_path).context("Failed to create src dir inside mod")?;
let modinfo = create_modinfo(name, self, config).context("Failed to construct modinfo")?;
let modinfo_path = src_path.join("info.json");
let modinfo_file = File::create(modinfo_path)
.context("Failed to create info.json for writing mod info")?;
serde_json::to_writer_pretty(modinfo_file, &modinfo)
.context("Failed to write mod info to info.json")?;
println!("Created new mod at {}", path.display());
Ok(())
}
}
fn init_dir(path: &Path) -> Result<()> {
let exists = path.exists();
if exists && path.is_file() {
warn!("Attempted to initialize mod in file {}", path.display());
bail!("{} is a file", path.display());
}
if exists && !is_dir_empty(path)? {
warn!(
"Attempted to initialize mod in non-empty directory {}",
path.display()
);
bail!("Directory {} is not empty", path.display());
}
if exists {
debug!(
"Directory {} already exists and is empty, no need to init",
path.display()
);
} else {
info!("Creating directory {}", path.display());
fs::create_dir_all(path)
.with_context(|| format!("Failed to create directory {}", path.display()))?;
}
Ok(())
}
fn is_dir_empty(path: &Path) -> Result<bool> {
Ok(path
.read_dir()
.with_context(|| format!("Failed to read dir {} to check if empty", path.display()))?
.next()
.is_none())
}
fn create_modinfo(name: &str, args: &NewArgs, config: &Config) -> Result<ModInfo> {
let mut builder = ModInfo::builder(
resolve_name(args, name),
args.mod_version,
resolve_title(args, name),
resolve_author(args, config)?,
);
if let Some(c) = resolve_contact(args, config)? {
builder.contact(c);
}
if let Some(homepage) = args.homepage.to_owned() {
builder.homepage(homepage);
}
if let Some(description) = args.description.to_owned() {
builder.description(description);
}
builder.factorio_version(resolve_factorio_version(args, config));
Ok(builder.build())
}
fn resolve_name<T: Into<String>>(args: &NewArgs, fallback: T) -> String {
args.name.to_owned().unwrap_or_else(|| fallback.into())
}
fn resolve_title<T: Into<String>>(args: &NewArgs, fallback: T) -> String {
args.title.to_owned().unwrap_or_else(|| fallback.into())
}
fn resolve_author(args: &NewArgs, config: &Config) -> Result<String> {
if let Some(name) = &args.author {
debug!("Author name resolved from CLI args");
Ok(name.to_owned())
} else if let Some(name) = &config.mod_defaults.author {
debug!("Author name resolved from config file");
Ok(name.to_owned())
} else if let Some(name) =
vcs::git::Git::user_name().context("Failed to resolve user name from VCS config")?
{
debug!("Author name resolved from VCS config");
Ok(name)
} else {
debug!("Unable to resolve author name, using 'Unknown' as placeholder");
Ok("Unknown".to_owned())
}
}
fn resolve_contact(args: &NewArgs, config: &Config) -> Result<Option<String>> {
if let Some(contact) = &args.contact {
Ok(Some(contact.to_owned()))
} else if let Some(contact) = &config.mod_defaults.contact {
Ok(Some(contact.to_owned()))
} else if let Some(email) =
vcs::git::Git::user_email().context("Failed to resolve user email from VCS config")?
{
Ok(Some(email.to_owned()))
} else {
Ok(None)
}
}
fn resolve_factorio_version(args: &NewArgs, config: &Config) -> FactorioVersion {
args.factorio_version
.or(config.mod_defaults.factorio_version)
.unwrap_or_default()
}