Skip to content

Commit

Permalink
use ANSI escape codes to control all console text colours
Browse files Browse the repository at this point in the history
  • Loading branch information
cfbao committed Jul 24, 2024
1 parent 2dd49e6 commit 71c58d1
Show file tree
Hide file tree
Showing 5 changed files with 47 additions and 37 deletions.
36 changes: 35 additions & 1 deletion src/D2L.Bmx/ConsoleWriter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,16 @@ namespace D2L.Bmx;

internal interface IConsoleWriter {
void WriteParameter( string description, string value, ParameterSource source );
void WriteUpdateMessage( string text );
void WriteWarning( string text );
void WriteError( string text );
}

// We use ANSI escape codes to control colours, because .NET's `Console.ForegroundColor` only targets stdout,
// if stdout is redirected (e.g. typical use case for `bmx print`), we won't get any coloured text on stderr.
// https://github.com/dotnet/runtime/issues/83146
// See https://github.com/dotnet/runtime/issues/83146.
// Furthermore, ANSI escape codes give us greater control over the spread of custom background colour.
// TODO: use a library to manage ANSI codes and NO_COLOR?
internal class ConsoleWriter : IConsoleWriter {
// .NET runtime subscribes to the informal standard from https://no-color.org/. We should too.
// https://github.com/dotnet/runtime/blob/v9.0.0-preview.6.24327.7/src/libraries/Common/src/System/Console/ConsoleUtils.cs#L32-L34
Expand All @@ -21,4 +26,33 @@ void IConsoleWriter.WriteParameter( string description, string value, ParameterS
// source: grey / bright black
Console.Error.WriteLine( $"\x1b[0m{description}: \x1b[96m{value} \x1b[90m(from {source})\x1b[0m" );
}

void IConsoleWriter.WriteUpdateMessage( string text ) {
if( _noColor || !VirtualTerminal.TryEnableOnStderr() ) {
Console.Error.WriteLine( text );
}
string[] lines = text.Split( '\n' );
int maxLineLength = lines.Max( l => l.Length );
foreach( string line in lines ) {
string paddedLine = line.PadRight( maxLineLength );
Console.Error.WriteLine( $"\x1b[0m\x1b[30;47m{paddedLine}\x1b[0m" );
}
Console.Error.WriteLine();
}

void IConsoleWriter.WriteWarning( string text ) {
if( _noColor || !VirtualTerminal.TryEnableOnStderr() ) {
Console.Error.WriteLine( text );
}
// bright yellow - 93
Console.Error.WriteLine( $"\x1b[0m\x1b[93m{text}\x1b[0m" );
}

void IConsoleWriter.WriteError( string text ) {
if( _noColor || !VirtualTerminal.TryEnableOnStderr() ) {
Console.Error.WriteLine( text );
}
// bright red - 91
Console.Error.WriteLine( $"\x1b[0m\x1b[91m{text}\x1b[0m" );
}
}
9 changes: 4 additions & 5 deletions src/D2L.Bmx/OktaAuthenticator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -91,11 +91,10 @@ mfaFactor is OktaMfaQuestionFactor // Security question factor is a static value
if( File.Exists( BmxPaths.CONFIG_FILE_NAME ) ) {
CacheOktaSession( user, org, sessionResp.Id, sessionResp.ExpiresAt );
} else {
Console.ResetColor();
Console.ForegroundColor = ConsoleColor.Yellow;
Console.Error.WriteLine( "No config file found. Your Okta session will not be cached. " +
"Consider running `bmx configure` if you own this machine." );
Console.ResetColor();
consoleWriter.WriteWarning(
"No config file found. Your Okta session will not be cached. " +
"Consider running `bmx configure` if you own this machine."
);
}
return new AuthenticatedOktaApi( Org: org, User: user, Api: oktaApi );
}
Expand Down
12 changes: 5 additions & 7 deletions src/D2L.Bmx/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -246,7 +246,7 @@
}

if( context.ParseResult.CommandResult.Command != updateCommand ) {
var updateChecker = new UpdateChecker( new GitHubClient(), new VersionProvider() );
var updateChecker = new UpdateChecker( new GitHubClient(), new VersionProvider(), new ConsoleWriter() );
await updateChecker.CheckForUpdatesAsync();
}

Expand All @@ -258,17 +258,15 @@
order: MiddlewareOrder.ExceptionHandler + 1
)
.UseExceptionHandler( ( exception, context ) => {
Console.ResetColor();
Console.ForegroundColor = ConsoleColor.Red;
IConsoleWriter consoleWriter = new ConsoleWriter();
if( exception is BmxException ) {
Console.Error.WriteLine( exception.Message );
consoleWriter.WriteError( exception.Message );
} else {
Console.Error.WriteLine( "BMX exited with unexpected internal error" );
consoleWriter.WriteError( "BMX exited with unexpected internal error" );
}
if( Environment.GetEnvironmentVariable( "BMX_DEBUG" ) == "1" ) {
Console.Error.WriteLine( exception );
consoleWriter.WriteError( exception.ToString() );
}
Console.ResetColor();
} )
.Build()
.InvokeAsync( args );
25 changes: 2 additions & 23 deletions src/D2L.Bmx/UpdateChecker.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

namespace D2L.Bmx;

internal class UpdateChecker( IGitHubClient github, IVersionProvider versionProvider ) {
internal class UpdateChecker( IGitHubClient github, IVersionProvider versionProvider, IConsoleWriter consoleWriter ) {
public async Task CheckForUpdatesAsync() {
try {
var updateCheckCache = GetUpdateCheckCacheOrNull();
Expand All @@ -29,7 +29,7 @@ public async Task CheckForUpdatesAsync() {
Version? localVersion = versionProvider.GetAssemblyVersion();
if( latestVersion > localVersion ) {
string? displayVersion = versionProvider.GetInformationalVersion() ?? localVersion.ToString();
DisplayUpdateMessage(
consoleWriter.WriteUpdateMessage(
$"""
A new BMX release is available: v{latestVersion} (current: {displayVersion})
Run "bmx update" now to upgrade.
Expand All @@ -41,27 +41,6 @@ public async Task CheckForUpdatesAsync() {
}
}

private static void DisplayUpdateMessage( string message ) {
var originalBackgroundColor = Console.BackgroundColor;
var originalForegroundColor = Console.ForegroundColor;

Console.BackgroundColor = ConsoleColor.Gray;
Console.ForegroundColor = ConsoleColor.Black;

string[] lines = message.Split( "\n" );
int consoleWidth = Console.WindowWidth;

foreach( string line in lines ) {
Console.Error.Write( line.PadRight( consoleWidth ) );
Console.Error.WriteLine();
}

Console.BackgroundColor = originalBackgroundColor;
Console.ForegroundColor = originalForegroundColor;
Console.ResetColor();
Console.Error.WriteLine();
}

private static void SetUpdateCheckCache( Version version ) {
var cache = new UpdateCheckCache(
VersionName: version,
Expand Down
2 changes: 1 addition & 1 deletion src/D2L.Bmx/WriteHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,7 @@ bool useCredentialProcess
}
parser.WriteFile( awsConfigFilePath, awsConfig, Utf8 );
if( foundCredentialProcess ) {
Console.WriteLine(
consoleWriter.WriteWarning(
"""
An existing profile with the same name using the `credential_process` setting was found in the default config file.
The setting has been removed, and static credentials will be used for the profile.
Expand Down

0 comments on commit 71c58d1

Please sign in to comment.