// 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;
}
}
}
}