Files
jira/util.go
T
2016-08-21 00:45:07 -07:00

386 lines
9.3 KiB
Go

package jira
import (
"bufio"
"bytes"
"encoding/json"
"fmt"
"github.com/mgutz/ansi"
"gopkg.in/coryb/yaml.v2"
"io"
"io/ioutil"
"net/http"
"os"
"path/filepath"
"runtime"
"strings"
"text/template"
"time"
)
func homedir() string {
if runtime.GOOS == "windows" {
return os.Getenv("USERPROFILE")
}
return os.Getenv("HOME")
}
// FindParentPaths will find all available paths from the current path up to the root
// that matches the given fileName path
func FindParentPaths(fileName string) []string {
cwd, _ := os.Getwd()
paths := []string{}
// special case if homedir is not in current path then check there anyway
homedir := homedir()
if !filepath.HasPrefix(cwd, homedir) {
path := filepath.Join(homedir, fileName)
if _, err := os.Stat(path); err == nil {
paths = append(paths, path)
}
}
path := filepath.Join(cwd, fileName)
if _, err := os.Stat(path); err == nil {
paths = append(paths, path)
}
for true {
cwd = filepath.Dir(cwd)
path := filepath.Join(cwd, fileName)
if _, err := os.Stat(path); err == nil {
paths = append(paths, path)
}
if cwd[len(cwd)-1] == filepath.Separator {
break
}
}
return paths
}
// FindClosestParentPath finds the path that matches the given fileName path that is
// closest to the current working directory
func FindClosestParentPath(fileName string) (string, error) {
paths := FindParentPaths(fileName)
if len(paths) > 0 {
return paths[len(paths)-1], nil
}
return "", fmt.Errorf("%s not found in parent directory hierarchy", fileName)
}
func readFile(file string) string {
var bytes []byte
var err error
log.Debugf("readFile: reading %q", file)
if bytes, err = ioutil.ReadFile(file); err != nil {
log.Errorf("Failed to read file %s: %s", file, err)
os.Exit(1)
}
return string(bytes)
}
func copyFile(src, dst string) (err error) {
var s, d *os.File
if s, err = os.Open(src); err == nil {
defer s.Close()
if d, err = os.Create(dst); err == nil {
if _, err = io.Copy(d, s); err != nil {
d.Close()
return
}
return d.Close()
}
}
return
}
func fuzzyAge(start string) (string, error) {
t, err := time.Parse("2006-01-02T15:04:05.000-0700", start)
if err != nil {
return "", err
}
delta := time.Now().Sub(t)
if delta.Minutes() < 2 {
return "a minute", nil
} else if dm := delta.Minutes(); dm < 45 {
return fmt.Sprintf("%d minutes", int(dm)), nil
} else if dm := delta.Minutes(); dm < 90 {
return "an hour", nil
} else if dh := delta.Hours(); dh < 24 {
return fmt.Sprintf("%d hours", int(dh)), nil
} else if dh := delta.Hours(); dh < 48 {
return "a day", nil
}
return fmt.Sprintf("%d days", int(delta.Hours()/24)), nil
}
func dateFormat(format string, content string) (string, error) {
t, err := time.Parse("2006-01-02T15:04:05.000-0700", content)
if err != nil {
return "", err
}
return t.Format(format), nil
}
// RunTemplate will run the give templateContent as a golang text/template
// and pass the provided data to the template execution. It will write
// the output to the provided "out" writer.
func RunTemplate(templateContent string, data interface{}, out io.Writer) error {
return runTemplate(templateContent, data, out)
}
func runTemplate(templateContent string, data interface{}, out io.Writer) error {
if out == nil {
out = os.Stdout
}
funcs := map[string]interface{}{
"toJson": func(content interface{}) (string, error) {
bytes, err := json.MarshalIndent(content, "", " ")
if err != nil {
return "", err
}
return string(bytes), nil
},
"append": func(more string, content interface{}) (string, error) {
switch value := content.(type) {
case string:
return string(append([]byte(content.(string)), []byte(more)...)), nil
case []byte:
return string(append(content.([]byte), []byte(more)...)), nil
default:
return "", fmt.Errorf("Unknown type: %s", value)
}
},
"indent": func(spaces int, content string) string {
indent := make([]rune, spaces+1, spaces+1)
indent[0] = '\n'
for i := 1; i < spaces+1; i++ {
indent[i] = ' '
}
lineSeps := []rune{'\n', '\u0085', '\u2028', '\u2029'}
for _, sep := range lineSeps {
indent[0] = sep
content = strings.Replace(content, string(sep), string(indent), -1)
}
return content
},
"comment": func(content string) string {
lineSeps := []rune{'\n', '\u0085', '\u2028', '\u2029'}
for _, sep := range lineSeps {
content = strings.Replace(content, string(sep), string([]rune{sep, '#', ' '}), -1)
}
return content
},
"color": func(color string) string {
return ansi.ColorCode(color)
},
"split": func(sep string, content string) []string {
return strings.Split(content, sep)
},
"join": func(sep string, content []interface{}) string {
vals := make([]string, len(content))
for i, v := range content {
vals[i] = v.(string)
}
return strings.Join(vals, sep)
},
"abbrev": func(max int, content string) string {
if len(content) > max {
var buffer bytes.Buffer
buffer.WriteString(content[:max-3])
buffer.WriteString("...")
return buffer.String()
}
return content
},
"rep": func(count int, content string) string {
var buffer bytes.Buffer
for i := 0; i < count; i++ {
buffer.WriteString(content)
}
return buffer.String()
},
"age": func(content string) (string, error) {
return fuzzyAge(content)
},
"dateFormat": func(format string, content string) (string, error) {
return dateFormat(format, content)
},
}
tmpl, err := template.New("template").Funcs(funcs).Parse(templateContent)
if err != nil {
log.Errorf("Failed to parse template: %s", err)
return err
}
if err := tmpl.Execute(out, data); err != nil {
log.Errorf("Failed to execute template: %s", err)
return err
}
return nil
}
func responseToJSON(resp *http.Response, err error) (interface{}, error) {
if err != nil {
return nil, err
}
data := jsonDecode(resp.Body)
if resp.StatusCode == 400 {
if val, ok := data.(map[string]interface{})["errorMessages"]; ok {
for _, errMsg := range val.([]interface{}) {
log.Errorf("%s", errMsg)
}
}
}
return data, nil
}
func jsonDecode(io io.Reader) interface{} {
content, err := ioutil.ReadAll(io)
var data interface{}
err = json.Unmarshal(content, &data)
if err != nil {
log.Errorf("JSON Parse Error: %s from %s", err, content)
}
return data
}
func jsonEncode(data interface{}) (string, error) {
buffer := bytes.NewBuffer(make([]byte, 0))
enc := json.NewEncoder(buffer)
err := enc.Encode(data)
if err != nil {
log.Errorf("Failed to encode data %s: %s", data, err)
return "", err
}
return buffer.String(), nil
}
func jsonWrite(file string, data interface{}) {
fh, err := os.OpenFile(file, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600)
defer fh.Close()
if err != nil {
log.Errorf("Failed to open %s: %s", file, err)
os.Exit(1)
}
enc := json.NewEncoder(fh)
enc.Encode(data)
}
func yamlWrite(file string, data interface{}) {
fh, err := os.OpenFile(file, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600)
defer fh.Close()
if err != nil {
log.Errorf("Failed to open %s: %s", file, err)
os.Exit(1)
}
if out, err := yaml.Marshal(data); err != nil {
log.Errorf("Failed to marshal yaml %v: %s", data, err)
os.Exit(1)
} else {
fh.Write(out)
}
}
func promptYN(prompt string, yes bool) bool {
reader := bufio.NewReader(os.Stdin)
if !yes {
prompt = fmt.Sprintf("%s [y/N]: ", prompt)
} else {
prompt = fmt.Sprintf("%s [Y/n]: ", prompt)
}
fmt.Printf("%s", prompt)
text, _ := reader.ReadString('\n')
ans := strings.ToLower(strings.TrimRight(text, "\n"))
if ans == "" {
return yes
}
if strings.HasPrefix(ans, "y") {
return true
}
return false
}
func yamlFixup(data interface{}) (interface{}, error) {
switch d := data.(type) {
case map[interface{}]interface{}:
// need to copy this map into a string map so json can encode it
copy := make(map[string]interface{})
for key, val := range d {
switch k := key.(type) {
case string:
if fixed, err := yamlFixup(val); err != nil {
return nil, err
} else if fixed != nil {
copy[k] = fixed
}
default:
err := fmt.Errorf("YAML: key %s is type '%T', require 'string'", key, k)
log.Errorf("%s", err)
return nil, err
}
}
if len(copy) == 0 {
return nil, nil
}
return copy, nil
case map[string]interface{}:
copy := make(map[string]interface{})
for k, v := range d {
if fixed, err := yamlFixup(v); err != nil {
return nil, err
} else if fixed != nil {
copy[k] = fixed
}
}
if len(copy) == 0 {
return nil, nil
}
return copy, nil
case []interface{}:
copy := make([]interface{}, 0, len(d))
for _, val := range d {
if fixed, err := yamlFixup(val); err != nil {
return nil, err
} else if fixed != nil {
copy = append(copy, fixed)
}
}
if len(copy) == 0 {
return nil, nil
}
return copy, nil
case string:
if d == "" || d == "\n" {
return nil, nil
}
return d, nil
default:
return d, nil
}
}
func mkdir(dir string) error {
if stat, err := os.Stat(dir); err != nil && !os.IsNotExist(err) {
log.Errorf("Failed to stat %s: %s", dir, err)
return err
} else if err == nil && !stat.IsDir() {
err := fmt.Errorf("%s exists and is not a directory", dir)
log.Errorf("%s", err)
return err
} else {
// dir does not exist, so try to create it
if err := os.MkdirAll(dir, 0755); err != nil {
log.Errorf("Failed to mkdir -p %s: %s", dir, err)
return err
}
}
return nil
}