NetscootShared/Common/StoredPath.ps1

# Netscoot.StoredPath: a relative path as written inside a file, rewritten in place when its target or its file moves.
# Add-Type in C# 5 so every engine module can name the type and Windows PowerShell 5.1 can compile it.

if (-not ('Netscoot.StoredPath' -as [type])) {
    Add-Type -TypeDefinition @'
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
 
namespace Netscoot
{
    public abstract class StoredPath
    {
        public string File { get; private set; }
        public string Raw { get; private set; }
        public string Target { get; private set; }
 
        protected StoredPath(string file, string raw, string target)
        {
            File = file;
            Raw = raw;
            Target = target;
        }
 
        // An attribute value in an MSBuild file, e.g. ProjectReference Include or Import Project.
        public static StoredPath InAttribute(string file, string attribute, string raw, string target)
        {
            return new AttributePath(file, attribute, raw, target, Path.DirectorySeparatorChar);
        }
 
        // A Project Path attribute in a .slnx, which always uses forward slashes.
        public static StoredPath InSlnxEntry(string file, string raw, string target)
        {
            return new AttributePath(file, "Path", raw, target, '/');
        }
 
        // A quoted project path on a .sln Project(...) line.
        public static StoredPath InSolutionEntry(string file, string raw, string target)
        {
            return new SolutionEntryPath(file, raw, target);
        }
 
        // The path string of a PowerShell dot-source, call or Import-Module.
        public static StoredPath InScript(string file, string raw, string target)
        {
            return new ScriptPath(file, raw, target);
        }
 
        public string RawPointingAt(string target)
        {
            return Format(RelativePath(Path.GetDirectoryName(File), target));
        }
 
        public string RawFollowing(string newFile)
        {
            return Format(RelativePath(Path.GetDirectoryName(newFile), Target));
        }
 
        public bool PointAt(string target)
        {
            return Rewrite(File, RawPointingAt(target));
        }
 
        public bool FollowFile(string newFile)
        {
            return Rewrite(newFile, RawFollowing(newFile));
        }
 
        protected abstract string Format(string relative);
 
        protected abstract string Token(string raw, char quote);
 
        protected virtual char[] Quotes
        {
            get { return new char[] { '"' }; }
        }
 
        protected virtual char DefaultSeparator
        {
            get { return Path.DirectorySeparatorChar; }
        }
 
        protected string Styled(string path)
        {
            bool slash = Raw.IndexOf('/') >= 0;
            bool backslash = Raw.IndexOf('\\') >= 0;
            if (slash && !backslash) { return path.Replace('\\', '/'); }
            if (backslash && !slash) { return path.Replace('/', '\\'); }
            if (!slash && !backslash) { return path.Replace('\\', DefaultSeparator).Replace('/', DefaultSeparator); }
            return path;
        }
 
        private bool Rewrite(string file, string newRaw)
        {
            byte[] bytes = System.IO.File.ReadAllBytes(file);
            bool bom = bytes.Length >= 3 && bytes[0] == 0xEF && bytes[1] == 0xBB && bytes[2] == 0xBF;
            string text = System.IO.File.ReadAllText(file);
            bool changed = false;
            foreach (char q in Quotes)
            {
                string oldToken = Token(Raw, q);
                if (text.IndexOf(oldToken, StringComparison.Ordinal) >= 0)
                {
                    text = text.Replace(oldToken, Token(newRaw, q));
                    changed = true;
                }
            }
            if (changed) { System.IO.File.WriteAllText(file, text, new UTF8Encoding(bom)); }
            return changed;
        }
 
        private static string RelativePath(string fromDir, string to)
        {
            char sep = Path.DirectorySeparatorChar;
            StringComparison cmp = sep == '\\' ? StringComparison.OrdinalIgnoreCase : StringComparison.Ordinal;
            char[] seps = new char[] { '\\', '/' };
            string[] from = fromDir.Split(seps, StringSplitOptions.RemoveEmptyEntries);
            string[] dest = to.Split(seps, StringSplitOptions.RemoveEmptyEntries);
            int common = 0;
            while (common < from.Length && common < dest.Length && string.Equals(from[common], dest[common], cmp)) { common++; }
            if (common == 0 && sep == '\\') { return to; }
            List<string> parts = new List<string>();
            for (int i = common; i < from.Length; i++) { parts.Add(".."); }
            for (int i = common; i < dest.Length; i++) { parts.Add(dest[i]); }
            if (parts.Count == 0) { return "."; }
            return string.Join(sep.ToString(), parts.ToArray());
        }
    }
 
    public sealed class AttributePath : StoredPath
    {
        private const string ThisFileDirectory = "$(MSBuildThisFileDirectory)";
 
        private readonly char defaultSeparator;
 
        public string Attribute { get; private set; }
 
        internal AttributePath(string file, string attribute, string raw, string target, char separator) : base(file, raw, target)
        {
            Attribute = attribute;
            defaultSeparator = separator;
        }
 
        protected override char DefaultSeparator
        {
            get { return defaultSeparator; }
        }
 
        protected override char[] Quotes
        {
            get { return new char[] { '"', '\'' }; }
        }
 
        protected override string Format(string relative)
        {
            string styled = Styled(relative);
            if (Raw.StartsWith(ThisFileDirectory, StringComparison.OrdinalIgnoreCase)) { return ThisFileDirectory + styled; }
            return styled;
        }
 
        protected override string Token(string raw, char quote)
        {
            return Attribute + "=" + quote + raw + quote;
        }
    }
 
    public sealed class SolutionEntryPath : StoredPath
    {
        internal SolutionEntryPath(string file, string raw, string target) : base(file, raw, target) { }
 
        protected override char DefaultSeparator
        {
            get { return '\\'; }
        }
 
        protected override string Format(string relative)
        {
            return Styled(relative);
        }
 
        protected override string Token(string raw, char quote)
        {
            return quote + raw + quote;
        }
    }
 
    public sealed class ScriptPath : StoredPath
    {
        private const string ScriptRoot = "$PSScriptRoot";
 
        internal ScriptPath(string file, string raw, string target) : base(file, raw, target) { }
 
        protected override string Format(string relative)
        {
            string styled = Styled(relative);
            string sep = Styled(Path.DirectorySeparatorChar.ToString());
            if (Raw.StartsWith(ScriptRoot, StringComparison.OrdinalIgnoreCase)) { return ScriptRoot + sep + styled; }
            if (styled.StartsWith("." + sep) || styled.StartsWith(".." + sep)) { return styled; }
            return "." + sep + styled;
        }
 
        protected override string Token(string raw, char quote)
        {
            return raw;
        }
    }
}
'@

}