├── .gitignore ├── .travis.yml ├── go.mod ├── LICENSE ├── cmd ├── share.go ├── team.go ├── mkdir.go ├── restore.go ├── info.go ├── remove-member.go ├── list-groups.go ├── du.go ├── share-list-folders.go ├── add-member.go ├── list-members.go ├── revs.go ├── share-list-links.go ├── logout.go ├── search.go ├── rm.go ├── cp.go ├── get.go ├── mv.go ├── account.go ├── put.go ├── ls.go └── root.go ├── main.go ├── contrib ├── test.sh ├── zsh-completion │ └── _dbxcli └── dbxcli_bash_completion.sh ├── README.md ├── CHANGELOG.md └── go.sum /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | 3 | go: 4 | - "1.11.x" 5 | 6 | before_script: 7 | - go get -u github.com/mitchellh/gox 8 | - env GO111MODULE=on go vet ./... 9 | 10 | install: true 11 | 12 | script: 13 | - ./build.sh 14 | 15 | deploy: 16 | provider: releases 17 | api_key: $GITHUB_TOKEN 18 | skip_cleanup: true 19 | file_glob: true 20 | file: "dist/*" 21 | on: 22 | tags: true 23 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/dropbox/dbxcli 2 | 3 | go 1.11 4 | 5 | require ( 6 | github.com/dropbox/dropbox-sdk-go-unofficial/v6 v6.0.1 7 | github.com/dustin/go-humanize v1.0.0 8 | github.com/inconshreveable/mousetrap v1.0.0 // indirect 9 | github.com/mitchellh/go-homedir v1.1.0 10 | github.com/mitchellh/ioprogress v0.0.0-20180201004757-6a23b12fa88e 11 | github.com/spf13/cobra v0.0.4-0.20190109003409-7547e83b2d85 12 | github.com/spf13/pflag v1.0.3 // indirect 13 | golang.org/x/net v0.0.0-20201224014010-6772e930b67b 14 | golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5 15 | ) 16 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2016 Dropbox 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. 14 | -------------------------------------------------------------------------------- /cmd/share.go: -------------------------------------------------------------------------------- 1 | // Copyright © 2016 Dropbox, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package cmd 16 | 17 | import ( 18 | "github.com/spf13/cobra" 19 | ) 20 | 21 | var shareCmd = &cobra.Command{ 22 | Use: "share", 23 | Short: "Sharing commands", 24 | } 25 | 26 | var shareListCmd = &cobra.Command{ 27 | Use: "list", 28 | Short: "List shared things", 29 | } 30 | 31 | func init() { 32 | RootCmd.AddCommand(shareCmd) 33 | shareCmd.AddCommand(shareListCmd) 34 | } 35 | -------------------------------------------------------------------------------- /cmd/team.go: -------------------------------------------------------------------------------- 1 | // Copyright © 2016 Dropbox, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package cmd 16 | 17 | import ( 18 | "fmt" 19 | 20 | "github.com/spf13/cobra" 21 | ) 22 | 23 | // teamCmd represents the team command 24 | var teamCmd = &cobra.Command{ 25 | Use: "team", 26 | Short: "Team management commands", 27 | PersistentPreRunE: func(cmd *cobra.Command, args []string) error { 28 | if member, _ := cmd.Flags().GetString("as-member"); member != "" { 29 | return fmt.Errorf("Flag `as-member` is invalid for team sub-commands") 30 | } 31 | return initDbx(cmd, args) 32 | }, 33 | } 34 | 35 | func init() { 36 | RootCmd.AddCommand(teamCmd) 37 | } 38 | -------------------------------------------------------------------------------- /cmd/mkdir.go: -------------------------------------------------------------------------------- 1 | // Copyright © 2016 Dropbox, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package cmd 16 | 17 | import ( 18 | "errors" 19 | 20 | "github.com/dropbox/dropbox-sdk-go-unofficial/v6/dropbox/files" 21 | "github.com/spf13/cobra" 22 | ) 23 | 24 | func mkdir(cmd *cobra.Command, args []string) (err error) { 25 | if len(args) != 1 { 26 | return errors.New("`mkdir` requires a `directory` argument") 27 | } 28 | 29 | dst, err := validatePath(args[0]) 30 | if err != nil { 31 | return 32 | } 33 | 34 | arg := files.NewCreateFolderArg(dst) 35 | 36 | dbx := files.New(config) 37 | if _, err = dbx.CreateFolderV2(arg); err != nil { 38 | return 39 | } 40 | 41 | return 42 | } 43 | 44 | // mkdirCmd represents the mkdir command 45 | var mkdirCmd = &cobra.Command{ 46 | Use: "mkdir [flags] ", 47 | Short: "Create a new directory", 48 | RunE: mkdir, 49 | } 50 | 51 | func init() { 52 | RootCmd.AddCommand(mkdirCmd) 53 | } 54 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | // Copyright © 2016 Dropbox, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package main 16 | 17 | import ( 18 | "fmt" 19 | "log" 20 | 21 | "github.com/dropbox/dbxcli/cmd" 22 | "github.com/dropbox/dropbox-sdk-go-unofficial/v6/dropbox" 23 | "github.com/spf13/cobra" 24 | ) 25 | 26 | var version = "0.1.0" 27 | 28 | // versionCmd represents the version command 29 | var versionCmd = &cobra.Command{ 30 | Use: "version", 31 | Short: "Print version information", 32 | Run: func(cmd *cobra.Command, args []string) { 33 | fmt.Println("dbxcli version:", version) 34 | sdkVersion, specVersion := dropbox.Version() 35 | fmt.Println("SDK version:", sdkVersion) 36 | fmt.Println("Spec version:", specVersion) 37 | }, 38 | } 39 | 40 | func init() { 41 | // Log date, time and file information by default 42 | log.SetFlags(log.Ldate | log.Ltime | log.Lshortfile) 43 | cmd.RootCmd.AddCommand(versionCmd) 44 | } 45 | 46 | func main() { 47 | cmd.Execute() 48 | } 49 | -------------------------------------------------------------------------------- /cmd/restore.go: -------------------------------------------------------------------------------- 1 | // Copyright © 2016 Dropbox, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package cmd 16 | 17 | import ( 18 | "errors" 19 | 20 | "github.com/dropbox/dropbox-sdk-go-unofficial/v6/dropbox/files" 21 | "github.com/spf13/cobra" 22 | ) 23 | 24 | func restore(cmd *cobra.Command, args []string) (err error) { 25 | if len(args) != 2 { 26 | return errors.New("`restore` requires `file` and `revision` arguments") 27 | } 28 | 29 | path, err := validatePath(args[0]) 30 | if err != nil { 31 | return 32 | } 33 | 34 | rev := args[1] 35 | 36 | arg := files.NewRestoreArg(path, rev) 37 | 38 | dbx := files.New(config) 39 | if _, err = dbx.Restore(arg); err != nil { 40 | return 41 | } 42 | 43 | return 44 | } 45 | 46 | // restoreCmd represents the restore command 47 | var restoreCmd = &cobra.Command{ 48 | Use: "restore [flags] ", 49 | Short: "Restore files", 50 | RunE: restore, 51 | } 52 | 53 | func init() { 54 | RootCmd.AddCommand(restoreCmd) 55 | } 56 | -------------------------------------------------------------------------------- /contrib/test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | dbxcli=$(realpath $1) 6 | echo "Testing binary at ${dbxcli}" 7 | 8 | echo "Testing version" 9 | ${dbxcli} version 10 | 11 | echo "Testing du" 12 | ${dbxcli} du 13 | 14 | echo "Testing mkdir" 15 | d=dbxcli-$(date +%s) 16 | ${dbxcli} mkdir ${d} 17 | 18 | echo "Testing put" 19 | ${dbxcli} put ${dbxcli} ${d}/dbxcli 20 | 21 | echo "Testing get" 22 | ${dbxcli} get ${d}/dbxcli /tmp/dbxcli 23 | # Make sure files are the same 24 | cmp --silent ${dbxcli} /tmp/dbxcli 25 | 26 | echo "Testing ls -l" 27 | ${dbxcli} ls -l ${d} 28 | 29 | echo "Testing search" 30 | ${dbxcli} search dropbox /${d} 31 | 32 | echo "Testing cp" 33 | ${dbxcli} cp ${d}/dbxcli ${d}/dbxcli-new 34 | 35 | echo "Testing revs" 36 | rev=$(${dbxcli} revs ${d}/dbxcli) 37 | 38 | echo "Testing mv" 39 | ${dbxcli} mv ${d}/dbxcli ${d}/dbxcli-old 40 | 41 | echo "Testing mv" 42 | ${dbxcli} mv ${d}/dbxcli ${d}/dbxcli-old/ 43 | 44 | echo "Testing restore" 45 | ${dbxcli} restore ${d}/dbxcli ${rev} 46 | 47 | echo "Testing rm -f" 48 | ${dbxcli} rm -f ${d} 49 | 50 | echo "Testing share commands" 51 | 52 | echo "Testing share list folder" 53 | ${dbxcli} share list folder 54 | echo "Testing share list link" 55 | ${dbxcli} share list link 56 | 57 | echo "Testing team commands" 58 | echo "Testing team info" 59 | ${dbxcli} team list-groups 60 | 61 | echo "Testing team list-members" 62 | ${dbxcli} team list-members 63 | 64 | echo "Testing ls as" 65 | id=$(${dbxcli} team list-members | grep active | cut -d' ' -f3) 66 | ${dbxcli} ls --as-member ${id} 67 | 68 | echo "All tests passed!" 69 | -------------------------------------------------------------------------------- /cmd/info.go: -------------------------------------------------------------------------------- 1 | // Copyright © 2016 Dropbox, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package cmd 16 | 17 | import ( 18 | "fmt" 19 | "os" 20 | "text/tabwriter" 21 | 22 | "github.com/dropbox/dropbox-sdk-go-unofficial/v6/dropbox/team" 23 | "github.com/spf13/cobra" 24 | ) 25 | 26 | func info(cmd *cobra.Command, args []string) (err error) { 27 | dbx := team.New(config) 28 | res, err := dbx.GetInfo() 29 | if err != nil { 30 | return err 31 | } 32 | 33 | w := new(tabwriter.Writer) 34 | w.Init(os.Stdout, 4, 8, 1, ' ', 0) 35 | fmt.Fprintf(w, "Name:\t%s\n", res.Name) 36 | fmt.Fprintf(w, "Team Id:\t%s\n", res.TeamId) 37 | fmt.Fprintf(w, "Licensed Users:\t%d\n", res.NumLicensedUsers) 38 | fmt.Fprintf(w, "Provisioned Users:\t%d\n", res.NumProvisionedUsers) 39 | return w.Flush() 40 | } 41 | 42 | // infoCmd represents the info command 43 | var infoCmd = &cobra.Command{ 44 | Use: "info", 45 | Short: "Get team information", 46 | RunE: info, 47 | } 48 | 49 | func init() { 50 | teamCmd.AddCommand(infoCmd) 51 | } 52 | -------------------------------------------------------------------------------- /cmd/remove-member.go: -------------------------------------------------------------------------------- 1 | // Copyright © 2016 Dropbox, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package cmd 16 | 17 | import ( 18 | "errors" 19 | "fmt" 20 | 21 | "github.com/dropbox/dropbox-sdk-go-unofficial/v6/dropbox/team" 22 | "github.com/spf13/cobra" 23 | ) 24 | 25 | func removeMember(cmd *cobra.Command, args []string) (err error) { 26 | if len(args) != 1 { 27 | return errors.New("`remove-member` requires an `email` argument") 28 | } 29 | 30 | dbx := team.New(config) 31 | email := args[0] 32 | selector := &team.UserSelectorArg{Email: email} 33 | selector.Tag = "email" 34 | arg := team.NewMembersRemoveArg(selector) 35 | res, err := dbx.MembersRemove(arg) 36 | if err != nil { 37 | return err 38 | } 39 | if res.Tag == "complete" { 40 | fmt.Printf("User successfully removed from team.\n") 41 | } 42 | return 43 | } 44 | 45 | // removeMemberCmd represents the remove-member command 46 | var removeMemberCmd = &cobra.Command{ 47 | Use: "remove-member [flags] ", 48 | Short: "Remove member from a team", 49 | RunE: removeMember, 50 | } 51 | 52 | func init() { 53 | teamCmd.AddCommand(removeMemberCmd) 54 | } 55 | -------------------------------------------------------------------------------- /cmd/list-groups.go: -------------------------------------------------------------------------------- 1 | // Copyright © 2016 Dropbox, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package cmd 16 | 17 | import ( 18 | "fmt" 19 | "os" 20 | "text/tabwriter" 21 | 22 | "github.com/dropbox/dropbox-sdk-go-unofficial/v6/dropbox/team" 23 | "github.com/spf13/cobra" 24 | ) 25 | 26 | func listGroups(cmd *cobra.Command, args []string) (err error) { 27 | dbx := team.New(config) 28 | arg := team.NewGroupsListArg() 29 | res, err := dbx.GroupsList(arg) 30 | if err != nil { 31 | return err 32 | } 33 | 34 | if len(res.Groups) == 0 { 35 | return 36 | } 37 | 38 | w := new(tabwriter.Writer) 39 | w.Init(os.Stdout, 4, 8, 1, ' ', 0) 40 | fmt.Fprintf(w, "Name\tId\t# Members\tExternal Id\n") 41 | for _, group := range res.Groups { 42 | fmt.Fprintf(w, "%s\t%s\t%d\t%s\n", group.GroupName, group.GroupId, group.MemberCount, group.GroupExternalId) 43 | } 44 | return w.Flush() 45 | } 46 | 47 | // listGroupsCmd represents the list-groups command 48 | var listGroupsCmd = &cobra.Command{ 49 | Use: "list-groups", 50 | Short: "List groups", 51 | RunE: listGroups, 52 | } 53 | 54 | func init() { 55 | teamCmd.AddCommand(listGroupsCmd) 56 | } 57 | -------------------------------------------------------------------------------- /cmd/du.go: -------------------------------------------------------------------------------- 1 | // Copyright © 2016 Dropbox, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package cmd 16 | 17 | import ( 18 | "fmt" 19 | 20 | "github.com/dropbox/dropbox-sdk-go-unofficial/v6/dropbox/users" 21 | "github.com/dustin/go-humanize" 22 | "github.com/spf13/cobra" 23 | ) 24 | 25 | func du(cmd *cobra.Command, args []string) (err error) { 26 | dbx := users.New(config) 27 | usage, err := dbx.GetSpaceUsage() 28 | if err != nil { 29 | return 30 | } 31 | 32 | fmt.Printf("Used: %s\n", humanize.IBytes(usage.Used)) 33 | fmt.Printf("Type: %s\n", usage.Allocation.Tag) 34 | 35 | allocation := usage.Allocation 36 | 37 | switch allocation.Tag { 38 | case "individual": 39 | fmt.Printf("Allocated: %s\n", humanize.IBytes(allocation.Individual.Allocated)) 40 | case "team": 41 | fmt.Printf("Allocated: %s (Used: %s)\n", 42 | humanize.IBytes(allocation.Team.Allocated), 43 | humanize.IBytes(allocation.Team.Used)) 44 | } 45 | 46 | return 47 | } 48 | 49 | // duCmd represents the du command 50 | var duCmd = &cobra.Command{ 51 | Use: "du", 52 | Short: "Display usage information", 53 | RunE: du, 54 | } 55 | 56 | func init() { 57 | RootCmd.AddCommand(duCmd) 58 | } 59 | -------------------------------------------------------------------------------- /cmd/share-list-folders.go: -------------------------------------------------------------------------------- 1 | // Copyright © 2016 Dropbox, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package cmd 16 | 17 | import ( 18 | "fmt" 19 | 20 | "github.com/dropbox/dropbox-sdk-go-unofficial/v6/dropbox/sharing" 21 | "github.com/spf13/cobra" 22 | ) 23 | 24 | func shareListFolders(cmd *cobra.Command, args []string) (err error) { 25 | arg := sharing.NewListFoldersArgs() 26 | 27 | dbx := sharing.New(config) 28 | res, err := dbx.ListFolders(arg) 29 | if err != nil { 30 | return 31 | } 32 | 33 | printFolders(res.Entries) 34 | 35 | for len(res.Cursor) > 0 { 36 | continueArg := sharing.NewListFoldersContinueArg(res.Cursor) 37 | 38 | res, err = dbx.ListFoldersContinue(continueArg) 39 | if err != nil { 40 | return 41 | } 42 | 43 | printFolders(res.Entries) 44 | } 45 | 46 | return 47 | } 48 | 49 | func printFolders(entries []*sharing.SharedFolderMetadata) { 50 | for _, f := range entries { 51 | fmt.Printf("%v\t%v\n", f.PathLower, f.PreviewUrl) 52 | } 53 | } 54 | 55 | var shareListFoldersCmd = &cobra.Command{ 56 | Use: "folder", 57 | Short: "List shared folders", 58 | RunE: shareListFolders, 59 | } 60 | 61 | func init() { 62 | shareListCmd.AddCommand(shareListFoldersCmd) 63 | } 64 | -------------------------------------------------------------------------------- /cmd/add-member.go: -------------------------------------------------------------------------------- 1 | // Copyright © 2016 Dropbox, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package cmd 16 | 17 | import ( 18 | "errors" 19 | "fmt" 20 | 21 | "github.com/dropbox/dropbox-sdk-go-unofficial/v6/dropbox/team" 22 | "github.com/spf13/cobra" 23 | ) 24 | 25 | func addMember(cmd *cobra.Command, args []string) (err error) { 26 | if len(args) != 3 { 27 | return errors.New("`add-member` requires `email`, `first`, and `last` arguments") 28 | } 29 | dbx := team.New(config) 30 | 31 | email := args[0] 32 | firstName := args[1] 33 | lastName := args[2] 34 | member := team.NewMemberAddArg(email) 35 | member.MemberGivenName = firstName 36 | member.MemberSurname = lastName 37 | arg := team.NewMembersAddArg([]*team.MemberAddArg{member}) 38 | res, err := dbx.MembersAdd(arg) 39 | if err != nil { 40 | return err 41 | } 42 | if res.Tag == "complete" { 43 | fmt.Printf("User successfully added to the team.\n") 44 | } 45 | return 46 | } 47 | 48 | // addMemberCmd represents the add-member command 49 | var addMemberCmd = &cobra.Command{ 50 | Use: "add-member [flags] ", 51 | Short: "Add a new member to a team", 52 | RunE: addMember, 53 | } 54 | 55 | func init() { 56 | teamCmd.AddCommand(addMemberCmd) 57 | } 58 | -------------------------------------------------------------------------------- /cmd/list-members.go: -------------------------------------------------------------------------------- 1 | // Copyright © 2016 Dropbox, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package cmd 16 | 17 | import ( 18 | "fmt" 19 | "os" 20 | "text/tabwriter" 21 | 22 | "github.com/dropbox/dropbox-sdk-go-unofficial/v6/dropbox/team" 23 | "github.com/spf13/cobra" 24 | ) 25 | 26 | func listMembers(cmd *cobra.Command, args []string) (err error) { 27 | dbx := team.New(config) 28 | arg := team.NewMembersListArg() 29 | res, err := dbx.MembersList(arg) 30 | if err != nil { 31 | return err 32 | } 33 | 34 | if len(res.Members) == 0 { 35 | return 36 | } 37 | 38 | w := new(tabwriter.Writer) 39 | w.Init(os.Stdout, 4, 8, 1, ' ', 0) 40 | fmtStr := "%s\t%s\t%s\t%s\t%s\n" 41 | fmt.Fprintf(w, fmtStr, "Name", "Id", "Status", "Email", "Role") 42 | for _, member := range res.Members { 43 | fmt.Fprintf(w, fmtStr, 44 | member.Profile.Name.DisplayName, 45 | member.Profile.TeamMemberId, 46 | member.Profile.Status.Tag, 47 | member.Profile.Email, 48 | member.Role.Tag) 49 | } 50 | return w.Flush() 51 | } 52 | 53 | // listMembersCmd represents the list-members command 54 | var listMembersCmd = &cobra.Command{ 55 | Use: "list-members", 56 | Short: "List team members", 57 | RunE: listMembers, 58 | } 59 | 60 | func init() { 61 | teamCmd.AddCommand(listMembersCmd) 62 | } 63 | -------------------------------------------------------------------------------- /cmd/revs.go: -------------------------------------------------------------------------------- 1 | // Copyright © 2016 Dropbox, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package cmd 16 | 17 | import ( 18 | "errors" 19 | "fmt" 20 | "os" 21 | 22 | "github.com/dropbox/dropbox-sdk-go-unofficial/v6/dropbox/files" 23 | "github.com/spf13/cobra" 24 | ) 25 | 26 | func revs(cmd *cobra.Command, args []string) (err error) { 27 | if len(args) != 1 { 28 | return errors.New("`revs` requires a `file` argument") 29 | } 30 | 31 | path, err := validatePath(args[0]) 32 | if err != nil { 33 | return 34 | } 35 | 36 | arg := files.NewListRevisionsArg(path) 37 | 38 | dbx := files.New(config) 39 | res, err := dbx.ListRevisions(arg) 40 | if err != nil { 41 | return 42 | } 43 | 44 | long, _ := cmd.Flags().GetBool("long") 45 | 46 | if long { 47 | fmt.Printf("Revision\tSize\tLast modified\tPath\n") 48 | } 49 | 50 | for _, e := range res.Entries { 51 | if long { 52 | printFileMetadata(os.Stdout, e, long) 53 | } else { 54 | fmt.Printf("%s\n", e.Rev) 55 | } 56 | } 57 | 58 | return 59 | } 60 | 61 | // revsCmd represents the revs command 62 | var revsCmd = &cobra.Command{ 63 | Use: "revs [flags] ", 64 | Short: "List file revisions", 65 | RunE: revs, 66 | } 67 | 68 | func init() { 69 | RootCmd.AddCommand(revsCmd) 70 | 71 | revsCmd.Flags().BoolP("long", "l", false, "Long listing") 72 | } 73 | -------------------------------------------------------------------------------- /cmd/share-list-links.go: -------------------------------------------------------------------------------- 1 | // Copyright © 2016 Dropbox, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package cmd 16 | 17 | import ( 18 | "fmt" 19 | 20 | "github.com/dropbox/dropbox-sdk-go-unofficial/v6/dropbox/sharing" 21 | "github.com/spf13/cobra" 22 | ) 23 | 24 | func shareListLinks(cmd *cobra.Command, args []string) (err error) { 25 | arg := sharing.NewListSharedLinksArg() 26 | 27 | dbx := sharing.New(config) 28 | res, err := dbx.ListSharedLinks(arg) 29 | if err != nil { 30 | return 31 | } 32 | 33 | printLinks(res.Links) 34 | 35 | for res.HasMore { 36 | arg = sharing.NewListSharedLinksArg() 37 | arg.Cursor = res.Cursor 38 | 39 | res, err = dbx.ListSharedLinks(arg) 40 | if err != nil { 41 | return 42 | } 43 | 44 | printLinks(res.Links) 45 | } 46 | 47 | return 48 | } 49 | 50 | func printLinks(links []sharing.IsSharedLinkMetadata) { 51 | for _, l := range links { 52 | switch sl := l.(type) { 53 | case *sharing.FileLinkMetadata: 54 | printLink(sl.SharedLinkMetadata) 55 | case *sharing.FolderLinkMetadata: 56 | printLink(sl.SharedLinkMetadata) 57 | default: 58 | fmt.Printf("found unknown shared link type") 59 | } 60 | } 61 | } 62 | 63 | func printLink(sl sharing.SharedLinkMetadata) { 64 | fmt.Printf("%v\t%v\n", sl.Name, sl.Url) 65 | } 66 | 67 | var shareListLinksCmd = &cobra.Command{ 68 | Use: "link", 69 | Short: "List shared links", 70 | RunE: shareListLinks, 71 | } 72 | 73 | func init() { 74 | shareListCmd.AddCommand(shareListLinksCmd) 75 | } 76 | -------------------------------------------------------------------------------- /cmd/logout.go: -------------------------------------------------------------------------------- 1 | // Copyright © 2016 Dropbox, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package cmd 16 | 17 | import ( 18 | "os" 19 | "path" 20 | 21 | "github.com/dropbox/dropbox-sdk-go-unofficial/v6/dropbox" 22 | "github.com/dropbox/dropbox-sdk-go-unofficial/v6/dropbox/auth" 23 | "github.com/mitchellh/go-homedir" 24 | "github.com/spf13/cobra" 25 | ) 26 | 27 | // Command logout revokes all saved API tokens and deletes auth.json. 28 | func logout(cmd *cobra.Command, args []string) error { 29 | dir, err := homedir.Dir() 30 | if err != nil { 31 | return err 32 | } 33 | filePath := path.Join(dir, ".config", "dbxcli", configFileName) 34 | 35 | tokMap, err := readTokens(filePath) 36 | if err != nil { 37 | return err 38 | } 39 | 40 | for domain, tokens := range tokMap { 41 | for _, token := range tokens { 42 | config := dropbox.Config{ 43 | Token: token, 44 | LogLevel: dropbox.LogOff, 45 | Logger: nil, 46 | AsMemberID: "", 47 | Domain: domain, 48 | Client: nil, 49 | HeaderGenerator: nil, 50 | URLGenerator: nil, 51 | } 52 | client := auth.New(config) 53 | err = client.TokenRevoke() 54 | if err != nil { 55 | return err 56 | } 57 | } 58 | } 59 | 60 | return os.Remove(filePath) 61 | } 62 | 63 | // logoutCmd represents the logout command 64 | var logoutCmd = &cobra.Command{ 65 | Use: "logout [flags]", 66 | Short: "Log out of the current session", 67 | RunE: logout, 68 | } 69 | 70 | func init() { 71 | RootCmd.AddCommand(logoutCmd) 72 | } 73 | -------------------------------------------------------------------------------- /cmd/search.go: -------------------------------------------------------------------------------- 1 | // Copyright © 2016 Dropbox, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package cmd 16 | 17 | import ( 18 | "errors" 19 | "fmt" 20 | "os" 21 | "strings" 22 | 23 | "github.com/dropbox/dropbox-sdk-go-unofficial/v6/dropbox/files" 24 | "github.com/spf13/cobra" 25 | ) 26 | 27 | func search(cmd *cobra.Command, args []string) (err error) { 28 | if len(args) == 0 { 29 | return errors.New("`search` requires a `query` argument") 30 | } 31 | 32 | // Parse path scope, if provided. 33 | var scope string 34 | if len(args) == 2 { 35 | scope = args[1] 36 | if !strings.HasPrefix(scope, "/") { 37 | return errors.New("`search` `path-scope` must begin with \"/\"") 38 | } 39 | } 40 | 41 | arg := files.NewSearchArg(scope, args[0]) 42 | 43 | dbx := files.New(config) 44 | res, err := dbx.Search(arg) 45 | if err != nil { 46 | return 47 | } 48 | 49 | long, _ := cmd.Flags().GetBool("long") 50 | if long { 51 | fmt.Printf("Revision\tSize\tLast modified\tPath\n") 52 | } 53 | 54 | for _, m := range res.Matches { 55 | switch f := m.Metadata.(type) { 56 | case *files.FileMetadata: 57 | printFileMetadata(os.Stdout, f, long) 58 | case *files.FolderMetadata: 59 | printFolderMetadata(os.Stdout, f, long) 60 | } 61 | } 62 | 63 | return 64 | } 65 | 66 | // searchCmd represents the search command 67 | var searchCmd = &cobra.Command{ 68 | Use: "search [flags] [path-scope]", 69 | Short: "Search", 70 | RunE: search, 71 | } 72 | 73 | func init() { 74 | RootCmd.AddCommand(searchCmd) 75 | searchCmd.Flags().BoolP("long", "l", false, "Long listing") 76 | } 77 | -------------------------------------------------------------------------------- /cmd/rm.go: -------------------------------------------------------------------------------- 1 | // Copyright © 2016 Dropbox, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package cmd 16 | 17 | import ( 18 | "errors" 19 | "fmt" 20 | 21 | "github.com/dropbox/dropbox-sdk-go-unofficial/v6/dropbox/files" 22 | 23 | "github.com/spf13/cobra" 24 | ) 25 | 26 | func rm(cmd *cobra.Command, args []string) error { 27 | if len(args) < 1 { 28 | return errors.New("rm: missing operand") 29 | } 30 | 31 | force, err := cmd.Flags().GetBool("force") 32 | if err != nil { 33 | return err 34 | } 35 | 36 | var deletePaths []string 37 | dbx := files.New(config) 38 | 39 | // Validate remove paths before executing removal 40 | for i := range args { 41 | path, err := validatePath(args[i]) 42 | if err != nil { 43 | return err 44 | } 45 | 46 | pathMetaData, err := getFileMetadata(dbx, path) 47 | if err != nil { 48 | return err 49 | } 50 | 51 | if _, ok := pathMetaData.(*files.FileMetadata); !ok { 52 | folderArg := files.NewListFolderArg(path) 53 | res, err := dbx.ListFolder(folderArg) 54 | if err != nil { 55 | return err 56 | } 57 | if len(res.Entries) != 0 && !force { 58 | return fmt.Errorf("rm: cannot remove ‘%s’: Directory not empty, use `--force` or `-f` to proceed", path) 59 | } 60 | } 61 | deletePaths = append(deletePaths, path) 62 | } 63 | 64 | // Execute removals 65 | for _, path := range deletePaths { 66 | arg := files.NewDeleteArg(path) 67 | 68 | if _, err = dbx.DeleteV2(arg); err != nil { 69 | return err 70 | } 71 | } 72 | 73 | return nil 74 | } 75 | 76 | // rmCmd represents the rm command 77 | var rmCmd = &cobra.Command{ 78 | Use: "rm [flags] ", 79 | Short: "Remove files", 80 | RunE: rm, 81 | } 82 | 83 | func init() { 84 | RootCmd.AddCommand(rmCmd) 85 | rmCmd.Flags().BoolP("force", "f", false, "Force removal") 86 | } 87 | -------------------------------------------------------------------------------- /contrib/zsh-completion/_dbxcli: -------------------------------------------------------------------------------- 1 | #compdef dbxcli 2 | 3 | function _dbxcli() { 4 | local context curcontext=$curcontext state line 5 | typeset -A opt_args 6 | local ret=1 7 | 8 | _arguments -C \ 9 | '1: :__dbxcli_sub_commands' \ 10 | '*:: :->args' \ 11 | && ret=0 12 | 13 | case $state in 14 | (args) 15 | case $words[1] in 16 | (cp|du|get|mkdir|mv|put|restore|rm|version) 17 | _arguments -C \ 18 | '(-h --help)'{-h,--help}'[Print information about a command]' \ 19 | '--token[(string) Access token]' \ 20 | '(-v --verbose)'{-v,--verbose}'[Enable verbose logging]' \ 21 | && ret=0 22 | ;; 23 | (ls|revs|search) 24 | _arguments -C \ 25 | '(-l --long)'{-l,--long}'[Long listing]' \ 26 | '(-h --help)'{-h,--help}'[Print information about a command]' \ 27 | '--token[(string) Access token]' \ 28 | '(-v --verbose)'{-v,--verbose}'[Enable verbose logging]' \ 29 | && ret=0 30 | ;; 31 | (team) 32 | _arguments -C \ 33 | '1: :__team_sub_commands' \ 34 | '(-h --help)'{-h,--help}'[Print information about a command]' \ 35 | '--token[(string) Access token]' \ 36 | '(-v --verbose)'{-v,--verbose}'[Enable verbose logging]' \ 37 | && ret=0 38 | ;; 39 | esac 40 | ;; 41 | esac 42 | 43 | return ret 44 | } 45 | 46 | __dbxcli_sub_commands() { 47 | local -a _c 48 | 49 | _c=( 50 | 'cp:Copy files' 51 | 'du:Display usage information' 52 | 'get:Download a file' 53 | 'ls:List files' 54 | 'mkdir:Create a new directory' 55 | 'mv:Move files' 56 | 'put:Upload files' 57 | 'restore:Restore files' 58 | 'revs:List file revisions' 59 | 'rm:Remove files' 60 | 'search:Search' 61 | 'team:Team management commands' 62 | 'version:Print version information' 63 | ) 64 | 65 | _describe -t commands dbxcli_sub_commands _c 66 | } 67 | 68 | __team_sub_commands() { 69 | local -a _c 70 | 71 | _c=( 72 | 'add-member:Add a new member to a team' 73 | 'info:Get team information' 74 | 'list-groups:List groups' 75 | 'list-members:List team members' 76 | 'remove-member:Remove member from a team' 77 | ) 78 | 79 | _describe -t commands team_sub_commands _c 80 | } 81 | 82 | _dbxcli "$@" 83 | -------------------------------------------------------------------------------- /cmd/cp.go: -------------------------------------------------------------------------------- 1 | // Copyright © 2016 Dropbox, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package cmd 16 | 17 | import ( 18 | "errors" 19 | "fmt" 20 | "os" 21 | 22 | "github.com/dropbox/dropbox-sdk-go-unofficial/v6/dropbox/files" 23 | "github.com/spf13/cobra" 24 | ) 25 | 26 | func cp(cmd *cobra.Command, args []string) error { 27 | var destination string 28 | var argsToCopy []string 29 | 30 | if len(args) > 2 { 31 | destination = args[len(args)-1] 32 | argsToCopy = args[0 : len(args)-1] 33 | } else if len(args) == 2 { 34 | destination = args[1] 35 | argsToCopy = append(argsToCopy, args[0]) 36 | } else { 37 | return errors.New("cp requires a source and a destination") 38 | } 39 | 40 | var cpErrors []error 41 | var relocationArgs []*files.RelocationArg 42 | 43 | for _, argument := range argsToCopy { 44 | arg, err := makeRelocationArg(argument, destination+"/"+argument) 45 | if err != nil { 46 | relocationError := fmt.Errorf("Error validating copy for %s to %s: %v", argument, destination, err) 47 | cpErrors = append(cpErrors, relocationError) 48 | } else { 49 | relocationArgs = append(relocationArgs, arg) 50 | } 51 | } 52 | 53 | dbx := files.New(config) 54 | for _, arg := range relocationArgs { 55 | if _, err := dbx.CopyV2(arg); err != nil { 56 | copyError := fmt.Errorf("Copy error: %v", arg) 57 | cpErrors = append(cpErrors, copyError) 58 | } 59 | } 60 | 61 | for _, cpError := range cpErrors { 62 | fmt.Fprintf(os.Stderr, "%v\n", cpError) 63 | } 64 | 65 | return nil 66 | } 67 | 68 | // cpCmd represents the cp command 69 | var cpCmd = &cobra.Command{ 70 | Use: "cp [flags] [more sources] ", 71 | Short: "Copy a file or folder to a different location in the user's Dropbox. " + 72 | "If the source path is a folder all its contents will be copied.", 73 | RunE: cp, 74 | } 75 | 76 | func init() { 77 | RootCmd.AddCommand(cpCmd) 78 | } 79 | -------------------------------------------------------------------------------- /cmd/get.go: -------------------------------------------------------------------------------- 1 | // Copyright © 2016 Dropbox, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package cmd 16 | 17 | import ( 18 | "errors" 19 | "fmt" 20 | "io" 21 | "os" 22 | "path" 23 | 24 | "github.com/dropbox/dropbox-sdk-go-unofficial/v6/dropbox/files" 25 | "github.com/dustin/go-humanize" 26 | "github.com/mitchellh/ioprogress" 27 | "github.com/spf13/cobra" 28 | ) 29 | 30 | func get(cmd *cobra.Command, args []string) (err error) { 31 | if len(args) == 0 || len(args) > 2 { 32 | return errors.New("`get` requires `src` and/or `dst` arguments") 33 | } 34 | 35 | src, err := validatePath(args[0]) 36 | if err != nil { 37 | return 38 | } 39 | 40 | // Default `dst` to the base segment of the source path; use the second argument if provided. 41 | dst := path.Base(src) 42 | if len(args) == 2 { 43 | dst = args[1] 44 | } 45 | // If `dst` is a directory, append the source filename. 46 | if f, err := os.Stat(dst); err == nil && f.IsDir() { 47 | dst = path.Join(dst, path.Base(src)) 48 | } 49 | 50 | arg := files.NewDownloadArg(src) 51 | 52 | dbx := files.New(config) 53 | res, contents, err := dbx.Download(arg) 54 | if err != nil { 55 | return 56 | } 57 | defer contents.Close() 58 | 59 | f, err := os.Create(dst) 60 | if err != nil { 61 | return 62 | } 63 | defer f.Close() 64 | 65 | progressbar := &ioprogress.Reader{ 66 | Reader: contents, 67 | DrawFunc: ioprogress.DrawTerminalf(os.Stderr, func(progress, total int64) string { 68 | return fmt.Sprintf("Downloading %s/%s", 69 | humanize.IBytes(uint64(progress)), humanize.IBytes(uint64(total))) 70 | }), 71 | Size: int64(res.Size), 72 | } 73 | 74 | if _, err = io.Copy(f, progressbar); err != nil { 75 | return 76 | } 77 | 78 | return 79 | } 80 | 81 | // getCmd represents the get command 82 | var getCmd = &cobra.Command{ 83 | Use: "get [flags] []", 84 | Short: "Download a file", 85 | RunE: get, 86 | } 87 | 88 | func init() { 89 | RootCmd.AddCommand(getCmd) 90 | } 91 | -------------------------------------------------------------------------------- /cmd/mv.go: -------------------------------------------------------------------------------- 1 | // Copyright © 2016 Dropbox, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package cmd 16 | 17 | import ( 18 | "fmt" 19 | "os" 20 | "regexp" 21 | 22 | "github.com/dropbox/dropbox-sdk-go-unofficial/v6/dropbox/files" 23 | "github.com/spf13/cobra" 24 | ) 25 | 26 | func mv(cmd *cobra.Command, args []string) error { 27 | var destination string 28 | var argsToMove []string 29 | 30 | if len(args) > 2 { 31 | destination = args[len(args)-1] 32 | argsToMove = args[0 : len(args)-1] 33 | } else if len(args) == 2 { 34 | destination = args[1] 35 | argsToMove = append(argsToMove, args[0]) 36 | } else { 37 | return fmt.Errorf("mv command requires a source and a destination") 38 | } 39 | 40 | var mvErrors []error 41 | var relocationArgs []*files.RelocationArg 42 | 43 | re := regexp.MustCompile("[^/]+$") 44 | for _, argument := range argsToMove { 45 | 46 | argumentFile := re.FindString(argument) 47 | lastCharDest := destination[len(destination)-1:] 48 | 49 | var err error 50 | var arg *files.RelocationArg 51 | 52 | if lastCharDest == "/" { 53 | arg, err = makeRelocationArg(argument, destination + argumentFile) 54 | } else { 55 | arg, err = makeRelocationArg(argument, destination) 56 | } 57 | 58 | if err != nil { 59 | relocationError := fmt.Errorf("Error validating move for %s to %s: %v", argument, destination, err) 60 | mvErrors = append(mvErrors, relocationError) 61 | } else { 62 | relocationArgs = append(relocationArgs, arg) 63 | } 64 | } 65 | 66 | dbx := files.New(config) 67 | for _, arg := range relocationArgs { 68 | if _, err := dbx.MoveV2(arg); err != nil { 69 | moveError := fmt.Errorf("Move error: %v", arg) 70 | mvErrors = append(mvErrors, moveError) 71 | } 72 | } 73 | 74 | for _, mvError := range mvErrors { 75 | fmt.Fprintf(os.Stderr, "%v\n", mvError) 76 | } 77 | 78 | return nil 79 | } 80 | 81 | // mvCmd represents the mv command 82 | var mvCmd = &cobra.Command{ 83 | Use: "mv [flags] ", 84 | Short: "Move files", 85 | RunE: mv, 86 | } 87 | 88 | func init() { 89 | RootCmd.AddCommand(mvCmd) 90 | } 91 | -------------------------------------------------------------------------------- /cmd/account.go: -------------------------------------------------------------------------------- 1 | // Copyright © 2016 Dropbox, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package cmd 16 | 17 | import ( 18 | "errors" 19 | "fmt" 20 | "os" 21 | "text/tabwriter" 22 | 23 | "github.com/dropbox/dropbox-sdk-go-unofficial/v6/dropbox/users" 24 | "github.com/spf13/cobra" 25 | ) 26 | 27 | // printFullAccount prints the account details returned by GetCurrentAccount 28 | func printFullAccount(w *tabwriter.Writer, fa *users.FullAccount) { 29 | fmt.Fprintf(w, "Logged in as %s <%s>\n\n", fa.Name.DisplayName, fa.Email) 30 | fmt.Fprintf(w, "Account Id:\t%s\n", fa.AccountId) 31 | fmt.Fprintf(w, "Account Type:\t%s\n", fa.AccountType.Tag) 32 | fmt.Fprintf(w, "Locale:\t%s\n", fa.Locale) 33 | fmt.Fprintf(w, "Referral Link:\t%s\n", fa.ReferralLink) 34 | fmt.Fprintf(w, "Profile Photo Url:\t%s\n", fa.ProfilePhotoUrl) 35 | fmt.Fprintf(w, "Paired Account:\t%t\n", fa.IsPaired) 36 | if fa.Team != nil { 37 | fmt.Fprintf(w, "Team:\n Name:\t%s\n Id:\t%s\n Member Id:\t%s\n", fa.Team.Name, fa.Team.Id, fa.TeamMemberId) 38 | } 39 | } 40 | 41 | // printBasicAccount prints the account details returned by GetAccount 42 | func printBasicAccount(w *tabwriter.Writer, ba *users.BasicAccount) { 43 | fmt.Fprintf(w, "Name:\t%s\n", ba.Name.DisplayName) 44 | if !ba.EmailVerified { 45 | ba.Email += " (unverified)" 46 | } 47 | fmt.Fprintf(w, "Email:\t%s\n", ba.Email) 48 | fmt.Fprintf(w, "Is Teammate:\t%t\n", ba.IsTeammate) 49 | if ba.TeamMemberId != "" { 50 | fmt.Fprintf(w, "Team Member Id:\t%s\n", ba.TeamMemberId) 51 | } 52 | fmt.Fprintf(w, "Profile Photo URL:\t%s\n", ba.ProfilePhotoUrl) 53 | } 54 | 55 | func account(cmd *cobra.Command, args []string) error { 56 | if len(args) > 1 { 57 | return errors.New("`account` accepts an optional `id` argument") 58 | } 59 | 60 | dbx := users.New(config) 61 | w := new(tabwriter.Writer) 62 | w.Init(os.Stdout, 4, 8, 1, ' ', 0) 63 | 64 | if len(args) == 0 { 65 | // If no arguments are provided get the current user's account 66 | res, err := dbx.GetCurrentAccount() 67 | if err != nil { 68 | return err 69 | } 70 | printFullAccount(w, res) 71 | } else { 72 | // Otherwise look up an account with the provided ID 73 | arg := users.NewGetAccountArg(args[0]) 74 | res, err := dbx.GetAccount(arg) 75 | if err != nil { 76 | return err 77 | } 78 | printBasicAccount(w, res) 79 | } 80 | 81 | return w.Flush() 82 | } 83 | 84 | var accountCmd = &cobra.Command{ 85 | Use: "account [flags] []", 86 | Short: "Display account information", 87 | Example: " dbxcli account\n dbxcli account dbid:xxxx", 88 | RunE: account, 89 | } 90 | 91 | func init() { 92 | RootCmd.AddCommand(accountCmd) 93 | } 94 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # `dbxcli`: A command line tool for Dropbox users and team admins [UNOFFICIAL] 2 | 3 | [![Build Status](https://travis-ci.org/dropbox/dbxcli.svg?branch=master)](https://travis-ci.org/dropbox/dbxcli) 4 | [![Go Report Card](https://goreportcard.com/badge/github.com/dropbox/dbxcli)](https://goreportcard.com/report/github.com/dropbox/dbxcli) 5 | 6 | :warning: WARNING: This project is **NOT official**. What does this mean? 7 | 8 | * There is no formal Dropbox support for this project 9 | * Bugs may or may not get fixed 10 | * Not all SDK features may be implemented and implemented features may be buggy or incorrect 11 | 12 | ## Features 13 | 14 | * Supports basic file operations like ls, cp, mkdir, mv (via the Files API) 15 | * Supports search 16 | * Supports file revisions and file restore 17 | * Chunked uploads for large files, paginated listing for large directories 18 | * Supports a growing set of Team operations 19 | 20 | ## Installation 21 | 22 | Download pre-compiled binaries for Mac, Windows and Linux from the [releases](https://github.com/dropbox/dbxcli/releases) page. 23 | 24 | ### Mac OSX Installation of pre-compiled binaries 25 | These instructions aim to help both experts and novice `dbxcli` users. Please submit an issue if they don't work for you. 26 | 27 | 1. Make sure you download and place the binary in a folder that's on your `$PATH`. If you are unsure what this means, go to *step 2*. Otherwise, skip to *step 3* 28 | 2. Create a `bin` directory under your home directory. 29 | ``` 30 | $ mkdir ~/bin 31 | $ cd ~/bin 32 | ``` 33 | 3. Add the following line at the end of your `~/.bash_profile` file. [Link with instructions](https://natelandau.com/my-mac-osx-bash_profile/) on how to find this file 34 | ```sh 35 | export PATH=$PATH:$HOME/bin 36 | ``` 37 | 4. Download the `dbxcli` binary for OSX and rename it. *IMPORTANT:* Check that the tag `v2.1.1` on the URL below is the latest release tag on the [Releases](https://github.com/dropbox/dbxcli/releases) page. 38 | ```sh 39 | $ wget https://github.com/dropbox/dbxcli/releases/download/v2.1.1/dbxcli-darwin-amd64 40 | $ mv dbxcli-darwin-amd64 dbxcli 41 | ``` 42 | 5. Finally, make the binary an executable file and you are good to go! 43 | ``` 44 | $ chmod +x dbxcli 45 | ``` 46 | 47 | ### Instructions for building yourself 48 | For newcomers the go build process can be a bit arcane, these steps can be followed to build `dbxcli` yourself. 49 | 50 | 1. Make sure `git`, `go`, and `gox` are installed. 51 | 2. Create a Go folder. For example, `mkdir $HOME/go` or `mkdir $HOME/.go`. Navigate to it. 52 | 3. `go get github.com/dropbox/dbxcli`. That's right, you don't manually clone it, this does it for you. 53 | 4. `cd ~/go/src/github.com/dropbox/dbxcli` (adapt accordingly based on step 2). 54 | 55 | Now we need to pause for a second to get development keys. 56 | 57 | 5. Head to `https://www.dropbox.com/developers/apps` (sign in if necessary) and choose "Create app". Use the Dropbox API and give it Full Dropbox access. Name and create the app. 58 | 6. You'll be presented with a dashboard with an "App key" and an "App secret". 59 | 7. Replace the value for `personalAppKey` in `root.go` with the key from the webpage. 60 | 8. Replace the value for `personalAppSecret` with the secret from the webpage. 61 | 62 | Finally we're ready to build. Run `go build`, and you'll see a `dbxcli` binary has been created in the current directory. Congrats, we're done! 63 | 64 | ## Usage 65 | 66 | `dbxcli` is largely self documenting. Run `dbxcli -h` for a list of supported commands: 67 | 68 | ```sh 69 | $ dbxcli --help 70 | Use dbxcli to quickly interact with your Dropbox, upload/download files, 71 | manage your team and more. It is easy, scriptable and works on all platforms! 72 | 73 | Usage: 74 | dbxcli [command] 75 | 76 | Available Commands: 77 | cp Copy files 78 | du Display usage information 79 | get Download a file 80 | ls List files 81 | mkdir Create a new directory 82 | mv Move files 83 | put Upload files 84 | restore Restore files 85 | revs List file revisions 86 | rm Remove files 87 | search Search 88 | team Team management commands 89 | version Print version information 90 | 91 | Flags: 92 | --as-member string Member ID to perform action as 93 | -v, --verbose Enable verbose logging 94 | 95 | Use "dbxcli [command] --help" for more information about a command. 96 | 97 | $ dbxcli team --help 98 | Team management commands 99 | 100 | Usage: 101 | dbxcli team [command] 102 | 103 | Available Commands: 104 | add-member Add a new member to a team 105 | info Get team information 106 | list-groups List groups 107 | list-members List team members 108 | remove-member Remove member from a team 109 | 110 | Global Flags: 111 | --as-member string Member ID to perform action as 112 | -v, --verbose Enable verbose logging 113 | 114 | Use "dbxcli team [command] --help" for more information about a command. 115 | ``` 116 | 117 | The `--verbose` option will turn on verbose logging and is useful for debugging. 118 | 119 | ## Contributing 120 | 121 | * Step 1: If you're submitting a non-trivial change, please fill out the [Dropbox Contributor License Agreement](https://opensource.dropbox.com/cla/) first. 122 | * Step 2: send a [pull request](https://help.github.com/articles/using-pull-requests/) 123 | * Step 3: Profit! 124 | 125 | ## Useful Resources 126 | 127 | * [Go SDK documentation](https://godoc.org/github.com/dropbox/dropbox-sdk-go-unofficial) 128 | * [API documentation](https://www.dropbox.com/developers/documentation/http/documentation) 129 | -------------------------------------------------------------------------------- /cmd/put.go: -------------------------------------------------------------------------------- 1 | // Copyright © 2016 Dropbox, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package cmd 16 | 17 | import ( 18 | "bytes" 19 | "errors" 20 | "fmt" 21 | "io" 22 | "io/ioutil" 23 | "log" 24 | "os" 25 | "path" 26 | "sync" 27 | "time" 28 | 29 | "github.com/dropbox/dropbox-sdk-go-unofficial/v6/dropbox/auth" 30 | "github.com/dropbox/dropbox-sdk-go-unofficial/v6/dropbox/files" 31 | "github.com/dustin/go-humanize" 32 | "github.com/mitchellh/ioprogress" 33 | "github.com/spf13/cobra" 34 | ) 35 | 36 | const singleShotUploadSizeCutoff int64 = 32 * (1 << 20) 37 | 38 | type uploadChunk struct { 39 | data []byte 40 | offset uint64 41 | close bool 42 | } 43 | 44 | func uploadOneChunk(dbx files.Client, args *files.UploadSessionAppendArg, data []byte) error { 45 | for { 46 | err := dbx.UploadSessionAppendV2(args, bytes.NewReader(data)) 47 | if err != nil { 48 | switch errt := err.(type) { 49 | case auth.RateLimitAPIError: 50 | time.Sleep(time.Second * time.Duration(errt.RateLimitError.RetryAfter)) 51 | continue 52 | default: 53 | return err 54 | } 55 | } 56 | return nil 57 | } 58 | } 59 | 60 | func uploadChunked(dbx files.Client, r io.Reader, commitInfo *files.CommitInfo, sizeTotal int64, workers int, chunkSize int64, debug bool) (err error) { 61 | t0 := time.Now() 62 | startArgs := files.NewUploadSessionStartArg() 63 | startArgs.SessionType = &files.UploadSessionType{} 64 | startArgs.SessionType.Tag = files.UploadSessionTypeConcurrent 65 | res, err := dbx.UploadSessionStart(startArgs, nil) 66 | if err != nil { 67 | return 68 | } 69 | if debug { 70 | log.Printf("Start took: %v\n", time.Since(t0)) 71 | } 72 | 73 | t1 := time.Now() 74 | wg := sync.WaitGroup{} 75 | workCh := make(chan uploadChunk, workers) 76 | errCh := make(chan error, 1) 77 | for i := 0; i < workers; i++ { 78 | wg.Add(1) 79 | go func() { 80 | defer wg.Done() 81 | for chunk := range workCh { 82 | cursor := files.NewUploadSessionCursor(res.SessionId, chunk.offset) 83 | args := files.NewUploadSessionAppendArg(cursor) 84 | args.Close = chunk.close 85 | 86 | t0 := time.Now() 87 | if err := uploadOneChunk(dbx, args, chunk.data); err != nil { 88 | errCh <- err 89 | } 90 | if debug { 91 | log.Printf("Chunk upload at offset %d took: %v\n", chunk.offset, time.Since(t0)) 92 | } 93 | } 94 | }() 95 | } 96 | 97 | written := int64(0) 98 | for written < sizeTotal { 99 | data, err := ioutil.ReadAll(&io.LimitedReader{R: r, N: chunkSize}) 100 | if err != nil { 101 | return err 102 | } 103 | expectedLen := chunkSize 104 | if written+chunkSize > sizeTotal { 105 | expectedLen = sizeTotal - written 106 | } 107 | if len(data) != int(expectedLen) { 108 | return fmt.Errorf("failed to read %d bytes from source", expectedLen) 109 | } 110 | 111 | chunk := uploadChunk{ 112 | data: data, 113 | offset: uint64(written), 114 | close: written+chunkSize >= sizeTotal, 115 | } 116 | 117 | select { 118 | case workCh <- chunk: 119 | case err := <-errCh: 120 | return err 121 | } 122 | 123 | written += int64(len(data)) 124 | } 125 | 126 | close(workCh) 127 | wg.Wait() 128 | select { 129 | case err := <-errCh: 130 | return err 131 | default: 132 | } 133 | if debug { 134 | log.Printf("Full upload took: %v\n", time.Since(t1)) 135 | } 136 | 137 | t2 := time.Now() 138 | cursor := files.NewUploadSessionCursor(res.SessionId, uint64(written)) 139 | args := files.NewUploadSessionFinishArg(cursor, commitInfo) 140 | _, err = dbx.UploadSessionFinish(args, nil) 141 | if debug { 142 | log.Printf("Finish took: %v\n", time.Since(t2)) 143 | } 144 | return 145 | } 146 | 147 | func put(cmd *cobra.Command, args []string) (err error) { 148 | if len(args) == 0 || len(args) > 2 { 149 | return errors.New("`put` requires `src` and/or `dst` arguments") 150 | } 151 | 152 | chunkSize, err := cmd.Flags().GetInt64("chunksize") 153 | if err != nil { 154 | return err 155 | } 156 | if chunkSize%(1<<22) != 0 { 157 | return errors.New("`put` requires chunk size to be multiple of 4MiB") 158 | } 159 | workers, err := cmd.Flags().GetInt("workers") 160 | if err != nil { 161 | return err 162 | } 163 | if workers < 1 { 164 | workers = 1 165 | } 166 | debug, _ := cmd.Flags().GetBool("debug") 167 | 168 | src := args[0] 169 | 170 | // Default `dst` to the base segment of the source path; use the second argument if provided. 171 | dst := "/" + path.Base(src) 172 | if len(args) == 2 { 173 | dst, err = validatePath(args[1]) 174 | if err != nil { 175 | return 176 | } 177 | } 178 | 179 | contents, err := os.Open(src) 180 | if err != nil { 181 | return 182 | } 183 | defer contents.Close() 184 | 185 | contentsInfo, err := contents.Stat() 186 | if err != nil { 187 | return 188 | } 189 | 190 | progressbar := &ioprogress.Reader{ 191 | Reader: contents, 192 | DrawFunc: ioprogress.DrawTerminalf(os.Stderr, func(progress, total int64) string { 193 | return fmt.Sprintf("Uploading %s/%s", 194 | humanize.IBytes(uint64(progress)), humanize.IBytes(uint64(total))) 195 | }), 196 | Size: contentsInfo.Size(), 197 | } 198 | 199 | commitInfo := files.NewCommitInfo(dst) 200 | commitInfo.Mode.Tag = "overwrite" 201 | 202 | // The Dropbox API only accepts timestamps in UTC with second precision. 203 | ts := time.Now().UTC().Round(time.Second) 204 | commitInfo.ClientModified = &ts 205 | 206 | dbx := files.New(config) 207 | if contentsInfo.Size() > singleShotUploadSizeCutoff { 208 | return uploadChunked(dbx, progressbar, commitInfo, contentsInfo.Size(), workers, chunkSize, debug) 209 | } 210 | 211 | if _, err = dbx.Upload(commitInfo, progressbar); err != nil { 212 | return 213 | } 214 | 215 | return 216 | } 217 | 218 | // putCmd represents the put command 219 | var putCmd = &cobra.Command{ 220 | Use: "put [flags] []", 221 | Short: "Upload a single file", 222 | Long: `Upload a single file 223 | - If target is not provided puts the file in the root of your Dropbox directory. 224 | - If target is provided it must be the desired filename in the cloud (and not a directory). 225 | `, 226 | 227 | RunE: put, 228 | } 229 | 230 | func init() { 231 | RootCmd.AddCommand(putCmd) 232 | putCmd.Flags().IntP("workers", "w", 4, "Number of concurrent upload workers to use") 233 | putCmd.Flags().Int64P("chunksize", "c", 1<<24, "Chunk size to use (should be multiple of 4MiB)") 234 | putCmd.Flags().BoolP("debug", "d", false, "Print debug timing") 235 | } 236 | -------------------------------------------------------------------------------- /cmd/ls.go: -------------------------------------------------------------------------------- 1 | // Copyright © 2016 Dropbox, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package cmd 16 | 17 | import ( 18 | "fmt" 19 | "io" 20 | "os" 21 | "text/tabwriter" 22 | 23 | "github.com/dropbox/dropbox-sdk-go-unofficial/v6/dropbox/files" 24 | "github.com/dustin/go-humanize" 25 | "github.com/spf13/cobra" 26 | ) 27 | 28 | const deletedItemFormatString = "<<%s>>" 29 | 30 | // Sends a get_metadata request for a given path and returns the response 31 | func getFileMetadata(c files.Client, path string) (files.IsMetadata, error) { 32 | arg := files.NewGetMetadataArg(path) 33 | 34 | arg.IncludeDeleted = true 35 | 36 | res, err := c.GetMetadata(arg) 37 | if err != nil { 38 | return nil, err 39 | } 40 | 41 | return res, nil 42 | } 43 | 44 | // Invoked by search.go 45 | func printFolderMetadata(w io.Writer, e *files.FolderMetadata, longFormat bool) { 46 | fmt.Fprintf(w, formatFolderMetadata(e, longFormat)) 47 | } 48 | 49 | // Invoked by search.go and revs.go 50 | func printFileMetadata(w io.Writer, e *files.FileMetadata, longFormat bool) { 51 | fmt.Fprintf(w, formatFileMetadata(e, longFormat)) 52 | } 53 | 54 | func formatFolderMetadata(e *files.FolderMetadata, longFormat bool) string { 55 | text := fmt.Sprintf("%s\t", e.PathDisplay) 56 | if longFormat { 57 | text = fmt.Sprintf("-\t-\t-\t") + text 58 | } 59 | return text 60 | } 61 | 62 | func formatFileMetadata(e *files.FileMetadata, longFormat bool) string { 63 | text := fmt.Sprintf("%s\t", e.PathDisplay) 64 | if longFormat { 65 | text = fmt.Sprintf("%s\t%s\t%s\t", e.Rev, humanize.IBytes(e.Size), humanize.Time(e.ServerModified)) + text 66 | } 67 | return text 68 | } 69 | 70 | func formatDeletedMetadata(e *files.DeletedMetadata, longFormat bool) string { 71 | text := fmt.Sprintf("%s\t", e.PathDisplay) 72 | if longFormat { 73 | text = fmt.Sprintf("-\t-\t-\t") + text 74 | } 75 | return text 76 | } 77 | 78 | func SetPathDisplayAsDeleted(metadata files.IsMetadata) { 79 | switch item := metadata.(type) { 80 | case *files.FileMetadata: 81 | item.PathDisplay = fmt.Sprintf(deletedItemFormatString, item.PathDisplay) 82 | case *files.FolderMetadata: 83 | item.PathDisplay = fmt.Sprintf(deletedItemFormatString, item.PathDisplay) 84 | case *files.DeletedMetadata: 85 | item.PathDisplay = fmt.Sprintf(deletedItemFormatString, item.PathDisplay) 86 | } 87 | } 88 | 89 | func ls(cmd *cobra.Command, args []string) (err error) { 90 | 91 | path := "" 92 | if len(args) > 0 { 93 | if path, err = validatePath(args[0]); err != nil { 94 | return err 95 | } 96 | } 97 | 98 | arg := files.NewListFolderArg(path) 99 | arg.Recursive, _ = cmd.Flags().GetBool("recurse") 100 | arg.IncludeDeleted, _ = cmd.Flags().GetBool("include-deleted") 101 | onlyDeleted, _ := cmd.Flags().GetBool("only-deleted") 102 | arg.IncludeDeleted = arg.IncludeDeleted || onlyDeleted 103 | long, _ := cmd.Flags().GetBool("long") 104 | 105 | w := new(tabwriter.Writer) 106 | w.Init(os.Stdout, 4, 8, 1, ' ', 0) 107 | itemCounter := 0 108 | printItem := func(message string) { 109 | itemCounter = itemCounter + 1 110 | fmt.Fprint(w, message) 111 | if (itemCounter%4 == 0) || long { 112 | fmt.Fprintln(w) 113 | } 114 | } 115 | 116 | dbx := files.New(config) 117 | 118 | // check if given object exists 119 | var metaRes files.IsMetadata 120 | metaRes, err = getFileMetadata(dbx, path) 121 | if err != nil { 122 | return err 123 | } 124 | 125 | if long { 126 | fmt.Fprint(w, "Revision\tSize\tLast modified\tPath\n") 127 | } 128 | 129 | // handle case when path to file was given 130 | switch f := metaRes.(type) { 131 | case *files.FileMetadata: 132 | if !onlyDeleted { 133 | printItem(formatFileMetadata(f, long)) 134 | err = w.Flush() 135 | return err 136 | } 137 | } 138 | 139 | res, err := dbx.ListFolder(arg) 140 | 141 | var entries []files.IsMetadata 142 | if err != nil { 143 | listRevisionError, ok := err.(files.ListRevisionsAPIError) 144 | if ok { 145 | // Don't treat a "not_folder" error as fatal; recover by sending a 146 | // get_metadata request for the same path and using that response instead. 147 | if listRevisionError.EndpointError.Path.Tag == files.LookupErrorNotFolder { 148 | var metaRes files.IsMetadata 149 | metaRes, err = getFileMetadata(dbx, path) 150 | entries = []files.IsMetadata{metaRes} 151 | } else { 152 | // Return if there's an error other than "not_folder" or if the follow-up 153 | // metadata request fails. 154 | return err 155 | } 156 | } else { 157 | return err 158 | } 159 | } else { 160 | entries = res.Entries 161 | 162 | for res.HasMore { 163 | arg := files.NewListFolderContinueArg(res.Cursor) 164 | 165 | res, err = dbx.ListFolderContinue(arg) 166 | if err != nil { 167 | return err 168 | } 169 | 170 | entries = append(entries, res.Entries...) 171 | } 172 | } 173 | 174 | for _, entry := range entries { 175 | deletedItem, isDeleted := entry.(*files.DeletedMetadata) 176 | if isDeleted { 177 | revisionArg := files.NewListRevisionsArg(deletedItem.PathLower) 178 | res, err := dbx.ListRevisions(revisionArg) 179 | if err != nil { 180 | listRevisionError, ok := err.(files.ListRevisionsAPIError) 181 | if ok { 182 | // We have a ListRevisionsAPIERror 183 | if listRevisionError.EndpointError.Path.Tag == files.LookupErrorNotFile { 184 | // Don't treat a "not_file" error as fatal; recover by sending a 185 | // get_metadata request for the same path and using that response instead. 186 | revision, err := getFileMetadata(dbx, deletedItem.PathLower) 187 | if err != nil { 188 | return err 189 | } 190 | entry = revision 191 | } 192 | } 193 | } else if len(res.Entries) == 0 { 194 | // Occasionally revisions will be returned with an empty Revision entry list. 195 | // So we just use the original entry. 196 | } else { 197 | entry = res.Entries[0] 198 | } 199 | SetPathDisplayAsDeleted(entry) 200 | } 201 | switch f := entry.(type) { 202 | case *files.FileMetadata: 203 | if !onlyDeleted { 204 | printItem(formatFileMetadata(f, long)) 205 | } 206 | case *files.FolderMetadata: 207 | if !onlyDeleted { 208 | printItem(formatFolderMetadata(f, long)) 209 | } 210 | case *files.DeletedMetadata: 211 | printItem(formatDeletedMetadata(f, long)) 212 | } 213 | } 214 | 215 | err = w.Flush() 216 | return err 217 | } 218 | 219 | // lsCmd represents the ls command 220 | var lsCmd = &cobra.Command{ 221 | Use: "ls [flags] []", 222 | Short: "List files and folders", 223 | Example: ` dbxcli ls / # Or just 'ls' 224 | dbxcli ls /some-folder # Or 'ls some-folder' 225 | dbxcli ls /some-folder/some-file.pdf 226 | dbxcli ls -l`, 227 | RunE: ls, 228 | } 229 | 230 | func init() { 231 | RootCmd.AddCommand(lsCmd) 232 | 233 | lsCmd.Flags().BoolP("long", "l", false, "Long listing") 234 | lsCmd.Flags().BoolP("recurse", "R", false, "Recursively list all subfolders") 235 | lsCmd.Flags().BoolP("include-deleted", "d", false, "Include deleted files") 236 | lsCmd.Flags().BoolP("only-deleted", "D", false, "Only show deleted files") 237 | } 238 | -------------------------------------------------------------------------------- /cmd/root.go: -------------------------------------------------------------------------------- 1 | // Copyright © 2016 Dropbox, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package cmd 16 | 17 | import ( 18 | "encoding/json" 19 | "fmt" 20 | "io/ioutil" 21 | "os" 22 | "path" 23 | "path/filepath" 24 | "strings" 25 | 26 | "golang.org/x/oauth2" 27 | 28 | "github.com/dropbox/dropbox-sdk-go-unofficial/v6/dropbox" 29 | "github.com/dropbox/dropbox-sdk-go-unofficial/v6/dropbox/files" 30 | "github.com/mitchellh/go-homedir" 31 | "github.com/spf13/cobra" 32 | "golang.org/x/net/context" 33 | ) 34 | 35 | const ( 36 | configFileName = "auth.json" 37 | tokenPersonal = "personal" 38 | tokenTeamAccess = "teamAccess" 39 | tokenTeamManage = "teamManage" 40 | ) 41 | 42 | func getEnv(key, fallback string) string { 43 | value, exists := os.LookupEnv(key) 44 | if !exists { 45 | value = fallback 46 | } 47 | return value 48 | } 49 | 50 | var ( 51 | personalAppKey = "mvhz183vwqibe7q" 52 | personalAppSecret = "q0kquhzgetjwcz1" 53 | teamAccessAppKey = "zud1va492pnehkc" 54 | teamAccessAppSecret = "p3ginm1gy0kmj54" 55 | teamManageAppKey = "xxe04eai4wmlitv" 56 | teamManageAppSecret = "t8ms714yun7nu5s" 57 | ) 58 | 59 | // TokenMap maps domains to a map of commands to tokens. 60 | // For each domain, we want to save different tokens depending on the 61 | // command type: personal, team access and team manage 62 | type TokenMap map[string]map[string]string 63 | 64 | var config dropbox.Config 65 | 66 | func oauthConfig(tokenType string, domain string) *oauth2.Config { 67 | var appKey, appSecret string 68 | switch tokenType { 69 | case "personal": 70 | appKey, appSecret = personalAppKey, personalAppSecret 71 | case "teamAccess": 72 | appKey, appSecret = teamAccessAppKey, teamAccessAppSecret 73 | case "teamManage": 74 | appKey, appSecret = teamManageAppKey, teamManageAppSecret 75 | } 76 | return &oauth2.Config{ 77 | ClientID: appKey, 78 | ClientSecret: appSecret, 79 | Endpoint: dropbox.OAuthEndpoint(domain), 80 | } 81 | } 82 | 83 | func validatePath(p string) (path string, err error) { 84 | path = p 85 | 86 | if !strings.HasPrefix(path, "/") { 87 | path = fmt.Sprintf("/%s", path) 88 | } 89 | 90 | path = strings.TrimSuffix(path, "/") 91 | 92 | return 93 | } 94 | 95 | func makeRelocationArg(s string, d string) (arg *files.RelocationArg, err error) { 96 | src, err := validatePath(s) 97 | if err != nil { 98 | return 99 | } 100 | dst, err := validatePath(d) 101 | if err != nil { 102 | return 103 | } 104 | 105 | arg = files.NewRelocationArg(src, dst) 106 | 107 | return 108 | } 109 | 110 | func readTokens(filePath string) (TokenMap, error) { 111 | b, err := ioutil.ReadFile(filePath) 112 | if err != nil { 113 | return nil, err 114 | } 115 | 116 | var tokens TokenMap 117 | if json.Unmarshal(b, &tokens) != nil { 118 | return nil, err 119 | } 120 | 121 | return tokens, nil 122 | } 123 | 124 | func writeTokens(filePath string, tokens TokenMap) { 125 | // Check if file exists 126 | if _, err := os.Stat(filePath); os.IsNotExist(err) { 127 | // Doesn't exist; lets create it 128 | err = os.MkdirAll(filepath.Dir(filePath), 0700) 129 | if err != nil { 130 | return 131 | } 132 | } 133 | 134 | // At this point, file must exist. Lets (over)write it. 135 | b, err := json.Marshal(tokens) 136 | if err != nil { 137 | return 138 | } 139 | if err = ioutil.WriteFile(filePath, b, 0600); err != nil { 140 | return 141 | } 142 | } 143 | 144 | func tokenType(cmd *cobra.Command) string { 145 | if cmd.Parent().Name() == "team" { 146 | return tokenTeamManage 147 | } 148 | if asMember, _ := cmd.Flags().GetString("as-member"); asMember != "" { 149 | return tokenTeamAccess 150 | } 151 | return tokenPersonal 152 | } 153 | 154 | func initDbx(cmd *cobra.Command, args []string) (err error) { 155 | verbose, _ := cmd.Flags().GetBool("verbose") 156 | asMember, _ := cmd.Flags().GetString("as-member") 157 | domain, _ := cmd.Flags().GetString("domain") 158 | 159 | dir, err := homedir.Dir() 160 | if err != nil { 161 | return 162 | } 163 | filePath := path.Join(dir, ".config", "dbxcli", configFileName) 164 | tokType := tokenType(cmd) 165 | conf := oauthConfig(tokType, domain) 166 | 167 | tokenMap, err := readTokens(filePath) 168 | if tokenMap == nil { 169 | tokenMap = make(TokenMap) 170 | } 171 | if tokenMap[domain] == nil { 172 | tokenMap[domain] = make(map[string]string) 173 | } 174 | tokens := tokenMap[domain] 175 | 176 | if err != nil || tokens[tokType] == "" { 177 | fmt.Printf("1. Go to %v\n", conf.AuthCodeURL("state")) 178 | fmt.Printf("2. Click \"Allow\" (you might have to log in first).\n") 179 | fmt.Printf("3. Copy the authorization code.\n") 180 | fmt.Printf("Enter the authorization code here: ") 181 | 182 | var code string 183 | if _, err = fmt.Scan(&code); err != nil { 184 | return 185 | } 186 | var token *oauth2.Token 187 | ctx := context.Background() 188 | token, err = conf.Exchange(ctx, code) 189 | if err != nil { 190 | return 191 | } 192 | tokens[tokType] = token.AccessToken 193 | writeTokens(filePath, tokenMap) 194 | } 195 | 196 | logLevel := dropbox.LogOff 197 | if verbose { 198 | logLevel = dropbox.LogInfo 199 | } 200 | config = dropbox.Config{ 201 | Token: tokens[tokType], 202 | LogLevel: logLevel, 203 | Logger: nil, 204 | AsMemberID: asMember, 205 | Domain: domain, 206 | Client: nil, 207 | HeaderGenerator: nil, 208 | URLGenerator: nil, 209 | } 210 | 211 | return 212 | } 213 | 214 | // RootCmd represents the base command when called without any subcommands 215 | var RootCmd = &cobra.Command{ 216 | Use: "dbxcli", 217 | Short: "A command line tool for Dropbox users and team admins", 218 | Long: `Use dbxcli to quickly interact with your Dropbox, upload/download files, 219 | manage your team and more. It is easy, scriptable and works on all platforms!`, 220 | SilenceUsage: true, 221 | PersistentPreRunE: initDbx, 222 | } 223 | 224 | // Execute adds all child commands to the root command sets flags appropriately. 225 | // This is called by main.main(). It only needs to happen once to the rootCmd. 226 | func Execute() { 227 | if err := RootCmd.Execute(); err != nil { 228 | os.Exit(-1) 229 | } 230 | } 231 | 232 | func init() { 233 | RootCmd.PersistentFlags().BoolP("verbose", "v", false, "Enable verbose logging") 234 | RootCmd.PersistentFlags().String("as-member", "", "Member ID to perform action as") 235 | // This flag should only be used for testing. Marked hidden so it doesn't clutter usage etc. 236 | RootCmd.PersistentFlags().String("domain", "", "Override default Dropbox domain, useful for testing") 237 | RootCmd.PersistentFlags().MarkHidden("domain") 238 | 239 | personalAppKey = getEnv("DROPBOX_PERSONAL_APP_KEY", personalAppKey) 240 | personalAppSecret = getEnv("DROPBOX_PERSONAL_APP_SECRET", personalAppSecret) 241 | teamAccessAppKey = getEnv("DROPBOX_TEAM_APP_KEY", teamAccessAppKey) 242 | teamAccessAppSecret = getEnv("DROPBOX_TEAM_APP_SECRET", teamAccessAppSecret) 243 | teamManageAppKey = getEnv("DROPBOX_MANAGE_APP_KEY", teamManageAppKey) 244 | teamManageAppSecret = getEnv("DROPBOX_MANAGE_APP_SECRET", teamAccessAppSecret) 245 | } 246 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | 3 | ## [Unreleased](https://github.com/dropbox/dbxcli/tree/HEAD) 4 | 5 | [Full Changelog](https://github.com/dropbox/dbxcli/compare/v2.0.8...HEAD) 6 | 7 | **Closed issues:** 8 | 9 | - Invalid client\_id when trying to get authorization code [\#79](https://github.com/dropbox/dbxcli/issues/79) 10 | - Build official binaries for more OS [\#76](https://github.com/dropbox/dbxcli/issues/76) 11 | 12 | ## [v2.0.8](https://github.com/dropbox/dbxcli/tree/v2.0.8) (2017-11-10) 13 | [Full Changelog](https://github.com/dropbox/dbxcli/compare/v2.0.7...v2.0.8) 14 | 15 | ## [v2.0.7](https://github.com/dropbox/dbxcli/tree/v2.0.7) (2017-11-10) 16 | [Full Changelog](https://github.com/dropbox/dbxcli/compare/v2.0.6...v2.0.7) 17 | 18 | **Closed issues:** 19 | 20 | - Better output for ls [\#78](https://github.com/dropbox/dbxcli/issues/78) 21 | 22 | ## [v2.0.6](https://github.com/dropbox/dbxcli/tree/v2.0.6) (2017-07-26) 23 | [Full Changelog](https://github.com/dropbox/dbxcli/compare/v2.0.5...v2.0.6) 24 | 25 | **Implemented enhancements:** 26 | 27 | - Add `--recurse` option to `ls` [\#74](https://github.com/dropbox/dbxcli/issues/74) 28 | 29 | **Merged pull requests:** 30 | 31 | - Add `account` command [\#15](https://github.com/dropbox/dbxcli/pull/15) ([waits](https://github.com/waits)) 32 | 33 | ## [v2.0.5](https://github.com/dropbox/dbxcli/tree/v2.0.5) (2017-07-26) 34 | [Full Changelog](https://github.com/dropbox/dbxcli/compare/v2.0.4...v2.0.5) 35 | 36 | ## [v2.0.4](https://github.com/dropbox/dbxcli/tree/v2.0.4) (2017-07-26) 37 | [Full Changelog](https://github.com/dropbox/dbxcli/compare/v2.0.3...v2.0.4) 38 | 39 | **Closed issues:** 40 | 41 | - Usage question with wildcards [\#72](https://github.com/dropbox/dbxcli/issues/72) 42 | - Could not able to copy a tar.gz file in my dropbox folder [\#69](https://github.com/dropbox/dbxcli/issues/69) 43 | - v2.0.2 in Windows ls not returning results unless verbose [\#68](https://github.com/dropbox/dbxcli/issues/68) 44 | - Rename a file? [\#66](https://github.com/dropbox/dbxcli/issues/66) 45 | 46 | ## [v2.0.3](https://github.com/dropbox/dbxcli/tree/v2.0.3) (2017-07-24) 47 | [Full Changelog](https://github.com/dropbox/dbxcli/compare/v2.0.2...v2.0.3) 48 | 49 | **Closed issues:** 50 | 51 | - How do I install this program? [\#67](https://github.com/dropbox/dbxcli/issues/67) 52 | - Unable to Authorize App [\#64](https://github.com/dropbox/dbxcli/issues/64) 53 | 54 | **Merged pull requests:** 55 | 56 | - Switch to `dep` and update dependencies [\#73](https://github.com/dropbox/dbxcli/pull/73) ([diwakergupta](https://github.com/diwakergupta)) 57 | 58 | ## [v2.0.2](https://github.com/dropbox/dbxcli/tree/v2.0.2) (2017-02-27) 59 | [Full Changelog](https://github.com/dropbox/dbxcli/compare/v2.0.1...v2.0.2) 60 | 61 | **Fixed bugs:** 62 | 63 | - `get` does not work on JPG files [\#59](https://github.com/dropbox/dbxcli/issues/59) 64 | 65 | **Closed issues:** 66 | 67 | - Can't authorize team management [\#58](https://github.com/dropbox/dbxcli/issues/58) 68 | 69 | **Merged pull requests:** 70 | 71 | - add build support for raspberry pi \(ARM\) [\#65](https://github.com/dropbox/dbxcli/pull/65) ([garyemerson](https://github.com/garyemerson)) 72 | 73 | ## [v2.0.1](https://github.com/dropbox/dbxcli/tree/v2.0.1) (2017-02-14) 74 | [Full Changelog](https://github.com/dropbox/dbxcli/compare/v2.0.0...v2.0.1) 75 | 76 | **Closed issues:** 77 | 78 | - Invalid client\_id when trying to get authorization code [\#62](https://github.com/dropbox/dbxcli/issues/62) 79 | - Generating authorization code no longer works [\#61](https://github.com/dropbox/dbxcli/issues/61) 80 | 81 | ## [v2.0.0](https://github.com/dropbox/dbxcli/tree/v2.0.0) (2017-01-26) 82 | [Full Changelog](https://github.com/dropbox/dbxcli/compare/v1.4.0...v2.0.0) 83 | 84 | **Closed issues:** 85 | 86 | - build instructions in readme [\#57](https://github.com/dropbox/dbxcli/issues/57) 87 | - Authorization error - app has reached its team limit [\#47](https://github.com/dropbox/dbxcli/issues/47) 88 | - `search` should show a full path to matching files [\#42](https://github.com/dropbox/dbxcli/issues/42) 89 | - Recursive and force flags for rm [\#26](https://github.com/dropbox/dbxcli/issues/26) 90 | 91 | **Merged pull requests:** 92 | 93 | - Display full path [\#55](https://github.com/dropbox/dbxcli/pull/55) ([hut8](https://github.com/hut8)) 94 | - Add multiple args to rm [\#49](https://github.com/dropbox/dbxcli/pull/49) ([GrantSeltzer](https://github.com/GrantSeltzer)) 95 | - Update Golumns package [\#48](https://github.com/dropbox/dbxcli/pull/48) ([GrantSeltzer](https://github.com/GrantSeltzer)) 96 | - Add force flag for `rm`ing non-empty directories, remove `rmdir` [\#43](https://github.com/dropbox/dbxcli/pull/43) ([GrantSeltzer](https://github.com/GrantSeltzer)) 97 | 98 | ## [v1.4.0](https://github.com/dropbox/dbxcli/tree/v1.4.0) (2016-08-01) 99 | [Full Changelog](https://github.com/dropbox/dbxcli/compare/v1.3.0...v1.4.0) 100 | 101 | **Merged pull requests:** 102 | 103 | - Update golumns package to latest version - major bug fix [\#44](https://github.com/dropbox/dbxcli/pull/44) ([GrantSeltzer](https://github.com/GrantSeltzer)) 104 | - Adds another subcommand layer `share list`. [\#39](https://github.com/dropbox/dbxcli/pull/39) ([bonafidehan](https://github.com/bonafidehan)) 105 | - Adds `share list-links`. Paging for `share list-folders`. [\#38](https://github.com/dropbox/dbxcli/pull/38) ([bonafidehan](https://github.com/bonafidehan)) 106 | - Introduces `share` command and `list-folders` subcommand. [\#37](https://github.com/dropbox/dbxcli/pull/37) ([bonafidehan](https://github.com/bonafidehan)) 107 | - Introduces scoped search. A search can be scoped to the provided folder. [\#36](https://github.com/dropbox/dbxcli/pull/36) ([bonafidehan](https://github.com/bonafidehan)) 108 | - Replace strings with consts defined in root.go [\#33](https://github.com/dropbox/dbxcli/pull/33) ([GrantSeltzer](https://github.com/GrantSeltzer)) 109 | - Allow for multiple arguments to cp [\#32](https://github.com/dropbox/dbxcli/pull/32) ([GrantSeltzer](https://github.com/GrantSeltzer)) 110 | - Add `logout` command [\#23](https://github.com/dropbox/dbxcli/pull/23) ([waits](https://github.com/waits)) 111 | 112 | ## [v1.3.0](https://github.com/dropbox/dbxcli/tree/v1.3.0) (2016-07-17) 113 | [Full Changelog](https://github.com/dropbox/dbxcli/compare/v1.2.0...v1.3.0) 114 | 115 | **Closed issues:** 116 | 117 | - Have seperate commands for `rm` and `rmdir` [\#25](https://github.com/dropbox/dbxcli/issues/25) 118 | - `put` command is sending wrong client\_modified timestamp [\#20](https://github.com/dropbox/dbxcli/issues/20) 119 | - Make `ls` list files in multiple columns [\#17](https://github.com/dropbox/dbxcli/issues/17) 120 | - Add `logout` or `revoke` command [\#16](https://github.com/dropbox/dbxcli/issues/16) 121 | 122 | **Merged pull requests:** 123 | 124 | - Allow for multiple arguments to mv [\#30](https://github.com/dropbox/dbxcli/pull/30) ([GrantSeltzer](https://github.com/GrantSeltzer)) 125 | - Split rm into rm/rmdir, added consts for dangling strings [\#28](https://github.com/dropbox/dbxcli/pull/28) ([GrantSeltzer](https://github.com/GrantSeltzer)) 126 | - Allow providing a directory as a destination for `get` [\#22](https://github.com/dropbox/dbxcli/pull/22) ([waits](https://github.com/waits)) 127 | - Set `client\_modified` parameter when uploading files [\#21](https://github.com/dropbox/dbxcli/pull/21) ([waits](https://github.com/waits)) 128 | - Display file sizes using multiples of 1024 for consistency with other Dropbox apps [\#19](https://github.com/dropbox/dbxcli/pull/19) ([waits](https://github.com/waits)) 129 | 130 | ## [v1.2.0](https://github.com/dropbox/dbxcli/tree/v1.2.0) (2016-06-07) 131 | [Full Changelog](https://github.com/dropbox/dbxcli/compare/v1.1.0...v1.2.0) 132 | 133 | **Implemented enhancements:** 134 | 135 | - Support `ls` on files [\#8](https://github.com/dropbox/dbxcli/issues/8) 136 | 137 | **Closed issues:** 138 | 139 | - "Usage" section of help text is missing arguments [\#13](https://github.com/dropbox/dbxcli/issues/13) 140 | - `get` command panics without second argument [\#10](https://github.com/dropbox/dbxcli/issues/10) 141 | 142 | **Merged pull requests:** 143 | 144 | - Check `args` slice bounds in all commands [\#18](https://github.com/dropbox/dbxcli/pull/18) ([waits](https://github.com/waits)) 145 | - Add argument information to "usage" section of help text [\#14](https://github.com/dropbox/dbxcli/pull/14) ([waits](https://github.com/waits)) 146 | - Check `args` slice bounds in `get` and `put` functions [\#12](https://github.com/dropbox/dbxcli/pull/12) ([waits](https://github.com/waits)) 147 | - Support `ls` on files [\#11](https://github.com/dropbox/dbxcli/pull/11) ([waits](https://github.com/waits)) 148 | 149 | ## [v1.1.0](https://github.com/dropbox/dbxcli/tree/v1.1.0) (2016-05-05) 150 | [Full Changelog](https://github.com/dropbox/dbxcli/compare/v1.0.0...v1.1.0) 151 | 152 | **Closed issues:** 153 | 154 | - Bad authorization URL generated. [\#9](https://github.com/dropbox/dbxcli/issues/9) 155 | - Fails on most uploads and downloads [\#7](https://github.com/dropbox/dbxcli/issues/7) 156 | 157 | ## [v1.0.0](https://github.com/dropbox/dbxcli/tree/v1.0.0) (2016-03-23) 158 | [Full Changelog](https://github.com/dropbox/dbxcli/compare/v0.6.0...v1.0.0) 159 | 160 | ## [v0.6.0](https://github.com/dropbox/dbxcli/tree/v0.6.0) (2016-03-19) 161 | [Full Changelog](https://github.com/dropbox/dbxcli/compare/v0.5.0...v0.6.0) 162 | 163 | ## [v0.5.0](https://github.com/dropbox/dbxcli/tree/v0.5.0) (2016-03-16) 164 | [Full Changelog](https://github.com/dropbox/dbxcli/compare/v0.4.0...v0.5.0) 165 | 166 | **Closed issues:** 167 | 168 | - Improve \(or add?\) error handling [\#1](https://github.com/dropbox/dbxcli/issues/1) 169 | 170 | ## [v0.4.0](https://github.com/dropbox/dbxcli/tree/v0.4.0) (2016-03-15) 171 | [Full Changelog](https://github.com/dropbox/dbxcli/compare/v0.3.0...v0.4.0) 172 | 173 | ## [v0.3.0](https://github.com/dropbox/dbxcli/tree/v0.3.0) (2016-03-15) 174 | [Full Changelog](https://github.com/dropbox/dbxcli/compare/v0.2.0...v0.3.0) 175 | 176 | ## [v0.2.0](https://github.com/dropbox/dbxcli/tree/v0.2.0) (2016-03-14) 177 | [Full Changelog](https://github.com/dropbox/dbxcli/compare/v0.1.1...v0.2.0) 178 | 179 | **Closed issues:** 180 | 181 | - Asks for authentication code on each run \[Linux\] [\#6](https://github.com/dropbox/dbxcli/issues/6) 182 | 183 | **Merged pull requests:** 184 | 185 | - Add zsh-completion [\#5](https://github.com/dropbox/dbxcli/pull/5) ([knakayama](https://github.com/knakayama)) 186 | - Run `go vet` in `before\_script` [\#4](https://github.com/dropbox/dbxcli/pull/4) ([diwakergupta](https://github.com/diwakergupta)) 187 | - Create directory [\#3](https://github.com/dropbox/dbxcli/pull/3) ([mattn](https://github.com/mattn)) 188 | 189 | ## [v0.1.1](https://github.com/dropbox/dbxcli/tree/v0.1.1) (2016-03-11) 190 | [Full Changelog](https://github.com/dropbox/dbxcli/compare/v0.1.0...v0.1.1) 191 | 192 | **Merged pull requests:** 193 | 194 | - Prepare to push releases through Travis [\#2](https://github.com/dropbox/dbxcli/pull/2) ([diwakergupta](https://github.com/diwakergupta)) 195 | 196 | ## [v0.1.0](https://github.com/dropbox/dbxcli/tree/v0.1.0) (2016-03-10) 197 | 198 | 199 | \* *This Change Log was automatically generated by [github_changelog_generator](https://github.com/skywinder/Github-Changelog-Generator)* -------------------------------------------------------------------------------- /contrib/dbxcli_bash_completion.sh: -------------------------------------------------------------------------------- 1 | # bash completion for dbxcli -*- shell-script -*- 2 | 3 | __debug() 4 | { 5 | if [[ -n ${BASH_COMP_DEBUG_FILE} ]]; then 6 | echo "$*" >> "${BASH_COMP_DEBUG_FILE}" 7 | fi 8 | } 9 | 10 | # Homebrew on Macs have version 1.3 of bash-completion which doesn't include 11 | # _init_completion. This is a very minimal version of that function. 12 | __my_init_completion() 13 | { 14 | COMPREPLY=() 15 | _get_comp_words_by_ref cur prev words cword 16 | } 17 | 18 | __index_of_word() 19 | { 20 | local w word=$1 21 | shift 22 | index=0 23 | for w in "$@"; do 24 | [[ $w = "$word" ]] && return 25 | index=$((index+1)) 26 | done 27 | index=-1 28 | } 29 | 30 | __contains_word() 31 | { 32 | local w word=$1; shift 33 | for w in "$@"; do 34 | [[ $w = "$word" ]] && return 35 | done 36 | return 1 37 | } 38 | 39 | __handle_reply() 40 | { 41 | __debug "${FUNCNAME}" 42 | case $cur in 43 | -*) 44 | if [[ $(type -t compopt) = "builtin" ]]; then 45 | compopt -o nospace 46 | fi 47 | local allflags 48 | if [ ${#must_have_one_flag[@]} -ne 0 ]; then 49 | allflags=("${must_have_one_flag[@]}") 50 | else 51 | allflags=("${flags[*]} ${two_word_flags[*]}") 52 | fi 53 | COMPREPLY=( $(compgen -W "${allflags[*]}" -- "$cur") ) 54 | if [[ $(type -t compopt) = "builtin" ]]; then 55 | [[ $COMPREPLY == *= ]] || compopt +o nospace 56 | fi 57 | return 0; 58 | ;; 59 | esac 60 | 61 | # check if we are handling a flag with special work handling 62 | local index 63 | __index_of_word "${prev}" "${flags_with_completion[@]}" 64 | if [[ ${index} -ge 0 ]]; then 65 | ${flags_completion[${index}]} 66 | return 67 | fi 68 | 69 | # we are parsing a flag and don't have a special handler, no completion 70 | if [[ ${cur} != "${words[cword]}" ]]; then 71 | return 72 | fi 73 | 74 | local completions 75 | if [[ ${#must_have_one_flag[@]} -ne 0 ]]; then 76 | completions=("${must_have_one_flag[@]}") 77 | elif [[ ${#must_have_one_noun[@]} -ne 0 ]]; then 78 | completions=("${must_have_one_noun[@]}") 79 | else 80 | completions=("${commands[@]}") 81 | fi 82 | COMPREPLY=( $(compgen -W "${completions[*]}" -- "$cur") ) 83 | 84 | if [[ ${#COMPREPLY[@]} -eq 0 ]]; then 85 | declare -F __custom_func >/dev/null && __custom_func 86 | fi 87 | 88 | __ltrim_colon_completions "$cur" 89 | } 90 | 91 | # The arguments should be in the form "ext1|ext2|extn" 92 | __handle_filename_extension_flag() 93 | { 94 | local ext="$1" 95 | _filedir "@(${ext})" 96 | } 97 | 98 | __handle_subdirs_in_dir_flag() 99 | { 100 | local dir="$1" 101 | pushd "${dir}" >/dev/null 2>&1 && _filedir -d && popd >/dev/null 2>&1 102 | } 103 | 104 | __handle_flag() 105 | { 106 | __debug "${FUNCNAME}: c is $c words[c] is ${words[c]}" 107 | 108 | # if a command required a flag, and we found it, unset must_have_one_flag() 109 | local flagname=${words[c]} 110 | local flagvalue 111 | # if the word contained an = 112 | if [[ ${words[c]} == *"="* ]]; then 113 | flagvalue=${flagname#*=} # take in as flagvalue after the = 114 | flagname=${flagname%=*} # strip everything after the = 115 | flagname="${flagname}=" # but put the = back 116 | fi 117 | __debug "${FUNCNAME}: looking for ${flagname}" 118 | if __contains_word "${flagname}" "${must_have_one_flag[@]}"; then 119 | must_have_one_flag=() 120 | fi 121 | 122 | # keep flag value with flagname as flaghash 123 | if [ ${flagvalue} ] ; then 124 | flaghash[${flagname}]=${flagvalue} 125 | elif [ ${words[ $((c+1)) ]} ] ; then 126 | flaghash[${flagname}]=${words[ $((c+1)) ]} 127 | else 128 | flaghash[${flagname}]="true" # pad "true" for bool flag 129 | fi 130 | 131 | # skip the argument to a two word flag 132 | if __contains_word "${words[c]}" "${two_word_flags[@]}"; then 133 | c=$((c+1)) 134 | # if we are looking for a flags value, don't show commands 135 | if [[ $c -eq $cword ]]; then 136 | commands=() 137 | fi 138 | fi 139 | 140 | c=$((c+1)) 141 | 142 | } 143 | 144 | __handle_noun() 145 | { 146 | __debug "${FUNCNAME}: c is $c words[c] is ${words[c]}" 147 | 148 | if __contains_word "${words[c]}" "${must_have_one_noun[@]}"; then 149 | must_have_one_noun=() 150 | fi 151 | 152 | nouns+=("${words[c]}") 153 | c=$((c+1)) 154 | } 155 | 156 | __handle_command() 157 | { 158 | __debug "${FUNCNAME}: c is $c words[c] is ${words[c]}" 159 | 160 | local next_command 161 | if [[ -n ${last_command} ]]; then 162 | next_command="_${last_command}_${words[c]//:/__}" 163 | else 164 | if [[ $c -eq 0 ]]; then 165 | next_command="_$(basename ${words[c]//:/__})" 166 | else 167 | next_command="_${words[c]//:/__}" 168 | fi 169 | fi 170 | c=$((c+1)) 171 | __debug "${FUNCNAME}: looking for ${next_command}" 172 | declare -F $next_command >/dev/null && $next_command 173 | } 174 | 175 | __handle_word() 176 | { 177 | if [[ $c -ge $cword ]]; then 178 | __handle_reply 179 | return 180 | fi 181 | __debug "${FUNCNAME}: c is $c words[c] is ${words[c]}" 182 | if [[ "${words[c]}" == -* ]]; then 183 | __handle_flag 184 | elif __contains_word "${words[c]}" "${commands[@]}"; then 185 | __handle_command 186 | elif [[ $c -eq 0 ]] && __contains_word "$(basename ${words[c]})" "${commands[@]}"; then 187 | __handle_command 188 | else 189 | __handle_noun 190 | fi 191 | __handle_word 192 | } 193 | 194 | _dbxcli_cp() 195 | { 196 | last_command="dbxcli_cp" 197 | commands=() 198 | 199 | flags=() 200 | two_word_flags=() 201 | flags_with_completion=() 202 | flags_completion=() 203 | 204 | flags+=("--token=") 205 | flags+=("--verbose") 206 | flags+=("-v") 207 | 208 | must_have_one_flag=() 209 | must_have_one_noun=() 210 | } 211 | 212 | _dbxcli_du() 213 | { 214 | last_command="dbxcli_du" 215 | commands=() 216 | 217 | flags=() 218 | two_word_flags=() 219 | flags_with_completion=() 220 | flags_completion=() 221 | 222 | flags+=("--token=") 223 | flags+=("--verbose") 224 | flags+=("-v") 225 | 226 | must_have_one_flag=() 227 | must_have_one_noun=() 228 | } 229 | 230 | _dbxcli_get() 231 | { 232 | last_command="dbxcli_get" 233 | commands=() 234 | 235 | flags=() 236 | two_word_flags=() 237 | flags_with_completion=() 238 | flags_completion=() 239 | 240 | flags+=("--token=") 241 | flags+=("--verbose") 242 | flags+=("-v") 243 | 244 | must_have_one_flag=() 245 | must_have_one_noun=() 246 | } 247 | 248 | _dbxcli_ls() 249 | { 250 | last_command="dbxcli_ls" 251 | commands=() 252 | 253 | flags=() 254 | two_word_flags=() 255 | flags_with_completion=() 256 | flags_completion=() 257 | 258 | flags+=("--long") 259 | flags+=("-l") 260 | flags+=("--token=") 261 | flags+=("--verbose") 262 | flags+=("-v") 263 | 264 | must_have_one_flag=() 265 | must_have_one_noun=() 266 | } 267 | 268 | _dbxcli_mkdir() 269 | { 270 | last_command="dbxcli_mkdir" 271 | commands=() 272 | 273 | flags=() 274 | two_word_flags=() 275 | flags_with_completion=() 276 | flags_completion=() 277 | 278 | flags+=("--token=") 279 | flags+=("--verbose") 280 | flags+=("-v") 281 | 282 | must_have_one_flag=() 283 | must_have_one_noun=() 284 | } 285 | 286 | _dbxcli_mv() 287 | { 288 | last_command="dbxcli_mv" 289 | commands=() 290 | 291 | flags=() 292 | two_word_flags=() 293 | flags_with_completion=() 294 | flags_completion=() 295 | 296 | flags+=("--token=") 297 | flags+=("--verbose") 298 | flags+=("-v") 299 | 300 | must_have_one_flag=() 301 | must_have_one_noun=() 302 | } 303 | 304 | _dbxcli_put() 305 | { 306 | last_command="dbxcli_put" 307 | commands=() 308 | 309 | flags=() 310 | two_word_flags=() 311 | flags_with_completion=() 312 | flags_completion=() 313 | 314 | flags+=("--token=") 315 | flags+=("--verbose") 316 | flags+=("-v") 317 | 318 | must_have_one_flag=() 319 | must_have_one_noun=() 320 | } 321 | 322 | _dbxcli_restore() 323 | { 324 | last_command="dbxcli_restore" 325 | commands=() 326 | 327 | flags=() 328 | two_word_flags=() 329 | flags_with_completion=() 330 | flags_completion=() 331 | 332 | flags+=("--token=") 333 | flags+=("--verbose") 334 | flags+=("-v") 335 | 336 | must_have_one_flag=() 337 | must_have_one_noun=() 338 | } 339 | 340 | _dbxcli_revs() 341 | { 342 | last_command="dbxcli_revs" 343 | commands=() 344 | 345 | flags=() 346 | two_word_flags=() 347 | flags_with_completion=() 348 | flags_completion=() 349 | 350 | flags+=("--long") 351 | flags+=("-l") 352 | flags+=("--token=") 353 | flags+=("--verbose") 354 | flags+=("-v") 355 | 356 | must_have_one_flag=() 357 | must_have_one_noun=() 358 | } 359 | 360 | _dbxcli_rm() 361 | { 362 | last_command="dbxcli_rm" 363 | commands=() 364 | 365 | flags=() 366 | two_word_flags=() 367 | flags_with_completion=() 368 | flags_completion=() 369 | 370 | flags+=("--token=") 371 | flags+=("--verbose") 372 | flags+=("-v") 373 | 374 | must_have_one_flag=() 375 | must_have_one_noun=() 376 | } 377 | 378 | _dbxcli_search() 379 | { 380 | last_command="dbxcli_search" 381 | commands=() 382 | 383 | flags=() 384 | two_word_flags=() 385 | flags_with_completion=() 386 | flags_completion=() 387 | 388 | flags+=("--long") 389 | flags+=("-l") 390 | flags+=("--token=") 391 | flags+=("--verbose") 392 | flags+=("-v") 393 | 394 | must_have_one_flag=() 395 | must_have_one_noun=() 396 | } 397 | 398 | _dbxcli_team_add-member() 399 | { 400 | last_command="dbxcli_team_add-member" 401 | commands=() 402 | 403 | flags=() 404 | two_word_flags=() 405 | flags_with_completion=() 406 | flags_completion=() 407 | 408 | flags+=("--token=") 409 | flags+=("--verbose") 410 | flags+=("-v") 411 | 412 | must_have_one_flag=() 413 | must_have_one_noun=() 414 | } 415 | 416 | _dbxcli_team_info() 417 | { 418 | last_command="dbxcli_team_info" 419 | commands=() 420 | 421 | flags=() 422 | two_word_flags=() 423 | flags_with_completion=() 424 | flags_completion=() 425 | 426 | flags+=("--token=") 427 | flags+=("--verbose") 428 | flags+=("-v") 429 | 430 | must_have_one_flag=() 431 | must_have_one_noun=() 432 | } 433 | 434 | _dbxcli_team_list-groups() 435 | { 436 | last_command="dbxcli_team_list-groups" 437 | commands=() 438 | 439 | flags=() 440 | two_word_flags=() 441 | flags_with_completion=() 442 | flags_completion=() 443 | 444 | flags+=("--token=") 445 | flags+=("--verbose") 446 | flags+=("-v") 447 | 448 | must_have_one_flag=() 449 | must_have_one_noun=() 450 | } 451 | 452 | _dbxcli_team_list-members() 453 | { 454 | last_command="dbxcli_team_list-members" 455 | commands=() 456 | 457 | flags=() 458 | two_word_flags=() 459 | flags_with_completion=() 460 | flags_completion=() 461 | 462 | flags+=("--token=") 463 | flags+=("--verbose") 464 | flags+=("-v") 465 | 466 | must_have_one_flag=() 467 | must_have_one_noun=() 468 | } 469 | 470 | _dbxcli_team_remove-member() 471 | { 472 | last_command="dbxcli_team_remove-member" 473 | commands=() 474 | 475 | flags=() 476 | two_word_flags=() 477 | flags_with_completion=() 478 | flags_completion=() 479 | 480 | flags+=("--token=") 481 | flags+=("--verbose") 482 | flags+=("-v") 483 | 484 | must_have_one_flag=() 485 | must_have_one_noun=() 486 | } 487 | 488 | _dbxcli_team() 489 | { 490 | last_command="dbxcli_team" 491 | commands=() 492 | commands+=("add-member") 493 | commands+=("info") 494 | commands+=("list-groups") 495 | commands+=("list-members") 496 | commands+=("remove-member") 497 | 498 | flags=() 499 | two_word_flags=() 500 | flags_with_completion=() 501 | flags_completion=() 502 | 503 | flags+=("--token=") 504 | flags+=("--verbose") 505 | flags+=("-v") 506 | 507 | must_have_one_flag=() 508 | must_have_one_noun=() 509 | } 510 | 511 | _dbxcli_version() 512 | { 513 | last_command="dbxcli_version" 514 | commands=() 515 | 516 | flags=() 517 | two_word_flags=() 518 | flags_with_completion=() 519 | flags_completion=() 520 | 521 | flags+=("--help") 522 | flags+=("-h") 523 | flags+=("--token=") 524 | flags+=("--verbose") 525 | flags+=("-v") 526 | 527 | must_have_one_flag=() 528 | must_have_one_noun=() 529 | } 530 | 531 | _dbxcli() 532 | { 533 | last_command="dbxcli" 534 | commands=() 535 | commands+=("cp") 536 | commands+=("du") 537 | commands+=("get") 538 | commands+=("ls") 539 | commands+=("mkdir") 540 | commands+=("mv") 541 | commands+=("put") 542 | commands+=("restore") 543 | commands+=("revs") 544 | commands+=("rm") 545 | commands+=("search") 546 | commands+=("team") 547 | commands+=("version") 548 | 549 | flags=() 550 | two_word_flags=() 551 | flags_with_completion=() 552 | flags_completion=() 553 | 554 | flags+=("--token=") 555 | flags+=("--verbose") 556 | flags+=("-v") 557 | 558 | must_have_one_flag=() 559 | must_have_one_noun=() 560 | } 561 | 562 | __start_dbxcli() 563 | { 564 | local cur prev words cword 565 | declare -A flaghash 2>/dev/null || : 566 | if declare -F _init_completion >/dev/null 2>&1; then 567 | _init_completion -s || return 568 | else 569 | __my_init_completion || return 570 | fi 571 | 572 | local c=0 573 | local flags=() 574 | local two_word_flags=() 575 | local flags_with_completion=() 576 | local flags_completion=() 577 | local commands=("dbxcli") 578 | local must_have_one_flag=() 579 | local must_have_one_noun=() 580 | local last_command 581 | local nouns=() 582 | 583 | __handle_word 584 | } 585 | 586 | if [[ $(type -t compopt) = "builtin" ]]; then 587 | complete -o default -F __start_dbxcli dbxcli 588 | else 589 | complete -o default -o nospace -F __start_dbxcli dbxcli 590 | fi 591 | 592 | # ex: ts=4 sw=4 et filetype=sh 593 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= 2 | cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= 3 | cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= 4 | cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= 5 | cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= 6 | cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= 7 | cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= 8 | cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To= 9 | cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4= 10 | cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M= 11 | cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc= 12 | cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk= 13 | cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs= 14 | cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc= 15 | cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY= 16 | cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= 17 | cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= 18 | cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= 19 | cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg= 20 | cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= 21 | cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= 22 | cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= 23 | cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= 24 | cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= 25 | cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= 26 | cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= 27 | cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU= 28 | cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= 29 | cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= 30 | cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= 31 | cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= 32 | cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= 33 | dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= 34 | github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= 35 | github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= 36 | github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= 37 | github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= 38 | github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= 39 | github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= 40 | github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= 41 | github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= 42 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 43 | github.com/dropbox/dropbox-sdk-go-unofficial/v6 v6.0.1 h1:JjhYyUMV9HVNcy01aYTmv1bKQN8/GGroBG0srI7+PUM= 44 | github.com/dropbox/dropbox-sdk-go-unofficial/v6 v6.0.1/go.mod h1:IlhWb/Wfy4arqxVNIAAmBaqJ/UiNd8t2EdRJJh04T2A= 45 | github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo= 46 | github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= 47 | github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= 48 | github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= 49 | github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= 50 | github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= 51 | github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= 52 | github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= 53 | github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= 54 | github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= 55 | github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= 56 | github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= 57 | github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= 58 | github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= 59 | github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= 60 | github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= 61 | github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= 62 | github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= 63 | github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= 64 | github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= 65 | github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 66 | github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 67 | github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 68 | github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= 69 | github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= 70 | github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= 71 | github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= 72 | github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= 73 | github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= 74 | github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= 75 | github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= 76 | github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= 77 | github.com/golang/protobuf v1.4.2 h1:+Z5KGCizgyZCbGh1KZqA0fcLLkwbsjIzS4aV2v7wJX0= 78 | github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= 79 | github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= 80 | github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= 81 | github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= 82 | github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= 83 | github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= 84 | github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 85 | github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 86 | github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 87 | github.com/google/go-cmp v0.5.1 h1:JFrFEBb2xKufg6XkJsJr+WbKb4FQlURi5RUcBveYu9k= 88 | github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 89 | github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= 90 | github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= 91 | github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= 92 | github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= 93 | github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= 94 | github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= 95 | github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= 96 | github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= 97 | github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= 98 | github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= 99 | github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= 100 | github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= 101 | github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= 102 | github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= 103 | github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= 104 | github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM= 105 | github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= 106 | github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= 107 | github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= 108 | github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= 109 | github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= 110 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 111 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 112 | github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= 113 | github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= 114 | github.com/mitchellh/ioprogress v0.0.0-20180201004757-6a23b12fa88e h1:Qa6dnn8DlasdXRnacluu8HzPts0S1I9zvvUPDbBnXFI= 115 | github.com/mitchellh/ioprogress v0.0.0-20180201004757-6a23b12fa88e/go.mod h1:waEya8ee1Ro/lgxpVhkJI4BVASzkm3UZqkx/cFJiYHM= 116 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 117 | github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= 118 | github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= 119 | github.com/spf13/cobra v0.0.4-0.20190109003409-7547e83b2d85 h1:RghwryY75x76zKqO9v7NF+9lcmfW1/RNZBfqK4LSCKE= 120 | github.com/spf13/cobra v0.0.4-0.20190109003409-7547e83b2d85/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= 121 | github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg= 122 | github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= 123 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 124 | github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= 125 | github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 126 | github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 127 | github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 128 | go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= 129 | go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= 130 | go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= 131 | go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= 132 | go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= 133 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 134 | golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 135 | golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 136 | golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 137 | golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 138 | golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= 139 | golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= 140 | golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= 141 | golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= 142 | golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= 143 | golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= 144 | golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= 145 | golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= 146 | golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= 147 | golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= 148 | golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= 149 | golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= 150 | golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= 151 | golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= 152 | golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= 153 | golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 154 | golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 155 | golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 156 | golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 157 | golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= 158 | golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= 159 | golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= 160 | golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= 161 | golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= 162 | golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= 163 | golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= 164 | golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= 165 | golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= 166 | golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 167 | golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 168 | golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 169 | golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 170 | golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 171 | golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 172 | golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 173 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 174 | golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 175 | golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 176 | golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= 177 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 178 | golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 179 | golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 180 | golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 181 | golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 182 | golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 183 | golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 184 | golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 185 | golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 186 | golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= 187 | golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= 188 | golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= 189 | golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= 190 | golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= 191 | golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= 192 | golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= 193 | golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= 194 | golang.org/x/net v0.0.0-20201224014010-6772e930b67b h1:iFwSg7t5GZmB/Q5TjiEAsdoLDrdJRC1RiF2WhuV29Qw= 195 | golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= 196 | golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= 197 | golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= 198 | golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= 199 | golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= 200 | golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= 201 | golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5 h1:Lm4OryKCca1vehdsWogr9N4t7NfZxLbJoc/H0w4K4S4= 202 | golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= 203 | golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 204 | golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 205 | golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 206 | golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 207 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 208 | golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 209 | golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 210 | golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 211 | golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 212 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 213 | golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 214 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 215 | golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 216 | golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 217 | golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 218 | golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 219 | golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 220 | golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 221 | golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 222 | golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 223 | golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 224 | golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 225 | golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 226 | golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 227 | golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 228 | golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 229 | golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 230 | golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 231 | golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 232 | golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 233 | golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 234 | golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 235 | golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 236 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 237 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= 238 | golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 239 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 240 | golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 241 | golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= 242 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 243 | golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= 244 | golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= 245 | golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= 246 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 247 | golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 248 | golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= 249 | golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 250 | golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 251 | golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 252 | golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= 253 | golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= 254 | golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= 255 | golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= 256 | golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= 257 | golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= 258 | golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 259 | golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 260 | golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 261 | golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 262 | golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 263 | golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 264 | golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 265 | golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 266 | golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 267 | golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 268 | golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 269 | golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 270 | golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 271 | golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 272 | golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 273 | golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 274 | golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 275 | golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 276 | golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= 277 | golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= 278 | golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= 279 | golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= 280 | golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= 281 | golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= 282 | golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= 283 | golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= 284 | golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= 285 | golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= 286 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 287 | golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 288 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 289 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= 290 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 291 | google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= 292 | google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= 293 | google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= 294 | google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= 295 | google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= 296 | google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= 297 | google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= 298 | google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= 299 | google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= 300 | google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= 301 | google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= 302 | google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= 303 | google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= 304 | google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= 305 | google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM= 306 | google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc= 307 | google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= 308 | google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= 309 | google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= 310 | google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= 311 | google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= 312 | google.golang.org/appengine v1.6.6 h1:lMO5rYAqUxkmaj76jAkRUvt5JZgFymx/+Q5Mzfivuhc= 313 | google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= 314 | google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= 315 | google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= 316 | google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= 317 | google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= 318 | google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= 319 | google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= 320 | google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= 321 | google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= 322 | google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= 323 | google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= 324 | google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= 325 | google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= 326 | google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= 327 | google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= 328 | google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA= 329 | google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= 330 | google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= 331 | google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= 332 | google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= 333 | google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= 334 | google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= 335 | google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= 336 | google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= 337 | google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U= 338 | google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= 339 | google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= 340 | google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= 341 | google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= 342 | google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= 343 | google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= 344 | google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= 345 | google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= 346 | google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= 347 | google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= 348 | google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= 349 | google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= 350 | google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= 351 | google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60= 352 | google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= 353 | google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= 354 | google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= 355 | google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= 356 | google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= 357 | google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= 358 | google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= 359 | google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= 360 | google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= 361 | google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= 362 | google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= 363 | google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= 364 | google.golang.org/protobuf v1.25.0 h1:Ejskq+SyPohKW+1uil0JJMtmHCgJPJ/qWTxr8qp+R4c= 365 | google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= 366 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 367 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 368 | gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= 369 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 370 | honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 371 | honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 372 | honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 373 | honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 374 | honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= 375 | honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= 376 | honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= 377 | rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= 378 | rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= 379 | rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= 380 | --------------------------------------------------------------------------------