diff --git a/cmd-access.go b/cmd-access.go index e46be3a7..1be010cb 100644 --- a/cmd-access.go +++ b/cmd-access.go @@ -17,6 +17,7 @@ package main import ( + "errors" "fmt" "time" @@ -31,42 +32,51 @@ func runAccessCmd(ctx *cli.Context) { cli.ShowCommandHelpAndExit(ctx, "access", 1) // last argument is exit code } if !isMcConfigExist() { - console.Fatalln("\"mc\" is not configured. Please run \"mc config generate\".") + console.Fatalln(console.ErrorMessage{ + Message: "Please run \"mc config generate\"", + Error: iodine.New(errors.New("\"mc\" is not configured"), nil), + }) } config, err := getMcConfig() if err != nil { - console.Fatalf("loading config file failed with following reason: [%s]\n", iodine.ToError(err)) + console.Fatalln(console.ErrorMessage{ + Message: "loading config file failed", + Error: iodine.New(err, nil), + }) } targetURLConfigMap := make(map[string]*hostConfig) targetURLs, err := getExpandedURLs(ctx.Args(), config.Aliases) if err != nil { - switch e := iodine.ToError(err).(type) { - case errUnsupportedScheme: - console.Fatalf("Unknown type of URL ‘%s’. Reason: %s.\n", e.url, e) - default: - console.Fatalf("reading URLs failed with following Reason: %s\n", e) - } + console.Fatalln(console.ErrorMessage{ + Message: "Unknown type of URL ", + Error: iodine.New(err, nil), + }) } acl := bucketACL(ctx.Args().First()) if !acl.isValidBucketACL() { - console.Fatalf("Access type ‘%s’ is not supported. Valid types are [private, public, readonly].\n", acl) + console.Fatalln(console.ErrorMessage{ + Message: "Valid types are [private, public, readonly].", + Error: iodine.New(errors.New("Invalid ACL Type ‘"+acl.String()+"’"), nil), + }) } targetURLs = targetURLs[1:] // 1 or more target URLs for _, targetURL := range targetURLs { targetConfig, err := getHostConfig(targetURL) if err != nil { - console.Fatalf("Unable to read configuration for host ‘%s’. Reason: %s.\n", targetURL, iodine.ToError(err)) + console.Fatalln(console.ErrorMessage{ + Message: "Unable to read configuration for host " + "‘" + targetURL + "’", + Error: iodine.New(err, nil), + }) } targetURLConfigMap[targetURL] = targetConfig } for targetURL, targetConfig := range targetURLConfigMap { errorMsg, err := doUpdateAccessCmd(targetURL, acl.String(), targetConfig) - err = iodine.New(err, nil) if err != nil { - if errorMsg == "" { - errorMsg = "Empty error message. Please rerun this command with --debug and file a bug report." - } - console.Errorf("%s", errorMsg) + console.Errorln(console.ErrorMessage{ + Message: errorMsg, + Error: iodine.New(err, nil), + }) } } } @@ -76,10 +86,8 @@ func doUpdateAccessCmd(targetURL, targetACL string, targetConfig *hostConfig) (s var clnt client.Client clnt, err = getNewClient(targetURL, targetConfig) if err != nil { - err := iodine.New(err, nil) - msg := fmt.Sprintf("Unable to initialize client for ‘%s’. Reason: %s.\n", - targetURL, iodine.ToError(err)) - return msg, err + msg := fmt.Sprintf("Unable to initialize client for ‘%s’", targetURL) + return msg, iodine.New(err, nil) } return doUpdateAccess(clnt, targetURL, targetACL) } @@ -87,15 +95,14 @@ func doUpdateAccessCmd(targetURL, targetACL string, targetConfig *hostConfig) (s func doUpdateAccess(clnt client.Client, targetURL, targetACL string) (string, error) { err := clnt.SetBucketACL(targetACL) for i := 0; i < globalMaxRetryFlag && err != nil && isValidRetry(err); i++ { - fmt.Println(console.Retry("Retrying ... %d", i)) + console.Retry("Retrying ...", i) // Progressively longer delays time.Sleep(time.Duration(i*i) * time.Second) err = clnt.SetBucketACL(targetACL) } if err != nil { - err := iodine.New(err, nil) - msg := fmt.Sprintf("Failed to add bucket access policy for URL ‘%s’. Reason: %s.\n", targetURL, iodine.ToError(err)) - return msg, err + msg := fmt.Sprintf("Failed to add bucket access policy for URL ‘%s’", targetURL) + return msg, iodine.New(err, nil) } return "", nil } diff --git a/cmd-cat.go b/cmd-cat.go index 58b3e2a5..d2ea4fa1 100644 --- a/cmd-cat.go +++ b/cmd-cat.go @@ -18,6 +18,7 @@ package main import ( "errors" + "fmt" "io" "os" @@ -31,33 +32,42 @@ func runCatCmd(ctx *cli.Context) { cli.ShowCommandHelpAndExit(ctx, "cat", 1) // last argument is exit code } if !isMcConfigExist() { - console.Fatalln("\"mc\" is not configured. Please run \"mc config generate\".") + console.Fatalln(console.ErrorMessage{ + Message: "Please run \"mc config generate\"", + Error: iodine.New(errors.New("\"mc\" is not configured"), nil), + }) } config, err := getMcConfig() if err != nil { - console.Fatalf("Unable to read config file ‘%s’. Reason: %s.\n", mustGetMcConfigPath(), iodine.ToError(err)) + console.Fatalln(console.ErrorMessage{ + Message: fmt.Sprintf("Unable to read config file ‘%s’", mustGetMcConfigPath()), + Error: iodine.New(err, nil), + }) } // Convert arguments to URLs: expand alias, fix format... urls, err := getExpandedURLs(ctx.Args(), config.Aliases) if err != nil { - switch e := iodine.ToError(err).(type) { - case errUnsupportedScheme: - console.Fatalf("Unknown type of URL ‘%s’. Reason: %s.\n", e.url, e) - default: - console.Fatalf("Unable to parse arguments. Reason: %s.\n", iodine.ToError(err)) - } + console.Fatalln(console.ErrorMessage{ + Message: fmt.Sprintf("Unknown type of URL ‘%s’", urls), + Error: iodine.New(err, nil), + }) } sourceURLs := urls sourceURLConfigMap, err := getHostConfigs(sourceURLs) if err != nil { - console.Fatalf("Unable to read host configuration for ‘%s’ from config file ‘%s’. Reason: %s.\n", - sourceURLs, mustGetMcConfigPath(), iodine.ToError(err)) + console.Fatalln(console.ErrorMessage{ + Message: fmt.Sprintf("Unable to read host configuration for ‘%s’ from config file ‘%s’", sourceURLs, mustGetMcConfigPath()), + Error: iodine.New(err, nil), + }) } humanReadable, err := doCatCmd(sourceURLConfigMap) if err != nil { - console.Fatalln(humanReadable) + console.Fatalln(console.ErrorMessage{ + Message: humanReadable, + Error: iodine.New(err, nil), + }) } } diff --git a/cmd-config.go b/cmd-config.go index 8277c5e5..d40652e8 100644 --- a/cmd-config.go +++ b/cmd-config.go @@ -34,13 +34,19 @@ func runConfigCmd(ctx *cli.Context) { arg := ctx.Args().First() tailArgs := ctx.Args().Tail() if len(tailArgs) > 2 { - console.Fatalln("Incorrect number of arguments, please use \"mc config help\"") + console.Fatalln(console.ErrorMessage{ + Message: "Incorrect number of arguments, please use \"mc config help\"", + Error: iodine.New(errInvalidArgument{}, nil), + }) } msg, err := doConfig(arg, tailArgs) if err != nil { - console.Fatalln(msg) + console.Fatalln(console.ErrorMessage{ + Message: msg, + Error: err, + }) } - console.Infoln(msg) + console.Infoln(console.Message(msg)) } // saveConfig writes configuration data in json format to config file. diff --git a/cmd-cp.go b/cmd-cp.go index 18690192..dacc495d 100644 --- a/cmd-cp.go +++ b/cmd-cp.go @@ -17,6 +17,8 @@ package main import ( + "errors" + "fmt" "runtime" "sync" @@ -33,7 +35,7 @@ func doCopy(sourceURL string, sourceConfig *hostConfig, targetURL string, target } switch globalQuietFlag { case true: - console.Infof("‘%s’ -> ‘%s’\n", sourceURL, targetURL) + console.Infoln(console.Message(fmt.Sprintf("‘%s’ -> ‘%s’", sourceURL, targetURL))) default: // set up progress reader = bar.NewProxyReader(reader) @@ -117,13 +119,21 @@ func runCopyCmd(ctx *cli.Context) { if len(ctx.Args()) < 2 || ctx.Args().First() == "help" { cli.ShowCommandHelpAndExit(ctx, "cp", 1) // last argument is exit code } + if !isMcConfigExist() { - console.Fatalln("\"mc\" is not configured. Please run \"mc config generate\".") + console.Fatalln(console.ErrorMessage{ + Message: "Please run \"mc config generate\"", + Error: iodine.New(errors.New("\"mc\" is not configured"), nil), + }) } + // extract URLs. URLs, err := args2URLs(ctx.Args()) if err != nil { - console.Fatalln(iodine.ToError(err)) + console.Fatalln(console.ErrorMessage{ + Message: fmt.Sprintf("Unknown URL types: ‘%s’", URLs), + Error: iodine.New(err, nil), + }) } // Separate source and target. 'cp' can take only one target, @@ -138,7 +148,10 @@ func runCopyCmd(ctx *cli.Context) { } for err := range doCopyCmd(sourceURLs, targetURL, bar) { if err != nil { - console.Errorln(iodine.ToError(err)) + console.Errorln(console.ErrorMessage{ + Message: "Failed with", + Error: iodine.New(err, nil), + }) } } if !globalQuietFlag { diff --git a/cmd-diff.go b/cmd-diff.go index acd16d3f..d01e0e0a 100644 --- a/cmd-diff.go +++ b/cmd-diff.go @@ -17,6 +17,9 @@ package main import ( + "errors" + "fmt" + "github.com/minio/cli" "github.com/minio/mc/pkg/console" "github.com/minio/minio/pkg/iodine" @@ -28,11 +31,17 @@ func runDiffCmd(ctx *cli.Context) { cli.ShowCommandHelpAndExit(ctx, "diff", 1) // last argument is exit code } if !isMcConfigExist() { - console.Fatalln("\"mc\" is not configured. Please run \"mc config generate\".") + console.Fatalln(console.ErrorMessage{ + Message: "Please run \"mc config generate\"", + Error: iodine.New(errors.New("\"mc\" is not configured"), nil), + }) } config, err := getMcConfig() if err != nil { - console.Fatalf("Unable to read config file ‘%s’. Reason: %s.\n", mustGetMcConfigPath(), iodine.ToError(err)) + console.Fatalln(console.ErrorMessage{ + Message: fmt.Sprintf("Unable to read config file ‘%s’", mustGetMcConfigPath()), + Error: err, + }) } firstURL := ctx.Args().First() @@ -42,40 +51,59 @@ func runDiffCmd(ctx *cli.Context) { if err != nil { switch iodine.ToError(err).(type) { case errUnsupportedScheme: - console.Fatalf("Unknown type of URL ‘%s’.\n", firstURL) + console.Fatalln(console.ErrorMessage{ + Message: fmt.Sprintf("Unknown type of URL ‘%s’.", firstURL), + Error: err, + }) default: - console.Fatalf("Unable to parse argument ‘%s’. Reason: %s.\n", firstURL, iodine.ToError(err)) + console.Fatalln(console.ErrorMessage{ + Message: fmt.Sprintf("Unable to parse argument ‘%s’.", firstURL), + Error: err, + }) } } _, err = getHostConfig(firstURL) if err != nil { - console.Fatalf("Unable to read host configuration for ‘%s’ from config file ‘%s’. Reason: %s.\n", - firstURL, mustGetMcConfigPath(), iodine.ToError(err)) + console.Fatalln(console.ErrorMessage{ + Message: fmt.Sprintf("Unable to read host configuration for ‘%s’ from config file ‘%s’.", firstURL, mustGetMcConfigPath()), + Error: err, + }) } _, err = getHostConfig(secondURL) if err != nil { - console.Fatalf("Unable to read host configuration for ‘%s’ from config file ‘%s’. Reason: %s.\n", - secondURL, mustGetMcConfigPath(), iodine.ToError(err)) + console.Fatalln(console.ErrorMessage{ + Message: fmt.Sprintf("Unable to read host configuration for ‘%s’ from config file ‘%s’. Reason: %s.", secondURL, mustGetMcConfigPath()), + Error: err, + }) } secondURL, err = getExpandedURL(secondURL, config.Aliases) if err != nil { switch iodine.ToError(err).(type) { case errUnsupportedScheme: - console.Fatalf("Unknown type of URL ‘%s’. Reason: %s.\n", secondURL, iodine.ToError(err)) + console.Fatalln(console.ErrorMessage{ + Message: fmt.Sprintf("Unknown type of URL ‘%s’.", secondURL), + Error: err, + }) default: - console.Fatalf("Unable to parse argument ‘%s’. Reason: %s.\n", secondURL, iodine.ToError(err)) + console.Fatalln(console.ErrorMessage{ + Message: fmt.Sprintf("Unable to parse argument ‘%s’.", secondURL), + Error: err, + }) } } // TODO recursive is not working yet newFirstURL := stripRecursiveURL(firstURL) for diff := range doDiffCmd(newFirstURL, secondURL, isURLRecursive(firstURL)) { if diff.err != nil { - console.Fatalln(diff.message) + console.Fatalln(console.ErrorMessage{ + Message: diff.message, + Error: diff.err, + }) } - console.Infoln(diff.message) + console.Infoln(console.Message(diff.message)) } } diff --git a/cmd-ls.go b/cmd-ls.go index 8ed1268d..7d59a430 100644 --- a/cmd-ls.go +++ b/cmd-ls.go @@ -17,6 +17,9 @@ package main import ( + "errors" + "fmt" + "github.com/minio/cli" "github.com/minio/mc/pkg/console" "github.com/minio/minio/pkg/iodine" @@ -27,12 +30,20 @@ func runListCmd(ctx *cli.Context) { if !ctx.Args().Present() || ctx.Args().First() == "help" { cli.ShowCommandHelpAndExit(ctx, "ls", 1) // last argument is exit code } + if !isMcConfigExist() { - console.Fatalln("\"mc\" is not configured. Please run \"mc config generate\".") + console.Fatalln(console.ErrorMessage{ + Message: "Please run \"mc config generate\"", + Error: iodine.New(errors.New("\"mc\" is not configured"), nil), + }) } + config, err := getMcConfig() if err != nil { - console.Fatalf("Unable to read config file ‘%s’. Reason: %s.\n", mustGetMcConfigPath(), iodine.ToError(err)) + console.Fatalln(console.ErrorMessage{ + Message: fmt.Sprintf("Unable to read config file ‘%s’", mustGetMcConfigPath()), + Error: iodine.New(err, nil), + }) } targetURLConfigMap := make(map[string]*hostConfig) for _, arg := range ctx.Args() { @@ -40,15 +51,23 @@ func runListCmd(ctx *cli.Context) { if err != nil { switch e := iodine.ToError(err).(type) { case errUnsupportedScheme: - console.Fatalf("Unknown type of URL ‘%s’. Reason: %s.\n", e.url, e) + console.Fatalln(console.ErrorMessage{ + Message: fmt.Sprintf("Unknown type of URL ‘%s’", e.url), + Error: iodine.New(e, nil), + }) default: - console.Fatalf("Unable to parse argument ‘%s’. Reason: %s.\n", arg, iodine.ToError(err)) + console.Fatalln(console.ErrorMessage{ + Message: fmt.Sprintf("Unable to parse argument ‘%s’", arg), + Error: iodine.New(err, nil), + }) } } targetConfig, err := getHostConfig(targetURL) if err != nil { - console.Fatalf("Unable to read host configuration for ‘%s’ from config file ‘%s’. Reason: %s.\n", - targetURL, mustGetMcConfigPath(), iodine.ToError(err)) + console.Fatalln(console.ErrorMessage{ + Message: fmt.Sprintf("Unable to read host configuration for ‘%s’ from config file ‘%s’", targetURL, mustGetMcConfigPath()), + Error: iodine.New(err, nil), + }) } targetURLConfigMap[targetURL] = targetConfig } @@ -56,9 +75,11 @@ func runListCmd(ctx *cli.Context) { // if recursive strip off the "..." newTargetURL := stripRecursiveURL(targetURL) err = doListCmd(newTargetURL, targetConfig, isURLRecursive(targetURL)) - err = iodine.New(err, nil) if err != nil { - console.Fatalf("Failed to list ‘%s’. Reason: %s.\n", targetURL, iodine.ToError(err)) + console.Fatalln(console.ErrorMessage{ + Message: fmt.Sprintf("Failed to list ‘%s’", targetURL), + Error: iodine.New(err, nil), + }) } } } diff --git a/cmd-mb.go b/cmd-mb.go index 666b8534..58cfe7b3 100644 --- a/cmd-mb.go +++ b/cmd-mb.go @@ -17,6 +17,7 @@ package main import ( + "errors" "fmt" "time" @@ -32,37 +33,51 @@ func runMakeBucketCmd(ctx *cli.Context) { cli.ShowCommandHelpAndExit(ctx, "mb", 1) // last argument is exit code } if !isMcConfigExist() { - console.Fatalln("\"mc\" is not configured. Please run \"mc config generate\".") + console.Fatalln(console.ErrorMessage{ + Message: "Please run \"mc config generate\"", + Error: errors.New("\"mc\" is not configured"), + }) } config, err := getMcConfig() if err != nil { - console.Fatalf("Unable to read config file ‘%s’. Reason: %s\n", mustGetMcConfigPath(), iodine.ToError(err)) + console.Fatalln(console.ErrorMessage{ + Message: "Unable to read config file ‘" + mustGetMcConfigPath() + "’", + Error: err, + }) } targetURLConfigMap := make(map[string]*hostConfig) - targetURLs, err := getExpandedURLs(ctx.Args(), config.Aliases) - if err != nil { - switch e := iodine.ToError(err).(type) { - case errUnsupportedScheme: - console.Fatalf("Unknown URL type ‘%s’ passed. Reason: %s.\n", e.url, e) - default: - console.Fatalf("Error in parsing path or URL. Reason: %s.\n", e) + for _, arg := range ctx.Args() { + targetURL, err := getExpandedURL(arg, config.Aliases) + if err != nil { + switch e := iodine.ToError(err).(type) { + case errUnsupportedScheme: + console.Fatalln(console.ErrorMessage{ + Message: fmt.Sprintf("Unknown type of URL ‘%s’", e.url), + Error: e, + }) + default: + console.Fatalln(console.ErrorMessage{ + Message: fmt.Sprintf("Unable to parse argument ‘%s’", arg), + Error: err, + }) + } } - } - for _, targetURL := range targetURLs { targetConfig, err := getHostConfig(targetURL) if err != nil { - console.Fatalf("Unable to read configuration for host ‘%s’. Reason: %s.\n", targetURL, iodine.ToError(err)) + console.Fatalln(console.ErrorMessage{ + Message: fmt.Sprintf("Unable to read host configuration for ‘%s’ from config file ‘%s’", targetURL, mustGetMcConfigPath()), + Error: err, + }) } targetURLConfigMap[targetURL] = targetConfig } for targetURL, targetConfig := range targetURLConfigMap { errorMsg, err := doMakeBucketCmd(targetURL, targetConfig) - err = iodine.New(err, nil) if err != nil { - if errorMsg == "" { - errorMsg = "Empty error message. Please rerun this command with --debug and file a bug report." - } - console.Errorf("%s", errorMsg) + console.Errorln(console.ErrorMessage{ + Message: errorMsg, + Error: err, + }) } } } @@ -73,10 +88,8 @@ func doMakeBucketCmd(targetURL string, targetConfig *hostConfig) (string, error) var clnt client.Client clnt, err = getNewClient(targetURL, targetConfig) if err != nil { - err := iodine.New(err, nil) - msg := fmt.Sprintf("Unable to initialize client for ‘%s’. Reason: %s.\n", - targetURL, iodine.ToError(err)) - return msg, err + msg := fmt.Sprintf("Unable to initialize client for ‘%s’", targetURL) + return msg, iodine.New(err, nil) } return doMakeBucket(clnt, targetURL) } @@ -85,15 +98,14 @@ func doMakeBucketCmd(targetURL string, targetConfig *hostConfig) (string, error) func doMakeBucket(clnt client.Client, targetURL string) (string, error) { err := clnt.MakeBucket() for i := 0; i < globalMaxRetryFlag && err != nil && isValidRetry(err); i++ { - fmt.Println(console.Retry("Retrying ... %d", i)) + console.Retry("Retrying ...", i) // Progressively longer delays time.Sleep(time.Duration(i*i) * time.Second) err = clnt.MakeBucket() } if err != nil { - err := iodine.New(err, nil) - msg := fmt.Sprintf("Failed to create bucket for URL ‘%s’. Reason: %s.\n", targetURL, iodine.ToError(err)) - return msg, err + msg := fmt.Sprintf("Failed to create bucket for URL ‘%s’", targetURL) + return msg, iodine.New(err, nil) } return "", nil } diff --git a/cmd-sync.go b/cmd-sync.go index 006eea64..e7d5714c 100644 --- a/cmd-sync.go +++ b/cmd-sync.go @@ -17,6 +17,8 @@ package main import ( + "errors" + "fmt" "runtime" "sync" @@ -29,12 +31,20 @@ func runSyncCmd(ctx *cli.Context) { if len(ctx.Args()) < 2 || ctx.Args().First() == "help" { cli.ShowCommandHelpAndExit(ctx, "sync", 1) // last argument is exit code } + if !isMcConfigExist() { - console.Fatalln("\"mc\" is not configured. Please run \"mc config generate\".") + console.Fatalln(console.ErrorMessage{ + Message: "Please run \"mc config generate\"", + Error: iodine.New(errors.New("\"mc\" is not configured"), nil), + }) } + URLs, err := args2URLs(ctx.Args()) if err != nil { - console.Fatalln(iodine.ToError(err)) + console.Fatalln(console.ErrorMessage{ + Message: fmt.Sprintf("Unknown URL types found: ‘%s’", URLs), + Error: iodine.New(err, nil), + }) } // Separate source and target. 'sync' can take only one source. @@ -66,7 +76,10 @@ func runSyncCmd(ctx *cli.Context) { for syncURLs := range prepareSyncURLs(sourceURL, targetURLs) { if syncURLs.Error != nil { - console.Errorln(iodine.ToError(syncURLs.Error)) + console.Errorln(console.ErrorMessage{ + Message: "Failed with", + Error: iodine.New(syncURLs.Error, nil), + }) continue } syncQueue <- true @@ -75,16 +88,25 @@ func runSyncCmd(ctx *cli.Context) { defer wg.Done() srcConfig, err := getHostConfig(syncURLs.SourceContent.Name) if err != nil { - console.Errorln(iodine.ToError(err)) + console.Fatalln(console.ErrorMessage{ + Message: "Failed with", + Error: iodine.New(err, nil), + }) return } tgtConfig, err := getHostConfig(syncURLs.TargetContent.Name) if err != nil { - console.Errorln(iodine.ToError(err)) + console.Fatalln(console.ErrorMessage{ + Message: "Failed with", + Error: iodine.New(err, nil), + }) return } if err := doCopy(syncURLs.SourceContent.Name, srcConfig, syncURLs.TargetContent.Name, tgtConfig, bar); err != nil { - console.Errorln(iodine.ToError(err)) + console.Errorln(console.ErrorMessage{ + Message: "Failed with", + Error: iodine.New(err, nil), + }) } <-syncQueue }(syncURLs, &bar) diff --git a/cmd-update.go b/cmd-update.go index d91f7a47..e0ed84da 100644 --- a/cmd-update.go +++ b/cmd-update.go @@ -17,6 +17,8 @@ package main import ( + "errors" + "fmt" "runtime" "time" @@ -56,17 +58,24 @@ func runUpdateCmd(ctx *cli.Context) { cli.ShowCommandHelpAndExit(ctx, "update", 1) // last argument is exit code } if !isMcConfigExist() { - console.Fatalln("\"mc\" is not configured. Please run \"mc config generate\".") + console.Fatalln(console.ErrorMessage{ + Message: "Please run \"mc config generate\"", + Error: iodine.New(errors.New("\"mc\" is not configured"), nil), + }) } hostConfig, err := getHostConfig(mcUpdateURL) if err != nil { - console.Fatalf("Unable to read configuration for host ‘%s’. Reason: %s.\n", mcUpdateURL, iodine.ToError(err)) + console.Fatalln(console.ErrorMessage{ + Message: fmt.Sprintf("Unable to read configuration for host ‘%s’", mcUpdateURL), + Error: iodine.New(err, nil), + }) } msg, err := doUpdateCheck(hostConfig) if err != nil { - console.Fatalln(msg) - } - if msg != "" { - console.Infoln(msg) + console.Fatalln(console.ErrorMessage{ + Message: msg, + Error: iodine.New(err, nil), + }) } + console.Infoln(console.Message(msg)) } diff --git a/config.go b/config.go index c7646966..2e4ea830 100644 --- a/config.go +++ b/config.go @@ -125,7 +125,7 @@ func getMcConfig() (*configV1, error) { } -// isMcConfigExist returns true/false if config exists +// isMcConfigExist returns err if config doesn't exist func isMcConfigExist() bool { configFile, err := getMcConfigPath() if err != nil { diff --git a/ls.go b/ls.go index 9a688f52..b5ca9c19 100644 --- a/ls.go +++ b/ls.go @@ -17,11 +17,7 @@ package main import ( - "encoding/json" - "fmt" - "os" "strings" - "time" "github.com/dustin/go-humanize" "github.com/minio/mc/pkg/client" @@ -36,49 +32,22 @@ const ( printDate = "2006-01-02 15:04:05 MST" ) -// printJSON rather than colored output -func printJSON(content *client.Content) { - type jsonContent struct { - Filetype string `json:"content-type"` - Date string `json:"last-modified"` - Size string `json:"size"` - Name string `json:"name"` - } - contentJSON := new(jsonContent) - contentJSON.Date = content.Time.Local().Format(printDate) - contentJSON.Size = humanize.IBytes(uint64(content.Size)) - contentJSON.Filetype = func() string { - if content.Type.IsDir() { +// printContent prints content meta-data +func printContent(c *client.Content) { + content := console.Content{} + content.Filetype = func() string { + if c.Type.IsDir() { return "inode/directory" } - if content.Type.IsRegular() { + if c.Type.IsRegular() { return "application/octet-stream" } return "application/octet-stream" }() - contentJSON.Name = content.Name - contentBytes, _ := json.MarshalIndent(contentJSON, "", "\t") - fmt.Println(string(contentBytes)) -} - -// printContent prints content meta-data -func printContent(date time.Time, size int64, name string, fileType os.FileMode) { - fmt.Printf(console.Time("[%s] ", date.Local().Format(printDate))) - fmt.Printf(console.Size("%6s ", humanize.IBytes(uint64(size)))) - - // just making it explicit - switch { - case fileType.IsDir() == true: - // if one finds a prior suffix no need to append a new one - switch { - case strings.HasSuffix(name, "/") == true: - fmt.Println(console.Dir("%s", name)) - default: - fmt.Println(console.Dir("%s/", name)) - } - default: - fmt.Println(console.File("%s", name)) - } + content.Size = humanize.IBytes(uint64(c.Size)) + content.Name = strings.TrimSuffix(c.Name, "/") + content.Time = c.Time.Local().Format(printDate) + console.ContentInfo(content) } // doList - list all entities inside a folder @@ -97,12 +66,8 @@ func doList(clnt client.Client, targetURL string, recursive bool) error { // To be consistent we have to filter them out contentName = strings.TrimPrefix(contentName, strings.TrimSuffix(targetURL, "/")+"/") } - switch { - case globalJSONFlag == true: - printJSON(contentCh.Content) - default: - printContent(contentCh.Content.Time, contentCh.Content.Size, contentName, contentCh.Content.Type) - } + contentCh.Content.Name = contentName + printContent(contentCh.Content) } if err != nil { return iodine.New(err, map[string]string{"Target": targetURL}) diff --git a/main.go b/main.go index 994c82bb..17556a0d 100644 --- a/main.go +++ b/main.go @@ -17,6 +17,7 @@ package main import ( + "errors" "fmt" "os" "os/user" @@ -34,7 +35,10 @@ import ( func checkConfig() { _, err := user.Current() if err != nil { - console.Fatalln("Unable to determine current user") + console.Fatalln(console.ErrorMessage{ + Message: "Unable to determine current user", + Error: err, + }) } // If config doesn't exist, do not attempt to read it @@ -45,7 +49,10 @@ func checkConfig() { // Ensures config file is sane _, err = getMcConfig() if err != nil { - console.Fatalf("Unable to read config file: %s\n", mustGetMcConfigPath()) + console.Fatalln(console.ErrorMessage{ + Message: fmt.Sprintf("Unable to read config file: %s", mustGetMcConfigPath()), + Error: err, + }) } } @@ -107,14 +114,23 @@ func main() { app.ExtraInfo = getSystemData() console.NoDebugPrint = false } + if globalJSONFlag { + console.NoJSONPrint = false + } themeName := ctx.GlobalString("theme") switch { case console.IsValidTheme(themeName) != true: - console.Fatalf("Theme ‘%s’ is not supported. Please choose from this list: %s.\n", themeName, console.GetThemeNames()) + console.Fatalln(console.ErrorMessage{ + Message: fmt.Sprintf("Please choose from this list: %s.", console.GetThemeNames()), + Error: fmt.Errorf("Theme ‘%s’ is not supported.", themeName), + }) default: err := console.SetTheme(themeName) if err != nil { - console.Fatalf("Failed to set theme ‘%s’. Reason: %s.\n", themeName, iodine.ToError(err)) + console.Fatalln(console.ErrorMessage{ + Message: fmt.Sprintf("Failed to set theme ‘%s’.", themeName), + Error: err, + }) } } checkConfig() @@ -122,7 +138,10 @@ func main() { } app.After = func(ctx *cli.Context) error { if !isMcConfigExist() { - console.Fatalln("\"mc\" is not configured. Please run \"mc config generate\".") + console.Fatalln(console.ErrorMessage{ + Message: "Please run \"mc config generate\"", + Error: iodine.New(errors.New("\"mc\" is not configured"), nil), + }) } return nil } diff --git a/pkg/client/s3/s3.go b/pkg/client/s3/s3.go index 8e264659..24879ca4 100644 --- a/pkg/client/s3/s3.go +++ b/pkg/client/s3/s3.go @@ -247,7 +247,7 @@ func (c *s3Client) listInRoutine(contentCh chan client.ContentOnChannel) { content := new(client.Content) content.Name = object.Data.Key switch { - case object.Data.Size == 0: + case strings.HasSuffix(object.Data.Key, "/"): content.Time = time.Now() content.Type = os.ModeDir default: diff --git a/pkg/client/s3/trace.go b/pkg/client/s3/trace.go index 2b5c9e5b..5beb98d8 100644 --- a/pkg/client/s3/trace.go +++ b/pkg/client/s3/trace.go @@ -75,9 +75,10 @@ func (t Trace) Response(res *http.Response) (err error) { // print HTTP Response func (t Trace) print(data []byte) { - if t.Writer != nil { + switch { + case t.Writer != nil: fmt.Fprintf(t.Writer, "%s", data) - } else { - console.Debugf("%s", data) + default: + console.Debugln(string(data)) } } diff --git a/pkg/console/console.go b/pkg/console/console.go index 2ad0bf4c..a0cf56b7 100644 --- a/pkg/console/console.go +++ b/pkg/console/console.go @@ -17,6 +17,7 @@ package console import ( + "encoding/json" "fmt" "os" "sync" @@ -31,6 +32,40 @@ import ( // NoDebugPrint defines if the input should be printed in debug or not. By default it's set to true. var NoDebugPrint = true +// NoJsonPrint defines if the input should be printed in json formatted or not. By default it's set to true. +var NoJSONPrint = true + +// Message info string +type Message string + +// ErrorMessage error message structure +type ErrorMessage struct { + Message string `json:"Message"` + Error error `json:"Reason"` +} + +// Content content message structure +type Content struct { + Filetype string `json:"ContentType"` + Time string `json:"LastModified"` + Size string `json:"Size"` + Name string `json:"Name"` +} + +// Theme holds console color scheme +type Theme struct { + Fatal *color.Color + Error *color.Color + Info *color.Color + Debug *color.Color + Size *color.Color + Time *color.Color + File *color.Color + Dir *color.Color + Retry *color.Color + JSON *color.Color +} + var ( mutex = &sync.RWMutex{} @@ -54,78 +89,134 @@ var ( return theme }() - // Print prints a error message and exits + // Print prints a message Print = themesDB[currThemeName].Info.Print - // Println prints a error message with a new line and exits - Println = themesDB[currThemeName].Info.Println - // Printf prints a error message with formatting and exits - Printf = themesDB[currThemeName].Info.Printf // Fatal prints a error message and exits - Fatal = func(a ...interface{}) { print(themesDB[currThemeName].Error, a...); os.Exit(1) } + Fatal = func(msg ErrorMessage) { + defer os.Exit(1) + if msg.Error != nil { + if NoJSONPrint { + reason := "Reason: " + iodine.ToError(msg.Error).Error() + message := msg.Message + ", " + reason + print(themesDB[currThemeName].Error, message) + if !NoDebugPrint { + print(themesDB[currThemeName].Error, msg.Error) + } + return + } + errorMessageBytes, _ := json.Marshal(&msg) + print(themesDB[currThemeName].JSON, string(errorMessageBytes)) + } + } // Fatalln prints a error message with a new line and exits - Fatalln = func(a ...interface{}) { println(themesDB[currThemeName].Error, a...); os.Exit(1) } - // Fatalf prints a error message with formatting and exits - Fatalf = func(f string, a ...interface{}) { printf(themesDB[currThemeName].Error, f, a...); os.Exit(1) } + Fatalln = func(msg ErrorMessage) { + defer os.Exit(1) + if msg.Error != nil { + if NoJSONPrint { + reason := "Reason: " + iodine.ToError(msg.Error).Error() + message := msg.Message + ", " + reason + println(themesDB[currThemeName].Error, message) + if !NoDebugPrint { + println(themesDB[currThemeName].Error, msg.Error) + } + return + } + errorMessageBytes, _ := json.Marshal(&msg) + println(themesDB[currThemeName].JSON, string(errorMessageBytes)) + } + } // Error prints a error message - Error = func(a ...interface{}) { print(themesDB[currThemeName].Error, a...) } + Error = func(msg ErrorMessage) { + if msg.Error != nil { + if NoJSONPrint { + reason := "Reason: " + iodine.ToError(msg.Error).Error() + message := msg.Message + ", " + reason + print(themesDB[currThemeName].Error, message) + if !NoDebugPrint { + print(themesDB[currThemeName].Error, msg.Error) + } + return + } + errorMessageBytes, _ := json.Marshal(&msg) + print(themesDB[currThemeName].JSON, string(errorMessageBytes)) + } + } // Errorln prints a error message with a new line - Errorln = func(a ...interface{}) { println(themesDB[currThemeName].Error, a...) } - // Errorf prints a error message with formatting - Errorf = func(f string, a ...interface{}) { printf(themesDB[currThemeName].Error, f, a...) } + Errorln = func(msg ErrorMessage) { + if msg.Error != nil { + if NoJSONPrint { + reason := "Reason: " + iodine.ToError(msg.Error).Error() + message := msg.Message + ", " + reason + println(themesDB[currThemeName].Error, message) + if !NoDebugPrint { + println(themesDB[currThemeName].Error, msg.Error) + } + return + } + errorMessageBytes, _ := json.Marshal(&msg) + println(themesDB[currThemeName].JSON, string(errorMessageBytes)) + } + } // Info prints a informational message - Info = func(a ...interface{}) { print(themesDB[currThemeName].Info, a...) } - // Infoln prints a informational message with a new line - Infoln = func(a ...interface{}) { println(themesDB[currThemeName].Info, a...) } - // Infof prints a informational message with formatting - Infof = func(f string, a ...interface{}) { printf(themesDB[currThemeName].Info, f, a...) } + Info = func(msg Message) { + if NoJSONPrint { + print(themesDB[currThemeName].Info, msg) + return + } + infoBytes, _ := json.Marshal(&msg) + print(themesDB[currThemeName].JSON, string(infoBytes)) + } + // Infoln prints a informational message with a new line + Infoln = func(msg Message) { + if NoJSONPrint { + println(themesDB[currThemeName].Info, msg) + return + } + infoBytes, _ := json.Marshal(&msg) + println(themesDB[currThemeName].JSON, string(infoBytes)) + } + + // Debug prints a debug message without a new line // Debug prints a debug message Debug = func(a ...interface{}) { if !NoDebugPrint { print(themesDB[currThemeName].Debug, a...) } } + // Debugln prints a debug message with a new line Debugln = func(a ...interface{}) { if !NoDebugPrint { println(themesDB[currThemeName].Debug, a...) } } - // Debugf prints a debug message with formatting - Debugf = func(f string, a ...interface{}) { - if !NoDebugPrint { - printf(themesDB[currThemeName].Debug, f, a...) + // ContentInfo prints a structure Content + ContentInfo = func(c Content) { + if NoJSONPrint { + print(themesDB[currThemeName].Time, c.Time) + print(themesDB[currThemeName].Size, c.Size) + switch c.Filetype { + case "inode/directory": + println(themesDB[currThemeName].Dir, c.Name) + case "application/octet-stream": + println(themesDB[currThemeName].File, c.Name) + } + return } + contentBytes, _ := json.Marshal(&c) + println(themesDB[currThemeName].JSON, string(contentBytes)) } - // File - File("foo.txt") - File = themesDB[currThemeName].File.SprintfFunc() - // Dir - Dir("dir/") - Dir = themesDB[currThemeName].Dir.SprintfFunc() - // Size - Size("12GB") - Size = themesDB[currThemeName].Size.SprintfFunc() - // Time - Time("12 Hours") - Time = themesDB[currThemeName].Time.SprintfFunc() - // Retry - Retry message number - Retry = themesDB[currThemeName].Retry.SprintfFunc() + // Retry prints a retry message + Retry = func(a ...interface{}) { + println(themesDB[currThemeName].Retry, a...) + } ) -// Theme holds console color scheme -type Theme struct { - Fatal *color.Color - Error *color.Color - Info *color.Color - Debug *color.Color - Size *color.Color - Time *color.Color - File *color.Color - Dir *color.Color - Retry *color.Color -} - var ( // wrap around standard fmt functions // print prints a message prefixed with message type and program name @@ -155,11 +246,24 @@ var ( c.Print(a...) color.Output = output mutex.Unlock() - default: + case themesDB[currThemeName].Info: mutex.Lock() c.Print(ProgramName() + ": ") c.Print(a...) mutex.Unlock() + // special cases only for Content where it requires custom formatting + case themesDB[currThemeName].Time: + mutex.Lock() + c.Print(fmt.Sprintf("[%s]", a...)) + mutex.Unlock() + case themesDB[currThemeName].Size: + mutex.Lock() + c.Printf(fmt.Sprintf("%6s ", a...)) + mutex.Unlock() + default: + mutex.Lock() + c.Print(a...) + mutex.Unlock() } } @@ -190,45 +294,23 @@ var ( c.Println(a...) color.Output = output mutex.Unlock() - default: + case themesDB[currThemeName].Info: mutex.Lock() c.Print(ProgramName() + ": ") c.Println(a...) mutex.Unlock() - } - } - - // printf - same as print, but takes a format specifier - printf = func(c *color.Color, f string, a ...interface{}) { - switch c { - case themesDB[currThemeName].Debug: + case themesDB[currThemeName].Dir: mutex.Lock() - output := color.Output - color.Output = stderrColoredOutput - c.Print(ProgramName() + ": ") - c.Printf(f, a...) - color.Output = output + // ugly but its needed + c.Printf("%s/\n", a...) mutex.Unlock() - case themesDB[currThemeName].Fatal: + case themesDB[currThemeName].File: mutex.Lock() - output := color.Output - color.Output = stderrColoredOutput - c.Print(ProgramName() + ": ") - c.Printf(f, a...) - color.Output = output - mutex.Unlock() - case themesDB[currThemeName].Error: - mutex.Lock() - output := color.Output - color.Output = stderrColoredOutput - c.Print(ProgramName() + ": ") - c.Printf(f, a...) - color.Output = output + c.Println(a...) mutex.Unlock() default: mutex.Lock() - c.Print(ProgramName() + ": ") - c.Printf(f, a...) + c.Println(a...) mutex.Unlock() } } @@ -262,46 +344,6 @@ func SetTheme(themeName string) error { } currThemeName = themeName - theme := themesDB[currThemeName] - - // Error prints a error message - Error = func(a ...interface{}) { print(theme.Error, a...) } - // Errorln prints a error message with a new line - Errorln = func(a ...interface{}) { println(theme.Error, a...) } - // Errorf prints a error message with formatting - Errorf = func(f string, a ...interface{}) { printf(theme.Error, f, a...) } - - // Info prints a informational message - Info = func(a ...interface{}) { print(theme.Info, a...) } - // Infoln prints a informational message with a new line - Infoln = func(a ...interface{}) { println(theme.Info, a...) } - // Infof prints a informational message with formatting - Infof = func(f string, a ...interface{}) { printf(theme.Info, f, a...) } - - // Debug prints a debug message - Debug = func(a ...interface{}) { - if !NoDebugPrint { - print(theme.Debug, a...) - } - } - // Debugln prints a debug message with a new line - Debugln = func(a ...interface{}) { - if !NoDebugPrint { - println(theme.Debug, a...) - } - } - // Debugf prints a debug message with formatting - Debugf = func(f string, a ...interface{}) { - if !NoDebugPrint { - printf(theme.Debug, f, a...) - } - } - - Dir = theme.Dir.SprintfFunc() - File = theme.File.SprintfFunc() - Size = theme.Size.SprintfFunc() - Time = theme.Time.SprintfFunc() - Retry = theme.Retry.SprintfFunc() mutex.Unlock() diff --git a/pkg/console/themes.go b/pkg/console/themes.go index 788a8859..d218f3e5 100644 --- a/pkg/console/themes.go +++ b/pkg/console/themes.go @@ -29,6 +29,7 @@ var MiniTheme = Theme{ Size: (color.New(color.FgYellow)), Time: (color.New(color.FgGreen)), Retry: (color.New(color.FgMagenta, color.Bold)), + JSON: (color.New(color.FgWhite, color.Italic)), } // WhiteTheme - All white color theme @@ -42,6 +43,7 @@ var WhiteTheme = Theme{ Size: (color.New(color.FgWhite, color.Bold)), Time: (color.New(color.FgWhite, color.Bold)), Retry: (color.New(color.FgWhite, color.Bold)), + JSON: (color.New(color.FgWhite, color.Bold, color.Italic)), } // NoColorTheme - Disables color theme @@ -55,4 +57,5 @@ var NoColorTheme = Theme{ Size: (color.New()), Time: (color.New()), Retry: (color.New()), + JSON: (color.New()), }