From 0c0ca0b128143edc01be017a25c5dfc48fb59e41 Mon Sep 17 00:00:00 2001 From: Muhammad Danish Date: Mon, 13 Nov 2023 00:06:38 +0500 Subject: [PATCH 1/8] Add support for creating and updating locales --- README.md | 2 + doc/new-locale.md | 33 ++ doc/update-locale.md | 32 ++ src/WingetCreateCLI/Commands/BaseCommand.cs | 163 ++++++++- src/WingetCreateCLI/Commands/NewCommand.cs | 24 -- .../Commands/NewLocaleCommand.cs | 322 ++++++++++++++++++ src/WingetCreateCLI/Commands/ShowCommand.cs | 16 - .../Commands/UpdateLocaleCommand.cs | 300 ++++++++++++++++ src/WingetCreateCLI/Program.cs | 3 + src/WingetCreateCLI/PromptHelper.cs | 5 +- .../Properties/Resources.Designer.cs | 254 +++++++++++++- src/WingetCreateCLI/Properties/Resources.resx | 90 ++++- .../Models/Partials/DefaultLocalePartials.cs | 74 ++++ .../Models/Partials/LocalePartials.cs | 72 ++++ .../TestPublisher.LocaleConversionTest.yaml | 39 +++ .../UnitTests/CommonTests.cs | 59 ++++ .../WingetCreateTests.csproj | 3 + 17 files changed, 1446 insertions(+), 45 deletions(-) create mode 100644 doc/new-locale.md create mode 100644 doc/update-locale.md create mode 100644 src/WingetCreateCLI/Commands/NewLocaleCommand.cs create mode 100644 src/WingetCreateCLI/Commands/UpdateLocaleCommand.cs create mode 100644 src/WingetCreateTests/WingetCreateTests/Resources/TestPublisher.LocaleConversionTest.yaml diff --git a/README.md b/README.md index 57fc0562..631e73c5 100644 --- a/README.md +++ b/README.md @@ -47,6 +47,8 @@ choco install wingetcreate | ------- | ----------- | | [New](doc/new.md) | Command for creating a new manifest from scratch | | [Update](doc/update.md) | Command for updating an existing manifest | +| [New-Locale](doc/new-locale.md) | Command for creating a new locale for an existing manifest | +| [Update-Locale](doc/update-locale.md) | Command for updating a locale for an existing manifest | | [Submit](doc/submit.md) | Command for submitting an existing PR | | [Show](doc/show.md) | Command for displaying existing manifests | | [Token](doc/token.md) | Command for managing cached GitHub personal access tokens | diff --git a/doc/new-locale.md b/doc/new-locale.md new file mode 100644 index 00000000..3efa8ed8 --- /dev/null +++ b/doc/new-locale.md @@ -0,0 +1,33 @@ +# new-locale command (Winget-Create) + +The **new-locale** command of the [Winget-Create](../README.md) tool is designed to create a new locale for an existing manifest from the [Windows Package Manager repo](https://docs.microsoft.com/windows/package-manager/). This command offers an interactive flow, prompting user for a set of locale fields and then generating a new locale manifest. + +## Usage + +Add a new locale for the latest version of a package: + +`wingetcreate.exe new-locale --token ` + +Add a new locale for a specific version of a package: + +`wingetcreate.exe new-locale --token --version ` + +Create a new locale and save the generated manifests to a specified directory: + +`wingetcreate.exe new-locale --out --token --version ` + +## Arguments + +The following arguments are available: + +| Argument | Description | +|--------------|-------------| +| **id** | Required. Package identifier used to lookup the existing manifest on the Windows Package Manager repo. +| **-v, --version** | The version of the package to add a new locale for. Default is the latest version. +| **-l, --locale** | The package locale to create a new manifest for. If not provided, the tool will prompt you for this value. +| **-r, --reference-locale** | Locale manifest to be used for offering auto-complete suggestions. +| **-o, --out** | The output directory where the newly created manifests will be saved locally. +| **-t,--token** | GitHub personal access token used for direct submission to the Windows Package Manager repo | +| **-?, --help** | Gets additional help on this command | + +Instructions on setting up GitHub Token for Winget-Create can be found [here](../README.md#github-personal-access-token-classic-permissions). diff --git a/doc/update-locale.md b/doc/update-locale.md new file mode 100644 index 00000000..5822f02f --- /dev/null +++ b/doc/update-locale.md @@ -0,0 +1,32 @@ +# update-locale command (Winget-Create) + +The **update-locale** command of the [Winget-Create](../README.md) tool is designed to update existing locales for a manifest from the [Windows Package Manager repo](https://docs.microsoft.com/windows/package-manager/). This command offers an interactive flow, prompting user for a set of locale fields and then generating a new manifest for submission. + +## Usage + +Update existing locales for the latest version of a package: + +`wingetcreate.exe update-locale --token ` + +Update existing locales for a specific version of a package: + +`wingetcreate.exe update-locale --token --version ` + +Update existing locale and save the generated manifests to a specified directory: + +`wingetcreate.exe update-locale --out --token --version ` + +## Arguments + +The following arguments are available: + +| Argument | Description | +|--------------|-------------| +| **id** | Required. Package identifier used to lookup the existing manifest on the Windows Package Manager repo. +| **-v, --version** | The version of the package to update the locale for. Default is the latest version. +| **-l, --locale** | The package locale to update the manifest for. If not provided, the tool will prompt you a list of existing locales to choose from. +| **-o, --out** | The output directory where the newly created manifests will be saved locally. +| **-t,--token** | GitHub personal access token used for direct submission to the Windows Package Manager repo | +| **-?, --help** | Gets additional help on this command | + +Instructions on setting up GitHub Token for Winget-Create can be found [here](../README.md#github-personal-access-token-classic-permissions). diff --git a/src/WingetCreateCLI/Commands/BaseCommand.cs b/src/WingetCreateCLI/Commands/BaseCommand.cs index 8ea0e26f..773dd151 100644 --- a/src/WingetCreateCLI/Commands/BaseCommand.cs +++ b/src/WingetCreateCLI/Commands/BaseCommand.cs @@ -6,10 +6,13 @@ namespace Microsoft.WingetCreateCLI.Commands using System; using System.Collections; using System.Collections.Generic; + using System.ComponentModel.DataAnnotations; + using System.Globalization; using System.IO; using System.Linq; using System.Net; using System.Net.Http; + using System.Reflection; using System.Threading.Tasks; using Microsoft.WingetCreateCLI.Logging; using Microsoft.WingetCreateCLI.Properties; @@ -23,8 +26,10 @@ namespace Microsoft.WingetCreateCLI.Commands using Microsoft.WingetCreateCore.Models.Installer; using Microsoft.WingetCreateCore.Models.Locale; using Microsoft.WingetCreateCore.Models.Version; + using Newtonsoft.Json; using Octokit; using RestSharp; + using Sharprompt; /// /// Abstract base command class that all commands inherit from. @@ -508,6 +513,149 @@ protected static void ShiftInstallerFieldsToRootLevel(InstallerManifest installe } } + /// + /// Prompts user to enter values for the optional properties of the manifest. + /// + /// Type of the manifest. + /// Object model of the manifest. + /// Optional parameter to specify the list of property names to be prompted for. + protected static void PromptOptionalProperties(T manifest, List optionalPropertiesNames = null) + { + List optionalProperties; + if (optionalPropertiesNames == null) + { + optionalProperties = manifest.GetType().GetProperties().ToList().Where(p => + p.GetCustomAttribute() == null && + p.GetCustomAttribute() != null).ToList(); + } + else + { + optionalProperties = manifest.GetType().GetProperties().Where(p => optionalPropertiesNames.Contains(p.Name)).ToList(); + } + + foreach (var property in optionalProperties) + { + Type type = property.PropertyType; + if (type.IsEnumerable()) + { + Type elementType = type.GetGenericArguments().SingleOrDefault(); + if (elementType.IsNonStringClassType() && !Prompt.Confirm(string.Format(Resources.EditObjectTypeField_Message, property.Name))) + { + continue; + } + } + + PromptHelper.PromptPropertyAndSetValue(manifest, property.Name, property.GetValue(manifest)); + Logger.Trace($"Property [{property.Name}] set to the value [{property.GetValue(manifest)}]"); + } + } + + /// + /// Prompts user to enter values for the input locale or default locale manifest properties. + /// + /// Type of the manifest. Expected to be either LocaleManifest or DefaultLocaleManifest. + /// Object model of the locale/defaultLocale manifest. + /// List of property names to be prompted for. + /// Optional parameter to be used when validating the user-inputted locale. Check whether the locale already exists in the original manifests. + protected static void PromptAndSetLocaleProperties(T localeManifest, List properties, Manifests originalManifests = null) + { + foreach (string propertyName in properties) + { + PropertyInfo property = typeof(T).GetProperty(propertyName); + PromptHelper.PromptPropertyAndSetValue(localeManifest, propertyName, property.GetValue(localeManifest)); + + if (propertyName == nameof(LocaleManifest.PackageLocale) && originalManifests != null) + { + while (!ValidateLocale(property.GetValue(localeManifest).ToString(), originalManifests)) + { + PromptHelper.PromptPropertyAndSetValue(localeManifest, propertyName, property.GetValue(localeManifest)); + } + + continue; + } + + Logger.Trace($"Property [{propertyName}] set to the value [{property.GetValue(localeManifest)}]"); + } + } + + /// + /// Checks whether the provided locale is valid. A locale is valid if it is in the correct format and does not already exist in the manifest. + /// This function handles the exception gracefully to be used in a prompt. + /// + /// The package locale string to check. + /// The base manifests to check against. + /// A boolean value indicating whether the locale is valid. + protected static bool ValidateLocale(string locale, Manifests manifests) + { + try + { + if (GetMatchingLocaleManifest(locale, manifests) != null) + { + Logger.ErrorLocalized(nameof(Resources.LocaleAlreadyExists_ErrorMessage), locale); + Console.WriteLine(); + return false; + } + + return true; + } + catch (ArgumentException) + { + Logger.ErrorLocalized(nameof(Resources.InvalidLocale_ErrorMessage)); + Console.WriteLine(); + return false; + } + } + + /// + /// Checks whether package locale already exists in the default locale manifest or one of the locale manifests and returns the matching manifest. + /// This function throws an exception if the locale string is in an invalid format. + /// + /// The package locale string to check. + /// The base manifests to check against. + /// An object representing the matching locale manifest. + protected static object GetMatchingLocaleManifest(string locale, Manifests originalManifests) + { + RegionInfo localeInfo = new RegionInfo(locale); + + if (localeInfo.Equals(new RegionInfo(originalManifests.DefaultLocaleManifest.PackageLocale))) + { + return originalManifests.DefaultLocaleManifest; + } + + foreach (var localeManifest in originalManifests.LocaleManifests) + { + if (localeInfo.Equals(new RegionInfo(localeManifest.PackageLocale))) + { + return localeManifest; + } + } + + return null; + } + + /// + /// Displays the preview of the default locale manifest to the console. + /// + /// Object model of the default locale manifest. + protected static void DisplayDefaultLocaleManifest(DefaultLocaleManifest defaultLocaleManifest) + { + Logger.InfoLocalized(nameof(Resources.DefaultLocaleManifest_Message), defaultLocaleManifest.PackageLocale); + Console.WriteLine(defaultLocaleManifest.ToYaml(true)); + } + + /// + /// Displays the preview of all the locale manifests to the console. + /// + /// List of locale manifest object models. + protected static void DisplayLocaleManifests(List localeManifests) + { + foreach (var localeManifest in localeManifests) + { + Logger.InfoLocalized(nameof(Resources.LocaleManifest_Message), localeManifest.PackageLocale); + Console.WriteLine(localeManifest.ToYaml(true)); + } + } + /// /// Launches the GitHub OAuth flow and obtains a GitHub token. /// @@ -682,8 +830,9 @@ protected async Task GitHubSubmitManifests(Manifests manifests, string prT /// /// Manifest object containing metadata of new manifest to be submitted. /// Manifest object representing an already exisitng manifest in the repository. + /// Name of the command calling this method. /// A string representing the pull request title. - protected string GetPRTitle(Manifests currentManifest, Manifests repositoryManifest = null) + protected string GetPRTitle(Manifests currentManifest, Manifests repositoryManifest = null, string commandName = null) { // Use custom PR title if provided by the user. if (!string.IsNullOrEmpty(this.PRTitle)) @@ -694,6 +843,18 @@ protected string GetPRTitle(Manifests currentManifest, Manifests repositoryManif string packageId = currentManifest.VersionManifest != null ? currentManifest.VersionManifest.PackageIdentifier : currentManifest.SingletonManifest.PackageIdentifier; string currentVersion = currentManifest.VersionManifest != null ? currentManifest.VersionManifest.PackageVersion : currentManifest.SingletonManifest.PackageVersion; + if (!string.IsNullOrEmpty(commandName)) + { + if (commandName.Equals(nameof(NewLocaleCommand))) + { + return $"Add locale: {packageId} version {currentVersion}"; + } + else if (commandName.Equals(nameof(UpdateLocaleCommand))) + { + return $"Update locale: {packageId} version {currentVersion}"; + } + } + // If no manifest exists in the repository, this is a new package. if (repositoryManifest == null) { diff --git a/src/WingetCreateCLI/Commands/NewCommand.cs b/src/WingetCreateCLI/Commands/NewCommand.cs index 91926b80..036d1755 100644 --- a/src/WingetCreateCLI/Commands/NewCommand.cs +++ b/src/WingetCreateCLI/Commands/NewCommand.cs @@ -338,30 +338,6 @@ private static void PromptRequiredProperties(T manifest, VersionManifest vers } } - private static void PromptOptionalProperties(T manifest) - { - var properties = manifest.GetType().GetProperties().ToList(); - var optionalProperties = properties.Where(p => - p.GetCustomAttribute() == null && - p.GetCustomAttribute() != null).ToList(); - - foreach (var property in optionalProperties) - { - Type type = property.PropertyType; - if (type.IsEnumerable()) - { - Type elementType = type.GetGenericArguments().SingleOrDefault(); - if (elementType.IsNonStringClassType() && !Prompt.Confirm(string.Format(Resources.EditObjectTypeField_Message, property.Name))) - { - continue; - } - } - - PromptHelper.PromptPropertyAndSetValue(manifest, property.Name, property.GetValue(manifest)); - Logger.Trace($"Property [{property.Name}] set to the value [{property.GetValue(manifest)}]"); - } - } - private static void PromptInstallerProperties(T manifest, PropertyInfo property) { List installers = new List((ICollection)property.GetValue(manifest)); diff --git a/src/WingetCreateCLI/Commands/NewLocaleCommand.cs b/src/WingetCreateCLI/Commands/NewLocaleCommand.cs new file mode 100644 index 00000000..3005a714 --- /dev/null +++ b/src/WingetCreateCLI/Commands/NewLocaleCommand.cs @@ -0,0 +1,322 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. + +namespace Microsoft.WingetCreateCLI.Commands +{ + using System; + using System.Collections.Generic; + using System.ComponentModel.DataAnnotations; + using System.IO; + using System.Linq; + using System.Reflection; + using System.Threading.Tasks; + using CommandLine; + using CommandLine.Text; + using Microsoft.WingetCreateCLI.Logging; + using Microsoft.WingetCreateCLI.Properties; + using Microsoft.WingetCreateCLI.Telemetry; + using Microsoft.WingetCreateCLI.Telemetry.Events; + using Microsoft.WingetCreateCore; + using Microsoft.WingetCreateCore.Models; + using Microsoft.WingetCreateCore.Models.Locale; + using Newtonsoft.Json; + using Sharprompt; + + /// + /// Command to create a new locale manifest for an existing manifest in the Windows Package Manager repository. + /// + [Verb("new-locale", HelpText = "NewLocaleCommand_HelpText", ResourceType = typeof(Resources))] + public class NewLocaleCommand : BaseCommand + { + /// + /// Gets the usage examples for the new-locale command. + /// + [Usage(ApplicationAlias = ProgramApplicationAlias)] + public static IEnumerable Examples + { + get + { + yield return new Example(Resources.Example_NewLocaleCommand_AddForLatestVersion, new NewLocaleCommand { Id = "", GitHubToken = "" }); + yield return new Example(Resources.Example_NewLocaleCommand_AddForSpecificVersion, new NewLocaleCommand { Id = "", Version = "", GitHubToken = "" }); + yield return new Example(Resources.Example_NewLocaleCommand_SaveToDirectory, new NewLocaleCommand { Id = "", Locale = "", Version = "", OutputDir = "", GitHubToken = "" }); + } + } + + /// + /// Gets or sets the id used for looking up an existing manifest in the Windows Package Manager repository. + /// + [Value(0, MetaName = "PackageIdentifier", Required = true, HelpText = "PackageIdentifier_HelpText", ResourceType = typeof(Resources))] + public string Id { get; set; } + + /// + /// Gets or sets the version of the package from the Windows Package Manager repository to create the new locales for. + /// + [Option('v', "version", Required = false, Default = null, HelpText = "NewLocaleCommand_Version_HelpText", ResourceType = typeof(Resources))] + public string Version { get; set; } + + /// + /// Gets or sets the locale to create a new manifest for. + /// + [Option('l', "locale", Required = false, HelpText = "NewLocaleCommand_Locale_HelpText", ResourceType = typeof(Resources))] + public string Locale { get; set; } + + /// + /// Gets or sets the reference locale to use for generating the new locale manifest. + /// + [Option('r', "reference-locale", Required = false, HelpText = "ReferenceLocale_HelpText", ResourceType = typeof(Resources))] + public string ReferenceLocale { get; set; } + + /// + /// Gets or sets the outputPath where the generated manifest file should be saved to. + /// + [Option('o', "out", Required = false, HelpText = "OutputDirectory_HelpText", ResourceType = typeof(Resources))] + public string OutputDir { get; set; } + + /// + /// Gets or sets the GitHub token used to submit a pull request on behalf of the user. + /// + [Option('t', "token", Required = false, HelpText = "GitHubToken_HelpText", ResourceType = typeof(Resources))] + public override string GitHubToken { get => base.GitHubToken; set => base.GitHubToken = value; } + + /// + /// Executes the new-locale command flow. + /// + /// Boolean representing success or fail of the command. + public override async Task Execute() + { + CommandExecutedEvent commandEvent = new CommandExecutedEvent + { + Command = nameof(NewLocaleCommand), + Id = this.Id, + Version = this.Version, + HasGitHubToken = !string.IsNullOrEmpty(this.GitHubToken), + }; + + Prompt.Symbols.Done = new Symbol(string.Empty, string.Empty); + Prompt.Symbols.Prompt = new Symbol(string.Empty, string.Empty); + + string exactId; + try + { + exactId = await this.GitHubClient.FindPackageId(this.Id); + } + catch (Octokit.RateLimitExceededException) + { + Logger.ErrorLocalized(nameof(Resources.RateLimitExceeded_Message)); + return false; + } + + if (!string.IsNullOrEmpty(exactId)) + { + this.Id = exactId; + } + + List manifestContent; + + try + { + manifestContent = await this.GitHubClient.GetManifestContentAsync(this.Id, this.Version); + Manifests originalManifests = Serialization.DeserializeManifestContents(manifestContent); + + // Validate input locale argument and switch command flow if applicable. + if (!string.IsNullOrEmpty(this.Locale)) + { + try + { + if (GetMatchingLocaleManifest(this.Locale, originalManifests) != null) + { + Logger.ErrorLocalized(nameof(Resources.LocaleAlreadyExists_ErrorMessage), this.Locale); + Console.WriteLine(); + + // Switch to update locale flow if user accepts to. + if (Prompt.Confirm(Resources.SwitchToUpdateLocaleFlow_Message)) + { + UpdateLocaleCommand command = new UpdateLocaleCommand + { + Id = this.Id, + Version = this.Version, + Locale = this.Locale, + GitHubToken = this.GitHubToken, + }; + await command.LoadGitHubClient(); + Console.WriteLine(); + return await command.Execute(); + } + } + } + catch (ArgumentException) + { + Logger.ErrorLocalized(nameof(Resources.InvalidLocale_ErrorMessage)); + return false; + } + } + + List newLocales = this.ParseArgumentsAndGenerateLocales(originalManifests); + + if (newLocales == null) + { + return false; + } + + Console.WriteLine(); + DisplayGeneratedLocales(newLocales); + + if (string.IsNullOrEmpty(this.OutputDir)) + { + this.OutputDir = Directory.GetCurrentDirectory(); + } + + string manifestDirectoryPath = SaveManifestDirToLocalPath(originalManifests, this.OutputDir); + + if (ValidateManifest(manifestDirectoryPath)) + { + if (Prompt.Confirm(Resources.ConfirmGitHubSubmitManifest_Message)) + { + return await this.LoadGitHubClient(true) ? + (commandEvent.IsSuccessful = await this.GitHubSubmitManifests( + originalManifests, + this.GetPRTitle(originalManifests, null, nameof(NewLocaleCommand)))) + : false; + } + else + { + Logger.WarnLocalized(nameof(Resources.SkippingPullRequest_Message)); + } + } + else + { + return false; + } + + return await Task.FromResult(commandEvent.IsSuccessful = true); + } + catch (Octokit.NotFoundException e) + { + Logger.ErrorLocalized(nameof(Resources.Error_Prefix), e.Message); + Logger.ErrorLocalized(nameof(Resources.OctokitNotFound_Error)); + return false; + } + finally + { + TelemetryManager.Log.WriteEvent(commandEvent); + } + } + + private static void DisplayGeneratedLocales(List newLocales) + { + Logger.DebugLocalized(nameof(Resources.GenerateNewLocalePreview_Message)); + foreach (var localeManifest in newLocales) + { + Logger.InfoLocalized(nameof(Resources.LocaleManifest_Message), localeManifest.PackageLocale); + Console.WriteLine(localeManifest.ToYaml(true)); + } + } + + private static void FillLocalePropertiesForUserCompletion(LocaleManifest fromManifest, LocaleManifest toManifest, List properties) + { + foreach (string propertyName in properties) + { + if (propertyName == nameof(LocaleManifest.PackageLocale)) + { + continue; + } + + var value = fromManifest.GetType().GetProperty(propertyName).GetValue(fromManifest); + if (value != null) + { + toManifest.GetType().GetProperty(propertyName).SetValue(toManifest, value); + } + } + } + + private List ParseArgumentsAndGenerateLocales(Manifests originalManifests) + { + LocaleManifest referenceLocaleManifest; + + if (!string.IsNullOrEmpty(this.ReferenceLocale)) + { + try + { + referenceLocaleManifest = (LocaleManifest)GetMatchingLocaleManifest(this.ReferenceLocale, originalManifests); + } + catch (ArgumentException) + { + Logger.ErrorLocalized(nameof(Resources.InvalidLocale_ErrorMessage)); + return null; + } + + if (referenceLocaleManifest == null) + { + Logger.ErrorLocalized(nameof(Resources.ReferenceLocaleNotFound_Error)); + return null; + } + } + else + { + referenceLocaleManifest = originalManifests.DefaultLocaleManifest.ToLocaleManifest(); + } + + List generatedLocales = new(); + + List defaultPromptPropertiesForNewLocale = new() + { + nameof(LocaleManifest.PackageLocale), + nameof(LocaleManifest.PackageName), + nameof(LocaleManifest.Publisher), + nameof(LocaleManifest.License), + nameof(LocaleManifest.ShortDescription), + }; + + bool localeArgumentUsed = false; + do + { + LocaleManifest newLocaleManifest = new LocaleManifest + { + PackageIdentifier = originalManifests.VersionManifest.PackageIdentifier, + PackageVersion = originalManifests.VersionManifest.PackageVersion, + }; + + // Fill in properties from the reference locale. This is to help user see previous values to quickly fill out the new manifest. + FillLocalePropertiesForUserCompletion(referenceLocaleManifest, newLocaleManifest, defaultPromptPropertiesForNewLocale); + + if (!string.IsNullOrEmpty(this.Locale) && !localeArgumentUsed) + { + newLocaleManifest.PackageLocale = this.Locale; + + // Don't prompt for PackageLocale if it is provided as an argument. + List properties = new(defaultPromptPropertiesForNewLocale); + properties.Remove(nameof(LocaleManifest.PackageLocale)); + + Logger.DebugLocalized(nameof(Resources.FieldSetToValue_Message), nameof(LocaleManifest.PackageLocale), this.Locale); + PromptAndSetLocaleProperties(newLocaleManifest, properties, originalManifests); + localeArgumentUsed = true; + } + else + { + PromptAndSetLocaleProperties(newLocaleManifest, defaultPromptPropertiesForNewLocale, originalManifests); + } + + if (Prompt.Confirm(Resources.AddAdditionalLocaleProperties_Message)) + { + // Get optional properties that have not been prompted before. + var optionalProperties = newLocaleManifest.GetType().GetProperties().ToList().Where(p => + p.GetCustomAttribute() == null && + p.GetCustomAttribute() != null && + !defaultPromptPropertiesForNewLocale.Any(d => d == p.Name)).Select(p => p.Name).ToList(); + + FillLocalePropertiesForUserCompletion(referenceLocaleManifest, newLocaleManifest, optionalProperties); + PromptOptionalProperties(newLocaleManifest, optionalProperties); + } + + originalManifests.LocaleManifests.Add(newLocaleManifest); + generatedLocales.Add(newLocaleManifest); + + Console.WriteLine(); + ValidateManifestsInTempDir(originalManifests); + } + while (Prompt.Confirm(Resources.CreateAnotherLocale_Message)); + + return generatedLocales; + } + } +} diff --git a/src/WingetCreateCLI/Commands/ShowCommand.cs b/src/WingetCreateCLI/Commands/ShowCommand.cs index 74101e12..610003c9 100644 --- a/src/WingetCreateCLI/Commands/ShowCommand.cs +++ b/src/WingetCreateCLI/Commands/ShowCommand.cs @@ -95,7 +95,6 @@ public override async Task Execute() HasGitHubToken = !string.IsNullOrEmpty(this.GitHubToken), }; - Console.OutputEncoding = System.Text.Encoding.UTF8; string exactId; try { @@ -153,21 +152,6 @@ private static void DisplayVersionManifest(VersionManifest versionManifest) Console.WriteLine(versionManifest.ToYaml(true)); } - private static void DisplayDefaultLocaleManifest(DefaultLocaleManifest defaultLocaleManifest) - { - Logger.InfoLocalized(nameof(Resources.DefaultLocaleManifest_Message), defaultLocaleManifest.PackageLocale); - Console.WriteLine(defaultLocaleManifest.ToYaml(true)); - } - - private static void DisplayLocaleManifests(List localeManifests) - { - foreach (var localeManifest in localeManifests) - { - Logger.InfoLocalized(nameof(Resources.LocaleManifest_Message), localeManifest.PackageLocale); - Console.WriteLine(localeManifest.ToYaml(true)); - } - } - private static void DisplaySingletonManifest(SingletonManifest singletonManifest) { Logger.InfoLocalized(nameof(Resources.SingletonManifest_Message)); diff --git a/src/WingetCreateCLI/Commands/UpdateLocaleCommand.cs b/src/WingetCreateCLI/Commands/UpdateLocaleCommand.cs new file mode 100644 index 00000000..b182123f --- /dev/null +++ b/src/WingetCreateCLI/Commands/UpdateLocaleCommand.cs @@ -0,0 +1,300 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. + +namespace Microsoft.WingetCreateCLI.Commands +{ + using System; + using System.Collections.Generic; + using System.ComponentModel.DataAnnotations; + using System.IO; + using System.Linq; + using System.Reflection; + using System.Threading.Tasks; + using CommandLine; + using CommandLine.Text; + using Microsoft.WingetCreateCLI.Logging; + using Microsoft.WingetCreateCLI.Properties; + using Microsoft.WingetCreateCLI.Telemetry; + using Microsoft.WingetCreateCLI.Telemetry.Events; + using Microsoft.WingetCreateCore; + using Microsoft.WingetCreateCore.Models; + using Microsoft.WingetCreateCore.Models.DefaultLocale; + using Microsoft.WingetCreateCore.Models.Locale; + using Newtonsoft.Json; + using Sharprompt; + + /// + /// Command to update an existing locale manifest for a package in the Windows Package Manager repository. + /// + [Verb("update-locale", HelpText = "UpdateLocaleCommand_HelpText", ResourceType = typeof(Resources))] + public class UpdateLocaleCommand : BaseCommand + { + /// + /// Gets the usage examples for the update-locale command. + /// + [Usage(ApplicationAlias = ProgramApplicationAlias)] + public static IEnumerable Examples + { + get + { + yield return new Example(Resources.Example_UpdateLocaleCommand_UpdateForLatestVersion, new UpdateLocaleCommand { Id = "", GitHubToken = "" }); + yield return new Example(Resources.Example_UpdateLocaleCommand_UpdateForSpecificVersion, new UpdateLocaleCommand { Id = "", Version = "", GitHubToken = "" }); + yield return new Example(Resources.Example_UpdateLocaleCommand_SaveToDirectory, new UpdateLocaleCommand { Id = "", Locale = "", Version = "", OutputDir = "", GitHubToken = "" }); + } + } + + /// + /// Gets or sets the id used for looking up an existing manifest in the Windows Package Manager repository. + /// + [Value(0, MetaName = "PackageIdentifier", Required = true, HelpText = "PackageIdentifier_HelpText", ResourceType = typeof(Resources))] + public string Id { get; set; } + + /// + /// Gets or sets the version of the package from the Windows Package Manager repository to update the locales for. + /// + [Option('v', "version", Required = false, Default = null, HelpText = "UpdateLocaleCommand_Version_HelpText", ResourceType = typeof(Resources))] + public string Version { get; set; } + + /// + /// Gets or sets the locale to update the manifest for. + /// + [Option('l', "locale", Required = false, HelpText = "UpdateLocaleCommand_Locale_HelpText", ResourceType = typeof(Resources))] + public string Locale { get; set; } + + /// + /// Gets or sets the outputPath where the generated manifest file should be saved to. + /// + [Option('o', "out", Required = false, HelpText = "OutputDirectory_HelpText", ResourceType = typeof(Resources))] + public string OutputDir { get; set; } + + /// + /// Gets or sets the GitHub token used to submit a pull request on behalf of the user. + /// + [Option('t', "token", Required = false, HelpText = "GitHubToken_HelpText", ResourceType = typeof(Resources))] + public override string GitHubToken { get => base.GitHubToken; set => base.GitHubToken = value; } + + /// + /// Executes the update-locale command flow. + /// + /// Boolean representing success or fail of the command. + public override async Task Execute() + { + CommandExecutedEvent commandEvent = new CommandExecutedEvent + { + Command = nameof(UpdateLocaleCommand), + Id = this.Id, + Version = this.Version, + HasGitHubToken = !string.IsNullOrEmpty(this.GitHubToken), + }; + + Prompt.Symbols.Done = new Symbol(string.Empty, string.Empty); + Prompt.Symbols.Prompt = new Symbol(string.Empty, string.Empty); + + string exactId; + try + { + exactId = await this.GitHubClient.FindPackageId(this.Id); + } + catch (Octokit.RateLimitExceededException) + { + Logger.ErrorLocalized(nameof(Resources.RateLimitExceeded_Message)); + return false; + } + + if (!string.IsNullOrEmpty(exactId)) + { + this.Id = exactId; + } + + List manifestContent; + + try + { + manifestContent = await this.GitHubClient.GetManifestContentAsync(this.Id, this.Version); + Manifests originalManifests = Serialization.DeserializeManifestContents(manifestContent); + + // Validate input locale argument and switch command flow if applicable. + if (!string.IsNullOrEmpty(this.Locale)) + { + try + { + if (GetMatchingLocaleManifest(this.Locale, originalManifests) == null) + { + Logger.ErrorLocalized(nameof(Resources.LocaleDoesNotExist_Message), this.Locale); + + // Switch to new locale flow if user accepts. + if (Prompt.Confirm(Resources.SwitchToNewLocaleFlow_Message)) + { + NewLocaleCommand command = new NewLocaleCommand + { + Id = this.Id, + Version = this.Version, + Locale = this.Locale, + GitHubToken = this.GitHubToken, + }; + await command.LoadGitHubClient(); + Console.WriteLine(); + return await command.Execute(); + } + } + } + catch (ArgumentException) + { + Logger.ErrorLocalized(nameof(Resources.InvalidLocale_ErrorMessage)); + return false; + } + } + + Manifests updatedLocales = this.PromptAndUpdateExistingLocales(originalManifests); + Console.WriteLine(); + DisplayUpdatedLocales(updatedLocales); + + if (string.IsNullOrEmpty(this.OutputDir)) + { + this.OutputDir = Directory.GetCurrentDirectory(); + } + + string manifestDirectoryPath = SaveManifestDirToLocalPath(originalManifests, this.OutputDir); + + if (ValidateManifest(manifestDirectoryPath)) + { + if (Prompt.Confirm(Resources.ConfirmGitHubSubmitManifest_Message)) + { + return await this.LoadGitHubClient(true) ? + (commandEvent.IsSuccessful = await this.GitHubSubmitManifests( + originalManifests, + this.GetPRTitle(originalManifests, null, nameof(UpdateLocaleCommand)))) + : false; + } + else + { + Logger.WarnLocalized(nameof(Resources.SkippingPullRequest_Message)); + } + } + else + { + return false; + } + + return await Task.FromResult(commandEvent.IsSuccessful = true); + } + catch (Octokit.NotFoundException e) + { + Logger.ErrorLocalized(nameof(Resources.Error_Prefix), e.Message); + Logger.ErrorLocalized(nameof(Resources.OctokitNotFound_Error)); + return false; + } + finally + { + TelemetryManager.Log.WriteEvent(commandEvent); + } + } + + private static object PromptAllLocalesAsMenuSelection(Manifests originalManifests) + { + Dictionary localeMap = new Dictionary + { + { originalManifests.DefaultLocaleManifest.PackageLocale + " (" + Resources.DefaultLocale_MenuItem + ")", originalManifests.DefaultLocaleManifest }, + }; + + foreach (var locale in originalManifests.LocaleManifests) + { + localeMap.Add(locale.PackageLocale, locale); + } + + string selectedLocale = Prompt.Select(Resources.SelectExistingLocale_Message, localeMap.Keys.ToList()); + return localeMap[selectedLocale]; + } + + private static void DisplayUpdatedLocales(Manifests manifest) + { + Logger.DebugLocalized(nameof(Resources.GenerateUpdatedLocalePreview_Message)); + if (manifest.DefaultLocaleManifest != null) + { + DisplayDefaultLocaleManifest(manifest.DefaultLocaleManifest); + } + + if (manifest.LocaleManifests != null) + { + DisplayLocaleManifests(manifest.LocaleManifests); + } + } + + private static List GetOptionalLocalePropertyNames(T genericLocaleManifest) + { + return genericLocaleManifest.GetType().GetProperties().ToList().Where(p => + p.GetCustomAttribute() == null && + p.GetCustomAttribute() != null) + .Select(p => p.Name).ToList(); + } + + private Manifests PromptAndUpdateExistingLocales(Manifests originalManifests) + { + Manifests updatedLocales = new Manifests() + { + SingletonManifest = null, + VersionManifest = null, + InstallerManifest = null, + }; + + List defaultPromptPropertiesForUpdateLocale = new() + { + nameof(LocaleManifest.PackageName), + nameof(LocaleManifest.Publisher), + nameof(LocaleManifest.License), + nameof(LocaleManifest.ShortDescription), + }; + + bool localeArgumentUsed = false; + object selectedLocale; + + do + { + if (!string.IsNullOrEmpty(this.Locale) && !localeArgumentUsed) + { + selectedLocale = GetMatchingLocaleManifest(this.Locale, originalManifests); + localeArgumentUsed = true; + } + else + { + selectedLocale = PromptAllLocalesAsMenuSelection(originalManifests); + } + + bool isDefaultLocale = false; + + if (selectedLocale is LocaleManifest localeManifest) + { + Logger.DebugLocalized(nameof(Resources.FieldSetToValue_Message), nameof(LocaleManifest.PackageLocale), localeManifest.PackageLocale); + PromptAndSetLocaleProperties(localeManifest, defaultPromptPropertiesForUpdateLocale); + updatedLocales.LocaleManifests.Add(localeManifest); + } + else if (selectedLocale is DefaultLocaleManifest defaultLocaleManifest) + { + Logger.DebugLocalized(nameof(Resources.FieldSetToValue_Message), nameof(LocaleManifest.PackageLocale), defaultLocaleManifest.PackageLocale); + PromptAndSetLocaleProperties(defaultLocaleManifest, defaultPromptPropertiesForUpdateLocale); + updatedLocales.DefaultLocaleManifest = defaultLocaleManifest; + isDefaultLocale = true; + } + + if (Prompt.Confirm(Resources.UpdateAdditionalLocaleProperties_Message)) + { + if (isDefaultLocale) + { + PromptOptionalProperties(updatedLocales.DefaultLocaleManifest, GetOptionalLocalePropertyNames(updatedLocales.DefaultLocaleManifest)); + } + else + { + LocaleManifest manifest = (LocaleManifest)selectedLocale; + PromptOptionalProperties(manifest, GetOptionalLocalePropertyNames(manifest)); + } + } + + Console.WriteLine(); + ValidateManifestsInTempDir(originalManifests); + } + while (Prompt.Confirm(Resources.UpdateAnotherLocale_Message)); + + return updatedLocales; + } + } +} diff --git a/src/WingetCreateCLI/Program.cs b/src/WingetCreateCLI/Program.cs index 8a132b59..2dae8b96 100644 --- a/src/WingetCreateCLI/Program.cs +++ b/src/WingetCreateCLI/Program.cs @@ -28,6 +28,7 @@ private static async Task Main(string[] args) UserSettings.FirstRunTelemetryConsent(); TelemetryEventListener.EventListener.IsTelemetryEnabled(); SentenceBuilder.Factory = () => new LocalizableSentenceBuilder(); + Console.OutputEncoding = System.Text.Encoding.UTF8; string arguments = string.Join(' ', Environment.GetCommandLineArgs()); Logger.Trace($"Command line args: {arguments}"); @@ -42,6 +43,8 @@ private static async Task Main(string[] args) { typeof(NewCommand), typeof(UpdateCommand), + typeof(NewLocaleCommand), + typeof(UpdateLocaleCommand), typeof(SubmitCommand), typeof(SettingsCommand), typeof(TokenCommand), diff --git a/src/WingetCreateCLI/PromptHelper.cs b/src/WingetCreateCLI/PromptHelper.cs index 549454ec..aa94595b 100644 --- a/src/WingetCreateCLI/PromptHelper.cs +++ b/src/WingetCreateCLI/PromptHelper.cs @@ -15,7 +15,6 @@ namespace Microsoft.WingetCreateCLI using Microsoft.WingetCreateCore.Common; using Microsoft.WingetCreateCore.Models.DefaultLocale; using Microsoft.WingetCreateCore.Models.Installer; - using Newtonsoft.Json; using Sharprompt; /// @@ -409,7 +408,9 @@ public static void PromptList(string message, object model, string memberName if (!value.Any()) { - value = null; + // In update scenarios, if an older value exists then use that value instead of setting the property to null. + var existingValue = model.GetType().GetProperty(property.Name).GetValue(model); + value = existingValue != null ? (IEnumerable)existingValue : null; } else { diff --git a/src/WingetCreateCLI/Properties/Resources.Designer.cs b/src/WingetCreateCLI/Properties/Resources.Designer.cs index 4fed488c..442522d3 100644 --- a/src/WingetCreateCLI/Properties/Resources.Designer.cs +++ b/src/WingetCreateCLI/Properties/Resources.Designer.cs @@ -69,6 +69,15 @@ public static string Add_MenuItem { } } + /// + /// Looks up a localized string similar to Would you like to add additional locale properties?. + /// + public static string AddAdditionalLocaleProperties_Message { + get { + return ResourceManager.GetString("AddAdditionalLocaleProperties_Message", resourceCulture); + } + } + /// /// Looks up a localized string similar to Additional metadata needed for installer from {0}. /// @@ -384,6 +393,15 @@ public static string CopyrightUrl_KeywordDescription { } } + /// + /// Looks up a localized string similar to Would you like to create another locale?. + /// + public static string CreateAnotherLocale_Message { + get { + return ResourceManager.GetString("CreateAnotherLocale_Message", resourceCulture); + } + } + /// /// Looks up a localized string similar to The custom installer switches. /// @@ -411,6 +429,15 @@ public static string DefaultLocale_KeywordDescription { } } + /// + /// Looks up a localized string similar to Default locale. + /// + public static string DefaultLocale_MenuItem { + get { + return ResourceManager.GetString("DefaultLocale_MenuItem", resourceCulture); + } + } + /// /// Looks up a localized string similar to Display the default locale manifest of the package.. /// @@ -735,6 +762,33 @@ public static string Example_NewCommand_StartFromScratch { } } + /// + /// Looks up a localized string similar to Add a new locale for the latest version of a package. + /// + public static string Example_NewLocaleCommand_AddForLatestVersion { + get { + return ResourceManager.GetString("Example_NewLocaleCommand_AddForLatestVersion", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Add a new locale for a specific version of a package. + /// + public static string Example_NewLocaleCommand_AddForSpecificVersion { + get { + return ResourceManager.GetString("Example_NewLocaleCommand_AddForSpecificVersion", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Create a new locale and save the generated manifests to a specified directory. + /// + public static string Example_NewLocaleCommand_SaveToDirectory { + get { + return ResourceManager.GetString("Example_NewLocaleCommand_SaveToDirectory", resourceCulture); + } + } + /// /// Looks up a localized string similar to Show the latest manifest of an existing package from the Windows Package Manager repo. /// @@ -816,6 +870,33 @@ public static string Example_UpdateCommand_SubmitToGitHub { } } + /// + /// Looks up a localized string similar to Update existing locale and save the generated manifests to a specified directory. + /// + public static string Example_UpdateLocaleCommand_SaveToDirectory { + get { + return ResourceManager.GetString("Example_UpdateLocaleCommand_SaveToDirectory", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Update existing locale for the latest version of a package. + /// + public static string Example_UpdateLocaleCommand_UpdateForLatestVersion { + get { + return ResourceManager.GetString("Example_UpdateLocaleCommand_UpdateForLatestVersion", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Update existing locale for a specific version of a package. + /// + public static string Example_UpdateLocaleCommand_UpdateForSpecificVersion { + get { + return ResourceManager.GetString("Example_UpdateLocaleCommand_UpdateForSpecificVersion", resourceCulture); + } + } + /// /// Looks up a localized string similar to The excluded installer target markets. /// @@ -852,6 +933,15 @@ public static string ExternalDependencies_KeywordDescription { } } + /// + /// Looks up a localized string similar to [{0}] value is: {1}. + /// + public static string FieldSetToValue_Message { + get { + return ResourceManager.GetString("FieldSetToValue_Message", resourceCulture); + } + } + /// /// Looks up a localized string similar to [{0}] value is. /// @@ -915,6 +1005,15 @@ public static string GenerateManifestPreview_Message { } } + /// + /// Looks up a localized string similar to Generating a preview of the created locale(s)..... + /// + public static string GenerateNewLocalePreview_Message { + get { + return ResourceManager.GetString("GenerateNewLocalePreview_Message", resourceCulture); + } + } + /// /// Looks up a localized string similar to No settings file found, generating new settings file from loaded settings.... /// @@ -924,6 +1023,15 @@ public static string GenerateNewSettingsFile_Message { } } + /// + /// Looks up a localized string similar to Generating a preview of the updated locale(s)..... + /// + public static string GenerateUpdatedLocalePreview_Message { + get { + return ResourceManager.GetString("GenerateUpdatedLocalePreview_Message", resourceCulture); + } + } + /// /// Looks up a localized string similar to For the latest version ({0}), go to {1}. /// @@ -1347,6 +1455,15 @@ public static string InvalidGitHubToken_Message { } } + /// + /// Looks up a localized string similar to Invalid locale. Enter a valid language tag.. + /// + public static string InvalidLocale_ErrorMessage { + get { + return ResourceManager.GetString("InvalidLocale_ErrorMessage", resourceCulture); + } + } + /// /// Looks up a localized string similar to Invalid token provided, please generate a new GitHub token and try again.. /// @@ -1464,6 +1581,24 @@ public static string LoadSettingsFromDefault_Message { } } + /// + /// Looks up a localized string similar to Locale manifest matching with input locale "{0}" already exists. + /// + public static string LocaleAlreadyExists_ErrorMessage { + get { + return ResourceManager.GetString("LocaleAlreadyExists_ErrorMessage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Manifest for input locale "{0}" does not exist. . + /// + public static string LocaleDoesNotExist_Message { + get { + return ResourceManager.GetString("LocaleDoesNotExist_Message", resourceCulture); + } + } + /// /// Looks up a localized string similar to Locale ({0}) Manifest:. /// @@ -1806,6 +1941,33 @@ public static string NewInstallerUrlMustMatchExisting_Message { } } + /// + /// Looks up a localized string similar to Launches a series of questions to help generate a new locale manifest. + /// + public static string NewLocaleCommand_HelpText { + get { + return ResourceManager.GetString("NewLocaleCommand_HelpText", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The package locale to create a new manifest for. If not provided, the tool will prompt for this value.. + /// + public static string NewLocaleCommand_Locale_HelpText { + get { + return ResourceManager.GetString("NewLocaleCommand_Locale_HelpText", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The version of the package to add a new locale for.. + /// + public static string NewLocaleCommand_Version_HelpText { + get { + return ResourceManager.GetString("NewLocaleCommand_Version_HelpText", resourceCulture); + } + } + /// /// Looks up a localized string similar to Submitting a manifest without any updated changes is not allowed. . /// @@ -1888,7 +2050,7 @@ public static string OutdatedVersionNotice_Message { } /// - /// Looks up a localized string similar to Output directory where the newly created manifests will be saved locally. + /// Looks up a localized string similar to The output directory to store the generated manifests locally.. /// public static string OutputDirectory_HelpText { get { @@ -2184,6 +2346,24 @@ public static string RateLimitExceeded_Message { } } + /// + /// Looks up a localized string similar to Locale manifest to be used for offering auto-complete suggestions. If not provided, the default locale manifest will be used.. + /// + public static string ReferenceLocale_HelpText { + get { + return ResourceManager.GetString("ReferenceLocale_HelpText", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The reference locale does not exist in the manifest.. + /// + public static string ReferenceLocaleNotFound_Error { + get { + return ResourceManager.GetString("ReferenceLocaleNotFound_Error", resourceCulture); + } + } + /// /// Looks up a localized string similar to Input does not match the valid format pattern for this field.. /// @@ -2382,6 +2562,15 @@ public static string SelectAction_Message { } } + /// + /// Looks up a localized string similar to Select an existing locale. + /// + public static string SelectExistingLocale_Message { + get { + return ResourceManager.GetString("SelectExistingLocale_Message", resourceCulture); + } + } + /// /// Looks up a localized string similar to Select the installer(s) from the zip archive:. /// @@ -2787,6 +2976,24 @@ public static string Switches_KeywordDescription { } } + /// + /// Looks up a localized string similar to Would you like to create the locale manifest instead?. + /// + public static string SwitchToNewLocaleFlow_Message { + get { + return ResourceManager.GetString("SwitchToNewLocaleFlow_Message", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Would you like to update the locale manifest instead?. + /// + public static string SwitchToUpdateLocaleFlow_Message { + get { + return ResourceManager.GetString("SwitchToUpdateLocaleFlow_Message", resourceCulture); + } + } + /// /// Looks up a localized string similar to Unable to create a reference to the forked repository. This can be caused when the forked repository is behind by too many commits. Sync your fork and try again.. /// @@ -2949,6 +3156,24 @@ public static string Update_KeywordDescription { } } + /// + /// Looks up a localized string similar to Would you like to modify additional locale properties?. + /// + public static string UpdateAdditionalLocaleProperties_Message { + get { + return ResourceManager.GetString("UpdateAdditionalLocaleProperties_Message", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Would you like to update another locale?. + /// + public static string UpdateAnotherLocale_Message { + get { + return ResourceManager.GetString("UpdateAnotherLocale_Message", resourceCulture); + } + } + /// /// Looks up a localized string similar to The upgrade method. /// @@ -2967,6 +3192,33 @@ public static string UpdateCommand_HelpText { } } + /// + /// Looks up a localized string similar to Launches a series of questions to help update an existing locale manifest. + /// + public static string UpdateLocaleCommand_HelpText { + get { + return ResourceManager.GetString("UpdateLocaleCommand_HelpText", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The package locale to update the manifest for. If not provided, the tool will prompt you a list of existing locales to choose from.. + /// + public static string UpdateLocaleCommand_Locale_HelpText { + get { + return ResourceManager.GetString("UpdateLocaleCommand_Locale_HelpText", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The version of the package to update the locale for.. + /// + public static string UpdateLocaleCommand_Version_HelpText { + get { + return ResourceManager.GetString("UpdateLocaleCommand_Version_HelpText", resourceCulture); + } + } + /// /// Looks up a localized string similar to Updating {0} of {1} installers:. /// diff --git a/src/WingetCreateCLI/Properties/Resources.resx b/src/WingetCreateCLI/Properties/Resources.resx index 5548ba0e..e3f0f348 100644 --- a/src/WingetCreateCLI/Properties/Resources.resx +++ b/src/WingetCreateCLI/Properties/Resources.resx @@ -325,7 +325,7 @@ Please check if the provided package arguments are correct. - Output directory where the newly created manifests will be saved locally + The output directory to store the generated manifests locally. The MSIX installer package family name @@ -1234,4 +1234,92 @@ The following commands are available: + + Generating a preview of the created locale(s).... + + + Would you like to add additional locale properties? + + + Would you like to create another locale? + + + Default locale + + + Add a new locale for the latest version of a package + + + Add a new locale for a specific version of a package + + + Create a new locale and save the generated manifests to a specified directory + + + Update existing locale and save the generated manifests to a specified directory + + + Update existing locale for the latest version of a package + + + Update existing locale for a specific version of a package + + + Generating a preview of the updated locale(s).... + + + Invalid locale. Enter a valid language tag. + + + Locale manifest matching with input locale "{0}" already exists + {0} - represents the input package locale string + + + The version of the package to add a new locale for. + + + Select an existing locale + + + Would you like to modify additional locale properties? + + + Would you like to update another locale? + + + The version of the package to update the locale for. + + + Launches a series of questions to help generate a new locale manifest + + + Locale manifest to be used for offering auto-complete suggestions. If not provided, the default locale manifest will be used. + + + Launches a series of questions to help update an existing locale manifest + + + The reference locale does not exist in the manifest. + + + [{0}] value is: {1} + {0} - represents the name of the manifest field +{1} - represents the value of the manifest field + + + The package locale to update the manifest for. If not provided, the tool will prompt you a list of existing locales to choose from. + + + The package locale to create a new manifest for. If not provided, the tool will prompt for this value. + + + Manifest for input locale "{0}" does not exist. + {0} - represents the package locale string + + + Would you like to create the locale manifest instead? + + + Would you like to update the locale manifest instead? + \ No newline at end of file diff --git a/src/WingetCreateCore/Models/Partials/DefaultLocalePartials.cs b/src/WingetCreateCore/Models/Partials/DefaultLocalePartials.cs index 8b59d35c..0a3f20fd 100644 --- a/src/WingetCreateCore/Models/Partials/DefaultLocalePartials.cs +++ b/src/WingetCreateCore/Models/Partials/DefaultLocalePartials.cs @@ -3,12 +3,86 @@ namespace Microsoft.WingetCreateCore.Models.DefaultLocale { + using Microsoft.WingetCreateCore.Models.Locale; + using Newtonsoft.Json; using YamlDotNet.Serialization; + /// + /// Partial class that extends the property definitions of LocaleManifest. + /// + public partial class DefaultLocaleManifest + { + /// + /// Method to convert a DefaultLocaleManifest to a LocaleManifest. + /// + /// Object model representing the locale manifest. + public LocaleManifest ToLocaleManifest() + { + LocaleManifest localeManifest = JsonConvert.DeserializeObject(JsonConvert.SerializeObject(this)); + localeManifest.ManifestType = "locale"; + return localeManifest; + } + } + + /// + /// Partial class that implements helper methods for the Icon class. + /// +#pragma warning disable SA1402 // File may only contain a single type + public partial class Icon +#pragma warning restore SA1402 // File may only contain a single type + { + /// + /// Gives the criteria for determining whether two instances of Icon objects are equal. + /// + /// A boolean value indicating whether the two objects are equal. + /// The object to compare with the current object. + public override bool Equals(object obj) + { + if (obj == null || this.GetType() != obj.GetType()) + { + return false; + } + + Icon other = (Icon)obj; + return this.IconUrl == other.IconUrl + && this.IconResolution == other.IconResolution + && this.IconSha256 == other.IconSha256 + && this.IconFileType == other.IconFileType + && this.IconTheme == other.IconTheme; + } + } + + /// + /// Partial class that implements helper methods for the Documentation class. + /// +#pragma warning disable SA1402 // File may only contain a single type + public partial class Documentation +#pragma warning restore SA1402 // File may only contain a single type + { + /// + /// Gives the criteria for determining whether two instances of Documentation objects are equal. + /// + /// A boolean value indicating whether the two objects are equal. + /// The object to compare with the current object. + public override bool Equals(object obj) + { + if (obj == null || this.GetType() != obj.GetType()) + { + return false; + } + + Documentation other = (Documentation)obj; + return this.DocumentUrl == other.DocumentUrl + && this.DocumentLabel == other.DocumentLabel; + } + } + /// /// Partial class that extends the property definitions of Agreement. /// +#pragma warning disable SA1402 // File may only contain a single type public partial class Agreement +#pragma warning restore SA1402 // File may only contain a single type { /// Gets or sets the agreement text content. [YamlMember(Alias = "Agreement")] diff --git a/src/WingetCreateCore/Models/Partials/LocalePartials.cs b/src/WingetCreateCore/Models/Partials/LocalePartials.cs index 7cade590..732db296 100644 --- a/src/WingetCreateCore/Models/Partials/LocalePartials.cs +++ b/src/WingetCreateCore/Models/Partials/LocalePartials.cs @@ -3,12 +3,84 @@ namespace Microsoft.WingetCreateCore.Models.Locale { + using Microsoft.WingetCreateCore.Models.DefaultLocale; + using Newtonsoft.Json; using YamlDotNet.Serialization; + /// + /// Partial class that extends the property definitions of LocaleManifest. + /// + public partial class LocaleManifest + { + /// + /// Explicit cast from DefaultLocaleManifest to a LocaleManifest. + /// + /// Represents the default locale manifest. + public static explicit operator LocaleManifest(DefaultLocaleManifest defaultLocaleManifest) + { + return defaultLocaleManifest.ToLocaleManifest(); + } + } + + /// + /// Partial class that implements helper methods for the Icon class. + /// +#pragma warning disable SA1402 // File may only contain a single type + public partial class Icon +#pragma warning restore SA1402 // File may only contain a single type + { + /// + /// Gives the criteria for determining whether two instances of Icon objects are equal. + /// + /// A boolean value indicating whether the two objects are equal. + /// The object to compare with the current object. + public override bool Equals(object obj) + { + if (obj == null || this.GetType() != obj.GetType()) + { + return false; + } + + Icon other = (Icon)obj; + return this.IconUrl == other.IconUrl + && this.IconResolution == other.IconResolution + && this.IconSha256 == other.IconSha256 + && this.IconFileType == other.IconFileType + && this.IconTheme == other.IconTheme; + } + } + + /// + /// Partial class that implements helper methods for the Documentation class. + /// +#pragma warning disable SA1402 // File may only contain a single type + public partial class Documentation +#pragma warning restore SA1402 // File may only contain a single type + { + /// + /// Gives the criteria for determining whether two instances of Documentation objects are equal. + /// + /// A boolean value indicating whether the two objects are equal. + /// The object to compare with the current object. + public override bool Equals(object obj) + { + if (obj == null || this.GetType() != obj.GetType()) + { + return false; + } + + Documentation other = (Documentation)obj; + return this.DocumentUrl == other.DocumentUrl + && this.DocumentLabel == other.DocumentLabel; + } + } + /// /// Partial class that extends the property definitions of Agreement. /// +#pragma warning disable SA1402 // File may only contain a single type public partial class Agreement +#pragma warning restore SA1402 // File may only contain a single type { /// Gets or sets the agreement text content. [YamlMember(Alias = "Agreement")] diff --git a/src/WingetCreateTests/WingetCreateTests/Resources/TestPublisher.LocaleConversionTest.yaml b/src/WingetCreateTests/WingetCreateTests/Resources/TestPublisher.LocaleConversionTest.yaml new file mode 100644 index 00000000..26315741 --- /dev/null +++ b/src/WingetCreateTests/WingetCreateTests/Resources/TestPublisher.LocaleConversionTest.yaml @@ -0,0 +1,39 @@ +PackageIdentifier: TestPublisher.LocaleConversionTest.yaml +PackageVersion: 7.4.0.0 +PackageLocale: en-US +Publisher: fakePublisher +PublisherUrl: https://fakePublisherUrl.com/ +PublisherSupportUrl: https://fakePublisherSupportUrl.com/ +PrivacyUrl: https://fakePrivacyUrl.com/ +Author: fakeAuthor +PackageName: fakePackageName +PackageUrl: https://fakePackageUrl.com/ +License: fakeLicense +LicenseUrl: https://fakeLicenseUrl.com/ +Copyright: fakeCopyright +CopyrightUrl: fakeCopyrightUrl +Description: |- + fakeExtended description of a fake package. +Moniker: fakeMoniker +Tags: +- fakeTag1 +- fakeTag2 +- fakeTag3 +Icons: + - IconUrl: https://fakeIconUrl.com/ + IconFileType: png + - IconUrl: https://fakeIconUrl2.com/ + IconFileType: jpeg +ReleaseNotes: |- + fakeReleaseNotes of a fake package. +ReleaseNotesUrl: https://fakeReleaseNotesUrl.com/ +PurchaseUrl: https://fakePurchaseUrl.com/ +InstallationNotes: |- + fakeInstallationNotes of a fake package. +Documentations: +- DocumentLabel: fakeDocumentation1 + DocumentUrl: https://fakeDocumentationUrl1.com/ +- DocumentLabel: fakeDocumentation2 + DocumentUrl: https://fakeDocumentationUrl2.com/ +ManifestType: defaultLocale +ManifestVersion: 1.5.0 diff --git a/src/WingetCreateTests/WingetCreateTests/UnitTests/CommonTests.cs b/src/WingetCreateTests/WingetCreateTests/UnitTests/CommonTests.cs index 1bb18a5a..0d6a9ac3 100644 --- a/src/WingetCreateTests/WingetCreateTests/UnitTests/CommonTests.cs +++ b/src/WingetCreateTests/WingetCreateTests/UnitTests/CommonTests.cs @@ -6,6 +6,11 @@ namespace Microsoft.WingetCreateUnitTests using System; using System.IO; using Microsoft.WingetCreateCLI; + using Microsoft.WingetCreateCore; + using Microsoft.WingetCreateCore.Models; + using Microsoft.WingetCreateCore.Models.DefaultLocale; + using Microsoft.WingetCreateCore.Models.Locale; + using Microsoft.WingetCreateTests; using NUnit.Framework; /// @@ -37,5 +42,59 @@ public void VerifyPathSubstitutions() 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."); } + + /// + /// Tests the ability to convert a DefaultLocaleManifest to a LocaleManifest. + /// + [Test] + public void VerifyDefaultLocaleToLocaleManifestTypeConversion() + { + var manifestContent = TestUtils.GetInitialManifestContent("TestPublisher.LocaleConversionTest.yaml"); + Manifests manifest = Serialization.DeserializeManifestContents(manifestContent); + + DefaultLocaleManifest defaultLocale = manifest.DefaultLocaleManifest; + LocaleManifest localeManifest = defaultLocale.ToLocaleManifest(); + + Assert.IsTrue(localeManifest.ManifestType == "locale", "The manifest type should be locale."); + + Assert.AreEqual(defaultLocale.PackageIdentifier, localeManifest.PackageIdentifier, "The package identifier should be the same."); + Assert.AreEqual(defaultLocale.PackageVersion, localeManifest.PackageVersion, "The package version should be the same."); + Assert.AreEqual(defaultLocale.PackageLocale, localeManifest.PackageLocale, "The package locale should be the same."); + Assert.AreEqual(defaultLocale.Publisher, localeManifest.Publisher, "The publisher should be the same."); + Assert.AreEqual(defaultLocale.PublisherUrl, localeManifest.PublisherUrl, "The publisher URL should be the same."); + Assert.AreEqual(defaultLocale.PublisherSupportUrl, localeManifest.PublisherSupportUrl, "The publisher support URL should be the same."); + Assert.AreEqual(defaultLocale.PrivacyUrl, localeManifest.PrivacyUrl, "The privacy URL should be the same."); + Assert.AreEqual(defaultLocale.Author, localeManifest.Author, "The author should be the same."); + Assert.AreEqual(defaultLocale.PackageName, localeManifest.PackageName, "The package name should be the same."); + Assert.AreEqual(defaultLocale.PackageUrl, localeManifest.PackageUrl, "The package URL should be the same."); + Assert.AreEqual(defaultLocale.License, localeManifest.License, "The license should be the same."); + Assert.AreEqual(defaultLocale.LicenseUrl, localeManifest.LicenseUrl, "The license URL should be the same."); + Assert.AreEqual(defaultLocale.Copyright, localeManifest.Copyright, "The copyright should be the same."); + Assert.AreEqual(defaultLocale.CopyrightUrl, localeManifest.CopyrightUrl, "The copyright URL should be the same."); + Assert.AreEqual(defaultLocale.ShortDescription, localeManifest.ShortDescription, "The short description should be the same."); + Assert.AreEqual(defaultLocale.Description, localeManifest.Description, "The description should be the same."); + Assert.AreEqual(defaultLocale.Tags, localeManifest.Tags, "The tags should be the same."); + Assert.AreEqual(defaultLocale.Icons[0].IconUrl, localeManifest.Icons[0].IconUrl, "First icon URL should be the same."); + Assert.AreEqual(defaultLocale.Icons[0].IconResolution, localeManifest.Icons[0].IconResolution, "First icon resolution should be the same."); + Assert.AreEqual(defaultLocale.Icons[0].IconSha256, localeManifest.Icons[0].IconSha256, "First icon SHA256 should be the same."); + Assert.AreEqual(defaultLocale.Icons[0].IconFileType.ToString(), localeManifest.Icons[0].IconFileType.ToString(), "First icon file type should be the same."); + Assert.AreEqual(defaultLocale.Icons[0].IconTheme, localeManifest.Icons[0].IconTheme, "First icon theme should be the same."); + Assert.AreEqual(defaultLocale.Icons[1].IconUrl, localeManifest.Icons[1].IconUrl, "Second icon URL should be the same."); + Assert.AreEqual(defaultLocale.Icons[1].IconResolution, localeManifest.Icons[1].IconResolution, "Second icon resolution should be the same."); + Assert.AreEqual(defaultLocale.Icons[1].IconSha256, localeManifest.Icons[1].IconSha256, "Second icon SHA256 should be the same."); + Assert.AreEqual(defaultLocale.Icons[1].IconFileType.ToString(), localeManifest.Icons[1].IconFileType.ToString(), "Second icon file type should be the same."); + Assert.AreEqual(defaultLocale.Icons[1].IconTheme, localeManifest.Icons[1].IconTheme, "Second icon theme should be the same."); + Assert.AreEqual(defaultLocale.ReleaseNotes, localeManifest.ReleaseNotes, "The release notes should be the same."); + Assert.AreEqual(defaultLocale.ReleaseNotesUrl, localeManifest.ReleaseNotesUrl, "The release notes URL should be the same."); + Assert.AreEqual(defaultLocale.InstallationNotes, localeManifest.InstallationNotes, "The installation notes should be the same."); + Assert.AreEqual(defaultLocale.Documentations[0].DocumentUrl, localeManifest.Documentations[0].DocumentUrl, "First document url should be the same."); + Assert.AreEqual(defaultLocale.Documentations[0].DocumentLabel, localeManifest.Documentations[0].DocumentLabel, "First document label should be the same."); + Assert.AreEqual(defaultLocale.Documentations[1].DocumentUrl, localeManifest.Documentations[1].DocumentUrl, "Second document url should be the same."); + Assert.AreEqual(defaultLocale.Documentations[1].DocumentLabel, localeManifest.Documentations[1].DocumentLabel, "Second document url should be the same."); + Assert.AreEqual(defaultLocale.ManifestVersion, localeManifest.ManifestVersion, "The manifest version should be the same."); + + // Skipped due to bad conversion model from schema. + // Assert.AreEqual(defaultLocale.Agreements, localeManifest.Agreements, "The agreements should be the same."); + } } } diff --git a/src/WingetCreateTests/WingetCreateTests/WingetCreateTests.csproj b/src/WingetCreateTests/WingetCreateTests/WingetCreateTests.csproj index afa8a83d..993fc816 100644 --- a/src/WingetCreateTests/WingetCreateTests/WingetCreateTests.csproj +++ b/src/WingetCreateTests/WingetCreateTests/WingetCreateTests.csproj @@ -36,6 +36,9 @@ PreserveNewest + + PreserveNewest + PreserveNewest From 56fdfa521a1c4430b9162232776cb8800f16c72a Mon Sep 17 00:00:00 2001 From: Muhammad Danish Date: Tue, 21 Nov 2023 18:27:54 +0500 Subject: [PATCH 2/8] moar changes --- src/WingetCreateCLI/Commands/BaseCommand.cs | 17 +++++++++++ src/WingetCreateCLI/Commands/NewCommand.cs | 9 ++---- .../Commands/NewLocaleCommand.cs | 12 ++++++++ src/WingetCreateCLI/Commands/UpdateCommand.cs | 17 ----------- .../Commands/UpdateLocaleCommand.cs | 2 ++ .../Properties/Resources.Designer.cs | 29 ++++++++++++------- src/WingetCreateCLI/Properties/Resources.resx | 9 ++++-- src/WingetCreateCore/Common/Constants.cs | 5 ++++ 8 files changed, 63 insertions(+), 37 deletions(-) diff --git a/src/WingetCreateCLI/Commands/BaseCommand.cs b/src/WingetCreateCLI/Commands/BaseCommand.cs index 773dd151..18372205 100644 --- a/src/WingetCreateCLI/Commands/BaseCommand.cs +++ b/src/WingetCreateCLI/Commands/BaseCommand.cs @@ -656,6 +656,23 @@ protected static void DisplayLocaleManifests(List localeManifest } } + /// + /// Ensures that the manifestVersion is consistent across all manifest object models. + /// + /// Manifests object model. + protected static void EnsureManifestVersionConsistency(Manifests manifests) + { + string latestManifestVersion = new VersionManifest().ManifestVersion; + manifests.VersionManifest.ManifestVersion = latestManifestVersion; + manifests.DefaultLocaleManifest.ManifestVersion = latestManifestVersion; + manifests.InstallerManifest.ManifestVersion = latestManifestVersion; + + foreach (var localeManifest in manifests.LocaleManifests) + { + localeManifest.ManifestVersion = latestManifestVersion; + } + } + /// /// Launches the GitHub OAuth flow and obtains a GitHub token. /// diff --git a/src/WingetCreateCLI/Commands/NewCommand.cs b/src/WingetCreateCLI/Commands/NewCommand.cs index 036d1755..0b0f60bc 100644 --- a/src/WingetCreateCLI/Commands/NewCommand.cs +++ b/src/WingetCreateCLI/Commands/NewCommand.cs @@ -33,11 +33,6 @@ namespace Microsoft.WingetCreateCLI.Commands [Verb("new", HelpText = "NewCommand_HelpText", ResourceType = typeof(Resources))] public class NewCommand : BaseCommand { - /// - /// The url path to the manifest documentation site. - /// - private const string ManifestDocumentationUrl = "https://aka.ms/winget-manifest-schema"; - /// /// Installer types for which we can trust that the detected architecture is correct, so don't need to prompt the user to confirm. /// @@ -210,9 +205,9 @@ public override async Task Execute() Console.WriteLine(); Console.WriteLine(Resources.NewCommand_Header); Console.WriteLine(); - Logger.InfoLocalized(nameof(Resources.ManifestDocumentation_HelpText), ManifestDocumentationUrl); + Logger.InfoLocalized(nameof(Resources.ManifestDocumentation_HelpText), Constants.ManifestDocumentationUrl); Console.WriteLine(); - Console.WriteLine(Resources.NewCommand_Description); + Console.WriteLine(Resources.PrePromptInstructions_Header); Console.WriteLine(); Logger.DebugLocalized(nameof(Resources.EnterFollowingFields_Message)); diff --git a/src/WingetCreateCLI/Commands/NewLocaleCommand.cs b/src/WingetCreateCLI/Commands/NewLocaleCommand.cs index 3005a714..5d29afc5 100644 --- a/src/WingetCreateCLI/Commands/NewLocaleCommand.cs +++ b/src/WingetCreateCLI/Commands/NewLocaleCommand.cs @@ -17,6 +17,7 @@ namespace Microsoft.WingetCreateCLI.Commands using Microsoft.WingetCreateCLI.Telemetry; using Microsoft.WingetCreateCLI.Telemetry.Events; using Microsoft.WingetCreateCore; + using Microsoft.WingetCreateCore.Common; using Microsoft.WingetCreateCore.Models; using Microsoft.WingetCreateCore.Models.Locale; using Newtonsoft.Json; @@ -151,6 +152,15 @@ public override async Task Execute() } } + Console.WriteLine(Resources.NewLocaleCommand_Header); + Console.WriteLine(); + Logger.InfoLocalized(nameof(Resources.ManifestDocumentation_HelpText), Constants.ManifestDocumentationUrl); + Console.WriteLine(); + Console.WriteLine(Resources.PrePromptInstructions_Header); + Console.WriteLine(); + + Logger.DebugLocalized(nameof(Resources.EnterFollowingFields_Message)); + List newLocales = this.ParseArgumentsAndGenerateLocales(originalManifests); if (newLocales == null) @@ -159,6 +169,7 @@ public override async Task Execute() } Console.WriteLine(); + EnsureManifestVersionConsistency(originalManifests); DisplayGeneratedLocales(newLocales); if (string.IsNullOrEmpty(this.OutputDir)) @@ -296,6 +307,7 @@ private List ParseArgumentsAndGenerateLocales(Manifests original PromptAndSetLocaleProperties(newLocaleManifest, defaultPromptPropertiesForNewLocale, originalManifests); } + Console.WriteLine(); if (Prompt.Confirm(Resources.AddAdditionalLocaleProperties_Message)) { // Get optional properties that have not been prompted before. diff --git a/src/WingetCreateCLI/Commands/UpdateCommand.cs b/src/WingetCreateCLI/Commands/UpdateCommand.cs index a9961457..1df4ac0c 100644 --- a/src/WingetCreateCLI/Commands/UpdateCommand.cs +++ b/src/WingetCreateCLI/Commands/UpdateCommand.cs @@ -544,23 +544,6 @@ private static void UpdatePropertyForLocaleManifests(string propertyName, string } } - /// - /// Ensures that the manifestVersion is consistent across all manifest object models. - /// - /// Manifests object model. - private static void EnsureManifestVersionConsistency(Manifests manifests) - { - string latestManifestVersion = new VersionManifest().ManifestVersion; - manifests.VersionManifest.ManifestVersion = latestManifestVersion; - manifests.DefaultLocaleManifest.ManifestVersion = latestManifestVersion; - manifests.InstallerManifest.ManifestVersion = latestManifestVersion; - - foreach (var localeManifest in manifests.LocaleManifests) - { - localeManifest.ManifestVersion = latestManifestVersion; - } - } - /// /// Resets the value of version specific fields to null. /// diff --git a/src/WingetCreateCLI/Commands/UpdateLocaleCommand.cs b/src/WingetCreateCLI/Commands/UpdateLocaleCommand.cs index b182123f..2d5afc3f 100644 --- a/src/WingetCreateCLI/Commands/UpdateLocaleCommand.cs +++ b/src/WingetCreateCLI/Commands/UpdateLocaleCommand.cs @@ -147,6 +147,7 @@ public override async Task Execute() Manifests updatedLocales = this.PromptAndUpdateExistingLocales(originalManifests); Console.WriteLine(); + EnsureManifestVersionConsistency(originalManifests); DisplayUpdatedLocales(updatedLocales); if (string.IsNullOrEmpty(this.OutputDir)) @@ -276,6 +277,7 @@ private Manifests PromptAndUpdateExistingLocales(Manifests originalManifests) isDefaultLocale = true; } + Console.WriteLine(); if (Prompt.Confirm(Resources.UpdateAdditionalLocaleProperties_Message)) { if (isDefaultLocale) diff --git a/src/WingetCreateCLI/Properties/Resources.Designer.cs b/src/WingetCreateCLI/Properties/Resources.Designer.cs index e29dd02e..ed00c5b4 100644 --- a/src/WingetCreateCLI/Properties/Resources.Designer.cs +++ b/src/WingetCreateCLI/Properties/Resources.Designer.cs @@ -1905,15 +1905,6 @@ public static string NetworkConnectionFailure_Message { } } - /// - /// Looks up a localized string similar to Press ENTER to submit the value for each question including accepting the (default) value.. - /// - public static string NewCommand_Description { - get { - return ResourceManager.GetString("NewCommand_Description", resourceCulture); - } - } - /// /// Looks up a localized string similar to This tool will walk you through a series of questions to help you create your package manifest.. /// @@ -1950,6 +1941,15 @@ public static string NewInstallerUrlMustMatchExisting_Message { } } + /// + /// Looks up a localized string similar to This command will walk you through a series of questions to help generate a new locale manifest.. + /// + public static string NewLocaleCommand_Header { + get { + return ResourceManager.GetString("NewLocaleCommand_Header", resourceCulture); + } + } + /// /// Looks up a localized string similar to Launches a series of questions to help generate a new locale manifest. /// @@ -2140,7 +2140,7 @@ public static string PackageIdentifier_KeywordDescription { } /// - /// Looks up a localized string similar to The package meta-data locale. + /// Looks up a localized string similar to The package meta-data locale |e.g. en-US|. /// public static string PackageLocale_KeywordDescription { get { @@ -2238,6 +2238,15 @@ public static string PortableCommandAlias_Message { } } + /// + /// Looks up a localized string similar to Press ENTER to submit the value for each question including accepting the (default/reference) value.. + /// + public static string PrePromptInstructions_Header { + get { + return ResourceManager.GetString("PrePromptInstructions_Header", resourceCulture); + } + } + /// /// Looks up a localized string similar to Press any key to continue.... /// diff --git a/src/WingetCreateCLI/Properties/Resources.resx b/src/WingetCreateCLI/Properties/Resources.resx index f55b568b..d58e7a39 100644 --- a/src/WingetCreateCLI/Properties/Resources.resx +++ b/src/WingetCreateCLI/Properties/Resources.resx @@ -312,8 +312,8 @@ The package name |e.g. Visual Studio| - - Press ENTER to submit the value for each question including accepting the (default) value. + + Press ENTER to submit the value for each question including accepting the (default/reference) value. This tool will walk you through a series of questions to help you create your package manifest. @@ -331,7 +331,7 @@ The MSIX installer package family name - The package meta-data locale + The package meta-data locale |e.g. en-US| The package home page @@ -1325,4 +1325,7 @@ Indicates whether the installer is prohibited from being downloaded for offline installation + + This command will walk you through a series of questions to help generate a new locale manifest. + \ No newline at end of file diff --git a/src/WingetCreateCore/Common/Constants.cs b/src/WingetCreateCore/Common/Constants.cs index 6ff6ce04..25ecae77 100644 --- a/src/WingetCreateCore/Common/Constants.cs +++ b/src/WingetCreateCore/Common/Constants.cs @@ -62,5 +62,10 @@ public static class Constants /// Represents the subdirectory name of the user's local app data folder where the tool stores its debug data. /// public const string DiagnosticOutputDirectoryFolderName = "DiagOutputDir"; + + /// + /// The url path to the manifest documentation site. + /// + public const string ManifestDocumentationUrl = "https://aka.ms/winget-manifest-schema"; } } From f2e405fd4909df78479e2b0b7322de0090e98dac Mon Sep 17 00:00:00 2001 From: Muhammad Danish Date: Fri, 24 Nov 2023 20:30:18 +0500 Subject: [PATCH 3/8] modify resource strings --- src/WingetCreateCLI/Commands/NewCommand.cs | 2 +- .../Commands/NewLocaleCommand.cs | 2 +- .../Properties/Resources.Designer.cs | 29 ++++++++++++------- src/WingetCreateCLI/Properties/Resources.resx | 9 ++++-- 4 files changed, 27 insertions(+), 15 deletions(-) diff --git a/src/WingetCreateCLI/Commands/NewCommand.cs b/src/WingetCreateCLI/Commands/NewCommand.cs index 0b0f60bc..8393607d 100644 --- a/src/WingetCreateCLI/Commands/NewCommand.cs +++ b/src/WingetCreateCLI/Commands/NewCommand.cs @@ -207,7 +207,7 @@ public override async Task Execute() Console.WriteLine(); Logger.InfoLocalized(nameof(Resources.ManifestDocumentation_HelpText), Constants.ManifestDocumentationUrl); Console.WriteLine(); - Console.WriteLine(Resources.PrePromptInstructions_Header); + Console.WriteLine(Resources.NewCommand_PrePrompt_Header); Console.WriteLine(); Logger.DebugLocalized(nameof(Resources.EnterFollowingFields_Message)); diff --git a/src/WingetCreateCLI/Commands/NewLocaleCommand.cs b/src/WingetCreateCLI/Commands/NewLocaleCommand.cs index 5d29afc5..24d1c0b3 100644 --- a/src/WingetCreateCLI/Commands/NewLocaleCommand.cs +++ b/src/WingetCreateCLI/Commands/NewLocaleCommand.cs @@ -156,7 +156,7 @@ public override async Task Execute() Console.WriteLine(); Logger.InfoLocalized(nameof(Resources.ManifestDocumentation_HelpText), Constants.ManifestDocumentationUrl); Console.WriteLine(); - Console.WriteLine(Resources.PrePromptInstructions_Header); + Console.WriteLine(Resources.NewLocaleCommand_PrePrompt_Header); Console.WriteLine(); Logger.DebugLocalized(nameof(Resources.EnterFollowingFields_Message)); diff --git a/src/WingetCreateCLI/Properties/Resources.Designer.cs b/src/WingetCreateCLI/Properties/Resources.Designer.cs index ed00c5b4..5d94aa04 100644 --- a/src/WingetCreateCLI/Properties/Resources.Designer.cs +++ b/src/WingetCreateCLI/Properties/Resources.Designer.cs @@ -1906,7 +1906,7 @@ public static string NetworkConnectionFailure_Message { } /// - /// Looks up a localized string similar to This tool will walk you through a series of questions to help you create your package manifest.. + /// Looks up a localized string similar to This command will walk you through a series of questions to help you create your package manifest.. /// public static string NewCommand_Header { get { @@ -1923,6 +1923,15 @@ public static string NewCommand_HelpText { } } + /// + /// Looks up a localized string similar to Press ENTER to submit the value for each question including accepting the (default) value.. + /// + public static string NewCommand_PrePrompt_Header { + get { + return ResourceManager.GetString("NewCommand_PrePrompt_Header", resourceCulture); + } + } + /// /// Looks up a localized string similar to What is the new installer url?. /// @@ -1968,6 +1977,15 @@ public static string NewLocaleCommand_Locale_HelpText { } } + /// + /// Looks up a localized string similar to Press ENTER to submit the value for each question including accepting the (reference) value from reference locale manifest.. + /// + public static string NewLocaleCommand_PrePrompt_Header { + get { + return ResourceManager.GetString("NewLocaleCommand_PrePrompt_Header", resourceCulture); + } + } + /// /// Looks up a localized string similar to The version of the package to add a new locale for.. /// @@ -2238,15 +2256,6 @@ public static string PortableCommandAlias_Message { } } - /// - /// Looks up a localized string similar to Press ENTER to submit the value for each question including accepting the (default/reference) value.. - /// - public static string PrePromptInstructions_Header { - get { - return ResourceManager.GetString("PrePromptInstructions_Header", resourceCulture); - } - } - /// /// Looks up a localized string similar to Press any key to continue.... /// diff --git a/src/WingetCreateCLI/Properties/Resources.resx b/src/WingetCreateCLI/Properties/Resources.resx index d58e7a39..eb348503 100644 --- a/src/WingetCreateCLI/Properties/Resources.resx +++ b/src/WingetCreateCLI/Properties/Resources.resx @@ -312,11 +312,11 @@ The package name |e.g. Visual Studio| - - Press ENTER to submit the value for each question including accepting the (default/reference) value. + + Press ENTER to submit the value for each question including accepting the (default) value. - This tool will walk you through a series of questions to help you create your package manifest. + This command will walk you through a series of questions to help you create your package manifest. Launches a series of questions to help generate a new manifest @@ -1328,4 +1328,7 @@ This command will walk you through a series of questions to help generate a new locale manifest. + + Press ENTER to submit the value for each question including accepting the (reference) value from reference locale manifest. + \ No newline at end of file From dc2a5f70e4d9d9e20a675bb18c3fe8a87ce58070 Mon Sep 17 00:00:00 2001 From: Muhammad Danish Date: Tue, 28 Nov 2023 13:06:16 +0500 Subject: [PATCH 4/8] refactor for better separation --- src/WingetCreateCLI/Commands/BaseCommand.cs | 84 ------------ .../Commands/NewLocaleCommand.cs | 18 +-- .../Commands/UpdateLocaleCommand.cs | 23 +--- src/WingetCreateCLI/LocaleHelper.cs | 121 ++++++++++++++++++ 4 files changed, 132 insertions(+), 114 deletions(-) create mode 100644 src/WingetCreateCLI/LocaleHelper.cs diff --git a/src/WingetCreateCLI/Commands/BaseCommand.cs b/src/WingetCreateCLI/Commands/BaseCommand.cs index 18372205..098d43d8 100644 --- a/src/WingetCreateCLI/Commands/BaseCommand.cs +++ b/src/WingetCreateCLI/Commands/BaseCommand.cs @@ -7,7 +7,6 @@ namespace Microsoft.WingetCreateCLI.Commands using System.Collections; using System.Collections.Generic; using System.ComponentModel.DataAnnotations; - using System.Globalization; using System.IO; using System.Linq; using System.Net; @@ -550,89 +549,6 @@ protected static void PromptOptionalProperties(T manifest, List optio } } - /// - /// Prompts user to enter values for the input locale or default locale manifest properties. - /// - /// Type of the manifest. Expected to be either LocaleManifest or DefaultLocaleManifest. - /// Object model of the locale/defaultLocale manifest. - /// List of property names to be prompted for. - /// Optional parameter to be used when validating the user-inputted locale. Check whether the locale already exists in the original manifests. - protected static void PromptAndSetLocaleProperties(T localeManifest, List properties, Manifests originalManifests = null) - { - foreach (string propertyName in properties) - { - PropertyInfo property = typeof(T).GetProperty(propertyName); - PromptHelper.PromptPropertyAndSetValue(localeManifest, propertyName, property.GetValue(localeManifest)); - - if (propertyName == nameof(LocaleManifest.PackageLocale) && originalManifests != null) - { - while (!ValidateLocale(property.GetValue(localeManifest).ToString(), originalManifests)) - { - PromptHelper.PromptPropertyAndSetValue(localeManifest, propertyName, property.GetValue(localeManifest)); - } - - continue; - } - - Logger.Trace($"Property [{propertyName}] set to the value [{property.GetValue(localeManifest)}]"); - } - } - - /// - /// Checks whether the provided locale is valid. A locale is valid if it is in the correct format and does not already exist in the manifest. - /// This function handles the exception gracefully to be used in a prompt. - /// - /// The package locale string to check. - /// The base manifests to check against. - /// A boolean value indicating whether the locale is valid. - protected static bool ValidateLocale(string locale, Manifests manifests) - { - try - { - if (GetMatchingLocaleManifest(locale, manifests) != null) - { - Logger.ErrorLocalized(nameof(Resources.LocaleAlreadyExists_ErrorMessage), locale); - Console.WriteLine(); - return false; - } - - return true; - } - catch (ArgumentException) - { - Logger.ErrorLocalized(nameof(Resources.InvalidLocale_ErrorMessage)); - Console.WriteLine(); - return false; - } - } - - /// - /// Checks whether package locale already exists in the default locale manifest or one of the locale manifests and returns the matching manifest. - /// This function throws an exception if the locale string is in an invalid format. - /// - /// The package locale string to check. - /// The base manifests to check against. - /// An object representing the matching locale manifest. - protected static object GetMatchingLocaleManifest(string locale, Manifests originalManifests) - { - RegionInfo localeInfo = new RegionInfo(locale); - - if (localeInfo.Equals(new RegionInfo(originalManifests.DefaultLocaleManifest.PackageLocale))) - { - return originalManifests.DefaultLocaleManifest; - } - - foreach (var localeManifest in originalManifests.LocaleManifests) - { - if (localeInfo.Equals(new RegionInfo(localeManifest.PackageLocale))) - { - return localeManifest; - } - } - - return null; - } - /// /// Displays the preview of the default locale manifest to the console. /// diff --git a/src/WingetCreateCLI/Commands/NewLocaleCommand.cs b/src/WingetCreateCLI/Commands/NewLocaleCommand.cs index 24d1c0b3..1e4af056 100644 --- a/src/WingetCreateCLI/Commands/NewLocaleCommand.cs +++ b/src/WingetCreateCLI/Commands/NewLocaleCommand.cs @@ -5,10 +5,7 @@ namespace Microsoft.WingetCreateCLI.Commands { using System; using System.Collections.Generic; - using System.ComponentModel.DataAnnotations; using System.IO; - using System.Linq; - using System.Reflection; using System.Threading.Tasks; using CommandLine; using CommandLine.Text; @@ -20,7 +17,6 @@ namespace Microsoft.WingetCreateCLI.Commands using Microsoft.WingetCreateCore.Common; using Microsoft.WingetCreateCore.Models; using Microsoft.WingetCreateCore.Models.Locale; - using Newtonsoft.Json; using Sharprompt; /// @@ -124,7 +120,7 @@ public override async Task Execute() { try { - if (GetMatchingLocaleManifest(this.Locale, originalManifests) != null) + if (LocaleHelper.GetMatchingLocaleManifest(this.Locale, originalManifests) != null) { Logger.ErrorLocalized(nameof(Resources.LocaleAlreadyExists_ErrorMessage), this.Locale); Console.WriteLine(); @@ -248,7 +244,7 @@ private List ParseArgumentsAndGenerateLocales(Manifests original { try { - referenceLocaleManifest = (LocaleManifest)GetMatchingLocaleManifest(this.ReferenceLocale, originalManifests); + referenceLocaleManifest = (LocaleManifest)LocaleHelper.GetMatchingLocaleManifest(this.ReferenceLocale, originalManifests); } catch (ArgumentException) { @@ -299,23 +295,19 @@ private List ParseArgumentsAndGenerateLocales(Manifests original properties.Remove(nameof(LocaleManifest.PackageLocale)); Logger.DebugLocalized(nameof(Resources.FieldSetToValue_Message), nameof(LocaleManifest.PackageLocale), this.Locale); - PromptAndSetLocaleProperties(newLocaleManifest, properties, originalManifests); + LocaleHelper.PromptAndSetLocaleProperties(newLocaleManifest, properties, originalManifests); localeArgumentUsed = true; } else { - PromptAndSetLocaleProperties(newLocaleManifest, defaultPromptPropertiesForNewLocale, originalManifests); + LocaleHelper.PromptAndSetLocaleProperties(newLocaleManifest, defaultPromptPropertiesForNewLocale, originalManifests); } Console.WriteLine(); if (Prompt.Confirm(Resources.AddAdditionalLocaleProperties_Message)) { // Get optional properties that have not been prompted before. - var optionalProperties = newLocaleManifest.GetType().GetProperties().ToList().Where(p => - p.GetCustomAttribute() == null && - p.GetCustomAttribute() != null && - !defaultPromptPropertiesForNewLocale.Any(d => d == p.Name)).Select(p => p.Name).ToList(); - + var optionalProperties = LocaleHelper.GetUnPromptedLocalePropertyNames(newLocaleManifest, defaultPromptPropertiesForNewLocale); FillLocalePropertiesForUserCompletion(referenceLocaleManifest, newLocaleManifest, optionalProperties); PromptOptionalProperties(newLocaleManifest, optionalProperties); } diff --git a/src/WingetCreateCLI/Commands/UpdateLocaleCommand.cs b/src/WingetCreateCLI/Commands/UpdateLocaleCommand.cs index 2d5afc3f..6b355823 100644 --- a/src/WingetCreateCLI/Commands/UpdateLocaleCommand.cs +++ b/src/WingetCreateCLI/Commands/UpdateLocaleCommand.cs @@ -5,10 +5,8 @@ namespace Microsoft.WingetCreateCLI.Commands { using System; using System.Collections.Generic; - using System.ComponentModel.DataAnnotations; using System.IO; using System.Linq; - using System.Reflection; using System.Threading.Tasks; using CommandLine; using CommandLine.Text; @@ -20,7 +18,6 @@ namespace Microsoft.WingetCreateCLI.Commands using Microsoft.WingetCreateCore.Models; using Microsoft.WingetCreateCore.Models.DefaultLocale; using Microsoft.WingetCreateCore.Models.Locale; - using Newtonsoft.Json; using Sharprompt; /// @@ -118,7 +115,7 @@ public override async Task Execute() { try { - if (GetMatchingLocaleManifest(this.Locale, originalManifests) == null) + if (LocaleHelper.GetMatchingLocaleManifest(this.Locale, originalManifests) == null) { Logger.ErrorLocalized(nameof(Resources.LocaleDoesNotExist_Message), this.Locale); @@ -221,14 +218,6 @@ private static void DisplayUpdatedLocales(Manifests manifest) } } - private static List GetOptionalLocalePropertyNames(T genericLocaleManifest) - { - return genericLocaleManifest.GetType().GetProperties().ToList().Where(p => - p.GetCustomAttribute() == null && - p.GetCustomAttribute() != null) - .Select(p => p.Name).ToList(); - } - private Manifests PromptAndUpdateExistingLocales(Manifests originalManifests) { Manifests updatedLocales = new Manifests() @@ -253,7 +242,7 @@ private Manifests PromptAndUpdateExistingLocales(Manifests originalManifests) { if (!string.IsNullOrEmpty(this.Locale) && !localeArgumentUsed) { - selectedLocale = GetMatchingLocaleManifest(this.Locale, originalManifests); + selectedLocale = LocaleHelper.GetMatchingLocaleManifest(this.Locale, originalManifests); localeArgumentUsed = true; } else @@ -266,13 +255,13 @@ private Manifests PromptAndUpdateExistingLocales(Manifests originalManifests) if (selectedLocale is LocaleManifest localeManifest) { Logger.DebugLocalized(nameof(Resources.FieldSetToValue_Message), nameof(LocaleManifest.PackageLocale), localeManifest.PackageLocale); - PromptAndSetLocaleProperties(localeManifest, defaultPromptPropertiesForUpdateLocale); + LocaleHelper.PromptAndSetLocaleProperties(localeManifest, defaultPromptPropertiesForUpdateLocale); updatedLocales.LocaleManifests.Add(localeManifest); } else if (selectedLocale is DefaultLocaleManifest defaultLocaleManifest) { Logger.DebugLocalized(nameof(Resources.FieldSetToValue_Message), nameof(LocaleManifest.PackageLocale), defaultLocaleManifest.PackageLocale); - PromptAndSetLocaleProperties(defaultLocaleManifest, defaultPromptPropertiesForUpdateLocale); + LocaleHelper.PromptAndSetLocaleProperties(defaultLocaleManifest, defaultPromptPropertiesForUpdateLocale); updatedLocales.DefaultLocaleManifest = defaultLocaleManifest; isDefaultLocale = true; } @@ -282,12 +271,12 @@ private Manifests PromptAndUpdateExistingLocales(Manifests originalManifests) { if (isDefaultLocale) { - PromptOptionalProperties(updatedLocales.DefaultLocaleManifest, GetOptionalLocalePropertyNames(updatedLocales.DefaultLocaleManifest)); + PromptOptionalProperties(updatedLocales.DefaultLocaleManifest, LocaleHelper.GetUnPromptedLocalePropertyNames(updatedLocales.DefaultLocaleManifest, defaultPromptPropertiesForUpdateLocale)); } else { LocaleManifest manifest = (LocaleManifest)selectedLocale; - PromptOptionalProperties(manifest, GetOptionalLocalePropertyNames(manifest)); + PromptOptionalProperties(manifest, LocaleHelper.GetUnPromptedLocalePropertyNames(manifest, defaultPromptPropertiesForUpdateLocale)); } } diff --git a/src/WingetCreateCLI/LocaleHelper.cs b/src/WingetCreateCLI/LocaleHelper.cs new file mode 100644 index 00000000..46b496f9 --- /dev/null +++ b/src/WingetCreateCLI/LocaleHelper.cs @@ -0,0 +1,121 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. + +namespace Microsoft.WingetCreateCLI +{ + using System; + using System.Collections.Generic; + using System.ComponentModel.DataAnnotations; + using System.Globalization; + using System.Linq; + using System.Reflection; + using Microsoft.WingetCreateCLI.Logging; + using Microsoft.WingetCreateCLI.Properties; + using Microsoft.WingetCreateCore.Models; + using Microsoft.WingetCreateCore.Models.Locale; + using Newtonsoft.Json; + + /// + /// Provides helper functions for dealing with prompting and validating locale properties. + /// + public static class LocaleHelper + { + /// + /// Prompts user to enter values for the input locale or default locale manifest properties. + /// + /// Type of the manifest. Expected to be either LocaleManifest or DefaultLocaleManifest. + /// Object model of the locale/defaultLocale manifest. + /// List of property names to be prompted for. + /// Optional parameter to be used when validating the user-inputted locale. Check whether the locale already exists in the original manifests. + public static void PromptAndSetLocaleProperties(T localeManifest, List properties, Manifests originalManifests = null) + { + foreach (string propertyName in properties) + { + PropertyInfo property = typeof(T).GetProperty(propertyName); + PromptHelper.PromptPropertyAndSetValue(localeManifest, propertyName, property.GetValue(localeManifest)); + + if (propertyName == nameof(LocaleManifest.PackageLocale) && originalManifests != null) + { + while (!ValidateLocale(property.GetValue(localeManifest).ToString(), originalManifests)) + { + PromptHelper.PromptPropertyAndSetValue(localeManifest, propertyName, property.GetValue(localeManifest)); + } + + continue; + } + + Logger.Trace($"Property [{propertyName}] set to the value [{property.GetValue(localeManifest)}]"); + } + } + + /// + /// Checks whether the provided locale is valid. A locale is valid if it is in the correct format and does not already exist in the manifest. + /// This function handles the exception gracefully to be used in a prompt. + /// + /// The package locale string to check. + /// The base manifests to check against. + /// A boolean value indicating whether the locale is valid. + public static bool ValidateLocale(string locale, Manifests manifests) + { + try + { + if (GetMatchingLocaleManifest(locale, manifests) != null) + { + Logger.ErrorLocalized(nameof(Resources.LocaleAlreadyExists_ErrorMessage), locale); + Console.WriteLine(); + return false; + } + + return true; + } + catch (ArgumentException) + { + Logger.ErrorLocalized(nameof(Resources.InvalidLocale_ErrorMessage)); + Console.WriteLine(); + return false; + } + } + + /// + /// Checks whether package locale already exists in the default locale manifest or one of the locale manifests and returns the matching manifest. + /// This function throws an exception if the locale string is in an invalid format. + /// + /// The package locale string to check. + /// The base manifests to check against. + /// An object representing the matching locale manifest. + public static object GetMatchingLocaleManifest(string locale, Manifests originalManifests) + { + RegionInfo localeInfo = new RegionInfo(locale); + + if (localeInfo.Equals(new RegionInfo(originalManifests.DefaultLocaleManifest.PackageLocale))) + { + return originalManifests.DefaultLocaleManifest; + } + + foreach (var localeManifest in originalManifests.LocaleManifests) + { + if (localeInfo.Equals(new RegionInfo(localeManifest.PackageLocale))) + { + return localeManifest; + } + } + + return null; + } + + /// + /// Gets the list of locale properties that have not been prompted for. + /// + /// Type of the manifest. Expected to be either LocaleManifest or DefaultLocaleManifest. + /// Object model of the locale/defaultLocale manifest. + /// Properties that have already been prompted for. + /// List of locale property names. + public static List GetUnPromptedLocalePropertyNames(T manifest, List promptedProperties) + { + return manifest.GetType().GetProperties().ToList().Where(p => + p.GetCustomAttribute() == null && + p.GetCustomAttribute() != null && + !promptedProperties.Any(d => d == p.Name)).Select(p => p.Name).ToList(); + } + } +} From 6c9475c0ca90a0d9f50902486d9cb2472ebe85df Mon Sep 17 00:00:00 2001 From: Muhammad Danish Date: Tue, 28 Nov 2023 19:05:55 +0500 Subject: [PATCH 5/8] more tests --- src/WingetCreateCLI/LocaleHelper.cs | 58 +++++------ .../UnitTests/CommonTests.cs | 59 ------------ .../UnitTests/LocaleCommandsTests.cs | 95 +++++++++++++++++++ 3 files changed, 124 insertions(+), 88 deletions(-) create mode 100644 src/WingetCreateTests/WingetCreateTests/UnitTests/LocaleCommandsTests.cs diff --git a/src/WingetCreateCLI/LocaleHelper.cs b/src/WingetCreateCLI/LocaleHelper.cs index 46b496f9..b91c0260 100644 --- a/src/WingetCreateCLI/LocaleHelper.cs +++ b/src/WingetCreateCLI/LocaleHelper.cs @@ -36,7 +36,7 @@ public static void PromptAndSetLocaleProperties(T localeManifest, List(T localeManifest, List - /// Checks whether the provided locale is valid. A locale is valid if it is in the correct format and does not already exist in the manifest. - /// This function handles the exception gracefully to be used in a prompt. - /// - /// The package locale string to check. - /// The base manifests to check against. - /// A boolean value indicating whether the locale is valid. - public static bool ValidateLocale(string locale, Manifests manifests) - { - try - { - if (GetMatchingLocaleManifest(locale, manifests) != null) - { - Logger.ErrorLocalized(nameof(Resources.LocaleAlreadyExists_ErrorMessage), locale); - Console.WriteLine(); - return false; - } - - return true; - } - catch (ArgumentException) - { - Logger.ErrorLocalized(nameof(Resources.InvalidLocale_ErrorMessage)); - Console.WriteLine(); - return false; - } - } - /// /// Checks whether package locale already exists in the default locale manifest or one of the locale manifests and returns the matching manifest. /// This function throws an exception if the locale string is in an invalid format. @@ -117,5 +89,33 @@ public static List GetUnPromptedLocalePropertyNames(T manifest, List< p.GetCustomAttribute() != null && !promptedProperties.Any(d => d == p.Name)).Select(p => p.Name).ToList(); } + + /// + /// Checks whether the provided locale is valid. A locale is valid if it is in the correct format and does not already exist in the manifest. + /// This function handles the exception gracefully to be used in a prompt. + /// + /// The package locale string to check. + /// The base manifests to check against. + /// A boolean value indicating whether the locale is valid. + private static bool ValidatePromptedLocale(string locale, Manifests manifests) + { + try + { + if (GetMatchingLocaleManifest(locale, manifests) != null) + { + Logger.ErrorLocalized(nameof(Resources.LocaleAlreadyExists_ErrorMessage), locale); + Console.WriteLine(); + return false; + } + + return true; + } + catch (ArgumentException) + { + Logger.ErrorLocalized(nameof(Resources.InvalidLocale_ErrorMessage)); + Console.WriteLine(); + return false; + } + } } } diff --git a/src/WingetCreateTests/WingetCreateTests/UnitTests/CommonTests.cs b/src/WingetCreateTests/WingetCreateTests/UnitTests/CommonTests.cs index 0d6a9ac3..1bb18a5a 100644 --- a/src/WingetCreateTests/WingetCreateTests/UnitTests/CommonTests.cs +++ b/src/WingetCreateTests/WingetCreateTests/UnitTests/CommonTests.cs @@ -6,11 +6,6 @@ namespace Microsoft.WingetCreateUnitTests using System; using System.IO; using Microsoft.WingetCreateCLI; - using Microsoft.WingetCreateCore; - using Microsoft.WingetCreateCore.Models; - using Microsoft.WingetCreateCore.Models.DefaultLocale; - using Microsoft.WingetCreateCore.Models.Locale; - using Microsoft.WingetCreateTests; using NUnit.Framework; /// @@ -42,59 +37,5 @@ public void VerifyPathSubstitutions() 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."); } - - /// - /// Tests the ability to convert a DefaultLocaleManifest to a LocaleManifest. - /// - [Test] - public void VerifyDefaultLocaleToLocaleManifestTypeConversion() - { - var manifestContent = TestUtils.GetInitialManifestContent("TestPublisher.LocaleConversionTest.yaml"); - Manifests manifest = Serialization.DeserializeManifestContents(manifestContent); - - DefaultLocaleManifest defaultLocale = manifest.DefaultLocaleManifest; - LocaleManifest localeManifest = defaultLocale.ToLocaleManifest(); - - Assert.IsTrue(localeManifest.ManifestType == "locale", "The manifest type should be locale."); - - Assert.AreEqual(defaultLocale.PackageIdentifier, localeManifest.PackageIdentifier, "The package identifier should be the same."); - Assert.AreEqual(defaultLocale.PackageVersion, localeManifest.PackageVersion, "The package version should be the same."); - Assert.AreEqual(defaultLocale.PackageLocale, localeManifest.PackageLocale, "The package locale should be the same."); - Assert.AreEqual(defaultLocale.Publisher, localeManifest.Publisher, "The publisher should be the same."); - Assert.AreEqual(defaultLocale.PublisherUrl, localeManifest.PublisherUrl, "The publisher URL should be the same."); - Assert.AreEqual(defaultLocale.PublisherSupportUrl, localeManifest.PublisherSupportUrl, "The publisher support URL should be the same."); - Assert.AreEqual(defaultLocale.PrivacyUrl, localeManifest.PrivacyUrl, "The privacy URL should be the same."); - Assert.AreEqual(defaultLocale.Author, localeManifest.Author, "The author should be the same."); - Assert.AreEqual(defaultLocale.PackageName, localeManifest.PackageName, "The package name should be the same."); - Assert.AreEqual(defaultLocale.PackageUrl, localeManifest.PackageUrl, "The package URL should be the same."); - Assert.AreEqual(defaultLocale.License, localeManifest.License, "The license should be the same."); - Assert.AreEqual(defaultLocale.LicenseUrl, localeManifest.LicenseUrl, "The license URL should be the same."); - Assert.AreEqual(defaultLocale.Copyright, localeManifest.Copyright, "The copyright should be the same."); - Assert.AreEqual(defaultLocale.CopyrightUrl, localeManifest.CopyrightUrl, "The copyright URL should be the same."); - Assert.AreEqual(defaultLocale.ShortDescription, localeManifest.ShortDescription, "The short description should be the same."); - Assert.AreEqual(defaultLocale.Description, localeManifest.Description, "The description should be the same."); - Assert.AreEqual(defaultLocale.Tags, localeManifest.Tags, "The tags should be the same."); - Assert.AreEqual(defaultLocale.Icons[0].IconUrl, localeManifest.Icons[0].IconUrl, "First icon URL should be the same."); - Assert.AreEqual(defaultLocale.Icons[0].IconResolution, localeManifest.Icons[0].IconResolution, "First icon resolution should be the same."); - Assert.AreEqual(defaultLocale.Icons[0].IconSha256, localeManifest.Icons[0].IconSha256, "First icon SHA256 should be the same."); - Assert.AreEqual(defaultLocale.Icons[0].IconFileType.ToString(), localeManifest.Icons[0].IconFileType.ToString(), "First icon file type should be the same."); - Assert.AreEqual(defaultLocale.Icons[0].IconTheme, localeManifest.Icons[0].IconTheme, "First icon theme should be the same."); - Assert.AreEqual(defaultLocale.Icons[1].IconUrl, localeManifest.Icons[1].IconUrl, "Second icon URL should be the same."); - Assert.AreEqual(defaultLocale.Icons[1].IconResolution, localeManifest.Icons[1].IconResolution, "Second icon resolution should be the same."); - Assert.AreEqual(defaultLocale.Icons[1].IconSha256, localeManifest.Icons[1].IconSha256, "Second icon SHA256 should be the same."); - Assert.AreEqual(defaultLocale.Icons[1].IconFileType.ToString(), localeManifest.Icons[1].IconFileType.ToString(), "Second icon file type should be the same."); - Assert.AreEqual(defaultLocale.Icons[1].IconTheme, localeManifest.Icons[1].IconTheme, "Second icon theme should be the same."); - Assert.AreEqual(defaultLocale.ReleaseNotes, localeManifest.ReleaseNotes, "The release notes should be the same."); - Assert.AreEqual(defaultLocale.ReleaseNotesUrl, localeManifest.ReleaseNotesUrl, "The release notes URL should be the same."); - Assert.AreEqual(defaultLocale.InstallationNotes, localeManifest.InstallationNotes, "The installation notes should be the same."); - Assert.AreEqual(defaultLocale.Documentations[0].DocumentUrl, localeManifest.Documentations[0].DocumentUrl, "First document url should be the same."); - Assert.AreEqual(defaultLocale.Documentations[0].DocumentLabel, localeManifest.Documentations[0].DocumentLabel, "First document label should be the same."); - Assert.AreEqual(defaultLocale.Documentations[1].DocumentUrl, localeManifest.Documentations[1].DocumentUrl, "Second document url should be the same."); - Assert.AreEqual(defaultLocale.Documentations[1].DocumentLabel, localeManifest.Documentations[1].DocumentLabel, "Second document url should be the same."); - Assert.AreEqual(defaultLocale.ManifestVersion, localeManifest.ManifestVersion, "The manifest version should be the same."); - - // Skipped due to bad conversion model from schema. - // Assert.AreEqual(defaultLocale.Agreements, localeManifest.Agreements, "The agreements should be the same."); - } } } diff --git a/src/WingetCreateTests/WingetCreateTests/UnitTests/LocaleCommandsTests.cs b/src/WingetCreateTests/WingetCreateTests/UnitTests/LocaleCommandsTests.cs new file mode 100644 index 00000000..4ce853f6 --- /dev/null +++ b/src/WingetCreateTests/WingetCreateTests/UnitTests/LocaleCommandsTests.cs @@ -0,0 +1,95 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. + +namespace Microsoft.WingetCreateUnitTests +{ + using System; + using Microsoft.WingetCreateCLI; + using Microsoft.WingetCreateCore; + using Microsoft.WingetCreateCore.Models; + using Microsoft.WingetCreateCore.Models.DefaultLocale; + using Microsoft.WingetCreateCore.Models.Locale; + using Microsoft.WingetCreateTests; + using NUnit.Framework; + + /// + /// Test cases for commands that deal with creating/updating locales. + /// + public class LocaleCommandsTests + { + /// + /// Checks whether locale validations are working as expected. + /// + [Test] + public void VerifyLocaleValidations() + { + var initialManifestContent = TestUtils.GetInitialMultifileManifestContent("Multifile.MsixTest"); + Manifests manifests = Serialization.DeserializeManifestContents(initialManifestContent); + + Assert.AreEqual(LocaleHelper.GetMatchingLocaleManifest("en-us", manifests), manifests.DefaultLocaleManifest, "Locale for en-US already exists in the manifest."); + Assert.AreEqual(LocaleHelper.GetMatchingLocaleManifest("en-USA", manifests), manifests.DefaultLocaleManifest, "Locale for en-US already exists in the manifest."); + Assert.AreEqual(LocaleHelper.GetMatchingLocaleManifest("en-GB", manifests), manifests.LocaleManifests[0], "Locale for en-GB already exists in the manifest."); + Assert.AreEqual(LocaleHelper.GetMatchingLocaleManifest("en-gb", manifests), manifests.LocaleManifests[0], "Locale for en-GB already exists in the manifest."); + + Assert.AreEqual(LocaleHelper.GetMatchingLocaleManifest("fr-FR", manifests), null, "Locale for fr-FR does not exist in the manifest."); + Assert.AreEqual(LocaleHelper.GetMatchingLocaleManifest("de-DE", manifests), null, "Locale for de-DE does not exist in the manifest."); + + Assert.Throws(() => LocaleHelper.GetMatchingLocaleManifest("en", manifests), "Locale is not in the correct format."); + Assert.Throws(() => LocaleHelper.GetMatchingLocaleManifest("fr_FR", manifests), "Locale is not in the correct format."); + } + + /// + /// Tests the ability to convert a DefaultLocaleManifest to a LocaleManifest. + /// + [Test] + public void VerifyDefaultLocaleToLocaleManifestTypeConversion() + { + var manifestContent = TestUtils.GetInitialManifestContent("TestPublisher.LocaleConversionTest.yaml"); + Manifests manifest = Serialization.DeserializeManifestContents(manifestContent); + + DefaultLocaleManifest defaultLocale = manifest.DefaultLocaleManifest; + LocaleManifest localeManifest = defaultLocale.ToLocaleManifest(); + + Assert.IsTrue(localeManifest.ManifestType == "locale", "The manifest type should be locale."); + + Assert.AreEqual(defaultLocale.PackageIdentifier, localeManifest.PackageIdentifier, "The package identifier should be the same."); + Assert.AreEqual(defaultLocale.PackageVersion, localeManifest.PackageVersion, "The package version should be the same."); + Assert.AreEqual(defaultLocale.PackageLocale, localeManifest.PackageLocale, "The package locale should be the same."); + Assert.AreEqual(defaultLocale.Publisher, localeManifest.Publisher, "The publisher should be the same."); + Assert.AreEqual(defaultLocale.PublisherUrl, localeManifest.PublisherUrl, "The publisher URL should be the same."); + Assert.AreEqual(defaultLocale.PublisherSupportUrl, localeManifest.PublisherSupportUrl, "The publisher support URL should be the same."); + Assert.AreEqual(defaultLocale.PrivacyUrl, localeManifest.PrivacyUrl, "The privacy URL should be the same."); + Assert.AreEqual(defaultLocale.Author, localeManifest.Author, "The author should be the same."); + Assert.AreEqual(defaultLocale.PackageName, localeManifest.PackageName, "The package name should be the same."); + Assert.AreEqual(defaultLocale.PackageUrl, localeManifest.PackageUrl, "The package URL should be the same."); + Assert.AreEqual(defaultLocale.License, localeManifest.License, "The license should be the same."); + Assert.AreEqual(defaultLocale.LicenseUrl, localeManifest.LicenseUrl, "The license URL should be the same."); + Assert.AreEqual(defaultLocale.Copyright, localeManifest.Copyright, "The copyright should be the same."); + Assert.AreEqual(defaultLocale.CopyrightUrl, localeManifest.CopyrightUrl, "The copyright URL should be the same."); + Assert.AreEqual(defaultLocale.ShortDescription, localeManifest.ShortDescription, "The short description should be the same."); + Assert.AreEqual(defaultLocale.Description, localeManifest.Description, "The description should be the same."); + Assert.AreEqual(defaultLocale.Tags, localeManifest.Tags, "The tags should be the same."); + Assert.AreEqual(defaultLocale.Icons[0].IconUrl, localeManifest.Icons[0].IconUrl, "First icon URL should be the same."); + Assert.AreEqual(defaultLocale.Icons[0].IconResolution, localeManifest.Icons[0].IconResolution, "First icon resolution should be the same."); + Assert.AreEqual(defaultLocale.Icons[0].IconSha256, localeManifest.Icons[0].IconSha256, "First icon SHA256 should be the same."); + Assert.AreEqual(defaultLocale.Icons[0].IconFileType.ToString(), localeManifest.Icons[0].IconFileType.ToString(), "First icon file type should be the same."); + Assert.AreEqual(defaultLocale.Icons[0].IconTheme, localeManifest.Icons[0].IconTheme, "First icon theme should be the same."); + Assert.AreEqual(defaultLocale.Icons[1].IconUrl, localeManifest.Icons[1].IconUrl, "Second icon URL should be the same."); + Assert.AreEqual(defaultLocale.Icons[1].IconResolution, localeManifest.Icons[1].IconResolution, "Second icon resolution should be the same."); + Assert.AreEqual(defaultLocale.Icons[1].IconSha256, localeManifest.Icons[1].IconSha256, "Second icon SHA256 should be the same."); + Assert.AreEqual(defaultLocale.Icons[1].IconFileType.ToString(), localeManifest.Icons[1].IconFileType.ToString(), "Second icon file type should be the same."); + Assert.AreEqual(defaultLocale.Icons[1].IconTheme, localeManifest.Icons[1].IconTheme, "Second icon theme should be the same."); + Assert.AreEqual(defaultLocale.ReleaseNotes, localeManifest.ReleaseNotes, "The release notes should be the same."); + Assert.AreEqual(defaultLocale.ReleaseNotesUrl, localeManifest.ReleaseNotesUrl, "The release notes URL should be the same."); + Assert.AreEqual(defaultLocale.InstallationNotes, localeManifest.InstallationNotes, "The installation notes should be the same."); + Assert.AreEqual(defaultLocale.Documentations[0].DocumentUrl, localeManifest.Documentations[0].DocumentUrl, "First document url should be the same."); + Assert.AreEqual(defaultLocale.Documentations[0].DocumentLabel, localeManifest.Documentations[0].DocumentLabel, "First document label should be the same."); + Assert.AreEqual(defaultLocale.Documentations[1].DocumentUrl, localeManifest.Documentations[1].DocumentUrl, "Second document url should be the same."); + Assert.AreEqual(defaultLocale.Documentations[1].DocumentLabel, localeManifest.Documentations[1].DocumentLabel, "Second document url should be the same."); + Assert.AreEqual(defaultLocale.ManifestVersion, localeManifest.ManifestVersion, "The manifest version should be the same."); + + // Skipped due to bad conversion model from schema. + // Assert.AreEqual(defaultLocale.Agreements, localeManifest.Agreements, "The agreements should be the same."); + } + } +} From c71019c309ec5e295302fe4694044f264842c66e Mon Sep 17 00:00:00 2001 From: Muhammad Danish Date: Fri, 1 Dec 2023 00:34:44 +0500 Subject: [PATCH 6/8] fix comment --- src/WingetCreateCore/Models/Partials/DefaultLocalePartials.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/WingetCreateCore/Models/Partials/DefaultLocalePartials.cs b/src/WingetCreateCore/Models/Partials/DefaultLocalePartials.cs index 0a3f20fd..3e446705 100644 --- a/src/WingetCreateCore/Models/Partials/DefaultLocalePartials.cs +++ b/src/WingetCreateCore/Models/Partials/DefaultLocalePartials.cs @@ -8,7 +8,7 @@ namespace Microsoft.WingetCreateCore.Models.DefaultLocale using YamlDotNet.Serialization; /// - /// Partial class that extends the property definitions of LocaleManifest. + /// Partial class that extends the property definitions of DefaultLocaleManifest. /// public partial class DefaultLocaleManifest { From 80c385d3568bef1409a4c24219ff75f1bc84bc75 Mon Sep 17 00:00:00 2001 From: Muhammad Danish Date: Sun, 10 Dec 2023 22:30:09 +0500 Subject: [PATCH 7/8] address review comments --- doc/new-locale.md | 2 +- src/WingetCreateCLI/Commands/BaseCommand.cs | 15 +- .../Commands/NewLocaleCommand.cs | 250 +++++++++--------- .../Commands/UpdateLocaleCommand.cs | 103 ++++---- src/WingetCreateCLI/LocaleHelper.cs | 35 ++- .../Properties/Resources.Designer.cs | 2 +- src/WingetCreateCLI/Properties/Resources.resx | 6 +- .../UnitTests/LocaleCommandsTests.cs | 21 +- 8 files changed, 229 insertions(+), 205 deletions(-) diff --git a/doc/new-locale.md b/doc/new-locale.md index 3efa8ed8..9d50b5a1 100644 --- a/doc/new-locale.md +++ b/doc/new-locale.md @@ -25,7 +25,7 @@ The following arguments are available: | **id** | Required. Package identifier used to lookup the existing manifest on the Windows Package Manager repo. | **-v, --version** | The version of the package to add a new locale for. Default is the latest version. | **-l, --locale** | The package locale to create a new manifest for. If not provided, the tool will prompt you for this value. -| **-r, --reference-locale** | Locale manifest to be used for offering auto-complete suggestions. +| **-r, --reference-locale** | Existing locale manifest to be used as reference for default values. If not provided, the default locale manifest will be used. | **-o, --out** | The output directory where the newly created manifests will be saved locally. | **-t,--token** | GitHub personal access token used for direct submission to the Windows Package Manager repo | | **-?, --help** | Gets additional help on this command | diff --git a/src/WingetCreateCLI/Commands/BaseCommand.cs b/src/WingetCreateCLI/Commands/BaseCommand.cs index c5e44a73..941b09a6 100644 --- a/src/WingetCreateCLI/Commands/BaseCommand.cs +++ b/src/WingetCreateCLI/Commands/BaseCommand.cs @@ -764,9 +764,8 @@ protected async Task GitHubSubmitManifests(Manifests manifests, string prT /// /// Manifest object containing metadata of new manifest to be submitted. /// Manifest object representing an already exisitng manifest in the repository. - /// Name of the command calling this method. /// A string representing the pull request title. - protected string GetPRTitle(Manifests currentManifest, Manifests repositoryManifest = null, string commandName = null) + protected string GetPRTitle(Manifests currentManifest, Manifests repositoryManifest = null) { // Use custom PR title if provided by the user. if (!string.IsNullOrEmpty(this.PRTitle)) @@ -777,18 +776,6 @@ protected string GetPRTitle(Manifests currentManifest, Manifests repositoryManif string packageId = currentManifest.VersionManifest != null ? currentManifest.VersionManifest.PackageIdentifier : currentManifest.SingletonManifest.PackageIdentifier; string currentVersion = currentManifest.VersionManifest != null ? currentManifest.VersionManifest.PackageVersion : currentManifest.SingletonManifest.PackageVersion; - if (!string.IsNullOrEmpty(commandName)) - { - if (commandName.Equals(nameof(NewLocaleCommand))) - { - return $"Add locale: {packageId} version {currentVersion}"; - } - else if (commandName.Equals(nameof(UpdateLocaleCommand))) - { - return $"Update locale: {packageId} version {currentVersion}"; - } - } - // If no manifest exists in the repository, this is a new package. if (repositoryManifest == null) { diff --git a/src/WingetCreateCLI/Commands/NewLocaleCommand.cs b/src/WingetCreateCLI/Commands/NewLocaleCommand.cs index 1e4af056..1b2d3825 100644 --- a/src/WingetCreateCLI/Commands/NewLocaleCommand.cs +++ b/src/WingetCreateCLI/Commands/NewLocaleCommand.cs @@ -5,6 +5,7 @@ namespace Microsoft.WingetCreateCLI.Commands { using System; using System.Collections.Generic; + using System.Globalization; using System.IO; using System.Threading.Tasks; using CommandLine; @@ -25,6 +26,17 @@ namespace Microsoft.WingetCreateCLI.Commands [Verb("new-locale", HelpText = "NewLocaleCommand_HelpText", ResourceType = typeof(Resources))] public class NewLocaleCommand : BaseCommand { + private readonly List defaultPromptPropertiesForNewLocale = new() + { + nameof(LocaleManifest.PackageLocale), + nameof(LocaleManifest.PackageName), + nameof(LocaleManifest.Publisher), + nameof(LocaleManifest.License), + nameof(LocaleManifest.ShortDescription), + }; + + private bool localeArgumentUsed = false; + /// /// Gets the usage examples for the new-locale command. /// @@ -89,56 +101,69 @@ public override async Task Execute() HasGitHubToken = !string.IsNullOrEmpty(this.GitHubToken), }; - Prompt.Symbols.Done = new Symbol(string.Empty, string.Empty); - Prompt.Symbols.Prompt = new Symbol(string.Empty, string.Empty); - - string exactId; try { - exactId = await this.GitHubClient.FindPackageId(this.Id); - } - catch (Octokit.RateLimitExceededException) - { - Logger.ErrorLocalized(nameof(Resources.RateLimitExceeded_Message)); - return false; - } + Prompt.Symbols.Done = new Symbol(string.Empty, string.Empty); + Prompt.Symbols.Prompt = new Symbol(string.Empty, string.Empty); - if (!string.IsNullOrEmpty(exactId)) - { - this.Id = exactId; - } + // Validate format of input locale and reference locale arguments + try + { + if (!string.IsNullOrEmpty(this.Locale)) + { + _ = new RegionInfo(this.Locale); + } - List manifestContent; + if (!string.IsNullOrEmpty(this.ReferenceLocale)) + { + _ = new RegionInfo(this.ReferenceLocale); + } + } + catch (ArgumentException) + { + Logger.ErrorLocalized(nameof(Resources.InvalidLocale_ErrorMessage)); + return false; + } + + string exactId; + try + { + exactId = await this.GitHubClient.FindPackageId(this.Id); + } + catch (Octokit.RateLimitExceededException) + { + Logger.ErrorLocalized(nameof(Resources.RateLimitExceeded_Message)); + return false; + } + + if (!string.IsNullOrEmpty(exactId)) + { + this.Id = exactId; + } + + List manifestContent; + + try + { + manifestContent = await this.GitHubClient.GetManifestContentAsync(this.Id, this.Version); + } + catch (Octokit.NotFoundException e) + { + Logger.ErrorLocalized(nameof(Resources.Error_Prefix), e.Message); + Logger.ErrorLocalized(nameof(Resources.OctokitNotFound_Error)); + return false; + } - try - { - manifestContent = await this.GitHubClient.GetManifestContentAsync(this.Id, this.Version); Manifests originalManifests = Serialization.DeserializeManifestContents(manifestContent); - // Validate input locale argument and switch command flow if applicable. if (!string.IsNullOrEmpty(this.Locale)) { try { - if (LocaleHelper.GetMatchingLocaleManifest(this.Locale, originalManifests) != null) + if (LocaleHelper.DoesLocaleManifestExist(this.Locale, originalManifests)) { Logger.ErrorLocalized(nameof(Resources.LocaleAlreadyExists_ErrorMessage), this.Locale); - Console.WriteLine(); - - // Switch to update locale flow if user accepts to. - if (Prompt.Confirm(Resources.SwitchToUpdateLocaleFlow_Message)) - { - UpdateLocaleCommand command = new UpdateLocaleCommand - { - Id = this.Id, - Version = this.Version, - Locale = this.Locale, - GitHubToken = this.GitHubToken, - }; - await command.LoadGitHubClient(); - Console.WriteLine(); - return await command.Execute(); - } + return false; } } catch (ArgumentException) @@ -148,6 +173,32 @@ public override async Task Execute() } } + // Validate input reference locale argument and set the reference locale + LocaleManifest referenceLocaleManifest; + + if (!string.IsNullOrEmpty(this.ReferenceLocale)) + { + try + { + referenceLocaleManifest = (LocaleManifest)LocaleHelper.GetMatchingLocaleManifest(this.ReferenceLocale, originalManifests); + } + catch (ArgumentException) + { + Logger.ErrorLocalized(nameof(Resources.InvalidLocale_ErrorMessage)); + return false; + } + + if (referenceLocaleManifest == null) + { + Logger.ErrorLocalized(nameof(Resources.ReferenceLocaleNotFound_Error)); + return false; + } + } + else + { + referenceLocaleManifest = originalManifests.DefaultLocaleManifest.ToLocaleManifest(); + } + Console.WriteLine(Resources.NewLocaleCommand_Header); Console.WriteLine(); Logger.InfoLocalized(nameof(Resources.ManifestDocumentation_HelpText), Constants.ManifestDocumentationUrl); @@ -157,12 +208,16 @@ public override async Task Execute() Logger.DebugLocalized(nameof(Resources.EnterFollowingFields_Message)); - List newLocales = this.ParseArgumentsAndGenerateLocales(originalManifests); - - if (newLocales == null) + List newLocales = new(); + do { - return false; + LocaleManifest newlocale = this.GenerateLocaleManifest(originalManifests, referenceLocaleManifest); + Console.WriteLine(); + ValidateManifestsInTempDir(originalManifests); + originalManifests.LocaleManifests.Add(newlocale); + newLocales.Add(newlocale); } + while (Prompt.Confirm(Resources.CreateAnotherLocale_Message)); Console.WriteLine(); EnsureManifestVersionConsistency(originalManifests); @@ -182,7 +237,7 @@ public override async Task Execute() return await this.LoadGitHubClient(true) ? (commandEvent.IsSuccessful = await this.GitHubSubmitManifests( originalManifests, - this.GetPRTitle(originalManifests, null, nameof(NewLocaleCommand)))) + $"Add locale: {originalManifests.VersionManifest.PackageIdentifier} version {originalManifests.VersionManifest.PackageVersion}")) : false; } else @@ -197,12 +252,6 @@ public override async Task Execute() return await Task.FromResult(commandEvent.IsSuccessful = true); } - catch (Octokit.NotFoundException e) - { - Logger.ErrorLocalized(nameof(Resources.Error_Prefix), e.Message); - Logger.ErrorLocalized(nameof(Resources.OctokitNotFound_Error)); - return false; - } finally { TelemetryManager.Log.WriteEvent(commandEvent); @@ -219,7 +268,7 @@ private static void DisplayGeneratedLocales(List newLocales) } } - private static void FillLocalePropertiesForUserCompletion(LocaleManifest fromManifest, LocaleManifest toManifest, List properties) + private static void PopulateLocalePropertiesWithDefaultValues(LocaleManifest reference, LocaleManifest target, List properties) { foreach (string propertyName in properties) { @@ -228,99 +277,52 @@ private static void FillLocalePropertiesForUserCompletion(LocaleManifest fromMan continue; } - var value = fromManifest.GetType().GetProperty(propertyName).GetValue(fromManifest); + var value = reference.GetType().GetProperty(propertyName).GetValue(reference); if (value != null) { - toManifest.GetType().GetProperty(propertyName).SetValue(toManifest, value); + target.GetType().GetProperty(propertyName).SetValue(target, value); } } } - private List ParseArgumentsAndGenerateLocales(Manifests originalManifests) + private LocaleManifest GenerateLocaleManifest(Manifests originalManifests, LocaleManifest referenceLocaleManifest) { - LocaleManifest referenceLocaleManifest; + LocaleManifest newLocaleManifest = new LocaleManifest + { + PackageIdentifier = originalManifests.VersionManifest.PackageIdentifier, + PackageVersion = originalManifests.VersionManifest.PackageVersion, + }; + + // Fill in properties from the reference locale. This is to help user see previous values to quickly fill out the new manifest. + PopulateLocalePropertiesWithDefaultValues(referenceLocaleManifest, newLocaleManifest, this.defaultPromptPropertiesForNewLocale); - if (!string.IsNullOrEmpty(this.ReferenceLocale)) + if (!string.IsNullOrEmpty(this.Locale) && !this.localeArgumentUsed) { - try - { - referenceLocaleManifest = (LocaleManifest)LocaleHelper.GetMatchingLocaleManifest(this.ReferenceLocale, originalManifests); - } - catch (ArgumentException) - { - Logger.ErrorLocalized(nameof(Resources.InvalidLocale_ErrorMessage)); - return null; - } + newLocaleManifest.PackageLocale = this.Locale; - if (referenceLocaleManifest == null) - { - Logger.ErrorLocalized(nameof(Resources.ReferenceLocaleNotFound_Error)); - return null; - } + // Don't prompt for PackageLocale if it is provided as an argument. + List properties = new(this.defaultPromptPropertiesForNewLocale); + properties.Remove(nameof(LocaleManifest.PackageLocale)); + + Logger.DebugLocalized(nameof(Resources.FieldSetToValue_Message), nameof(LocaleManifest.PackageLocale), this.Locale); + LocaleHelper.PromptAndSetLocaleProperties(newLocaleManifest, properties, originalManifests); + this.localeArgumentUsed = true; } else { - referenceLocaleManifest = originalManifests.DefaultLocaleManifest.ToLocaleManifest(); + LocaleHelper.PromptAndSetLocaleProperties(newLocaleManifest, this.defaultPromptPropertiesForNewLocale, originalManifests); } - List generatedLocales = new(); - - List defaultPromptPropertiesForNewLocale = new() - { - nameof(LocaleManifest.PackageLocale), - nameof(LocaleManifest.PackageName), - nameof(LocaleManifest.Publisher), - nameof(LocaleManifest.License), - nameof(LocaleManifest.ShortDescription), - }; - - bool localeArgumentUsed = false; - do + Console.WriteLine(); + if (Prompt.Confirm(Resources.AddAdditionalLocaleProperties_Message)) { - LocaleManifest newLocaleManifest = new LocaleManifest - { - PackageIdentifier = originalManifests.VersionManifest.PackageIdentifier, - PackageVersion = originalManifests.VersionManifest.PackageVersion, - }; - - // Fill in properties from the reference locale. This is to help user see previous values to quickly fill out the new manifest. - FillLocalePropertiesForUserCompletion(referenceLocaleManifest, newLocaleManifest, defaultPromptPropertiesForNewLocale); - - if (!string.IsNullOrEmpty(this.Locale) && !localeArgumentUsed) - { - newLocaleManifest.PackageLocale = this.Locale; - - // Don't prompt for PackageLocale if it is provided as an argument. - List properties = new(defaultPromptPropertiesForNewLocale); - properties.Remove(nameof(LocaleManifest.PackageLocale)); - - Logger.DebugLocalized(nameof(Resources.FieldSetToValue_Message), nameof(LocaleManifest.PackageLocale), this.Locale); - LocaleHelper.PromptAndSetLocaleProperties(newLocaleManifest, properties, originalManifests); - localeArgumentUsed = true; - } - else - { - LocaleHelper.PromptAndSetLocaleProperties(newLocaleManifest, defaultPromptPropertiesForNewLocale, originalManifests); - } - - Console.WriteLine(); - if (Prompt.Confirm(Resources.AddAdditionalLocaleProperties_Message)) - { - // Get optional properties that have not been prompted before. - var optionalProperties = LocaleHelper.GetUnPromptedLocalePropertyNames(newLocaleManifest, defaultPromptPropertiesForNewLocale); - FillLocalePropertiesForUserCompletion(referenceLocaleManifest, newLocaleManifest, optionalProperties); - PromptOptionalProperties(newLocaleManifest, optionalProperties); - } - - originalManifests.LocaleManifests.Add(newLocaleManifest); - generatedLocales.Add(newLocaleManifest); - - Console.WriteLine(); - ValidateManifestsInTempDir(originalManifests); + // Get optional properties that have not been prompted before. + var optionalProperties = LocaleHelper.GetOptionalLocalePropertyNames(newLocaleManifest, this.defaultPromptPropertiesForNewLocale); + PopulateLocalePropertiesWithDefaultValues(referenceLocaleManifest, newLocaleManifest, optionalProperties); + PromptOptionalProperties(newLocaleManifest, optionalProperties); } - while (Prompt.Confirm(Resources.CreateAnotherLocale_Message)); - return generatedLocales; + return newLocaleManifest; } } } diff --git a/src/WingetCreateCLI/Commands/UpdateLocaleCommand.cs b/src/WingetCreateCLI/Commands/UpdateLocaleCommand.cs index 6b355823..c8376758 100644 --- a/src/WingetCreateCLI/Commands/UpdateLocaleCommand.cs +++ b/src/WingetCreateCLI/Commands/UpdateLocaleCommand.cs @@ -5,6 +5,7 @@ namespace Microsoft.WingetCreateCLI.Commands { using System; using System.Collections.Generic; + using System.Globalization; using System.IO; using System.Linq; using System.Threading.Tasks; @@ -84,55 +85,65 @@ public override async Task Execute() HasGitHubToken = !string.IsNullOrEmpty(this.GitHubToken), }; - Prompt.Symbols.Done = new Symbol(string.Empty, string.Empty); - Prompt.Symbols.Prompt = new Symbol(string.Empty, string.Empty); - - string exactId; try { - exactId = await this.GitHubClient.FindPackageId(this.Id); - } - catch (Octokit.RateLimitExceededException) - { - Logger.ErrorLocalized(nameof(Resources.RateLimitExceeded_Message)); - return false; - } + Prompt.Symbols.Done = new Symbol(string.Empty, string.Empty); + Prompt.Symbols.Prompt = new Symbol(string.Empty, string.Empty); - if (!string.IsNullOrEmpty(exactId)) - { - this.Id = exactId; - } + // Validate format of input locale argument + try + { + if (!string.IsNullOrEmpty(this.Locale)) + { + _ = new RegionInfo(this.Locale); + } + } + catch (ArgumentException) + { + Logger.ErrorLocalized(nameof(Resources.InvalidLocale_ErrorMessage)); + return false; + } - List manifestContent; + string exactId; + try + { + exactId = await this.GitHubClient.FindPackageId(this.Id); + } + catch (Octokit.RateLimitExceededException) + { + Logger.ErrorLocalized(nameof(Resources.RateLimitExceeded_Message)); + return false; + } + + if (!string.IsNullOrEmpty(exactId)) + { + this.Id = exactId; + } + + List manifestContent; + + try + { + manifestContent = await this.GitHubClient.GetManifestContentAsync(this.Id, this.Version); + } + catch (Octokit.NotFoundException e) + { + Logger.ErrorLocalized(nameof(Resources.Error_Prefix), e.Message); + Logger.ErrorLocalized(nameof(Resources.OctokitNotFound_Error)); + return false; + } - try - { - manifestContent = await this.GitHubClient.GetManifestContentAsync(this.Id, this.Version); Manifests originalManifests = Serialization.DeserializeManifestContents(manifestContent); - // Validate input locale argument and switch command flow if applicable. + // Validate input locale argument if (!string.IsNullOrEmpty(this.Locale)) { try { - if (LocaleHelper.GetMatchingLocaleManifest(this.Locale, originalManifests) == null) + if (!LocaleHelper.DoesLocaleManifestExist(this.Locale, originalManifests)) { Logger.ErrorLocalized(nameof(Resources.LocaleDoesNotExist_Message), this.Locale); - - // Switch to new locale flow if user accepts. - if (Prompt.Confirm(Resources.SwitchToNewLocaleFlow_Message)) - { - NewLocaleCommand command = new NewLocaleCommand - { - Id = this.Id, - Version = this.Version, - Locale = this.Locale, - GitHubToken = this.GitHubToken, - }; - await command.LoadGitHubClient(); - Console.WriteLine(); - return await command.Execute(); - } + return false; } } catch (ArgumentException) @@ -161,7 +172,7 @@ public override async Task Execute() return await this.LoadGitHubClient(true) ? (commandEvent.IsSuccessful = await this.GitHubSubmitManifests( originalManifests, - this.GetPRTitle(originalManifests, null, nameof(UpdateLocaleCommand)))) + $"Update locale: {originalManifests.VersionManifest.PackageIdentifier} version {originalManifests.VersionManifest.PackageVersion}")) : false; } else @@ -176,26 +187,20 @@ public override async Task Execute() return await Task.FromResult(commandEvent.IsSuccessful = true); } - catch (Octokit.NotFoundException e) - { - Logger.ErrorLocalized(nameof(Resources.Error_Prefix), e.Message); - Logger.ErrorLocalized(nameof(Resources.OctokitNotFound_Error)); - return false; - } finally { TelemetryManager.Log.WriteEvent(commandEvent); } } - private static object PromptAllLocalesAsMenuSelection(Manifests originalManifests) + private static object PromptAllLocalesAsMenuSelection(DefaultLocaleManifest defaultLocale, List localeManifests) { Dictionary localeMap = new Dictionary { - { originalManifests.DefaultLocaleManifest.PackageLocale + " (" + Resources.DefaultLocale_MenuItem + ")", originalManifests.DefaultLocaleManifest }, + { string.Format("{0} ({1})", defaultLocale.PackageLocale, Resources.DefaultLocale_MenuItem), defaultLocale }, }; - foreach (var locale in originalManifests.LocaleManifests) + foreach (var locale in localeManifests) { localeMap.Add(locale.PackageLocale, locale); } @@ -247,7 +252,7 @@ private Manifests PromptAndUpdateExistingLocales(Manifests originalManifests) } else { - selectedLocale = PromptAllLocalesAsMenuSelection(originalManifests); + selectedLocale = PromptAllLocalesAsMenuSelection(originalManifests.DefaultLocaleManifest, originalManifests.LocaleManifests); } bool isDefaultLocale = false; @@ -271,12 +276,12 @@ private Manifests PromptAndUpdateExistingLocales(Manifests originalManifests) { if (isDefaultLocale) { - PromptOptionalProperties(updatedLocales.DefaultLocaleManifest, LocaleHelper.GetUnPromptedLocalePropertyNames(updatedLocales.DefaultLocaleManifest, defaultPromptPropertiesForUpdateLocale)); + PromptOptionalProperties(updatedLocales.DefaultLocaleManifest, LocaleHelper.GetOptionalLocalePropertyNames(updatedLocales.DefaultLocaleManifest, defaultPromptPropertiesForUpdateLocale)); } else { LocaleManifest manifest = (LocaleManifest)selectedLocale; - PromptOptionalProperties(manifest, LocaleHelper.GetUnPromptedLocalePropertyNames(manifest, defaultPromptPropertiesForUpdateLocale)); + PromptOptionalProperties(manifest, LocaleHelper.GetOptionalLocalePropertyNames(manifest, defaultPromptPropertiesForUpdateLocale)); } } diff --git a/src/WingetCreateCLI/LocaleHelper.cs b/src/WingetCreateCLI/LocaleHelper.cs index b91c0260..c5f7ac55 100644 --- a/src/WingetCreateCLI/LocaleHelper.cs +++ b/src/WingetCreateCLI/LocaleHelper.cs @@ -49,22 +49,22 @@ public static void PromptAndSetLocaleProperties(T localeManifest, List - /// Checks whether package locale already exists in the default locale manifest or one of the locale manifests and returns the matching manifest. + /// Returns the locale manifest that matches the provided locale string. Returns null if the locale does not exist. /// This function throws an exception if the locale string is in an invalid format. /// /// The package locale string to check. - /// The base manifests to check against. + /// The base manifests to check against. /// An object representing the matching locale manifest. - public static object GetMatchingLocaleManifest(string locale, Manifests originalManifests) + public static object GetMatchingLocaleManifest(string locale, Manifests manifests) { RegionInfo localeInfo = new RegionInfo(locale); - if (localeInfo.Equals(new RegionInfo(originalManifests.DefaultLocaleManifest.PackageLocale))) + if (localeInfo.Equals(new RegionInfo(manifests.DefaultLocaleManifest.PackageLocale))) { - return originalManifests.DefaultLocaleManifest; + return manifests.DefaultLocaleManifest; } - foreach (var localeManifest in originalManifests.LocaleManifests) + foreach (var localeManifest in manifests.LocaleManifests) { if (localeInfo.Equals(new RegionInfo(localeManifest.PackageLocale))) { @@ -75,6 +75,25 @@ public static object GetMatchingLocaleManifest(string locale, Manifests original return null; } + /// + /// Checks whether package locale already exists in the default locale manifest or one of the locale manifests. + /// This function throws an exception if the locale string is in an invalid format. + /// + /// The package locale string to check. + /// The base manifests to check against. + /// A boolean value indicating whether the locale exists. + public static bool DoesLocaleManifestExist(string locale, Manifests manifests) + { + RegionInfo targetLocaleInfo = new RegionInfo(locale); + + if (targetLocaleInfo.Equals(new RegionInfo(manifests.DefaultLocaleManifest.PackageLocale))) + { + return true; + } + + return manifests.LocaleManifests.Any(localeManifest => targetLocaleInfo.Equals(new RegionInfo(localeManifest.PackageLocale))); + } + /// /// Gets the list of locale properties that have not been prompted for. /// @@ -82,7 +101,7 @@ public static object GetMatchingLocaleManifest(string locale, Manifests original /// Object model of the locale/defaultLocale manifest. /// Properties that have already been prompted for. /// List of locale property names. - public static List GetUnPromptedLocalePropertyNames(T manifest, List promptedProperties) + public static List GetOptionalLocalePropertyNames(T manifest, List promptedProperties) { return manifest.GetType().GetProperties().ToList().Where(p => p.GetCustomAttribute() == null && @@ -101,7 +120,7 @@ private static bool ValidatePromptedLocale(string locale, Manifests manifests) { try { - if (GetMatchingLocaleManifest(locale, manifests) != null) + if (DoesLocaleManifestExist(locale, manifests)) { Logger.ErrorLocalized(nameof(Resources.LocaleAlreadyExists_ErrorMessage), locale); Console.WriteLine(); diff --git a/src/WingetCreateCLI/Properties/Resources.Designer.cs b/src/WingetCreateCLI/Properties/Resources.Designer.cs index 9a18c36d..dc9c783d 100644 --- a/src/WingetCreateCLI/Properties/Resources.Designer.cs +++ b/src/WingetCreateCLI/Properties/Resources.Designer.cs @@ -2374,7 +2374,7 @@ public static string RateLimitExceeded_Message { } /// - /// Looks up a localized string similar to Locale manifest to be used for offering auto-complete suggestions. If not provided, the default locale manifest will be used.. + /// Looks up a localized string similar to Existing locale manifest to be used as reference for default values. If not provided, the default locale manifest will be used.. /// public static string ReferenceLocale_HelpText { get { diff --git a/src/WingetCreateCLI/Properties/Resources.resx b/src/WingetCreateCLI/Properties/Resources.resx index 0848d869..2f21bc7a 100644 --- a/src/WingetCreateCLI/Properties/Resources.resx +++ b/src/WingetCreateCLI/Properties/Resources.resx @@ -1271,7 +1271,7 @@ Invalid locale. Enter a valid language tag. - Locale manifest matching with input locale "{0}" already exists + Locale manifest matching with input locale "{0}" already exists. Use the update-locale command to update an existing locale manifest. {0} - represents the input package locale string @@ -1293,7 +1293,7 @@ Launches a series of questions to help generate a new locale manifest - Locale manifest to be used for offering auto-complete suggestions. If not provided, the default locale manifest will be used. + Existing locale manifest to be used as reference for default values. If not provided, the default locale manifest will be used. Launches a series of questions to help update an existing locale manifest @@ -1313,7 +1313,7 @@ The package locale to create a new manifest for. If not provided, the tool will prompt for this value. - Manifest for input locale "{0}" does not exist. + Manifest for input locale "{0}" does not exist. Use the new-locale command to generate a new locale manifest. {0} - represents the package locale string diff --git a/src/WingetCreateTests/WingetCreateTests/UnitTests/LocaleCommandsTests.cs b/src/WingetCreateTests/WingetCreateTests/UnitTests/LocaleCommandsTests.cs index 4ce853f6..00e62e91 100644 --- a/src/WingetCreateTests/WingetCreateTests/UnitTests/LocaleCommandsTests.cs +++ b/src/WingetCreateTests/WingetCreateTests/UnitTests/LocaleCommandsTests.cs @@ -26,16 +26,27 @@ public void VerifyLocaleValidations() var initialManifestContent = TestUtils.GetInitialMultifileManifestContent("Multifile.MsixTest"); Manifests manifests = Serialization.DeserializeManifestContents(initialManifestContent); - Assert.AreEqual(LocaleHelper.GetMatchingLocaleManifest("en-us", manifests), manifests.DefaultLocaleManifest, "Locale for en-US already exists in the manifest."); - Assert.AreEqual(LocaleHelper.GetMatchingLocaleManifest("en-USA", manifests), manifests.DefaultLocaleManifest, "Locale for en-US already exists in the manifest."); - Assert.AreEqual(LocaleHelper.GetMatchingLocaleManifest("en-GB", manifests), manifests.LocaleManifests[0], "Locale for en-GB already exists in the manifest."); + // Verify with different casing. + Assert.AreEqual(LocaleHelper.GetMatchingLocaleManifest("en-us", manifests), manifests.DefaultLocaleManifest, "The locale manifest for en-US should be returned."); + Assert.IsTrue(LocaleHelper.DoesLocaleManifestExist("en-us", manifests), "Locale for en-US already exists in the manifest."); + Assert.AreEqual(LocaleHelper.GetMatchingLocaleManifest("en-USA", manifests), manifests.DefaultLocaleManifest, "The locale manifest for en-US should be returned."); + Assert.IsTrue(LocaleHelper.DoesLocaleManifestExist("en-USA", manifests), "Locale for en-US already exists in the manifest."); + Assert.AreEqual(LocaleHelper.GetMatchingLocaleManifest("en-GB", manifests), manifests.LocaleManifests[0], "The locale manifest for en-GB should be returned."); + Assert.IsTrue(LocaleHelper.DoesLocaleManifestExist("en-GB", manifests), "Locale for en-GB already exists in the manifest."); Assert.AreEqual(LocaleHelper.GetMatchingLocaleManifest("en-gb", manifests), manifests.LocaleManifests[0], "Locale for en-GB already exists in the manifest."); + Assert.IsTrue(LocaleHelper.DoesLocaleManifestExist("en-gb", manifests), "Locale for en-GB already exists in the manifest."); - Assert.AreEqual(LocaleHelper.GetMatchingLocaleManifest("fr-FR", manifests), null, "Locale for fr-FR does not exist in the manifest."); - Assert.AreEqual(LocaleHelper.GetMatchingLocaleManifest("de-DE", manifests), null, "Locale for de-DE does not exist in the manifest."); + // Check for non-existent locales. + Assert.AreEqual(LocaleHelper.GetMatchingLocaleManifest("fr-FR", manifests), null, "Null should be returned as the locale manifest for fr-FR does not exist."); + Assert.IsFalse(LocaleHelper.DoesLocaleManifestExist("fr-FR", manifests), "Locale for fr-FR does not exist in the manifest."); + Assert.AreEqual(LocaleHelper.GetMatchingLocaleManifest("de-DE", manifests), null, "Null should be returned as the locale manifest for de-DE does not exist."); + Assert.IsFalse(LocaleHelper.DoesLocaleManifestExist("de-DE", manifests), "Locale for de-DE does not exist in the manifest."); + // Check for invalid locale formats. Assert.Throws(() => LocaleHelper.GetMatchingLocaleManifest("en", manifests), "Locale is not in the correct format."); + Assert.Throws(() => LocaleHelper.DoesLocaleManifestExist("en", manifests), "Locale is not in the correct format."); Assert.Throws(() => LocaleHelper.GetMatchingLocaleManifest("fr_FR", manifests), "Locale is not in the correct format."); + Assert.Throws(() => LocaleHelper.DoesLocaleManifestExist("fr_FR", manifests), "Locale is not in the correct format."); } /// From 9a9d7fbb564e54492bb75038165880cb72966a9b Mon Sep 17 00:00:00 2001 From: Muhammad Danish Date: Fri, 15 Dec 2023 00:06:21 +0500 Subject: [PATCH 8/8] address PR comments --- src/WingetCreateCLI/Commands/BaseCommand.cs | 6 ++ .../Commands/NewLocaleCommand.cs | 38 ++++---- .../Commands/UpdateLocaleCommand.cs | 90 ++++++++----------- src/WingetCreateCLI/LocaleHelper.cs | 25 ++++-- 4 files changed, 85 insertions(+), 74 deletions(-) diff --git a/src/WingetCreateCLI/Commands/BaseCommand.cs b/src/WingetCreateCLI/Commands/BaseCommand.cs index 941b09a6..c16fca53 100644 --- a/src/WingetCreateCLI/Commands/BaseCommand.cs +++ b/src/WingetCreateCLI/Commands/BaseCommand.cs @@ -223,6 +223,12 @@ protected static bool ValidateManifestsInTempDir(Manifests manifests) File.WriteAllText(Path.Combine(randomDirPath, installerManifestFileName), manifests.InstallerManifest.ToYaml()); File.WriteAllText(Path.Combine(randomDirPath, defaultLocaleManifestFileName), manifests.DefaultLocaleManifest.ToYaml()); + foreach (LocaleManifest localeManifest in manifests.LocaleManifests) + { + string localeManifestFileName = Manifests.GetFileName(localeManifest); + File.WriteAllText(Path.Combine(randomDirPath, localeManifestFileName), localeManifest.ToYaml()); + } + bool result = ValidateManifest(randomDirPath); Directory.Delete(randomDirPath, true); return result; diff --git a/src/WingetCreateCLI/Commands/NewLocaleCommand.cs b/src/WingetCreateCLI/Commands/NewLocaleCommand.cs index 1b2d3825..98667ab0 100644 --- a/src/WingetCreateCLI/Commands/NewLocaleCommand.cs +++ b/src/WingetCreateCLI/Commands/NewLocaleCommand.cs @@ -5,8 +5,11 @@ namespace Microsoft.WingetCreateCLI.Commands { using System; using System.Collections.Generic; + using System.ComponentModel.DataAnnotations; using System.Globalization; using System.IO; + using System.Linq; + using System.Reflection; using System.Threading.Tasks; using CommandLine; using CommandLine.Text; @@ -18,6 +21,7 @@ namespace Microsoft.WingetCreateCLI.Commands using Microsoft.WingetCreateCore.Common; using Microsoft.WingetCreateCore.Models; using Microsoft.WingetCreateCore.Models.Locale; + using Newtonsoft.Json; using Sharprompt; /// @@ -221,7 +225,7 @@ public override async Task Execute() Console.WriteLine(); EnsureManifestVersionConsistency(originalManifests); - DisplayGeneratedLocales(newLocales); + this.DisplayGeneratedLocales(newLocales); if (string.IsNullOrEmpty(this.OutputDir)) { @@ -258,17 +262,7 @@ public override async Task Execute() } } - private static void DisplayGeneratedLocales(List newLocales) - { - Logger.DebugLocalized(nameof(Resources.GenerateNewLocalePreview_Message)); - foreach (var localeManifest in newLocales) - { - Logger.InfoLocalized(nameof(Resources.LocaleManifest_Message), localeManifest.PackageLocale); - Console.WriteLine(localeManifest.ToYaml(true)); - } - } - - private static void PopulateLocalePropertiesWithDefaultValues(LocaleManifest reference, LocaleManifest target, List properties) + private void PopulateLocalePropertiesWithDefaultValues(LocaleManifest reference, LocaleManifest target, List properties) { foreach (string propertyName in properties) { @@ -294,7 +288,7 @@ private LocaleManifest GenerateLocaleManifest(Manifests originalManifests, Local }; // Fill in properties from the reference locale. This is to help user see previous values to quickly fill out the new manifest. - PopulateLocalePropertiesWithDefaultValues(referenceLocaleManifest, newLocaleManifest, this.defaultPromptPropertiesForNewLocale); + this.PopulateLocalePropertiesWithDefaultValues(referenceLocaleManifest, newLocaleManifest, this.defaultPromptPropertiesForNewLocale); if (!string.IsNullOrEmpty(this.Locale) && !this.localeArgumentUsed) { @@ -317,12 +311,26 @@ private LocaleManifest GenerateLocaleManifest(Manifests originalManifests, Local if (Prompt.Confirm(Resources.AddAdditionalLocaleProperties_Message)) { // Get optional properties that have not been prompted before. - var optionalProperties = LocaleHelper.GetOptionalLocalePropertyNames(newLocaleManifest, this.defaultPromptPropertiesForNewLocale); - PopulateLocalePropertiesWithDefaultValues(referenceLocaleManifest, newLocaleManifest, optionalProperties); + List optionalProperties = newLocaleManifest.GetType().GetProperties().ToList().Where(p => + p.GetCustomAttribute() == null && + p.GetCustomAttribute() != null && + !this.defaultPromptPropertiesForNewLocale.Any(d => d == p.Name)) + .Select(p => p.Name).ToList(); + this.PopulateLocalePropertiesWithDefaultValues(referenceLocaleManifest, newLocaleManifest, optionalProperties); PromptOptionalProperties(newLocaleManifest, optionalProperties); } return newLocaleManifest; } + + private void DisplayGeneratedLocales(List newLocales) + { + Logger.DebugLocalized(nameof(Resources.GenerateNewLocalePreview_Message)); + foreach (var localeManifest in newLocales) + { + Logger.InfoLocalized(nameof(Resources.LocaleManifest_Message), localeManifest.PackageLocale); + Console.WriteLine(localeManifest.ToYaml(true)); + } + } } } diff --git a/src/WingetCreateCLI/Commands/UpdateLocaleCommand.cs b/src/WingetCreateCLI/Commands/UpdateLocaleCommand.cs index c8376758..54bd1bcd 100644 --- a/src/WingetCreateCLI/Commands/UpdateLocaleCommand.cs +++ b/src/WingetCreateCLI/Commands/UpdateLocaleCommand.cs @@ -153,10 +153,10 @@ public override async Task Execute() } } - Manifests updatedLocales = this.PromptAndUpdateExistingLocales(originalManifests); + var updatedLocalesTuple = this.PromptAndUpdateExistingLocales(originalManifests); Console.WriteLine(); EnsureManifestVersionConsistency(originalManifests); - DisplayUpdatedLocales(updatedLocales); + this.DisplayUpdatedLocales(updatedLocalesTuple); if (string.IsNullOrEmpty(this.OutputDir)) { @@ -193,7 +193,7 @@ public override async Task Execute() } } - private static object PromptAllLocalesAsMenuSelection(DefaultLocaleManifest defaultLocale, List localeManifests) + private object PromptAllLocalesAsMenuSelection(DefaultLocaleManifest defaultLocale, List localeManifests) { Dictionary localeMap = new Dictionary { @@ -205,92 +205,80 @@ private static object PromptAllLocalesAsMenuSelection(DefaultLocaleManifest defa localeMap.Add(locale.PackageLocale, locale); } - string selectedLocale = Prompt.Select(Resources.SelectExistingLocale_Message, localeMap.Keys.ToList()); + string selectedLocale = Prompt.Select(Resources.SelectExistingLocale_Message, localeMap.Keys.ToList(), defaultValue: localeMap.Keys.First()); return localeMap[selectedLocale]; } - private static void DisplayUpdatedLocales(Manifests manifest) + private void DisplayUpdatedLocales(Tuple> updatedLocales) { Logger.DebugLocalized(nameof(Resources.GenerateUpdatedLocalePreview_Message)); - if (manifest.DefaultLocaleManifest != null) + if (updatedLocales.Item1 != null) { - DisplayDefaultLocaleManifest(manifest.DefaultLocaleManifest); + DisplayDefaultLocaleManifest(updatedLocales.Item1); } - if (manifest.LocaleManifests != null) + if (updatedLocales.Item2 != null) { - DisplayLocaleManifests(manifest.LocaleManifests); + DisplayLocaleManifests(updatedLocales.Item2); } } - private Manifests PromptAndUpdateExistingLocales(Manifests originalManifests) + /// + /// Displays a list of existing locale manifests and prompts user to update properties for the selected locale. + /// + /// Object model containing existing locale manifests. + /// A tuple containing the updated locale manifests. + private Tuple> PromptAndUpdateExistingLocales(Manifests manifests) { - Manifests updatedLocales = new Manifests() - { - SingletonManifest = null, - VersionManifest = null, - InstallerManifest = null, - }; - - List defaultPromptPropertiesForUpdateLocale = new() - { - nameof(LocaleManifest.PackageName), - nameof(LocaleManifest.Publisher), - nameof(LocaleManifest.License), - nameof(LocaleManifest.ShortDescription), - }; + // Object containing only the locales that are updated by the user. + Manifests updatedLocales = new Manifests(); bool localeArgumentUsed = false; object selectedLocale; + // Properties that should not be prompted for. + List excludedProperties = new List() + { + nameof(LocaleManifest.PackageIdentifier), + nameof(LocaleManifest.PackageVersion), + nameof(LocaleManifest.PackageLocale), + nameof(LocaleManifest.ManifestType), + nameof(LocaleManifest.ManifestVersion), + }; + do { if (!string.IsNullOrEmpty(this.Locale) && !localeArgumentUsed) { - selectedLocale = LocaleHelper.GetMatchingLocaleManifest(this.Locale, originalManifests); + selectedLocale = LocaleHelper.GetMatchingLocaleManifest(this.Locale, manifests); localeArgumentUsed = true; } else { - selectedLocale = PromptAllLocalesAsMenuSelection(originalManifests.DefaultLocaleManifest, originalManifests.LocaleManifests); + selectedLocale = this.PromptAllLocalesAsMenuSelection(manifests.DefaultLocaleManifest, manifests.LocaleManifests); } - bool isDefaultLocale = false; - - if (selectedLocale is LocaleManifest localeManifest) - { - Logger.DebugLocalized(nameof(Resources.FieldSetToValue_Message), nameof(LocaleManifest.PackageLocale), localeManifest.PackageLocale); - LocaleHelper.PromptAndSetLocaleProperties(localeManifest, defaultPromptPropertiesForUpdateLocale); - updatedLocales.LocaleManifests.Add(localeManifest); - } - else if (selectedLocale is DefaultLocaleManifest defaultLocaleManifest) + if (selectedLocale is DefaultLocaleManifest defaultLocaleManifest) { Logger.DebugLocalized(nameof(Resources.FieldSetToValue_Message), nameof(LocaleManifest.PackageLocale), defaultLocaleManifest.PackageLocale); - LocaleHelper.PromptAndSetLocaleProperties(defaultLocaleManifest, defaultPromptPropertiesForUpdateLocale); + var properties = LocaleHelper.GetLocalePropertyNames(defaultLocaleManifest).Except(excludedProperties).ToList(); + LocaleHelper.PromptAndSetLocaleProperties(defaultLocaleManifest, properties); updatedLocales.DefaultLocaleManifest = defaultLocaleManifest; - isDefaultLocale = true; } - - Console.WriteLine(); - if (Prompt.Confirm(Resources.UpdateAdditionalLocaleProperties_Message)) + else if (selectedLocale is LocaleManifest localeManifest) { - if (isDefaultLocale) - { - PromptOptionalProperties(updatedLocales.DefaultLocaleManifest, LocaleHelper.GetOptionalLocalePropertyNames(updatedLocales.DefaultLocaleManifest, defaultPromptPropertiesForUpdateLocale)); - } - else - { - LocaleManifest manifest = (LocaleManifest)selectedLocale; - PromptOptionalProperties(manifest, LocaleHelper.GetOptionalLocalePropertyNames(manifest, defaultPromptPropertiesForUpdateLocale)); - } + Logger.DebugLocalized(nameof(Resources.FieldSetToValue_Message), nameof(LocaleManifest.PackageLocale), localeManifest.PackageLocale); + var properties = LocaleHelper.GetLocalePropertyNames(localeManifest).Except(excludedProperties).ToList(); + LocaleHelper.PromptAndSetLocaleProperties(localeManifest, properties); + updatedLocales.LocaleManifests.Add(localeManifest); } Console.WriteLine(); - ValidateManifestsInTempDir(originalManifests); + ValidateManifestsInTempDir(manifests); } while (Prompt.Confirm(Resources.UpdateAnotherLocale_Message)); - return updatedLocales; + return new Tuple>(updatedLocales.DefaultLocaleManifest, updatedLocales.LocaleManifests); } } } diff --git a/src/WingetCreateCLI/LocaleHelper.cs b/src/WingetCreateCLI/LocaleHelper.cs index c5f7ac55..a7c11b9e 100644 --- a/src/WingetCreateCLI/LocaleHelper.cs +++ b/src/WingetCreateCLI/LocaleHelper.cs @@ -5,15 +5,16 @@ namespace Microsoft.WingetCreateCLI { using System; using System.Collections.Generic; - using System.ComponentModel.DataAnnotations; using System.Globalization; using System.Linq; using System.Reflection; using Microsoft.WingetCreateCLI.Logging; using Microsoft.WingetCreateCLI.Properties; + using Microsoft.WingetCreateCore.Common; using Microsoft.WingetCreateCore.Models; using Microsoft.WingetCreateCore.Models.Locale; using Newtonsoft.Json; + using Sharprompt; /// /// Provides helper functions for dealing with prompting and validating locale properties. @@ -32,6 +33,17 @@ public static void PromptAndSetLocaleProperties(T localeManifest, List - /// Gets the list of locale properties that have not been prompted for. + /// Gets the list of all locale properties. /// /// Type of the manifest. Expected to be either LocaleManifest or DefaultLocaleManifest. /// Object model of the locale/defaultLocale manifest. - /// Properties that have already been prompted for. /// List of locale property names. - public static List GetOptionalLocalePropertyNames(T manifest, List promptedProperties) + public static List GetLocalePropertyNames(T manifest) { - return manifest.GetType().GetProperties().ToList().Where(p => - p.GetCustomAttribute() == null && - p.GetCustomAttribute() != null && - !promptedProperties.Any(d => d == p.Name)).Select(p => p.Name).ToList(); + return manifest.GetType().GetProperties().ToList().Where(p => p.GetCustomAttribute() != null) + .Select(property => property.Name).ToList(); } ///