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
12 changes: 12 additions & 0 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,12 @@ With an empty history, or when stdin is redirected / non-interactive (CI, pipes)
Local paths that no longer exist are dropped from the picker; remote refs stay listed
even when their download bundle is gone (the next run re-downloads as usual).

Gists are labeled like GitHub’s UI as `owner/filename` (for example `kzu/run.cs`)
instead of the full host URL. When two or more history entries share the same
owner and file name, the picker switches to a short ref form with the first
seven characters of the gist id: `owner/shortsha:file` (for example
`kzu/0ac826d:run.cs`).

```console
# Interactive: pick a previous run, then optional args
dnx go
Expand Down Expand Up @@ -111,9 +117,15 @@ dnx go -- kzu/sandbox@v1.2.3:src/hello.cs
# Full host (GitHub, Gist, GitLab, Azure DevOps)
dnx go -- github.com/kzu/sandbox@main:hello.cs
dnx go -- gist.github.com/kzu/0ac826dc7de666546aaedd38e5965381
# Multi-file gist: pick a specific entry file with :path (same as repos)
dnx go -- gist.github.com/kzu/0ac826dc7de666546aaedd38e5965381:run.cs
dnx go -- gitlab.com/kzu/runcs/-/blob/main/program.cs
```

Without `:path`, a gist (or repo) defaults to `program.cs` when present, otherwise
the first top-level `.cs` file. Use `:filename.cs` when a multi-file gist should
run a different entry point.

The first argument is resolved by first checking if it is a local file (`File.Exists`).
If not, it falls back to parsing it as a remote ref (`owner/repo[@ref][:path]`).

Expand Down
230 changes: 230 additions & 0 deletions src/Tests/RunHistoryTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -315,6 +315,236 @@ public void ParseArgsLine_roundtrips_via_ToRunInputs_as_shipped_path()
Assert.Equal(["arg1", "arg2"], appArgs);
}

[Fact]
public void List_formats_gists_as_owner_filename_from_path_suffix()
{
var path = NewSettingsPath();
try
{
SettingsStore.Save(new Settings
{
History =
[
new HistoryEntry
{
Input = "gist.github.com/kzu/0ac826dc7de666546aaedd38e5965381:run.cs",
LastUsedUtc = DateTimeOffset.UtcNow,
UseCount = 1,
},
new HistoryEntry
{
Input = "kzu/sandbox",
LastUsedUtc = DateTimeOffset.UtcNow,
UseCount = 1,
},
],
}, path);

var listed = RunHistory.List(path);
var gist = listed.Single(e => e.Input.Contains("0ac826dc", StringComparison.Ordinal));
Assert.Equal("kzu/run.cs", gist.Display);

var repo = listed.Single(e => e.Input == "kzu/sandbox");
Assert.Equal("kzu/sandbox", repo.Display);
}
finally
{
TryDelete(path);
}
}

[Fact]
public void List_formats_gists_as_owner_filename_from_cached_entry()
{
var path = NewSettingsPath();
try
{
SettingsStore.Save(new Settings
{
History =
[
new HistoryEntry
{
Input = "gist.github.com/kzu/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
LastUsedUtc = DateTimeOffset.UtcNow,
UseCount = 1,
Entry = "hello.cs",
},
],
}, path);

var listed = RunHistory.List(path);
Assert.Single(listed);
Assert.Equal("kzu/hello.cs", listed[0].Display);
}
finally
{
TryDelete(path);
}
}

[Fact]
public void List_disambiguates_same_owner_file_with_short_gist_id()
{
var path = NewSettingsPath();
try
{
SettingsStore.Save(new Settings
{
History =
[
new HistoryEntry
{
Input = "gist.github.com/kzu/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
LastUsedUtc = DateTimeOffset.UtcNow,
UseCount = 2,
Entry = "run.cs",
},
new HistoryEntry
{
Input = "gist.github.com/kzu/bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb",
LastUsedUtc = DateTimeOffset.UtcNow,
UseCount = 1,
Entry = "run.cs",
},
new HistoryEntry
{
Input = "gist.github.com/other/cccccccccccccccccccccccccccccccc",
LastUsedUtc = DateTimeOffset.UtcNow,
UseCount = 1,
Entry = "run.cs",
},
],
}, path);

var listed = RunHistory.List(path);
var kzuA = listed.Single(e => e.Input.Contains("aaaaaaaa", StringComparison.Ordinal));
var kzuB = listed.Single(e => e.Input.Contains("bbbbbbbb", StringComparison.Ordinal));
var other = listed.Single(e => e.Input.Contains("cccccccc", StringComparison.Ordinal));

Assert.Equal("kzu/aaaaaaa:run.cs", kzuA.Display);
Assert.Equal("kzu/bbbbbbb:run.cs", kzuB.Display);
// Different owner — no disambiguation needed even though file name matches.
Assert.Equal("other/run.cs", other.Display);
}
finally
{
TryDelete(path);
}
}

[Fact]
public void List_gist_without_known_file_keeps_raw_input()
{
var path = NewSettingsPath();
try
{
var input = "gist.github.com/kzu/dddddddddddddddddddddddddddddddd";
SettingsStore.Save(new Settings
{
History =
[
new HistoryEntry
{
Input = input,
LastUsedUtc = DateTimeOffset.UtcNow,
UseCount = 1,
},
],
}, path);

var listed = RunHistory.List(path);
Assert.Single(listed);
Assert.Equal(input, listed[0].Display);
}
finally
{
TryDelete(path);
}
}

[Fact]
public void FormatBaseDisplay_and_ShortId_helpers()
{
Assert.Equal("aaaaaaa", RunHistory.ShortId("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"));
Assert.Equal("short", RunHistory.ShortId("short"));

var withPath = RunHistory.FormatBaseDisplay(
"gist.github.com/kzu/0ac826dc7de666546aaedd38e5965381:app.cs",
cachedEntry: null,
out var owner, out var gistId, out var file);
Assert.Equal("kzu/app.cs", withPath);
Assert.Equal("kzu", owner);
Assert.Equal("0ac826dc7de666546aaedd38e5965381", gistId);
Assert.Equal("app.cs", file);

var withCache = RunHistory.FormatBaseDisplay(
"gist.github.com/kzu/0ac826dc7de666546aaedd38e5965381",
cachedEntry: "from-cache.cs",
out owner, out gistId, out file);
Assert.Equal("kzu/from-cache.cs", withCache);
Assert.Equal("kzu", owner);
Assert.Equal("0ac826dc7de666546aaedd38e5965381", gistId);
Assert.Equal("from-cache.cs", file);

var repo = RunHistory.FormatBaseDisplay("kzu/sandbox", null, out owner, out gistId, out file);
Assert.Equal("kzu/sandbox", repo);
Assert.Null(owner);
Assert.Null(gistId);
Assert.Null(file);
}

[Fact]
public void Record_captures_gist_entry_from_path_suffix()
{
var path = NewSettingsPath();
try
{
RunHistory.Record("gist.github.com/kzu/0ac826dc7de666546aaedd38e5965381:run.cs", path);

var settings = SettingsStore.Load(path);
Assert.Single(settings.History!);
Assert.Equal("run.cs", settings.History![0].Entry);

var listed = RunHistory.List(path);
Assert.Equal("kzu/run.cs", listed[0].Display);
}
finally
{
TryDelete(path);
}
}

[Fact]
public void List_formats_https_gist_url_with_cached_entry()
{
var path = NewSettingsPath();
try
{
SettingsStore.Save(new Settings
{
History =
[
new HistoryEntry
{
Input = "https://gist.github.com/kzu/0ac826dc7de666546aaedd38e5965381",
LastUsedUtc = DateTimeOffset.UtcNow,
UseCount = 1,
Entry = "run.cs",
},
],
}, path);

var listed = RunHistory.List(path);
Assert.Single(listed);
Assert.Equal("kzu/run.cs", listed[0].Display);
}
finally
{
TryDelete(path);
}
}

static string NewSettingsPath()
=> Path.Combine(Path.GetTempPath(), "go-hist-test-" + Guid.NewGuid().ToString("N") + ".toml");

Expand Down
Loading
Loading