Skip to content

Commit

Permalink
Add an option to disable the detection of a CI/CD environment to over…
Browse files Browse the repository at this point in the history
…ride the disabling of output coloring
  • Loading branch information
silkfire committed Apr 24, 2024
1 parent 1e0f7c9 commit f8ace43
Show file tree
Hide file tree
Showing 6 changed files with 118 additions and 73 deletions.
5 changes: 3 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -70,11 +70,12 @@ Both foreground and background colors can be combined by chaining the methods:

## Disabling / enabling color output

If you for some reason want to disable any future color output produced by Pastel for the duration of your app, simply call `ConsoleExtensions.Disable()`. To re-enable color output, call `ConsoleExtensions.Enable()`.
If you for any reason would like to disable any future color output produced by Pastel for the duration of your app, simply call `ConsoleExtensions.Disable()`. To re-enable color output, call `ConsoleExtensions.Enable()`.

### CI/CD environments

Pastel will detect if your application is running under a common CI/CD environment and will disable all coloring if this is the case.
Pastel will detect if your application is running under a common CI/CD environment and will disable all coloring if this is the case.
If you'd like to override this check and force colors in CI/CD environments, you can set an environment variable named `PASTEL_DISABLE_ENVIRONMENT_DETECTION` (value does not matter).

### NO_COLOR

Expand Down
11 changes: 4 additions & 7 deletions src/ConsoleExtensions.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,4 @@
#if NET7_0_OR_GREATER
[assembly: System.Runtime.CompilerServices.DisableRuntimeMarshalling]
#endif
namespace Pastel
namespace Pastel
{
using System;
using System.Collections.Generic;
Expand Down Expand Up @@ -115,13 +112,13 @@ static ConsoleExtensions()
}


if (EnvironmentDetector.ColorsDisabled)
if (EnvironmentDetector.ColorsEnabled())
{
Disable();
Enable();
}
else
{
Enable();
Disable();
}
}

Expand Down
67 changes: 40 additions & 27 deletions src/EnvironmentDetector.cs
Original file line number Diff line number Diff line change
@@ -1,27 +1,41 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;

[assembly: System.Runtime.CompilerServices.InternalsVisibleTo("Pastel.Tests")]

namespace Pastel
namespace Pastel
{
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;

internal static class EnvironmentDetector
{
private const string DisableEnvironmentDetectionEnvironmentVariableName = "PASTEL_DISABLE_ENVIRONMENT_DETECTION";

/// <summary>
/// Returns <see langword="true"/> if at least one of a predefined set of environment variables are set. These environment variables could e.g. indicate that the application is running in a CI/CD environment.
/// </summary>
public static bool ColorsDisabled => HasEnvironmentVariable((key, value) => s_environmentVariableDetectors.Any(evd => evd(key, value)));
public static bool ColorsEnabled()
{
return Environment.GetEnvironmentVariable(DisableEnvironmentDetectionEnvironmentVariableName) != null || !HasEnvironmentVariable();
}

#if NET7_0_OR_GREATER
private static readonly Func<string, string, bool>[] s_environmentVariableDetectors = [
IsBitbucketEnvironmentVariableKey,
IsTeamCityEnvironmentVariableKey,
NoColor,
IsGitHubAction,
IsCI,
IsJenkins
];
#else
private static readonly Func<string, string, bool>[] s_environmentVariableDetectors = {
IsBitbucketEnvironmentVariableKey,
IsTeamCityEnvironmentVariableKey,
NoColor,
IsGitHubAction,
IsCI,
IsJenkins
};
};
#endif

private static bool IsBitbucketEnvironmentVariableKey(string key, string value)
{
Expand All @@ -48,9 +62,8 @@ private static bool IsGitHubAction(string key, string value)
// Set by GitHub Actions and Travis CI
private static bool IsCI(string key, string value)
{
return key.Equals("CI", StringComparison.OrdinalIgnoreCase)
&& (value.Equals("true", StringComparison.OrdinalIgnoreCase)
|| value.Equals("1", StringComparison.OrdinalIgnoreCase));
return key.Equals("CI", StringComparison.OrdinalIgnoreCase)
&& (value.Equals("true", StringComparison.OrdinalIgnoreCase) || value == "1");
}

// Detect Jenkins enviroment
Expand All @@ -59,24 +72,24 @@ private static bool IsJenkins(string key, string value)
return key.StartsWith("JENKINS_URL", StringComparison.OrdinalIgnoreCase);
}

private static bool HasEnvironmentVariable(Func<string, string, bool> environmentDetectorPredicate)
private static bool HasEnvironmentVariable()
{
var processKeys = EnumerateEnvironmentVariables(EnvironmentVariableTarget.Process);
var userKeys = EnumerateEnvironmentVariables(EnvironmentVariableTarget.User);
var machineKeys = EnumerateEnvironmentVariables(EnvironmentVariableTarget.Machine);
var environmentVariables = Environment.GetEnvironmentVariables(EnvironmentVariableTarget.Process).Cast<DictionaryEntry>()
.Concat(Environment.GetEnvironmentVariables(EnvironmentVariableTarget.User).Cast<DictionaryEntry>())
.Concat(Environment.GetEnvironmentVariables(EnvironmentVariableTarget.Machine).Cast<DictionaryEntry>())
.Select(de => new KeyValuePair<string, string>(de.Key.ToString(), de.Value != null ? de.Value.ToString() : ""))
.GroupBy(kvp => kvp.Key, (_, kvps) => kvps.First())
.ToList();

return processKeys
.Concat(userKeys)
.Concat(machineKeys)
.Any(kvp => environmentDetectorPredicate(kvp.Key, kvp.Value));
}

private static IEnumerable<KeyValuePair<string, string>> EnumerateEnvironmentVariables(EnvironmentVariableTarget target)
{
foreach (var entry in Environment.GetEnvironmentVariables(target).OfType<DictionaryEntry>())
foreach (var environmentVariable in environmentVariables)
{
yield return new KeyValuePair<string, string>(entry.Key.ToString(), entry.Value?.ToString() ?? string.Empty);
if (s_environmentVariableDetectors.Any(evd => evd(environmentVariable.Key, environmentVariable.Value)))
{
return true;
}
}

return false;
}
}
}
16 changes: 13 additions & 3 deletions src/Pastel.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,24 @@
<Copyright>Copyright © 2024</Copyright>
<PackageProjectUrl>https://github.com/silkfire/Pastel</PackageProjectUrl>
<Description>A tiny utility class that makes colorizing console output a breeze.</Description>
<Version>5.0.0</Version>
<AssemblyVersion>5.0.0.0</AssemblyVersion>
<PackageReleaseNotes>v5.0.0 Significant performance improvements</PackageReleaseNotes>
<Version>5.1.0</Version>
<AssemblyVersion>5.1.0.0</AssemblyVersion>
<PackageReleaseNotes>v5.1.0 Add an option to disable the detection of a CI/CD environment to override the disabling of output coloring</PackageReleaseNotes>
</PropertyGroup>

<ItemGroup Condition=" '$(TargetFramework)' == 'net462' ">
<PackageReference Include="System.Runtime.InteropServices.RuntimeInformation" Version="4.3.0" />
<PackageReference Include="System.Memory" Version="4.5.5" />
</ItemGroup>

<ItemGroup Condition="$(DefineConstants.Contains('NET7_0_OR_GREATER'))">
<AssemblyAttribute Include="System.Runtime.CompilerServices.DisableRuntimeMarshalling" />
</ItemGroup>

<ItemGroup>
<AssemblyAttribute Include="System.Runtime.CompilerServices.InternalsVisibleToAttribute">
<_Parameter1>Pastel.Tests</_Parameter1>
</AssemblyAttribute>
</ItemGroup>

</Project>
87 changes: 53 additions & 34 deletions tests/Pastel.Tests/EnvironmentTests.cs
Original file line number Diff line number Diff line change
@@ -1,55 +1,74 @@
using Xunit;
namespace Pastel.Tests
{
using Xunit;

using System;
using System;
using System.Collections.Generic;

namespace Pastel.Tests
{
public class EnvironmentTests
{
private static readonly object _lock = new object();

[Theory]
[InlineData("BITBUCKET_SOMEKEY", "somevalue", true)]
[InlineData("BITbucket_SOMEKEY", "somevalue", true)]
[InlineData("TEAMCITY_SOMEKEY", "somevalue", true)]
[InlineData("TEAMcity_SOMEKEY", "somevalue", true)]
[InlineData("NO_COLOR", "true", true)]
[InlineData("no_color", "true", true)]
[InlineData("GITHUB_ACTION", "somevalue", true)]
[InlineData("GitHuB_action", "somevalue", true)]
[InlineData("CI", "true", true)]
[InlineData("ci", "true", true)]
[InlineData("CI", "1", true)]
[InlineData("ci", "1", true)]
[InlineData("CI", "false", false)]
[InlineData("ci", "false", false)]
[InlineData("CI", "0", false)]
[InlineData("ci", "0", false)]
[InlineData("JENKINS_URL", "http://localhost:8080", true)]
[InlineData("jenkins_URL", "http://localhost:8080", true)]
[InlineData("TEAMCITY_VERSION", "2023.1.1", true)]
[InlineData("teamcity_VERSION", "2023.1.1", true)]
[InlineData("SOME_OTHER_KEY", "somevalue", false)]
[InlineData("some_other_KEY", "somevalue", false)]
private const string DisableEnvironmentDetectionEnvironmentVariableName = "PASTEL_DISABLE_ENVIRONMENT_DETECTION";

public void TestEnvironmentVariables(string key, string value, bool expected)
[Theory, CombinatorialData]
public void TestEnvironmentVariables([CombinatorialMemberData(nameof(GetEnvironmentVariables))] (string Key, string Value, bool ExpectedOutcome) environmentVariable, [CombinatorialMemberData(nameof(GetEnvironmentDetectionDisabledEnvironmentVariables))] string environmentDetectionDisabledEnvironmentVariable)
{
try
{
// Arrange
Environment.SetEnvironmentVariable(key, value, EnvironmentVariableTarget.Process);
if (environmentDetectionDisabledEnvironmentVariable != null)
{
Environment.SetEnvironmentVariable(DisableEnvironmentDetectionEnvironmentVariableName, environmentDetectionDisabledEnvironmentVariable, EnvironmentVariableTarget.Process);
}
Environment.SetEnvironmentVariable(environmentVariable.Key, environmentVariable.Value, EnvironmentVariableTarget.Process);

// Act
var result = EnvironmentDetector.ColorsDisabled;
var result = EnvironmentDetector.ColorsEnabled();

// Assert
Assert.Equal(expected, result);
Assert.Equal(environmentDetectionDisabledEnvironmentVariable != null || environmentVariable.ExpectedOutcome, result);
}
finally
{
// Cleanup
Environment.SetEnvironmentVariable(key, null, EnvironmentVariableTarget.Process);
Environment.SetEnvironmentVariable(DisableEnvironmentDetectionEnvironmentVariableName, null, EnvironmentVariableTarget.Process);
Environment.SetEnvironmentVariable(environmentVariable.Key, null, EnvironmentVariableTarget.Process);
}
}

private static IEnumerable<(string Key, string Value, bool ExpectedOutcome)> GetEnvironmentVariables()
{
yield return ("BITBUCKET_SOMEKEY", "somevalue", false);
yield return ("BITbucket_SOMEKEY", "somevalue", false);
yield return ("TEAMCITY_SOMEKEY", "somevalue", false);
yield return ("TEAMcity_SOMEKEY", "somevalue", false);
yield return ("NO_COLOR", "true", false);
yield return ("no_color", "true", false);
yield return ("GITHUB_ACTION", "somevalue", false);
yield return ("GitHuB_action", "somevalue", false);
yield return ("CI", "true", false);
yield return ("ci", "true", false);
yield return ("CI", "1", false);
yield return ("ci", "1", false);
yield return ("CI", "false", true);
yield return ("ci", "false", true);
yield return ("CI", "0", true);
yield return ("ci", "0", true);
yield return ("JENKINS_URL", "http://localhost:8080", false);
yield return ("jenkins_URL", "http://localhost:8080", false);
yield return ("TEAMCITY_VERSION", "2023.1.1", false);
yield return ("teamcity_VERSION", "2023.1.1", false);
yield return ("SOME_OTHER_KEY", "somevalue", true);
yield return ("some_other_KEY", "somevalue", true);
}

private static IEnumerable<string> GetEnvironmentDetectionDisabledEnvironmentVariables()
{
yield return null;
yield return "1";
yield return "true";
yield return "TRUE";
yield return "trUe";
yield return "something_random";
}
}
}
5 changes: 5 additions & 0 deletions tests/Pastel.Tests/Pastel.Tests.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.9.0" />
<PackageReference Include="xunit" Version="2.7.1" />
<PackageReference Include="Xunit.Combinatorial" Version="1.6.24" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.5.8">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
Expand All @@ -19,6 +20,10 @@
<ProjectReference Include="..\..\src\Pastel.csproj" />
</ItemGroup>

<ItemGroup Condition=" '$(TargetFramework)' == 'net462' ">
<PackageReference Include="System.ValueTuple" Version="4.5.0" />
</ItemGroup>

<ItemGroup>
<AssemblyAttribute Include="Xunit.CollectionBehavior">
<DisableTestParallelization>true</DisableTestParallelization>
Expand Down

0 comments on commit f8ace43

Please sign in to comment.