Skip to content

Commit

Permalink
Fixed CLI output format argument not working #2699 (#2700)
Browse files Browse the repository at this point in the history
  • Loading branch information
BernieWhite authored Jan 6, 2025
1 parent 7d5a1d8 commit 0a6566d
Show file tree
Hide file tree
Showing 15 changed files with 191 additions and 52 deletions.
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

0 comments on commit 0a6566d

Please sign in to comment.