github.com/wtfutil/wtf@v0.43.0/utils/utils.go (about) 1 package utils 2 3 import ( 4 "bytes" 5 "encoding/json" 6 "fmt" 7 "io" 8 "os" 9 "os/exec" 10 "path/filepath" 11 "regexp" 12 "runtime" 13 "strings" 14 15 "github.com/logrusorgru/aurora/v4" 16 "github.com/olebedev/config" 17 ) 18 19 const ( 20 SimpleDateFormat = "Jan 2" 21 SimpleTimeFormat = "15:04 MST" 22 MinimumTimeFormat12 = "3:04 PM" 23 MinimumTimeFormat24 = "15:04" 24 25 FullDateFormat = "Monday, Jan 2" 26 FriendlyDateFormat = "Mon, Jan 2" 27 FriendlyDateTimeFormat = "Mon, Jan 2, 15:04" 28 29 TimestampFormat = "2006-01-02T15:04:05-0700" 30 ) 31 32 // DoesNotInclude takes a slice of strings and a target string and returns 33 // TRUE if the slice does not include the target, FALSE if it does 34 // 35 // Example: 36 // 37 // x := DoesNotInclude([]string{"cat", "dog", "rat"}, "dog") 38 // > false 39 // 40 // x := DoesNotInclude([]string{"cat", "dog", "rat"}, "pig") 41 // > true 42 func DoesNotInclude(strs []string, val string) bool { 43 return !Includes(strs, val) 44 } 45 46 // ExecuteCommand executes an external command on the local machine as the current user 47 func ExecuteCommand(cmd *exec.Cmd) string { 48 if cmd == nil { 49 return "" 50 } 51 52 buf := &bytes.Buffer{} 53 cmd.Stdout = buf 54 55 if err := cmd.Run(); err != nil { 56 return err.Error() 57 } 58 59 return buf.String() 60 } 61 62 // FindMatch takes a regex pattern and a string of data and returns back all the matches 63 // in that string 64 func FindMatch(pattern string, data string) [][]string { 65 r := regexp.MustCompile(pattern) 66 return r.FindAllStringSubmatch(data, -1) 67 } 68 69 // Includes takes a slice of strings and a target string and returns 70 // TRUE if the slice includes the target, FALSE if it does not 71 // 72 // Example: 73 // 74 // x := Includes([]string{"cat", "dog", "rat"}, "dog") 75 // > true 76 // 77 // x := Includes([]string{"cat", "dog", "rat"}, "pig") 78 // > false 79 func Includes(strs []string, val string) bool { 80 for _, str := range strs { 81 if val == str { 82 return true 83 } 84 } 85 return false 86 } 87 88 // OpenFile opens the file defined in `path` via the operating system 89 func OpenFile(path string) { 90 if (strings.HasPrefix(path, "http://")) || (strings.HasPrefix(path, "https://")) { 91 if len(OpenUrlUtil) > 0 { 92 commands := append(OpenUrlUtil, path) 93 cmd := exec.Command(commands[0], commands[1:]...) 94 err := cmd.Start() 95 if err != nil { 96 return 97 } 98 return 99 } 100 101 var cmd *exec.Cmd 102 switch runtime.GOOS { 103 case "linux": 104 cmd = exec.Command("xdg-open", path) 105 case "windows": 106 cmd = exec.Command("rundll32", "url.dll,FileProtocolHandler", path) 107 case "darwin": 108 cmd = exec.Command("open", path) 109 default: 110 // for the BSDs 111 cmd = exec.Command("xdg-open", path) 112 } 113 114 err := cmd.Start() 115 if err != nil { 116 return 117 } 118 return 119 } 120 121 filePath, _ := ExpandHomeDir(path) 122 cmd := exec.Command(OpenFileUtil, filePath) 123 ExecuteCommand(cmd) 124 } 125 126 // ReadFileBytes reads the contents of a file and returns those contents as a slice of bytes 127 func ReadFileBytes(filePath string) ([]byte, error) { 128 fileData, err := os.ReadFile(filepath.Clean(filePath)) 129 if err != nil { 130 return []byte{}, err 131 } 132 133 return fileData, nil 134 } 135 136 // ParseJSON is a standard JSON reader from text 137 func ParseJSON(obj interface{}, text io.Reader) error { 138 d := json.NewDecoder(text) 139 return d.Decode(obj) 140 } 141 142 // CalculateDimensions reads the module dimensions from the module and global config. The border is already subtracted. 143 func CalculateDimensions(moduleConfig, globalConfig *config.Config) (int, int, error) { 144 grid, err := globalConfig.Get("wtf.grid") 145 if err != nil { 146 return 0, 0, err 147 } 148 149 cols := ToInts(grid.UList("columns")) 150 rows := ToInts(grid.UList("rows")) 151 152 // If they're defined in the config, they cannot be empty 153 if len(cols) == 0 || len(rows) == 0 { 154 displayGridConfigError() 155 os.Exit(1) 156 } 157 158 // Read the source data from the config 159 left := moduleConfig.UInt("position.left", 0) 160 top := moduleConfig.UInt("position.top", 0) 161 width := moduleConfig.UInt("position.width", 0) 162 height := moduleConfig.UInt("position.height", 0) 163 164 // Make sure the values are in bounds 165 left = Clamp(left, 0, len(cols)-1) 166 top = Clamp(top, 0, len(rows)-1) 167 width = Clamp(width, 0, len(cols)-left) 168 height = Clamp(height, 0, len(rows)-top) 169 170 // Start with the border subtracted and add all the spanned rows and cols 171 w, h := -2, -2 172 for _, x := range cols[left : left+width] { 173 w += x 174 } 175 for _, y := range rows[top : top+height] { 176 h += y 177 } 178 179 // The usable space may be empty 180 w = MaxInt(w, 0) 181 h = MaxInt(h, 0) 182 183 return w, h, nil 184 } 185 186 // MaxInt returns the larger of x or y 187 // 188 // Examples: 189 // 190 // MaxInt(3, 2) => 3 191 // MaxInt(2, 3) => 3 192 func MaxInt(x, y int) int { 193 if x > y { 194 return x 195 } 196 return y 197 } 198 199 // Clamp restricts values to a minimum and maximum value 200 // 201 // Examples: 202 // 203 // clamp(6, 3, 8) => 6 204 // clamp(1, 3, 8) => 3 205 // clamp(9, 3, 8) => 8 206 func Clamp(x, a, b int) int { 207 if a > x { 208 return a 209 } 210 if b < x { 211 return b 212 } 213 return x 214 } 215 216 /* -------------------- Unexported Functions -------------------- */ 217 218 func displayGridConfigError() { 219 fmt.Printf("\n%s 'grid' config values are invalid. 'columns' and 'rows' cannot be empty.\n", aurora.Red("ERROR")) 220 fmt.Println() 221 fmt.Println("This is invalid:") 222 fmt.Println() 223 fmt.Println(" grid:") 224 fmt.Println(" columns: []") 225 fmt.Println(" rows: []") 226 fmt.Println() 227 fmt.Printf("%s If you want the columns and rows to be dynamically-determined, remove the 'grid' key and child keys from your config file.\n", aurora.Yellow("*")) 228 fmt.Printf("%s If you want explicit widths and heights, add integer values to the 'columns' and 'rows' arrays.\n", aurora.Yellow("*")) 229 fmt.Println() 230 }