From ddba0856ef28f7044a3a2b3d5937b74f4f23c4bc Mon Sep 17 00:00:00 2001 From: xplshn <114888778+xplshn@users.noreply.github.com> Date: Sun, 26 May 2024 19:55:40 -0300 Subject: [PATCH] 1.6.7 is out! (#72) * Use the basename of files in run.go, install.go, remove.go. (0/3) * Groundwork for Android AND Windows compatibility * Fixed info.go. * typo * Update fsearch.go * move fetchJson to helperFunctions.go -> emply fetchJson in fsearch.go. * Great improvements! Reduced cyclic complexity! * Modified main.go to work with Termux + Modified help message to show available env variables * Get rid of redundant function and use os/exec instead. * Adapt to the new format. (1/3) * Fix fsearch.go to comply with the new format (1/3) * Prepare for a pre-release * Tweaks * Fix info.go * Version bump --------- --- findURL.go | 4 +- fsearch.go | 49 +++++------------ go.mod | 2 +- go.sum | 6 +-- helperFunctions.go | 95 ++++++++++++++------------------- info.go | 130 +++++++++++++-------------------------------- install.go | 87 ++++++++++++++---------------- listBinaries.go | 4 +- main.go | 115 +++++++++++++++++++++++---------------- remove.go | 10 ++-- run.go | 32 ++++++----- update.go | 18 ++----- 12 files changed, 233 insertions(+), 319 deletions(-) diff --git a/findURL.go b/findURL.go index 7da42c7..a718f88 100644 --- a/findURL.go +++ b/findURL.go @@ -10,7 +10,7 @@ import ( func findURLCommand(binaryName string) { url, err := findURL(binaryName) if err != nil { - errorOut("Error: %v\n", err) + errorOut("error: %v\n", err) } fmt.Println(url) @@ -30,5 +30,5 @@ func findURL(binaryName string) (string, error) { } } - return "", fmt.Errorf("Binary's SOURCE_URL was not found") + return "", fmt.Errorf("binary's SOURCE_URL was not found") } diff --git a/fsearch.go b/fsearch.go index a6bf559..01fd227 100644 --- a/fsearch.go +++ b/fsearch.go @@ -2,51 +2,32 @@ package main import ( - "encoding/json" "fmt" - "io/ioutil" - "net/http" + "os/exec" "path/filepath" "strings" ) // fSearch searches for binaries based on the given search term. func fSearch(searchTerm string, limit int) { - // Fetch metadata - response, err := http.Get(RMetadataURL) - if err != nil { - fmt.Println("Failed to fetch binary information.") - return + type tBinary struct { + Architecture string `json:"architecture"` + Name string `json:"name"` + Description string `json:"description"` } - defer response.Body.Close() - body, err := ioutil.ReadAll(response.Body) + // Fetch metadata + var binaries []tBinary + err := fetchJSON(RNMetadataURL, &binaries) if err != nil { - fmt.Println("Failed to read metadata.") + fmt.Println("Failed to fetch and decode binary information:", err) return } - // Define a struct to match the JSON structure from RMetadataURL - type Binary struct { - Name string `json:"name"` - Description string `json:"description"` - // Include other fields if needed - } - - type RMetadata struct { - Binaries []Binary `json:"packages"` - } - - // Unmarshal the description as an RMetadata object - var rMetadata RMetadata - if err := json.Unmarshal(body, &rMetadata); err != nil { - errorOut("Error while decoding metadata from %s: %v\n", RMetadataURL, err) - } - // Filter binaries based on the search term and architecture searchResultsSet := make(map[string]struct{}) - for _, binary := range rMetadata.Binaries { - if strings.Contains(strings.ToLower(binary.Name+binary.Description), strings.ToLower(searchTerm)) { + for _, binary := range binaries { + if strings.Contains(strings.ToLower(binary.Name), strings.ToLower(searchTerm)) || strings.Contains(strings.ToLower(binary.Description), strings.ToLower(searchTerm)) { ext := strings.ToLower(filepath.Ext(binary.Name)) base := filepath.Base(binary.Name) if _, excluded := excludedFileTypes[ext]; excluded { @@ -92,11 +73,9 @@ func fSearch(searchTerm string, limit int) { if fileExists(installPath) { prefix = "[i]" } else { - binaryPath, err := isBinaryInPath(name) // Capture both the path and error - if err != nil { - errorOut("Error checking the existence of a binary in the user's $PATH\n") - } else if binaryPath != "" { - prefix = "[I]" + binaryPath, _ := exec.LookPath(name) // is it okay to ignore the err channel of LookPath? + if binaryPath != "" { + prefix = "[\033[4mi\033[0m]" // Print [i], I is underlined. } else if cachedLocation != "" && isExecutable(cachedLocation) { prefix = "[c]" } else { diff --git a/go.mod b/go.mod index c67ce3d..f36abb8 100644 --- a/go.mod +++ b/go.mod @@ -2,7 +2,7 @@ module github.com/xplshn/bigdl go 1.22.0 -require github.com/schollz/progressbar/v3 v3.14.2 +require github.com/schollz/progressbar/v3 v3.14.3 require ( github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db // indirect diff --git a/go.sum b/go.sum index 863fe8f..29ef316 100644 --- a/go.sum +++ b/go.sum @@ -9,15 +9,13 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 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/v3 v3.14.2 h1:EducH6uNLIWsr560zSV1KrTeUb/wZGAHqyMFIEa99ks= -github.com/schollz/progressbar/v3 v3.14.2/go.mod h1:aQAZQnhF4JGFtRJiw/eobaXpsqpVQAftEQ+hLGXaRc4= +github.com/schollz/progressbar/v3 v3.14.3 h1:oOuWW19ka12wxYU1XblR4n16wF/2Y1dBLMarMo6p4xU= +github.com/schollz/progressbar/v3 v3.14.3/go.mod h1:aT3UQ7yGm+2ZjeXPqsjTenwL3ddUiuZ0kfQ/2tHlyNI= 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/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y= golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= golang.org/x/term v0.20.0 h1:VnkxpohqXaOBYJtBmEppKUG6mXpi+4O6purfc2+sMhw= golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY= diff --git a/helperFunctions.go b/helperFunctions.go index 700906d..b950ba9 100644 --- a/helperFunctions.go +++ b/helperFunctions.go @@ -3,6 +3,7 @@ package main import ( "context" + "encoding/json" "fmt" "io" "net/http" @@ -168,6 +169,25 @@ func copyFile(src, dst string) error { return nil } +func fetchJSON(url string, v interface{}) error { + response, err := http.Get(url) + if err != nil { + return fmt.Errorf("error fetching from %s: %v", url, err) + } + defer response.Body.Close() + + body, err := io.ReadAll(response.Body) + if err != nil { + return fmt.Errorf("error reading from %s: %v", url, err) + } + + if err := json.Unmarshal(body, v); err != nil { + return fmt.Errorf("error decoding from %s: %v", url, err) + } + + return nil +} + // removeDuplicates removes duplicate elements from the input slice. func removeDuplicates(input []string) []string { seen := make(map[string]bool) @@ -197,33 +217,6 @@ func fileExists(filePath string) bool { return !os.IsNotExist(err) } -// appendLineToFile appends a line to the end of a file. -func appendLineToFile(filePath, line string) error { - file, err := os.OpenFile(filePath, os.O_APPEND|os.O_WRONLY|os.O_CREATE, 0o644) - if err != nil { - return err - } - defer file.Close() - _, err = fmt.Fprintln(file, line) - return err -} - -// fileSize returns the size of the file at the specified path. -func fileSize(filePath string) int64 { - file, err := os.Open(filePath) - if err != nil { - return 0 - } - defer file.Close() - - stat, err := file.Stat() - if err != nil { - return 0 - } - - return stat.Size() -} - // isExecutable checks if the file at the specified path is executable. func isExecutable(filePath string) bool { info, err := os.Stat(filePath) @@ -249,7 +242,7 @@ func listFilesInDir(dir string) ([]string, error) { } func spawnProgressBar(contentLength int64) *progressbar.ProgressBar { - if useProgressBar { + if UseProgressBar { return progressbar.NewOptions(int(contentLength), progressbar.OptionClearOnFinish(), progressbar.OptionFullWidth(), @@ -263,7 +256,10 @@ func spawnProgressBar(contentLength int64) *progressbar.ProgressBar { }), ) } - return progressbar.NewOptions(0, progressbar.OptionClearOnFinish()) // A dummy + return progressbar.NewOptions( + -1, + progressbar.OptionSetWriter(io.Discard), + ) } // sanitizeString removes certain punctuation from the end of the string and converts it to lower case. @@ -276,14 +272,22 @@ func sanitizeString(s string) string { // Remove specified punctuation from the end of the string for _, p := range punctuation { - if strings.HasSuffix(s, p) { - s = s[:len(s)-len(p)] - } + s = s[:len(s)-len(p)] } return s } +// contanins will return true if the provided slice of []strings contains the word str +func contains(slice []string, str string) bool { + for _, v := range slice { + if v == str { + return true + } + } + return false +} + // 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...) @@ -313,10 +317,8 @@ func getTerminalWidth() int { // stty size returns rows and columns parts := strings.Split(strings.TrimSpace(string(out)), " ") if len(parts) == 2 { - width, err := strconv.Atoi(parts[1]) - if err == nil { - return width - } + width, _ := strconv.Atoi(parts[1]) + return width } } @@ -325,10 +327,8 @@ func getTerminalWidth() int { cmd.Stdin = os.Stdin out, err = cmd.Output() if err == nil { - width, err := strconv.Atoi(strings.TrimSpace(string(out))) - if err == nil { - return width - } + width, _ := strconv.Atoi(strings.TrimSpace(string(out))) + return width } // Fallback to 80 columns @@ -355,7 +355,7 @@ 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) { - if disableTruncation { + if DisableTruncation { return fmt.Print(fmt.Sprintf(format, a...)) } return fmt.Print(truncateSprintf(format, a...)) @@ -418,16 +418,3 @@ func validateProgramsFrom(InstallDir string, programsToValidate []string) ([]str } return validPrograms, nil } - -// isBinaryInPath checks if the binary is in the user's PATH, and it returns the path to it if so -func isBinaryInPath(binaryName string) (string, 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 binaryPath, nil // Return the path to the binary if found - } - } - return "", nil // Return an empty string and no error if the binary is not found -} diff --git a/info.go b/info.go index f837562..10bdd60 100644 --- a/info.go +++ b/info.go @@ -1,119 +1,61 @@ +// info.go // This file implements binInfo, which `info` and `update` use //> package main import ( - "encoding/json" "fmt" - "io/ioutil" - "net/http" ) // BinaryInfo struct holds binary metadata used in main.go for the `info`, `update`, `list` functionality type BinaryInfo struct { - Name string `json:"Name"` - Description string `json:"Description"` - Repo string `json:"Repo"` - ModTime string `json:"ModTime"` - Version string `json:"Version"` - Updated string `json:"Updated"` - Size string `json:"Size"` - SHA256 string `json:"SHA256"` - B3SUM string `json:"B3SUM"` - Source string `json:"Source"` + Name string `json:"name"` + Description string `json:"description"` + Repo string `json:"repo_url"` + ModTime string `json:"build_date"` + Version string `json:"repo_version"` + Updated string `json:"repo_updated"` + Size string `json:"size"` + SHA256 string `json:"sha256"` + Source string `json:"download_url"` } -func fetchJSON(url string, v interface{}) error { - response, err := http.Get(url) - if err != nil { - return fmt.Errorf("error fetching from %s: %v", url, err) - } - defer response.Body.Close() - - body, err := ioutil.ReadAll(response.Body) - if err != nil { - return fmt.Errorf("error reading from %s: %v", url, err) - } - - if err := json.Unmarshal(body, v); err != nil { - return fmt.Errorf("error decoding from %s: %v", url, err) - } - - return nil -} - -func findBinaryInfo(metadata [][]map[string]interface{}, binaryName string) (BinaryInfo, bool) { - for _, hostInfoArray := range metadata { - for _, hostInfo := range hostInfoArray { - if hostInfo["host"].(string) == validatedArch[2] { - mainBins, ok := hostInfo["Main"].([]interface{}) - if !ok { - continue - } - for _, bin := range mainBins { - binMap, ok := bin.(map[string]interface{}) - if !ok { - continue - } - if binMap["Name"].(string) == binaryName { - return BinaryInfo{ - Name: binMap["Name"].(string), - Size: binMap["Size"].(string), - ModTime: binMap["ModTime"].(string), - Source: binMap["Source"].(string), - B3SUM: binMap["B3SUM"].(string), - SHA256: binMap["SHA256"].(string), - Repo: binMap["Repo"].(string), - }, true - } - } - } +func findBinaryInfo(metadata []map[string]interface{}, binaryName string) (BinaryInfo, bool) { + for _, binMap := range metadata { + if name, ok := binMap["name"].(string); ok && name == binaryName { + description, _ := binMap["description"].(string) + repo, _ := binMap["repo_url"].(string) + build_date, _ := binMap["build_date"].(string) + version, _ := binMap["repo_version"].(string) + updated, _ := binMap["repo_updated"].(string) + size, _ := binMap["size"].(string) + sha256, _ := binMap["sha256"].(string) + source, _ := binMap["download_url"].(string) + + return BinaryInfo{ + Name: name, + Description: description, + Repo: repo, + ModTime: build_date, + Version: version, + Updated: updated, + Size: size, + SHA256: sha256, + Source: source, + }, true } } return BinaryInfo{}, false } func getBinaryInfo(binaryName string) (*BinaryInfo, error) { - var metadata [][]map[string]interface{} + var metadata []map[string]interface{} if err := fetchJSON(RNMetadataURL, &metadata); err != nil { return nil, err } binInfo, found := findBinaryInfo(metadata, binaryName) if !found { - return nil, fmt.Errorf("info for the requested binary ('%s') not found in the metadata.json file for architecture '%s'", binaryName, validatedArch[2]) - } - - var rMetadata map[string][]BinaryInfo - if err := fetchJSON(RMetadataURL, &rMetadata); err != nil { - return nil, err - } - - binaries, exists := rMetadata["packages"] - if !exists { - return nil, fmt.Errorf("invalid description metadata format. No 'packages' field found") - } - - var description, updated, version string - for _, binInfo := range binaries { - if binInfo.Name == binaryName { - description = binInfo.Description - updated = binInfo.Updated - version = binInfo.Version - break - } - } - - combinedInfo := BinaryInfo{ - Name: binInfo.Name, - Description: description, - Repo: binInfo.Repo, - ModTime: binInfo.ModTime, - Version: version, - Updated: updated, - Size: binInfo.Size, - SHA256: binInfo.SHA256, - B3SUM: binInfo.B3SUM, - Source: binInfo.Source, + return nil, fmt.Errorf("error: info for the requested binary ('%s') not found in the metadata.json file", binaryName) } - return &combinedInfo, nil + return &binInfo, nil } diff --git a/install.go b/install.go index 71c6b56..37e085e 100644 --- a/install.go +++ b/install.go @@ -5,62 +5,57 @@ import ( "fmt" "os" "path/filepath" + "strings" ) -func installCommand(binaryName string, installMessage ...string) error { - installPath := filepath.Join(InstallDir, binaryName) +func installCommand(silent bool, binaryNames string) error { + // Disable the progressbar if the installation is to be performed silently + if silent { + UseProgressBar = false + } + binaries := strings.Fields(binaryNames) + for _, binaryName := range binaries { + // Extract the last part of the binaryName to use as the filename + fileName := filepath.Base(binaryName) - // Use ReturnCachedFile to check for a cached file - if installUseCache { - 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 - if err := copyFile(cachedFile, installPath); err != nil { - return fmt.Errorf("Error: Could not copy cached file: %v", err) - } + // Construct the installPath using the extracted filename + installPath := filepath.Join(InstallDir, fileName) + + // Use ReturnCachedFile to check for a cached file + if InstallUseCache { + cachedFile, err := ReturnCachedFile(binaryName) + if err == 0 { + // If the cached file exists, use it + if !silent { + fmt.Printf("Using cached file: %s\n", cachedFile) + } + // Copy the cached file to the install path + if err := copyFile(cachedFile, installPath); err != nil { + return fmt.Errorf("error: Could not copy cached file: %v", err) + } + + // Set executable bit immediately after copying + if err := os.Chmod(installPath, 0o755); err != nil { + return fmt.Errorf("failed to set executable bit: %v", err) + } - // Set executable bit immediately after copying - if err := os.Chmod(installPath, 0o755); err != nil { - return fmt.Errorf("failed to set executable bit: %v", err) + continue } + } - return nil + // If the cached file does not exist, download the binary + url, err := findURL(binaryName) + if err != nil { + errorOut("%v\n", err) } - } - // If the cached file does not exist, download the binary - url, err := findURL(binaryName) - if err != nil { - errorOut("%v\n", err) - } - if err := fetchBinaryFromURL(url, installPath); err != nil { - return fmt.Errorf("Error: Could not install binary: %v", err) - } + if err := fetchBinaryFromURL(url, installPath); err != nil { + return fmt.Errorf("error: Could not install binary: %v", err) + } - // 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 { - if installMessage[0] != "--silent" { - fmt.Println(installMessage[0]) - } - } + if !silent { + fmt.Printf("Installed: %s\n", installPath) } - } else { - fmt.Printf("Installation complete: %s\n", installPath) } return nil } diff --git a/listBinaries.go b/listBinaries.go index e133c7c..0ec35bc 100644 --- a/listBinaries.go +++ b/listBinaries.go @@ -4,7 +4,7 @@ package main import ( "encoding/json" "fmt" - "io/ioutil" + "io" "net/http" "path/filepath" "sort" @@ -29,7 +29,7 @@ func listBinaries() ([]string, error) { } // Read response body - body, err := ioutil.ReadAll(resp.Body) + body, err := io.ReadAll(resp.Body) if err != nil { return nil, fmt.Errorf("failed to read response body: %v", err) } diff --git a/main.go b/main.go index b10a516..1531c12 100644 --- a/main.go +++ b/main.go @@ -7,34 +7,39 @@ import ( "path/filepath" "runtime" "strconv" + "strings" ) var ( // Repositories contains all available repos - This variable is used by findURL.go Repositories []string // MetadataURLs are used for listing the binaries themselves. Not to be confused with R*MetadataURLs. - MetadataURLs []string - validatedArch = [3]string{} + MetadataURLs []string + // RNMetadataURL should contain the JSON that describes available binaries for your architecture + RNMetadataURL string + // ValidatedArch is used in fsearch.go, info.go and main.go to determine which repos to use. + ValidatedArch = [3]string{} // InstallDir holds the directory that shall be used for installing, removing, updating, listing with `info`. It takes the value of $INSTALL_DIR if it is set in the user's env, otherwise it is set to have a default value - InstallDir = os.Getenv("INSTALL_DIR") - installUseCache = true - useProgressBar = true - disableTruncation = false + InstallDir = os.Getenv("INSTALL_DIR") + // TEMPDIR will be used as the dir to download files to before moving them to a final destination AND as the place that will hold cached binaries downloaded by `run` + TEMPDIR = os.Getenv("BIGDL_CACHEDIR") // Will default to "/tmp/bigdl_cached" if $TMPDIR is not set + // InstallUseCache determines if cached files should be used when requesting an install + InstallUseCache = true + // UseProgressBar determines if the progressbar is shown or not + UseProgressBar = true + // DisableTruncation determines if update.go, fsearch.go, etc, truncate their messages or not + DisableTruncation = false ) const ( - RMetadataURL = "https://raw.githubusercontent.com/Azathothas/Toolpacks/main/metadata.json" // RMetadataURL is the file from which we extract descriptions, etc, for different binaries - RNMetadataURL = "https://bin.ajam.dev/METADATA.json" // RNMetadataURL is the file which contains a concatenation of all metadata in the different repos, this one also contains sha256 checksums - VERSION = "1.6.2" // VERSION to be displayed - usagePage = " [-v|-h] [list|install|remove|update|run|info|search|tldr] <{args}>" // usagePage to be shown + VERSION = "1.6.7p" // VERSION to be displayed + usagePage = " [-v|-h] [list|install|remove|update|run|info|search|tldr] <-args->" // usagePage to be shown // Truncation indicator indicator = "...>" // MaxCacheSize is the limit of binaries which can be stored at TEMP_DIR MaxCacheSize = 10 // BinariesToDelete - Once the cache is filled - The programs populate the list of binaries to be removed in order of least used. This variable sets the amount of binaries that should be deleted BinariesToDelete = 5 - // TEMPDIR will be used by the `run` functionality. See run.go - TEMPDIR = "/tmp/bigdl_cached" ) // Exclude specified file types and file names, these shall not appear in Lists nor in the Search Results @@ -47,8 +52,9 @@ var excludedFileTypes = map[string]struct{}{ ".txt": {}, ".tar": {}, ".zip": {}, - ".exe": {}, ".cfg": {}, + ".dir": {}, + ".test": {}, } var excludedFileNames = map[string]struct{}{ @@ -69,37 +75,51 @@ func init() { if InstallDir == "" { homeDir, err := os.UserHomeDir() if err != nil { - fmt.Fprintf(os.Stderr, "Error: Failed to get user's Home directory. %v\n", err) + errorOut("error: Failed to get user's Home directory. Maybe set $BIGDL_CACHEDIR? %v\n", err) os.Exit(1) } InstallDir = filepath.Join(homeDir, ".local", "bin") } - if os.Getenv("DISABLE_TRUNCATION") == "true" || os.Getenv("DISABLE_TRUNCATION") == "1" { - disableTruncation = true + if TEMPDIR == "" { + cacheDir, err := os.UserCacheDir() + if err != nil { + errorOut("error: Failed to get user's Cache directory. Maybe set $BIGDL_CACHEDIR? %v\n", err) + os.Exit(1) + } + TEMPDIR = filepath.Join(cacheDir, "bigdl_cache") } - - if os.Getenv("DISABLE_PRBAR") == "true" || os.Getenv("DISABLE_PRBAR") == "1" { - useProgressBar = false + if os.Getenv("BIGDL_TRUNCATION") == "0" { + DisableTruncation = true + } + if os.Getenv("BIGDL_PRBAR") == "0" { + UseProgressBar = false } - switch runtime.GOARCH { - case "amd64": - validatedArch = [3]string{"x86_64_Linux", "x86_64", "x86_64-Linux"} - case "arm64": - validatedArch = [3]string{"aarch64_arm64_Linux", "aarch64_arm64", "aarch64-Linux"} + // The repos are a mess. So we need to do this. Sorry + arch := runtime.GOARCH + "_" + runtime.GOOS + switch arch { + case "amd64_linux": + ValidatedArch = [3]string{"x86_64_Linux", "x86_64", "x86_64-Linux"} + case "arm64_linux": + ValidatedArch = [3]string{"aarch64_arm64_Linux", "aarch64_arm64", "aarch64-Linux"} + case "arm64_android": + ValidatedArch = [3]string{"arm64_v8a_Android", "arm64_v8a_Android", "arm64-v8a-Android"} + case "amd64_windows": + ValidatedArch = [3]string{"x64_Windows", "x64_Windows", "AMD64-Windows_NT"} default: - fmt.Println("Unsupported architecture:", runtime.GOARCH) + fmt.Println("Unsupported architecture:", arch) os.Exit(1) } - arch := validatedArch[0] + arch = ValidatedArch[0] Repositories = append(Repositories, "https://bin.ajam.dev/"+arch+"/") Repositories = append(Repositories, "https://bin.ajam.dev/"+arch+"/Baseutils/") Repositories = append(Repositories, "https://raw.githubusercontent.com/xplshn/Handyscripts/master/") // Binaries that are available in the Repositories but aren't described in any MetadataURLs will not be updated, nor listed with `info` nor `list` + RNMetadataURL = "https://bin.ajam.dev/" + arch + "/METADATA.json" // RNMetadataURL is the file which contains a concatenation of all metadata in the different repos, this one also contains sha256 checksums 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. + MetadataURLs = append(MetadataURLs, "https://api.github.com/repos/xplshn/Handyscripts/contents") } func printHelp() { @@ -117,7 +137,13 @@ Commands: run Run a specified binary from cache info Show information about a specific binary OR display installed binaries search Search for a binary - (not all binaries have metadata. Use list to see all binaries) - tldr Equivalent to "run --transparent --verbose tlrc" as argument. + tldr Equivalent to "run --transparent --verbose tlrc" as argument + +Variables: + BIGDL_PRBAR If present, and set to ZERO (0), the download progressbar will be disabled + BIGDL_TRUNCATION If present, and set to ZERO (0), string truncation will be disabled + BIGDL_CACHEDIR If present, it must contain a valid directory + INSTALL_DIR If present, it must contain a valid directory Examples: bigdl search editor @@ -187,19 +213,25 @@ func main() { } } case "install", "add": - // 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() + fmt.Printf("Usage: bigdl %s <--silent> [binar|y|ies]\n", flag.Arg(0)) + os.Exit(1) } - binaryName := os.Args[2] - installMessage := os.Args[3:] + // Join the binary names into a single string separated by spaces + binaries := strings.Join(flag.Args()[1:], " ") + silent := false + if flag.Arg(1) == "--silent" { + silent = true + // Skip the "--silent" flag when joining the binary names + binaries = strings.Join(flag.Args()[2:], " ") + } - installCommand(binaryName, installMessage...) + err := installCommand(silent, binaries) + if err != nil { + fmt.Printf("Installation failed: %v\n", err) + os.Exit(1) + } case "remove", "del": if flag.NArg() < 2 { fmt.Printf("Usage: bigdl %s [binar|y|ies]\n", flag.Arg(0)) @@ -217,11 +249,6 @@ func main() { RunFromCache(args[0], args[1:]) case "info": binaryName := flag.Arg(1) - - if len(os.Args) > 3 { - fmt.Fprintln(os.Stderr, "Warning: The command contains more arguments than expected. Part of the input unused.") - } - if len(os.Args) < 3 { installedPrograms, err := validateProgramsFrom(InstallDir, nil) if err != nil { @@ -232,7 +259,6 @@ func main() { fmt.Println(program) } } else { - binaryInfo, err := getBinaryInfo(binaryName) if err != nil { errorOut("%v\n", err) @@ -259,9 +285,6 @@ func main() { if binaryInfo.SHA256 != "" { fmt.Printf("SHA256: %s\n", binaryInfo.SHA256) } - if binaryInfo.B3SUM != "" { - fmt.Printf("B3SUM: %s\n", binaryInfo.B3SUM) - } } case "search": limit := 90 diff --git a/remove.go b/remove.go index 34b8252..a102764 100644 --- a/remove.go +++ b/remove.go @@ -9,16 +9,18 @@ import ( func remove(binariesToRemove []string) { for _, binaryName := range binariesToRemove { - installPath := filepath.Join(InstallDir, binaryName) + // Use the base name of binaryName for constructing the cachedFile path + baseName := filepath.Base(binaryName) + installPath := filepath.Join(InstallDir, baseName) err := os.Remove(installPath) if err != nil { if os.IsNotExist(err) { - fmt.Fprintf(os.Stderr, "Warning: '%s' does not exist in %s\n", binaryName, InstallDir) + fmt.Fprintf(os.Stderr, "Warning: '%s' does not exist in %s\n", baseName, InstallDir) } else { - fmt.Fprintf(os.Stderr, "Error: Failed to remove '%s' from %s. %v\n", binaryName, InstallDir, err) + fmt.Fprintf(os.Stderr, "Error: Failed to remove '%s' from %s. %v\n", baseName, InstallDir, err) } continue } - fmt.Printf("'%s' removed from %s\n", binaryName, InstallDir) + fmt.Printf("'%s' removed from %s\n", baseName, InstallDir) } } diff --git a/run.go b/run.go index 6b835ba..0dcae5a 100644 --- a/run.go +++ b/run.go @@ -14,9 +14,8 @@ import ( ) var ( - verboseMode bool - silentMode bool - transparentMode bool + verboseMode bool + silentMode bool ) // ReturnCachedFile retrieves the cached file location. Returns an empty string and error code 1 if not found. @@ -54,7 +53,7 @@ func RunFromCache(binaryName string, args []string) { flag.CommandLine.Parse(flagsAndBinaryName) if *verbose && *silent { - errorOut("Error: --verbose and --silent are mutually exclusive\n") + errorOut("error: --verbose and --silent are mutually exclusive\n") } if *verbose { @@ -64,17 +63,13 @@ func RunFromCache(binaryName string, args []string) { if *silent { silentMode = true + UseProgressBar = false purifyVars() } if *transparent { - transparentMode = true - purifyVars() - binaryPath, err := isBinaryInPath(binaryName) - if err != nil { - errorOut("Error checking if binary is in PATH: %s\n", err) - } + binaryPath, _ := exec.LookPath(binaryName) // is it okay to ignore the err channel of LookPath? if binaryPath != "" { if !silentMode { @@ -88,7 +83,10 @@ func RunFromCache(binaryName string, args []string) { errorOut("Error: Binary name not provided\n") } - cachedFile := filepath.Join(TEMPDIR, binaryName+".bin") + // Use the base name of binaryName for constructing the cachedFile path // This way we can support requests like: toybox/wget + baseName := filepath.Base(binaryName) + cachedFile := filepath.Join(TEMPDIR, fmt.Sprintf("%s.bin", baseName)) + if fileExists(cachedFile) && isExecutable(cachedFile) { if !silentMode { fmt.Printf("Running '%s' from cache...\n", binaryName) @@ -158,18 +156,18 @@ func runBinary(binaryPath string, args []string, verboseMode bool) { // fetchBinary downloads the binary and caches it. func fetchBinary(binaryName string) error { - if silentMode { - useProgressBar = false - } - url, err := findURL(binaryName) if err != nil { return err } - cachedFile := filepath.Join(TEMPDIR, binaryName+".bin") + // Extract the base name from the binaryName to use for the cached file name + baseName := filepath.Base(binaryName) + + // Construct the cachedFile path using the base name + cachedFile := filepath.Join(TEMPDIR, baseName+".bin") - // Fetch the binary from the internet and save it to the cache + // Fetch the binary from the repos and save it to the cache err = fetchBinaryFromURL(url, cachedFile) if err != nil { return fmt.Errorf("error fetching binary for %s: %v", binaryName, err) diff --git a/update.go b/update.go index cc0748a..2184c47 100644 --- a/update.go +++ b/update.go @@ -15,8 +15,8 @@ import ( // update checks for updates to the valid programs and installs any that have changed. func update(programsToUpdate []string) error { // 'Configure' external functions - useProgressBar = false - installUseCache = false + UseProgressBar = false + InstallUseCache = false // Initialize counters var ( @@ -92,8 +92,7 @@ func update(programsToUpdate []string) error { if checkDifferences(localSHA256, binaryInfo.SHA256) == 1 { truncatePrintf("\033[2K\r<%d/%d> %s | Detected a difference in %s. Updating...", atomic.LoadUint32(&checked), toBeChecked, padding, program) - installMessage := "--silent" - err := installCommand(program, installMessage) + err := installCommand(true, program) if err != nil { progressMutex.Lock() atomic.AddUint32(&errors, 1) @@ -125,20 +124,11 @@ func update(programsToUpdate []string) error { } // Print final counts fmt.Println(finalCounts) - fmt.Printf(errorMessages) + fmt.Print(errorMessages) return nil } -func contains(slice []string, str string) bool { - for _, v := range slice { - if v == str { - return true - } - } - return false -} - // getLocalSHA256 calculates the SHA256 checksum of the local file. func getLocalSHA256(filePath string) (string, error) { // Open the file for reading