utpm 0.3.0

UTPM is a package manager for local and remote Typst packages. Quickly create and manage projects and templates on your system, and publish them directly to Typst Universe.
Documentation
use anyhow::Result;
use clap::Parser;
use tracing::{error, instrument, level_filters::LevelFilter};
use tracing_subscriber::{self, Layer, layer::SubscriberExt, util::SubscriberInitExt};

use utpm::{
    args::{ARGS, get_args},
    commands::{self, Cli, Commands, PackagesArgs, ProjectArgs},
    utils::{
        dryrun::get_dry_run,
        output::{OutputFormat, get_output_format},
        state::UtpmError,
    },
    utpm_log,
};

/// The main entry point of the UTPM application.
///
/// This function initializes the command-line interface, parses arguments,
/// sets up logging, and dispatches to the appropriate command handler.
#[instrument]
#[tokio::main]
async fn main() {
    // Parse command-line arguments.
    ARGS.set(Cli::parse()).expect("ARGS should still be empty");
    let x = get_args();

    if get_dry_run() {
        utpm_log!(info, "Using dry-run")
    }

    // Initialize the tracing subscriber based on the output format.
    if get_output_format() != OutputFormat::Text {
        // Use JSON format for logs if output is not plain text.
        tracing_subscriber::registry()
            .with(
                tracing_subscriber::fmt::layer()
                    .json()
                    .with_filter(LevelFilter::from_level(x.verbose)),
            )
            .init();
    } else {
        // Use standard format for text output.
        tracing_subscriber::registry()
            .with(tracing_subscriber::fmt::layer().with_filter(LevelFilter::from_level(x.verbose)))
            .init();
    }

    // Dispatch the command to its handler.
    let res = async move {
        match &x.command {
            Commands::Project(w) => match w {
                ProjectArgs::Link(cmd) => commands::link::run(cmd, &None, true).await,

                ProjectArgs::Init(cmd) => commands::init::run(&mut cmd.clone()).await,

                ProjectArgs::Clone(cmd) => commands::clone::run(cmd).await,

                ProjectArgs::Bump(cmd) => commands::bump::run(cmd).await,

                ProjectArgs::Sync(cmd) => commands::sync::run(cmd).await,
                ProjectArgs::Publish(cmd) => commands::publish::run(cmd).await,

                ProjectArgs::Metadata(cmd) => commands::metadata::run(cmd).await,
            },
            Commands::Packages(p) => match p {
                PackagesArgs::List(cmd) => commands::list::run(cmd).await,

                PackagesArgs::Path => commands::package_path::run().await,

                PackagesArgs::Unlink(cmd) => commands::unlink::run(cmd).await,

                PackagesArgs::Get(cmd) => commands::get::run(cmd).await,

                PackagesArgs::Install(cmd) => commands::install::run(cmd).await,
            },

            Commands::Generate(cmd) => commands::generate::run(cmd).await,
        }
    }
    .await;

    // Handle any errors that occurred during command execution.
    if let Err(err) = res {
        match check_errors(err) {
            Ok(_) => (),
            Err(err2) => error!("{err2}"), // If utpm_log errors, it will fallback to that
        };
    }
}

/// A fallback mechanism to print errors if the primary logging fails.
///
/// If the command execution results in an error, this function is called
/// to log the error to the console.
fn check_errors(err: UtpmError) -> Result<()> {
    utpm_log!(@f error, "{err}");
    Ok(())
}

// Nothing to see except a commit