key.go 4.8 KB
Newer Older
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
1 2 3
package datastore

import (
Juan Batiz-Benet's avatar
go fmt  
Juan Batiz-Benet committed
4 5 6
	"code.google.com/p/go-uuid/uuid"
	"path"
	"strings"
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
)

/*
A Key represents the unique identifier of an object.
Our Key scheme is inspired by file systems and Google App Engine key model.

Keys are meant to be unique across a system. Keys are hierarchical,
incorporating more and more specific namespaces. Thus keys can be deemed
'children' or 'ancestors' of other keys::

    Key("/Comedy")
    Key("/Comedy/MontyPython")

Also, every namespace can be parametrized to embed relevant object
information. For example, the Key `name` (most specific namespace) could
include the object type::

    Key("/Comedy/MontyPython/Actor:JohnCleese")
    Key("/Comedy/MontyPython/Sketch:CheeseShop")
    Key("/Comedy/MontyPython/Sketch:CheeseShop/Character:Mousebender")

*/
type Key struct {
Juan Batiz-Benet's avatar
go fmt  
Juan Batiz-Benet committed
30
	string
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
31 32 33
}

func NewKey(s string) Key {
Juan Batiz-Benet's avatar
go fmt  
Juan Batiz-Benet committed
34 35 36
	k := Key{s}
	k.Clean()
	return k
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
37 38 39 40
}

// Cleans up a Key, using path.Clean.
func (k *Key) Clean() {
Juan Batiz-Benet's avatar
go fmt  
Juan Batiz-Benet committed
41
	k.string = path.Clean("/" + k.string)
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
42 43 44 45
}

// Returns the string value of Key
func (k Key) String() string {
Juan Batiz-Benet's avatar
go fmt  
Juan Batiz-Benet committed
46
	return k.string
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
47 48
}

Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
49 50
// Returns the bytes value of Key
func (k Key) Bytes() []byte {
Juan Batiz-Benet's avatar
go fmt  
Juan Batiz-Benet committed
51
	return []byte(k.string)
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
52 53
}

Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
54 55 56 57
// Returns the `list` representation of this Key.
// NewKey("/Comedy/MontyPython/Actor:JohnCleese").List()
// ["Comedy", "MontyPythong", "Actor:JohnCleese"]
func (k Key) List() []string {
Juan Batiz-Benet's avatar
go fmt  
Juan Batiz-Benet committed
58
	return strings.Split(k.string, "/")[1:]
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
59 60 61 62 63 64
}

// Returns the reverse of this Key.
// NewKey("/Comedy/MontyPython/Actor:JohnCleese").Reverse()
// NewKey("/Actor:JohnCleese/MontyPython/Comedy")
func (k Key) Reverse() Key {
Juan Batiz-Benet's avatar
go fmt  
Juan Batiz-Benet committed
65 66 67 68 69 70
	l := k.List()
	r := make([]string, len(l), len(l))
	for i, e := range l {
		r[len(l)-i-1] = e
	}
	return NewKey(strings.Join(r, "/"))
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
71 72 73 74 75 76
}

// Returns the `namespaces` making up this Key.
// NewKey("/Comedy/MontyPython/Actor:JohnCleese").List()
// ["Comedy", "MontyPythong", "Actor:JohnCleese"]
func (k Key) Namespaces() []string {
Juan Batiz-Benet's avatar
go fmt  
Juan Batiz-Benet committed
77
	return k.List()
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
78 79 80 81 82 83
}

// Returns the "base" namespace of this key (like path.Base(filename))
// NewKey("/Comedy/MontyPython/Actor:JohnCleese").BaseNamespace()
// "Actor:JohnCleese"
func (k Key) BaseNamespace() string {
Juan Batiz-Benet's avatar
go fmt  
Juan Batiz-Benet committed
84 85
	n := k.Namespaces()
	return n[len(n)-1]
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
86 87 88 89 90 91
}

// Returns the "type" of this key (value of last namespace).
// NewKey("/Comedy/MontyPython/Actor:JohnCleese").List()
// "Actor"
func (k Key) Type() string {
Juan Batiz-Benet's avatar
go fmt  
Juan Batiz-Benet committed
92
	return NamespaceType(k.BaseNamespace())
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
93 94 95 96 97 98
}

// Returns the "name" of this key (field of last namespace).
// NewKey("/Comedy/MontyPython/Actor:JohnCleese").List()
// "Actor"
func (k Key) Name() string {
Juan Batiz-Benet's avatar
go fmt  
Juan Batiz-Benet committed
99
	return NamespaceValue(k.BaseNamespace())
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
100 101 102 103 104 105
}

// Returns an "instance" of this type key (appends value to namespace).
// NewKey("/Comedy/MontyPython/Actor:JohnCleese").List()
// "JohnCleese"
func (k Key) Instance(s string) Key {
Juan Batiz-Benet's avatar
go fmt  
Juan Batiz-Benet committed
106
	return NewKey(k.string + ":" + s)
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
107 108 109 110 111 112
}

// Returns the "path" of this key (parent + type).
// NewKey("/Comedy/MontyPython/Actor:JohnCleese").Path()
// NewKey("/Comedy/MontyPython/Actor")
func (k Key) Path() Key {
Juan Batiz-Benet's avatar
go fmt  
Juan Batiz-Benet committed
113 114
	s := k.Parent().string + "/" + NamespaceType(k.BaseNamespace())
	return NewKey(s)
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
115 116 117 118 119 120
}

// Returns the `parent` Key of this Key.
// NewKey("/Comedy/MontyPython/Actor:JohnCleese").Parent()
// NewKey("/Comedy/MontyPython")
func (k Key) Parent() Key {
Juan Batiz-Benet's avatar
go fmt  
Juan Batiz-Benet committed
121 122 123 124 125
	n := k.List()
	if len(n) == 1 {
		return NewKey("/")
	}
	return NewKey(strings.Join(n[:len(n)-1], "/"))
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
126 127 128 129 130 131
}

// Returns the `child` Key of this Key.
// NewKey("/Comedy/MontyPython").Child("Actor:JohnCleese")
// NewKey("/Comedy/MontyPython/Actor:JohnCleese")
func (k Key) Child(s string) Key {
Juan Batiz-Benet's avatar
go fmt  
Juan Batiz-Benet committed
132
	return NewKey(k.string + "/" + s)
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
133 134 135 136 137 138
}

// Returns whether this key is an ancestor of `other`
// NewKey("/Comedy").IsAncestorOf("/Comedy/MontyPython")
// true
func (k Key) IsAncestorOf(other Key) bool {
Juan Batiz-Benet's avatar
go fmt  
Juan Batiz-Benet committed
139 140 141 142
	if other.string == k.string {
		return false
	}
	return strings.HasPrefix(other.string, k.string)
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
143 144 145 146 147 148
}

// Returns whether this key is a descendent of `other`
// NewKey("/Comedy/MontyPython").IsDescendantOf("/Comedy")
// true
func (k Key) IsDescendantOf(other Key) bool {
Juan Batiz-Benet's avatar
go fmt  
Juan Batiz-Benet committed
149 150 151 152
	if other.string == k.string {
		return false
	}
	return strings.HasPrefix(k.string, other.string)
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
153 154 155
}

func (k Key) IsTopLevel() bool {
Juan Batiz-Benet's avatar
go fmt  
Juan Batiz-Benet committed
156
	return len(k.List()) == 1
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
157 158 159 160 161 162
}

// Returns a randomly (uuid) generated key.
// RandomKey()
// NewKey("/f98719ea086343f7b71f32ea9d9d521d")
func RandomKey() Key {
Juan Batiz-Benet's avatar
go fmt  
Juan Batiz-Benet committed
163
	return NewKey(strings.Replace(uuid.New(), "-", "", -1))
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
164 165 166 167 168 169 170 171 172 173 174 175 176 177 178
}

/*
A Key Namespace is like a path element.
A namespace can optionally include a type (delimited by ':')

    > NamespaceValue("Song:PhilosopherSong")
    PhilosopherSong
    > NamespaceType("Song:PhilosopherSong")
    Song
    > NamespaceType("Music:Song:PhilosopherSong")
    Music:Song
*/

func NamespaceType(namespace string) string {
Juan Batiz-Benet's avatar
go fmt  
Juan Batiz-Benet committed
179 180 181 182 183
	parts := strings.Split(namespace, ":")
	if len(parts) < 2 {
		return ""
	}
	return strings.Join(parts[0:len(parts)-1], ":")
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
184 185 186
}

func NamespaceValue(namespace string) string {
Juan Batiz-Benet's avatar
go fmt  
Juan Batiz-Benet committed
187 188
	parts := strings.Split(namespace, ":")
	return parts[len(parts)-1]
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
189
}