path.go 2.75 KB
Newer Older
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
1 2 3
package path

import (
4
	"errors"
5 6
	"path"
	"strings"
7

Steven Allen's avatar
Steven Allen committed
8
	cid "gx/ipfs/QmcZfnkapfECQGcLZaf9B79NRg7cRa9EnZh4LSbkCzwNvY/go-cid"
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
9 10
)

11
// ErrBadPath is returned when a given path is incorrectly formatted
12
var ErrBadPath = errors.New("invalid 'ipfs ref' path")
13

Jeromy's avatar
Jeromy committed
14 15 16
// TODO: debate making this a private struct wrapped in a public interface
// would allow us to control creation, and cache segments.
type Path string
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
17

18 19 20 21 22
// FromString safely converts a string type to a Path type
func FromString(s string) Path {
	return Path(s)
}

Jeromy's avatar
Jeromy committed
23 24 25
// FromCid safely converts a cid.Cid type to a Path type
func FromCid(c *cid.Cid) Path {
	return Path("/ipfs/" + c.String())
26 27
}

Jeromy's avatar
Jeromy committed
28 29 30
func (p Path) Segments() []string {
	cleaned := path.Clean(string(p))
	segments := strings.Split(cleaned, "/")
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
31

Jeromy's avatar
Jeromy committed
32 33 34
	// Ignore leading slash
	if len(segments[0]) == 0 {
		segments = segments[1:]
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
35 36
	}

Jeromy's avatar
Jeromy committed
37
	return segments
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
38 39
}

Jeromy's avatar
Jeromy committed
40 41
func (p Path) String() string {
	return string(p)
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
42
}
43

44 45 46 47 48 49
// IsJustAKey returns true if the path is of the form <key> or /ipfs/<key>.
func (p Path) IsJustAKey() bool {
	parts := p.Segments()
	return (len(parts) == 2 && parts[0] == "ipfs")
}

50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67
// PopLastSegment returns a new Path without its final segment, and the final
// segment, separately. If there is no more to pop (the path is just a key),
// the original path is returned.
func (p Path) PopLastSegment() (Path, string, error) {

	if p.IsJustAKey() {
		return p, "", nil
	}

	segs := p.Segments()
	newPath, err := ParsePath("/" + strings.Join(segs[:len(segs)-1], "/"))
	if err != nil {
		return "", "", err
	}

	return newPath, segs[len(segs)-1], nil
}

68 69
func FromSegments(prefix string, seg ...string) (Path, error) {
	return ParsePath(prefix + strings.Join(seg, "/"))
70
}
71 72 73

func ParsePath(txt string) (Path, error) {
	parts := strings.Split(txt, "/")
Jeromy's avatar
Jeromy committed
74
	if len(parts) == 1 {
Jeromy's avatar
Jeromy committed
75
		kp, err := ParseCidToPath(txt)
Jeromy's avatar
Jeromy committed
76 77 78 79
		if err == nil {
			return kp, nil
		}
	}
80

81 82
	// if the path doesnt being with a '/'
	// we expect this to start with a hash, and be an 'ipfs' path
83
	if parts[0] != "" {
Jeromy's avatar
Jeromy committed
84
		if _, err := ParseCidToPath(parts[0]); err != nil {
85 86 87 88
			return "", ErrBadPath
		}
		// The case when the path starts with hash without a protocol prefix
		return Path("/ipfs/" + txt), nil
89 90
	}

91 92 93 94
	if len(parts) < 3 {
		return "", ErrBadPath
	}

95
	if parts[1] == "ipfs" {
Jeromy's avatar
Jeromy committed
96
		if _, err := ParseCidToPath(parts[2]); err != nil {
97 98 99
			return "", err
		}
	} else if parts[1] != "ipns" {
100 101 102 103 104 105
		return "", ErrBadPath
	}

	return Path(txt), nil
}

Jeromy's avatar
Jeromy committed
106
func ParseCidToPath(txt string) (Path, error) {
107 108 109 110
	if txt == "" {
		return "", ErrNoComponents
	}

Jeromy's avatar
Jeromy committed
111 112
	c, err := cid.Decode(txt)
	if err != nil {
113 114
		return "", err
	}
Jeromy's avatar
Jeromy committed
115 116

	return FromCid(c), nil
117
}
Jeromy's avatar
Jeromy committed
118 119 120 121 122

func (p *Path) IsValid() error {
	_, err := ParsePath(p.String())
	return err
}
123 124 125 126

func Join(pths []string) string {
	return strings.Join(pths, "/")
}
rht's avatar
rht committed
127 128 129 130

func SplitList(pth string) []string {
	return strings.Split(pth, "/")
}