param([parameter(Position=0,Mandatory=$false)] [Hashtable] $CondaModuleArgs=@{}) # Defaults from before we had arguments. if (-not $CondaModuleArgs.ContainsKey('ChangePs1')) { $CondaModuleArgs.ChangePs1 = $True } ## ENVIRONMENT MANAGEMENT ###################################################### <# .SYNOPSIS Obtains a list of valid conda environments. .EXAMPLE Get-CondaEnvironment .EXAMPLE genv #> function Get-CondaEnvironment { [CmdletBinding()] param(); begin {} process { # NB: the JSON output of conda env list does not include the names # of each env, so we need to parse the fragile output instead. & $Env:CONDA_EXE $Env:_CE_M $Env:_CE_CONDA env list | ` Where-Object { -not $_.StartsWith("#") } | ` Where-Object { -not $_.Trim().Length -eq 0 } | ` ForEach-Object { $envLine = $_ -split "\s+"; $Active = $envLine[1] -eq "*"; [PSCustomObject] @{ Name = $envLine[0]; Active = $Active; Path = if ($Active) {$envLine[2]} else {$envLine[1]}; } | Write-Output; } } end {} } <# .SYNOPSIS Activates a conda environment, placing its commands and packages at the head of $Env:PATH. .EXAMPLE Enter-CondaEnvironment base .EXAMPLE etenv base .NOTES This command does not currently support activating environments stored in a non-standard location. #> function Enter-CondaEnvironment { [CmdletBinding()] param( [Parameter(Mandatory=$false)][switch]$Stack, [Parameter(Position=0)][string]$Name ); begin { If ($Stack) { $activateCommand = (& $Env:CONDA_EXE $Env:_CE_M $Env:_CE_CONDA shell.powershell activate --stack $Name | Out-String); } Else { $activateCommand = (& $Env:CONDA_EXE $Env:_CE_M $Env:_CE_CONDA shell.powershell activate $Name | Out-String); } Write-Verbose "[conda shell.powershell activate $Name]`n$activateCommand"; Invoke-Expression -Command $activateCommand; } process {} end {} } <# .SYNOPSIS Deactivates the current conda environment, if any. .EXAMPLE Exit-CondaEnvironment .EXAMPLE exenv #> function Exit-CondaEnvironment { [CmdletBinding()] param(); begin { $deactivateCommand = (& $Env:CONDA_EXE $Env:_CE_M $Env:_CE_CONDA shell.powershell deactivate | Out-String); # If deactivate returns an empty string, we have nothing more to do, # so return early. if ($deactivateCommand.Trim().Length -eq 0) { return; } Write-Verbose "[conda shell.powershell deactivate]`n$deactivateCommand"; Invoke-Expression -Command $deactivateCommand; } process {} end {} } ## CONDA WRAPPER ############################################################### <# .SYNOPSIS conda is a tool for managing and deploying applications, environments and packages. .PARAMETER Command Subcommand to invoke. .EXAMPLE conda install toolz #> function Invoke-Conda() { # Don't use any explicit args here, we'll use $args and tab completion # so that we can capture everything, INCLUDING short options (e.g. -n). if ($Args.Count -eq 0) { # No args, just call the underlying conda executable. & $Env:CONDA_EXE $Env:_CE_M $Env:_CE_CONDA; } else { $Command = $Args[0]; if ($Args.Count -ge 2) { $OtherArgs = $Args[1..($Args.Count - 1)]; } else { $OtherArgs = @(); } switch ($Command) { "activate" { Enter-CondaEnvironment @OtherArgs; } "deactivate" { Exit-CondaEnvironment; } default { # There may be a command we don't know want to handle # differently in the shell wrapper, pass it through # verbatim. & $Env:CONDA_EXE $Env:_CE_M $Env:_CE_CONDA $Command @OtherArgs; } } } } ## TAB COMPLETION ############################################################## # We borrow the approach used by posh-git, in which we override any existing # functions named TabExpansion, look for commands we can complete on, and then # default to the previously defined TabExpansion function for everything else. if (Test-Path Function:\TabExpansion) { # Since this technique is common, we encounter an infinite loop if it's # used more than once unless we give our backup a unique name. Rename-Item Function:\TabExpansion CondaTabExpansionBackup } function Expand-CondaEnv() { param( [string] $Filter ); $ValidEnvs = Get-CondaEnvironment; $ValidEnvs ` | Where-Object { $_.Name -like "$filter*" } ` | ForEach-Object { $_.Name } ` | Write-Output; $ValidEnvs ` | Where-Object { $_.Path -like "$filter*" } ` | ForEach-Object { $_.Path } ` | Write-Output; } function Expand-CondaSubcommands() { param( [string] $Filter ); $ValidCommands = Invoke-Conda shell.powershell commands; # Add in the commands defined within this wrapper, filter, sort, and return. $ValidCommands + @('activate', 'deactivate') ` | Where-Object { $_ -like "$Filter*" } ` | Sort-Object ` | Write-Output; } function TabExpansion($line, $lastWord) { $lastBlock = [regex]::Split($line, '[|;]')[-1].TrimStart() switch -regex ($lastBlock) { # Pull out conda commands we recognize first before falling through # to the general patterns for conda itself. "^conda activate (.*)" { Expand-CondaEnv $lastWord; break; } "^etenv (.*)" { Expand-CondaEnv $lastWord; break; } # If we got down to here, check arguments to conda itself. "^conda (.*)" { Expand-CondaSubcommands $lastWord; break; } # Finally, fall back on existing tab expansion. default { if (Test-Path Function:\CondaTabExpansionBackup) { CondaTabExpansionBackup $line $lastWord } } } } ## PROMPT MANAGEMENT ########################################################### <# .SYNOPSIS Modifies the current prompt to show the currently activated conda environment, if any. .EXAMPLE Add-CondaEnvironmentToPrompt Causes the current session's prompt to display the currently activated conda environment. #> if ($CondaModuleArgs.ChangePs1) { # We use the same procedure to nest prompts as we did for nested tab completion. if (Test-Path Function:\prompt) { Rename-Item Function:\prompt CondaPromptBackup } else { function CondaPromptBackup() { # Restore a basic prompt if the definition is missing. "PS $($executionContext.SessionState.Path.CurrentLocation)$('>' * ($nestedPromptLevel + 1)) "; } } function global:prompt() { if ($Env:CONDA_PROMPT_MODIFIER) { $Env:CONDA_PROMPT_MODIFIER | Write-Host -NoNewline } CondaPromptBackup; } } ## ALIASES ##################################################################### New-Alias conda Invoke-Conda -Force New-Alias genv Get-CondaEnvironment -Force New-Alias etenv Enter-CondaEnvironment -Force New-Alias exenv Exit-CondaEnvironment -Force ## EXPORTS ################################################################### Export-ModuleMember ` -Alias * ` -Function ` Invoke-Conda, ` Get-CondaEnvironment, ` Enter-CondaEnvironment, Exit-CondaEnvironment, ` TabExpansion, prompt