Skip to content

Commit

Permalink
Complete powershell script
Browse files Browse the repository at this point in the history
  • Loading branch information
frostming committed Apr 3, 2021
1 parent b15bbc0 commit b8349a0
Show file tree
Hide file tree
Showing 2 changed files with 148 additions and 59 deletions.
1 change: 1 addition & 0 deletions news/367.feature.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Full-featured completion scripts for Zsh and Powershell - section selection, package name autocompletion and so on. Windows is a first-class citizen!
206 changes: 147 additions & 59 deletions pdm/cli/completions/pdm.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@ if ((Test-Path Function:\TabExpansion) -and -not (Test-Path Function:\_pdm_compl
Rename-Item Function:\TabExpansion _pdm_completeBackup
}

$PDM_PYTHON = "D:\Workspace\pdm\venv\Scripts\python.exe"
$PDM_PYTHON = "%{python_executable}"
$PDM_PIP_INDEX = (& $PDM_PYTHON -m pdm config pypi.url).Trim()

class Option {
[string[]] $Opts
Expand All @@ -14,7 +15,7 @@ class Option {
$this.Opts = $opts
}

[Option] WithValues([string []] $values) {
[Option] WithValues([string[]] $values) {
$this.Values = $values
return $this
}
Expand Down Expand Up @@ -46,19 +47,21 @@ class Completer {
$expectArg = $null
$lastWord = $words[-1]
$paramUsed = $false
foreach ($word in $words[0..-2]) {
if ($expectArg) {
$expectArg = $null
continue
}
if ($word.StartsWith("-")) {
$opt = $this.opts.Where( { $_.Match($word) })[0]
if ($null -ne $opt -and $opt.TakesArg()) {
$expectArg = $opt
if ($words.Length -gt 1) {
foreach ($word in $words[0..($words.Length - 2)]) {
if ($expectArg) {
$expectArg = $null
continue
}
if ($word.StartsWith("-")) {
$opt = $this.opts.Where( { $_.Match($word) })[0]
if ($null -ne $opt -and $opt.TakesArg()) {
$expectArg = $opt
}
}
elseif (-not $this.multiple) {
$paramUsed = $true
}
}
elseif (-not $this.multiple) {
$paramUsed = $true
}
}
$candidates = @()
Expand All @@ -76,190 +79,275 @@ class Completer {
return $candidates.Where( { $_.StartsWith($lastWord) })
}

[Completer] addOpts([Option[]] $options) {
[void] AddOpts([Option[]] $options) {
$this.opts += $options
return $this
}

[Completer] addParams([string[]] $params, [bool]$multiple = $false) {
[void] AddParams([string[]] $params, [bool]$multiple = $false) {
$this.params = $params
$this.multiple = $multiple
return $this
}
}

function getSections() {
return @()
if (-not (Test-Path -Path "pyproject.toml")) {
return @()
}
[string[]] $sections = @()
[bool] $inSection = $false
foreach ($line in (Get-Content "pyproject.toml")) {
if (($line -match ' *\[project\.optional-dependencies\]') -or ($line -match ' *\[tool\.pdm.dev-dependencies\]')) {
$inSection = $true
}
elseif ($inSection -and ($line -match '(\S+) *= *\[')) {
$sections += $Matches[1]
}
elseif ($line -like '`[*`]') {
$inSection = $false
}
}
return $sections
}

function _fetchPackageListFromPyPI() {
if (-not (Test-Path -Path "~/.pdm")) {
mkdir "~/.pdm"
}
(Invoke-WebRequest $PDM_PIP_INDEX).Links | ForEach-Object { $_.innerText } | Out-File -FilePath "~/.pdm/.pypiPackages"
}

function getPyPIPackages() {
return @()
# $cacheFile = "~/.pdm/.pypiPackages"
# if (-not (Test-Path -Path $cacheFile) -or (Get-Item $cacheFile).LastWriteTime -lt (Get-Date).AddDays(-28)) {
# _fetchPackageListFromPyPI
# }
# Get-Content $cacheFile
}

function getPdmPackages() {
return @()
& $PDM_PYTHON -c "import os, re, toml
PACKAGE_REGEX = re.compile(r'^[A-Za-z][A-Za-z0-9._-]*')
def get_packages(lines):
return [PACKAGE_REGEX.match(line).group() for line in lines]
with open('pyproject.toml', encoding='utf8') as f:
data = toml.load(f)
packages = get_packages(data.get('project', {}).get('dependencies', []))
for reqs in data.get('project', {}).get('optional-dependencies', {}).values():
packages.extend(get_packages(reqs))
for reqs in data.get('tool', {}).get('pdm', {}).get('dev-dependencies', {}).values():
packages.extend(get_packages(reqs))
print(*set(packages), sep='\n')
"
}

$_cachedConfigKeys = $null
function getConfigKeys() {
[string[]] $keys = @()
$config = ("& $PDM_PYTHON -m pdm config")
foreach ($line in $($config -split "`r`n")) {
if ($line -match ' *(\s+) *=') {
$keys += $Matches[1]
if ($null -eq $_cachedConfigKeys) {
[string[]] $keys = @()
$config = @(& $PDM_PYTHON -m pdm config)
foreach ($line in $config) {
if ($line -match ' *(\S+) *=') {
$keys += $Matches[1]
}
}
$_cachedConfigKeys = $keys
}
return $keys
return $_cachedConfigKeys
}

function getScripts() {
return @()
if (-not (Test-Path -Path "pyproject.toml")) {
return @()
}
[string[]] $scripts = @()
[bool] $inScripts = $false
foreach ($line in (Get-Content "pyproject.toml")) {
if ($line -match ' *\[tool\.pdm\.scripts\]') {
$inScripts = $true
}
elseif ($inScripts -and ($line -match '(\S+) *= *')) {
$scripts += $Matches[1]
}
elseif ($line -like '`[*`]') {
$inScripts = $false
}
}
return $scripts

}

function TabExpansion($line, $lastWord) {
$lastBlock = [regex]::Split($line, '[|;]')[-1].TrimStart()

if ($lastBlock -match "^pdm ") {
$words = $lastBlock.Split()[1..-1]
$commands = $words.Where( { $_ -notlike "-*" })
[string[]]$words = $lastBlock.Split()[1..$lastBlock.Length]
[string[]]$AllCommands = ("add", "build", "cache", "config", "export", "import", "info", "init", "install", "list", "lock", "remove", "run", "search", "show", "sync", "update", "use")
[string[]]$commands = $words.Where( { $_ -notlike "-*" })
$command = $commands[0]
$completer = [Completer]::new().addOpts(([Option]::new(("-h", "--help", "-v", "--verbose"))))
$completer = [Completer]::new()
$completer.AddOpts(([Option]::new(("-h", "--help", "-v", "--verbose"))))
$sectionOption = [Option]::new(@("-s", "--section")).WithValues(@(getSections))
$projectOption = [Option]::new(@("-p", "--project")).WithValues(@())
$formatOption = [Option]::new(@("-f", "--format")).WithValues(@("setuppy", "requirements", "poetry", "flit"))

Switch ($command) {

"add" {
$completer.addOpts(@(
$completer.AddOpts(@(
[Option]::new(("-d", "--dev", "--save-compatible", "--save-wildcard", "--save-exact", "--update-eager", "--update-reuse", "-g", "--global", "--no-sync")),
$sectionOption,
$projectOption,
[Option]::new(@("-e", "--editable")).WithValues(@(getPyPIPackages))
)).addParams(@(getPyPIPackages))
))
$completer.AddParams(@(getPyPIPackages), $true)
break
}
"build" { $completer.addOpts(@([Option]::new(@("-d", "--dest", "--no-clean", "--no-sdist", "--no-wheel")), $projectOption)) }
"build" { $completer.AddOpts(@([Option]::new(@("-d", "--dest", "--no-clean", "--no-sdist", "--no-wheel")), $projectOption)) }
"cache" {
$subCommand = $commands[1]
switch ($subCommand) {
"clear" {
$completer.addParams(@("wheels", "http", "hashes", "metadata"))
$completer.AddParams(@("wheels", "http", "hashes", "metadata"), $false)
$command = $subCommand
break
}
$null {
$completer.addParams(@("clear", "remove", "info", "list"))
$completer.AddParams(@("clear", "remove", "info", "list"), $false)
break
}
Default {}
}
}
"completion" { $completer.addParams(@("powershell", "bash", "zsh", "fish")) }
"config" { $completer.addOpts(@([Option]::new(@("--delete", "--global", "--local", "-d", "-l", "-g")), $projectOption)) }
"completion" { $completer.AddParams(@("powershell", "bash", "zsh", "fish")); break }
"config" {
$completer.AddOpts(@([Option]::new(@("--delete", "--global", "--local", "-d", "-l", "-g")), $projectOption))
$completer.AddParams(@(getConfigKeys), $false)
break
}
"export" {
$completer.addOpts(@(
$completer.AddOpts(@(
[Option]::new(@("--dev", "--output", "--global", "--no-default", "-g", "-d", "-o", "--without-hashes")),
$formatOption,
$sectionOption,
$projectOption
))
break
}
"import" {
$completer.addOpts(@(
$completer.AddOpts(@(
[Option]::new(@("--dev", "--global", "--no-default", "-g", "-d")),
$formatOption,
$sectionOption,
$projectOption
))
break
}
"info" {
$completer.addOpts(
$completer.AddOpts(
@(
[Option]::new(@("--env", "--global", "-g", "--python", "--where", "--packages")),
$projectOption
))
break
}
"init" {
$completer.addOpts(
$completer.AddOpts(
@(
[Option]::new(@("-g", "--global", "--non-interactive", "-n")),
$projectOption
))
break
}
"install" {
$completer.addOpts(@(
$completer.AddOpts(@(
[Option]::new(("-d", "--dev", "-g", "--global", "--no-default", "--no-lock")),
$sectionOption,
$projectOption
))
break
}
"list" {
$completer.addOpts(
$completer.AddOpts(
@(
[Option]::new(@("--graph", "--global", "-g", "--reverse", "-r")),
$projectOption
))
break
}
"lock" {
$completer.addOpts(
$completer.AddOpts(
@(
[Option]::new(@("--global", "-g")),
$projectOption
))
break
}
"remove" {
$completer.addOpts(
$completer.AddOpts(
@(
[Option]::new(@("--global", "-g", "--dev", "-d", "--no-sync")),
$projectOption,
$sectionOption
)).addParams(@(getPdmPackages))
))
$completer.AddParams(@(getPdmPackages), $true)
break
}
"run" {
$completer.addOpts(
$completer.AddOpts(
@(
[Option]::new(@("--global", "-g", "-l", "--list")),
$projectOption
)).addParams(@(getScripts))
))
$completer.AddParams(@(getScripts), $false)
break
}
"search" { }
"search" { break }
"show" {
$completer.addOpts(
$completer.AddOpts(
@(
[Option]::new(@("--global", "-g")),
$projectOption
))
break
}
"sync" {
$completer.addOpts(@(
$completer.AddOpts(@(
[Option]::new(("-d", "--dev", "-g", "--global", "--no-default", "--clean", "--no-clean", "--dry-run")),
$sectionOption,
$projectOption
))
break
}
"update" {
$completer.addOpts(@(
$completer.AddOpts(@(
[Option]::new(("-d", "--dev", "--save-compatible", "--save-wildcard", "--save-exact", "--update-eager", "--update-reuse", "-g", "--global", "--dry-run", "--outdated", "--top")),
$sectionOption,
$projectOption
)).addParams(@(getPdmPackages))
))
$completer.AddParams(@(getPdmPackages), $true)
break
}
"use" {
$completer.addOpts(
$completer.AddOpts(
@(
[Option]::new(@("--global", "-g", "-f", "--first")),
$projectOption
))
break
}

default {
# No command
$completer.addParams(@("add", "build", "cache", "config", "export", "import", "info", "init", "install", "list", "lock", "remove", "run", "search", "show", "sync", "update", "use"))
$command = $null
$completer.AddParams($AllCommands, $false)
}
}
$completer.Complete($words[[array]::IndexOf($words, $command) + 1..-1])
$start = [array]::IndexOf($words, $command) + 1
$completer.Complete($words[$start..$words.Length])
}
elseif (Test-Path Function:\_pdm_completeBackup) {
# Fall back on existing tab expansion
_pdm_completeBackup $line $lastWord
}
}

TabExpansion "pdm config " ""

0 comments on commit b8349a0

Please sign in to comment.