git.delta.rocks / jrsonnet / refs/heads / master

difftreelog

source

cmds/jrb/src/main.rs5.3 KiBsourcehistory
1use std::{2	path::{Path, PathBuf},3	process::exit,4};56use clap::{Parser, Subcommand};7use jrsonnet_pkg::{8	install,9	jsonnet_bundler::{GitSource, JsonnetFile},10};11use tracing::{error, info, warn};1213#[derive(Parser)]14#[clap(about = "A jsonnet package manager")]15struct Opts {16	/// The directory used to cache packages in.17	#[clap(long, default_value = "vendor")]18	jsonnetpkg_home: PathBuf,19	#[clap(subcommand)]20	command: Command,21}2223#[derive(Subcommand)]24enum Command {25	/// Initialize a new empty jsonnetfile26	Init,27	/// Install new dependencies. Existing ones are silently skipped28	Install {29		/// Package URIs to install30		uris: Vec<String>,31		/// Show what would be done without making changes32		#[clap(long)]33		dry_run: bool,34	},35	/// Update all or specific dependencies36	Update {37		/// Package URIs to update (all if empty)38		uris: Vec<String>,39		/// Show what would be done without making changes40		#[clap(long)]41		dry_run: bool,42	},43	/// Remove dependencies by name44	Remove {45		/// Dependency names (matched against both canonical and legacy names)46		names: Vec<String>,47		/// Show what would be removed without making changes48		#[clap(long)]49		dry_run: bool,50	},51}5253const MANIFEST: &str = "jsonnetfile.json";54const LOCKFILE: &str = "jsonnetfile.lock.json";5556fn load_manifest() -> JsonnetFile {57	let path = Path::new(MANIFEST);58	if path.exists() {59		JsonnetFile::load(path).unwrap_or_else(|e| {60			error!("failed to load {MANIFEST}: {e}");61			exit(1);62		})63	} else {64		JsonnetFile {65			version: 1,66			dependencies: Vec::new(),67			legacy_imports: true,68		}69	}70}7172fn save_json(path: &Path, value: &impl serde::Serialize) {73	let json = serde_json::to_string_pretty(value).expect("serialization failed");74	std::fs::write(path, format!("{json}\n")).unwrap_or_else(|e| {75		error!("failed to write {}: {e}", path.display());76		exit(1);77	});78}7980fn load_lockfile() -> Option<JsonnetFile> {81	let path = Path::new(LOCKFILE);82	if path.exists() {83		Some(JsonnetFile::load(path).unwrap_or_else(|e| {84			error!("failed to load {LOCKFILE}: {e}");85			exit(1);86		}))87	} else {88		None89	}90}9192fn do_install(93	manifest: &JsonnetFile,94	lock: Option<&JsonnetFile>,95	vendor_dir: &Path,96	dry_run: bool,97) {98	let new_lock = install::install(manifest, lock, vendor_dir, dry_run).unwrap_or_else(|e| {99		error!("install failed: {e}");100		exit(1);101	});102	if !dry_run {103		save_json(Path::new(LOCKFILE), &new_lock);104	}105}106107#[allow(clippy::too_many_lines)]108fn main() {109	tracing_subscriber::fmt().init();110111	rustls::crypto::ring::default_provider()112		.install_default()113		.expect("install rustls crypto provider");114115	let opts = Opts::parse();116117	match opts.command {118		Command::Init => {119			let path = Path::new(MANIFEST);120			if path.exists() {121				warn!("{MANIFEST} already exists");122				exit(1);123			}124			let jf = JsonnetFile {125				version: 1,126				dependencies: Vec::new(),127				legacy_imports: true,128			};129			save_json(path, &jf);130		}131		Command::Install { uris, dry_run } => {132			let mut manifest = load_manifest();133134			for uri in &uris {135				let dep = GitSource::parse(uri).unwrap_or_else(|| {136					eprintln!("failed to parse URI: {uri}");137					exit(1);138				});139				let is_new = !manifest.dependencies.iter().any(|d| {140					std::mem::discriminant(&d.source) == std::mem::discriminant(&dep.source)141						&& d.canonical_name() == dep.canonical_name()142				});143				if is_new {144					manifest.dependencies.push(dep);145				}146			}147148			if !uris.is_empty() {149				save_json(Path::new(MANIFEST), &manifest);150			}151152			let lock = load_lockfile();153			do_install(&manifest, lock.as_ref(), &opts.jsonnetpkg_home, dry_run);154		}155		Command::Update { uris, dry_run } => {156			let mut manifest = load_manifest();157158			if !uris.is_empty() {159				for uri in &uris {160					let dep = GitSource::parse(uri).unwrap_or_else(|| {161						eprintln!("failed to parse URI: {uri}");162						exit(1);163					});164					if let Some(existing) = manifest165						.dependencies166						.iter_mut()167						.find(|d| d.canonical_name() == dep.canonical_name())168					{169						*existing = dep;170					} else {171						manifest.dependencies.push(dep);172					}173				}174				save_json(Path::new(MANIFEST), &manifest);175			}176177			do_install(&manifest, None, &opts.jsonnetpkg_home, dry_run);178		}179		Command::Remove { names, dry_run } => {180			let mut manifest = load_manifest();181182			let matched: Vec<_> = manifest183				.dependencies184				.iter()185				.filter(|dep| {186					names.iter().any(|name| {187						dep.canonical_name() == *name || dep.legacy_link_name() == *name188					})189				})190				.cloned()191				.collect::<Vec<_>>();192193			if matched.is_empty() {194				eprintln!("no matching dependencies found");195				exit(1);196			}197198			for dep in &matched {199				let canonical = dep.canonical_name();200				let dir = opts.jsonnetpkg_home.join(&canonical);201				let legacy = dep.legacy_link_name();202				let link = opts.jsonnetpkg_home.join(&legacy);203				if dry_run {204					info!("would remove: {canonical} ({})", dir.display());205				} else {206					info!("removing: {canonical}");207					if dir.exists() {208						let _ = std::fs::remove_dir_all(&dir);209					}210					if link.symlink_metadata().is_ok() {211						let _ = std::fs::remove_file(&link);212					}213				}214			}215216			if !dry_run {217				manifest.dependencies.retain(|dep| {218					!names.iter().any(|name| {219						dep.canonical_name() == *name || dep.legacy_link_name() == *name220					})221				});222				save_json(Path::new(MANIFEST), &manifest);223				save_json(Path::new(LOCKFILE), &manifest);224			}225		}226	}227}