Skip to content

Add F# source file to F# project when invoking a F# source file template? #162

Open
@trondr

Description

@trondr

Pre and post action support when invoking a template?

Is it feasible for the template system to do some post actions after invoking a template? Or is this out of scope of the design?

I am doing cmdlet development in F# and when invoking a F# source file template I would like the F# source file to be added to the F# project.

I have created a prototype implementation to be added to the function Invoke-PSMDTemplate, sub function Write-TemplateItem. It is probably not the ideal place to put such implementation? General support for pre-action script and post-action script would probably be better? At least I have something that works on my dev machine for the time being.

Write-TemplateItem (changed from approx line 342):

[...]
[System.IO.File]::WriteAllText($destPath, $text, $Encoding)

#Start: Support for adding *fs file to F# project
#If destination path is a *.fs file it should to be included in a F# project in the current directory
#or some parent directory futher up. Look for the neares .*fsproj file and add the file to the project.
if($destPath.EndsWith(".fs"))
{
	#Destination is a F# source file. Find the parent project(s) and add.
	$destinationFile = [System.IO.FileInfo]::new($destPath)
	Find-PsmdFsProject -FolderPath $($destinationFile.Directory.FullName) | ForEach-Object {
		$fsProjectFilePath = $_
		Add-PSMDFsFileToFsProject -FsFilePath $destPath -FsProjectPath $fsProjectFilePath
	}
}
#End: Support for adding *fs file to F# project
[...]

The implementation of the functions Find-PsmdFsProject and Add-PSMDFsFileToFsProject follows:

Find-PsmdFsProject

function Find-PsmdFsProject
{
    <#
        .SYNOPSIS
        Find F# nearest project file(s) in current directory or in parent directories.
        
        .DESCRIPTION
        Find F# nearest project file(s) in current directory or in parent directories.

        .PARAMETER FolderPath
        Full path to the folder to search
        
        .EXAMPLE
        Write-Host Find neares F# project file.
        Find-PsmdFsProject -FolderPath "C:\temp\Fsharp.Console.TestApp\Tests"

        .NOTES               
		Version:        1.0
		Author:         github/trondr
		Company:        github/trondr
		Repository:     https://github.com/trondr/PSModuleDevelopment.git
    
    #>
    param(
        [ValidateScript({ (Test-Path $_ -PathType 'Container') })]
        [string]
        $FolderPath
    )
    $projectFiles = Get-ChildItem -LiteralPath $FolderPath -Filter "*.fsproj" -File
    if($projectFiles.Length -eq 0)
    {
        #Did not find *.fsproj file(s) in current directory, continue recursively up the tree.
        $Directory = [System.IO.DirectoryInfo]::new($FolderPath)
        if($null -ne $Directory.Parent)
        {
            Find-PsmdFsProject -FolderPath $($Directory.Parent.FullName)
        }
    }
    else {
        #Found *.fsproj file(s) in current directory, stop the search and return the findings.
        $projectFiles | ForEach-Object{ Write-Output -InputObject $_.FullName}
    }
}

Add-PSMDFsFileToFsProject

function Add-PsmdFsFileToFsProject {
    <#
        .SYNOPSIS
        Adds a f# source file to a F# project.
        
        .DESCRIPTION
        Adds a f# source file to a F# project. Source file is inserted before Program.fs 
        if project is a console project. Otherwise source file is added to the end.

        .PARAMETER FsProjectPath
        Full path to the F# project file.

        .PARAMETER FsFilePath
        Full path to the F# source file.

        .EXAMPLE
        Write-Host Add F# source file to F# project
        $fsprojectFilePath = "C:\temp\Fsharp.Console.TestApp\Fsharp.Console.TestApp.fsproj"
        $fsFilePath = "C:\temp\Fsharp.Console.TestApp\Tests\ExampleTests2.fs"
        Add-PSMDFsFileToFsProject -FsProjectPath $fsprojectFilePath -FsFilePath $fsFilePath

        .NOTES               
		Version:        1.0
		Author:         github/trondr
		Company:        github/trondr
		Repository:     https://github.com/trondr/PSModuleDevelopment.git
    
    #>
    
    [CmdletBinding()]
    param (
        [ValidateScript({
            [string]$path = $_
            (Test-Path $path -PathType 'Leaf') -and ($path.EndsWith(".fsproj"))
        })]
        [string]
        $FsProjectPath,
        [ValidateScript({
            [string]$path = $_
            (Test-Path $path -PathType 'Leaf') -and ($path.EndsWith(".fs"))
        })]
        [string]
        $FsFilePath
    )
    
    begin {
        $fsProjectFileDirectoryPath = [System.IO.FileInfo]::new($FsProjectPath).Directory.FullName
        $fsFilePathRelativePath = $FsFilePath.Replace($fsProjectFileDirectoryPath,"").TrimStart([System.IO.Path]::DirectorySeparatorChar).Replace([System.IO.Path]::DirectorySeparatorChar,[System.IO.Path]::AltDirectorySeparatorChar)
        function Format-XML
        {
            param(
                [xml]$Xml, 
                $Indent=2
            )
            $StringWriter = New-Object System.IO.StringWriter
            $XmlWriter = New-Object System.XMl.XmlTextWriter $StringWriter
            $xmlWriter.Formatting = "indented"
            $xmlWriter.Indentation = $Indent
            $xml.WriteContentTo($XmlWriter)| Out-Null
            $XmlWriter.Flush()
            $StringWriter.Flush()
            Write-Output $StringWriter.ToString()
        }
    }
    
    process {
        [xml]$fsprojXmlDoc = Get-Content $FsProjectPath
        $itemGroups = $fsprojXmlDoc.SelectNodes("/Project/ItemGroup/Compile")
        if($itemGroups.Count -gt 0)
        {
            $allreadyExists = (($itemGroups.Include | Where-Object { ($_ -eq $fsFilePathRelativePath) }) | Measure-Object).Count -gt 0
            if($allreadyExists -eq $false)
            {
                #Fs file is not allready added, so add it.
                Write-Host "Adding file '$FsFilePath' to project '$FsProjectPath'." -ForegroundColor Green
                [System.Xml.XmlNode]$compileElement = $fsprojXmlDoc.CreateElement("Compile")
                ($compileElement.SetAttribute("Include",$fsFilePathRelativePath)) | Out-Null
                [System.Xml.XmlNode]$parentNode = $itemGroups[0].ParentNode
                [System.Xml.XmlNode]$lastChild = $parentNode.LastChild
                if ($parentNode.LastChild.Include -ieq "Program.fs")
                {
                    #Add the new Compile item before Program.fs).
                    ($parentNode.InsertBefore($compileElement, $lastChild)) | Out-Null
                }
                else {
                    #Add the new Compile item to the end of the list.
                    ($parentNode.AppendChild($compileElement)) | Out-Null
                }
                #Write changes back to fsproj file.
                (Format-XML -Xml $($fsprojXmlDoc.InnerXml) -Indent 4) | Set-Content -Path $fsprojectFilePath -Encoding utf8BOM | Out-Null
            }
            else {
                Write-Host "File '$FsFilePath' has allready been added to project '$FsProjectPath'." -ForegroundColor Yellow
            }
        }
        else {
            Write-Host "No ItemGroup with Compile items found in project file '$FsProjectPath'. There must be at least one Compile item in one ItemGroup." -ForegroundColor Yellow
        }
    }
    
    end {
        
    }
}

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions