feat(spec): support tool aliases (#107)
closes #57 IRIS now automatically read and suggest internal configuration aliases for Git (`.gitconfig`, `.git/config`) and Cargo (`.cargo/config.toml`) - Added the `Provider` interface and extended registry for CLI tools that define aliases - Git: parse aliases via `git config --show-scope`, supporting raw tab/space separation and scope differentiation - Cargo: traverse the directory tree upwards to root to read the `.cargo/config.toml` configuration - 0ms hot-path caching: key caching is calculated entirely via `os.Stat` mtime (no subprocess spawn) combined with a 2-second TTL window to ensure 0ms latency when typing - Lookup integration: display alias suggestions at the subcommand level and automatically expand aliases as the user enters further arguments (ignore shell pipeline commands starting with `!`)
This commit is contained in:
@@ -6,6 +6,8 @@ import (
|
||||
"strings"
|
||||
"sync"
|
||||
"testing"
|
||||
|
||||
"github.com/versenilvis/iris/spec/alias"
|
||||
)
|
||||
|
||||
func TestLookup(t *testing.T) {
|
||||
@@ -231,3 +233,160 @@ func TestLookup_NestedDirectoryTrailingSpace(t *testing.T) {
|
||||
t.Errorf("expected hello.txt in results, got %v", results)
|
||||
}
|
||||
}
|
||||
|
||||
type mockGitProvider struct{}
|
||||
|
||||
func (m *mockGitProvider) ToolName() string { return "git" }
|
||||
func (m *mockGitProvider) GetAliases(cwd string) []alias.AliasEntry {
|
||||
return []alias.AliasEntry{
|
||||
{
|
||||
Name: "co",
|
||||
Expansion: "checkout",
|
||||
Scope: "global",
|
||||
},
|
||||
{
|
||||
Name: "recent",
|
||||
Expansion: "!git for-each-ref --sort=committerdate --format='%(committerdate:relative) %(refname:short)' refs/heads/ | tail -10",
|
||||
Scope: "global",
|
||||
},
|
||||
{
|
||||
Name: "local-alias",
|
||||
Expansion: "status",
|
||||
Scope: "local",
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func TestLookup_GitRecentAlias(t *testing.T) {
|
||||
ResetRegistry()
|
||||
Register(&Spec{
|
||||
Name: "git",
|
||||
Subcommands: []Subcommand{
|
||||
{Name: "commit"},
|
||||
{Name: "checkout", Options: []Option{{Name: "-b"}}},
|
||||
},
|
||||
})
|
||||
alias.Register(&mockGitProvider{})
|
||||
t.Cleanup(alias.Reset)
|
||||
|
||||
// Test Type 1: Standard subcommand alias 'git co' -> 'git checkout'
|
||||
resultsCo := Lookup("git co ")
|
||||
foundCo := false
|
||||
for _, r := range resultsCo {
|
||||
if strings.Contains(r.Cmd, "-b") {
|
||||
foundCo = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !foundCo {
|
||||
t.Errorf("expected '-b' option after expanding 'git co ', got %v", resultsCo)
|
||||
}
|
||||
|
||||
// Test Type 2: Shell pipeline alias 'git rec' -> 'git recent'
|
||||
resultsRec := Lookup("git rec")
|
||||
foundRec := false
|
||||
for _, r := range resultsRec {
|
||||
if strings.Contains(r.Cmd, "git recent") {
|
||||
foundRec = true
|
||||
if r.Priority != 70 {
|
||||
t.Errorf("expected global alias priority 70, got %d", r.Priority)
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
if !foundRec {
|
||||
t.Errorf("expected 'git recent' suggestion for 'git rec', got %v", resultsRec)
|
||||
}
|
||||
|
||||
// Test Local Scope Priority (Priority = 85)
|
||||
resultsLocal := Lookup("git local")
|
||||
foundLocal := false
|
||||
for _, r := range resultsLocal {
|
||||
if strings.Contains(r.Cmd, "git local-alias") {
|
||||
foundLocal = true
|
||||
if r.Priority != 85 {
|
||||
t.Errorf("expected local alias priority 85, got %d", r.Priority)
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
if !foundLocal {
|
||||
t.Errorf("expected 'git local-alias' suggestion, got %v", resultsLocal)
|
||||
}
|
||||
|
||||
// Test Type 2 expansion with trailing space: 'git recent ' (shell pipeline alias starting with !) should be skipped and return nil
|
||||
resultsRecentSpace := Lookup("git recent ")
|
||||
if len(resultsRecentSpace) != 0 {
|
||||
t.Errorf("expected 0 suggestions for shell pipeline alias 'git recent ', got %v", resultsRecentSpace)
|
||||
}
|
||||
}
|
||||
|
||||
func TestLookup_InvalidSubcommandAfterAliasExpansion(t *testing.T) {
|
||||
ResetRegistry()
|
||||
Register(&Spec{
|
||||
Name: "git",
|
||||
Subcommands: []Subcommand{
|
||||
{Name: "commit"},
|
||||
{Name: "status", Subcommands: []Subcommand{{Name: "porcelain"}}},
|
||||
},
|
||||
})
|
||||
alias.Register(&mockGitProvider{})
|
||||
|
||||
// token beyond the alias expansion must be validated against the expanded subcommand
|
||||
results := Lookup("git local-alias badcmd ")
|
||||
if len(results) != 0 {
|
||||
t.Errorf("expected 0 suggestions for invalid subcommand after alias expansion, got %v", results)
|
||||
}
|
||||
|
||||
// expansion itself is still allowed even when it is not a registered subcommand
|
||||
resultsOk := Lookup("git co ")
|
||||
if len(resultsOk) == 0 {
|
||||
t.Errorf("expected suggestions for expanded alias 'git co ', got none")
|
||||
}
|
||||
}
|
||||
|
||||
func TestLookup_RealGitProvider(t *testing.T) {
|
||||
ResetRegistry()
|
||||
Register(&Spec{
|
||||
Name: "git",
|
||||
})
|
||||
alias.Reset()
|
||||
|
||||
// Isolate from the developer's real git configuration by pointing HOME at a temp dir
|
||||
home := t.TempDir()
|
||||
t.Setenv("HOME", home)
|
||||
if err := os.WriteFile(filepath.Join(home, ".gitconfig"), []byte("[alias]\nrecent = for-each-ref --sort=committerdate refs/heads\n"), 0644); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
cwd, _ := os.Getwd()
|
||||
provider := &alias.GitProvider{}
|
||||
aliases := provider.GetAliases(cwd)
|
||||
t.Logf("Real Git aliases: %v", aliases)
|
||||
foundAlias := false
|
||||
for _, a := range aliases {
|
||||
if a.Name == "recent" {
|
||||
foundAlias = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !foundAlias {
|
||||
t.Skipf("git unavailable or could not read isolated config, got aliases: %v", aliases)
|
||||
}
|
||||
alias.Register(provider)
|
||||
t.Cleanup(alias.Reset)
|
||||
|
||||
results := Lookup("git rec")
|
||||
t.Logf("Lookup('git rec') results: %v", results)
|
||||
found := false
|
||||
for _, r := range results {
|
||||
if strings.Contains(r.Cmd, "recent") {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
t.Errorf("expected 'git recent' in results for real GitProvider, got %v", results)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user