Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement Add-PoshGitToProfile command #361

Merged
merged 10 commits into from
Jan 16, 2017
130 changes: 127 additions & 3 deletions Utils.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@ function Invoke-NullCoalescing {
foreach($arg in $args) {
if ($arg -is [ScriptBlock]) {
$result = & $arg
} else {
}
else {
$result = $arg
}
if ($result) { break }
Expand All @@ -26,6 +27,98 @@ function Invoke-Utf8ConsoleCommand ([ScriptBlock]$cmd) {
}
}

function Add-ImportModuleToProfile {
param (
[Parameter(Position = 0, Mandatory = $true)]
[ValidateNotNullOrEmpty()]
[string]
$ProfilePath,

# This is only required to support PS v2 there $PSScriptRoot only works in a .psm1 file
[Parameter(Position = 1, Mandatory = $true)]
[ValidateNotNullOrEmpty()]
[string]
$ScriptRoot
)

# Check if profile script exists
if (Test-Path -LiteralPath $profilePath) {
$profileContent = @(Get-Content -LiteralPath $profilePath)
$profileContent += [string]::Empty # Empty line between previous profile script and import statement
$encoding = Get-FileEncoding $profilePath
}
else {
# Doesn't exist, so create it
New-Item -Path $profilePath -ItemType File
$profileContent = @()
$encoding = 'utf8'
}

# Check if the location of this module file is in the PSModulePath
if (Test-InModulePath $ScriptRoot) {
$profileContent += "Import-Module posh-git"
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we instead check Get-Module posh-git -ListAvailable to see if it finds this particular instance of the module? Or check that it will find any posh-git module and consider that good enough?

I'm almost tempted to just skip straight to always hard-coding the full module path.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would avoid hard-coding the module path as a "bad practice" for PowerShell modules. I can switch to using Get-Module -ListAvailable.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Doh, that makes the Pester test a little harder. Oh well, hopefully not too much harder. :-)

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK this is making the Pester tests a lot harder. For some reason, Mock Get-Module is not working. Will look at it more tomorrow.

}
else {
$profileContent += "Import-Module '$ScriptRoot\posh-git.psd1'"
}

Set-Content -LiteralPath $profilePath -Value $profileContent -Encoding $encoding
}

<#
.SYNOPSIS
Gets the file encoding of the specified file.
.DESCRIPTION
Gets the file encoding of the specified file.
.PARAMETER Path
Path to the file to check. The file must exist.
.EXAMPLE
PS C:\> Get-FileEncoding $profile
Get's the file encoding of the profile file.
.INPUTS
None.
.OUTPUTS
[System.String]
.NOTES
Adapted from http://www.west-wind.com/Weblog/posts/197245.aspx
#>
function Get-FileEncoding($Path) {
$bytes = [byte[]](Get-Content $Path -Encoding byte -ReadCount 4 -TotalCount 4)

if (!$bytes) { return 'utf8' }

switch -regex ('{0:x2}{1:x2}{2:x2}{3:x2}' -f $bytes[0],$bytes[1],$bytes[2],$bytes[3]) {
'^efbbbf' { return 'utf8' }
'^2b2f76' { return 'utf7' }
'^fffe' { return 'unicode' }
'^feff' { return 'bigendianunicode' }
'^0000feff' { return 'utf32' }
default { return 'ascii' }
}
}

<#
.SYNOPSIS
Gets a StringComparison enum value appropriate for comparing paths on the OS platform.
.DESCRIPTION
Gets a StringComparison enum value appropriate for comparing paths on the OS platform.
.EXAMPLE
PS C:\> $pathStringComparison = Get-PathStringComparison
.INPUTS
None
.OUTPUTS
[System.StringComparison]
#>
function Get-PathStringComparison {
# File system paths are case-sensitive on Linux and case-insensitive on Windows and macOS
if (($PSVersionTable.PSVersion.Major -ge 6) -and $IsLinux) {
[System.StringComparison]::Ordinal
}
else {
[System.StringComparison]::OrdinalIgnoreCase
}
}

function Get-LocalOrParentPath($path) {
$checkIn = Get-Item -Force .
if ($checkIn.PSProvider.Name -ne 'FileSystem') {
Expand All @@ -35,15 +128,46 @@ function Get-LocalOrParentPath($path) {
$pathToTest = [System.IO.Path]::Combine($checkIn.fullname, $path)
if (Test-Path -LiteralPath $pathToTest) {
return $pathToTest
} else {
}
else {
$checkIn = $checkIn.parent
}
}
return $null
}

function Test-InModulePath {
param (
[Parameter(Position=0, Mandatory=$true)]
[ValidateNotNull()]
[string]
$Path
)

$modulePaths = $env:PSModulePath -split ';'
if (!$modulePaths) { return $false }

$pathStringComparison = Get-PathStringComparison
$inModulePath = @($modulePaths | Where-Object { $Path.StartsWith($_, $pathStringComparison) }).Count -gt 0
$inModulePath
}

function Test-PoshGitImportedInScript {
param (
[Parameter(Position=0, Mandatory=$true)]
[string]
$Path
)

if (!$Path -or !(Test-Path -LiteralPath $Path)) {
return $false
}

@((Get-Content $Path -ErrorAction SilentlyContinue) -match 'posh-git').Count -gt 0
}

function dbg ($Message, [Diagnostics.Stopwatch]$Stopwatch) {
if($Stopwatch) {
if ($Stopwatch) {
Write-Verbose ('{0:00000}:{1}' -f $Stopwatch.ElapsedMilliseconds,$Message) -Verbose # -ForegroundColor Yellow
}
}
20 changes: 4 additions & 16 deletions install.ps1
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
param([switch]$WhatIf = $false)

# Dot source for Get-FileEncoding
$scriptRoot = Split-Path $MyInvocation.MyCommand.Path -Parent
Copy link
Owner

@dahlbyk dahlbyk Jan 8, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We have $installDir = Split-Path $MyInvocation.MyCommand.Path -Parent on L22. Rename this one and delete that line? (Or keep this name and fix uses of $installDir.)

. $scriptRoot\Utils.ps1

if($PSVersionTable.PSVersion.Major -lt 2) {
Write-Warning "posh-git requires PowerShell 2.0 or better; you have version $($Host.Version)."
return
Expand All @@ -20,22 +24,6 @@ if(!(. (Join-Path $installDir "CheckVersion.ps1"))) {
return
}

# Adapted from http://www.west-wind.com/Weblog/posts/197245.aspx
function Get-FileEncoding($Path) {
$bytes = [byte[]](Get-Content $Path -Encoding byte -ReadCount 4 -TotalCount 4)

if(!$bytes) { return 'utf8' }

switch -regex ('{0:x2}{1:x2}{2:x2}{3:x2}' -f $bytes[0],$bytes[1],$bytes[2],$bytes[3]) {
'^efbbbf' { return 'utf8' }
'^2b2f76' { return 'utf7' }
'^fffe' { return 'unicode' }
'^feff' { return 'bigendianunicode' }
'^0000feff' { return 'utf32' }
default { return 'ascii' }
}
}

$profileLine = ". '$installDir\profile.example.ps1'"
if(Select-String -Path $PROFILE -Pattern $profileLine -Quiet -SimpleMatch) {
Write-Host "It seems posh-git is already installed..."
Expand Down
49 changes: 47 additions & 2 deletions posh-git.psm1
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
param([switch]$NoVersionWarn)
param([switch]$NoVersionWarn, [switch]$NoProfileCheck)

if (Get-Module posh-git) { return }

Expand Down Expand Up @@ -38,7 +38,7 @@ $poshGitPromptScriptBlock = $null

$currentPromptDef = if ($funcInfo = Get-Command prompt -ErrorAction SilentlyContinue) { $funcInfo.Definition }

# HACK: If prompt is missing, create a global one we can overwrite with Set-Item
# HACK: If prompt is missing, create a global one we can overwrite with Set-Item
if (!$currentPromptDef) {
function global:prompt { ' ' }
}
Expand Down Expand Up @@ -92,6 +92,51 @@ if (!$currentPromptDef -or ($currentPromptDef -eq $defaultPromptDef)) {
Set-Item Function:\prompt -Value $poshGitPromptScriptBlock
}

# IFF running interactive, check if user wants their profile script to be modified to import the module
if (!$NoProfileCheck -and !$MyInvocation.ScriptName) {
# Search the user's profiles to see if any are using posh-git already
$importedInProfile = Test-PoshGitImportedInScript $PROFILE.CurrentUserCurrentHost
if (!$importedInProfile) {
$importedInProfile = Test-PoshGitImportedInScript $PROFILE.CurrentUserAllHosts
}
if (!$importedInProfile) {
$importedInProfile = Test-PoshGitImportedInScript $PROFILE.AllUsersCurrentHost
}
if (!$importedInProfile) {
$importedInProfile = Test-PoshGitImportedInScript $PROFILE.AllUsersAllHosts
}

# If we haven't detected that a profile script is using posh-git, ask if they want their profile modified to import posh-git
if (!$importedInProfile) {
$yesCurrent = New-Object System.Management.Automation.Host.ChoiceDescription -ArgumentList @(
"&Yes, for the current PowerShell host",
"Modify the profile for $($Host.Name) to automatically import posh-git."
)

$yesAll = New-Object System.Management.Automation.Host.ChoiceDescription -ArgumentList @(
"Yes, for &all PowerShell hosts",
"Modify the profile for all hosts to automatically import posh-git."
)

$no = New-Object System.Management.Automation.Host.ChoiceDescription -ArgumentList @(
"&No",
"Do not modify my profile. To suppress this prompt in the future, execute: Import-Module posh-git -Arg `$false, `$true"
)

$options = [System.Management.Automation.Host.ChoiceDescription[]]($yesCurrent, $yesAll, $no)

$title = "Modify Profile"
$message = "Do you want posh-git to modify your profile to automatically import this module whenever your start PowerShell?"
$result = $host.UI.PromptForChoice($title, $message, $options, 0)
switch ($result) {
0 { Add-ImportModuleToProfile $PROFILE.CurrentUserCurrentHost $PSScriptRoot }
1 { Add-ImportModuleToProfile $PROFILE.CurrentUserAllHosts $PSScriptRoot }
2 { } # Nothing to do if user picks 'No'
default { throw "Unexpected choice result: $result" }
}
}
}

# Install handler for removal/unload of the module
$ExecutionContext.SessionState.Module.OnRemove = {
$global:VcsPromptStatuses = $global:VcsPromptStatuses | Where-Object { $_ -ne $PoshGitVcsPrompt }
Expand Down
104 changes: 104 additions & 0 deletions test/Utils.Tests.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
. $PSScriptRoot\..\Utils.ps1

Describe 'Utils Function Tests' {
Context 'Add-ImportModuleToProfile Tests' {
BeforeAll {
$newLine = [System.Environment]::NewLine
}
BeforeEach {
$profilePath = [System.IO.Path]::GetTempFileName()
}
AfterEach {
Remove-Item $profilePath -ErrorAction SilentlyContinue
}
It 'Creates profile file if it does not exist' {
Remove-Item -LiteralPath $profilePath
Test-Path -LiteralPath $profilePath | Should Be $false
$scriptRoot = Split-Path $profilePath -Parent
Add-ImportModuleToProfile $profilePath $scriptRoot
Test-Path -LiteralPath $profilePath | Should Be $true
Get-FileEncoding $profilePath | Should Be 'utf8'
$content = Get-Content $profilePath
$content.Count | Should Be 1
@($content)[0] | Should BeExactly "Import-Module '$scriptRoot\posh-git.psd1'"
}
It 'Modifies existing (Unicode) profile file correctly' {
$profileContent = @'
Import-Module PSCX

New-Alias pscore C:\Users\Keith\GitHub\rkeithhill\PowerShell\src\powershell-win-core\bin\Debug\netcoreapp1.1\win10-x64\powershell.exe
'@
Set-Content $profilePath -Value $profileContent -Encoding Unicode
$scriptRoot = Split-Path $profilePath -Parent
Add-ImportModuleToProfile $profilePath $scriptRoot
Test-Path -LiteralPath $profilePath | Should Be $true
Get-FileEncoding $profilePath | Should Be 'unicode'
$content = Get-Content $profilePath
$content.Count | Should Be 5
$profileContent += "${newLine}${newLine}Import-Module '$scriptRoot\posh-git.psd1'"
$content -join $newLine | Should BeExactly $profileContent
}
}

Context 'Test-PoshGitImportedInScript Tests' {
BeforeEach {
$profilePath = [System.IO.Path]::GetTempFileName()
}
AfterEach {
Remove-Item $profilePath -ErrorAction SilentlyContinue
}
It 'Detects Import-Module posh-git in profile script' {
$profileContent = "Import-Module posh-git"
Set-Content $profilePath -Value $profileContent -Encoding Unicode
Test-PoshGitImportedInScript $profilePath | Should Be $true
}
It 'Detects chocolatey installed line in profile script' {
$profileContent = ". 'C:\tools\poshgit\dahlbyk-posh-git-18d600a\profile.example.ps1"
Set-Content $profilePath -Value $profileContent -Encoding Unicode
Test-PoshGitImportedInScript $profilePath | Should Be $true
}
It 'Returns false when profile script does not import posh-git' {
$profileContent = "Import-Module Pscx`nImport-Module platyPS`nImport-Module Plaster"
Set-Content $profilePath -Value $profileContent -Encoding Unicode
Test-PoshGitImportedInScript $profilePath | Should Be $false
}
}

Context 'Test-InModulePath Tests' {
BeforeAll {
$standardPSModulePath = "C:\Users\Keith\Documents\WindowsPowerShell\Modules;C:\Program Files\WindowsPowerShell\Modules;C:\WINDOWS\system32\WindowsPowerShell\v1.0\Modules\"
}
BeforeEach {
$origPSModulePath = $env:PSModulePath
}
AfterEach {
$env:PSModulePath = $origPSModulePath
}
It 'Works for install from PSGallery to current user modules location' {
$env:PSModulePath = $standardPSModulePath
$path = "C:\Users\Keith\Documents\WindowsPowerShell\Modules\posh-git\0.7.0"
Test-InModulePath $path | Should Be $true
}
It 'Works for install from Chocolatey not in any modules path' {
$env:PSModulePath = $standardPSModulePath
$path = "C:\tools\posh-git\dahlbyk-posh-git-18d600a"
Test-InModulePath $path | Should Be $false
}
It 'Works for running from posh-git Git repo and location not in modules path' {
$env:PSModulePath = $standardPSModulePath
$path = "C:\Users\Keith\GitHub\posh-git"
Test-InModulePath $path | Should Be $false
}
It 'Returns false when PSModulePath is empty' {
$env:PSModulePath = ''
$path = "C:\Users\Keith\Documents\WindowsPowerShell\Modules\posh-git\0.7.0"
Test-InModulePath $path | Should Be $false
}
It 'Returns false when PSModulePath is missing' {
Remove-Item Env:\PSModulePath
Test-Path Env:\PSModulePath | Should Be $false
$path = "C:\Users\Keith\Documents\WindowsPowerShell\Modules\posh-git\0.7.0"
Test-InModulePath $path | Should Be $false
}
}
}
2 changes: 2 additions & 0 deletions test/testDebugHarness.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@

Invoke-Pester $PSScriptRoot\Utils.Tests.ps1