Public/New-PstFunction.ps1
|
function New-PstFunction { <# .SYNOPSIS Creates a new PowerShell function from a template or using intelligent code generation. .DESCRIPTION This function takes an approved verb and noun, and creates a new PowerShell function based on the specified complexity level. It supports two modes: 1. Template Mode (default): Copies a function template and renames it 2. Generator Mode (-UseGenerator): Uses the intelligent code generation system to create customized functions with proper structure based on complexity level Complexity Levels: - Basic: Simple function with minimal structure (no CmdletBinding, simple process block) - Standard: Function with CmdletBinding, begin/process/end blocks, pipeline support - Advanced: Full cmdlet features including ShouldProcess, parameter sets, comprehensive error handling Legacy ComplexityLevel values (Basic, Moderate, Complex) are automatically mapped to the new system. .PARAMETER Verb (Required) The approved verb for the function name. .PARAMETER Noun (Required) The noun for the function name. .PARAMETER Complexity (Optional) The complexity level for code generation. Valid values: Basic, Standard, Advanced. Defaults to Standard. Only used with -UseGenerator switch. .PARAMETER ComplexityLevel (Optional) Legacy parameter for template mode. Valid values: Basic, Moderate, Complex. Defaults to Complex for backward compatibility. Ignored when -UseGenerator is specified. .PARAMETER Parameters (Optional) Array of parameter definitions for the generated function. Each can be: - A string (parameter name, defaults to [string] type) - A hashtable with Name, Type, Mandatory, Pipeline, DefaultValue, etc. Only used with -UseGenerator switch. .PARAMETER Synopsis (Optional) Brief description for the function's help. Only used with -UseGenerator. .PARAMETER UseGenerator (Optional) Switch to use the intelligent code generation system instead of templates. When specified, uses New-PstGeneratedCode to create the function. .PARAMETER Force (Optional) Forces creation even if file exists, useful in automation scenarios. .PARAMETER IncludeProjectLayout (Optional) Creates a complete project structure including src/, tests/, docs/ directories. .EXAMPLE New-PstFunction -Verb Get -Noun Item Creates a new function using the Complex template (legacy mode). .EXAMPLE New-PstFunction -Verb Get -Noun UserData -UseGenerator -Complexity Standard Creates a Standard complexity function using the intelligent code generator. .EXAMPLE New-PstFunction -Verb Set -Noun Configuration -UseGenerator -Complexity Advanced -Parameters @('ConfigPath', @{Name='Force';Type='switch'}) Creates an Advanced function with custom parameters using the code generator. .EXAMPLE New-PstFunction -Verb Get -Noun UserData -UseGenerator -IncludeProjectLayout Creates a complete project structure with generated function code. .NOTES Template mode requires template files in Resources/Samples directory. Generator mode uses the new complexity schema system for dynamic code creation. .LINK https://docs.microsoft.com/en-us/powershell/scripting/developer/cmdlet/approved-verbs-for-windows-powershell-commands #> [CmdletBinding(SupportsShouldProcess)] param ( [Parameter(Mandatory = $true, Position = 0, HelpMessage = "The approved verb for the function name.")] [ValidateSet("Add", "Approve", "Assert", "Backup", "Block", "Checkpoint", "Clear", "Close", "Compare", "Complete", "Compress", "Confirm", "Connect", "Convert", "ConvertFrom", "ConvertTo", "Copy", "Debug", "Deny", "Disable", "Disconnect", "Dismount", "Edit", "Enable", "Enter", "Exit", "Expand", "Export", "Find", "Format", "Get", "Grant", "Group", "Hide", "Import", "Initialize", "Install", "Invoke", "Join", "Limit", "Lock", "Measure", "Merge", "Mount", "Move", "New", "Open", "Optimize", "Out", "Ping", "Pop", "Protect", "Publish", "Push", "Read", "Receive", "Redo", "Register", "Remove", "Rename", "Repair", "Request", "Reset", "Resize", "Resolve", "Restart", "Restore", "Resume", "Revoke", "Save", "Search", "Select", "Send", "Set", "Show", "Skip", "Split", "Start", "Step", "Stop", "Submit", "Suspend", "Switch", "Sync", "Test", "Trace", "Unblock", "Undo", "Uninstall", "Unlock", "Unprotect", "Unpublish", "Unregister", "Update", "Use", "Wait", "Watch", "Write")] [string]$Verb, [Parameter(Mandatory = $true, Position = 1, HelpMessage = "The noun for the function name.")] [ValidatePattern('^[A-Za-z][A-Za-z0-9]*$')] [string]$Noun, [Parameter(Mandatory = $false, HelpMessage = "Complexity level for code generation (Basic, Standard, Advanced).")] [ValidateSet("Basic", "Standard", "Advanced")] [string]$Complexity = "Standard", [Parameter(Mandatory = $false, HelpMessage = "Legacy complexity level for template mode.")] [ValidateSet("Basic", "Moderate", "Complex")] [string]$ComplexityLevel = "Complex", [Parameter(Mandatory = $false, HelpMessage = "Parameter definitions for the generated function.")] [object[]]$Parameters = @(), [Parameter(Mandatory = $false, HelpMessage = "Brief description for the function's help.")] [string]$Synopsis, [Parameter(Mandatory = $false, HelpMessage = "Use the intelligent code generation system instead of templates.")] [switch]$UseGenerator, [Parameter(Mandatory = $false)] [switch]$Force, [Parameter(Mandatory = $false)] [switch]$IncludeProjectLayout ) begin { Write-Debug -Message "Begin '$($MyInvocation.MyCommand.Name)' at '$(Get-Date)'" $newFunctionName = "$Verb-$Noun" # Determine file path based on project layout option if ($IncludeProjectLayout) { $projectRoot = $newFunctionName $newFilePath = Join-Path $projectRoot "src" "$newFunctionName.ps1" } else { $newFilePath = "$newFunctionName.ps1" } if ($UseGenerator) { # Using new code generation system Write-Verbose "Using intelligent code generation with complexity: $Complexity" } else { # Using legacy template mode # Select template based on complexity level $templateFileName = switch ($ComplexityLevel) { "Basic" { "Function-Basic.ps1" } "Moderate" { "Function-Moderate.ps1" } "Complex" { "Function-Complex.ps1" } default { "Function-Complex.ps1" } # Fallback to Complex } $templatePath = $Samples[$templateFileName] Write-Verbose "Using template mode with complexity level: $ComplexityLevel" Write-Verbose "Selected template: $templateFileName" if (-not $templatePath) { throw "Template '$templateFileName' not found in Samples hashtable. Available templates: $($Samples.Keys -join ', ')" } if (-not (Test-Path $templatePath)) { throw "Template file not found: $templatePath" } else { Write-Verbose "Template file found: $templatePath" } } } process { try { # Check if we're in automation mode - inline check to avoid scope issues $isAutomationMode = $Force -or ($Global:PstAutomationMode -eq $true) -or ($env:CI -eq 'true') -or ($env:GITHUB_ACTIONS -eq 'true') -or ($Host.Name -eq 'ServerRemoteHost') -or (-not [Environment]::UserInteractive) -or ($global:ConfirmPreference -eq 'None') # Create project structure if IncludeProjectLayout is specified if ($IncludeProjectLayout) { $projectRoot = $newFunctionName # Create directory structure $directories = @( $projectRoot, (Join-Path $projectRoot "src"), (Join-Path $projectRoot "tests"), (Join-Path $projectRoot "tests" "Unit"), (Join-Path $projectRoot "tests" "Integration"), (Join-Path $projectRoot "docs") ) foreach ($dir in $directories) { if (-not (Test-Path $dir)) { if ($isAutomationMode -or $PSCmdlet.ShouldProcess($dir, "Create directory")) { New-Item -Path $dir -ItemType Directory -Force | Out-Null Write-Verbose "Created directory: $dir" } } } # Create README.md in root $readmeContent = @" # $newFunctionName PowerShell function for [brief description]. ## Installation ``````powershell Import-Module .\src\$newFunctionName.ps1 `````` ## Usage ``````powershell $newFunctionName -Parameter Value `````` ## Testing ``````powershell Invoke-Pester -Path .\tests\ `````` ## License [Specify license] "@ $readmePath = Join-Path $projectRoot "README.md" if ($isAutomationMode -or $PSCmdlet.ShouldProcess($readmePath, "Create README.md")) { $readmeContent | Set-Content -Path $readmePath Write-Verbose "Created: $readmePath" } # Create .gitignore $gitignoreContent = @" # Build artifacts build/ *.nupkg # Test results TestResults*.xml coverage*.xml # Temporary files temp/ *.tmp *.log "@ $gitignorePath = Join-Path $projectRoot ".gitignore" if ($isAutomationMode -or $PSCmdlet.ShouldProcess($gitignorePath, "Create .gitignore")) { $gitignoreContent | Set-Content -Path $gitignorePath Write-Verbose "Created: $gitignorePath" } # Create docs README.md $docsReadmeContent = @" # $newFunctionName Documentation ## Overview [Function overview and purpose] ## Parameters [Parameter documentation] ## Examples [Usage examples] ## Notes [Additional notes] "@ $docsReadmePath = Join-Path $projectRoot "docs" "README.md" if ($isAutomationMode -or $PSCmdlet.ShouldProcess($docsReadmePath, "Create docs README.md")) { $docsReadmeContent | Set-Content -Path $docsReadmePath Write-Verbose "Created: $docsReadmePath" } # Create test file $testContent = @" BeforeAll { Import-Module "`$PSScriptRoot\..\..\src\$newFunctionName.ps1" -Force } Describe "$newFunctionName" { Context "When valid parameters are provided" { It "Should execute without errors" { # Arrange # Act # Assert `$true | Should -Be `$true } } Context "When invalid parameters are provided" { It "Should throw an error" { # Arrange & Act & Assert { $newFunctionName -Parameter `$null } | Should -Throw } } } "@ $testFilePath = Join-Path $projectRoot "tests" "Unit" "$newFunctionName.Tests.ps1" if ($isAutomationMode -or $PSCmdlet.ShouldProcess($testFilePath, "Create test file")) { $testContent | Set-Content -Path $testFilePath Write-Verbose "Created: $testFilePath" } } # Check if file already exists if ((Test-Path $newFilePath) -and -not $isAutomationMode) { if (-not $PSCmdlet.ShouldProcess($newFilePath, "Overwrite existing function file")) { Write-Warning "Operation cancelled by user." return } } elseif ((Test-Path $newFilePath) -and $isAutomationMode -and -not $Force) { Write-Warning "File '$newFilePath' already exists. Use -Force to overwrite in automation mode." return } # Proceed with file creation - skip ShouldProcess in automation mode if ($isAutomationMode -or $PSCmdlet.ShouldProcess($newFilePath, "Create new function file")) { # Create parent directory if it doesn't exist (for project layout) $parentDir = Split-Path -Path $newFilePath -Parent if ($parentDir -and -not (Test-Path $parentDir)) { New-Item -Path $parentDir -ItemType Directory -Force | Out-Null } if ($UseGenerator) { # Use intelligent code generation Write-Verbose "Generating function code with complexity: $Complexity" $generatorParams = @{ FunctionName = $newFunctionName Type = 'Function' Complexity = $Complexity } if ($Parameters.Count -gt 0) { $generatorParams.Parameters = $Parameters } if ($Synopsis) { $generatorParams.Synopsis = $Synopsis } $newContent = New-PstGeneratedCode @generatorParams } else { # Use template mode Write-Verbose "Getting file content from '$($templatePath)'" $templateContent = (Get-Content -Path $templatePath) -join "`n" Write-Verbose "Replacing 'Function Verb-Noun' with 'Function $($newFunctionName)'" $newContent = $templateContent -replace "Function Verb-Noun", "Function $newFunctionName" } Write-Verbose "Set-Content to path '$($newFilePath)'" $newContent | Set-Content -Path $newFilePath if ($IncludeProjectLayout) { Write-Output "New function project created: $projectRoot" Write-Output " - Function file: $newFilePath" Write-Output " - Test file: tests\Unit\$newFunctionName.Tests.ps1" Write-Output " - Documentation: docs\README.md" if ($UseGenerator) { Write-Output " - Generated with: $Complexity complexity" } } else { if ($UseGenerator) { Write-Output "New function file created: $newFilePath (Generated: $Complexity complexity)" } else { Write-Output "New function file created: $newFilePath" } } } else { Write-Verbose "Operation cancelled or skipped." } } catch { if ($_.Exception -and $_.Exception.Message) { Write-Error "An error occurred: $($_.Exception.Message)" } else { Write-Error "An error occurred, but no additional information is available." } } } end { if ($?) { Write-Debug -Message "End '$($MyInvocation.MyCommand.Name)' at '$(Get-Date)'" } } } |