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  }