Exemplo n.º 1
0
        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);
            }
Exemplo n.º 2
0
    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);
        }
Exemplo n.º 3
0
        public static Parser Create(
            IServiceCollection services,
            StartServer startServer = null,
            StartHttp startHttp     = null,
            Action onServerStarted  = 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();
            };

            startHttp ??= HttpCommand.Do;



            var rootCommand = HttpServer();

            rootCommand.AddCommand(HttpServer());

            return(new CommandLineBuilder(rootCommand)
                   .UseDefaults()
                   .Build());

            Command HttpServer()
            {
                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 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 workingDirOption = new Option <DirectoryInfo>(
                    "--working-dir",
                    () => new DirectoryInfo(Environment.CurrentDirectory),
                    "Working directory to which to change after launching the kernel.");


                var httpCommand = new RootCommand("Starts rover and exposes http endpoint")
                {
                    httpPortOption, workingDirOption, logPathOption, verboseOption
                };


                httpCommand.Handler = CommandHandler.Create <StartupOptions, IConsole, InvocationContext, CancellationToken>(
                    (startupOptions, console, context, cancellationToken) =>
                {
                    var frontendEnvironment = new BrowserFrontendEnvironment();
                    var kernel = CreateKernel(frontendEnvironment, startupOptions);
                    kernel.UseQuitCommand();
                    services.AddKernel(kernel);

                    var kernelServer = kernel.CreateKernelServer(startupOptions.WorkingDir);
                    kernelServer.Start();

                    onServerStarted ??= () =>
                    {
                        kernelServer.NotifyIsReady();
                    };

                    return(startHttp(startupOptions, console, startServer, context));
                });

                return(httpCommand);
            }
        }
Exemplo n.º 4
0
        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));
            }

            startServer ??= (startupOptions, invocationContext) =>
            Program.ConstructWebHost(startupOptions).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 port 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>(JupyterHandler);

                var installCommand = new Command("install", "Install the .NET kernel for Jupyter")
                {
                    logPathOption,
                    verboseOption,
                    httpPortRangeOption,
                    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)
                {
                    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 port to use to enable HTTP services");

                var command = new Command(
                    "stdio",
                    "Starts dotnet-interactive with kernel functionality exposed over standard I/O")
                {
                    defaultKernelOption,
                    logPathOption,
                    httpPortRangeOption
                };

                command.Handler = CommandHandler.Create <StartupOptions, StdIOOptions, IConsole, InvocationContext>(
                    (startupOptions, options, console, context) =>
                {
                    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);
                        });

                        return(startHttp(startupOptions, console, startServer, context));
                    }
                    return(startStdIO(
                               startupOptions,
                               CreateKernel(options.DefaultKernel, new BrowserFrontendEnvironment(), startupOptions, null),
                               console));
                });

                return(command);
            }