mirror of
https://github.com/Threnklyn/jira.git
synced 2026-05-18 20:23:28 +02:00
386 lines
9.3 KiB
Go
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
|
|
}
|