Skip to content

Commit 8a89c29

Browse files
committed
*Support for enabling/disabling color output on demand
*Honor NO_COLOR environment variable
1 parent b079010 commit 8a89c29

3 files changed

Lines changed: 173 additions & 25 deletions

File tree

‎src/ConsoleExtensions.cs‎

Lines changed: 90 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -8,19 +8,6 @@
88

99
public static class ConsoleExtensions
1010
{
11-
12-
static ConsoleExtensions()
13-
{
14-
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
15-
{
16-
var iStdOut = GetStdHandle(STD_OUTPUT_HANDLE);
17-
18-
var enable = GetConsoleMode(iStdOut, out var outConsoleMode)
19-
&& SetConsoleMode(iStdOut, outConsoleMode | ENABLE_VIRTUAL_TERMINAL_PROCESSING | DISABLE_NEWLINE_AUTO_RETURN);
20-
}
21-
}
22-
23-
2411
private const int STD_OUTPUT_HANDLE = -11;
2512
private const uint ENABLE_VIRTUAL_TERMINAL_PROCESSING = 0x0004;
2613
private const uint DISABLE_NEWLINE_AUTO_RETURN = 0x0008;
@@ -38,6 +25,92 @@ static ConsoleExtensions()
3825
public static extern uint GetLastError();
3926

4027

28+
private delegate string ColorFormat( string input, Color color);
29+
private delegate string ColorFormatHex(string input, string hexColor);
30+
31+
32+
private const string _formatString = "\u001b[{0};2;{1};{2};{3}m{4}\u001b[0m";
33+
private const string _foregroundModifier = "38";
34+
private const string _backgroundModifier = "48";
35+
36+
37+
private static readonly Func<string, int> _parseHexColor = hc => int.Parse(hc.Replace("#", ""), NumberStyles.HexNumber);
38+
39+
private static readonly Func<string, Color, string, string> _colorFormat = (s, c, f) => string.Format(_formatString, f, c.R, c.G, c.B, s);
40+
private static readonly Func<string, string, string, string> _colorHexFormat = (s, c, f) => _colorFormat(s, Color.FromArgb(_parseHexColor(c)), f);
41+
42+
private static readonly ColorFormat _noColorOutputFormat = (s, _) => s;
43+
private static readonly ColorFormatHex _noColorOutputHexFormat = (s, _) => s;
44+
45+
private static readonly ColorFormat _foregroundColorFormat = (s, c) => _colorFormat( s, c, _foregroundModifier);
46+
private static readonly ColorFormatHex _foregroundColorHexFormat = (s, c) => _colorHexFormat(s, c, _foregroundModifier);
47+
48+
private static readonly ColorFormat _backgroundColorFormat = (s, c) => _colorFormat( s, c, _backgroundModifier);
49+
private static readonly ColorFormatHex _backgroundColorHexFormat = (s, c) => _colorHexFormat(s, c, _backgroundModifier);
50+
51+
52+
private static ColorFormat _foregroundColorFormatFunc;
53+
private static ColorFormatHex _foregroundColorHexFormatFunc;
54+
55+
private static ColorFormat _backgroundColorFormatFunc;
56+
private static ColorFormatHex _backgroundColorHexFormatFunc;
57+
58+
59+
static ConsoleExtensions()
60+
{
61+
if (Environment.GetEnvironmentVariable("NO_COLOR") == null)
62+
{
63+
Enable();
64+
65+
66+
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
67+
{
68+
var iStdOut = GetStdHandle(STD_OUTPUT_HANDLE);
69+
70+
var enable = GetConsoleMode(iStdOut, out var outConsoleMode)
71+
&& SetConsoleMode(iStdOut, outConsoleMode | ENABLE_VIRTUAL_TERMINAL_PROCESSING | DISABLE_NEWLINE_AUTO_RETURN);
72+
}
73+
}
74+
else
75+
{
76+
Disable();
77+
}
78+
}
79+
80+
81+
82+
83+
84+
85+
86+
87+
88+
89+
90+
91+
/// <summary>
92+
/// Enables any future console color output produced by Pastel.
93+
/// </summary>
94+
public static void Enable()
95+
{
96+
_foregroundColorFormatFunc = _foregroundColorFormat;
97+
_foregroundColorHexFormatFunc = _foregroundColorHexFormat;
98+
99+
_backgroundColorFormatFunc = _backgroundColorFormat;
100+
_backgroundColorHexFormatFunc = _backgroundColorHexFormat;
101+
}
102+
103+
/// <summary>
104+
/// Disables any future console color output produced by Pastel.
105+
/// </summary>
106+
public static void Disable()
107+
{
108+
_foregroundColorFormatFunc = _noColorOutputFormat;
109+
_foregroundColorHexFormatFunc = _noColorOutputHexFormat;
110+
111+
_backgroundColorFormatFunc = _noColorOutputFormat;
112+
_backgroundColorHexFormatFunc = _noColorOutputHexFormat;
113+
}
41114

42115

43116
/// <summary>
@@ -47,7 +120,7 @@ static ConsoleExtensions()
47120
/// <param name="color">The color to use on the specified string.</param>
48121
public static string Pastel(this string input, Color color)
49122
{
50-
return $"\u001b[38;2;{color.R};{color.G};{color.B}m{input}\u001b[0m";
123+
return _foregroundColorFormatFunc(input, color);
51124
}
52125

53126
/// <summary>
@@ -57,9 +130,7 @@ public static string Pastel(this string input, Color color)
57130
/// <param name="hexColor">The color to use on the specified string.<para>Supported format: [#]RRGGBB.</para></param>
58131
public static string Pastel(this string input, string hexColor)
59132
{
60-
var color = Color.FromArgb(int.Parse(hexColor.Replace("#", ""), NumberStyles.HexNumber));
61-
62-
return Pastel(input, color);
133+
return _foregroundColorHexFormatFunc(input, hexColor);
63134
}
64135

65136

@@ -71,7 +142,7 @@ public static string Pastel(this string input, string hexColor)
71142
/// <param name="color">The color to use on the specified string.</param>
72143
public static string PastelBg(this string input, Color color)
73144
{
74-
return $"\u001b[48;2;{color.R};{color.G};{color.B}m{input}\u001b[0m";
145+
return _backgroundColorFormatFunc(input, color);
75146
}
76147

77148
/// <summary>
@@ -81,9 +152,7 @@ public static string PastelBg(this string input, Color color)
81152
/// <param name="hexColor">The color to use on the specified string.<para>Supported format: [#]RRGGBB.</para></param>
82153
public static string PastelBg(this string input, string hexColor)
83154
{
84-
var color = Color.FromArgb(int.Parse(hexColor.Replace("#", ""), NumberStyles.HexNumber));
85-
86-
return PastelBg(input, color);
155+
return _backgroundColorHexFormatFunc(input, hexColor);
87156
}
88157
}
89158
}

‎src/Pastel.csproj‎

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,10 @@
77
<Description>A tiny utility class that makes colorizing console output a breeze.</Description>
88
<PackageLicenseUrl>https://github.com/silkfire/Pastel/blob/master/LICENSE</PackageLicenseUrl>
99
<PackageProjectUrl>https://github.com/silkfire/Pastel</PackageProjectUrl>
10-
<PackageTags>console colors ansi colorize</PackageTags>
11-
<Version>1.1.0</Version>
12-
<AssemblyVersion>1.1.0.0</AssemblyVersion>
13-
<PackageReleaseNotes>1.1.0 Added support for background colors.</PackageReleaseNotes>
10+
<PackageTags>console colors ansi colorize NO_COLOR</PackageTags>
11+
<Version>1.2.0</Version>
12+
<AssemblyVersion>1.2.0.0</AssemblyVersion>
13+
<PackageReleaseNotes>1.2.0 Added support for NO_COLOR (https://no-color.org). Color output can now be enabled/disabled on demand.</PackageReleaseNotes>
1414
<PackageIconUrl>https://raw.githubusercontent.com/silkfire/Pastel/master/img/logo.png</PackageIconUrl>
1515
</PropertyGroup>
1616

‎tests/Pastel.Tests/ColorTests.cs‎

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -200,5 +200,84 @@ public void A_Given_Hex_Color_String_Should_Return_Same_Ansi_Output_String_Regar
200200
Assert.Equal(outputAnsiColorString1, outputAnsiColorString2);
201201
}
202202
}
203+
204+
205+
public class NoOutputColor
206+
{
207+
private const string _input = "input";
208+
209+
210+
private void ColorOutputEnabledTest()
211+
{
212+
/////////////////
213+
// ARRANGE
214+
/////////
215+
216+
ConsoleExtensions.Enable();
217+
218+
219+
/////////////////
220+
// ACT
221+
/////////
222+
223+
var outputAnsiColorString1 = _input.Pastel( Color.FromArgb(1, 1, 1));
224+
var outputAnsiColorString2 = _input.Pastel( "#010101");
225+
var outputAnsiColorString3 = _input.PastelBg(Color.FromArgb(1, 1, 1));
226+
var outputAnsiColorString4 = _input.PastelBg("#010101");
227+
228+
229+
/////////////////
230+
// ASSERT
231+
/////////
232+
233+
Assert.Equal($"\u001b[38;2;1;1;1m{_input}\u001b[0m", outputAnsiColorString1);
234+
Assert.Equal($"\u001b[38;2;1;1;1m{_input}\u001b[0m", outputAnsiColorString2);
235+
Assert.Equal($"\u001b[48;2;1;1;1m{_input}\u001b[0m", outputAnsiColorString3);
236+
Assert.Equal($"\u001b[48;2;1;1;1m{_input}\u001b[0m", outputAnsiColorString4);
237+
}
238+
239+
240+
[Fact]
241+
public void Output_Should_Honor_Current_State_When_Switching_Between_States()
242+
{
243+
// Enable color output
244+
245+
ColorOutputEnabledTest();
246+
247+
248+
// Disable color output
249+
250+
/////////////////
251+
// ARRANGE
252+
/////////
253+
254+
ConsoleExtensions.Disable();
255+
256+
257+
/////////////////
258+
// ACT
259+
/////////
260+
261+
var outputAnsiColorString1 = _input.Pastel( Color.FromArgb(1, 1, 1));
262+
var outputAnsiColorString2 = _input.Pastel( "#010101");
263+
var outputAnsiColorString3 = _input.PastelBg(Color.FromArgb(1, 1, 1));
264+
var outputAnsiColorString4 = _input.PastelBg("#010101");
265+
266+
267+
/////////////////
268+
// ASSERT
269+
/////////
270+
271+
Assert.Equal(_input, outputAnsiColorString1);
272+
Assert.Equal(_input, outputAnsiColorString2);
273+
Assert.Equal(_input, outputAnsiColorString3);
274+
Assert.Equal(_input, outputAnsiColorString4);
275+
276+
277+
// Re-enable color output
278+
279+
ColorOutputEnabledTest();
280+
}
281+
}
203282
}
204283
}

0 commit comments

Comments
 (0)