Skip to content

Script info cmdlets with Line parsing and class implementations #708

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 30 commits into from
Jul 20, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
ffa51f7
change design to use classes for Metadata, Content, Requires,HelpInfo
anamnavi Jul 14, 2022
0f96feb
Merge branch 'master' of https://github.com/anamnavi/PowerShellGet in…
anamnavi Jul 14, 2022
3d718f8
revert commit to test perf bin and obj files
anamnavi Jul 14, 2022
8fc841e
add requiredModules parse and validation single method
anamnavi Jul 14, 2022
4815e22
push code for Test-PSScriptFile
anamnavi Jul 14, 2022
2a91069
add methods to validate parsed content, all tests for Test-PSScriptFi…
anamnavi Jul 14, 2022
6dc2ab0
Update-PSScriptFileInfo now works
anamnavi Jul 14, 2022
5f8c7d4
code review feedback- ensure methods have summary blocks ending in pe…
anamnavi Jul 18, 2022
faf9704
refactor method into Utils
anamnavi Jul 18, 2022
5506e6c
refactor TryParsePSScript() to use helpers
anamnavi Jul 18, 2022
cf85b83
add newlines to end of files
anamnavi Jul 18, 2022
63a140f
Update src/code/PSScriptContents.cs
anamnavi Jul 18, 2022
b9b5177
Update src/code/PSScriptFileInfo.cs
anamnavi Jul 18, 2022
59698a7
Update src/code/PSScriptFileInfo.cs
anamnavi Jul 18, 2022
6a2b2fa
Update src/code/PSScriptFileInfo.cs
anamnavi Jul 18, 2022
3167cee
Update src/code/PSScriptFileInfo.cs
anamnavi Jul 18, 2022
970aae4
Update src/code/Utils.cs
anamnavi Jul 18, 2022
f0e51c4
Update src/code/Utils.cs
anamnavi Jul 18, 2022
aff3cb3
Update src/code/Utils.cs
anamnavi Jul 18, 2022
2b42ed5
Update src/code/Utils.cs
anamnavi Jul 18, 2022
2002fbd
Update src/code/Utils.cs
anamnavi Jul 18, 2022
759b657
Update src/code/PSScriptFileInfo.cs
anamnavi Jul 18, 2022
37a41f5
Update src/code/PSScriptFileInfo.cs
anamnavi Jul 18, 2022
815579e
Update src/code/PSScriptFileInfo.cs
anamnavi Jul 18, 2022
ed4f227
Update src/code/UpdatePSScriptFileInfo.cs
anamnavi Jul 18, 2022
4d240e4
preserve newlines by using File.WriteAllLines, and address other code…
anamnavi Jul 19, 2022
6be12d1
remove this. from Constructor, and remove bracket initializers for st…
anamnavi Jul 19, 2022
75e7f34
replace array initializer bracket with Array.Empty<T>()
anamnavi Jul 19, 2022
ce039ed
one more bracket initializer and one capitalization typo
anamnavi Jul 19, 2022
761f3a9
add note about extra newline for Metadata and HelpInfo emitted content
anamnavi Jul 19, 2022
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
3 changes: 3 additions & 0 deletions src/PowerShellGet.psd1
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@
'Register-PSResourceRepository',
'Save-PSResource',
'Set-PSResourceRepository',
'New-PSScriptFileInfo',
'Test-PSScriptFileInfo',
'Update-PSScriptFileInfo',
'Publish-PSResource',
'Uninstall-PSResource',
'Unregister-PSResourceRepository',
Expand Down
258 changes: 258 additions & 0 deletions src/code/NewPSScriptFileInfo.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,258 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

using System;
using System.Collections;
using System.IO;
using System.Management.Automation;
using Microsoft.PowerShell.Commands;
using Microsoft.PowerShell.PowerShellGet.UtilClasses;

namespace Microsoft.PowerShell.PowerShellGet.Cmdlets
{
/// <summary>
/// Creates a new .ps1 file with script information required for publishing a script.
/// </summary>
[Cmdlet(VerbsCommon.New, "PSScriptFileInfo")]
public sealed class NewPSScriptFileInfo : PSCmdlet
{
#region Parameters

/// <summary>
/// The path the .ps1 script info file will be created at.
/// </summary>
[Parameter(Position = 0, Mandatory = true)]
[ValidateNotNullOrEmpty]
public string FilePath { get; set; }

/// <summary>
/// The version of the script.
/// </summary>
[Parameter]
[ValidateNotNullOrEmpty()]
public string Version { get; set; }

/// <summary>
/// The author of the script.
/// </summary>
[Parameter]
[ValidateNotNullOrEmpty()]
public string Author { get; set; }

/// <summary>
/// The description of the script.
/// </summary>
[Parameter(Mandatory = true)]
[ValidateNotNullOrEmpty()]
public string Description { get; set; }

/// <summary>
/// A unique identifier for the script. The GUID can be used to distinguish among scripts with the same name.
/// </summary>
[Parameter]
[ValidateNotNullOrEmpty()]
public Guid Guid { get; set; }

/// <summary>
/// The name of the company owning the script.
/// </summary>
[Parameter]
[ValidateNotNullOrEmpty()]
public string CompanyName { get; set; }

/// <summary>
/// The copyright statement for the script.
/// </summary>
[Parameter]
[ValidateNotNullOrEmpty()]
public string Copyright { get; set; }

/// <summary>
/// The list of modules required by the script.
/// </summary>
[Parameter]
[ValidateNotNullOrEmpty()]
public Hashtable[] RequiredModules { get; set; }

/// <summary>
/// The list of external module dependencies taken by this script.
/// </summary>
[Parameter]
[ValidateNotNullOrEmpty()]
public string[] ExternalModuleDependencies { get; set; }

/// <summary>
/// The list of scripts required by the script.
/// </summary>
[Parameter]
[ValidateNotNullOrEmpty()]
public string[] RequiredScripts { get; set; }

/// <summary>
/// The list of external script dependencies taken by this script.
/// </summary>
[Parameter]
[ValidateNotNullOrEmpty()]
public string[] ExternalScriptDependencies { get; set; }

/// <summary>
/// The tags associated with the script.
/// </summary>
[Parameter]
[ValidateNotNullOrEmpty()]
public string[] Tags { get; set; }

/// <summary>
/// The Uri for the project associated with the script.
/// </summary>
[Parameter]
[ValidateNotNullOrEmpty()]
public string ProjectUri { get; set; }

/// <summary>
/// The Uri for the license associated with the script.
/// </summary>
[Parameter]
[ValidateNotNullOrEmpty()]
public string LicenseUri { get; set; }

/// <summary>
/// The Uri for the icon associated with the script.
/// </summary>
[Parameter]
[ValidateNotNullOrEmpty()]
public string IconUri { get; set; }

/// <summary>
/// The release notes for the script.
/// </summary>
[Parameter]
[ValidateNotNullOrEmpty()]
public string ReleaseNotes { get; set; }

/// <summary>
/// The private data associated with the script.
/// </summary>
[Parameter]
[ValidateNotNullOrEmpty()]
public string PrivateData { get; set; }

/// <summary>
/// If used with Path parameter and .ps1 file specified at the path exists, it rewrites the file.
/// </summary>
[Parameter]
public SwitchParameter Force { get; set; }

#endregion

#region Methods

protected override void EndProcessing()
{
// validate Uri related parameters passed in as strings
Uri projectUri = null;
if (!String.IsNullOrEmpty(ProjectUri) && !Utils.TryCreateValidUri(uriString: ProjectUri,
cmdletPassedIn: this,
uriResult: out projectUri,
errorRecord: out ErrorRecord projectErrorRecord))
{
ThrowTerminatingError(projectErrorRecord);
}

Uri licenseUri = null;
if (!String.IsNullOrEmpty(LicenseUri) && !Utils.TryCreateValidUri(uriString: LicenseUri,
cmdletPassedIn: this,
uriResult: out licenseUri,
errorRecord: out ErrorRecord licenseErrorRecord))
{
ThrowTerminatingError(licenseErrorRecord);
}

Uri iconUri = null;
if (!String.IsNullOrEmpty(IconUri) && !Utils.TryCreateValidUri(uriString: IconUri,
cmdletPassedIn: this,
uriResult: out iconUri,
errorRecord: out ErrorRecord iconErrorRecord))
{
ThrowTerminatingError(iconErrorRecord);
}

if (!FilePath.EndsWith(".ps1", StringComparison.OrdinalIgnoreCase))
{
var exMessage = "Path needs to end with a .ps1 file. Example: C:/Users/john/x/MyScript.ps1";
var ex = new ArgumentException(exMessage);
var InvalidPathError = new ErrorRecord(ex, "InvalidPath", ErrorCategory.InvalidArgument, null);
ThrowTerminatingError(InvalidPathError);
}

var resolvedFilePath = SessionState.Path.GetUnresolvedProviderPathFromPSPath(FilePath);
if (String.IsNullOrEmpty(resolvedFilePath))
{
var exMessage = "Error: Could not resolve provided Path argument into a single path.";
var ex = new PSArgumentException(exMessage);
var InvalidPathArgumentError = new ErrorRecord(ex, "InvalidPathArgumentError", ErrorCategory.InvalidArgument, null);
ThrowTerminatingError(InvalidPathArgumentError);
}

if (File.Exists(resolvedFilePath) && !Force)
{
// .ps1 file at specified location already exists and Force parameter isn't used to rewrite the file
var exMessage = ".ps1 file at specified path already exists. Specify a different location or use -Force parameter to overwrite the .ps1 file.";
var ex = new ArgumentException(exMessage);
var ScriptAtPathAlreadyExistsError = new ErrorRecord(ex, "ScriptAtPathAlreadyExists", ErrorCategory.InvalidArgument, null);
ThrowTerminatingError(ScriptAtPathAlreadyExistsError);
}

ModuleSpecification[] validatedRequiredModuleSpecifications = Array.Empty<ModuleSpecification>();
if (RequiredModules != null && RequiredModules.Length > 0)
{
if (!Utils.TryCreateModuleSpecification(
moduleSpecHashtables: RequiredModules,
out validatedRequiredModuleSpecifications,
out ErrorRecord[] moduleSpecErrors))
{
foreach (ErrorRecord err in moduleSpecErrors)
{
WriteError(err);
}

return;
}
}

PSScriptFileInfo scriptInfo = new PSScriptFileInfo(
version: Version,
guid: Guid,
author: Author,
companyName: CompanyName,
copyright: Copyright,
tags: Tags,
licenseUri: licenseUri,
projectUri: projectUri,
iconUri: iconUri,
requiredModules: validatedRequiredModuleSpecifications,
externalModuleDependencies: ExternalModuleDependencies,
requiredScripts: RequiredScripts,
externalScriptDependencies: ExternalScriptDependencies,
releaseNotes: ReleaseNotes,
privateData: PrivateData,
description: Description);

if (!scriptInfo.TryCreateScriptFileInfoString(
psScriptFileContents: out string[] psScriptFileContents,
errors: out ErrorRecord[] errors))
{
foreach (ErrorRecord err in errors)
{
WriteError(err);
}

return;
}

File.WriteAllLines(resolvedFilePath, psScriptFileContents);
}

#endregion
}
}
117 changes: 117 additions & 0 deletions src/code/PSScriptContents.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

using System;

namespace Microsoft.PowerShell.PowerShellGet.UtilClasses
{
/// <summary>
/// This class contains information for a PSScriptFileInfo (representing a .ps1 file contents).
/// </summary>
public sealed class PSScriptContents
{
#region Properties

/// <summary>
/// End of file contents for the .ps1 file.
/// </summary>
public string[] EndOfFileContents { get; private set; } = Utils.EmptyStrArray;

/// <summary>
/// End of file contents for the .ps1 file.
/// </summary>
public bool ContainsSignature { get; set; } = false;

#endregion

#region Private Members

private const string signatureStartString = "# SIG # Begin signature block";
private int _signatureStartIndex = -1;

#endregion

#region Constructor

/// <summary>
/// This constructor takes end of file contents as a string and checks if it has a signature.
/// </summary>
public PSScriptContents(string[] endOfFileContents)
{
EndOfFileContents = endOfFileContents;
ContainsSignature = CheckForSignature();
}

/// <summary>
/// This constructor creates a PSScriptContents instance with default values for its properties.
/// The calling method, like PSScriptContents.ParseContent() could then populate the properties.
/// </summary>
internal PSScriptContents() {}

#endregion

#region Internal Methods

/// <summary>
/// Parses end of file contents as a string from the file lines passed in
/// and sets property indicating whether those contents contain a signature.
/// </summary>
internal void ParseContent(string[] commentLines)
{
if (commentLines.Length != 0)
{
EndOfFileContents = commentLines;
ContainsSignature = CheckForSignature();
}
}

/// <summary>
/// This function is called by PSScriptFileInfo.TryCreateScriptFileInfoString(),
/// by the New-PSScriptFileInfo cmdlet (in which case EndOfFileContents is an empty string so there's no signature that'll get removed)
/// or by Update-PSScriptFileInfo cmdlet (in which case EndOfFileContents may not be empty and may contain a signature.
/// When emitting contents, any file signature is always removed because it is invalidated when the content is updated.
/// </summary>
internal string[] EmitContent()
{
RemoveSignatureString();
return EndOfFileContents;
}

#endregion

#region Private Methods

/// <summary>
/// Checks if the end of file contents contain a signature.
/// </summary>
private bool CheckForSignature()
{
for (int i = 0; i < EndOfFileContents.Length; i++)
{
if (String.Equals(EndOfFileContents[i], signatureStartString, StringComparison.InvariantCultureIgnoreCase))
{
_signatureStartIndex = i;
}
}

return _signatureStartIndex != -1;
}

/// <summary>
/// Removes the signature from EndOfFileContents property
/// as the signature would be invalidated during update.
/// </summary>
private void RemoveSignatureString()
{
if (ContainsSignature)
{
string[] newEndOfFileContents = new string[EndOfFileContents.Length - _signatureStartIndex];
Array.Copy(EndOfFileContents, newEndOfFileContents, _signatureStartIndex);
EndOfFileContents = newEndOfFileContents;

ContainsSignature = false;
}
}
#endregion
}
}
Loading