From c4502019ccd1d35028beab30897c3afada00a9cf Mon Sep 17 00:00:00 2001 From: "Joel Sallow (/u/ta11ow)" <32407840+vexx32@users.noreply.github.com> Date: Sun, 26 Jan 2020 22:33:55 -0500 Subject: [PATCH] :sparkles: Add "Open File" Feature (#346) * :sparkles: Add Line info in Get-Karma result * :sparkles: Add params to open current file - Move `-Meditate` to open the current file - Add `-Library` to open the folder. * :recycle: Cleanup extra brace * :memo: Update help files * :wrench: Fix error in Show-Karma * :wrench: Add embedded quotes for default case * :bug: Check to ensure koan location exists When Get-PSKoan is called with the user scope it needs to check if the folder actually exists. * :recycle: Streamline Show-Karma editor logic * :white_check_mark: Update tests - Use temp dir in show-karma test - Restructure tests to avoid duplication --- PSKoans/Public/Get-Karma.ps1 | 8 +- PSKoans/Public/Get-PSKoan.ps1 | 11 +- PSKoans/Public/Show-Karma.ps1 | 41 +++- Tests/Functions/Public/Show-Karma.Tests.ps1 | 208 ++++++++++++++------ docs/Show-Karma.md | 35 +++- 5 files changed, 234 insertions(+), 69 deletions(-) diff --git a/PSKoans/Public/Get-Karma.ps1 b/PSKoans/Public/Get-Karma.ps1 index 66104813e..61877af43 100644 --- a/PSKoans/Public/Get-Karma.ps1 +++ b/PSKoans/Public/Get-Karma.ps1 @@ -32,6 +32,7 @@ 'ModuleOnly' { $GetParams['Module'] = $Module } { $Topic } { $GetParams['Topic'] = $Topic } } + switch ($PSCmdlet.ParameterSetName) { 'ListKoans' { Get-PSKoan @GetParams @@ -110,9 +111,10 @@ KoansPassed = $KoansPassed TotalKoans = $TotalKoans CurrentTopic = [PSCustomObject]@{ - Name = $KoanFile.Topic - Completed = $PesterTests.PassedCount - Total = $PesterTests.TotalCount + Name = $KoanFile.Topic + Completed = $PesterTests.PassedCount + Total = $PesterTests.TotalCount + CurrentLine = ($NextKoanFailed.StackTrace -split '\r?\n')[1] -replace ':.+' } Results = $PesterTests.TestResult RequestedTopic = $Topic diff --git a/PSKoans/Public/Get-PSKoan.ps1 b/PSKoans/Public/Get-PSKoan.ps1 index 6836a8472..d0c2ef43c 100644 --- a/PSKoans/Public/Get-PSKoan.ps1 +++ b/PSKoans/Public/Get-PSKoan.ps1 @@ -32,7 +32,16 @@ function Get-PSKoan { ) $ParentPath = switch ($Scope) { - 'User' { Get-PSKoanLocation } + 'User' { + $KoanLocation = Get-PSKoanLocation + Write-Verbose "Checking existence of koans folder" + if (-not (Test-Path $KoanLocation)) { + Write-Verbose "Koans folder does not exist. Initiating full reset..." + Update-PSKoan -Confirm:$false + } + + $KoanLocation + } 'Module' { Join-Path -Path $script:ModuleRoot -ChildPath 'Koans' } } diff --git a/PSKoans/Public/Show-Karma.ps1 b/PSKoans/Public/Show-Karma.ps1 index f06a9d500..2df5c3f86 100644 --- a/PSKoans/Public/Show-Karma.ps1 +++ b/PSKoans/Public/Show-Karma.ps1 @@ -29,11 +29,16 @@ function Show-Karma { [switch] $List, - [Parameter(Mandatory, ParameterSetName = 'OpenFolder')] + [Parameter(Mandatory, ParameterSetName = 'OpenFile')] [Alias('Meditate')] [switch] $Contemplate, + [Parameter(Mandatory, ParameterSetName = 'OpenFolder')] + [Alias('OpenFolder')] + [switch] + $Library, + [Parameter()] [Alias()] [switch] @@ -80,6 +85,40 @@ function Show-Karma { $KoanLocation | Invoke-Item } } + 'OpenFile' { + try { + $Results = Get-Karma @GetParams + } + catch { + $PSCmdlet.ThrowTerminatingError($_) + } + + $Editor = Get-PSKoanSetting -Name Editor + $FilePath = (Get-PSKoan -Topic $Results.CurrentTopic.Name -Scope User).Path + $LineNumber = $Results.CurrentTopic.CurrentLine + + $Arguments = switch ($Editor) { + { $_ -in 'code', 'code-insiders' } { + '--goto' + '"{0}":{1}' -f (Resolve-Path $FilePath), $LineNumber + '--reuse-window' + } + atom { + '"{0}":{1}' -f (Resolve-Path $FilePath), $LineNumber + } + default { + '"{0}"' -f (Resolve-Path $FilePath) + } + } + + if ($Editor -and (Get-Command -Name $Editor -ErrorAction SilentlyContinue)) { + Start-Process -FilePath $Editor -ArgumentList $Arguments + } + else { + Invoke-Item -Path $FilePath + } + } + default { if ($ClearScreen) { Clear-Host diff --git a/Tests/Functions/Public/Show-Karma.Tests.ps1 b/Tests/Functions/Public/Show-Karma.Tests.ps1 index 2226af389..f256db0db 100644 --- a/Tests/Functions/Public/Show-Karma.Tests.ps1 +++ b/Tests/Functions/Public/Show-Karma.Tests.ps1 @@ -7,6 +7,16 @@ if (-not (Get-Module PSKoans)) { #endregion Describe 'Show-Karma' { + BeforeAll { + $StartingLocation = Get-PSKoanLocation + Set-PSKoanLocation -Path "$TestDrive/Koans" + + Reset-PSKoan -Confirm:$false + } + + AfterAll { + Set-PSKoanLocation -Path $StartingLocation + } InModuleScope 'PSKoans' { @@ -23,9 +33,10 @@ Describe 'Show-Karma' { Expectation = 'ExpectedTest' It = 'TestIt' CurrentTopic = [PSCustomObject]@{ - Name = 'TestTopic"' - Completed = 0 - Total = 4 + Name = 'TestTopic"' + Completed = 0 + Total = 4 + CurrentLine = 1 } } } @@ -77,9 +88,10 @@ Describe 'Show-Karma' { Expectation = 'ExpectedTest' It = 'TestIt' CurrentTopic = [PSCustomObject]@{ - Name = 'TestTopic"' - Completed = 0 - Total = 4 + Name = 'TestTopic"' + Completed = 0 + Total = 4 + CurrentLine = 1 } } } @@ -109,6 +121,9 @@ Describe 'Show-Karma' { Mock Get-PSKoan -ModuleName 'PSKoans' { } Mock Update-PSKoan -ModuleName 'PSKoans' { throw 'Prevent recursion' } Mock Write-Warning + Mock Test-Path { $false } + Mock Invoke-Item + Mock New-Item } It 'should attempt to populate koans and then recurse to reassess' { @@ -126,6 +141,22 @@ Describe 'Show-Karma' { It 'throws an error if a Topic is specified that matches nothing' { { Show-Karma -Topic 'AboutAbsolutelyNothing' } | Should -Throw -ErrorId 'PSKoans.TopicNotFound' } + + It 'should create PSKoans directory with -Library' { + { Show-Karma -Library } | Should -Throw -ExpectedMessage 'Prevent recursion' + + Assert-MockCalled Test-Path -Times 1 + Assert-MockCalled Update-PSKoan -Times 1 + Assert-MockCalled New-Item -Times 1 + } + + It 'should create PSKoans directory with -Contemplate' { + { Show-Karma -Contemplate } | Should -Throw -ExpectedMessage 'Prevent recursion' + + Assert-MockCalled Test-Path -Times 1 + Assert-MockCalled Update-PSKoan -Times 1 + Assert-MockCalled New-Item -Times 1 + } } Context 'With -ListTopics Parameter' { @@ -152,9 +183,10 @@ Describe 'Show-Karma' { Expectation = 'ExpectedTest' It = 'TestIt' CurrentTopic = [PSCustomObject]@{ - Name = 'TestTopic"' - Completed = 0 - Total = 4 + Name = 'TestTopic"' + Completed = 0 + Total = 4 + CurrentLine = 1 } RequestedTopic = $Topic } @@ -186,73 +218,131 @@ Describe 'Show-Karma' { } } - Context 'With -Meditate Switch' { + Context 'With -Contemplate Switch' { + BeforeAll { + $TestFile = New-TemporaryFile - Context 'With "code" Set as the Editor' { - BeforeAll { - Mock Get-Command { $true } - Mock Start-Process { - @{ Editor = $FilePath; Path = $ArgumentList } + Mock Invoke-Item { $Path } + Mock Get-Command { $true } -ParameterFilter { $Name -ne "missing_editor" } + Mock Get-Command { $false } -ParameterFilter { $Name -eq "missing_editor" } + Mock Start-Process { + @{ Editor = $FilePath; Arguments = $ArgumentList } + } + Mock Get-Karma -ModuleName 'PSKoans' { + [PSCustomObject]@{ + PSTypeName = 'PSKoans.Result' + Meditation = 'TestMeditation' + KoansPassed = 0 + TotalKoans = 400 + Describe = 'TestDescribe' + Expectation = 'ExpectedTest' + It = 'TestIt' + CurrentTopic = [PSCustomObject]@{ + Name = 'TestTopic"' + Completed = 0 + Total = 4 + CurrentLine = 1 + } } - Set-PSKoanSetting -Name Editor -Value 'code' - - $Result = Show-Karma -Contemplate } + Mock Get-PSKoan { + [PSCustomObject]@{ Path = $TestFile.FullName } + } + } - It 'should start VS Code with Start-Process' { - $Result.Editor | Should -Be 'code' + AfterAll { + $TestFile | Remove-Item + } - Assert-MockCalled Get-Command -Times 1 - Assert-MockCalled Start-Process -Times 1 - } + It 'invokes VS Code with "code" set as Editor with proper arguments' { + Set-PSKoanSetting -Name Editor -Value 'code' + $Result = Show-Karma -Contemplate - It 'should pass a resolved path' { - # Resolve-Path doesn't like embedded quotes - $Path = $Result.Path -replace '"' - $Path | Should -BeExactly (Resolve-Path -Path $Path).Path - } + $Result.Editor | Should -BeExactly 'code' + $Result.Arguments[0] | Should -BeExactly '--goto' + $Result.Arguments[1] | Should -MatchExactly '"[^"]+":\d+' + $Result.Arguments[2] | Should -BeExactly '--reuse-window' - It 'should enclose the path in quotes' { - $Result.Path | Should -MatchExactly '"[^"]+"' - } + # Resolve-Path doesn't like embedded quotes + $Path = ($Result.Arguments[1] -split '(?<="):')[0] -replace '"' + $Path | Should -BeExactly (Resolve-Path -Path $Path).Path + + Assert-MockCalled Get-Command -Times 1 + Assert-MockCalled Start-Process -Times 1 } - Context 'With Editor Not Found' { - BeforeAll { - Mock Get-Command { $false } - Mock Invoke-Item - Set-PSKoanSetting -Name Editor -Value "ascsadsa" - } + It 'invokes the set editor with unknown editor chosen' { + Set-PSKoanSetting -Name Editor -Value 'vim' - It 'should not produce output' { - Show-Karma -Meditate | Should -BeNullOrEmpty - } + $Result = Show-Karma -Contemplate + $Result.Editor | Should -BeExactly 'vim' + $Result.Arguments | Should -MatchExactly '"[^"]+"' - It 'should open the koans directory with Invoke-Item' { - Assert-MockCalled Get-Command -Times 1 -ParameterFilter { $Name -eq "ascsadsa" } - Assert-MockCalled Invoke-Item -Times 1 - } + # Resolve-Path doesn't like embedded quotes + $Path = $Result.Arguments -replace '"' + $Path | Should -BeExactly (Resolve-Path -Path $Path).Path + + Assert-MockCalled Get-Command -Times 1 + Assert-MockCalled Start-Process -Times 1 } - Context 'With Nonexistent KoanLocation' { - BeforeAll { - Mock Test-Path { $false } - Mock Update-PSKoan - Mock Get-Command { $false } - Mock Invoke-Item - Mock New-Item - } + It 'opens the file directly when selected editor is unavailable' { + Set-PSKoanSetting -Name Editor -Value "missing_editor" + + Show-Karma -Contemplate | Should -BeExactly $TestFile.FullName - It 'should create PSKoans directory' { - Show-Karma -Meditate + Assert-MockCalled Get-Command -Times 1 -ParameterFilter { $Name -eq "missing_editor" } + Assert-MockCalled Invoke-Item -Times 1 + } + } - Assert-MockCalled Test-Path -Times 1 - Assert-MockCalled Update-PSKoan -Times 1 - Assert-MockCalled Get-Command -Times 1 - Assert-MockCalled New-Item -Times 1 - Assert-MockCalled Invoke-Item -Times 1 + Context 'With -Library Switch' { + BeforeAll { + Mock Get-Command { $true } -ParameterFilter { $Name -ne "missing_editor" } + Mock Get-Command { $false } -ParameterFilter { $Name -eq "missing_editor" } + Mock Start-Process { + @{ Editor = $FilePath; Arguments = $ArgumentList } } + Mock Invoke-Item { $Path } + } + + It 'invokes VS Code with "code" set as Editor with proper arguments' { + Set-PSKoanSetting -Name Editor -Value 'code' + $Result = Show-Karma -Library + + $Result.Editor | Should -BeExactly 'code' + + # Resolve-Path doesn't like embedded quotes + $Path = $Result.Arguments -replace '"' + $Path | Should -BeExactly (Resolve-Path -Path $Path).Path + + Assert-MockCalled Get-Command -Times 1 + Assert-MockCalled Start-Process -Times 1 + } + + It 'invokes the set editor with unknown editor chosen' { + Set-PSKoanSetting -Name Editor -Value 'vim' + + $Result = Show-Karma -Library + $Result.Editor | Should -BeExactly 'vim' + + # Resolve-Path doesn't like embedded quotes + $Path = $Result.Arguments -replace '"' + $Path | Should -BeExactly (Resolve-Path -Path $Path).Path + + Assert-MockCalled Get-Command -Times 1 + Assert-MockCalled Start-Process -Times 1 + } + + It 'opens the file directly when selected editor is unavailable' { + Set-PSKoanSetting -Name Editor -Value "missing_editor" + + Show-Karma -Library | Should -BeExactly (Get-PSKoanLocation) + + Assert-MockCalled Get-Command -Times 1 -ParameterFilter { $Name -eq "missing_editor" } + Assert-MockCalled Invoke-Item -Times 1 } } + } } diff --git a/docs/Show-Karma.md b/docs/Show-Karma.md index 8f1b46ac5..228a9f80f 100644 --- a/docs/Show-Karma.md +++ b/docs/Show-Karma.md @@ -32,11 +32,16 @@ Show-Karma [-Topic ] -Module [-ClearScreen] [-Detailed] [] [-Module ] [-List] [-ClearScreen] [] ``` -### OpenFolder +### OpenFile ``` Show-Karma [-Contemplate] [-ClearScreen] [] ``` +### OpenFolder +``` +Show-Karma [-Library] [-ClearScreen] [] +``` + ## DESCRIPTION Show-Karma executes Pester against the koans to evaluate if you have made the necessary corrections for success. @@ -54,9 +59,10 @@ Assesses the koan lessons, and displays the meditation prompt with the results. Show-Karma -Contemplate ``` -Opens the user's koans folder, housed in `$home\PSKoans`. -If VS Code is in `$env:PATH`, opens VS Code to the workspace location. -Otherwise, the folder is opened in a file explorer. +Opens the current koan file in the editor specified by the `Editor` setting. +Use [`Set-PSKoanSetting`](./Set-PSKoanSetting.md) to change the editor used. + +If a known editor (`code`, `code-insiders`, or `atom`) is used, PSKoans will pass along line information as well. ## PARAMETERS @@ -84,7 +90,7 @@ If you have VS Code Insiders installed, you can set `$env:PSKoans_EditorPreferen ```yaml Type: SwitchParameter -Parameter Sets: OpenFolder +Parameter Sets: OpenFile Aliases: Meditate Required: True @@ -125,6 +131,25 @@ Accept pipeline input: False Accept wildcard characters: False ``` +### -Library +Opens the current `KoanLocation` folder in the preferred editor. +To set the preferred editor, use [`Set-PSKoanSetting`](./Set-PSKoanSetting.md). + +If the preferred editor cannot be found or the setting is cleared, the folder will be opened in the default handler. +This should be Windows Explorer on Windows, Finder on Mac, etc. + +```yaml +Type: SwitchParameter +Parameter Sets: OpenFolder +Aliases: OpenFolder + +Required: True +Position: Named +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + ### -List Output a complete list of available koan topics.