gimmetool 0.1.3

A multi-repo manager that lets you jump between projects, pin favorites, clean stale branches, and alias repos — all from one CLI
use anyhow::{Context, Result};
use std::path::{Component, Path, PathBuf};

pub fn normalize(raw: &str) -> Result<String> {
  let expanded = expand_tilde(raw)?;
  let expanded = expand_env(&expanded);
  Ok(clean(&expanded))
}

fn expand_tilde(path: &str) -> Result<String> {
  if !path.starts_with('~') {
    return Ok(path.to_string());
  }
  let home = dirs::home_dir().context("Could not determine home directory")?;
  Ok(format!("{}{}", home.display(), &path[1..]))
}

fn expand_env(input: &str) -> String {
  let mut result = input.to_string();
  while let Some(start) = result.find("${") {
    let rest = &result[start + 2..];
    match rest.find('}') {
      Some(end) => {
        let key = &rest[..end];
        let value = std::env::var(key).unwrap_or_default();
        result = format!("{}{}{}", &result[..start], value, &rest[end + 1..]);
      }
      None => break,
    }
  }
  result
}

fn clean(path: &str) -> String {
  let mut components = Vec::new();
  for component in Path::new(path).components() {
    match component {
      Component::ParentDir => {
        components.pop();
      }
      Component::CurDir => {}
      c => components.push(c),
    }
  }
  let result: PathBuf = components.into_iter().collect();
  result.to_string_lossy().to_string()
}