package main import ( "fmt" "io" "os" "path/filepath" ) // IsDir returns whether the path is a directory. func IsDir(dir string) bool { info, err := os.Lstat(dir) return err == nil && info.Mode().IsDir() && info.Mode()&os.ModeSymlink == 0 } // SameFile returns true if the two file paths specify the same path. // While Linux is case-preserving case-sensitive (and therefore a string comparison will work), // Windows is case-preserving case-insensitive; we use os.SameFile() to work cross-platform. func SameFile(filename1 string, filename2 string) (bool, error) { fi1, err := os.Stat(filename1) if err != nil { return false, err } fi2, err := os.Stat(filename2) if err != nil { return false, err } return os.SameFile(fi1, fi2), nil } func openInputFile(input string) (io.ReadCloser, error) { var r *os.File if input == "-" { r = os.Stdin } else { err := retry(5, func() error { var err error r, err = os.Open(input) return err }) if err != nil { return nil, fmt.Errorf("open input file %q: %w", input, err) } } return r, nil } func openInputFiles(filenames []string, sep []byte) (*concatFileReader, error) { return newConcatFileReader(filenames, openInputFile, sep) } func openOutputFile(output string) (*os.File, error) { var w *os.File if output == "-" { w = os.Stdout } else { dir := filepath.Dir(output) if err := os.MkdirAll(dir, 0755); err != nil { return nil, fmt.Errorf("creating directory %q: %w", dir, err) } err := retry(5, func() error { var err error w, err = os.OpenFile(output, os.O_WRONLY|os.O_TRUNC|os.O_CREATE, 0644) return err }) if err != nil { return nil, fmt.Errorf("open output file %q: %w", output, err) } } return w, nil } func createSymlink(input, output string) error { if _, err := os.Stat(output); err == nil { if err = os.Remove(output); err != nil { return err } } if err := os.MkdirAll(filepath.Dir(output), 0755); err != nil { return err } if err := os.Symlink(input, output); err != nil { return err } return nil } type concatFileReader struct { filenames []string sep []byte opener func(string) (io.ReadCloser, error) cur io.ReadCloser sepLeft int } func newConcatFileReader(filenames []string, opener func(string) (io.ReadCloser, error), sep []byte) (*concatFileReader, error) { var cur io.ReadCloser if 0 < len(filenames) { var filename string filename, filenames = filenames[0], filenames[1:] var err error if cur, err = opener(filename); err != nil { return nil, err } } return &concatFileReader{filenames, sep, opener, cur, 0}, nil } func (r *concatFileReader) Read(p []byte) (int, error) { m := r.writeSep(p) // write remaining separator if r.cur == nil { return m, io.EOF } n, err := r.cur.Read(p[m:]) n += m // current reader is finished, load in the new reader if err == io.EOF { if err := r.cur.Close(); err != nil { return n, err } r.cur = nil if 0 < len(r.filenames) { var filename string filename, r.filenames = r.filenames[0], r.filenames[1:] if r.cur, err = r.opener(filename); err != nil { return n, err } r.sepLeft = len(r.sep) // if previous read returned (0, io.EOF), read from the new reader if n == 0 { return r.Read(p) } n += r.writeSep(p[n:]) } } return n, err } func (r *concatFileReader) writeSep(p []byte) int { if 0 < r.sepLeft { m := copy(p, r.sep[len(r.sep)-r.sepLeft:]) r.sepLeft -= m return m } return 0 } func (r *concatFileReader) Close() error { if r.cur != nil { return r.cur.Close() } return nil }