Skip to content
Merged
Show file tree
Hide file tree
Changes from 9 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
122 changes: 122 additions & 0 deletions GitHubContents.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
function Get-GitHubContent {
<#
.SYNOPSIS
Retrieve the contents of a file or directory in a repository on GitHub.
.DESCRIPTION
Retrieve content from files on GitHub.
The Git repo for this module can be found here: http://aka.ms/PowerShellForGitHub
.PARAMETER OwnerName
Owner of the repository.
If not supplied here, the DefaultOwnerName configuration property value will be used.
.PARAMETER RepositoryName
Name of the repository.
If not supplied here, the DefaultRepositoryName configuration property value will be used.
.PARAMETER Path
The file path for which to retrieve contents
.PARAMETER MediaType
The format in which the API will return the body of the issue.
Object - Return a json object representation a file or folder. This is the default if you do not pass any specific media type.
Raw - Return the raw contents of a file.
Html - For markup files such as Markdown or AsciiDoc, you can retrieve the rendered HTML using the Html media type.
.PARAMETER ResultAsString
If this switch is specified and the MediaType is either Raw or Html then the resulting bytes will be decoded the result will be
returned as a string instead of bytes. If the MediaType is Object, then an additional property on the object is returned 'contentAsString'
which will be the decoded base64 result as a string.
.PARAMETER AccessToken
If provided, this will be used as the AccessToken for authentication with the
REST Api. Otherwise, will attempt to use the configured value or will run unauthenticated.
.PARAMETER NoStatus
If this switch is specified, long-running commands will run on the main thread
with no commandline status update. When not specified, those commands run in
the background, enabling the command prompt to provide status information.
If not supplied here, the DefaultNoStatus configuration property value will be used.
.EXAMPLE
Get-GitHubContent -OwnerName microsoft -RepositoryName PowerShellForGitHub -Path README.md -MediaType Html
Get the Html output for the README.md file
.EXAMPLE
Get-GitHubContent -OwnerName microsoft -RepositoryName PowerShellForGitHub -Path LICENSE
Get the Binary file output for the LICENSE file
.EXAMPLE
Get-GitHubContent -OwnerName microsoft -RepositoryName PowerShellForGitHub -Path Tests
List the files within the "Tests" path of the repository
#>
[CmdletBinding(
SupportsShouldProcess,
DefaultParametersetName = 'Elements')]
[Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSShouldProcess", "", Justification = "Methods called within here make use of PSShouldProcess, and the switch is passed on to them inherently.")]
param(
[Parameter(Mandatory, ParameterSetName = 'Elements')]
[string] $OwnerName,

[Parameter(Mandatory, ParameterSetName = 'Elements')]
[string] $RepositoryName,

[string] $Path,

[ValidateSet('Raw', 'Html', 'Object')]
[string] $MediaType = 'Object',

[switch] $ResultAsString,

[string] $AccessToken,

[switch] $NoStatus
)

Write-InvocationLog

$elements = Resolve-RepositoryElements -DisableValidation
$OwnerName = $elements.ownerName
$RepositoryName = $elements.repositoryName

$telemetryProperties = @{
'OwnerName' = (Get-PiiSafeString -PlainText $OwnerName)
'RepositoryName' = (Get-PiiSafeString -PlainText $RepositoryName)
}

$uriFragment = [String]::Empty
$description = [String]::Empty

$uriFragment = "/repos/$OwnerName/$RepositoryName/contents"

if ($PSBoundParameters.ContainsKey('Path')) {
$Path = $Path.TrimStart("\", "/")
$uriFragment += "/$Path"
$description = "Getting content for $Path in $RepositoryName"
}
else {
$description = "Getting all content for in $RepositoryName"
}

$params = @{
'UriFragment' = $uriFragment
'Description' = $description
'AcceptHeader' = (Get-ContentMediaType -MediaType $MediaType)
'AccessToken' = $AccessToken
'TelemetryEventName' = $MyInvocation.MyCommand.Name
'TelemetryProperties' = $telemetryProperties
'NoStatus' = (Resolve-ParameterWithDefaultConfigurationValue -Name NoStatus -ConfigValueName DefaultNoStatus)
}

$result = Invoke-GHRestMethodMultipleResult @params

if ($ResultAsString) {
if ($MediaType -eq 'Raw' -or $MediaType -eq 'Html') {
# Decode bytes to string
$result = [System.Text.Encoding]::UTF8.GetString($result)
}
elseif ($MediaType -eq 'Object') {
# Convert from base64
$decoded = [System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String($result.content))
Add-Member -InputObject $result -NotePropertyName "contentAsString" -NotePropertyValue $decoded
}
}

return $result
}
35 changes: 34 additions & 1 deletion GitHubCore.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,7 @@ function Invoke-GHRestMethod
# be coming from the Location header in a previous response. Either way, we don't want there
# to be a leading "/" or trailing '/'
if ($UriFragment.StartsWith('/')) { $UriFragment = $UriFragment.Substring(1) }
if ($UriFragment.EndsWIth('/')) { $UriFragment = $UriFragment.Substring(0, $UriFragment.Length - 1) }
if ($UriFragment.EndsWith('/')) { $UriFragment = $UriFragment.Substring(0, $UriFragment.Length - 1) }

if ([String]::IsNullOrEmpty($Description))
{
Expand Down Expand Up @@ -973,3 +973,36 @@ function Get-MediaAcceptHeader

return ($acceptHeaders -join ',')
}

function Get-ContentMediaType
{
<#
.DESCRIPTION
Returns a formatted AcceptHeader based on the requested MediaType for working with GitHub Content,

The Git repo for this module can be found here: http://aka.ms/PowerShellForGitHub

.PARAMETER MediaType
The format in which the API will return the body of the comment or issue.

Object - Return a json object representation a file or folder. This is the default if you do not pass any specific media type.
Raw - Return the raw contents of a file.
Html - For markup files such as Markdown or AsciiDoc, you can retrieve the rendered HTML using the Html media type.

.PARAMETER AcceptHeader
The accept header that should be included with the MediaType accept header.

.EXAMPLE
Get-ContentMediaType -MediaType Raw

Returns a formatted AcceptHeader for v3 of the response object
#>
[CmdletBinding()]
[OutputType([String])]
param(
[ValidateSet('Raw', 'Html', 'Object')]
[string] $MediaType = 'Object'
)

return "application/vnd.github.$mediaTypeVersion.$($MediaType.ToLower())"
}
2 changes: 2 additions & 0 deletions PowerShellForGitHub.psd1
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
'GitHubBranches.ps1',
'GitHubCore.ps1',
'GitHubComments.ps1',
'GitHubContents.ps1',
'GitHubEvents.ps1',
'GitHubIssues.ps1',
'GitHubLabels.ps1',
Expand Down Expand Up @@ -55,6 +56,7 @@
'Get-GitHubCodeOfConduct',
'Get-GitHubComment',
'Get-GitHubConfiguration',
'Get-GitHubContent',
'Get-GitHubEmoji',
'Get-GitHubEvent',
'Get-GitHubGitIgnore',
Expand Down
151 changes: 151 additions & 0 deletions Tests/GitHubContents.tests.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
# Copyright (c) Microsoft Corporation. All rights reserved.
# Licensed under the MIT License.

<#
.Synopsis
Tests for GitHubContents.ps1 module
#>

# This is common test code setup logic for all Pester test files
$moduleRootPath = Split-Path -Path $PSScriptRoot -Parent
. (Join-Path -Path $moduleRootPath -ChildPath 'Tests\Common.ps1')

try {
# Define Script-scoped, readonly, hidden variables.
@{
repoGuid = [Guid]::NewGuid().Guid
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you capitalize these (PascalCase) (and their uses) to indice that it's a constant vs a local var?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure thing, i was just following the convention from the GitHubComments.tests.ps1, in there these items are camelCase such as defaultIssueTitle. Or are you referring specifically to repoGuid since that is not a constant?

readmeFileName = "README.md"
}.GetEnumerator() | ForEach-Object {
Set-Variable -Force -Scope Script -Option ReadOnly -Visibility Private -Name $_.Key -Value $_.Value
}

# Need this twice since we need to use the above var here
@{
htmlOutput = "<div id=`"file`" class=`"md`" data-path=`"README.md`"><article class=`"markdown-body entry-content p-5`" itemprop=`"text`"><h1><a id=`"user-content-$repoGuid`" class=`"anchor`" aria-hidden=`"true`" href=`"#$repoGuid`"><svg class=`"octicon octicon-link`" viewBox=`"0 0 16 16`" version=`"1.1`" width=`"16`" height=`"16`" aria-hidden=`"true`"><path fill-rule=`"evenodd`" d=`"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z`"></path></svg></a>$repoGuid</h1></article></div>"
rawOutput = "# $repoGuid"
}.GetEnumerator() | ForEach-Object {
Set-Variable -Force -Scope Script -Option ReadOnly -Visibility Private -Name $_.Key -Value $_.Value
}

Describe 'Getting file and folder content' {
# AutoInit will create a readme with the GUID of the repo name
$repo = New-GitHubRepository -RepositoryName ($repoGuid) -AutoInit

Context 'For getting folder contents' {

$folderOutput = Get-GitHubContent -OwnerName $script:ownerName -RepositoryName $repo.name

It "Should have the expected name" {
$folderOutput.name | Should be ""
}
It "Should have the expected path" {
$folderOutput.path | Should be ""
}
It "Should have the expected type" {
$folderOutput.type | Should be "dir"
}
It "Should have the expected entries" {
$folderOutput.entries.length | Should be 1
}
It "Should have the expected entry data" {
$folderOutput.entries[0].name | Should be $readmeFileName
$folderOutput.entries[0].path | Should be $readmeFileName
}
}

Context 'For getting raw (byte) file contents' {

$readmeFileBytes = Get-GitHubContent -OwnerName $script:ownerName -RepositoryName $repo.name -Path $readmeFileName -MediaType Raw
$readmeFileString = [System.Text.Encoding]::UTF8.GetString($readmeFileBytes)

It "Should have the expected content" {
$readmeFileString | Should be $rawOutput
}
}

Context 'For getting raw (string) file contents' {

$readmeFileString = Get-GitHubContent -OwnerName $script:ownerName -RepositoryName $repo.name -Path $readmeFileName -MediaType Raw -ResultAsString

It "Should have the expected content" {
$readmeFileString | Should be $rawOutput
}
}

Context 'For getting html (byte) file contents' {

$readmeFileBytes = Get-GitHubContent -OwnerName $script:ownerName -RepositoryName $repo.name -Path $readmeFileName -MediaType Html
$readmeFileString = [System.Text.Encoding]::UTF8.GetString($readmeFileBytes)

It "Should have the expected content" {
# Replace newlines with empty for comparison
$readmeFileString.Replace("`n", "").Replace("`r", "") | Should be $htmlOutput
}
}

Context 'For getting html (string) file contents' {

$readmeFileString = Get-GitHubContent -OwnerName $script:ownerName -RepositoryName $repo.name -Path $readmeFileName -MediaType Html -ResultAsString

It "Should have the expected content" {
# Replace newlines with empty for comparison
$readmeFileString.Replace("`n", "").Replace("`r", "") | Should be $htmlOutput
}
}

Context 'For getting object (default) file result' {

$readmeFileObject = Get-GitHubContent -OwnerName $script:ownerName -RepositoryName $repo.name -Path $readmeFileName

It "Should have the expected name" {
$readmeFileObject.name | Should be $readmeFileName
}
It "Should have the expected path" {
$readmeFileObject.path | Should be $readmeFileName
}
It "Should have the expected type" {
$readmeFileObject.type | Should be "file"
}
It "Should have the expected encoding" {
$readmeFileObject.encoding | Should be "base64"
}

It "Should have the expected content" {
# Convert from base64
$readmeFileString = [System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String($readmeFileObject.content))
$readmeFileString | Should be $rawOutput
}
}

Context 'For getting object file result as string' {

$readmeFileObject = Get-GitHubContent -OwnerName $script:ownerName -RepositoryName $repo.name -Path $readmeFileName -MediaType Object -ResultAsString

It "Should have the expected name" {
$readmeFileObject.name | Should be $readmeFileName
}
It "Should have the expected path" {
$readmeFileObject.path | Should be $readmeFileName
}
It "Should have the expected type" {
$readmeFileObject.type | Should be "file"
}
It "Should have the expected encoding" {
$readmeFileObject.encoding | Should be "base64"
}

It "Should have the expected content" {
$readmeFileObject.contentAsString | Should be $rawOutput
}
}

Remove-GitHubRepository -Uri $repo.svn_url
}
}
finally {
if (Test-Path -Path $script:originalConfigFile -PathType Leaf) {
# Restore the user's configuration to its pre-test state
Restore-GitHubConfiguration -Path $script:originalConfigFile
$script:originalConfigFile = $null
}
}