Skip to content

Commit

Permalink
Merge pull request #99 from nerdalize/feature/output-fmt
Browse files Browse the repository at this point in the history
Outputting overhaul
  • Loading branch information
advdv committed Jun 7, 2017
2 parents 41b2a54 + 83bbbae commit 57a5cd4
Show file tree
Hide file tree
Showing 258 changed files with 30,530 additions and 8,795 deletions.
5 changes: 4 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,10 @@ The structure of the config and the defaults are show below:
"oauth_localserver": "localhost:9876", #address of local oauth server
"oauth_success_url": "https://cloud.nerdalize.com" #redirect URL after successful login
},
"enable_logging": false, # When set to true, all output will be logged to ~/.nerd/log
"logging": { # write all command output to a log file
"enabled": false,
"file_location": "~/.nerd/log"
},
"nerd_api_endpoint": "https://batch.nerdalize.com/v1/" # URL of nerdalize API (NCE)
}
```
Expand Down
105 changes: 68 additions & 37 deletions command/command.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,16 @@ package command
import (
"bytes"
"fmt"
"log"
"os"
"path/filepath"

"github.com/pkg/errors"

"github.com/Sirupsen/logrus"
"github.com/jessevdk/go-flags"
"github.com/mitchellh/cli"
homedir "github.com/mitchellh/go-homedir"
"github.com/nerdalize/nerd/command/format"
"github.com/nerdalize/nerd/nerd/conf"
)

Expand All @@ -32,6 +34,7 @@ func newCommand(title, synopsis, help string, opts interface{}) (*command, error
Reader: os.Stdin,
Writer: os.Stderr,
},
outputter: format.NewOutputter(os.Stdout, os.Stderr, log.New(os.Stderr, "", 0)),
}
if opts != nil {
_, err := cmd.parser.AddGroup("options", "options", opts)
Expand All @@ -43,8 +46,8 @@ func newCommand(title, synopsis, help string, opts interface{}) (*command, error
ConfigFile: cmd.setConfig,
SessionFile: cmd.setSession,
OutputOpts: OutputOpts{
VerboseOutput: setVerbose,
JSONOutput: cmd.setJSON,
Output: cmd.setOutput,
Debug: cmd.setDebug,
},
}
_, err := cmd.parser.AddGroup("output options", "output options", confOpts)
Expand All @@ -56,14 +59,14 @@ func newCommand(title, synopsis, help string, opts interface{}) (*command, error

//command is an abstract implementation for embedding in concrete commands and allows basic command functionality to be reused.
type command struct {
help string //extended help message, show when --help a command
synopsis string //short help message, shown on the command overview
parser *flags.Parser //option parser that will be used when parsing args
ui cli.Ui
config *conf.Config
jsonOutput bool
session *conf.Session
runFunc func(args []string) error
help string //extended help message, show when --help a command
synopsis string //short help message, shown on the command overview
parser *flags.Parser //option parser that will be used when parsing args
ui cli.Ui
config *conf.Config
outputter *format.Outputter
session *conf.Session
runFunc func(args []string) error
}

//Will write help text for when a user uses --help, it automatically renders all option groups of the flags.Parser (augmented with default values). It will show an extended help message if it is not empty, else it shows the synopsis.
Expand Down Expand Up @@ -101,7 +104,7 @@ func (c *command) Run(args []string) int {
if err == errShowHelp {
return cli.RunResultHelp
}
c.ui.Error(err.Error())
c.outputter.WriteError(err)
return 1
}

Expand All @@ -123,24 +126,38 @@ func (c *command) setConfig(loc string) {
var err error
loc, err = conf.GetDefaultConfigLocation()
if err != nil {
fmt.Fprint(os.Stderr, errors.Wrap(err, "failed to find config location"))
c.outputter.WriteError(errors.Wrap(err, "failed to find config location"))
os.Exit(-1)
}
os.MkdirAll(filepath.Dir(loc), 0755)
f, err := os.OpenFile(loc, os.O_CREATE|os.O_EXCL|os.O_WRONLY, 0644)
if err != nil && !os.IsExist(err) {
fmt.Fprint(os.Stderr, errors.Wrapf(err, "failed to create config file %v", loc))
err = createFile(loc, "{}")
if err != nil {
c.outputter.WriteError(errors.Wrapf(err, "failed to create config file %v", loc))
os.Exit(-1)
}
f.Write([]byte("{}"))
f.Close()
}
conf, err := conf.Read(loc)
if err != nil {
fmt.Fprint(os.Stderr, errors.Wrap(err, "failed to read config file"))
c.outputter.WriteError(errors.Wrap(err, "failed to read config file"))
os.Exit(-1)
}
c.config = conf
if conf.Logging.Enabled {
logPath, err := homedir.Expand(conf.Logging.FileLocation)
if err != nil {
c.outputter.WriteError(errors.Wrap(err, "failed to find home directory"))
os.Exit(-1)
}
err = createFile(logPath, "")
if err != nil {
c.outputter.WriteError(errors.Wrapf(err, "failed to create log file %v", logPath))
os.Exit(-1)
}
err = c.outputter.SetLogToDisk(logPath)
if err != nil {
c.outputter.WriteError(errors.Wrap(err, "failed to set logging"))
os.Exit(-1)
}
}
}

//setSession sets the cmd.session field according to the session file location
Expand All @@ -149,35 +166,49 @@ func (c *command) setSession(loc string) {
var err error
loc, err = conf.GetDefaultSessionLocation()
if err != nil {
fmt.Fprint(os.Stderr, errors.Wrap(err, "failed to find session location"))
c.outputter.WriteError(errors.Wrap(err, "failed to find session location"))
os.Exit(-1)
}
os.MkdirAll(filepath.Dir(loc), 0755)
f, err := os.OpenFile(loc, os.O_CREATE|os.O_EXCL|os.O_WRONLY, 0644)
if err != nil && !os.IsExist(err) {
fmt.Fprint(os.Stderr, errors.Wrapf(err, "failed to create session file %v", loc))
err = createFile(loc, "{}")
if err != nil {
c.outputter.WriteError(errors.Wrapf(err, "failed to create session file %v", loc))
os.Exit(-1)
}
f.Write([]byte("{}"))
f.Close()
}
c.session = conf.NewSession(loc)
if proj := os.Getenv(EnvNerdProject); proj != "" {
c.session.WriteProject(proj, conf.DefaultAWSRegion)
}
}

//setVerbose sets verbose output formatting
func setVerbose(verbose bool) {
if verbose {
logrus.SetFormatter(new(logrus.TextFormatter))
logrus.SetLevel(logrus.DebugLevel)
//setDebug sets debug output formatting
func (c *command) setDebug(debug bool) {
c.outputter.SetDebug(debug)
}

//setOutput specifies the type of output
func (c *command) setOutput(output string) {
switch output {
case "json":
c.outputter.SetOutputType(format.OutputTypeJSON)
case "raw":
c.outputter.SetOutputType(format.OutputTypeRaw)
case "pretty":
fallthrough
default:
c.outputter.SetOutputType(format.OutputTypePretty)
}
}

//setJSON sets json output formatting
func (c *command) setJSON(json bool) {
c.jsonOutput = json
if json {
logrus.SetFormatter(new(logrus.JSONFormatter))
func createFile(path, content string) error {
os.MkdirAll(filepath.Dir(path), 0755)
f, err := os.OpenFile(path, os.O_CREATE|os.O_EXCL|os.O_WRONLY, 0644)
if err != nil && !os.IsExist(err) {
return err
}
if err == nil {
f.Write([]byte(content))
}
f.Close()
return nil
}
23 changes: 11 additions & 12 deletions command/dataset_download.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import (
"fmt"
"os"

"github.com/Sirupsen/logrus"
"github.com/mitchellh/cli"
"github.com/nerdalize/nerd/nerd/aws"
v1datatransfer "github.com/nerdalize/nerd/nerd/service/datatransfer/v1"
Expand Down Expand Up @@ -52,34 +51,34 @@ func (cmd *Download) DoRun(args []string) (err error) {
if err != nil && os.IsNotExist(err) {
err = os.MkdirAll(outputDir, OutputDirPermissions)
if err != nil {
HandleError(errors.Errorf("The provided path '%s' does not exist and could not be created.", outputDir))
return HandleError(errors.Errorf("The provided path '%s' does not exist and could not be created.", outputDir))
}
fi, err = os.Stat(outputDir)
}
if err != nil {
HandleError(err)
return HandleError(err)
} else if !fi.IsDir() {
HandleError(errors.Errorf("The provided path '%s' is not a directory", outputDir))
return HandleError(errors.Errorf("The provided path '%s' is not a directory", outputDir))
}

// Clients
batchclient, err := NewClient(cmd.config, cmd.session)
batchclient, err := NewClient(cmd.config, cmd.session, cmd.outputter)
if err != nil {
HandleError(err)
return HandleError(err)
}
ss, err := cmd.session.Read()
if err != nil {
HandleError(err)
return HandleError(err)
}
dataOps, err := aws.NewDataClient(
aws.NewNerdalizeCredentials(batchclient, ss.Project.Name),
ss.Project.AWSRegion,
)
if err != nil {
HandleError(errors.Wrap(err, "could not create aws dataops client"))
return HandleError(errors.Wrap(err, "could not create aws dataops client"))
}

logrus.Infof("Downloading dataset with ID '%v'", datasetID)
cmd.outputter.Logger.Printf("Downloading dataset with ID '%v'", datasetID)
downloadConf := v1datatransfer.DownloadConfig{
BatchClient: batchclient,
DataOps: dataOps,
Expand All @@ -93,13 +92,13 @@ func (cmd *Download) DoRun(args []string) (err error) {
var size int64
size, err = v1datatransfer.GetRemoteDatasetSize(context.Background(), batchclient, dataOps, ss.Project.Name, datasetID)
if err != nil {
HandleError(err)
return HandleError(err)
}
go ProgressBar(size, progressCh, progressBarDoneCh)
go ProgressBar(cmd.outputter.ErrW(), size, progressCh, progressBarDoneCh)
downloadConf.ProgressCh = progressCh
err = v1datatransfer.Download(context.Background(), downloadConf)
if err != nil {
HandleError(errors.Wrapf(err, "failed to download dataset '%v'", datasetID))
return HandleError(errors.Wrapf(err, "failed to download dataset '%v'", datasetID))
}
<-progressBarDoneCh
return nil
Expand Down
51 changes: 15 additions & 36 deletions command/dataset_list.go
Original file line number Diff line number Diff line change
@@ -1,15 +1,8 @@
package command

import (
"context"
"os"
"time"

humanize "github.com/dustin/go-humanize"
"github.com/mitchellh/cli"
"github.com/nerdalize/nerd/nerd/aws"
v1datatransfer "github.com/nerdalize/nerd/nerd/service/datatransfer/v1"
"github.com/olekukonko/tablewriter"
"github.com/nerdalize/nerd/command/format"
"github.com/pkg/errors"
)

Expand All @@ -34,43 +27,29 @@ func DatasetListFactory() (cli.Command, error) {

//DoRun is called by run and allows an error to be returned
func (cmd *DatasetList) DoRun(args []string) (err error) {
bclient, err := NewClient(cmd.config, cmd.session)
bclient, err := NewClient(cmd.config, cmd.session, cmd.outputter)
if err != nil {
HandleError(err)
return HandleError(err)
}

ss, err := cmd.session.Read()
if err != nil {
HandleError(err)
}
dataOps, err := aws.NewDataClient(
aws.NewNerdalizeCredentials(bclient, ss.Project.Name),
ss.Project.AWSRegion,
)
if err != nil {
HandleError(errors.Wrap(err, "could not create aws dataops client"))
return HandleError(err)
}
out, err := bclient.ListDatasets(ss.Project.Name)

datasets, err := bclient.ListDatasets(ss.Project.Name)
if err != nil {
HandleError(err)
return HandleError(err)
}

table := tablewriter.NewWriter(os.Stdout)
table.SetHeader([]string{"ProjectID", "DatasetID", "Created", "Size"})
for _, t := range out.Datasets {
var size int64
size, err = v1datatransfer.GetRemoteDatasetSize(context.Background(), bclient, dataOps, ss.Project.Name, t.DatasetID)
if err != nil {
HandleError(err)
}
row := []string{}
row = append(row, t.ProjectID)
row = append(row, t.DatasetID)
row = append(row, humanize.Time(time.Unix(t.CreatedAt, 0)))
row = append(row, humanize.Bytes(uint64(size)))
table.Append(row)
}
header := "DatasetID\tCreated"
pretty := "{{range $i, $x := $.Datasets}}{{$x.DatasetID}}\t{{$x.CreatedAt | fmtUnixAgo }}\n{{end}}"
raw := "{{range $i, $x := $.Datasets}}{{$x.DatasetID}}\t{{$x.CreatedAt}}\n{{end}}"
cmd.outputter.Output(format.DecMap{
format.OutputTypePretty: format.NewTableDecorator(datasets, header, pretty),
format.OutputTypeRaw: format.NewTmplDecorator(datasets, raw),
format.OutputTypeJSON: format.NewJSONDecorator(datasets.Datasets),
})

table.Render()
return nil
}

0 comments on commit 57a5cd4

Please sign in to comment.