1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164
|
package main
import (
"fmt"
"io"
"os"
"path/filepath"
"github.com/matryer/try"
)
// 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 := try.Do(func(attempt int) (bool, error) {
var ferr error
r, ferr = os.Open(input)
return attempt < 5, ferr
})
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, 0777); err != nil {
return nil, fmt.Errorf("creating directory %q: %w", dir, err)
}
err := try.Do(func(attempt int) (bool, error) {
var ferr error
w, ferr = os.OpenFile(output, os.O_WRONLY|os.O_TRUNC|os.O_CREATE, 0666)
return attempt < 5, ferr
})
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), 0777); 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
}
|