github.com/olivere/camlistore@v0.0.0-20140121221811-1b7ac2da0199/pkg/misc/closure/gendeps.go (about) 1 /* 2 Copyright 2013 The Camlistore Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 // Package closure provides tools to help with the use of the 18 // closure library. 19 // 20 // See https://code.google.com/p/closure-library/ 21 package closure 22 23 import ( 24 "bufio" 25 "bytes" 26 "fmt" 27 "io" 28 "net/http" 29 "os" 30 "regexp" 31 "strings" 32 "sync" 33 "time" 34 ) 35 36 // GenDeps returns the namespace dependencies between the closure javascript files in root. It does not descend in directories. 37 // Each of the files listed in the output is prepended with the path "../../", which is assumed to be the location where these files can be found, relative to Closure's base.js. 38 // 39 // The format for each relevant javascript file is: 40 // goog.addDependency("filepath", ["namespace provided"], ["required namespace 1", "required namespace 2", ...]); 41 func GenDeps(root http.FileSystem) ([]byte, error) { 42 // In the typical configuration, Closure is served at 'closure/goog/...'' 43 return GenDepsWithPath("../../", root) 44 } 45 46 // GenDepsWithPath is like GenDeps, but you can specify a path where the files are to be found at runtime relative to Closure's base.js. 47 func GenDepsWithPath(pathPrefix string, root http.FileSystem) ([]byte, error) { 48 d, err := root.Open("/") 49 if err != nil { 50 return nil, fmt.Errorf("Failed to open root of %v: %v", root, err) 51 } 52 fi, err := d.Stat() 53 if err != nil { 54 return nil, err 55 } 56 if !fi.IsDir() { 57 return nil, fmt.Errorf("root of %v is not a dir", root) 58 } 59 ent, err := d.Readdir(-1) 60 if err != nil { 61 return nil, fmt.Errorf("Could not read dir entries of root: %v", err) 62 } 63 var buf bytes.Buffer 64 for _, info := range ent { 65 name := info.Name() 66 if !strings.HasSuffix(name, ".js") { 67 continue 68 } 69 f, err := root.Open(name) 70 if err != nil { 71 return nil, fmt.Errorf("Could not open %v: %v", name, err) 72 } 73 prov, req, err := parseProvidesRequires(info, name, f) 74 f.Close() 75 if err != nil { 76 return nil, fmt.Errorf("Could not parse deps for %v: %v", name, err) 77 } 78 if len(prov) > 0 { 79 fmt.Fprintf(&buf, "goog.addDependency(%q, %v, %v);\n", pathPrefix+name, jsList(prov), jsList(req)) 80 } 81 } 82 return buf.Bytes(), nil 83 } 84 85 var provReqRx = regexp.MustCompile(`^goog\.(provide|require)\(['"]([\w\.]+)['"]\)`) 86 87 type depCacheItem struct { 88 modTime time.Time 89 provides, requires []string 90 } 91 92 var ( 93 depCacheMu sync.Mutex 94 depCache = map[string]depCacheItem{} 95 ) 96 97 func parseProvidesRequires(fi os.FileInfo, path string, f io.Reader) (provides, requires []string, err error) { 98 mt := fi.ModTime() 99 depCacheMu.Lock() 100 defer depCacheMu.Unlock() 101 if ci := depCache[path]; ci.modTime.Equal(mt) { 102 return ci.provides, ci.requires, nil 103 } 104 105 scanner := bufio.NewScanner(f) 106 for scanner.Scan() { 107 l := scanner.Text() 108 if !strings.HasPrefix(l, "goog.") { 109 continue 110 } 111 m := provReqRx.FindStringSubmatch(l) 112 if m != nil { 113 if m[1] == "provide" { 114 provides = append(provides, m[2]) 115 } else { 116 requires = append(requires, m[2]) 117 } 118 } 119 } 120 if err := scanner.Err(); err != nil { 121 return nil, nil, err 122 } 123 depCache[path] = depCacheItem{provides: provides, requires: requires, modTime: mt} 124 return provides, requires, nil 125 } 126 127 // jsList prints a list of strings as JavaScript list. 128 type jsList []string 129 130 func (s jsList) String() string { 131 var buf bytes.Buffer 132 buf.WriteByte('[') 133 for i, v := range s { 134 if i > 0 { 135 buf.WriteString(", ") 136 } 137 fmt.Fprintf(&buf, "%q", v) 138 } 139 buf.WriteByte(']') 140 return buf.String() 141 } 142 143 // Example of a match: 144 // goog.addDependency('asserts/asserts.js', ['goog.asserts', 'goog.asserts.AssertionError'], ['goog.debug.Error', 'goog.string']); 145 // So with m := depsRx.FindStringSubmatch, 146 // the provider: m[1] == "asserts/asserts.js" 147 // the provided namespaces: m[2] == "'goog.asserts', 'goog.asserts.AssertionError'" 148 // the required namespaces: m[5] == "'goog.debug.Error', 'goog.string'" 149 var depsRx = regexp.MustCompile(`^goog.addDependency\(['"]([^/]+[a-zA-Z0-9\-\_/\.]*\.js)['"], \[((['"][\w\.]+['"])+(, ['"][\w\.]+['"])*)\], \[((['"][\w\.]+['"])+(, ['"][\w\.]+['"])*)?\]\);`) 150 151 // ParseDeps reads closure namespace dependency lines and 152 // returns a map giving the js file provider for each namespace, 153 // and a map giving the namespace dependencies for each namespace. 154 func ParseDeps(r io.Reader) (providedBy map[string]string, requires map[string][]string, err error) { 155 providedBy = make(map[string]string) 156 requires = make(map[string][]string) 157 scanner := bufio.NewScanner(r) 158 for scanner.Scan() { 159 l := scanner.Text() 160 if strings.HasPrefix(l, "//") { 161 continue 162 } 163 if l == "" { 164 continue 165 } 166 m := depsRx.FindStringSubmatch(l) 167 if m == nil { 168 return nil, nil, fmt.Errorf("Invalid line in deps: %q", l) 169 } 170 jsfile := m[1] 171 provides := strings.Split(m[2], ", ") 172 var required []string 173 if m[5] != "" { 174 required = strings.Split( 175 strings.Replace(strings.Replace(m[5], "'", "", -1), `"`, "", -1), ", ") 176 } 177 for _, v := range provides { 178 namespace := strings.Trim(v, `'"`) 179 if otherjs, ok := providedBy[namespace]; ok { 180 return nil, nil, fmt.Errorf("Name %v is provided by both %v and %v", namespace, jsfile, otherjs) 181 } 182 providedBy[namespace] = jsfile 183 if _, ok := requires[namespace]; ok { 184 return nil, nil, fmt.Errorf("Name %v has two sets of dependencies") 185 } 186 if required != nil { 187 requires[namespace] = required 188 } 189 } 190 } 191 if err := scanner.Err(); err != nil { 192 return nil, nil, err 193 } 194 return providedBy, requires, nil 195 } 196 197 // DeepParseDeps reads closure namespace dependency lines and 198 // returns a map giving all the required js files for each namespace. 199 func DeepParseDeps(r io.Reader) (map[string][]string, error) { 200 providedBy, requires, err := ParseDeps(r) 201 if err != nil { 202 return nil, err 203 } 204 filesDeps := make(map[string][]string) 205 var deeperDeps func(namespace string) []string 206 deeperDeps = func(namespace string) []string { 207 if jsdeps, ok := filesDeps[namespace]; ok { 208 return jsdeps 209 } 210 jsfiles := []string{providedBy[namespace]} 211 for _, dep := range requires[namespace] { 212 jsfiles = append(jsfiles, deeperDeps(dep)...) 213 } 214 return jsfiles 215 } 216 for namespace, _ := range providedBy { 217 filesDeps[namespace] = deeperDeps(namespace) 218 } 219 return filesDeps, nil 220 }