1package script 2 3import ( 4 "fmt" 5 "github.com/hashicorp/go-multierror" 6 "io" 7 "io/ioutil" 8 "os" 9 "path/filepath" 10) 11 12// Files is a stream of a list of files. A user can either use the file list directly or the the 13// created stream. In the stream, each line contains a path to a file. 14type Files struct { 15 Stream 16 Files []FileInfo 17} 18 19// File contains information about a file. 20type FileInfo struct { 21 // FileInfo contains information about the file. 22 os.FileInfo 23 // Path is the path of the file. It may be relative or absolute, depending on how the `Ls` 24 // command was invoked. 25 Path string 26} 27 28// Ls returns a stream of a list files. In the returned stream, each line will contain a path to 29// a single file. 30// 31// If the provided paths list is empty, the local directory will be listed. 32// 33// The provided paths may be relative to the local directory or absolute - this will influence the 34// format of the returned paths in the output. 35// 36// If some provided paths correlate to the arguments correlate to the same file, it will also appear 37// multiple times in the output. 38// 39// If any of the paths fails to be listed, it will result in an error in the output, but the stream 40// will still conain all paths that were successfully listed. 41// 42// Shell command: `ls`. 43func Ls(paths ...string) Files { 44 // Default to local directory. 45 if len(paths) == 0 { 46 paths = append(paths, ".") 47 } 48 49 var ( 50 files []FileInfo 51 errors *multierror.Error 52 ) 53 54 for _, path := range paths { 55 info, err := os.Stat(path) 56 if err != nil { 57 errors = multierror.Append(errors, fmt.Errorf("stat path: %s", err)) 58 continue 59 } 60 61 // Path is a single file. 62 if !info.IsDir() { 63 files = append(files, FileInfo{Path: path, FileInfo: info}) 64 continue 65 } 66 67 // Path is a directory. 68 infos, err := ioutil.ReadDir(path) 69 if err != nil { 70 errors = multierror.Append(errors, fmt.Errorf("read dir: %s", err)) 71 continue 72 } 73 74 for _, info := range infos { 75 files = append(files, FileInfo{Path: filepath.Join(path, info.Name()), FileInfo: info}) 76 } 77 } 78 79 return Files{ 80 Stream: Stream{ 81 stage: fmt.Sprintf("ls (%+v)", paths), 82 r: &filesReader{files: files}, 83 err: errors.ErrorOrNil(), 84 }, 85 Files: files, 86 } 87} 88 89// filesReader reads from a file info list. 90type filesReader struct { 91 files []FileInfo 92 // seek indicates which file to write for the next Read function call. 93 seek int 94} 95 96func (f *filesReader) Read(out []byte) (int, error) { 97 if f.seek >= len(f.files) { 98 return 0, io.EOF 99 } 100 101 line := []byte(f.files[f.seek].Path + "\n") 102 f.seek++ 103 104 n := copy(out, line) 105 return n, nil 106} 107