New-TempDirectoryTree.ps1

# Copyright 2012 - 2015 Aaron Jensen
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

function New-TempDirectoryTree
{
    <#
    .SYNOPSIS
    Creates a directory tree using a custom DSL.
 
    .DESCRIPTION
    Use this function to setup a quick directory/file structure in the current temporary directory for a test using a concise DSL. The directory structure is specified in a string. Each item for the directory tree should be on its own line. Directory names should be prefixed with `+`. Files should be prefixed with `*`. Items that should be within a parent directory should be indented two spaces from the position of its parent. A quick example will probably show it better:
 
        + RootDir
          * ChildFile.txt
          + ChildDir
            * GrandchildFile.txt
            * GrandchildFile2.txt
        * RootFile.txt
 
    Easy! A `System.IO.DirectoryInfo` object is returned for the directory which contains your new directory/file tree.
 
    .OUTPUTS
    System.IO.DirectoryInfo.
 
    .EXAMPLE
    New-TempDirectoryTree -Tree "+ RootDir`n*RootFile.txt"
 
    Creates the following directory tree in its own directory under the current user's temporary directory:
 
        + RootDir
        * RootFile.txt
 
    The directory name is randomly generated.
 
    .EXAMPLE
    New-TempDirectoryTree -Tree "* File1`n* File2" -Prefix 'Test-FilesSkipped'
 
    Creates the following directory tree in its own directory under the current user's temporary directory:
 
        * File1
        * File2
 
    The directory name (randomly generated) is prefixed with `Test-FilesSkipped`, e.g `Test-FilesSkipped53q5rekv.kab'
 
    .EXAMPLE
    New-TempDirectoryTree -Tree "+ Dir1`n + Dir 2`n + Dir3" -Path 'C:\Projects\Blade\Test\Test-TempDirectoryTree'
 
    Creates the following directories in the `C:\Projects\Blade\Test\Test-TempDirectoryTree` directory:
 
        + Dir1
          + Dir2
            + Dir3
 
    If `Path` doesn't exist, it is created.
    #>

    [CmdletBinding(DefaultParameterSetName='TempPath')]
    param(
        [Parameter(Mandatory=$true,Position=1)]
        [AllowEmptyString()]
        [string]
        # The directory tree to create.
        $Tree,
        
        [Parameter(ParameterSetName='TempPath')]
        [string]
        # An optional prefix for the temporary directory's name.
        $Prefix,

        [Parameter(Mandatory=$true,ParameterSetName='ExistingPath')]
        [string]
        # The path where the directory tree should be created. Defaults to a new directory in the `$env:TEMP` directory.
        $Path
    )
    
    $stackName = 'New-TempDirectoryTree'
    
    if( $PSCmdlet.ParameterSetName -eq 'TempPath' )
    {
        $optionalParams = @{ }
        if( $Prefix )
        {
            $optionalParams.Prefix = $Prefix
        }
    
        $tempDir = New-TempDirectory @optionalParams
    }
    else
    {
        if( (Test-Path -Path $Path -PathType Container) )
        {
            $tempDir = Get-Item -Path $Path
        }
        else
        {
            $tempDir = New-Item -Path $Path -ItemType Directory -Force
        }
    }
    $startLocation = Get-Location
    Push-Location -Path $tempDir -StackName $stackName
    
    try
    {
        $parent = $tempDir
        $lastDir = $tempDir
        $lastIndent = ''
        
        ($Tree -split "`r?`n") |
            Where-Object { $_ } |
            ForEach-Object {
                if( $_ -notmatch '^(( )+)?(\*|\+) ?(.*)$' )
                {
                    Write-Error ('Invalid line for directory tree: <{0}>' -f $_)
                    return
                }
                $indent = $matches[1]
                if( -not $indent )
                {
                    $indent = ''
                }
                
                $itemType = $matches[3]
                $name = $matches[4]
                
                if( $lastIndent.Length -lt $indent.Length )
                {
                    Push-Location -Path (Join-Path (Get-Location) $lastDir) -StackName $stackName
                }
                elseif( $indent.Length -lt $lastIndent.Length )
                {
                    $levelsUp = (($lastIndent.Length - $indent.Length) / 2) - 1
                    (0..$levelsUp) | ForEach-Object { Pop-Location -StackName $stackName }
                }
                else
                {
                    # Same level. Do nothing.
                }
                
                if( $itemType -eq '*' )
                {
                    $itemType = 'File'
                    $pathType = 'Leaf'
                }
                else
                {
                    $itemType = 'Directory'
                    $pathType = 'Container'
                    $lastDir = $name
                }
                
                if( -not (Test-Path -Path $name -PathType $pathType) )
                {
                    $null = New-Item -Path $name -ItemType $itemType
                }
                
                $lastIndent = $indent
            }
            
        $tempDir
    }
    finally
    {
        Set-Location $startLocation
    }
}