diff --git a/src/dsc/v3/PowerToys.DSC/Commands/BaseCommand.cs b/src/dsc/v3/PowerToys.DSC/Commands/BaseCommand.cs index a13748ba5e..b05e787ab1 100644 --- a/src/dsc/v3/PowerToys.DSC/Commands/BaseCommand.cs +++ b/src/dsc/v3/PowerToys.DSC/Commands/BaseCommand.cs @@ -5,7 +5,7 @@ using System; using System.CommandLine; using System.CommandLine.Invocation; -using ManagedCommon; +using System.CommandLine.IO; using PowerToys.DSC.Options; using PowerToys.DSC.Resources; @@ -17,12 +17,12 @@ internal abstract class BaseCommand : Command private readonly ResourceOption _resourceOption; private readonly InputOption _inputOption; - protected ModuleType Module { get; private set; } - protected BaseResource? Resource { get; private set; } protected string? Input { get; private set; } + private string? Module { get; set; } + public BaseCommand(string name, string description) : base(name, description) { @@ -40,17 +40,24 @@ internal abstract class BaseCommand : Command public void CommandHandler(InvocationContext context) { - var moduleName = context.ParseResult.GetValueForOption(_moduleOption); var resourceName = context.ParseResult.GetValueForOption(_resourceOption); Input = context.ParseResult.GetValueForOption(_inputOption); - Module = Enum.Parse(moduleName!, ignoreCase: true); + Module = context.ParseResult.GetValueForOption(_moduleOption); Resource = resourceName switch { SettingsResource.ResourceName => new SettingsResource(Module), _ => throw new ArgumentException($"Unknown resource name: {resourceName}"), }; + var supportedModules = Resource.GetSupportedModules(); + if (!string.IsNullOrEmpty(Module) && !supportedModules.Contains(Module)) + { + context.Console.Error.WriteLine($"Module '{Module}' is not supported for the resource {resourceName}. Supported modules are: {string.Join(", ", supportedModules)}"); + context.ExitCode = 1; + return; + } + CommandHandlerInternal(context); } diff --git a/src/dsc/v3/PowerToys.DSC/Commands/ManifestCommand.cs b/src/dsc/v3/PowerToys.DSC/Commands/ManifestCommand.cs index 1ef4c0ac20..39eeed04cb 100644 --- a/src/dsc/v3/PowerToys.DSC/Commands/ManifestCommand.cs +++ b/src/dsc/v3/PowerToys.DSC/Commands/ManifestCommand.cs @@ -15,6 +15,7 @@ internal sealed class ManifestCommand : BaseCommand : base("manifest", "Get the manifest of the dsc resource") { _outputDirectoryOption = new OutputDirectoryOption(); + AddOption(_outputDirectoryOption); } public override void CommandHandlerInternal(InvocationContext context) diff --git a/src/dsc/v3/PowerToys.DSC/Commands/ModulesCommand.cs b/src/dsc/v3/PowerToys.DSC/Commands/ModulesCommand.cs new file mode 100644 index 0000000000..a39b3762cb --- /dev/null +++ b/src/dsc/v3/PowerToys.DSC/Commands/ModulesCommand.cs @@ -0,0 +1,25 @@ +// Copyright (c) Microsoft Corporation +// The Microsoft Corporation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.CommandLine.Invocation; + +namespace PowerToys.DSC.Commands; + +internal sealed class ModulesCommand : BaseCommand +{ + public ModulesCommand() + : base("modules", "Get all supported modules") + { + } + + public override void CommandHandlerInternal(InvocationContext context) + { + // Print the supported modules for the specified resource + foreach (var module in Resource!.GetSupportedModules()) + { + Console.WriteLine(module); + } + } +} diff --git a/src/dsc/v3/PowerToys.DSC/Options/InputOption.cs b/src/dsc/v3/PowerToys.DSC/Options/InputOption.cs index 006040b1b7..2ecc6caf7e 100644 --- a/src/dsc/v3/PowerToys.DSC/Options/InputOption.cs +++ b/src/dsc/v3/PowerToys.DSC/Options/InputOption.cs @@ -8,7 +8,7 @@ using System.Text.Json; namespace PowerToys.DSC.Options; -internal sealed class InputOption : Option +internal sealed class InputOption : Option { public InputOption() : base("--input", "The JSON input") @@ -18,7 +18,7 @@ internal sealed class InputOption : Option private void OptionValidator(OptionResult result) { - var value = result.GetValueOrDefault() ?? string.Empty; + var value = result.GetValueOrDefault() ?? string.Empty; try { diff --git a/src/dsc/v3/PowerToys.DSC/Options/ModuleOption.cs b/src/dsc/v3/PowerToys.DSC/Options/ModuleOption.cs index 4641d5fdeb..9a03dcc631 100644 --- a/src/dsc/v3/PowerToys.DSC/Options/ModuleOption.cs +++ b/src/dsc/v3/PowerToys.DSC/Options/ModuleOption.cs @@ -2,28 +2,14 @@ // The Microsoft Corporation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -using System; using System.CommandLine; -using System.CommandLine.Parsing; -using ManagedCommon; namespace PowerToys.DSC.Options; -internal sealed class ModuleOption : Option +internal sealed class ModuleOption : Option { public ModuleOption() : base("--module", "The module name") { - IsRequired = true; - AddValidator(OptionValidator); - } - - private void OptionValidator(OptionResult result) - { - var value = result.GetValueOrDefault() ?? string.Empty; - if (!Enum.TryParse(value, ignoreCase: true, out _)) - { - result.ErrorMessage = $"Invalid module name. Valid values are: {string.Join(", ", Enum.GetValues())}"; - } } } diff --git a/src/dsc/v3/PowerToys.DSC/Program.cs b/src/dsc/v3/PowerToys.DSC/Program.cs index bb68685905..eb2576424b 100644 --- a/src/dsc/v3/PowerToys.DSC/Program.cs +++ b/src/dsc/v3/PowerToys.DSC/Program.cs @@ -25,6 +25,7 @@ public class Program rootCommand.AddCommand(new TestCommand()); rootCommand.AddCommand(new SchemaCommand()); rootCommand.AddCommand(new ManifestCommand()); + rootCommand.AddCommand(new ModulesCommand()); return await rootCommand.InvokeAsync(args); } } diff --git a/src/dsc/v3/PowerToys.DSC/Resources/BaseResource.cs b/src/dsc/v3/PowerToys.DSC/Resources/BaseResource.cs index 15bb48c89f..ba88149844 100644 --- a/src/dsc/v3/PowerToys.DSC/Resources/BaseResource.cs +++ b/src/dsc/v3/PowerToys.DSC/Resources/BaseResource.cs @@ -5,7 +5,6 @@ using System; using System.Collections.Generic; using System.Text.Json.Serialization; -using ManagedCommon; using Newtonsoft.Json; using NJsonSchema.Generation; using PowerToys.DSC.Models; @@ -14,9 +13,9 @@ namespace PowerToys.DSC.Resources; internal abstract class BaseResource { - public ModuleType Module { get; } + public string? Module { get; } - public BaseResource(ModuleType module) + public BaseResource(string? module) { Module = module; } @@ -33,6 +32,8 @@ internal abstract class BaseResource public abstract bool Manifest(string? outputDir); + public abstract IList GetSupportedModules(); + protected void WriteJsonOutputLine(string output) { Console.WriteLine(output); diff --git a/src/dsc/v3/PowerToys.DSC/Resources/SettingsResource.cs b/src/dsc/v3/PowerToys.DSC/Resources/SettingsResource.cs index 2a6ca1f671..4c9fee0a5c 100644 --- a/src/dsc/v3/PowerToys.DSC/Resources/SettingsResource.cs +++ b/src/dsc/v3/PowerToys.DSC/Resources/SettingsResource.cs @@ -4,6 +4,8 @@ using System; using System.Collections.Generic; +using System.IO; +using System.Linq; using System.Text.Json; using System.Text.Json.Nodes; using ManagedCommon; @@ -18,41 +20,45 @@ internal sealed class SettingsResource : BaseResource private sealed record ModuleActions(Func Get, Action Set, Func Schema); public const string ResourceName = "settings"; + public const string AppModule = "App"; private static readonly SettingsUtils _settingsUtils = new(); - private readonly Dictionary _moduleActions; + private readonly Dictionary _moduleActions; - public SettingsResource(ModuleType module) + public string ModuleOrDefault => Module ?? AppModule; + + public SettingsResource(string? module) : base(module) { - _moduleActions = new Dictionary() + _moduleActions = new Dictionary() { - { ModuleType.AdvancedPaste, CreateModuleActions() }, - { ModuleType.AlwaysOnTop, CreateModuleActions() }, - { ModuleType.Awake, CreateModuleActions() }, - { ModuleType.ColorPicker, CreateModuleActions() }, - { ModuleType.CropAndLock, CreateModuleActions() }, - { ModuleType.EnvironmentVariables, CreateModuleActions() }, - { ModuleType.FancyZones, CreateModuleActions() }, - { ModuleType.FileLocksmith, CreateModuleActions() }, - { ModuleType.FindMyMouse, CreateModuleActions() }, - { ModuleType.Hosts, CreateModuleActions() }, - { ModuleType.ImageResizer, CreateModuleActions() }, - { ModuleType.KeyboardManager, CreateModuleActions() }, - { ModuleType.MouseHighlighter, CreateModuleActions() }, - { ModuleType.MouseJump, CreateModuleActions() }, - { ModuleType.MousePointerCrosshairs, CreateModuleActions() }, - { ModuleType.MouseWithoutBorders, CreateModuleActions() }, - { ModuleType.NewPlus, CreateModuleActions() }, - { ModuleType.Peek, CreateModuleActions() }, - { ModuleType.PowerRename, CreateModuleActions() }, - { ModuleType.PowerLauncher, CreateModuleActions() }, - { ModuleType.PowerAccent, CreateModuleActions() }, - { ModuleType.RegistryPreview, CreateModuleActions() }, - { ModuleType.MeasureTool, CreateModuleActions() }, - { ModuleType.ShortcutGuide, CreateModuleActions() }, - { ModuleType.PowerOCR, CreateModuleActions() }, - { ModuleType.Workspaces, CreateModuleActions() }, - { ModuleType.ZoomIt, CreateModuleActions() }, + { AppModule, CreateModuleActions() }, + { nameof(ModuleType.AdvancedPaste), CreateModuleActions() }, + { nameof(ModuleType.AlwaysOnTop), CreateModuleActions() }, + { nameof(ModuleType.Awake), CreateModuleActions() }, + { nameof(ModuleType.ColorPicker), CreateModuleActions() }, + { nameof(ModuleType.CropAndLock), CreateModuleActions() }, + { nameof(ModuleType.EnvironmentVariables), CreateModuleActions() }, + { nameof(ModuleType.FancyZones), CreateModuleActions() }, + { nameof(ModuleType.FileLocksmith), CreateModuleActions() }, + { nameof(ModuleType.FindMyMouse), CreateModuleActions() }, + { nameof(ModuleType.Hosts), CreateModuleActions() }, + { nameof(ModuleType.ImageResizer), CreateModuleActions() }, + { nameof(ModuleType.KeyboardManager), CreateModuleActions() }, + { nameof(ModuleType.MouseHighlighter), CreateModuleActions() }, + { nameof(ModuleType.MouseJump), CreateModuleActions() }, + { nameof(ModuleType.MousePointerCrosshairs), CreateModuleActions() }, + { nameof(ModuleType.MouseWithoutBorders), CreateModuleActions() }, + { nameof(ModuleType.NewPlus), CreateModuleActions() }, + { nameof(ModuleType.Peek), CreateModuleActions() }, + { nameof(ModuleType.PowerRename), CreateModuleActions() }, + { nameof(ModuleType.PowerLauncher), CreateModuleActions() }, + { nameof(ModuleType.PowerAccent), CreateModuleActions() }, + { nameof(ModuleType.RegistryPreview), CreateModuleActions() }, + { nameof(ModuleType.MeasureTool), CreateModuleActions() }, + { nameof(ModuleType.ShortcutGuide), CreateModuleActions() }, + { nameof(ModuleType.PowerOCR), CreateModuleActions() }, + { nameof(ModuleType.Workspaces), CreateModuleActions() }, + { nameof(ModuleType.ZoomIt), CreateModuleActions() }, }; } @@ -63,7 +69,7 @@ internal sealed class SettingsResource : BaseResource public override bool Get(string? input) { - if (_moduleActions.TryGetValue(Module, out ModuleActions? actions)) + if (_moduleActions.TryGetValue(ModuleOrDefault, out ModuleActions? actions)) { WriteJsonOutputLine(actions.Get()); return true; @@ -81,7 +87,7 @@ internal sealed class SettingsResource : BaseResource return false; } - if (_moduleActions.TryGetValue(Module, out ModuleActions? actions)) + if (_moduleActions.TryGetValue(ModuleOrDefault, out ModuleActions? actions)) { actions.Set(input!); return true; @@ -99,9 +105,9 @@ internal sealed class SettingsResource : BaseResource public override bool Schema() { - if (_moduleActions.TryGetValue(Module, out ModuleActions? actions)) + if (_moduleActions.TryGetValue(ModuleOrDefault, out ModuleActions? actions)) { - WriteJsonOutputLine(actions.Get()); + WriteJsonOutputLine(actions.Schema()); return true; } @@ -111,10 +117,38 @@ internal sealed class SettingsResource : BaseResource public override bool Manifest(string? outputDir) { - foreach (var moduleType in _moduleActions.Keys) + List<(string Name, string Manifest)> manifests = []; + if (!string.IsNullOrEmpty(Module)) { - var manifest = GenerateManifest(moduleType); - WriteJsonOutputLine(manifest); + if (!_moduleActions.ContainsKey(Module)) + { + WriteMessageOutputLine(DscMessageLevel.Error, $"Unsupported module type: {Module}"); + return false; + } + + manifests.Add((Module, GenerateManifest(Module))); + } + else + { + foreach (var module in GetSupportedModules()) + { + manifests.Add((module, GenerateManifest(module))); + } + } + + if (!string.IsNullOrEmpty(outputDir)) + { + foreach (var (name, manifest) in manifests) + { + File.WriteAllText(Path.Combine(outputDir, $"microsoft.powertoys.{name}.settings.dsc.resource.json"), manifest); + } + } + else + { + foreach (var (_, manifest) in manifests) + { + WriteJsonOutputLine(manifest); + } } return true; @@ -141,33 +175,33 @@ internal sealed class SettingsResource : BaseResource return new(GetSettings, SaveSettings, GenerateSchema>); } - private string GenerateManifest(ModuleType moduleType) + private string GenerateManifest(string module) { var manifest = new JsonObject() { ["$schema"] = "https://aka.ms/dsc/schemas/v3/bundled/resource/manifest.vscode.json", - ["description"] = $"Allows management of {moduleType} settings state via the DSC v3 command line interface protocol.", - ["type"] = "Microsoft.PowerToys/AwakeSettings", + ["description"] = $"Allows management of {module} settings state via the DSC v3 command line interface protocol.", + ["type"] = $"Microsoft.PowerToys/{module}Settings", ["version"] = "0.1.0", - ["tag"] = new JsonArray("PowerToys"), + ["tags"] = new JsonArray("PowerToys"), ["export"] = new JsonObject { - ["executable"] = "PowerToys.Dsc.exe", + ["executable"] = "PowerToys.Dsc", ["input"] = "stdin", - ["args"] = new JsonArray("export", "--module", moduleType.ToString(), "--resource", "settings"), + ["args"] = new JsonArray("export", "--module", module, "--resource", "settings"), }, ["get"] = new JsonObject { - ["executable"] = "PowerToys.Dsc.exe", + ["executable"] = "PowerToys.Dsc", ["input"] = "stdin", - ["args"] = new JsonArray("get", "--module", moduleType.ToString(), "--resource", "settings"), + ["args"] = new JsonArray("get", "--module", module, "--resource", "settings"), }, ["set"] = new JsonObject { - ["executable"] = "PowerToys.Dsc.exe", + ["executable"] = "PowerToys.Dsc", ["implementsPretest"] = false, ["return"] = "stateAndDiff", - ["args"] = new JsonArray("set", "--module", moduleType.ToString(), "--resource", "settings", new JsonObject + ["args"] = new JsonArray("set", "--module", module, "--resource", "settings", new JsonObject { ["jsonInputArg"] = "--input", ["mandatory"] = true, @@ -177,12 +211,17 @@ internal sealed class SettingsResource : BaseResource { ["command"] = new JsonObject { - ["executable"] = "PowerToys.Dsc.exe", - ["args"] = new JsonArray("schema", "--module", moduleType.ToString(), "--resource", "settings"), + ["executable"] = "PowerToys.Dsc", + ["args"] = new JsonArray("schema", "--module", module, "--resource", "settings"), }, }, }; - return manifest.ToJsonString(new() { WriteIndented = false }); + return manifest.ToJsonString(new() { WriteIndented = true }); + } + + public override IList GetSupportedModules() + { + return [.. _moduleActions.Keys.Order()]; } }