Skip to content

Commit

Permalink
Color output (#2606)
Browse files Browse the repository at this point in the history
Co-authored-by: Andreas Willich <[email protected]>
  • Loading branch information
Socolin and SabotageAndi committed Jul 5, 2022
1 parent 677044f commit b590fd4
Show file tree
Hide file tree
Showing 26 changed files with 796 additions and 24 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ public Configuration.SpecFlowConfiguration LoadAppConfig(Configuration.SpecFlowC
StepDefinitionSkeletonStyle stepDefinitionSkeletonStyle = specFlowConfiguration.StepDefinitionSkeletonStyle;
List<string> additionalStepAssemblies = specFlowConfiguration.AdditionalStepAssemblies;
ObsoleteBehavior obsoleteBehavior = specFlowConfiguration.ObsoleteBehavior;
bool coloredOutput = specFlowConfiguration.ColoredOutput;

bool allowRowTests = specFlowConfiguration.AllowRowTests;
bool allowDebugGeneratedFiles = specFlowConfiguration.AllowDebugGeneratedFiles;
Expand Down Expand Up @@ -82,6 +83,7 @@ public Configuration.SpecFlowConfiguration LoadAppConfig(Configuration.SpecFlowC
traceTimings = configSection.Trace.TraceTimings;
minTracedDuration = configSection.Trace.MinTracedDuration;
stepDefinitionSkeletonStyle = configSection.Trace.StepDefinitionSkeletonStyle;
coloredOutput = configSection.Trace.ColoredOutput;
}

foreach (var element in configSection.StepAssemblies)
Expand All @@ -105,7 +107,8 @@ public Configuration.SpecFlowConfiguration LoadAppConfig(Configuration.SpecFlowC
allowDebugGeneratedFiles,
allowRowTests,
addNonParallelizableMarkerForTags,
obsoleteBehavior
obsoleteBehavior,
coloredOutput
);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,5 +40,12 @@ public StepDefinitionSkeletonStyle StepDefinitionSkeletonStyle
get { return (StepDefinitionSkeletonStyle)this["stepDefinitionSkeletonStyle"]; }
set { this["stepDefinitionSkeletonStyle"] = value; }
}

[ConfigurationProperty("coloredOutput", IsRequired = false, DefaultValue = ConfigDefaults.ColoredOutput)]
public bool ColoredOutput
{
get { return (bool)this["coloredOutput"]; }
set { this["coloredOutput"] = value; }
}
}
}
1 change: 1 addition & 0 deletions TechTalk.SpecFlow/Configuration/ConfigDefaults.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ public static class ConfigDefaults
public const string MinTracedDuration = "0:0:0.1";
public const StepDefinitionSkeletonStyle StepDefinitionSkeletonStyle = TechTalk.SpecFlow.BindingSkeletons.StepDefinitionSkeletonStyle.RegexAttribute;
public const ObsoleteBehavior ObsoleteBehavior = Configuration.ObsoleteBehavior.Warn;
public const bool ColoredOutput = false;

public const bool AllowDebugGeneratedFiles = false;
public const bool AllowRowTests = true;
Expand Down
5 changes: 4 additions & 1 deletion TechTalk.SpecFlow/Configuration/ConfigurationLoader.cs
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,8 @@ public ConfigurationLoader(ISpecFlowJsonLocator specFlowJsonLocator)

public static ObsoleteBehavior DefaultObsoleteBehavior => ConfigDefaults.ObsoleteBehavior;

public static bool DefaultColoredOutput => ConfigDefaults.ColoredOutput;

public bool HasAppConfig => ConfigurationManager.GetSection("specFlow") != null;

public bool HasJsonConfig
Expand Down Expand Up @@ -129,7 +131,8 @@ public static SpecFlowConfiguration GetDefault()
DefaultAllowDebugGeneratedFiles,
DefaultAllowRowTests,
DefaultAddNonParallelizableMarkerForTags,
DefaultObsoleteBehavior
DefaultObsoleteBehavior,
DefaultColoredOutput
);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ public SpecFlowConfiguration LoadJson(SpecFlowConfiguration specFlowConfiguratio
bool traceSuccessfulSteps = specFlowConfiguration.TraceSuccessfulSteps;
bool traceTimings = specFlowConfiguration.TraceTimings;
var minTracedDuration = specFlowConfiguration.MinTracedDuration;
bool coloredOutput = specFlowConfiguration.ColoredOutput;
var stepDefinitionSkeletonStyle = specFlowConfiguration.StepDefinitionSkeletonStyle;
var additionalStepAssemblies = specFlowConfiguration.AdditionalStepAssemblies;
bool allowRowTests = specFlowConfiguration.AllowRowTests;
Expand Down Expand Up @@ -75,6 +76,7 @@ public SpecFlowConfiguration LoadJson(SpecFlowConfiguration specFlowConfiguratio
traceTimings = jsonConfig.Trace.TraceTimings;
minTracedDuration = jsonConfig.Trace.MinTracedDuration;
stepDefinitionSkeletonStyle = jsonConfig.Trace.StepDefinitionSkeletonStyle;
coloredOutput = jsonConfig.Trace.ColoredOutput;
}

if (jsonConfig.StepAssemblies != null)
Expand All @@ -101,7 +103,8 @@ public SpecFlowConfiguration LoadJson(SpecFlowConfiguration specFlowConfiguratio
allowDebugGeneratedFiles,
allowRowTests,
addNonParallelizableMarkerForTags,
obsoleteBehavior
obsoleteBehavior,
coloredOutput
);
}
}
Expand Down
5 changes: 5 additions & 0 deletions TechTalk.SpecFlow/Configuration/JsonConfig/TraceElement.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,11 @@ public class TraceElement
[DefaultValue(ConfigDefaults.StepDefinitionSkeletonStyle)]
public StepDefinitionSkeletonStyle StepDefinitionSkeletonStyle { get; set; }

//[JsonProperty("ColoredOutput", DefaultValueHandling = DefaultValueHandling.Populate, NullValueHandling = NullValueHandling.Ignore)]
[DataMember(Name = "ColoredOutput")]
[DefaultValue(ConfigDefaults.ColoredOutput)]
public bool ColoredOutput { get; set; }

//[JsonProperty("traceSuccessfulSteps", DefaultValueHandling = DefaultValueHandling.Populate, NullValueHandling = NullValueHandling.Ignore)]
[DataMember(Name = "traceSuccessfulSteps")]
[DefaultValue(ConfigDefaults.TraceSuccessfulSteps)]
Expand Down
6 changes: 5 additions & 1 deletion TechTalk.SpecFlow/Configuration/SpecFlowConfiguration.cs
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,9 @@ public SpecFlowConfiguration(ConfigSource configSource,
bool allowDebugGeneratedFiles,
bool allowRowTests,
string[] addNonParallelizableMarkerForTags,
ObsoleteBehavior obsoleteBehavior)
ObsoleteBehavior obsoleteBehavior,
bool coloredOutput
)
{
ConfigSource = configSource;
CustomDependencies = customDependencies;
Expand All @@ -49,6 +51,7 @@ public SpecFlowConfiguration(ConfigSource configSource,
AllowRowTests = allowRowTests;
AddNonParallelizableMarkerForTags = addNonParallelizableMarkerForTags;
ObsoleteBehavior = obsoleteBehavior;
ColoredOutput = coloredOutput;
}

public ConfigSource ConfigSource { get; set; }
Expand All @@ -71,6 +74,7 @@ public SpecFlowConfiguration(ConfigSource configSource,
public string[] AddNonParallelizableMarkerForTags { get; set; }

//tracing settings
public bool ColoredOutput { get; set; }
public bool TraceSuccessfulSteps { get; set; }
public bool TraceTimings { get; set; }
public TimeSpan MinTracedDuration { get; set; }
Expand Down
2 changes: 2 additions & 0 deletions TechTalk.SpecFlow/Infrastructure/DefaultDependencyProvider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ public virtual void RegisterGlobalContainerDefaults(ObjectContainer container)

container.RegisterTypeAs<StepFormatter, IStepFormatter>();
container.RegisterTypeAs<TestTracer, ITestTracer>();
container.RegisterTypeAs<ColorOutputTheme, IColorOutputTheme>();
container.RegisterTypeAs<ColorOutputHelper, IColorOutputHelper>();

container.RegisterTypeAs<DefaultListener, ITraceListener>();
container.RegisterTypeAs<TraceListenerQueue, ITraceListenerQueue>();
Expand Down
8 changes: 4 additions & 4 deletions TechTalk.SpecFlow/TechTalk.SpecFlow.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -69,14 +69,14 @@
</PropertyGroup>

<ItemGroup>
<Compile Remove="Analytics\AppInsights\AppInsightsInstrumentationKey.template.cs" />
<None Include="Analytics\AppInsights\AppInsightsInstrumentationKey.template.cs" />
<Compile Remove="Analytics/AppInsights/AppInsightsInstrumentationKey.template.cs" />
<None Include="Analytics/AppInsights/AppInsightsInstrumentationKey.template.cs" />
</ItemGroup>

<Target Name="RunTokenReplace" AfterTargets="GetBuildVersion" BeforeTargets="BeforeCompile" Condition="$(DesignTimeBuild) != 'true' OR '$(BuildingProject)' == 'true'">
<ReplaceTextInFileTask InputFile="$(SolutionDir)TechTalk.SpecFlow\Analytics\AppInsights\AppInsightsInstrumentationKey.template.cs" OutputFile="$(SolutionDir)TechTalk.SpecFlow\Analytics\AppInsights\AppInsightsInstrumentationKey.cs" TextToReplace="&lt;InstrumentationKeyGoesHere&gt;" TextToReplaceWith="$(AppInsightsInstrumentationKey)" WriteOnlyWhenChanged="true" />
<ReplaceTextInFileTask InputFile="$(ProjectDir)Analytics/AppInsights/AppInsightsInstrumentationKey.template.cs" OutputFile="$(ProjectDir)Analytics/AppInsights/AppInsightsInstrumentationKey.cs" TextToReplace="&lt;InstrumentationKeyGoesHere&gt;" TextToReplaceWith="$(AppInsightsInstrumentationKey)" WriteOnlyWhenChanged="true" />
<ItemGroup>
<Compile Include="$(SolutionDir)TechTalk.SpecFlow\Analytics\AppInsights\AppInsightsInstrumentationKey.cs" />
<Compile Include="$(ProjectDir)Analytics/AppInsights/AppInsightsInstrumentationKey.cs" />
</ItemGroup>
</Target>

Expand Down
191 changes: 191 additions & 0 deletions TechTalk.SpecFlow/Tracing/AnsiColor/AnsiColor.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,191 @@
#nullable enable
using System.Text;

namespace TechTalk.SpecFlow.Tracing.AnsiColor;

public readonly struct AnsiColor
{
private const char EscapeCode = '\x1b';

public AnsiColor[]? Codes { get; } = null;

public static AnsiColor Reset = new(TerminalControlSequences.Reset);
public static AnsiColor Bold = new(TerminalControlSequences.Bold);
public static AnsiColor Italic = new(TerminalControlSequences.Italic);
public static AnsiColor Underline = new(TerminalControlSequences.Underline);
public static AnsiColor Strike = new(TerminalControlSequences.Strike);

public static AnsiColor Foreground(Terminal256ColorCodes color) => new(TerminalControlSequences.SetForegroundColor, color);
public static AnsiColor Foreground(TerminalRgbColor color) => new(TerminalControlSequences.SetForegroundColor, color);
public static AnsiColor Foreground(int r, int g, int b) => new(TerminalControlSequences.SetForegroundColor, new TerminalRgbColor(r, g, b));
public static AnsiColor Background(Terminal256ColorCodes color) => new(TerminalControlSequences.SetBackgroundColor, color);
public static AnsiColor Background(TerminalRgbColor color) => new(TerminalControlSequences.SetBackgroundColor, color);
public static AnsiColor Background(int r, int g, int b) => new(TerminalControlSequences.SetBackgroundColor, new TerminalRgbColor(r, g, b));
public static AnsiColor Composite(params AnsiColor[] codes) => new(codes);

#if NET3_0_OR_GREATER
[return: System.Diagnostics.CodeAnalysis.NotNullIfNotNull("text")]
#endif
public static string? ColorizeText(string? text, AnsiColor code)
{
if (text == null) return null;
var sb = new StringBuilder();
code.ToEscapeSequence(sb);
sb.Append(text);
code.ToResetSequence(sb);
return sb.ToString();
}

public readonly TerminalControlSequences ControlSequence = TerminalControlSequences.None;
public readonly TerminalControlSequences ResetSequence = TerminalControlSequences.Reset;
public readonly Terminal256ColorCodes ColorCode256 = Terminal256ColorCodes.None;
public readonly TerminalRgbColor RgbColorCode = TerminalRgbColor.None;

public AnsiColor(TerminalControlSequences controlSequence)
{
ControlSequence = controlSequence;
switch (ControlSequence)
{
case TerminalControlSequences.SetForegroundColor:
case TerminalControlSequences.SetBackgroundColor:
throw new InvalidColorException($"{ControlSequence} requires a color parameter");
}

ResetSequence = GetResetSequence(controlSequence);
}

public AnsiColor(TerminalControlSequences controlSequence, Terminal256ColorCodes colorCode256)
{
ControlSequence = controlSequence;
ColorCode256 = colorCode256;
ResetSequence = GetResetSequence(controlSequence);
}

public AnsiColor(TerminalControlSequences controlSequence, TerminalRgbColor rgbColor)
{
ControlSequence = controlSequence;
RgbColorCode = rgbColor;
ResetSequence = GetResetSequence(controlSequence);
}

private TerminalControlSequences GetResetSequence(TerminalControlSequences controlSequence)
{
switch (ControlSequence)
{
case TerminalControlSequences.SetForegroundColor: return TerminalControlSequences.DefaultForegroundColor;
case TerminalControlSequences.SetBackgroundColor: return TerminalControlSequences.DefaultBackgroundColor;
case TerminalControlSequences.Bold: return TerminalControlSequences.Faint;
case TerminalControlSequences.RapidBlink:
case TerminalControlSequences.SlowBlink:
return TerminalControlSequences.NotBlinking;
case TerminalControlSequences.Strike: return TerminalControlSequences.NotCrossedOut;
case TerminalControlSequences.Underline: return TerminalControlSequences.NotUnderline;
case TerminalControlSequences.Italic: return TerminalControlSequences.NeitherItalicNorBlackletter;
case TerminalControlSequences.Hide: return TerminalControlSequences.Reveal;
}

return TerminalControlSequences.Reset;
}

public AnsiColor(params AnsiColor[] codes)
{
Codes = codes;
}

public string Colorize(string? text)
{
var sb = new StringBuilder();
ToEscapeSequence(sb);
sb.Append(text);
ToResetSequence(sb);
return sb.ToString();
}

public override string ToString()
{
return ToEscapeSequence();
}

public string ToResetSequence()
{
var sb = new StringBuilder();
ToResetSequence(sb);
return sb.ToString();
}

public void ToString(StringBuilder sb)
{
ToEscapeSequence(sb);
}

public string ToEscapeSequence()
{
var sb = new StringBuilder();
ToEscapeSequence(sb);
return sb.ToString();
}

public void ToResetSequence(StringBuilder sb)
{
sb.Append(EscapeCode);
sb.Append('[');

if (Codes != null && Codes.Length > 0)
{
foreach (var colorCode in Codes)
{
sb.Append((int)colorCode.ResetSequence);
sb.Append(';');
}

sb.Length--;
sb.Append('m');
return;
}

sb.Append((int)ResetSequence);
sb.Append('m');
}

public void ToEscapeSequence(StringBuilder sb)
{
sb.Append(EscapeCode);
sb.Append('[');
if (Codes != null && Codes.Length > 0)
{
foreach (var colorCode in Codes)
{
colorCode.ToEscapeParameters(sb);
sb.Append(';');
}

sb.Length--;
sb.Append('m');
return;
}

ToEscapeParameters(sb);
sb.Append('m');
}

public void ToEscapeParameters(StringBuilder sb)
{
switch (ControlSequence)
{
case TerminalControlSequences.SetForegroundColor:
case TerminalControlSequences.SetBackgroundColor:
sb.Append((int)ControlSequence);
sb.Append(';');
if (ColorCode256 != Terminal256ColorCodes.None)
sb.Append(ColorCode256.ToColorParameter());
else if (RgbColorCode != TerminalRgbColor.None)
sb.Append(RgbColorCode.ToColorParameter());
else
throw new InvalidColorException($"{ControlSequence} requires a color parameter");
break;
default:
sb.Append((int)ControlSequence);
break;
}
}
}
12 changes: 12 additions & 0 deletions TechTalk.SpecFlow/Tracing/AnsiColor/InvalidColorException.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
#nullable enable
using System;

namespace TechTalk.SpecFlow.Tracing.AnsiColor;

public class InvalidColorException : Exception
{
public InvalidColorException(string? message)
: base(message)
{
}
}
Loading

0 comments on commit b590fd4

Please sign in to comment.