Private/Angular/Setup/Edit-NgModule.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 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 |
<############################################################################ # Update angular app.module.ts to include a new angular component # with a new "import" statement and add class to "declarations" section ############################################################################> Function Edit-NgModuleAddComponent([WebCsprojInfo]$webCsprojInfo, [string]$className, [string]$importFromFile) { Edit-NgModule $webCsprojInfo.appModuleFile "$($className)" $importFromFile "declarations: \[" "$($className)" } <############################################################################ # Update angular app.module.ts to include a new angular service # with a new "import" statement and add class to "providers" section ############################################################################> Function Edit-NgModuleAddService([WebCsprojInfo]$webCsprojInfo, [string]$className, [string]$importFromFile) { # Make sure a "providers" section exists Edit-NgModuleAddSection $webCsprojInfo.appModuleFile "providers" Edit-NgModule $webCsprojInfo.appModuleFile "$($className)" $importFromFile "providers: \[" "$($className)" } <############################################################################ # Update angular app.module.ts to include importing a new library # with a new "import" statement and add class to "imports" section ############################################################################> Function Edit-NgModuleAddImport([WebCsprojInfo]$webCsprojInfo, [string]$className, [string]$importFromFile, [string]$classToInsert) { if([string]::IsNullOrWhitespace($classToInsert)) { $classToInsert = $className } Edit-NgModule $webCsprojInfo.appModuleFile "$($className)" $importFromFile "imports: \[" "$classToInsert" } <############################################################################ # Update angular app-routing.module.ts with new route and # importing the component with an "import" statement ############################################################################> Function Edit-NgModuleAddRoute([WebCsprojInfo]$webCsprojInfo, [string]$className, [string]$importFromFile, [string]$desiredUrl) { if($webCsprojInfo.angularStyle -eq "ANGULAR_IO") { Edit-NgModule $webCsprojInfo.appRoutingFile "$($className)" $importFromFile "const routes: Routes = \[" " { path: '$($desiredUrl)', component: $($className) }" } else { Edit-NgModule $webCsprojInfo.appRoutingFile "$($className)" $importFromFile "RouterModule.forRoot\(\[" " { path: '$($desiredUrl)', component: $($className) }" # Make sure default route is last in route list Edit-NgRouteListDefault $webCsprojInfo.appRoutingFile } } <############################################################################ # Update angular app.module.ts or app-routing.module.ts by adding a # new "import { ..." line up top and adding a class or string to one of the other # JSON arrays in the body of the file. Update the file in place. # # Arguments: # - $moduleFileName - name of app.module.ts or app-routing.module.ts file fully qualified # - $classNamem - name of TypeScript class we want to add, e.g. a component or service # - $importFromRelFile - relative path to location of source for this class in Angular terms # - $headerPattern - look for this line like "imports: [ ", and add an entry to this section # - $insertMe - string to add, either a Component, or maybe a routing line # # Algorithm: # - first update file so all TypeScript array markers "[" have a newline after # and "]" have a newline before. This makes our simple parser work better # - keep track of each line that looks like "import { ....", and record the index # of that last line that matches that pattern. We'll insert a new import directly # after this # - then look for the line that matches the header, e.g. "import: [ " # - once we're inside the header, look for the closing "]", but keep the index # of the last nonblank line within that section, possibly none if the array is empty. # - some of the middle lines of the section will have children arrays, e.g. # @NgModule({ // ignore this line # imports: [ // header matches, we're in the right section # BrowserModule, // in section, haven't found closing "]" yet # FormsModule, // in section, haven't found closing "]" yet # AgGridModule.withComponents( [ // in section, start of child array # 'RedComponent' // in section, middle of child array # ]), // in section, end of child array *NOT* end of section # FormsModule // out of child array, back in main section, this is the last nonblank line # // in main section, blank row # ], // end of main section # In this case we'll add a comma after "FormsModule" and insert a new entry after this. # # NOTES: # - this is *NOT* a real parser, that was way too hard. # - it's possible that other valid TypeScript will mess up this trivial algorithm ############################################################################> Function Edit-NgModule([string]$moduleFileName, [string]$className, [string]$importFromRelFile, [string]$headerPattern, [string]$insertMe) { # Force a newline after "[" and before "]" to make parsing easier (Get-Content $moduleFileName) ` -replace '(\[)(.*)([^ \t])(.*)$', "`$1`r`n`t`$2`$3`$4" ` -replace '^(.*)([^ \t])(.*)(\])', "`$1`$2`$3`r`n`t`$4" | Out-FileUtf8NoBom $moduleFileName $lines = (Get-Content $moduleFileName) # Go through all lines, find the last one that matches "import {..." $lastImportAt = -1; $thisLine = 0; $alreadyThere = $false foreach($line in $lines) { if($line -match "import\s+{\s*$className\s*}") { # Hey it's already there, don't add it $alreadyThere = $true } elseif($line -match "import {") { $lastImportAt = $thisLine; } $thisLine++; } if(-not $alreadyThere) { # Insert new import after last $lastImportLine = $lines[$lastImportAt] $lines[$lastImportAt] = $lastImportLine + "`r`nimport { $className } from '$importFromRelFile';" } # Look for header e.g."imports: [", # and find last nonblank entry before "]", # but ignore children records like "AgGridModule.withComponents([`r`n])" $thisLine = 0; $headerIndex = -1 $footerIndex = -1 $lastNonBlankIndex = -1 $inChild = $false $alreadyThere = $false foreach($line in $lines) { if($headerIndex -eq -1) { # haven't started yet if($line -match $headerPattern) { # Matched header, let's track it $headerIndex = $thisLine; } } else { # we're in the section # check if lines are the same, ignoring commas and after trimming if($line.Trim().ToUpper().Replace(",", "") -eq $insertMe.Trim().ToUpper().Replace(",", "") ) { $alreadyThere = $true break } if( ($inChild -eq $false) -and ($line -match "\[") ) { $inChild = $true } elseif( ($inChild -eq $true) -and ($line -match "\]") ) { $inChild = $false $lastNonBlankIndex = $thisLine } elseif( ($inChild -eq $false) -and ($line -match "\]") ) { $footerIndex = $thisLine break } else { if(-not [string]::IsNullOrWhitespace($line)) { $lastNonBlankIndex = $thisLine } } } $thisLine++ } if(-not $alreadyThere) { if($headerIndex -eq -1) { throw "Can't find start sequence '$headerPattern' when attempting to update '$moduleFileName'" } if($footerIndex -eq -1) { throw "Can't find end sequence ']' for start sequence '$headerPattern' when attempting to update '$moduleFileName'" } if($inChild -eq $true) { throw "Got confused in child entry for start sequence '$headerPattern' when attempting to update '$moduleFileName'" } # Special case - was array empty? if($lastNonBlankIndex -eq -1) { # Array was empty, but brackets were there $lines[$headerIndex] = "`t`t" + $lines[$headerIndex] + "`r`n`t$($insertMe)" } else { $priorLastLine = $lines[$lastNonBlankIndex].Trim() # Add comma to end of last line, soon to be second-to-last-line, if needed if(-not ($priorLastLine -match ",\s*$")) { $priorLastLine = "`t`t$($priorLastLine), " } # Add new line to end of this line $priorLastLine = $priorLastLine + "`r`n`t`t$($insertMe)" $lines[$lastNonBlankIndex] = $priorLastLine } } $lines | Out-FileUtf8NoBom $moduleFileName } <############################################################################ # Update angular app.module.ts or related to make sure it has a "providers" # section or whatever section we pass in ############################################################################> Function Edit-NgModuleAddSection([string] $appModuleFile, [string]$sectionName) { [string]$contents = Get-Content -raw $appModuleFile if(-not ($contents -match "$($sectionName):")) { # "providers:" section is missing, add it $lines = Get-Content $appModuleFile $index = 0 $exportClassLine = -1 for($index = 0; $index -lt $lines.length; $index++) { if($lines[$index] -match "export class") { $exportClassLine = $index break } } if($exportClassLine -ge 0) { # We found the bottom "export class" line # if previous line is "})" and prior to that "]", # that's where we'll add this if($lines[$exportClassLine - 1] -match "^\s*}\s*\)\s*$") { if($lines[$exportClassLine - 2] -match "^\s*\]\s*,\s*$") { # Found final "]" and it already had a comma $lines[$exportClassLine - 2] = $lines[$exportClassLine - 2] + "`r`n $($sectionName): [`r`n ]" } elseif($lines[$exportClassLine - 2] -match "^\s*\]\s*$") { # Found final "]" and it doesn't have a comma $lines[$exportClassLine - 2] = $lines[$exportClassLine - 2] + ", `r`n $($sectionName): [`r`n ]" } $lines | Out-FileUtf8NoBom $appModuleFile } } } } <############################################################################ # Move ** route to bottom of route list. This can happen with generated code. # Update file in place. # # Example: # # RouterModule.forRoot([ # { path: '', redirectTo: 'home', pathMatch: 'full' }, # { path: 'home', component: HomeComponent }, # { path: '**', redirectTo: 'home' }, # <------ this is bad, all further routes ignored, move to end # { path: 'counter', component: CounterComponent },= # ]) ############################################################################> Function Edit-NgRouteListDefault([string] $appModuleFile) { [string[]]$lines = Get-Content $appModuleFile [int]$index = -1 [int]$starStarIndex = -1 [int]$lastPathRouteIndex = -1 for($index = 0; $index -lt $lines.Length; $index++) { $line = $lines[$index] if($line -match "path: '\*\*'") { $starStarIndex = $index } if($line -match "path: '") { $lastPathRouteIndex = $index } } if( ($starStarIndex -gt 0) -and ($lastPathRouteIndex -gt 0) -and ($lastPathRouteIndex -gt $starStarIndex) ) { # We have a problem, routes below ** are skipped if($lines[$lastPathRouteIndex] -match ",\s*$") { # last route already has a final comma } else { $lines[$lastPathRouteIndex] = $lines[$lastPathRouteIndex] + "," } $starStarLine = $lines[$starStarIndex] $lines[$lastPathRouteIndex] = $lines[$lastPathRouteIndex] + "`r`n" + $starStarLine # Remove old ** line, convert to ArrayList first $linesAsArrayList = [System.Collections.ArrayList]$lines $linesAsArrayList.RemoveRange($starStarIndex, 1) $lines = [string[]]$linesAsArrayList $lines | Out-FileUtf8NoBom $appModuleFile } } |