github.com/gocaveman/caveman@v0.0.0-20191211162744-0ddf99dbdf6e/uifiles/uiregistry/uiregistry.go (about) 1 // FIXME: crap, this should really be "uifilesregistry" to follow the convention... 2 // A registry of CSS, JS files and their dependency chain. 3 package uiregistry 4 5 import ( 6 "errors" 7 "fmt" 8 "strings" 9 "sync" 10 11 "github.com/gocaveman/caveman/webutil" 12 ) 13 14 var ErrNotFound = errors.New("not found") 15 16 // Global is the global registry instance, modules which provide libraries should register them 17 // here in their init() function. 18 // FIXME: Instead of a variable, I think we need two function calls - one to get something that implements 19 // an interface for registering, and another that returns an interface that can read. In the read call we 20 // can verify it is called from main - webutil.MainOnly() 21 var global = NewUIRegistry() 22 23 func Contents() *UIRegistry { 24 return global 25 } 26 27 func ParseName(s string) (typ, name string, err error) { 28 parts := strings.Split(s, ":") 29 if len(parts) != 2 { 30 return "", "", fmt.Errorf("expected exactly two colon separated parts but instead found %d part(s)", len(parts)) 31 } 32 return parts[0], parts[1], nil 33 } 34 35 // FIXME: what if we use http.FileSystem and a path, instead of using webutil.DataSource? 36 // It would be one less funky custom type and has effectively the same semantics. 37 38 func MustRegister(name string, deps []string, ds webutil.DataSource) { 39 global.MustRegister(name, deps, ds) 40 } 41 42 func Register(name string, deps []string, ds webutil.DataSource) error { 43 return global.Register(name, deps, ds) 44 } 45 46 // NewUIRegistry makes a new empty initialized UIRegistry. 47 func NewUIRegistry() *UIRegistry { 48 return &UIRegistry{ 49 reg: make(map[string]*entry), 50 } 51 } 52 53 // UIRegistry is a registry of JS and CSS libraries; the global instance of which is called Global in this package. 54 type UIRegistry struct { 55 rwmu sync.RWMutex 56 57 reg map[string]*entry 58 } 59 60 func (r *UIRegistry) MustRegister(name string, deps []string, ds webutil.DataSource) { 61 err := r.Register(name, deps, ds) 62 if err != nil { 63 panic(err) 64 } 65 } 66 func (r *UIRegistry) Register(name string, deps []string, ds webutil.DataSource) error { 67 r.rwmu.Lock() 68 defer r.rwmu.Unlock() 69 70 _, _, err := ParseName(name) 71 if err != nil { 72 return err 73 } 74 75 r.reg[name] = &entry{ 76 DataSource: ds, 77 Deps: deps, 78 } 79 80 return nil 81 } 82 83 // Lookup takes a single name and returns the corresponding DataSource. 84 func (r *UIRegistry) Lookup(name string) (webutil.DataSource, error) { 85 86 r.rwmu.RLock() 87 defer r.rwmu.RUnlock() 88 89 if ret, ok := r.reg[name]; ok { 90 return ret.DataSource, nil 91 } 92 93 return nil, ErrNotFound 94 } 95 96 // ResolveDeps takes one or more names and returns a list of the libraries that need to be 97 // included in order to resolve all dependencies, in the correct sequence. 98 func (r *UIRegistry) ResolveDeps(names ...string) ([]string, error) { 99 100 r.rwmu.RLock() 101 defer r.rwmu.RUnlock() 102 103 var out []string 104 105 // go through each requested library 106 for _, name := range names { 107 108 entry, ok := r.reg[name] 109 if !ok { 110 return nil, fmt.Errorf("unable to find registry entry for %q", name) 111 } 112 113 // for each dependency add it before this entry, unless it's already there 114 for _, dep := range entry.Deps { 115 if !stringSliceContains(out, dep) { 116 out = append(out, dep) 117 } 118 } 119 120 // add this entry 121 out = append(out, name) 122 123 } 124 125 // if after including dependencies the list was changed, recurse and resolve dependencies again 126 if !isSameStrings(out, names) { 127 return r.ResolveDeps(out...) 128 } 129 130 // dependencies didn't change the list, we're done 131 return out, nil 132 133 } 134 135 // Resolver describes a registry which is capable of resolving requests for libraries. 136 // Components which need to resolve libraries but not register them (i.e. stuff dealing 137 // with js and css files during the render path) should use this interface as the 138 // appropriate abstraction. 139 type Resolver interface { 140 Lookup(name string) (webutil.DataSource, error) 141 ResolveDeps(name ...string) ([]string, error) 142 } 143 144 // Entry describes a specific version of a library, it's dependencies and provides a way to get it's raw data (DataSource) 145 type entry struct { 146 DataSource webutil.DataSource 147 Deps []string 148 } 149 150 func isSameStrings(s1 []string, s2 []string) bool { 151 152 if len(s1) != len(s2) { 153 return false 154 } 155 156 for i := 0; i < len(s1); i++ { 157 if s1[i] != s2[i] { 158 return false 159 } 160 } 161 162 return true 163 } 164 165 func stringSliceContains(haystack []string, needle string) bool { 166 for _, e := range haystack { 167 if e == needle { 168 return true 169 } 170 } 171 return false 172 }