{"name":"ext/env.nu","source":"// stdlib/ext/env.nu — process environment + argv + cwd\n//\n// CLI tooling primitives. Each String (and env_args_list's Vec) returned\n// is the caller's own, dropped at scope exit.\n//\n//   ( env_args_count )       → i              like argc\n//   ( env_arg i )            → String         argv[i]; empty when out of range\n//   ( env_args_list )        → ( Vec String ) all argv copied into owned Strings\n//   ( env_get name )         → ? String       None when unset\n//   ( env_var_or name def )  → String         convenience: get-or-default\n//   ( env_home_dir )         → String         the user's home directory; \"\"\n//                                             when the platform names none\n//   ( env_set name value )   → ! v IoErr      Other on failure\n//   ( env_unset name )       → ! v IoErr\n//   ( env_cwd )              → ! String IoErr current working directory\n//   ( env_chdir path )       → ! v IoErr      change cwd\n//   ( env_exit code )        → v              terminate process\n\n$ `stdlib/core/string.nu`\n$ `stdlib/core/vec.nu`\n$ `stdlib/core/errors.nu`\n$ `stdlib/core/posix.nu`  // posix_const + nurl_errno_get\n\n// ── libc FFI surface for env / cwd / chdir ──────────────────────────\n//\n// PURIFY §13 batch 1 (2026-05-24): the five `nurl_env_*` / `nurl_cwd`\n// / `nurl_chdir` thunks moved out of `stdlib/runtime.c`. Both libc\n// flavours we target (Linux/macOS primary libc, mingw-w64 libmingwex)\n// expose the POSIX names `getenv` / `setenv` / `unsetenv` / `getcwd` /\n// `chdir` with the same ABI — no platform gating needed.\n//\n// `getenv` returns a BORROWED pointer into the environment block;\n// callers must NOT free it. `getcwd(buf, cap)` writes into a\n// caller-owned buffer and returns the buffer on success or NULL with\n// errno = ERANGE when the buffer is too small (the loop below doubles\n// the buffer and retries up to a 1 MB ceiling).\n\n// `getenv` is declared globally by the compiler (it is used by nurlc's own\n// import-path resolution), so — like fopen/access — it must NOT be\n// re-declared here, or the program would emit two `declare @getenv` lines.\n\n& `c` @ setenv s name s value i32 overwrite → i32\n\n& `c` @ unsetenv s name → i32\n\n& `c` @ getcwd *u buf i size → s\n\n& `c` @ chdir s path → i32\n\n@ env_args_count → i {\n    ^ ( nurl_argv_count )\n}\n\n@ env_arg i idx → String {\n    // The raw argv copy is auto-dropped after string_from copies it.\n    : s raw ( nurl_argv_get idx )\n    : String out ( string_from raw )\n    ^ out\n}\n\n@ env_args_list → ( Vec String ) {\n    : ( Vec String ) out ( vec_new [String] )\n    : i n ( nurl_argv_count )\n    : ~ i i 0\n    ~ < i n {\n        : s raw ( nurl_argv_get i )\n        ( vec_push [String] out ( string_from raw ) )\n        = i + i 1\n    }\n    ^ out\n}\n\n// ── Enumerating the environment ─────────────────────────────────────\n//\n// `env_get` answers about a name you already know. Listing what is\n// actually set — which `env` / `printenv` / a shell's `export -p` needs,\n// and which a program logging its own configuration wants — needs the\n// vector itself, and that is a C *variable* (`environ`, `_environ` on\n// Windows), not something NURL can bind through FFI. The runtime\n// enumerates it: `nurl_env_count` / `nurl_env_at`.\n\n& `c` @ nurl_env_count → i\n\n& `c` @ nurl_env_at i idx → s\n\n@ env_count → i { ^ ( nurl_env_count ) }\n\n// The `NAME=value` entry at `idx`, as an owned String; the empty string\n// past the end.\n@ env_entry i idx → String {\n    : s raw ( nurl_env_at idx )\n    ? == # i raw 0 { ^ ( string_new ) } {}\n    ^ ( string_from raw )\n}\n\n// Every `NAME=value` currently set, in the process's own order. The\n// caller owns the Vec and every String in it (vec_free_with).\n@ env_list → ( Vec String ) {\n    : ( Vec String ) out ( vec_new [String] )\n    : i n ( nurl_env_count )\n    : ~ i i 0\n    ~ < i n {\n        : s raw ( nurl_env_at i )\n        ? != # i raw 0 { ( vec_push [String] out ( string_from raw ) ) } {}\n        = i + i 1\n    }\n    ^ out\n}\n\n@ env_get s name → ?String {\n    ? == # i name 0 { ^ @ ?String { F # String 0 } } {}\n    // getenv returns a libc-owned pointer; do not free. `string_from`\n    // copies the bytes into an owned String so the result outlives\n    // any subsequent setenv that might invalidate the borrow.\n    : s raw ( getenv name )\n    ? == # i raw 0 { ^ @ ?String { F # String 0 } } {}\n    : String out ( string_from raw )\n    ^ @ ?String { T out }\n}\n\n@ env_var_or s name s default → String {\n    : ?String got ( env_get name )\n    ^ ?? got {\n        T s → s\n        F → ( string_from default )\n    }\n}\n\n// A variable's value, or \"\" when it is unset or empty.\n@ __env_nonempty s name → String {\n    ^ ?? ( env_get name ) {\n        T v → v\n        F → ( string_new )\n    }\n}\n\n// The user's home directory — the one place in the stdlib that answers\n// \"where is ~\". POSIX: $HOME. Windows: %USERPROFILE%, else %HOMEDRIVE% +\n// %HOMEPATH% — the profile Windows itself and its tools use (Go's\n// os.UserHomeDir, Rust's home_dir). HOME is NOT read on Windows: it is\n// unset in PowerShell and cmd and set only by Unix-flavoured shells, so a\n// program that read it kept its files in a different place depending on\n// the shell it was started from — or, with a \".\" fallback, in whatever\n// directory it happened to run in (nurlpkg login once wrote a registry\n// token into a checkout that way).\n//\n// \"\" when the platform names no home. There is deliberately no relative\n// fallback: a caller that would store something under the home must\n// refuse, not write into the working directory.\n@ env_home_dir → String {\n    // The target's path-list separator is ';' exactly on Windows — a\n    // property of the build, not of the environment, so it cannot be\n    // spoofed by setting a variable.\n    ? != ( posix_const `PATH_LIST_SEPARATOR` ) 59 { ^ ( __env_nonempty `HOME` ) } {}\n    : String prof ( __env_nonempty `USERPROFILE` )\n    ? > ( string_len prof ) 0 { ^ prof } {}\n    : String drive ( __env_nonempty `HOMEDRIVE` )\n    : String hpath ( __env_nonempty `HOMEPATH` )\n    ? | == ( string_len drive ) 0 == ( string_len hpath ) 0 { ^ ( string_new ) } {}\n    ( string_push_str drive ( string_data hpath ) )\n    ^ drive\n}\n\n@ env_set s name s value → !v IoErr {\n    ? | == # i name 0 == # i value 0\n    { ^ @ !v IoErr { F @ IoErr { Other } } } {}\n    : i32 rc ( setenv name value # i32 1 )\n    ? == rc # i32 0 { ^ @ !v IoErr { T 0 } } {}\n    ^ @ !v IoErr { F @ IoErr { Other } }\n}\n\n@ env_unset s name → !v IoErr {\n    ? == # i name 0 { ^ @ !v IoErr { F @ IoErr { Other } } } {}\n    : i32 rc ( unsetenv name )\n    ? == rc # i32 0 { ^ @ !v IoErr { T 0 } } {}\n    ^ @ !v IoErr { F @ IoErr { Other } }\n}\n\n// Owned-cwd loop: `getcwd(buf, cap)` returns NULL with errno=ERANGE\n// when the buffer is too small; we double and retry until we land on\n// a fit or hit the 1 MB ceiling (which would mean a pathologically\n// long cwd — well past any real filesystem's PATH_MAX). Each attempt's\n// buffer is a `( Vec u )` the scope drops, so no path frees anything by\n// hand — the raw `nurl_alloc` / `nurl_free` loop this replaced had a\n// free on one branch and a reuse on another.\n@ env_cwd → !String IoErr {\n    : ~ i cap 256\n    ~ <= cap 1048576 {\n        : ( Vec u ) buf ( vec_zeroed [u] cap )\n        : s rc ( getcwd # s ( vec_data [u] buf ) cap )\n        ? != # i rc 0 { ^ @ !String IoErr { T ( string_from rc ) } } {}\n        ? != ( nurl_errno_get ) ( posix_const `ERANGE` ) { ^ @ !String IoErr { F @ IoErr { Other } } } {}\n        = cap * cap 2\n    }\n    ^ @ !String IoErr { F @ IoErr { Other } }\n}\n\n@ env_chdir s path → !v IoErr {\n    ? == # i path 0 { ^ @ !v IoErr { F @ IoErr { Other } } } {}\n    : i32 rc ( chdir path )\n    ? == rc # i32 0 { ^ @ !v IoErr { T 0 } } {}\n    : i k ( errno_kind )\n    ? == k 0 { ^ @ !v IoErr { F @ IoErr { NotFound } } } {}\n    ? == k 1 { ^ @ !v IoErr { F @ IoErr { PermissionDenied } } } {}\n    ^ @ !v IoErr { F @ IoErr { Other } }\n}\n\n@ env_exit i code → v {\n    ( nurl_exit code )\n}\n","bytes":8166}