winget-cli

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | README | LICENSE

ConfigurationUnitInternal.cs (10143B)


      1 // -----------------------------------------------------------------------------
      2 // <copyright file="ConfigurationUnitInternal.cs" company="Microsoft Corporation">
      3 //     Copyright (c) Microsoft Corporation. Licensed under the MIT License.
      4 // </copyright>
      5 // -----------------------------------------------------------------------------
      6 
      7 namespace Microsoft.Management.Configuration.Processor.Helpers
      8 {
      9     using System;
     10     using System.Collections.Generic;
     11     using System.Diagnostics.CodeAnalysis;
     12     using System.IO;
     13     using Microsoft.Management.Configuration.Processor.Constants;
     14     using Microsoft.Management.Configuration.Processor.Exceptions;
     15     using Windows.Foundation.Collections;
     16 
     17     /// <summary>
     18     /// Wrapper around Configuration units and its directives.
     19     /// Creates a normalized directives map for consumption.
     20     /// </summary>
     21     internal class ConfigurationUnitInternal
     22     {
     23         private const string ConfigRootVar = "${WinGetConfigRoot}";
     24 
     25         private readonly string? configurationFileRootPath = null;
     26         private readonly Dictionary<string, object> normalizedDirectives = new ();
     27 
     28         /// <summary>
     29         /// Initializes a new instance of the <see cref="ConfigurationUnitInternal"/> class.
     30         /// </summary>
     31         /// <param name="unit">Configuration unit.</param>
     32         /// <param name="configurationFilePath">The configuration file path.</param>
     33         public ConfigurationUnitInternal(
     34             ConfigurationUnit unit,
     35             string? configurationFilePath)
     36         {
     37             this.Unit = unit;
     38             this.InitializeDirectives();
     39             this.InitializeNames();
     40 
     41             if (!string.IsNullOrEmpty(configurationFilePath))
     42             {
     43                 if (!File.Exists(configurationFilePath))
     44                 {
     45                     throw new FileNotFoundException(configurationFilePath);
     46                 }
     47 
     48                 this.configurationFileRootPath = Path.GetDirectoryName(configurationFilePath);
     49             }
     50         }
     51 
     52         /// <summary>
     53         /// Gets the configuration unit.
     54         /// </summary>
     55         public ConfigurationUnit Unit { get; }
     56 
     57         /// <summary>
     58         /// Gets a value indicating whether the unit type should be treated as the resource name.
     59         /// </summary>
     60         public bool UnitTypeIsResourceName { get; init; } = false;
     61 
     62         /// <summary>
     63         /// Gets the resource name *only*. For example, "Resource".
     64         /// </summary>
     65         public string ResourceName { get; private set; }
     66 
     67         /// <summary>
     68         /// Gets the qualified name, which includes the module. For example, "Module/Resource".
     69         /// </summary>
     70         public string QualifiedName { get; private set; }
     71 
     72         /// <summary>
     73         /// Gets the directive value from the unit taking into account the directives overlay.
     74         /// </summary>
     75         /// <param name="directiveName">Directive name.</param>
     76         /// <returns>Value of directive, null if not found.</returns>
     77         /// <typeparam name="TType">Directive type value.</typeparam>
     78         public TType? GetDirective<TType>(string directiveName)
     79             where TType : class
     80         {
     81             var normalizedDirectiveName = StringHelpers.Normalize(directiveName);
     82             if (this.normalizedDirectives.TryGetValue(normalizedDirectiveName, out object? value))
     83             {
     84                 return value as TType;
     85             }
     86 
     87             return null;
     88         }
     89 
     90         /// <summary>
     91         /// Gets the bool value of a directive from the unit taking into account the directives overlay.
     92         /// </summary>
     93         /// <param name="directiveName">Directive name.</param>
     94         /// <returns>Value of directive, false if not found.</returns>
     95         public bool? GetDirective(string directiveName)
     96         {
     97             var normalizedDirectiveName = StringHelpers.Normalize(directiveName);
     98             if (this.normalizedDirectives.TryGetValue(normalizedDirectiveName, out object? value))
     99             {
    100                 return value as bool?;
    101             }
    102 
    103             return null;
    104         }
    105 
    106         /// <summary>
    107         /// Gets the semantic version, if any.
    108         /// </summary>
    109         /// <returns>SemanticVersion, null if not specified.</returns>
    110         public SemanticVersion? GetSemanticVersion()
    111         {
    112             string? semanticVersion = this.GetDirective<string>(DirectiveConstants.Version);
    113             if (!string.IsNullOrWhiteSpace(semanticVersion))
    114             {
    115                 return new SemanticVersion(semanticVersion);
    116             }
    117 
    118             return null;
    119         }
    120 
    121         /// <summary>
    122         /// Gets the semantic min version, if any.
    123         /// </summary>
    124         /// <returns>SemanticVersion, null if not specified.</returns>
    125         public SemanticVersion? GetSemanticMinVersion()
    126         {
    127             string? semanticVersion = this.GetDirective<string>(DirectiveConstants.MinVersion);
    128             if (!string.IsNullOrWhiteSpace(semanticVersion))
    129             {
    130                 return new SemanticVersion(semanticVersion);
    131             }
    132 
    133             return null;
    134         }
    135 
    136         /// <summary>
    137         /// Gets the semantic max version, if any.
    138         /// </summary>
    139         /// <returns>SemanticVersion, null if not specified.</returns>
    140         public SemanticVersion? GetSemanticMaxVersion()
    141         {
    142             string? semanticVersion = this.GetDirective<string>(DirectiveConstants.MaxVersion);
    143             if (!string.IsNullOrWhiteSpace(semanticVersion))
    144             {
    145                 return new SemanticVersion(semanticVersion);
    146             }
    147 
    148             return null;
    149         }
    150 
    151         /// <summary>
    152         /// TODO: Implement for more variables.
    153         /// I am so sad because rs.SessionStateProxy.InvokeCommand.ExpandString doesn't work as I wanted.
    154         /// PowerShell assumes all code passed to ExpandString is trusted and we cannot assume that.
    155         /// </summary>
    156         /// <returns>ValueSet with settings.</returns>
    157         public ValueSet GetExpandedSettings()
    158         {
    159             var valueSet = new ValueSet();
    160             foreach (var value in this.Unit.Settings)
    161             {
    162                 if (value.Value is string)
    163                 {
    164                     // For now, we just expand config root.
    165                     valueSet.Add(value.Key, this.ExpandConfigRoot(value.Value as string, value.Key));
    166                 }
    167                 else
    168                 {
    169                     valueSet.Add(value);
    170                 }
    171             }
    172 
    173             return valueSet;
    174         }
    175 
    176         private string? ExpandConfigRoot(string? value, string settingName)
    177         {
    178             if (!string.IsNullOrEmpty(value))
    179             {
    180                 // TODO: since we only support one variable, this only finds and replace
    181                 // ${WingetConfigRoot} if found in the string when the work of expanding
    182                 // string is done it should take into account other operators like the subexpression operator $()
    183                 if (value.Contains(ConfigRootVar, StringComparison.OrdinalIgnoreCase))
    184                 {
    185                     if (string.IsNullOrEmpty(this.configurationFileRootPath))
    186                     {
    187                         throw new UnitSettingConfigRootException(this.QualifiedName, settingName);
    188                     }
    189 
    190                     return value.Replace(ConfigRootVar, this.configurationFileRootPath, StringComparison.OrdinalIgnoreCase);
    191                 }
    192             }
    193 
    194             return value;
    195         }
    196 
    197         private void InitializeDirectives()
    198         {
    199             foreach (var directive in this.Unit.Metadata)
    200             {
    201                 var normalizedKey = StringHelpers.Normalize(directive.Key);
    202                 this.normalizedDirectives.Add(normalizedKey, directive.Value);
    203             }
    204         }
    205 
    206         private string ConstructQualifiedName(string? moduleName)
    207         {
    208             return $"{(moduleName == null ? string.Empty : $"{moduleName}/")}{this.ResourceName}";
    209         }
    210 
    211         [MemberNotNull(nameof(ResourceName), nameof(QualifiedName))]
    212         private void InitializeNames()
    213         {
    214             // Determine ResourceName, QualifiedName, and the module directive
    215             string unitType = this.Unit.Type;
    216             string? moduleDirective = this.GetDirective<string>(DirectiveConstants.Module);
    217 
    218             if (this.UnitTypeIsResourceName)
    219             {
    220                 this.ResourceName = unitType;
    221                 this.QualifiedName = this.ConstructQualifiedName(moduleDirective);
    222                 return;
    223             }
    224 
    225             int unitTypeDividerPosition = unitType.IndexOf('/');
    226 
    227             if (unitTypeDividerPosition == unitType.Length - 1)
    228             {
    229                 throw new ArgumentException($"Invalid unit Type: {unitType}");
    230             }
    231 
    232             string? moduleName;
    233 
    234             if (unitTypeDividerPosition == -1)
    235             {
    236                 moduleName = moduleDirective;
    237                 this.ResourceName = unitType;
    238                 this.QualifiedName = this.ConstructQualifiedName(moduleDirective);
    239             }
    240             else
    241             {
    242                 moduleName = unitType.Substring(0, unitTypeDividerPosition);
    243                 this.ResourceName = unitType.Substring(unitTypeDividerPosition + 1);
    244                 this.QualifiedName = unitType;
    245             }
    246 
    247             if (moduleName != null)
    248             {
    249                 if (moduleDirective != null)
    250                 {
    251                     if (moduleName != moduleDirective)
    252                     {
    253                         throw new ArgumentException($"Mismatched module specifiers: {moduleName} != {moduleDirective}");
    254                     }
    255                 }
    256                 else
    257                 {
    258                     this.normalizedDirectives.Add(DirectiveConstants.Module, moduleName);
    259                 }
    260             }
    261         }
    262     }
    263 }