diff --git a/src/Microsoft.DotNet.Watcher.Tools/CommandLineOptions.cs b/src/Microsoft.DotNet.Watcher.Tools/CommandLineOptions.cs index 4d7c998622..6960ea93b6 100644 --- a/src/Microsoft.DotNet.Watcher.Tools/CommandLineOptions.cs +++ b/src/Microsoft.DotNet.Watcher.Tools/CommandLineOptions.cs @@ -4,6 +4,8 @@ using System; using System.Collections.Generic; using System.IO; +using Microsoft.DotNet.Cli.Utils; +using Microsoft.DotNet.Watcher.Tools; using Microsoft.Extensions.CommandLineUtils; namespace Microsoft.DotNet.Watcher @@ -11,8 +13,10 @@ namespace Microsoft.DotNet.Watcher internal class CommandLineOptions { public bool IsHelp { get; private set; } + public bool IsQuiet { get; private set; } + public bool IsVerbose { get; private set; } public IList RemainingArguments { get; private set; } - public static CommandLineOptions Parse(string[] args, TextWriter consoleOutput) + public static CommandLineOptions Parse(string[] args, TextWriter stdout, TextWriter stderr) { if (args == null) { @@ -23,11 +27,16 @@ namespace Microsoft.DotNet.Watcher { Name = "dotnet watch", FullName = "Microsoft DotNet File Watcher", - Out = consoleOutput, + Out = stdout, + Error = stderr, AllowArgumentSeparator = true }; app.HelpOption("-?|-h|--help"); + var optQuiet = app.Option("-q|--quiet", "Suppresses all output except warnings and errors", + CommandOptionType.NoValue); + var optVerbose = app.Option("-v|--verbose", "Show verbose output", + CommandOptionType.NoValue); app.OnExecute(() => { @@ -44,8 +53,16 @@ namespace Microsoft.DotNet.Watcher return null; } + if (optQuiet.HasValue() && optVerbose.HasValue()) + { + stderr.WriteLine(Resources.Error_QuietAndVerboseSpecified.Bold().Red()); + return null; + } + return new CommandLineOptions { + IsQuiet = optQuiet.HasValue(), + IsVerbose = optVerbose.HasValue(), RemainingArguments = app.RemainingArguments, IsHelp = app.IsShowingInformation }; diff --git a/src/Microsoft.DotNet.Watcher.Tools/CommandOutputLogger.cs b/src/Microsoft.DotNet.Watcher.Tools/CommandOutputLogger.cs index aeb90f45c2..43b2da4507 100644 --- a/src/Microsoft.DotNet.Watcher.Tools/CommandOutputLogger.cs +++ b/src/Microsoft.DotNet.Watcher.Tools/CommandOutputLogger.cs @@ -50,12 +50,12 @@ namespace Microsoft.DotNet.Watcher { switch (logLevel) { - case LogLevel.Trace: return "\x1b[35mtrace\x1b[39m"; - case LogLevel.Debug: return "\x1b[35mdebug\x1b[39m"; + case LogLevel.Trace: return "\x1b[35mtrce\x1b[39m"; + case LogLevel.Debug: return "\x1b[35mdbug\x1b[39m"; case LogLevel.Information: return "\x1b[32minfo\x1b[39m"; case LogLevel.Warning: return "\x1b[33mwarn\x1b[39m"; case LogLevel.Error: return "\x1b[31mfail\x1b[39m"; - case LogLevel.Critical: return "\x1b[31mcritical\x1b[39m"; + case LogLevel.Critical: return "\x1b[31mcrit\x1b[39m"; } throw new Exception("Unknown LogLevel"); diff --git a/src/Microsoft.DotNet.Watcher.Tools/DotNetWatcher.cs b/src/Microsoft.DotNet.Watcher.Tools/DotNetWatcher.cs index d159af747f..1d9f2fd0fb 100644 --- a/src/Microsoft.DotNet.Watcher.Tools/DotNetWatcher.cs +++ b/src/Microsoft.DotNet.Watcher.Tools/DotNetWatcher.cs @@ -121,7 +121,7 @@ namespace Microsoft.DotNet.Watcher private Task WaitForDotnetToExitAsync(string dotnetArguments, string workingDir, CancellationToken cancellationToken) { - _logger.LogInformation($"Running dotnet with the following arguments: {dotnetArguments}"); + _logger.LogDebug($"Running dotnet with the following arguments: {dotnetArguments}"); var dotnetWatcher = _processWatcherFactory(); int dotnetProcessId = dotnetWatcher.Start("dotnet", dotnetArguments, workingDir); diff --git a/src/Microsoft.DotNet.Watcher.Tools/Program.cs b/src/Microsoft.DotNet.Watcher.Tools/Program.cs index d96726f258..71cdc7f5d6 100644 --- a/src/Microsoft.DotNet.Watcher.Tools/Program.cs +++ b/src/Microsoft.DotNet.Watcher.Tools/Program.cs @@ -6,16 +6,18 @@ using System.IO; using System.Threading; using System.Threading.Tasks; using Microsoft.Extensions.Logging; +using Microsoft.DotNet.Cli.Utils; namespace Microsoft.DotNet.Watcher { public class Program { - private readonly ILoggerFactory _loggerFactory; + private readonly ILoggerFactory _loggerFactory = new LoggerFactory(); private readonly CancellationToken _cancellationToken; - private readonly TextWriter _out; + private readonly TextWriter _stdout; + private readonly TextWriter _stderr; - public Program(TextWriter consoleOutput, CancellationToken cancellationToken) + public Program(TextWriter consoleOutput, TextWriter consoleError, CancellationToken cancellationToken) { if (consoleOutput == null) { @@ -28,23 +30,8 @@ namespace Microsoft.DotNet.Watcher } _cancellationToken = cancellationToken; - _out = consoleOutput; - - _loggerFactory = new LoggerFactory(); - - var logVar = Environment.GetEnvironmentVariable("DOTNET_WATCH_LOG_LEVEL"); - - LogLevel logLevel; - if (string.IsNullOrEmpty(logVar) || !Enum.TryParse(logVar, out logLevel)) - { - logLevel = LogLevel.Information; - } - - var commandProvider = new CommandOutputProvider() - { - LogLevel = logLevel - }; - _loggerFactory.AddProvider(commandProvider); + _stdout = consoleOutput; + _stderr = consoleError; } public static int Main(string[] args) @@ -60,7 +47,7 @@ namespace Microsoft.DotNet.Watcher int exitCode; try { - exitCode = new Program(Console.Out, ctrlCTokenSource.Token) + exitCode = new Program(Console.Out, Console.Error, ctrlCTokenSource.Token) .MainInternalAsync(args) .GetAwaiter() .GetResult(); @@ -76,7 +63,7 @@ namespace Microsoft.DotNet.Watcher private async Task MainInternalAsync(string[] args) { - var options = CommandLineOptions.Parse(args, _out); + var options = CommandLineOptions.Parse(args, _stdout, _stdout); if (options == null) { // invalid args syntax @@ -88,6 +75,12 @@ namespace Microsoft.DotNet.Watcher return 2; } + var commandProvider = new CommandOutputProvider + { + LogLevel = ResolveLogLevel(options) + }; + _loggerFactory.AddProvider(commandProvider); + var projectToWatch = Path.Combine(Directory.GetCurrentDirectory(), ProjectModel.Project.FileName); await DotNetWatcher @@ -96,5 +89,24 @@ namespace Microsoft.DotNet.Watcher return 0; } + + private LogLevel ResolveLogLevel(CommandLineOptions options) + { + if (options.IsQuiet) + { + return LogLevel.Warning; + } + + bool globalVerbose; + bool.TryParse(Environment.GetEnvironmentVariable(CommandContext.Variables.Verbose), out globalVerbose); + + if (options.IsVerbose // dotnet watch --verbose + || globalVerbose) // dotnet --verbose watch + { + return LogLevel.Debug; + } + + return LogLevel.Information; + } } } diff --git a/src/Microsoft.DotNet.Watcher.Tools/Properties/Resources.Designer.cs b/src/Microsoft.DotNet.Watcher.Tools/Properties/Resources.Designer.cs new file mode 100644 index 0000000000..92987141e4 --- /dev/null +++ b/src/Microsoft.DotNet.Watcher.Tools/Properties/Resources.Designer.cs @@ -0,0 +1,46 @@ +// +namespace Microsoft.DotNet.Watcher.Tools +{ + using System.Globalization; + using System.Reflection; + using System.Resources; + + internal static class Resources + { + private static readonly ResourceManager _resourceManager + = new ResourceManager("Microsoft.DotNet.Watcher.Tools.Resources", typeof(Resources).GetTypeInfo().Assembly); + + /// + /// Cannot specify both '--quiet' and '--verbose' options. + /// + internal static string Error_QuietAndVerboseSpecified + { + get { return GetString("Error_QuietAndVerboseSpecified"); } + } + + /// + /// Cannot specify both '--quiet' and '--verbose' options. + /// + internal static string FormatError_QuietAndVerboseSpecified() + { + return GetString("Error_QuietAndVerboseSpecified"); + } + + private static string GetString(string name, params string[] formatterNames) + { + var value = _resourceManager.GetString(name); + + System.Diagnostics.Debug.Assert(value != null); + + if (formatterNames != null) + { + for (var i = 0; i < formatterNames.Length; i++) + { + value = value.Replace("{" + formatterNames[i] + "}", "{" + i + "}"); + } + } + + return value; + } + } +} diff --git a/src/Microsoft.DotNet.Watcher.Tools/README.md b/src/Microsoft.DotNet.Watcher.Tools/README.md index 8c47b9c58c..e97e579e1b 100644 --- a/src/Microsoft.DotNet.Watcher.Tools/README.md +++ b/src/Microsoft.DotNet.Watcher.Tools/README.md @@ -20,7 +20,12 @@ Add `Microsoft.DotNet.Watcher.Tools` to the `tools` section of your `project.jso dotnet watch [-?|-h|--help] - dotnet watch [[--] ...] + dotnet watch [options] [[--] ...] + + Options: + -?|-h|--help Show help information + -q|--quiet Suppresses all output except warnings and errors + -v|--verbose Show verbose output Add `watch` after `dotnet` in the command that you want to run: @@ -31,11 +36,10 @@ Add `watch` after `dotnet` in the command that you want to run: | dotnet run --framework net451 -- --arg1 value1 | dotnet **watch** run --framework net451 -- --arg1 value1 | | dotnet test | dotnet **watch** test | -### Advanced configuration options +### Environment variables -Configuration options can be passed to `dotnet watch` through environment variables. The available variables are: +Some configuration options can be passed to `dotnet watch` through environment variables. The available variables are: | Variable | Effect | | ---------------------------------------------- | -------------------------------------------------------- | | DOTNET_USE_POLLING_FILE_WATCHER | If set to "1" or "true", `dotnet watch` will use a polling file watcher instead of CoreFx's `FileSystemWatcher`. Used when watching files on network shares or Docker mounted volumes. | -| DOTNET_WATCH_LOG_LEVEL | Used to set the logging level for messages coming from `dotnet watch`. Accepted values `None`, `Trace`, `Debug`, `Information`, `Warning`, `Error`, `Critical`. Default: `Information`. | diff --git a/src/Microsoft.DotNet.Watcher.Tools/Resources.resx b/src/Microsoft.DotNet.Watcher.Tools/Resources.resx new file mode 100644 index 0000000000..47ccd0a26c --- /dev/null +++ b/src/Microsoft.DotNet.Watcher.Tools/Resources.resx @@ -0,0 +1,123 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Cannot specify both '--quiet' and '--verbose' options. + + \ No newline at end of file diff --git a/test/Microsoft.DotNet.Watcher.Tools.Tests/CommandLineOptionsTests.cs b/test/Microsoft.DotNet.Watcher.Tools.Tests/CommandLineOptionsTests.cs index 306cddaacd..be63a361fc 100644 --- a/test/Microsoft.DotNet.Watcher.Tools.Tests/CommandLineOptionsTests.cs +++ b/test/Microsoft.DotNet.Watcher.Tools.Tests/CommandLineOptionsTests.cs @@ -21,7 +21,7 @@ namespace Microsoft.DotNet.Watcher.Tools.FunctionalTests { var stdout = new StringBuilder(); - var options = CommandLineOptions.Parse(args, new StringWriter(stdout)); + var options = CommandLineOptions.Parse(args, new StringWriter(stdout), new StringWriter()); Assert.True(options.IsHelp); Assert.Contains("Usage: dotnet watch ", stdout.ToString()); @@ -36,11 +36,20 @@ namespace Microsoft.DotNet.Watcher.Tools.FunctionalTests { var stdout = new StringBuilder(); - var options = CommandLineOptions.Parse(args, new StringWriter(stdout)); + var options = CommandLineOptions.Parse(args, new StringWriter(stdout), new StringWriter()); Assert.Equal(expected, options.RemainingArguments.ToArray()); Assert.False(options.IsHelp); Assert.Empty(stdout.ToString()); } + + [Fact] + public void CannotHaveQuietAndVerbose() + { + var sb = new StringBuilder(); + var stderr = new StringWriter(sb); + Assert.Null(CommandLineOptions.Parse(new[] { "--quiet", "--verbose" }, new StringWriter(), stderr)); + Assert.Contains(Resources.Error_QuietAndVerboseSpecified, sb.ToString()); + } } }