From 79d22a0c4489eead64c4effffac2a0a40fea10dc Mon Sep 17 00:00:00 2001 From: Muhammad Danish Date: Tue, 24 Oct 2023 23:33:15 +0500 Subject: [PATCH 01/12] Add --info command to display key information --- doc/settings.md | 16 +- src/WingetCreateCLI/Commands/BaseCommand.cs | 2 +- src/WingetCreateCLI/Common.cs | 36 ++++ src/WingetCreateCLI/Models/SettingsModel.cs | 17 +- src/WingetCreateCLI/Program.cs | 129 +++++++++++---- .../Properties/Resources.Designer.cs | 155 +++++++++++++++++- src/WingetCreateCLI/Properties/Resources.resx | 53 +++++- src/WingetCreateCLI/RootOptions.cs | 74 +++++++++ .../Schemas/settings.schema.0.1.json | 20 ++- src/WingetCreateCLI/TableOutput.cs | 95 +++++++++++ src/WingetCreateCLI/UserSettings.cs | 15 ++ src/WingetCreateCore/Common/Utils.cs | 2 + .../UnitTests/SettingsCommandTests.cs | 7 +- 13 files changed, 582 insertions(+), 39 deletions(-) create mode 100644 src/WingetCreateCLI/RootOptions.cs create mode 100644 src/WingetCreateCLI/TableOutput.cs diff --git a/doc/settings.md b/doc/settings.md index b0a22b8b..aecb47c2 100644 --- a/doc/settings.md +++ b/doc/settings.md @@ -21,7 +21,7 @@ If set to true, the `telemetry.disable` setting will prevent any event from bein ## CleanUp -The `CleanUp` settings determine whether Winget-Create will handle the removal of temporary files (installer cache and logs) generated during the manifest creation process. These settings provide control over the decision to remove files or not and the frequency at which this clean up occurs. +The `CleanUp` settings determine whether Winget-Create will handle the removal of temporary files i.e., installers downloaded and logs generated during the manifest creation process. You can view the location of these files using `wingetcreate --info` command. These settings provide control over the decision to remove files or not and the frequency at which this clean up occurs. ### disable @@ -60,3 +60,17 @@ The `name` setting specifies the name of the targeted GitHub repository. By defa "name": "winget-pkgs" } ``` + +## Visual + +The `Visual` settings control the appearance of the Winget-Create CLI output. + +### anonymizePaths + +The `anonymizePaths` setting controls whether the paths of files and directories are anonymized in the Winget-Create CLI output. This means that a path such as `C:\Users\user\Documents\manifests\` will be displayed as `%USERPROFILE%\Documents\manifests` (i.e., substitute environment variables where possible). By default, this is set to `true`. + +```json + "Visual": { + "anonymizePaths": true + }, +``` diff --git a/src/WingetCreateCLI/Commands/BaseCommand.cs b/src/WingetCreateCLI/Commands/BaseCommand.cs index e1ca5ff8..8ea0e26f 100644 --- a/src/WingetCreateCLI/Commands/BaseCommand.cs +++ b/src/WingetCreateCLI/Commands/BaseCommand.cs @@ -194,7 +194,7 @@ protected static string SaveManifestDirToLocalPath( } Console.WriteLine(); - Logger.InfoLocalized(nameof(Resources.ManifestSaved_Message), fullDirPath); + Logger.InfoLocalized(nameof(Resources.ManifestSaved_Message), Common.GetPathForDisplay(fullDirPath, UserSettings.AnonymizePaths)); Console.WriteLine(); return fullDirPath; diff --git a/src/WingetCreateCLI/Common.cs b/src/WingetCreateCLI/Common.cs index 8a2a3c23..4b3a8c48 100644 --- a/src/WingetCreateCLI/Common.cs +++ b/src/WingetCreateCLI/Common.cs @@ -13,6 +13,9 @@ namespace Microsoft.WingetCreateCLI public static class Common { private const string ModuleName = "WindowsPackageManagerManifestCreator"; + private const string UserProfileEnvironmentVariable = "%USERPROFILE%"; + private const string LocalAppDataEnvironmentVariable = "%LOCALAPPDATA%"; + private const string TempEnvironmentVariable = "%TEMP%"; private static readonly Lazy AppStatePathLazy = new(() => { @@ -61,6 +64,39 @@ public static void CleanUpFilesOlderThan(string cleanUpDirectory, int cleanUpDay } } + /// + /// Gets the path for display. This will anonymize the path if caller provides the appropriate flag. + /// + /// Path to be displayed. + /// Whether or not to substitute environment variables. + /// Anonymized path or original path. + public static string GetPathForDisplay(string path, bool substitueEnvironmentVariables = true) + { + if (string.IsNullOrEmpty(path) || !substitueEnvironmentVariables) + { + return path; + } + + string userProfilePath = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile); + string localAppDataPath = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData); + string tempPath = Path.GetTempPath(); + + if (path.StartsWith(tempPath, StringComparison.OrdinalIgnoreCase)) + { + return path.Replace(tempPath, TempEnvironmentVariable + Path.DirectorySeparatorChar, StringComparison.OrdinalIgnoreCase); + } + else if (path.StartsWith(localAppDataPath, StringComparison.OrdinalIgnoreCase)) + { + return path.Replace(localAppDataPath, LocalAppDataEnvironmentVariable, StringComparison.OrdinalIgnoreCase); + } + else if (path.StartsWith(userProfilePath, StringComparison.OrdinalIgnoreCase)) + { + return path.Replace(userProfilePath, UserProfileEnvironmentVariable, StringComparison.OrdinalIgnoreCase); + } + + return path; + } + private static bool IsRunningAsUwp() { DesktopBridge.Helpers helpers = new DesktopBridge.Helpers(); diff --git a/src/WingetCreateCLI/Models/SettingsModel.cs b/src/WingetCreateCLI/Models/SettingsModel.cs index b1a0643f..c9b84efd 100644 --- a/src/WingetCreateCLI/Models/SettingsModel.cs +++ b/src/WingetCreateCLI/Models/SettingsModel.cs @@ -48,6 +48,17 @@ public partial class WindowsPackageManagerRepository public string Name { get; set; } = "winget-pkgs"; + } + + /// Visual settings + [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "10.4.3.0 (Newtonsoft.Json v11.0.0.0)")] + public partial class Visual + { + /// Controls whether paths displayed on the console are substituted with environment variables + [Newtonsoft.Json.JsonProperty("anonymizePaths", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public bool AnonymizePaths { get; set; } = true; + + } [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "10.4.3.0 (Newtonsoft.Json v11.0.0.0)")] @@ -69,6 +80,10 @@ public partial class SettingsManifest [System.ComponentModel.DataAnnotations.Required] public WindowsPackageManagerRepository WindowsPackageManagerRepository { get; set; } = new WindowsPackageManagerRepository(); + [Newtonsoft.Json.JsonProperty("Visual", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + [System.ComponentModel.DataAnnotations.Required] + public Visual Visual { get; set; } = new Visual(); + } -} +} \ No newline at end of file diff --git a/src/WingetCreateCLI/Program.cs b/src/WingetCreateCLI/Program.cs index 6468fbcb..04bf3829 100644 --- a/src/WingetCreateCLI/Program.cs +++ b/src/WingetCreateCLI/Program.cs @@ -22,6 +22,18 @@ namespace Microsoft.WingetCreateCLI /// internal class Program { + /// + /// Displays the application header and the copyright. + /// + public static void DisplayApplicationHeaderAndCopyright() + { + Console.WriteLine(string.Format( + Resources.Heading, + Utils.GetEntryAssemblyVersion()) + + Environment.NewLine + + Constants.MicrosoftCopyright); + } + private static async Task Main(string[] args) { Logger.Initialize(); @@ -48,25 +60,46 @@ private static async Task Main(string[] args) typeof(CacheCommand), typeof(ShowCommand), }; - var parserResult = myParser.ParseArguments(args, types); - BaseCommand command = parserResult.MapResult(c => c as BaseCommand, err => null); + var baseCommandsParserResult = myParser.ParseArguments(args, types); + BaseCommand parsedCommand = baseCommandsParserResult.MapResult(c => c as BaseCommand, err => null); - if (command == null) + if (parsedCommand == null) { - DisplayHelp(parserResult as NotParsed); - DisplayParsingErrors(parserResult as NotParsed); + // In case of unsuccessful parsing, check if user tried to run a valid command. + bool isBaseCommand = baseCommandsParserResult.TypeInfo.Current.BaseType.Equals(typeof(BaseCommand)); + + /* Parse root options. + * Adding '--help' is a workaround to force parser to display the options help text when no arguments are passed. + * This makes rootOptionsParserResult to be a NotParsed object which makes HelpText.AutoBuild print the correct help text. + * This is done since the parser does not print the correct help text on a successful parse. + */ + ParserResult rootOptionsParserResult = myParser.ParseArguments( + args.Length == 0 ? new string[] { "--help" } : args); + + if (!isBaseCommand) + { + rootOptionsParserResult.WithParsed(RootOptions.ParseRootOptions); + + if (rootOptionsParserResult.Tag == ParserResultType.Parsed) + { + return 0; + } + } + + DisplayHelp(baseCommandsParserResult as NotParsed, isBaseCommand ? null : rootOptionsParserResult as NotParsed); + DisplayParsingErrors(baseCommandsParserResult as NotParsed); return args.Any() ? 1 : 0; } - if (command is not SettingsCommand && command is not CacheCommand) + if (parsedCommand is not SettingsCommand && parsedCommand is not CacheCommand) { // Do not load github client for settings or cache command. - if (await command.LoadGitHubClient()) + if (await parsedCommand.LoadGitHubClient()) { try { - string latestVersion = await command.GitHubClient.GetLatestRelease(); + string latestVersion = await parsedCommand.GitHubClient.GetLatestRelease(); string trimmedVersion = latestVersion.TrimStart('v').Split('-').First(); if (trimmedVersion != Utils.GetEntryAssemblyVersion()) { @@ -84,7 +117,7 @@ private static async Task Main(string[] args) else { // Do not block creating a new manifest if loading the GitHub client fails. InstallerURL could point to a local network. - if (command is not NewCommand) + if (parsedCommand is not NewCommand) { return 1; } @@ -94,7 +127,7 @@ private static async Task Main(string[] args) try { WingetCreateCore.Serialization.ProducedBy = string.Join(" ", Constants.ProgramName, Utils.GetEntryAssemblyVersion()); - return await command.Execute() ? 0 : 1; + return await parsedCommand.Execute() ? 0 : 1; } catch (Exception ex) { @@ -118,28 +151,66 @@ private static async Task Main(string[] args) } } - private static void DisplayHelp(NotParsed result) + private static void DisplayHelp(NotParsed baseCommandsParserResult, ParserResult rootOptionsParserResult) + { + DisplayApplicationHeaderAndCopyright(); + DisplayCommandsHelpText(baseCommandsParserResult); + if (rootOptionsParserResult != null) + { + DisplayRootOptionsHelpText(rootOptionsParserResult); + } + + DisplayFooter(); + } + + private static void DisplayCommandsHelpText(NotParsed result) { var helpText = HelpText.AutoBuild( - result, - h => - { - h.AddDashesToOption = true; - h.AdditionalNewLineAfterOption = false; - h.Heading = string.Format(Resources.Heading, Utils.GetEntryAssemblyVersion()) + Environment.NewLine; - h.Copyright = Constants.MicrosoftCopyright; - h.AddNewLineBetweenHelpSections = true; - h.AddPreOptionsLine(Resources.AppDescription_HelpText); - h.AddPostOptionsLines(new string[] { Resources.MoreHelp_HelpText, Resources.PrivacyStatement_HelpText }); - h.MaximumDisplayWidth = 100; - h.AutoHelp = false; - h.AutoVersion = false; - return h; - }, - e => e, - verbsIndex: true); + result, + h => + { + h.AddDashesToOption = true; + h.AdditionalNewLineAfterOption = false; + h.Heading = string.Empty; + h.Copyright = string.Empty; + h.AddNewLineBetweenHelpSections = true; + h.AddPreOptionsLine(Resources.AppDescription_HelpText); + h.AddPreOptionsLine(Environment.NewLine); + h.AddPreOptionsLine(Resources.CommandsAvailable_Message); + h.MaximumDisplayWidth = 100; + h.AutoHelp = false; + h.AutoVersion = false; + return h; + }, + e => e, + verbsIndex: true); + Console.WriteLine(helpText); + } + + private static void DisplayRootOptionsHelpText(ParserResult result) + { + var helpText = HelpText.AutoBuild( + result, + h => + { + h.AddDashesToOption = true; + h.AdditionalNewLineAfterOption = false; + h.Heading = Resources.OptionsAvailable_Message; + h.Copyright = string.Empty; + h.AddNewLineBetweenHelpSections = false; + h.MaximumDisplayWidth = 100; + h.AutoHelp = false; + h.AutoVersion = false; + return h; + }, + e => e); Console.WriteLine(helpText); - Console.WriteLine(); + } + + private static void DisplayFooter() + { + Console.WriteLine(Resources.MoreHelp_HelpText); + Console.WriteLine(Resources.PrivacyStatement_HelpText); } private static void DisplayParsingErrors(NotParsed result) diff --git a/src/WingetCreateCLI/Properties/Resources.Designer.cs b/src/WingetCreateCLI/Properties/Resources.Designer.cs index 459935a5..7cf428a1 100644 --- a/src/WingetCreateCLI/Properties/Resources.Designer.cs +++ b/src/WingetCreateCLI/Properties/Resources.Designer.cs @@ -321,6 +321,15 @@ public static string Commands_KeywordDescription { } } + /// + /// Looks up a localized string similar to The following commands are available:. + /// + public static string CommandsAvailable_Message { + get { + return ResourceManager.GetString("CommandsAvailable_Message", resourceCulture); + } + } + /// /// Looks up a localized string similar to Please ensure that the updated manifest you are submitting is different from the existing package.. /// @@ -969,6 +978,24 @@ public static string Heading { } } + /// + /// Looks up a localized string similar to Shows help about the selected command. + /// + public static string HelpRootOption_HelpText { + get { + return ResourceManager.GetString("HelpRootOption_HelpText", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Homepage. + /// + public static string Homepage_Heading { + get { + return ResourceManager.GetString("Homepage_Heading", resourceCulture); + } + } + /// /// Looks up a localized string similar to The home page for the package. /// @@ -1041,6 +1068,15 @@ public static string IconUrl_KeywordDescription { } } + /// + /// Looks up a localized string similar to Displays general info of the tool. + /// + public static string InfoRootOption_HelpText { + get { + return ResourceManager.GetString("InfoRootOption_HelpText", resourceCulture); + } + } + /// /// Looks up a localized string similar to Initiating GitHub login.... /// @@ -1095,6 +1131,15 @@ public static string InstallerBinaryMismatch_Message { } } + /// + /// Looks up a localized string similar to Installer cache. + /// + public static string InstallerCache_Heading { + get { + return ResourceManager.GetString("InstallerCache_Heading", resourceCulture); + } + } + /// /// Looks up a localized string similar to Installer cache cleaned.. /// @@ -1374,6 +1419,15 @@ public static string License_KeywordDescription { } } + /// + /// Looks up a localized string similar to License Agreement. + /// + public static string LicenseAgreement_Heading { + get { + return ResourceManager.GetString("LicenseAgreement_Heading", resourceCulture); + } + } + /// /// Looks up a localized string similar to The license page. /// @@ -1383,6 +1437,15 @@ public static string LicenseUrl_KeywordDescription { } } + /// + /// Looks up a localized string similar to Links. + /// + public static string Links_Heading { + get { + return ResourceManager.GetString("Links_Heading", resourceCulture); + } + } + /// /// Looks up a localized string similar to Lists out all the downloaded installers stored in cache. /// @@ -1446,6 +1509,15 @@ public static string Log_KeywordDescription { } } + /// + /// Looks up a localized string similar to Logs. + /// + public static string Logs_Heading { + get { + return ResourceManager.GetString("Logs_Heading", resourceCulture); + } + } + /// /// Looks up a localized string similar to For information about the restrictions for each field, visit {0}. /// @@ -1806,6 +1878,24 @@ public static string Open_HelpText { } } + /// + /// Looks up a localized string similar to Operating System: {0}. + /// + public static string OperatingSystem_Info { + get { + return ResourceManager.GetString("OperatingSystem_Info", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The following options are available:. + /// + public static string OptionsAvailable_Message { + get { + return ResourceManager.GetString("OptionsAvailable_Message", resourceCulture); + } + } + /// /// Looks up a localized string similar to This is an older version of Winget-Create and may be missing some critical features.. /// @@ -1941,6 +2031,15 @@ public static string PackageVersion_KeywordDescription { } } + /// + /// Looks up a localized string similar to Path. + /// + public static string Path_Heading { + get { + return ResourceManager.GetString("Path_Heading", resourceCulture); + } + } + /// /// Looks up a localized string similar to Path to a manifest file or directory containing the manifests that you want to submit to the Windows Package Manager repo. /// @@ -1996,7 +2095,16 @@ public static string PressKeyToContinue_Message { } /// - /// Looks up a localized string similar to Privacy statement: https://aka.ms/privacy. + /// Looks up a localized string similar to Privacy Statement. + /// + public static string PrivacyStatement_Heading { + get { + return ResourceManager.GetString("PrivacyStatement_Heading", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Privacy statement: https://aka.ms/winget-create-privacy. /// public static string PrivacyStatement_HelpText { get { @@ -2265,6 +2373,15 @@ public static string ReturnResponseUrl_KeywordDescription { } } + /// + /// Looks up a localized string similar to Display general info of the tool. + /// + public static string RootOption_Info_HelpText { + get { + return ResourceManager.GetString("RootOption_Info_HelpText", resourceCulture); + } + } + /// /// Looks up a localized string similar to SAVE AND EXIT. /// @@ -2706,6 +2823,15 @@ public static string SyncForkWithUpstream_Message { } } + /// + /// Looks up a localized string similar to System Architecture: {0}. + /// + public static string SystemArchitecture_Info { + get { + return ResourceManager.GetString("SystemArchitecture_Info", resourceCulture); + } + } + /// /// Looks up a localized string similar to List of additional package search terms. /// @@ -2751,6 +2877,15 @@ public static string TelemetrySettings_Message { } } + /// + /// Looks up a localized string similar to Third Party Notices. + /// + public static string ThirdPartyNotices_Heading { + get { + return ResourceManager.GetString("ThirdPartyNotices_Heading", resourceCulture); + } + } + /// /// Looks up a localized string similar to Modifies the GitHub auth token cache. /// @@ -2922,6 +3057,15 @@ public static string UseOverrides_ErrorMessage { } } + /// + /// Looks up a localized string similar to User settings. + /// + public static string UserSettings_Heading { + get { + return ResourceManager.GetString("UserSettings_Heading", resourceCulture); + } + } + /// /// Looks up a localized string similar to Please check and verify the usage of this command by passing in the --help flag.. /// @@ -2994,6 +3138,15 @@ public static string WindowsLibraries_KeywordDescription { } } + /// + /// Looks up a localized string similar to Winget-Create Directories. + /// + public static string WingetCreateDirectories_Heading { + get { + return ResourceManager.GetString("WingetCreateDirectories_Heading", resourceCulture); + } + } + /// /// Looks up a localized string similar to Writing the token to cache failed: {0}. /// diff --git a/src/WingetCreateCLI/Properties/Resources.resx b/src/WingetCreateCLI/Properties/Resources.resx index e7b55e9a..1561454e 100644 --- a/src/WingetCreateCLI/Properties/Resources.resx +++ b/src/WingetCreateCLI/Properties/Resources.resx @@ -540,7 +540,7 @@ More help can be found at: https://aka.ms/winget-create - Privacy statement: https://aka.ms/privacy + Privacy statement: https://aka.ms/winget-create-privacy HTTP response was unsuccessful. Status code: {0} @@ -1189,4 +1189,55 @@ A required value not bound to option name is missing. + + Operating System: {0} + + + Path + + + System Architecture: {0} + + + Winget-Create Directories + + + Display general info of the tool + + + Installer cache + + + Logs + + + User settings + + + Homepage + + + Displays general info of the tool + + + License Agreement + + + Links + + + Privacy Statement + + + Third Party Notices + + + The following commands are available: + + + The following options are available: + + + Shows help about the selected command + \ No newline at end of file diff --git a/src/WingetCreateCLI/RootOptions.cs b/src/WingetCreateCLI/RootOptions.cs new file mode 100644 index 00000000..46063268 --- /dev/null +++ b/src/WingetCreateCLI/RootOptions.cs @@ -0,0 +1,74 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. + +namespace Microsoft.WingetCreateCLI +{ + using System; + using System.IO; + using CommandLine; + using Microsoft.WingetCreateCLI.Logging; + using Microsoft.WingetCreateCLI.Properties; + using Microsoft.WingetCreateCore; + + /// + /// Command line options for the root command. + /// + internal class RootOptions + { + /// + /// Gets or sets a value indicating whether or not to display the general info text. + /// + [Option("info", Required = false, HelpText = "InfoRootOption_HelpText", ResourceType = typeof(Resources))] + public bool Info { get; set; } + + /// + /// Gets or sets a value indicating whether or not to display the help text. + /// + [Option("help", Required = false, HelpText = "HelpRootOption_HelpText", ResourceType = typeof(Resources))] + public bool Help { get; set; } + + /// + /// Parses the root options and displays the appropriate output. + /// + /// Root options to execute. + public static void ParseRootOptions(RootOptions rootOptions) + { + if (rootOptions.Info) + { + Program.DisplayApplicationHeaderAndCopyright(); + Console.WriteLine(); + DisplaySystemInformation(); + Console.WriteLine(); + DisplayInfoTable(); + } + } + + private static void DisplaySystemInformation() + { + Logger.DebugLocalized(nameof(Resources.OperatingSystem_Info), Environment.OSVersion.VersionString); + Logger.DebugLocalized(nameof(Resources.SystemArchitecture_Info), System.Runtime.InteropServices.RuntimeInformation.OSArchitecture); + } + + private static void DisplayInfoTable() + { + string logsdirectory = Common.GetPathForDisplay(Path.Combine(Common.LocalAppStatePath, "DiagOutputDir"), UserSettings.AnonymizePaths); + string settingsDirectory = Common.GetPathForDisplay(UserSettings.SettingsJsonPath, UserSettings.AnonymizePaths); + string installerCacheDirectory = Common.GetPathForDisplay(PackageParser.InstallerDownloadPath, UserSettings.AnonymizePaths); + + new TableOutput(Resources.WingetCreateDirectories_Heading, Resources.Path_Heading) + .AddRow(Resources.Logs_Heading, logsdirectory) + .AddRow(Resources.UserSettings_Heading, settingsDirectory) + .AddRow(Resources.InstallerCache_Heading, installerCacheDirectory) + .Print(); + + Console.WriteLine(); + + new TableOutput(Resources.Links_Heading, string.Empty) + .AddRow(Resources.PrivacyStatement_Heading, "https://aka.ms/winget-create-privacy") + .AddRow(Resources.LicenseAgreement_Heading, "https://aka.ms/winget-create-license") + .AddRow(Resources.ThirdPartyNotices_Heading, "https://aka.ms/winget-create-3rdPartyNotices") + .AddRow(Resources.Homepage_Heading, "https://aka.ms/winget-create") + .Print(); + } + } +} diff --git a/src/WingetCreateCLI/Schemas/settings.schema.0.1.json b/src/WingetCreateCLI/Schemas/settings.schema.0.1.json index 13d63433..cd72df3e 100644 --- a/src/WingetCreateCLI/Schemas/settings.schema.0.1.json +++ b/src/WingetCreateCLI/Schemas/settings.schema.0.1.json @@ -25,7 +25,7 @@ "default": 7, "minimum": 1 }, - "disable" : { + "disable": { "description": "Controls whether clean up is disabled", "type": "boolean", "default": false @@ -49,6 +49,18 @@ } }, "additionalProperties": false + }, + "Visual": { + "description": "Visual settings", + "type": "object", + "properties": { + "anonymizePaths": { + "description": "Controls whether paths displayed on the console are substituted with environment variables", + "type": "boolean", + "default": true + } + }, + "additionalProperties": false } }, "type": "object", @@ -60,12 +72,14 @@ }, "Telemetry": { "$ref": "#/definitions/Telemetry" }, "CleanUp": { "$ref": "#/definitions/CleanUp" }, - "WindowsPackageManagerRepository": { "$ref": "#/definitions/WindowsPackageManagerRepository" } + "WindowsPackageManagerRepository": { "$ref": "#/definitions/WindowsPackageManagerRepository" }, + "Visual": { "$ref": "#/definitions/Visual" } }, "required": [ "Telemetry", "CleanUp", - "WindowsPackageManagerRepository" + "WindowsPackageManagerRepository", + "Visual" ], "additionalProperties": false } \ No newline at end of file diff --git a/src/WingetCreateCLI/TableOutput.cs b/src/WingetCreateCLI/TableOutput.cs new file mode 100644 index 00000000..d291b266 --- /dev/null +++ b/src/WingetCreateCLI/TableOutput.cs @@ -0,0 +1,95 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. + +namespace Microsoft.WingetCreateCLI +{ + using System; + using System.Collections.Generic; + using System.Linq; + + /// + /// Helper class to generate a formatted table. + /// + public class TableOutput + { + private readonly List columns = new(); + private readonly List> rows = new(); + private readonly int padding = 2; + + /// + /// Initializes a new instance of the class. + /// Provide column names to generate a formatted table. + /// + /// List of column names. + public TableOutput(params string[] columnNames) + { + this.columns.AddRange(columnNames); + } + + /// + /// Add a row to the table. + /// + /// List of row entries. + /// TableOutput object to allow chaining method calls. + public TableOutput AddRow(params string[] rowData) + { + this.rows.Add(new List(rowData)); + return this; + } + + /// + /// Write the table to the standard output. + /// + public void Print() + { + // Calculate the maximum width of each column. + List columnWidths = this.CalculateColumnWidths(); + + // Print the column names. + for (int i = 0; i < this.columns.Count; i++) + { + Console.Write("{0, -" + (columnWidths[i] + this.padding) + "}", this.columns[i]); + } + + Console.WriteLine(); + + // Print a line of dashes to separate the column names from the rows. + Console.WriteLine(new string('-', columnWidths.Sum() + columnWidths.Count + this.padding)); + + // Print the rows. + foreach (var row in this.rows) + { + for (int i = 0; i < this.columns.Count; i++) + { + Console.Write("{0, -" + (columnWidths[i] + this.padding) + "}", row[i]); + } + + Console.WriteLine(); + } + } + + private List CalculateColumnWidths() + { + List columnWidths = new List(); + + for (int i = 0; i < this.columns.Count; i++) + { + // Initially set the column width to the column name length. + int maxLength = this.columns[i].Length; + + foreach (var row in this.rows) + { + // Check if any row entry in the respective column is longer than the column name. + if (i < row.Count && row[i].Length > maxLength) + { + maxLength = row[i].Length; + } + } + + columnWidths.Add(maxLength); + } + + return columnWidths; + } + } +} diff --git a/src/WingetCreateCLI/UserSettings.cs b/src/WingetCreateCLI/UserSettings.cs index 908b4016..c717bd7d 100644 --- a/src/WingetCreateCLI/UserSettings.cs +++ b/src/WingetCreateCLI/UserSettings.cs @@ -105,6 +105,20 @@ public static string WindowsPackageManagerRepositoryName } } + /// + /// Gets or sets a value indicating whether paths displayed on the console are substituted with environment variables. + /// + public static bool AnonymizePaths + { + get => Settings.Visual.AnonymizePaths; + + set + { + Settings.Visual.AnonymizePaths = value; + SaveSettings(); + } + } + private static SettingsManifest Settings { get; set; } /// @@ -198,6 +212,7 @@ private static void LoadSettings() Telemetry = new Models.Settings.Telemetry(), CleanUp = new CleanUp(), WindowsPackageManagerRepository = new WindowsPackageManagerRepository(), + Visual = new Visual(), }; } } diff --git a/src/WingetCreateCore/Common/Utils.cs b/src/WingetCreateCore/Common/Utils.cs index 365e1cf5..e0af5d38 100644 --- a/src/WingetCreateCore/Common/Utils.cs +++ b/src/WingetCreateCore/Common/Utils.cs @@ -3,7 +3,9 @@ namespace Microsoft.WingetCreateCore.Common { using System; + using System.Collections.Generic; using System.IO; + using Windows.Storage; /// /// Helper class for common utility functions. diff --git a/src/WingetCreateTests/WingetCreateTests/UnitTests/SettingsCommandTests.cs b/src/WingetCreateTests/WingetCreateTests/UnitTests/SettingsCommandTests.cs index 51e7b659..0bce6f8b 100644 --- a/src/WingetCreateTests/WingetCreateTests/UnitTests/SettingsCommandTests.cs +++ b/src/WingetCreateTests/WingetCreateTests/UnitTests/SettingsCommandTests.cs @@ -89,24 +89,27 @@ public void VerifyEmptySettingsFile() } /// - /// Compares the state of the telemetry.disabled field before and after to ensure the change is reflected accordingly. + /// Compares the state of modifying user settings and verifying that the settings file is updated correctly. /// [Test] - public void VerifySavingTelemetrySettings() + public void VerifySavingSettings() { bool isTelemetryDisabled = UserSettings.TelemetryDisabled; bool isCleanUpDisabled = UserSettings.CleanUpDisabled; + bool arePathsAnonymized = UserSettings.AnonymizePaths; int cleanUpDays = 30; string testRepoOwner = "testRepoOwner"; string testRepoName = "testRepoName"; UserSettings.TelemetryDisabled = !isTelemetryDisabled; UserSettings.CleanUpDisabled = !isCleanUpDisabled; + UserSettings.AnonymizePaths = !arePathsAnonymized; UserSettings.CleanUpDays = cleanUpDays; UserSettings.WindowsPackageManagerRepositoryOwner = testRepoOwner; UserSettings.WindowsPackageManagerRepositoryName = testRepoName; UserSettings.ParseJsonFile(UserSettings.SettingsJsonPath, out SettingsManifest manifest); Assert.IsTrue(manifest.Telemetry.Disable == !isTelemetryDisabled, "Changed Telemetry setting was not reflected in the settings file."); Assert.IsTrue(manifest.CleanUp.Disable == !isCleanUpDisabled, "Changed CleanUp.Disable setting was not reflected in the settings file."); + Assert.IsTrue(manifest.Visual.AnonymizePaths == !arePathsAnonymized, "Changed Visual.AnonymizePaths setting was not reflected in the settings file."); Assert.IsTrue(manifest.CleanUp.IntervalInDays == cleanUpDays, "Changed CleanUp.IntervalInDays setting was not reflected in the settings file."); Assert.IsTrue(manifest.WindowsPackageManagerRepository.Owner == testRepoOwner, "Changed WindowsPackageManagerRepository.Owner setting was not reflected in the settings file."); Assert.IsTrue(manifest.WindowsPackageManagerRepository.Name == testRepoName, "Changed WindowsPackageManagerRepository.Name setting was not reflected in the settings file."); From 82dd07b1644097d9313046c91ab53a4de2704f9b Mon Sep 17 00:00:00 2001 From: Muhammad Danish Date: Tue, 24 Oct 2023 23:56:52 +0500 Subject: [PATCH 02/12] remove unused imports --- src/WingetCreateCore/Common/Utils.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/WingetCreateCore/Common/Utils.cs b/src/WingetCreateCore/Common/Utils.cs index e0af5d38..365e1cf5 100644 --- a/src/WingetCreateCore/Common/Utils.cs +++ b/src/WingetCreateCore/Common/Utils.cs @@ -3,9 +3,7 @@ namespace Microsoft.WingetCreateCore.Common { using System; - using System.Collections.Generic; using System.IO; - using Windows.Storage; /// /// Helper class for common utility functions. From f57baf9637807b967aebe521340fe48b4ce4ea23 Mon Sep 17 00:00:00 2001 From: Muhammad Danish Date: Wed, 1 Nov 2023 00:40:36 +0500 Subject: [PATCH 03/12] i always forget adding comments --- doc/settings.md | 2 +- src/WingetCreateCLI/Properties/Resources.resx | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/doc/settings.md b/doc/settings.md index aecb47c2..b6b01ba3 100644 --- a/doc/settings.md +++ b/doc/settings.md @@ -72,5 +72,5 @@ The `anonymizePaths` setting controls whether the paths of files and directories ```json "Visual": { "anonymizePaths": true - }, + } ``` diff --git a/src/WingetCreateCLI/Properties/Resources.resx b/src/WingetCreateCLI/Properties/Resources.resx index 1561454e..ce32c19f 100644 --- a/src/WingetCreateCLI/Properties/Resources.resx +++ b/src/WingetCreateCLI/Properties/Resources.resx @@ -1191,12 +1191,14 @@ Operating System: {0} + {0} - represents the version of the operating system installed on the user's machine Path System Architecture: {0} + {0} - represents the CPU architecture of the user's machine Winget-Create Directories From e3fed2977918e596a8ff8b4949f94d04d03596bf Mon Sep 17 00:00:00 2001 From: Muhammad Danish Date: Wed, 1 Nov 2023 21:10:17 +0500 Subject: [PATCH 04/12] address PR comments --- .../InfoCommand.cs} | 52 +++---- src/WingetCreateCLI/Common.cs | 10 +- src/WingetCreateCLI/Program.cs | 130 ++++-------------- .../Properties/Resources.Designer.cs | 31 +---- src/WingetCreateCLI/Properties/Resources.resx | 11 +- 5 files changed, 66 insertions(+), 168 deletions(-) rename src/WingetCreateCLI/{RootOptions.cs => Commands/InfoCommand.cs} (62%) diff --git a/src/WingetCreateCLI/RootOptions.cs b/src/WingetCreateCLI/Commands/InfoCommand.cs similarity index 62% rename from src/WingetCreateCLI/RootOptions.cs rename to src/WingetCreateCLI/Commands/InfoCommand.cs index 46063268..097e4399 100644 --- a/src/WingetCreateCLI/RootOptions.cs +++ b/src/WingetCreateCLI/Commands/InfoCommand.cs @@ -1,46 +1,50 @@ // Copyright (c) Microsoft. All rights reserved. // Licensed under the MIT license. -namespace Microsoft.WingetCreateCLI +namespace Microsoft.WingetCreateCLI.Commands { using System; using System.IO; + using System.Threading.Tasks; using CommandLine; using Microsoft.WingetCreateCLI.Logging; using Microsoft.WingetCreateCLI.Properties; + using Microsoft.WingetCreateCLI.Telemetry; + using Microsoft.WingetCreateCLI.Telemetry.Events; using Microsoft.WingetCreateCore; + using Microsoft.WingetCreateCore.Common; /// - /// Command line options for the root command. + /// Info command to display general information regarding the tool. /// - internal class RootOptions + [Verb("info", HelpText = "InfoCommand_HelpText", ResourceType = typeof(Resources))] + public class InfoCommand : BaseCommand { /// - /// Gets or sets a value indicating whether or not to display the general info text. + /// Executes the info command flow. /// - [Option("info", Required = false, HelpText = "InfoRootOption_HelpText", ResourceType = typeof(Resources))] - public bool Info { get; set; } + /// Boolean representing success or fail of the command. + public override async Task Execute() + { + CommandExecutedEvent commandEvent = new CommandExecutedEvent(); - /// - /// Gets or sets a value indicating whether or not to display the help text. - /// - [Option("help", Required = false, HelpText = "HelpRootOption_HelpText", ResourceType = typeof(Resources))] - public bool Help { get; set; } + DisplayApplicationHeaderAndCopyright(); + Console.WriteLine(); + DisplaySystemInformation(); + Console.WriteLine(); + DisplayInfoTable(); - /// - /// Parses the root options and displays the appropriate output. - /// - /// Root options to execute. - public static void ParseRootOptions(RootOptions rootOptions) + TelemetryManager.Log.WriteEvent(commandEvent); + return await Task.FromResult(commandEvent.IsSuccessful = true); + } + + private static void DisplayApplicationHeaderAndCopyright() { - if (rootOptions.Info) - { - Program.DisplayApplicationHeaderAndCopyright(); - Console.WriteLine(); - DisplaySystemInformation(); - Console.WriteLine(); - DisplayInfoTable(); - } + Console.WriteLine(string.Format( + Resources.Heading, + Utils.GetEntryAssemblyVersion()) + + Environment.NewLine + + Constants.MicrosoftCopyright); } private static void DisplaySystemInformation() diff --git a/src/WingetCreateCLI/Common.cs b/src/WingetCreateCLI/Common.cs index 4b3a8c48..e3c5b395 100644 --- a/src/WingetCreateCLI/Common.cs +++ b/src/WingetCreateCLI/Common.cs @@ -68,22 +68,22 @@ public static void CleanUpFilesOlderThan(string cleanUpDirectory, int cleanUpDay /// Gets the path for display. This will anonymize the path if caller provides the appropriate flag. /// /// Path to be displayed. - /// Whether or not to substitute environment variables. + /// Whether or not to substitute environment variables. /// Anonymized path or original path. - public static string GetPathForDisplay(string path, bool substitueEnvironmentVariables = true) + public static string GetPathForDisplay(string path, bool substituteEnvironmentVariables = true) { - if (string.IsNullOrEmpty(path) || !substitueEnvironmentVariables) + if (string.IsNullOrEmpty(path) || !substituteEnvironmentVariables) { return path; } string userProfilePath = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile); string localAppDataPath = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData); - string tempPath = Path.GetTempPath(); + string tempPath = Path.GetTempPath().TrimEnd(Path.DirectorySeparatorChar); if (path.StartsWith(tempPath, StringComparison.OrdinalIgnoreCase)) { - return path.Replace(tempPath, TempEnvironmentVariable + Path.DirectorySeparatorChar, StringComparison.OrdinalIgnoreCase); + return path.Replace(tempPath, TempEnvironmentVariable, StringComparison.OrdinalIgnoreCase); } else if (path.StartsWith(localAppDataPath, StringComparison.OrdinalIgnoreCase)) { diff --git a/src/WingetCreateCLI/Program.cs b/src/WingetCreateCLI/Program.cs index 04bf3829..746893a5 100644 --- a/src/WingetCreateCLI/Program.cs +++ b/src/WingetCreateCLI/Program.cs @@ -22,18 +22,6 @@ namespace Microsoft.WingetCreateCLI /// internal class Program { - /// - /// Displays the application header and the copyright. - /// - public static void DisplayApplicationHeaderAndCopyright() - { - Console.WriteLine(string.Format( - Resources.Heading, - Utils.GetEntryAssemblyVersion()) + - Environment.NewLine + - Constants.MicrosoftCopyright); - } - private static async Task Main(string[] args) { Logger.Initialize(); @@ -59,47 +47,27 @@ private static async Task Main(string[] args) typeof(TokenCommand), typeof(CacheCommand), typeof(ShowCommand), + typeof(InfoCommand), }; + var parserResult = myParser.ParseArguments(args, types); - var baseCommandsParserResult = myParser.ParseArguments(args, types); - BaseCommand parsedCommand = baseCommandsParserResult.MapResult(c => c as BaseCommand, err => null); + BaseCommand command = parserResult.MapResult(c => c as BaseCommand, err => null); - if (parsedCommand == null) + if (command == null) { - // In case of unsuccessful parsing, check if user tried to run a valid command. - bool isBaseCommand = baseCommandsParserResult.TypeInfo.Current.BaseType.Equals(typeof(BaseCommand)); - - /* Parse root options. - * Adding '--help' is a workaround to force parser to display the options help text when no arguments are passed. - * This makes rootOptionsParserResult to be a NotParsed object which makes HelpText.AutoBuild print the correct help text. - * This is done since the parser does not print the correct help text on a successful parse. - */ - ParserResult rootOptionsParserResult = myParser.ParseArguments( - args.Length == 0 ? new string[] { "--help" } : args); - - if (!isBaseCommand) - { - rootOptionsParserResult.WithParsed(RootOptions.ParseRootOptions); - - if (rootOptionsParserResult.Tag == ParserResultType.Parsed) - { - return 0; - } - } - - DisplayHelp(baseCommandsParserResult as NotParsed, isBaseCommand ? null : rootOptionsParserResult as NotParsed); - DisplayParsingErrors(baseCommandsParserResult as NotParsed); + DisplayHelp(parserResult as NotParsed); + DisplayParsingErrors(parserResult as NotParsed); return args.Any() ? 1 : 0; } - if (parsedCommand is not SettingsCommand && parsedCommand is not CacheCommand) + if (command is not SettingsCommand && command is not CacheCommand) { // Do not load github client for settings or cache command. - if (await parsedCommand.LoadGitHubClient()) + if (await command.LoadGitHubClient()) { try { - string latestVersion = await parsedCommand.GitHubClient.GetLatestRelease(); + string latestVersion = await command.GitHubClient.GetLatestRelease(); string trimmedVersion = latestVersion.TrimStart('v').Split('-').First(); if (trimmedVersion != Utils.GetEntryAssemblyVersion()) { @@ -117,7 +85,7 @@ private static async Task Main(string[] args) else { // Do not block creating a new manifest if loading the GitHub client fails. InstallerURL could point to a local network. - if (parsedCommand is not NewCommand) + if (command is not NewCommand) { return 1; } @@ -127,7 +95,7 @@ private static async Task Main(string[] args) try { WingetCreateCore.Serialization.ProducedBy = string.Join(" ", Constants.ProgramName, Utils.GetEntryAssemblyVersion()); - return await parsedCommand.Execute() ? 0 : 1; + return await command.Execute() ? 0 : 1; } catch (Exception ex) { @@ -151,66 +119,28 @@ private static async Task Main(string[] args) } } - private static void DisplayHelp(NotParsed baseCommandsParserResult, ParserResult rootOptionsParserResult) - { - DisplayApplicationHeaderAndCopyright(); - DisplayCommandsHelpText(baseCommandsParserResult); - if (rootOptionsParserResult != null) - { - DisplayRootOptionsHelpText(rootOptionsParserResult); - } - - DisplayFooter(); - } - - private static void DisplayCommandsHelpText(NotParsed result) - { - var helpText = HelpText.AutoBuild( - result, - h => - { - h.AddDashesToOption = true; - h.AdditionalNewLineAfterOption = false; - h.Heading = string.Empty; - h.Copyright = string.Empty; - h.AddNewLineBetweenHelpSections = true; - h.AddPreOptionsLine(Resources.AppDescription_HelpText); - h.AddPreOptionsLine(Environment.NewLine); - h.AddPreOptionsLine(Resources.CommandsAvailable_Message); - h.MaximumDisplayWidth = 100; - h.AutoHelp = false; - h.AutoVersion = false; - return h; - }, - e => e, - verbsIndex: true); - Console.WriteLine(helpText); - } - - private static void DisplayRootOptionsHelpText(ParserResult result) + private static void DisplayHelp(NotParsed result) { var helpText = HelpText.AutoBuild( - result, - h => - { - h.AddDashesToOption = true; - h.AdditionalNewLineAfterOption = false; - h.Heading = Resources.OptionsAvailable_Message; - h.Copyright = string.Empty; - h.AddNewLineBetweenHelpSections = false; - h.MaximumDisplayWidth = 100; - h.AutoHelp = false; - h.AutoVersion = false; - return h; - }, - e => e); + result, + h => + { + h.AddDashesToOption = true; + h.AdditionalNewLineAfterOption = false; + h.Heading = string.Format(Resources.Heading, Utils.GetEntryAssemblyVersion()) + Environment.NewLine; + h.Copyright = Constants.MicrosoftCopyright; + h.AddNewLineBetweenHelpSections = true; + h.AddPreOptionsLine(Resources.AppDescription_HelpText); + h.AddPostOptionsLines(new string[] { Resources.MoreHelp_HelpText, Resources.PrivacyStatement_HelpText }); + h.MaximumDisplayWidth = 100; + h.AutoHelp = false; + h.AutoVersion = false; + return h; + }, + e => e, + verbsIndex: true); Console.WriteLine(helpText); - } - - private static void DisplayFooter() - { - Console.WriteLine(Resources.MoreHelp_HelpText); - Console.WriteLine(Resources.PrivacyStatement_HelpText); + Console.WriteLine(); } private static void DisplayParsingErrors(NotParsed result) diff --git a/src/WingetCreateCLI/Properties/Resources.Designer.cs b/src/WingetCreateCLI/Properties/Resources.Designer.cs index 7cf428a1..f11189e3 100644 --- a/src/WingetCreateCLI/Properties/Resources.Designer.cs +++ b/src/WingetCreateCLI/Properties/Resources.Designer.cs @@ -978,15 +978,6 @@ public static string Heading { } } - /// - /// Looks up a localized string similar to Shows help about the selected command. - /// - public static string HelpRootOption_HelpText { - get { - return ResourceManager.GetString("HelpRootOption_HelpText", resourceCulture); - } - } - /// /// Looks up a localized string similar to Homepage. /// @@ -1071,9 +1062,9 @@ public static string IconUrl_KeywordDescription { /// /// Looks up a localized string similar to Displays general info of the tool. /// - public static string InfoRootOption_HelpText { + public static string InfoCommand_HelpText { get { - return ResourceManager.GetString("InfoRootOption_HelpText", resourceCulture); + return ResourceManager.GetString("InfoCommand_HelpText", resourceCulture); } } @@ -1887,15 +1878,6 @@ public static string OperatingSystem_Info { } } - /// - /// Looks up a localized string similar to The following options are available:. - /// - public static string OptionsAvailable_Message { - get { - return ResourceManager.GetString("OptionsAvailable_Message", resourceCulture); - } - } - /// /// Looks up a localized string similar to This is an older version of Winget-Create and may be missing some critical features.. /// @@ -2373,15 +2355,6 @@ public static string ReturnResponseUrl_KeywordDescription { } } - /// - /// Looks up a localized string similar to Display general info of the tool. - /// - public static string RootOption_Info_HelpText { - get { - return ResourceManager.GetString("RootOption_Info_HelpText", resourceCulture); - } - } - /// /// Looks up a localized string similar to SAVE AND EXIT. /// diff --git a/src/WingetCreateCLI/Properties/Resources.resx b/src/WingetCreateCLI/Properties/Resources.resx index ce32c19f..ab4da078 100644 --- a/src/WingetCreateCLI/Properties/Resources.resx +++ b/src/WingetCreateCLI/Properties/Resources.resx @@ -1203,9 +1203,6 @@ Winget-Create Directories - - Display general info of the tool - Installer cache @@ -1218,7 +1215,7 @@ Homepage - + Displays general info of the tool @@ -1236,10 +1233,4 @@ The following commands are available: - - The following options are available: - - - Shows help about the selected command - \ No newline at end of file From 60f716295a172fe4b2f7169ca4464ce4abfa366f Mon Sep 17 00:00:00 2001 From: Muhammad Danish Date: Wed, 1 Nov 2023 21:18:24 +0500 Subject: [PATCH 05/12] make header consistent with winget-cli --- src/WingetCreateCLI/Program.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/WingetCreateCLI/Program.cs b/src/WingetCreateCLI/Program.cs index 746893a5..1d1af308 100644 --- a/src/WingetCreateCLI/Program.cs +++ b/src/WingetCreateCLI/Program.cs @@ -127,10 +127,12 @@ private static void DisplayHelp(NotParsed result) { h.AddDashesToOption = true; h.AdditionalNewLineAfterOption = false; - h.Heading = string.Format(Resources.Heading, Utils.GetEntryAssemblyVersion()) + Environment.NewLine; + h.Heading = string.Format(Resources.Heading, Utils.GetEntryAssemblyVersion()); h.Copyright = Constants.MicrosoftCopyright; h.AddNewLineBetweenHelpSections = true; h.AddPreOptionsLine(Resources.AppDescription_HelpText); + h.AddPreOptionsLine(Environment.NewLine); + h.AddPreOptionsLine(Resources.CommandsAvailable_Message); h.AddPostOptionsLines(new string[] { Resources.MoreHelp_HelpText, Resources.PrivacyStatement_HelpText }); h.MaximumDisplayWidth = 100; h.AutoHelp = false; From 3a2ba2ecbd99e33ec93c889f7aa9ab64b5968f90 Mon Sep 17 00:00:00 2001 From: Muhammad Danish Date: Wed, 1 Nov 2023 21:19:31 +0500 Subject: [PATCH 06/12] expose padding field to the caller --- src/WingetCreateCLI/TableOutput.cs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/WingetCreateCLI/TableOutput.cs b/src/WingetCreateCLI/TableOutput.cs index d291b266..222e4381 100644 --- a/src/WingetCreateCLI/TableOutput.cs +++ b/src/WingetCreateCLI/TableOutput.cs @@ -40,7 +40,8 @@ public TableOutput AddRow(params string[] rowData) /// /// Write the table to the standard output. /// - public void Print() + /// Number of spaces to pad each column. Default is set to 2. + public void Print(int padding = 2) { // Calculate the maximum width of each column. List columnWidths = this.CalculateColumnWidths(); @@ -48,20 +49,20 @@ public void Print() // Print the column names. for (int i = 0; i < this.columns.Count; i++) { - Console.Write("{0, -" + (columnWidths[i] + this.padding) + "}", this.columns[i]); + Console.Write("{0, -" + (columnWidths[i] + padding) + "}", this.columns[i]); } Console.WriteLine(); // Print a line of dashes to separate the column names from the rows. - Console.WriteLine(new string('-', columnWidths.Sum() + columnWidths.Count + this.padding)); + Console.WriteLine(new string('-', columnWidths.Sum() + columnWidths.Count + padding)); // Print the rows. foreach (var row in this.rows) { for (int i = 0; i < this.columns.Count; i++) { - Console.Write("{0, -" + (columnWidths[i] + this.padding) + "}", row[i]); + Console.Write("{0, -" + (columnWidths[i] + padding) + "}", row[i]); } Console.WriteLine(); From d615a6616a54268febc22bb7aac2bf0800ee6f12 Mon Sep 17 00:00:00 2001 From: Muhammad Danish Date: Wed, 1 Nov 2023 21:22:32 +0500 Subject: [PATCH 07/12] remove class attribute --- src/WingetCreateCLI/TableOutput.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/WingetCreateCLI/TableOutput.cs b/src/WingetCreateCLI/TableOutput.cs index 222e4381..4d34b083 100644 --- a/src/WingetCreateCLI/TableOutput.cs +++ b/src/WingetCreateCLI/TableOutput.cs @@ -14,7 +14,6 @@ public class TableOutput { private readonly List columns = new(); private readonly List> rows = new(); - private readonly int padding = 2; /// /// Initializes a new instance of the class. From 3d3744c52d2e09f79175e0bd4b1ca6b71ba54627 Mon Sep 17 00:00:00 2001 From: Muhammad Danish Date: Wed, 1 Nov 2023 23:57:09 +0500 Subject: [PATCH 08/12] docs --- README.md | 1 + doc/info.md | 7 +++++++ doc/settings.md | 2 +- 3 files changed, 9 insertions(+), 1 deletion(-) create mode 100644 doc/info.md diff --git a/README.md b/README.md index 2037596a..57fc0562 100644 --- a/README.md +++ b/README.md @@ -52,6 +52,7 @@ choco install wingetcreate | [Token](doc/token.md) | Command for managing cached GitHub personal access tokens | | [Settings](doc/settings.md) | Command for editing the settings file configurations | | [Cache](doc/cache.md) | Command for managing downloaded installers stored in cache +| [Info](doc/info.md) | Displays information about the client | | [-?](doc/help.md) | Displays command line help | Click on the individual commands to learn more. diff --git a/doc/info.md b/doc/info.md new file mode 100644 index 00000000..6dc873b3 --- /dev/null +++ b/doc/info.md @@ -0,0 +1,7 @@ +# info command (Winget-Create) + +The **info** command of the [Winget-Create](../README.md) tool is used to show useful information about the client. This information includes the location of the settings file, the local installer repository, logs directory, and it also offers links to external resources like the GitHub repository and privacy statement. + +## Usage + +* Display information related to the client: `wingetcreate.exe info` diff --git a/doc/settings.md b/doc/settings.md index b6b01ba3..7860cc34 100644 --- a/doc/settings.md +++ b/doc/settings.md @@ -21,7 +21,7 @@ If set to true, the `telemetry.disable` setting will prevent any event from bein ## CleanUp -The `CleanUp` settings determine whether Winget-Create will handle the removal of temporary files i.e., installers downloaded and logs generated during the manifest creation process. You can view the location of these files using `wingetcreate --info` command. These settings provide control over the decision to remove files or not and the frequency at which this clean up occurs. +The `CleanUp` settings determine whether Winget-Create will handle the removal of temporary files i.e., installers downloaded and logs generated during the manifest creation process. You can view the location of these files using the [info](./info.md) command. These settings provide control over the decision to remove files or not and the frequency at which this clean up occurs. ### disable From 2284d306feff32bb2c38037dffc580b8ac8cb33d Mon Sep 17 00:00:00 2001 From: Muhammad Danish Date: Thu, 2 Nov 2023 00:00:49 +0500 Subject: [PATCH 09/12] docs (1) --- doc/info.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/info.md b/doc/info.md index 6dc873b3..8d34726e 100644 --- a/doc/info.md +++ b/doc/info.md @@ -1,6 +1,6 @@ # info command (Winget-Create) -The **info** command of the [Winget-Create](../README.md) tool is used to show useful information about the client. This information includes the location of the settings file, the local installer repository, logs directory, and it also offers links to external resources like the GitHub repository and privacy statement. +The **info** command of the [Winget-Create](../README.md) tool is used to show useful information about the client. This information includes the location of the settings file, the local installer cache (download directory), logs directory, and it also offers links to external resources like the GitHub repository and privacy statement. ## Usage From bdce3171c9ad2a9022eb08ea30b1a6de35200e37 Mon Sep 17 00:00:00 2001 From: Muhammad Danish Date: Thu, 2 Nov 2023 20:48:29 +0500 Subject: [PATCH 10/12] address PR comments --- doc/info.md | 2 +- src/WingetCreateCLI/Commands/InfoCommand.cs | 15 ++++--- src/WingetCreateCLI/Logger/Logger.cs | 3 +- src/WingetCreateCLI/Program.cs | 12 +++--- .../Properties/Resources.Designer.cs | 2 +- src/WingetCreateCLI/Properties/Resources.resx | 3 +- src/WingetCreateCore/Common/Constants.cs | 30 ++++++++++++++ .../UnitTests/CommonTests.cs | 40 +++++++++++++++++++ 8 files changed, 92 insertions(+), 15 deletions(-) create mode 100644 src/WingetCreateTests/WingetCreateTests/UnitTests/CommonTests.cs diff --git a/doc/info.md b/doc/info.md index 8d34726e..b16c865c 100644 --- a/doc/info.md +++ b/doc/info.md @@ -1,6 +1,6 @@ # info command (Winget-Create) -The **info** command of the [Winget-Create](../README.md) tool is used to show useful information about the client. This information includes the location of the settings file, the local installer cache (download directory), logs directory, and it also offers links to external resources like the GitHub repository and privacy statement. +The **info** command of the [Winget-Create](../README.md) tool is used to show useful information about the client. This information includes the location of the settings file, the local installer cache (download directory) and the logs directory. It also offers links to external resources like the GitHub repository and privacy statement. ## Usage diff --git a/src/WingetCreateCLI/Commands/InfoCommand.cs b/src/WingetCreateCLI/Commands/InfoCommand.cs index 097e4399..ab918a71 100644 --- a/src/WingetCreateCLI/Commands/InfoCommand.cs +++ b/src/WingetCreateCLI/Commands/InfoCommand.cs @@ -26,7 +26,10 @@ public class InfoCommand : BaseCommand /// Boolean representing success or fail of the command. public override async Task Execute() { - CommandExecutedEvent commandEvent = new CommandExecutedEvent(); + CommandExecutedEvent commandEvent = new CommandExecutedEvent + { + Command = nameof(InfoCommand), + }; DisplayApplicationHeaderAndCopyright(); Console.WriteLine(); @@ -55,7 +58,7 @@ private static void DisplaySystemInformation() private static void DisplayInfoTable() { - string logsdirectory = Common.GetPathForDisplay(Path.Combine(Common.LocalAppStatePath, "DiagOutputDir"), UserSettings.AnonymizePaths); + string logsdirectory = Common.GetPathForDisplay(Path.Combine(Common.LocalAppStatePath, Constants.DiagnosticOutputDirectoryFolderName), UserSettings.AnonymizePaths); string settingsDirectory = Common.GetPathForDisplay(UserSettings.SettingsJsonPath, UserSettings.AnonymizePaths); string installerCacheDirectory = Common.GetPathForDisplay(PackageParser.InstallerDownloadPath, UserSettings.AnonymizePaths); @@ -68,10 +71,10 @@ private static void DisplayInfoTable() Console.WriteLine(); new TableOutput(Resources.Links_Heading, string.Empty) - .AddRow(Resources.PrivacyStatement_Heading, "https://aka.ms/winget-create-privacy") - .AddRow(Resources.LicenseAgreement_Heading, "https://aka.ms/winget-create-license") - .AddRow(Resources.ThirdPartyNotices_Heading, "https://aka.ms/winget-create-3rdPartyNotices") - .AddRow(Resources.Homepage_Heading, "https://aka.ms/winget-create") + .AddRow(Resources.PrivacyStatement_Heading, Constants.PrivacyStatementUrl) + .AddRow(Resources.LicenseAgreement_Heading, Constants.LicenseUrl) + .AddRow(Resources.ThirdPartyNotices_Heading, Constants.ThirdPartyNoticesUrl) + .AddRow(Resources.Homepage_Heading, Constants.HomePageUrl) .Print(); } } diff --git a/src/WingetCreateCLI/Logger/Logger.cs b/src/WingetCreateCLI/Logger/Logger.cs index e1b1f959..273fb03e 100644 --- a/src/WingetCreateCLI/Logger/Logger.cs +++ b/src/WingetCreateCLI/Logger/Logger.cs @@ -7,6 +7,7 @@ namespace Microsoft.WingetCreateCLI.Logging using System.Globalization; using System.IO; using Microsoft.WingetCreateCLI.Properties; + using Microsoft.WingetCreateCore.Common; using NLog; using NLog.Conditions; using NLog.Config; @@ -21,7 +22,7 @@ public static class Logger private static readonly FileTarget FileTarget = new() { - FileName = @$"{Path.Combine(Common.LocalAppStatePath, "DiagOutputDir")}\WingetCreateLog-{DateTime.Now:yyyy-MM-dd-HH-mm.fff}.txt", + FileName = @$"{Path.Combine(Common.LocalAppStatePath, Constants.DiagnosticOutputDirectoryFolderName)}\WingetCreateLog-{DateTime.Now:yyyy-MM-dd-HH-mm.fff}.txt", // Current layout example: 2021-01-01 08:30:59.0000|INFO|Microsoft.WingetCreateCLI.Commands.NewCommand.Execute|Log Message Example Layout = "${longdate}|${level:uppercase=true}|${callsite}|${message}", diff --git a/src/WingetCreateCLI/Program.cs b/src/WingetCreateCLI/Program.cs index 1d1af308..8a132b59 100644 --- a/src/WingetCreateCLI/Program.cs +++ b/src/WingetCreateCLI/Program.cs @@ -60,9 +60,11 @@ private static async Task Main(string[] args) return args.Any() ? 1 : 0; } - if (command is not SettingsCommand && command is not CacheCommand) + bool commandHandlesToken = command is not CacheCommand and not InfoCommand and not SettingsCommand; + + // Do not load github client for commands that do not deal with a GitHub token. + if (commandHandlesToken) { - // Do not load github client for settings or cache command. if (await command.LoadGitHubClient()) { try @@ -72,7 +74,7 @@ private static async Task Main(string[] args) if (trimmedVersion != Utils.GetEntryAssemblyVersion()) { Logger.WarnLocalized(nameof(Resources.OutdatedVersionNotice_Message)); - Logger.WarnLocalized(nameof(Resources.GetLatestVersion_Message), latestVersion, "https://github.com/microsoft/winget-create/releases"); + Logger.WarnLocalized(nameof(Resources.GetLatestVersion_Message), latestVersion, Constants.GitHubReleasesUrl); Logger.WarnLocalized(nameof(Resources.UpgradeUsingWinget_Message)); Console.WriteLine(); } @@ -114,7 +116,7 @@ private static async Task Main(string[] args) if (!UserSettings.CleanUpDisabled) { Common.CleanUpFilesOlderThan(PackageParser.InstallerDownloadPath, UserSettings.CleanUpDays); - Common.CleanUpFilesOlderThan(Path.Combine(Common.LocalAppStatePath, "DiagOutputDir"), UserSettings.CleanUpDays); + Common.CleanUpFilesOlderThan(Path.Combine(Common.LocalAppStatePath, Constants.DiagnosticOutputDirectoryFolderName), UserSettings.CleanUpDays); } } } @@ -133,7 +135,7 @@ private static void DisplayHelp(NotParsed result) h.AddPreOptionsLine(Resources.AppDescription_HelpText); h.AddPreOptionsLine(Environment.NewLine); h.AddPreOptionsLine(Resources.CommandsAvailable_Message); - h.AddPostOptionsLines(new string[] { Resources.MoreHelp_HelpText, Resources.PrivacyStatement_HelpText }); + h.AddPostOptionsLines(new string[] { Resources.MoreHelp_HelpText, string.Format(Resources.PrivacyStatement_HelpText, Constants.PrivacyStatementUrl) }); h.MaximumDisplayWidth = 100; h.AutoHelp = false; h.AutoVersion = false; diff --git a/src/WingetCreateCLI/Properties/Resources.Designer.cs b/src/WingetCreateCLI/Properties/Resources.Designer.cs index f11189e3..4fed488c 100644 --- a/src/WingetCreateCLI/Properties/Resources.Designer.cs +++ b/src/WingetCreateCLI/Properties/Resources.Designer.cs @@ -2086,7 +2086,7 @@ public static string PrivacyStatement_Heading { } /// - /// Looks up a localized string similar to Privacy statement: https://aka.ms/winget-create-privacy. + /// Looks up a localized string similar to Privacy statement: {0}. /// public static string PrivacyStatement_HelpText { get { diff --git a/src/WingetCreateCLI/Properties/Resources.resx b/src/WingetCreateCLI/Properties/Resources.resx index ab4da078..5548ba0e 100644 --- a/src/WingetCreateCLI/Properties/Resources.resx +++ b/src/WingetCreateCLI/Properties/Resources.resx @@ -540,7 +540,8 @@ More help can be found at: https://aka.ms/winget-create - Privacy statement: https://aka.ms/winget-create-privacy + Privacy statement: {0} + {0} - represents the privacy statement url HTTP response was unsuccessful. Status code: {0} diff --git a/src/WingetCreateCore/Common/Constants.cs b/src/WingetCreateCore/Common/Constants.cs index 97a7e967..6137a189 100644 --- a/src/WingetCreateCore/Common/Constants.cs +++ b/src/WingetCreateCore/Common/Constants.cs @@ -28,9 +28,39 @@ public static class Constants /// public const int GitHubAppId = 100205; + /// + /// Link to the GitHub releases page for the winget-create tool. + /// + public const string GitHubReleasesUrl = "https://github.com/microsoft/winget-create/releases"; + /// /// Program name of the app. /// public const string ProgramName = "wingetcreate"; + + /// + /// Link to the privacy statement for the winget-create tool. + /// + public const string PrivacyStatementUrl = "https://aka.ms/winget-create-privacy"; + + /// + /// Link to the license for the winget-create tool. + /// + public const string LicenseUrl = "https://aka.ms/winget-create-license"; + + /// + /// Link to the notices file containing third party attributions for the winget-create tool. + /// + public const string ThirdPartyNoticesUrl = "https://aka.ms/winget-create-3rdPartyNotices"; + + /// + /// Link to the GitHub repository for the winget-create tool. + /// + public const string HomePageUrl = "https://aka.ms/winget-create"; + + /// + /// Represents the subdirectory name of the user's local app data folder where the tool stores its debug data. + /// + public const string DiagnosticOutputDirectoryFolderName = "DiagOutputDir"; } } diff --git a/src/WingetCreateTests/WingetCreateTests/UnitTests/CommonTests.cs b/src/WingetCreateTests/WingetCreateTests/UnitTests/CommonTests.cs new file mode 100644 index 00000000..b1ed74c6 --- /dev/null +++ b/src/WingetCreateTests/WingetCreateTests/UnitTests/CommonTests.cs @@ -0,0 +1,40 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. + +namespace Microsoft.WingetCreateUnitTests +{ + using System; + using System.IO; + using Microsoft.WingetCreateCLI; + using NUnit.Framework; + + /// + /// Test cases for verifying common functions for the CLI. + /// + public class CommonTests + { + /// + /// Tests the ability to retrieve the path for display purposes. + /// + [Test] + public void VerifyPathSubstitutions() + { + string path1 = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile) + "\\foo\\bar\\baz"; + string path2 = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData) + "\\foo\\bar\\baz"; + string path3 = Path.GetTempPath().TrimEnd(Path.DirectorySeparatorChar) + "\\foo\\bar\\baz"; + + string expectedPath1 = "%USERPROFILE%\\foo\\bar\\baz"; + string expectedPath2 = "%LOCALAPPDATA%\\foo\\bar\\baz"; + string expectedPath3 = "%TEMP%\\foo\\bar\\baz"; + + Assert.AreEqual(expectedPath1, Common.GetPathForDisplay(path1, true), "The path does not contain the expected substitutions."); + Assert.AreEqual(path1, Common.GetPathForDisplay(path1, false), "The path should not contain any substitutions."); + + Assert.AreEqual(expectedPath2, Common.GetPathForDisplay(path2, true), "The path does not contain the expected substitutions."); + Assert.AreEqual(path2, Common.GetPathForDisplay(path2, false), "The path should not contain any substitutions."); + + Assert.AreEqual(expectedPath3, Common.GetPathForDisplay(path3, true), "The path does not contain the expected substitutions."); + Assert.AreEqual(path3, Common.GetPathForDisplay(path3, false), "The path should not contain any substitutions."); + } + } +} From f0ee2366f95110b5e0a9e4f2941dbee74b7c4de8 Mon Sep 17 00:00:00 2001 From: Muhammad Danish Date: Thu, 2 Nov 2023 21:08:30 +0500 Subject: [PATCH 11/12] variable name --- .../WingetCreateTests/UnitTests/CommonTests.cs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/WingetCreateTests/WingetCreateTests/UnitTests/CommonTests.cs b/src/WingetCreateTests/WingetCreateTests/UnitTests/CommonTests.cs index b1ed74c6..d2245d9d 100644 --- a/src/WingetCreateTests/WingetCreateTests/UnitTests/CommonTests.cs +++ b/src/WingetCreateTests/WingetCreateTests/UnitTests/CommonTests.cs @@ -23,17 +23,17 @@ public void VerifyPathSubstitutions() string path2 = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData) + "\\foo\\bar\\baz"; string path3 = Path.GetTempPath().TrimEnd(Path.DirectorySeparatorChar) + "\\foo\\bar\\baz"; - string expectedPath1 = "%USERPROFILE%\\foo\\bar\\baz"; - string expectedPath2 = "%LOCALAPPDATA%\\foo\\bar\\baz"; - string expectedPath3 = "%TEMP%\\foo\\bar\\baz"; + string substitutedPath1 = "%USERPROFILE%\\foo\\bar\\baz"; + string substitutedPath2 = "%LOCALAPPDATA%\\foo\\bar\\baz"; + string substitutedPath3 = "%TEMP%\\foo\\bar\\baz"; - Assert.AreEqual(expectedPath1, Common.GetPathForDisplay(path1, true), "The path does not contain the expected substitutions."); + Assert.AreEqual(substitutedPath1, Common.GetPathForDisplay(path1, true), "The path does not contain the expected substitutions."); Assert.AreEqual(path1, Common.GetPathForDisplay(path1, false), "The path should not contain any substitutions."); - Assert.AreEqual(expectedPath2, Common.GetPathForDisplay(path2, true), "The path does not contain the expected substitutions."); + Assert.AreEqual(substitutedPath2, Common.GetPathForDisplay(path2, true), "The path does not contain the expected substitutions."); Assert.AreEqual(path2, Common.GetPathForDisplay(path2, false), "The path should not contain any substitutions."); - Assert.AreEqual(expectedPath3, Common.GetPathForDisplay(path3, true), "The path does not contain the expected substitutions."); + Assert.AreEqual(substitutedPath3, Common.GetPathForDisplay(path3, true), "The path does not contain the expected substitutions."); Assert.AreEqual(path3, Common.GetPathForDisplay(path3, false), "The path should not contain any substitutions."); } } From f307dbb971b7edb6f99fe9eb2210b4827c41dea7 Mon Sep 17 00:00:00 2001 From: Muhammad Danish Date: Fri, 3 Nov 2023 00:29:21 +0500 Subject: [PATCH 12/12] address comments --- src/WingetCreateCLI/Commands/InfoCommand.cs | 2 +- src/WingetCreateCore/Common/Constants.cs | 4 ++-- .../WingetCreateTests/UnitTests/CommonTests.cs | 13 +++++++------ 3 files changed, 10 insertions(+), 9 deletions(-) diff --git a/src/WingetCreateCLI/Commands/InfoCommand.cs b/src/WingetCreateCLI/Commands/InfoCommand.cs index ab918a71..b4441e72 100644 --- a/src/WingetCreateCLI/Commands/InfoCommand.cs +++ b/src/WingetCreateCLI/Commands/InfoCommand.cs @@ -73,7 +73,7 @@ private static void DisplayInfoTable() new TableOutput(Resources.Links_Heading, string.Empty) .AddRow(Resources.PrivacyStatement_Heading, Constants.PrivacyStatementUrl) .AddRow(Resources.LicenseAgreement_Heading, Constants.LicenseUrl) - .AddRow(Resources.ThirdPartyNotices_Heading, Constants.ThirdPartyNoticesUrl) + .AddRow(Resources.ThirdPartyNotices_Heading, Constants.ThirdPartyNoticeUrl) .AddRow(Resources.Homepage_Heading, Constants.HomePageUrl) .Print(); } diff --git a/src/WingetCreateCore/Common/Constants.cs b/src/WingetCreateCore/Common/Constants.cs index 6137a189..6ff6ce04 100644 --- a/src/WingetCreateCore/Common/Constants.cs +++ b/src/WingetCreateCore/Common/Constants.cs @@ -49,9 +49,9 @@ public static class Constants public const string LicenseUrl = "https://aka.ms/winget-create-license"; /// - /// Link to the notices file containing third party attributions for the winget-create tool. + /// Link to the notice file containing third party attributions for the winget-create tool. /// - public const string ThirdPartyNoticesUrl = "https://aka.ms/winget-create-3rdPartyNotices"; + public const string ThirdPartyNoticeUrl = "https://aka.ms/winget-create-3rdPartyNotice"; /// /// Link to the GitHub repository for the winget-create tool. diff --git a/src/WingetCreateTests/WingetCreateTests/UnitTests/CommonTests.cs b/src/WingetCreateTests/WingetCreateTests/UnitTests/CommonTests.cs index d2245d9d..1bb18a5a 100644 --- a/src/WingetCreateTests/WingetCreateTests/UnitTests/CommonTests.cs +++ b/src/WingetCreateTests/WingetCreateTests/UnitTests/CommonTests.cs @@ -19,13 +19,14 @@ public class CommonTests [Test] public void VerifyPathSubstitutions() { - string path1 = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile) + "\\foo\\bar\\baz"; - string path2 = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData) + "\\foo\\bar\\baz"; - string path3 = Path.GetTempPath().TrimEnd(Path.DirectorySeparatorChar) + "\\foo\\bar\\baz"; + string examplePath = "\\foo\\bar\\baz"; + string path1 = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile) + examplePath; + string path2 = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData) + examplePath; + string path3 = Path.GetTempPath().TrimEnd(Path.DirectorySeparatorChar) + examplePath; - string substitutedPath1 = "%USERPROFILE%\\foo\\bar\\baz"; - string substitutedPath2 = "%LOCALAPPDATA%\\foo\\bar\\baz"; - string substitutedPath3 = "%TEMP%\\foo\\bar\\baz"; + string substitutedPath1 = "%USERPROFILE%" + examplePath; + string substitutedPath2 = "%LOCALAPPDATA%" + examplePath; + string substitutedPath3 = "%TEMP%" + examplePath; Assert.AreEqual(substitutedPath1, Common.GetPathForDisplay(path1, true), "The path does not contain the expected substitutions."); Assert.AreEqual(path1, Common.GetPathForDisplay(path1, false), "The path should not contain any substitutions.");