blob: cb67d40918819826a20eddd9e6467363d3bfeef5 [file] [log] [blame]
// Copyright 2015 The LUCI Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package gs
import (
"strings"
)
// Path is a Google Storage path. A full path consists of a Google storage
// bucket and a series of path components.
//
// An example of a Path is:
//
// gs://test-bucket/path/to/thing.txt
type Path string
// MakePath constructs a Google Storage path from optional bucket and filename
// components.
//
// Trailing forward slashes will be removed from the bucket name, if present.
func MakePath(bucket string, parts ...string) Path {
if len(parts) == 0 {
return makePath(bucket, "")
} else if len(parts) <= 1 {
return makePath(bucket, parts[0])
}
path := makePath(bucket, parts[0])
return path.Concat(parts[1], parts[2:]...)
}
func makePath(bucket, filename string) Path {
var carr [2]string
comps := carr[:0]
if b := stripTrailingSlashes(bucket); b != "" {
comps = append(comps, "gs://"+b)
}
if filename != "" {
comps = append(comps, filename)
}
return Path(strings.Join(comps, "/"))
}
// Bucket returns the Google Storage bucket component of the Path. If there is
// no bucket, an empty string will be returned.
func (p Path) Bucket() string {
b, _ := p.Split()
return b
}
// Filename returns the filename component of the Path. If there is no filename
// component, an empty string will be returned.
//
// Leading and trailing slashes will be truncated.
func (p Path) Filename() string {
_, f := p.Split()
return f
}
// Split returns the bucket and filename components of the Path.
//
// If a bucket is not defined (doesn't begin with "gs://"), the remainder will
// be considered to be the filename component. If a filename is not defined,
// an empty string will be returned.
func (p Path) Split() (bucket string, filename string) {
v, ok := trimPrefix(string(p), "gs://")
if ok {
// Has a "gs://" prefix, trim that to get the bucket.
sidx := strings.IndexRune(v, '/')
if sidx <= 0 {
// Only a Google Storage bucket name.
bucket = v
return
}
bucket = v[:sidx]
v = v[sidx+1:]
}
filename = v
return
}
// IsFullPath returns true if the Path contains both a bucket and file name.
func (p Path) IsFullPath() bool {
bucket, filename := p.Split()
return (bucket != "" && filename != "")
}
// Concat concatenates a filename component to the end of Path.
//
// Multiple components may be specified. In this case, each will be added as a
// "/"-delimited component, and will have any present trailing slashes stripped.
func (p Path) Concat(v string, parts ...string) Path {
comps := make([]string, 0, len(parts)+2)
add := func(v string) {
v = stripTrailingSlashes(v)
if len(v) > 0 {
comps = append(comps, v)
}
}
// Build our components slice.
b, f := p.Split()
if cleanBucket := stripTrailingSlashes(b); cleanBucket != "" {
add("gs://" + cleanBucket)
}
add(f)
add(v)
for _, p := range parts {
add(p)
}
return Path(strings.Join(comps, "/"))
}
func trimPrefix(s, prefix string) (string, bool) {
if strings.HasPrefix(s, prefix) {
return s[len(prefix):], true
}
return s, false
}
func stripTrailingSlashes(v string) string {
return strings.TrimRight(v, "/")
}