github.com/slspeek/camlistore_namedsearch@v0.0.0-20140519202248-ed6f70f7721a/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 if strings.HasPrefix(name, ".#") { 70 // Emacs noise. 71 continue 72 } 73 f, err := root.Open(name) 74 if err != nil { 75 return nil, fmt.Errorf("Could not open %v: %v", name, err) 76 } 77 prov, req, err := parseProvidesRequires(info, name, f) 78 f.Close() 79 if err != nil { 80 return nil, fmt.Errorf("Could not parse deps for %v: %v", name, err) 81 } 82 if len(prov) > 0 { 83 fmt.Fprintf(&buf, "goog.addDependency(%q, %v, %v);\n", pathPrefix+name, jsList(prov), jsList(req)) 84 } 85 } 86 return buf.Bytes(), nil 87 } 88 89 var provReqRx = regexp.MustCompile(`^goog\.(provide|require)\(['"]([\w\.]+)['"]\)`) 90 91 type depCacheItem struct { 92 modTime time.Time 93 provides, requires []string 94 } 95 96 var ( 97 depCacheMu sync.Mutex 98 depCache = map[string]depCacheItem{} 99 ) 100 101 func parseProvidesRequires(fi os.FileInfo, path string, f io.Reader) (provides, requires []string, err error) { 102 mt := fi.ModTime() 103 depCacheMu.Lock() 104 defer depCacheMu.Unlock() 105 if ci := depCache[path]; ci.modTime.Equal(mt) { 106 return ci.provides, ci.requires, nil 107 } 108 109 scanner := bufio.NewScanner(f) 110 for scanner.Scan() { 111 l := scanner.Text() 112 if !strings.HasPrefix(l, "goog.") { 113 continue 114 } 115 m := provReqRx.FindStringSubmatch(l) 116 if m != nil { 117 if m[1] == "provide" { 118 provides = append(provides, m[2]) 119 } else { 120 requires = append(requires, m[2]) 121 } 122 } 123 } 124 if err := scanner.Err(); err != nil { 125 return nil, nil, err 126 } 127 depCache[path] = depCacheItem{provides: provides, requires: requires, modTime: mt} 128 return provides, requires, nil 129 } 130 131 // jsList prints a list of strings as JavaScript list. 132 type jsList []string 133 134 func (s jsList) String() string { 135 var buf bytes.Buffer 136 buf.WriteByte('[') 137 for i, v := range s { 138 if i > 0 { 139 buf.WriteString(", ") 140 } 141 fmt.Fprintf(&buf, "%q", v) 142 } 143 buf.WriteByte(']') 144 return buf.String() 145 } 146 147 // Example of a match: 148 // goog.addDependency('asserts/asserts.js', ['goog.asserts', 'goog.asserts.AssertionError'], ['goog.debug.Error', 'goog.string']); 149 // So with m := depsRx.FindStringSubmatch, 150 // the provider: m[1] == "asserts/asserts.js" 151 // the provided namespaces: m[2] == "'goog.asserts', 'goog.asserts.AssertionError'" 152 // the required namespaces: m[5] == "'goog.debug.Error', 'goog.string'" 153 var depsRx = regexp.MustCompile(`^goog.addDependency\(['"]([^/]+[a-zA-Z0-9\-\_/\.]*\.js)['"], \[((['"][\w\.]+['"])+(, ['"][\w\.]+['"])*)\], \[((['"][\w\.]+['"])+(, ['"][\w\.]+['"])*)?\]\);`) 154 155 // ParseDeps reads closure namespace dependency lines and 156 // returns a map giving the js file provider for each namespace, 157 // and a map giving the namespace dependencies for each namespace. 158 func ParseDeps(r io.Reader) (providedBy map[string]string, requires map[string][]string, err error) { 159 providedBy = make(map[string]string) 160 requires = make(map[string][]string) 161 scanner := bufio.NewScanner(r) 162 for scanner.Scan() { 163 l := scanner.Text() 164 if strings.HasPrefix(l, "//") { 165 continue 166 } 167 if l == "" { 168 continue 169 } 170 m := depsRx.FindStringSubmatch(l) 171 if m == nil { 172 return nil, nil, fmt.Errorf("Invalid line in deps: %q", l) 173 } 174 jsfile := m[1] 175 provides := strings.Split(m[2], ", ") 176 var required []string 177 if m[5] != "" { 178 required = strings.Split( 179 strings.Replace(strings.Replace(m[5], "'", "", -1), `"`, "", -1), ", ") 180 } 181 for _, v := range provides { 182 namespace := strings.Trim(v, `'"`) 183 if otherjs, ok := providedBy[namespace]; ok { 184 return nil, nil, fmt.Errorf("Name %v is provided by both %v and %v", namespace, jsfile, otherjs) 185 } 186 providedBy[namespace] = jsfile 187 if _, ok := requires[namespace]; ok { 188 return nil, nil, fmt.Errorf("Name %v has two sets of dependencies") 189 } 190 if required != nil { 191 requires[namespace] = required 192 } 193 } 194 } 195 if err := scanner.Err(); err != nil { 196 return nil, nil, err 197 } 198 return providedBy, requires, nil 199 } 200 201 // DeepParseDeps reads closure namespace dependency lines and 202 // returns a map giving all the required js files for each namespace. 203 func DeepParseDeps(r io.Reader) (map[string][]string, error) { 204 providedBy, requires, err := ParseDeps(r) 205 if err != nil { 206 return nil, err 207 } 208 filesDeps := make(map[string][]string) 209 var deeperDeps func(namespace string) []string 210 deeperDeps = func(namespace string) []string { 211 if jsdeps, ok := filesDeps[namespace]; ok { 212 return jsdeps 213 } 214 jsfiles := []string{providedBy[namespace]} 215 for _, dep := range requires[namespace] { 216 jsfiles = append(jsfiles, deeperDeps(dep)...) 217 } 218 return jsfiles 219 } 220 for namespace, _ := range providedBy { 221 filesDeps[namespace] = deeperDeps(namespace) 222 } 223 return filesDeps, nil 224 }