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

Code before:
  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 {
  use is_executable::IsExecutable;

  let cmd = PathBuf::from(s);

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

  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.
      let cwd = context
        .evaluator
        .context
        .search
        .justfile
        .parent()
        .ok_or_else(|| {
          format!(
            "Could not resolve absolute path from `{}` relative to the justfile directory. Justfile `{}` had no parent.",
            candidate.display(),
            context.evaluator.context.search.justfile.display()
          )
        })?;

Review comment (suggestion):
Let's handle the empty case up-front:

```rust
if s.is_empty() {
  return Err("empty command".into());
}
```

Code after:

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.
      candidate = context
        .evaluator
        .context