Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 5 additions & 3 deletions PSModuleDevelopment/PSModuleDevelopment.psd1
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
RootModule = 'PSModuleDevelopment.psm1'

# Version number of this module.
ModuleVersion = '2.2.8.104'
ModuleVersion = '2.2.9.106'

# ID used to uniquely identify this module
GUID = '37dd5fce-e7b5-4d57-ac37-832055ce49d6'
Expand All @@ -27,7 +27,8 @@
# Modules that must be imported into the global environment prior to importing
# this module
RequiredModules = @(
@{ ModuleName = 'PSFramework'; ModuleVersion = '1.1.59' }
@{ ModuleName = 'PSFramework'; ModuleVersion = '1.4.149' }
@{ ModuleName = 'string'; ModuleVersion = '0.6.1' }
)

# Assemblies that must be loaded prior to importing this module
Expand All @@ -48,7 +49,8 @@
NestedModules = @()

# Functions to export from this module
FunctionsToExport = @(
FunctionsToExport = @(
'Convert-PSMDMessage',
'Expand-PSMDTypeName',
'Export-PSMDString',
'Find-PSMDFileContent',
Expand Down
5 changes: 5 additions & 0 deletions PSModuleDevelopment/changelog.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
# Changelog

## 2.2.9.106 (September 10th, 2020)

- New: Convert-PSMDMessage - Converts a file's use of PSFramework messages to strings.
- Fix: Export-PSMDString - Failed with splatting detection

## 2.2.8.104 (July 26th, 2020)

- Fix: Various bugs in the new functions
Expand Down
3 changes: 3 additions & 0 deletions PSModuleDevelopment/en-us/strings.psd1
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
@{
'Convert-PSMDMessage.Parameter.NonAffected' = 'No commands found that should be switched to strings in {0}' # $Path
'Convert-PSMDMessage.SyntaxError' = 'Syntax error in result after converting the file {0}. Please validate your file and if it is valid, file an issue with the source file it failed to convert' # $Path

'Get-PSMDFileCommand.SyntaxError' = 'Syntax error in file: {0}' # $pathItem

'MeasurePSMDLinesOfCode.Processing' = 'Processing Path: {0}' # $fileItem
Expand Down
199 changes: 199 additions & 0 deletions PSModuleDevelopment/functions/refactor/Convert-PSMDMessage.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,199 @@
function Convert-PSMDMessage
{
<#
.SYNOPSIS
Converts a file's use of PSFramework messages to strings.

.DESCRIPTION
Converts a file's use of PSFramework messages to strings.

.PARAMETER Path
Path to the file to convert.

.PARAMETER OutPath
Folder in which to generate the output ps1 and psd1 file.

.PARAMETER EnableException
Replaces user friendly yellow warnings with bloody red exceptions of doom!
Use this if you want the function to throw terminating errors you want to catch.

.EXAMPLE
PS C:\> Convert-PSMDMessage -Path 'C:\Scripts\logrotate.ps1' -OutPath 'C:\output'

Converts all instances of writing messages in logrotate.ps1 to use strings instead.
#>
[CmdletBinding()]
param (
[Parameter(Mandatory = $true, Position = 0)]
[PsfValidateScript('PSFramework.Validate.FSPath.File', ErrorString = 'PSFramework.Validate.FSPath.File')]
[string]
$Path,

[Parameter(Mandatory = $true, Position = 1)]
[PsfValidateScript('PSFramework.Validate.FSPath.Folder', ErrorString = 'PSFramework.Validate.FSPath.Folder')]
[string]
$OutPath,

[switch]
$EnableException
)

begin
{
#region Utility Functions
function Get-Text
{
[OutputType([string])]
[CmdletBinding()]
param (
$Value
)

if (-not $Value.NestedExpressions) { return $Value.Extent.Text }

$expressions = @{ }
$expIndex = 0

$builder = [System.Text.StringBuilder]::new()
$baseIndex = $Value.Extent.StartOffset
$astIndex = 0

foreach ($nestedExpression in $Value.NestedExpressions)
{
$null = $builder.Append($Value.Extent.Text.SubString($astIndex, ($nestedExpression.Extent.StartOffset - $baseIndex - $astIndex)).Replace("{", "{{").Replace('}', '}}'))
$astIndex = $nestedExpression.Extent.EndOffset - $baseIndex

if ($expressions.ContainsKey($nestedExpression.Extent.Text)) { $effectiveIndex = $expressions[$nestedExpression.Extent.Text] }
else
{
$expressions[$nestedExpression.Extent.Text] = $expIndex
$effectiveIndex = $expIndex
$expIndex++
}

$null = $builder.Append("{$effectiveIndex}")
}

$null = $builder.Append($Value.Extent.Text.SubString($astIndex).Replace("{", "{{").Replace('}', '}}'))
$builder.ToString()
}

function Get-Insert
{
[OutputType([string])]
[CmdletBinding()]
param (
$Value
)

if (-not $Value.NestedExpressions) { return "" }

$processed = @{ }
$elements = foreach ($nestedExpression in $Value.NestedExpressions)
{
if ($processed[$nestedExpression.Extent.Text]) { continue }
else { $processed[$nestedExpression.Extent.Text] = $true }

if ($nestedExpression -is [System.Management.Automation.Language.SubExpressionAst])
{
if (
($nestedExpression.SubExpression.Statements.Count -eq 1) -and
($nestedExpression.SubExpression.Statements[0].PipelineElements.Count -eq 1) -and
($nestedExpression.SubExpression.Statements[0].PipelineElements[0].Expression -is [System.Management.Automation.Language.MemberExpressionAst])
) { $nestedExpression.SubExpression.Extent.Text }
else { $nestedExpression.Extent.Text.SubString(1) }
}
else { $nestedExpression.Extent.Text }
}
$elements -join ", "
}
#endregion Utility Functions

$parameterMapping = @{
'Message' = 'String'
'Action' = 'ActionString'
}
$insertMapping = @{
'String' = '-StringValues'
'Action' = '-ActionStringValues'
}
}
process
{
$ast = (Read-PSMDScript -Path $Path).Ast

#region Parse Input
$functionName = (Get-Item $Path).BaseName

$commandAsts = $ast.FindAll({
if ($args[0] -isnot [System.Management.Automation.Language.CommandAst]) { return $false }
if ($args[0].CommandElements[0].Value -notmatch '^Invoke-PSFProtectedCommand$|^Write-PSFMessage$|^Stop-PSFFunction$') { return $false }
if (-not ($args[0].CommandElements.ParameterName -match '^Message$|^Action$')) { return $false }
$true
}, $true)
if (-not $commandAsts)
{
Write-PSFMessage -Level Host -String 'Convert-PSMDMessage.Parameter.NonAffected' -StringValues $Path
return
}
#endregion Parse Input

#region Build Replacements table
$currentCount = 1
$replacements = foreach ($command in $commandAsts)
{
$parameter = $command.CommandElements | Where-Object ParameterName -in 'Message', 'Action'
$paramIndex = $command.CommandElements.IndexOf($parameter)
$parameterValue = $command.CommandElements[$paramIndex + 1]

[PSCustomObject]@{
OriginalText = $parameterValue.Value
Text = Get-Text -Value $parameterValue
Inserts = Get-Insert -Value $parameterValue
String = "$($functionName).Message$($currentCount)"
StartOffset = $parameter.Extent.StartOffset
EndOffset = $parameterValue.Extent.EndOffset
OldParameterName = $parameter.ParameterName
NewParameterName = $parameterMapping[$parameter.ParameterName]
Parameter = $parameter
ParameterValue = $parameterValue
}
$currentCount++
}
#endregion Build Replacements table

#region Calculate new text body
$fileText = [System.IO.File]::ReadAllText((Resolve-PSFPath -Path $Path))
$builder = [System.Text.StringBuilder]::new()
$index = 0
foreach ($replacement in $replacements)
{
$null = $builder.Append($fileText.Substring($index, ($replacement.StartOffset - $index)))
$null = $builder.Append("-$($replacement.NewParameterName) '$($replacement.String)'")
if ($replacement.Inserts) { $null = $builder.Append(" $($insertMapping[$replacement.NewParameterName]) $($replacement.Inserts)") }
$index = $replacement.EndOffset
}
$null = $builder.Append($fileText.Substring($index))
$newDefinition = $builder.ToString()
$testResult = Read-PSMDScript -ScriptCode ([Scriptblock]::create($newDefinition))

if ($testResult.Errors)
{
Stop-PSFFunction -String 'Convert-PSMDMessage.SyntaxError' -StringValues $Path -Target $Path -EnableException $EnableException
return
}
#endregion Calculate new text body

$resolvedOutPath = Resolve-PSFPath -Path $OutPath
$encoding = [System.Text.UTF8Encoding]::new($true)
$filePath = Join-Path -Path $resolvedOutPath -ChildPath "$functionName.ps1"
[System.IO.File]::WriteAllText($filePath, $newDefinition, $encoding)
$stringsPath = Join-Path -Path $resolvedOutPath -ChildPath "$functionName.psd1"
$stringsText = @"
@{
$($replacements | Format-String "`t'{0}' = {1} # {2}" -Property String, Text, Inserts | Join-String -Separator "`n")
}
"@
[System.IO.File]::WriteAllText($stringsPath, $stringsText, $encoding)
}
}
Loading