PsKrane.psm1

Enum ProjectType {
    Module
    Script
}

Enum ItemFileType{
    Class
    PublicFunction
    PrivateFunction
}

Enum KraneTemplateType{
    Class
    Function
    Script
    Test
}

Class KraneFile {
    #CraneFile is a class that represents the .Krane.json file that is used to store the configuration of the Krane project.
    [System.IO.FileInfo]$Path
    [System.Collections.Hashtable]$Data = @{}
    [Bool]$IsPresent

    KraneFile([String]$Path) {

        #Handeling case when Path doesn't exists yet (For creation scenarios)
        $Root = ""
        if ((Test-Path -Path $Path) -eq $False) {
            if ($Path.EndsWith(".krane.json")) {
                [System.Io.DirectoryInfo]$Root = ([System.Io.FileInfo]$Path).Directory

            }
            else {
                [System.Io.DirectoryInfo]$Root = $Path
            }
        }
        else {
            #Path exists. We need to determine if it is a file or a folder.
            $Item = Get-Item -Path $Path

            if ($Item.PSIsContainer) {
                $Root = $Item
            }
            else {
                $Root = $Item.Directory
            }
        }
        


        $this.Path = Join-Path -Path $Root.FullName -ChildPath ".krane.json"
        $this.IsPresent = $this.Path.Exists
        if (!$this.Path.Exists) {
            #Krane file doesn't exists. No point in importing data from a file that doesn't exists.
            $this.Data = @{}
            return
        }
        $Raw = Get-Content -Path $This.Path.FullName -Raw | ConvertFrom-Json

        #Convert the JSON to a hashtable as it is easier to manipulate.

        foreach ($key in $Raw.PsObject.Properties) {
            $this.Data.$($key.Name) = $key.Value
        }
    }

    [String]Get([String]$Key) {
        return $this.Data.$Key
    }

    [Void]Set([String]$Key, [String]$Value) {
        $this.Data.$Key = $Value
    }

    [Void]Save() {
        if (!($this.Path.Exists)) {

            $Null = [System.Io.Directory]::CreateDirectory($this.Path.Directory.FullName) | Out-Null
        }
        $this.Data | ConvertTo-Json | Out-File -FilePath $this.Path.FullName -Encoding utf8 -Force
        $this.Path.Refresh()
        $this.IsPresent = $this.File.Exists
    }

    [void]Fetch() {
        $Raw = Get-Content -Path $This.Path.FullName -Raw | ConvertFrom-Json

        #Convert the JSON to a hashtable as it is easier to manipulate.

        foreach ($key in $Raw.PsObject.Properties) {
            $this.Data.$($key.Name) = $key.Value
        }
        $this.Path.Refresh()
        $this.IsPresent = $this.Path.Exists
    }

    [String]ToString() {
        return "ProjectName:{0} ProjectType:{1}" -f $this.Get("Name"), $this.Get("ProjectType")
    }

    static [KraneFile] Create([System.IO.DirectoryInfo]$Path, [String]$Name, [ProjectType]$Type) {
        $KraneFile = [KraneFile]::New($Path)
        if ($KraneFile.Path.Exists) {
            Throw ".Krane File $($KraneFile.Path.FullName) already exists"
        }

        $KraneFile.Set("Name", $Name)
        $KraneFile.Set("ProjectType", $Type)
        $KraneFile.Set("ProjectVersion", "0.0.1")
        $KraneFile.Save()

        Return $KraneFile
    }

}

Class KraneProject {
    [System.IO.DirectoryInfo]$Root
    [KraneFile]$KraneFile
    [ProjectType]$ProjectType
    [String]$ProjectVersion
    [System.IO.DirectoryInfo]$TemplatesPath
    [KraneTemplateCollection]$Templates = [KraneTemplateCollection]::New()


    KraneProject() {}

    KraneProject([System.IO.DirectoryInfo]$Root) {

        $this.KraneFile = [KraneFile]::New($Root)
        $this.ProjectVersion = $this.KraneFile.Get("ProjectVersion")

    }

    AddItem([String]$Name, [String]$Type) {
        throw "Must be overwritten!"
        #Add an item to the project. The item can be a script, a module, a test, etc.
    }

    hidden [void] LoadTemplates() {
        #Load the templates from the templates folder
        $AllModuleTemplates = Get-ChildItem -Path "$($PSScriptRoot)\Templates" -Filter "*.KraneTemplate.ps1"
        foreach ($TemplateFile in $AllModuleTemplates) {
            $Template = [KraneTemplate]::New($TemplateFile)
            $Template.SetLocation([LocationType]::Module)
            $this.Templates.Templates.Add($Template)
        }
        $AllSystemTemplates = $null


        if ($global:PSVersionTable.os -match '^.*Windows.*$' ) {
            $AllSystemTemplates = Get-ChildItem -Path "$($env:ProgramData)\PsKrane\Templates" -Filter "*.KraneTemplate.ps1" -ErrorAction SilentlyContinue
        }
        elseif ($env:IsLinux) {
            $AllSystemTemplates = Get-ChildItem -Path " /opt/PsKrane/Templates" -Filter "*.KraneTemplate.ps1" -ErrorAction SilentlyContinue
        }
        elseif ($env:IsMacOS) {
            $AllSystemTemplates = Get-ChildItem -Path "/Applications/PsKrane/Templates" -Filter "*.KraneTemplate.ps1" -ErrorAction SilentlyContinue
        }

        foreach ($TemplateFile in $AllSystemTemplates) {
            $Template = [KraneTemplate]::New($TemplateFile)
            $Template.SetLocation([LocationType]::Customer)
            $this.Templates.Templates.Add($Template)
        }

        $ProjectRootFolder = $this.Root.FullName
        [System.Io.DirectoryInfo] $TemplatesProjectFolder = Join-Path -Path $ProjectRootFolder -ChildPath "Krane/Templates"
        if ($TemplatesProjectFolder.Exists) {
            $AllProjectTemplates = Get-ChildItem -Path $TemplatesProjectFolder.FullName -Filter "*.KraneTemplate.ps1"
            foreach ($TemplateFile in $AllProjectTemplates) {
                $Template = [KraneTemplate]::New($TemplateFile)
                $Template.SetLocation([LocationType]::Project)
                $this.Templates.Templates.Add($Template)
            }
        }
    }

    [System.Io.DirectoryInfo] GetTestsFolderPath() {
        return Join-Path -Path $this.Root.FullName -ChildPath "Tests"
    }

    [System.Io.DirectoryInfo] GetSourcesFolderPath() {
        return Join-Path -Path $this.Root.FullName -ChildPath "Sources"
    }

    [System.Io.DirectoryInfo] GetOutputsFolderPath() {
        return Join-Path -Path $this.Root.FullName -ChildPath "Outputs"
    }

    [System.Io.DirectoryInfo] GetBuildFolderPath() {
        return Join-Path -Path $this.Root.FullName -ChildPath "Outputs"
    }
}

Class KraneFileStructure {
    [System.IO.DirectoryInfo]$RootFolder
    [System.IO.DirectoryInfo]$BuildFolder
    [System.IO.DirectoryInfo]$SourcesFolder
    [System.IO.DirectoryInfo]$TestsFolder
    [System.IO.DirectoryInfo]$OutputsFolder
    [System.IO.DirectoryInfo]$Modulefolder
    [System.IO.DirectoryInfo]$NugetFolder

    KraneFileStructure([System.IO.DirectoryInfo]$Root) {
        $this.RootFolder = $Root
        $this.BuildFolder = Join-Path -Path $Root.FullName -ChildPath "Build"
        $this.SourcesFolder = Join-Path -Path $Root.FullName -ChildPath "Sources"
        $this.TestsFolder = Join-Path -Path $Root.FullName -ChildPath "Tests"
        $this.OutputsFolder = Join-Path -Path $Root.FullName -ChildPath "Outputs"
        $this.Modulefolder = Join-Path -Path $this.OutputsFolder.FullName -ChildPath "Module"
        $this.NugetFolder = Join-Path -Path $this.OutputsFolder.FullName -ChildPath "Nuget"
    }

    [string] ToString(){
        return "RootFolder -> {0}" -f " $($this.RootFolder.FullName)"
    }
}

Class KraneModule : KraneProject {
    [String]$ModuleName
    hidden [System.IO.FileInfo]$ModuleFile
    hidden [System.IO.FileInfo]$ModuleDataFile
    hidden [System.IO.DirectoryInfo]$Build
    hidden [System.IO.DirectoryInfo]$Sources
    hidden [System.IO.DirectoryInfo]$Tests
    hidden [System.IO.DirectoryInfo]$Outputs
    [String[]] $Tags = @( 'PSEdition_Core', 'PSEdition_Desktop' )
    [String]$Description
    [String]$ProjectUri
    [Bool]$IsGitInitialized
    [PsModule]$PsModule
    [TestHelper]$TestData
    [KraneFileStructure]$FileStructure
    Hidden [System.Collections.Hashtable]$ModuleData = @{}

    #Add option Overwrite

    KraneModule([System.IO.DirectoryInfo]$Root) {
        #When the module Name is Not passed, we assume that a .Krane.json file is already present at the root.
        Write-Debug "[KraneModule][Constructor([System.IO.DirectoryInfo]Root)] Start"
        Write-Debug "[KraneModule][Constructor([System.IO.DirectoryInfo]Root)] Creating module from root -> $($Root.FullName)"
        $this.KraneFile = [KraneFile]::New($Root)
        $this.ProjectType = [ProjectType]::Module
        #TODO Remove root, build, sources, tests and ouptuts and use $this.FilteStructure instead
        $this.Root = $Root
        $this.Build = "$($Root.FullName)\Build"
        $this.Sources = "$($Root.FullName)\Sources"
        $this.Tests = "$($Root.FullName)\Tests"
        $this.Outputs = "$($Root.FullName)\Outputs"
        $this.FileStructure = [KraneFileStructure]::New($Root)
        $this.LoadTemplates()
        #get the module name from the krane file
        
        $this.TestData = [PesterTestHelper]::New($this.Tests)
        $this.SetDescription("Created with Love using PsKrane")
        $mName = $this.KraneFile.Get("Name")
        $this.SetModuleName($mName)
        $this.ProjectType = $this.KraneFile.Get("ProjectType")
        $this.FetchModuleInfo()
        $this.FetchGitInitStatus()
        Write-Debug "[KraneModule][Constructor([System.IO.DirectoryInfo]Root)] End"
    }
    
    KraneModule([System.IO.DirectoryInfo]$Root, [String]$ModuleName) {
        #When the module Name is passed, we assume that the module is being created, and that there is not a .Krane.json file present. yet.
        #$this.KraneFile = [KraneFile]::New($Root)
        $Root = Join-Path -Path $Root -ChildPath $ModuleName
        $This.KraneFile = [KraneFile]::Create($Root, $ModuleName, [ProjectType]::Module)
        $this.ProjectType = [ProjectType]::Module
        #TODO Remove root, build, sources, tests and ouptuts and use $this.FilteStructure instead
        $this.Root = $Root
        $this.Build = "$Root\Build"
        $this.Sources = "$Root\Sources"
        $this.Tests = "$Root\Tests"
        $this.Outputs = "$Root\Outputs"
        $this.ModuleName = $ModuleName
        $this.SetDescription("Created with Love using PsKrane")
        $this.FileStructure = [KraneFileStructure]::New($Root)
        $this.ProjectVersion = $this.GetProjectVersion()
        $this.LoadTemplates()
        $this.FetchModuleInfo()
        $this.FetchGitInitStatus()
    }

    hidden [void] FetchModuleInfo() {

        if (($null -eq $this.ModuleName)) {
            Throw "Module Name not provided."
        }
        
        $this.SetModuleName($this.ModuleName)

        if ($this.ModuleDataFile.Exists) {
            $this.ModuleData = Import-PowerShellDataFile -Path $this.ModuleDataFile.FullName
            $this.Description = $this.ModuleData.Description
            $this.ProjectUri = $this.ModuleData.PrivateData.PsData.ProjectUri
            $this.Tags = $this.ModuleData.PrivateData.PsData.Tags
        }
        
        $this.PsModule = [PsModule]::New($this.ModuleFile.FullName)
        
    }

    [void] BuildModule() {
        
        Write-Verbose "[KraneModule][BuildModule] Start"
        Write-Verbose "[KraneModule][BuildModule][PSM1] Starting PSM1 Operations $($this.ModuleName)"
        if ($this.ModuleFile.Exists) {
            Write-Verbose "[KraneModule][BuildModule][PSM1] Module file already exists. Deleting."
            $this.ModuleFile.Delete()
            $this.ModuleFile.Refresh()
        }

        Write-Verbose "[KraneModule][BuildModule][PSM1] (Re)creating file $($this.ModuleFile.FullName)"
        $Null = New-Item -Path $this.ModuleFile.FullName -ItemType "file" -Force
        

        $MainPSM1Contents = @()
        Write-Verbose "[KraneModule][BuildModule][PSM1] Searching for classes and functions"


        [System.IO.FileInfo]$PreContentPath = Join-Path -Path $this.Sources.FullName -ChildPath "PreContent.ps1"
        If ($PrecontentPath.Exists) {
    
            Write-Verbose "[KraneModule][BuildModule][PSM1] Precontent.ps1 file found. Adding to module file."
            $MainPSM1Contents += $PreContentPath

        }
        else {
            Write-Verbose "[KraneModule][BuildModule][PSM1] No Precontent detected."

        }


        [System.IO.DirectoryInfo]$ClassFolderPath = Join-Path -Path $this.Sources.FullName -ChildPath "Classes"
        If ($ClassFolderPath.Exists) {
            
            $PublicClasses = Get-ChildItem -Path $ClassFolderPath.FullName -Filter *.ps1 | sort-object Name
            if ($PublicClasses) {
                write-Verbose "[KraneModule][BuildModule][PSM1] Classes Found. Importing..."
                $MainPSM1Contents += $PublicClasses
            }

        }

        [System.IO.DirectoryInfo]$PrivateFunctionsFolderPath = Join-Path -Path $this.Sources.FullName -ChildPath "Functions/Private"
        If ($PrivateFunctionsFolderPath.Exists) {
            $Privatefunctions = Get-ChildItem -Path $PrivateFunctionsFolderPath.FullName -Filter *.ps1 | sort-object Name

            if ($Privatefunctions) {
                write-Verbose "[KraneModule][BuildModule][PSM1] Private functions Found. Importing..."
                $MainPSM1Contents += $Privatefunctions
            }

        }

        $Publicfunctions = $null
        [System.IO.DirectoryInfo]$PublicFunctionsFolderPath = Join-Path -Path $this.Sources.FullName -ChildPath "Functions/Public"
        If ($PublicFunctionsFolderPath.Exists) {
            $Publicfunctions = Get-ChildItem -Path $PublicFunctionsFolderPath.FullName -Filter *.ps1 | sort-object Name
            
            if ($Publicfunctions) {
                write-Verbose "[KraneModule][BuildModule][PSM1] Public functions Found. Importing..."
                $MainPSM1Contents += $Publicfunctions
            }

        }

        [System.IO.FileInfo]$PostContentPath = Join-Path -Path $this.Sources.FullName -ChildPath "postContent.ps1"
        If ($PostContentPath.Exists) {
            write-Verbose "[KraneModule][BuildModule][PSM1] Postcontent Found. Importing..."

            $MainPSM1Contents += $PostContentPath
        }
        

        #Creating PSM1
        
        write-Verbose "[KraneModule][BuildModule][PSM1] Building PSM1 content"
        Foreach ($file in $MainPSM1Contents) {
            write-Verbose "[KraneModule][BuildModule][PSM1] Adding -> $($File.FullName)"
            Get-Content $File.FullName | out-File -FilePath $this.ModuleFile.FullName -Encoding utf8 -Append
    
        }

        Write-verbose "[KraneModule][BuildModule][PSD1] Starding PSD1 actions. Adding functions to export"

        if (!$this.ModuleDataFile.Exists) {
            Write-verbose "[KraneModule][BuildModule][PSD1] Module Manifest not found. Creating one."
            New-ModuleManifest -Path $this.ModuleDataFile.FullName
        }

        $ManifestParams = @{}
        $ManifestParams.Path = $this.ModuleDataFile.FullName
        $ManifestParams.FunctionsToExport = $Publicfunctions.BaseName
        $ManifestParams.Tags = $This.Tags
        $ManifestParams.RootModule = $this.ModuleFile.Name

        if($this.ProjectUri) {
            $ManifestParams.ProjectUri = $this.ProjectUri
        }
        
        if($this.Description) {
            $ManifestParams.Description = $this.Description
        }

        $ManifestParams.ModuleVersion = $this.ProjectVersion

        Write-verbose "[KraneModule][BuildModule][PSD1] Writing Manifest settings:"

        foreach ($ManifestSetting in $ManifestParams.GetEnumerator()) {
            Write-Verbose "[KraneModule][BuildModule][PSD1][Setting] $($ManifestSetting.Key) -> $($ManifestSetting.Value)"
        }

        try {
            Update-ModuleManifest @ManifestParams
        }
        Catch {
            Write-Error "[KraneModule][BuildModule][PSD1] Error updating module manifest. $_"
        }

        Write-Verbose "[KraneModule][BuildModule] End"

    }
    
    [void] SetModuleName([String]$ModuleName) {
        $this.ModuleName = $ModuleName
        $this.ModuleFile = Join-Path -Path $this.Outputs.FullName -ChildPath "Module\$($ModuleName).psm1"
        $this.ModuleDataFile = Join-Path -Path $this.Outputs.FullName -ChildPath "Module\$($ModuleName).psd1"
    }

    [void] CreateBaseStructure() {
        if ($this.Outputs.Exists -eq $false) {
            $Null = New-Item -Path $this.Outputs.FullName -ItemType "directory"
        }

        if ($this.Build.Exists -eq $false) {
            $Null = New-Item -Path $this.Build.FullName -ItemType "directory"
        }

        if ($this.Sources.Exists -eq $false) {
            $Null = New-Item -Path $this.Sources.FullName -ItemType "directory"
        }

        [System.IO.DirectoryInfo] $PrivateFunctions = Join-Path -Path $this.Sources.FullName -ChildPath "Functions/Private"
        if ($PrivateFunctions.Exists -eq $false) {
            $Null = New-Item -Path $PrivateFunctions.FullName -ItemType "directory"
        }
        
        [System.IO.DirectoryInfo] $PublicFunctions = Join-Path -Path $this.Sources.FullName -ChildPath "Functions/Public"
        if ($PublicFunctions.Exists -eq $false) {
            $Null = New-Item -Path $PublicFunctions.FullName -ItemType "directory"
        }

        if ($this.Tests.Exists -eq $false) {
            $Null = New-Item -Path $this.Tests.FullName -ItemType "directory"
        }


    }

    [void]ReverseBuild([bool]$Force) {
        #ReverseBuild will take the module file and extract the content to the sources folder.
        Write-Debug "[KraneModule][ReverseBuild([bool]Force)] Start"
        $this.PsModule.ReverseBuild($Force)
        Write-Debug "[KraneModule][ReverseBuild([bool]Force)] End"
    }

    [void]ReverseBuild([string]$Name,[bool]$Force) {
        #ReverseBuild will take the module file and extract the content to the sources folder.
        Write-Debug "[KraneModule][ReverseBuild([string]Name,[bool]Force)] Start"
        $this.PsModule.ReverseBuild($Name,$Force)
        Write-Debug "[KraneModule][ReverseBuild([string]Name,[bool]Force)] End"
    }

    [string]GetProjectVersion() {
        return $this.KraneFile.Get("ProjectVersion")
    }

    #TODO: Check if this can be removed. Not sure this is actually used
    Fetch() {
        if ($this.Build.Exists) {

            $e = Import-PowerShellDataFile -Path $this.Build.FullName
            $this.ProjectVersion = $this.setProjectVersion($e.ModuleVersion)
        }
    }

    hidden [void]SetProjectVersion($Version) {

        $this.KraneFile.Set("ProjectVersion", $Version)
        $this.KraneFile.Save()
    }

    [Void] FetchGitInitStatus() {
        [System.IO.DirectoryInfo]$GitFolderpath = join-Path -Path $this.Root.FullName -ChildPath ".git\"
        $this.IsGitInitialized = $GitFolderpath.Exists
    }

    [void] AddItem([String]$Name, [ItemFileType]$Type) {
        #Add an item to the project. The item can be a script, a module, a test, etc.
        switch ($Type) {
            "Class" {
                $this.AddClass($Name)
            }
            "PublicFunction" {
                $this.AddPublicFunction($Name)
            }
            "PrivateFunction" {
                $this.AddPrivateFunction($Name)
            }
            "Test" {
                $this.AddTest($Name)
            }
            default {
                Throw "Type $Type not supported"
            }
        }
    }

    hidden AddClass([String]$Name, [String]$Content) {
        $ClassRootFolder = Join-Path -Path $this.Sources.FullName -ChildPath "Classes"
        [System.IO.FileInfo] $ClassPath = Join-Path -Path $ClassRootFolder -ChildPath "$Name.ps1"
        if ($ClassPath.Exists) {
            Throw "Class $Name already exists"
        }
        $Null = New-Item -Path $ClassPath.FullName -ItemType "file" -Value $Content -Force
        $this.PsModule.GetAstClasses($ClassRootFolder)
    }

    hidden AddPublicFunction([String]$Name, [String]$Content) {
        [System.IO.FileInfo] $FunctionPath = Join-Path -Path $this.Sources.FullName -ChildPath "Functions\Public\$Name.ps1"
        if ($FunctionPath.Exists) {
            Throw "Function $Name already exists at $($FunctionPath.FullName)"
        }
        $Null = New-Item -Path $FunctionPath.FullName -ItemType "file" -Value $Content
        $this.PsModule.GetASTFunctions($FunctionPath)
    }

    hidden AddPrivateFunction([String]$Name, [String]$Content) {
        [System.IO.FileInfo]$FunctionPath = Join-Path -Path $this.Sources.FullName -ChildPath "Functions\Private\$Name.ps1"
        if ($FunctionPath.Exists) {
            Throw "Function $Name already exists"
        }
        $Null = New-Item -Path $FunctionPath.FullName -ItemType "file" -Value $Content
        $this.PsModule.GetASTFunctions($FunctionPath)
    }

    [KraneTemplate[]] GetTemplate() {
        #Returns ALL existing templates
        return $this.Templates.GetTemplate()
    }

    [KraneTemplate] GetTemplate([KraneTemplateType]$TemplateType) {
        #Retrieve specific template by type

        $Template = $this.Templates | Where-Object { $_.Type -eq $TemplateType }
        
        if ($null -eq $Template) {
            Throw "Template '$TemplateType' not found"
        }
        Return $Template

    }

    [KraneTemplate[]] GetTemplate([String]$Type, [LocationType]$Location) {
        #Retrieves specific template by type and location.

        $Template = $this.Templates.GetTemplate($Type, $Location)

        if ($null -eq $Template) {
            Throw "Template '$Type' of location type '$Location' not found"
        }
        Return $Template

    }

    [KraneTemplate[]] GetTemplate([LocationType]$Location) {
        #Retrieves specific template by type and location.

        $Template = $this.Templates.GetTemplate($Location)

        Return $Template

    }

    [Void] ReloadAll(){
        $this.ProjectVersion = $this.GetProjectVersion()
        $this.LoadTemplates()
        $this.FetchModuleInfo()
        $this.FetchGitInitStatus()
    }

    [Void]SetDescription([string]$Description){
        $this.Description = $Description
    }
}

Class ModuleObfuscator {
    [String]$ModuleName
    [KraneModule]$Module
    [System.IO.DirectoryInfo]$Bin
    [System.IO.FileInfo]$BinaryModuleFile
    [System.IO.FileInfo]$ModuleDataFile

    Obfuscator() {}

    SetKraneModule([KraneModule]$Module) {
        $Module.ModuleDataFile.Refresh()
        if (!$Module.ModuleDataFile.Exists) {
            Write-Verbose "[BUILD][OBFUSCATE] Module data file Not found. Building module"
            $this.Module.BuildModule()
        }
        $this.Module = $Module
        $this.Bin = $Module.Outputs.FullName + "\Bin"
        $this.BinaryModuleFile = Join-Path -Path $this.Bin.FullName -ChildPath ($this.Module.ModuleFile.BaseName + ".dll")
        $this.ModuleDataFile = $this.Bin.FullName + "\" + $this.Module.ModuleName + ".psd1"
        
    }

    Obfuscate() {

        Write-Verbose "[BUILD][OBFUSCATE] Obfuscating module"
        Write-Verbose "[BUILD][OBFUSCATE] Starting psd1 operations"

        if (!$this.ModuleDataFile.Exists) {
            $this.Module.ModuleDataFile.CopyTo($this.ModuleDataFile.FullName)
            $this.ModuleDataFile.Refresh()
        }
        #Does seem to work.
        #Update-ModuleManifest -Path $this.ModuleDataFile.FullName -RootModule $this.BinaryModuleFile.Name
        $MdfContent = Get-Content -Path $this.ModuleDataFile.FullName
        $MdfContent.Replace($this.Module.ModuleFile.Name, $this.BinaryModuleFile.Name) | Set-Content -Path $this.ModuleDataFile.FullName

        


        #We obfuscate
        #Create the DLL in the Artifacts folder
    }
}

Class KraneFactory {
    static [KraneProject]GetProject([System.IO.FileInfo]$KraneFile) {
        $KraneDocument = [KraneFile]::New($KraneFile)
        $ProjectType = $KraneDocument.Get("ProjectType")
        $Root = $KraneFile.Directory

        switch ($ProjectType) {
            "Module" {
                write-verbose "[KraneFactory][GetProject] Returning root project of type Module $($Root.FullName)"
                $KM = [KraneModule]::New($Root)
                $KM.ProjectVersion = $KraneDocument.Get("ProjectVersion")
                return $KM
            }
            default {
                Throw "Project type $ProjectType not supported"
            }
        }
        
        Throw "Project type $ProjectType not supported" #For some strange reason, having the throw in the switch statement does no suffice for the compiler...
    }
}

Class NuSpecFile {
    [KraneModule]$KraneModule
    [String]$Version
    [System.IO.DirectoryInfo]$ExportFolderPath
    [System.IO.FileInfo]$NuSpecFilePath
    hidden [String]$RawContent

    NuspecFile([KraneModule]$KraneModule) {
        $this.SetKraneModule($KraneModule)
        $this.ExportFolderPath = Join-Path -Path $this.KraneModule.Outputs -ChildPath "Nuget"
    }

    [void] SetKraneModule([KraneModule]$KraneModule) {
        $this.KraneModule = $KraneModule
    }

    hidden [Void]Generate() {
        $psd1Data = Import-PowerShellDataFile -Path $this.KraneModule.ModuleDataFile.FullName
        $NuSpecString = @"
<?xml version="1.0" encoding="utf-8"?>
<package>
    <metadata>
    <id>{0}</id>
    <version>{1}</version>
    <authors>{2}</authors>
    <requireLicenseAcceptance>false</requireLicenseAcceptance>
    <license type="expression">MIT</license>
    <!-- <icon>icon.png</icon> -->
    <projectUrl>{3}</projectUrl>
    <description>{4}</description>
    <releaseNotes>{5}</releaseNotes>
    <copyright>Copyright All rights reserved</copyright>
    <tags>{6}</tags>
    <dependencies>
    </dependencies>
    </metadata>
</package>
"@


        
        $Id = $this.KraneModule.ModuleName #0
        $this.Version = $psd1Data.ModuleVersion #1
        $Authors = $psd1Data.Author #2
        $ProjectUri = $psd1Data.PrivateData.PsData.ProjectUri #3
        $Description = $psd1Data.Description #4
        $ReleaseNotes = $psd1Data.releaseNotes #4
        $Tags = $psd1Data.PrivateData.PsData.tags -join "," #5
        $Final = $NuSpecString -f $Id, $this.Version, $Authors, $ProjectUri, $Description, $ReleaseNotes, $Tags
        $this.RawContent = $Final
    }

    [void] CreateNuSpecFile() {
        $this.Generate()

        $Modulefolder = Join-Path -Path $this.KraneModule.Outputs.FullName -ChildPath "Module"
        
        if(-not (Test-Path $ModuleFolder)){
            $null = New-Item -Path $ModuleFolder -ItemType Directory
        }
        $this.NuSpecFilePath = Join-Path -Path $Modulefolder -ChildPath ($this.KraneModule.ModuleName + ".nuspec")
        $null = $this.RawContent | Out-File -FilePath $this.NuspecFilePath.FullName -Encoding utf8 -Force
    }

    [void] CreateNugetFile() {
        if (!($this.ExportFolderPath.Exists)) {
            $this.ExportFolderPath.Create()
        }
        & nuget pack $this.NuSpecFilePath.FullName -OutputDirectory $this.ExportFolderPath
    }
}

Class PsScriptFile {
    [System.Io.FileInfo]$Path
    [String]$Content
    PsScriptFile(){}

    PsScriptFile([System.Io.FileInfo]$Path) {
        $this.Path = $Path
        if ($this.Path.Exists) {
            $this.Content = Get-Content -Path $this.Path.FullName -Raw
        }
    }
}

Class BuildScript : PsScriptFile {
    #Creates the build script that will be used to build the module and create the nuspec file
    BuildScript([KraneModule]$KraneModule) {
        
        $this.Path = Join-Path -Path $KraneModule.Build.FullName -ChildPath "Build.Krane.ps1"
    }

    BuildScript([System.Io.DirectoryInfo]$Path) {
        $this.Path = Join-Path -Path $Path.FullName -ChildPath "Build.Krane.ps1"
        if($this.Path.Exists){
            $this.Content = Get-Content -Path $this.Path.FullName -Raw
        }
    }

    [void] CreateBuildScript() {
        $Content = @'
        
# This script is used to invoke PsKrane and to build the module and create the nuspec file
 
install-Module PsKrane -Repository PSGallery -Force
import-Module PsKrane -Force
 
$psr = $PSScriptRoot
$Root = split-Path -Path $psr -Parent
 
$KraneModule = Get-KraneProject -Root $Root
$KraneModule.Description = "This module is a test module"
$KraneModule.ProjectUri = "http://link.com"
$KraneModule.BuildModule()
 
New-KraneNugetFile -KraneModule $KraneModule -Force
'@


        $Content | Out-File -FilePath $this.Path.FullName -Encoding utf8 -Force
    }
}

Class TestScript : PsScriptFile {
    #A test script file that will be used to test launch tests
    [string]$Name
    [string]$Content
    
    TestScript([System.IO.FileInfo]$Path) {
        if(-not $Path.Exists) {
            Throw "Test script file $($Path.FullName) does not exist"
        }

        $this.Path = $Path
        $this.Name = $Path.Name.Replace(".Tests.ps1","")
        $this.Content = Get-Content -Path $Path.FullName -Raw
    }

    TestScript([KraneModule]$KraneModule, [String]$TestName) {
        
        $this.Name = $TestName

        if (!($TestName.Contains(".Tests.ps1"))) {
            $TestName = $TestName + ".Tests.ps1"
        }
        $this.Path = Join-Path -Path $KraneModule.Tests.FullName -ChildPath $TestName
        
    }

    [void] CreateTestScript() {
        Write-Debug "[PsKrane][TestScript][CreateTestScript()] Start"
        if (Test-Path $this.Path.FullName) {
            Write-debug "[PsKrane][TestScript][CreateTestScript]Test script $($this.Path.FullName) already exists"
            return
        }
        
        #Create the test script
        if ($this.Path.BaseName -match "^.*\.Tests$") {
            $ItemName = $this.Path.BaseName.Split(".")[0]
        }else{
            $ItemName = $this.Path.BaseName
        }
        $this.Content = [TestScript]::GetTemplate($ItemName)
        Write-Debug "[PsKrane][TestScript][CreateTestScript]Creating Test script at -> $($this.Path.FullName)"
        $This.Content | Out-File -FilePath $this.Path.FullName -Encoding utf8 -Force

        Write-Debug "[PsKrane][TestScript][CreateTestScript] End"
    }

    static [string] GetTemplate([String]$ItemName) {
        $Template = @'
Generated with love using PsKrane
 
Import-Module PsKrane
[System.IO.DirectoryInfo]$psroot = $PSScriptRoot
 
$KraneProject = Get-KraneProject -Root $PsRoot.Parent
 
Import-Module $($KraneProject.ModuleDataFile.FullName) -Force
 
InModuleScope -ModuleName $KraneProject.ModuleName -ScriptBlock {
    Describe "[###ITEMNAME###] Testing" {
        it "Should return Plop" {
            $result = ###ITEMNAME###
            $result | Should -Be "Plop"
        }
    }
}
'@

        $TemplateWithItemName = $Template -replace '###ITEMNAME###', $ItemName
        return $TemplateWithItemName
    }

}  

Class GitHelper {
    [System.io.FileInfo]$Git
    GitHelper() {
        $GitCommand = Get-Command -Name "git"
        if ($null -eq $GitCommand) {
            Throw "Git not found. Please install git and make sure it is in the PATH"
        }
        Write-Verbose "[GitHelper] git command found at $($GitCommand.Source)"
        $this.Git = $GitCommand.Source
    }

    GitTag([string]$Tag) {

        try {
            Write-Verbose "[GitHelper][GitTag] tagging with value -> $tag"
            #& $this.Git.FullName tag -a $tag -m $tag
            $strOutput = & $this.Git.FullName tag -a $tag -m $tag 2>&1
            if ($LASTEXITCODE -ne 0) {
                throw "Failed to write tag: $strOutput"
            }
        }
        catch {
            throw "Error creating tag $tag. $_"
        }
    }

    GitTag([string]$TagAnnotation, [String]$TagMessage) {

        try {
            Write-Verbose "[GitHelper][GitTag] tagging with anonotation -> $TagAnnotation and message $TagMessage"
            $strOutput = & $this.Git.FullName tag -a $TagAnnotation -m $TagMessage 2>&1
            if ($LASTEXITCODE -ne 0) {
                throw "Failed to write tag: $strOutput"
            }
        }
        catch {
            throw "Error creating tag with annotation : $TagAnnotation and message: $TagMessage. error -> $_"
        }
    }

    GitCommit([string]$Message) {
        try {
            
            Write-Verbose "[GitHelper][GitCommit] commit with message -> $Message"
            $strOutput = & $this.Git.FullName commit -m $Message 2>&1
            if ($LASTEXITCODE -ne 0) {
                throw "Failed to commit: $strOutput"
            }
        }
        catch {
            throw "Error creating commit. $_"
        }
    }

    GitAdd([string]$Path) {
        try {
            & $this.Git.FullName add $Path
        }
        catch {
            throw "Error adding $Path to git. $_"
        }
    }

    GitPushTags() {
        $strOutput = ""
        try {
            #& $this.Git.FullName push --tags
            Write-Verbose "[GitHelper][GitPushTags] pushing tags"
            $strOutput = & $this.Git.FullName push --tags -q 2>&1
            if ($LASTEXITCODE -ne 0) {
                throw "LastExitcode: $LASTEXITCODE . Failed to push tags. Received output: $strOutput"
            }
        }
        catch {
            
            throw "Error pushing tags to git. output: $($strOutput). Error content: $_"
        }
    }

    GitPushWithTags() {
        try {
            Write-Verbose "[GitHelper][GitPushWithTags] pushing with tags"
            $strOutput = & $this.Git.FullName push --follow-tags 2>&1
            if ($LASTEXITCODE -ne 0) {
                throw "Failed to push with tags: $strOutput"
            }
        }
        catch {
            throw "Error pushing with tags to git. $_"
        }
    
    }
}

Class PsFunction {
    [string]$Name
    [bool]$IsPrivate
    [bool]$HasCommentBasedHelp
    [bool]$HasTest
    [System.Io.FileInfo]$TestPath
    $CommentBasedHelp
    [System.Io.FileInfo]$Path
    hidden $RawAst

    PsFunction([System.Management.Automation.Language.FunctionDefinitionAst]$FunctionAst, [bool]$IsPrivate) {
        Write-Verbose "[PsFunction] Creating function: $($FunctionAst.Name) IsPrivate: $IsPrivate"
        $this.RawAst = $FunctionAst
        $this.Name = $FunctionAst.Name
        $this.IsPrivate = $IsPrivate
        $this.HasCommentBasedHelp = $FunctionAst.GetHelpContent().Length -gt 0
        $this.CommentBasedHelp = $FunctionAst.GetHelpContent()
    }
}
Class PsModule {
    [String]$ModuleName
    [System.IO.FileInfo]$ModuleFile
    [System.IO.FileInfo]$ModuleDataFile
    [bool] $IsPresent
    [System.Collections.ArrayList]$Classes = [System.Collections.ArrayList]::New()
    [System.Collections.ArrayList]$Functions = [System.Collections.ArrayList]::New()
    [System.Collections.ArrayList]$Tests = [System.Collections.ArrayList]::New()
    Hidden [System.Collections.Hashtable]$ModuleData = @{}


    PsModule([System.IO.FileInfo]$Path) {
        if ($Path.Extension -ne '.psm1') {
            throw "Invalid file type $($Path.Extension) for module file $($Path.FullName)"
        }
        $this.ModuleFile = $Path
        $this.ModuleName = $Path.BaseName
        $PsdFileName = $Path.FullName.Replace('.psm1', '.psd1')
        $this.ModuleDataFile = $PsdFileName
        
        $this.FetchDataFileContent()

        if ($Path.Exists) {
            Write-Verbose "[PsModule] PSM1 file detected -> $($Path.FullName)"
            $this.IsPresent = $true
            $this.GetAstClasses($Path)
            $this.GetASTFunctions($Path)

        }
        else {
            $this.IsPresent = $false
        }
        #We are assuming that the Tests folder is located at the root level as the module file / at the same level as the .krane.json file
        $TestFolder = $Path.Directory.Parent.Parent.FullName + "\Tests"
        $this.FetchTests($TestFolder)
    }

    GetAstClasses([System.IO.FileInfo]$p) {
        $p.Refresh()
        Write-Verbose "[PsModule][GetAstClasses] Fetching classes from $($p.FullName)"
        If ( $P.Exists) {
            $Raw = [System.Management.Automation.Language.Parser]::ParseFile($p.FullName, [ref]$null, [ref]$Null)
            $ASTClasses = $Raw.FindAll( { $args[0] -is [System.Management.Automation.Language.TypeDefinitionAst] }, $true)

            foreach ($ASTClass in $ASTClasses) {

                $null = $this.Classes.Add($ASTClass)
            }
        }
    }


    #TODO Refactor this method, so that it is used by GetASTFunctionS($Path)
    GetASTFunction([System.IO.FileInfo]$Path, [String]$FunctionName) {

        Write-Verbose "[PsModule][GetAstFunctions] Fetching functions from $($Path.FullName)"
        $RawFunctions = $null
        $ParsedFile = [System.Management.Automation.Language.Parser]::ParseFile($Path.FullName, [ref]$null, [ref]$Null)
        $RawAstDocument = $ParsedFile.FindAll({ $args[0] -is [System.Management.Automation.Language.Ast] }, $true)

        If ( $RawASTDocument.Count -gt 0 ) {

            ## source: https://stackoverflow.com/questions/45929043/get-all-functions-in-a-powershell-script/45929412
            $RawFunctions = $RawASTDocument.FindAll({ $args[0] -is [System.Management.Automation.Language.FunctionDefinitionAst] -and $($args[0].parent) -isnot [System.Management.Automation.Language.FunctionMemberAst] })
        }

        $RawFunction = $RawFunctions | Where-Object { $_.Name -eq $FunctionName }
        if ($null -eq $RawFunction) {
            throw "Function $FunctionName not found in file $($Path.FullName)"
        }
        Write-Debug "[PsModule][GetAstFunctions] Found function '$($RawFunction.Name)'"
        if ($this.ModuleData.FunctionsToExport -contains $RawFunction.Name) {
            $IsPrivate = $false
        }
        else {
            $IsPrivate = $true

        }

        Write-Verbose "[PsModule][GetAstFunctions] Found function $($RawFunction.Name) IsPrivate: $IsPrivate"
        $Func = [PsFunction]::New($RawFunction, $IsPrivate)
        $ExistingFunction = $this.Functions | Where-Object { $_.Name -eq $FunctionName }
        if ($null -ne $ExistingFunction) {
            $this.Functions.Remove($ExistingFunction)
        }
        $null = $This.Functions.Add($Func)
        
    }

    GetASTFunctions([System.IO.FileInfo]$Path) {

        Write-Verbose "[PsModule][GetAstFunctions] Fetching functions from $($Path.FullName)"
        $RawFunctions = $null
        $ParsedFile = [System.Management.Automation.Language.Parser]::ParseFile($Path.FullName, [ref]$null, [ref]$Null)
        $RawAstDocument = $ParsedFile.FindAll({ $args[0] -is [System.Management.Automation.Language.Ast] }, $true)

        If ( $RawASTDocument.Count -gt 0 ) {

            ## source: https://stackoverflow.com/questions/45929043/get-all-functions-in-a-powershell-script/45929412
            $RawFunctions = $RawASTDocument.FindAll({ $args[0] -is [System.Management.Automation.Language.FunctionDefinitionAst] -and $($args[0].parent) -isnot [System.Management.Automation.Language.FunctionMemberAst] })
        }
        foreach ($RawFunction in $RawFunctions) {
            Write-Verbose "[PsModule][GetAstFunctions] Found function $($RawFunction.Name)"
            if($this.ModuleData.FunctionsToExport -contains $RawFunction.Name){
                $IsPrivate = $false
            }
            else{
                $IsPrivate = $true

            }
            Write-Verbose "[PsModule][GetAstFunctions] Found function $($RawFunction.Name) IsPrivate: $IsPrivate"
            $Func = [PsFunction]::New($RawFunction, $IsPrivate)
            $null = $This.Functions.Add($Func)
        }
    }

    FetchTests([System.IO.DirectoryInfo]$TestsFolderPath) {

        $null = $this.Tests.Clear()
        $AllTestFiles = Get-ChildItem -Path $TestsFolderPath.FullName -File -Recurse
        foreach($TestFile in $AllTestFiles){
            $Test = [TestScript]::New($TestFile)
            $null = $this.Tests.Add($Test)
        }

        foreach($function in $this.Functions){
            $Test = $this.Tests | Where-Object { $_.Name.Replace(".Tests.ps1","") -eq $function.Name }
            if($null -ne $Test){
                $function.HasTest = $true
                $function.TestPath = $Test.Path
            }
        }
    }

    [TestScript[]] GetTests() {
        if($null -eq $this.Tests){
           $this.FetchTests() 
        }
        return $this.Tests
    }

    [Object[]] GetClasses() {
        return $this.Classes
    }

    [Object[]] GetAllFunctions() {
        return $this.Functions
    }

    [void] ReverseBuild([Bool]$Force) {
        $ExportFolderPath = $This.GetClassFolderPath().Directory
        #This method will take the module file and extract the content to the sources folder and put the functions in the right folder.
        #It is recommended to export to a folder called 'Sources' as other internal Krane functions rely on this folder structure.
        #By default, the files are NOT overwritten. Set $Force = $True to overwrite existing files.
        write-debug "[PsModule][ReverseBuild] Start"
        [System.IO.DirectoryInfo]$PrivatePath = Join-Path -Path $ExportFolderPath.FullName -ChildPath "Functions\Private"
        [System.IO.DirectoryInfo]$PublicPath = Join-Path -Path $ExportFolderPath.FullName -ChildPath "Functions\Public"
        [System.IO.DirectoryInfo]$ClassesFolder = Join-Path -Path $ExportFolderPath.FullName -ChildPath "Classes"

        if ($PrivatePath.Exists -eq $false) {
            write-debug "[PsModule][ReverseBuild] Creating folder -> $($PrivatePath.FullName)"
            $null = New-Item -Path $PrivatePath.FullName -ItemType "directory" -Force
        }
        if ($PublicPath.Exists -eq $false) {
            write-debug "[PsModule][ReverseBuild] Creating folder -> $($PublicPath.FullName)"
            $null = New-Item -Path $PublicPath.FullName -ItemType "directory" -Force
        }
        if ($ClassesFolder.Exists -eq $false) {
            write-debug "[PsModule][ReverseBuild] Creating folder -> $($ClassesFolder.FullName)"
            $null = New-Item -Path $ClassesFolder.FullName -ItemType "directory" -Force
        }
        $ParameterSplat = @{}
        $ParameterSplat.Force = $Force
        $ParameterSplat.Encoding = 'utf8'
        write-debug "[PsModule][ReverseBuild] Starting export of functions to individual files process"
        foreach ($funct in $this.functions) {
            
            $FileName = $funct.Name + ".ps1"
            if ($funct.IsPrivate) {
                $FullExportPath = Join-Path -Path $PrivatePath.FullName -ChildPath $FileName
                write-verbose "[PsModule][ReverseBuild] Exporting Private function '$($funct.Name)' to -> '$($FullExportPath)'"
                $funct.RawAst.Extent.Text | Out-File -FilePath $FullExportPath @ParameterSplat
            }
            else {
                $FullExportPath = Join-Path -Path $PublicPath.FullName -ChildPath $FileName
                write-verbose "[PsModule][ReverseBuild] Exporting public function '$($funct.Name)' to -> '$($FullExportPath)'"
                $funct.RawAst.Extent.Text | Out-File -FilePath $FullExportPath @ParameterSplat
            }

        }

        foreach ($Class in $this.Classes) {
            $FileName = $Class.Name + ".ps1"
            $FullExportPath = Join-Path -Path $ClassesFolder.FullName -ChildPath $FileName

            Write-Verbose "[PsModule][ReverseBuild] Exporting class '$($Class.Name)' to -> '$($FullExportPath)'"

            $Class.Extent.Text | Out-File -FilePath $FullExportPath @ParameterSplat
        }

        write-debug "[PsModule][ReverseBuild] End"
    }

    [void] ReverseBuild([String]$Name, [Bool]$Force) {
        #This method reverse builds a single function or class.
        write-Debug "[PsModule][ReverseBuild([String]Name,[Bool]Force)] Starting operations"
        $ParameterSplat = @{
            Force = $Force
            Encoding = 'utf8'
        }

        $this.GetASTFunction($this.ModuleFile.FullName,$Name)
        $Function =  $this.Functions | Where-Object { $_.Name -eq $Name }
        if($null -ne $Function){
            #Element trying to reververse build is a function
            #The function is already existing. We will export it to the right folder, but we need to NOT touch the other existing functions.
            #GetAstFunctions will automatically load the functions and classes into the $this.PsModule.Functions list.
            #Since we are only interested in one function, we will backup the existing functions and classes and then reload the functions from the backup.

            $FileName = $Function.Name + ".ps1"
        
            if ($Function.IsPrivate) {
                $PrivatePath = $this.GetPrivateFolderPath()
                $FullExportPath = Join-Path -Path $PrivatePath.FullName -ChildPath $FileName
                write-Debug "[PsModule][ReverseBuild([String]Name,[Bool]Force)] Exporting Private function '$($Function.Name)' to -> '$($FullExportPath)'"
                $Function.RawAst.Extent.Text | Out-File -FilePath $FullExportPath @ParameterSplat
            }
            else {
                $PublicPath = $this.GetPublicFolderPath()
                $FullExportPath = Join-Path -Path $PublicPath.FullName -ChildPath $FileName
                Write-Debug "[PsModule][ReverseBuild([String]Name,[Bool]Force)] Exporting public function '$($Function.Name)' to -> '$($FullExportPath)'"
                $Function.RawAst.Extent.Text | Out-File -FilePath $FullExportPath @ParameterSplat
            }
        }
        else{
            #$Name is a not a function, trying to see if it is a class
            $Class = $this.GetClasses() | Where-Object { $_.Name -eq $Name }
            if($null -ne $Class){
                $ClassesFolder = $this.GetClassFolderPath()
                $FileName = $Class.Name + ".ps1"
                $FullExportPath = Join-Path -Path $ClassesFolder.FullName -ChildPath $FileName

                Write-Debug "[PsModule][ReverseBuild([String]Name,[Bool]Force)] Exporting class '$($Class.Name)' to -> '$($FullExportPath)'"

                $Class.Extent.Text | Out-File -FilePath $FullExportPath @ParameterSplat
                
            }
            else{
                throw "[PsModule][ReverseBuild([String]Name,[Bool]Force)] Item $Name not found as either class nor function."
            }
        }
        write-Debug "[PsModule][ReverseBuild([String]Name,[Bool]Force)] End of operations"
    }

    [void]WriteTest([KraneModule]$KraneModule,[string]$Name) {
        #Write the tests to the test folder
        
        #writing tests for functions
        $ItemToCreate = $this.Functions| Where-Object { $_.Name -eq $Name }
        if($null -eq $ItemToCreate){
            throw "[PsModule][WriteTest] Item function / Class named $Name is not found. Please ensure that $Name is already present in the module and try again."
        }
        $TestScript = [TestScript]::New($KraneModule, $ItemToCreate.Name)
        $TestScript.CreateTestScript()

    }

    [void]WriteTests([KraneModule]$KraneModule) {
        #Write the tests to the test folder
        
        #writing tests for functions

        foreach($function in $this.Functions){
            $TestScript = [TestScript]::New($KraneModule,$function.Name)
            $TestScript.CreateTestScript()
        }

        #writing tests for classes

        foreach($class in $this.Classes){
            $TestScript = [TestScript]::New($KraneModule,$class.Name)
            $TestScript.CreateTestScript()
        }
    }

    [void] FetchDataFileContent() {
        $this.ModuleDataFile.Refresh()
        if ($this.ModuleDataFile.Exists) {
            Write-Verbose "[PsModule] PSD1 file detected -> $($this.ModuleDataFile.FullName)"
            $this.ModuleData = Import-PowerShellDataFile -Path $this.ModuleDataFile.FullName

        }
        else {
            Write-Verbose "[PsModule] No PSD1 file found for $($this.ModuleDataFile.FullName)"
        }
    }

    [void] FetchModuleData(){

        $this.FetchDataFileContent()
        $this.GetAstClasses($this.ModuleFile)
        $this.GetASTFunctions($this.ModuleFile)
        $this.FetchTests($this.ModuleFile.Directory.Parent.Parent.FullName + "\Tests") #The PsKrane convention stipulates that the Tests folder is located right under the root. We don't have access to the $KraneProject object here.
    }

    [PsFunction[]] GetPublicFunctions() {
        return $this.Functions | Where-Object { $_.IsPrivate -eq $false }
    }

    [PsFunction[]] GetPrivateFunctions() {
        return $this.Functions | Where-Object { $_.IsPrivate -eq $true }
    }

    [System.IO.FileInfo] GetPrivateFolderPath(){
        $FileInfo = $this.ModuleFile.Directory.Parent.Parent.FullName + "\Sources\Functions\Private"
        return $FileInfo
    }

    [System.IO.FileInfo] GetPublicFolderPath(){
        $FileInfo = $this.ModuleFile.Directory.Parent.Parent.FullName + "\Sources\Functions\Public"
        return $FileInfo
    }

    [System.IO.FileInfo] GetClassFolderPath(){
        $FileInfo = $this.ModuleFile.Directory.Parent.Parent.FullName + "\Sources\Classes"
        return $FileInfo
    }

    [System.Io.FileInfo[]] GetPublicFunctionFiles(){
        $AllFiles = Get-ChildItem -Path $this.GetPublicFolderPath().FullName -File
        return $AllFiles
    }

    [System.Io.FileInfo[]] GetPrivateFunctionFiles(){
        $AllFiles = Get-ChildItem -Path $this.GetPrivateFolderPath().FullName -File
        return $AllFiles
    }

    [System.Io.FileInfo[]] GetClassFiles(){
        $AllFiles = Get-ChildItem -Path $this.GetClassFolderPath().FullName -File
        return $AllFiles
    }
}

Class TestHelper {}

Class PesterTestHelper : TestHelper {
    [object]$TestData
    [String[]]$Path
    [String]$Version = "Latest"
    [System.IO.FileInfo[]]$Tests

    PesterTestHelper() {}

    PesterTestHelper([System.IO.DirectoryInfo]$TestsFolderPath) {
        #Constructor to build based on Tests folder path and Version of Pester
        $PesterModule = Get-Module -Name Pester -ListAvailable
        if(-not $PesterModule){
            write-warning "A Pester module could not be found on the system."
        }
        $this.Path = $TestsFolderPath
        $this.Tests = Get-ChildItem -Path $TestsFolderPath -File -Recurse
        $this.Version = 'Latest'
        
    }

    [void] InvokeTests() {
        
        if ($this.Version -eq 'Latest') {
            Import-Module -Name Pester -Force
        }
        else {
            Import-Module -Name Pester -RequiredVersion $this.Version -Force -Global   
        }

        $this.TestData = Invoke-Pester -Path $this.Tests -PassThru -Show None
    }

    [void] InvokeTests([String[]]$TestsPath) {
        #Accepts eithern a string or an array of strings that should be the path to the test script(s) or the folder containing test scripts.
        if ([string]::IsNullOrEmpty($TestsPath)) {
            throw "No path provided for tests"
        }

        if ($this.Version -eq 'Latest') {
            Import-Module -Name Pester -Force
        }
        else {
            Import-Module -Name Pester -RequiredVersion $this.Version -Force -Global   
        }


        $this.Path = $TestsPath
        $this.TestData = Invoke-Pester -Path $TestsPath -PassThru -Show None
    }

    [void] SetVersion([String]$Version) {
        $this.Version = $Version
    }

    [String] ToString() {
        return "Result: {0} PassedCount: {1} FailedCount: {2}" -f $this.TestData.Result, $this.TestData.PassedCount, $this.TestData.FailedCount
    
    }

    [object] GetFailedTests() {
        return $this.TestData.Failed
    }

    [object] GetPassedTests() {
        return $this.TestData.Passed
    }

    [void] FetchTest(){

    }
}

function Get-KraneTestScript {
    <#
    .SYNOPSIS
        Retrieves a test script
    .DESCRIPTION
        Retrieves a test script
    .PARAMETER KraneModule
        The Krane module object
    .PARAMETER Name
        The name of the test script.
    .EXAMPLE
        Get-KraneTestScript -KraneModule $KraneModule -Name "TestScript"
 
    #>

    [CmdletBinding()]
    Param(
        [Parameter(Mandatory = $True)]
        [KraneModule]$KraneModule,

        [Parameter(Mandatory = $False)]
        [String]$Name
    )

    if($Name){

        if(-not $Name.EndsWith(".Tests.ps1")){
            $Name = $Name.Replace(".Tests.ps1","")
        }

        $return = $KraneModule.PsModule.Tests | where-Object { $_.Name -eq $Name }
        return $return
    }else{
        return $KraneModule.PsModule.Tests
    }
}
# Public functions

Function Get-KraneProjectVersion {
    <#
    .SYNOPSIS
        Retrieves the version of the Krane project
    .DESCRIPTION
        Retrieves the version of the Krane project
    .PARAMETER KraneProject
        The Krane project object
    .EXAMPLE
        Get-KraneProjectVersion -KraneProject $KraneProject
    #>

    [CmdletBinding()]
    Param(
        [Parameter(Mandatory = $True)]
        [KraneProject]$KraneProject
    )

    Return $KraneProject.KraneFile.Get("ProjectVersion")
}

Function New-KraneProject {
    <#
    .SYNOPSIS
        Creates a new Krane project
    .DESCRIPTION
        Will create a base .krane.json project file. The project can be either a module or a script.
    .PARAMETER Type
        Type of project to create. Can be either 'Module' or 'Script'
    .PARAMETER Name
        Name of the project
    .PARAMETER Path
        Root folder of the project
    .NOTES
        Author: Stéphane vg
    .LINK
        https://github.com/Stephanevg/PsKrane
    .EXAMPLE
        New-KraneProject -Type Module -Path C:\Users\Stephane\Code\KraneTest\wip -Name "wip" -verbose
 
        ModuleName : wip
        ModuleFile : C:\Users\Stephane\Code\KraneTest\wip\Outputs\Module\wip.psm1
        ModuleDataFile : C:\Users\Stephane\Code\KraneTest\wip\Outputs\Module\wip.psd1
        Build : C:\Users\Stephane\Code\KraneTest\wip\Build
        Sources : C:\Users\Stephane\Code\KraneTest\wip\Sources
        Tests : C:\Users\Stephane\Code\KraneTest\wip\Tests
        Outputs : C:\Users\Stephane\Code\KraneTest\wip\Outputs
        Tags : {PSEdition_Core, PSEdition_Desktop}
        Description :
        ProjectUri :
        KraneFile : ProjectName:wip ProjectType:Module
        ProjectType : Module
        Root : C:\Users\Stephane\Code\KraneTest\wip
 
    .EXAMPLE
        New-KraneProject -Type Module -Path C:\Users\Stephane\Code\KraneTest\plop -Name "Plop"
 
        C:\USERS\STEPHANE\CODE\KRANETEST\PLOP
        â”‚ .krane.json
        â”œâ”€â”€â”€Build
        â”‚ └───Build.Krane.ps1
        â”œâ”€â”€â”€Outputs
        â”œâ”€â”€â”€Sources
        â”‚ └───Functions
        â”‚ ├───Private
        â”‚ └───Public
        â””───Tests
    .PARAMETER Type
            Type of project to create. Can be either 'Module' or 'Script'
    .PARAMETER Name
            Name of the project
    .PARAMETER Path
            Root folder of the project
    #>

    
    [cmdletBinding()]
    [OutputType([KraneProject])]
    Param(
        [Parameter(Mandatory = $True, HelpMessage = "Type of project to create. Can be either 'Module' or 'Script'")]
        [ProjectType]$Type,

        [Parameter(Mandatory = $True, HelpMessage = "Name of the project")]
        [String]$Name,

        [Parameter(Mandatory = $True, HelpMessage = "Root folder of the project")]
        [ValidateScript({
            if(-not (Test-Path $_)){

                throw "Path $_ doesn't exists"
            }
            $Item = Get-Item -Path  $_
            if($Item.GetType().Name -eq "DirectoryInfo"){
                return $true
            }else{
                throw "Path $_ is not a directory"
            }
           
        })]
        [object]$Path
    )
    $ResolvedPath = Resolve-Path $Path
    $Path = Get-Item -Path $ResolvedPath.Path

    if ($Path.GetType().Name -ne "DirectoryInfo") {
        throw "Path $($Path.FullName) is not a directory"
    }

    [System.IO.DirectoryInfo]$DestinationPath = Join-Path -Path $Path.FullName -ChildPath $Name

    if ($DestinationPath.Exists) {
        $KraneFile = Get-ChildItem -Path $DestinationPath.FullName -Filter ".krane.json"
        if ($KraneFile) {

            Write-warning "[New-KraneProject] Project already exists at '$($DestinationPath.FullName)'."
            return
        }
        #Kranefile doesn't exists. This means the folder is empty. We can create the project
    }

    switch ($Type) {
        "Module" {

            $KraneProject = [KraneModule]::New($Path, $Name)
        }
        default {
            Throw "Project type $Type not supported"
        }
    }

    $KraneProject.CreateBaseStructure()
    Add-KraneBuildScript -KraneProject $KraneProject
    
    Return $KraneProject
 
}

Function New-KraneNuspecFile {
    <#
    .SYNOPSIS
        Creates a new NuSpec file
    .DESCRIPTION
        Creates a new Nuspec File based on a PsKrane project.
    .PARAMETER KraneProject
        The Krane project object
    .LINK
        https://github.com/Stephanevg/PsKrane
    .EXAMPLE
        $KraneProject = Get-KraneProject -Root C:\Plop\
        New-KraneNuspecFile -KraneProject $KraneProject
         
        Generates a .nuspec file in .\Outputs\Module\ folder of the KraneProject
    #>

    
    
    Param(
        [Parameter(Mandatory = $True)]
        [KraneModule]$KraneModule
    )

    $NuSpec = [NuSpecFile]::New($KraneModule)
    $NuSpec.CreateNuSpecFile()
}

Function Get-KraneProject {
    <#
        .SYNOPSIS
            Retrieves a Krane project
        .DESCRIPTION
            Retrieves a Krane project
        .PARAMETER Root
            The root folder of the project. If not specified, it assumes it is located in a folder called 'Build' in the root of the project.
        .EXAMPLE
            Get-KraneProject -Root C:\Code\MyKraneModule
    #>

    [CmdletBinding()]
    [OutputType([KraneProject])]
    Param(
        [Parameter(Mandatory = $False, HelpMessage = "Root folder of the project. If not specified, it assumes it is located in the current folder.")]
        [System.IO.DirectoryInfo]$Root
    )

    
    # Retrieve parent folder
    if (!$Root) {
        #Stole this part from PSHTML
        $EC = Get-Variable ExecutionContext -ValueOnly
        $Root = $ec.SessionState.Path.CurrentLocation.Path 
        write-Verbose "[Get-KraneProject] Root parameter was omitted. Using Current location: $Root"
 
    }
    ElseIf ($Root.Exists -eq $false) {
        Throw "Root $($Root.FullName) folder not found"
    }

    [System.IO.FileInfo]$KraneFile = Join-Path -Path $Root.FullName -ChildPath ".krane.json"
    If (!($KraneFile.Exists)) {
        Throw "No .Krane file found in $($Root.FullName). Verify the path, or create a new project using New-KraneProject"
    }
    write-Verbose "[Get-KraneProject] Fetching Krane project from path: $Root"
    Return [KraneFactory]::GetProject($KraneFile)
    
}

Function Add-KraneBuildScript {
    <#
    .SYNOPSIS
        Adds the build script 'Build.Krane.ps1' to the project.
    .DESCRIPTION
        Adds the build script to the project. The build script is used to invoke PsKrane and to build the module and create the nuspec file.
        This build script is buy default called Build.Krane.ps1 and located in the folder: '<KraneProject>\Build\'.
        The build script is created with a Base build template. But it is recommended to customize it to your needs.
    .PARAMETER Path
        The folder of where the Build.Krane.ps1 file should be created.
    .PARAMETER KraneProject
        The KraneProject object that represents the project
    .NOTES
        Author: Stephane van Gulick
        version: 0.1
    .LINK
        http://github.com/stephanevg/PsKrane
    .EXAMPLE
        Add-BuildScript -Root C:\Users\Stephane\Code\KraneTest\wip
         
    #>

    
    [CmdletBinding()]
    Param(
        [Parameter(Mandatory = $False, ParameterSetName = "Path")]
        [System.IO.DirectoryInfo]$Path,

        [Parameter(Mandatory = $False, ParameterSetName = "KraneProject")]
        [KraneModule]$KraneProject
    )

    Switch ($PSCmdlet.ParameterSetName) {
        "Path" {
            $BuildScript = [BuildScript]::New($Path)
            $BuildScript.CreateBuildScript()
        }
        "KraneProject" {
            $BuildScript = [BuildScript]::New($KraneProject.Build)
            $BuildScript.CreateBuildScript()
        }
    }

}

Function New-KraneTestScript {
    <#
    .SYNOPSIS
        Creates a new test script.
    .DESCRIPTION
        Creates a new test script in the Tests folder of the project.
        The Test script can be used for any purpose.
        If you want to create tests for existing functions / classes, have a look at Write-KraneTestScript
    .PARAMETER KraneProject
        The KraneProject object that represents the project
        Example:
        $KraneProject = Get-KraneProject
    .PARAMETER TestName
        The name of the test script
    .EXAMPLE
        New-KraneTestScript -KraneModule $KraneModule -TestName "Plop"
        Creates a new test script called Plop.Tests.ps1 in the Tests folder of the project
    #>

    [CmdletBinding()]
    Param(
        [Parameter(Mandatory = $True)]
        [KraneProject]$KraneProject,

        [Parameter(Mandatory = $True)]
        [String]$Name
    )

    Write-Verbose "[New-KraneTestScript] Creating test '$Name'"
    $TestScript = [TestScript]::New($KraneProject, $Name)

    $TestScript.CreateTestScript()
    $KraneProject.PsModule.FetchTests($KraneProject.Tests.FullName)
    
}


#TODO TO delete ??
Function Write-KraneTestScript {
    <#
    .SYNOPSIS
        Creates one or more test scripts based on a $KraneModuleProject functions / Classes
    .DESCRIPTION
        Creates a new test script in the Tests folder of the project.
        Only tests for existing functions / classes can be created.
    .PARAMETER KraneProject
        The KraneProject object that represents the project
        (Uswe $KraneProject = Get-KraneProject to get the project)
    .PARAMETER TestName
        The name of the test script
    .EXAMPLE
        New-KraneTestScript -KraneModule $KraneModule -TestName "Plop"
        Creates a new test script called Plop.Tests.ps1 in the Tests folder of the project
    #>

    [CmdletBinding()]
    Param(
        [Parameter(Mandatory = $True)]
        [KraneProject]$KraneProject,

        [Parameter(Mandatory = $False,ParameterSetName = 'SingleItem')]
        [String]$Name,

        [Parameter(Mandatory = $False)]
        [Switch]$Force

        
    )

    if($Name){
        Write-Verbose "[Write-KraneTestScript] Creating test script for $Name"
        $KraneProject.PsModule.WriteTest($KraneProject,$Name)
    }
    else{
        Write-Verbose "[Write-KraneTestScript] Creating test scripts for all functions"
        $KraneProject.PsModule.WriteTests($KraneProject)
    }
    
    $KraneProject.PsModule.FetchTests($KraneProject.Tests.FullName)

}

Function Invoke-KraneBuild {
    <#
        .SYNOPSIS
            Invokes the build script
        .DESCRIPTION
            Invokes the build script of the project. The build script is used to build the module and create the nuspec file.
            The build script is by default called Build.Krane.ps1 and located in the folder: '<KraneProject>\Build\'.
            The build script is created with a Base build template. But it is recommended to customize it to your needs.
        .PARAMETER KraneProject
            The KraneProject object that represents the project
        .EXAMPLE
            Invoke-KraneBuild -KraneProject $KraneProject
    #>

    [CmdletBinding()]
    Param(
        [KraneProject]$KraneProject
    )
    $BuildFile = Join-Path -Path $KraneProject.Build.FullName -ChildPath "Build.Krane.ps1"
    if (!(Test-Path -Path $BuildFile)) {
        Throw "BuildFile $($BuildFile) not found. Please make sure it is there, and try again"
    }

    & $BuildFile
}

Function New-KraneNugetFile {
    <#
    .SYNOPSIS
        Creates a new nuget package
    .DESCRIPTION
        Create a new nuget package based for a specific kraneproject (Nuspec must already have been generated)
    .NOTES
        Information or caveats about the function e.g. 'This function is not supported in Linux'
    .LINK
        https://github.com/Stephanevg/PsKrane
    .EXAMPLE
        $KraneProject = Get-KraneProject -Root C:\Plop\
        New-KraneNugetFile -KraneProject $KraneProject -Force
         
        Generates a .nupkg file in .\Outputs\Nuget\ folder of the KraneProject.
        -Force will create the nuspec file
 
    .PARAMETER KraneModule
        The KraneModule object that represents the project
 
    .PARAMETER Force
        Creates the nuspec file first
    #>

    
    
    Param(
        [Parameter(Mandatory = $True)]
        [KraneModule]$KraneModule,

        [Switch]$Force
    )

    $NuSpec = [NuSpecFile]::New($KraneModule)
    
    if ($Force) {
        $NuSpec.CreateNuSpecFile()
    }

    $NuSpec.CreateNugetFile()
}

Function Invoke-KraneGitCommand {
    <#
        .SYNOPSIS
            Invokes a Git command
        .DESCRIPTION
            Invokes a Git command. The supported commands are:
            - tag: Creates a tag with the version of the project
            - PushTags: Pushes the tags to the remote repository
            - PushWithTags: Pushes the tags to the remote repository and pushes the changes
        .PARAMETER KraneProject
            The KraneProject object that represents the project
        .PARAMETER GitAction
            The Git action to perform. The supported actions are: 'tag', 'PushTags', 'PushWithTags'
        .PARAMETER Argument
            The argument to pass to the Git command. If not specified, the version of the project will be used.
        .EXAMPLE
            Invoke-KraneGitCommand -KraneProject $KraneProject -GitAction tag
    #>

    [CmdletBinding()]
    Param(
        [Parameter(Mandatory = $True)]
        [KraneProject]$KraneProject,

        [Parameter(Mandatory = $true)]
        [ValidateSet("tag", "PushTags", "PushWithTags")]
        [String]$GitAction,

        [String]$Argument


    )

    $GitHelper = [GitHelper]::New()

    switch ($GitAction) {
        "tag" {
            if (!($Argument)) {
                $Argument = "v{0}" -f $KraneProject.ProjectVersion
            }
            Write-Verbose "[Invoke-KraneGitCommand] Invoking Git action $GitAction with argument $Argument"
            $GitHelper.GitTag($Argument)
        }
        "PushWithTags" {
            Write-Verbose "[Invoke-KraneGitCommand] Invoking Git action $GitAction"
            $GitHelper.GitPushWithTags()
        }
        "PushTags" {
            Write-Verbose "[Invoke-KraneGitCommand] Invoking Git action $GitAction"
            $GitHelper.GitPushTags()
        }
    }
    
}

Function Invoke-KraneTestScripts {
    <#
        .SYNOPSIS
            Invokes the test scripts
        .DESCRIPTION
            Invokes the test scripts of the project. The test scripts are used to test the functions and classes of the project.
            The test scripts are located in the folder: '<KraneProject>\Tests\'.
        .PARAMETER KraneProject
            The KraneProject object that represents the project
        .PARAMETER Version
            The version of Pester to use. By default, the latest version is used.
        .EXAMPLE
            Invoke-KraneTestScripts -KraneProject $KraneProject
        .EXAMPLE
            #with version 4.10.0 of Pester
            Invoke-KraneTestScripts -KraneProject $KraneProject -Version 4.10.0
    #>

    [CmdletBinding()]
    Param(
        [Parameter(Mandatory = $True)]
        [KraneProject]$KraneProject,

        [Parameter(Mandatory = $False)]
        [String]$Version = "Latest"

    )

    $TestHelper = [PesterTestHelper]::New()
    $TestHelper.SetVersion($Version)
    $TestHelper.InvokeTests($KraneProject.Tests.FullName)
    $KraneProject.TestData = $TestHelper
    Return $TestHelper
}

Enum LocationType {
    Module
    System
    Project
}

Class KraneTemplateCollection {
    [System.Collections.ArrayList]$Templates = [System.Collections.ArrayList]::New()

    KraneTemplateCollection() {}

    AddTemplate([KraneTemplate]$Template) {
        $null = $this.Templates.Add($Template)
    }

    [KraneTemplate[]] GetTemplate() {
        return $this.Templates
    }

    [KraneTemplate[]]GetTemplate([String]$Type) {
        $Template = $this.Templates | Where-Object { $_.Type -eq $Type }
        
        Return $Template
    }
    
    [KraneTemplate] GetTemplate([LocationType]$Location) {
        Write-Verbose "[KraneTemplateCollection] Getting template of by location -> $Location"
        $Template = $this.Templates | Where-Object { $_.Location -eq $Location }
        
        Return $Template
    }

    [KraneTemplate] GetTemplate([String]$Type, [LocationType]$Location) {
        Write-Verbose "[KraneTemplateCollection] Getting template of type $Type and location $Location"
        $Template = $this.Templates | Where-Object { $_.Type -eq $Type -and $_.Location -eq $Location }
        if ($null -eq $Template) {
            Throw "Template '$Type' of location type '$Location' not found"
        }
        Return $Template
    }
}

Class KraneTemplate {
    [String]$Type
    hidden [String]$Content
    [System.Io.FileInfo]$Path
    [LocationType]$Location

    KraneTemplate([System.Io.FileInfo]$Path) {
        Write-Verbose '[KraneTemplate] Start Constructor [System.Io.FileInfo]$Path'
        if ($Path.Exists -eq $false) {
            Throw "Template file $($Path.FullName) not found"
        }

        $This.Type = $Path.BaseName.Split(".")[0]
        $this.Path = $Path
        $this.Content = Get-Content -Path $Path.FullName -Raw
        Write-Verbose "[KraneTemplate] End Constructor"
    }

    SetLocation([LocationType]$Location) {
        $this.Location = $Location
    }

    [String] ToString() {
        return "{0}->{1}" -f $this.Type, $this.Location
    }

    [string] GetContent() {
        Write-Verbose "[KraneTemplate] Getting content of template $($this.Path.FullName)"
        return $this.Content
    }
}

function New-KraneItem {
    <#
    .SYNOPSIS
        This function helps to create a new item in the project
    .DESCRIPTION
        Items in a krane project kan be a private function, a public function or a class
    .PARAMETER KraneProject
        The KraneProject object that represents the project.
    .PARAMETER Name
        The name of the item to create.
    .PARAMETER Location
        This parameter specifies from where the template should be taken to create the item. The values that are supported are based on the values of enum 'LocationType'.
        By default, the location is set to 'Module' that is the location where the out-of-the-box templates are located.
    .PARAMETER Type
        The Type of the item to create. The Types that are supported are based on the values of enum 'KraneTemplateType'.
    .PARAMETER Visibility
        This parameter is ONLY available when Type is set to 'Function'. This setting allows to specify wheter a function should be 'Public' or 'Private'.
        This will result in the function being created in the right sub folder (functions/pubic or funtions/private).
    .LINK
        Specify a URI to a help page, this will show when Get-Help -Online is used.
    .EXAMPLE
        #The following example creates a new class called 'Myclass' in the krane project located at 'C:\MyKraneProject', and will use the tamplate located in the location 'Module'.
        $KraneProject = Get-KraneProject -Root "C:\MyKraneProject"
        New-KraneItem -KraneProject $KraneProject -Name Myclass -Type Class -Location 'Module'
    .EXAMPLE
        #The following example creates a new private function called 'plop' in the krane project located at 'C:\MyKraneProject'
        $KraneProject = Get-KraneProject -Root "C:\MyKraneProject"
        New-KraneItem -KraneProject $KraneProject -Name plop -Type Function -visibility "private"
         
    #>

    
    param(
        [Parameter(Mandatory = $True)]
        [KraneProject]$KraneProject,

        [Parameter(Mandatory = $True)]
        [KraneTemplateType]$Type,

        [Parameter(Mandatory = $True)]
        [String]$Name,

        [Parameter(Mandatory = $False)]
        [LocationType]$Location = [LocationType]::Module
    )
    dynamicparam {
        # Create a dictionary to hold dynamic parameters
        $paramDictionary = New-Object System.Management.Automation.RuntimeDefinedParameterDictionary

        # Only create the FunctionType parameter if Type is "Function"
        if ($PSCmdlet.MyInvocation.BoundParameters["Type"] -eq "Function") {
            $attributeCollection = New-Object System.Collections.ObjectModel.Collection[System.Attribute]

            # Make the parameter mandatory when Type is "Function"
            $paramAttribute = New-Object System.Management.Automation.ParameterAttribute
            $paramAttribute.Mandatory = $true

            #Adding inline help.
            $paramAttribute.HelpMessage = "The visibility the function should have. Can be either 'Public' or 'Private'. In doubt, use 'Public'"

            # Define ValidateSet (Restricts input to "Public" or "Private")
            $validateSet = New-Object System.Management.Automation.ValidateSetAttribute("Public", "Private")
            $attributeCollection.Add($validateSet)

            $attributeCollection.Add($paramAttribute)

            # Define the parameter
            $functionTypeParam = New-Object System.Management.Automation.RuntimeDefinedParameter(
                "Visibility", [string], $attributeCollection
            )

            # Add to dictionary
            $paramDictionary.Add("Visibility", $functionTypeParam)
        }

        return $paramDictionary
    }
    begin {
        # Retrieve dynamic parameter values
        if ($PSBoundParameters.ContainsKey("Visibility")) {
            $Visibility = $PSBoundParameters["Visibility"]
        }
    }
    process{

        write-verbose "[New-KraneItem] Start of function"
        write-verbose "[New-KraneItem] Creating new item of type '$Type' with name '$Name' in location '$Location'"
        #Any function can be of type Prive or Public. We will use the same template for both and copy the content to the right location.
        switch ($Type) {
            'PublicFunction' { $typ = "function" }
            'PrivateFunction' { $typ = "function" }
            Default {
                $typ = $Type
            }
        }
    
        $Template = $KraneProject.GetTemplate($Type, $Location)
    
        if ($null -eq $Template) {
            throw "No Template not found for '$Name' in location '$location'"
        }
    
        switch ($Type) {
            "Class" {
                $NewContent = $Template.GetContent().Replace('###ClassName###', $Name)
                $KraneProject.addClass($Name, $NewContent)
            }
            "PublicFunction" {
                $NewContent = $Template.GetContent().Replace('###FunctionName###', $Name)
                $KraneProject.addPublicFunction($Name, $NewContent)
            }
            "PrivateFunction" {
                $NewContent = $Template.GetContent().Replace('###FunctionName###', $Name)
                $KraneProject.addPrivateFunction($Name, $Newcontent)
            }
            "Function" {
                $NewContent = $Template.GetContent().Replace('###FunctionName###', $Name)
                if ($Visibility -eq "Private") {
                    $KraneProject.addPrivateFunction($Name, $NewContent)
                }
                else {
                    $KraneProject.addPublicFunction($Name, $NewContent)
                }
            }
            "Test"{
                #TODO Refactor, and either remove complete New-KRaneTestScript, or remove Test creation functionality from this function
                New-KraneTestScript -KraneProject $KraneProject -TestName $Name
            }
            default {
                throw "Type $Type not supported"
            }
        }
    
        #TODO Need to load the new data into $Krane.psmodule (getAllFunctions seems to NOT do the job. Add method 'LoadAll' ?)
        write-verbose "[New-KraneItem] End of function"
    }
    
}

function Get-KraneTemplate {
    <#
    .SYNOPSIS
        Retrieves all existing templates from a specific krane project.
    .DESCRIPTION
        Retrieves all existing templates from a specific krane project.
    .PARAMETER KraneProject
        The KraneProject object that represents the project.
    .PARAMETER Name
        The name of the template to retrieve.
    .PARAMETER Type
        The type of the template to retrieve.
    .PARAMETER Location
        The location where to search from to get the template. (System,Module,Project).
    .LINK
        https://www.github.com/stephanevg/PsKrane
    .EXAMPLE
        Get-KraneTemplate -KraneProject $KraneProject
    #>

    
    param(
        [Parameter(Mandatory = $True)]
        [KraneProject]$KraneProject,

        [Parameter(Mandatory = $False)]
        [String]$Name,

        [Parameter(Mandatory = $False)]
        [KraneTemplateType]$Type,

        [Parameter(Mandatory = $False)]
        [LocationType]$Location
    )
    write-verbose "[Get-KraneTemplate] Start of function"

    $Template = $KraneProject.GetTemplate()


    if ($Name) {
        $Template = $Template | Where-Object { $_.Name -eq $Name }
    }

    #Since $Type is an Enum, when using if($type){} it is evaluated to be $false, although it DOES contain a value...
    #Using PsBoundParameters instead.
    if ($PSBoundParameters.ContainsKey("Type")) {
        $Template = $Template | Where-Object { $_.Type -eq $PSBoundParameters["Type"]}
    } 
    
    if ($Location) {
        $Template = $Template | Where-Object { $_.Location -eq $Location }
    }

    if (-not $Template) {
        #No existing templates found. This can happen when several parameters are used, and some conditions are not met (name / location for instance).
        write-verbose "[Get-KraneTemplate] No templates found"
        return $null
    }

    write-verbose "[Get-KraneTemplate] End of function"
    return $Template

}

Function Invoke-KraneReverseBuild {
    <#
        .SYNOPSIS
            This function allows to decompose an existing .psm1 file into multiple individual .ps1 files.
        .DESCRIPTION
            Creates the individual function and class files (.ps1) based on the functions present in a .psm1 file.
        .NOTES
            Changelog:
                V1.0.0 14.01.2025 stephanevg -> Initial Creation
         
        .PARAMETER KraneModule
            The KraneModule object that represents the project
        .PARAMETER Force
            Overwrites the file(s) if already present.
            (Be sure you commit your changes BEFORE you use this one).
        .PARAMETER AddTests
            Allows to add a relevant test file.
        .PARAMETER ItemName
            Allows to limit the reverse build to a specific item.
        .EXAMPLE
            $KraneModule = Get-KraneProject -Root C:\MyModule\
            Invoke-KraneReverseBuild -KraneModule $KraneModule
        .EXAMPLE
            #The following will add tests to you
            $KraneModule = Get-KraneProject -Root C:\MyModule\
            Invoke-KraneReverseBuild -KraneModule $KraneModule -AddTests
        .EXAMPLE
            #The following limits the reverse build to a specific function called 'MyFunction'
            $KraneModule = Get-KraneProject -Root C:\MyModule\
            Invoke-KraneReverseBuild -KraneModule $KraneModule -ItemName "MyFunction"
    #>

    
    [CmdletBinding()]
    Param(
        [Parameter(Mandatory = $True)]
        [KraneModule]$KraneModule,
        
        [Switch]$AddTests,

        [String]$ItemName,

        [Switch]$Force
    )

    Write-Verbose "[Invoke-KraneReverseBuild] Starting operations"

    if($Force) {
        Write-Verbose "[Invoke-KraneReverseBuild] Force parameter detected. This will overwrite existing files. Make sure you have committed your changes before using this parameter"
    }

    if ($ItemName) {
        Write-Verbose "[Invoke-KraneReverseBuild] Limiting the reverse build to item $ItemName"
        $KraneModule.ReverseBuild($ItemName, $Force)
    }else{
        Write-Verbose "[Invoke-KraneReverseBuild] Launching reverseBuild on all Item"
        $KraneModule.ReverseBuild($Force)
        Write-Verbose "[Invoke-KraneReverseBuild] Done"
    }

    if($AddTests){
        Write-Verbose "[Invoke-KraneReverseBuild] AddTests parameter detected."
        if($ItemName){
            $Functions = $KraneModule.psModule.Functions | Where-Object { $_.Name -eq $ItemName }
        }else{
            $Functions = $KraneModule.psModule.Functions
        }
        foreach($function in $Functions){
            Write-Verbose "[Invoke-KraneReverseBuild] Adding test function $($Function.Name)"
            New-KraneTestScript -TestName $Function.Name -KraneProject $KraneModule
        }

        foreach($Class in $KraneModule.PsModule.Classes){
            Write-Verbose "[Invoke-KraneReverseBuild] Adding test for function $($Function.Name)"
            New-KraneTestScript -TestName $Class.Name -KraneProject $KraneModule
        }
        
        #Refreshing tests
        $KraneProject.PsModule.FetchTests($KraneModule.Tests)
    } 

    Write-Verbose "[Invoke-KraneReverseBuild] End of operations"
}