functions/stack.ps1
function get-syncdir() { if (test-path "HKCU:\Software\Microsoft\OneDrive") { $prop = get-itemproperty "HKCU:\Software\Microsoft\OneDrive\" "UserFolder" if ($prop -ne $null) { $dir = $prop.userfolder } } if ($dir -ne $null) { $syncdir = join-path $dir ".powershell-data" if (!(test-path $syncdir)) { $null = mkdir $syncdir } return $syncdir } } function add-stackitem { param( [Parameter(Mandatory=$true,ValueFromPipeline=$true)] $what, $stackname = "default", [switch][bool] $get, [Alias("est")] $estimatedTime, [alias("c")] [switch][bool] $asChild, [alias("i")] [switch][bool]$interruption, $level = 0) $stack = import-cache -container "stack.$stackname" -dir (get-syncdir) if ($stack -eq $null) { $stack = @(); $no = 1 } else { $stack = @($stack); $no = (($stack | measure -Property "no" -Maximum).maximum) + 1 } if ($estimatedTime -ne $null) { if ($estimatedTime -match "([0-9]+)p") { $estimatedTime = [timespan]::FromMinutes([int]::Parse($matches[1]) * 30) } elseif ($estimatedTime -match "([0-9]+)(m|min)") { $estimatedTime = [timespan]::FromMinutes([int]::Parse($matches[1])) } else { $estimatedTime = [timespan]::Parse($estimatedTime) } } else { $estimatedTime = [timespan]::FromMinutes(5) } $parent = $null if ($asChild) { $parent = peek -stackname $stackname } if ($parent -ne $null) { $plev = $parent.level if ($plev -eq $null) { $plev = 0 } $level = $plev + 1 } if ($interruption) { $level = 0 $what = "▼ " + $what } if ($level -gt 0 -and $what.gettype() -eq [string]) { $what = ('┗' + [string]$what) $what = $what.Padleft($what.length + $level) } if ($what.no -ne $null -and $what.value -ne $null) { $item = $what $item.no = $no } else { $props = [ordered]@{ no = $no value = $what ts = get-date -Format "yyyy-MM-dd HH:mm:ss" est = $estimatedTime.ToString() level = $level } $item = new-object -type pscustomobject -Property $props } $stack += @($item) export-cache -data $stack -container "stack.$stackname" -dir (get-syncdir) if ($get) { return peek -stackname $stackname } else { invoke-stackcmd -cmd show -stackname $stackname } } function remove-stackitem { param($stackname = "default",[switch][bool]$get) $stack = import-cache -container "stack.$stackname" -dir (get-syncdir) if ($stack -eq $null -or $stack.length -eq 0) { return $null } else { $stack = @($stack) } $item = $stack[$stack.length-1] $stack = $stack | select -First ($stack.Length-1) if ($stack -eq $null) { remove-stack -stackname "$stackname" -Confirm:$false } else { export-cache -data $stack -container "stack.$stackname" -dir (get-syncdir) } if ($get) { Write-Output $item } else { Write-host (get-stack $stackname | format-table | out-string) } $started = [datetime]::parse($item.ts) $now = get-date $elapsed = $now - $started write-host "" write-host "task $($item.value) took: $elapsed (estimated: $($item.est) overtime: $($elapsed - [timespan]::Parse($item.est)))" write-host "" } function get-stackitem { param($stackname = "default") $stack = @(import-cache -container "stack.$stackname" -dir (get-syncdir)) if ($stack -eq $null -or $stack.length -eq 0) { return $null } $item = $stack[$stack.length-1] return $item } function get-stack { param($stackname = "default", [switch][bool] $short) $stack = import-cache -container "stack.$stackname" -dir (get-syncdir) if ($short) { return $stack | select no,value,ts,est } else { return $stack } } function remove-stack { [Cmdletbinding(SupportsShouldProcess=$true)] param($stackname = "default") if ($PSCmdlet.ShouldProcess("Will remove stack named '$stackname'")) { remove-cache -container "stack.$stackname" -dir (get-syncdir) } } function invoke-stackcmd { [Cmdletbinding(DefaultParameterSetName="add")] param( [Parameter(mandatory=$false,ParameterSetName="cmd",Position=1)] [ValidateSet("push","pop","show","search","remove","done","list","showall","move")] $cmd, [Parameter(mandatory=$false,ParameterSetName="add",Position=1)] [Parameter(mandatory=$false,ParameterSetName="cmd",Position=2)] $what, [Parameter(mandatory=$true,ParameterSetName="search")] $search, [Parameter(mandatory=$false,ParameterSetName="add")] [Parameter(mandatory=$false,ParameterSetName="cmd")] [switch][bool]$go, [Parameter(mandatory=$false,ParameterSetName="add")] [Parameter(mandatory=$false,ParameterSetName="list")] [switch][bool]$done, [Parameter(mandatory=$false,ParameterSetName="add")] [switch][bool]$remove, [Alias("l")] [Parameter(mandatory=$false,ParameterSetName="add")] [switch][bool]$list, [Parameter(mandatory=$false,ParameterSetName="add")] [switch][bool]$all, [Alias("n")][Alias("name")] [Parameter(mandatory=$false)]$stackname = "default", [Alias("est")] [Parameter(mandatory=$false,ParameterSetName="add")] $estimatedTime = $null, [alias("c")] [switch][bool]$asChild, [alias("i")] [switch][bool]$interruption, [switch][bool]$full, [Alias("moveto")] [Parameter(mandatory=$false,ParameterSetName="cmd")] $to, [Parameter(ValueFromRemainingArguments)] $remaining ) $bound = $PSBoundParameters $a = $args $pipelinemode = $PSCmdlet.MyInvocation.PipelineLength -gt 1 $command = $cmd if ($what -ne $null -and $what -in @("push","pop","show","search","remove","done","list","showall")) { $command = $what $what = $null } if ($command -eq $null) { switch($PSCmdlet.ParameterSetName) { { $_ -eq "add" -and !$done -and !$remove } { if ($what -eq $null) { if ($list -or $all) { $command = "list" } else { $command = "show" } } else { if ($what.gettype() -eq [int] -or $what -match "^#[0-9]+$") { $command = "search" $search = $what } else { $command = "push" } if ($go) { $command = "push" } } } "list" { $command = "show" } { $_ -eq "search" -or ($_ -eq "add" -and ($done -or $remove)) } { if ($done) { $command = "done" } elseif ($remove) { $command = "remove" } else { $command = "search" } } default { if ($to -ne $null) { $command = "move" } } } } switch($command) { "push" { if ($go) { if ($what.gettype() -eq [int]) { $found = stack -stackname $stackname -search $what if ($found -eq $null) { return } $what = $found } else { push $what -stackname $stackname -estimatedTime $estimatedTime -asChild:$aschild -interruption:$interruption $what = peek -stackname $stackname } push "$stackname #$($what.no): $($what.value)" -asChild:$aschild -interruption:$interruption } else { push $what -stackname $stackname -estimatedTime $estimatedTime -asChild:$aschild -interruption:$interruption } } "pop" { pop -stackname $stackname } "show" { $short = !$full -and !$pipelinemode if ($done) { $stack = get-stack -stackname "$stackname.done" -short:$short } else { $stack = get-stack -stackname $stackname -short:$short } return $stack } { $_ -in "list","showall" } { $files = get-childitem (get-syncdir) -Filter "stack.*" $stacks = $files | % { if ($_.name -match "stack\.(.*)\.json" -and $_.name -notmatch "\.done\.json") { return $matches[1] } } if ($all) { foreach ($s in $stacks) { write-host "== $s ==" get-stack $s | format-table | out-string | write-host } } else { return $stacks } } { $_ -in @("search","remove","done") } { if ($_ -eq "remove") { $remove = $true } $whats = get-stack -stackname $stackname if ($search -eq $null) { $search = $what } $id = $null if ($search.gettype() -eq [double]) { $search = [int]$search } if ($search.gettype() -eq [int]) { $id = $search } if ($search -match "^\#([0-9]+)$") { $id = [int]::parse($matches[1]) } $found = $whats | ? { ($id -ne $null -and $_.no -eq $id) -or ($id -eq $null -and $_.value -match "$search") } if ($found -eq $null) { if ($search.gettype() -eq [int]) { write-warning "no item with id $search found on stack '$stackname'" } else { write-warning "no item matching '$search' found on stack '$stackname'" } return } $found = @($found) if ($_ -eq "search") { return $found } if ($found.Length -gt 1) { write-warning "more than one item matching '$search' found:" $found | format-table | out-string | write-host return } write-verbose "found matching item: $found" if ($done) { $oldid = $found[0].no $item = $found[0] $started = [datetime]::parse($item.ts) $now = get-date $elapsed = $now - $started write-host "" write-host "task $($found[0].value) took: $elapsed (estimated: $($item.est) overtime: $($elapsed - [timespan]::Parse($item.est)))" write-host "" $cur = stack -stackname "default" -search "$stackname \#$oldid" -WarningAction SilentlyContinue $null = push $found[0] -stackname "$stackname.done" # remove if found on default stack if ($cur -ne $null -and $cur -match "$stackname \#$($oldid)") { $null = stack -stackname "default" "#$($cur.no)" -remove } # remove if the task is copied from other stack if ($found[0].value -match "^([a-zA-Z]+) (\#\d+):") { $otherstack = $matches[1] $id = $matches[2] $null = stack -stackname $otherstack "$id" -remove } } if ($done -or $remove) { $newstack = $whats | ? { $_.no -ne $found[0].no } export-cache -data $newstack -container "stack.$stackname" -dir (get-syncdir) invoke-stackcmd show -stackname $stackname } } "move" { if ($to -eq $null) { throw "Missing -to argument" } $it = invoke-stackcmd -cmd search -stackname $stackname -what $what if ($it -eq $null) { throw "item matching '$what' not found" } if ($it.length -gt 1) { throw "more than one item matching '$what' found" } $no = $it.no invoke-stackcmd -cmd push -what $it -stackname $to invoke-stackcmd -cmd remove -what $no -stackname $stackname } } } function pop-idea { pop -stackname "ideas" } function todo { param( [Parameter(mandatory=$true,ParameterSetName="add",Position=1)] $what, [Parameter(mandatory=$true,ParameterSetName="search")] $search, [Parameter(mandatory=$false,ParameterSetName="add")] [switch][bool]$go, [Parameter(mandatory=$false,ParameterSetName="add")] [Parameter(mandatory=$false,ParameterSetName="list")] [switch][bool]$done, [Parameter(mandatory=$false,ParameterSetName="add")] [switch][bool]$remove ) stack @PSBoundParameters -stackname "todo" } function idea { param( [Parameter(mandatory=$true,ParameterSetName="add",Position=1)] $what, [Parameter(mandatory=$true,ParameterSetName="search")] $search, [Parameter(mandatory=$false,ParameterSetName="add")] [switch][bool]$go, [Parameter(mandatory=$false,ParameterSetName="add")] [Parameter(mandatory=$false,ParameterSetName="list")] [switch][bool]$done, [Parameter(mandatory=$false,ParameterSetName="add")] [switch][bool]$remove ) stack @PSBoundParameters -stackname "ideas" } function stack { [Cmdletbinding(DefaultParameterSetName="add")] param( [Parameter(mandatory=$false,ParameterSetName="cmd",Position=1)] [ValidateSet("push","pop","show","search","remove","done","list","showall","move")] $cmd, [Parameter(mandatory=$false,ParameterSetName="add",Position=1)] [Parameter(mandatory=$false,ParameterSetName="cmd",Position=2)] $what, [Parameter(mandatory=$true,ParameterSetName="search")] $search, [Parameter(mandatory=$false,ParameterSetName="add")] [Parameter(mandatory=$false,ParameterSetName="cmd")] [switch][bool]$go, [Parameter(mandatory=$false,ParameterSetName="add")] [Parameter(mandatory=$false,ParameterSetName="list")] [switch][bool]$done, [Parameter(mandatory=$false,ParameterSetName="add")] [switch][bool]$remove, [Alias("l")] [Parameter(mandatory=$false,ParameterSetName="add")] [switch][bool]$list, [Parameter(mandatory=$false,ParameterSetName="add")] [switch][bool]$all, [Alias("n")][Alias("name")] [Parameter(mandatory=$false)]$stackname = "default", [Alias("est")] [Parameter(mandatory=$false,ParameterSetName="add")] $estimatedTime = $null, [alias("c")] [switch][bool]$asChild, [alias("i")] [switch][bool]$interruption, [switch][bool]$full, [Alias("moveto")] $to ) $bound = $PSBoundParameters invoke-stackcmd @bound } New-Alias "st" stack -Force New-Alias "stk" stack -Force new-alias push add-stackitem -force new-alias pop remove-stackitem -force new-alias peek get-stackitem -Force |