mod artifact;
mod builtin;
mod checksum;
mod cmd;
mod dev;
mod fabric;
mod game;
mod id;
mod index;
mod install;
mod java;
mod launch;
mod lock;
mod maven;
mod mc;
mod ms;
mod neoforge;
mod pack;
mod path;
mod pbar;
mod prelude;
mod pubgrub;
mod registry;
mod tool;
mod user;
mod util;
mod vanilla;
mod yggdrasil;
mod zip;
use clap::Parser;
use reqwest::Client;
use serde::{Deserialize, Serialize};
use serde_inline_default::serde_inline_default;
use std::{
ops::Deref,
path::{Path, PathBuf},
sync::Arc,
};
use stop::fatal;
use tokio::{
fs::{read_to_string, write},
runtime,
};
use tracing::{Level, info, level_filters::LevelFilter};
use tracing_indicatif::IndicatifLayer;
use tracing_subscriber::{fmt, layer::SubscriberExt, util::SubscriberInitExt};
use url::Url;
use crate::{
artifact::ArtifactManager,
cmd::Execute,
dev::Dev,
fabric::{FabricManager, IntermediaryManager},
game::GameManager,
index::IndexCache,
neoforge::NeoforgeManager,
path::{creeper_config_dir, init_creeper_dirs},
registry::Registry,
tool::Tool,
user::UserManager,
vanilla::VanillaManager,
};
pub use prelude::*;
pub const VERSION: &str = env!("CARGO_PKG_VERSION");
pub struct CreeperInner {
pub config: Config,
artifact: ArtifactManager,
vanilla: VanillaManager,
http: Client,
registry: Registry,
index_cache: IndexCache,
game: GameManager,
neoforge: NeoforgeManager,
fabric: FabricManager,
intermediary: IntermediaryManager,
user: UserManager,
}
#[derive(Clone)]
pub struct Creeper(Arc<CreeperInner>);
impl Deref for Creeper {
type Target = CreeperInner;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl Creeper {
async fn load_config(path: impl AsRef<Path>) -> anyhow::Result<Config> {
let path = path.as_ref();
if !path.exists() {
info!("no config file at {}, using default", path.display());
let config = Config::default();
let toml = toml::to_string_pretty(&config)?;
write(path, toml).await?;
return Ok(config);
}
let toml = read_to_string(path).await?;
let config = toml::from_str(&toml)?;
Ok(config)
}
pub async fn new(config: Option<PathBuf>, dir: Option<PathBuf>) -> anyhow::Result<Self> {
init_creeper_dirs().await?;
let path = config.unwrap_or(creeper_config_dir()?.join("config.toml"));
let config = Self::load_config(path).await?;
let http = Client::default();
let registry = Registry::new(config.registry.clone(), http.clone())?;
let game = GameManager::new(dir);
let neoforge = NeoforgeManager::new(http.clone());
let vanilla = VanillaManager::new(http.clone());
let artifact = ArtifactManager::new(http.clone()).await?;
let user = UserManager::new();
let fabric = FabricManager::new(http.clone());
let intermediary = IntermediaryManager::new(http.clone());
let val = CreeperInner {
config,
artifact,
vanilla,
http,
registry,
index_cache: IndexCache::new(),
neoforge,
game,
user,
fabric,
intermediary,
};
Ok(Self(Arc::new(val)))
}
pub async fn default() -> anyhow::Result<Self> {
Self::new(None, None).await
}
pub async fn execute(&self, cmd: impl Execute) -> anyhow::Result<()> {
cmd.execute(self).await
}
pub async fn update_all(&self) -> anyhow::Result<()> {
self.update_registry().await?;
self.update_vanilla().await?;
self.update_neoforge().await?;
self.update_fabric().await?;
self.update_intermediary().await?;
Ok(())
}
}
#[serde_inline_default]
#[derive(Clone, Debug, Serialize, Deserialize)]
#[serde(deny_unknown_fields, rename_all = "kebab-case")]
pub struct Config {
#[serde_inline_default("https://creeper-registry.pages.dev/".parse().unwrap())]
#[serde(skip_serializing_if = "is_default_registry")]
pub registry: Url,
#[serde_inline_default(4)]
#[serde(skip_serializing_if = "is_default_parallel_download")]
pub parallel_download: usize,
#[serde_inline_default(false)]
#[serde(skip_serializing_if = "std::ops::Not::not")]
pub use_bmclapi: bool,
}
fn is_default_registry(registry: &Url) -> bool {
registry == &"https://creeper-registry.pages.dev/".parse().unwrap()
}
fn is_default_parallel_download(parallel_download: &usize) -> bool {
*parallel_download == 4
}
impl Default for Config {
fn default() -> Self {
Self {
registry: "https://creeper-registry.pages.dev/".parse().unwrap(),
parallel_download: 4,
use_bmclapi: false,
}
}
}
pub const CREEPER_TEXT_ART: &str = r#"
🟩🟩🟩⬜⬜🟩🟩🟩
🟩🟩🟩🟩🟩🟩🟩⬜
🟩⬛⬛🟩🟩⬛⬛⬜
🟩⬛⬛🟩🟩⬛⬛🟩
🟩🟩🟩⬛⬛⬜🟩🟩
🟩🟩⬛⬛⬛⬛🟩⬜
⬜🟩⬛⬛⬛⬛🟩🟩
🟩🟩⬛🟩🟩⬛🟩🟩
"#;
#[derive(Clone, Debug, Parser)]
#[command(version)]
pub struct Command {
#[arg(short, long)]
pub config: Option<PathBuf>,
#[arg(short, long)]
dir: Option<PathBuf>,
#[arg(name = "loglevel", long, default_value_t = Level::INFO)]
log_level: Level,
#[arg(short, long)]
verbose: bool,
#[arg(short, long)]
noisy: bool,
#[command(subcommand)]
cmd: SubCommand,
}
#[derive(Clone, Debug, Parser)]
pub enum SubCommand {
#[command(subcommand)]
Tool(Tool),
Add(cmd::Add),
Launch(cmd::Launch),
BuildIndex(cmd::BuildIndex),
Install(cmd::Install),
Nuke(cmd::Nuke),
Login(cmd::Login),
Init(cmd::Init),
#[command(subcommand)]
Dev(Dev),
Complete(cmd::Complete),
#[clap(hide = true)]
AwwMan,
}
impl Execute for SubCommand {
async fn execute(self, lib: &Creeper) -> anyhow::Result<()> {
match self {
SubCommand::Tool(tool) => lib.execute(tool).await,
SubCommand::AwwMan => Ok(println!("{CREEPER_TEXT_ART}")),
SubCommand::BuildIndex(build_index) => lib.execute(build_index).await,
SubCommand::Install(install) => lib.execute(install).await,
SubCommand::Launch(launch) => lib.execute(launch).await,
SubCommand::Nuke(nuke) => lib.execute(nuke).await,
SubCommand::Login(login) => lib.execute(login).await,
SubCommand::Init(init) => lib.execute(init).await,
SubCommand::Add(add) => lib.execute(add).await,
SubCommand::Dev(_dev) => todo!(),
SubCommand::Complete(complete) => lib.execute(complete).await,
}
}
}
fn main() {
let Command {
config,
cmd,
log_level,
verbose,
noisy,
dir,
} = Command::parse();
let log_level = if noisy {
Level::TRACE
} else if verbose {
Level::DEBUG
} else {
log_level
};
let layer = IndicatifLayer::new();
tracing_subscriber::registry()
.with(LevelFilter::from_level(log_level))
.with(fmt::layer().with_writer(layer.get_stderr_writer()))
.with(layer)
.init();
let run = runtime::Builder::new_multi_thread()
.enable_all()
.build()
.unwrap_or_else(fatal!());
let creeper = run
.block_on(Creeper::new(config, dir))
.unwrap_or_else(fatal!());
run.block_on(creeper.execute(cmd)).unwrap_or_else(fatal!());
}