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 }