diff --git a/cmd/lsf/lsf.go b/cmd/lsf/lsf.go index 58c77d157..ee5dd2aae 100644 --- a/cmd/lsf/lsf.go +++ b/cmd/lsf/lsf.go @@ -23,6 +23,7 @@ var ( hashType = hash.MD5 filesOnly bool dirsOnly bool + csv bool ) func init() { @@ -34,6 +35,7 @@ func init() { flags.VarP(&hashType, "hash", "", "Use this hash when `h` is used in the format MD5|SHA-1|DropboxHash") flags.BoolVarP(&filesOnly, "files-only", "", false, "Only list files.") flags.BoolVarP(&dirsOnly, "dirs-only", "", false, "Only list directories.") + flags.BoolVarP(&csv, "csv", "", false, "Output in CSV format.") commandDefintion.Flags().BoolVarP(&recurse, "recursive", "R", false, "Recurse into the listing.") } @@ -113,11 +115,28 @@ Eg 2018-04-26 08:52:53,0,,ferejej3gux/ 2016-06-25 18:55:40,37600,8fd37c3810dd660778137ac3a66cc06d,fubuwic +You can output in CSV standard format. This will escape things in " +if they contain , + +Eg + + $ rclone lsf --csv --files-only --format ps remote:path + test.log,22355 + test.sh,449 + "this file contains a comma, in the file name.txt",6 + ` + lshelp.Help, Run: func(command *cobra.Command, args []string) { cmd.CheckArgs(1, 1, command, args) fsrc := cmd.NewFsSrc(args) cmd.Run(false, false, command, func() error { + // Work out if the separatorFlag was supplied or not + separatorFlag := command.Flags().Lookup("separator") + separatorFlagSupplied := separatorFlag != nil && separatorFlag.Changed + // Default the separator to , if using CSV + if csv && !separatorFlagSupplied { + separator = "," + } return Lsf(fsrc, os.Stdout) }) }, @@ -128,6 +147,7 @@ Eg func Lsf(fsrc fs.Fs, out io.Writer) error { var list operations.ListFormat list.SetSeparator(separator) + list.SetCSV(csv) list.SetDirSlash(dirSlash) for _, char := range format { diff --git a/fs/operations/operations.go b/fs/operations/operations.go index d6434afa1..848811519 100644 --- a/fs/operations/operations.go +++ b/fs/operations/operations.go @@ -4,6 +4,7 @@ package operations import ( "bytes" "context" + "encoding/csv" "fmt" "io" "io/ioutil" @@ -1368,6 +1369,8 @@ type ListFormat struct { dirSlash bool output []func() string entry fs.DirEntry + csv *csv.Writer + buf bytes.Buffer } // SetSeparator changes separator in struct @@ -1380,6 +1383,21 @@ func (l *ListFormat) SetDirSlash(dirSlash bool) { l.dirSlash = dirSlash } +// SetCSV defines if the output should be csv +// +// Note that you should call SetSeparator before this if you want a +// custom separator +func (l *ListFormat) SetCSV(useCSV bool) { + if useCSV { + l.csv = csv.NewWriter(&l.buf) + if l.separator != "" { + l.csv.Comma = []rune(l.separator)[0] + } + } else { + l.csv = nil + } +} + // SetOutput sets functions used to create files information func (l *ListFormat) SetOutput(output []func() string) { l.output = output @@ -1439,18 +1457,23 @@ func (l *ListFormat) AddMimeType() { // AppendOutput adds string generated by specific function to printed output func (l *ListFormat) AppendOutput(functionToAppend func() string) { - if len(l.output) > 0 { - l.output = append(l.output, func() string { return l.separator }) - } l.output = append(l.output, functionToAppend) } // Format prints information about the DirEntry in the format defined -func (l *ListFormat) Format(entry fs.DirEntry) string { +func (l *ListFormat) Format(entry fs.DirEntry) (result string) { l.entry = entry - var out string + var out []string for _, fun := range l.output { - out += fun() + out = append(out, fun()) } - return out + if l.csv != nil { + l.buf.Reset() + _ = l.csv.Write(out) // can't fail writing to bytes.Buffer + l.csv.Flush() + result = strings.TrimRight(l.buf.String(), "\n") + } else { + result = strings.Join(out, l.separator) + } + return result } diff --git a/fs/operations/operations_test.go b/fs/operations/operations_test.go index a2000b8be..a5227c5ed 100644 --- a/fs/operations/operations_test.go +++ b/fs/operations/operations_test.go @@ -747,4 +747,15 @@ func TestListFormat(t *testing.T) { assert.Equal(t, test.want, got) } } + + list.SetOutput(nil) + list.SetSeparator("|") + list.SetCSV(true) + list.AddSize() + list.AddPath() + list.AddModTime() + list.SetDirSlash(true) + assert.Equal(t, "1|a|"+items[0].ModTime().Local().Format("2006-01-02 15:04:05"), list.Format(items[0])) + assert.Equal(t, fmt.Sprintf("%d", items[1].Size())+"|subdir/|"+items[1].ModTime().Local().Format("2006-01-02 15:04:05"), list.Format(items[1])) + }