utils/inheritance/Inheritance.cs

using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text.RegularExpressions;

namespace Publishmap.Utils.Inheritance
{
    public static class Inheritance
    {
        public static void Init()
        {
            // force linq loading
            new Dictionary<string, string>().Any();
        }
        public static void AddProperties(
            IDictionary obj,
            IDictionary props,
            bool ifNotExists = false,
            bool merge = false,
            IEnumerable<object> exclude = null
            )
        {
            foreach (var key in props.Keys)
            {
                var val = props[key];
                if (exclude?.Contains(key) ?? false) continue;

                try
                {
                    AddProperty(obj, name: (string)key, value: val, ifNotExists: ifNotExists, merge: merge);
                }
                catch (Exception ex)
                {
                    throw new Exception($"failed to add property '{key}' with value '{val}': {ex.Message}");
                }
            }
        }
        public static void AddProperty(
            IDictionary obj,
            string name,
            object value,
            bool ifNotExists = false,
            bool overwrite = false,
            bool merge = false)
        {
            if (obj.Contains(name))
            {
                if (merge && obj[name] is IDictionary && value is IDictionary)
                {
                    AddProperties((IDictionary)obj[name], (IDictionary)value, ifNotExists: ifNotExists, merge: merge);
                    // $r = add-properties $object.$name $value -ifNotExists:$ifNotExists -merge:$merge
                    // return $object
                }
                else if (ifNotExists)
                {
                    return;
                }
                else if (overwrite)
                {
                    obj[name] = value;
                }
                else
                {
                    throw new ArgumentException($"property '{name}' already exists with value '{obj[name]}'");
                }
            }
            else
            {
                if (obj is IDictionary)
                {
                    obj[name] = value;
                }
                else
                {
                    throw new NotSupportedException("optimization - only support hashtabes");
                }
            }
        }

        public static void AddMetaProperties(
            IDictionary group, string fullpath, IEnumerable<object> specialKeys)
        {

            var splits = fullpath.Split('.');
            var level = splits.Length - 1;

            AddProperty(group, "_level", level);
            AddProperty(group, "_fullpath", fullpath.Trim('.'));
            if (splits.Length > 0)
            {
                AddProperty(group, "_name", splits[splits.Length - 1]);
            }

            var keys = group.Keys;

            foreach (var projk in keys)
            {
                //do not process special global settings
                if (specialKeys.Contains(projk))
                {
                    continue;
                }
                var path = $"{fullpath}.{projk}";
                if (group[projk] is IDictionary)
                {
                    AddMetaProperties((IDictionary)group[projk], path, specialKeys);
                }
            }


        }

        public static IDictionary CopyHashtable(IDictionary org)
        {
            var result = new Hashtable();
            foreach (var key in org.Keys)
            {
                if (org[key] is IDictionary)
                {
                    result[key] = CopyHashtable((IDictionary)org[key]);
                }
                else
                {
                    result[key] = org[key];
                }
            }
            return result;
        }

        public static void AddInheritedProperties(
            IDictionary from,
            IDictionary to,
            IEnumerable<object> exclude = null,
            bool valuesOnly = false
        )
        {
            if (exclude == null) exclude = new string[] { };

            var toProcess = new Dictionary<string, object>();
            foreach (string key in from.Keys)
            {
                var value = from[key];
                var shouldExclude = false;
                if (exclude.Contains(key)) shouldExclude = true;
                if (exclude.Any(e => Regex.IsMatch(key, $"^{e}$"))) shouldExclude = true;

                if (value is System.Collections.IDictionary)
                {
                    if (valuesOnly)
                    {
                        shouldExclude = true;
                    }
                    else
                    {
                        var newvalue = CopyHashtable((IDictionary)value);
                        value = newvalue;
                    }
                }
                if (!shouldExclude)
                {
                    toProcess[key] = value;
                }
            }

            if (toProcess.Any())
            {
                try
                {
                    AddProperties(to, toProcess, merge: true, ifNotExists: true);
                }
                catch (Exception ex)
                {
                    throw new Exception($@"failed to inherit properties:{ex.Message}
                        from:
                        {from}
                        to:
                        {to}");
                }
            }


            /*
                      <# foreach($key in $from.keys) {

                      $value = $from[$key]

                      if ($value -is [System.Collections.IDictionary]) {
                          if ($valuesOnly) {
                              $shouldExclude = $true
                          }
                          else {
                              $value = $value.Clone()
                          }
                       }

                      if (!$shouldExclude) {
                          add-property $to -name $key -value $value
                      }
                  }
                  #>
                  }
              }
                         */
        }

        public static void PostProcessPublishmap(IDictionary map)
        {
            var groupsToRemove = new List<string>();
            foreach (string groupk in map.Keys)
            {
                // remove generated properties from top-level
                if (groupk.StartsWith("_"))
                {
                    groupsToRemove.Add(groupk);
                    continue;
                }
                var group = (IDictionary)map[groupk];

                foreach (string projk in group.Keys)
                {
                    if (!(group[projk] is IDictionary)) continue;
                    var proj = (IDictionary)group[projk];
                    if (proj.Contains("profiles"))
                    {
                        var profiles = ((IDictionary)proj["profiles"]);
                        var profilesToRemove = new List<string>();
                        foreach (string profk in profiles.Keys)
                        {
                            var profitem = profiles[profk];

                            if (!(profitem is IDictionary))
                            {
                                // write-verbose "removing non-profile property '$groupk.$projk.$profk'"
                                // remove every property that isn't a real profile
                                profilesToRemove.Add(profk);
                                continue;
                            }
                            else
                            {
                                // write - verbose "adding post-properties to '$groupk.$projk.$profk'"
                                // set full path as if profiles were created at project level
                                var prof = (IDictionary)profitem;
                                AddProperty(prof, "_fullpath", $"{groupk}.{projk}.{profk}", overwrite: true);
                                AddProperty(prof, "_name", profk, overwrite: true);
                                // use fullpath for backward compatibility
                                AddProperty(prof, "fullpath", $"{groupk}.{projk}.{profk}", overwrite: true);
                                AddProperty(prof, "project", proj, overwrite: true);

                                if (prof.Contains("_inherit_from"))
                                {
                                    if (!profiles.Contains(prof["_inherit_from"]))
                                    {
                                        // write - warning "cannot find inheritance base '$($prof._inherit_from)' for profile '$($prof._fullpath)'"
                                    }
                                    else
                                    {
                                        var cur = prof;
                                        var hierarchy = new List<IDictionary>();
                                        while (cur.Contains("_inherit_from") && !cur.Contains("_inherited_from"))
                                        {
                                            hierarchy.Add(cur);
                                            var baseprof = (IDictionary)profiles[cur["_inherit_from"]];
                                            cur = baseprof;
                                        }
                                        for (var i = hierarchy.Count - 1; i >= 0; i--)
                                        {
                                            cur = hierarchy[i];
                                            var baseprof = (IDictionary)profiles[cur["_inherit_from"]];
                                            // write-verbose "inheriting properties from '$($cur._inherit_from)' to '$($cur._fullpath)'"
                                            AddInheritedProperties(baseprof, cur, valuesOnly: true, exclude: new[] { "_inherit_from", "_inherited_from" });
                                            AddProperty(cur, "_inherited_from", cur["_inherit_from"]);
                                        }
                                    }
                                }
                            }
                        }
                        foreach (var k in profilesToRemove) profiles.Remove(k);
                        // # expose profiles at project level
                        AddProperties(proj, profiles, merge: true, ifNotExists: true);

                    }
                    // use fullpath for backward compatibility
                    if (proj.Contains("_fullpath"))
                    {
                        AddProperty(proj, "fullpath", proj["_fullpath"], overwrite: true);
                    }
                }

                //# use fullpath for backward compatibility
                if (group.Contains("_fullpath"))
                {
                    AddProperty(group, "fullpath", group["_fullpath"], overwrite: true);
                }
            }

            foreach (var k in groupsToRemove) map.Remove(k);
        }

        /*
            $proj = $group.$projk
            if ($null -ne $proj.profiles) {
                foreach($profk in get-propertynames $proj.profiles) {
                    $prof = $proj.profiles.$profk
                    if ($prof -is [System.Collections.IDictionary]) {
                        write-verbose "adding post-properties to '$groupk.$projk.$profk'"
                        # set full path as if profiles were created at project level
                        $null = add-property $prof -name _fullpath -value "$groupk.$projk.$profk" -overwrite
                        $null = add-property $prof -name _name -value "$profk" -overwrite
                        # use fullpath for backward compatibility
                        if ($prof._fullpath -eq $null) {
                            write-warning "no fullpath property!"
                        }
                        $null = add-property $prof -name fullpath -value $prof._fullpath -overwrite
                        # expose project at profile level
                        $null = add-property $prof -name project -value $proj
                    } else {
                        #write-verbose "removing non-profile property '$groupk.$projk.$profk'"
                        #remove every property that isn't a real profile
                        $proj.profiles.Remove($profk)
                    }
                    if ($null -ne $prof._inherit_from) {
                        if ($proj.profiles.$($null -eq $prof._inherit_from)) {
                            write-warning "cannot find inheritance base '$($prof._inherit_from)' for profile '$($prof._fullpath)'"
                        } else {
                            $cur = $prof
                            $hierarchy = @()
                            while($null -ne $cur._inherit_from -and $null -eq $cur._inherited_from) {
                                $hierarchy += $cur
                                $base = $proj.profiles.$($cur._inherit_from)
                                $cur = $base
                            }
                            for($i = ($hierarchy.length - 1); $i -ge 0; $i--) {
                                $cur = @($hierarchy)[$i]
                                $base = $proj.profiles.$($cur._inherit_from)
                               # write-verbose "inheriting properties from '$($cur._inherit_from)' to '$($cur._fullpath)'"
                                inherit-properties -from $base -to $cur -valuesonly -exclude @("_inherit_from","_inherited_from")
                                $null = add-property $cur -name _inherited_from -value $($cur._inherit_from)
                            }
                        }
                    }
                }
                # expose profiles at project level
                $null = add-properties $proj $proj.profiles -merge -ifNotExists


            }
            # use fullpath for backward compatibility
            if ($proj._fullpath) {
                $null = add-property $proj -name fullpath -value $proj._fullpath -overwrite
            }
        }

        # use fullpath for backward compatibility
        if ($group._fullpath) {
            $null = add-property $group -name fullpath -value $group._fullpath -overwrite
        }

    }
    return $pmap
    }
    }
    */

        public static void AddGlobalSettings(IDictionary proj, IDictionary settings)
        {
            if (null != settings)
            {
                //write-verbose "inheriting global settings to $($proj._fullpath). strip=$stripsettingswrapper"
                if (settings.Contains("_strip"))
                {
                    var stripsettingswrapper = settings["_strip"];
                    AddInheritedProperties(settings, proj, /*ifNotExist: true, merge:true,*/ exclude: new[] { "_strip" });
                }
                else
                {
                    AddProperty(proj, "settings", settings, ifNotExists: true, merge: true);
                }
            }

        }

        /*
        function Add-GlobalSettings($proj, $settings) {
            Measure-function "$($MyInvocation.MyCommand.Name)" {

                if ($null -ne $settings) {
                    write-verbose "inheriting global settings to $($proj._fullpath). strip=$stripsettingswrapper"
                    $stripsettingswrapper = $settings._strip
                    if ($null -ne $stripsettingswrapper -and $stripsettingswrapper) {
                        $null = inherit-properties -from $settings -to $proj -ifNotExists -merge -exclude "_strip"
                    }
                    else {
                        $null = add-property $proj "settings" $settings -ifNotExists -merge
                    }
                }
            }
            */

        public static IDictionary ImportGenericGroup(IDictionary group,
            string fullpath,
            IDictionary settings = null,
            string settingskey = "settings",
            IEnumerable<object> specialkeys = null
        )
        {
            if (specialkeys == null)
            {
                specialkeys = new[] { "settings", "global_profiles" };
            }

            // Write-Verbose "processing map path $fullpath"

            var result = new Hashtable();

            //# only direct children inherit settings
            var onelevelsettingsinheritance = true;
            IDictionary childsettings = null;
            //#get settings for children
            if (group.Contains(settingskey))
            {
                childsettings = (IDictionary)group[settingskey];
            }
            else
            {
                if (!onelevelsettingsinheritance)
                {
                    childsettings = settings;
                }
            }

            object[] keys = new object[group.Keys.Count];
            group.Keys.CopyTo(keys, 0);
            foreach (string projk in keys)
            {
                //#do not process special global settings
                if (specialkeys.Contains(projk))
                {
                    continue;
                }
                var subgroup = group[projk];
                if (!(subgroup is System.Collections.IDictionary))
                {
                    continue;
                }
                var path = $"{fullpath}.{projk}";

                AddInheritedProperties(group, (IDictionary)subgroup, valuesOnly: true);
                // # this should be run only once per group, right?
                // # why is this needed here?
                if (null != settings)
                {
                    AddGlobalSettings(group, settings);
                }
                var r = ImportGenericGroup((IDictionary)subgroup, path, childsettings, settingskey, specialkeys);

                //result.Add("" r);
            }

            if (null != settings)
            {
                AddGlobalSettings(group, settings);
            }

            return group;
        }

        /*
    if ($null -ne $settings) {
        inherit-globalsettings $group $settings

        <# $keys = get-propertynames $group
    foreach($projk in $keys) {
        $subgroup = $group.$projk
        if ($projk -in $specialkeys) {
            continue
        }
        if (!($subgroup -is [System.Collections.IDictionary])) {
            continue
        }
        inherit-properties -from $group -to $subgroup -valuesonly
    }
    #>
    }



    return $map
}
} */

    }

}