-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Sometimes it is useful to find specific files inside repository directories. This commit adds a `ggman find-file` command that allows to search for files within repository directories.
- Loading branch information
Showing
4 changed files
with
229 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,92 @@ | ||
package cmd | ||
|
||
//spellchecker:words path filepath github ggman goprogram exit pkglib | ||
import ( | ||
"path/filepath" | ||
|
||
"github.com/tkw1536/ggman" | ||
"github.com/tkw1536/ggman/env" | ||
"github.com/tkw1536/goprogram/exit" | ||
"github.com/tkw1536/pkglib/fsx" | ||
) | ||
|
||
//spellchecker:words positionals | ||
|
||
// FindFile is the 'ggman find-file' command. | ||
// | ||
// The 'find-file' command lists all repositories that currently contain a file or directory with the provided name. | ||
// The provided path may be relative to the root of the repository. | ||
// | ||
// --exit-code | ||
// | ||
// When provided, exit with code 1 if no repositories are found. | ||
// | ||
// --print-file | ||
// | ||
// Instead of listing the repository paths, print the filepath instead. | ||
var FindFile ggman.Command = findFile{} | ||
|
||
type findFile struct { | ||
Positionals struct { | ||
Path string `required:"1-1" positional-arg-name:"PATH" description:"name (or path) file to find"` | ||
} `positional-args:"true"` | ||
PrintFilePath bool `short:"p" long:"print-file" description:"instead of printing the repository paths, print the file paths"` | ||
ExitCode bool `short:"e" long:"exit-code" description:"exit with status code 1 when no repositories with provided file exist"` | ||
} | ||
|
||
func (findFile) Description() ggman.Description { | ||
return ggman.Description{ | ||
Command: "find-file", | ||
Description: "list repositories containing a specific file", | ||
|
||
Requirements: env.Requirement{ | ||
NeedsRoot: true, | ||
}, | ||
} | ||
} | ||
|
||
func (f findFile) AfterParse() error { | ||
if !filepath.IsLocal(f.Positionals.Path) { | ||
return errFindFileNotLocal | ||
} | ||
return nil | ||
} | ||
|
||
var errFindFileCustom = exit.Error{ | ||
ExitCode: exit.ExitGeneric, | ||
} | ||
|
||
var errFindFileNotLocal = exit.Error{ | ||
ExitCode: exit.ExitCommandArguments, | ||
Message: "path argument is not a local path", | ||
} | ||
|
||
func (f findFile) Run(context ggman.Context) error { | ||
foundRepo := false | ||
for _, repo := range context.Environment.Repos(true) { | ||
|
||
candidate := filepath.Join(repo, f.Positionals.Path) | ||
ok, err := fsx.Exists(candidate) | ||
if err != nil { | ||
panic(err) | ||
} | ||
if !ok { | ||
continue | ||
} | ||
|
||
foundRepo = true | ||
if f.PrintFilePath { | ||
context.Println(candidate) | ||
} else { | ||
context.Println(repo) | ||
} | ||
} | ||
|
||
// if we have --exit-code set and no results | ||
// we need to exit with an error code | ||
if f.ExitCode && !foundRepo { | ||
return errFindFileCustom | ||
} | ||
|
||
return nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,124 @@ | ||
package cmd | ||
|
||
//spellchecker:words path filepath testing github ggman internal mockenv | ||
import ( | ||
"os" | ||
"path/filepath" | ||
"testing" | ||
|
||
"github.com/tkw1536/ggman/internal/mockenv" | ||
) | ||
|
||
//spellchecker:words GGROOT workdir | ||
|
||
func TestCommandFindFile(t *testing.T) { | ||
mock := mockenv.NewMockEnv(t) | ||
|
||
// with file 'example.txt' | ||
{ | ||
clonePath := mock.Clone("https://github.com/hello/world.git", "github.com", "hello", "world") | ||
if err := os.WriteFile(filepath.Join(clonePath, "example.txt"), nil, os.ModePerm); err != nil { | ||
panic(err) | ||
} | ||
} | ||
|
||
// with file 'example/example.txt' | ||
{ | ||
clonePath := mock.Clone("[email protected]/repo", "server.com", "user", "repo") | ||
if err := os.Mkdir(filepath.Join(clonePath, "example"), os.ModePerm|os.ModeDir); err != nil { | ||
panic(err) | ||
} | ||
if err := os.WriteFile(filepath.Join(clonePath, "example", "example.txt"), nil, os.ModePerm); err != nil { | ||
panic(err) | ||
} | ||
|
||
} | ||
|
||
// with nothing | ||
mock.Clone("https://gitlab.com/hello/world.git", "gitlab.com", "hello", "world") | ||
|
||
tests := []struct { | ||
name string | ||
workdir string | ||
args []string | ||
|
||
wantCode uint8 | ||
wantStdout string | ||
wantStderr string | ||
}{ | ||
{ | ||
"find example.txt file", | ||
"", | ||
[]string{"find-file", "example.txt"}, | ||
|
||
0, | ||
"${GGROOT github.com hello world}\n", | ||
"", | ||
}, | ||
{ | ||
"find example.txt file with paths", | ||
"", | ||
[]string{"find-file", "--print-file", "example.txt"}, | ||
|
||
0, | ||
"${GGROOT github.com hello world example.txt}\n", | ||
"", | ||
}, | ||
{ | ||
"find example directory", | ||
"", | ||
[]string{"find-file", "example"}, | ||
|
||
0, | ||
"${GGROOT server.com user repo}\n", | ||
"", | ||
}, | ||
{ | ||
"find example/example.txt file", | ||
"", | ||
[]string{"find-file", "example/example.txt"}, | ||
|
||
0, | ||
"${GGROOT server.com user repo}\n", | ||
"", | ||
}, | ||
{ | ||
"don't find non-existent file", | ||
"", | ||
[]string{"find-file", "iDoNotExist.txt"}, | ||
|
||
0, | ||
"", | ||
"", | ||
}, | ||
{ | ||
"don't find non-existent file with exit code", | ||
"", | ||
[]string{"find-file", "--exit-code", "iDoNotExist.txt"}, | ||
|
||
1, | ||
"", | ||
"", | ||
}, | ||
{ | ||
"find existent file with exit code", | ||
"", | ||
[]string{"find-file", "--exit-code", "example.txt"}, | ||
|
||
0, | ||
"${GGROOT github.com hello world}\n", | ||
"", | ||
}, | ||
} | ||
|
||
for _, tt := range tests { | ||
t.Run(tt.name, func(t *testing.T) { | ||
code, stdout, stderr := mock.Run(FindFile, tt.workdir, "", tt.args...) | ||
if code != tt.wantCode { | ||
t.Errorf("Code = %d, wantCode = %d", code, tt.wantCode) | ||
} | ||
mock.AssertOutput(t, "Stdout", stdout, tt.wantStdout) | ||
mock.AssertOutput(t, "Stderr", stderr, tt.wantStderr) | ||
}) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -130,6 +130,7 @@ func init() { | |
cmd.Exec, | ||
cmd.Fetch, | ||
cmd.FindBranch, | ||
cmd.FindFile, | ||
cmd.Fix, | ||
cmd.Here, | ||
cmd.License, | ||
|