posh-gist.ps1

Function Get-CredentialBasic {
    <#
    .Synopsis
       Outputs BASIC credentials.
    .INPUTS
       PSCredential
    .OUTPUTS
       string
    .NOTES
       The accepted credentials is either your Github username/password pair, or, preferably,
       your username and a token created at https://github.com/settings/tokens

       The PSCredential object has to be decoded as Github breaks RFC2617 and never sends the
       needed HTTP code. See https://developer.github.com/v3/auth/#basic-authentication
    .COMPONENT
       poshgist
    .LINK
       https://gist.github.com/XPlantefeve/5e85af5998abf08f2d86c9aef968ef9f
    #>

    [CmdletBinding()]
    Param (
        # Credentials. See notes.
        [Parameter(Mandatory=$true,ValueFromPipeline=$true,Position=0)]
        [Alias('Cred')]
        [PSCredential]$Credential
    )
    
    $BasicString = [System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($Credential.Password)
    $UnsecurePassword = [System.Runtime.InteropServices.Marshal]::PtrToStringAuto($BasicString)
    $auth = '{0}:{1}' -f $Credential.UserName,$UnsecurePassword
    $bytes = [System.Text.Encoding]::ASCII.GetBytes($auth)
    $base64 = [System.Convert]::ToBase64String($bytes)
    Return 'Basic {0}' -f $base64
}

Function Get-Gist {
    <#
    .Synopsis
       Retrieves Github gists
    .DESCRIPTION
       Retrieves gists, either public ones or for the current user.
       Optionally writes the files contained in the gist to the disk.
    .EXAMPLE
       Get-Gist
       Retrieves the most recent public gists.
    .EXAMPLE
       Get-Gist -Credential ( Get-Credential )
       Retrieves the current users gists.
    .EXAMPLE
       Get-Gist -User johndoe -Since 2016-03-03
       Retrieves johndoe's gist updated after the given date.
    .EXAMPLE
       Get-Gist -Credential $cred -Starred
       Retrieves gists starred by the current user.
    .EXAMPLE
       Get-Gist -Id 5e85af5998abf08f2d86c9aef968ef9f -Forks
       Retrieves the forks of a specific gist.
    .EXAMPLE
       Get-Gist -User johndoe | select -First 1 | Get-Gist -File * -Destination out
       Retrieves johndoe's last gist and outputs the files in the 'out' folder.
    .INPUTS
       System.String
       You can pipe strings that contains gists Ids.

       posh-gist.gist
       You can pipe the custom object posh-gist.gist (type output by the poshgist CMDlets)
    .OUTPUTS
       posh-gist.gist
       Get-Gists outputs a custom object type that contains all information pertaining to a gist.

       The CMDlet returns $false when an error occured.
    .NOTES
       The accepted credentials is either your Github username/password pair, or, preferably,
       your username and a token created at https://github.com/settings/tokens
    .COMPONENT
       poshgist
    .LINK
       https://gist.github.com/XPlantefeve/5e85af5998abf08f2d86c9aef968ef9f
    #>

    [CmdletBinding(DefaultParameterSetName='User')]
    Param (
        # To retrieves the gists of the selected user.
        [Parameter(ParameterSetName='User')]
        [string[]]$User,
        # Limits the gists to the ones updated after the given date.
        [Parameter(ParameterSetName='User')]
        [string]$Since,
        # Retrieves gists by Id.
        [Parameter(ParameterSetName='Id',ValueFromPipeline=$true,ValueFromPipelineByPropertyName=$true,Position=0,Mandatory=$true)]
        [Parameter(ParameterSetName='Forks',Mandatory=$true)]
        [string[]]$Id,
        # To retrieves a specific revision of the gist.
        [Parameter(ParameterSetName='Id')]
        [string]$Sha,
        # Select files from the gist to be written to disk.
        # Wildcard permitted (IE. '*.txt' to get all text files)
        [Parameter(ParameterSetName='Id')]
        [string[]]$File,
        # Folder to write the files to. Will be created if needed. Defaults to current folder.
        [Parameter(ParameterSetName='Id')]
        [string]$Destination,
        # Overwrites files if they already exist.
        [Parameter(ParameterSetName='Id')]
        [switch]$Force,
        # To retrieves the forks of a specific gist.
        [Parameter(ParameterSetName='Forks',Mandatory=$true)]
        [switch]$Forks,
        # To retrieves the last public gists.
        [Parameter(ParameterSetName='Public')]
        [switch]$Public,
        # To retrieves the gists starred by the current user.
        [Parameter(ParameterSetName='Starred')]
        [switch]$Starred,
        # Credentials. See notes.
        [Alias('Cred')]
        [PSCredential]$Credential
        #FIXME
        #[int[]]$Page
    )
    Begin {
        $headers = @{}
        If ( $Credential ) {
            $headers.Authorization = Get-CredentialBasic -Credential $Credential
        }
        $parameters = @()
        If ( $Since ) {
            $parameters += 'since={0}' -f ( Get-Date -Date $Since -Format s )
        }
        if ($parameters.count) { $reqparameters = '?' + ( $parameters -join '&' ) }
        If ( $Destination -and ( -not $File ) ) { $File = '*' }
    }
    Process {
        If ( $Public ) {
            $URI = 'https://api.github.com/gists/public'
        } elseIf ( $Starred ) {
            $URI = 'https://api.github.com/gists/starred'
        } elseIf ( $Forks ) {
            $URI = 'https://api.github.com/gists/{0}/forks' -f $Id
        } elseIf ( $Id ) {
            $URI = 'https://api.github.com/gists/{0}' -f $Id
            If ( $Sha ) {
                $URI += '/' + $Sha
            }
        } ElseIf ( $User ) {
            $URI = 'https://api.github.com/users/{0}/gists' -f $User
        } Else {
            $URI = 'https://api.github.com/gists'
        }
        
        Try {
            $WebRequest = Invoke-WebRequest -Headers $headers -Uri ( $URI + $reqparameters ) -Method Get
            $gists = ConvertFrom-Json -InputObject $WebRequest.content
            foreach ($gist in $gists) 
            {
                $gist.PSTypeNames.Insert(0,'posh-gist.gist')
            }

            $outfiles = @()
            foreach ( $filename in $File ) {
                If ( $filename -notmatch '[*?]' ) {
                    $outfiles += $filename
                } Else {
                    $regexp = [Regex]::Escape($filename) -replace '\\\*','.*' -replace '\\\?','.'
                    $outfiles += $gist.files.psobject.Properties | ? name -Match $regexp | select -ExpandProperty name
                }
            }
            foreach ( $OutFile in ( $outfiles | select -Unique ) ) {
                If ( $gist.files."$OutFile" ) {
                    $DestinationPath = Join-Path -Path $Destination -ChildPath $OutFile
                    If ( ( Test-Path -Path $DestinationPath ) -and ( -not $Force ) ) {
                        Throw '{0} already exists. Use -Force to overwrite' -f $DestinationPath
                    } Else {
                        If ( $gist.file."$OutFile".truncated ) {
                            $content = Invoke-WebRequest -Uri $tg.files.'poshgist.ps1'.raw_url | select -ExpandProperty content
                        } Else {
                            $content = $gist.files."$OutFile".content
                        }
                        If ( -not ( Test-Path -Path $Destination ) ) { $null = New-Item -Path $Destination -ItemType directory }
                        Out-File -FilePath $DestinationPath -InputObject $content -Force
                        # Trade-off. Getting the real timestamp of every file would require analysing history
                        (Get-Item -Path $DestinationPath).LastWriteTime = $gist.updated_at
                    }
                } Else {
                    Throw '{0} was not found in the gist.' -f $OutFile
                }
            }
            Return $gists
        }
        Catch {
            Write-Host -Object $_.Exception.Message
            Return $false
        }
    }
}

Function New-Gist {
    <#
    .Synopsis
       Creates a Github gist
    .DESCRIPTION
       Creates a gist by including files, or by forking an existing one.
    .EXAMPLE
       New-Gist -file poshgist.*,readme.txt -Description 'Gist management CMDlets' -Public
       Creates a gist containing the selected files.
    .EXAMPLE
       New-Gist -Fork 5e85af5998abf08f2d86c9aef968ef9f
       Forks a gist. Forks are always public.
    .INPUTS
       None.
       You cannot pipe anything to New-Gist.
    .OUTPUTS
       posh-gist.gist
       New-Gists outputs a custom object type that contains all information pertaining to the new gist.
       
       The CMDlet returns $false when an error occured.
    .NOTES
       The accepted credentials is either your Github username/password pair, or, preferably,
       your username and a token created at https://github.com/settings/tokens
    .COMPONENT
       poshgist
    .LINK
       https://gist.github.com/XPlantefeve/5e85af5998abf08f2d86c9aef968ef9f
    #>

    [CmdletBinding()]
    Param (
        # Files to include in the gist. Wildcard are permitted.
        [Parameter(ParameterSetName='New',Mandatory=$true,ValueFromPipeline=$true,Position=0)]
        [Parameter(ParameterSetName='Anonymous',Mandatory=$true)]
        [string[]]$Path,
        # Description of the gist.
        [Parameter(ParameterSetName='New')]
        [Parameter(ParameterSetName='Anonymous')]
        [string]$Description,
        # Creates a public gist. The default option is a secret one.
        [Parameter(ParameterSetName='New')]
        [Parameter(ParameterSetName='Anonymous')]
        [switch]$Public,
        # Creates an anonymous gist.
        [Parameter(ParameterSetName='Anonymous',Mandatory=$true)]
        [switch]$Anonymous,
        # The id of the gist to fork.
        [Parameter(ParameterSetName='Fork',Mandatory=$true)]
        [string]$Fork,
        # Credentials. See notes.
        [Alias('Cred')]
        [Parameter(ParameterSetName='Fork')]
        [Parameter(ParameterSetName='New',Mandatory=$true)]
        [PSCredential]$Credential
    )
    Begin {
        $Body = @{}
        $Files = @{}
        If ( $Description ) { $Body.description = $Description }
        If ( $Public ) { $Body.public = $true }
        If ( $Credential ) {
            $headers = @{ Authorization = Get-CredentialBasic -Credential $Credential }
        }
    }
    Process {
        If ( -not $Fork ) {
            $FileList = Get-Item -Path $Path
            foreach ( $File in $FileList ) { $Files[$File.Name] = @{content=( Get-Content $File -Encoding UTF8 | Out-String )} }
        }
    }
    End {
        If ( $Fork ) {
            Try {
                $Uri = 'https://api.github.com/gists/92fa6e65eacf26219022/forks'
                $RawReq = Invoke-WebRequest -Headers $headers -Uri $Uri -Method Post
                $Req = ConvertFrom-Json -InputObject $RawReq
                $Req.PSTypeNames.Insert(0,'posh-gist.gist')
                Return $Req
            }
            Catch {
                Write-Host -Object $_.Exception.Message
                Return $false
            }
        } Else {
            Try {
                $Body.files = $Files
                $json = ConvertTo-Json -InputObject $Body
                $json = [System.Text.Encoding]::UTF8.GetBytes($json)
                $RawReq = Invoke-WebRequest -Headers $headers -Uri https://api.github.com/gists -Method Post -Body $json
                $Req = ConvertFrom-Json -InputObject $RawReq
                $Req.PSTypeNames.Insert(0,'posh-gist.gist')
                Return $Req
            }
            Catch {
                Write-Host -Object $_.Exception.Message
                Return $false
            }
        }
    }
}

Function Remove-Gist {
    <#
    .Synopsis
       Deletes a Github gist
    .DESCRIPTION
       Deletes a gist by ID. You must be authenticated.
    .EXAMPLE
       Remove-Gist -Id <gist id> -Credential ( Get-Credential )
       Deletes the gist.
    .INPUTS
       None.
       You cannot pipe anything to New-Gist.
    .OUTPUTS
        Boolean
        Remove-Gists returns true if the gist was deleted. False otherwise.
    .NOTES
        The accepted credentials is either your Github username/password pair, or, preferably,
        your username and a token created at https://github.com/settings/tokens
    .COMPONENT
        poshgist
    .LINK
        https://gist.github.com/XPlantefeve/5e85af5998abf08f2d86c9aef968ef9f
    #>

    [CmdletBinding()]
    Param (
        # Id of the gist to delete.
        [Parameter(ParameterSetName='Id',ValueFromPipeline=$true,ValueFromPipelineByPropertyName=$true,Position=0)]
        [string[]]$Id,
        # Credential. Mandatory, as you can only delete your own gists.
        [Parameter(Mandatory=$true)]
        [Alias('Cred')]
        [PSCredential]$Credential
    )
    Begin {
        If ( $Credential ) {
            $headers = @{ Authorization = Get-CredentialBasic -Credential $Credential }
        }
    }
    Process {
        Try {
            $Uri = 'https://api.github.com/gists/{0}' -f $Id
            Invoke-WebRequest -Headers $headers -Uri $Uri -Method Delete
            Return $true
        }
        Catch {
            Write-Host -Object $_.Exception.Message
            Return $false
        }
    }
}

Function Update-Gist {
    <#
    .Synopsis
       Updates a Github gist
    .DESCRIPTION
       Updates a gist by adding, removing, updating or renoming files.
    .EXAMPLE
       Update-Gist -id 5e85af5998abf08f2d86c9aef968ef9f -Update poshgist.ps1 -Add poshgist.md
       Updates poshgist.ps1 in the gist, and adds poshgist.md
    .EXAMPLE
       Update-Gist -id 5e85af5998abf08f2d86c9aef968ef9f -Remove *.txt
       Removes text files from the gist.
    .EXAMPLE
       Update-Gist -id 5e85af5998abf08f2d86c9aef968ef9f -Rename readme.txt -Destination readme.md
       Renames the file in the gist.
    .EXAMPLE
       Update-Gist -id 5e85af5998abf08f2d86c9aef968ef9f -Description 'My oh so very nice gist'
       Adds or changes the gist's description
    .EXAMPLE
       Update-Gist -id 5e85af5998abf08f2d86c9aef968ef9f -Star
       Stars a gist. Go on, try it.
    .INPUTS
       System.String
       You can pipe strings that contains gists Ids.

       posh-gist.gist
       You can pipe the custom object posh-gist.gist (type output by the poshgist CMDlets)
    .OUTPUTS
       posh-gist.gist
       New-Gists outputs a custom object type that contains all information pertaining to the updated gist.
       
       The CMDlet returns $false when an error occured.
    .NOTES
       The accepted credentials is either your Github username/password pair, or, preferably,
       your username and a token created at https://github.com/settings/tokens
    .COMPONENT
       poshgist
    .LINK
       https://gist.github.com/XPlantefeve/5e85af5998abf08f2d86c9aef968ef9f
    #>

    [CmdletBinding()]
    Param (
        # Id of the gists to update
        [Parameter(ValueFromPipeline=$true,ValueFromPipelineByPropertyName=$true,Position=0)]
        [string[]]$Id,
        # Description of the gist
        [Parameter(ParameterSetName='Update')]
        [string]$Description,
        # File(s) to add to the gist
        [Parameter(ParameterSetName='Update')]
        [string[]]$Add,
        # File(s) to update in the gist
        [Parameter(ParameterSetName='Update')]
        [string[]]$Update,
        # File(s) to remove from the gist
        [Parameter(ParameterSetName='Update')]
        [string[]]$Remove,
        # File(s) to rename in the gist
        [Parameter(ParameterSetName='Update')]
        [string[]]$Rename,
        # New name(s) for the file(s) in the gist
        [Parameter(ParameterSetName='Update')]
        [string[]]$Destination,
        # Stars a gist. Can belong to another user.
        [Parameter(ParameterSetName='Star')]
        [switch]$Star,
        # Unstars a gist. Can belongs to another user.
        [Parameter(ParameterSetName='Unstar')]
        [switch]$Unstar,
        [Parameter(Mandatory=$true)]
        # Credential. Mandatory, as you can only modify your own gists, and stars are per account.
        [Alias('Cred')]
        [PSCredential]$Credential
    )
    Begin {
        $headers = @{}
        If ( $Credential ) {
            $headers.Authorization = Get-CredentialBasic -Credential $Credential
        }
        $Files = @{}
    }
    Process {
        If ( $Star ) {
            Try {
                $Uri = 'https://api.github.com/gists/{0}/star' -f $Id
                $headers.'Content-Length' = 0
                Invoke-WebRequest -Headers $headers -Uri $Uri -Method Put
                Return $true
            }
            Catch {
                Write-Host -Object $_.Exception.Message
                Return $false
            }
        } Elseif ( $Unstar ) {
            Try {
                $Uri = 'https://api.github.com/gists/{0}/star' -f $Id
                Invoke-WebRequest -Headers $headers -Uri $Uri -Method Delete
                Return $true
            }
            Catch {
                Write-Host -Object $_.Exception.Message
                Return $false
            }
        } Else {
            $Body = @{}
            If ( $Description ) { $Body.description = $Description }
            If ( $Add ) {
                $FileList = Get-Item -Path $Add
                foreach ( $File in $FileList ) { $Files[$File.Name] = @{content=( Get-Content $File -Encoding UTF8 | Out-String )} }
            }
            If ( $Update ) {
                $FileList = Get-Item -Path $Update
                foreach ( $File in $FileList ) { $Files[$File.Name] = @{content=( Get-Content $File -Encoding UTF8 | Out-String )} }
            }
            If ( $Remove ) {
                foreach ( $filename in $Remove ) {
                    $Files[$filename] = $null
                }
            }
            If ( $Rename ) {
                If ( $Rename.count -eq $Destination.count ) {
                    foreach ( $i in ( 0..($Rename.Count -1) ) ) {
                        $Files[$Rename[$i]] = @{filename=$Destination[$i]}
                    }
                } Else {
                    Throw '-Rename and -Destination must include the same number of elements.'
                }
            }
            If ( $Files.count -ne 0 ) { $Body.files = $Files }

            # $Body
            # ConvertTo-Json $Body

            Try {
                $Uri = 'https://api.github.com/gists/{0}' -f $Id
                $json = ConvertTo-Json -InputObject $Body
                $json = [System.Text.Encoding]::UTF8.GetBytes($json)
                $RawReq = Invoke-WebRequest -Headers $headers -Uri $Uri -Method Patch -Body $json
                $Req = ConvertFrom-Json -InputObject $RawReq
                $Req.PSTypeNames.Insert(0,'posh-gist.gist')
                Return $Req
            }
            Catch {
                Write-Host -Object $_.Exception.Message
                Return $false
            }
        }
    }
}

Function Get-GistCommits {
    <#
    .Synopsis
       Retrieves a Github gist history
    .DESCRIPTION
       Retrieves a list of commits for a gist.
    .EXAMPLE
       Get-GistCommits -Id 5e85af5998abf08f2d86c9aef968ef9f
       Retrieves the commits history of a specific gist.
    .INPUTS
       System.String
       You can pipe strings that contains gists Ids.

       posh-gist.gist
       You can pipe the custom object posh-gist.gist (type output by the poshgist CMDlets)
    .OUTPUTS
       posh-gist.gistcommit
       Get-GistsCommits outputs a custom object type that contains all information pertaining to commits.

       The CMDlet returns $false when an error occured.
    .NOTES
       Commits get their own CMDlets because it has a different output format.
    .COMPONENT
       poshgist
    .LINK
       https://gist.github.com/XPlantefeve/5e85af5998abf08f2d86c9aef968ef9f
    #>

    [CmdletBinding()]
    Param (
        # Id of the gists to history of.
        [Parameter(ValueFromPipeline=$true,ValueFromPipelineByPropertyName=$true,Position=0,Mandatory=$true)]
        [string]$Id
    )

    $Uri = 'https://api.github.com/gists/{0}/commits' -f $Id
    Try {
        $Req = Invoke-WebRequest -Uri $Uri
        $Commits = ConvertFrom-Json -InputObject $Req.content
        foreach ($commit in $Commits) 
            {
                $commit.PSTypeNames.Insert(0,'posh-gist.gistcommit')
            }
        Return $Commits

    }
    Catch {
        Write-Host -Object $_.Exception.Message
        Return $false
    }
}

Function Get-GistStar {
    <#
    .Synopsis
       Retrieves a Github gist starred status
    .DESCRIPTION
       Retrieves a gist's the starred status for the current user.
    .EXAMPLE
       Get-GistStar -Id 5e85af5998abf08f2d86c9aef968ef9f -Credential $cred
       Check whether you starred the gist or not.
    .INPUTS
       System.String
       You can pipe a string that contains the gist Id.

       posh-gist.gist
       You can pipe the custom object posh-gist.gist (type output by the poshgist CMDlets)
    .OUTPUTS
       Boolean
       Get-GistStar outputs $true if the gist is starred. $false otherwise.
    .NOTES
       The accepted credentials is either your Github username/password pair, or, preferably,
       your username and a token created at https://github.com/settings/tokens
    .COMPONENT
       poshgist
    .LINK
       https://gist.github.com/XPlantefeve/5e85af5998abf08f2d86c9aef968ef9f
    #>

    [CmdletBinding()]
    Param (
        # Id of the gist to history of.
        [Parameter(ValueFromPipeline=$true,ValueFromPipelineByPropertyName=$true,Position=0,Mandatory=$true)]
        [string]$Id,
        # Credentials. Mandatory as stars are a per-user thing.
        [Parameter(Mandatory=$true)]
        [Alias('Cred')]
        [PSCredential]$Credential
    )

    $headers = @{}
    $headers.Authorization = Get-CredentialBasic -Credential $Credential
    $Uri = 'https://api.github.com/gists/{0}/star' -f $Id
    Try {
        $Req = Invoke-WebRequest -Uri $Uri -Headers $headers
        Return $true
    }
    Catch {
        Return $false
    }
}