# encoding: ascii
# api: powershell
# title: Autoload (beta)
# description: My first attempt at the Autoload functionality of the Korn shell
# version: 0.1
# type: function
# license: CC0
# function: Skip-Object
# x-poshcode-id: 1777
# x-archived: 2010-04-17T07:58:12
#
#
#Requires -Version 2.0

## Automatically load functions from scripts on-demand, instead of having to dot-source them ahead of time, or reparse them from the script every time.
## To use:
## 1) Create a function. To be 100% compatible, it should specify pipeline arguments
## For example:
<#
function Skip-Object {
param( 
   [int]$First = 0, [int]$Last = 0, [int]$Every = 0, [int]$UpTo = 0,  
   [Parameter(Mandatory=$true,ValueFromPipeline=$true)]
   $InputObject
)
begin {
   if($Last) {
      $Queue = new-object System.Collections.Queue $Last
   }
   $e = $every; $UpTo++; $u = 0
}
process {
   $InputObject | where { --$First -lt 0 } | 
   foreach {
      if($Last) {
         $Queue.EnQueue($_)
         if($Queue.Count -gt $Last) { $Queue.DeQueue() }
      } else { $_ }
   } |
   foreach { 
      if(!$UpTo) { $_ } elseif( --$u -le 0) {  $_; $U = $UpTo }
   } |
   foreach { 
      if($every -and (--$e -le 0)) {  $e = $every  } else { $_ } 
   }
}
}
#>
## 2) Put the function into a script with the same name (in our case: Skip-Object.ps1)
## 3) Put the script in your PATH ($env:Path) somewhere (i have a "scripts" folder I add to my path as part of my profile)
## 4) Dot-source this file, or include it as part of your profile
## 5) Add one line to your profile (or on the commandline):
<#
autoload Skip-Object
#>
## This tells us that you want to have that function loaded for you out of the script file if you ever try to use it.
## Now, you can just use the function:
## 1..10 | Skip-Object -first 2 -upto 2

function autoload {
[CmdletBinding()]
param(
[Parameter(Mandatory=$True,ValueFromPipeline=$true,ValueFromPipelineByPropertyName=$true)]
[string[]]$Name
)
begin {
   $xlr8r = [type]::gettype("System.Management.Automation.TypeAccelerators")
   if(!$xlr8r::Get["PSParser"]) {
      $xlr8r::Add( "PSParser", "System.Management.Automation.PSParser, System.Management.Automation, Version=1.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" )
   }

   function global:Autoloaded {
      [CmdletBinding()]Param()
      DYNAMICPARAM {
         Write-Verbose "Autoloaded DynamicParam: $($MyInvocation.InvocationName)"
         ## Determine the Type name based on the alias used to invoke us
         ## Store the parameter set for use in the function later...
         $paramDictionary = new-object System.Management.Automation.RuntimeDefinedParameterDictionary
         # $command = Get-Command $MyInvocation.InvocationName -Type ExternalScript
         $externalScript = $ExecutionContext.InvokeCommand.GetCommand( $MyInvocation.InvocationName, [System.Management.Automation.CommandTypes]::ExternalScript )
         $parserrors = $null
         $prev = $null
         $script = $externalScript.ScriptContents
         [System.Management.Automation.PSToken[]]$tokens = [PSParser]::Tokenize( $script, [ref]$parserrors )
         [Array]::Reverse($tokens)
         
         ForEach($token in $tokens) {
            if($prev -and $token.Type -eq "Keyword" -and $token.Content -ieq "function" -and $prev.Content -eq $MyInvocation.InvocationName ) {
               $script = $script.Insert( $prev.Start, "global:" )
            }
            $prev = $token
         }
         Invoke-Expression $script | out-null
         $command = $ExecutionContext.InvokeCommand.GetCommand( $MyInvocation.InvocationName, [System.Management.Automation.CommandTypes]::Function )
         if(!$command) {
            throw "Something went wrong autoloading the $($MyInvocation.InvocationName) function. Function definition doesn't exist in script: $($externalScript.Path)"
         }
         foreach( $pkv in $command.Parameters.GetEnumerator() ){
            $parameter = $pkv.Value
            if( $parameter.Aliases -match "vb|db|ea|wa|ev|wv|ov|ob" ) { continue } 
            $param = new-object System.Management.Automation.RuntimeDefinedParameter( $parameter.Name, $parameter.ParameterType, $parameter.Attributes)
            $paramdictionary.Add($pkv.Key, $param)
         } 
         return $paramdictionary
      }#DynamicParam

      begin {
         ## Determine the Type name based on the alias used to invoke us
         Write-Verbose "Autoloaded Begin: $($MyInvocation.InvocationName)"
         Remove-Item Alias::$($MyInvocation.InvocationName)
         $command = $ExecutionContext.InvokeCommand.GetCommand( $MyInvocation.InvocationName, [System.Management.Automation.CommandTypes]::Function )
         $scriptCmd = {& $command @PSBoundParameters }

         $steppablePipeline = $scriptCmd.GetSteppablePipeline($myInvocation.CommandOrigin)
         $steppablePipeline.Begin($true)
      }
      process
      {
         Write-Verbose "Autoloaded Process: $($MyInvocation.InvocationName) ($_)"
         try {
            $steppablePipeline.Process($_)
         } catch {
            throw
         }
      }

      end
      {
         try {
            $steppablePipeline.End()
         } catch {
            throw
         }
         Write-Verbose "Autoloaded End: $($MyInvocation.InvocationName)"
      }
   }#AutoLoaded

}
process {
   foreach($function in $Name) {
      Write-Verbose "Set-Alias $function Autoloaded -Scope global"
      Set-Alias $function Autoloaded -Scope global
   }
}
}
