Example #1
0
File: cmd.go Project: hawx/xesende
func receivedCmd(client receivedClient) *hadfield.Command {
	var page int

	cmd := &hadfield.Command{
		Usage: "received [options]",
		Short: "lists received messages",
		Long: `
  Received displays a list of received messages.

    --page NUM       # Display given page
`,
		Run: func(cmd *hadfield.Command, args []string) {
			resp, err := client.Received()
			if err != nil {
				log.Fatal(err)
			}

			pretty.PrettyPrint(resp.Messages)
		},
	}

	cmd.Flag.IntVar(&page, "page", 0, "")

	return cmd
}
Example #2
0
File: cmd.go Project: hawx/xesende
func sentCmd(client sentClient) *hadfield.Command {
	var page int

	cmd := &hadfield.Command{
		Usage: "sent [options]",
		Short: "lists sent messages",
		Long: `
  Sent displays a list of sent messages.

    --page NUM       # Display given page
`,
		Run: func(cmd *hadfield.Command, args []string) {
			resp, err := client.Sent(pageOpts(page))
			if err != nil {
				log.Fatal(err)
			}

			pretty.PrettyPrint(resp.Messages)
		},
	}

	cmd.Flag.IntVar(&page, "page", 1, "")

	return cmd
}
Example #3
0
File: cmd.go Project: hawx/xesende
func accountsCmd(client *xesende.Client) *hadfield.Command {
	return &hadfield.Command{
		Usage: "accounts",
		Short: "list accounts",
		Long: `
  List accounts available to the user.
`,
		Run: func(cmd *hadfield.Command, args []string) {
			resp, err := client.Accounts()
			if err != nil {
				log.Fatal(err)
			}

			pretty.PrettyPrint(resp.Accounts)
		},
	}
}
Example #4
0
File: cmd.go Project: hawx/xesende
func messageCmd(client *xesende.Client) *hadfield.Command {
	return &hadfield.Command{
		Usage: "message MESSAGEID",
		Short: "displays a message",
		Long: `
  Message displays the details for a message.
`,
		Run: func(cmd *hadfield.Command, args []string) {
			if len(args) < 1 {
				log.Fatal("Require MESSAGEID parameter")
			}

			resp, err := client.Message(args[0])
			if err != nil {
				log.Fatal(err)
			}

			pretty.PrettyPrint(resp)
		},
	}
}
Example #5
0
File: cmd.go Project: hawx/xesende
func sendCmd(client *xesende.AccountClient) *hadfield.Command {
	return &hadfield.Command{
		Usage: "send TO BODY",
		Short: "send messages",
		Long: `
  Send a single sms message.
`,
		Run: func(cmd *hadfield.Command, args []string) {
			resp, err := client.Send([]xesende.Message{
				{
					To:   args[0],
					Body: args[1],
				},
			})

			if err != nil {
				log.Fatal(err)
			}

			pretty.PrettyPrint(resp)
		},
	}
}
Example #6
0
func main() {
	env := flag.String("env", "", "select environment/profile")
	debug := flag.Bool("debug", false, "enable/disable debug mode")
	prompt := flag.Bool("prompt", true, "enable/disable prompt")

	flag.Parse()

	var nextKey dynago.AttributeNameValue

	config := ReadConfig(CONFIG_FILE, *env)
	selected := config.Dynago.Profile
	profile := config.Profile[selected]
	if profile == nil {
		log.Fatal("no profile for ", selected)
	}

	if *debug {
		httpclient.StartLogging(true, true)
	}

	if len(profile.URL) > 0 {
		db.SetRegionAndURL(profile.Region, profile.URL)
	} else if len(profile.Region) > 0 {
		db.SetRegion(profile.Region)
	}

	if len(profile.AccessKey) > 0 {
		db.SetCredentials(profile.AccessKey, profile.SecretKey)
	}

	commander := &cmd.Cmd{HistoryFile: HISTORY_FILE, Complete: CompletionFunction, EnableShell: true}
	if *prompt {
		commander.Prompt = "dynagosh> "
	} else {
		commander.Prompt = "\n"
	}

	commander.Init()

	commander.Add(cmd.Command{"config",
		`
		config : display current configuration
		`,
		func(string) (stop bool) {
			pretty.PrettyPrint(config)
			return
		},
		nil})

	commander.Add(cmd.Command{"list",
		`
                list : display list of available tables
                `,
		func(string) (stop bool) {
			tables, err := db.ListTables()

			if err != nil {
				fmt.Println(err)
				return
			}

			if len(tables) > 0 {
				fmt.Println("Available tables")

				for _, tableName := range tables {
					fmt.Println("  ", tableName)
				}

				table_list = tables
			} else {
				fmt.Println("No available tables")
			}

			return
		},
		nil})

	commander.Add(cmd.Command{"describe",
		`
                describe {table} : display table configuration
                `,
		func(line string) (stop bool) {
			var tableName string

			if len(line) > 0 {
				tableName = line
			} else if selected_table != nil {
				tableName = selected_table.Name
			} else {
				fmt.Println("nothing to describe")
				return
			}

			if table, err := db.DescribeTable(tableName); err != nil {
				fmt.Println(err)
			} else {
				pretty.PrettyPrint(table)
			}

			return
		},
		nil})

	commander.Add(cmd.Command{"use",
		`
                use {table} : select table for queries
                `,
		func(line string) (stop bool) {
			if len(line) > 0 {
				tableName := line
				table, err := db.GetTable(tableName)
				if err != nil {
					fmt.Println(err)
				} else {
					selected_table = table
					if *prompt {
						commander.Prompt = "dynagosh: " + tableName + "> "
					}
				}
			}

			if selected_table != nil {
				fmt.Println("using", selected_table.Name)
			} else {
				fmt.Println("no table selected")
			}

			return
		},
		nil})

	commander.Add(cmd.Command{"create",
		`
		create --table=name --hash=name:type [--range=name:type] [--attrs=name:type,name:type] [--rc=readCapacity] [--wc=writeCapacity] [--streams=streamView]
		`,
		func(line string) (stop bool) {
			flags := args.NewFlags("create")

			tableName := flags.String("table", "", "table name")
			hashKey := flags.String("hash", "", "hash key name")
			rangeKey := flags.String("range", "", "range key name")
			rc := flags.Int("rc", 1, "read capacity")
			wc := flags.Int("wc", 1, "write capacity")
			streamView := flags.String("streams", "no", "stream view (all|new|old|keys|no)")

			attributes := AttrDefinitions{attrs: []dynago.AttributeDefinition{}}
			flags.Var(&attributes, "attrs", "attributes definition")

			keys := []string{}

			if err := args.ParseFlags(flags, line); err != nil {
				return
			}

			if len(*tableName) == 0 {
				fmt.Println("missing table name")
				return
			}

			if len(*hashKey) == 0 {
				fmt.Println("missing hash key")
				return
			}

			keys = append(keys, *hashKey)

			if len(*rangeKey) == 0 {
				keys = append(keys, *rangeKey)
			}

			switch *streamView {
			case "old":
				*streamView = dynago.STREAM_VIEW_OLD
			case "new":
				*streamView = dynago.STREAM_VIEW_NEW
			case "all":
				*streamView = dynago.STREAM_VIEW_ALL
			case "keys":
				*streamView = dynago.STREAM_VIEW_KEYS
			case "no", "":
				*streamView = dynago.STREAM_VIEW_DISABLED
			}

			if table, err := db.CreateTable(*tableName,
				attributes.attrs,
				keys,
				*rc, *wc, *streamView); err != nil {
				fmt.Println(err)
			} else {
				pretty.PrettyPrint(table)
				addTable(*tableName)
			}

			return
		},
		nil})

	commander.Add(cmd.Command{"drop",
		`
                drop {table} : delete table
                `,
		func(line string) (stop bool) {
			tableName := line
			table, err := db.DeleteTable(tableName)
			if err != nil {
				fmt.Println(err)
			} else {
				pretty.PrettyPrint(table)
				removeTable(tableName)
			}

			return
		},
		nil})

	commander.Add(cmd.Command{"updateTable",
		`
		updateTable {tablename} readCapacity writeCapacity streamView
		`,
		func(line string) (stop bool) {
			flags := args.NewFlags("updateTable")

			tableName := flags.String("table", "", "table name")
			rc := flags.Int("rc", 0, "read capacity")
			wc := flags.Int("wc", 0, "write capacity")
			streamView := flags.String("streams", "no", "stream view (all|new|old|keys|no)")

			if err := args.ParseFlags(flags, line); err != nil {
				return
			}

			if len(*tableName) == 0 {
				fmt.Println("missing table name")
				return
			}

			switch *streamView {
			case "old":
				*streamView = dynago.STREAM_VIEW_OLD
			case "new":
				*streamView = dynago.STREAM_VIEW_NEW
			case "all":
				*streamView = dynago.STREAM_VIEW_ALL
			case "keys":
				*streamView = dynago.STREAM_VIEW_KEYS
			case "no", "":
				*streamView = dynago.STREAM_VIEW_DISABLED
			}

			if *rc <= 0 && *wc <= 0 && len(*streamView) == 0 {
				fmt.Println("no valid value for rc, wc or streamView")
				return
			}

			if *rc <= 0 {
				*rc = 0 // table.ProvisionedThroughput.ReadCapacityUnits
			}

			if *wc <= 0 {
				*wc = 0 // table.ProvisionedThroughput.WriteCapacityUnits
			}

			if table, err := db.UpdateTable(*tableName, *rc, *wc, *streamView); err != nil {
				fmt.Println(err)
			} else {
				pretty.PrettyPrint(table)
			}

			return
		},
		nil})

	commander.Add(cmd.Command{"put",
		`
                put [--table=tablename] [--condition={condition-expression} [--names={expression-names}] [--values={expression-values}]] {item}
                `,
		func(line string) (stop bool) {
			flags := args.NewFlags("put")
			tableName := flags.String("table", "", "table name")
			condition := flags.String("condition", "", "condition expression")
			names := flags.String("names", "", `attribute names (json: {"x.y.x": "#n"}`)
			values := flags.String("values", "", `expression values (json: {":name": value})`)

			if err := args.ParseFlags(flags, line); err != nil {
				return
			}

			args := flags.Args()

			if len(args) != 1 {
				fmt.Println("one parameter (javascript object) required")
				return
			}

			table := getTable(*tableName)
			if table == nil {
				return
			}

			var item map[string]interface{}
			var nlist map[string]string
			var vlist map[string]interface{}

			if len(*names) > 0 {
				if err := json.Unmarshal([]byte(*names), &nlist); err != nil {
					fmt.Printf("can't parse %q %v\n", *names, err)
					return
				}
			}

			if len(*values) > 0 {
				if err := json.Unmarshal([]byte(*values), &vlist); err != nil {
					fmt.Printf("can't parse %q %v\n", *values, err)
					return
				}
			}

			if err := json.Unmarshal([]byte(args[0]), &item); err != nil {
				fmt.Printf("can't parse %q %v\n", args[0], err)
				return
			}

			if item, consumed, err := table.PutItem(
				dynago.Item(item),
				dynago.ConditionExpression(*condition),
				dynago.ExpressionAttributeNames(nlist),
				dynago.ExpressionAttributeValues(vlist),
				dynago.ReturnValues(dynago.RETURN_ALL_OLD),
				dynago.ReturnConsumed(dynago.RETURN_TOTAL_CONSUMED)); err != nil {
				fmt.Println(err)
			} else {
				pretty.PrettyPrint(item)
				fmt.Println("consumed:", consumed)
			}

			return
		},
		nil})

	commander.Add(cmd.Command{"remove",
		`
		remove [--table=tablename] --hash=hashKey [--range=rangeKey]
		`,
		func(line string) (stop bool) {
			flags := args.NewFlags("remove")
			tableName := flags.String("table", "", "table name")
			hashKey := flags.String("hash", "", "hash key")
			rangeKey := flags.String("range", "", "range key")

			if err := args.ParseFlags(flags, line); err != nil {
				return
			}

			table := getTable(*tableName)
			if table == nil {
				return
			}

			if len(*hashKey) == 0 {
				fmt.Println("hash key is required")
				return
			}

			if table.HashRange() {
				if len(*rangeKey) == 0 {
					fmt.Println("range key is required")
					return
				}
			} else {
				*rangeKey = ""
			}

			if item, consumed, err := table.DeleteItem(
				*hashKey,
				*rangeKey,
				dynago.ReturnValues(dynago.RETURN_ALL_OLD),
				dynago.ReturnConsumed(dynago.RETURN_TOTAL_CONSUMED)); err != nil {
				fmt.Println(err)
			} else {
				pretty.PrettyPrint(item)
				fmt.Println("consumed:", consumed)
			}

			return
		},
		nil})

	commander.Add(cmd.Command{"update",
		`
		update [--table=tablename] --hash=hashKey [--range=rangeKey] [--condition={condition-expression} [--names={expression-names}]] [--values={expression-values}] {update-expression} 
		`,
		func(line string) (stop bool) {
			flags := args.NewFlags("remove")
			tableName := flags.String("table", "", "table name")
			hashKey := flags.String("hash", "", "hash key")
			rangeKey := flags.String("range", "", "range key")
			condition := flags.String("condition", "", "condition expression")
			names := flags.String("names", "", `attribute names (json: {"x.y.x": "#n"}`)
			values := flags.String("values", "", `expression values (json: {":name": value})`)

			if err := args.ParseFlags(flags, line); err != nil {
				return
			}

			table := getTable(*tableName)
			if table == nil {
				return
			}

			if len(*hashKey) == 0 {
				fmt.Println("hash key is required")
				return
			}

			if table.HashRange() {
				if len(*rangeKey) == 0 {
					fmt.Println("range key is required")
					return
				}
			} else {
				*rangeKey = ""
			}

			var nlist map[string]string
			var vlist map[string]interface{}

			if len(*names) > 0 {
				if err := json.Unmarshal([]byte(*names), &nlist); err != nil {
					fmt.Printf("can't parse %q %v\n", *names, err)
					return
				}
			}

			if len(*values) > 0 {
				if err := json.Unmarshal([]byte(*values), &vlist); err != nil {
					fmt.Printf("can't parse %q %v\n", *values, err)
					return
				}
			}

			args := flags.Args()

			updates := args[0]

			if item, consumed, err := table.UpdateItem(
				*hashKey,
				*rangeKey,
				updates,
				dynago.ConditionExpression(*condition),
				dynago.ExpressionAttributeNames(nlist),
				dynago.ExpressionAttributeValues(vlist),
				dynago.ReturnValues(dynago.RETURN_ALL_OLD),
				dynago.ReturnConsumed(dynago.RETURN_TOTAL_CONSUMED)); err != nil {
				fmt.Println(err)
			} else {
				pretty.PrettyPrint(item)
				fmt.Println("consumed:", consumed)
			}

			return
		},
		nil})

	commander.Add(cmd.Command{"get",
		`
		get [--table=tablename] --hash=hashKey [--range=rangeKey] [attributes]
		`,
		func(line string) (stop bool) {
			flags := args.NewFlags("remove")
			tableName := flags.String("table", "", "table name")
			hashKey := flags.String("hash", "", "hash key")
			rangeKey := flags.String("range", "", "range key")

			if err := args.ParseFlags(flags, line); err != nil {
				return
			}

			table := getTable(*tableName)
			if table == nil {
				return
			}

			if len(*hashKey) == 0 {
				fmt.Println("hash key is required")
				return
			}

			if table.HashRange() {
				if len(*rangeKey) == 0 {
					fmt.Println("range key is required")
					return
				}
			} else {
				*rangeKey = ""
			}

			attributes := flags.Args()

			if item, consumed, err := table.GetItem(*hashKey, *rangeKey, attributes, false, true); err != nil {
				fmt.Println(err)
			} else if len(attributes) > 0 {
				for _, n := range attributes {
					fmt.Print(" ", item[n])
				}
				fmt.Println()
			} else {
				pretty.PrettyPrint(item)
				fmt.Println("consumed:", consumed)
			}

			return
		},
		nil})

	commander.Add(cmd.Command{"query",
		`
		query [--table=tablename] [--limit=pagesize] [--next] [--count] [--consumed] --hash hash-key-value [--range[-rangeop] range-key-value]
		`,
		func(line string) (stop bool) {
			flags := args.NewFlags("query")

			tableName := flags.String("table", "", "table name")
			limit := flags.Int("limit", 0, "maximum number of items per page")
			count := flags.Bool("count", false, "only return item count")
			next := flags.Bool("next", false, "get next page")
			consumed := flags.Bool("consumed", false, "return consumed capacity")
			filter := flags.String("filter", "", "filter expression")
			projection := flags.String("projection", "", "projection expression")

			hashKey := flags.String("hash", "", "hash-key value")

			var rangeCond RangeCondition

			flags.Var(&RangeParam{"EQ", &rangeCond, false}, "range", "range-key value")
			flags.Var(&RangeParam{"EQ", &rangeCond, false}, "range-eq", "range-key equal value")
			flags.Var(&RangeParam{"NE", &rangeCond, false}, "range-ne", "range-key not-equal value")
			flags.Var(&RangeParam{"LE", &rangeCond, false}, "range-le", "range-key less-or-equal value")
			flags.Var(&RangeParam{"LT", &rangeCond, false}, "range-lt", "range-key less-than value")
			flags.Var(&RangeParam{"GE", &rangeCond, false}, "range-ge", "range-key less-or-equal value")
			flags.Var(&RangeParam{"GT", &rangeCond, false}, "range-gt", "range-key less-than value")
			flags.Var(&RangeParam{"CONTAINS", &rangeCond, false}, "range-contains", "range-key contains value")
			flags.Var(&RangeParam{"NOT_CONTAINS", &rangeCond, false}, "range-not-contains", "range-key not-contains value")
			flags.Var(&RangeParam{"BEGINS_WITH", &rangeCond, false}, "range-begins-with", "range-key begins-with value")
			flags.Var(&RangeParam{"NULL", &rangeCond, true}, "range-null", "range-key is null")
			flags.Var(&RangeParam{"NOT_NULL", &rangeCond, true}, "range-not-null", "range-key is-not null")

			if err := args.ParseFlags(flags, line); err != nil {
				return
			}

			args := flags.Args()

			table := getTable(*tableName)
			if table == nil {
				return
			}

			if len(*hashKey) < 1 {
				if len(args) < 1 {
					fmt.Println("not enough arguments")
					return
				}

				*hashKey = args[0]

				if len(rangeCond.Operator) < 1 && len(args) > 1 {
					rangeCond.Operator = "EQ"
					rangeCond.Value = args[1]
				}
			}

			query := table.Query(*hashKey)

			if len(rangeCond.Operator) > 0 {
				switch rangeCond.Operator {
				case "NULL", "NOT_NULL":
					query.SetAttrCondition(table.RangeKey().Condition(rangeCond.Operator))
				default:
					query.SetAttrCondition(table.RangeKey().Condition(rangeCond.Operator, rangeCond.Value))
				}
			}

			query.SetFilterExpression(*filter)
			query.SetProjectionExpression(*projection)

			if *limit > 0 {
				query.SetLimit(*limit)
			}

			if *count {
				query.SetSelect(dynago.SELECT_COUNT)
			}

			if *next {
				query.SetStartKey(nextKey)
			}

			if *consumed {
				query.SetConsumed(true)
			}

			if items, lastKey, consumed, err := query.Exec(nil); err != nil {
				fmt.Println(err)
			} else {
				pretty.PrettyPrint(items)
				fmt.Println("consumed:", consumed)

				nextKey = lastKey
			}

			return
		},
		nil})

	commander.Add(cmd.Command{"scan",
		`
		scan [--table=tablename] [--limit=pagesize] [--next] [--count] [--consumed] [--format=pretty|compact|json] [--segment=n --total=m]
		`,
		func(line string) (stop bool) {
			flags := args.NewFlags("scan")

			tableName := flags.String("table", "", "table name")
			limit := flags.Int("limit", 0, "maximum number of items per page")
			count := flags.Bool("count", false, "only return item count")
			cons := flags.Bool("consumed", false, "return consumed capacity")
			segment := flags.Int("segment", 0, "segment number")
			total := flags.Int("total", 0, "total segment")
			delay := flags.Duration("delay", 0, "delay (as duration string) between scan requests")
			format := flags.String("format", "pretty", "output format: pretty, compact or json")
			max := flags.Int("max", 0, "maximum number of items to fetch - 0: one page, -1: all items")
			next := flags.Bool("next", false, "get next page")
			start := flags.String("start", "", "start from this key")
			projection := flags.String("projection", "", "projection expression")
			filter := flags.String("filter", "", "filter expression")
			names := flags.String("names", "", `attribute names (json: {"x.y.x": "#n"}`)
			values := flags.String("values", "", `expression values (json: {":name": value})`)

			if err := args.ParseFlags(flags, line); err != nil {
				return
			}

			table := getTable(*tableName)
			if table == nil {
				return
			}

			scan := dynago.ScanTable(table)

			scan.SetFilterExpression(*filter)
			scan.SetProjectionExpression(*projection)

			if len(*names) > 0 {
				var nlist map[string]string
				if err := json.Unmarshal([]byte(*names), &nlist); err != nil {
					fmt.Printf("can't parse %q %v\n", *names, err)
					return
				}

				scan.SetAttributeNames(nlist)
			}

			if len(*values) > 0 {
				var vlist map[string]interface{}
				if err := json.Unmarshal([]byte(*values), &vlist); err != nil {
					fmt.Printf("can't parse %q %v\n", *values, err)
					return
				}

				scan.SetAttributeValues(vlist)
			}

			if *segment != 0 || *total != 0 {
				scan.SetSegment(*segment, *total)
			}

			if *limit > 0 {
				scan.SetLimit(*limit)
			}

			if *cons {
				scan.SetConsumed(true)
			}

			if *count {
				if totalCount, scanCount, consumed, err := scan.CountWithDelay(db, *delay); err != nil {
					log.Println(err)
				} else {
					fmt.Println("count:", totalCount)
					fmt.Println("scan count:", scanCount)
					if *cons {
						fmt.Println("consumed:", consumed)
					}
				}

				return
			}

			if *max != 0 { // if fetching multiple pages
				*next = true
			}

			if len(*start) > 0 {
				if *debug {
					log.Println("start from", *start)
				}

				if err := json.Unmarshal([]byte(*start), &nextKey); err != nil {
					fmt.Printf("can't parse %q %v\n", *start, err)
					return
				}
			}

			remaining := *max
			errors := 0

			for {
				if *next {
					scan.SetStartKey(nextKey)
				}

				if *debug {
					log.Printf("request: %#v\n", scan)
				}

				items, lastKey, consumed, err := scan.Exec(db)
				if err != nil {
					log.Printf("%T %#v", err, err)

					if !networkError(err) {
						break
					} else {
						errors += 1
						if errors > 10 {
							break
						}
					}
				} else {
					errors = 0

					if *format == "compact" {
						p := &pretty.Pretty{Indent: "", Out: os.Stdout, NilString: "null"}
						for _, i := range items {
							p.Println(i)
						}
					} else if *format == "json" {
						for _, i := range items {
							fmt.Println(jsonString(i))
						}
					} else {
						pretty.PrettyPrint(items)
					}

					if *cons {
						log.Println("count:", len(items))
						log.Println("consumed:", consumed)
					}

					if remaining > 0 {
						if len(items) > remaining {
							remaining = 0
						} else {
							remaining -= len(items)
						}
					}

					nextKey = lastKey

					if (remaining == 0) || len(nextKey) == 0 {
						break
					}
				}

				if *delay > 0 {
					if *debug {
						log.Println(jsonString(nextKey), consumed)
					}

					time.Sleep(*delay)
				}
			}

			return
		},
		nil})

	commander.Add(cmd.Command{"listStreams",
		`
                listStreams : display list of available streams
                `,
		func(line string) (stop bool) {
			flags := args.NewFlags("listStreams")

			tableName := flags.String("table", "", "table name")
			limit := flags.Int("limit", 0, "maximum number of items per page")

			if err := args.ParseFlags(flags, line); err != nil {
				return
			}

			options := []dynago.ListStreamsOption{}

			if len(*tableName) > 0 {
				options = append(options, dynago.LsTable(*tableName))
			}

			if *limit > 0 {
				options = append(options, dynago.LsLimit(*limit))
			}

			streams, err := db.ListStreams(options...)

			if err != nil {
				fmt.Println(err)
				return
			}

			if len(streams) > 0 {
				fmt.Println("Available streams")

				for i, s := range streams {
					fmt.Println(i, s)
				}

				stream_list = streams
			} else {
				fmt.Println("No available streams")
			}

			return
		},
		nil})

	commander.Add(cmd.Command{"describeStream",
		`
                describeStream {streamId} : display stream information
                `,
		func(line string) (stop bool) {
			flags := args.NewFlags("describeStream")

			start := flags.String("start", "", "start from this shard id")
			limit := flags.Int("limit", 0, "maximum number of items per page")

			if err := args.ParseFlags(flags, line); err != nil {
				return
			}

			args := flags.Args()
			if len(args) != 1 {
				fmt.Println("one argument required")
				return
			}

			streamId := getStream(args[0])

			options := []dynago.DescribeStreamOption{}

			if len(*start) > 0 {
				options = append(options, dynago.DsStart(*start))
			}

			if *limit > 0 {
				options = append(options, dynago.DsLimit(*limit))
			}

			stream, err := db.DescribeStream(streamId, options...)
			if err != nil {
				fmt.Println(err)
				return
			} else {
				pretty.PrettyPrint(stream)
			}

			return
		},
		nil})

	commander.Add(cmd.Command{"streamRecords",
		`
                streamRecords {streamId} : display stream records
                `,
		func(line string) (stop bool) {
			flags := args.NewFlags("streamRecords")

			limit := flags.Int("limit", 0, "maximum number of items per page")
			itype := flags.String("type", "last", "shard iterator type (last, latest, at, after)")
			iseq := flags.String("seq", "", "sequence number")
			verbose := flags.Bool("verbose", false, "display full records")

			//follow := flags.Bool("follow", false, "follow iterator")
			//wait := flags.Duration("wait", time.Second, "time to wait if --follow and no new records")
			//iter := flags.String("iter", "", "use this shard iterator")
			//shardId := flags.String("shard", "", "shard id")

			if err := args.ParseFlags(flags, line); err != nil {
				return
			}

			switch *itype {
			case "at":
				*itype = dynago.AT_SEQUENCE
			case "after":
				*itype = dynago.AFTER_SEQUENCE
			case "last":
				*itype = dynago.LAST
			case "latest":
				*itype = dynago.LATEST
			}

			args := flags.Args()
			if len(args) == 0 {
				fmt.Println("missing stream id")
				return
			}

			streamId := getStream(args[0])
			stream, err := db.DescribeStream(streamId)
			if err != nil {
				fmt.Println(err)
				return
			}

			for _, shard := range stream.Shards {
				last := len(shard.SequenceNumberRange.EndingSequenceNumber) == 0

				iterator, err := db.GetShardIterator(streamId, shard.ShardId, *itype, *iseq)
				if err != nil {
					fmt.Println(err)
					return
				}

				for {
					records, err := db.GetRecords(iterator, *limit)
					if err != nil {
						fmt.Println(err)
						return
					} else {
						if *verbose {
							pretty.PrettyPrint(records)
						} else {
							for _, r := range records.Records {
								op := r.EventName
								values := r.Dynamodb
								s := values.SequenceNumber

								switch values.StreamViewType {
								case dynago.STREAM_VIEW_OLD:
									if len(values.OldImage) > 0 {
										fmt.Println(s, op, pretty.PrettyFormat(values.OldImage))
									} else {
										fmt.Println(s, op, "key", pretty.PrettyFormat(values.Keys))
									}

								case dynago.STREAM_VIEW_NEW:
									if len(values.NewImage) > 0 {
										fmt.Println(s, op, pretty.PrettyFormat(values.NewImage))
									} else {
										fmt.Println(s, op, "key", pretty.PrettyFormat(values.Keys))
									}

								case dynago.STREAM_VIEW_KEYS:
									fmt.Println(s, op, pretty.PrettyFormat(values.Keys))

								case dynago.STREAM_VIEW_ALL:
									fmt.Println(s, op,
										"old", pretty.PrettyFormat(values.OldImage),
										"new", pretty.PrettyFormat(values.NewImage))
								}

							}

						}

						iterator = records.NextShardIterator
					}

					if len(iterator) == 0 || last {
						break
					}
				}
			}

			return
		},
		nil})

	commander.Commands["modify"] = commander.Commands["updateTable"]
	commander.Commands["dt"] = commander.Commands["describe"]
	commander.Commands["ls"] = commander.Commands["list"]
	commander.Commands["rm"] = commander.Commands["remove"]
	commander.Commands["lss"] = commander.Commands["listStreams"]
	commander.Commands["ds"] = commander.Commands["describeStream"]
	commander.Commands["lsr"] = commander.Commands["streamRecords"]

	commander.CmdLoop()
}