ProjectTools.psm1
$Script:Toolbelt = (Split-Path $PROFILE) + "\ProjectTools.json" $Script:ProjectsJSON = (Split-Path $PROFILE) + "\Projects.json" $Script:Tools = @() if ($Script:Toolbelt) { $Script:Tools += Get-Content $Script:Toolbelt | ConvertFrom-Json } $Script:Projects = @() if ($Script:ProjectsJSON) { $Script:Projects += Get-Content $Script:ProjectsJSON | ConvertFrom-Json } <# .SYNOPSIS Installs the POSH ProjectTools .DESCRIPTION Creates the .json files required by the ProjectTools module, scans the current environment and adds compatible tools to it's toolbelt and registers with the powershell environment to start when PowerShell is launched. Only needs to be run once, not everytime you open a PowerShell prompt. .PARAMETER Rescan Scans the environment for new tools and creates a new tools database from scratch. .PARAMETER Force OverWrites any existing files and creates new ones. In essence this means that the existing Tools and Project databases are completly whiped. .EXAMPLE PS C:\> Install-ProjectTools -Rescan Will generate a new ProjectTools.JSON file .LINK https://github.com/kazaamjt/POSH-ProjectTools #> function Install-ProjectToolsEnvironment { [CmdletBinding(SupportsShouldProcess, ConfirmImpact='Low')] Param( [switch]$Rescan, [switch]$Force ) process { Write-Verbose "Checking for existing PowerShell profiles." if (!(Test-Path $PROFILE)) { if ($PSCmdlet.ShouldProcess("Creation of PowerShell Profile $PROFILE Successful")) { Write-Verbose "Creating a PowerShell profile." New-Item -Path $PROFILE -ItemType File -Force | Out-Null } } elseif (Get-Content $PROFILE | ForEach-Object {$_ -notmatch "Initialize-ProjectToolsEnvironment"}) { # Kazaamjt: Fucks up in a really weird way without "-Encoding ascii" on my machine Write-Output "`nInitialize-ProjectToolsEnvironment" | Out-File $PROFILE -Append -Encoding ascii Write-Verbose "Creating ProjectTools.json file." New-Item -Path $Script:Toolbelt -ItemType File -Force | Out-Null } Write-Verbose "Checking environment to add items to toolbelt." $SupportedTools = @() if (!($Script:Toolbelt) -or $Rescan) { # check git Write-Verbose "Checking git" $Git = Get-Command git.exe 2>&1 if ($Git.Name) { $Object = New-Object -TypeName psobject $Object | Add-Member -MemberType NoteProperty -Name "Name" -Value "git" $Object | Add-Member -MemberType NoteProperty -Name "Command" -Value $Git.Name $Object | Add-Member -MemberType NoteProperty -Name "Path" -Value $Git.Path $SupportedTools += $Object Write-Verbose "git added." } else { Write-Verbose "git not Added." } # Check VSCode Write-Verbose "Checking VSCode." $VSCode = Get-Command code.cmd 2>&1 if ($VSCode.Name) { $Object = New-Object -TypeName psobject $Object | Add-Member -MemberType NoteProperty -Name "Name" -Value "VSCode" $Object | Add-Member -MemberType NoteProperty -Name "Command" -Value $VSCode.Name $Object | Add-Member -MemberType NoteProperty -Name "Path" -Value $VSCode.Path $SupportedTools += $Object Write-Verbose "VSCode added." } else { Write-Verbose "VSCode not Added." } # Check Python $Python = Get-Command python.exe 2>&1 if ($Python.Name) { $Object = New-Object -TypeName psobject $Object | Add-Member -MemberType NoteProperty -Name "Name" -Value "Python" $Object | Add-Member -MemberType NoteProperty -Name "Command" -Value $Python.Name $Object | Add-Member -MemberType NoteProperty -Name "Path" -Value $Python.Path $SupportedTools += $Object Write-Verbose "Python Added." # Check venv Write-Verbose "Checking virtualEnv." if (($Python.Version.Major -eq 3) -and ($Python.Version.Minor -ge 3)) { $Builtin = $true } elseif (($Python.Version.Major -eq 2) -and ($Python.Version.Minor -ge 7)) { if ((python.exe --version).replace("Python 2.7.", "") -ge 9 ) { $Builtin = $true } else { Write-Verbose "VirtualEnv not added." } } else { Write-Verbose "VirtualEnv not added." } if ($Builtin) { Write-Verbose "VirtualEnv added." $Object = New-Object -TypeName psobject $Object | Add-Member -MemberType NoteProperty -Name "Name" -Value "VirtualEnv" $Object | Add-Member -MemberType NoteProperty -Name "Command" -Value "python.exe -m venv" $SupportedTools += $Object } } else { Write-Verbose "Python not Added." } # Add the tools to the belt Write-Verbose "Commiting found tools to ProjectTools.json" $SupportedTools | ConvertTo-Json | Out-File $Script:Toolbelt } Write-Verbose "Checking for existing Projects.json file." if ($Force -or !(Test-Path $Script:ProjectsJSON)) { Write-Verbose "Creating Projects.json file." New-Item -Path $Script:ProjectsJSON -ItemType File -Force | Out-Null } } } <# .SYNOPSIS Starts the ProjectTools environment .DESCRIPTION Loads the last used Project. This cmdlet is not meant for use outside PowerShell Profiles. Should only be executed upon opening a new PowerShell session. #> function Initialize-ProjectToolsEnvironment { if ($env:CurrentProject) { $Project = GetProject -Name $env:CurrentProject if ($Project) { LoadProject -Project $Project } } } <# .SYNOPSIS Creates a new Project and registers it in the ProjectTools database. .DESCRIPTION Creates a new Project with the following steps: - If the name of the project is the same as the current path, it uses this path, otherwise it uses the name of the project to create a folder or the path parameter. - Based on the available tools it sets up an environment. e.g.: It creates a local git repo, a readme.md, virtual env, etc.. It does not overwrite any existing objects in the folder. - Finaly it adds the project to the project database. .PARAMETER Name Name of the project. If a path is not set a directory is created with the same name. If the current path has the same name as the name parameter it uses this instead. See the examples for more info. .PARAMETER Path A path for the new project. Can be a relative or a direct path. .PARAMETER ProjectType The type of project this will be. .PARAMETER ToolsDir Creates a directory under the path in which tools can be placed. Can be a path, but must be relative to the set path of the project. .PARAMETER PSTools Name of the tools module. This module will be loaded and unloaded automatically upon switching projects. Can be a path, but must be relative to the set path of the ToolsDir. .PARAMETER NoVirtualEnv If a python project is selected and virtualEnv is available, but you don't want a virtual env. VirtualEnv can take a while to set up, so this is for you don't want to wait that long. .PARAMETER VirtualEnvDir Name of the virtualEnv directory that is being created. If the project is not a python project, VirtualEnv is not available or NoVirtualEnv is selected, this is ignore. If a directory with the same name already existed it is used as the VirtualEnv directory instead. Can be a path, but must be relative to the set path of the project. .EXAMPLE PS C:\Users\John\Projects> New-Project test Creates a directory called test under "C:\Users\John\Projects" using the blank project template. .EXAMPLE PS C:\Users\John\Projects\test> New-Project test Creates a project called test, using the already existing test directory currently selected. The Project path will thus be: "C:\Users\John\Projects\test" .LINK https://github.com/kazaamjt/POSH-ProjectTools #> function New-Project { [CmdletBinding(SupportsShouldProcess, ConfirmImpact='Low')] param( [parameter(mandatory=$True, Position=0)] [string]$Name, [string]$Path, [validateset('PowerShellScript', 'PowerShellModule', 'C', 'C++', 'Python', 'blank')] [string]$ProjectType='blank', [string]$ToolsDir='tools', [string]$PSTools='Tools.psm1', [switch]$NoVirtualEnv, [string]$VirtualEnvDir='venv' ) process { try { GetProject -Name $Name throw "Project already exists." } catch { $Project = New-Object -TypeName psobject $Project | Add-Member -MemberType NoteProperty -Name "Name" -Value $Name # TODO: This is some gross, hard-to-follow nesting. Need to clean this up. if (!($Path)) { if (Test-Path "..\$Name") { if ((get-location).Path -eq (Resolve-Path "..\$Name").Path) { $Path = "..\$Name" } else { $Path = ".\$Name" } } else { $Path = ".\$Name" } } if (!(Test-Path $Path)) { New-Item $Path -ItemType Directory -Force | Out-Null } if ($pscmdlet.ShouldProcess("$Path", "Resolving and adding to project")) { $Project | Add-Member -MemberType NoteProperty -Name "Path" -Value (Resolve-Path $Path).Path Push-Location $Path } if ($Script:Tools | Where-Object {$_.Name -eq 'git'}) { if (!(Test-Path ".\.git")) { git.exe init . Write-Output "# $Name`n" | Out-File "readme.md" if (!(Test-Path ".gitignore")) { New-Item -Path ".gitignore" -ItemType File | Out-Null } git.exe add . | Out-Null git.exe commit -m "initial commit" | Out-Null } } $Project | Add-Member -MemberType NoteProperty -Name 'ProjectType' -Value $ProjectType switch ($ProjectType) { 'PowerShellScript' { if (!(Test-Path "$Name.ps1")) { New-Item "$Name.ps1" -ItemType File | Out-Null } $Project | Add-Member -MemberType NoteProperty -Name "PrimaryFile" -Value "$Name.ps1" } 'PowerShellModule' { if (!(Test-Path "$Name.psm1")) { New-Item "$Name.psm1" -ItemType File | Out-Null } if (!(Test-Path "$Name.psd1")) { New-ModuleManifest -Path "$Name.psd1" -Author $env:USERNAME | Out-Null } $Project | Add-Member -MemberType NoteProperty -Name "PrimaryFile" -Value "$Name.psm1" } 'C' { $CreatePSTools = $True if (!(Test-Path "$Name.c")) { New-Item "$Name.c" -ItemType File | Out-Null } $BoilerPlate = "function Build-$Name {`n `n}`n" $Project | Add-Member -MemberType NoteProperty -Name "PrimaryFile" -Value "$Name.c" } 'C++' { $CreatePSTools = $True if (!(Test-Path "$Name.cpp")) { New-Item "$Name.cpp" -ItemType File | Out-Null } $BoilerPlate = "function Build-$Name {`n `n}`n" $Project | Add-Member -MemberType NoteProperty -Name "PrimaryFile" -Value "$Name.cpp" } 'Python' { $CreatePSTools = $True if (($Script:Tools | Where-Object {$_.Name -eq 'VirtualEnv'}) -and ($NoVirtualEnv -eq $false)) { if (!(Test-Path $VirtualEnvDir)) { Write-Verbose "Creating python VirtualEnv." $venv = ($Script:Tools | Where-Object {$_.Name -eq 'VirtualEnv'}).Command + " $VirtualEnvDir" & $venv } $Project | Add-Member -MemberType NoteProperty -Name 'VirtualEnv' -Value $VirtualEnvDir Write-Output "$VirtualEnvDir/" | Out-File ".gitignore" -Append } else { $Project | Add-Member -MemberType NoteProperty -Name 'VirtualEnv' -Value $null } if (!(Test-Path "$Name.py")) { New-Item "$Name.py" -ItemType File | Out-Null } Write-Output "__pycache__/" | Out-File ".gitignore" -Append $BoilerPlate = "function Start-$Name {`n python $Name.py`n}`n" $Project | Add-Member -MemberType NoteProperty -Name "PrimaryFile" -Value "$Name.py" } default { $Project | Add-Member -MemberType NoteProperty -Name "PrimaryFile" -Value $null } } if ($CreatePSTools) { if (!(Test-Path "$ToolsDir")) { New-Item -Path "$ToolsDir" -ItemType Directory | Out-Null } if (!(Test-Path "$ToolsDir\$PSTools")) { New-Item -Path "$ToolsDir\$PSTools" -ItemType File | Out-Null $BoilerPlate | Out-File -FilePath "$ToolsDir\$PSTools" } $Project | Add-Member -MemberType NoteProperty -Name 'ToolsDir' -Value "$ToolsDir" $Project | Add-Member -MemberType NoteProperty -Name 'PSTools' -Value "$PSTools" } if ($pscmdlet.ShouldProcess("ProjectToolsDatabase", "Add project")) { $Script:Projects += $Project $Script:Projects | ConvertTo-Json | Out-File $Script:ProjectsJSON Pop-Location } if ($pscmdlet.ShouldProcess("$Name", "Switch-Project")) { Switch-Project -Name $Name } } } } <# .SYNOPSIS Gets a project or several projects from the project database. .DESCRIPTION Gets a single or list of projects from the project database. Can be used with wildcards. (Wildcard is *) See examples. .PARAMETER Name The name of the project you want. Accepts wildcards. .EXAMPLE PS C:\Users\John\Projects> Get-Project *Test* Gets any and all projects containing "Test". .LINK https://github.com/kazaamjt/POSH-ProjectTools #> function Get-Project { [CmdletBinding()] param( [string]$Name='*' ) process { $Projects = $Script:Projects | Where-Object {$_.Name -like $Name} return $Projects } } <# .SYNOPSIS Selects an existing project and updates it's settings. .DESCRIPTION Selects an existing project and updates it's settings. Unlike New-Project it does not create any new files or environments, it merely updates what the projects object is pointing to. .PARAMETER Name Name of the project. .PARAMETER Path A path for the new project. Can be a relative or a direct path. .PARAMETER ProjectType The type of project this will be. .PARAMETER PrimaryFile The name of the primary execution file. Can be a path, but must be relative to the set path of the project. .PARAMETER ToolsDir Name of the ToolDirectory. Will not be created if set this way. Can be a path, but must be relative to the set path of the project. .PARAMETER PSTools Name of the PSTools module file. Will not be created if set this way. Can be a path, but must be relative to the set path of the ToolsDir. .PARAMETER VirtualEnvDir Sets the name of the virtualEnvDir. Can be a path, but must be relative to the set path of the project. .PARAMETER RemoveSettings Removes the specified setting from the chooses project. Overwrites the specified setting if it was set in the same command. .EXAMPLE PS C:\Users\John\Projects> Set-Project SomeProject -VirtualEnvDir ".\VirtualEnvironmentDir" Sets the virtual environment directory path to "Path\to\project\VirtualEnvironmentDir". This will cause the project tools to try to load VirtualEnvironmentDir as the VirtuelEnv directory. .LINK https://github.com/kazaamjt/POSH-ProjectTools #> function Set-Project { [CmdletBinding(SupportsShouldProcess, ConfirmImpact='Low')] param( [parameter(mandatory=$True, Position=0)] [string]$Name, [string]$Path, [validateset('PowerShellScript', 'PowerShellModule', 'C', 'C++', 'Python', 'blank')] [string]$ProjectType, [string]$PrimaryFile, [string]$ToolsDir, [string]$PSTools, [string]$VirtualEnvDir, [validateset('ToolsDir','PSTools','VirtualEnvDir')] [string]$RemoveSettings ) process { $Project = GetProject -Name $Name if ($Path) { if ($pscmdlet.ShouldProcess("$Name", "Update path")){ $Project.Path = (Resolve-Path $Path).Path } } if ($ProjectType) { $Project.ProjectType = $ProjectType } if ($PrimaryFile) { $Project.PrimaryFile = $PrimaryFile } if ($ToolsDir) { $Project.ToolsDir = $ToolsDir } if ($PSTools) { $Project.PSTools = $PSTools } if ($VirtualEnvDir) { $Project.VirtualEnv = $VirtualEnvDir } switch ($RemoveSettings) { 'ToolsDir' { $Project.ToolsDir = $null; break} 'PSTools' {$Project.PSTools = $null; break} 'VirtualEnvDir' {$Project.VirtualEnv = $null; break} } $NewProjects = @() $OldProjects = Get-Content $Script:ProjectsJSON | ConvertFrom-Json foreach ($OldProject in $OldProjects) { if ($OldProject.Name -ne $Name) { $NewProjects += $OldProject } } if ($pscmdlet.ShouldProcess("ProjectToolsDatabase", "Updating project $Name")) { $Script:Projects = $NewProjects $Script:Projects += $Project $Script:Projects | ConvertTo-Json | Out-File $Script:ProjectsJSON } } } <# .SYNOPSIS Unloads the current project and loads a new project. .DESCRIPTION Checks if the given project exists, then, checks if there are any projects currently loaded and unloads them. Finaly it loads the given projects environment. .PARAMETER Name Name of the project. .EXAMPLE PS C:\Users\John\Projects> Switch-Project some-other-project Unloads the current environment and loads some-other-project if it exists .LINK https://github.com/kazaamjt/POSH-ProjectTools #> function Switch-Project { [CmdletBinding()] param( [parameter(mandatory=$True, Position=0)] [string]$Name ) process { # Set environment Variables $Project = GetProject -Name $Name # If there was a previous project, unload it. if ($env:CurrentProject) { $CurrentProject = Get-Project $env:CurrentProject UnloadProject -Project $CurrentProject } LoadProject -Project $Project } } <# .SYNOPSIS Unloads the current project environment and loads it again. .DESCRIPTION This cmdlet is meant for when you make changes to the environment and need it to be reloaded. For example, changes to the tools.psm1 module are not loaded automatically. Using this cmdlet will load any changes made. .LINK https://github.com/kazaamjt/POSH-ProjectTools #> function Reset-ProjectEnvironment { [CmdletBinding(SupportsShouldProcess, ConfirmImpact='None')] param() Switch-Project -Name $env:CurrentProject } <# .SYNOPSIS Removes the given project from the project database. .DESCRIPTION Used to delete a project from the project database. Optionally deletes the whole project directory. Be carefull deleting anything using this command as it will whipe everything in it's root directory. .PARAMETER Name Name of the project. .PARAMETER RemoveFiles Remove the root project directory and anything in it. It is not advised to use this. .LINK https://github.com/kazaamjt/POSH-ProjectTools #> function Remove-Project { [CmdletBinding(SupportsShouldProcess, ConfirmImpact='Low')] param( [parameter(mandatory=$True, Position=0)] [string]$Name, [switch]$RemoveFiles ) process { $Project = GetProject -Name $Name if ($Project) { if ($pscmdlet.ShouldProcess("ProjectToolsDatabase", "removing project $Name")) { $Script:Projects = $Script:Projects | Where-Object {$_.Name -ne $Name} $Script:Projects | ConvertTo-Json | Out-File $Script:ProjectsJSON } if ((Get-Location).Path -eq (Resolve-Path $Project.Path).Path) { if ($pscmdlet.ShouldProcess(($Project.Path + "\.."), "Set-Location")) { Set-Location ($Project.Path + '\..') } } if ($Project.Name -eq $env:CurrentProject) { if ($pscmdlet.ShouldProcess("$Name", "Unload Project")) { UnloadProject -Project $Project } if ($pscmdlet.ShouldProcess("Env:", "Updating environment")) { Remove-Item Env:\CurrentProject -ErrorAction SilentlyContinue [Environment]::SetEnvironmentVariable("CurrentProject",$null,"User") } } if ($RemoveFiles) { Remove-Item -Path $Project.Path -Recurse -Force -Confirm } } else { throw "No such project." } } } <# .SYNOPSIS Uninstalls the POSH-ProjectTools. .DESCRIPTION Removes all traces of the POSH-ProjectTools from the environment. Does not remove the ProjectTools install-directory, this must be deleted manualy. Wipes the project database, but does not delete any projects or environments. .LINK https://github.com/kazaamjt/POSH-ProjectTools #> function Remove-ProjectToolsEnvironment { [CmdletBinding(SupportsShouldProcess, ConfirmImpact='Medium')] param( [switch]$KeepDatabaseFiles ) process { Write-Warning -Message "This will clean your environment, but the module itself will still be under: $PSScriptRoot" if ($pscmdlet.ShouldProcess("Env:", "Updating environment")) { Remove-Item Env:\CurrentProject -ErrorAction SilentlyContinue [Environment]::SetEnvironmentVariable("CurrentProject",$null,"User") } if (!($KeepDatabaseFiles)) { Remove-Item $Script:Toolbelt -ErrorAction SilentlyContinue Remove-Item $Script:ProjectsJSON -ErrorAction SilentlyContinue } if ($pscmdlet.ShouldProcess($PROFILE, "Updating userProfile")) { $ProfileContent = Get-Content $PROFILE if (Get-Content $PROFILE | ForEach-Object {$_ -match "Initialize-ProjectToolsEnvironment"}) { $ProfileContent.Replace("Initialize-ProjectToolsEnvironment", "") | Out-File $PROFILE } } } } # Internal functions # These functions are only meant to be used internally. # Because of this, they are not user facing. function SetCurrentEnvVar { param([string]$Name) # Change for current session $env:CurrentProject = $Name # Change for subsequent sessions [System.Environment]::SetEnvironmentVariable('CurrentProject', $env:CurrentProject, [System.EnvironmentVariableTarget]::User) } # A strict version of get-project. No wildcards here. function GetProject{ param([string]$Name) process { $Project = $Script:Projects | Where-Object {$_.Name -eq $Name} if ($Project) { return $Project } else { $ProjectNotFoundError ="Project $Name was not found." throw $ProjectNotFoundError } } } function LoadProject { param($Project) SetCurrentEnvVar -Name $Project.Name Set-Location -Path $Project.Path if ($Project.VirtualEnv) { & ($Project.VirtualEnv +"\Scripts\Activate.ps1") } if ($Project.PSTools) { Import-Module (Resolve-Path ($Project.Path + "\" +$Project.ToolsDir + "\" + $Project.PSTools)).Path -WarningAction SilentlyContinue -Global } } function UnloadProject { param($Project) process { try { deactivate } catch [system.management.automation.commandnotfoundexception] { Write-Verbose "Deactivate was not found, this is ok if venv is not active." } if ($Project.PSTools) { $PSToolsModule = Get-Module -Name ($Project.PSTools).replace(".psm1", "") } if ($PSToolsModule) { Remove-Module -Name ($Project.PSTools).replace(".psm1", "") } } } |