-
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.
Co-authored-by: Alessio Treglia <alessio@jur.io>
- Loading branch information
Showing
8 changed files
with
329 additions
and
13 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,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 { | ||
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) | ||
} |
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 |
---|---|---|
@@ -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 | ||
) |
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,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= |
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,10 @@ | ||
package path | ||
|
||
// List handles a list of directories in a predictable way. | ||
type List interface { | ||
String() interface{} // | ||
StringSlice() []string | ||
Prepend(path string) bool | ||
Append(path string) bool | ||
Drop(path string) bool | ||
} |
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,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) | ||
} | ||
|
||
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) | ||
} |
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,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()) | ||
} |
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