Files
gt-tool/src/main.rs
Robert Garrett da8f008f1a Use current-dir as final fallback repo name
It all falls into place! I had been dreading doing this bit, but after
updating the usage guide I realized the CLI args should be split, too.
Which finally means that I can just glue on the PWD name as a final
fallback for the repo name.

Try the args, then the config file(s), then PWD. If nothing works, the
user is in a world of hurt. Bail out.
2025-07-21 14:48:30 -05:00

180 lines
6.8 KiB
Rust

use std::path::{self, PathBuf};
use gt_tool::cli::Args;
use gt_tool::structs::release::{CreateReleaseOption, Release};
use clap::Parser;
use reqwest::header;
use reqwest::header::ACCEPT;
#[tokio::main]
async fn main() -> Result<(), gt_tool::Error> {
let args = Args::parse();
let project_path =
args.project
.map(PathBuf::from)
.unwrap_or(std::env::current_dir().map_err(|_e| {
gt_tool::Error::WrappedConfigErr(gt_tool::config::Error::CouldntReadFile)
})?);
let config = gt_tool::config::get_config(
project_path
.to_str()
.expect("I assumed the path can be UTF-8, but that didn't work out..."),
gt_tool::config::default_paths(),
)?;
println!("->> Loaded Config: {config:?}");
// arg parser also checks the environment. Prefer CLI/env, then config file.
let gitea_url = args
.gitea_url
.or(config.gitea_url)
.ok_or(gt_tool::Error::MissingGiteaUrl)?;
let owner = args.owner
.or(config.owner)
.ok_or(gt_tool::Error::MissingRepoOwner)?;
let repo = args.repo
.or(config.repo)
.or_else(infer_repo)
.ok_or(gt_tool::Error::MissingRepoName)?;
let repo_fqrn = String::from(format!("{owner}/{repo}"));
let mut headers = reqwest::header::HeaderMap::new();
headers.append(ACCEPT, header::HeaderValue::from_static("application/json"));
// Gitea expects to see "token " for token auth.
if let Ok(token) = std::env::var("RELEASE_KEY_GITEA") {
let token = format!("token {token}");
headers.append("Authorization", token.parse().unwrap());
}
let client = reqwest::Client::builder()
.user_agent(format!("gt-tools-agent-{}", env!("CARGO_PKG_VERSION")))
.default_headers(headers)
.build()?;
match args.command {
gt_tool::cli::Commands::ListReleases => {
let releases =
gt_tool::api::release::list_releases(&client, &gitea_url, &repo_fqrn).await?;
// Print in reverse order so the newest items are closest to the
// user's command prompt. Otherwise the newest item scrolls off the
// screen and can't be seen.
itertools::Itertools::intersperse(
releases.iter().rev().map(|release| release.colorized()),
String::from(""),
)
.map(|release| println!("{release}"))
.fold((), |_, _| ());
}
gt_tool::cli::Commands::CreateRelease {
name,
body,
draft,
tag_name,
target_commitish,
} => {
let submission = CreateReleaseOption {
body,
draft,
name,
prerelease: false,
tag_name,
target_commitish,
};
gt_tool::api::release::create_release(&client, &gitea_url, &repo_fqrn, submission)
.await?;
}
gt_tool::cli::Commands::UploadRelease {
tag_name,
// create,
files,
} => {
println!("Uploading files to a release!");
println!("Release Tag: {tag_name}");
// println!("Creating?: {create}");
println!("Files...");
for file in &files {
println!("--- {file}");
}
// TODO: Pre-create the release, if it doesn't exist.
// There's only Gitea APIs to get all releases, or one by-id.
// Grab all, find the one that matches the input tag.
// Scream if there are multiple matches.
let release_candidates =
gt_tool::api::release::list_releases(&client, &gitea_url, &repo_fqrn).await?;
if let Some(release) = match_release_by_tag(&tag_name, release_candidates) {
for file in &files {
let path = path::Path::new(&file);
match path.try_exists() {
Ok(true) => continue,
Ok(false) => return Err(gt_tool::Error::NoSuchFile),
Err(e) => {
eprintln!("Uh oh! The file-exists check couldn't be done: {e}");
panic!(
"TODO: Deal with scenario where the file's existence cannot be checked (e.g.: no permission)"
);
}
}
}
for file in files {
let _attach_desc = gt_tool::api::release_attachment::create_release_attachment(
&client, &gitea_url, &repo_fqrn, release.id, file,
)
.await?;
}
} else {
println!("ERR: Couldn't find a release matching the tag \"{tag_name}\".");
return Err(gt_tool::Error::NoSuchRelease);
}
}
}
Ok(())
}
// Util to scan a release list for a given tag. Returns Some(release) if found,
// None if not, and crashes the program if multiple are found.
//
// The Gitea webpage won't create multiple releases for a given tag, but the
// API might... And someone could always fiddle with the database directly.
// Until I find a guarantee of any particular behavior, I'm going to assume it
// *can* happen and crash when it does. Clearly it isn't supposed to, so I'm
// not going to meaningfully handle it, only prevent garbage from propagating.
fn match_release_by_tag(tag: &String, releases: Vec<Release>) -> Option<Release> {
let mut release: Option<Release> = None;
for rel in releases {
if rel.tag_name == *tag {
// Only store the value if one hasn't been stored already
if let Some(first_release) = &release {
// if there was already a match, begin the error diagnostic creation.
let first_id = first_release.id;
let second_id = rel.id;
assert!(
first_id != second_id,
"FAILURE: Found the same release ID twice while scanning for duplicate tags. How did we get the same one twice?"
);
eprintln!("ERROR: Two releases have been found for the tag \"{tag}\".");
eprintln!("ERROR: first ID: {first_id}");
eprintln!("ERROR: second ID: {second_id}");
panic!("ERROR: Nonsense detected, I'm bailing out!");
} else {
// else, store our first (and hopefully only) match
release = Some(rel);
}
}
}
release
}
fn infer_repo() -> Option<String> {
let pwd = std::env::current_dir().ok()?;
let file_name = pwd.file_name()?;
let file_name_string = file_name.to_str()?;
Some(String::from(file_name_string))
}