-
Notifications
You must be signed in to change notification settings - Fork 0
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
new pathctl command #18
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||||
---|---|---|---|---|---|---|---|---|
@@ -0,0 +1,146 @@ | ||||||||
package main | ||||||||
|
||||||||
import ( | ||||||||
"flag" | ||||||||
"fmt" | ||||||||
"log" | ||||||||
"os" | ||||||||
"path/filepath" | ||||||||
|
||||||||
"github.com/alessio/unixtools/internal/path" | ||||||||
"github.com/alessio/unixtools/internal/version" | ||||||||
) | ||||||||
|
||||||||
const ( | ||||||||
progName = "pathctl" | ||||||||
) | ||||||||
|
||||||||
var ( | ||||||||
helpMode bool | ||||||||
versionMode bool | ||||||||
pathListSep string | ||||||||
) | ||||||||
|
||||||||
var ( | ||||||||
envVar string | ||||||||
paths path.List | ||||||||
) | ||||||||
|
||||||||
func init() { | ||||||||
flag.BoolVar(&helpMode, "help", false, "display this help and exit.") | ||||||||
flag.BoolVar(&versionMode, "version", false, "output version information and exit.") | ||||||||
flag.StringVar(&pathListSep, "s", string(os.PathListSeparator), "path list separator.") | ||||||||
flag.StringVar(&envVar, "e", "PATH", "input environment variable") | ||||||||
flag.Usage = usage | ||||||||
flag.CommandLine.SetOutput(os.Stderr) | ||||||||
} | ||||||||
|
||||||||
func main() { | ||||||||
log.SetFlags(0) | ||||||||
log.SetPrefix(fmt.Sprintf("%s: ", progName)) | ||||||||
log.SetOutput(os.Stderr) | ||||||||
flag.Parse() | ||||||||
|
||||||||
handleHelpAndVersionModes() | ||||||||
|
||||||||
paths = path.NewPathList(envVar) | ||||||||
|
||||||||
if flag.NArg() < 1 { | ||||||||
list() | ||||||||
os.Exit(0) | ||||||||
} | ||||||||
|
||||||||
if flag.NArg() == 1 { | ||||||||
switch flag.Arg(0) { | ||||||||
case "list", "l": | ||||||||
list() | ||||||||
case "appendPathctlDir", "apd": | ||||||||
appendPath(exePath()) | ||||||||
case "prependPathctlDir", "ppd": | ||||||||
prepend(exePath()) | ||||||||
default: | ||||||||
log.Fatalf("unrecognized command: %s", flag.Arg(0)) | ||||||||
} | ||||||||
} else { | ||||||||
switch flag.Arg(0) { | ||||||||
case "prepend", "p": | ||||||||
prepend(flag.Arg(1)) | ||||||||
case "drop", "d": | ||||||||
drop(flag.Arg(1)) | ||||||||
case "append", "a": | ||||||||
appendPath(flag.Arg(1)) | ||||||||
} | ||||||||
} | ||||||||
|
||||||||
fmt.Println(paths.String()) | ||||||||
} | ||||||||
|
||||||||
func list() { | ||||||||
for _, p := range paths.StringSlice() { | ||||||||
fmt.Println(p) | ||||||||
} | ||||||||
} | ||||||||
|
||||||||
func prepend(p string) { | ||||||||
//oldPath := pathEnvvar | ||||||||
if ok := paths.Prepend(p); !ok { | ||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The error messages in the - log.Println("the path already exists")
+ log.Printf("failed to %s: the path '%s' already exists", <command>, p) Replace Also applies to: 92-92, 98-98 Committable suggestion
Suggested change
|
||||||||
log.Println("the path already exists") | ||||||||
} | ||||||||
} | ||||||||
|
||||||||
func drop(p string) { | ||||||||
if ok := paths.Drop(p); !ok { | ||||||||
log.Println("the path already exists") | ||||||||
} | ||||||||
} | ||||||||
|
||||||||
func appendPath(p string) { | ||||||||
if ok := paths.Append(p); !ok { | ||||||||
log.Println("the path already exists") | ||||||||
} | ||||||||
} | ||||||||
|
||||||||
func handleHelpAndVersionModes() { | ||||||||
if helpMode { | ||||||||
usage() | ||||||||
os.Exit(0) | ||||||||
} | ||||||||
|
||||||||
if versionMode { | ||||||||
version.PrintWithCopyright() | ||||||||
os.Exit(0) | ||||||||
} | ||||||||
} | ||||||||
|
||||||||
func usage() { | ||||||||
s := fmt.Sprintf(`Usage: %s COMMAND [PATH] | ||||||||
Make the management of the PATH environment variable | ||||||||
simple, fast, and predictable. | ||||||||
|
||||||||
Commands: | ||||||||
|
||||||||
append, a append a path to the end | ||||||||
drop, d drop a path | ||||||||
list, l list the paths | ||||||||
prepend, p prepend a path to the list | ||||||||
|
||||||||
Options: | ||||||||
`, progName) | ||||||||
_, _ = fmt.Fprintln(os.Stderr, s) | ||||||||
|
||||||||
flag.PrintDefaults() | ||||||||
|
||||||||
_, _ = fmt.Fprintln(os.Stderr, ` | ||||||||
If COMMAND is not provided, it prints the contents of the PATH | ||||||||
environment variable; the default output format is one path per | ||||||||
line.`) | ||||||||
} | ||||||||
|
||||||||
func exePath() string { | ||||||||
exePath, err := os.Executable() | ||||||||
if err != nil { | ||||||||
log.Fatal(err) | ||||||||
} | ||||||||
|
||||||||
return filepath.Dir(exePath) | ||||||||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,11 @@ | ||
module github.com/alessio/unixtools | ||
|
||
go 1.17 | ||
go 1.21 | ||
|
||
require github.com/stretchr/testify v1.8.4 | ||
|
||
require ( | ||
github.com/davecgh/go-spew v1.1.1 // indirect | ||
github.com/pmezard/go-difflib v1.0.0 // indirect | ||
gopkg.in/yaml.v3 v3.0.1 // indirect | ||
) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= | ||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= | ||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= | ||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= | ||
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= | ||
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= | ||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= | ||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= | ||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= | ||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= |
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
@@ -0,0 +1,10 @@ | ||||||
package path | ||||||
|
||||||
// List handles a list of directories in a predictable way. | ||||||
type List interface { | ||||||
String() interface{} // | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The - String() interface{} //
+ String() string Committable suggestion
Suggested change
|
||||||
StringSlice() []string | ||||||
Prepend(path string) bool | ||||||
Append(path string) bool | ||||||
Drop(path string) bool | ||||||
} |
Original file line number | Diff line number | Diff line change | ||||||||
---|---|---|---|---|---|---|---|---|---|---|
@@ -0,0 +1,79 @@ | ||||||||||
package path | ||||||||||
|
||||||||||
import ( | ||||||||||
"os" | ||||||||||
"path/filepath" | ||||||||||
"slices" | ||||||||||
"strings" | ||||||||||
) | ||||||||||
|
||||||||||
var ListSeparator = string(os.PathListSeparator) | ||||||||||
|
||||||||||
type dirList struct { | ||||||||||
lst []string | ||||||||||
} | ||||||||||
|
||||||||||
func newDirList(lst []string) *dirList { | ||||||||||
return &dirList{lst: lst} | ||||||||||
} | ||||||||||
|
||||||||||
func NewPathList(v string) List { | ||||||||||
return newDirList(makePathList(os.Getenv(v))) | ||||||||||
} | ||||||||||
|
||||||||||
func (p *dirList) String() interface{} { | ||||||||||
return strings.Join(p.lst, ListSeparator) | ||||||||||
Comment on lines
+24
to
+25
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The - func (p *dirList) String() interface{} {
+ func (p *dirList) String() string { Committable suggestion
Suggested change
|
||||||||||
} | ||||||||||
|
||||||||||
func (p *dirList) StringSlice() []string { | ||||||||||
return p.lst | ||||||||||
} | ||||||||||
|
||||||||||
func (p *dirList) Prepend(path string) bool { | ||||||||||
cleanPath := normalizePath(path) | ||||||||||
if idx := slices.Index(p.lst, cleanPath); idx == -1 { | ||||||||||
p.lst = append([]string{cleanPath}, p.lst...) | ||||||||||
return true | ||||||||||
} | ||||||||||
|
||||||||||
return false | ||||||||||
} | ||||||||||
|
||||||||||
func (p *dirList) Append(path string) bool { | ||||||||||
cleanPath := normalizePath(path) | ||||||||||
if idx := slices.Index(p.lst, cleanPath); idx == -1 { | ||||||||||
p.lst = append(p.lst, cleanPath) | ||||||||||
return true | ||||||||||
} | ||||||||||
|
||||||||||
return false | ||||||||||
} | ||||||||||
|
||||||||||
func (p *dirList) Drop(path string) bool { | ||||||||||
cleanPath := normalizePath(path) | ||||||||||
if idx := slices.Index(p.lst, cleanPath); idx != -1 { | ||||||||||
p.lst = slices.Delete(p.lst, idx, idx+1) | ||||||||||
return true | ||||||||||
} | ||||||||||
|
||||||||||
return false | ||||||||||
} | ||||||||||
|
||||||||||
func makePathList(pathStr string) []string { | ||||||||||
if pathStr == "" { | ||||||||||
return nil | ||||||||||
} | ||||||||||
|
||||||||||
rawList := strings.Split(pathStr, ListSeparator) | ||||||||||
cleanList := make([]string, len(rawList)) | ||||||||||
|
||||||||||
for i, s := range rawList { | ||||||||||
cleanList[i] = normalizePath(s) | ||||||||||
} | ||||||||||
|
||||||||||
return cleanList | ||||||||||
} | ||||||||||
|
||||||||||
func normalizePath(s string) string { | ||||||||||
return filepath.Clean(s) | ||||||||||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,54 @@ | ||
package path_test | ||
|
||
import ( | ||
"fmt" | ||
"github.com/alessio/unixtools/internal/path" | ||
"github.com/stretchr/testify/require" | ||
"testing" | ||
) | ||
|
||
func TestPathList_Prepend(t *testing.T) { | ||
envVarName := fmt.Sprintf("TEST_%s", t.Name()) | ||
t.Setenv(envVarName, "/var/:/root/config:/Programs///") | ||
lst := path.NewPathList(envVarName) | ||
|
||
require.Equal(t, lst.String(), "/var:/root/config:/Programs") | ||
|
||
require.True(t, lst.Prepend("/usr/local/go/bin")) | ||
require.False(t, lst.Prepend("/usr/local/go/bin")) | ||
require.False(t, lst.Prepend("/usr///local///go/bin/")) | ||
|
||
require.Equal(t, "/usr/local/go/bin:/var:/root/config:/Programs", lst.String()) | ||
require.Equal(t, []string{"/usr/local/go/bin", "/var", "/root/config", "/Programs"}, lst.StringSlice()) | ||
} | ||
|
||
func TestPathList_Append(t *testing.T) { | ||
envVarName := fmt.Sprintf("TEST_%s", t.Name()) | ||
t.Setenv(envVarName, "/var/:/root/config:/Programs///") | ||
lst := path.NewPathList(envVarName) | ||
|
||
require.Equal(t, "/var:/root/config:/Programs", lst.String()) | ||
|
||
require.True(t, lst.Append("/usr/local/go/bin")) | ||
require.False(t, lst.Append("/usr/local/go/bin")) | ||
require.False(t, lst.Append("/usr///local///go/bin/")) | ||
|
||
require.Equal(t, "/var:/root/config:/Programs:/usr/local/go/bin", lst.String()) | ||
require.Equal(t, []string{"/var", "/root/config", "/Programs", "/usr/local/go/bin"}, lst.StringSlice()) | ||
} | ||
|
||
func TestPathList_Drop(t *testing.T) { | ||
envVarName := fmt.Sprintf("TEST_%s", t.Name()) | ||
t.Setenv(envVarName, | ||
"/usr/local/bin:/home/user/.local/bin/:/usr/local/sbin:/var:/root") | ||
lst := path.NewPathList(envVarName) | ||
|
||
require.Equal(t, "/usr/local/bin:/home/user/.local/bin:/usr/local/sbin:/var:/root", lst.String()) | ||
require.False(t, lst.Drop("/etc")) // non existing | ||
require.True(t, lst.Drop("/home/user/.local/bin")) | ||
require.False(t, lst.Drop("/home/user/.local/bin")) | ||
require.True(t, lst.Drop("/root/./")) | ||
|
||
require.Equal(t, "/usr/local/bin:/usr/local/sbin:/var", lst.String()) | ||
require.Equal(t, []string{"/usr/local/bin", "/usr/local/sbin", "/var"}, lst.StringSlice()) | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The
main
function could be refactored to improve readability and maintainability. The current implementation has nestedif
andswitch
statements, which can be simplified by breaking down the command handling into separate functions or by using a map of functions indexed by command names.This approach would make it easier to add or modify commands in the future and would make the
main
function more concise.