/ src / settings-ui / Settings.UI.Library / Interfaces / ICmdLineRepresentable.cs
ICmdLineRepresentable.cs
  1  // Copyright (c) Microsoft Corporation
  2  // The Microsoft Corporation licenses this file to you under the MIT license.
  3  // See the LICENSE file in the project root for more information.
  4  
  5  using System;
  6  using System.Globalization;
  7  using System.Reflection;
  8  
  9  namespace Microsoft.PowerToys.Settings.UI.Library;
 10  
 11  /// <summary>
 12  /// A helper interface to allow parsing property values from their command line representation.
 13  /// </summary>
 14  public interface ICmdLineRepresentable
 15  {
 16      public static abstract bool TryParseFromCmd(string cmd, out object result);
 17  
 18      public abstract bool TryToCmdRepresentable(out string result);
 19  
 20      public static sealed bool TryToCmdRepresentableFor(Type type, object value, out string result)
 21      {
 22          result = null;
 23          if (!typeof(ICmdLineRepresentable).IsAssignableFrom(type))
 24          {
 25              throw new ArgumentException($"{type} doesn't implement {nameof(ICmdLineRepresentable)}");
 26          }
 27  
 28          var method = type.GetMethod(nameof(TryToCmdRepresentable));
 29          var parameters = new object[] { result };
 30          if ((bool)method.Invoke(value, parameters))
 31          {
 32              result = (string)parameters[0];
 33              return true;
 34          }
 35  
 36          return false;
 37      }
 38  
 39      public static sealed bool TryParseFromCmdFor(Type type, string cmd, out object result)
 40      {
 41          result = null;
 42          if (!typeof(ICmdLineRepresentable).IsAssignableFrom(type))
 43          {
 44              throw new ArgumentException($"{type} doesn't implement {nameof(ICmdLineRepresentable)}");
 45          }
 46  
 47          var method = type.GetMethod(nameof(TryParseFromCmd), BindingFlags.Static | BindingFlags.Public);
 48          var parameters = new object[] { cmd, null };
 49          if ((bool)method.Invoke(null, parameters))
 50          {
 51              result = parameters[1];
 52              return true;
 53          }
 54  
 55          return false;
 56      }
 57  
 58      public static sealed object ParseFor(Type type, string cmdRepr)
 59      {
 60          if (type.IsEnum)
 61          {
 62              return Enum.Parse(type, cmdRepr);
 63          }
 64          else if (type.IsPrimitive)
 65          {
 66              if (type == typeof(bool))
 67              {
 68                  return bool.Parse(cmdRepr.ToLowerInvariant());
 69              }
 70              else
 71              {
 72                  // Converts numeric types like Uint32
 73                  return Convert.ChangeType(cmdRepr, type, CultureInfo.InvariantCulture);
 74              }
 75          }
 76          else if (type.IsValueType && type == typeof(DateTimeOffset))
 77          {
 78              if (DateTimeOffset.TryParse(cmdRepr, out var structResult))
 79              {
 80                  return structResult;
 81              }
 82  
 83              throw new ArgumentException($"Invalid DateTimeOffset format '{cmdRepr}'");
 84          }
 85          else if (type.IsClass)
 86          {
 87              if (type == typeof(string))
 88              {
 89                  return cmdRepr;
 90              }
 91              else
 92              {
 93                  TryParseFromCmdFor(type, cmdRepr, out var classResult);
 94                  return classResult;
 95              }
 96          }
 97  
 98          throw new NotImplementedException($"Parsing type {type} is not supported yet");
 99      }
100  
101      public static string ToCmdRepr(Type type, object value)
102      {
103          if (type.IsEnum || type.IsPrimitive)
104          {
105              return value.ToString();
106          }
107          else if (type.IsValueType && type == typeof(DateTimeOffset))
108          {
109              return ((DateTimeOffset)value).ToString("o");
110          }
111          else if (type.IsClass)
112          {
113              if (type == typeof(string))
114              {
115                  return (string)value;
116              }
117              else
118              {
119                  TryToCmdRepresentableFor(type, value, out var result);
120                  return result;
121              }
122          }
123  
124          throw new NotImplementedException($"CmdRepr of {type} is not supported yet");
125      }
126  }