Skip to content

Commit a8973a8

Browse files
author
Friedrich Weinmann
committed
implementing Convert-PSMDMessage
1 parent 5fc37ec commit a8973a8

File tree

4 files changed

+204
-1
lines changed

4 files changed

+204
-1
lines changed

PSModuleDevelopment/PSModuleDevelopment.psd1

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
# this module
2929
RequiredModules = @(
3030
@{ ModuleName = 'PSFramework'; ModuleVersion = '1.1.59' }
31+
@{ ModuleName = 'string'; ModuleVersion = '0.6.1' }
3132
)
3233

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

5051
# Functions to export from this module
51-
FunctionsToExport = @(
52+
FunctionsToExport = @(
53+
'Convert-PSMDMessage',
5254
'Expand-PSMDTypeName',
5355
'Export-PSMDString',
5456
'Find-PSMDFileContent',

PSModuleDevelopment/changelog.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
## ???
44

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

78
## 2.2.8.104 (July 26th, 2020)

PSModuleDevelopment/en-us/strings.psd1

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
11
@{
2+
'Convert-PSMDMessage.Parameter.NonAffected' = 'No commands found that should be switched to strings in {0}' # $Path
3+
'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
4+
25
'Get-PSMDFileCommand.SyntaxError' = 'Syntax error in file: {0}' # $pathItem
36

47
'MeasurePSMDLinesOfCode.Processing' = 'Processing Path: {0}' # $fileItem
Lines changed: 197 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,197 @@
1+
function Convert-PSMDMessage
2+
{
3+
<#
4+
.SYNOPSIS
5+
Converts a file's use of PSFramework messages to strings.
6+
7+
.DESCRIPTION
8+
Converts a file's use of PSFramework messages to strings.
9+
10+
.PARAMETER Path
11+
Path to the file to convert.
12+
13+
.PARAMETER OutPath
14+
Folder in which to generate the output ps1 and psd1 file.
15+
16+
.PARAMETER EnableException
17+
Replaces user friendly yellow warnings with bloody red exceptions of doom!
18+
Use this if you want the function to throw terminating errors you want to catch.
19+
20+
.EXAMPLE
21+
PS C:\> Convert-PSMDMessage -Path 'C:\Scripts\logrotate.ps1' -OutPath 'C:\output'
22+
23+
Converts all instances of writing messages in logrotate.ps1 to use strings instead.
24+
#>
25+
[CmdletBinding()]
26+
param (
27+
[Parameter(Mandatory = $true, Position = 0)]
28+
[PsfValidateScript('PSFramework.Validate.FSPath.File', ErrorString = 'PSFramework.Validate.FSPath.File')]
29+
[string]
30+
$Path,
31+
32+
[Parameter(Mandatory = $true, Position = 1)]
33+
[PsfValidateScript('PSFramework.Validate.FSPath.Folder', ErrorString = 'PSFramework.Validate.FSPath.Folder')]
34+
[string]
35+
$OutPath,
36+
37+
[switch]
38+
$EnableException
39+
)
40+
41+
begin
42+
{
43+
#region Utility Functions
44+
function Get-Text
45+
{
46+
[CmdletBinding()]
47+
param (
48+
$Value
49+
)
50+
51+
if (-not $Value.NestedExpressions) { return $Value.Extent.Text }
52+
53+
$expressions = @{ }
54+
$expIndex = 0
55+
56+
$builder = [System.Text.StringBuilder]::new()
57+
$baseIndex = $Value.Extent.StartOffset
58+
$astIndex = 0
59+
60+
foreach ($nestedExpression in $Value.NestedExpressions)
61+
{
62+
$null = $builder.Append($Value.Extent.Text.SubString($astIndex, ($nestedExpression.Extent.StartOffset - $baseIndex - $astIndex)).Replace("{", "{{").Replace('}', '}}'))
63+
$astIndex = $nestedExpression.Extent.EndOffset - $baseIndex
64+
65+
if ($expressions.ContainsKey($nestedExpression.Extent.Text)) { $effectiveIndex = $expressions[$nestedExpression.Extent.Text] }
66+
else
67+
{
68+
$expressions[$nestedExpression.Extent.Text] = $expIndex
69+
$effectiveIndex = $expIndex
70+
$expIndex++
71+
}
72+
73+
$null = $builder.Append("{$effectiveIndex}")
74+
}
75+
76+
$null = $builder.Append($Value.Extent.Text.SubString($astIndex).Replace("{", "{{").Replace('}', '}}'))
77+
$builder.ToString()
78+
}
79+
80+
function Get-Insert
81+
{
82+
[CmdletBinding()]
83+
param (
84+
$Value
85+
)
86+
87+
if (-not $Value.NestedExpressions) { return "" }
88+
89+
$processed = @{ }
90+
$elements = foreach ($nestedExpression in $Value.NestedExpressions)
91+
{
92+
if ($processed[$nestedExpression.Extent.Text]) { continue }
93+
else { $processed[$nestedExpression.Extent.Text] = $true }
94+
95+
if ($nestedExpression -is [System.Management.Automation.Language.SubExpressionAst])
96+
{
97+
if (
98+
($nestedExpression.SubExpression.Statements.Count -eq 1) -and
99+
($nestedExpression.SubExpression.Statements[0].PipelineElements.Count -eq 1) -and
100+
($nestedExpression.SubExpression.Statements[0].PipelineElements[0].Expression -is [System.Management.Automation.Language.MemberExpressionAst])
101+
) { $nestedExpression.SubExpression.Extent.Text }
102+
else { $nestedExpression.Extent.Text.SubString(1) }
103+
}
104+
else { $nestedExpression.Extent.Text }
105+
}
106+
$elements -join ", "
107+
}
108+
#endregion Utility Functions
109+
110+
$parameterMapping = @{
111+
'Message' = 'String'
112+
'Action' = 'ActionString'
113+
}
114+
$insertMapping = @{
115+
'String' = '-StringValues'
116+
'Action' = '-ActionStringValues'
117+
}
118+
}
119+
process
120+
{
121+
$ast = (Read-PSMDScript -Path $Path).Ast
122+
123+
#region Parse Input
124+
$functionName = (Get-Item $Path).BaseName
125+
126+
$commandAsts = $ast.FindAll({
127+
if ($args[0] -isnot [System.Management.Automation.Language.CommandAst]) { return $false }
128+
if ($args[0].CommandElements[0].Value -notmatch '^Invoke-PSFProtectedCommand$|^Write-PSFMessage$|^Stop-PSFFunction$') { return $false }
129+
if (-not ($args[0].CommandElements.ParameterName -match '^Message$|^Action$')) { return $false }
130+
$true
131+
}, $true)
132+
if (-not $commandAsts)
133+
{
134+
Write-PSFMessage -Level Host -String 'Convert-PSMDMessage.Parameter.NonAffected' -StringValues $Path
135+
return
136+
}
137+
#endregion Parse Input
138+
139+
#region Build Replacements table
140+
$currentCount = 1
141+
$replacements = foreach ($command in $commandAsts)
142+
{
143+
$parameter = $command.CommandElements | Where-Object ParameterName -in 'Message', 'Action'
144+
$paramIndex = $command.CommandElements.IndexOf($parameter)
145+
$parameterValue = $command.CommandElements[$paramIndex + 1]
146+
147+
[PSCustomObject]@{
148+
OriginalText = $parameterValue.Value
149+
Text = Get-Text -Value $parameterValue
150+
Inserts = Get-Insert -Value $parameterValue
151+
String = "$($functionName).Message$($currentCount)"
152+
StartOffset = $parameter.Extent.StartOffset
153+
EndOffset = $parameterValue.Extent.EndOffset
154+
OldParameterName = $parameter.ParameterName
155+
NewParameterName = $parameterMapping[$parameter.ParameterName]
156+
Parameter = $parameter
157+
ParameterValue = $parameterValue
158+
}
159+
$currentCount++
160+
}
161+
#endregion Build Replacements table
162+
163+
#region Calculate new text body
164+
$fileText = [System.IO.File]::ReadAllText((Resolve-PSFPath -Path $Path))
165+
$builder = [System.Text.StringBuilder]::new()
166+
$index = 0
167+
foreach ($replacement in $replacements)
168+
{
169+
$null = $builder.Append($fileText.Substring($index, ($replacement.StartOffset - $index)))
170+
$null = $builder.Append("-$($replacement.NewParameterName) '$($replacement.String)'")
171+
if ($replacement.Inserts) { $null = $builder.Append(" $($insertMapping[$replacement.NewParameterName]) $($replacement.Inserts)") }
172+
$index = $replacement.EndOffset
173+
}
174+
$null = $builder.Append($fileText.Substring($index))
175+
$newDefinition = $builder.ToString()
176+
$testResult = Read-PSMDScript -ScriptCode ([Scriptblock]::create($newDefinition))
177+
178+
if ($testResult.Errors)
179+
{
180+
Stop-PSFFunction -String 'Convert-PSMDMessage.SyntaxError' -StringValues $Path -Target $Path -EnableException $EnableException
181+
return
182+
}
183+
#endregion Calculate new text body
184+
185+
$resolvedOutPath = Resolve-PSFPath -Path $OutPath
186+
$encoding = [System.Text.UTF8Encoding]::new($true)
187+
$filePath = Join-Path -Path $resolvedOutPath -ChildPath "$functionName.ps1"
188+
[System.IO.File]::WriteAllText($filePath, $newDefinition, $encoding)
189+
$stringsPath = Join-Path -Path $resolvedOutPath -ChildPath "$functionName.psd1"
190+
$stringsText = @"
191+
@{
192+
$($replacements | Format-String "`t'{0}' = {1} # {2}" -Property String, Text, Inserts | Join-String -Separator "`n")
193+
}
194+
"@
195+
[System.IO.File]::WriteAllText($stringsPath, $stringsText, $encoding)
196+
}
197+
}

0 commit comments

Comments
 (0)