Skip to content
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

Fixed CLI output format argument not working #2699 #2700

Merged
merged 1 commit into from
Jan 6, 2025
Merged
Show file tree
Hide file tree
Changes from all 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
2 changes: 2 additions & 0 deletions docs/CHANGELOG-v3.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,8 @@ What's changed since pre-release v3.0.0-B0351:
[#1832](https://github.com/microsoft/PSRule/issues/1832)
- Fixed path navigation with XML nodes by @BernieWhite.
[#1518](https://github.com/microsoft/PSRule/issues/1518)
- Fixed CLI output format argument not working by @BernieWhite.
[#2699](https://github.com/microsoft/PSRule/issues/2699)

## v3.0.0-B0351 (pre-release)

Expand Down
16 changes: 14 additions & 2 deletions docs/concepts/cli/run.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,15 +51,27 @@ The supported values are:
This aggregated outcome includes `Fail`, or `Error` results.

To specify multiple values, specify the parameter multiple times.
For example: `--outcome Pass --Outcome Fail`.
For example: `--outcome Pass --outcome Fail`.

### `--output` | `-o`

Specifies the format to use when outputting results.
Specifies the format to use when outputting results to file in addition to the console.
By default, results are not written to a file.

The supported values are:

- `Yaml` - Output results in YAML format.
- `Json` - Output results in JSON format.
- `Markdown` - Output results in Markdown format.
- `NUnit3` - Output results in NUnit format.
- `Csv` - Output results in CSV format.
- `Sarif` - Output results in SARIF format.

### `--output-path`

Specifies a path to write results to.
Use this argument in conjunction with the `--output` to set the output format.
By default, results are not written to a file.

## Next steps

Expand Down
14 changes: 13 additions & 1 deletion src/PSRule.CommandLine/ClientContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,9 @@ public sealed class ClientContext
/// <param name="option"></param>
/// <param name="verbose"></param>
/// <param name="debug"></param>
/// <param name="workingPath"></param>
/// <exception cref="ArgumentNullException"></exception>
public ClientContext(InvocationContext invocation, string? option, bool verbose, bool debug)
public ClientContext(InvocationContext invocation, string? option, bool verbose, bool debug, string? workingPath = null)
{
Path = AppDomain.CurrentDomain.BaseDirectory;
Invocation = invocation ?? throw new ArgumentNullException(nameof(invocation));
Expand All @@ -31,6 +32,7 @@ public ClientContext(InvocationContext invocation, string? option, bool verbose,
Option = GetOption(Host, option);
CachePath = Path;
IntegrityAlgorithm = Option.Execution.HashAlgorithm.GetValueOrDefault(ExecutionOption.Default.HashAlgorithm!.Value).ToIntegrityAlgorithm();
WorkingPath = workingPath;
}

/// <summary>
Expand Down Expand Up @@ -74,6 +76,16 @@ public ClientContext(InvocationContext invocation, string? option, bool verbose,
/// </summary>
public IntegrityAlgorithm IntegrityAlgorithm { get; }

/// <summary>
/// A map of resolved module versions when no version is specified.
/// </summary>
public Dictionary<string, string> ResolvedModuleVersions { get; } = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);

/// <summary>
/// The current working path.
/// </summary>
public string? WorkingPath { get; }

private static PSRuleOption GetOption(ClientHost host, string? path)
{
PSRuleOption.UseHostContext(host);
Expand Down
9 changes: 9 additions & 0 deletions src/PSRule.CommandLine/ClientHost.cs
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,15 @@ public override void Debug(string text)
_Context.Invocation.Console.WriteLine(text);
}

/// <summary>
///
/// </summary>
/// <returns></returns>
public override string GetWorkingPath()
{
return _Context.WorkingPath == null ? base.GetWorkingPath() : _Context.WorkingPath;
}

/// <inheritdoc/>
public override string? CachePath => _Context.CachePath;
}
4 changes: 4 additions & 0 deletions src/PSRule.CommandLine/Commands/ModuleCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -65,13 +65,15 @@ public static async Task<int> ModuleRestoreAsync(RestoreOptions operationOptions
// clientContext.LogVerbose(Messages.UsingModule, module, targetVersion.ToString());
if (IsInstalled(pwsh, module, targetVersion, out var installedVersion, out _) && !operationOptions.Force)
{
clientContext.ResolvedModuleVersions[module] = installedVersion.ToShortString();
clientContext.LogVerbose($"[PSRule][M] -- The module {module} is already installed.");
continue;
}

var idealVersion = await FindVersionAsync(module, null, targetVersion, null, cancellationToken);
if (idealVersion != null)
{
clientContext.ResolvedModuleVersions[module] = idealVersion.ToShortString();
installedVersion = await InstallVersionAsync(clientContext, module, idealVersion, kv.Value.Integrity, cancellationToken);
}

Expand Down Expand Up @@ -104,6 +106,7 @@ public static async Task<int> ModuleRestoreAsync(RestoreOptions operationOptions
(moduleConstraint == null || moduleConstraint.Accepts(installedVersion)))
{
// invocation.Log(Messages.UsingModule, includeModule, installedVersion.ToString());
clientContext.ResolvedModuleVersions[includeModule] = installedVersion.ToShortString();
clientContext.LogVerbose($"[PSRule][M] -- The module {includeModule} is already installed.");
continue;
}
Expand All @@ -112,6 +115,7 @@ public static async Task<int> ModuleRestoreAsync(RestoreOptions operationOptions
var idealVersion = await FindVersionAsync(includeModule, moduleConstraint, null, null, cancellationToken);
if (idealVersion != null)
{
clientContext.ResolvedModuleVersions[includeModule] = idealVersion.ToShortString();
await InstallVersionAsync(clientContext, includeModule, idealVersion, null, cancellationToken);
}
else if (idealVersion == null)
Expand Down
12 changes: 11 additions & 1 deletion src/PSRule.CommandLine/Commands/RunCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -36,10 +36,20 @@ public static async Task<int> RunAsync(RunOptions operationOptions, ClientContex
[Environment.GetWorkingPath()] : operationOptions.InputPath;

if (operationOptions.Path != null)
{
clientContext.Option.Include.Path = operationOptions.Path;
}

if (operationOptions.Outcome != null && operationOptions.Outcome.Value != Rules.RuleOutcome.None)
{
clientContext.Option.Output.Outcome = operationOptions.Outcome;
}

if (operationOptions.OutputPath != null && operationOptions.OutputFormat != null && operationOptions.OutputFormat.Value != OutputFormat.None)
{
clientContext.Option.Output.Path = operationOptions.OutputPath;
clientContext.Option.Output.Format = operationOptions.OutputFormat.Value;
}

// Run restore command.
if (!operationOptions.NoRestore)
Expand All @@ -52,7 +62,7 @@ public static async Task<int> RunAsync(RunOptions operationOptions, ClientContex
}

// Build command.
var builder = CommandLineBuilder.Assert(operationOptions.Module ?? [], clientContext.Option, clientContext.Host, file);
var builder = CommandLineBuilder.Assert(operationOptions.Module ?? [], clientContext.Option, clientContext.Host, file, clientContext.ResolvedModuleVersions);
builder.Baseline(BaselineOption.FromString(operationOptions.Baseline));
builder.InputPath(inputPath);
builder.UnblockPublisher(PUBLISHER);
Expand Down
21 changes: 16 additions & 5 deletions src/PSRule.CommandLine/Models/RunOptions.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

using PSRule.Configuration;
using PSRule.Rules;

namespace PSRule.CommandLine.Models;
Expand All @@ -11,30 +12,40 @@ namespace PSRule.CommandLine.Models;
public sealed class RunOptions
{
/// <summary>
///
/// The path to search for rules.
/// </summary>
public string[]? Path { get; set; }

/// <summary>
///
/// A list of modules to use.
/// </summary>
public string[]? Module { get; set; }

/// <summary>
///
/// A baseline to use.
/// </summary>
public string? Baseline { get; set; }

/// <summary>
///
/// Only show output with the specified outcome.
/// </summary>
public RuleOutcome? Outcome { get; set; }

/// <summary>
///
/// The input path to search for input files.
/// </summary>
public string[]? InputPath { get; set; }

/// <summary>
/// The output path to write output files.
/// </summary>
public string? OutputPath { get; set; }

/// <summary>
/// The format to write output files.
/// </summary>
public OutputFormat? OutputFormat { get; set; }

/// <summary>
/// Do not restore modules before running rules.
/// </summary>
Expand Down
25 changes: 25 additions & 0 deletions src/PSRule.CommandLine/StringExtensions.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

using PSRule.Configuration;
using PSRule.Rules;

namespace PSRule.CommandLine;

/// <summary>
Expand All @@ -24,4 +27,26 @@ public static class StringExtensions

return value[0] == APOSTROPHE && value[value.Length - 1] == APOSTROPHE ? value.Substring(1, value.Length - 2) : value;
}

/// <summary>
/// Convert a string to <see cref="OutputFormat"/>.
/// </summary>
public static OutputFormat ToOutputFormat(this string? value)
{
return value != null && Enum.TryParse<OutputFormat>(value, true, out var result) ? result : OutputFormat.None;
}

/// <summary>
/// Convert string arguments to flags of <see cref="RuleOutcome"/>.
/// </summary>
public static RuleOutcome? ToRuleOutcome(this string[]? s)
{
var result = RuleOutcome.None;
for (var i = 0; s != null && i < s.Length; i++)
{
if (Enum.TryParse(s[i], ignoreCase: true, result: out RuleOutcome flag))
result |= flag;
}
return result == RuleOutcome.None ? null : result;
}
}
25 changes: 6 additions & 19 deletions src/PSRule.EditorServices/ClientBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@
using PSRule.CommandLine.Models;
using PSRule.EditorServices.Resources;
using PSRule.Pipeline;
using PSRule.Rules;

namespace PSRule.EditorServices;

Expand All @@ -31,7 +30,7 @@ internal sealed class ClientBuilder
private readonly Option<bool> _Module_Add_Force;
private readonly Option<bool> _Module_Add_SkipVerification;
private readonly Option<string[]> _Global_Path;
private readonly Option<DirectoryInfo> _Run_OutputPath;
private readonly Option<string> _Run_OutputPath;
private readonly Option<string> _Run_OutputFormat;
private readonly Option<string[]> _Run_InputPath;
private readonly Option<string[]> _Run_Module;
Expand Down Expand Up @@ -63,14 +62,14 @@ private ClientBuilder(RootCommand cmd)
);

// Options for the run command.
_Run_OutputPath = new Option<DirectoryInfo>(
_Run_OutputPath = new Option<string>(
["--output-path"],
description: CmdStrings.Run_OutputPath_Description
);
_Run_OutputFormat = new Option<string>(
["-o", "--output"],
description: CmdStrings.Run_OutputFormat_Description
);
).FromAmong("Yaml", "Json", "Markdown", "NUnit3", "Csv", "Sarif");
_Run_InputPath = new Option<string[]>(
["-f", "--input-path"],
description: CmdStrings.Run_InputPath_Description
Expand Down Expand Up @@ -158,7 +157,9 @@ private void AddRun()
InputPath = invocation.ParseResult.GetValueForOption(_Run_InputPath),
Module = invocation.ParseResult.GetValueForOption(_Run_Module),
Baseline = invocation.ParseResult.GetValueForOption(_Run_Baseline),
Outcome = ParseOutcome(invocation.ParseResult.GetValueForOption(_Run_Outcome)),
Outcome = invocation.ParseResult.GetValueForOption(_Run_Outcome).ToRuleOutcome(),
OutputPath = invocation.ParseResult.GetValueForOption(_Run_OutputPath),
OutputFormat = invocation.ParseResult.GetValueForOption(_Run_OutputFormat).ToOutputFormat(),
NoRestore = invocation.ParseResult.GetValueForOption(_Run_NoRestore),
};
var client = GetClientContext(invocation);
Expand Down Expand Up @@ -342,18 +343,4 @@ private ClientContext GetClientContext(InvocationContext invocation)
debug: debug
);
}

/// <summary>
/// Convert string arguments to flags of <see cref="RuleOutcome"/>.
/// </summary>
private static RuleOutcome? ParseOutcome(string[]? s)
{
var result = RuleOutcome.None;
for (var i = 0; s != null && i < s.Length; i++)
{
if (Enum.TryParse(s[i], ignoreCase: true, result: out RuleOutcome flag))
result |= flag;
}
return result == RuleOutcome.None ? null : result;
}
}
26 changes: 7 additions & 19 deletions src/PSRule.Tool/ClientBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@
using PSRule.CommandLine;
using PSRule.CommandLine.Commands;
using PSRule.CommandLine.Models;
using PSRule.Rules;
using PSRule.Tool.Resources;

namespace PSRule.Tool;
Expand All @@ -31,7 +30,7 @@ internal sealed class ClientBuilder
private readonly Option<bool> _Module_Add_SkipVerification;
private readonly Option<bool> _Module_Prerelease;
private readonly Option<string[]> _Global_Path;
private readonly Option<DirectoryInfo> _Run_OutputPath;
private readonly Option<string> _Run_OutputPath;
private readonly Option<string> _Run_OutputFormat;
private readonly Option<string[]> _Run_InputPath;
private readonly Option<string[]> _Run_Module;
Expand Down Expand Up @@ -63,14 +62,14 @@ private ClientBuilder(RootCommand cmd)
);

// Options for the run command.
_Run_OutputPath = new Option<DirectoryInfo>(
_Run_OutputPath = new Option<string>(
["--output-path"],
description: CmdStrings.Run_OutputPath_Description
);
_Run_OutputFormat = new Option<string>(
["-o", "--output"],
description: CmdStrings.Run_OutputFormat_Description
);
).FromAmong("Yaml", "Json", "Markdown", "NUnit3", "Csv", "Sarif");
_Run_InputPath = new Option<string[]>(
["-f", "--input-path"],
description: CmdStrings.Run_InputPath_Description
Expand Down Expand Up @@ -162,9 +161,12 @@ private void AddRun()
InputPath = invocation.ParseResult.GetValueForOption(_Run_InputPath),
Module = invocation.ParseResult.GetValueForOption(_Run_Module),
Baseline = invocation.ParseResult.GetValueForOption(_Run_Baseline),
Outcome = ParseOutcome(invocation.ParseResult.GetValueForOption(_Run_Outcome)),
Outcome = invocation.ParseResult.GetValueForOption(_Run_Outcome).ToRuleOutcome(),
OutputPath = invocation.ParseResult.GetValueForOption(_Run_OutputPath),
OutputFormat = invocation.ParseResult.GetValueForOption(_Run_OutputFormat).ToOutputFormat(),
NoRestore = invocation.ParseResult.GetValueForOption(_Run_NoRestore),
};

var client = GetClientContext(invocation);
invocation.ExitCode = await RunCommand.RunAsync(option, client);
});
Expand Down Expand Up @@ -358,18 +360,4 @@ private ClientContext GetClientContext(InvocationContext invocation)
debug: debug
);
}

/// <summary>
/// Convert string arguments to flags of <see cref="RuleOutcome"/>.
/// </summary>
private static RuleOutcome? ParseOutcome(string[]? s)
{
var result = RuleOutcome.None;
for (var i = 0; s != null && i < s.Length; i++)
{
if (Enum.TryParse(s[i], ignoreCase: true, result: out RuleOutcome flag))
result |= flag;
}
return result == RuleOutcome.None ? null : result;
}
}
1 change: 1 addition & 0 deletions src/PSRule.Types/Properties/AssemblyInfo.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,4 @@
[assembly: InternalsVisibleTo("Microsoft.PSRule.Tool")]
[assembly: InternalsVisibleTo("PSRule.Tests")]
[assembly: InternalsVisibleTo("PSRule.Types.Tests")]
[assembly: InternalsVisibleTo("PSRule.CommandLine.Tests")]
Loading
Loading