harn-stdlib 0.10.48

Embedded Harn standard library source catalog
Documentation
// std/os - environment and host information helpers.
import { command_run } from "std/command"

/**
 * Return a compact host/runtime information dict for diagnostics.
 *
 * @effects: [host]
 * @errors: []
 */
pub fn os_info(harness: Harness) -> dict {
  return {
    platform: harness.system.platform().os,
    arch: harness.system.platform().arch,
    cwd: harness.fs.cwd(),
    home_dir: harness.fs.home_dir(),
    temp_dir: harness.fs.temp_dir(),
    username: harness.system.identity().username,
    hostname: harness.system.identity().hostname,
    pid: harness.system.identity().pid,
    runtime_paths: harness.fs.runtime_paths(),
    stdin_tty: harness.term.is_tty("stdin"),
    stdout_tty: harness.term.is_tty("stdout"),
    stderr_tty: harness.term.is_tty("stderr"),
  }
}

/**
 * Read an environment variable as a bool, accepting common CLI spellings.
 *
 * @effects: [env]
 * @errors: []
 */
pub fn env_bool(env: HarnessEnv, name: string, fallback: bool = false) -> bool {
  const value = env.get(name)
  if value == nil {
    return fallback
  }
  const normalized = lowercase(trim(value))
  if normalized == "1" || normalized == "true" || normalized == "yes" || normalized == "y"
    || normalized
    == "on" {
    return true
  }
  if normalized == "0" || normalized == "false" || normalized == "no" || normalized == "n"
    || normalized
    == "off" {
    return false
  }
  return fallback
}

/**
 * Read an environment variable as an int, returning fallback on absence or parse failure.
 *
 * @effects: [env]
 * @errors: []
 */
pub fn env_int(env: HarnessEnv, name: string, fallback = nil) {
  const value = env.get(name)
  if value == nil {
    return fallback
  }
  return to_int(value) ?? fallback
}

/**
 * Split a path/list-style environment variable, dropping empty entries.
 *
 * @effects: [env, host]
 * @errors: []
 */
pub fn env_list(
  env: HarnessEnv,
  system: HarnessSystem,
  name: string,
  separator = nil,
) -> list<string> {
  const value = env.get(name)
  if value == nil || value == "" {
    return []
  }
  let sep = separator
  if sep == nil {
    sep = if system.platform().os == "windows" {
      ";"
    } else {
      ":"
    }
  }
  let out = []
  for item in split(value, sep) {
    const trimmed = trim(item)
    if trimmed != "" {
      out = out.appending(trimmed)
    }
  }
  return out
}

/**
 * Read a required environment variable or throw a clear error.
 *
 * @effects: [env]
 * @errors: []
 */
pub fn require_env(env: HarnessEnv, name: string) -> string {
  const value = env.get(name)
  if value == nil || value == "" {
    throw "required environment variable is not set: " + name
  }
  return value
}

/**
 * Resolve an executable on PATH, returning nil when not found.
 *
 * @effects: [process]
 * @errors: []
 */
pub fn which(tools: HarnessTools, system: HarnessSystem, binary: string) {
  const executable = trim(binary ?? "")
  if executable == "" {
    return nil
  }
  const argv = if system.platform().os == "windows" {
    ["where", executable]
  } else {
    ["which", executable]
  }
  const result = try {
    command_run(tools, {argv: argv}, {capture: {max_inline_bytes: 4096}})
  }
  if !is_ok(result) {
    return nil
  }
  const out = unwrap(result)
  if !(out?.success ?? false) {
    return nil
  }
  const lines = split(trim(out?.stdout ?? ""), "\n").filter({ line -> trim(line) != "" })
  if len(lines) == 0 {
    return nil
  }
  return trim(lines[0])
}

/**
 * Return whether an executable is visible on PATH.
 *
 * @effects: [process]
 * @errors: []
 */
pub fn command_exists(tools: HarnessTools, system: HarnessSystem, binary: string) -> bool {
  return which(tools, system, binary) != nil
}