mirror of
https://github.com/RGBCube/nu_scripts
synced 2025-07-30 13:47:46 +00:00
Fix(conda.nu): Update Mamba/Conda info parsing and fix syntax errors (#1104)
This PR updates the `modules/virtual_environments/nu_conda_2/conda.nu` script to address several issues encountered with newer versions of Mamba/Conda and Nushell. **Problem:** The existing script failed when used with recent Mamba versions (e.g., Mamba 2.x) due to changes in the JSON output format of `mamba info --json`. Specifically: * The key for environment directories is now `"envs directories"` instead of `"envs_dirs"`. * The key for the base environment path is now `"base environment"` instead of `"root_prefix"`. * The `mamba info --envs --json` command returns minimal information, requiring separate calls to `mamba info --json` to get necessary details like `envs_dirs` and `root_prefix`. Additionally, several Nushell syntax errors were present: * Incorrect syntax for assigning the result of a multi-branch `if/else` expression to a variable using `let`. * Incorrect usage of `run-external`, attempting to pass flags like `--json` directly instead of as separate string arguments. * The `--no-banner` flag is not supported by `mamba info`. These issues resulted in errors like `Cannot find column 'envs_dirs'`, `Could not find environment named '...'`, `keyword_missing_arg`, and `unknown_flag`. **Solution:** This PR implements the following fixes: 1. **Updated `load-conda-info-env`:** * Detects Mamba/Conda/Micromamba correctly. * For Mamba, makes separate calls to `mamba info --json` and `mamba info --envs --json`. * Explicitly extracts `envs_dirs` and `root_prefix` using the correct key names (`"envs directories"`, `"base environment"`) observed in Mamba 2.x output. * For Conda, assumes `conda info --json` provides all necessary keys (`envs_dirs`, `root_prefix`, `envs`). * Includes basic logic for Micromamba (parsing text output). * Constructs the `$env.CONDA_INFO` record reliably with the required keys. 2. **Corrected Nushell Syntax:** * Fixed the `let cmd_base = ...` assignment by wrapping the `if/else` expression in parentheses `()`. * Fixed all `run-external` calls to pass the command and arguments as separate strings (e.g., `run-external "mamba" "info" "--json"`). 3. **Removed Unsupported Flag:** Removed `--no-banner` from `mamba info` calls. 4. **Improved Error Handling:** Added checks in `activate` to ensure `$env.CONDA_INFO` was loaded successfully. Changed `error make` to `print --stderr` and `return null` in `check-if-env-exists` for potentially smoother failure modes. 5. **Minor Improvements:** Cleaned up PATH manipulation, improved the completer function, added comments. **Testing:** This version has been tested successfully with: * Mamba 2.1.0 * Nushell 0.103.0 * Activating environments by name (`activate myenv`) and by full path (`activate /path/to/myenv`). * Deactivating environments (`deactivate`). It should also work correctly with standard Conda installations. Micromamba support is based on the original script's logic and may require further testing. Fixes issues like those encountered during the debugging session leading to this PR.
This commit is contained in:
parent
67e74c5657
commit
9560df9370
1 changed files with 224 additions and 67 deletions
|
@ -1,23 +1,97 @@
|
|||
# Nushell Conda/Mamba/Micromamba Activation Script
|
||||
#
|
||||
# Based on nu_conda_2 from nushell/nu_scripts, with fixes for:
|
||||
# - Mamba JSON output changes (key names like "envs directories", "base environment")
|
||||
# - Nushell syntax errors (let assignment, run-external usage)
|
||||
# - Tested with Mamba 2.1.0 / Conda (implicitly via Mamba) / Nushell 0.103.0
|
||||
|
||||
# Loads conda info once per session and caches in $env.CONDA_INFO
|
||||
def --env load-conda-info-env [] {
|
||||
# Check only once per session if CONDA_INFO is already loaded
|
||||
if (not (has-env CONDA_INFO)) {
|
||||
export-env {
|
||||
$env.CONDA_INFO = (
|
||||
# Determine which command to use (prioritize micromamba > mamba > conda)
|
||||
# Corrected syntax: Wrap the entire if/else expression in parentheses for assignment
|
||||
let cmd_base = (
|
||||
if not (which micromamba | is-empty) {
|
||||
mut info = micromamba env list --json | from json
|
||||
let extra_info = micromamba info --json | from json
|
||||
$info.envs_dirs = $extra_info."envs directories"
|
||||
$info.root_prefix = $extra_info."base environment"
|
||||
$info
|
||||
} else if not (which mamba | is-empty) {
|
||||
(mamba info --envs --json --no-banner | from json)
|
||||
} else if not (which conda | is-empty) {
|
||||
(conda info --envs --json | from json)
|
||||
"micromamba"
|
||||
} else {
|
||||
null
|
||||
if not (which mamba | is-empty) {
|
||||
"mamba"
|
||||
} else {
|
||||
if not (which conda | is-empty) {
|
||||
"conda"
|
||||
} else {
|
||||
null # No command found
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
# If a command was found, try to load info
|
||||
$env.CONDA_INFO = if ($cmd_base == "mamba") {
|
||||
try {
|
||||
# Mamba requires separate calls as `info --envs` is minimal
|
||||
# Corrected syntax: Pass arguments as separate strings to run-external
|
||||
let mamba_info = (run-external $cmd_base "info" "--json" | from json)
|
||||
let mamba_envs = (run-external $cmd_base "info" "--envs" "--json" | from json | get envs)
|
||||
|
||||
# Explicitly extract keys using names observed in Mamba 2.x output
|
||||
let root_prefix = ($mamba_info | get "base environment")
|
||||
let envs_dirs = ($mamba_info | get "envs directories")
|
||||
|
||||
# Construct the final record cleanly
|
||||
{
|
||||
root_prefix: $root_prefix,
|
||||
envs_dirs: $envs_dirs,
|
||||
envs: $mamba_envs
|
||||
}
|
||||
|
||||
} catch { |err|
|
||||
print --stderr $"WARN: Failed to get info from 'mamba': ($err)"
|
||||
null # Indicate failure
|
||||
}
|
||||
} else if ($cmd_base == "conda") {
|
||||
# Conda 'info --json' usually contains all necessary info
|
||||
try {
|
||||
# Corrected syntax: Pass arguments as separate strings to run-external
|
||||
let conda_info_all = (run-external $cmd_base "info" "--json" | from json)
|
||||
# Extract required fields, assuming standard conda output keys
|
||||
{
|
||||
root_prefix: ($conda_info_all | get root_prefix),
|
||||
envs_dirs: ($conda_info_all | get envs_dirs),
|
||||
envs: ($conda_info_all | get envs)
|
||||
}
|
||||
} catch { |err|
|
||||
print --stderr $"WARN: Failed to get info from 'conda': ($err)"
|
||||
null # Indicate failure
|
||||
}
|
||||
} else if ($cmd_base == "micromamba") {
|
||||
# Micromamba requires parsing text output and separate calls
|
||||
try {
|
||||
# Corrected syntax: Pass arguments as separate strings to run-external
|
||||
let mi_info_lines = (run-external $cmd_base "info" | lines)
|
||||
let base = ($mi_info_lines | where $it =~ "Base Environment" | parse "{key}: {value}" | get value | str trim | first)
|
||||
let dirs_line = ($mi_info_lines | where $it =~ "Envs Directories" | first)
|
||||
let dirs = if ($dirs_line | is-empty) { [] } else { $dirs_line | parse "{key}: {value}" | get value | str trim | split row " " }
|
||||
|
||||
# Corrected syntax: Pass arguments as separate strings to run-external
|
||||
let env_list = (run-external $cmd_base "env" "list" "--json" | from json | get envs)
|
||||
|
||||
# Construct record using consistent key names where possible
|
||||
{ root_prefix: $base, envs_dirs: $dirs, envs: $env_list }
|
||||
|
||||
} catch { |err|
|
||||
print --stderr $"WARN: Failed to get info from 'micromamba': ($err)"
|
||||
null
|
||||
}
|
||||
} else {
|
||||
# No command found
|
||||
print --stderr "WARN: No conda, mamba, or micromamba command found."
|
||||
null
|
||||
}
|
||||
} # End export-env
|
||||
} # End if not (has-env CONDA_INFO)
|
||||
}
|
||||
|
||||
# Activate conda environment
|
||||
|
@ -27,20 +101,38 @@ export def --env activate [
|
|||
load-conda-info-env
|
||||
let conda_info = $env.CONDA_INFO
|
||||
if ($conda_info == null) {
|
||||
print "Error: No Conda, Mamba or Micromamba install could be found in the environment. Please install either and add them to the environment. See: https://www.nushell.sh/book/environment.html for more info"
|
||||
print --stderr "Error: Conda/Mamba/Micromamba info could not be loaded. Cannot activate."
|
||||
return
|
||||
}
|
||||
# Check if essential keys were populated correctly
|
||||
if ($conda_info.envs_dirs == null) or ($conda_info.root_prefix == null) or ($conda_info.envs == null) {
|
||||
print --stderr "Error: Failed to load essential Conda/Mamba/Micromamba info (envs_dirs, root_prefix, envs). Cannot activate."
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
let env_dir = if $env_name != "base" {
|
||||
if ($env_name | path exists) and (($env_name | path expand) in $conda_info.envs ) {
|
||||
($env_name | path expand)
|
||||
# Check if env_name is already a valid path to a known environment
|
||||
let expanded_env_name = ($env_name | path expand)
|
||||
if ($env_name | path exists) and ($expanded_env_name in $conda_info.envs ) {
|
||||
$expanded_env_name
|
||||
} else {
|
||||
((check-if-env-exists $env_name $conda_info) | into string)
|
||||
# Otherwise, try to find the environment by name in the known envs_dirs
|
||||
(check-if-env-exists $env_name $conda_info)
|
||||
}
|
||||
} else {
|
||||
$conda_info.root_prefix
|
||||
}
|
||||
|
||||
# If check-if-env-exists returned an error (implicitly null/empty), stop
|
||||
if ($env_dir | is-empty) {
|
||||
# Error message was already printed by check-if-env-exists
|
||||
return
|
||||
}
|
||||
|
||||
# Ensure env_dir is a string for path operations
|
||||
let env_dir = ($env_dir | into string)
|
||||
|
||||
let old_path = (system-path | str join (char esep))
|
||||
|
||||
let new_path = if (windows?) {
|
||||
|
@ -51,14 +143,19 @@ export def --env activate [
|
|||
|
||||
let virtual_prompt = $'[($env_name)] '
|
||||
|
||||
let new_env = ({
|
||||
CONDA_DEFAULT_ENV: $env_name
|
||||
CONDA_PREFIX: $env_dir
|
||||
CONDA_PROMPT_MODIFIER: $virtual_prompt
|
||||
CONDA_SHLVL: "1"
|
||||
# Base environment variables to set
|
||||
let new_env_base = {
|
||||
CONDA_DEFAULT_ENV: $env_name,
|
||||
CONDA_PREFIX: $env_dir,
|
||||
CONDA_PROMPT_MODIFIER: $virtual_prompt,
|
||||
CONDA_SHLVL: ((($env.CONDA_SHLVL? | default 0) | into int) + 1 | into string), # Increment shell level
|
||||
CONDA_OLD_PATH: $old_path
|
||||
} | merge $new_path)
|
||||
}
|
||||
|
||||
# Merge the new path (PATH or Path)
|
||||
let new_env = ($new_env_base | merge $new_path)
|
||||
|
||||
# Handle prompt modification only if CONDA_NO_PROMPT is not set
|
||||
let new_env = if not (has-env CONDA_NO_PROMPT) {
|
||||
let old_prompt_command = if (has-env CONDA_OLD_PROMPT_COMMAND) {
|
||||
$env.CONDA_OLD_PROMPT_COMMAND
|
||||
|
@ -66,117 +163,177 @@ export def --env activate [
|
|||
if (has-env 'PROMPT_COMMAND') {
|
||||
$env.PROMPT_COMMAND
|
||||
} else {
|
||||
''
|
||||
null # Use null if no prompt command exists
|
||||
}
|
||||
}
|
||||
|
||||
# Store the old prompt command only if it wasn't null
|
||||
let env_update_prompt = if ($old_prompt_command != null) {
|
||||
{ CONDA_OLD_PROMPT_COMMAND: $old_prompt_command }
|
||||
} else {
|
||||
{} # Empty record if no old prompt to save
|
||||
}
|
||||
|
||||
let new_prompt = if (has-env 'PROMPT_COMMAND') {
|
||||
# Create the new prompt command
|
||||
let new_prompt = if ($old_prompt_command != null) {
|
||||
# Check if the old command is a closure
|
||||
if 'closure' in ($old_prompt_command | describe) {
|
||||
{|| $'($virtual_prompt)(do $old_prompt_command)' }
|
||||
} else {
|
||||
# Assume it's a string or something else printable
|
||||
{|| $'($virtual_prompt)($old_prompt_command)' }
|
||||
}
|
||||
} else {
|
||||
# If no old prompt, just use the virtual prompt
|
||||
{|| $'($virtual_prompt)' }
|
||||
}
|
||||
|
||||
$new_env | merge {
|
||||
CONDA_OLD_PROMPT_COMMAND: $old_prompt_command
|
||||
PROMPT_COMMAND: $new_prompt
|
||||
}
|
||||
# Merge prompt updates into the environment changes
|
||||
$new_env | merge $env_update_prompt | merge { PROMPT_COMMAND: $new_prompt }
|
||||
|
||||
} else {
|
||||
$new_env | merge { CONDA_OLD_PROMPT_COMMAND: null }
|
||||
# If prompt is disabled, ensure old prompt command is cleared if it exists
|
||||
$new_env | merge { CONDA_OLD_PROMPT_COMMAND: null }
|
||||
}
|
||||
|
||||
|
||||
# Load the calculated environment variables
|
||||
load-env $new_env
|
||||
}
|
||||
|
||||
# Deactivate currently active conda environment
|
||||
export def --env deactivate [] {
|
||||
let path_name = if "PATH" in $env { "PATH" } else { "Path" }
|
||||
$env.$path_name = $env.CONDA_OLD_PATH
|
||||
# Check if CONDA_OLD_PATH exists (indicates an environment is active)
|
||||
if not (has-env CONDA_OLD_PATH) {
|
||||
print --stderr "WARN: No active conda environment detected to deactivate."
|
||||
return
|
||||
}
|
||||
|
||||
# Restore the old PATH
|
||||
let path_name = if "PATH" in $env { "PATH" } else { "Path" }
|
||||
load-env { ($path_name): $env.CONDA_OLD_PATH }
|
||||
|
||||
# Decrement or remove CONDA_SHLVL
|
||||
let current_shlvl = (($env.CONDA_SHLVL? | default 1) | into int)
|
||||
if $current_shlvl <= 1 {
|
||||
hide-env CONDA_SHLVL
|
||||
} else {
|
||||
load-env { CONDA_SHLVL: ($current_shlvl - 1 | into string) }
|
||||
}
|
||||
|
||||
# Hide other conda variables
|
||||
hide-env CONDA_PROMPT_MODIFIER
|
||||
hide-env CONDA_PREFIX
|
||||
hide-env CONDA_SHLVL
|
||||
hide-env CONDA_DEFAULT_ENV
|
||||
hide-env CONDA_OLD_PATH
|
||||
|
||||
$env.PROMPT_COMMAND = (
|
||||
# Restore the old prompt command if it was saved
|
||||
if (has-env CONDA_OLD_PROMPT_COMMAND) {
|
||||
if $env.CONDA_OLD_PROMPT_COMMAND == null {
|
||||
$env.PROMPT_COMMAND
|
||||
# If saved value is null, maybe hide PROMPT_COMMAND? Or leave as is?
|
||||
# Let's assume leaving it is safer if it was null.
|
||||
# hide-env PROMPT_COMMAND # Optional: uncomment to fully remove prompt if old was null
|
||||
} else {
|
||||
$env.CONDA_OLD_PROMPT_COMMAND
|
||||
load-env { PROMPT_COMMAND: $env.CONDA_OLD_PROMPT_COMMAND }
|
||||
}
|
||||
)
|
||||
hide-env CONDA_OLD_PROMPT_COMMAND
|
||||
hide-env CONDA_OLD_PROMPT_COMMAND # Hide the saved value itself
|
||||
}
|
||||
}
|
||||
|
||||
# Finds the full path for a given environment name
|
||||
def check-if-env-exists [ env_name: string, conda_info: record ] {
|
||||
let env_dirs = (
|
||||
$conda_info.envs_dirs |
|
||||
each { || path join $env_name }
|
||||
# Get the list of base directories where envs might live
|
||||
# Ensure it's a list, default to empty list if key is missing or null
|
||||
let base_env_dirs = ($conda_info | get envs_dirs | default [])
|
||||
|
||||
# Construct potential full paths for the named environment in each base directory
|
||||
let potential_env_paths = (
|
||||
$base_env_dirs | each { |dir| $dir | path join $env_name }
|
||||
)
|
||||
|
||||
let en = ($env_dirs | each {|en| $conda_info.envs | where $it == $en } | where ($it | length) == 1 | flatten)
|
||||
if ($en | length) > 1 {
|
||||
error make --unspanned {msg: $"You have environments in multiple locations: ($en)"}
|
||||
# Ensure $conda_info.envs is also a list, default to empty
|
||||
let known_envs = ($conda_info | get envs | default [])
|
||||
|
||||
# Find which of these potential paths actually exist in the list of known environments
|
||||
let found_envs = (
|
||||
$potential_env_paths | where {|potential_path| $potential_path in $known_envs }
|
||||
)
|
||||
|
||||
# --- Error Checking ---
|
||||
if ($found_envs | is-empty) {
|
||||
# Use print --stderr instead of error make to avoid stopping script execution in some contexts
|
||||
print --stderr $"Error: Could not find environment named '($env_name)' in any of the search directories: ($base_env_dirs | str join ', ')"
|
||||
return null # Return null to indicate failure
|
||||
}
|
||||
if ($en | length) == 0 {
|
||||
error make --unspanned {msg: $"Could not find given environment: ($env_name)"}
|
||||
if ($found_envs | length) > 1 {
|
||||
# Removed debug print
|
||||
print --stderr $"Error: Found multiple environments named '($env_name)' in different locations: ($found_envs | str join ', ')"
|
||||
return null # Return null to indicate failure
|
||||
}
|
||||
$en.0
|
||||
|
||||
# Return the single found path
|
||||
$found_envs | first
|
||||
}
|
||||
|
||||
# Completer function for environment names
|
||||
def 'nu-complete conda envs' [] {
|
||||
load-conda-info-env
|
||||
$env.CONDA_INFO
|
||||
| get env_vars.CONDA_ENVS
|
||||
| lines
|
||||
| where not ($it | str starts-with '#')
|
||||
| where not ($it | is-empty)
|
||||
| each {|entry| $entry | split row ' ' | get 0 }
|
||||
if (has-env CONDA_INFO) and ($env.CONDA_INFO != null) {
|
||||
$env.CONDA_INFO
|
||||
| get envs # Get the list of full paths
|
||||
# FIX: Pipe the path into path basename
|
||||
| each { |env_path| $env_path | path basename } # Get just the name part
|
||||
| append "base" # Always include 'base' as an option
|
||||
| uniq # Remove duplicates (e.g., if base path was listed)
|
||||
| sort # Sort the names alphabetically
|
||||
} else {
|
||||
[] # Return empty list if info failed to load
|
||||
}
|
||||
}
|
||||
|
||||
def conda-create-path-windows [env_dir: path] {
|
||||
# Helper to create PATH string on Windows
|
||||
def conda-create-path-windows [env_dir: string] {
|
||||
# Conda on Windows needs a few additional Path elements
|
||||
let env_path = [
|
||||
$env_dir
|
||||
([$env_dir "Scripts"] | path join)
|
||||
([$env_dir "Library" "mingw-w64"] | path join)
|
||||
let env_path_parts = [
|
||||
$env_dir,
|
||||
([$env_dir "Scripts"] | path join),
|
||||
([$env_dir "Library" "mingw-w64" "bin"] | path join), # Added bin here
|
||||
([$env_dir "Library" "usr" "bin"] | path join),
|
||||
([$env_dir "Library" "bin"] | path join)
|
||||
([$env_dir "Library" "usr" "bin"] | path join)
|
||||
]
|
||||
|
||||
let new_path = ([$env_path (system-path)]
|
||||
# Prepend these parts to the existing system path
|
||||
let new_path = ([$env_path_parts (system-path | split row (char esep))]
|
||||
| flatten
|
||||
| uniq # Ensure uniqueness
|
||||
| str join (char esep))
|
||||
|
||||
{ Path: $new_path }
|
||||
}
|
||||
|
||||
def conda-create-path-unix [env_dir: path] {
|
||||
let env_path = [
|
||||
([$env_dir "bin"] | path join)
|
||||
]
|
||||
# Helper to create PATH string on Unix-like systems
|
||||
def conda-create-path-unix [env_dir: string] {
|
||||
let env_bin_path = ([$env_dir "bin"] | path join)
|
||||
|
||||
let new_path = ([$env_path $env.PATH]
|
||||
# Prepend the env bin path to the existing PATH
|
||||
let new_path = ([$env_bin_path ($env.PATH | split row (char esep))]
|
||||
| flatten
|
||||
| uniq # Ensure uniqueness
|
||||
| str join (char esep))
|
||||
|
||||
{ PATH: $new_path }
|
||||
}
|
||||
|
||||
# Helper to check if running on Windows
|
||||
def windows? [] {
|
||||
$nu.os-info.name == 'windows'
|
||||
}
|
||||
|
||||
# Helper to get the system PATH variable name correctly
|
||||
def system-path [] {
|
||||
if "PATH" in $env { $env.PATH } else { $env.Path }
|
||||
}
|
||||
|
||||
# Helper to check if an environment variable exists
|
||||
def has-env [name: string] {
|
||||
$name in $env
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue