/src/main.rs
https://github.com/Manishearth/oreutils · Rust · 187 lines · 171 code · 16 blank · 0 comment · 18 complexity · 808b2007e8f47855459e71e6bf48f101 MD5 · raw file
- use regex::Regex;
- use semver::Version;
- use std::process::Command;
- use structopt::StructOpt;
- mod fetch;
- #[derive(Debug, StructOpt)]
- #[structopt(
- name = "oreutils",
- about = "Installation manager for various CLI utilities reimagined in Rust",
- rename_all = "kebab-case"
- )]
- enum Opt {
- #[structopt(about = "Install the basic utilities: ripgrep, exa, bat, fd")]
- Install {
- #[structopt(help = "Specific tool to install. Omit to install all.")]
- tool: Option<String>,
- },
- #[structopt(
- about = "Upgrade any installed tools. Use `oreutils install` to install missing ones."
- )]
- Upgrade {
- #[structopt(help = "Specific tool to upgrade. Omit to upgrade all.")]
- tool: Option<String>,
- },
- #[structopt(about = "Uninstall all oreutils tools")]
- Uninstall,
- }
- #[derive(Clone, Copy)]
- struct Tool {
- name: &'static str,
- package: &'static str,
- cli: &'static str,
- }
- impl Tool {
- fn equals(&self, other: &str) -> bool {
- self.name == other || self.package == other || self.cli == other
- }
- }
- const TOOLS: &[Tool] = &[
- Tool {
- name: "ripgrep",
- package: "ripgrep",
- cli: "rg",
- },
- Tool {
- name: "exa",
- package: "exa",
- cli: "exa",
- },
- Tool {
- name: "bat",
- package: "bat",
- cli: "bat",
- },
- Tool {
- name: "fd",
- package: "fd-find",
- cli: "fd",
- },
- Tool {
- name: "sd",
- package: "sd",
- cli: "sd",
- }
- ];
- fn main() {
- let opt = Opt::from_args();
- match opt {
- Opt::Install {tool} => install(tool),
- Opt::Upgrade {tool} => upgrade(tool),
- Opt::Uninstall => uninstall(),
- }
- }
- fn install(tool: Option<String>) {
- for_each_tool(tool, |tool| {
- let exists = which::which(tool.cli);
- if exists.is_ok() {
- println!(
- "Tool {:?} already installed, use `oreutils upgrade` to upgrade",
- tool.name
- );
- return;
- }
- cargo_install(tool.package, false);
- });
- }
- fn for_each_tool<F: Fn(&Tool)>(tool: Option<String>, f: F) {
- if let Some(tool) = tool {
- for tool in TOOLS.iter().filter(|x| x.equals(&tool)) {
- f(tool)
- }
- } else {
- for tool in TOOLS.iter() {
- f(tool)
- }
- };
- }
- fn upgrade(tool: Option<String>) {
- for_each_tool(tool, |tool| {
- let res = upgrade_tool(tool);
- match res {
- Ok(vers) => println!("Tool {} updated to version {}", tool.name, vers),
- Err(Error::NotFound) => println!(
- "Tool {} not installed, use `oreutils install` to install",
- tool.name
- ),
- Err(Error::VersionBroken(None)) => {
- println!("`{} --version` didn't produce expected output", tool.cli)
- }
- Err(Error::VersionBroken(Some(v))) => println!(
- "`{} --version` didn't produce expected output: could not parse {}",
- tool.cli, v
- ),
- Err(Error::AlreadyUpdated(v)) => println!("Tool {} is already up to date at version {}", tool.name, v),
- Err(Error::CratesFetchError(e)) => println!(
- "Failed to fetch information for crate {} from crates.io: {}",
- tool.name, e
- ),
- }
- });
- }
- enum Error {
- NotFound,
- VersionBroken(Option<String>),
- CratesFetchError(fetch::FetchError),
- AlreadyUpdated(Version)
- }
- fn upgrade_tool(tool: &Tool) -> Result<Version, Error> {
- let output = Command::new(tool.cli)
- .args(&["--version"])
- .output()
- .map_err(|_| Error::NotFound)?;
- let output = String::from_utf8(output.stdout).map_err(|_| Error::VersionBroken(None))?;
- let output = output.lines().next().ok_or(Error::VersionBroken(None))?;
- let re = Regex::new(r"\d+\.\d+\.\d+").unwrap();
- let vers = re
- .find(output)
- .ok_or(Error::VersionBroken(Some(output.into())))?;
- let vers = vers.as_str();
- let vers = Version::parse(vers).map_err(|_| Error::VersionBroken(Some(vers.into())))?;
- let latest_vers =
- fetch::get_latest_version(tool.package).map_err(|e| Error::CratesFetchError(e))?;
- if vers < latest_vers {
- cargo_install(tool.package, true);
- Ok(latest_vers)
- } else {
- Err(Error::AlreadyUpdated(vers))
- }
- }
- fn uninstall() {
- unimplemented!()
- }
- fn cargo_install(pkg: &str, force: bool) {
- let mut cmd = Command::new("cargo");
- if force {
- cmd.args(&["install", "-f", pkg]);
- } else {
- cmd.args(&["install", pkg]);
- }
- cmd.env("RUSTFLAGS", "-Ctarget-cpu=native");
- let res = cmd.spawn();
- match res {
- Ok(mut child) => {
- let status = child.wait().expect("Command wasn't running");
- if !status.success() {
- eprintln!("Installing {:?} failed", pkg);
- }
- }
- Err(_) => eprintln!("Cargo didn't start"),
- }
- }