Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Add Navigational Menu to Edit Optional Installer Fields #133

Merged
merged 19 commits into from
Aug 25, 2021
Merged
Show file tree
Hide file tree
Changes from 11 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
215 changes: 140 additions & 75 deletions src/WingetCreateCLI/Commands/NewCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -100,10 +100,7 @@ public override async Task<bool> Execute()

if (!this.InstallerUrls.Any())
{
this.InstallerUrls = PromptProperty(
new Installer(),
this.InstallerUrls,
nameof(Installer.InstallerUrl));
PromptHelper.PromptAndSetPropertyValue(this, nameof(this.InstallerUrls), minimum: 1, validationModel: new Installer(), validationName: nameof(Installer.InstallerUrl));
Console.Clear();
}

Expand Down Expand Up @@ -190,11 +187,16 @@ private static void PromptPropertiesAndDisplayManifests(Manifests manifests)
PromptRequiredProperties(manifests.DefaultLocaleManifest, manifests.VersionManifest);

Console.WriteLine();
if (Prompt.Confirm(Resources.ModifyOptionalFields_Message))
if (Prompt.Confirm(Resources.ModifyOptionalDefaultLocaleFields_Message))
{
PromptOptionalProperties(manifests.DefaultLocaleManifest);
}

if (Prompt.Confirm(Resources.ModifyOptionalInstallerFields_Message))
{
DisplayInstallersAsMenuSelection(manifests.InstallerManifest);
}

Console.WriteLine();
DisplayManifestPreview(manifests);
}
Expand Down Expand Up @@ -237,10 +239,130 @@ private static void PromptRequiredProperties<T>(T manifest, VersionManifest vers
continue;
}

var currentValue = property.GetValue(manifest);
var result = PromptProperty(manifest, currentValue, property.Name);
property.SetValue(manifest, result);
Logger.Trace($"Property [{property.Name}] set to the value [{result}]");
PromptHelper.PromptAndSetPropertyValue(manifest, property.Name);
Logger.Trace($"Property [{property.Name}] set to the value [{property.GetValue(manifest)}]");
}
}
}

/// <summary>
/// Displays all installers from an Installer manifest as a selection menu.
/// </summary>
private static void DisplayInstallersAsMenuSelection(InstallerManifest installerManifest)
{
Console.Clear();

while (true)
{
List<string> selectionList = GenerateInstallerSelectionList(installerManifest.Installers);
var selectedItem = Prompt.Select(Resources.SelectInstallerToEdit_Message, selectionList);

if (selectedItem == Resources.None_MenuItem)
{
break;
}
else if (selectedItem == Resources.AllInstallers_MenuItem)
{
Installer installerCopy = new Installer();
PromptHelper.DisplayPropertiesAsMenuSelection(installerCopy, Resources.None_MenuItem);
ApplyChangesToIndividualInstallers(installerCopy, installerManifest.Installers);
}
else if (selectedItem == Resources.DisplayPreview_MenuItem)
{
Console.Clear();
Console.WriteLine();
Logger.InfoLocalized(nameof(Resources.DisplayPreviewOfSelectedInstaller_Message));
var serializer = Serialization.CreateSerializer();
string installerString = serializer.Serialize(installerManifest);
Console.WriteLine(installerString);
Console.WriteLine();
}
else
{
Installer selectedInstaller = MatchMenuSelectionToInstaller(selectedItem, installerManifest);
ryfu-msft marked this conversation as resolved.
Show resolved Hide resolved
PromptHelper.DisplayPropertiesAsMenuSelection(selectedInstaller, Resources.None_MenuItem);
}
}
}

private static Installer MatchMenuSelectionToInstaller(string selectedItem, InstallerManifest installerManifest)
{
string[] selection = selectedItem.Split('|');
var matchedInstallers = installerManifest.Installers.Where(
item => item.Architecture == (InstallerArchitecture)Enum.Parse(typeof(InstallerArchitecture), selection[0]) &&
ryfu-msft marked this conversation as resolved.
Show resolved Hide resolved
item.InstallerType == (InstallerType)Enum.Parse(typeof(InstallerType), selection[1], true) &&
item.InstallerUrl == selection[2]);

Installer selectedInstaller;
if (matchedInstallers.Count() > 1)
{
Scope scope;
ryfu-msft marked this conversation as resolved.
Show resolved Hide resolved
Scope? nullableScope = null;
string installerLocale = string.Empty;

for (int i = 3; i < selection.Length; i++)
{
// if parsing for scope enum fails, value must be an installer locale
if (Enum.TryParse(selection[i], true, out scope))
{
nullableScope = scope;
}
else
{
installerLocale = selection[i];
}
}

selectedInstaller = matchedInstallers.Single(
item => nullableScope != null && item.Scope == nullableScope &&
!string.IsNullOrEmpty(installerLocale) && item.InstallerLocale == installerLocale);
ryfu-msft marked this conversation as resolved.
Show resolved Hide resolved
}
else
{
selectedInstaller = matchedInstallers.Single();
}

return selectedInstaller;
}

private static List<string> GenerateInstallerSelectionList(List<Installer> installers)
{
List<string> selectionList = new List<string>();
selectionList.Add(Resources.AllInstallers_MenuItem);

foreach (Installer installer in installers)
{
var installerTuple = string.Join('|', installer.Architecture, installer.InstallerType.ToEnumAttributeValue(), installer.InstallerUrl);
ryfu-msft marked this conversation as resolved.
Show resolved Hide resolved
ryfu-msft marked this conversation as resolved.
Show resolved Hide resolved
if (installer.Scope != null)
ryfu-msft marked this conversation as resolved.
Show resolved Hide resolved
{
installerTuple = string.Join('|', installerTuple, installer.Scope);
}

if (installer.InstallerLocale != null)
{
installerTuple = string.Join('|', installerTuple, installer.InstallerLocale);
}

selectionList.Add(installerTuple);
}

selectionList.Add(Resources.DisplayPreview_MenuItem);
selectionList.Add(Resources.None_MenuItem);
return selectionList;
}

private static void ApplyChangesToIndividualInstallers(Installer installerCopy, List<Installer> installers)
{
var modifiedFields = installerCopy.GetType().GetProperties()
.Select(prop => prop)
.Where(pi => pi.GetValue(installerCopy) != null && pi.Name != nameof(Installer.Architecture));
ryfu-msft marked this conversation as resolved.
Show resolved Hide resolved

foreach (var field in modifiedFields)
{
foreach (Installer installer in installers)
{
var fieldValue = field.GetValue(installerCopy);
installer.GetType().GetProperty(field.Name).SetValue(installer, fieldValue);
}
}
}
Expand All @@ -254,10 +376,8 @@ private static void PromptOptionalProperties<T>(T manifest)

foreach (var property in optionalProperties)
{
var currentValue = property.GetValue(manifest);
var result = PromptProperty(manifest, currentValue, property.Name);
property.SetValue(manifest, result);
Logger.Trace($"Property [{property.Name}] set to the value [{result}]");
PromptHelper.PromptAndSetPropertyValue(manifest, property.Name);
Logger.Trace($"Property [{property.Name}] set to the value [{property.GetValue(manifest)}]");
}
}

Expand All @@ -277,9 +397,8 @@ private static void PromptInstallerProperties<T>(T manifest, PropertyInfo proper
if (installer.InstallerType == InstallerType.Exe)
{
Console.WriteLine();
Logger.Debug($"Additional metadata needed for installer from {installer.InstallerUrl}");
Logger.DebugLocalized(nameof(Resources.AdditionalMetadataNeeded_Message), installer.InstallerUrl);
prompted = true;

PromptInstallerSwitchesForExe(manifest);
}

Expand All @@ -294,12 +413,11 @@ private static void PromptInstallerProperties<T>(T manifest, PropertyInfo proper
if (!prompted)
{
Console.WriteLine();
Logger.Debug($"Additional metadata needed for installer from {installer.InstallerUrl}");
Logger.DebugLocalized(nameof(Resources.AdditionalMetadataNeeded_Message), installer.InstallerUrl);
prompted = true;
}

var result = PromptProperty(installer, currentValue, requiredProperty.Name);
requiredProperty.SetValue(installer, result);
PromptHelper.PromptAndSetPropertyValue(installer, requiredProperty.Name);
}
}
}
Expand All @@ -309,68 +427,15 @@ private static void PromptInstallerSwitchesForExe<T>(T manifest)
{
InstallerSwitches installerSwitches = new InstallerSwitches();

var silentSwitchResult = PromptProperty(installerSwitches, installerSwitches.Silent, nameof(InstallerSwitches.Silent));
var silentWithProgressSwitchResult = PromptProperty(installerSwitches, installerSwitches.SilentWithProgress, nameof(InstallerSwitches.SilentWithProgress));

bool updateSwitches = false;
PromptHelper.PromptAndSetPropertyValue(installerSwitches, nameof(InstallerSwitches.Silent));
PromptHelper.PromptAndSetPropertyValue(installerSwitches, nameof(InstallerSwitches.SilentWithProgress));

if (!string.IsNullOrEmpty(silentSwitchResult))
{
installerSwitches.Silent = silentSwitchResult;
updateSwitches = true;
}

if (!string.IsNullOrEmpty(silentWithProgressSwitchResult))
{
installerSwitches.SilentWithProgress = silentWithProgressSwitchResult;
updateSwitches = true;
}

if (updateSwitches)
if (!string.IsNullOrEmpty(installerSwitches.Silent) || !string.IsNullOrEmpty(installerSwitches.SilentWithProgress))
{
manifest.GetType().GetProperty(nameof(InstallerSwitches)).SetValue(manifest, installerSwitches);
}
}

private static T PromptProperty<T>(object model, T property, string memberName, string message = null)
{
message ??= $"[{memberName}] " +
Resources.ResourceManager.GetString($"{memberName}_KeywordDescription") ?? memberName;

// Because some properties don't have a current value, we can't rely on T or the property to obtain the type.
// Use reflection to obtain the type by looking up the property type by membername based on the model.
Type typeFromModel = model.GetType().GetProperty(memberName).PropertyType;
if (typeFromModel.IsEnum)
{
// For enums, we want to call Prompt.Select<T>, specifically the overload that takes 4 parameters
var generic = typeof(Prompt)
.GetMethods()
.Where(mi => mi.Name == nameof(Prompt.Select) && mi.GetParameters().Length == 5)
.Single()
.MakeGenericMethod(property.GetType());

return (T)generic.Invoke(null, new object[] { message, property.GetType().GetEnumValues(), null, property, null });
}
else if (typeof(IEnumerable<string>).IsAssignableFrom(typeof(T)) || typeof(IEnumerable<string>).IsAssignableFrom(typeFromModel))
{
string combinedString = null;

if (property is IEnumerable<string> propList && propList.Any())
{
combinedString = string.Join(", ", propList);
}

// Take in a comma-delimited string, and validate each split item, then return the split array
string promptResult = Prompt.Input<string>(message, combinedString, new[] { FieldValidation.ValidateProperty(model, memberName, property) });
return (T)(object)promptResult?.Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries).ToList();
}
else
{
var promptResult = Prompt.Input<T>(message, property, new[] { FieldValidation.ValidateProperty(model, memberName, property) });
return promptResult is string str ? (T)(object)str.Trim() : promptResult;
}
}

/// <summary>
/// Prompts for the package identifier and checks if the package identifier already exists.
/// If the package identifier is valid, the value is applied to the other manifests.
Expand All @@ -380,7 +445,7 @@ private static T PromptProperty<T>(object model, T property, string memberName,
private async Task<bool> PromptPackageIdentifierAndCheckDuplicates(Manifests manifests)
{
VersionManifest versionManifest = manifests.VersionManifest;
versionManifest.PackageIdentifier = PromptProperty(versionManifest, versionManifest.PackageIdentifier, nameof(versionManifest.PackageIdentifier));
PromptHelper.PromptAndSetPropertyValue(versionManifest, nameof(versionManifest.PackageIdentifier));

string exactMatch;
try
Expand Down
Loading