mirror of
https://github.com/rclone/rclone.git
synced 2024-11-22 00:13:49 +01:00
listremotes: added options for filtering, ordering and json output
This commit is contained in:
parent
d6b0743cf4
commit
024ff6ed15
@ -2,60 +2,236 @@
|
|||||||
package ls
|
package ls
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"regexp"
|
||||||
"sort"
|
"sort"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/rclone/rclone/cmd"
|
"github.com/rclone/rclone/cmd"
|
||||||
|
"github.com/rclone/rclone/fs"
|
||||||
"github.com/rclone/rclone/fs/config"
|
"github.com/rclone/rclone/fs/config"
|
||||||
"github.com/rclone/rclone/fs/config/flags"
|
"github.com/rclone/rclone/fs/config/flags"
|
||||||
|
"github.com/rclone/rclone/fs/filter"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Globals
|
|
||||||
var (
|
var (
|
||||||
listLong bool
|
listLong bool
|
||||||
|
jsonOutput bool
|
||||||
|
filterName string
|
||||||
|
filterType string
|
||||||
|
filterSource string
|
||||||
|
filterDescription string
|
||||||
|
orderBy string
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
cmd.Root.AddCommand(commandDefinition)
|
cmd.Root.AddCommand(commandDefinition)
|
||||||
cmdFlags := commandDefinition.Flags()
|
cmdFlags := commandDefinition.Flags()
|
||||||
flags.BoolVarP(cmdFlags, &listLong, "long", "", listLong, "Show the type and the description as well as names", "")
|
flags.BoolVarP(cmdFlags, &listLong, "long", "", false, "Show type, source and description in addition to name", "")
|
||||||
|
flags.StringVarP(cmdFlags, &filterName, "name", "", "", "Filter remotes by name", "")
|
||||||
|
flags.StringVarP(cmdFlags, &filterType, "type", "", "", "Filter remotes by type", "")
|
||||||
|
flags.StringVarP(cmdFlags, &filterSource, "source", "", "", "filter remotes by source", "")
|
||||||
|
flags.StringVarP(cmdFlags, &filterDescription, "description", "", "", "filter remotes by description", "")
|
||||||
|
flags.StringVarP(cmdFlags, &orderBy, "order-by", "", "", "Instructions on how to order the result, e.g. 'type,name=descending'", "")
|
||||||
|
flags.BoolVarP(cmdFlags, &jsonOutput, "json", "", false, "Format output as JSON", "")
|
||||||
|
}
|
||||||
|
|
||||||
|
// lessFn compares to remotes for order by
|
||||||
|
type lessFn func(a, b config.Remote) bool
|
||||||
|
|
||||||
|
// newLess returns a function for comparing remotes based on an order by string
|
||||||
|
func newLess(orderBy string) (less lessFn, err error) {
|
||||||
|
if orderBy == "" {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
parts := strings.Split(strings.ToLower(orderBy), ",")
|
||||||
|
n := len(parts)
|
||||||
|
for i := n - 1; i >= 0; i-- {
|
||||||
|
fieldAndDirection := strings.SplitN(parts[i], "=", 2)
|
||||||
|
|
||||||
|
descending := false
|
||||||
|
if len(fieldAndDirection) > 1 {
|
||||||
|
switch fieldAndDirection[1] {
|
||||||
|
case "ascending", "asc":
|
||||||
|
case "descending", "desc":
|
||||||
|
descending = true
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("unknown --order-by direction %q", fieldAndDirection[1])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var field func(o config.Remote) string
|
||||||
|
switch fieldAndDirection[0] {
|
||||||
|
case "name":
|
||||||
|
field = func(o config.Remote) string {
|
||||||
|
return o.Name
|
||||||
|
}
|
||||||
|
case "type":
|
||||||
|
field = func(o config.Remote) string {
|
||||||
|
return o.Type
|
||||||
|
}
|
||||||
|
case "source":
|
||||||
|
field = func(o config.Remote) string {
|
||||||
|
return o.Source
|
||||||
|
}
|
||||||
|
case "description":
|
||||||
|
field = func(o config.Remote) string {
|
||||||
|
return o.Description
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("unknown --order-by field %q", fieldAndDirection[0])
|
||||||
|
}
|
||||||
|
|
||||||
|
var thisLess lessFn
|
||||||
|
if descending {
|
||||||
|
thisLess = func(a, b config.Remote) bool {
|
||||||
|
return field(a) > field(b)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
thisLess = func(a, b config.Remote) bool {
|
||||||
|
return field(a) < field(b)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if i == n-1 {
|
||||||
|
less = thisLess
|
||||||
|
} else {
|
||||||
|
nextLess := less
|
||||||
|
less = func(a, b config.Remote) bool {
|
||||||
|
if field(a) == field(b) {
|
||||||
|
return nextLess(a, b)
|
||||||
|
}
|
||||||
|
return thisLess(a, b)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return less, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
var commandDefinition = &cobra.Command{
|
var commandDefinition = &cobra.Command{
|
||||||
Use: "listremotes",
|
Use: "listremotes [<filter>]",
|
||||||
Short: `List all the remotes in the config file and defined in environment variables.`,
|
Short: `List all the remotes in the config file and defined in environment variables.`,
|
||||||
Long: `
|
Long: `
|
||||||
rclone listremotes lists all the available remotes from the config file.
|
rclone listremotes lists all the available remotes from the config file,
|
||||||
|
or the remotes matching an optional filter.
|
||||||
|
|
||||||
When used with the ` + "`--long`" + ` flag it lists the types and the descriptions too.
|
Prints the result in human-readable format by default, and as a simple list of
|
||||||
|
remote names, or if used with flag ` + "`--long`" + ` a tabular format including
|
||||||
|
all attributes of the remotes: name, type, source and description. Using flag
|
||||||
|
` + "`--json`" + ` produces machine-readable output instead, which always includes
|
||||||
|
all attributes.
|
||||||
|
|
||||||
|
Result can be filtered by a filter argument which applies to all attributes,
|
||||||
|
and/or filter flags specific for each attribute. The values must be specified
|
||||||
|
according to regular rclone filtering pattern syntax.
|
||||||
`,
|
`,
|
||||||
Annotations: map[string]string{
|
Annotations: map[string]string{
|
||||||
"versionIntroduced": "v1.34",
|
"versionIntroduced": "v1.34",
|
||||||
},
|
},
|
||||||
Run: func(command *cobra.Command, args []string) {
|
RunE: func(command *cobra.Command, args []string) error {
|
||||||
cmd.CheckArgs(0, 0, command, args)
|
cmd.CheckArgs(0, 1, command, args)
|
||||||
remotes := config.FileSections()
|
var filterDefault string
|
||||||
sort.Strings(remotes)
|
if len(args) > 0 {
|
||||||
maxlen := 1
|
filterDefault = args[0]
|
||||||
maxlentype := 1
|
}
|
||||||
for _, remote := range remotes {
|
filters := make(map[string]*regexp.Regexp)
|
||||||
if len(remote) > maxlen {
|
for k, v := range map[string]string{
|
||||||
maxlen = len(remote)
|
"all": filterDefault,
|
||||||
}
|
"name": filterName,
|
||||||
t := config.FileGet(remote, "type")
|
"type": filterType,
|
||||||
if len(t) > maxlentype {
|
"source": filterSource,
|
||||||
maxlentype = len(t)
|
"description": filterDescription,
|
||||||
|
} {
|
||||||
|
if v != "" {
|
||||||
|
filterRe, err := filter.GlobStringToRegexp(v, false)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("invalid %s filter argument: %w", k, err)
|
||||||
|
}
|
||||||
|
fs.Debugf(nil, "Filter for %s: %s", k, filterRe.String())
|
||||||
|
filters[k] = filterRe
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
remotes := config.GetRemotes()
|
||||||
|
maxName := 0
|
||||||
|
maxType := 0
|
||||||
|
maxSource := 0
|
||||||
|
i := 0
|
||||||
for _, remote := range remotes {
|
for _, remote := range remotes {
|
||||||
if listLong {
|
include := true
|
||||||
remoteType := config.FileGet(remote, "type")
|
for k, v := range filters {
|
||||||
description := config.FileGet(remote, "description")
|
if k == "all" && !(v.MatchString(remote.Name) || v.MatchString(remote.Type) || v.MatchString(remote.Source) || v.MatchString(remote.Description)) {
|
||||||
fmt.Printf("%-*s %-*s %s\n", maxlen+1, remote+":", maxlentype+1, remoteType, description)
|
include = false
|
||||||
} else {
|
} else if k == "name" && !v.MatchString(remote.Name) {
|
||||||
fmt.Printf("%s:\n", remote)
|
include = false
|
||||||
|
} else if k == "type" && !v.MatchString(remote.Type) {
|
||||||
|
include = false
|
||||||
|
} else if k == "source" && !v.MatchString(remote.Source) {
|
||||||
|
include = false
|
||||||
|
} else if k == "description" && !v.MatchString(remote.Description) {
|
||||||
|
include = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if include {
|
||||||
|
if len(remote.Name) > maxName {
|
||||||
|
maxName = len(remote.Name)
|
||||||
|
}
|
||||||
|
if len(remote.Type) > maxType {
|
||||||
|
maxType = len(remote.Type)
|
||||||
|
}
|
||||||
|
if len(remote.Source) > maxSource {
|
||||||
|
maxSource = len(remote.Source)
|
||||||
|
}
|
||||||
|
remotes[i] = remote
|
||||||
|
i++
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
remotes = remotes[:i]
|
||||||
|
|
||||||
|
less, err := newLess(orderBy)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if less != nil {
|
||||||
|
sliceLessFn := func(i, j int) bool {
|
||||||
|
return less(remotes[i], remotes[j])
|
||||||
|
}
|
||||||
|
sort.SliceStable(remotes, sliceLessFn)
|
||||||
|
}
|
||||||
|
|
||||||
|
if jsonOutput {
|
||||||
|
fmt.Println("[")
|
||||||
|
first := true
|
||||||
|
for _, remote := range remotes {
|
||||||
|
out, err := json.Marshal(remote)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to marshal remote object: %w", err)
|
||||||
|
}
|
||||||
|
if first {
|
||||||
|
first = false
|
||||||
|
} else {
|
||||||
|
fmt.Print(",\n")
|
||||||
|
}
|
||||||
|
_, err = os.Stdout.Write(out)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to write to output: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !first {
|
||||||
|
fmt.Println()
|
||||||
|
}
|
||||||
|
fmt.Println("]")
|
||||||
|
} else if listLong {
|
||||||
|
for _, remote := range remotes {
|
||||||
|
fmt.Printf("%-*s %-*s %-*s %s\n", maxName+1, remote.Name+":", maxType, remote.Type, maxSource, remote.Source, remote.Description)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for _, remote := range remotes {
|
||||||
|
fmt.Printf("%s:\n", remote.Name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
@ -444,11 +444,12 @@ func SetValueAndSave(remote, key, value string) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remote defines a remote with a name, type and source
|
// Remote defines a remote with a name, type, source and description
|
||||||
type Remote struct {
|
type Remote struct {
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
Type string `json:"type"`
|
Type string `json:"type"`
|
||||||
Source string `json:"source"`
|
Source string `json:"source"`
|
||||||
|
Description string `json:"description"`
|
||||||
}
|
}
|
||||||
|
|
||||||
var remoteEnvRe = regexp.MustCompile(`^RCLONE_CONFIG_(.+?)_TYPE=(.+)$`)
|
var remoteEnvRe = regexp.MustCompile(`^RCLONE_CONFIG_(.+?)_TYPE=(.+)$`)
|
||||||
@ -482,10 +483,12 @@ func GetRemotes() []Remote {
|
|||||||
if !remoteExists(section) {
|
if !remoteExists(section) {
|
||||||
typeValue, found := LoadedData().GetValue(section, "type")
|
typeValue, found := LoadedData().GetValue(section, "type")
|
||||||
if found {
|
if found {
|
||||||
|
description, _ := LoadedData().GetValue(section, "description")
|
||||||
remotes = append(remotes, Remote{
|
remotes = append(remotes, Remote{
|
||||||
Name: section,
|
Name: section,
|
||||||
Type: typeValue,
|
Type: typeValue,
|
||||||
Source: "file",
|
Source: "file",
|
||||||
|
Description: description,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user