public Telemetry( IOptions <TelemetryOptions> options, IEnumerable <ITelemetryInitializer> initializers, IFirstTimeUseNoticeSentinel sentinel) { if (options is null) { throw new ArgumentNullException(nameof(options)); } _options = options.Value; Enabled = _options.IsEnabled && !EnvironmentHelper.GetEnvironmentVariableAsBool(_options.TelemetryOptout) && PermissionExists(sentinel); if (!Enabled) { return; } _telemetryConfig = TelemetryConfiguration.CreateDefault(); foreach (var initializer in initializers) { _telemetryConfig.TelemetryInitializers.Add(initializer); } _client = new TelemetryClient(_telemetryConfig) { InstrumentationKey = _options.InstrumentationKey }; _client.Context.Session.Id = _options.CurrentSessionId; _client.Context.Device.OperatingSystem = RuntimeEnvironment.OperatingSystem; }
private static void ConfigureDotNetForFirstTimeUse( INuGetCacheSentinel nugetCacheSentinel, IFirstTimeUseNoticeSentinel firstTimeUseNoticeSentinel, CliFallbackFolderPathCalculator cliFallbackFolderPathCalculator) { using (PerfTrace.Current.CaptureTiming()) { var nugetPackagesArchiver = new NuGetPackagesArchiver(); var environmentProvider = new EnvironmentProvider(); var commandFactory = new DotNetCommandFactory(alwaysRunOutOfProc: true); var nugetCachePrimer = new NuGetCachePrimer( nugetPackagesArchiver, nugetCacheSentinel, cliFallbackFolderPathCalculator); var dotnetConfigurer = new DotnetFirstTimeUseConfigurer( nugetCachePrimer, nugetCacheSentinel, firstTimeUseNoticeSentinel, environmentProvider, Reporter.Output, cliFallbackFolderPathCalculator.CliFallbackFolderPath); dotnetConfigurer.Configure(); } }
private static void ConfigureDotNetForFirstTimeUse( IFirstTimeUseNoticeSentinel firstTimeUseNoticeSentinel, IAspNetCertificateSentinel aspNetCertificateSentinel, IFileSentinel toolPathSentinel, bool isDotnetBeingInvokedFromNativeInstaller, DotnetFirstRunConfiguration dotnetFirstRunConfiguration, IEnvironmentProvider environmentProvider, Dictionary <string, double> performanceMeasurements) { var environmentPath = EnvironmentPathFactory.CreateEnvironmentPath(isDotnetBeingInvokedFromNativeInstaller, environmentProvider); var commandFactory = new DotNetCommandFactory(alwaysRunOutOfProc: true); var aspnetCertificateGenerator = new AspNetCoreCertificateGenerator(); var dotnetConfigurer = new DotnetFirstTimeUseConfigurer( firstTimeUseNoticeSentinel, aspNetCertificateSentinel, aspnetCertificateGenerator, toolPathSentinel, dotnetFirstRunConfiguration, Reporter.Output, CliFolderPathCalculator.CliFallbackFolderPath, environmentPath, performanceMeasurements); dotnetConfigurer.Configure(); if (isDotnetBeingInvokedFromNativeInstaller && OperatingSystem.IsWindows()) { DotDefaultPathCorrector.Correct(); } }
private static void ConfigureDotNetForFirstTimeUse( INuGetCacheSentinel nugetCacheSentinel, IFirstTimeUseNoticeSentinel firstTimeUseNoticeSentinel, IAspNetCertificateSentinel aspNetCertificateSentinel, CliFolderPathCalculator cliFolderPathCalculator, bool hasSuperUserAccess) { var environmentProvider = new EnvironmentProvider(); using (PerfTrace.Current.CaptureTiming()) { var nugetPackagesArchiver = new NuGetPackagesArchiver(); var environmentPath = EnvironmentPathFactory.CreateEnvironmentPath(cliFolderPathCalculator, hasSuperUserAccess, environmentProvider); var commandFactory = new DotNetCommandFactory(alwaysRunOutOfProc: true); var nugetCachePrimer = new NuGetCachePrimer( nugetPackagesArchiver, nugetCacheSentinel, cliFolderPathCalculator); var aspnetCertificateGenerator = new AspNetCoreCertificateGenerator(); var dotnetConfigurer = new DotnetFirstTimeUseConfigurer( nugetCachePrimer, nugetCacheSentinel, firstTimeUseNoticeSentinel, aspNetCertificateSentinel, aspnetCertificateGenerator, environmentProvider, Reporter.Output, cliFolderPathCalculator.CliFallbackFolderPath, environmentPath); dotnetConfigurer.Configure(); } }
private static void ConfigureDotNetForFirstTimeUse( IFirstTimeUseNoticeSentinel firstTimeUseNoticeSentinel, IAspNetCertificateSentinel aspNetCertificateSentinel, IFileSentinel toolPathSentinel, bool isDotnetBeingInvokedFromNativeInstaller, DotnetFirstRunConfiguration dotnetFirstRunConfiguration, IEnvironmentProvider environmentProvider) { using (PerfTrace.Current.CaptureTiming()) { var environmentPath = EnvironmentPathFactory.CreateEnvironmentPath(isDotnetBeingInvokedFromNativeInstaller, environmentProvider); var commandFactory = new DotNetCommandFactory(alwaysRunOutOfProc: true); var aspnetCertificateGenerator = new AspNetCoreCertificateGenerator(); var dotnetConfigurer = new DotnetFirstTimeUseConfigurer( firstTimeUseNoticeSentinel, aspNetCertificateSentinel, aspnetCertificateGenerator, toolPathSentinel, dotnetFirstRunConfiguration, Reporter.Output, CliFolderPathCalculator.CliFallbackFolderPath, environmentPath); dotnetConfigurer.Configure(); if (isDotnetBeingInvokedFromNativeInstaller && RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) { DotDefaultPathCorrector.Correct(); } } }
public Telemetry( IFirstTimeUseNoticeSentinel sentinel, string sessionId, bool blockThreadInitialization = false, IEnvironmentProvider environmentProvider = null, int senderCount = 3) { if (environmentProvider == null) { environmentProvider = new EnvironmentProvider(); } Enabled = !environmentProvider.GetEnvironmentVariableAsBool(TelemetryOptout, false) && PermissionExists(sentinel); if (!Enabled) { return; } // Store the session ID in a static field so that it can be reused CurrentSessionId = sessionId ?? Guid.NewGuid().ToString(); _senderCount = senderCount; if (blockThreadInitialization) { InitializeTelemetry(); } else { //initialize in task to offload to parallel thread _trackEventTask = Task.Run(() => InitializeTelemetry()); } }
public Telemetry( string productVersion, IFirstTimeUseNoticeSentinel sentinel = null, string sessionId = null, bool blockThreadInitialization = false) { FirstTimeUseNoticeSentinel = sentinel ?? new FirstTimeUseNoticeSentinel(productVersion); Enabled = !EnvironmentHelper.GetEnvironmentVariableAsBool(TelemetryOptout) && PermissionExists(FirstTimeUseNoticeSentinel); if (!Enabled) { return; } // Store the session ID in a static field so that it can be reused CurrentSessionId = sessionId ?? Guid.NewGuid().ToString(); if (blockThreadInitialization) { InitializeTelemetry(productVersion); } else { //initialize in task to offload to parallel thread _trackEventTask = Task.Factory.StartNew(() => InitializeTelemetry(productVersion), CancellationToken.None, TaskCreationOptions.None, TaskScheduler.Default); } }
private static void ConfigureDotNetForFirstTimeUse( IFirstTimeUseNoticeSentinel firstTimeUseNoticeSentinel, IAspNetCertificateSentinel aspNetCertificateSentinel, IFileSentinel toolPathSentinel, bool hasSuperUserAccess, DotnetFirstRunConfiguration dotnetFirstRunConfiguration, IEnvironmentProvider environmentProvider) { using (PerfTrace.Current.CaptureTiming()) { var environmentPath = EnvironmentPathFactory.CreateEnvironmentPath(hasSuperUserAccess, environmentProvider); var commandFactory = new DotNetCommandFactory(alwaysRunOutOfProc: true); var aspnetCertificateGenerator = new AspNetCoreCertificateGenerator(); var dotnetConfigurer = new DotnetFirstTimeUseConfigurer( firstTimeUseNoticeSentinel, aspNetCertificateSentinel, aspnetCertificateGenerator, toolPathSentinel, dotnetFirstRunConfiguration, Reporter.Output, CliFolderPathCalculator.CliFallbackFolderPath, environmentPath); dotnetConfigurer.Configure(); } }
public Telemetry( string productVersion, IFirstTimeUseNoticeSentinel sentinel, string eventsNamespace, string sessionId = null, bool blockThreadInitialization = false) { if (string.IsNullOrWhiteSpace(eventsNamespace)) { throw new ArgumentException("Value cannot be null or whitespace.", nameof(eventsNamespace)); } _eventsNamespace = eventsNamespace; Enabled = !GetEnvironmentVariableAsBool(TelemetryOptout) && PermissionExists(sentinel); if (!Enabled) { return; } // Store the session ID in a static field so that it can be reused CurrentSessionId = sessionId ?? Guid.NewGuid().ToString(); if (blockThreadInitialization) { InitializeTelemetry(productVersion); } else { //initialize in task to offload to parallel thread _trackEventTask = Task.Factory.StartNew(() => InitializeTelemetry(productVersion)); } }
private bool PermissionExists(IFirstTimeUseNoticeSentinel sentinel) { if (sentinel == null) { return(false); } return(sentinel.Exists()); }
public Telemetry(IFirstTimeUseNoticeSentinel sentinel, string sessionId) { Enabled = !Env.GetEnvironmentVariableAsBool(TelemetryOptout) && PermissionExists(sentinel); if (!Enabled) { return; } // Store the session ID in a static field so that it can be reused CurrentSessionId = sessionId ?? Guid.NewGuid().ToString(); //initialize in task to offload to parallel thread _trackEventTask = Task.Factory.StartNew(() => InitializeTelemetry()); }
public DotnetFirstTimeUseConfigurer( INuGetCachePrimer nugetCachePrimer, INuGetCacheSentinel nugetCacheSentinel, IFirstTimeUseNoticeSentinel firstTimeUseNoticeSentinel, IEnvironmentProvider environmentProvider, IReporter reporter, string cliFallbackFolderPath) { _nugetCachePrimer = nugetCachePrimer; _nugetCacheSentinel = nugetCacheSentinel; _firstTimeUseNoticeSentinel = firstTimeUseNoticeSentinel; _environmentProvider = environmentProvider; _reporter = reporter; _cliFallbackFolderPath = cliFallbackFolderPath; }
public DotnetFirstTimeUseConfigurer( INuGetCachePrimer nugetCachePrimer, INuGetCacheSentinel nugetCacheSentinel, IFirstTimeUseNoticeSentinel firstTimeUseNoticeSentinel, IEnvironmentProvider environmentProvider, IReporter reporter, string cliFallbackFolderPath, IEnvironmentPath pathAdder) { _nugetCachePrimer = nugetCachePrimer; _nugetCacheSentinel = nugetCacheSentinel; _firstTimeUseNoticeSentinel = firstTimeUseNoticeSentinel; _environmentProvider = environmentProvider; _reporter = reporter; _cliFallbackFolderPath = cliFallbackFolderPath; _pathAdder = pathAdder ?? throw new ArgumentNullException(nameof(pathAdder)); }
public DotnetFirstTimeUseConfigurer( IFirstTimeUseNoticeSentinel firstTimeUseNoticeSentinel, IAspNetCertificateSentinel aspNetCertificateSentinel, IAspNetCoreCertificateGenerator aspNetCoreCertificateGenerator, IFileSentinel toolPathSentinel, DotnetFirstRunConfiguration dotnetFirstRunConfiguration, IReporter reporter, string cliFallbackFolderPath, IEnvironmentPath pathAdder) { _firstTimeUseNoticeSentinel = firstTimeUseNoticeSentinel; _aspNetCertificateSentinel = aspNetCertificateSentinel; _aspNetCoreCertificateGenerator = aspNetCoreCertificateGenerator; _toolPathSentinel = toolPathSentinel; _dotnetFirstRunConfiguration = dotnetFirstRunConfiguration; _reporter = reporter; _cliFallbackFolderPath = cliFallbackFolderPath; _pathAdder = pathAdder ?? throw new ArgumentNullException(nameof(pathAdder)); }
public DotnetFirstTimeUseConfigurer( INuGetCachePrimer nugetCachePrimer, INuGetCacheSentinel nugetCacheSentinel, IFirstTimeUseNoticeSentinel firstTimeUseNoticeSentinel, IAspNetCertificateSentinel aspNetCertificateSentinel, IAspNetCoreCertificateGenerator aspNetCoreCertificateGenerator, IFileSentinel toolPathSentinel, IEnvironmentProvider environmentProvider, IReporter reporter, string cliFallbackFolderPath, IEnvironmentPath pathAdder) { _nugetCachePrimer = nugetCachePrimer; _nugetCacheSentinel = nugetCacheSentinel; _firstTimeUseNoticeSentinel = firstTimeUseNoticeSentinel; _aspNetCertificateSentinel = aspNetCertificateSentinel; _aspNetCoreCertificateGenerator = aspNetCoreCertificateGenerator; _toolPathSentinel = toolPathSentinel; _environmentProvider = environmentProvider; _reporter = reporter; _cliFallbackFolderPath = cliFallbackFolderPath; _pathAdder = pathAdder ?? throw new ArgumentNullException(nameof(pathAdder)); }
internal static int ProcessArgs(string[] args, TimeSpan startupTime, ITelemetry telemetryClient = null) { Dictionary <string, double> performanceData = new Dictionary <string, double>(); PerformanceLogEventSource.Log.BuiltInCommandParserStart(); Stopwatch parseStartTime = Stopwatch.StartNew(); var parseResult = Parser.Instance.Parse(args); // Avoid create temp directory with root permission and later prevent access in non sudo // This method need to be run very early before temp folder get created // https://github.com/dotnet/sdk/issues/20195 SudoEnvironmentDirectoryOverride.OverrideEnvironmentVariableToTmp(parseResult); performanceData.Add("Parse Time", parseStartTime.Elapsed.TotalMilliseconds); PerformanceLogEventSource.Log.BuiltInCommandParserStop(); using (IFirstTimeUseNoticeSentinel disposableFirstTimeUseNoticeSentinel = new FirstTimeUseNoticeSentinel()) { IFirstTimeUseNoticeSentinel firstTimeUseNoticeSentinel = disposableFirstTimeUseNoticeSentinel; IAspNetCertificateSentinel aspNetCertificateSentinel = new AspNetCertificateSentinel(); IFileSentinel toolPathSentinel = new FileSentinel( new FilePath( Path.Combine( CliFolderPathCalculator.DotnetUserProfileFolderPath, ToolPathSentinelFileName))); if (parseResult.GetValueForOption(Parser.DiagOption) && parseResult.IsDotnetBuiltInCommand()) { Environment.SetEnvironmentVariable(CommandContext.Variables.Verbose, bool.TrueString); CommandContext.SetVerbose(true); Reporter.Reset(); } if (parseResult.HasOption(Parser.VersionOption) && parseResult.IsTopLevelDotnetCommand()) { CommandLineInfo.PrintVersion(); return(0); } else if (parseResult.HasOption(Parser.InfoOption) && parseResult.IsTopLevelDotnetCommand()) { CommandLineInfo.PrintInfo(); return(0); } else { PerformanceLogEventSource.Log.FirstTimeConfigurationStart(); var environmentProvider = new EnvironmentProvider(); bool generateAspNetCertificate = environmentProvider.GetEnvironmentVariableAsBool("DOTNET_GENERATE_ASPNET_CERTIFICATE", defaultValue: true); bool telemetryOptout = environmentProvider.GetEnvironmentVariableAsBool("DOTNET_CLI_TELEMETRY_OPTOUT", defaultValue: false); bool addGlobalToolsToPath = environmentProvider.GetEnvironmentVariableAsBool("DOTNET_ADD_GLOBAL_TOOLS_TO_PATH", defaultValue: true); bool nologo = environmentProvider.GetEnvironmentVariableAsBool("DOTNET_NOLOGO", defaultValue: false); ReportDotnetHomeUsage(environmentProvider); var isDotnetBeingInvokedFromNativeInstaller = false; if (parseResult.CommandResult.Command.Name.Equals(Parser.InstallSuccessCommand.Name)) { aspNetCertificateSentinel = new NoOpAspNetCertificateSentinel(); firstTimeUseNoticeSentinel = new NoOpFirstTimeUseNoticeSentinel(); toolPathSentinel = new NoOpFileSentinel(exists: false); isDotnetBeingInvokedFromNativeInstaller = true; } var dotnetFirstRunConfiguration = new DotnetFirstRunConfiguration( generateAspNetCertificate: generateAspNetCertificate, telemetryOptout: telemetryOptout, addGlobalToolsToPath: addGlobalToolsToPath, nologo: nologo); ConfigureDotNetForFirstTimeUse( firstTimeUseNoticeSentinel, aspNetCertificateSentinel, toolPathSentinel, isDotnetBeingInvokedFromNativeInstaller, dotnetFirstRunConfiguration, environmentProvider, performanceData); PerformanceLogEventSource.Log.FirstTimeConfigurationStop(); } PerformanceLogEventSource.Log.TelemetryRegistrationStart(); if (telemetryClient == null) { telemetryClient = new Telemetry.Telemetry(firstTimeUseNoticeSentinel); } TelemetryEventEntry.Subscribe(telemetryClient.TrackEvent); TelemetryEventEntry.TelemetryFilter = new TelemetryFilter(Sha256Hasher.HashWithNormalizedCasing); PerformanceLogEventSource.Log.TelemetryRegistrationStop(); } if (CommandContext.IsVerbose()) { Console.WriteLine($"Telemetry is: {(telemetryClient.Enabled ? "Enabled" : "Disabled")}"); } PerformanceLogEventSource.Log.TelemetrySaveIfEnabledStart(); performanceData.Add("Startup Time", startupTime.TotalMilliseconds); TelemetryEventEntry.SendFiltered(Tuple.Create(parseResult, performanceData)); PerformanceLogEventSource.Log.TelemetrySaveIfEnabledStop(); int exitCode; if (parseResult.CanBeInvoked()) { PerformanceLogEventSource.Log.BuiltInCommandStart(); exitCode = parseResult.Invoke(); PerformanceLogEventSource.Log.BuiltInCommandStop(); } else { PerformanceLogEventSource.Log.ExtensibleCommandResolverStart(); var resolvedCommand = CommandFactoryUsingResolver.Create( "dotnet-" + parseResult.GetValueForArgument(Parser.DotnetSubCommand), args.GetSubArguments(), FrameworkConstants.CommonFrameworks.NetStandardApp15); PerformanceLogEventSource.Log.ExtensibleCommandResolverStop(); PerformanceLogEventSource.Log.ExtensibleCommandStart(); var result = resolvedCommand.Execute(); PerformanceLogEventSource.Log.ExtensibleCommandStop(); exitCode = result.ExitCode; } PerformanceLogEventSource.Log.TelemetryClientFlushStart(); telemetryClient.Flush(); PerformanceLogEventSource.Log.TelemetryClientFlushStop(); return(exitCode); }
public ConsoleFirstTimeUserNotifier(IFirstTimeUseNoticeSentinel sentinel, InputOutputStreams io) { _sentinel = sentinel ?? throw new ArgumentNullException(nameof(sentinel)); _io = io ?? throw new ArgumentNullException(nameof(io)); }
public static Parser Create( IServiceCollection services, StartServer startServer = null, Jupyter jupyter = null, StartStdIO startStdIO = null, StartHttp startHttp = null, Action onServerStarted = null, ITelemetry telemetry = null, IFirstTimeUseNoticeSentinel firstTimeUseNoticeSentinel = null) { var operation = Log.OnEnterAndExit(); if (services == null) { throw new ArgumentNullException(nameof(services)); } var disposeOnQuit = new CompositeDisposable(); startServer ??= (startupOptions, invocationContext) => { operation.Info("constructing webhost"); var webHost = Program.ConstructWebHost(startupOptions); disposeOnQuit.Add(webHost); operation.Info("starting kestrel server"); webHost.Start(); onServerStarted?.Invoke(); webHost.WaitForShutdown(); operation.Dispose(); }; jupyter ??= JupyterCommand.Do; startStdIO ??= StdIOCommand.Do; startHttp ??= HttpCommand.Do; // Setup first time use notice sentinel. firstTimeUseNoticeSentinel ??= new FirstTimeUseNoticeSentinel(VersionSensor.Version().AssemblyInformationalVersion); var clearTextProperties = new[] { "frontend" }; // Setup telemetry. telemetry ??= new Telemetry.Telemetry( VersionSensor.Version().AssemblyInformationalVersion, firstTimeUseNoticeSentinel, "dotnet/interactive/cli"); var filter = new TelemetryFilter( Sha256Hasher.HashWithNormalizedCasing, clearTextProperties, (commandResult, directives, entryItems) => { // add frontend var frontendTelemetryAdded = false; foreach (var directive in directives) { switch (directive.Key) { case "vscode": case "jupyter": case "synapse": frontendTelemetryAdded = true; entryItems.Add(new KeyValuePair <string, string>("frontend", directive.Key)); break; } } if (!frontendTelemetryAdded) { if (commandResult.Command.Name == "jupyter") { entryItems.Add(new KeyValuePair <string, string>("frontend", "jupyter")); } else { entryItems.Add(new KeyValuePair <string, string>("frontend", "unknown")); } } }); var verboseOption = new Option <bool>( "--verbose", "Enable verbose logging to the console"); var logPathOption = new Option <DirectoryInfo>( "--log-path", "Enable file logging to the specified directory"); var pathOption = new Option <DirectoryInfo>( "--path", "Installs the kernelspecs to the specified directory") .ExistingOnly(); var defaultKernelOption = new Option <string>( "--default-kernel", description: "The default language for the kernel", getDefaultValue: () => "csharp"); var rootCommand = DotnetInteractive(); rootCommand.AddCommand(Jupyter()); rootCommand.AddCommand(StdIO()); rootCommand.AddCommand(HttpServer()); return(new CommandLineBuilder(rootCommand) .UseDefaults() .UseMiddleware(async(context, next) => { if (context.ParseResult.Errors.Count == 0) { telemetry.SendFiltered(filter, context.ParseResult); } // If sentinel does not exist, print the welcome message showing the telemetry notification. if (!Telemetry.Telemetry.SkipFirstTimeExperience && !firstTimeUseNoticeSentinel.Exists()) { context.Console.Out.WriteLine(); context.Console.Out.WriteLine(Telemetry.Telemetry.WelcomeMessage); firstTimeUseNoticeSentinel.CreateIfNotExists(); } await next(context); }) .Build()); RootCommand DotnetInteractive() { var command = new RootCommand { Name = "dotnet-interactive", Description = "Interactive programming for .NET." }; command.AddGlobalOption(logPathOption); command.AddGlobalOption(verboseOption); return(command); } Command Jupyter() { var httpPortRangeOption = new Option <HttpPortRange>( "--http-port-range", parseArgument: result => result.Tokens.Count == 0 ? HttpPortRange.Default : ParsePortRangeOption(result), description: "Specifies the range of ports to use to enable HTTP services", isDefault: true); var jupyterCommand = new Command("jupyter", "Starts dotnet-interactive as a Jupyter kernel") { defaultKernelOption, httpPortRangeOption, new Argument <FileInfo> { Name = "connection-file" }.ExistingOnly() }; jupyterCommand.Handler = CommandHandler.Create <StartupOptions, JupyterOptions, IConsole, InvocationContext, CancellationToken>(JupyterHandler); var installCommand = new Command("install", "Install the .NET kernel for Jupyter") { httpPortRangeOption, pathOption }; installCommand.Handler = CommandHandler.Create <IConsole, InvocationContext, HttpPortRange, DirectoryInfo>(InstallHandler); jupyterCommand.AddCommand(installCommand); return(jupyterCommand); Task <int> JupyterHandler(StartupOptions startupOptions, JupyterOptions options, IConsole console, InvocationContext context, CancellationToken cancellationToken) { var frontendEnvironment = new HtmlNotebookFrontedEnvironment(); var kernel = CreateKernel(options.DefaultKernel, frontendEnvironment, startupOptions); services.AddKernel(kernel); services.AddSingleton(c => ConnectionInformation.Load(options.ConnectionFile)) .AddSingleton(c => { return(new JupyterRequestContextScheduler(delivery => c.GetRequiredService <JupyterRequestContextHandler>() .Handle(delivery))); }) .AddSingleton(c => new JupyterRequestContextHandler(kernel)) .AddSingleton <IHostedService, Shell>() .AddSingleton <IHostedService, Heartbeat>(); return(jupyter(startupOptions, console, startServer, context)); } Task <int> InstallHandler(IConsole console, InvocationContext context, HttpPortRange httpPortRange, DirectoryInfo path) { var jupyterInstallCommand = new JupyterInstallCommand(console, new JupyterKernelSpecInstaller(console), httpPortRange, path); return(jupyterInstallCommand.InvokeAsync()); } } Command HttpServer() { var httpPortOption = new Option <HttpPort>( "--http-port", description: "Specifies the port on which to enable HTTP services", parseArgument: result => { if (result.Tokens.Count == 0) { return(HttpPort.Auto); } var source = result.Tokens[0].Value; if (source == "*") { return(HttpPort.Auto); } if (!int.TryParse(source, out var portNumber)) { result.ErrorMessage = "Must specify a port number or *."; return(null); } return(new HttpPort(portNumber)); }, isDefault: true); var httpCommand = new Command("http", "Starts dotnet-interactive with kernel functionality exposed over http") { defaultKernelOption, httpPortOption }; httpCommand.Handler = CommandHandler.Create <StartupOptions, KernelHttpOptions, IConsole, InvocationContext>( (startupOptions, options, console, context) => { var frontendEnvironment = new BrowserFrontendEnvironment(); var kernel = CreateKernel(options.DefaultKernel, frontendEnvironment, startupOptions); services.AddKernel(kernel); onServerStarted ??= () => { console.Out.WriteLine("Application started. Press Ctrl+C to shut down."); }; return(startHttp(startupOptions, console, startServer, context)); }); return(httpCommand); } Command StdIO() { var httpPortRangeOption = new Option <HttpPortRange>( "--http-port-range", parseArgument: result => result.Tokens.Count == 0 ? HttpPortRange.Default : ParsePortRangeOption(result), description: "Specifies the range of ports to use to enable HTTP services"); var httpPortOption = new Option <HttpPort>( "--http-port", description: "Specifies the port on which to enable HTTP services", parseArgument: result => { if (result.FindResultFor(httpPortRangeOption) is { } conflictingOption) { var parsed = result.Parent as OptionResult; result.ErrorMessage = $"Cannot specify both {conflictingOption.Token.Value} and {parsed.Token.Value} together"; return(null); } if (result.Tokens.Count == 0) { return(HttpPort.Auto); } var source = result.Tokens[0].Value; if (source == "*") { return(HttpPort.Auto); } if (!int.TryParse(source, out var portNumber)) { result.ErrorMessage = "Must specify a port number or *."; return(null); } return(new HttpPort(portNumber)); }); var workingDirOption = new Option <DirectoryInfo>( "--working-dir", () => new DirectoryInfo(Environment.CurrentDirectory), "Working directory to which to change after launching the kernel."); var stdIOCommand = new Command( "stdio", "Starts dotnet-interactive with kernel functionality exposed over standard I/O") { defaultKernelOption, httpPortRangeOption, httpPortOption, workingDirOption }; stdIOCommand.Handler = CommandHandler.Create <StartupOptions, StdIOOptions, IConsole, InvocationContext, CancellationToken>( (startupOptions, options, console, context, cancellationToken) => { FrontendEnvironment frontendEnvironment = startupOptions.EnableHttpApi ? new HtmlNotebookFrontedEnvironment() : new BrowserFrontendEnvironment(); var kernel = CreateKernel(options.DefaultKernel, frontendEnvironment, startupOptions); services.AddKernel(kernel); kernel.UseQuitCommand(disposeOnQuit, cancellationToken); var kernelServer = kernel.CreateKernelServer(startupOptions.WorkingDir); if (startupOptions.EnableHttpApi) { if (context.ParseResult.Directives.Contains("vscode")) { ((HtmlNotebookFrontedEnvironment)frontendEnvironment).RequiresAutomaticBootstrapping = false; } kernelServer.Start(); onServerStarted ??= () => { kernelServer.NotifyIsReady(); }; return(startHttp(startupOptions, console, startServer, context)); } disposeOnQuit.Add(kernel); return(startStdIO( startupOptions, kernelServer, console)); }); return(stdIOCommand); }
public static Parser Create( IServiceCollection services, StartServer startServer = null, Demo demo = null, TryGitHub tryGithub = null, Pack pack = null, Install install = null, Verify verify = null, Jupyter jupyter = null, StartKernelServer startKernelServer = null, ITelemetry telemetry = null, IFirstTimeUseNoticeSentinel firstTimeUseNoticeSentinel = null) { if (services == null) { throw new ArgumentNullException(nameof(services)); } startServer = startServer ?? ((startupOptions, invocationContext) => Program.ConstructWebHost(startupOptions).Run()); jupyter = jupyter ?? ((startupOptions, console, server, context) => JupyterCommand.Do(startupOptions, console, server, context)); demo = demo ?? DemoCommand.Do; tryGithub = tryGithub ?? ((repo, console) => GitHubHandler.Handler(repo, console, new GitHubRepoLocator())); verify = verify ?? ((options, console, startupOptions) => VerifyCommand.Do(options, console, startupOptions)); pack = pack ?? PackCommand.Do; install = install ?? InstallCommand.Do; startKernelServer = startKernelServer ?? ((startupOptions, kernel, console) => KernelServerCommand.Do(startupOptions, kernel, console)); // Setup first time use notice sentinel. firstTimeUseNoticeSentinel = firstTimeUseNoticeSentinel ?? new FirstTimeUseNoticeSentinel(); // Setup telemetry. telemetry = telemetry ?? new Telemetry.Telemetry(firstTimeUseNoticeSentinel); var filter = new TelemetryFilter(Sha256Hasher.HashWithNormalizedCasing); Action <ParseResult> track = o => telemetry.SendFiltered(filter, o); var dirArgument = new Argument <FileSystemDirectoryAccessor>(() => new FileSystemDirectoryAccessor(Directory.GetCurrentDirectory())) { Name = nameof(StartupOptions.RootDirectory), Arity = ArgumentArity.ZeroOrOne, Description = "Specify the path to the root directory for your documentation", }; dirArgument.AddValidator(symbolResult => { var directory = symbolResult.Tokens .Select(t => t.Value) .FirstOrDefault(); if (!string.IsNullOrEmpty(directory) && !Directory.Exists(directory)) { return($"Directory does not exist: {directory}"); } return(null); }); var rootCommand = StartInTryMode(); rootCommand.AddCommand(StartInHostedMode()); rootCommand.AddCommand(Demo()); rootCommand.AddCommand(GitHub()); rootCommand.AddCommand(Pack()); rootCommand.AddCommand(Install()); rootCommand.AddCommand(Verify()); rootCommand.AddCommand(Jupyter()); rootCommand.AddCommand(KernelServer()); return(new CommandLineBuilder(rootCommand) .UseDefaults() .UseMiddleware(async(context, next) => { // If sentinel does not exist, print the welcome message showing the telemetry notification. if (!firstTimeUseNoticeSentinel.Exists() && !Telemetry.Telemetry.SkipFirstTimeExperience) { context.Console.Out.WriteLine(); context.Console.Out.WriteLine(Telemetry.Telemetry.WelcomeMessage); firstTimeUseNoticeSentinel.CreateIfNotExists(); } if (context.ParseResult.Directives.Contains("debug") && !(Clock.Current is VirtualClock)) { VirtualClock.Start(); } await next(context); }) .Build()); RootCommand StartInTryMode() { var command = new RootCommand { Name = "dotnet-try", Description = "Interactive documentation in your browser" }; command.AddArgument(dirArgument); command.AddOption(new Option( "--add-package-source", "Specify an additional NuGet package source") { Argument = new Argument <PackageSource>(() => new PackageSource(Directory.GetCurrentDirectory())) { Name = "NuGet source" } }); command.AddOption(new Option( "--package", "Specify a Try .NET package or path to a .csproj to run code samples with") { Argument = new Argument <string> { Name = "name or .csproj" } }); command.AddOption(new Option( "--package-version", "Specify a Try .NET package version to use with the --package option") { Argument = new Argument <string> { Name = "version" } }); command.AddOption(new Option( "--uri", "Specify a URL or a relative path to a Markdown file") { Argument = new Argument <Uri>() }); command.AddOption(new Option( "--enable-preview-features", "Enable preview features") { Argument = new Argument <bool>() }); command.AddOption(new Option( "--log-path", "Enable file logging to the specified directory") { Argument = new Argument <DirectoryInfo> { Name = "dir" } }); command.AddOption(new Option( "--verbose", "Enable verbose logging to the console") { Argument = new Argument <bool>() }); var portArgument = new Argument <ushort>(); portArgument.AddValidator(symbolResult => { if (symbolResult.Tokens .Select(t => t.Value) .Count(value => !ushort.TryParse(value, out _)) > 0) { return("Invalid argument for --port option"); } return(null); }); command.AddOption(new Option( "--port", "Specify the port for dotnet try to listen on") { Argument = portArgument }); command.Handler = CommandHandler.Create <InvocationContext, StartupOptions>((context, options) => { services.AddSingleton(_ => PackageRegistry.CreateForTryMode( options.RootDirectory, options.AddPackageSource)); startServer(options, context); }); return(command); } Command StartInHostedMode() { var command = new Command("hosted") { new Option( "--id", "A unique id for the agent instance (e.g. its development environment id).") { Argument = new Argument <string>(defaultValue: () => Environment.MachineName) }, new Option( "--production", "Specifies whether the agent is being run using production resources") { Argument = new Argument <bool>() }, new Option( "--language-service", "Specifies whether the agent is being run in language service-only mode") { Argument = new Argument <bool>() }, new Option( new[] { "-k", "--key" }, "The encryption key") { Argument = new Argument <string>() }, new Option( new[] { "--ai-key", "--application-insights-key" }, "Application Insights key.") { Argument = new Argument <string>() }, new Option( "--region-id", "A unique id for the agent region") { Argument = new Argument <string>() }, new Option( "--log-to-file", "Writes a log file") { Argument = new Argument <bool>() } }; command.Description = "Starts the Try .NET agent"; command.IsHidden = true; command.Handler = CommandHandler.Create <InvocationContext, StartupOptions>((context, options) => { services.AddSingleton(_ => PackageRegistry.CreateForHostedMode()); services.AddSingleton(c => new MarkdownProject(c.GetRequiredService <PackageRegistry>())); services.AddSingleton <IHostedService, Warmup>(); startServer(options, context); }); return(command); } Command Demo() { var demoCommand = new Command( "demo", "Learn how to create Try .NET content with an interactive demo") { new Option("--output", "Where should the demo project be written to?") { Argument = new Argument <DirectoryInfo>( defaultValue: () => new DirectoryInfo(Directory.GetCurrentDirectory())) } }; demoCommand.Handler = CommandHandler.Create <DemoOptions, InvocationContext>((options, context) => { demo(options, context.Console, startServer, context); }); return(demoCommand); } Command GitHub() { var argument = new Argument <string> { // System.CommandLine parameter binding does lookup by name, // so name the argument after the github command's string param Name = nameof(TryGitHubOptions.Repo) }; var github = new Command("github", "Try a GitHub repo") { argument }; github.IsHidden = true; github.Handler = CommandHandler.Create <TryGitHubOptions, IConsole>((repo, console) => tryGithub(repo, console)); return(github); } Command Jupyter() { var jupyterCommand = new Command("jupyter", "Starts dotnet try as a Jupyter kernel"); var defaultKernelOption = new Option("--default-kernel", "The default .NET kernel language for the notebook.") { Argument = new Argument <string>(defaultValue: () => "csharp") }; jupyterCommand.AddOption(defaultKernelOption); var connectionFileArgument = new Argument <FileInfo> { Name = "ConnectionFile", Arity = ArgumentArity.ZeroOrOne //should be removed once the commandlineapi allows subcommands to not have arguments from the main command }.ExistingOnly(); jupyterCommand.AddArgument(connectionFileArgument); jupyterCommand.Handler = CommandHandler.Create <StartupOptions, JupyterOptions, IConsole, InvocationContext>((startupOptions, options, console, context) => { track(context.ParseResult); services .AddSingleton(c => ConnectionInformation.Load(options.ConnectionFile)) .AddSingleton( c => { return(CommandScheduler .Create <JupyterRequestContext>(delivery => c.GetRequiredService <ICommandHandler <JupyterRequestContext> >() .Trace() .Handle(delivery))); }) .AddSingleton(c => CreateKernel(options.DefaultKernel)) .AddSingleton(c => new JupyterRequestContextHandler(c.GetRequiredService <IKernel>()) .Trace()) .AddSingleton <IHostedService, Shell>() .AddSingleton <IHostedService, Heartbeat>(); return(jupyter(startupOptions, console, startServer, context)); }); var installCommand = new Command("install", "Install the .NET kernel for Jupyter"); installCommand.Handler = CommandHandler.Create <IConsole, InvocationContext>((console, context) => { track(context.ParseResult); return(new JupyterCommandLine(console, new FileSystemJupyterKernelSpec()).InvokeAsync()); }); jupyterCommand.AddCommand(installCommand); return(jupyterCommand); } Command KernelServer() { var startKernelServerCommand = new Command("kernel-server", "Starts dotnet-try with kernel functionality exposed over standard I/O"); var defaultKernelOption = new Option("--default-kernel", "The default .NET kernel language for the notebook.") { Argument = new Argument <string>(defaultValue: () => "csharp") }; startKernelServerCommand.AddOption(defaultKernelOption); startKernelServerCommand.Handler = CommandHandler.Create <StartupOptions, KernelServerOptions, IConsole, InvocationContext>( (startupOptions, options, console, context) => { track(context.ParseResult); return(startKernelServer(startupOptions, CreateKernel(options.DefaultKernel), console)); }); return(startKernelServerCommand); } Command Pack() { var packCommand = new Command("pack", "Create a Try .NET package") { new Argument <DirectoryInfo> { Name = nameof(PackOptions.PackTarget) }, new Option("--version", "The version of the Try .NET package") { Argument = new Argument <string>() }, new Option("--enable-wasm", "Enables web assembly code execution") }; packCommand.IsHidden = true; packCommand.Handler = CommandHandler.Create <PackOptions, IConsole>( (options, console) => { return(pack(options, console)); }); return(packCommand); } Command Install() { var installCommand = new Command("install", "Install a Try .NET package") { new Argument <string> { Name = nameof(InstallOptions.PackageName), Arity = ArgumentArity.ExactlyOne }, new Option("--add-source") { Argument = new Argument <PackageSource>() } }; installCommand.IsHidden = true; installCommand.Handler = CommandHandler.Create <InstallOptions, IConsole>((options, console) => install(options, console)); return(installCommand); } Command Verify() { var verifyCommand = new Command("verify", "Verify Markdown files in the target directory and its children.") { dirArgument }; verifyCommand.Handler = CommandHandler.Create <VerifyOptions, IConsole, StartupOptions>( (options, console, startupOptions) => { return(verify(options, console, startupOptions)); }); return(verifyCommand); } }
public static Parser Create( IServiceCollection services, StartServer startServer = null, Jupyter jupyter = null, StartKernelServer startKernelServer = null, ITelemetry telemetry = null, IFirstTimeUseNoticeSentinel firstTimeUseNoticeSentinel = null) { if (services == null) { throw new ArgumentNullException(nameof(services)); } startServer ??= ((startupOptions, invocationContext) => Program.ConstructWebHost(startupOptions).Run()); jupyter ??= JupyterCommand.Do; startKernelServer ??= (async(startupOptions, kernel, console) => { var disposable = Program.StartToolLogging(startupOptions); if (kernel is KernelBase kernelBase) { kernelBase.RegisterForDisposal(disposable); } var server = new StandardIOKernelServer( kernel, Console.In, Console.Out); await server.Input.LastAsync(); }); // Setup first time use notice sentinel. firstTimeUseNoticeSentinel ??= new FirstTimeUseNoticeSentinel(VersionSensor.Version().AssemblyInformationalVersion); // Setup telemetry. telemetry ??= new Telemetry.Telemetry( VersionSensor.Version().AssemblyInformationalVersion, firstTimeUseNoticeSentinel); var filter = new TelemetryFilter(Sha256Hasher.HashWithNormalizedCasing); void Track(ParseResult o) => telemetry.SendFiltered(filter, o); var verboseOption = new Option <bool>( "--verbose", "Enable verbose logging to the console"); var logPathOption = new Option <DirectoryInfo>( "--log-path", "Enable file logging to the specified directory"); var defaultKernelOption = new Option <string>( "--default-kernel", description: "The default language for the kernel", getDefaultValue: () => "csharp"); var rootCommand = DotnetInteractive(); rootCommand.AddCommand(Jupyter()); rootCommand.AddCommand(KernelServer()); return(new CommandLineBuilder(rootCommand) .UseDefaults() .UseMiddleware(async(context, next) => { // If sentinel does not exist, print the welcome message showing the telemetry notification. if (!firstTimeUseNoticeSentinel.Exists() && !Telemetry.Telemetry.SkipFirstTimeExperience) { context.Console.Out.WriteLine(); context.Console.Out.WriteLine(Telemetry.Telemetry.WelcomeMessage); firstTimeUseNoticeSentinel.CreateIfNotExists(); } await next(context); }) .Build()); RootCommand DotnetInteractive() { var command = new RootCommand { Name = "dotnet-interactive", Description = "Interactive programming for .NET." }; command.AddOption(logPathOption); command.AddOption(verboseOption); return(command); } Command Jupyter() { var jupyterCommand = new Command("jupyter", "Starts dotnet-interactive as a Jupyter kernel") { defaultKernelOption, logPathOption, verboseOption, new Argument <FileInfo> { Name = "connection-file" }.ExistingOnly() }; jupyterCommand.Handler = CommandHandler.Create <StartupOptions, JupyterOptions, IConsole, InvocationContext>((startupOptions, options, console, context) => { Track(context.ParseResult); services .AddSingleton(c => ConnectionInformation.Load(options.ConnectionFile)) .AddSingleton( c => { return(CommandScheduler .Create <JupyterRequestContext>(delivery => c.GetRequiredService <ICommandHandler <JupyterRequestContext> >() .Trace() .Handle(delivery))); }) .AddSingleton(c => CreateKernel(options.DefaultKernel)) .AddSingleton(c => new JupyterRequestContextHandler(c.GetRequiredService <IKernel>()) .Trace()) .AddSingleton <IHostedService, Shell>() .AddSingleton <IHostedService, Heartbeat>(); return(jupyter(startupOptions, console, startServer, context)); }); var installCommand = new Command("install", "Install the .NET kernel for Jupyter") { logPathOption, verboseOption }; installCommand.Handler = CommandHandler.Create <IConsole, InvocationContext>((console, context) => { Track(context.ParseResult); return(new JupyterInstallCommand(console, new JupyterKernelSpec()).InvokeAsync()); }); jupyterCommand.AddCommand(installCommand); return(jupyterCommand); } Command KernelServer() { var startKernelServerCommand = new Command("kernel-server", "Starts dotnet-interactive with kernel functionality exposed over standard I/O") { defaultKernelOption, logPathOption, }; startKernelServerCommand.Handler = CommandHandler.Create <StartupOptions, KernelServerOptions, IConsole, InvocationContext>( (startupOptions, options, console, context) => { Track(context.ParseResult); return(startKernelServer(startupOptions, CreateKernel(options.DefaultKernel), console)); }); return(startKernelServerCommand); } }
public Telemetry(IFirstTimeUseNoticeSentinel sentinel) : this(sentinel, null) { }
internal static int ProcessArgs(string[] args, ITelemetry telemetryClient = null) { // CommandLineApplication is a bit restrictive, so we parse things ourselves here. Individual apps should use CLA. var success = true; var command = string.Empty; var lastArg = 0; TopLevelCommandParserResult topLevelCommandParserResult = TopLevelCommandParserResult.Empty; using (IFirstTimeUseNoticeSentinel disposableFirstTimeUseNoticeSentinel = new FirstTimeUseNoticeSentinel()) { IFirstTimeUseNoticeSentinel firstTimeUseNoticeSentinel = disposableFirstTimeUseNoticeSentinel; IAspNetCertificateSentinel aspNetCertificateSentinel = new AspNetCertificateSentinel(); IFileSentinel toolPathSentinel = new FileSentinel( new FilePath( Path.Combine( CliFolderPathCalculator.DotnetUserProfileFolderPath, ToolPathSentinelFileName))); for (; lastArg < args.Length; lastArg++) { if (IsArg(args[lastArg], "d", "diagnostics")) { Environment.SetEnvironmentVariable(CommandContext.Variables.Verbose, bool.TrueString); CommandContext.SetVerbose(true); } else if (IsArg(args[lastArg], "version")) { PrintVersion(); return(0); } else if (IsArg(args[lastArg], "info")) { PrintInfo(); return(0); } else if (IsArg(args[lastArg], "h", "help") || args[lastArg] == "-?" || args[lastArg] == "/?") { HelpCommand.PrintHelp(); return(0); } else if (args[lastArg].StartsWith("-", StringComparison.OrdinalIgnoreCase)) { Reporter.Error.WriteLine($"Unknown option: {args[lastArg]}"); success = false; } else { // It's the command, and we're done! command = args[lastArg]; if (string.IsNullOrEmpty(command)) { command = "help"; } var environmentProvider = new EnvironmentProvider(); bool generateAspNetCertificate = environmentProvider.GetEnvironmentVariableAsBool("DOTNET_GENERATE_ASPNET_CERTIFICATE", true); bool skipFirstRunExperience = environmentProvider.GetEnvironmentVariableAsBool("DOTNET_SKIP_FIRST_TIME_EXPERIENCE", false); bool telemetryOptout = environmentProvider.GetEnvironmentVariableAsBool("DOTNET_CLI_TELEMETRY_OPTOUT", false); ReportDotnetHomeUsage(environmentProvider); topLevelCommandParserResult = new TopLevelCommandParserResult(command); var hasSuperUserAccess = false; if (IsDotnetBeingInvokedFromNativeInstaller(topLevelCommandParserResult)) { aspNetCertificateSentinel = new NoOpAspNetCertificateSentinel(); firstTimeUseNoticeSentinel = new NoOpFirstTimeUseNoticeSentinel(); toolPathSentinel = new NoOpFileSentinel(exists: false); hasSuperUserAccess = true; // When running through a native installer, we want the cache expansion to happen, so // we need to override this. skipFirstRunExperience = false; } var dotnetFirstRunConfiguration = new DotnetFirstRunConfiguration( generateAspNetCertificate: generateAspNetCertificate, skipFirstRunExperience: skipFirstRunExperience, telemetryOptout: telemetryOptout); ConfigureDotNetForFirstTimeUse( firstTimeUseNoticeSentinel, aspNetCertificateSentinel, toolPathSentinel, hasSuperUserAccess, dotnetFirstRunConfiguration, environmentProvider); break; } } if (!success) { HelpCommand.PrintHelp(); return(1); } if (telemetryClient == null) { telemetryClient = new Telemetry.Telemetry(firstTimeUseNoticeSentinel); } TelemetryEventEntry.Subscribe(telemetryClient.TrackEvent); TelemetryEventEntry.TelemetryFilter = new TelemetryFilter(Sha256Hasher.HashWithNormalizedCasing); } IEnumerable <string> appArgs = (lastArg + 1) >= args.Length ? Enumerable.Empty <string>() : args.Skip(lastArg + 1).ToArray(); if (CommandContext.IsVerbose()) { Console.WriteLine($"Telemetry is: {(telemetryClient.Enabled ? "Enabled" : "Disabled")}"); } TelemetryEventEntry.SendFiltered(topLevelCommandParserResult); int exitCode; if (BuiltInCommandsCatalog.Commands.TryGetValue(topLevelCommandParserResult.Command, out var builtIn)) { var parseResult = Parser.Instance.ParseFrom($"dotnet {topLevelCommandParserResult.Command}", appArgs.ToArray()); if (!parseResult.Errors.Any()) { TelemetryEventEntry.SendFiltered(parseResult); } exitCode = builtIn.Command(appArgs.ToArray()); } else { CommandResult result = CommandFactoryUsingResolver.Create( "dotnet-" + topLevelCommandParserResult.Command, appArgs, FrameworkConstants.CommonFrameworks.NetStandardApp15) .Execute(); exitCode = result.ExitCode; } return(exitCode); }
public static Parser Create( IServiceCollection services, StartServer startServer = null, Jupyter jupyter = null, StartKernelHost startKernelHost = null, StartNotebookParser startNotebookParser = null, StartHttp startHttp = null, Action onServerStarted = null, ITelemetry telemetry = null, IFirstTimeUseNoticeSentinel firstTimeUseNoticeSentinel = null) { var operation = Log.OnEnterAndExit(); if (services is null) { throw new ArgumentNullException(nameof(services)); } var disposeOnQuit = new CompositeDisposable(); startServer ??= (startupOptions, invocationContext) => { operation.Info("constructing webhost"); var webHost = Program.ConstructWebHost(startupOptions); disposeOnQuit.Add(webHost); operation.Info("starting kestrel server"); webHost.Start(); onServerStarted?.Invoke(); webHost.WaitForShutdown(); operation.Dispose(); }; jupyter ??= JupyterCommand.Do; startKernelHost ??= KernelHostLauncher.Do; startNotebookParser ??= ParseNotebookCommand.Do; startHttp ??= HttpCommand.Do; var isVSCode = false; // Setup first time use notice sentinel. firstTimeUseNoticeSentinel ??= new FirstTimeUseNoticeSentinel(VersionSensor.Version().AssemblyInformationalVersion); var clearTextProperties = new[] { "frontend" }; // Setup telemetry. telemetry ??= new Telemetry.Telemetry( VersionSensor.Version().AssemblyInformationalVersion, firstTimeUseNoticeSentinel, "dotnet/interactive/cli"); var filter = new TelemetryFilter( Sha256Hasher.HashWithNormalizedCasing, clearTextProperties, (commandResult, directives, entryItems) => { // add frontend var frontendTelemetryAdded = false; // check if is codespaces if (!string.IsNullOrWhiteSpace(Environment.GetEnvironmentVariable("CODESPACES"))) { frontendTelemetryAdded = true; isVSCode = true; entryItems.Add(new KeyValuePair <string, string>("frontend", "gitHubCodeSpaces")); } if (!frontendTelemetryAdded) { foreach (var directive in directives) { switch (directive.Key) { case "jupyter": case "synapse": case "vscode": frontendTelemetryAdded = true; isVSCode = directive.Key.ToLowerInvariant() == "vscode"; entryItems.Add(new KeyValuePair <string, string>("frontend", directive.Key)); break; } } } if (!frontendTelemetryAdded) { switch (commandResult.Command.Name) { case "jupyter": entryItems.Add(new KeyValuePair <string, string>("frontend", commandResult.Command.Name)); frontendTelemetryAdded = true; break; } } if (!frontendTelemetryAdded) { var frontendName = Environment.GetEnvironmentVariable("DOTNET_INTERACTIVE_FRONTEND_NAME"); if (string.IsNullOrWhiteSpace(frontendName)) { frontendName = "unknown"; } entryItems.Add(new KeyValuePair <string, string>("frontend", frontendName)); } }); var verboseOption = new Option <bool>( "--verbose", "Enable verbose logging to the console"); var logPathOption = new Option <DirectoryInfo>( "--log-path", "Enable file logging to the specified directory"); var pathOption = new Option <DirectoryInfo>( "--path", "Installs the kernelspecs to the specified directory") .ExistingOnly(); var defaultKernelOption = new Option <string>( "--default-kernel", description: "The default language for the kernel", getDefaultValue: () => "csharp").AddCompletions("fsharp", "csharp", "pwsh"); var rootCommand = DotnetInteractive(); rootCommand.AddCommand(Jupyter()); rootCommand.AddCommand(StdIO()); rootCommand.AddCommand(NotebookParser()); rootCommand.AddCommand(HttpServer()); return(new CommandLineBuilder(rootCommand) .UseDefaults() .AddMiddleware(async(context, next) => { if (context.ParseResult.Errors.Count == 0) { telemetry.SendFiltered(filter, context.ParseResult); } // If sentinel does not exist, print the welcome message showing the telemetry notification. if (!Telemetry.Telemetry.SkipFirstTimeExperience && !firstTimeUseNoticeSentinel.Exists()) { context.Console.Out.WriteLine(); context.Console.Out.WriteLine(Telemetry.Telemetry.WelcomeMessage); firstTimeUseNoticeSentinel.CreateIfNotExists(); } await next(context); }) .Build()); RootCommand DotnetInteractive() { var command = new RootCommand { Name = "dotnet-interactive", Description = "Interactive programming for .NET." }; command.AddGlobalOption(logPathOption); command.AddGlobalOption(verboseOption); return(command); } Command Jupyter() { var httpPortRangeOption = new Option <HttpPortRange>( "--http-port-range", parseArgument: result => result.Tokens.Count == 0 ? HttpPortRange.Default : ParsePortRangeOption(result), description: "Specifies the range of ports to use to enable HTTP services", isDefault: true); var jupyterCommand = new Command("jupyter", "Starts dotnet-interactive as a Jupyter kernel") { defaultKernelOption, httpPortRangeOption, new Argument <FileInfo> { Name = "connection-file", Description = "The path to a connection file provided by Jupyter" }.ExistingOnly() }; jupyterCommand.Handler = CommandHandler.Create <StartupOptions, JupyterOptions, IConsole, InvocationContext, CancellationToken>(JupyterHandler); var installCommand = new Command("install", "Install the .NET kernel for Jupyter") { httpPortRangeOption, pathOption }; installCommand.Handler = CommandHandler.Create <IConsole, InvocationContext, HttpPortRange, DirectoryInfo>(InstallHandler); jupyterCommand.AddCommand(installCommand); return(jupyterCommand); async Task <int> JupyterHandler(StartupOptions startupOptions, JupyterOptions options, IConsole console, InvocationContext context, CancellationToken cancellationToken) { var frontendEnvironment = new HtmlNotebookFrontendEnvironment(); var kernel = CreateKernel(options.DefaultKernel, frontendEnvironment, startupOptions); kernel.Add( new JavaScriptKernel(), new[] { "js" }); services.AddKernel(kernel); kernel.VisitSubkernels(k => { switch (k) { case CSharpKernel csharpKernel: csharpKernel.UseJupyterHelpers(); break; case FSharpKernel fsharpKernel: fsharpKernel.UseJupyterHelpers(); break; case PowerShellKernel powerShellKernel: powerShellKernel.UseJupyterHelpers(); break; } }); var clientSideKernelClient = new SignalRBackchannelKernelClient(); services.AddSingleton(c => ConnectionInformation.Load(options.ConnectionFile)) .AddSingleton(clientSideKernelClient) .AddSingleton(c => { return(new JupyterRequestContextScheduler(delivery => c.GetRequiredService <JupyterRequestContextHandler>() .Handle(delivery))); }) .AddSingleton(c => new JupyterRequestContextHandler(kernel)) .AddSingleton <IHostedService, Shell>() .AddSingleton <IHostedService, Heartbeat>(); var result = await jupyter(startupOptions, console, startServer, context); return(result); } Task <int> InstallHandler(IConsole console, InvocationContext context, HttpPortRange httpPortRange, DirectoryInfo path) { var jupyterInstallCommand = new JupyterInstallCommand(console, new JupyterKernelSpecInstaller(console), httpPortRange, path); return(jupyterInstallCommand.InvokeAsync()); } } Command HttpServer() { var httpPortOption = new Option <HttpPort>( "--http-port", description: "Specifies the port on which to enable HTTP services", parseArgument: result => { if (result.Tokens.Count == 0) { return(HttpPort.Auto); } var source = result.Tokens[0].Value; if (source == "*") { return(HttpPort.Auto); } if (!int.TryParse(source, out var portNumber)) { result.ErrorMessage = "Must specify a port number or *."; return(null); } return(new HttpPort(portNumber)); }, isDefault: true); var httpCommand = new Command("http", "Starts dotnet-interactive with kernel functionality exposed over http") { defaultKernelOption, httpPortOption }; httpCommand.Handler = CommandHandler.Create <StartupOptions, KernelHttpOptions, IConsole, InvocationContext>( (startupOptions, options, console, context) => { var frontendEnvironment = new BrowserFrontendEnvironment(); var kernel = CreateKernel(options.DefaultKernel, frontendEnvironment, startupOptions); kernel.Add( new JavaScriptKernel(), new[] { "js" }); services.AddKernel(kernel) .AddSingleton(new SignalRBackchannelKernelClient()); onServerStarted ??= () => { console.Out.WriteLine("Application started. Press Ctrl+C to shut down."); }; return(startHttp(startupOptions, console, startServer, context)); }); return(httpCommand); } Command StdIO() { var httpPortRangeOption = new Option <HttpPortRange>( "--http-port-range", parseArgument: result => result.Tokens.Count == 0 ? HttpPortRange.Default : ParsePortRangeOption(result), description: "Specifies the range of ports to use to enable HTTP services"); var httpPortOption = new Option <HttpPort>( "--http-port", description: "Specifies the port on which to enable HTTP services", parseArgument: result => { if (result.FindResultFor(httpPortRangeOption) is { } conflictingOption) { var parsed = result.Parent as OptionResult; result.ErrorMessage = $"Cannot specify both {conflictingOption.Token.Value} and {parsed.Token.Value} together"; return(null); } if (result.Tokens.Count == 0) { return(HttpPort.Auto); } var source = result.Tokens[0].Value; if (source == "*") { return(HttpPort.Auto); } if (!int.TryParse(source, out var portNumber)) { result.ErrorMessage = "Must specify a port number or *."; return(null); } return(new HttpPort(portNumber)); }); var workingDirOption = new Option <DirectoryInfo>( "--working-dir", () => new DirectoryInfo(Environment.CurrentDirectory), "Working directory to which to change after launching the kernel."); var stdIOCommand = new Command( "stdio", "Starts dotnet-interactive with kernel functionality exposed over standard I/O") { defaultKernelOption, httpPortRangeOption, httpPortOption, workingDirOption }; stdIOCommand.Handler = CommandHandler.Create <StartupOptions, StdIOOptions, IConsole, InvocationContext>( async(startupOptions, options, console, context) => { Console.InputEncoding = Encoding.UTF8; Console.OutputEncoding = Encoding.UTF8; Environment.CurrentDirectory = startupOptions.WorkingDir.FullName; FrontendEnvironment frontendEnvironment = startupOptions.EnableHttpApi ? new HtmlNotebookFrontendEnvironment() : new BrowserFrontendEnvironment(); var kernel = CreateKernel(options.DefaultKernel, frontendEnvironment, startupOptions); services.AddKernel(kernel); kernel = kernel.UseQuitCommand(); var host = new KernelHost(kernel, new KernelCommandAndEventTextStreamSender(Console.Out), new MultiplexingKernelCommandAndEventReceiver( new KernelCommandAndEventTextReceiver(Console.In))); if (isVSCode) { var vscodeSetup = new VSCodeClientKernelsExtension(); await vscodeSetup.OnLoadAsync(kernel); } if (startupOptions.EnableHttpApi) { var clientSideKernelClient = new SignalRBackchannelKernelClient(); services.AddSingleton(clientSideKernelClient); if (isVSCode) { ((HtmlNotebookFrontendEnvironment)frontendEnvironment).RequiresAutomaticBootstrapping = false; } else { kernel.Add( new JavaScriptKernel(clientSideKernelClient), new[] { "js" }); } onServerStarted ??= () => { var _ = host.ConnectAsync(); }; await startHttp(startupOptions, console, startServer, context); } else { if (!isVSCode) { await host.CreateProxyKernelOnDefaultConnectorAsync(new KernelInfo("javascript", "javascript") { Aliases = new[] { "js" }, DestinationUri = new Uri("kernel://webview/javascript") }); } await startKernelHost(startupOptions, host, console); } return(0); }); return(stdIOCommand); }
internal static int ProcessArgs(string[] args, TimeSpan startupTime, ITelemetry telemetryClient = null) { Dictionary <string, double> performanceData = new Dictionary <string, double>(); PerformanceLogEventSource.Log.BuiltInCommandParserStart(); Stopwatch parseStartTime = Stopwatch.StartNew(); var parseResult = Parser.Instance.Parse(args); performanceData.Add("Parse Time", parseStartTime.Elapsed.TotalMilliseconds); PerformanceLogEventSource.Log.BuiltInCommandParserStop(); using (IFirstTimeUseNoticeSentinel disposableFirstTimeUseNoticeSentinel = new FirstTimeUseNoticeSentinel()) { IFirstTimeUseNoticeSentinel firstTimeUseNoticeSentinel = disposableFirstTimeUseNoticeSentinel; IAspNetCertificateSentinel aspNetCertificateSentinel = new AspNetCertificateSentinel(); IFileSentinel toolPathSentinel = new FileSentinel( new FilePath( Path.Combine( CliFolderPathCalculator.DotnetUserProfileFolderPath, ToolPathSentinelFileName))); if (parseResult.ValueForOption <bool>(Parser.DiagOption) && parseResult.IsDotnetBuiltInCommand()) { Environment.SetEnvironmentVariable(CommandContext.Variables.Verbose, bool.TrueString); CommandContext.SetVerbose(true); Reporter.Reset(); } if (parseResult.HasOption(Parser.VersionOption) && parseResult.IsTopLevelDotnetCommand()) { CommandLineInfo.PrintVersion(); return(0); } else if (parseResult.HasOption(Parser.InfoOption) && parseResult.IsTopLevelDotnetCommand()) { CommandLineInfo.PrintInfo(); return(0); } else if (parseResult.HasOption("-h") && parseResult.IsTopLevelDotnetCommand()) { HelpCommand.PrintHelp(); return(0); } else if (parseResult.Directives.Count() > 0) { return(parseResult.Invoke()); } else { PerformanceLogEventSource.Log.FirstTimeConfigurationStart(); var environmentProvider = new EnvironmentProvider(); bool generateAspNetCertificate = environmentProvider.GetEnvironmentVariableAsBool("DOTNET_GENERATE_ASPNET_CERTIFICATE", defaultValue: true); bool telemetryOptout = environmentProvider.GetEnvironmentVariableAsBool("DOTNET_CLI_TELEMETRY_OPTOUT", defaultValue: false); bool addGlobalToolsToPath = environmentProvider.GetEnvironmentVariableAsBool("DOTNET_ADD_GLOBAL_TOOLS_TO_PATH", defaultValue: true); bool nologo = environmentProvider.GetEnvironmentVariableAsBool("DOTNET_NOLOGO", defaultValue: false); ReportDotnetHomeUsage(environmentProvider); var isDotnetBeingInvokedFromNativeInstaller = false; if (parseResult.CommandResult.Command.Name.Equals(Parser.InstallSuccessCommand.Name)) { aspNetCertificateSentinel = new NoOpAspNetCertificateSentinel(); firstTimeUseNoticeSentinel = new NoOpFirstTimeUseNoticeSentinel(); toolPathSentinel = new NoOpFileSentinel(exists: false); isDotnetBeingInvokedFromNativeInstaller = true; } var dotnetFirstRunConfiguration = new DotnetFirstRunConfiguration( generateAspNetCertificate: generateAspNetCertificate, telemetryOptout: telemetryOptout, addGlobalToolsToPath: addGlobalToolsToPath, nologo: nologo); ConfigureDotNetForFirstTimeUse( firstTimeUseNoticeSentinel, aspNetCertificateSentinel, toolPathSentinel, isDotnetBeingInvokedFromNativeInstaller, dotnetFirstRunConfiguration, environmentProvider, performanceData); PerformanceLogEventSource.Log.FirstTimeConfigurationStop(); } PerformanceLogEventSource.Log.TelemetryRegistrationStart(); if (telemetryClient == null) { telemetryClient = new Telemetry.Telemetry(firstTimeUseNoticeSentinel); } TelemetryEventEntry.Subscribe(telemetryClient.TrackEvent); TelemetryEventEntry.TelemetryFilter = new TelemetryFilter(Sha256Hasher.HashWithNormalizedCasing); PerformanceLogEventSource.Log.TelemetryRegistrationStop(); } if (CommandContext.IsVerbose()) { Console.WriteLine($"Telemetry is: {(telemetryClient.Enabled ? "Enabled" : "Disabled")}"); } PerformanceLogEventSource.Log.TelemetrySaveIfEnabledStart(); performanceData.Add("Startup Time", startupTime.TotalMilliseconds); TelemetryEventEntry.SendFiltered(Tuple.Create(parseResult, performanceData)); PerformanceLogEventSource.Log.TelemetrySaveIfEnabledStop(); var topLevelCommands = new string[] { "dotnet", parseResult.RootSubCommandResult() }.Concat(Parser.DiagOption.Aliases); int exitCode; if (parseResult.CommandResult.Command.Name.Equals("dotnet") && string.IsNullOrEmpty(parseResult.ValueForArgument <string>(Parser.DotnetSubCommand))) { exitCode = 0; } else if (BuiltInCommandsCatalog.Commands.TryGetValue(parseResult.RootSubCommandResult(), out var builtIn)) { PerformanceLogEventSource.Log.BuiltInCommandStart(); exitCode = builtIn.Command(args.Where(t => !topLevelCommands.Contains(t)).ToArray()); PerformanceLogEventSource.Log.BuiltInCommandStop(); } else { PerformanceLogEventSource.Log.ExtensibleCommandResolverStart(); var resolvedCommand = CommandFactoryUsingResolver.Create( "dotnet-" + parseResult.ValueForArgument <string>(Parser.DotnetSubCommand), args.Where(t => !topLevelCommands.Contains(t)).ToArray(), FrameworkConstants.CommonFrameworks.NetStandardApp15); PerformanceLogEventSource.Log.ExtensibleCommandResolverStop(); PerformanceLogEventSource.Log.ExtensibleCommandStart(); var result = resolvedCommand.Execute(); PerformanceLogEventSource.Log.ExtensibleCommandStop(); exitCode = result.ExitCode; } PerformanceLogEventSource.Log.TelemetryClientFlushStart(); telemetryClient.Flush(); PerformanceLogEventSource.Log.TelemetryClientFlushStop(); return(exitCode); }
public static Parser Create( IServiceCollection services, StartServer startServer = null, Install install = null, Demo demo = null, TryGitHub tryGithub = null, Pack pack = null, Verify verify = null, Publish publish = null, ITelemetry telemetry = null, IFirstTimeUseNoticeSentinel firstTimeUseNoticeSentinel = null) { if (services == null) { throw new ArgumentNullException(nameof(services)); } startServer ??= (startupOptions, invocationContext) => Program.ConstructWebHost(startupOptions).Run(); demo ??= DemoCommand.Do; tryGithub ??= (repo, console) => GitHubHandler.Handler(repo, console, new GitHubRepoLocator()); verify ??= VerifyCommand.Do; publish ??= (options, console, startupOptions) => PublishCommand.Do(options, console, startupOptions); pack ??= PackCommand.Do; // Setup first time use notice sentinel. firstTimeUseNoticeSentinel ??= new FirstTimeUseNoticeSentinel(VersionSensor.Version().AssemblyInformationalVersion); // Setup telemetry. telemetry ??= new Telemetry( VersionSensor.Version().AssemblyInformationalVersion, firstTimeUseNoticeSentinel); var filter = new TelemetryFilter(Sha256Hasher.HashWithNormalizedCasing); Action <ParseResult> track = o => telemetry.SendFiltered(filter, o); var dirArgument = new Argument <FileSystemDirectoryAccessor>(result => { var directory = result.Tokens .Select(t => t.Value) .FirstOrDefault(); if (!string.IsNullOrEmpty(directory) && !Directory.Exists(directory)) { result.ErrorMessage = $"Directory does not exist: {directory}"; return(null); } return(new FileSystemDirectoryAccessor( directory ?? Directory.GetCurrentDirectory())); }, isDefault: true) { Name = "root-directory", Arity = ArgumentArity.ZeroOrOne, Description = "The root directory for your documentation" }; var rootCommand = StartInTryMode(); rootCommand.AddCommand(StartInHostedMode()); rootCommand.AddCommand(Demo()); rootCommand.AddCommand(GitHub()); rootCommand.AddCommand(Install()); rootCommand.AddCommand(Pack()); rootCommand.AddCommand(Verify()); rootCommand.AddCommand(Publish()); return(new CommandLineBuilder(rootCommand) .UseDefaults() .UseMiddleware(async(context, next) => { if (context.ParseResult.Errors.Count == 0) { telemetry.SendFiltered(filter, context.ParseResult); } // If sentinel does not exist, print the welcome message showing the telemetry notification. if (!firstTimeUseNoticeSentinel.Exists() && !Telemetry.SkipFirstTimeExperience) { context.Console.Out.WriteLine(); context.Console.Out.WriteLine(Telemetry.WelcomeMessage); firstTimeUseNoticeSentinel.CreateIfNotExists(); } if (context.ParseResult.Directives.Contains("debug") && !(Clock.Current is VirtualClock)) { VirtualClock.Start(); } await next(context); }) .Build()); RootCommand StartInTryMode() { var command = new RootCommand { Name = "dotnet-try", Description = "Interactive documentation in your browser" }; command.AddArgument(dirArgument); command.AddOption(new Option( "--add-package-source", "Specify an additional NuGet package source") { Argument = new Argument <PackageSource>(() => new PackageSource(Directory.GetCurrentDirectory())) { Name = "NuGet source" } }); command.AddOption(new Option( "--package", "Specify a Try .NET package or path to a .csproj to run code samples with") { Argument = new Argument <string> { Name = "name or .csproj" } }); command.AddOption(new Option( "--package-version", "Specify a Try .NET package version to use with the --package option") { Argument = new Argument <string> { Name = "version" } }); command.AddOption(new Option <Uri>( "--uri", "Specify a URL or a relative path to a Markdown file")); command.AddOption(new Option <bool>( "--enable-preview-features", "Enable preview features")); command.AddOption(new Option( "--log-path", "Enable file logging to the specified directory") { Argument = new Argument <DirectoryInfo> { Name = "dir" } }); command.AddOption(new Option <bool>( "--verbose", "Enable verbose logging to the console")); var portArgument = new Argument <ushort>(); portArgument.AddValidator(symbolResult => { if (symbolResult.Tokens .Select(t => t.Value) .Count(value => !ushort.TryParse(value, out _)) > 0) { return("Invalid argument for --port option"); } return(null); }); command.AddOption(new Option( "--port", "Specify the port for dotnet try to listen on") { Argument = portArgument }); command.Handler = CommandHandler.Create <InvocationContext, StartupOptions>((context, options) => { services.AddSingleton(_ => PackageRegistry.CreateForTryMode( options.RootDirectory, options.AddPackageSource)); startServer(options, context); }); return(command); } Command StartInHostedMode() { var command = new Command("hosted") { new Option <string>( "--id", description: "A unique id for the agent instance (e.g. its development environment id).", getDefaultValue: () => Environment.MachineName), new Option <bool>( "--production", "Specifies whether the agent is being run using production resources"), new Option <bool>( "--language-service", "Specifies whether the agent is being run in language service-only mode"), new Option <string>( new[] { "-k", "--key" }, "The encryption key"), new Option <string>( new[] { "--ai-key", "--application-insights-key" }, "Application Insights key."), new Option <string>( "--region-id", "A unique id for the agent region"), new Option <bool>( "--log-to-file", "Writes a log file") }; command.Description = "Starts the Try .NET agent"; command.IsHidden = true; command.Handler = CommandHandler.Create <InvocationContext, StartupOptions>((context, options) => { services.AddSingleton(_ => PackageRegistry.CreateForHostedMode()); services.AddSingleton(c => new MarkdownProject(c.GetRequiredService <PackageRegistry>())); startServer(options, context); }); return(command); } Command Demo() { var demoCommand = new Command( "demo", "Learn how to create Try .NET content with an interactive demo") { new Option <DirectoryInfo>( "--output", description: "Where should the demo project be written to?", getDefaultValue: () => new DirectoryInfo(Directory.GetCurrentDirectory())) }; demoCommand.Handler = CommandHandler.Create <DemoOptions, InvocationContext>((options, context) => { demo(options, context.Console, startServer, context); }); return(demoCommand); } Command GitHub() { var argument = new Argument <string> { // System.CommandLine parameter binding does lookup by name, // so name the argument after the github command's string param Name = nameof(TryGitHubOptions.Repo) }; var github = new Command("github", "Try a GitHub repo") { argument }; github.IsHidden = true; github.Handler = CommandHandler.Create <TryGitHubOptions, IConsole>((repo, console) => tryGithub(repo, console)); return(github); } Command Install() { var installCommand = new Command("install", "Install a Try .NET package") { new Argument <string> { Name = nameof(InstallOptions.PackageName), Arity = ArgumentArity.ExactlyOne }, new Option <PackageSource>("--add-source") }; installCommand.IsHidden = true; installCommand.Handler = CommandHandler.Create <InstallOptions, IConsole>((options, console) => install(options, console)); return(installCommand); } Command Pack() { var packCommand = new Command("pack", "Create a Try .NET package") { new Argument <DirectoryInfo> { Name = nameof(PackOptions.PackTarget) }, new Option <string>("--version", "The version of the Try .NET package"), new Option <bool>("--enable-wasm", "Enables web assembly code execution") }; packCommand.IsHidden = true; packCommand.Handler = CommandHandler.Create <PackOptions, IConsole>( (options, console) => { return(pack(options, console)); }); return(packCommand); } Command Verify() { var verifyCommand = new Command("verify", "Verify Markdown files found under the root directory.") { dirArgument }; verifyCommand.Handler = CommandHandler.Create <VerifyOptions, IConsole, StartupOptions>( (options, console, startupOptions) => { return(verify(options, console, startupOptions)); }); return(verifyCommand); } Command Publish() { var publishCommand = new Command("publish", "Publish code from sample projects found under the root directory into Markdown files in the target directory") { new Option <PublishFormat>( "--format", description: "Format of the files to publish", getDefaultValue: () => PublishFormat.Markdown), new Option <IDirectoryAccessor>( "--target-directory", description: "The path where the output files should go. This can be the same as the root directory, which will overwrite files in place.", parseArgument: result => { var directory = result.Tokens .Select(t => t.Value) .Single(); return(new FileSystemDirectoryAccessor(directory)); } ), dirArgument }; publishCommand.Handler = CommandHandler.Create <PublishOptions, IConsole, StartupOptions>( (options, console, startupOptions) => publish(options, console, startupOptions)); return(publishCommand); } }
internal static int ProcessArgs(string[] args, ITelemetry telemetryClient = null) { // CommandLineApplication is a bit restrictive, so we parse things ourselves here. Individual apps should use CLA. var success = true; var command = string.Empty; var lastArg = 0; var cliFallbackFolderPathCalculator = new CliFallbackFolderPathCalculator(); using (INuGetCacheSentinel nugetCacheSentinel = new NuGetCacheSentinel(cliFallbackFolderPathCalculator)) using (IFirstTimeUseNoticeSentinel disposableFirstTimeUseNoticeSentinel = new FirstTimeUseNoticeSentinel(cliFallbackFolderPathCalculator)) { IFirstTimeUseNoticeSentinel firstTimeUseNoticeSentinel = disposableFirstTimeUseNoticeSentinel; for (; lastArg < args.Length; lastArg++) { if (IsArg(args[lastArg], "d", "diagnostics")) { Environment.SetEnvironmentVariable(CommandContext.Variables.Verbose, bool.TrueString); CommandContext.SetVerbose(true); } else if (IsArg(args[lastArg], "version")) { PrintVersion(); return(0); } else if (IsArg(args[lastArg], "info")) { PrintInfo(); return(0); } else if (IsArg(args[lastArg], "h", "help") || args[lastArg] == "-?" || args[lastArg] == "/?") { HelpCommand.PrintHelp(); return(0); } else if (args[lastArg].StartsWith("-")) { Reporter.Error.WriteLine($"Unknown option: {args[lastArg]}"); success = false; } else { // It's the command, and we're done! command = args[lastArg]; if (IsDotnetBeingInvokedFromNativeInstaller(command)) { firstTimeUseNoticeSentinel = new NoOpFirstTimeUseNoticeSentinel(); } ConfigureDotNetForFirstTimeUse( nugetCacheSentinel, firstTimeUseNoticeSentinel, cliFallbackFolderPathCalculator); break; } } if (!success) { HelpCommand.PrintHelp(); return(1); } if (telemetryClient == null) { telemetryClient = new Telemetry.Telemetry(firstTimeUseNoticeSentinel); } TelemetryEventEntry.Subscribe(telemetryClient.TrackEvent); TelemetryEventEntry.TelemetryFilter = new TelemetryFilter(); } IEnumerable <string> appArgs = (lastArg + 1) >= args.Length ? Enumerable.Empty <string>() : args.Skip(lastArg + 1).ToArray(); if (CommandContext.IsVerbose()) { Console.WriteLine($"Telemetry is: {(telemetryClient.Enabled ? "Enabled" : "Disabled")}"); } if (string.IsNullOrEmpty(command)) { command = "help"; } TelemetryEventEntry.TrackEvent(command, null, null); int exitCode; if (BuiltInCommandsCatalog.Commands.TryGetValue(command, out var builtIn)) { TelemetryEventEntry.SendFiltered(Parser.Instance.ParseFrom($"dotnet {command}", appArgs.ToArray())); exitCode = builtIn.Command(appArgs.ToArray()); } else { CommandResult result = Command.Create( "dotnet-" + command, appArgs, FrameworkConstants.CommonFrameworks.NetStandardApp15) .Execute(); exitCode = result.ExitCode; } return(exitCode); }
public static Parser Create( IServiceCollection services, StartServer startServer = null, Jupyter jupyter = null, StartStdIO startStdIO = null, ITelemetry telemetry = null, IFirstTimeUseNoticeSentinel firstTimeUseNoticeSentinel = null) { if (services == null) { throw new ArgumentNullException(nameof(services)); } startServer ??= (startupOptions, invocationContext) => Program.ConstructWebHost(startupOptions).Run(); jupyter ??= JupyterCommand.Do; startStdIO ??= StdIOCommand.Do; // Setup first time use notice sentinel. firstTimeUseNoticeSentinel ??= new FirstTimeUseNoticeSentinel(VersionSensor.Version().AssemblyInformationalVersion); // Setup telemetry. telemetry ??= new Telemetry.Telemetry( VersionSensor.Version().AssemblyInformationalVersion, firstTimeUseNoticeSentinel); var filter = new TelemetryFilter(Sha256Hasher.HashWithNormalizedCasing); var verboseOption = new Option <bool>( "--verbose", "Enable verbose logging to the console"); var httpPortOption = new Option <HttpPort>( "--http-port", description: "Specifies the port on which to enable HTTP services", parseArgument: result => { var source = result.Tokens[0].Value; if (source == "*") { return(HttpPort.Auto); } if (!int.TryParse(source, out var portNumber)) { result.ErrorMessage = "Must specify a port number or *."; return(null); } return(new HttpPort(portNumber)); }); var httpPortRangeOption = new Option <PortRange>( "--http-port-range", parseArgument: result => { if (result.Parent.Parent.Children.FirstOrDefault(c => c.Symbol == httpPortOption) is OptionResult conflictingOption) { var parsed = result.Parent as OptionResult; result.ErrorMessage = $"Cannot specify both {conflictingOption.Token.Value} and {parsed.Token.Value} together"; return(null); } var source = result.Tokens[0].Value; if (string.IsNullOrWhiteSpace(source)) { result.ErrorMessage = "Must specify a port range"; return(null); } var parts = source.Split(new[] { "-" }, StringSplitOptions.RemoveEmptyEntries); if (parts.Length != 2) { result.ErrorMessage = "Must specify a port range"; return(null); } if (!int.TryParse(parts[0], out var start) || !int.TryParse(parts[1], out var end)) { result.ErrorMessage = "Must specify a port range as StartPort-EndPort"; return(null); } if (start > end) { result.ErrorMessage = "Start port must be lower then end port"; return(null); } var pr = new PortRange(start, end); return(pr); }, description: "Specifies the range of port to use to enable HTTP services"); var logPathOption = new Option <DirectoryInfo>( "--log-path", "Enable file logging to the specified directory"); var pathOption = new Option <DirectoryInfo>( "--path", "Installs the kernelspecs to the specified directory") .ExistingOnly(); var defaultKernelOption = new Option <string>( "--default-kernel", description: "The default language for the kernel", getDefaultValue: () => "csharp"); var rootCommand = DotnetInteractive(); rootCommand.AddCommand(Jupyter()); rootCommand.AddCommand(StdIO()); rootCommand.AddCommand(HttpServer()); return(new CommandLineBuilder(rootCommand) .UseDefaults() .UseMiddleware(async(context, next) => { if (context.ParseResult.Errors.Count == 0) { telemetry.SendFiltered(filter, context.ParseResult); } // If sentinel does not exist, print the welcome message showing the telemetry notification. if (!firstTimeUseNoticeSentinel.Exists() && !Telemetry.Telemetry.SkipFirstTimeExperience) { context.Console.Out.WriteLine(); context.Console.Out.WriteLine(Telemetry.Telemetry.WelcomeMessage); firstTimeUseNoticeSentinel.CreateIfNotExists(); } await next(context); }) .Build()); RootCommand DotnetInteractive() { var command = new RootCommand { Name = "dotnet-interactive", Description = "Interactive programming for .NET." }; command.AddOption(logPathOption); command.AddOption(verboseOption); return(command); } Command Jupyter() { var command = new Command("jupyter", "Starts dotnet-interactive as a Jupyter kernel") { defaultKernelOption, logPathOption, verboseOption, httpPortRangeOption, new Argument <FileInfo> { Name = "connection-file" }.ExistingOnly() }; command.Handler = CommandHandler.Create <StartupOptions, JupyterOptions, IConsole, InvocationContext>(JupyterHandler); var installCommand = new Command("install", "Install the .NET kernel for Jupyter") { logPathOption, verboseOption, httpPortRangeOption, pathOption }; installCommand.Handler = CommandHandler.Create <IConsole, InvocationContext, PortRange, DirectoryInfo>(InstallHandler); command.AddCommand(installCommand); return(command); Task <int> JupyterHandler(StartupOptions startupOptions, JupyterOptions options, IConsole console, InvocationContext context) { services = RegisterKernelInServiceCollection(services, startupOptions, options.DefaultKernel); services.AddSingleton(c => ConnectionInformation.Load(options.ConnectionFile)) .AddSingleton <FrontendEnvironment>(c => c.GetService <BrowserFrontendEnvironment>()) .AddSingleton(c => { return(CommandScheduler.Create <JupyterRequestContext>(delivery => c.GetRequiredService <ICommandHandler <JupyterRequestContext> >() .Trace() .Handle(delivery))); }) .AddSingleton(c => new JupyterRequestContextHandler( c.GetRequiredService <IKernel>()) .Trace()) .AddSingleton <IHostedService, Shell>() .AddSingleton <IHostedService, Heartbeat>(); return(jupyter(startupOptions, console, startServer, context)); } Task <int> InstallHandler(IConsole console, InvocationContext context, PortRange httpPortRange, DirectoryInfo location) => new JupyterInstallCommand(console, jupyterKernelSpecInstaller: new JupyterKernelSpecInstaller(console), httpPortRange, location).InvokeAsync(); } Command HttpServer() { var command = new Command("http", "Starts dotnet-interactive with kernel functionality exposed over http") { defaultKernelOption, httpPortOption, logPathOption, httpPortRangeOption }; command.Handler = CommandHandler.Create <StartupOptions, KernelHttpOptions, IConsole, InvocationContext>( (startupOptions, options, console, context) => { if (startupOptions.HttpPort == null && startupOptions.HttpPortRange == null) { startupOptions.HttpPort = HttpPort.Auto; } RegisterKernelInServiceCollection(services, startupOptions, options.DefaultKernel); return(jupyter(startupOptions, console, startServer, context)); }); return(command); } Command StdIO() { var command = new Command( "stdio", "Starts dotnet-interactive with kernel functionality exposed over standard I/O") { defaultKernelOption, logPathOption, httpPortOption, httpPortRangeOption }; command.Handler = CommandHandler.Create <StartupOptions, StdIOOptions, IConsole, InvocationContext>( (startupOptions, options, console, context) => { if (startupOptions.EnableHttpApi) { RegisterKernelInServiceCollection(services, startupOptions, options.DefaultKernel, kernel => StdIOCommand.CreateServer(kernel, console)); startServer?.Invoke(startupOptions, context); return(Task.FromResult(0)); } return(startStdIO( startupOptions, CreateKernel(options.DefaultKernel, new BrowserFrontendEnvironment(), startupOptions, null), console)); }); return(command); } }
public static Parser Create( IServiceCollection services, StartServer startServer = null, Jupyter jupyter = null, StartStdIO startStdIO = null, StartHttp startHttp = null, ITelemetry telemetry = null, IFirstTimeUseNoticeSentinel firstTimeUseNoticeSentinel = null) { if (services == null) { throw new ArgumentNullException(nameof(services)); } var disposeOnQuit = new CompositeDisposable(); startServer ??= (startupOptions, invocationContext) => { var webHost = Program.ConstructWebHost(startupOptions); disposeOnQuit.Add(webHost); webHost.Run(); }; jupyter ??= JupyterCommand.Do; startStdIO ??= StdIOCommand.Do; startHttp ??= HttpCommand.Do; // Setup first time use notice sentinel. firstTimeUseNoticeSentinel ??= new FirstTimeUseNoticeSentinel(VersionSensor.Version().AssemblyInformationalVersion); // Setup telemetry. telemetry ??= new Telemetry.Telemetry( VersionSensor.Version().AssemblyInformationalVersion, firstTimeUseNoticeSentinel); var filter = new TelemetryFilter(Sha256Hasher.HashWithNormalizedCasing); var verboseOption = new Option <bool>( "--verbose", "Enable verbose logging to the console"); var logPathOption = new Option <DirectoryInfo>( "--log-path", "Enable file logging to the specified directory"); var pathOption = new Option <DirectoryInfo>( "--path", "Installs the kernelspecs to the specified directory") .ExistingOnly(); var defaultKernelOption = new Option <string>( "--default-kernel", description: "The default language for the kernel", getDefaultValue: () => "csharp"); var rootCommand = DotnetInteractive(); rootCommand.AddCommand(Jupyter()); rootCommand.AddCommand(StdIO()); rootCommand.AddCommand(HttpServer()); return(new CommandLineBuilder(rootCommand) .UseDefaults() .UseMiddleware(async(context, next) => { if (context.ParseResult.Errors.Count == 0) { telemetry.SendFiltered(filter, context.ParseResult); } // If sentinel does not exist, print the welcome message showing the telemetry notification. if (!firstTimeUseNoticeSentinel.Exists() && !Telemetry.Telemetry.SkipFirstTimeExperience) { context.Console.Out.WriteLine(); context.Console.Out.WriteLine(Telemetry.Telemetry.WelcomeMessage); firstTimeUseNoticeSentinel.CreateIfNotExists(); } await next(context); }) .Build()); RootCommand DotnetInteractive() { var command = new RootCommand { Name = "dotnet-interactive", Description = "Interactive programming for .NET." }; command.AddOption(logPathOption); command.AddOption(verboseOption); return(command); } Command Jupyter() { var httpPortRangeOption = new Option <HttpPortRange>( "--http-port-range", parseArgument: result => result.Tokens.Count == 0 ? HttpPortRange.Default : ParsePortRangeOption(result), description: "Specifies the range of ports to use to enable HTTP services", isDefault: true); var command = new Command("jupyter", "Starts dotnet-interactive as a Jupyter kernel") { defaultKernelOption, logPathOption, verboseOption, httpPortRangeOption, new Argument <FileInfo> { Name = "connection-file" }.ExistingOnly() }; command.Handler = CommandHandler.Create <StartupOptions, JupyterOptions, IConsole, InvocationContext, CancellationToken>(JupyterHandler); var installCommand = new Command("install", "Install the .NET kernel for Jupyter") { httpPortRangeOption, logPathOption, verboseOption, pathOption }; installCommand.Handler = CommandHandler.Create <IConsole, InvocationContext, HttpPortRange, DirectoryInfo>(InstallHandler); command.AddCommand(installCommand); return(command); Task <int> JupyterHandler(StartupOptions startupOptions, JupyterOptions options, IConsole console, InvocationContext context, CancellationToken cancellationToken) { services = RegisterKernelInServiceCollection( services, startupOptions, options.DefaultKernel, serviceCollection => { serviceCollection.AddSingleton(_ => new HtmlNotebookFrontedEnvironment()); serviceCollection.AddSingleton <FrontendEnvironment>(c => c.GetService <HtmlNotebookFrontedEnvironment>()); }); services.AddSingleton(c => ConnectionInformation.Load(options.ConnectionFile)) .AddSingleton(c => { return(CommandScheduler.Create <JupyterRequestContext>(delivery => c.GetRequiredService <ICommandHandler <JupyterRequestContext> >() .Trace() .Handle(delivery))); }) .AddSingleton(c => new JupyterRequestContextHandler( c.GetRequiredService <IKernel>()) .Trace()) .AddSingleton <IHostedService, Shell>() .AddSingleton <IHostedService, Heartbeat>(); return(jupyter(startupOptions, console, startServer, context)); } Task <int> InstallHandler(IConsole console, InvocationContext context, HttpPortRange httpPortRange, DirectoryInfo path) { var jupyterInstallCommand = new JupyterInstallCommand(console, new JupyterKernelSpecInstaller(console), httpPortRange, path); return(jupyterInstallCommand.InvokeAsync()); } } Command HttpServer() { var httpPortOption = new Option <HttpPort>( "--http-port", description: "Specifies the port on which to enable HTTP services", parseArgument: result => { if (result.Tokens.Count == 0) { return(HttpPort.Auto); } var source = result.Tokens[0].Value; if (source == "*") { return(HttpPort.Auto); } if (!int.TryParse(source, out var portNumber)) { result.ErrorMessage = "Must specify a port number or *."; return(null); } return(new HttpPort(portNumber)); }, isDefault: true); var command = new Command("http", "Starts dotnet-interactive with kernel functionality exposed over http") { defaultKernelOption, httpPortOption, logPathOption }; command.Handler = CommandHandler.Create <StartupOptions, KernelHttpOptions, IConsole, InvocationContext>( (startupOptions, options, console, context) => { RegisterKernelInServiceCollection( services, startupOptions, options.DefaultKernel, serviceCollection => { serviceCollection.AddSingleton(_ => { var frontendEnvironment = new BrowserFrontendEnvironment(); return(frontendEnvironment); }); serviceCollection.AddSingleton <FrontendEnvironment>(c => c.GetRequiredService <BrowserFrontendEnvironment>()); }); return(startHttp(startupOptions, console, startServer, context)); }); return(command); } Command StdIO() { var httpPortRangeOption = new Option <HttpPortRange>( "--http-port-range", parseArgument: result => result.Tokens.Count == 0 ? HttpPortRange.Default : ParsePortRangeOption(result), description: "Specifies the range of ports to use to enable HTTP services"); var command = new Command( "stdio", "Starts dotnet-interactive with kernel functionality exposed over standard I/O") { defaultKernelOption, httpPortRangeOption }; command.Handler = CommandHandler.Create <StartupOptions, StdIOOptions, IConsole, InvocationContext, CancellationToken>( (startupOptions, options, console, context, cancellationToken) => { if (startupOptions.EnableHttpApi) { RegisterKernelInServiceCollection( services, startupOptions, options.DefaultKernel, serviceCollection => { serviceCollection.AddSingleton(_ => new HtmlNotebookFrontedEnvironment()); serviceCollection.AddSingleton <FrontendEnvironment>(c => c.GetService <HtmlNotebookFrontedEnvironment>()); }, kernel => { StdIOCommand.CreateServer(kernel, console); kernel.UseQuiCommand(disposeOnQuit, cancellationToken); }); return(startHttp(startupOptions, console, startServer, context)); } { var kernel = CreateKernel(options.DefaultKernel, new BrowserFrontendEnvironment(), startupOptions); disposeOnQuit.Add(kernel); kernel.UseQuiCommand(disposeOnQuit, cancellationToken); return(startStdIO( startupOptions, kernel, console)); } }); return(command); }