Skip to content

Commit 9b7b7e1

Browse files
Merge pull request #129 from PowershellFrameworkCollective/strings
2.2.9.106
2 parents 7fec660 + deefa06 commit 9b7b7e1

File tree

6 files changed

+276
-76
lines changed

6 files changed

+276
-76
lines changed

PSModuleDevelopment/PSModuleDevelopment.psd1

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
RootModule = 'PSModuleDevelopment.psm1'
55

66
# Version number of this module.
7-
ModuleVersion = '2.2.8.104'
7+
ModuleVersion = '2.2.9.106'
88

99
# ID used to uniquely identify this module
1010
GUID = '37dd5fce-e7b5-4d57-ac37-832055ce49d6'
@@ -27,7 +27,8 @@
2727
# Modules that must be imported into the global environment prior to importing
2828
# this module
2929
RequiredModules = @(
30-
@{ ModuleName = 'PSFramework'; ModuleVersion = '1.1.59' }
30+
@{ ModuleName = 'PSFramework'; ModuleVersion = '1.4.149' }
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: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
11
# Changelog
22

3+
## 2.2.9.106 (September 10th, 2020)
4+
5+
- New: Convert-PSMDMessage - Converts a file's use of PSFramework messages to strings.
6+
- Fix: Export-PSMDString - Failed with splatting detection
7+
38
## 2.2.8.104 (July 26th, 2020)
49

510
- Fix: Various bugs in the new functions

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: 199 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,199 @@
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+
[OutputType([string])]
47+
[CmdletBinding()]
48+
param (
49+
$Value
50+
)
51+
52+
if (-not $Value.NestedExpressions) { return $Value.Extent.Text }
53+
54+
$expressions = @{ }
55+
$expIndex = 0
56+
57+
$builder = [System.Text.StringBuilder]::new()
58+
$baseIndex = $Value.Extent.StartOffset
59+
$astIndex = 0
60+
61+
foreach ($nestedExpression in $Value.NestedExpressions)
62+
{
63+
$null = $builder.Append($Value.Extent.Text.SubString($astIndex, ($nestedExpression.Extent.StartOffset - $baseIndex - $astIndex)).Replace("{", "{{").Replace('}', '}}'))
64+
$astIndex = $nestedExpression.Extent.EndOffset - $baseIndex
65+
66+
if ($expressions.ContainsKey($nestedExpression.Extent.Text)) { $effectiveIndex = $expressions[$nestedExpression.Extent.Text] }
67+
else
68+
{
69+
$expressions[$nestedExpression.Extent.Text] = $expIndex
70+
$effectiveIndex = $expIndex
71+
$expIndex++
72+
}
73+
74+
$null = $builder.Append("{$effectiveIndex}")
75+
}
76+
77+
$null = $builder.Append($Value.Extent.Text.SubString($astIndex).Replace("{", "{{").Replace('}', '}}'))
78+
$builder.ToString()
79+
}
80+
81+
function Get-Insert
82+
{
83+
[OutputType([string])]
84+
[CmdletBinding()]
85+
param (
86+
$Value
87+
)
88+
89+
if (-not $Value.NestedExpressions) { return "" }
90+
91+
$processed = @{ }
92+
$elements = foreach ($nestedExpression in $Value.NestedExpressions)
93+
{
94+
if ($processed[$nestedExpression.Extent.Text]) { continue }
95+
else { $processed[$nestedExpression.Extent.Text] = $true }
96+
97+
if ($nestedExpression -is [System.Management.Automation.Language.SubExpressionAst])
98+
{
99+
if (
100+
($nestedExpression.SubExpression.Statements.Count -eq 1) -and
101+
($nestedExpression.SubExpression.Statements[0].PipelineElements.Count -eq 1) -and
102+
($nestedExpression.SubExpression.Statements[0].PipelineElements[0].Expression -is [System.Management.Automation.Language.MemberExpressionAst])
103+
) { $nestedExpression.SubExpression.Extent.Text }
104+
else { $nestedExpression.Extent.Text.SubString(1) }
105+
}
106+
else { $nestedExpression.Extent.Text }
107+
}
108+
$elements -join ", "
109+
}
110+
#endregion Utility Functions
111+
112+
$parameterMapping = @{
113+
'Message' = 'String'
114+
'Action' = 'ActionString'
115+
}
116+
$insertMapping = @{
117+
'String' = '-StringValues'
118+
'Action' = '-ActionStringValues'
119+
}
120+
}
121+
process
122+
{
123+
$ast = (Read-PSMDScript -Path $Path).Ast
124+
125+
#region Parse Input
126+
$functionName = (Get-Item $Path).BaseName
127+
128+
$commandAsts = $ast.FindAll({
129+
if ($args[0] -isnot [System.Management.Automation.Language.CommandAst]) { return $false }
130+
if ($args[0].CommandElements[0].Value -notmatch '^Invoke-PSFProtectedCommand$|^Write-PSFMessage$|^Stop-PSFFunction$') { return $false }
131+
if (-not ($args[0].CommandElements.ParameterName -match '^Message$|^Action$')) { return $false }
132+
$true
133+
}, $true)
134+
if (-not $commandAsts)
135+
{
136+
Write-PSFMessage -Level Host -String 'Convert-PSMDMessage.Parameter.NonAffected' -StringValues $Path
137+
return
138+
}
139+
#endregion Parse Input
140+
141+
#region Build Replacements table
142+
$currentCount = 1
143+
$replacements = foreach ($command in $commandAsts)
144+
{
145+
$parameter = $command.CommandElements | Where-Object ParameterName -in 'Message', 'Action'
146+
$paramIndex = $command.CommandElements.IndexOf($parameter)
147+
$parameterValue = $command.CommandElements[$paramIndex + 1]
148+
149+
[PSCustomObject]@{
150+
OriginalText = $parameterValue.Value
151+
Text = Get-Text -Value $parameterValue
152+
Inserts = Get-Insert -Value $parameterValue
153+
String = "$($functionName).Message$($currentCount)"
154+
StartOffset = $parameter.Extent.StartOffset
155+
EndOffset = $parameterValue.Extent.EndOffset
156+
OldParameterName = $parameter.ParameterName
157+
NewParameterName = $parameterMapping[$parameter.ParameterName]
158+
Parameter = $parameter
159+
ParameterValue = $parameterValue
160+
}
161+
$currentCount++
162+
}
163+
#endregion Build Replacements table
164+
165+
#region Calculate new text body
166+
$fileText = [System.IO.File]::ReadAllText((Resolve-PSFPath -Path $Path))
167+
$builder = [System.Text.StringBuilder]::new()
168+
$index = 0
169+
foreach ($replacement in $replacements)
170+
{
171+
$null = $builder.Append($fileText.Substring($index, ($replacement.StartOffset - $index)))
172+
$null = $builder.Append("-$($replacement.NewParameterName) '$($replacement.String)'")
173+
if ($replacement.Inserts) { $null = $builder.Append(" $($insertMapping[$replacement.NewParameterName]) $($replacement.Inserts)") }
174+
$index = $replacement.EndOffset
175+
}
176+
$null = $builder.Append($fileText.Substring($index))
177+
$newDefinition = $builder.ToString()
178+
$testResult = Read-PSMDScript -ScriptCode ([Scriptblock]::create($newDefinition))
179+
180+
if ($testResult.Errors)
181+
{
182+
Stop-PSFFunction -String 'Convert-PSMDMessage.SyntaxError' -StringValues $Path -Target $Path -EnableException $EnableException
183+
return
184+
}
185+
#endregion Calculate new text body
186+
187+
$resolvedOutPath = Resolve-PSFPath -Path $OutPath
188+
$encoding = [System.Text.UTF8Encoding]::new($true)
189+
$filePath = Join-Path -Path $resolvedOutPath -ChildPath "$functionName.ps1"
190+
[System.IO.File]::WriteAllText($filePath, $newDefinition, $encoding)
191+
$stringsPath = Join-Path -Path $resolvedOutPath -ChildPath "$functionName.psd1"
192+
$stringsText = @"
193+
@{
194+
$($replacements | Format-String "`t'{0}' = {1} # {2}" -Property String, Text, Inserts | Join-String -Separator "`n")
195+
}
196+
"@
197+
[System.IO.File]::WriteAllText($stringsPath, $stringsText, $encoding)
198+
}
199+
}

0 commit comments

Comments
 (0)