src/module/Update-RootModuleUsingStatements.ps1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
using module .\.\MKModuleInfo.psm1
using module .\manifest\AutoUpdateSemVerDelegate.ps1

function Update-RootModuleUsingStatements {
    [CmdletBinding(PositionalBinding = $true, 
        DefaultParameterSetName = "ByPath")]
    Param
    (
        [Parameter(Mandatory = $false,
            Position = 0,
            ValueFromPipeline = $false, 
            ParameterSetName = "ByPath")]
        [string]$Path = '.',

        [Parameter(Mandatory = $false,
            Position = 1,
            ValueFromPipeline = $true, 
            ParameterSetName = "ByPipe")]
        [MKModuleInfo]$ModInfo,

        [Parameter(Mandatory = $false)]
        [string]$SourceFolderPath = 'src',

        [Parameter(Mandatory = $false)]
        [string[]]$Include = @('*.ps1', '*.psm1'),

        [Parameter(Mandatory = $false)]
        [string[]]$Exclude = '*.Tests.ps1',

        [switch]
        $PassThru
    )
    
    DynamicParam {
        return GetModuleNameSet -Position 0 -Mandatory 
    }

    begin {
        $Name = $PSBoundParameters['Name']

        # Prevents single space for each item in an iteration:
        # https://docs.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_preference_variables?view=powershell-6#ofs
        $OFS = ''
    }

    end {

        if ((-not $ModInfo) -and (-not $Name)) {
            $ModInfo = Get-MKModuleInfo -Path $Path
        }
        elseif (-not $ModInfo) {
            $ModInfo = Get-MKModuleInfo -Name $Name
        }
        
        AutoUpdateSemVerDelegate($ModInfo.Path)

        $TargetDirectory = Join-Path -Path $ModInfo.Path -ChildPath $SourceFolderPath -Resolve

        # $StopMatchingImportStatements: stop matching when there is a break of consecutive
        # 'using module' statements. a break with additional statements means that developer
        # manually added that line; so keep it.
        $RootModuleContent = Get-Content $ModInfo.RootModuleFilePath
        [string[]]$ModuleContentsCleaned = $RootModuleContent | `
            ForEach-Object -Begin {$StopMatchingImportStatements = $false} -Process {
            if ($StopMatchingImportStatements) {
                $($_ + "`n")
            }
            elseif (-not ($_ -match '(?<=(using module \.\\)).*(?=(\.ps1))')) {
                if ($_ -match '\S+') {
                    $($_ + "`n")
                }
                $StopMatchingImportStatements = $true
            }
        }

        $TargetFunctionsToExport = Get-ChildItem -Path $TargetDirectory -Include $Include -Exclude $Exclude -Recurse | `
            Get-Item -Include $Include -PipelineVariable File | `
            Get-Content -Raw | `
            ForEach-Object {
            $NoExportMatches = [regex]::Matches($_, '(?<=NoExport)(?:[:\s]*?)(?<sanitized>\w*-\w*)')
            $FunctionMatches = [regex]::Matches($_, '(?<=function )[\w]*[-][\w]*')
            for ($i = 0; $i -lt $FunctionMatches.Count; $i++) {
                $FunctionName = $FunctionMatches[$i].Value
                if (($NoExportMatches | ForEach-Object {$_.Groups['sanitized'].Value}) -notcontains $FunctionName) {
                    @{
                        FilePath     = $File
                        FunctionName = $FunctionName
                    }
                }
            }
        }

        [string[]]$UniqueSourceFiles = $TargetFunctionsToExport.FilePath.FullName | `
            Sort-Object -Unique | `
            Group-Object -Property {$_.Split("$SourceFolderPath\")[1]} | `
            Select-Object -ExpandProperty Group | `
            ForEach-Object {
            if ($_ -ne $ModInfo.RootModuleFilePath) {
                $("using module .$($_.Split($ModInfo.Path)[1])".Trim() + "`n")
            }
        }
    
        if ($ModuleContentsCleaned -eq $null) {
            $UpdatedModuleContentRaw = @"
$UniqueSourceFiles
"@

        }
        elseif ($ModuleContentsCleaned -ne $null) {
            $UpdatedModuleContentRaw = @"
$UniqueSourceFiles
$ModuleContentsCleaned
"@

        }
    
        @{
            ManifestPath            = $ModInfo.ManifestFilePath
            TargetFunctionsToExport = $TargetFunctionsToExport
        }

        if ($UpdatedModuleContentRaw) {
            $UpdatedModuleContent = $($UpdatedModuleContentRaw -split "`n").TrimEnd("`r")
        }
        else {
            $UpdatedModuleContent = @()
        }

        if (-not $RootModuleContent) {
            $RootModuleContent = @()
        }

        $Differences = Compare-Object -ReferenceObject $UpdatedModuleContent -DifferenceObject $RootModuleContent -PassThru
        
        # touch the file only when there is a difference
        if ($Differences) {
            Set-Content -Path $ModInfo.RootModuleFilePath -Value $UpdatedModuleContentRaw -PassThru:$PassThru.IsPresent -Encoding UTF8 -NoNewline
        }

        $OFS = ' '
    }
}