Skip to content

Commit

Permalink
readme and command line improvements
Browse files Browse the repository at this point in the history
  • Loading branch information
jonsequitur committed Sep 29, 2022
1 parent aa4149d commit 743cb22
Show file tree
Hide file tree
Showing 6 changed files with 115 additions and 62 deletions.
76 changes: 54 additions & 22 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,19 +9,19 @@ This is a personal project. Hopefully you enjoy it and find it useful. Contribut
[![NuGet Status](https://img.shields.io/nuget/v/dotnet-repl.svg?style=flat)](https://www.nuget.org/packages/dotnet-repl/)
[![Build status](https://ci.appveyor.com/api/projects/status/j544mv4bxysjryru?svg=true)](https://ci.appveyor.com/project/jonsequitur/dotnet-repl)

## Installation
# Installation

To install `dotnet-repl`, run the following in your terminal:

```console
> dotnet tool install -g dotnet-repl
```

## Features
# Features

Here's what you can do with it:

### *Code in C#*
## *Code in C#*

You can start `dotnet-repl` in one of a number of different language modes. The default is C#, so the following two commands are equivalent:

Expand All @@ -38,7 +38,7 @@ One notable feature of C# scripting is the ability to specify a return value for

<img src="https://user-images.githubusercontent.com/547415/121977410-d0cfab00-cd3a-11eb-84a0-ab4f8889c9c7.png" width="60%" />

### *Code in F#*
## *Code in F#*

You can also start up the REPL in F# mode with `--default-kernel` or set the environment variable `DOTNET_REPL_DEFAULT_KERNEL` to `fsharp`:

Expand All @@ -52,7 +52,7 @@ You can also start up the REPL in F# mode with `--default-kernel` or set the env

<img src="https://user-images.githubusercontent.com/547415/121456837-8d9cc300-c95b-11eb-9a91-1daae2dbc655.png" width="60%" />

### 📝 *Submit multi-line entries*
## 📝 *Submit multi-line entries*

By pressing `Shift-Enter`, you can add multiple lines before running your code using `Enter`. This can be useful for creating multi-line code constructs, including declaring classes.

Expand All @@ -62,49 +62,81 @@ Another handy aspect of multi-line entries is that you no longer need to use the

<img src="https://user-images.githubusercontent.com/547415/121977822-b5b16b00-cd3b-11eb-90d6-2798289a47d5.png" width="60%" />

### 🚥 *Switch languages within the same session*
## 🚥 *Switch languages within the same session*

<img src="https://user-images.githubusercontent.com/547415/121456913-ab6a2800-c95b-11eb-9a47-0f0828b2ba3b.png" width="60%" />

### 🎁 *Add NuGet packages*
## 🎁 *Add NuGet packages*

You can use `#r nuget` to install a package for the duration of the current session.

<img src="https://user-images.githubusercontent.com/547415/121978012-235d9700-cd3c-11eb-89d0-ba367089208c.gif" width="60%" />


### *Initialize your REPL session using a notebook*
## 🌱 *Initialize your REPL session using a notebook, script, or code file*

You can use a notebook file (either `.ipynb` or `.dib`) as an initialization script for the REPL.
You can use a file containing code as an initialization script for the REPL.

```console
> dotnet repl --notebook /path/to/notebook.ipynb
> dotnet repl --run /path/to/notebook.ipynb
```

<img src="https://user-images.githubusercontent.com/547415/121982282-13e24c00-cd44-11eb-9c00-b0e04bb18276.gif" width="60%" />
The following file types are supported

### *Run a notebook as a script*
<img src="https://user-images.githubusercontent.com/547415/192895883-5e80e419-26dd-422c-b4bc-4d3533b861fb.gif" width="60%" />

You might also want to just use a notebook as a non-interactive script. You can do this by adding the `--exit-after-run` flag.
## 🏃🏽 *Run a notebook, script, or code file and then exit*

You might also want to just use a notebook or other file containing code as a non-interactive script. You can do this by adding the `--exit-after-run` flag. As long as the file extension indicates a language understood by .NET Interactive, it will try to run it.

```console
> dotnet repl --notebook /path/to/notebook.ipynb --exit-after-run
> dotnet repl --run /path/to/notebook.ipynb --exit-after-run
```

Both `.ipynb` and `.dib` files are supported.
File formats currently supported are:

* `.ipynb`: A Jupyter notebook, which can contain code cells in multiple different languages understood by .NET Interactive.
* `.dib`: A .NET Interactive script file.
* `.cs`: A C# source code file. (Some language constructs, such as namespaces, are not supported, so this one is extra experimental.)
* `.csx`: A C# script file.
* `.fs`: An F# source code file.
* `.fsx`: An F# script file.
* `.ps1`: A PowerShell script.
* `.html`: An HTML file. (This will render in an external browser window.)
* `.js`: A JavaScript file. (This will render in an external browser window.)

If all of the notebook's cells execute successfully, a `0` exit code is returned. Otherwise, `1` is returned. This can be used as a
If all of the notebook's cells execute successfully, a `0` exit code is returned. Otherwise, `2` is returned. This can be used as a
way to test notebooks.

<img width="60%" alt="image" src="https://user-images.githubusercontent.com/547415/176486922-8db22f68-3198-4a5f-bdf7-398805b9f295.png">
<img width="60%" alt="image" src="https://user-images.githubusercontent.com/547415/192914782-1c977e51-1715-40ca-9466-5ccc8219c23e.png">

If you also want to capture the notebook output when it runs, you can do so by specifying the ` --output-path` and `--output-format` options. `--output-path` should be the file name you would like to write to. `--output-format` can be either `ipynb` or `trx`. `ipynb` is the default and will write a Jupyter notebook file with the outputs captured from the run. You can open this file using the [.NET Interactive Notebooks](https://marketplace.visualstudio.com/items?itemName=ms-dotnettools.dotnet-interactive-vscode) extension in Visual Studio Code, or any number of other Jupyter readers, and it can be displayed in GitHub. The `trx` format is a .NET test result file and can be useful in CI pipelines such as Azure DevOps, or can be opened with Visual Studio, or read with the [`t-rex`](https://www.nuget.org/packages/t-rex) tool.

## 🛳️ *Import a notebook or script and run it*

If the REPL is already running, you can import a file into it and run it immediately using the `#!import` magic command. All of the same file types that `--run` supports are supported by `#!import`.

## Pass parameters when running a notebook or script

If a notebook contains magic commands with `@input` tokens, running them in a notebook editor like [.NET Interactive Notebooks](https://marketplace.visualstudio.com/items?itemName=ms-dotnettools.dotnet-interactive-vscode) will create a prompt for user input. Values for these inputs can provided on the command line using the `--input` option.

If you redirect output when using `--exit-after-run`, the output will be formatted using the `.ipynb` JSON format, allowing you to rerun the code or view the results in a notebook editor.
For example, let's say you have a notebook called `notebook.ipynb` containing the following magic commnand:

```csharp
#!connect mssql --kernel-name mydatabase @input:connectionString
```

You can pass in the connection string from the command line like this:

```console
> dotnet repl --run notebook.ipynb --input connectionString="Persist Security Info=False; Integrated Security=true; Initial Catalog=MyDatabase; Server=localhost"
```

### 💁‍♀️ *Ask for help*
## 💁‍♀️ *Ask for help*

You can see help for the REPL by running the `#!help` magic command. I won't print it here because it's a work in progress. Just give it a try.
You can see help for the REPL by running the `#!help` magic command. I won't print it all here because it's a work in progress. Just give it a try.

### *Keyboard shortcuts*
## *Keyboard shortcuts*

`dotnet-repl` supports a number of keyboard shortcuts. These will evolve over time but for now, here they are:

Expand All @@ -119,7 +151,7 @@ Keybinding | What it does
`Ctrl+Down` | Go forward through your submission history (current session only)


### 🧙‍♂️ *Magic commands*
## 🧙‍♂️ *Magic commands*

Because `dotnet-repl` is built on .NET Interactive, it supports "magic commands". You can recognize a magic command by the `#!` at the start of a line.

Expand Down
37 changes: 35 additions & 2 deletions src/dotnet-repl.Tests/CommandLineParserTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,13 @@ namespace dotnet_repl.Tests;

public class CommandLineParserTests
{
private readonly Parser _parser;

public CommandLineParserTests()
{
_parser = CommandLineParser.Create();
}

[Fact(Skip = "Needs System.CommandLine improvements")]
public void Help_is_snazzy()
{
Expand All @@ -22,10 +29,36 @@ public void Help_is_snazzy()
Assert.True(false, "Test Help_is_snazzy is not written yet.");
}


[Fact]
public void Parser_configuration_is_valid()
{
CommandLineParser.Create().Configuration.ThrowIfInvalid();
_parser.Configuration.ThrowIfInvalid();
}

[Fact]
public void Inputs_parse_key_value_pairs_into_a_dictionary()
{
var result = _parser.Parse("--input one=1 --input two=2");

result.GetValueForOption(CommandLineParser.InputsOption)
.Should()
.ContainKey("one")
.WhoseValue.Should().Be("1");

result.GetValueForOption(CommandLineParser.InputsOption)
.Should()
.ContainKey("two")
.WhoseValue.Should().Be("2");
}

[Fact]
public void Input_values_can_contain_spaces_if_quoted()
{
var result = _parser.Parse("--input words=\"the quick brown fox\"");

result.GetValueForOption(CommandLineParser.InputsOption)
.Should()
.ContainKey("words")
.WhoseValue.Should().Be("the quick brown fox");
}
}
52 changes: 19 additions & 33 deletions src/dotnet-repl/CommandLineParser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -56,14 +56,21 @@ public static class CommandLineParser
public static Option<IDictionary<string, string>> InputsOption = new(
"--input",
description:
"Specifies in a value for @input tokens in magic commands in the notebook, using the format --input <key>=<value>",
"Specifies in a value for @input tokens in magic commands in the notebook, using the format --input <key>=<value>. Values containing spaces should be wrapped in quotes.",
parseArgument: result =>
{
var dict = new Dictionary<string, string>();

foreach (var token in result.Tokens.Select(t => t.Value))
{
var keyAndValue = token.Split("=");
var keyAndValue = token.Split("=", 2);

if (keyAndValue.Length != 2)
{
result.ErrorMessage = "The --input option requires an argument in the format <key>=<value>";
return null;
}

dict[keyAndValue[0]] = keyAndValue[1];
}

Expand All @@ -76,7 +83,7 @@ public static class CommandLineParser
public static Option<FileInfo> OutputPathOption = new Option<FileInfo>(
"--output-path",
description:
"Run the file specified by --notebook and writes the output to the file specified by --output-path")
$"Run the file specified by {RunOption.Aliases.First()} and writes the output to the file specified by --output-path")
.LegalFilePathsOnly();

public static Option<OutputFormat> OutputFormatOption = new(
Expand All @@ -99,7 +106,6 @@ public static Parser Create(
OutputFormatOption,
OutputPathOption,
InputsOption,
ConvertCommand(),
DescribeCommand(),
};

Expand Down Expand Up @@ -127,35 +133,8 @@ public static Parser Create(
.UseHelpBuilder(_ => new SpectreHelpBuilder(LocalizationResources.Instance))
.Build();

Command ConvertCommand()
{
// FIX: (ConvertCommand)

var notebookOption = new Option<FileInfo>("--notebook", "The notebook file to convert")
.ExistingOnly();

var outputPathOption = new Option<FileInfo>("--output-path")
.LegalFilePathsOnly();

var outputFormatOption = new Option<OutputFormat>(
"--output-format",
description: $"The output format to be used when running a notebook with the {RunOption.Aliases.First()} and {ExitAfterRunOption.Aliases.First()} options",
getDefaultValue: () => OutputFormat.ipynb);

var command = new Command("convert")
{
notebookOption,
outputPathOption,
outputFormatOption
};

return command;
}

Command DescribeCommand()
{
// FIX: (DescribeCommand)

var notebookArgument = new Argument<FileInfo>("notebook")
.ExistingOnly();

Expand Down Expand Up @@ -216,7 +195,7 @@ private static async Task<IDisposable> StartAsync(

InteractiveDocument? notebook = default;

if (options.Notebook is { } file)
if (options.FileToRun is { } file)
{
notebook = await DocumentParser.LoadInteractiveDocumentAsync(file, kernel);
}
Expand All @@ -225,7 +204,14 @@ private static async Task<IDisposable> StartAsync(
{
if (isTerminal)
{
ansiConsole.Announce($"📓 Running notebook: {options.Notebook}");
if (options.FileToRun?.Extension is ".ipynb" or ".dib")
{
ansiConsole.Announce($"📓 Running notebook: {options.FileToRun}");
}
else
{
ansiConsole.Announce($"📄 Running file: {options.FileToRun}");
}
}
}

Expand Down
2 changes: 0 additions & 2 deletions src/dotnet-repl/DocumentParser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -30,15 +30,13 @@ public static async Task<InteractiveDocument> LoadInteractiveDocumentAsync(
{
".ipynb" => Notebook.Parse(fileContents, kernelInfos),
".dib" => CodeSubmission.Parse(fileContents, kernelInfos),

".cs" => new InteractiveDocument { new InteractiveDocumentElement(fileContents, "csharp") },
".csx" => new InteractiveDocument { new InteractiveDocumentElement(fileContents, "csharp") },
".fs" => new InteractiveDocument { new InteractiveDocumentElement(fileContents, "fsharp") },
".fsx" => new InteractiveDocument { new InteractiveDocumentElement(fileContents, "fsharp") },
".ps1" => new InteractiveDocument { new InteractiveDocumentElement(fileContents, "pwsh") },
".html" => new InteractiveDocument { new InteractiveDocumentElement(fileContents, "html") },
".js" => new InteractiveDocument { new InteractiveDocumentElement(fileContents, "javascript") },

_ => throw new InvalidOperationException($"Unrecognized extension for a notebook: {file.Extension}"),
};
}
Expand Down
4 changes: 4 additions & 0 deletions src/dotnet-repl/Properties/launchSettings.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,10 @@
"REPL with --run": {
"commandName": "Project",
"commandLineArgs": "--run c:\\temp\\automation\\github.html"
},
"run and exit, params": {
"commandName": "Project",
"commandLineArgs": " --run c:\\temp\\automation\\script.dib --input greeting=\"Hey!\""
}
}
}
6 changes: 3 additions & 3 deletions src/dotnet-repl/StartupOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ public class StartupOptions
public StartupOptions(
string defaultKernel = "csharp",
DirectoryInfo? workingDir = null,
FileInfo? notebook = null,
FileInfo? fileToRun = null,
DirectoryInfo? logPath = null,
bool exitAfterRun = false,
OutputFormat outputFormat = OutputFormat.ipynb,
Expand All @@ -17,7 +17,7 @@ public StartupOptions(
{
DefaultKernelName = defaultKernel;
WorkingDir = workingDir ?? new DirectoryInfo(Directory.GetCurrentDirectory());
Notebook = notebook;
FileToRun = fileToRun;
LogPath = logPath;
ExitAfterRun = exitAfterRun;
OutputFormat = outputFormat;
Expand All @@ -31,7 +31,7 @@ public StartupOptions(

public DirectoryInfo WorkingDir { get; }

public FileInfo? Notebook { get; set; }
public FileInfo? FileToRun { get; set; }

public bool ExitAfterRun { get; set; }

Expand Down

0 comments on commit 743cb22

Please sign in to comment.