1+ function Export-PSMDString
2+ {
3+ <#
4+ . SYNOPSIS
5+ Parses a module that uses the PSFramework localization feature for strings and their value.
6+
7+ . DESCRIPTION
8+ Parses a module that uses the PSFramework localization feature for strings and their value.
9+ This command can be used to generate and update the language files used by the module.
10+ It is also used in automatic tests, ensuring no abandoned string has been left behind and no key is unused.
11+
12+ . PARAMETER ModuleRoot
13+ The root of the module to process.
14+ Must be the root folder where the psd1 file is stored in.
15+
16+ . EXAMPLE
17+ PS C:\> Export-PSMDString -ModuleRoot 'C:\Code\Github\MyModuleProject\MyModule'
18+
19+ Generates the strings data for the MyModule module.
20+ #>
21+ [CmdletBinding ()]
22+ param (
23+ [Parameter (Mandatory = $true , ValueFromPipeline = $true , ValueFromPipelineByPropertyName = $true )]
24+ [Alias (' ModuleBase' )]
25+ [string ]
26+ $ModuleRoot
27+ )
28+
29+ process
30+ {
31+ # region Find Language Files : $languageFiles
32+ $languageFiles = @ { }
33+ $languageFolders = Get-ChildItem - Path $ModuleRoot - Directory | Where-Object Name -match ' ^\w\w-\w\w$'
34+ foreach ($languageFolder in $languageFolders )
35+ {
36+ $languageFiles [$languageFolder.Name ] = @ { }
37+ foreach ($file in (Get-ChildItem - Path $languageFolder.FullName - Filter * .psd1))
38+ {
39+ $languageFiles [$languageFolder.Name ] += Import-PSFPowerShellDataFile - Path $file.FullName
40+ }
41+ }
42+ # endregion Find Language Files : $languageFiles
43+
44+ # region Find Keys : $foundKeys
45+ $foundKeys = foreach ($file in (Get-ChildItem - Path $ModuleRoot - Recurse | Where-Object Extension -match ' ^\.ps1$|^\.psm1$' ))
46+ {
47+ $ast = (Read-PSMDScript - Path $file.FullName ).Ast
48+ $commandAsts = $ast.FindAll ({
49+ if ($args [0 ] -isnot [System.Management.Automation.Language.CommandAst ]) { return $false }
50+ if ($args [0 ].CommandElements[0 ].Value -notmatch ' ^Invoke-PSFProtectedCommand$|^Write-PSFMessage$|^Stop-PSFFunction$' ) { return $false }
51+ if (-not ($args [0 ].CommandElements.ParameterName -match ' ^String$|^ActionString$' )) { return $false }
52+ $true
53+ }, $true )
54+
55+ foreach ($commandAst in $commandAsts )
56+ {
57+ $stringParam = $commandAst.CommandElements | Where-Object ParameterName -match ' ^String$|^ActionString$'
58+ $stringParamValue = $commandAst.CommandElements [($commandAst.CommandElements.IndexOf ($stringParam ) + 1 )].Value
59+
60+ $stringValueParam = $commandAst.CommandElements | Where-Object ParameterName -match ' ^StringValues$|^ActionStringValues$'
61+ if ($stringValueParam )
62+ {
63+ $stringValueParamValue = $commandAst.CommandElements [($commandAst.CommandElements.IndexOf ($stringValueParam ) + 1 )].Extent.Text
64+ }
65+ else { $stringValueParamValue = ' ' }
66+ [PSCustomObject ]@ {
67+ PSTypeName = ' PSModuleDevelopment.String.ParsedItem'
68+ File = $file.FullName
69+ Line = $commandAst.Extent.StartLineNumber
70+ CommandName = $commandAst.CommandElements [0 ].Value
71+ String = $stringParamValue
72+ StringValues = $stringValueParamValue
73+ }
74+ }
75+
76+ $validateAsts = $ast.FindAll ({
77+ if ($args [0 ] -isnot [System.Management.Automation.Language.AttributeAst ]) { return $false }
78+ if ($args [0 ].TypeName -notmatch ' ^PsfValidateScript$|^PsfValidatePattern$' ) { return $false }
79+ if (-not ($args [0 ].NamedArguments.ArgumentName -eq ' ErrorString' )) { return $false }
80+ $true
81+ }, $true )
82+
83+ foreach ($validateAst in $validateAsts )
84+ {
85+ [PSCustomObject ]@ {
86+ PSTypeName = ' PSModuleDevelopment.String.ParsedItem'
87+ File = $file.FullName
88+ Line = $commandAst.Extent.StartLineNumber
89+ CommandName = ' [{0}]' -f $validateAst.TypeName
90+ String = (($validateAst.NamedArguments | Where-Object ArgumentName -eq ' ErrorString' ).Argument.Value -split " \." , 2 )[1 ] # The first element is the module element
91+ StringValues = ' <user input>, <validation item>'
92+ }
93+ }
94+ }
95+ # endregion Find Keys : $foundKeys
96+
97+ # region Report Findings
98+ $totalResults = foreach ($languageFile in $languageFiles.Keys )
99+ {
100+ # region Phase 1: Matching parsed strings to language file
101+ $results = @ { }
102+ foreach ($foundKey in $foundKeys )
103+ {
104+ if ($results [$foundKey.String ])
105+ {
106+ $results [$foundKey.String ].Entries += $foundKey
107+ continue
108+ }
109+
110+ $results [$foundKey.String ] = [PSCustomObject ] @ {
111+ PSTypeName = ' PSmoduleDevelopment.String.LanguageFinding'
112+ Language = $languageFile
113+ Surplus = $false
114+ String = $foundKey.String
115+ StringValues = $foundKey.StringValues
116+ Text = $languageFiles [$languageFile ][$foundKey.String ]
117+ Line = " '{0}' = '{1}' # {2}" -f $foundKey.String , $languageFiles [$languageFile ][$foundKey.String ], $foundKey.StringValues
118+ Entries = @ ($foundKey )
119+ }
120+ }
121+ $results.Values
122+ # endregion Phase 1: Matching parsed strings to language file
123+
124+ # region Phase 2: Finding unneeded strings
125+ foreach ($key in $languageFiles [$languageFile ].Keys)
126+ {
127+ if ($key -notin $foundKeys.String )
128+ {
129+ [PSCustomObject ] @ {
130+ PSTypeName = ' PSmoduleDevelopment.String.LanguageFinding'
131+ Language = $languageFile
132+ Surplus = $true
133+ String = $key
134+ StringValues = ' '
135+ Text = $languageFiles [$languageFile ][$key ]
136+ Line = ' '
137+ Entries = @ ()
138+ }
139+ }
140+ }
141+ # endregion Phase 2: Finding unneeded strings
142+ }
143+ $totalResults | Sort-Object String
144+ # endregion Report Findings
145+ }
146+ }
0 commit comments