// Copyright 2023 The Hugo Authors. All rights reserved. // // 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 identity import ( "path/filepath" "strings" "sync" "sync/atomic" ) // NewIdentityManager creates a new Manager starting at id. func NewManager(id Provider) Manager { return &identityManager{ Provider: id, ids: Identities{id.GetIdentity(): id}, } } // NewPathIdentity creates a new Identity with the two identifiers // type and path. func NewPathIdentity(typ, pat string) PathIdentity { pat = strings.ToLower(strings.TrimPrefix(filepath.ToSlash(pat), "/")) return PathIdentity{Type: typ, Path: pat} } // Identities stores identity providers. type Identities map[Identity]Provider func (ids Identities) search(depth int, id Identity) Provider { if v, found := ids[id.GetIdentity()]; found { return v } depth++ // There may be infinite recursion in templates. if depth > 100 { // Bail out. return nil } for _, v := range ids { switch t := v.(type) { case IdentitiesProvider: if nested := t.GetIdentities().search(depth, id); nested != nil { return nested } } } return nil } // IdentitiesProvider provides all Identities. type IdentitiesProvider interface { GetIdentities() Identities } // Identity represents an thing that can provide an identify. This can be // any Go type, but the Identity returned by GetIdentify must be hashable. type Identity interface { Provider Name() string } // Manager manages identities, and is itself a Provider of Identity. type Manager interface { SearchProvider Add(ids ...Provider) Reset() } // SearchProvider provides access to the chained set of identities. type SearchProvider interface { Provider IdentitiesProvider Search(id Identity) Provider } // A PathIdentity is a common identity identified by a type and a path, e.g. "layouts" and "_default/single.html". type PathIdentity struct { Type string Path string } // GetIdentity returns itself. func (id PathIdentity) GetIdentity() Identity { return id } // Name returns the Path. func (id PathIdentity) Name() string { return id.Path } // A KeyValueIdentity a general purpose identity. type KeyValueIdentity struct { Key string Value string } // GetIdentity returns itself. func (id KeyValueIdentity) GetIdentity() Identity { return id } // Name returns the Key. func (id KeyValueIdentity) Name() string { return id.Key } // Provider provides the comparable Identity. type Provider interface { // GetIdentity is for internal use. GetIdentity() Identity } type identityManager struct { sync.Mutex Provider ids Identities } func (im *identityManager) Add(ids ...Provider) { im.Lock() for _, id := range ids { im.ids[id.GetIdentity()] = id } im.Unlock() } func (im *identityManager) Reset() { im.Lock() id := im.GetIdentity() im.ids = Identities{id.GetIdentity(): id} im.Unlock() } // TODO(bep) these identities are currently only read on server reloads // so there should be no concurrency issues, but that may change. func (im *identityManager) GetIdentities() Identities { im.Lock() defer im.Unlock() return im.ids } func (im *identityManager) Search(id Identity) Provider { im.Lock() defer im.Unlock() return im.ids.search(0, id.GetIdentity()) } // Incrementer increments and returns the value. // Typically used for IDs. type Incrementer interface { Incr() int } // IncrementByOne implements Incrementer adding 1 every time Incr is called. type IncrementByOne struct { counter uint64 } func (c *IncrementByOne) Incr() int { return int(atomic.AddUint64(&c.counter, uint64(1))) }