From a9679b7ade12de50add5fddc43b35487925aba4f Mon Sep 17 00:00:00 2001 From: Aaron Raddon Date: Sat, 29 Jul 2017 13:55:52 -0700 Subject: [PATCH 1/4] New cli tool --- account.go | 19 ----- command/account.go | 92 +++++++++++++++++++++++++ command/commands.go | 164 ++++++++++++++++++++++++++++++++++++++++++++ lytics/main.go | 30 ++++++++ 4 files changed, 286 insertions(+), 19 deletions(-) delete mode 100644 account.go create mode 100644 command/account.go create mode 100644 command/commands.go create mode 100644 lytics/main.go diff --git a/account.go b/account.go deleted file mode 100644 index cb6fa75..0000000 --- a/account.go +++ /dev/null @@ -1,19 +0,0 @@ -package main - -func (c *Cli) getAccounts(id interface{}) (interface{}, error) { - if id != nil && id != "" { - acct, err := c.Client.GetAccount(id.(string)) - if err != nil { - return nil, err - } - - return acct, nil - } else { - accts, err := c.Client.GetAccounts() - if err != nil { - return nil, err - } - - return accts, nil - } -} diff --git a/command/account.go b/command/account.go new file mode 100644 index 0000000..1a24f43 --- /dev/null +++ b/command/account.go @@ -0,0 +1,92 @@ +package command + +import ( + "fmt" + "strings" +) + +func accountCommands(api *apiCommand) map[string]*command { + a := &account{apiCommand: api} + return map[string]*command{ + "list": &command{a.HelpList, a.List, "Account List."}, + "show": &command{a.HelpGet, a.Get, "Account Show Summary."}, + } +} + +type account struct { + *apiCommand +} + +func (c *account) HelpList() string { + helpText := fmt.Sprintf(` +Usage: lytics account list [options] + + List accounts + +%s + +Options: + + -to=yesterday End date to extract events. + -format=json Choose export format between json/csv. + -event=E Extract data for only event E. + -out=STDOUT Decides where to write the data. +`, globalHelp) + return strings.TrimSpace(helpText) +} +func (c *account) HelpGet() string { + helpText := fmt.Sprintf(` +Usage: lytics account show [options] id + + Get Account and show summary + +%s + +Options: + +`, globalHelp) + return strings.TrimSpace(helpText) +} + +func (c *account) Get(args []string) int { + //c.f.StringVar(&c.stuff, "stuff", "", "Account stuff") + c.ui.Info("GET") + c.init(args, c.HelpGet) + id := c.f.Arg(0) + if id == "" { + c.ui.Error("Must provide account ID") + } + c.cols = []string{"id", "name", "created", "description"} + + //c.ui.Info(fmt.Sprintf("hello world aid=%v arg=%v", c.aid, c.f.Arg(0))) + + acct, err := c.l.GetAccount(id) + c.exitIfErr(err, "Could not get account") + + c.writeSingle(acct) + return 0 +} + +func (c *account) List(args []string) int { + //c.f.StringVar(&c.stuff, "stuff", "", "Account stuff") + c.init(args, c.HelpList) + c.cols = []string{"id", "name", "created", "description"} + + c.ui.Info(fmt.Sprintf("Account List id=%v", c.f.Arg(0))) + + accts, err := c.l.GetAccounts() + if err != nil { + c.ui.Error(fmt.Sprintf("Could not get accounts %v", err)) + return 1 + } + items := make([]interface{}, len(accts)) + for i, acct := range accts { + items[i] = acct + //fmt.Printf("ACCT: %T %#v \n", acct, acct) + } + c.writeList(items) + return 0 +} + +// func items(accts []lytics.Account) { +// } diff --git a/command/commands.go b/command/commands.go new file mode 100644 index 0000000..145bb85 --- /dev/null +++ b/command/commands.go @@ -0,0 +1,164 @@ +package command + +import ( + "encoding/json" + "flag" + "fmt" + "os" + + "github.com/apcera/termtables" + "github.com/araddon/qlbridge/datasource" + "github.com/mitchellh/cli" + + lytics "github.com/lytics/go-lytics" +) + +var globalHelp = ` +Global Options: + --key=xyz reads LIOKEY envvar, or pass ass command line api key + --datakey=xyz Data Api Key, Reads LIODATAKEY envvar as default + --format=table [json,csv,table] output as tabular data? csv? json? + --aid=aid Account id shortcut +` + +type commandList func(api *apiCommand) map[string]*command + +func Commands(ui cli.Ui) map[string]cli.CommandFactory { + + api := &apiCommand{ui: ui} + api.f = flag.NewFlagSet("lytics", flag.ContinueOnError) + + topLevelCommands := map[string]commandList{ + "account": accountCommands, + } + + cmds := make(map[string]cli.CommandFactory) + + for cmd, cmdList := range topLevelCommands { + for subCmdName, subCmd := range cmdList(api) { + sub := subCmd + cmds[fmt.Sprintf("%s %s", cmd, subCmdName)] = func() (cli.Command, error) { + return sub, nil + } + } + + } + return cmds +} + +type command struct { + h func() string + r func(args []string) int + summary string +} + +func (c *command) Run(args []string) int { + return c.r(args) +} +func (c *command) Help() string { + return c.h() +} +func (c *command) Synopsis() string { + return c.summary +} + +type apiCommand struct { + l *lytics.Client + f *flag.FlagSet + ui cli.Ui + aid int + format string + apiKey string + dataKey string + args []string + cols []string +} + +func (c *apiCommand) init(args []string, help func() string) { + c.args = args + c.f.Usage = func() { c.ui.Output(help()) } + + format := os.Getenv("LYTICSFORMAT") + if format == "" { + format = "table" + } + c.f.IntVar(&c.aid, "aid", 0, "Account aid") + c.f.StringVar(&c.format, "format", format, "Output format Reads LYTICSFORMAT envvar as default") + c.f.StringVar(&c.apiKey, "key", os.Getenv("LIOKEY"), "Api Key, Reads LIOKEY envvar as default") + c.f.StringVar(&c.dataKey, "datakey", os.Getenv("LIODATAKEY"), "Data Key, Reads LIODATAKEY envvar as default") + + if err := c.f.Parse(c.args); err != nil { + c.ui.Error(fmt.Sprintf("Could not parse args %v", err)) + os.Exit(1) + } + + // create lytics client with auth info + c.l = lytics.NewLytics(c.apiKey, c.dataKey, nil) +} +func (c *apiCommand) writeTable(item interface{}) { + table := termtables.CreateTable() + + cw := datasource.NewContextWrapper(item) + + row := make([]interface{}, len(c.cols)) + for i, col := range c.cols { + table.AddHeaders(col) + val, _ := cw.Get(col) + if val != nil { + row[i] = val.Value() + } + } + + table.AddRow(row...) + + fmt.Println(table.Render()) +} +func (c *apiCommand) writeTableList(items []interface{}) { + table := termtables.CreateTable() + for _, col := range c.cols { + table.AddHeaders(col) + } + + for _, item := range items { + cw := datasource.NewContextWrapper(item) + row := make([]interface{}, len(c.cols)) + for i, col := range c.cols { + val, _ := cw.Get(col) + if val != nil { + row[i] = val.Value() + } + } + table.AddRow(row...) + } + + fmt.Println(table.Render()) +} +func (c *apiCommand) writeSingle(item interface{}) { + if c.format == "table" { + c.writeTable(item) + return + } + jsonOut, err := json.MarshalIndent(item, "", " ") + if err != nil { + c.ui.Error(fmt.Sprintf("Failed to marshal json? %v", err)) + } + c.ui.Output(string(jsonOut)) +} +func (c *apiCommand) writeList(items []interface{}) { + if c.format == "table" { + c.writeTableList(items) + return + } + jsonOut, err := json.MarshalIndent(items, "", " ") + if err != nil { + c.ui.Error(fmt.Sprintf("Failed to marshal json? %v", err)) + } + c.ui.Output(string(jsonOut)) +} + +func (c *apiCommand) exitIfErr(err error, msg string) { + if err != nil { + c.ui.Error(fmt.Sprintf("%v: %s\n", err, msg)) + os.Exit(1) + } +} diff --git a/lytics/main.go b/lytics/main.go new file mode 100644 index 0000000..a66d62b --- /dev/null +++ b/lytics/main.go @@ -0,0 +1,30 @@ +package main + +import ( + "log" + "os" + + "github.com/mitchellh/cli" + + "github.com/lytics/lytics/command" +) + +func main() { + ui := &cli.ColoredUi{ + OutputColor: cli.UiColorNone, + InfoColor: cli.UiColorBlue, + ErrorColor: cli.UiColorRed, + WarnColor: cli.UiColorGreen, + Ui: &cli.BasicUi{Writer: os.Stdout}, + } + c := cli.NewCLI("lytics", "1.0.0") + c.Args = os.Args[1:] + c.Commands = command.Commands(ui) + + exitStatus, err := c.Run() + if err != nil { + log.Println(err) + } + + os.Exit(exitStatus) +} From 21d45d716ced79fc71cce472514aa0f8a9458874 Mon Sep 17 00:00:00 2001 From: Aaron Raddon Date: Sat, 29 Jul 2017 14:35:28 -0700 Subject: [PATCH 2/4] Refactor for new cli --- auth.go | 19 -- catalog.go | 16 -- command/account.go | 3 - command/auth.go | 75 ++++++ command/catalog.go | 63 +++++ command/commands.go | 22 +- entity.go => command/entity.go | 2 +- provider.go => command/provider.go | 2 +- query.go => command/query.go | 2 +- segment.go => command/segment.go | 2 +- user.go => command/user.go | 2 +- watchlql.go => command/watchlql.go | 2 +- work.go => command/work.go | 2 +- command/writefile.go | 1 + lytics/main.go | 30 --- main.go | 411 +++++------------------------ writefile.go | 1 - 17 files changed, 227 insertions(+), 428 deletions(-) delete mode 100644 auth.go delete mode 100644 catalog.go create mode 100644 command/auth.go create mode 100644 command/catalog.go rename entity.go => command/entity.go (93%) rename provider.go => command/provider.go (95%) rename query.go => command/query.go (90%) rename segment.go => command/segment.go (98%) rename user.go => command/user.go (95%) rename watchlql.go => command/watchlql.go (99%) rename work.go => command/work.go (95%) create mode 100644 command/writefile.go delete mode 100644 lytics/main.go delete mode 100644 writefile.go diff --git a/auth.go b/auth.go deleted file mode 100644 index e12b33e..0000000 --- a/auth.go +++ /dev/null @@ -1,19 +0,0 @@ -package main - -func (c *Cli) getAuths(id interface{}) (interface{}, error) { - if id != nil && id != "" { - auth, err := c.Client.GetAuth(id.(string)) - if err != nil { - return nil, err - } - - return auth, nil - } else { - auths, err := c.Client.GetAuths() - if err != nil { - return nil, err - } - - return auths, nil - } -} diff --git a/catalog.go b/catalog.go deleted file mode 100644 index 766a8a0..0000000 --- a/catalog.go +++ /dev/null @@ -1,16 +0,0 @@ -package main - -import () - -func (c *Cli) getSchema(table string) (interface{}, error) { - if table == "" { - table = "user" - } - - schema, err := c.Client.GetSchemaTable(table) - if err != nil { - return nil, err - } - - return schema, nil -} diff --git a/command/account.go b/command/account.go index 1a24f43..562be6e 100644 --- a/command/account.go +++ b/command/account.go @@ -87,6 +87,3 @@ func (c *account) List(args []string) int { c.writeList(items) return 0 } - -// func items(accts []lytics.Account) { -// } diff --git a/command/auth.go b/command/auth.go new file mode 100644 index 0000000..890a224 --- /dev/null +++ b/command/auth.go @@ -0,0 +1,75 @@ +package command + +import ( + "fmt" + "strings" +) + +func authCommands(api *apiCommand) map[string]*command { + a := &auth{apiCommand: api} + return map[string]*command{ + "list": &command{a.HelpList, a.List, "Auth List, 3rd party auth tokens."}, + "show": &command{a.HelpGet, a.Get, "Auth Show Summary."}, + } +} + +type auth struct { + *apiCommand +} + +func (c *auth) HelpList() string { + helpText := fmt.Sprintf(` +Usage: lytics auth list [options] + + List auths + +%s +`, globalHelp) + return strings.TrimSpace(helpText) +} +func (c *auth) HelpGet() string { + helpText := fmt.Sprintf(` +Usage: lytics auth show [options] id + + Get Auth and show summary + +%s + +Options: + +`, globalHelp) + return strings.TrimSpace(helpText) +} + +func (c *auth) Get(args []string) int { + + c.init(args, c.HelpGet) + id := c.f.Arg(0) + if id == "" { + c.ui.Error("Must provide auth ID") + } + c.cols = []string{"id", "name", "created", "description"} + + auth, err := c.l.GetAuth(id) + c.exitIfErr(err, "Could not get auth") + + c.writeSingle(auth) + return 0 +} + +func (c *auth) List(args []string) int { + c.init(args, c.HelpList) + c.cols = []string{"id", "name", "created", "description"} + + auths, err := c.l.GetAuths() + if err != nil { + c.ui.Error(fmt.Sprintf("Could not get auths %v", err)) + return 1 + } + items := make([]interface{}, len(auths)) + for i, auth := range auths { + items[i] = auth + } + c.writeList(items) + return 0 +} diff --git a/command/catalog.go b/command/catalog.go new file mode 100644 index 0000000..5cf166e --- /dev/null +++ b/command/catalog.go @@ -0,0 +1,63 @@ +package command + +import ( + "fmt" + "strings" +) + +func schemaCommands(api *apiCommand) map[string]*command { + c := &schema{apiCommand: api} + return map[string]*command{ + "": &command{c.HelpSchema, c.Schema, "Schema Show fields for a table."}, + "tables": &command{c.HelpTables, c.Schema, "Schema show tables."}, + } +} + +type schema struct { + *apiCommand +} + +func (c *schema) HelpTables() string { + helpText := fmt.Sprintf(` +Usage: lytics schema tables [options] + + List schema tables + +%s +`, globalHelp) + return strings.TrimSpace(helpText) +} +func (c *schema) HelpSchema() string { + helpText := fmt.Sprintf(` +Usage: lytics schema [options] + + Get Schema and show columns + +%s + +Options: + +`, globalHelp) + return strings.TrimSpace(helpText) +} + +func (c *schema) Schema(args []string) int { + + c.init(args, c.HelpSchema) + table := c.f.Arg(0) + if table == "" { + table = "user" + } + //{As:"country", IsBy:false, Type:"string", ShortDesc:"Country", LongDesc:"Country Code", Froms:[]string{"default", "app", "clearbit_users"}, Identities:[]string{"geocountry", "GeoCountryCode"}} + c.cols = []string{"as", "type", "shortdesc"} + + schema, err := c.l.GetSchemaTable(table) + c.exitIfErr(err, "Could not get schema") + + items := make([]interface{}, len(schema.Columns)) + for i, col := range schema.Columns { + items[i] = col + } + c.writeList(items) + return 0 +} diff --git a/command/commands.go b/command/commands.go index 145bb85..4384351 100644 --- a/command/commands.go +++ b/command/commands.go @@ -30,6 +30,8 @@ func Commands(ui cli.Ui) map[string]cli.CommandFactory { topLevelCommands := map[string]commandList{ "account": accountCommands, + "auth": authCommands, + "schema": schemaCommands, } cmds := make(map[string]cli.CommandFactory) @@ -37,7 +39,10 @@ func Commands(ui cli.Ui) map[string]cli.CommandFactory { for cmd, cmdList := range topLevelCommands { for subCmdName, subCmd := range cmdList(api) { sub := subCmd - cmds[fmt.Sprintf("%s %s", cmd, subCmdName)] = func() (cli.Command, error) { + if subCmdName != "" { + subCmdName = fmt.Sprintf(" %s", subCmdName) + } + cmds[fmt.Sprintf("%s%s", cmd, subCmdName)] = func() (cli.Command, error) { return sub, nil } } @@ -46,6 +51,10 @@ func Commands(ui cli.Ui) map[string]cli.CommandFactory { return cmds } +type Cli struct { + Client *lytics.Client +} + type command struct { h func() string r func(args []string) int @@ -162,3 +171,14 @@ func (c *apiCommand) exitIfErr(err error, msg string) { os.Exit(1) } } +func exitIfErr(err error, msg string) { + if err != nil { + fmt.Fprintf(os.Stderr, "%v: %s\n", err, msg) + os.Exit(1) + } +} + +func errExit(err error, msg string) { + fmt.Fprintf(os.Stderr, "%v: %s\n", err, msg) + os.Exit(1) +} diff --git a/entity.go b/command/entity.go similarity index 93% rename from entity.go rename to command/entity.go index 600c58a..332a3d8 100644 --- a/entity.go +++ b/command/entity.go @@ -1,4 +1,4 @@ -package main +package command func (c *Cli) getEntity(entitytype, fieldname, fieldval string, fields []string) (interface{}, error) { entity, err := c.Client.GetEntity(entitytype, fieldname, fieldval, fields) diff --git a/provider.go b/command/provider.go similarity index 95% rename from provider.go rename to command/provider.go index 33a9258..8ceeaa6 100644 --- a/provider.go +++ b/command/provider.go @@ -1,4 +1,4 @@ -package main +package command func (c *Cli) getProviders(id interface{}) (interface{}, error) { if id != nil && id != "" { diff --git a/query.go b/command/query.go similarity index 90% rename from query.go rename to command/query.go index edde12a..2cf32e5 100644 --- a/query.go +++ b/command/query.go @@ -1,4 +1,4 @@ -package main +package command func (c *Cli) getQueries(alias string) (interface{}, error) { if alias == "" { diff --git a/segment.go b/command/segment.go similarity index 98% rename from segment.go rename to command/segment.go index a41ff6d..be1d7ef 100644 --- a/segment.go +++ b/command/segment.go @@ -1,4 +1,4 @@ -package main +package command import ( lytics "github.com/lytics/go-lytics" diff --git a/user.go b/command/user.go similarity index 95% rename from user.go rename to command/user.go index f24d7a3..1636ba6 100644 --- a/user.go +++ b/command/user.go @@ -1,4 +1,4 @@ -package main +package command func (c *Cli) getUsers(id interface{}) (interface{}, error) { if id != nil && id != "" { diff --git a/watchlql.go b/command/watchlql.go similarity index 99% rename from watchlql.go rename to command/watchlql.go index 1e619d7..716ee0b 100644 --- a/watchlql.go +++ b/command/watchlql.go @@ -1,4 +1,4 @@ -package main +package command import ( "bytes" diff --git a/work.go b/command/work.go similarity index 95% rename from work.go rename to command/work.go index ddb23c6..e26a66d 100644 --- a/work.go +++ b/command/work.go @@ -1,4 +1,4 @@ -package main +package command func (c *Cli) getWorks(id interface{}) (interface{}, error) { if id != nil && id != "" { diff --git a/command/writefile.go b/command/writefile.go new file mode 100644 index 0000000..d47dcf0 --- /dev/null +++ b/command/writefile.go @@ -0,0 +1 @@ +package command diff --git a/lytics/main.go b/lytics/main.go deleted file mode 100644 index a66d62b..0000000 --- a/lytics/main.go +++ /dev/null @@ -1,30 +0,0 @@ -package main - -import ( - "log" - "os" - - "github.com/mitchellh/cli" - - "github.com/lytics/lytics/command" -) - -func main() { - ui := &cli.ColoredUi{ - OutputColor: cli.UiColorNone, - InfoColor: cli.UiColorBlue, - ErrorColor: cli.UiColorRed, - WarnColor: cli.UiColorGreen, - Ui: &cli.BasicUi{Writer: os.Stdout}, - } - c := cli.NewCLI("lytics", "1.0.0") - c.Args = os.Args[1:] - c.Commands = command.Commands(ui) - - exitStatus, err := c.Run() - if err != nil { - log.Println(err) - } - - os.Exit(exitStatus) -} diff --git a/main.go b/main.go index 9860176..7ca8057 100644 --- a/main.go +++ b/main.go @@ -1,378 +1,87 @@ package main import ( - "encoding/json" - "flag" + "bytes" "fmt" - "io/ioutil" "log" "os" + "sort" "strings" - lytics "github.com/lytics/go-lytics" -) + "github.com/mitchellh/cli" -var ( - apikey string - dataapikey string - method string - id string - output string - file string - fields string - fieldsSlice []string - segments string - segmentsSlice []string - entitytype string - fieldname string - fieldvalue string - table string - limit int + "github.com/lytics/lytics/command" ) -type Cli struct { - Client *lytics.Client -} - -func init() { - flag.Usage = func() { - flag.PrintDefaults() - usageExit() - } - - flag.StringVar(&apikey, "apikey", os.Getenv("LIOKEY"), "Lytics API Key - Or use env LIOKEY") - flag.StringVar(&dataapikey, "dataapikey", os.Getenv("LIODATAKEY"), "Lytics Data API Key - Or use env LIODATAKEY") - flag.StringVar(&id, "id", "", "Id of object") - flag.StringVar(&segments, "segments", "", "Comma Separated Segments") - flag.StringVar(&fields, "fields", "", "Comma Separated Fields") - flag.StringVar(&fieldname, "fieldname", "", "Field Name") - flag.StringVar(&fieldvalue, "fieldvalue", "", "Field Value") - flag.StringVar(&entitytype, "entitytype", "", "Entity Type") - flag.StringVar(&table, "table", "", "Schema Table") - flag.StringVar(&file, "file", "", "Output File Name") - flag.IntVar(&limit, "limit", -1, "Result Limit") - flag.Parse() -} - func main() { - if apikey == "" && dataapikey == "" { - fmt.Println(`Missing -apikey and/or -method: use -help for assistance - - LIOKEY env variable will fullfill api key needs - `) - os.Exit(1) + ui := &cli.ColoredUi{ + OutputColor: cli.UiColorNone, + InfoColor: cli.UiColorBlue, + ErrorColor: cli.UiColorRed, + WarnColor: cli.UiColorGreen, + Ui: &cli.BasicUi{Writer: os.Stdout}, } + c := cli.NewCLI("lytics", "1.0.0") + c.HelpFunc = helpFunc + c.Args = os.Args[1:] + c.Commands = command.Commands(ui) - if len(flag.Args()) < 1 { - flag.Usage() - return - } - method = flag.Args()[0] - - log.SetFlags(log.LstdFlags | log.Lshortfile) - - // create lytics client with auth info - c := Cli{ - Client: lytics.NewLytics(apikey, dataapikey, nil), - } - - output, err := c.handleFunction(method) + exitStatus, err := c.Run() if err != nil { - fmt.Println(fmt.Sprintf("Error: %v", err)) - os.Exit(1) - } - - if file != "" { - err := writeToFile(file, output) - if err != nil { - fmt.Println("Failed to write data to file %s: %v", file, err) - return - } - - fmt.Println("Data written to %s successfully.", file) - return + log.Println(err) } - if len(output) > 0 { - fmt.Println(output) - } -} - -func writeToFile(file, data string) error { - err := ioutil.WriteFile(file, []byte(data), 0644) - return err + os.Exit(exitStatus) } -func appendToFile(file, data string) error { - f, err := os.OpenFile(file, os.O_APPEND|os.O_WRONLY, 0600) - if err != nil { - return err - } - - defer f.Close() - - if _, err = f.WriteString(data); err != nil { - return err - } - - return nil -} - -func (c *Cli) handleFunction(method string) (string, error) { - var ( - result interface{} - err error - ) - - if fields != "" { - fieldsSlice = strings.Split(fields, ",") - } - - if segments != "" { - segmentsSlice = strings.Split(segments, ",") - } - - switch method { - case "account": - result, err = c.getAccounts(id) - - case "auth": - result, err = c.getAuths(id) - - case "schema": - result, err = c.getSchema(table) - - case "entity": - result, err = c.getEntity(entitytype, fieldname, fieldvalue, fieldsSlice) - - case "provider": - result, err = c.getProviders(id) - - case "segment": - result, err = c.getSegments("user", segmentsSlice) - - case "segmentscan": - if id == "" && len(flag.Args()) == 2 { - id = flag.Args()[1] +// BasicHelpFunc generates some basic help output that is usually good enough +// for most CLI applications. +func helpFunc(commands map[string]cli.CommandFactory) string { + var buf bytes.Buffer + buf.WriteString("Usage: lytics [--version] [--help] []\n\n") + buf.WriteString("Available commands are:\n") + + // Get the list of keys so we can sort them, and also get the maximum + // key length so they can be aligned properly. + keys := make([]string, 0, len(commands)) + maxKeyLen := 0 + for key := range commands { + if len(key) > maxKeyLen { + maxKeyLen = len(key) } - c.getEntityScan(id, limit, func(e *lytics.Entity) { - fmt.Println(e.PrettyJson()) - }) - return "", nil - - case "segmentsize": - result, err = c.getSegmentSizes(segmentsSlice) - case "segmentattribution": - result, err = c.getSegmentAttributions(segmentsSlice, limit) - - case "work": - result, err = c.getWorks(id) - - case "user": - result, err = c.getUsers(id) - - case "query": - result, err = c.getQueries(id) - - case "watch": - c.watch() - default: - flag.Usage() - return "", nil - } - - if err != nil { - return "", err - } - - return makeJSON(result), err -} - -func validate() bool { - return true -} - -func makeJSON(blob interface{}) string { - jsonOut, err := json.MarshalIndent(blob, "", " ") - if err != nil { - return fmt.Sprintf("Failed: %v", err) + keys = append(keys, key) } + sort.Strings(keys) + lastSub := "" + + for _, key := range keys { + parts := strings.Split(key, " ") + if len(parts) == 2 { + if lastSub != parts[0] { + buf.WriteString(fmt.Sprintf("\n%s\n", parts[0])) + lastSub = parts[0] + } + } else { + buf.WriteString(fmt.Sprintf("\n%s\n", key)) + } + commandFunc, ok := commands[key] + if !ok { + // This should never happen since we JUST built the list of + // keys. + panic("command not found: " + key) + } - return string(jsonOut) -} + command, err := commandFunc() + if err != nil { + log.Printf("[ERR] cli: Command '%s' failed to load: %s", + key, err) + continue + } -func exitIfErr(err error, msg string) { - if err != nil { - fmt.Fprintf(os.Stderr, "%v: %s\n", err, msg) - os.Exit(1) + key = fmt.Sprintf("%s%s", key, strings.Repeat(" ", maxKeyLen-len(key))) + buf.WriteString(fmt.Sprintf(" %s %s\n", key, command.Synopsis())) } -} - -func errExit(err error, msg string) { - fmt.Fprintf(os.Stderr, "%v: %s\n", err, msg) - os.Exit(1) -} - -func usageExit() { - fmt.Printf(` --------------------------------------------------------- -************** LYTICS COMMAND LINE HELP ************** --------------------------------------------------------- - -ENV Vars: - export LIOKEY="your_api_key" - export LIODATAKEY="your_api_key" - -METHODS: - [account] - retrieves account information based upon api key. - if no id is passed, all accounts returned. - ------- - params: - ------- - OPTIONAL string - ------- - example: - ------- - lytics account - lytics --id= account - - [schema] - retrieves table schema (fields, types) - ------- - params: - ------- - REQUIRED string - OPTIONAL int - ------- - example: - ------- - lytics schema - lytics --limit= --table=user schema - - [entity] - retrieves entity (a single user) information - ------- - params: - ------- - REQUIRED string (user or content) - REQUIRED string (name of field to search by, e.g. email) - REQUIRED string (value of field to search by) - OPTIONAL string (comma separated list of fields to filter by) - ------- - example: - ------- - lytics -entitytype=user -fieldname=email -fieldvalue="me@me.com" entity - lytics -entitytype=user -fieldname=email -fieldvalue="me@me.com" -fields=email entity - - [segmentscan] - retrieves a list of users (actually entities, could be content, etc). - - ------- - params: - ------- - id=id_or_slug - ------- - example: - ------- - lytics --id=slug_of_segment segmentscan - - # use a segment QL query - lytics segmentscan ' - FILTER AND ( - EXISTS email - last_active_ts > "now-7d" - ) - ' - - # see what single user looks like - lytics --limit=1 segmentscan ' FILTER * FROM user' - - # see what content looks like - lytics --limit=1 segmentscan ' FILTER * FROM content' - - [segment] - retrieves segment information based upon api key. - if no id is passed, all segments returned. - ------- - params: - ------- - OPTIONAL string (comma separated list of segment ids, max 1) - ------- - example: - ------- - lytics segment - lytics -segments=slug_of_segment segment - - [segmentsize] - retrieves segment sizes information based upon api key. - if no id is passed, all segment sizes returned. - ------- - params: - ------- - OPTIONAL string (comma separated list of segment ids) - ------- - example: - ------- - lytics segmentsize - lytics -segmentes=one,two segmentsize - - [segmentattribution] - retrieves segment information based upon api key. - if no id is passed, all segments returned. - ------- - params: - ------- - OPTIONAL string (comma separated list of segment ids) - OPTIONAL int - ------- - example: - ------- - lytics segmentattribution - lytics -segments=one,two -limit=5 segmentattribution - - [user] - retrieves administrative user information based upon api key. - if no id is passed, all users returned. - ------- - params: - ------- - OPTIONAL string - ------- - example: - ------- - lytics user - lytics -id= user - - [query] - retrieves query information - ------- - params: - ------- - OPTIONAL string - ------- - example: - ------- - lytics query - lytics --id= query - - [watch] - watch the current folder for .lql, .json files to evaluate - the .lql query against the data in .json to preview output. - - .lql file name must match the json file name. - - For Example: - cd /tmp - ls *.lql # assume a temp.lql - cat temp.json # file of data - - ------- - example: - ------- - lytics watch -`) - os.Exit(1) + return buf.String() } diff --git a/writefile.go b/writefile.go deleted file mode 100644 index 06ab7d0..0000000 --- a/writefile.go +++ /dev/null @@ -1 +0,0 @@ -package main From 1fac1feaabd33dec40ab980e670d21fb46e512c3 Mon Sep 17 00:00:00 2001 From: Aaron Raddon Date: Sat, 29 Jul 2017 15:05:28 -0700 Subject: [PATCH 3/4] Segment List, get cli --- command/commands.go | 10 +++++- command/segment.go | 75 ++++++++++++++++++++++++++++++++++++++ command/user.go | 88 +++++++++++++++++++++++++++++++++++++-------- main.go | 2 +- 4 files changed, 158 insertions(+), 17 deletions(-) diff --git a/command/commands.go b/command/commands.go index 4384351..c2ae949 100644 --- a/command/commands.go +++ b/command/commands.go @@ -18,7 +18,6 @@ Global Options: --key=xyz reads LIOKEY envvar, or pass ass command line api key --datakey=xyz Data Api Key, Reads LIODATAKEY envvar as default --format=table [json,csv,table] output as tabular data? csv? json? - --aid=aid Account id shortcut ` type commandList func(api *apiCommand) map[string]*command @@ -31,7 +30,9 @@ func Commands(ui cli.Ui) map[string]cli.CommandFactory { topLevelCommands := map[string]commandList{ "account": accountCommands, "auth": authCommands, + "user": userCommands, "schema": schemaCommands, + "segment": segmentCommands, } cmds := make(map[string]cli.CommandFactory) @@ -101,6 +102,8 @@ func (c *apiCommand) init(args []string, help func() string) { os.Exit(1) } + //c.ui.Info(fmt.Sprintf("args %v", c.f.Args())) + // create lytics client with auth info c.l = lytics.NewLytics(c.apiKey, c.dataKey, nil) } @@ -109,10 +112,13 @@ func (c *apiCommand) writeTable(item interface{}) { cw := datasource.NewContextWrapper(item) + //fmt.Printf("%#v \n\n", item) + row := make([]interface{}, len(c.cols)) for i, col := range c.cols { table.AddHeaders(col) val, _ := cw.Get(col) + //fmt.Printf("%s %#v \n", col, val) if val != nil { row[i] = val.Value() } @@ -133,6 +139,7 @@ func (c *apiCommand) writeTableList(items []interface{}) { row := make([]interface{}, len(c.cols)) for i, col := range c.cols { val, _ := cw.Get(col) + //fmt.Println("%s %#v", col, val) if val != nil { row[i] = val.Value() } @@ -143,6 +150,7 @@ func (c *apiCommand) writeTableList(items []interface{}) { fmt.Println(table.Render()) } func (c *apiCommand) writeSingle(item interface{}) { + //c.ui.Info(fmt.Sprintf("write sigle format=%v", c.format)) if c.format == "table" { c.writeTable(item) return diff --git a/command/segment.go b/command/segment.go index be1d7ef..a933430 100644 --- a/command/segment.go +++ b/command/segment.go @@ -1,9 +1,84 @@ package command import ( + "fmt" + "strings" + lytics "github.com/lytics/go-lytics" ) +func segmentCommands(api *apiCommand) map[string]*command { + c := &segment{apiCommand: api} + return map[string]*command{ + "list": &command{c.HelpList, c.List, "Segment List, segments for account."}, + "show": &command{c.HelpGet, c.Get, "Segment Show Summary."}, + } +} + +type segment struct { + *apiCommand +} + +func (c *segment) HelpList() string { + helpText := fmt.Sprintf(` +Usage: lytics segment list [options] + + List segments + +%s +`, globalHelp) + return strings.TrimSpace(helpText) +} +func (c *segment) HelpGet() string { + helpText := fmt.Sprintf(` +Usage: lytics segment show [options] id + + Get Segment and show summary + +%s + +Options: + --table="user" +`, globalHelp) + return strings.TrimSpace(helpText) +} + +func (c *segment) Get(args []string) int { + + c.init(args, c.HelpGet) + id := c.f.Arg(0) + if id == "" { + c.ui.Error("Must provide segment ID/Slug") + return 1 + } + c.cols = []string{"name", "SegKind", "created", "filterql"} + + segment, err := c.l.GetSegment(id) + c.exitIfErr(err, "Could not get segment") + + c.writeSingle(segment) + return 0 +} + +func (c *segment) List(args []string) int { + c.init(args, c.HelpList) + table := c.f.Arg(0) + if table == "" { + table = "user" + } + + c.cols = []string{"name", "SegKind", "created", "filterql"} + + segments, err := c.l.GetSegments(table) + c.exitIfErr(err, "Could not get segment") + items := make([]interface{}, len(segments)) + for i, u := range segments { + items[i] = u + } + c.writeList(items) + return 0 +} + func (c *Cli) getSegments(table string, segments []string) (interface{}, error) { if len(segments) == 1 { segment, err := c.Client.GetSegment(segments[0]) diff --git a/command/user.go b/command/user.go index 1636ba6..b0b7f45 100644 --- a/command/user.go +++ b/command/user.go @@ -1,19 +1,77 @@ package command -func (c *Cli) getUsers(id interface{}) (interface{}, error) { - if id != nil && id != "" { - user, err := c.Client.GetUser(id.(string)) - if err != nil { - return nil, err - } - - return user, nil - } else { - users, err := c.Client.GetUsers() - if err != nil { - return nil, err - } - - return users, nil +import ( + "fmt" + "strings" +) + +func userCommands(api *apiCommand) map[string]*command { + c := &user{apiCommand: api} + return map[string]*command{ + "list": &command{c.HelpList, c.List, "User List, admi users for account."}, + "show": &command{c.HelpGet, c.Get, "Admin User Show Summary."}, + } +} + +type user struct { + *apiCommand +} + +func (c *user) HelpList() string { + helpText := fmt.Sprintf(` +Usage: lytics user list [options] + + List users + +%s +`, globalHelp) + return strings.TrimSpace(helpText) +} +func (c *user) HelpGet() string { + helpText := fmt.Sprintf(` +Usage: lytics user show [options] id + + Get Admin User and show summary + +%s + +Options: + +`, globalHelp) + return strings.TrimSpace(helpText) +} + +func (c *user) Get(args []string) int { + + c.init(args, c.HelpGet) + id := c.f.Arg(0) + if id == "" { + c.ui.Error("Must provide user ID") + } + c.cols = []string{"email", "name", "created", "roles"} + + user, err := c.l.GetUser(id) + c.exitIfErr(err, "Could not get user") + // for _, ua := range user.Accounts { + // } + + c.writeSingle(user) + return 0 +} + +func (c *user) List(args []string) int { + c.init(args, c.HelpList) + c.cols = []string{"email", "name", "created", "roles"} + + users, err := c.l.GetUsers() + if err != nil { + c.ui.Error(fmt.Sprintf("Could not get users %v", err)) + return 1 + } + items := make([]interface{}, len(users)) + for i, u := range users { + items[i] = u } + c.writeList(items) + return 0 } diff --git a/main.go b/main.go index 7ca8057..661606d 100644 --- a/main.go +++ b/main.go @@ -60,11 +60,11 @@ func helpFunc(commands map[string]cli.CommandFactory) string { if len(parts) == 2 { if lastSub != parts[0] { buf.WriteString(fmt.Sprintf("\n%s\n", parts[0])) - lastSub = parts[0] } } else { buf.WriteString(fmt.Sprintf("\n%s\n", key)) } + lastSub = parts[0] commandFunc, ok := commands[key] if !ok { // This should never happen since we JUST built the list of From 92690f4b08167e8906be53e2d451e1f050befc81 Mon Sep 17 00:00:00 2001 From: Aaron Raddon Date: Mon, 4 Sep 2017 11:19:57 -0700 Subject: [PATCH 4/4] cli ui updates --- command/commands.go | 7 ++++ command/segment.go | 97 +++++++++++++++++++++++++++++---------------- 2 files changed, 70 insertions(+), 34 deletions(-) diff --git a/command/commands.go b/command/commands.go index c2ae949..905da76 100644 --- a/command/commands.go +++ b/command/commands.go @@ -5,6 +5,7 @@ import ( "flag" "fmt" "os" + "strings" "github.com/apcera/termtables" "github.com/araddon/qlbridge/datasource" @@ -77,9 +78,11 @@ type apiCommand struct { f *flag.FlagSet ui cli.Ui aid int + limit int format string apiKey string dataKey string + fields []string args []string cols []string } @@ -92,15 +95,19 @@ func (c *apiCommand) init(args []string, help func() string) { if format == "" { format = "table" } + fields := "" c.f.IntVar(&c.aid, "aid", 0, "Account aid") + c.f.IntVar(&c.limit, "limit", 0, "Page Size/Limit for apis that support paging (segment scan)") c.f.StringVar(&c.format, "format", format, "Output format Reads LYTICSFORMAT envvar as default") c.f.StringVar(&c.apiKey, "key", os.Getenv("LIOKEY"), "Api Key, Reads LIOKEY envvar as default") c.f.StringVar(&c.dataKey, "datakey", os.Getenv("LIODATAKEY"), "Data Key, Reads LIODATAKEY envvar as default") + c.f.StringVar(&fields, "fields", "", "List of fields to show in table") if err := c.f.Parse(c.args); err != nil { c.ui.Error(fmt.Sprintf("Could not parse args %v", err)) os.Exit(1) } + c.fields = strings.Split(fields, ",") //c.ui.Info(fmt.Sprintf("args %v", c.f.Args())) diff --git a/command/segment.go b/command/segment.go index a933430..b880985 100644 --- a/command/segment.go +++ b/command/segment.go @@ -3,8 +3,6 @@ package command import ( "fmt" "strings" - - lytics "github.com/lytics/go-lytics" ) func segmentCommands(api *apiCommand) map[string]*command { @@ -12,6 +10,7 @@ func segmentCommands(api *apiCommand) map[string]*command { return map[string]*command{ "list": &command{c.HelpList, c.List, "Segment List, segments for account."}, "show": &command{c.HelpGet, c.Get, "Segment Show Summary."}, + "scan": &command{c.HelpScan, c.Scan, "Segment Scan List Users (or content, etc)."}, } } @@ -42,6 +41,26 @@ Options: `, globalHelp) return strings.TrimSpace(helpText) } +func (c *segment) HelpScan() string { + helpText := fmt.Sprintf(` +Usage: lytics segment scan [options] id + + Get Entities in segment + + lytics segment scan --format=json "all" + + lytics segment scan --format=json ' + +FILTER * from content +' + +%s + +Options: + +`, globalHelp) + return strings.TrimSpace(helpText) +} func (c *segment) Get(args []string) int { @@ -79,22 +98,51 @@ func (c *segment) List(args []string) int { return 0 } -func (c *Cli) getSegments(table string, segments []string) (interface{}, error) { - if len(segments) == 1 { - segment, err := c.Client.GetSegment(segments[0]) - if err != nil { - return nil, err - } +func (c *segment) Size(args []string) int { - return segment, nil - } else { - segments, err := c.Client.GetSegments(table) - if err != nil { - return nil, err - } + c.init(args, c.HelpSize) + id := c.f.Arg(0) + if id == "" { + c.ui.Error("Must provide segment ID/Slug") + return 1 + } + c.cols = []string{"name", "SegKind", "created", "filterql"} - return segments, nil + segment, err := c.l.GetSegment(id) + c.exitIfErr(err, "Could not get segment") + + c.writeSingle(segment) + return 0 +} + +func (c *segment) Scan(args []string) int { + // table := "user" + // c.f.StringVar(&table, "table", table, "Table (default = user)") + c.init(args, c.HelpScan) + idOrQl := c.f.Arg(0) + if idOrQl == "" { + c.ui.Error("Must provide segment ID/Slug/Ql") + return 1 } + + c.cols = []string{"name", "SegKind", "created", "filterql"} + + scan := c.l.PageSegment(idOrQl) + + ct := 0 + // handle processing the entities + for { + e := scan.Next() + if e == nil { + break + } + c.writeSingle(&e) + ct++ + if c.limit > 0 && ct == c.limit { + return 0 + } + } + return 0 } func (c *Cli) getSegmentSizes(segments []string) (interface{}, error) { @@ -123,22 +171,3 @@ func (c *Cli) getSegmentAttributions(segments []string, limit int) (interface{}, return attributions, nil } - -func (c *Cli) getEntityScan(segmentIdOrQl string, limit int, handler lytics.EntityHandler) { - - scan := c.Client.PageSegment(segmentIdOrQl) - - ct := 0 - // handle processing the entities - for { - e := scan.Next() - if e == nil { - break - } - handler(&e) - ct++ - if limit > 0 && ct == limit { - return - } - } -}