Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 50 additions & 0 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,56 @@ The last download etag is used to avoid downloading on each run.

<!-- #gist -->

## Aliases

<!-- #aliases -->

`runfile` and `gist` share the same alias store, so a name created with one tool works with the other.
Aliases are stored in your global `.netconfig` file (`~/.netconfig` on Linux/macOS, `%USERPROFILE%\.netconfig` on Windows).

### Creating an alias

Pass `--dnx-alias` (or `--alias`) when running a ref:

```
dnx TOOL REF --dnx-alias NAME
```

Examples:

```
dnx runfile kzu/sandbox@main:run.cs --dnx-alias sandbox
dnx gist kzu/0ac826dc7de666546aaedd38e5965381 --dnx-alias demo
```

After that, use the alias instead of the full ref:

```
dnx TOOL NAME
```

Re-running with the same alias name updates the ref it points to.

### Managing aliases

```
[dnx] TOOL alias
[dnx] TOOL alias delete NAME
[dnx] TOOL alias rename OLD NEW
```

`TOOL` is `runfile` or `gist`.

| Command | Description |
| --- | --- |
| `alias` | List all configured aliases and the refs they point to. Refs link to the source on the web; `github.com/` is omitted when it is the default host. |
| `alias delete NAME` | Remove an alias. |
| `alias rename OLD NEW` | Rename an alias, keeping the same ref. Fails if `NEW` already exists. |

Running a tool with no arguments shows the usage help followed by the alias table when any aliases are configured.

<!-- #aliases -->

# Dogfooding

[![CI Version](https://img.shields.io/endpoint?url=https://shields.kzu.app/vpre/gist/main&label=nuget.ci&color=brightgreen)](https://pkg.kzu.app/index.json)
Expand Down
155 changes: 155 additions & 0 deletions src/Core/AliasCommands.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
using DotNetConfig;
using Spectre.Console;

namespace Devlooped;

public static class AliasCommands
{
public const string CommandName = "alias";
internal const string Section = "runfile";

public static int Execute(string[] args, Config config)
{
if (args.Length == 0)
return List(config);

if (args[0] is "delete")
return Delete(args, config);

if (args[0] is "rename")
return Rename(args, config);

ShowUsage();
return 1;
}

/// <summary>Writes the alias table when any are configured. Returns whether a table was written.</summary>
public static bool WriteTableIfAny(Config config)
{
var aliases = GetAliases(config);
if (aliases.Length == 0)
return false;

AnsiConsole.WriteLine();
AnsiConsole.WriteLine("Aliases:");

WriteTable(aliases);
return true;
}

static int List(Config config)
{
var aliases = GetAliases(config);
if (aliases.Length == 0)
{
AnsiConsole.MarkupLine(":information_source: [grey]No aliases configured. Use[/] [yellow]--dnx-alias[/] [grey]to create one.[/]");
return 0;
}

AnsiConsole.MarkupLine($"[grey]Configured ref aliases[/] [dim]({aliases.Length})[/]:");
WriteTable(aliases);
return 0;
}

static int Delete(string[] args, Config config)
{
if (args.Length < 2 || string.IsNullOrWhiteSpace(args[1]))
{
AnsiConsole.MarkupLine(":cross_mark: [red]Missing alias name.[/]");
ShowUsage();
return 1;
}

var name = args[1];
if (config.GetString(Section, name) is null)
{
AnsiConsole.MarkupLine($":cross_mark: [red]Alias[/] [cyan bold]{Markup.Escape(name)}[/] [red]not found.[/]");
return 1;
}

config.Unset(Section, name);
AnsiConsole.MarkupLine($":check_mark_button: [green]Deleted alias[/] [cyan bold]{Markup.Escape(name)}[/][green].[/]");
return 0;
}

static int Rename(string[] args, Config config)
{
if (args.Length < 3 || string.IsNullOrWhiteSpace(args[1]) || string.IsNullOrWhiteSpace(args[2]))
{
AnsiConsole.MarkupLine(":cross_mark: [red]Missing alias name.[/]");
ShowUsage();
return 1;
}

var oldName = args[1];
var newName = args[2];

if (string.Equals(oldName, newName, StringComparison.OrdinalIgnoreCase))
{
AnsiConsole.MarkupLine($":information_source: [grey]Alias[/] [cyan bold]{Markup.Escape(oldName)}[/] [grey]already has that name.[/]");
return 0;
}

if (config.GetString(Section, oldName) is not { } @ref)
{
AnsiConsole.MarkupLine($":cross_mark: [red]Alias[/] [cyan bold]{Markup.Escape(oldName)}[/] [red]not found.[/]");
return 1;
}

if (config.GetString(Section, newName) is not null)
{
AnsiConsole.MarkupLine($":cross_mark: [red]Alias[/] [cyan bold]{Markup.Escape(newName)}[/] [red]already exists.[/]");
return 1;
}

config.SetString(Section, newName, @ref).Unset(Section, oldName);
AnsiConsole.MarkupLine($":check_mark_button: [green]Renamed alias[/] [cyan bold]{Markup.Escape(oldName)}[/] [green]to[/] [cyan bold]{Markup.Escape(newName)}[/][green].[/]");
return 0;
}

static (string Name, string Ref)[] GetAliases(Config config) =>
config
.Where(entry => entry.Section == Section && entry.Subsection == null)
.Select(entry => entry.Variable)
.Distinct(StringComparer.OrdinalIgnoreCase)
.OrderBy(name => name, StringComparer.OrdinalIgnoreCase)
.Select(name => (Name: name, Ref: config.GetString(Section, name)!))
.Where(alias => alias.Ref != null)
.ToArray();

static void WriteTable((string Name, string Ref)[] aliases)
{
var table = new Table()
.RoundedBorder()
.BorderColor(Color.Grey23)
.AddColumn(new TableColumn("[lime bold]:label: Alias[/]").LeftAligned())
.AddColumn(new TableColumn("[lime bold]:link: Ref[/]").LeftAligned());

foreach (var (name, @ref) in aliases)
table.AddRow($"[cyan bold]{Markup.Escape(name)}[/]", FormatRefLink(@ref));

AnsiConsole.Write(table);
}

static string FormatRef(string @ref) =>
@ref.StartsWith("github.com/", StringComparison.OrdinalIgnoreCase)
? @ref["github.com/".Length..]
: @ref;

static string FormatRefLink(string @ref)
{
var display = Markup.Escape(FormatRef(@ref));
return RemoteRef.TryParse(@ref, out var parsed)
? $"[link={parsed.ToWebUrl()}]{display}[/]"
: $"[link]{display}[/]";
}

static void ShowUsage() =>
AnsiConsole.MarkupLine(
"""
[grey]Usage:[/]
[cyan bold]alias[/] [grey]List configured ref aliases.[/]
[cyan bold]alias delete[/] [yellow]NAME[/] [grey]Delete a ref alias.[/]
[cyan bold]alias rename[/] [yellow]OLD[/] [yellow]NEW[/] [grey]Rename a ref alias.[/]
""");
}
29 changes: 29 additions & 0 deletions src/Core/RemoteRefExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,35 @@ public static class RemoteRefExtensions
public string TempPath => Path.Join(GetTempRoot(), location.Host ?? "github.com", location.Owner, location.Project ?? "", location.Repo, location.Ref ?? "main");

public string EnsureTempPath() => Directory.CreateUserDirectory(location.TempPath);

public string ToWebUrl()
{
var host = location.Host ?? "github.com";
var reference = location.Ref ?? "main";

return host switch
{
"gist.github.com" => $"https://gist.github.com/{location.Owner}/{location.Repo}",
"gitlab.com" => location.Path != null
? $"https://gitlab.com/{location.Owner}/{location.Repo}/-/blob/{reference}/{location.Path}"
: $"https://gitlab.com/{location.Owner}/{location.Repo}",
"dev.azure.com" => ToAzureDevOpsWebUrl(location),
_ => location.Path != null
? $"https://github.com/{location.Owner}/{location.Repo}/blob/{reference}/{location.Path}"
: $"https://github.com/{location.Owner}/{location.Repo}/tree/{reference}",
};
}
}

static string ToAzureDevOpsWebUrl(RemoteRef location)
{
var project = location.Project ?? location.Repo;
var url = $"https://dev.azure.com/{location.Owner}/{project}/_git/{location.Repo}";
if (location.Path != null)
url += $"?path=/{location.Path}";
if (location.Ref != null)
url += location.Path != null ? $"&version=GB{location.Ref}" : $"?version=GB{location.Ref}";
return url;
}

/// <summary>Obtains the temporary directory root, e.g., <c>/tmp/dotnet/runfile/</c>.</summary>
Expand Down
1 change: 1 addition & 0 deletions src/Tests/Tests.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="18.5.1" />
<PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="10.0.7" />
<PackageReference Include="Microsoft.Extensions.Configuration.UserSecrets" Version="10.0.7" />
<PackageReference Include="Tmds.DBus.Protocol" Version="0.93.0" />
<PackageReference Include="xunit" Version="2.9.3" />
<PackageReference Include="xunit.runner.visualstudio" Version="3.1.5" PrivateAssets="all" />
</ItemGroup>
Expand Down
11 changes: 11 additions & 0 deletions src/gist/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,9 @@
}

var config = Config.Build(Config.GlobalLocation);
if (args.Length > 0 && args[0] == AliasCommands.CommandName)
Environment.Exit(AliasCommands.Execute(args[1..], config));

if (args.Length > 0 && config.GetString("runfile", args[0]) is string aliased)
args = [aliased, .. args[1..]];

Expand All @@ -57,6 +60,12 @@
$"""
Usage:
[grey][[dnx]][/] [lime]{ThisAssembly.Project.ToolCommandName}[/] [grey][[OPTIONS]][/] [bold]<gistRef>[/] [grey italic][[<appArgs>...]][/]
[grey][[dnx]][/] [lime]{ThisAssembly.Project.ToolCommandName}[/] [bold]alias[/] [[delete NAME]]

Commands:
[bold]alias[/] List configured ref aliases.
[bold]alias delete[/] NAME Delete a ref alias.
[bold]alias rename[/] OLD NEW Rename a ref alias.

Arguments:
[bold]<GIST_REF>[/] Reference to gist file to run, with format [yellow]owner/gist[[@commit]][[:path]][/]
Expand All @@ -77,6 +86,8 @@
[bold]--dnx-debug[/] Launch the debugger before running.
[bold]--dnx-force[/] Force download, skipping ETag checking.
""");
if (args.Length == 0)
AliasCommands.WriteTableIfAny(config);
return;
}

Expand Down
1 change: 1 addition & 0 deletions src/gist/gist.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
<PackageReference Include="Spectre.Console" Version="0.55.2" />
<PackageReference Include="ThisAssembly" Version="2.1.2" PrivateAssets="all" />
<PackageReference Include="System.CommandLine" Version="2.0.7" />
<PackageReference Include="Tmds.DBus.Protocol" Version="0.93.0" />
</ItemGroup>

<ItemGroup Condition="'$(Pkggit-credential-manager)' != ''">
Expand Down
52 changes: 52 additions & 0 deletions src/gist/readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,58 @@ The last download etag is used to avoid downloading on each run.

<!-- #gist -->
<!-- ../../readme.md#gist -->

## Aliases

<!-- include ../../readme.md#aliases -->
<!-- #aliases -->

`runfile` and `gist` share the same alias store, so a name created with one tool works with the other.
Aliases are stored in your global `.netconfig` file (`~/.netconfig` on Linux/macOS, `%USERPROFILE%\.netconfig` on Windows).

### Creating an alias

Pass `--dnx-alias` (or `--alias`) when running a ref:

```
dnx TOOL REF --dnx-alias NAME
```

Examples:

```
dnx runfile kzu/sandbox@main:run.cs --dnx-alias sandbox
dnx gist kzu/0ac826dc7de666546aaedd38e5965381 --dnx-alias demo
```

After that, use the alias instead of the full ref:

```
dnx TOOL NAME
```

Re-running with the same alias name updates the ref it points to.

### Managing aliases

```
[dnx] TOOL alias
[dnx] TOOL alias delete NAME
[dnx] TOOL alias rename OLD NEW
```

`TOOL` is `runfile` or `gist`.

| Command | Description |
| --- | --- |
| `alias` | List all configured aliases and the refs they point to. Refs link to the source on the web; `github.com/` is omitted when it is the default host. |
| `alias delete NAME` | Remove an alias. |
| `alias rename OLD NEW` | Rename an alias, keeping the same ref. Fails if `NEW` already exists. |

Running a tool with no arguments shows the usage help followed by the alias table when any aliases are configured.

<!-- #aliases -->
<!-- ../../readme.md#aliases -->
<!-- include https://github.com/devlooped/.github/raw/main/osmf.md -->
## Open Source Maintenance Fee

Expand Down
11 changes: 11 additions & 0 deletions src/runfile/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,9 @@
}

var config = Config.Build(Config.GlobalLocation);
if (args.Length > 0 && args[0] == AliasCommands.CommandName)
Environment.Exit(AliasCommands.Execute(args[1..], config));

if (args.Length > 0 && config.GetString("runfile", args[0]) is string aliased)
args = [aliased, .. args[1..]];

Expand All @@ -52,6 +55,12 @@
$"""
Usage:
[grey][[dnx]][/] [lime]{ThisAssembly.Project.ToolCommandName}[/] [grey][[OPTIONS]][/] [bold]<repoRef>[/] [grey italic][[<appArgs>...]][/]
[grey][[dnx]][/] [lime]{ThisAssembly.Project.ToolCommandName}[/] [bold]alias[/] [[delete NAME]]

Commands:
[bold]alias[/] List configured ref aliases.
[bold]alias delete[/] NAME Delete a ref alias.
[bold]alias rename[/] OLD NEW Rename a ref alias.

Arguments:
[bold]<REPO_REF>[/] Reference to remote file to run, with format [yellow][[host/]]owner/repo[[@ref]][[:path]][/]
Expand All @@ -74,6 +83,8 @@
[bold]--dnx-debug[/] Launch the debugger before running.
[bold]--dnx-force[/] Force download, skipping ETag checking.
""");
if (args.Length == 0)
AliasCommands.WriteTableIfAny(config);
return;
}

Expand Down
4 changes: 4 additions & 0 deletions src/runfile/Properties/launchSettings.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,10 @@
"help": {
"commandName": "Project"
},
"alias": {
"commandName": "Project",
"commandLineArgs": "alias"
},
"args": {
"commandName": "Project",
"commandLineArgs": "--aot kzu/runfile@v1 dotnet rocks"
Expand Down
Loading
Loading