github.com/orangenpresse/up@v0.6.0/internal/util/util.go (about) 1 // Package util haters gonna hate. 2 package util 3 4 import ( 5 "bufio" 6 "crypto/md5" 7 "encoding/hex" 8 "encoding/json" 9 "fmt" 10 "io" 11 "io/ioutil" 12 "math" 13 "net" 14 "net/http" 15 "net/url" 16 "os" 17 "os/exec" 18 "strings" 19 "syscall" 20 "time" 21 22 "github.com/apex/up/internal/colors" 23 "github.com/pascaldekloe/name" 24 "github.com/pkg/errors" 25 "github.com/tj/backoff" 26 "github.com/tj/go-progress" 27 "github.com/tj/go/term" 28 "golang.org/x/net/publicsuffix" 29 ) 30 31 // Fields retained when clearing. 32 var keepFields = map[string]bool{ 33 "X-Powered-By": true, 34 } 35 36 // ClearHeader removes all header fields. 37 func ClearHeader(h http.Header) { 38 for k := range h { 39 if keepFields[k] { 40 continue 41 } 42 43 h.Del(k) 44 } 45 } 46 47 // ManagedByUp appends "Managed by Up". 48 func ManagedByUp(s string) string { 49 if s == "" { 50 return "Managed by Up." 51 } 52 53 return s + " (Managed by Up)." 54 } 55 56 // Exists returns true if the file exists. 57 func Exists(path string) bool { 58 _, err := os.Stat(path) 59 return err == nil 60 } 61 62 // ReadFileJSON reads json from the given path. 63 func ReadFileJSON(path string, v interface{}) error { 64 b, err := ioutil.ReadFile(path) 65 if err != nil { 66 return errors.Wrap(err, "reading") 67 } 68 69 if err := json.Unmarshal(b, &v); err != nil { 70 return errors.Wrap(err, "unmarshaling") 71 } 72 73 return nil 74 } 75 76 // Camelcase string with optional args. 77 func Camelcase(s string, v ...interface{}) string { 78 return name.CamelCase(fmt.Sprintf(s, v...), true) 79 } 80 81 // NewProgressInt with the given total. 82 func NewProgressInt(total int) *progress.Bar { 83 b := progress.NewInt(total) 84 b.Template(`{{.Bar}} {{.Percent | printf "%0.0f"}}% {{.Text}}`) 85 b.Width = 35 86 b.StartDelimiter = colors.Gray("|") 87 b.EndDelimiter = colors.Gray("|") 88 b.Filled = colors.Purple("█") 89 b.Empty = colors.Gray("░") 90 return b 91 } 92 93 // NewInlineProgressInt with the given total. 94 func NewInlineProgressInt(total int) *progress.Bar { 95 b := progress.NewInt(total) 96 b.Template(`{{.Bar}} {{.Percent | printf "%0.0f"}}% {{.Text}}`) 97 b.Width = 20 98 b.StartDelimiter = colors.Gray("|") 99 b.EndDelimiter = colors.Gray("|") 100 b.Filled = colors.Purple("█") 101 b.Empty = colors.Gray(" ") 102 return b 103 } 104 105 // Pad helper. 106 func Pad() func() { 107 println() 108 return func() { 109 println() 110 } 111 } 112 113 // Fatal error. 114 func Fatal(err error) { 115 fmt.Fprintf(os.Stderr, "\n %s %s\n\n", colors.Red("Error:"), err) 116 os.Exit(1) 117 } 118 119 // IsJSON returns true if the string looks like json. 120 func IsJSON(s string) bool { 121 return len(s) > 1 && s[0] == '{' && s[len(s)-1] == '}' 122 } 123 124 // IsJSONLog returns true if the string looks likes a json log. 125 func IsJSONLog(s string) bool { 126 return IsJSON(s) && strings.Contains(s, `"level"`) 127 } 128 129 // IsNotFound returns true if err is not nil and represents a missing resource. 130 func IsNotFound(err error) bool { 131 switch { 132 case err == nil: 133 return false 134 case strings.Contains(err.Error(), "ResourceNotFoundException"): 135 return true 136 case strings.Contains(err.Error(), "does not exist"): 137 return true 138 case strings.Contains(err.Error(), "not found"): 139 return true 140 default: 141 return false 142 } 143 } 144 145 // IsBucketExists returns true if err is not nil and represents an existing bucket. 146 func IsBucketExists(err error) bool { 147 switch { 148 case err == nil: 149 return false 150 case strings.Contains(err.Error(), "BucketAlreadyOwnedByYou"): 151 return true 152 default: 153 return false 154 } 155 } 156 157 // IsThrottled returns true if err is not nil and represents a throttled request. 158 func IsThrottled(err error) bool { 159 switch { 160 case err == nil: 161 return false 162 case strings.Contains(err.Error(), "Throttling: Rate exceeded"): 163 return true 164 default: 165 return false 166 } 167 } 168 169 // IsNoCredentials returns true if err is not nil and represents missing credentials. 170 func IsNoCredentials(err error) bool { 171 switch { 172 case err == nil: 173 return false 174 case strings.Contains(err.Error(), "NoCredentialProviders"): 175 return true 176 default: 177 return false 178 } 179 } 180 181 // Env returns a slice from environment variable map. 182 func Env(m map[string]string) (env []string) { 183 for k, v := range m { 184 env = append(env, fmt.Sprintf("%s=%s", k, v)) 185 } 186 return 187 } 188 189 // PrefixLines prefixes the lines in s with prefix. 190 func PrefixLines(s string, prefix string) string { 191 lines := strings.Split(s, "\n") 192 for i, l := range lines { 193 lines[i] = prefix + l 194 } 195 return strings.Join(lines, "\n") 196 } 197 198 // Indent the given string. 199 func Indent(s string) string { 200 return PrefixLines(s, " ") 201 } 202 203 // WaitForListen blocks until `u` is listening with timeout. 204 func WaitForListen(u *url.URL, timeout time.Duration) error { 205 timedout := time.After(timeout) 206 207 b := backoff.Backoff{ 208 Min: 100 * time.Millisecond, 209 Max: time.Second, 210 Factor: 1.5, 211 } 212 213 for { 214 select { 215 case <-timedout: 216 return errors.Errorf("timed out after %s", timeout) 217 case <-time.After(b.Duration()): 218 if IsListening(u) { 219 return nil 220 } 221 } 222 } 223 } 224 225 // IsListening returns true if there's a server listening on `u`. 226 func IsListening(u *url.URL) bool { 227 conn, err := net.Dial("tcp", u.Host) 228 if err != nil { 229 return false 230 } 231 232 conn.Close() 233 return true 234 } 235 236 // ExitStatus returns the exit status of cmd. 237 func ExitStatus(cmd *exec.Cmd, err error) string { 238 ps := cmd.ProcessState 239 240 if e, ok := err.(*exec.ExitError); ok { 241 ps = e.ProcessState 242 } 243 244 if ps != nil { 245 s, ok := ps.Sys().(syscall.WaitStatus) 246 if ok { 247 return fmt.Sprintf("%d", s.ExitStatus()) 248 } 249 } 250 251 return "?" 252 } 253 254 // StringsContains returns true if list contains s. 255 func StringsContains(list []string, s string) bool { 256 for _, v := range list { 257 if v == s { 258 return true 259 } 260 } 261 return false 262 } 263 264 // BasePath returns a normalized base path, 265 // stripping the leading '/' if present. 266 func BasePath(s string) string { 267 return strings.TrimLeft(s, "/") 268 } 269 270 // LogPad outputs a log message with padding. 271 func LogPad(msg string, v ...interface{}) { 272 defer Pad()() 273 Log(msg, v...) 274 } 275 276 // Log outputs a log message. 277 func Log(msg string, v ...interface{}) { 278 fmt.Printf(" %s\n", colors.Purple(fmt.Sprintf(msg, v...))) 279 } 280 281 // LogClear clears the line and outputs a log message. 282 func LogClear(msg string, v ...interface{}) { 283 term.MoveUp(1) 284 term.ClearLine() 285 fmt.Printf("\r %s\n", colors.Purple(fmt.Sprintf(msg, v...))) 286 } 287 288 // LogTitle outputs a log title. 289 func LogTitle(msg string, v ...interface{}) { 290 fmt.Printf("\n \x1b[1m%s\x1b[m\n\n", fmt.Sprintf(msg, v...)) 291 } 292 293 // LogName outputs a log message with name. 294 func LogName(name, msg string, v ...interface{}) { 295 fmt.Printf(" %s %s\n", colors.Purple(name+":"), fmt.Sprintf(msg, v...)) 296 } 297 298 // LogListItem outputs a list item. 299 func LogListItem(msg string, v ...interface{}) { 300 fmt.Printf(" • %s\n", fmt.Sprintf(msg, v...)) 301 } 302 303 // ToFloat returns a float or NaN. 304 func ToFloat(v interface{}) float64 { 305 switch n := v.(type) { 306 case int: 307 return float64(n) 308 case int8: 309 return float64(n) 310 case int16: 311 return float64(n) 312 case int32: 313 return float64(n) 314 case int64: 315 return float64(n) 316 case uint: 317 return float64(n) 318 case uint8: 319 return float64(n) 320 case uint16: 321 return float64(n) 322 case uint32: 323 return float64(n) 324 case uint64: 325 return float64(n) 326 case float32: 327 return float64(n) 328 case float64: 329 return n 330 default: 331 return math.NaN() 332 } 333 } 334 335 // Milliseconds returns the duration as milliseconds. 336 func Milliseconds(d time.Duration) int { 337 return int(d / time.Millisecond) 338 } 339 340 // MillisecondsSince returns the duration as milliseconds relative to time t. 341 func MillisecondsSince(t time.Time) int { 342 return int(time.Since(t) / time.Millisecond) 343 } 344 345 // ParseDuration string with day and month approximation support. 346 func ParseDuration(s string) (d time.Duration, err error) { 347 r := strings.NewReader(s) 348 349 switch { 350 case strings.HasSuffix(s, "d"): 351 var v float64 352 _, err = fmt.Fscanf(r, "%fd", &v) 353 d = time.Duration(v * float64(24*time.Hour)) 354 case strings.HasSuffix(s, "w"): 355 var v float64 356 _, err = fmt.Fscanf(r, "%fw", &v) 357 d = time.Duration(v * float64(24*time.Hour*7)) 358 case strings.HasSuffix(s, "mo"): 359 var v float64 360 _, err = fmt.Fscanf(r, "%fmo", &v) 361 d = time.Duration(v * float64(30*24*time.Hour)) 362 case strings.HasSuffix(s, "M"): 363 var v float64 364 _, err = fmt.Fscanf(r, "%fM", &v) 365 d = time.Duration(v * float64(30*24*time.Hour)) 366 default: 367 d, err = time.ParseDuration(s) 368 } 369 370 return 371 } 372 373 // Md5 returns an md5 hash for s. 374 func Md5(s string) string { 375 h := md5.New() 376 h.Write([]byte(s)) 377 return hex.EncodeToString(h.Sum(nil)) 378 } 379 380 // Domain returns the effective domain (TLD plus one). 381 func Domain(s string) string { 382 d, err := publicsuffix.EffectiveTLDPlusOne(s) 383 if err != nil { 384 panic(errors.Wrap(err, "effective domain")) 385 } 386 387 return d 388 } 389 390 // CertDomainNames returns the certificate domain name 391 // and alternative names for a requested domain. 392 func CertDomainNames(s string) []string { 393 // effective domain 394 if Domain(s) == s { 395 return []string{s, "*." + s} 396 } 397 398 // subdomain 399 return []string{RemoveSubdomains(s, 1), "*." + RemoveSubdomains(s, 1)} 400 } 401 402 // IsWildcardDomain returns true if the domain is a wildcard. 403 func IsWildcardDomain(s string) bool { 404 return strings.HasPrefix(s, "*.") 405 } 406 407 // WildcardMatches returns true if wildcard is a wildcard domain 408 // and it satisfies the given domain. 409 func WildcardMatches(wildcard, domain string) bool { 410 if !IsWildcardDomain(wildcard) { 411 return false 412 } 413 414 w := RemoveSubdomains(wildcard, 1) 415 d := RemoveSubdomains(domain, 1) 416 return w == d 417 } 418 419 // RemoveSubdomains returns the domain without the n left-most subdomain(s). 420 func RemoveSubdomains(s string, n int) string { 421 domains := strings.Split(s, ".") 422 return strings.Join(domains[n:], ".") 423 } 424 425 // ParseSections returns INI style sections from r. 426 func ParseSections(r io.Reader) (sections []string, err error) { 427 s := bufio.NewScanner(r) 428 429 for s.Scan() { 430 t := s.Text() 431 if strings.HasPrefix(t, "[") { 432 sections = append(sections, strings.Trim(t, "[]")) 433 } 434 } 435 436 err = s.Err() 437 438 return 439 } 440 441 // UniqueStrings returns a string slice of unique values. 442 func UniqueStrings(s []string) (v []string) { 443 m := make(map[string]struct{}) 444 for _, val := range s { 445 _, ok := m[val] 446 if !ok { 447 v = append(v, val) 448 m[val] = struct{}{} 449 } 450 } 451 return 452 } 453 454 // IsCI returns true if the env looks like it's a CI platform. 455 func IsCI() bool { 456 return os.Getenv("CI") == "true" 457 } 458 459 // EncodeAlias encodes an alias string so that it conforms to the 460 // requirement of matching (?!^[0-9]+$)([a-zA-Z0-9-_]+). 461 func EncodeAlias(s string) string { 462 return "commit-" + strings.Replace(s, ".", "_", -1) 463 } 464 465 // DecodeAlias decodes an alias string which was encoded by 466 // the EncodeAlias function. 467 func DecodeAlias(s string) string { 468 s = strings.Replace(s, "_", ".", -1) 469 s = strings.Replace(s, "commit-", "", 1) 470 return s 471 } 472 473 // DateSuffix returns the date suffix for t. 474 func DateSuffix(t time.Time) string { 475 switch t.Day() { 476 case 1, 21, 31: 477 return "st" 478 case 2, 22: 479 return "nd" 480 case 3, 23: 481 return "rd" 482 default: 483 return "th" 484 } 485 }