this repo has no description
at master 109 lines 3.7 kB view raw
1// Copyright 2019 CUE Authors 2// 3// Licensed under the Apache License, Version 2.0 (the "License"); 4// you may not use this file except in compliance with the License. 5// You may obtain a copy of the License at 6// 7// http://www.apache.org/licenses/LICENSE-2.0 8// 9// Unless required by applicable law or agreed to in writing, software 10// distributed under the License is distributed on an "AS IS" BASIS, 11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12// See the License for the specific language governing permissions and 13// limitations under the License. 14 15// Package source contains utility functions that standardize reading source 16// bytes across cue packages. 17package source 18 19import ( 20 "bytes" 21 "fmt" 22 "io" 23 "math" 24 "os" 25 "strings" 26) 27 28// ReadAll loads the source bytes for the given arguments. If src != nil, 29// ReadAll converts src to a []byte if possible; otherwise it returns an 30// error. If src == nil, ReadAll returns the result of reading the file 31// specified by filename. 32func ReadAll(filename string, src any) ([]byte, error) { 33 if src != nil { 34 switch src := src.(type) { 35 case string: 36 return []byte(src), nil 37 case []byte: 38 return src, nil 39 case *bytes.Buffer: 40 // is io.Reader, but src is already available in []byte form 41 return src.Bytes(), nil 42 case io.Reader: 43 return io.ReadAll(src) 44 } 45 return nil, fmt.Errorf("invalid source type %T", src) 46 } 47 return os.ReadFile(filename) 48} 49 50// ReadAllSize is like [io.ReadAll] while taking advantage of a size hint for the input reader, 51// much like [os.ReadFile] does when reading regular files with a known size. 52// When the size hint is negative, it simply uses [io.ReadAll]. 53func ReadAllSize(r io.Reader, size int) ([]byte, error) { 54 if size >= 0 { 55 // We use a [bytes.Buffer] here, because the given size is a hint, 56 // and not guaranteed to be exactly correct. 57 // 58 // Before each read, [bytes.Buffer] ensures that the internal buffer 59 // has enough available capacity to read at least [bytes.MinRead] bytes. 60 // Many readers tend to signal EOF via a final (0, EOF) read, 61 // which then triggers growing the slice to accomodate [bytes.MinRead]. 62 buf := bytes.NewBuffer(make([]byte, 0, size+bytes.MinRead)) 63 _, err := buf.ReadFrom(r) 64 return buf.Bytes(), err 65 } 66 return io.ReadAll(r) 67} 68 69// Open creates a source reader for the given arguments. 70// If src != nil, Open converts src to an [io.Reader] if possible; otherwise it returns an error. 71// If src == nil, Open returns the result of opening the file specified by filename. 72// 73// The caller must check if the result is an [io.Closer], and if so, close it when done. 74// The size of the opened reader is returned if possible, or -1 otherwise. 75func Open(filename string, src any) (_ io.Reader, size int, _ error) { 76 if src != nil { 77 switch src := src.(type) { 78 case string: 79 return strings.NewReader(src), len(src), nil 80 case []byte: 81 return bytes.NewReader(src), len(src), nil 82 case *os.File: 83 return fileWithSize(src) 84 case io.Reader: 85 return src, -1, nil 86 } 87 return nil, -1, fmt.Errorf("invalid source type %T", src) 88 } 89 f, err := os.Open(filename) 90 if err != nil { 91 return nil, -1, err 92 } 93 return fileWithSize(f) 94} 95 96func fileWithSize(f *os.File) (io.Reader, int, error) { 97 // If we just opened a regular file, return its size too. 98 // If we can't get its size, such as non-regular files, don't give one. 99 stat, err := f.Stat() 100 if err != nil || !stat.Mode().IsRegular() { 101 return f, -1, nil 102 } 103 size := stat.Size() 104 // If the size would overflow an int, it won't fit in memory anyway. 105 if size > math.MaxInt { 106 return f, -1, nil 107 } 108 return f, int(size), nil 109}