gload 0.5.0

A command line client for the Gemini protocol.
Documentation
use serde::Deserialize;
use std::{collections::HashMap, env, fs, path::Path};

static VERSION: &str = env!("CARGO_PKG_VERSION");

fn main() {
	let out_dir = env::var_os("OUT_DIR").unwrap();

	// Compose platform string (e.g. "x86-64-redhat-linux-gnu")
	let platform = rustc_host::from_cli().unwrap_or_else(|_| {
		let arch = str_or_unknown(env::var("CARGO_CFG_TARGET_ARCH").unwrap());
		let vendor = str_or_unknown(env::var("CARGO_CFG_TARGET_VENDOR").unwrap());
		let os = str_or_unknown(env::var("CARGO_CFG_TARGET_OS").unwrap());
		let env_abi = str_or_unknown(env::var("CARGO_CFG_TARGET_ENV").unwrap());
		[arch, vendor, os, env_abi].join("-")
	});

	// Parse changelog for this version's release date
	let changelog = include_str!("CHANGELOG.gmi");
	let release_date = {
		let version_line_start = format!("## {VERSION} - ");
		let date_maybe = changelog
			.lines()
			.find_map(|l| l.strip_prefix(&version_line_start));
		if let Some(date) = date_maybe
			&& date.contains("-")
		{
			date
		} else {
			"Unreleased"
		}
	};

	// Parse Cargo.toml for our dependency versions
	let cargo_toml: CargoToml = toml::from_str(include_str!("Cargo.toml")).unwrap();
	let rustls = cargo_toml.get_dep("rustls").unwrap().version();
	let rustls_native_certs = cargo_toml.get_dep("rustls-native-certs").unwrap().version();
	let tokio = cargo_toml.get_dep("tokio").unwrap().version();
	let webpki_roots = cargo_toml.get_dep("webpki-roots").unwrap().version();
	let x509_parser = cargo_toml.get_dep("x509-parser").unwrap().version();

	// Write version string to {OUT_DIR}/full_version.txt
	let full_version = format!(
		r"{VERSION} ({platform}) rustls/{rustls} rustls-native-certs/{rustls_native_certs} tokio/{tokio} webpki-roots/{webpki_roots} x509-parser/{x509_parser}
Release-Date: {release_date}
Protocols: gemini"
	);
	let full_version_txt = Path::new(&out_dir).join("full_version.txt");
	fs::write(full_version_txt, full_version).unwrap();

	println!("cargo::rerun-if-changed=Cargo.toml,build.rs");
}

fn str_or_unknown(value: String) -> String {
	if value.is_empty() {
		String::from("unknown")
	} else {
		value
	}
}

#[derive(Deserialize)]
struct CargoToml {
	dependencies: HashMap<String, Package>,
}

impl CargoToml {
	fn get_dep(&self, name: &'static str) -> Option<&Package> {
		self.dependencies.get(name)
	}
}

#[derive(Deserialize)]
#[serde(untagged)]
enum Package {
	Version(String),
	Manifest { version: String },
}

impl Package {
	const fn version(&self) -> &str {
		match self {
			Self::Version(version) => version.as_str(),
			Self::Manifest { version } => version.as_str(),
		}
	}
}