go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/common/gcloud/gs/path.go (about) 1 // Copyright 2015 The LUCI 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 gs 16 17 import ( 18 "strings" 19 ) 20 21 // Path is a Google Storage path. A full path consists of a Google storage 22 // bucket and a series of path components. 23 // 24 // An example of a Path is: 25 // 26 // gs://test-bucket/path/to/thing.txt 27 type Path string 28 29 // MakePath constructs a Google Storage path from optional bucket and filename 30 // components. 31 // 32 // Trailing forward slashes will be removed from the bucket name, if present. 33 func MakePath(bucket string, parts ...string) Path { 34 if len(parts) == 0 { 35 return makePath(bucket, "") 36 } else if len(parts) <= 1 { 37 return makePath(bucket, parts[0]) 38 } 39 path := makePath(bucket, parts[0]) 40 return path.Concat(parts[1], parts[2:]...) 41 } 42 43 func makePath(bucket, filename string) Path { 44 var carr [2]string 45 46 comps := carr[:0] 47 if b := stripTrailingSlashes(bucket); b != "" { 48 comps = append(comps, "gs://"+b) 49 } 50 if filename != "" { 51 comps = append(comps, filename) 52 } 53 return Path(strings.Join(comps, "/")) 54 } 55 56 // Bucket returns the Google Storage bucket component of the Path. If there is 57 // no bucket, an empty string will be returned. 58 func (p Path) Bucket() string { 59 b, _ := p.Split() 60 return b 61 } 62 63 // Filename returns the filename component of the Path. If there is no filename 64 // component, an empty string will be returned. 65 // 66 // Leading and trailing slashes will be truncated. 67 func (p Path) Filename() string { 68 _, f := p.Split() 69 return f 70 } 71 72 // Split returns the bucket and filename components of the Path. 73 // 74 // If a bucket is not defined (doesn't begin with "gs://"), the remainder will 75 // be considered to be the filename component. If a filename is not defined, 76 // an empty string will be returned. 77 func (p Path) Split() (bucket string, filename string) { 78 v, ok := trimPrefix(string(p), "gs://") 79 if ok { 80 // Has a "gs://" prefix, trim that to get the bucket. 81 sidx := strings.IndexRune(v, '/') 82 if sidx <= 0 { 83 // Only a Google Storage bucket name. 84 bucket = v 85 return 86 } 87 88 bucket = v[:sidx] 89 v = v[sidx+1:] 90 } 91 filename = v 92 return 93 } 94 95 // IsFullPath returns true if the Path contains both a bucket and file name. 96 func (p Path) IsFullPath() bool { 97 bucket, filename := p.Split() 98 return (bucket != "" && filename != "") 99 } 100 101 // Concat concatenates a filename component to the end of Path. 102 // 103 // Multiple components may be specified. In this case, each will be added as a 104 // "/"-delimited component, and will have any present trailing slashes stripped. 105 func (p Path) Concat(v string, parts ...string) Path { 106 comps := make([]string, 0, len(parts)+2) 107 add := func(v string) { 108 v = stripTrailingSlashes(v) 109 if len(v) > 0 { 110 comps = append(comps, v) 111 } 112 } 113 114 // Build our components slice. 115 b, f := p.Split() 116 if cleanBucket := stripTrailingSlashes(b); cleanBucket != "" { 117 add("gs://" + cleanBucket) 118 } 119 add(f) 120 add(v) 121 for _, p := range parts { 122 add(p) 123 } 124 return Path(strings.Join(comps, "/")) 125 } 126 127 func trimPrefix(s, prefix string) (string, bool) { 128 if strings.HasPrefix(s, prefix) { 129 return s[len(prefix):], true 130 } 131 return s, false 132 } 133 134 func stripTrailingSlashes(v string) string { 135 return strings.TrimRight(v, "/") 136 }