repo_description: Deploy infinitely scalable serverless apps, apis, and sites in seconds to AWS.
repo_topics: api api-gateway aws aws-infrastructure aws-lambda deploy-tool deployment golang graphql heroku http lambda microservice microservices nodejs serverless static up
code: // Package util haters gonna hate.
  package util
  
  import (
  	"bufio"
  	"crypto/md5"
  	"encoding/hex"
  	"encoding/json"
  	"fmt"
  	"io"
  	"io/ioutil"
  	"math"
  	"net"
  	"net/http"
  	"net/url"
  	"os"
  	"os/exec"
  	"strings"
  	"syscall"
  	"time"
  
  	"github.com/apex/up/internal/colors"
  	"github.com/pascaldekloe/name"
  	"github.com/pkg/errors"
  	"github.com/tj/backoff"
  	"github.com/tj/go-progress"
  	"github.com/tj/go/term"
  	"golang.org/x/net/publicsuffix"
  )
  
  // ClearHeader removes all content header fields.
  func ClearHeader(h http.Header) {
  	h.Del("Content-Type")
  	h.Del("Content-Length")
  	h.Del("Content-Encoding")
  	h.Del("Content-Range")
  	h.Del("Content-MD5")
  	h.Del("Cache-Control")
  	h.Del("ETag")
  	h.Del("Last-Modified")
  }
  
  // ManagedByUp appends "Managed by Up".
  func ManagedByUp(s string) string {
  	if s == "" {
  		return "Managed by Up."
  	}
  
  	return s + " (Managed by Up)."
  }
  
  // Exists returns true if the file exists.
  func Exists(path string) bool {
  	_, err := os.Stat(path)
  	return err == nil
  }
  
  // ReadFileJSON reads json from the given path.
  func ReadFileJSON(path string, v interface{}) error {
  	b, err := ioutil.ReadFile(path)
  	if err != nil {
  		return errors.Wrap(err, "reading")
  	}
  
  	if err := json.Unmarshal(b, &v); err != nil {
  		return errors.Wrap(err, "unmarshaling")
  	}
  
  	return nil
  }
  
  // Camelcase string with optional args.
  func Camelcase(s string, v ...interface{}) string {
  	return name.CamelCase(fmt.Sprintf(s, v...), true)
  }
  
  // NewProgressInt with the given total.
  func NewProgressInt(total int) *progress.Bar {
  	b := progress.NewInt(total)
  	b.Template(`{{.Bar}} {{.Percent | printf "%0.0f"}}% {{.Text}}`)
  	b.Width = 35
  	b.StartDelimiter = colors.Gray("|")
  	b.EndDelimiter = colors.Gray("|")
  	b.Filled = colors.Purple("█")
  	b.Empty = colors.Gray("░")
  	return b
  }
  
  // NewInlineProgressInt with the given total.
  func NewInlineProgressInt(total int) *progress.Bar {
  	b := progress.NewInt(total)
  	b.Template(`{{.Bar}} {{.Percent | printf "%0.0f"}}% {{.Text}}`)
  	b.Width = 20
  	b.StartDelimiter = colors.Gray("|")
  	b.EndDelimiter = colors.Gray("|")
  	b.Filled = colors.Purple("█")
  	b.Empty = colors.Gray(" ")
  	return b
  }
  
  // Pad helper.
  func Pad() func() {
  	println()
  	return func() {
  		println()
  	}
  }
  
  // Fatal error.
  func Fatal(err error) {
  	fmt.Fprintf(os.Stderr, "\n     %s %s\n\n", colors.Red("Error:"), err)
  	os.Exit(1)
  }
  
  // IsJSON returns true if the string looks like json.
  func IsJSON(s string) bool {
  	return len(s) > 1 && s[0] == '{' && s[len(s)-1] == '}'
  }
  
  // IsJSONLog returns true if the string looks likes a json log.
  func IsJSONLog(s string) bool {
  	return IsJSON(s) && strings.Contains(s, `"level"`)
  }
  
  // IsNotFound returns true if err is not nil and represents a missing resource.
  func IsNotFound(err error) bool {
  	switch {
  	case err == nil:
  		return false
  	case strings.Contains(err.Error(), "ResourceNotFoundException"):
  		return true
  	case strings.Contains(err.Error(), "NoSuchEntity"):
  		return true
  	case strings.Contains(err.Error(), "does not exist"):
  		return true
  	case strings.Contains(err.Error(), "not found"):
  		return true
  	default:
  		return false
  	}
  }
  
  // IsBucketExists returns true if err is not nil and represents an existing bucket.
  func IsBucketExists(err error) bool {
  	switch {
  	case err == nil:
  		return false
  	case strings.Contains(err.Error(), "BucketAlreadyOwnedByYou"):
  		return true
  	default:
  		return false
  	}
  }
  
  // IsThrottled returns true if err is not nil and represents a throttled request.
  func IsThrottled(err error) bool {
  	switch {
  	case err == nil:
  		return false
  	case strings.Contains(err.Error(), "Throttling: Rate exceeded"):
  		return true
  	default:
  		return false
  	}
  }
  
  // IsNoCredentials returns true if err is not nil and represents missing credentials.
  func IsNoCredentials(err error) bool {
  	switch {
  	case err == nil:
  		return false
  	case strings.Contains(err.Error(), "NoCredentialProviders"):
  		return true
  	default:
  		return false
  	}
  }
  
  // Env returns a slice from environment variable map.
  func Env(m map[string]string) (env []string) {
  	for k, v := range m {
  		env = append(env, fmt.Sprintf("%s=%s", k, v))
  	}
  	return
  }
  
  // PrefixLines prefixes the lines in s with prefix.
  func PrefixLines(s string, prefix string) string {
  	lines := strings.Split(s, "\n")
  	for i, l := range lines {
  		lines[i] = prefix + l
  	}
  	return strings.Join(lines, "\n")
  }
  
  // Indent the given string.
  func Indent(s string) string {
  	return PrefixLines(s, "  ")
  }
  
  // WaitForListen blocks until `u` is listening with timeout.
  func WaitForListen(u *url.URL, timeout time.Duration) error {
  	timedout := time.After(timeout)
  
  	b := backoff.Backoff{
  		Min:    100 * time.Millisecond,
  		Max:    time.Second,
  		Factor: 1.5,
  	}
  
  	for {
  		select {
  		case <-timedout:
  			return errors.Errorf("timed out after %s", timeout)
  		case <-time.After(b.Duration()):
  			if IsListening(u) {
  				return nil
  			}
  		}
  	}
  }
  
  // IsListening returns true if there's a server listening on `u`.
  func IsListening(u *url.URL) bool {
  	conn, err := net.Dial("tcp", u.Host)
  	if err != nil {
  		return false
  	}
  
  	conn.Close()
  	return true
  }
  
  // ExitStatus returns the exit status of cmd.
  func ExitStatus(cmd *exec.Cmd, err error) string {
  	ps := cmd.ProcessState
  
  	if e, ok := err.(*exec.ExitError); ok {
  		ps = e.ProcessState
  	}
  
  	if ps != nil {
  		s, ok := ps.Sys().(syscall.WaitStatus)
  		if ok {
  			return fmt.Sprintf("%d", s.ExitStatus())
  		}
  	}
  
  	return "?"
  }
  
  // StringsContains returns true if list contains s.
  func StringsContains(list []string, s string) bool {
  	for _, v := range list {
  		if v == s {
  			return true
  		}
  	}
  	return false
  }
  
  // BasePath returns a normalized base path,
  // stripping the leading '/' if present.
  func BasePath(s string) string {
  	return strings.TrimLeft(s, "/")
  }
  
  // LogPad outputs a log message with padding.
  func LogPad(msg string, v ...interface{}) {
  	defer Pad()()
  	Log(msg, v...)
  }
  
  // Log outputs a log message.
  func Log(msg string, v ...interface{}) {
  	fmt.Printf("     %s\n", colors.Purple(fmt.Sprintf(msg, v...)))
  }
  
  // LogClear clears the line and outputs a log message.
  func LogClear(msg string, v ...interface{}) {
  	term.MoveUp(1)
  	term.ClearLine()
  	fmt.Printf("\r     %s\n", colors.Purple(fmt.Sprintf(msg, v...)))
  }
  
  // LogTitle outputs a log title.
  func LogTitle(msg string, v ...interface{}) {
  	fmt.Printf("\n     \x1b[1m%s\x1b[m\n\n", fmt.Sprintf(msg, v...))
  }
  
  // LogName outputs a log message with name.
  func LogName(name, msg string, v ...interface{}) {
  	fmt.Printf("     %s %s\n", colors.Purple(name+":"), fmt.Sprintf(msg, v...))
  }
  
  // LogListItem outputs a list item.
  func LogListItem(msg string, v ...interface{}) {
  	fmt.Printf("      • %s\n", fmt.Sprintf(msg, v...))
  }
  
  // ToFloat returns a float or NaN.
  func ToFloat(v interface{}) float64 {
  	switch n := v.(type) {
  	case int:
  		return float64(n)
  	case int8:
  		return float64(n)
  	case int16:
  		return float64(n)
  	case int32:
  		return float64(n)
  	case int64:
  		return float64(n)
  	case uint:
  		return float64(n)
  	case uint8:
  		return float64(n)
  	case uint16:
  		return float64(n)
  	case uint32:
  		return float64(n)
  	case uint64:
  		return float64(n)
  	case float32:
  		return float64(n)
  	case float64:
  		return n
  	default:
  		return math.NaN()
  	}
  }
  
  // Milliseconds returns the duration as milliseconds.
  func Milliseconds(d time.Duration) int {
  	return int(d / time.Millisecond)
  }
  
  // MillisecondsSince returns the duration as milliseconds relative to time t.
  func MillisecondsSince(t time.Time) int {
  	return int(time.Since(t) / time.Millisecond)
  }
  
  // ParseDuration string with day and month approximation support.
  func ParseDuration(s string) (d time.Duration, err error) {
  	r := strings.NewReader(s)
  
  	switch {
  	case strings.HasSuffix(s, "d"):
  		var v float64
  		_, err = fmt.Fscanf(r, "%fd", &v)
  		d = time.Duration(v * float64(24*time.Hour))
  	case strings.HasSuffix(s, "w"):
  		var v float64
  		_, err = fmt.Fscanf(r, "%fw", &v)
  		d = time.Duration(v * float64(24*time.Hour*7))
  	case strings.HasSuffix(s, "mo"):
  		var v float64
  		_, err = fmt.Fscanf(r, "%fmo", &v)
  		d = time.Duration(v * float64(30*24*time.Hour))
  	case strings.HasSuffix(s, "M"):
  		var v float64
  		_, err = fmt.Fscanf(r, "%fM", &v)
  		d = time.Duration(v * float64(30*24*time.Hour))
  	default:
  		d, err = time.ParseDuration(s)
  	}
  
  	return
  }
  
  // Md5 returns an md5 hash for s.
  func Md5(s string) string {
  	h := md5.New()
  	h.Write([]byte(s))
  	return hex.EncodeToString(h.Sum(nil))
  }
  
  // Domain returns the effective domain (TLD plus one).
  func Domain(s string) string {
  	d, err := publicsuffix.EffectiveTLDPlusOne(s)
  	if err != nil {
  		panic(errors.Wrap(err, "effective domain"))
  	}
  
  	return d
  }
  
  // CertDomainNames returns the certificate domain name
  // and alternative names for a requested domain.
  func CertDomainNames(s string) []string {
  	// effective domain
  	if Domain(s) == s {
  		return []string{s, "*." + s}
  	}
  
  	// subdomain
  	return []string{RemoveSubdomains(s, 1), "*." + RemoveSubdomains(s, 1)}
  }
  
  // IsWildcardDomain returns true if the domain is a wildcard.
  func IsWildcardDomain(s string) bool {
  	return strings.HasPrefix(s, "*.")
  }
  
  // WildcardMatches returns true if wildcard is a wildcard domain
  // and it satisfies the given domain.
  func WildcardMatches(wildcard, domain string) bool {
  	if !IsWildcardDomain(wildcard) {
  		return false
  	}
  
  	w := RemoveSubdomains(wildcard, 1)
  	d := RemoveSubdomains(domain, 1)
  	return w == d
  }
  
  // RemoveSubdomains returns the domain without the n left-most subdomain(s).
  func RemoveSubdomains(s string, n int) string {
  	domains := strings.Split(s, ".")
  	return strings.Join(domains[n:], ".")
  }
  
  // ParseSections returns INI style sections from r.
  func ParseSections(r io.Reader) (sections []string, err error) {
  	s := bufio.NewScanner(r)
  
  	for s.Scan() {
  		t := s.Text()
  		if strings.HasPrefix(t, "[") {
  			sections = append(sections, strings.Trim(t, "[]"))
  		}
  	}
  
  	err = s.Err()
  
  	return
  }
  
  // UniqueStrings returns a string slice of unique values.
  func UniqueStrings(s []string) (v []string) {
  	m := make(map[string]struct{})
  	for _, val := range s {
  		_, ok := m[val]
  		if !ok {
  			v = append(v, val)
  			m[val] = struct{}{}
  		}
  	}
  	return
  }
  
  // IsCI returns true if the env looks like it's a CI platform.
  func IsCI() bool {
  	return os.Getenv("CI") == "true"
  }
  
  // EncodeAlias encodes an alias string so that it conforms to the
  // requirement of matching (?!^[0-9]+$)([a-zA-Z0-9-_]+).
  func EncodeAlias(s string) string {
  	return "commit-" + strings.Replace(s, ".", "_", -1)
  }
  
  // DecodeAlias decodes an alias string which was encoded by
  // the EncodeAlias function.
  func DecodeAlias(s string) string {
  	s = strings.Replace(s, "_", ".", -1)
  	s = strings.Replace(s, "commit-", "", 1)
  	return s
  }
  
  // DateSuffix returns the date suffix for t.
  func DateSuffix(t time.Time) string {
  	switch t.Day() {
  	case 1, 21, 31:
  		return "st"
  	case 2, 22:
  		return "nd"
  	case 3, 23:
  		return "rd"
  	default:
  		return "th"
  	}
  }
  
  // StripLerna strips the owner portion of a Lerna-based tag. See #670 for
  // details. They are in the form of "@owner/repo@0.5.0".
  func StripLerna(s string) string {
  	if strings.HasPrefix(s, "@") {
  		p := strings.Split(s, "@")
  		return p[len(p)-1]
  	}
  
  	return s
  }
  
  // FixMultipleSetCookie staggers the casing of each set-cookie
  // value to trick API Gateway into setting multiple in the response.
  func FixMultipleSetCookie(h http.Header) {
  	cookies := h["Set-Cookie"]
  
  	if len(cookies) == 0 {
  		return
  	}
  
  	h.Del("Set-Cookie")
  
  	for i, v := range cookies {
  		h[BinaryCase("set-cookie", i)] = []string{v}
  	}
  }
  
  // BinaryCase ported from https://github.com/Gi60s/binary-case/blob/master/index.js#L86.
  func BinaryCase(s string, n int) string {
  	var res []rune
  
  	for _, c := range s {
  		if c >= 65 && c <= 90 {
  			if n&1 > 0 {
  				c += 32
  			}
  			res = append(res, c)
  			n >>= 1
  		} else if c >= 97 && c <= 122 {
  			if n&1 > 0 {
  				c -= 32
  			}
  			res = append(res, c)
  			n >>= 1
  		} else {
  			res = append(res, c)
  		}
  	}
  
  	return string(res)
  }
  