Private/Angular/Setup/Edit-NgModule.ps1
<############################################################################ # 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 } } |