219 lines
5.2 KiB
Go
219 lines
5.2 KiB
Go
package cmdutil
|
|
|
|
import (
|
|
"fmt"
|
|
"io"
|
|
"os"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/briandowns/spinner"
|
|
"github.com/fatih/color"
|
|
"github.com/mitchellh/go-homedir"
|
|
"github.com/spf13/viper"
|
|
"golang.org/x/term"
|
|
|
|
"github.com/ankitpokhrel/jira-cli/pkg/browser"
|
|
"github.com/ankitpokhrel/jira-cli/pkg/jira"
|
|
"github.com/ankitpokhrel/jira-cli/pkg/tui"
|
|
)
|
|
|
|
// ExitIfError exists with error message if err is not nil.
|
|
func ExitIfError(err error) {
|
|
if err == nil {
|
|
return
|
|
}
|
|
|
|
var msg string
|
|
|
|
if e, ok := err.(*jira.ErrUnexpectedResponse); ok {
|
|
dm := fmt.Sprintf(
|
|
"\njira: Received unexpected response '%s'.\nPlease check the parameters you supplied and try again.",
|
|
e.Status,
|
|
)
|
|
bd := e.Error()
|
|
|
|
msg = dm
|
|
if len(bd) > 0 {
|
|
msg = fmt.Sprintf("%s%s", bd, dm)
|
|
}
|
|
} else if e, ok := err.(*jira.ErrMultipleFailed); ok {
|
|
msg = fmt.Sprintf("\n%s%s", "SOME REQUESTS REPORTED ERROR:", e.Error())
|
|
} else {
|
|
switch err {
|
|
case jira.ErrEmptyResponse:
|
|
msg = "jira: Received empty response.\nPlease try again."
|
|
default:
|
|
msg = fmt.Sprintf("Error: %s", err.Error())
|
|
}
|
|
}
|
|
|
|
fmt.Fprintf(os.Stderr, "%s\n", msg)
|
|
os.Exit(1)
|
|
}
|
|
|
|
// Info displays spinner.
|
|
func Info(msg string) *spinner.Spinner {
|
|
const refreshRate = 100 * time.Millisecond
|
|
|
|
s := spinner.New(
|
|
spinner.CharSets[14],
|
|
refreshRate,
|
|
spinner.WithSuffix(" "+msg),
|
|
spinner.WithHiddenCursor(true),
|
|
spinner.WithWriter(color.Error),
|
|
)
|
|
s.Start()
|
|
|
|
return s
|
|
}
|
|
|
|
// Success prints success message in stdout.
|
|
func Success(msg string, args ...interface{}) {
|
|
fmt.Fprintf(os.Stdout, fmt.Sprintf("\n\u001B[0;32m✓\u001B[0m %s\n", msg), args...)
|
|
}
|
|
|
|
// Warn prints warning message in stderr.
|
|
func Warn(msg string, args ...interface{}) {
|
|
fmt.Fprintf(os.Stderr, fmt.Sprintf("\u001B[0;33m%s\u001B[0m\n", msg), args...)
|
|
}
|
|
|
|
// Fail prints failure message in stderr.
|
|
func Fail(msg string, args ...interface{}) {
|
|
fmt.Fprintf(os.Stderr, fmt.Sprintf("\u001B[0;31m✗\u001B[0m %s\n", msg), args...)
|
|
}
|
|
|
|
// Failed prints failure message in stderr and exits.
|
|
func Failed(msg string, args ...interface{}) {
|
|
Fail(msg, args...)
|
|
os.Exit(1)
|
|
}
|
|
|
|
// Navigate navigates to jira issue.
|
|
func Navigate(server, path string) error {
|
|
url := GenerateServerBrowseURL(server, path)
|
|
return browser.Browse(url)
|
|
}
|
|
|
|
// GenerateServerBrowseURL will return the `browse` URL for a given key.
|
|
// The server section can be overridden via `browse_server` in config.
|
|
// This is useful if your API endpoint is separate from the web client endpoint.
|
|
func GenerateServerBrowseURL(server, key string) string {
|
|
if viper.GetString("browse_server") != "" {
|
|
server = viper.GetString("browse_server")
|
|
}
|
|
return fmt.Sprintf("%s/browse/%s", server, key)
|
|
}
|
|
|
|
// FormatDateTimeHuman formats date time in human readable format.
|
|
func FormatDateTimeHuman(dt, format string) string {
|
|
t, err := time.Parse(format, dt)
|
|
if err != nil {
|
|
return dt
|
|
}
|
|
return t.Format("Mon, 02 Jan 06")
|
|
}
|
|
|
|
// GetConfigHome returns the config home directory.
|
|
func GetConfigHome() (string, error) {
|
|
home := os.Getenv("XDG_CONFIG_HOME")
|
|
if home != "" {
|
|
return home, nil
|
|
}
|
|
home, err := homedir.Dir()
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
return home + "/.config", nil
|
|
}
|
|
|
|
// StdinHasData checks if standard input has any data to be processed.
|
|
func StdinHasData() bool {
|
|
return !term.IsTerminal(int(os.Stdin.Fd()))
|
|
}
|
|
|
|
// ReadFile reads contents of the given file.
|
|
func ReadFile(filePath string) ([]byte, error) {
|
|
if filePath != "-" && filePath != "" {
|
|
return os.ReadFile(filePath)
|
|
}
|
|
if filePath == "-" || StdinHasData() {
|
|
b, err := io.ReadAll(os.Stdin)
|
|
_ = os.Stdin.Close()
|
|
return b, err
|
|
}
|
|
return []byte(""), nil
|
|
}
|
|
|
|
// GetJiraIssueKey constructs actual issue key based on given key.
|
|
func GetJiraIssueKey(project, key string) string {
|
|
if project == "" {
|
|
return key
|
|
}
|
|
if _, err := strconv.Atoi(key); err != nil {
|
|
return strings.ToUpper(key)
|
|
}
|
|
return fmt.Sprintf("%s-%s", project, key)
|
|
}
|
|
|
|
// NormalizeJiraError normalizes error message we receive from jira.
|
|
func NormalizeJiraError(msg string) string {
|
|
msg = strings.TrimSpace(strings.Replace(msg, "Error:\n", "", 1))
|
|
msg = strings.Replace(msg, "- ", "", 1)
|
|
|
|
return msg
|
|
}
|
|
|
|
// GetSubtaskHandle fetches actual subtask handle.
|
|
// This value can either be handle or name based
|
|
// on the used jira version.
|
|
func GetSubtaskHandle(issueType string, issueTypes []*jira.IssueType) string {
|
|
get := func(it *jira.IssueType) string {
|
|
if it.Handle != "" {
|
|
return it.Handle
|
|
}
|
|
return it.Name
|
|
}
|
|
|
|
var fallback string
|
|
|
|
for _, it := range issueTypes {
|
|
if it.Subtask {
|
|
// Exact matches return immediately.
|
|
if strings.EqualFold(issueType, it.Name) {
|
|
return get(it)
|
|
}
|
|
|
|
// Store the first subtask type as backup.
|
|
if fallback == "" {
|
|
fallback = get(it)
|
|
}
|
|
}
|
|
}
|
|
|
|
// Set default for fallback if none found
|
|
if strings.EqualFold(issueType, jira.IssueTypeSubTask) && fallback == "" {
|
|
fallback = jira.IssueTypeSubTask
|
|
}
|
|
|
|
return fallback
|
|
}
|
|
|
|
// GetTUIStyleConfig returns the custom style configured by the user.
|
|
func GetTUIStyleConfig() tui.TableStyle {
|
|
var bold bool
|
|
|
|
if !viper.IsSet("tui.selection.bold") {
|
|
bold = true
|
|
} else {
|
|
bold = viper.GetBool("tui.selection.bold")
|
|
}
|
|
|
|
return tui.TableStyle{
|
|
SelectionBackground: viper.GetString("tui.selection.background"),
|
|
SelectionForeground: viper.GetString("tui.selection.foreground"),
|
|
SelectionTextIsBold: bold,
|
|
}
|
|
}
|