diff --git a/bigdl b/bigdl old mode 100644 new mode 100755 index 4a33a0d..d0ea713 Binary files a/bigdl and b/bigdl differ diff --git a/findURL.go b/findURL.go index 37093df..15a96c8 100644 --- a/findURL.go +++ b/findURL.go @@ -4,15 +4,13 @@ package main import ( "fmt" "net/http" - "os" ) // findURLCommand returns the URL for the specified binary. We do not use info.go for this because unmarshalling such big files is slower than pinging to see which exists func findURLCommand(binaryName string) { url, err := findURL(binaryName) if err != nil { - fmt.Printf("Error: %v\n", err) - os.Exit(1) + errorOut("Error: %v\n", err) } fmt.Println(url) @@ -32,5 +30,5 @@ func findURL(binaryName string) (string, error) { } } - return "", fmt.Errorf("Binary's SOURCE_URL was not found") + return "", fmt.Errorf("Error: Binary's SOURCE_URL was not found.") } diff --git a/fsearch.go b/fsearch.go index e477322..1f23bf5 100644 --- a/fsearch.go +++ b/fsearch.go @@ -1,5 +1,4 @@ // fsearch.go // this file implements the search option - package main import ( @@ -59,7 +58,7 @@ func fSearch(searchTerm string) { fmt.Printf("No matching binaries found for '%s'.\n", searchTerm) return } else if len(searchResultsSet) > 90 { - fmt.Printf("Too many matching binaries (+90. [Limit defined in fsearch.go:63:36,37]) found for '%s'.\n", searchTerm) + fmt.Printf("Too many matching binaries (+90. [Limit defined in fsearch.go:60,36]) found for '%s'.\n", searchTerm) return } diff --git a/go.mod b/go.mod index 539079b..710b34e 100644 --- a/go.mod +++ b/go.mod @@ -1,11 +1,12 @@ module github.com/xplshn/bigdl -go 1.21.6 +go 1.22.0 + +require github.com/schollz/progressbar/v3 v3.14.2 require ( github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db // indirect github.com/rivo/uniseg v0.4.7 // indirect - github.com/schollz/progressbar/v3 v3.14.2 // indirect golang.org/x/sys v0.17.0 // indirect golang.org/x/term v0.17.0 // indirect ) diff --git a/go.sum b/go.sum index 4eee56b..3d7fd58 100644 --- a/go.sum +++ b/go.sum @@ -1,67 +1,21 @@ -github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k= -github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8= -github.com/charmbracelet/bubbles v0.18.0 h1:PYv1A036luoBGroX6VWjQIE9Syf2Wby2oOl/39KLfy0= -github.com/charmbracelet/bubbles v0.18.0/go.mod h1:08qhZhtIwzgrtBjAcJnij1t1H0ZRjwHyGsy6AL11PSw= -github.com/charmbracelet/bubbletea v0.25.0 h1:bAfwk7jRz7FKFl9RzlIULPkStffg5k6pNt5dywy4TcM= -github.com/charmbracelet/bubbletea v0.25.0/go.mod h1:EN3QDR1T5ZdWmdfDzYcqOCAps45+QIJbLOBxmVNWNNg= -github.com/charmbracelet/harmonica v0.2.0 h1:8NxJWRWg/bzKqqEaaeFNipOu77YR5t8aSwG4pgaUBiQ= -github.com/charmbracelet/harmonica v0.2.0/go.mod h1:KSri/1RMQOZLbw7AHqgcBycp8pgJnQMYYT8QZRqZ1Ao= -github.com/charmbracelet/lipgloss v0.9.1 h1:PNyd3jvaJbg4jRHKWXnCj1akQm4rh8dbEzN1p/u1KWg= -github.com/charmbracelet/lipgloss v0.9.1/go.mod h1:1mPmG4cxScwUQALAAnacHaigiiHB9Pmr+v1VEawJl6I= -github.com/containerd/console v1.0.4-0.20230313162750-1ae8d489ac81 h1:q2hJAaP1k2wIvVRd/hEHD7lacgqrCPS+k8g1MndzfWY= -github.com/containerd/console v1.0.4-0.20230313162750-1ae8d489ac81/go.mod h1:YynlIjWYF8myEu6sdkwKIvGQq+cOckRm6So2avqoYAk= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +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/gabe565/go-spinners v1.0.1 h1:E8C4qV+TxyvSPpIqAjE6gPNsZ9bqb8aHPiVxMPwPBk0= -github.com/gabe565/go-spinners v1.0.1/go.mod h1:xB3UuNr3Rb97UoOcX7Irq5UWFhpPuieXUMnZ+KsIXck= github.com/k0kubun/go-ansi v0.0.0-20180517002512-3bf9e2903213/go.mod h1:vNUNkEQ1e29fT/6vq2aBdFsgNPmy8qMdSay1npru+Sw= -github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY= -github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= -github.com/mattn/go-isatty v0.0.18 h1:DOKFKCQ7FNG2L1rbrmstDN4QVRdS89Nkh85u68Uwp98= -github.com/mattn/go-isatty v0.0.18/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= -github.com/mattn/go-localereader v0.0.1 h1:ygSAOl7ZXTx4RdPYinUpg6W99U8jWvWi9Ye2JC/oIi4= -github.com/mattn/go-localereader v0.0.1/go.mod h1:8fBrzywKY7BI3czFoHkuzRoWE9C+EiG4R1k4Cjx5p88= -github.com/mattn/go-runewidth v0.0.12/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk= -github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U= -github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db h1:62I3jR2EmQ4l5rM/4FEfDWcRD+abF5XlKShorW5LRoQ= github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db/go.mod h1:l0dey0ia/Uv7NcFFVbCLtqEBQbrT4OCwCSKTEv6enCw= -github.com/muesli/ansi v0.0.0-20211018074035-2e021307bc4b h1:1XF24mVaiu7u+CFywTdcDo2ie1pzzhwjt6RHqzpMU34= -github.com/muesli/ansi v0.0.0-20211018074035-2e021307bc4b/go.mod h1:fQuZ0gauxyBcmsdE3ZT4NasjaRdxmbCS0jRHsrWu3Ho= -github.com/muesli/cancelreader v0.2.2 h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELUXHmA= -github.com/muesli/cancelreader v0.2.2/go.mod h1:3XuTXfFS2VjM+HTLZY9Ak0l6eUKfijIfMUZ4EgX0QYo= -github.com/muesli/reflow v0.3.0 h1:IFsN6K9NfGtjeggFP+68I4chLZV2yIKsXJFNZ+eWh6s= -github.com/muesli/reflow v0.3.0/go.mod h1:pbwTDkVPibjO2kyvBQRBxTWEEGDGq0FlB1BIKtnHY/8= -github.com/muesli/termenv v0.15.2 h1:GohcuySI0QmI3wN8Ok9PtKGkgkFIk7y6Vpb5PvrY+Wo= -github.com/muesli/termenv v0.15.2/go.mod h1:Epx+iuz8sNs7mNKhxzH4fWXGNpZwUaJKRS1noLXviQ8= +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/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= -github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY= -github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= -github.com/rivo/uniseg v0.4.6 h1:Sovz9sDSwbOz9tgUy8JpT+KgCkPYJEN/oYzlJiYTNLg= -github.com/rivo/uniseg v0.4.6/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= -github.com/schollz/progressbar v1.0.0 h1:gbyFReLHDkZo8mxy/dLWMr+Mpb1MokGJ1FqCiqacjZM= -github.com/schollz/progressbar v1.0.0/go.mod h1:/l9I7PC3L3erOuz54ghIRKUEFcosiWfLvJv+Eq26UMs= github.com/schollz/progressbar/v3 v3.14.2 h1:EducH6uNLIWsr560zSV1KrTeUb/wZGAHqyMFIEa99ks= github.com/schollz/progressbar/v3 v3.14.2/go.mod h1:aQAZQnhF4JGFtRJiw/eobaXpsqpVQAftEQ+hLGXaRc4= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= -golang.org/x/crypto v0.19.0 h1:ENy+Az/9Y1vSrlrvBSyna3PITt4tiZLf7sgCjZBX7Wo= -golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= -golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o= -golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.12.0 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o= -golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.17.0 h1:25cE3gD+tdBA7lp7QfhuV+rJiE9YXTcS3VG1SqssI/Y= golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/term v0.6.0 h1:clScbb1cHjoCkyRbWwBEUZ5H/tIFu5TAXIqaZD0Gcjw= -golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U= golang.org/x/term v0.17.0 h1:mkTF7LCd6WGJNL3K1Ad7kwxNfYAW6a8a8QqtMblp/4U= golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= -golang.org/x/text v0.3.8 h1:nAL+RVCQ9uMn3vJZbV+MRnydTJFPf8qqY42YiA6MrqY= -golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= diff --git a/helperFunctions.go b/helperFunctions.go index a0e9cd8..88255b0 100644 --- a/helperFunctions.go +++ b/helperFunctions.go @@ -1,5 +1,4 @@ // helperFunctions.go // This file contains commonly used functions //> -// TODO: Add *PROPER* error handling in the truncate functions. Ensure escape sequences are correctly handled? package main import ( @@ -18,6 +17,8 @@ import ( "syscall" ) +// TODO: Add *PROPER* error handling in the truncate functions. Ensure escape sequences are correctly handled? + // signalHandler sets up a channel to listen for interrupt signals and returns a function // that can be called to check if an interrupt has been received. func signalHandler(ctx context.Context, cancel context.CancelFunc) (func() bool, error) { @@ -93,27 +94,7 @@ func fetchBinaryFromURL(url, destination string) error { return fmt.Errorf("failed to fetch binary from %s. HTTP status code: %d", url, resp.StatusCode) } - // Create a progress bar - var bar *progressbar.ProgressBar - if useProgressBar { - bar = progressbar.NewOptions(int(resp.ContentLength), - progressbar.OptionClearOnFinish(), - progressbar.OptionFullWidth(), - progressbar.OptionShowBytes(true), - progressbar.OptionSetTheme(progressbar.Theme{ - Saucer: "=", - SaucerHead: ">", - SaucerPadding: " ", - BarStart: "[", - BarEnd: "]", - }), - ) - } else { - bar = progressbar.NewOptions(-1, - progressbar.OptionSetVisibility(false), // Couldn't make update.go work well enough with it. - progressbar.OptionSpinnerType(9), // Type 9 spinner (Classic BSD styled spinner; "|/-\"). - ) - } + bar := spawnProgressBar(resp.ContentLength) // Write the binary to the temporary file with progress bar _, err = io.Copy(io.MultiWriter(out, bar), resp.Body) @@ -144,7 +125,8 @@ func fetchBinaryFromURL(url, destination string) error { fmt.Printf("failed to remove temporary file: %v\n", err) } } - fmt.Println("\r\033[K") + + //fmt.Printf("\033[A") return nil } @@ -188,12 +170,12 @@ func copyFile(src, dst string) error { // removeDuplicates removes duplicate elements from the input slice. func removeDuplicates(input []string) []string { - seen := make(map[string]struct{}) - var unique []string - for _, s := range input { - if _, ok := seen[s]; !ok { - seen[s] = struct{}{} - unique = append(unique, s) + seen := make(map[string]bool) + unique := []string{} + for _, entry := range input { + if _, value := seen[entry]; !value { + seen[entry] = true + unique = append(unique, entry) } } return unique @@ -262,6 +244,24 @@ func listFilesInDir(dir string) ([]string, error) { return files, nil } +func spawnProgressBar(contentLength int64) *progressbar.ProgressBar { + if useProgressBar { + return progressbar.NewOptions(int(contentLength), + progressbar.OptionClearOnFinish(), + progressbar.OptionFullWidth(), + progressbar.OptionShowBytes(true), + progressbar.OptionSetTheme(progressbar.Theme{ + Saucer: "=", + SaucerHead: ">", + SaucerPadding: " ", + BarStart: "[", + BarEnd: "]", + }), + ) + } + return progressbar.NewOptions(0, progressbar.OptionClearOnFinish()) // A dummy +} + // sanitizeString removes certain punctuation from the end of the string and converts it to lower case. func sanitizeString(s string) string { // Define the punctuation to remove @@ -280,6 +280,24 @@ func sanitizeString(s string) string { return s } +// errorEncoder generates a unique error code based on the sum of ASCII values of the error message. +func errorEncoder(format string, args ...interface{}) int { + formattedErrorMessage := fmt.Sprintf(format, args...) + + var sum int + for _, char := range formattedErrorMessage { + sum += int(char) + } + errorCode := sum % 256 + fmt.Fprint(os.Stderr, formattedErrorMessage) + return errorCode +} + +// errorOut prints the error message to stderr and exits the program with the error code generated by errorEncoder. +func errorOut(format string, args ...interface{}) { + os.Exit(errorEncoder(format, args...)) +} + // GetTerminalWidth attempts to determine the width of the terminal. // It first tries using "stty size", then "tput cols", and finally falls back to 80 columns. func getTerminalWidth() int { @@ -333,8 +351,11 @@ func truncateSprintf(format string, a ...interface{}) string { // truncatePrintf is a drop-in replacement for fmt.Printf that truncates the input string if it exceeds a certain length. func truncatePrintf(format string, a ...interface{}) (n int, err error) { - formatted := truncateSprintf(format, a...) - return fmt.Print(formatted) + if disableTruncation { + return fmt.Print(fmt.Sprintf(format, a...)) + } else { + return fmt.Print(truncateSprintf(format, a...)) + } } // NOTE: Both truncate functions will remove the escape sequences of truncated lines, and sometimes break them in half because of the truncation. Avoid using escape sequences with truncate functions, as it is UNSAFE. diff --git a/install.go b/install.go index 6efc478..3a444f2 100644 --- a/install.go +++ b/install.go @@ -6,13 +6,13 @@ import ( "path/filepath" ) -func installCommand(binaryName string, messages ...string) error { +func installCommand(binaryName string, installMessage ...string) error { installPath := filepath.Join(InstallDir, binaryName) // Use ReturnCachedFile to check for a cached file if installUseCache { - cachedFile, errCode := ReturnCachedFile(binaryName) - if errCode == 0 { + cachedFile, err := ReturnCachedFile(binaryName) + if err == 0 { // If the cached file exists, use it fmt.Printf("\r\033[KUsing cached file: %s\n", cachedFile) // Copy the cached file to the install path @@ -26,20 +26,32 @@ func installCommand(binaryName string, messages ...string) error { // If the cached file does not exist, download the binary url, err := findURL(binaryName) if err != nil { - return fmt.Errorf("Error: %v", err) + errorOut("%v\n", err) } if err := fetchBinaryFromURL(url, installPath); err != nil { return fmt.Errorf("Error: Could not install binary: %v", err) } - // Check if any messages are provided and print them - if len(messages) > 0 && messages[0] != "" { - for _, message := range messages { - fmt.Printf(message) + // Check if the user provided a custom installMessage and If so, print it as per his requirements. + if len(installMessage) != 0 { + if installMessage[0] == "--fancy" { + if installMessage[1] == "--truncate" { + truncatePrintf(installMessage[2], binaryName) + } else { + fmt.Printf(installMessage[1], binaryName) + } + if len(installMessage) > 2 && installMessage[2] == "--newline" || len(installMessage) > 3 && installMessage[3] == "--newline" { + fmt.Println() + } + } else { + if installMessage[0] == "--truncate" { + fmt.Println(truncateSprintf("%s", installMessage[1])) + } else { + fmt.Println(installMessage[0]) + } } } else { - // If no message provided, print default installation complete message - fmt.Printf("\x1b[A\033[KInstallation complete: %s \n", installPath) + fmt.Printf("Installation complete: %s\n", installPath) } return nil } diff --git a/listBinaries.go b/listBinaries.go index e4159cf..7ae7851 100644 --- a/listBinaries.go +++ b/listBinaries.go @@ -1,5 +1,4 @@ // listBinaries.go // This file implements the listBinaries function //> - package main import ( @@ -37,8 +36,8 @@ func listBinaries() ([]string, error) { // Unmarshal JSON var metadata []struct { - Name string `json:"name"` - NameAlt string `json:"Name"` // Consider both "name" and "Name" fields + Name string `json:"Name"` // Consider both "name" and "Name" fields + NameAlt string `json:"name"` } if err := json.Unmarshal(body, &metadata); err != nil { return nil, fmt.Errorf("failed to unmarshal metadata JSON from %s: %v", url, err) diff --git a/main.go b/main.go index 7ea93f4..9e69911 100644 --- a/main.go +++ b/main.go @@ -2,6 +2,7 @@ package main import ( + "flag" "fmt" "os" "path/filepath" @@ -9,12 +10,26 @@ import ( ) var ( - Repositories []string - MetadataURLs []string - validatedArch = [3]string{} - InstallDir = os.Getenv("INSTALL_DIR") - installUseCache = true - useProgressBar = true + Repositories []string + MetadataURLs []string + validatedArch = [3]string{} + InstallDir = os.Getenv("INSTALL_DIR") + installUseCache = true + useProgressBar = true + disableTruncation = false +) + +const ( + RMetadataURL = "https://raw.githubusercontent.com/Azathothas/Toolpacks/main/metadata.json" // This is the file from which we extract descriptions for different binaries + RNMetadataURL = "https://bin.ajam.dev/METADATA.json" // This is the file which contains a concatenation of all metadata in the different repos, this one also contains sha256 checksums. + VERSION = "1.4" + usagePage = " <-v|-h|-d> [list|install|remove|update|run|info|search|tldr] {}" + // Truncation indicator + indicator = "...>" + // Cache size limit & handling. + MaxCacheSize = 10 + BinariesToDelete = 5 // Once the cache is filled - The programs populate the list of binaries to be removed in order of least used. + TEMP_DIR = "/tmp/bigdl_cached" ) func init() { @@ -47,46 +62,45 @@ func init() { MetadataURLs = append(MetadataURLs, "https://bin.ajam.dev/"+arch+"/METADATA.json") MetadataURLs = append(MetadataURLs, "https://bin.ajam.dev/"+arch+"/Baseutils/METADATA.json") MetadataURLs = append(MetadataURLs, "https://api.github.com/repos/xplshn/Handyscripts/contents") // You may add other repos if need be? bigdl is customizable, feel free to open a PR, ask questions, etc. -} -const ( - RMetadataURL = "https://raw.githubusercontent.com/Azathothas/Toolpacks/main/metadata.json" // This is the file from which we extract descriptions for different binaries - RNMetadataURL = "https://bin.ajam.dev/METADATA.json" // This is the file which contains a concatenation of all metadata in the different repos, this one also contains sha256 checksums. - VERSION = "1.3.1" - usagePage = "Usage: bigdl [-vh] [list|install|remove|update|run|info|search|tldr] " - // Truncation indicator - indicator = "...>" - // Cache size limit & handling. - MaxCacheSize = 10 - BinariesToDelete = 5 - TEMP_DIR = "/tmp/bigdl_cached" -) + if os.Getenv("DISABLE_TRUNCATION") == "true" || os.Getenv("DISABLE_TRUNCATION") == "1" { + disableTruncation = true + } + if os.Getenv("DISABLE_PRBAR") == "true" || os.Getenv("DISABLE_PRBAR") == "1" { + useProgressBar = false + } +} func printHelp() { - helpMessage := usagePage + ` - + helpMessage := "Usage:\n" + usagePage + ` + Options: - -h, --help Show this help message - -v, --version Show the version number + -h, --help Show this help message + -v, --version Show the version number Commands: - list List all available binaries - install, add Install a binary - remove, del Remove a binary - update Update binaries, by checking their SHA against the repo's SHA. - run Run a binary - info Show information about a specific binary - search Search for a binary - (not all binaries have metadata. Use list to see all binaries) - tldr Show a brief description & usage examples for a given program/command + list List all available binaries + install, add Install a binary + remove, del Remove a binary + update Update binaries, by checking their SHA against the repo's SHA. + run Run a binary + info Show information about a specific binary + search Search for a binary - (not all binaries have metadata. Use list to see all binaries) + tldr Show a brief description & usage examples for a given program/command. This is an alias equivalent to using "run" with "tlrc" as argument. Examples: + bigdl search editor bigdl install micro + bigdl install lux --fancy "%s was installed to $INSTALL_DIR." --newline + bigdl install bed --fancy --truncate "%s was installed to $INSTALL_DIR." --newline + bigdl install orbiton --truncate "installed Orbiton to $INSTALL_DIR." bigdl remove bed + bigdl remove orbiton tgpt lux bigdl info jq - bigdl search editor bigdl tldr gum - bigdl run --verbose neofetch - bigdl run --silent micro + bigdl run --verbose curl -qsfSL "https://raw.githubusercontent.com/xplshn/bigdl/master/stubdl" | sh - + bigdl run --silent elinks -no-home "https://fatbuffalo.neocities.org/def" + bigdl run --transparent --silent micro .profile bigdl run btop Version: ` + VERSION @@ -95,31 +109,32 @@ Version: ` + VERSION } func main() { - // Check for flags directly in the main function - if len(os.Args) > 1 { - switch os.Args[1] { - case "--version", "-v": - fmt.Println("bigdl", VERSION) - os.Exit(0) - case "--help", "-h": - printHelp() - os.Exit(0) - } + + errorOutInsufficientArgs := func() { os.Exit(errorEncoder("Error: Insufficient parameters\n")) } + version := flag.Bool("v", false, "Show the version number") + versionLong := flag.Bool("version", false, "Show the version number") + + flag.Usage = printHelp + flag.Parse() + + if *version || *versionLong { + fmt.Println("bigdl", VERSION) + os.Exit(0) } - // If no arguments are received, show the usage text - if len(os.Args) < 2 { - fmt.Printf("%s\n", usagePage) + if flag.NArg() < 1 { + fmt.Printf(" bigdl:%s\n", usagePage) os.Exit(1) } - switch os.Args[1] { + switch flag.Arg(0) { case "find_url": - if len(os.Args) < 3 { + binaryName := flag.Arg(1) + if binaryName == "" { fmt.Println("Usage: bigdl find_url [binary]") - os.Exit(1) + errorOutInsufficientArgs() } - findURLCommand(os.Args[2]) + findURLCommand(binaryName) case "list": binaries, err := listBinaries() if err != nil { @@ -130,54 +145,47 @@ func main() { fmt.Println(binary) } case "install", "add": - if len(os.Args) < 3 { - fmt.Printf("Usage: bigdl %s [binary] \n", os.Args[1]) - os.Exit(1) + // Check if the binary name is provided + if flag.NArg() < 2 { + fmt.Printf("Usage: bigdl %s [binary] \n", flag.Arg(0)) + fmt.Println("Options:") + fmt.Println(" --fancy <--truncate> : Will replace exactly ONE '%s' with the name of the requested binary in the install message <--newline>") + fmt.Println(" --truncate: Truncates the message to fit into the terminal") + errorOutInsufficientArgs() } + binaryName := os.Args[2] - var installMessage string - if len(os.Args) > 3 { - InstallDir = os.Args[3] - } - if len(os.Args) > 4 { - installMessage = os.Args[4] - } - err := installCommand(binaryName, installMessage) - if err != nil { - fmt.Printf("%s\n", err.Error()) - os.Exit(1) - } + installMessage := os.Args[3:] + + installCommand(binaryName, installMessage...) case "remove", "del": - if len(os.Args) < 3 { - fmt.Printf("Usage: bigdl %s [binary]\n", os.Args[1]) - os.Exit(1) + if flag.NArg() < 2 { + fmt.Printf("Usage: bigdl %s [binar|y|ies]\n", flag.Arg(0)) + errorOutInsufficientArgs() } - remove(os.Args[2:]) + remove(flag.Args()[1:]) case "run": - if len(os.Args) < 3 { - fmt.Println("Usage: bigdl run <--verbose, --silent> [binary] ") - os.Exit(1) + if flag.NArg() < 2 { + fmt.Println("Usage: bigdl run <--verbose, --silent, --transparent> [binary] ") + errorOutInsufficientArgs() } - RunFromCache(os.Args[2], os.Args[3:]) + RunFromCache(flag.Arg(1), flag.Args()[2:]) case "tldr": - if len(os.Args) < 3 { + if flag.NArg() < 2 { fmt.Println("Usage: bigdl tldr [page]") - os.Exit(1) + errorOutInsufficientArgs() } - RunFromCache("tlrc", os.Args[2:]) + RunFromCache("tlrc", flag.Args()[1:]) case "info": - if len(os.Args) != 3 { + binaryName := flag.Arg(1) + if binaryName == "" { fmt.Println("Usage: bigdl info [binary]") - os.Exit(1) + errorOutInsufficientArgs() } - binaryName := os.Args[2] binaryInfo, err := getBinaryInfo(binaryName) if err != nil { - fmt.Println(err) - os.Exit(1) + errorOut("%v\n", err) } - - // Print the fields fmt.Printf("Name: %s\n", binaryInfo.Name) if binaryInfo.Description != "" { fmt.Printf("Description: %s\n", binaryInfo.Description) @@ -198,24 +206,19 @@ func main() { fmt.Printf("Source: %s\n", binaryInfo.Source) } case "search": - if len(os.Args) != 3 { + query := flag.Arg(1) + if query == "" { fmt.Println("Usage: bigdl search [query]") - os.Exit(1) + errorOutInsufficientArgs() } - searchTerm := os.Args[2] - fSearch(searchTerm) + fSearch(query) case "update": var programsToUpdate []string if len(os.Args) > 2 { - // Bulk update with list of programs to update programsToUpdate = os.Args[2:] } - if err := update(programsToUpdate); err != nil { - fmt.Printf("Error updating programs: %v\n", err) - os.Exit(1) - } + update(programsToUpdate) default: - fmt.Printf("bigdl: Unknown command: %s\n", os.Args[1]) - os.Exit(1) + errorOut("bigdl: Unknown command.\n") } } diff --git a/remove.go b/remove.go index c5bb04e..34b8252 100644 --- a/remove.go +++ b/remove.go @@ -13,12 +13,12 @@ func remove(binariesToRemove []string) { err := os.Remove(installPath) if err != nil { if os.IsNotExist(err) { - fmt.Fprintf(os.Stderr, "Warning: Binary %s does not exist in %s\n", binaryName, InstallDir) + fmt.Fprintf(os.Stderr, "Warning: '%s' does not exist in %s\n", binaryName, InstallDir) } else { - fmt.Fprintf(os.Stderr, "Error: Failed to remove binary %s from %s. %v\n", binaryName, InstallDir, err) + fmt.Fprintf(os.Stderr, "Error: Failed to remove '%s' from %s. %v\n", binaryName, InstallDir, err) } continue } - fmt.Printf("Binary %s removed from %s\n", binaryName, InstallDir) + fmt.Printf("'%s' removed from %s\n", binaryName, InstallDir) } } diff --git a/run.go b/run.go index 1af4c36..f52bd1a 100644 --- a/run.go +++ b/run.go @@ -2,20 +2,22 @@ package main import ( + "flag" "fmt" "os" "os/exec" "path/filepath" "sort" + "strings" "syscall" "time" ) var verboseMode bool var silentMode bool +var transparentMode bool -// ReturnCachedFile retrieves the cached file location. -// Returns an empty string and error code 1 if not found. +// ReturnCachedFile retrieves the cached file location. Returns an empty string and error code 1 if not found. func ReturnCachedFile(binaryName string) (string, int) { // Construct the expected cached file pattern expectedCachedFile := filepath.Join(TEMP_DIR, fmt.Sprintf("%s.bin", binaryName)) @@ -31,49 +33,77 @@ func ReturnCachedFile(binaryName string) (string, int) { // RunFromCache runs the binary from cache or fetches it if not found. func RunFromCache(binaryName string, args []string) { - // Check for verbose mode flag - if binaryName == "--verbose" { - // In this case, we should set binaryName to the next argument if available + + // purifyVars is a function to purify binaryName and args. + purifyVars := func() { if len(args) > 0 { - binaryName = args[0] - args = args[1:] // Remove the flag from the arguments - verboseMode = true + binaryName = (args)[0] // Purify binaryName + args = (args)[1:] // Appropriately set args to exclude any of the flags } else { - fmt.Println("Error: Binary name not provided after --verbose flag.") - os.Exit(1) + errorOut("Error: Binary name not provided after flag.\n") } } - // Check for silent mode flag - if binaryName == "--silent" { - // In this case, we should set binaryName to the next argument if available - if len(args) > 0 { - binaryName = args[0] - args = args[1:] // Remove the flag from the arguments - silentMode = true - } else { - fmt.Println("Error: Binary name not provided after --silent flag.") - os.Exit(1) + // Process flags + verbose := flag.Bool("verbose", false, "Enable verbose mode") + silent := flag.Bool("silent", false, "Enable silent mode") + transparent := flag.Bool("transparent", false, "Enable transparent mode") + + flags_AndBinaryName := append(strings.Fields(binaryName), args...) + flag.CommandLine.Parse(flags_AndBinaryName) + + if *verbose && *silent { + errorOut("Error: --verbose and --silent are mutually exclusive\n") + } + + if *verbose { + verboseMode = true + purifyVars() + } + + if *silent { + silentMode = true + purifyVars() + } + + if *transparent { + transparentMode = true + + purifyVars() + isInPath, err := isBinaryInPath(binaryName) + if err != nil { + errorOut("Error checking if binary is in PATH: %s\n", err) + } + + if isInPath { + if !silentMode { + fmt.Printf("Running '%s' from PATH...\n", binaryName) + } + runBinary(binaryName, args, verboseMode) } } + if binaryName == "" { + errorOut("Error: Binary name not provided\n") + } + cachedFile := filepath.Join(TEMP_DIR, binaryName+".bin") if fileExists(cachedFile) && isExecutable(cachedFile) { - if !silentMode { // Check if not in silent mode before printing + if !silentMode { fmt.Printf("Running '%s' from cache...\n", binaryName) } runBinary(cachedFile, args, verboseMode) cleanCache() } else { - if !silentMode { // Check if not in silent mode before printing + if !silentMode { fmt.Printf("Error: cached binary for '%s' not found. Fetching a new one...\n", binaryName) } err := fetchBinary(binaryName) if err != nil { - if !silentMode { // Check if not in silent mode before printing - fmt.Printf("Error fetching binary for '%s': %v\n", binaryName, err) + if !silentMode { + fmt.Fprintf(os.Stderr, "Error fetching binary for '%s'\n", binaryName) + errorOut("Error: %s\n", err) } - os.Exit(1) } cleanCache() runBinary(cachedFile, args, verboseMode) @@ -90,10 +120,7 @@ func runBinary(binaryPath string, args []string, verboseMode bool) { if err := cmd.Run(); err != nil { // Check if the error is an exit error and if the exit status is non-zero if exitError, ok := err.(*exec.ExitError); ok { - // The program has exited with a non-zero exit status - // This is an error from the binary itself if status, ok := exitError.Sys().(syscall.WaitStatus); ok { - // In verbose mode, print the error message with the program name and exit code if verboseMode { fmt.Printf("The program (%s) errored out with a non-zero exit code (%d).\n", binaryPath, status.ExitStatus()) } @@ -101,7 +128,7 @@ func runBinary(binaryPath string, args []string, verboseMode bool) { os.Exit(status.ExitStatus()) } } - // If we can't determine the exit code, exit with a default code + // Exit with a default code, in case we can't determine the binary's os.Exit(1) } @@ -111,6 +138,19 @@ func runBinary(binaryPath string, args []string, verboseMode bool) { } } +// isBinaryInPath checks if the binary is in the user's PATH, and it returns the path to it if so +func isBinaryInPath(binaryName string) (bool, error) { + pathEnv := os.Getenv("PATH") + paths := strings.Split(pathEnv, string(os.PathListSeparator)) + for _, path := range paths { + binaryPath := filepath.Join(path, binaryName) + if fileExists(binaryPath) && isExecutable(binaryPath) { + return true, nil + } + } + return false, nil +} + // fetchBinary downloads the binary and caches it. func fetchBinary(binaryName string) error { url, err := findURL(binaryName) @@ -126,9 +166,7 @@ func fetchBinary(binaryName string) error { return fmt.Errorf("error fetching binary for %s: %v", binaryName, err) } - // Ensure the cache size does not exceed the limit cleanCache() - return nil } diff --git a/update.go b/update.go index 4cde01e..2481c31 100644 --- a/update.go +++ b/update.go @@ -12,6 +12,7 @@ import ( "sync/atomic" ) +// TODO: padding should be used to align the messages // TODO: Validation of programs could be done without a list (current bottleneck) // update checks for updates to the valid programs and installs any that have changed. @@ -21,9 +22,12 @@ func update(programsToUpdate []string) error { installUseCache = false // Initialize counters - var skipped, updated, errors, toBeChecked uint32 - var errorMessages string - var checked uint32 = 1 + var ( + skipped, updated, errors, toBeChecked uint32 + checked uint32 + errorMessages string + padding = " " + ) // Fetch the list of binaries from the remote source once remotePrograms, err := listBinaries() @@ -65,12 +69,13 @@ func update(programsToUpdate []string) error { // Launch a goroutine to update the program go func(program string) { defer wg.Done() + installPath := filepath.Join(InstallDir, program) - _, err := os.Stat(installPath) - if os.IsNotExist(err) { + if !fileExists(installPath) { + atomic.AddUint32(&skipped, 1) progressMutex.Lock() - truncatePrintf("\033[2K\rWarning: Tried to update a non-existent program %s. <%d/%d>", program, atomic.LoadUint32(&checked), toBeChecked) - fmt.Printf("\n") + atomic.AddUint32(&checked, 1) // Increment the checked counter + truncatePrintf("\033[2K\r<%d/%d> %s | Warning: Tried to update a non-existent program %s. Skipping.", atomic.LoadUint32(&checked), toBeChecked, padding, program) progressMutex.Unlock() return } @@ -78,7 +83,8 @@ func update(programsToUpdate []string) error { if err != nil { atomic.AddUint32(&skipped, 1) progressMutex.Lock() - truncatePrintf("\033[2K\rWarning: Failed to get SHA256 for %s. Skipping. <%d/%d>", program, atomic.LoadUint32(&checked), toBeChecked) + atomic.AddUint32(&checked, 1) // Increment the checked counter + truncatePrintf("\033[2K\r<%d/%d> %s | Warning: Failed to get SHA256 for %s. Skipping.", atomic.LoadUint32(&checked), toBeChecked, padding, program) progressMutex.Unlock() return } @@ -87,7 +93,8 @@ func update(programsToUpdate []string) error { if err != nil { atomic.AddUint32(&skipped, 1) progressMutex.Lock() - truncatePrintf("\033[2K\rWarning: Failed to get metadata for %s. Skipping. <%d/%d>", program, atomic.LoadUint32(&checked), toBeChecked) + atomic.AddUint32(&checked, 1) // Increment the checked counter + truncatePrintf("\033[2K\r<%d/%d> %s | Warning: Failed to get metadata for %s. Skipping.", atomic.LoadUint32(&checked), toBeChecked, padding, program) progressMutex.Unlock() return } @@ -96,14 +103,15 @@ func update(programsToUpdate []string) error { if binaryInfo.SHA256 == "" { atomic.AddUint32(&skipped, 1) progressMutex.Lock() - truncatePrintf("\033[2K\rSkipping %s because the SHA256 field is null. <%d/%d>", program, atomic.LoadUint32(&checked), toBeChecked) + atomic.AddUint32(&checked, 1) // Increment the checked counter + truncatePrintf("\033[2K\r<%d/%d> %s | Skipping %s because the SHA256 field is null.", atomic.LoadUint32(&checked), toBeChecked, padding, program) progressMutex.Unlock() return } if checkDifferences(localSHA256, binaryInfo.SHA256) == 1 { - truncatePrintf("\033[2K\rDetected a difference in %s. Updating...", program) - installMessage := truncateSprintf("\x1b[A\033[KUpdating %s to version %s", program, binaryInfo.SHA256) + truncatePrintf("\033[2K\r<%d/%d> %s | Detected a difference in %s. Updating...", atomic.LoadUint32(&checked), toBeChecked, padding, program) + installMessage := truncateSprintf("\x1b[A\033[KUpdating %s", program) err := installCommand(program, installMessage) if err != nil { atomic.AddUint32(&errors, 1) @@ -113,15 +121,16 @@ func update(programsToUpdate []string) error { return } progressMutex.Lock() - truncatePrintf("\033[2K\rSuccessfully updated %s. <%d/%d>", program, atomic.LoadUint32(&checked), toBeChecked) + atomic.AddUint32(&checked, 1) // Increment the checked counter + truncatePrintf("\033[2K\r<%d/%d> %s | Successfully updated %s.", atomic.LoadUint32(&checked), toBeChecked, padding, program) progressMutex.Unlock() atomic.AddUint32(&updated, 1) } else { progressMutex.Lock() - truncatePrintf("\033[2K\rNo updates available for %s. <%d/%d>", program, atomic.LoadUint32(&checked), toBeChecked) + atomic.AddUint32(&checked, 1) // Increment the checked counter + truncatePrintf("\033[2K\r<%d/%d> %s | No updates available for %s.", atomic.LoadUint32(&checked), toBeChecked, padding, program) progressMutex.Unlock() } - atomic.AddUint32(&checked, 1) }(program) }