github.com/soulteary/pocket-bookcase@v0.0.0-20240428065142-0b5a9a0fc98a/internal/cmd/utils.go (about)

     1  package cmd
     2  
     3  import (
     4  	"errors"
     5  	"fmt"
     6  	nurl "net/url"
     7  	"os"
     8  	"os/exec"
     9  	"path"
    10  	"runtime"
    11  	"strconv"
    12  	"strings"
    13  	"unicode/utf8"
    14  
    15  	"github.com/fatih/color"
    16  	"github.com/soulteary/pocket-bookcase/internal/model"
    17  	"golang.org/x/term"
    18  )
    19  
    20  var (
    21  	cIndex   = color.New(color.FgHiCyan)
    22  	cSymbol  = color.New(color.FgHiMagenta)
    23  	cTitle   = color.New(color.FgHiGreen).Add(color.Bold)
    24  	cURL     = color.New(color.FgHiYellow)
    25  	cExcerpt = color.New(color.FgHiWhite)
    26  	cTag     = color.New(color.FgHiBlue)
    27  
    28  	cInfo  = color.New(color.FgHiCyan)
    29  	cError = color.New(color.FgHiRed)
    30  
    31  	errInvalidIndex = errors.New("index is not valid")
    32  )
    33  
    34  func normalizeSpace(str string) string {
    35  	str = strings.TrimSpace(str)
    36  	return strings.Join(strings.Fields(str), " ")
    37  }
    38  
    39  func isURLValid(s string) bool {
    40  	tmp, err := nurl.Parse(s)
    41  	return err == nil && tmp.Scheme != "" && tmp.Hostname() != ""
    42  }
    43  
    44  func printBookmarks(bookmarks ...model.BookmarkDTO) {
    45  	for _, bookmark := range bookmarks {
    46  		// Create bookmark index
    47  		strBookmarkIndex := fmt.Sprintf("%d. ", bookmark.ID)
    48  		strSpace := strings.Repeat(" ", len(strBookmarkIndex))
    49  
    50  		// Print bookmark title
    51  		cIndex.Print(strBookmarkIndex)
    52  		cTitle.Println(bookmark.Title)
    53  
    54  		// Print bookmark URL
    55  		cSymbol.Print(strSpace + "> ")
    56  		cURL.Println(bookmark.URL)
    57  
    58  		// Print bookmark excerpt
    59  		if bookmark.Excerpt != "" {
    60  			cSymbol.Print(strSpace + "+ ")
    61  			cExcerpt.Println(bookmark.Excerpt)
    62  		}
    63  
    64  		// Print bookmark tags
    65  		if len(bookmark.Tags) > 0 {
    66  			cSymbol.Print(strSpace + "# ")
    67  			for i, tag := range bookmark.Tags {
    68  				if i == len(bookmark.Tags)-1 {
    69  					cTag.Println(tag.Name)
    70  				} else {
    71  					cTag.Print(tag.Name + ", ")
    72  				}
    73  			}
    74  		}
    75  
    76  		// Append new line
    77  		fmt.Println()
    78  	}
    79  }
    80  
    81  // parseStrIndices converts a list of indices to their integer values
    82  func parseStrIndices(indices []string) ([]int, error) {
    83  	var listIndex []int
    84  	for _, strIndex := range indices {
    85  		if !strings.Contains(strIndex, "-") {
    86  			index, err := strconv.Atoi(strIndex)
    87  			if err != nil || index < 1 {
    88  				return nil, errInvalidIndex
    89  			}
    90  
    91  			listIndex = append(listIndex, index)
    92  			continue
    93  		}
    94  
    95  		parts := strings.Split(strIndex, "-")
    96  		if len(parts) != 2 {
    97  			return nil, errInvalidIndex
    98  		}
    99  
   100  		minIndex, errMin := strconv.Atoi(parts[0])
   101  		maxIndex, errMax := strconv.Atoi(parts[1])
   102  		if errMin != nil || errMax != nil || minIndex < 1 || minIndex > maxIndex {
   103  			return nil, errInvalidIndex
   104  		}
   105  
   106  		for i := minIndex; i <= maxIndex; i++ {
   107  			listIndex = append(listIndex, i)
   108  		}
   109  	}
   110  
   111  	return listIndex, nil
   112  }
   113  
   114  // openBrowser tries to open the URL in a browser,
   115  // and returns any error if it happened.
   116  func openBrowser(url string) error {
   117  	var args []string
   118  	switch runtime.GOOS {
   119  	case "darwin":
   120  		args = []string{"open"}
   121  	case "windows":
   122  		args = []string{"cmd", "/c", "start"}
   123  	default:
   124  		args = []string{"xdg-open"}
   125  	}
   126  
   127  	cmd := exec.Command(args[0], append(args[1:], url)...)
   128  	return cmd.Run()
   129  }
   130  
   131  func getTerminalWidth() int {
   132  	width, _, _ := term.GetSize(int(os.Stdin.Fd()))
   133  	return width
   134  }
   135  
   136  func validateTitle(title, fallback string) string {
   137  	// Normalize spaces before we begin
   138  	title = normalizeSpace(title)
   139  	title = strings.TrimSpace(title)
   140  
   141  	// If at this point title already empty, just uses fallback
   142  	if title == "" {
   143  		return fallback
   144  	}
   145  
   146  	// Check if it's already valid UTF-8 string
   147  	if valid := utf8.ValidString(title); valid {
   148  		return title
   149  	}
   150  
   151  	// Remove invalid runes to get the valid UTF-8 title
   152  	fixUtf := func(r rune) rune {
   153  		if r == utf8.RuneError {
   154  			return -1
   155  		}
   156  		return r
   157  	}
   158  	validUtf := strings.Map(fixUtf, title)
   159  
   160  	// If it's empty use fallback string
   161  	validUtf = strings.TrimSpace(validUtf)
   162  	if validUtf == "" {
   163  		return fallback
   164  	}
   165  
   166  	return validUtf
   167  }
   168  
   169  func SFCallerPrettyfier(frame *runtime.Frame) (string, string) {
   170  	return "", fmt.Sprintf("%s:%d", path.Base(frame.File), frame.Line)
   171  }