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  }