Private/BoxTurtleClasses.ps1

<############################################################################
 # Store metadata about a Visual Studio solution.
 # By convention, the solution has a nickname like "MyThing"
 # and the solution is stored in "MyThingSolution\MyThingSolution.sln"
 #
 # $nickName User supplied abbreviation for this whole solution
 # $solnDir Directory where .sln file lives
 # $solnFile Fully qualified path to .sln file
 # $solnName Name of *.sln file without path or .sln suffix
 ############################################################################>

class SolnInfo
{
    [string]$nickName
    [string]$solnDir
    [string]$solnFile
    [string]$solnName
    [WebCsprojInfo]$webCsprojInfo
    [ModelCsprojInfo]$modelCsprojInfo
    [CsprojInfo]$bizCsprojInfo
    [DbInfo]$dbInfo
    [string]$rootDir
    [string]$confFile
    $tableInfos

    SolnInfo() {
    }

    SolnInfo($nickName, $outputDir) {
        $this.solnName = "${nickName}Solution"
        $this.solnDir = $outputDir + "\" + $this.solnName
        $this.nickName = $nickName
        $this.solnFile = $this.solnDir + "\" + $this.solnName + ".sln"

        $this.rootDir = $outputDir
        $this.confFile = "$($this.rootDir)\boxTurtle.conf.json"
        $this.tableInfos = $null
    }

    
    [SolnInfo] static Load() {

        # Get boxturtle.conf.json from this directory tree
        [bool]$foundIt = $false
        [SolnInfo]$solnInfo = $null
        [string]$boxTurtleConfJson = ""
        [string]$boxTurlteConfJson = ""

        # Check dir, parent dir, grand-parent, etc. recursively
        [string]$myDir = (Get-Location)
        while($myDir.length -gt 3 <# because "c:\" is 3 characters#>) {
            # Make absolutely sure path ends in slash
            $myDir = $myDir.TrimEnd('\') + '\'

            $boxTurtleConfJson = "$($myDir)boxturtle.conf.json"
            #Write-Host "### Looking up for $boxTurtleConfJson"
            if(Test-Path $boxTurtleConfJson) {
                $foundIt = $true
                break
            } else {
                $parent = (Get-Item $myDir).Parent
                if($parent -eq $null) {
                    break 
                } else {
                    $myDir = [string]($parent.FullName)
                }
            }
        }

        # Try child directories. If one and only one subdir has it, use that
        [int]$subdirsWithConf = 0
        [string]$latestMatch = ""
        if($foundIt -eq $false) {
            $subdirs = get-childitem | where-object {$_.Psiscontainer -eq "True"} | select-object FullName
            foreach($subdir in $subdirs) {
                $myDir = $subdir.FullName
                $myDir = $myDir.TrimEnd('\') + '\'
                $temp = "$($myDir)boxTurtle.conf.json"
                #Write-Host "### Looking down for $temp in myDir=$($myDir)###"
                if(Test-Path $temp) {
                    $subdirsWithConf++
                    $latestMatch = $temp
                }
            }
            if($subdirsWithConf -eq 1) {
                $foundIt = $true
                $boxTurtleConfJson = $latestMatch
            }
        }

        if($foundIt -eq $false) {
            throw "Unable to find a unique boxturtle.conf.json in current directory '" + (Get-Location) + "' or lower"
        } else {
            Write-Host "### Using config $boxTurtleConfJson"
            [string]$jsonStr = Get-Content -raw $boxTurtleConfJson 
            $solnInfo = [SolnInfo](ConvertFrom-Json $jsonStr)
        }

        # Force this to be loaded explicitly when needed
        $solnInfo.tableInfos = $null

        return $solnInfo
    }


    SaveConf() {
        # Don't save table metadata, we'll reload each time to get it fresh
        $this.tableInfos = $null

        $this | ConvertTo-Json | Out-File $this.confFile
    }

    [System.Collections.Hashtable]GetTableInfos() {
        if($this.tableInfos -eq $null) {
            $this.tableInfos = New-PsModel $this.dbInfo
        }
        return $this.tableInfos
    }

    [CsprojInfo]GetProjInfoByName([string]$projName) {
        if(($this.webCsprojInfo -ne $null) -and ($this.webCsprojInfo.csprojName -eq $projName)) {
            return $this.webCsprojInfo
        } elseif(($this.modelCsprojInfo -ne $null) -and ($this.modelCsprojInfo.csprojName -eq $projName)) {
            return $this.modelCsprojInfo
        } elseif(($this.bizCsprojInfo -ne $null) -and ($this.bizCsprojInfo.csprojName -eq $projName)) {
            return $this.bizCsprojInfo
        } else {
            throw "cannot find project named '$projName'"
        }
    }
}

<############################################################################
 # Store metadata about a Visual Studio project.
 # By convention, it has a name csprojName like "MyProject"
 # and the project will be $solnInfo.solnDir\MyProject\MyProject.csproj
 #
 # $csprojName Name of project without path or .csproj suffix
 # $csprojDir Directory where .csproj file lives
 # $csprojFile Fully qualified path to .csproj file
 # $namespace Default C# namespace to use for this project
 ############################################################################>

class CsprojInfo
{
    [string]$csprojName
    [string]$csprojDir
    [string]$csprojFile
    [string]$namespace

    CsprojInfo() {
    }

    CsprojInfo([SolnInfo] $solnInfo, [string]$csprojName) {
        $this.csprojName = $csprojName
        $this.csprojDir = "{0}\{1}" -f $solnInfo.solnDir, $csprojName
        $this.csprojFile = "{0}\{1}.csproj" -f $this.csprojDir, $this.csprojName
        $this.namespace = $csprojName
    }
}

<############################################################################
 # Subclass of CsprojInfo, but for ASP DotNet Core websites with angular.
 #
 # $angularDir Directory where angular is stored, by convention just inside C# web project
 # $angularModelDir Directory where angular model TypeScripts are stored, by convention "$($webcsprojInfo.csprojDir)\ClientApp\app\model"
 # $angularServiceDir Directory where angular Service TypeScripts are stored, by convention "$($webcsprojInfo.csprojDir)\ClientApp\app\Service"
 # $angularComponentDir Directory where angular Component TypeScripts are stored, by convention "$($webcsprojInfo.csprojDir)\ClientApp\app\Component"
 ############################################################################>

class WebCsprojInfo : CsprojInfo
{
    [string]$angularDir
    [string]$angularAppDir
    [string]$angularModelDir
    [string]$angularServiceDir
    [string]$angularComponentDir
    [string]$appModuleFile
    [string]$appRoutingFile
    [string]$angularStyle
    [string]$styleCss

    WebCsprojInfo() {
    }

    WebCsprojInfo([SolnInfo] $solnInfo, [string]$csprojName, [string]$angularStyle):base($solnInfo, $csprojName) { 
        $this.angularStyle = $angularStyle
        if($angularStyle -eq 'ANGULAR_IO') {
            # It's Angular.io style against MS Web API
            $this.angularDir = "$($this.csprojDir)\angular"
            $this.angularAppDir = "$($this.csprojDir)\angular\src\app"
            $this.appModuleFile = "$($this.angularAppDir)\app.module.ts"
            $this.appRoutingFile = "$($this.angularAppDir)\app-routing.module.ts"
            $this.styleCss = "$($this.angularDir)/src/styles.css"
        } else {
            # It's microsoft "dotnet new angular" style against MS MVC
            $this.angularDir = "$($this.csprojDir)"
            $this.angularAppDir = "$($this.csprojDir)\ClientApp\app"
            $this.appModuleFile = "$($this.angularAppDir)\app.module.shared.ts"
            $this.appRoutingFile = "$($this.angularAppDir)\app.module.shared.ts"
            $this.styleCss = "$($this.angularDir)/src/styles.css"
        }
        
        $this.angularModelDir = "$($this.angularAppDir)\model"
        $this.angularServiceDir = "$($this.angularAppDir)\service"
        $this.angularComponentDir = "$($this.angularAppDir)\component"
    }
}


<############################################################################
 # Subclass of CsprojInfo, but for ASP DotNet Core entity framework projects.
 #
 ############################################################################>

class ModelCsprojInfo : CsprojInfo
{
    [string]$efContextName
    [string]$efWithViewContextName
    [string]$contextName
    [string]$efNamespace
    [string]$efWithViewNamespace

    ModelCsprojInfo() {
    }

    ModelCsprojInfo([SolnInfo] $solnInfo, [string]$csprojName):base($solnInfo, $csprojName) { 
        $this.efContextName = "$($solnInfo.nickName)Context"
        $this.efWithViewContextName = "$($solnInfo.nickName)WithViewContext"
        $this.contextName = $this.efWithViewContextName
        $this.efNamespace = "$($this.namespace).Model"
        $this.efWithViewNamespace = "$($this.namespace).ModelWithView"
    }
}

<############################################################################
 # Database connection info
 #
 # $dbServer Database server name
 # $db Database name
 # $connStr C# database connection string
 ############################################################################>

class DbInfo
{
    [string]$dbServer
    [string]$db
    [string]$connStr
    
    DbInfo() {
    }

    DbInfo([SolnInfo]$solnInfo, $dbServer, $db) {
        $this.dbServer = $dbServer
        $this.db = $solnInfo.nickName 
        $this.connStr = "Server=${dbServer};Database=${db};Trusted_Connection=True" 
    }
}

<############################################################################
 # Metadata about database column used to generate angular ORM stuff and some C# stuff
 #
 # $columnCapitalCamel Column name in capitalized format like "ClientFirstName"
 # $columnLowerCamel Column name in not-capitalized format like "clientFirstName"
 # $dataType Column data type in SQL Server syntax
 # $isNullable True if column is nullable
 # $maxLength Max char length in database
 # $caption Column name as a caption "Client First Name"
 ############################################################################>

class ColumnInfo
{
    [string]$columnCapitalCamel
    [string]$columnLowerCamel
    [string]$dataType
    [bool]$isNullable
    [bool]$isPrimaryKey
    [int]$maxLength
    [string]$caption

    ColumnInfo([string]$column, [string]$dataType, [bool]$isNullable, [int]$maxLength, [bool]$isPrimarykey) {
        $this.columnCapitalCamel = (ConvertTo-CapitalCamelCase $column)
        $this.caption = (ConvertTo-TitleCase $column)
        $this.columnLowerCamel = (ConvertTo-LowerCamelCase $column)
        $this.dataType = $dataType
        $this.isNullable = $isNullable
        $this.maxLength = $maxLength
        $this.isPrimaryKey = $isPrimaryKey
    }
}

<############################################################################
 # Metadata about database table used to generate angular ORM stuff and some C# stuff
 #
 # $tablePretty Table name like "Customer Account"
 # $tableCapitalCamel Table name like "CustomerAccount"
 # $tableLowerCamel Table name like "customerAccount"
 # $tableLowerKebab Table name like "customer-account"
 # $columnInfos Array of ColumnInfos describing columns
 ############################################################################>

class TableInfo
{
    [string]$tablePretty
    [string]$tableCapitalCamel
    [string]$tableLowerCamel
    [string]$tableLowerKebab
    [ColumnInfo[]]$columnInfos
    [bool]$isView

    TableInfo([string]$entity, [ColumnInfo[]]$columnInfos, [bool]$isView) {
        $this.tableCapitalCamel = (ConvertTo-CapitalCamelCase $entity)
        $this.tableLowerCamel = (ConvertTo-LowerCamelCase $entity)
        $this.tableLowerKebab = (ConvertTo-KebabCase $entity)
        $this.tablePretty = (ConvertTo-TitleCase $entity)
        $this.columnInfos = $columnInfos
        $this.isView = $isView
    }
}