// Copyright Keysight Technologies 2012-2019 // This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this // file, you can obtain one at http://mozilla.org/MPL/2.0/. using System; using System.Collections.Generic; using System.Linq; namespace OpenTap { /// /// Converts a macro to its expanded string format. /// class MacroExpansion { /// /// Name of macro. /// public string MacroName { get; set; } /// /// Description read from attribute. /// public string Description { get; set; } /// /// String to identify where the macro comes from. /// public string Origin { get; set; } } /// a string that can be expanded with macros. public class MacroString { /// Optional context for macro strings that refers to a test step. This is used to find additional macro definitions such as TestPlanDir. public ITestStepParent Context { get; set; } string text = ""; /// The text that can be expanded. public string Text { get { return text; } set { if (value == null) throw new ArgumentNullException("value"); if (text == value) return; text = value; } } /// Creates a new instance of MacroString with a context. /// public MacroString(ITestStepParent context) { Context = context; } /// Creates a new MacroString without a context. If a TestStep is used use that as context to get access to TestPlan related macros. public MacroString() { } /// Expands the text. Macros are harvested from the optional TestPlanRun or the test step. /// A place to find additional metadata for macro expansion. /// If no date was found in the metadata, this date will be used. If date is not supplied, DateTime.Now will be used. /// If no TestPlanDir was found in the metata, this TestPlanDir will be used. /// The expanded string. public string Expand(TestPlanRun run = null, DateTime? date = null, string testPlanDir = null) { return Expand(run, date, testPlanDir, null); } /// Expands the text. Macros are harvested from the optional TestPlanRun or the test step. /// A place to find additional metadata for macro expansion. /// If no date was found in the metadata, this date will be used. If date is not supplied, DateTime.Now will be used. /// If no TestPlanDir was found in the metata, this TestPlanDir will be used. /// Overrides other macro parameters. /// The expanded string. public string Expand(TestPlanRun run, DateTime? date, string testPlanDir, Dictionary replacements) { ITestStepParent context = Context; IEnumerable<(string, object)> getMacro() { // note: macros are case-insensitive. if(testPlanDir != null) yield return ("TestPlanDir", testPlanDir); if (date != null) yield return ("date", date); if(replacements != null) foreach (var elem in replacements) yield return (elem.Key, elem.Value); if (run != null) { var runparams = run.Parameters.Concat(ResultParameters.GetMetadataFromObject(run)).Where(y => y.IsMetaData); foreach(var v in runparams) { var path = v.Value; yield return (v.Name, path); yield return (v.MacroName, path); } } ITestStepParent ctx = context; while (ctx != null) { var p = ResultParameters.GetMetadataFromObject(ctx); foreach (var v in p) { if (v.IsMetaData == false) continue; var path = v.Value; yield return (v.Name, path); yield return (v.MacroName, path); } ctx = ctx.Parent; } yield return ("date", DateTime.Now); yield return ("Verdict", Verdict.NotSet); var met = ResultParameters.GetComponentSettingsMetadataLazy(false); foreach (var ps in met) { foreach (var v in ps) { if (v.IsMetaData == false) continue; var path = v.Value; yield return (v.Name, path); yield return (v.MacroName, path); } } } return ReplaceMacros(Text, getMacro().Select(x => (x.Item1, StringConvertProvider.GetString(x.Item2)))); } /// Expands the text. public override string ToString() { return Expand(date: DateTime.Now); } /// Implicit to string conversion that expands the text of the macroString. This makes it possible to seamlessly switch between string and MacroString in implementation. public static implicit operator string(MacroString macroString) { return macroString.Expand(); } /// /// Replaces macro strings with the strings in the macroDef dictionary. /// If the macro name does not exist in the expanders dictionary. /// /// The string to replace macros in. /// The macro definitions. /// Default value if MacroName is not in macroDef. /// A string with macros expanded. internal static string ReplaceMacros(string userString, IEnumerable<(string,string)> keyvaluepairs, string macroDefault = "TBD") { var cmp = StringComparer.OrdinalIgnoreCase; Dictionary cache = new Dictionary(cmp); IEnumerator<(string, string)> valueiterator = keyvaluepairs.GetEnumerator(); string getMacroDef(string key) { if (cache.TryGetValue(key, out string val)) return val; while(valueiterator != null) { if (valueiterator.MoveNext() == false) return macroDefault; var kv = valueiterator.Current; if (kv.Item1 == null) continue; if (cache.ContainsKey(kv.Item1)) continue; cache[kv.Item1] = kv.Item2; if (cmp.Compare(kv.Item1, key) == 0) return kv.Item2; } return macroDefault; } List macroLocations = macroLocation.GetMacroLocations(userString); int removed = 0; foreach (var mloc in macroLocations) { int taglen = mloc.MacroTagLength; string inserted = getMacroDef(mloc.MacroName); userString = userString.Remove(mloc.MacroTagBegin - removed, mloc.MacroTagLength); userString = userString.Insert(mloc.MacroTagBegin - removed, inserted); removed += taglen - inserted.Length; } if(userString.Contains('%')) userString = Environment.ExpandEnvironmentVariables(userString); return userString; } /// /// To keep track of a macro in the user string. /// class macroLocation { /// /// Name of the macro. /// public readonly string MacroName; /// /// Index of the first character of the macro. /// public readonly int MacroBegin; /// /// The location of the first macro delimiter. /// public int MacroTagBegin { get { return MacroBegin - 1; } } /// /// Location of the last macro delimiter. /// public readonly int MacroEnd; public int MacroTagEnd { get { return MacroEnd + 1; } } /// /// Length from delimiter to delimiter. /// public int MacroTagLength { get { return MacroTagEnd - MacroTagBegin + 1; } } public macroLocation(string macroName, int macroStart, int macroStop) { MacroName = macroName; MacroBegin = macroStart; MacroEnd = macroStop; } /// /// Extract macro information from a string. /// /// /// public static List GetMacroLocations(string suppliedString) { List locations = new List(); // search for things delimited by '<' and '>'. for(int i = 0; i < suppliedString.Length; i++) { if(suppliedString[i] == '<') { i += 1; // skip to first char. int start = i; for (; i < suppliedString.Length; i++) { if(suppliedString[i] == '>') { int stop = i - 1; // last char thats not a '>'. locations.Add(new macroLocation(suppliedString.Substring(start, stop - start + 1).Trim(), start, stop)); break; } } } } return locations; } } } }