Repository: casey/just
File: src/function.rs (Rust)

Code before:
fn trim_start(_context: Context, s: &str) -> FunctionResult {
  Ok(s.trim_start().to_owned())
}

fn trim_start_match(_context: Context, s: &str, pat: &str) -> FunctionResult {
  Ok(s.strip_prefix(pat).unwrap_or(s).to_owned())
}

fn trim_start_matches(_context: Context, s: &str, pat: &str) -> FunctionResult {
  Ok(s.trim_start_matches(pat).to_owned())
}

fn uppercamelcase(_context: Context, s: &str) -> FunctionResult {
  Ok(s.to_upper_camel_case())
}

fn uppercase(_context: Context, s: &str) -> FunctionResult {
  Ok(s.to_uppercase())
}

fn uuid(_context: Context) -> FunctionResult {
  Ok(uuid::Uuid::new_v4().to_string())
}

fn which_exec(_context: Context, s: &str) -> FunctionResult {
  let path = which::which(s).unwrap_or_default();
  path.to_str().map(str::to_string).ok_or_else(|| {
    format!(
      "unable to convert which executable path to string: {}",
      path.display()
    )
  })
}

fn without_extension(_context: Context, path: &str) -> FunctionResult {
  let parent = Utf8Path::new(path)
    .parent()
    .ok_or_else(|| format!("Could not extract parent from `{path}`"))?;

  let file_stem = Utf8Path::new(path)
    .file_stem()
    .ok_or_else(|| format!("Could not extract file stem from `{path}`"))?;

  Ok(parent.join(file_stem).to_string())
}

/// Check whether a string processes properly as semver (e.x. "0.1.0")
/// and matches a given semver requirement (e.x. ">=0.1.0")
fn semver_matches(_context: Context, version: &str, requirement: &str) -> FunctionResult {
  Ok(
    requirement

Review comment (suggestion):
This will swallow the error from `which`. But I checked out the `which` crate, and it looks like it will actually swallow I/O errors, which (haha) isn't ideal. I opened an error on the crate: https://github.com/harryfei/which-rs/issues/100

I don't think this is a show-stopper, since at the very worst, we can swap out the implementation later.

Code after:
fn trim_end_matches(_context: Context, s: &str, pat: &str) -> FunctionResult {
  Ok(s.trim_end_matches(pat).to_owned())
}

fn trim_start(_context: Context, s: &str) -> FunctionResult {
  Ok(s.trim_start().to_owned())
}

fn trim_start_match(_context: Context, s: &str, pat: &str) -> FunctionResult {
  Ok(s.strip_prefix(pat).unwrap_or(s).to_owned())
}

fn trim_start_matches(_context: Context, s: &str, pat: &str) -> FunctionResult {
  Ok(s.trim_start_matches(pat).to_owned())
}

fn uppercamelcase(_context: Context, s: &str) -> FunctionResult {
  Ok(s.to_upper_camel_case())
}

fn uppercase(_context: Context, s: &str) -> FunctionResult {
  Ok(s.to_uppercase())
}

fn uuid(_context: Context) -> FunctionResult {
  Ok(uuid::Uuid::new_v4().to_string())
}

fn which(context: Context, s: &str) -> FunctionResult {
  let cmd = Path::new(s);

  let candidates = match cmd.components().count() {
    0 => return Err("empty command".into()),
    1 => {
      // cmd is a regular command
      let path_var = env::var_os("PATH").ok_or("Environment variable `PATH` is not set")?;
      env::split_paths(&path_var)
        .map(|path| path.join(cmd))
        .collect()
    }
    _ => {
      // cmd contains a path separator, treat it as a path
      vec![cmd.into()]
    }
  };

  for mut candidate in candidates {
    if candidate.is_relative() {
      // This candidate is a relative path, either because the user invoked `which("rel/path")`,
      // or because there was a relative path in `PATH`. Resolve it to an absolute path,
      // relative to the working directory of the just invocation.