depswriter.py (6286B)
1 #!/usr/bin/env python 2 # 3 # Copyright 2009 The Closure Library Authors. All Rights Reserved. 4 # 5 # Licensed under the Apache License, Version 2.0 (the "License"); 6 # you may not use this file except in compliance with the License. 7 # You may obtain a copy of the License at 8 # 9 # http://www.apache.org/licenses/LICENSE-2.0 10 # 11 # Unless required by applicable law or agreed to in writing, software 12 # distributed under the License is distributed on an "AS-IS" BASIS, 13 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 # See the License for the specific language governing permissions and 15 # limitations under the License. 16 17 18 """Generates out a Closure deps.js file given a list of JavaScript sources. 19 20 Paths can be specified as arguments or (more commonly) specifying trees 21 with the flags (call with --help for descriptions). 22 23 Usage: depswriter.py [path/to/js1.js [path/to/js2.js] ...] 24 """ 25 26 import logging 27 import optparse 28 import os 29 import posixpath 30 import shlex 31 import sys 32 33 import source 34 import treescan 35 36 37 __author__ = 'nnaze@google.com (Nathan Naze)' 38 39 40 def MakeDepsFile(source_map): 41 """Make a generated deps file. 42 43 Args: 44 source_map: A dict map of the source path to source.Source object. 45 46 Returns: 47 str, A generated deps file source. 48 """ 49 50 # Write in path alphabetical order 51 paths = sorted(source_map.keys()) 52 53 lines = [] 54 55 for path in paths: 56 js_source = source_map[path] 57 58 # We don't need to add entries that don't provide anything. 59 if js_source.provides: 60 lines.append(_GetDepsLine(path, js_source)) 61 62 return ''.join(lines) 63 64 65 def _GetDepsLine(path, js_source): 66 """Get a deps.js file string for a source.""" 67 68 provides = sorted(js_source.provides) 69 requires = sorted(js_source.requires) 70 module = 'true' if js_source.is_goog_module else 'false' 71 72 return 'goog.addDependency(\'%s\', %s, %s, %s);\n' % ( 73 path, provides, requires, module) 74 75 76 def _GetOptionsParser(): 77 """Get the options parser.""" 78 79 parser = optparse.OptionParser(__doc__) 80 81 parser.add_option('--output_file', 82 dest='output_file', 83 action='store', 84 help=('If specified, write output to this path instead of ' 85 'writing to standard output.')) 86 parser.add_option('--root', 87 dest='roots', 88 default=[], 89 action='append', 90 help='A root directory to scan for JS source files. ' 91 'Paths of JS files in generated deps file will be ' 92 'relative to this path. This flag may be specified ' 93 'multiple times.') 94 parser.add_option('--root_with_prefix', 95 dest='roots_with_prefix', 96 default=[], 97 action='append', 98 help='A root directory to scan for JS source files, plus ' 99 'a prefix (if either contains a space, surround with ' 100 'quotes). Paths in generated deps file will be relative ' 101 'to the root, but preceded by the prefix. This flag ' 102 'may be specified multiple times.') 103 parser.add_option('--path_with_depspath', 104 dest='paths_with_depspath', 105 default=[], 106 action='append', 107 help='A path to a source file and an alternate path to ' 108 'the file in the generated deps file (if either contains ' 109 'a space, surround with whitespace). This flag may be ' 110 'specified multiple times.') 111 return parser 112 113 114 def _NormalizePathSeparators(path): 115 """Replaces OS-specific path separators with POSIX-style slashes. 116 117 Args: 118 path: str, A file path. 119 120 Returns: 121 str, The path with any OS-specific path separators (such as backslash on 122 Windows) replaced with URL-compatible forward slashes. A no-op on systems 123 that use POSIX paths. 124 """ 125 return path.replace(os.sep, posixpath.sep) 126 127 128 def _GetRelativePathToSourceDict(root, prefix=''): 129 """Scans a top root directory for .js sources. 130 131 Args: 132 root: str, Root directory. 133 prefix: str, Prefix for returned paths. 134 135 Returns: 136 dict, A map of relative paths (with prefix, if given), to source.Source 137 objects. 138 """ 139 # Remember and restore the cwd when we're done. We work from the root so 140 # that paths are relative from the root. 141 start_wd = os.getcwd() 142 os.chdir(root) 143 144 path_to_source = {} 145 for path in treescan.ScanTreeForJsFiles('.'): 146 prefixed_path = _NormalizePathSeparators(os.path.join(prefix, path)) 147 path_to_source[prefixed_path] = source.Source(source.GetFileContents(path)) 148 149 os.chdir(start_wd) 150 151 return path_to_source 152 153 154 def _GetPair(s): 155 """Return a string as a shell-parsed tuple. Two values expected.""" 156 try: 157 # shlex uses '\' as an escape character, so they must be escaped. 158 s = s.replace('\\', '\\\\') 159 first, second = shlex.split(s) 160 return (first, second) 161 except: 162 raise Exception('Unable to parse input line as a pair: %s' % s) 163 164 165 def main(): 166 """CLI frontend to MakeDepsFile.""" 167 logging.basicConfig(format=(sys.argv[0] + ': %(message)s'), 168 level=logging.INFO) 169 options, args = _GetOptionsParser().parse_args() 170 171 path_to_source = {} 172 173 # Roots without prefixes 174 for root in options.roots: 175 path_to_source.update(_GetRelativePathToSourceDict(root)) 176 177 # Roots with prefixes 178 for root_and_prefix in options.roots_with_prefix: 179 root, prefix = _GetPair(root_and_prefix) 180 path_to_source.update(_GetRelativePathToSourceDict(root, prefix=prefix)) 181 182 # Source paths 183 for path in args: 184 path_to_source[path] = source.Source(source.GetFileContents(path)) 185 186 # Source paths with alternate deps paths 187 for path_with_depspath in options.paths_with_depspath: 188 srcpath, depspath = _GetPair(path_with_depspath) 189 path_to_source[depspath] = source.Source(source.GetFileContents(srcpath)) 190 191 # Make our output pipe. 192 if options.output_file: 193 out = open(options.output_file, 'w') 194 else: 195 out = sys.stdout 196 197 out.write('// This file was autogenerated by %s.\n' % sys.argv[0]) 198 out.write('// Please do not edit.\n') 199 200 out.write(MakeDepsFile(path_to_source)) 201 202 203 if __name__ == '__main__': 204 main()