Simplify console output and fix color output on CMD

This commit is contained in:
Nate McMaster 2017-03-10 09:18:38 -08:00
parent beb53221c3
commit c97dd446eb
No known key found for this signature in database
GPG Key ID: BD729980AA6A21BD
18 changed files with 131 additions and 455 deletions

View File

@ -15,5 +15,7 @@ namespace Microsoft.Extensions.Tools.Internal
bool IsInputRedirected { get; }
bool IsOutputRedirected { get; }
bool IsErrorRedirected { get; }
ConsoleColor ForegroundColor { get; set; }
void ResetColor();
}
}

View File

@ -25,5 +25,12 @@ namespace Microsoft.Extensions.Tools.Internal
public bool IsInputRedirected => Console.IsInputRedirected;
public bool IsOutputRedirected => Console.IsOutputRedirected;
public bool IsErrorRedirected => Console.IsErrorRedirected;
public ConsoleColor ForegroundColor
{
get => Console.ForegroundColor;
set => Console.ForegroundColor = value;
}
public void ResetColor() => Console.ResetColor();
}
}

View File

@ -1,57 +0,0 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Collections.Generic;
namespace Microsoft.Extensions.Tools.Internal
{
public class ColorFormatter : IFormatter
{
// resets foreground color only
private const string ResetColor = "\x1B[39m";
private static readonly IDictionary<ConsoleColor, int> AnsiColorCodes
= new Dictionary<ConsoleColor, int>
{
{ConsoleColor.Black, 30},
{ConsoleColor.DarkRed, 31},
{ConsoleColor.DarkGreen, 32},
{ConsoleColor.DarkYellow, 33},
{ConsoleColor.DarkBlue, 34},
{ConsoleColor.DarkMagenta, 35},
{ConsoleColor.DarkCyan, 36},
{ConsoleColor.Gray, 37},
{ConsoleColor.DarkGray, 90},
{ConsoleColor.Red, 91},
{ConsoleColor.Green, 92},
{ConsoleColor.Yellow, 93},
{ConsoleColor.Blue, 94},
{ConsoleColor.Magenta, 95},
{ConsoleColor.Cyan, 96},
{ConsoleColor.White, 97},
};
private readonly string _prefix;
public ColorFormatter(ConsoleColor color)
{
_prefix = GetAnsiCode(color);
}
public string Format(string text)
=> text?.Length > 0
? $"{_prefix}{text}{ResetColor}"
: text;
private static string GetAnsiCode(ConsoleColor color)
{
int code;
if (!AnsiColorCodes.TryGetValue(color, out code))
{
throw new ArgumentOutOfRangeException(nameof(color), color, null);
}
return $"\x1B[{code}m";
}
}
}

View File

@ -1,26 +0,0 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
namespace Microsoft.Extensions.Tools.Internal
{
public class CompositeFormatter : IFormatter
{
private readonly IFormatter[] _formatters;
public CompositeFormatter(IFormatter[] formatters)
{
Ensure.NotNull(formatters, nameof(formatters));
_formatters = formatters;
}
public string Format(string text)
{
foreach (var formatter in _formatters)
{
text = formatter.Format(text);
}
return text;
}
}
}

View File

@ -1,24 +0,0 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
namespace Microsoft.Extensions.Tools.Internal
{
public class ConditionalFormatter : IFormatter
{
private readonly Func<bool> _predicate;
public ConditionalFormatter(Func<bool> predicate)
{
Ensure.NotNull(predicate, nameof(predicate));
_predicate = predicate;
}
public string Format(string text)
=> _predicate()
? text
: null;
}
}

View File

@ -0,0 +1,72 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.IO;
namespace Microsoft.Extensions.Tools.Internal
{
public class ConsoleReporter : IReporter
{
private object _writeLock = new object();
public ConsoleReporter(IConsole console)
: this(console, verbose: false, quiet: false)
{ }
public ConsoleReporter(IConsole console, bool verbose, bool quiet)
{
Ensure.NotNull(console, nameof(console));
Console = console;
IsVerbose = verbose;
IsQuiet = quiet;
}
protected IConsole Console { get; }
public bool IsVerbose { get; set; }
public bool IsQuiet { get; set; }
protected virtual void WriteLine(TextWriter writer, string message, ConsoleColor? color)
{
lock (_writeLock)
{
if (color.HasValue)
{
Console.ForegroundColor = color.Value;
}
writer.WriteLine(message);
if (color.HasValue)
{
Console.ResetColor();
}
}
}
public virtual void Error(string message)
=> WriteLine(Console.Error, message, ConsoleColor.Red);
public virtual void Warn(string message)
=> WriteLine(Console.Out, message, ConsoleColor.Yellow);
public virtual void Output(string message)
{
if (IsQuiet)
{
return;
}
WriteLine(Console.Out, message, color: null);
}
public virtual void Verbose(string message)
{
if (!IsVerbose)
{
return;
}
WriteLine(Console.Out, message, ConsoleColor.DarkGray);
}
}
}

View File

@ -1,14 +0,0 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
namespace Microsoft.Extensions.Tools.Internal
{
public class DefaultFormatter : IFormatter
{
public static readonly IFormatter Instance = new DefaultFormatter();
private DefaultFormatter() {}
public string Format(string text)
=> text;
}
}

View File

@ -1,46 +0,0 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Collections.Generic;
namespace Microsoft.Extensions.Tools.Internal
{
public class FormatterBuilder
{
private readonly List<IFormatter> _formatters = new List<IFormatter>();
public FormatterBuilder WithColor(ConsoleColor color)
{
_formatters.Add(new ColorFormatter(color));
return this;
}
public FormatterBuilder WithPrefix(string prefix)
{
_formatters.Add(new PrefixFormatter(prefix));
return this;
}
public FormatterBuilder When(Func<bool> predicate)
{
_formatters.Add(new ConditionalFormatter(predicate));
return this;
}
public IFormatter Build()
{
if (_formatters.Count == 0)
{
return DefaultFormatter.Instance;
}
if (_formatters.Count == 1)
{
return _formatters[0];
}
return new CompositeFormatter(_formatters.ToArray());
}
}
}

View File

@ -1,62 +0,0 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.IO;
namespace Microsoft.Extensions.Tools.Internal
{
public class FormattingReporter : IReporter
{
private readonly object _writelock = new object();
private readonly IConsole _console;
private readonly IFormatter _verbose;
private readonly IFormatter _warn;
private readonly IFormatter _output;
private readonly IFormatter _error;
public FormattingReporter(IConsole console,
IFormatter verbose,
IFormatter output,
IFormatter warn,
IFormatter error)
{
Ensure.NotNull(console, nameof(console));
Ensure.NotNull(verbose, nameof(verbose));
Ensure.NotNull(output, nameof(output));
Ensure.NotNull(warn, nameof(warn));
Ensure.NotNull(error, nameof(error));
_console = console;
_verbose = verbose;
_output = output;
_warn = warn;
_error = error;
}
public void Verbose(string message)
=> Write(_console.Out, _verbose.Format(message));
public void Output(string message)
=> Write(_console.Out, _output.Format(message));
public void Warn(string message)
=> Write(_console.Out, _warn.Format(message));
public void Error(string message)
=> Write(_console.Error, _error.Format(message));
private void Write(TextWriter writer, string message)
{
if (message == null)
{
return;
}
lock (_writelock)
{
writer.WriteLine(message);
}
}
}
}

View File

@ -1,10 +0,0 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
namespace Microsoft.Extensions.Tools.Internal
{
public interface IFormatter
{
string Format(string text);
}
}

View File

@ -1,20 +0,0 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
namespace Microsoft.Extensions.Tools.Internal
{
public class PrefixFormatter : IFormatter
{
private readonly string _prefix;
public PrefixFormatter(string prefix)
{
Ensure.NotNullOrEmpty(prefix, nameof(prefix));
_prefix = prefix;
}
public string Format(string text)
=> _prefix + text;
}
}

View File

@ -1,65 +0,0 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
namespace Microsoft.Extensions.Tools.Internal
{
public class ReporterBuilder
{
private readonly FormatterBuilder _verbose = new FormatterBuilder();
private readonly FormatterBuilder _output = new FormatterBuilder();
private readonly FormatterBuilder _warn = new FormatterBuilder();
private readonly FormatterBuilder _error = new FormatterBuilder();
private IConsole _console;
public ReporterBuilder WithConsole(IConsole console)
{
_console = console;
return this;
}
public FormatterBuilder Verbose() => _verbose;
public FormatterBuilder Output() => _output;
public FormatterBuilder Warn() => _warn;
public FormatterBuilder Error() => _error;
public ReporterBuilder Verbose(Action<FormatterBuilder> configure)
{
configure(_verbose);
return this;
}
public ReporterBuilder Output(Action<FormatterBuilder> configure)
{
configure(_output);
return this;
}
public ReporterBuilder Warn(Action<FormatterBuilder> configure)
{
configure(_warn);
return this;
}
public ReporterBuilder Error(Action<FormatterBuilder> configure)
{
configure(_error);
return this;
}
public IReporter Build()
{
if (_console == null)
{
throw new InvalidOperationException($"Cannot build without first calling {nameof(WithConsole)}");
}
return new FormattingReporter(_console,
_verbose.Build(),
_output.Build(),
_warn.Build(),
_error.Build());
}
}
}

View File

@ -0,0 +1,32 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.IO;
using Microsoft.Extensions.Tools.Internal;
namespace Microsoft.DotNet.Watcher
{
public class PrefixConsoleReporter : ConsoleReporter
{
private object _lock = new object();
public PrefixConsoleReporter(IConsole console, bool verbose, bool quiet)
: base(console, verbose, quiet)
{ }
protected override void WriteLine(TextWriter writer, string message, ConsoleColor? color)
{
const string prefix = "watch : ";
lock (_lock)
{
Console.ForegroundColor = ConsoleColor.DarkGray;
writer.Write(prefix);
Console.ResetColor();
base.WriteLine(writer, message, color);
}
}
}
}

View File

@ -176,51 +176,6 @@ namespace Microsoft.DotNet.Watcher
}
private static IReporter CreateReporter(bool verbose, bool quiet, IConsole console)
{
const string prefix = "watch : ";
var colorPrefix = new ColorFormatter(ConsoleColor.DarkGray).Format(prefix);
return new ReporterBuilder()
.WithConsole(console)
.Verbose(f =>
{
if (console.IsOutputRedirected)
{
f.WithPrefix(prefix);
}
else
{
f.WithColor(ConsoleColor.DarkGray).WithPrefix(colorPrefix);
}
f.When(() => verbose || CliContext.IsGlobalVerbose());
})
.Output(f => f
.WithPrefix(console.IsOutputRedirected ? prefix : colorPrefix)
.When(() => !quiet))
.Warn(f =>
{
if (console.IsOutputRedirected)
{
f.WithPrefix(prefix);
}
else
{
f.WithColor(ConsoleColor.Yellow).WithPrefix(colorPrefix);
}
})
.Error(f =>
{
if (console.IsOutputRedirected)
{
f.WithPrefix(prefix);
}
else
{
f.WithColor(ConsoleColor.Red).WithPrefix(colorPrefix);
}
})
.Build();
}
=> new PrefixConsoleReporter(console, verbose || CliContext.IsGlobalVerbose(), quiet);
}
}

View File

@ -99,34 +99,7 @@ namespace Microsoft.Extensions.Caching.SqlConfig.Tools
}
private IReporter CreateReporter(bool verbose)
{
return new ReporterBuilder()
.WithConsole(_console)
.Verbose(f =>
{
f.When(() => verbose);
if (!_console.IsOutputRedirected)
{
f.WithColor(ConsoleColor.DarkGray);
}
})
.Warn(f =>
{
if (!_console.IsOutputRedirected)
{
f.WithColor(ConsoleColor.Yellow);
}
})
.Error(f =>
{
if (!_console.IsErrorRedirected)
{
f.WithColor(ConsoleColor.Red);
}
})
.Build();
}
=> new ConsoleReporter(_console, verbose, quiet: false);
private int CreateTableAndIndexes(IReporter reporter)
{
ValidateConnectionString();
@ -197,4 +170,4 @@ namespace Microsoft.Extensions.Caching.SqlConfig.Tools
}
}
}
}
}

View File

@ -89,33 +89,7 @@ namespace Microsoft.Extensions.SecretManager.Tools
}
private IReporter CreateReporter(bool verbose)
{
return new ReporterBuilder()
.WithConsole(_console)
.Verbose(f =>
{
f.When(() => verbose);
if (!_console.IsOutputRedirected)
{
f.WithColor(ConsoleColor.DarkGray);
}
})
.Warn(f =>
{
if (!_console.IsOutputRedirected)
{
f.WithColor(ConsoleColor.Yellow);
}
})
.Error(f =>
{
if (!_console.IsErrorRedirected)
{
f.WithColor(ConsoleColor.Red);
}
})
.Build();
}
=> new ConsoleReporter(_console, verbose, quiet: false);
internal string ResolveId(CommandLineOptions options, IReporter reporter)
{
@ -128,4 +102,4 @@ namespace Microsoft.Extensions.SecretManager.Tools
return resolver.Resolve(options.Project, options.Configuration);
}
}
}
}

View File

@ -13,30 +13,11 @@ namespace Microsoft.Extensions.Tools.Tests
{
private static readonly string EOL = Environment.NewLine;
[Theory]
[InlineData(ConsoleColor.DarkGray, "\x1B[90m")]
[InlineData(ConsoleColor.Red, "\x1B[91m")]
[InlineData(ConsoleColor.Yellow, "\x1B[93m")]
public void WrapsWithAnsiColorCode(ConsoleColor color, string code)
{
Assert.Equal($"{code}sample\x1B[39m", new ColorFormatter(color).Format("sample"));
}
[Fact]
public void SkipsColorCodesForEmptyOrNullInput()
{
var formatter = new ColorFormatter(ConsoleColor.Blue);
Assert.Empty(formatter.Format(string.Empty));
Assert.Null(formatter.Format(null));
}
[Fact]
public void WritesToStandardStreams()
{
var testConsole = new TestConsole();
var reporter = new FormattingReporter(testConsole,
DefaultFormatter.Instance, DefaultFormatter.Instance,
DefaultFormatter.Instance, DefaultFormatter.Instance);
var reporter = new ConsoleReporter(testConsole, verbose: true, quiet: false);
// stdout
reporter.Verbose("verbose");
@ -57,13 +38,6 @@ namespace Microsoft.Extensions.Tools.Tests
testConsole.Clear();
}
[Fact]
public void FailsToBuildWithoutConsole()
{
Assert.Throws<InvalidOperationException>(
() => new ReporterBuilder().Build());
}
private class TestConsole : IConsole
{
private readonly StringBuilder _out;
@ -92,12 +66,18 @@ namespace Microsoft.Extensions.Tools.Tests
_error.Clear();
}
public void ResetColor()
{
ForegroundColor = default(ConsoleColor);
}
public TextWriter Out { get; }
public TextWriter Error { get; }
public TextReader In { get; }
public bool IsInputRedirected { get; }
public bool IsOutputRedirected { get; }
public bool IsErrorRedirected { get; }
public ConsoleColor ForegroundColor { get; set; }
}
}
}
}

View File

@ -27,6 +27,7 @@ namespace Microsoft.Extensions.Tools.Internal
public bool IsInputRedirected { get; set; } = false;
public bool IsOutputRedirected { get; } = false;
public bool IsErrorRedirected { get; } = false;
public ConsoleColor ForegroundColor { get; set; }
public ConsoleCancelEventArgs ConsoleCancelKey()
{
@ -39,6 +40,10 @@ namespace Microsoft.Extensions.Tools.Internal
return args;
}
public void ResetColor()
{
}
private class TestOutputWriter : TextWriter
{
private readonly ITestOutputHelper _output;