diff options
Diffstat (limited to 'chromium/third_party/google-closure-library/closure/bin')
21 files changed, 2938 insertions, 0 deletions
diff --git a/chromium/third_party/google-closure-library/closure/bin/build/closurebuilder.py b/chromium/third_party/google-closure-library/closure/bin/build/closurebuilder.py new file mode 100755 index 00000000000..b542bd0d1b6 --- /dev/null +++ b/chromium/third_party/google-closure-library/closure/bin/build/closurebuilder.py @@ -0,0 +1,293 @@ +#!/usr/bin/env python +# +# Copyright 2009 The Closure Library Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS-IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +"""Utility for Closure Library dependency calculation. + +ClosureBuilder scans source files to build dependency info. From the +dependencies, the script can produce a manifest in dependency order, +a concatenated script, or compiled output from the Closure Compiler. + +Paths to files can be expressed as individual arguments to the tool (intended +for use with find and xargs). As a convenience, --root can be used to specify +all JS files below a directory. + +usage: %prog [options] [file1.js file2.js ...] +""" + +__author__ = 'nnaze@google.com (Nathan Naze)' + + +import io +import logging +import optparse +import os +import sys + +import depstree +import jscompiler +import source +import treescan + + +def _GetOptionsParser(): + """Get the options parser.""" + + parser = optparse.OptionParser(__doc__) + parser.add_option('-i', + '--input', + dest='inputs', + action='append', + default=[], + help='One or more input files to calculate dependencies ' + 'for. The namespaces in this file will be combined with ' + 'those given with the -n flag to form the set of ' + 'namespaces to find dependencies for.') + parser.add_option('-n', + '--namespace', + dest='namespaces', + action='append', + default=[], + help='One or more namespaces to calculate dependencies ' + 'for. These namespaces will be combined with those given ' + 'with the -i flag to form the set of namespaces to find ' + 'dependencies for. A Closure namespace is a ' + 'dot-delimited path expression declared with a call to ' + 'goog.provide() (e.g. "goog.array" or "foo.bar").') + parser.add_option('--root', + dest='roots', + action='append', + default=[], + help='The paths that should be traversed to build the ' + 'dependencies.') + parser.add_option('-o', + '--output_mode', + dest='output_mode', + type='choice', + action='store', + choices=['list', 'script', 'compiled'], + default='list', + help='The type of output to generate from this script. ' + 'Options are "list" for a list of filenames, "script" ' + 'for a single script containing the contents of all the ' + 'files, or "compiled" to produce compiled output with ' + 'the Closure Compiler. Default is "list".') + parser.add_option('-c', + '--compiler_jar', + dest='compiler_jar', + action='store', + help='The location of the Closure compiler .jar file.') + parser.add_option('-f', + '--compiler_flags', + dest='compiler_flags', + default=[], + action='append', + help='Additional flags to pass to the Closure compiler. ' + 'To pass multiple flags, --compiler_flags has to be ' + 'specified multiple times.') + parser.add_option('-j', + '--jvm_flags', + dest='jvm_flags', + default=[], + action='append', + help='Additional flags to pass to the JVM compiler. ' + 'To pass multiple flags, --jvm_flags has to be ' + 'specified multiple times.') + parser.add_option('--output_file', + dest='output_file', + action='store', + help=('If specified, write output to this path instead of ' + 'writing to standard output.')) + + return parser + + +def _GetInputByPath(path, sources): + """Get the source identified by a path. + + Args: + path: str, A path to a file that identifies a source. + sources: An iterable collection of source objects. + + Returns: + The source from sources identified by path, if found. Converts to + real paths for comparison. + """ + for js_source in sources: + # Convert both to real paths for comparison. + if os.path.realpath(path) == os.path.realpath(js_source.GetPath()): + return js_source + + +def _GetClosureBaseFile(sources): + """Given a set of sources, returns the one base.js file. + + Note that if zero or two or more base.js files are found, an error message + will be written and the program will be exited. + + Args: + sources: An iterable of _PathSource objects. + + Returns: + The _PathSource representing the base Closure file. + """ + base_files = [ + js_source for js_source in sources if _IsClosureBaseFile(js_source) + ] + + if not base_files: + logging.error('No Closure base.js file found.') + sys.exit(1) + if len(base_files) > 1: + logging.error('More than one Closure base.js files found at these paths:') + for base_file in base_files: + logging.error(base_file.GetPath()) + sys.exit(1) + return base_files[0] + + +def _IsClosureBaseFile(js_source): + """Returns true if the given _PathSource is the Closure base.js source.""" + return (os.path.basename(js_source.GetPath()) == 'base.js' and + js_source.provides == set(['goog'])) + + +class _PathSource(source.Source): + """Source file subclass that remembers its file path.""" + + def __init__(self, path): + """Initialize a source. + + Args: + path: str, Path to a JavaScript file. The source string will be read + from this file. + """ + super(_PathSource, self).__init__(source.GetFileContents(path)) + + self._path = path + + def __str__(self): + return 'PathSource %s' % self._path + + def GetPath(self): + """Returns the path.""" + return self._path + + +def _WrapGoogModuleSource(src): + return (u'goog.loadModule(function(exports) {{' + '"use strict";' + '{0}' + '\n' # terminate any trailing single line comment. + ';return exports' + '}});\n').format(src) + + +def main(): + logging.basicConfig(format=(sys.argv[0] + ': %(message)s'), + level=logging.INFO) + options, args = _GetOptionsParser().parse_args() + + # Make our output pipe. + if options.output_file: + out = io.open(options.output_file, 'wb') + else: + version = sys.version_info[:2] + if version >= (3, 0): + # Write bytes to stdout + out = sys.stdout.buffer + else: + out = sys.stdout + + sources = set() + + logging.info('Scanning paths...') + for path in options.roots: + for js_path in treescan.ScanTreeForJsFiles(path): + sources.add(_PathSource(js_path)) + + # Add scripts specified on the command line. + for js_path in args: + sources.add(_PathSource(js_path)) + + logging.info('%s sources scanned.', len(sources)) + + # Though deps output doesn't need to query the tree, we still build it + # to validate dependencies. + logging.info('Building dependency tree..') + tree = depstree.DepsTree(sources) + + input_namespaces = set() + inputs = options.inputs or [] + for input_path in inputs: + js_input = _GetInputByPath(input_path, sources) + if not js_input: + logging.error('No source matched input %s', input_path) + sys.exit(1) + input_namespaces.update(js_input.provides) + + input_namespaces.update(options.namespaces) + + if not input_namespaces: + logging.error('No namespaces found. At least one namespace must be ' + 'specified with the --namespace or --input flags.') + sys.exit(2) + + # The Closure Library base file must go first. + base = _GetClosureBaseFile(sources) + deps = [base] + tree.GetDependencies(input_namespaces) + + output_mode = options.output_mode + if output_mode == 'list': + out.writelines([js_source.GetPath() + '\n' for js_source in deps]) + elif output_mode == 'script': + for js_source in deps: + src = js_source.GetSource() + if js_source.is_goog_module: + src = _WrapGoogModuleSource(src) + out.write(src.encode('utf-8') + b'\n') + elif output_mode == 'compiled': + logging.warning("""\ +Closure Compiler now natively understands and orders Closure dependencies and +is prefererred over using this script for performing JavaScript compilation. + +Please migrate your codebase. + +See: +https://github.com/google/closure-compiler/wiki/Managing-Dependencies +""") + + # Make sure a .jar is specified. + if not options.compiler_jar: + logging.error('--compiler_jar flag must be specified if --output is ' + '"compiled"') + sys.exit(2) + + # Will throw an error if the compilation fails. + compiled_source = jscompiler.Compile(options.compiler_jar, + [js_source.GetPath() + for js_source in deps], + jvm_flags=options.jvm_flags, + compiler_flags=options.compiler_flags) + + logging.info('JavaScript compilation succeeded.') + out.write(compiled_source.encode('utf-8')) + + else: + logging.error('Invalid value for --output flag.') + sys.exit(2) + + +if __name__ == '__main__': + main() diff --git a/chromium/third_party/google-closure-library/closure/bin/build/depstree.py b/chromium/third_party/google-closure-library/closure/bin/build/depstree.py new file mode 100644 index 00000000000..f288dd3aa61 --- /dev/null +++ b/chromium/third_party/google-closure-library/closure/bin/build/depstree.py @@ -0,0 +1,189 @@ +# Copyright 2009 The Closure Library Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS-IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + +"""Class to represent a full Closure Library dependency tree. + +Offers a queryable tree of dependencies of a given set of sources. The tree +will also do logical validation to prevent duplicate provides and circular +dependencies. +""" + +__author__ = 'nnaze@google.com (Nathan Naze)' + + +class DepsTree(object): + """Represents the set of dependencies between source files.""" + + def __init__(self, sources): + """Initializes the tree with a set of sources. + + Args: + sources: A set of JavaScript sources. + + Raises: + MultipleProvideError: A namespace is provided by muplitple sources. + NamespaceNotFoundError: A namespace is required but never provided. + """ + + self._sources = sources + self._provides_map = dict() + + # Ensure nothing was provided twice. + for source in sources: + for provide in source.provides: + if provide in self._provides_map: + raise MultipleProvideError( + provide, [self._provides_map[provide], source]) + + self._provides_map[provide] = source + + # Check that all required namespaces are provided. + for source in sources: + for require in source.requires: + if require not in self._provides_map: + raise NamespaceNotFoundError(require, source) + + def GetDependencies(self, required_namespaces): + """Get source dependencies, in order, for the given namespaces. + + Args: + required_namespaces: A string (for one) or list (for one or more) of + namespaces. + + Returns: + A list of source objects that provide those namespaces and all + requirements, in dependency order. + + Raises: + NamespaceNotFoundError: A namespace is requested but doesn't exist. + CircularDependencyError: A cycle is detected in the dependency tree. + """ + if isinstance(required_namespaces, str): + required_namespaces = [required_namespaces] + + deps_sources = [] + + for namespace in required_namespaces: + for source in DepsTree._ResolveDependencies( + namespace, [], self._provides_map, []): + if source not in deps_sources: + deps_sources.append(source) + + return deps_sources + + @staticmethod + def _ResolveDependencies(required_namespace, deps_list, provides_map, + traversal_path): + """Resolve dependencies for Closure source files. + + Follows the dependency tree down and builds a list of sources in dependency + order. This function will recursively call itself to fill all dependencies + below the requested namespaces, and then append its sources at the end of + the list. + + Args: + required_namespace: String of required namespace. + deps_list: List of sources in dependency order. This function will append + the required source once all of its dependencies are satisfied. + provides_map: Map from namespace to source that provides it. + traversal_path: List of namespaces of our path from the root down the + dependency/recursion tree. Used to identify cyclical dependencies. + This is a list used as a stack -- when the function is entered, the + current namespace is pushed and popped right before returning. + Each recursive call will check that the current namespace does not + appear in the list, throwing a CircularDependencyError if it does. + + Returns: + The given deps_list object filled with sources in dependency order. + + Raises: + NamespaceNotFoundError: A namespace is requested but doesn't exist. + CircularDependencyError: A cycle is detected in the dependency tree. + """ + + source = provides_map.get(required_namespace) + if not source: + raise NamespaceNotFoundError(required_namespace) + + if required_namespace in traversal_path: + traversal_path.append(required_namespace) # do this *after* the test + + # This must be a cycle. + raise CircularDependencyError(traversal_path) + + # If we don't have the source yet, we'll have to visit this namespace and + # add the required dependencies to deps_list. + if source not in deps_list: + traversal_path.append(required_namespace) + + for require in source.requires: + + # Append all other dependencies before we append our own. + DepsTree._ResolveDependencies(require, deps_list, provides_map, + traversal_path) + deps_list.append(source) + + traversal_path.pop() + + return deps_list + + +class BaseDepsTreeError(Exception): + """Base DepsTree error.""" + + def __init__(self): + Exception.__init__(self) + + +class CircularDependencyError(BaseDepsTreeError): + """Raised when a dependency cycle is encountered.""" + + def __init__(self, dependency_list): + BaseDepsTreeError.__init__(self) + self._dependency_list = dependency_list + + def __str__(self): + return ('Encountered circular dependency:\n%s\n' % + '\n'.join(self._dependency_list)) + + +class MultipleProvideError(BaseDepsTreeError): + """Raised when a namespace is provided more than once.""" + + def __init__(self, namespace, sources): + BaseDepsTreeError.__init__(self) + self._namespace = namespace + self._sources = sources + + def __str__(self): + source_strs = map(str, self._sources) + + return ('Namespace "%s" provided more than once in sources:\n%s\n' % + (self._namespace, '\n'.join(source_strs))) + + +class NamespaceNotFoundError(BaseDepsTreeError): + """Raised when a namespace is requested but not provided.""" + + def __init__(self, namespace, source=None): + BaseDepsTreeError.__init__(self) + self._namespace = namespace + self._source = source + + def __str__(self): + msg = 'Namespace "%s" never provided.' % self._namespace + if self._source: + msg += ' Required in %s' % self._source + return msg diff --git a/chromium/third_party/google-closure-library/closure/bin/build/depstree_test.py b/chromium/third_party/google-closure-library/closure/bin/build/depstree_test.py new file mode 100755 index 00000000000..eb4c99958ec --- /dev/null +++ b/chromium/third_party/google-closure-library/closure/bin/build/depstree_test.py @@ -0,0 +1,127 @@ +#!/usr/bin/env python +# +# Copyright 2009 The Closure Library Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS-IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + +"""Unit test for depstree.""" + +__author__ = 'nnaze@google.com (Nathan Naze)' + + +import unittest + +import depstree + + +def _GetProvides(sources): + """Get all namespaces provided by a collection of sources.""" + + provides = set() + for source in sources: + provides.update(source.provides) + return provides + + +class MockSource(object): + """Mock Source file.""" + + def __init__(self, provides, requires): + self.provides = set(provides) + self.requires = set(requires) + + def __repr__(self): + return 'MockSource %s' % self.provides + + +class DepsTreeTestCase(unittest.TestCase): + """Unit test for DepsTree. Tests several common situations and errors.""" + + def AssertValidDependencies(self, deps_list): + """Validates a dependency list. + + Asserts that a dependency list is valid: For every source in the list, + ensure that every require is provided by a source earlier in the list. + + Args: + deps_list: A list of sources that should be in dependency order. + """ + + for i in range(len(deps_list)): + source = deps_list[i] + previous_provides = _GetProvides(deps_list[:i]) + for require in source.requires: + self.assertTrue( + require in previous_provides, + 'Namespace "%s" not provided before required by %s' % ( + require, source)) + + def testSimpleDepsTree(self): + a = MockSource(['A'], ['B', 'C']) + b = MockSource(['B'], []) + c = MockSource(['C'], ['D']) + d = MockSource(['D'], ['E']) + e = MockSource(['E'], []) + + tree = depstree.DepsTree([a, b, c, d, e]) + + self.AssertValidDependencies(tree.GetDependencies('A')) + self.AssertValidDependencies(tree.GetDependencies('B')) + self.AssertValidDependencies(tree.GetDependencies('C')) + self.AssertValidDependencies(tree.GetDependencies('D')) + self.AssertValidDependencies(tree.GetDependencies('E')) + + def testCircularDependency(self): + # Circular deps + a = MockSource(['A'], ['B']) + b = MockSource(['B'], ['C']) + c = MockSource(['C'], ['A']) + + tree = depstree.DepsTree([a, b, c]) + + self.assertRaises(depstree.CircularDependencyError, + tree.GetDependencies, 'A') + + def testRequiresUndefinedNamespace(self): + a = MockSource(['A'], ['B']) + b = MockSource(['B'], ['C']) + c = MockSource(['C'], ['D']) # But there is no D. + + def MakeDepsTree(): + return depstree.DepsTree([a, b, c]) + + self.assertRaises(depstree.NamespaceNotFoundError, MakeDepsTree) + + def testDepsForMissingNamespace(self): + a = MockSource(['A'], ['B']) + b = MockSource(['B'], []) + + tree = depstree.DepsTree([a, b]) + + # There is no C. + self.assertRaises(depstree.NamespaceNotFoundError, + tree.GetDependencies, 'C') + + def testMultipleRequires(self): + a = MockSource(['A'], ['B']) + b = MockSource(['B'], ['C']) + c = MockSource(['C'], []) + d = MockSource(['D'], ['B']) + + tree = depstree.DepsTree([a, b, c, d]) + self.AssertValidDependencies(tree.GetDependencies(['D', 'A'])) + + +if __name__ == '__main__': + unittest.main() diff --git a/chromium/third_party/google-closure-library/closure/bin/build/depswriter.py b/chromium/third_party/google-closure-library/closure/bin/build/depswriter.py new file mode 100755 index 00000000000..dc5a1019060 --- /dev/null +++ b/chromium/third_party/google-closure-library/closure/bin/build/depswriter.py @@ -0,0 +1,212 @@ +#!/usr/bin/env python +# +# Copyright 2009 The Closure Library Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS-IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + +"""Generates out a Closure deps.js file given a list of JavaScript sources. + +Paths can be specified as arguments or (more commonly) specifying trees +with the flags (call with --help for descriptions). + +Usage: depswriter.py [path/to/js1.js [path/to/js2.js] ...] +""" + +import json +import logging +import optparse +import os +import posixpath +import shlex +import sys + +import source +import treescan + + +__author__ = 'nnaze@google.com (Nathan Naze)' + + +def MakeDepsFile(source_map): + """Make a generated deps file. + + Args: + source_map: A dict map of the source path to source.Source object. + + Returns: + str, A generated deps file source. + """ + + # Write in path alphabetical order + paths = sorted(source_map.keys()) + + lines = [] + + for path in paths: + js_source = source_map[path] + + # We don't need to add entries that don't provide anything. + if js_source.provides: + lines.append(_GetDepsLine(path, js_source)) + + return ''.join(lines) + + +def _GetDepsLine(path, js_source): + """Get a deps.js file string for a source.""" + + provides = _ToJsSrc(sorted(js_source.provides)) + requires = _ToJsSrc(sorted(js_source.requires)) + module = "{'module': 'goog'}" if js_source.is_goog_module else '{}' + + return 'goog.addDependency(\'%s\', %s, %s, %s);\n' % ( + path, provides, requires, module) + + +def _ToJsSrc(arr): + """Convert a python arr to a js source string.""" + + return json.dumps(arr).replace('"', '\'') + + +def _GetOptionsParser(): + """Get the options parser.""" + + parser = optparse.OptionParser(__doc__) + + parser.add_option('--output_file', + dest='output_file', + action='store', + help=('If specified, write output to this path instead of ' + 'writing to standard output.')) + parser.add_option('--root', + dest='roots', + default=[], + action='append', + help='A root directory to scan for JS source files. ' + 'Paths of JS files in generated deps file will be ' + 'relative to this path. This flag may be specified ' + 'multiple times.') + parser.add_option('--root_with_prefix', + dest='roots_with_prefix', + default=[], + action='append', + help='A root directory to scan for JS source files, plus ' + 'a prefix (if either contains a space, surround with ' + 'quotes). Paths in generated deps file will be relative ' + 'to the root, but preceded by the prefix. This flag ' + 'may be specified multiple times.') + parser.add_option('--path_with_depspath', + dest='paths_with_depspath', + default=[], + action='append', + help='A path to a source file and an alternate path to ' + 'the file in the generated deps file (if either contains ' + 'a space, surround with whitespace). This flag may be ' + 'specified multiple times.') + return parser + + +def _NormalizePathSeparators(path): + """Replaces OS-specific path separators with POSIX-style slashes. + + Args: + path: str, A file path. + + Returns: + str, The path with any OS-specific path separators (such as backslash on + Windows) replaced with URL-compatible forward slashes. A no-op on systems + that use POSIX paths. + """ + return path.replace(os.sep, posixpath.sep) + + +def _GetRelativePathToSourceDict(root, prefix=''): + """Scans a top root directory for .js sources. + + Args: + root: str, Root directory. + prefix: str, Prefix for returned paths. + + Returns: + dict, A map of relative paths (with prefix, if given), to source.Source + objects. + """ + # Remember and restore the cwd when we're done. We work from the root so + # that paths are relative from the root. + start_wd = os.getcwd() + os.chdir(root) + + path_to_source = {} + for path in treescan.ScanTreeForJsFiles('.'): + prefixed_path = _NormalizePathSeparators(os.path.join(prefix, path)) + path_to_source[prefixed_path] = source.Source(source.GetFileContents(path)) + + os.chdir(start_wd) + + return path_to_source + + +def _GetPair(s): + """Return a string as a shell-parsed tuple. Two values expected.""" + try: + # shlex uses '\' as an escape character, so they must be escaped. + s = s.replace('\\', '\\\\') + first, second = shlex.split(s) + return (first, second) + except: + raise Exception('Unable to parse input line as a pair: %s' % s) + + +def main(): + """CLI frontend to MakeDepsFile.""" + logging.basicConfig(format=(sys.argv[0] + ': %(message)s'), + level=logging.INFO) + options, args = _GetOptionsParser().parse_args() + + path_to_source = {} + + # Roots without prefixes + for root in options.roots: + path_to_source.update(_GetRelativePathToSourceDict(root)) + + # Roots with prefixes + for root_and_prefix in options.roots_with_prefix: + root, prefix = _GetPair(root_and_prefix) + path_to_source.update(_GetRelativePathToSourceDict(root, prefix=prefix)) + + # Source paths + for path in args: + path_to_source[path] = source.Source(source.GetFileContents(path)) + + # Source paths with alternate deps paths + for path_with_depspath in options.paths_with_depspath: + srcpath, depspath = _GetPair(path_with_depspath) + path_to_source[depspath] = source.Source(source.GetFileContents(srcpath)) + + # Make our output pipe. + if options.output_file: + out = open(options.output_file, 'w') + else: + out = sys.stdout + + out.write(('// This file was autogenerated by %s.\n' % + os.path.basename(__file__))) + out.write('// Please do not edit.\n') + + out.write(MakeDepsFile(path_to_source)) + + +if __name__ == '__main__': + main() diff --git a/chromium/third_party/google-closure-library/closure/bin/build/depswriter_test.py b/chromium/third_party/google-closure-library/closure/bin/build/depswriter_test.py new file mode 100755 index 00000000000..30b840c7e2d --- /dev/null +++ b/chromium/third_party/google-closure-library/closure/bin/build/depswriter_test.py @@ -0,0 +1,72 @@ +#!/usr/bin/env python +# +# Copyright 2010 The Closure Library Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS-IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + +"""Unit test for depswriter.""" + +__author__ = 'johnlenz@google.com (John Lenz)' + + +import unittest + +import depswriter + + +class MockSource(object): + """Mock Source file.""" + + def __init__(self, provides, requires, is_goog_module=False): + self.provides = set(provides) + self.requires = set(requires) + self.is_goog_module = is_goog_module + + def __repr__(self): + return 'MockSource %s' % self.provides + + +class DepsWriterTestCase(unittest.TestCase): + """Unit test for depswriter.""" + + def testMakeDepsFile(self): + sources = {} + sources['test.js'] = MockSource(['A'], ['B', 'C']) + deps = depswriter.MakeDepsFile(sources) + + self.assertEqual( + 'goog.addDependency(\'test.js\', [\'A\'], [\'B\', \'C\'], {});\n', + deps) + + def testMakeDepsFileUnicode(self): + sources = {} + sources['test.js'] = MockSource([u'A'], [u'B', u'C']) + deps = depswriter.MakeDepsFile(sources) + + self.assertEqual( + 'goog.addDependency(\'test.js\', [\'A\'], [\'B\', \'C\'], {});\n', + deps) + + def testMakeDepsFileModule(self): + sources = {} + sources['test.js'] = MockSource(['A'], ['B', 'C'], True) + deps = depswriter.MakeDepsFile(sources) + + self.assertEqual( + "goog.addDependency('test.js', " + "['A'], ['B', 'C'], {'module': 'goog'});\n", + deps) + +if __name__ == '__main__': + unittest.main() diff --git a/chromium/third_party/google-closure-library/closure/bin/build/jscompiler.py b/chromium/third_party/google-closure-library/closure/bin/build/jscompiler.py new file mode 100644 index 00000000000..a2f6d9c380f --- /dev/null +++ b/chromium/third_party/google-closure-library/closure/bin/build/jscompiler.py @@ -0,0 +1,161 @@ +# Copyright 2010 The Closure Library Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS-IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +"""Utility to use the Closure Compiler CLI from Python.""" + +import logging +import os +import re +import subprocess +import tempfile + +# Pulls just the major and minor version numbers from the first line of +# 'java -version'. Versions are in the format of [0-9]+(\.[0-9]+)? See: +# http://openjdk.java.net/jeps/223 +_VERSION_REGEX = re.compile(r'"([0-9]+)(?:\.([0-9]+))?') + + +class JsCompilerError(Exception): + """Raised if there's an error in calling the compiler.""" + pass + + +def _GetJavaVersionString(): + """Get the version string from the Java VM.""" + return subprocess.check_output(['java', '-version'], stderr=subprocess.STDOUT) + + +def _ParseJavaVersion(version_string): + """Returns a 2-tuple for the current version of Java installed. + + Args: + version_string: String of the Java version (e.g. '1.7.2-ea'). + + Returns: + The major and minor versions, as a 2-tuple (e.g. (1, 7)). + """ + match = _VERSION_REGEX.search(version_string) + if match: + version = tuple(int(x or 0) for x in match.groups()) + assert len(version) == 2 + return version + + +def _JavaSupports32BitMode(): + """Determines whether the JVM supports 32-bit mode on the platform.""" + # Suppresses process output to stderr and stdout from showing up in the + # console as we're only trying to determine 32-bit JVM support. + supported = False + try: + devnull = open(os.devnull, 'wb') + return subprocess.call( + ['java', '-d32', '-version'], stdout=devnull, stderr=devnull) == 0 + except IOError: + pass + else: + devnull.close() + return supported + + +def _GetJsCompilerArgs(compiler_jar_path, java_version, jvm_flags): + """Assembles arguments for call to JsCompiler.""" + + if java_version < (1, 7): + raise JsCompilerError('Closure Compiler requires Java 1.7 or higher. ' + 'Please visit http://www.java.com/getjava') + + args = ['java'] + + # Add JVM flags we believe will produce the best performance. See + # https://groups.google.com/forum/#!topic/closure-library-discuss/7w_O9-vzlj4 + + # Attempt 32-bit mode if available (Java 7 on Mac OS X does not support 32-bit + # mode, for example). + if _JavaSupports32BitMode(): + args += ['-d32'] + + # Prefer the "client" VM. + args += ['-client'] + + # Add JVM flags, if any + if jvm_flags: + args += jvm_flags + + # Add the application JAR. + args += ['-jar', compiler_jar_path] + + return args + + +def _GetFlagFile(source_paths, compiler_flags): + """Writes given source paths and compiler flags to a --flagfile. + + The given source_paths will be written as '--js' flags and the compiler_flags + are written as-is. + + Args: + source_paths: List of string js source paths. + compiler_flags: List of string compiler flags. + + Returns: + The file to which the flags were written. + """ + args = [] + for path in source_paths: + args += ['--js', path] + + # Add compiler flags, if any. + if compiler_flags: + args += compiler_flags + + flags_file = tempfile.NamedTemporaryFile(mode='w+t', delete=False) + flags_file.write(' '.join(args)) + flags_file.close() + + return flags_file + + +def Compile(compiler_jar_path, + source_paths, + jvm_flags=None, + compiler_flags=None): + """Prepares command-line call to Closure Compiler. + + Args: + compiler_jar_path: Path to the Closure compiler .jar file. + source_paths: Source paths to build, in order. + jvm_flags: A list of additional flags to pass on to JVM. + compiler_flags: A list of additional flags to pass on to Closure Compiler. + + Returns: + The compiled source, as a string, or None if compilation failed. + """ + + java_version = _ParseJavaVersion(str(_GetJavaVersionString())) + + args = _GetJsCompilerArgs(compiler_jar_path, java_version, jvm_flags) + + # Write source path arguments to flag file for avoiding "The filename or + # extension is too long" error in big projects. See + # https://github.com/google/closure-library/pull/678 + flags_file = _GetFlagFile(source_paths, compiler_flags) + args += ['--flagfile', flags_file.name] + + logging.info('Compiling with the following command: %s', ' '.join(args)) + + try: + return subprocess.check_output(args) + except subprocess.CalledProcessError: + raise JsCompilerError('JavaScript compilation failed.') + finally: + os.remove(flags_file.name) diff --git a/chromium/third_party/google-closure-library/closure/bin/build/jscompiler_test.py b/chromium/third_party/google-closure-library/closure/bin/build/jscompiler_test.py new file mode 100755 index 00000000000..38a98eb43df --- /dev/null +++ b/chromium/third_party/google-closure-library/closure/bin/build/jscompiler_test.py @@ -0,0 +1,122 @@ +#!/usr/bin/env python +# +# Copyright 2013 The Closure Library Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS-IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +"""Unit test for depstree.""" + +__author__ = 'nnaze@google.com (Nathan Naze)' + +import os +import unittest + +import jscompiler + + +class JsCompilerTestCase(unittest.TestCase): + """Unit tests for jscompiler module.""" + + def testGetFlagFile(self): + flags_file = jscompiler._GetFlagFile(['path/to/src1.js', 'path/to/src2.js'], + ['--test_compiler_flag']) + + def file_get_contents(filename): + with open(filename) as f: + content = f.read() + f.close() + return content + + flags_file_content = file_get_contents(flags_file.name) + os.remove(flags_file.name) + + self.assertEqual( + '--js path/to/src1.js --js path/to/src2.js --test_compiler_flag', + flags_file_content) + + def testGetJsCompilerArgs(self): + + original_check = jscompiler._JavaSupports32BitMode + jscompiler._JavaSupports32BitMode = lambda: False + args = jscompiler._GetJsCompilerArgs('path/to/jscompiler.jar', (1, 7), + ['--test_jvm_flag']) + + self.assertEqual([ + 'java', '-client', '--test_jvm_flag', '-jar', 'path/to/jscompiler.jar' + ], args) + + def CheckJava15RaisesError(): + jscompiler._GetJsCompilerArgs('path/to/jscompiler.jar', (1, 5), + ['--test_jvm_flag']) + + self.assertRaises(jscompiler.JsCompilerError, CheckJava15RaisesError) + jscompiler._JavaSupports32BitMode = original_check + + def testGetJsCompilerArgs32BitJava(self): + + original_check = jscompiler._JavaSupports32BitMode + + # Should include the -d32 flag only if 32-bit Java is supported by the + # system. + jscompiler._JavaSupports32BitMode = lambda: True + args = jscompiler._GetJsCompilerArgs('path/to/jscompiler.jar', (1, 7), + ['--test_jvm_flag']) + + self.assertEqual([ + 'java', '-d32', '-client', '--test_jvm_flag', '-jar', + 'path/to/jscompiler.jar' + ], args) + + # Should exclude the -d32 flag if 32-bit Java is not supported by the + # system. + jscompiler._JavaSupports32BitMode = lambda: False + args = jscompiler._GetJsCompilerArgs('path/to/jscompiler.jar', (1, 7), + ['--test_jvm_flag']) + + self.assertEqual([ + 'java', '-client', '--test_jvm_flag', '-jar', 'path/to/jscompiler.jar' + ], args) + + jscompiler._JavaSupports32BitMode = original_check + + def testGetJavaVersion(self): + + def assertVersion(expected, version_string): + self.assertEqual(expected, jscompiler._ParseJavaVersion(version_string)) + + assertVersion((9, 0), _TEST_JAVA_JEP_223_VERSION_STRING) + assertVersion((1, 7), _TEST_JAVA_VERSION_STRING) + assertVersion((1, 6), _TEST_JAVA_NESTED_VERSION_STRING) + assertVersion((1, 4), 'java version "1.4.0_03-ea"') + + +_TEST_JAVA_VERSION_STRING = """\ +openjdk version "1.7.0-google-v5" +OpenJDK Runtime Environment (build 1.7.0-google-v5-64327-39803485) +OpenJDK Server VM (build 22.0-b10, mixed mode) +""" + +_TEST_JAVA_NESTED_VERSION_STRING = """\ +Picked up JAVA_TOOL_OPTIONS: -Dfile.encoding=UTF-8 +java version "1.6.0_35" +Java(TM) SE Runtime Environment (build 1.6.0_35-b10-428-11M3811) +Java HotSpot(TM) Client VM (build 20.10-b01-428, mixed mode) +""" + +_TEST_JAVA_JEP_223_VERSION_STRING = """\ +openjdk version "9-Ubuntu" +OpenJDK Runtime Environment (build 9-Ubuntu+0-9b134-2ubuntu1) +OpenJDK 64-Bit Server VM (build 9-Ubuntu+0-9b134-2ubuntu1, mixed mode) +""" + +if __name__ == '__main__': + unittest.main() diff --git a/chromium/third_party/google-closure-library/closure/bin/build/source.py b/chromium/third_party/google-closure-library/closure/bin/build/source.py new file mode 100644 index 00000000000..447b2f0aa95 --- /dev/null +++ b/chromium/third_party/google-closure-library/closure/bin/build/source.py @@ -0,0 +1,132 @@ +# Copyright 2009 The Closure Library Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS-IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + +"""Scans a source JS file for its provided and required namespaces. + +Simple class to scan a JavaScript file and express its dependencies. +""" + +__author__ = 'nnaze@google.com' + + +import codecs +import re + +_BASE_REGEX_STRING = r'^\s*goog\.%s\(\s*[\'"](.+)[\'"]\s*\)' +_MODULE_REGEX = re.compile(_BASE_REGEX_STRING % 'module') +_PROVIDE_REGEX = re.compile(_BASE_REGEX_STRING % 'provide') + +_REQUIRE_REGEX_STRING = (r'^\s*(?:(?:var|let|const)\s+[a-zA-Z0-9$_,:{}\s]*' + r'\s*=\s*)?goog\.require\(\s*[\'"](.+)[\'"]\s*\)') +_REQUIRES_REGEX = re.compile(_REQUIRE_REGEX_STRING) + +class Source(object): + """Scans a JavaScript source for its provided and required namespaces.""" + + # Matches a "/* ... */" comment. + # Note: We can't definitively distinguish a "/*" in a string literal without a + # state machine tokenizer. We'll assume that a line starting with whitespace + # and "/*" is a comment. + _COMMENT_REGEX = re.compile( + r""" + ^\s* # Start of a new line and whitespace + /\* # Opening "/*" + .*? # Non greedy match of any characters (including newlines) + \*/ # Closing "*/""", + re.MULTILINE | re.DOTALL | re.VERBOSE) + + def __init__(self, source): + """Initialize a source. + + Args: + source: str, The JavaScript source. + """ + + self.provides = set() + self.requires = set() + self.is_goog_module = False + + self._source = source + self._ScanSource() + + def GetSource(self): + """Get the source as a string.""" + return self._source + + @classmethod + def _StripComments(cls, source): + return cls._COMMENT_REGEX.sub('', source) + + @classmethod + def _HasProvideGoogFlag(cls, source): + """Determines whether the @provideGoog flag is in a comment.""" + for comment_content in cls._COMMENT_REGEX.findall(source): + if '@provideGoog' in comment_content: + return True + + return False + + def _ScanSource(self): + """Fill in provides and requires by scanning the source.""" + + stripped_source = self._StripComments(self.GetSource()) + + source_lines = stripped_source.splitlines() + for line in source_lines: + match = _PROVIDE_REGEX.match(line) + if match: + self.provides.add(match.group(1)) + match = _MODULE_REGEX.match(line) + if match: + self.provides.add(match.group(1)) + self.is_goog_module = True + match = _REQUIRES_REGEX.match(line) + if match: + self.requires.add(match.group(1)) + + # Closure's base file implicitly provides 'goog'. + # This is indicated with the @provideGoog flag. + if self._HasProvideGoogFlag(self.GetSource()): + + if len(self.provides) or len(self.requires): + raise Exception( + 'Base file should not provide or require namespaces.') + + self.provides.add('goog') + + +def GetFileContents(path): + """Get a file's contents as a string. + + Args: + path: str, Path to file. + + Returns: + str, Contents of file. + + Raises: + IOError: An error occurred opening or reading the file. + + """ + fileobj = None + try: + fileobj = codecs.open(path, encoding='utf-8-sig') + return fileobj.read() + except IOError as error: + raise IOError('An error occurred opening or reading the file: %s. %s' + % (path, error)) + finally: + if fileobj is not None: + fileobj.close() diff --git a/chromium/third_party/google-closure-library/closure/bin/build/source_test.py b/chromium/third_party/google-closure-library/closure/bin/build/source_test.py new file mode 100755 index 00000000000..063cb3aced1 --- /dev/null +++ b/chromium/third_party/google-closure-library/closure/bin/build/source_test.py @@ -0,0 +1,164 @@ +#!/usr/bin/env python +# +# Copyright 2010 The Closure Library Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS-IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + +"""Unit test for source.""" + +__author__ = 'nnaze@google.com (Nathan Naze)' + + +import unittest + +import source + + +class SourceTestCase(unittest.TestCase): + """Unit test for source. Tests the parser on a known source input.""" + + def testSourceScan(self): + test_source = source.Source(_TEST_SOURCE) + + self.assertEqual(set(['foo', 'foo.test']), + test_source.provides) + self.assertEqual(set(['goog.dom', 'goog.events.EventType']), + test_source.requires) + self.assertFalse(test_source.is_goog_module) + + def testSourceScanBase(self): + test_source = source.Source(_TEST_BASE_SOURCE) + + self.assertEqual(set(['goog']), + test_source.provides) + self.assertEqual(test_source.requires, set()) + self.assertFalse(test_source.is_goog_module) + + def testSourceScanBadBase(self): + + def MakeSource(): + source.Source(_TEST_BAD_BASE_SOURCE) + + self.assertRaises(Exception, MakeSource) + + def testSourceScanGoogModule(self): + test_source = source.Source(_TEST_MODULE_SOURCE) + + self.assertEqual(set(['foo']), + test_source.provides) + self.assertEqual(set(['bar']), + test_source.requires) + self.assertTrue(test_source.is_goog_module) + + def testSourceScanModuleAlias(self): + test_source = source.Source(_TEST_MODULE_ALIAS_SOURCE) + + self.assertEqual(set(['goog.dom', 'goog.events']), test_source.requires) + self.assertTrue(test_source.is_goog_module) + + def testStripComments(self): + self.assertEqual( + '\nvar foo = function() {}', + source.Source._StripComments(('/* This is\n' + ' a comment split\n' + ' over multiple lines\n' + '*/\n' + 'var foo = function() {}'))) + + def testGoogStatementsInComments(self): + test_source = source.Source(_TEST_COMMENT_SOURCE) + + self.assertEqual(set(['foo']), + test_source.provides) + self.assertEqual(set(['goog.events.EventType']), + test_source.requires) + self.assertFalse(test_source.is_goog_module) + + def testHasProvideGoog(self): + self.assertTrue(source.Source._HasProvideGoogFlag(_TEST_BASE_SOURCE)) + self.assertTrue(source.Source._HasProvideGoogFlag(_TEST_BAD_BASE_SOURCE)) + self.assertFalse(source.Source._HasProvideGoogFlag(_TEST_COMMENT_SOURCE)) + + +_TEST_MODULE_ALIAS_SOURCE = """ +goog.module('foo'); +const {createDom: domCreator} = goog.require('goog.dom'); +const {listen} = goog.require('goog.events'); +""" + + +_TEST_MODULE_SOURCE = """ +goog.module('foo'); +var b = goog.require('bar'); +""" + + +_TEST_SOURCE = """// Fake copyright notice + +/** Very important comment. */ + +goog.provide('foo'); +goog.provide('foo.test'); + +goog.require('goog.dom'); +goog.require('goog.events.EventType'); + +function foo() { + // Set bar to seventeen to increase performance. + this.bar = 17; +} +""" + +_TEST_COMMENT_SOURCE = """// Fake copyright notice + +goog.provide('foo'); + +/* +goog.provide('foo.test'); + */ + +/* +goog.require('goog.dom'); +*/ + +// goog.require('goog.dom'); + +goog.require('goog.events.EventType'); + +function bar() { + this.baz = 55; +} +""" + +_TEST_BASE_SOURCE = """ +/** + * @fileoverview The base file. + * @provideGoog + */ + +var goog = goog || {}; +""" + +_TEST_BAD_BASE_SOURCE = """ +/** + * @fileoverview The base file. + * @provideGoog + */ + +goog.provide('goog'); +""" + + +if __name__ == '__main__': + unittest.main() diff --git a/chromium/third_party/google-closure-library/closure/bin/build/treescan.py b/chromium/third_party/google-closure-library/closure/bin/build/treescan.py new file mode 100755 index 00000000000..6694593aab0 --- /dev/null +++ b/chromium/third_party/google-closure-library/closure/bin/build/treescan.py @@ -0,0 +1,78 @@ +#!/usr/bin/env python +# +# Copyright 2010 The Closure Library Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS-IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + +"""Shared utility functions for scanning directory trees.""" + +import os +import re + + +__author__ = 'nnaze@google.com (Nathan Naze)' + + +# Matches a .js file path. +_JS_FILE_REGEX = re.compile(r'^.+\.js$') + + +def ScanTreeForJsFiles(root): + """Scans a directory tree for JavaScript files. + + Args: + root: str, Path to a root directory. + + Returns: + An iterable of paths to JS files, relative to cwd. + """ + return ScanTree(root, path_filter=_JS_FILE_REGEX) + + +def ScanTree(root, path_filter=None, ignore_hidden=True): + """Scans a directory tree for files. + + Args: + root: str, Path to a root directory. + path_filter: A regular expression filter. If set, only paths matching + the path_filter are returned. + ignore_hidden: If True, do not follow or return hidden directories or files + (those starting with a '.' character). + + Yields: + A string path to files, relative to cwd. + """ + + def OnError(os_error): + raise os_error + + for dirpath, dirnames, filenames in os.walk(root, onerror=OnError): + # os.walk allows us to modify dirnames to prevent decent into particular + # directories. Avoid hidden directories. + for dirname in dirnames: + if ignore_hidden and dirname.startswith('.'): + dirnames.remove(dirname) + + for filename in filenames: + + # nothing that starts with '.' + if ignore_hidden and filename.startswith('.'): + continue + + fullpath = os.path.join(dirpath, filename) + + if path_filter and not path_filter.match(fullpath): + continue + + yield os.path.normpath(fullpath) diff --git a/chromium/third_party/google-closure-library/closure/bin/calcdeps.py b/chromium/third_party/google-closure-library/closure/bin/calcdeps.py new file mode 100755 index 00000000000..0f80ea7411f --- /dev/null +++ b/chromium/third_party/google-closure-library/closure/bin/calcdeps.py @@ -0,0 +1,587 @@ +#!/usr/bin/env python +# +# Copyright 2006 The Closure Library Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS-IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + +"""Calculates JavaScript dependencies without requiring Google's build system. + +This tool is deprecated and is provided for legacy users. +See build/closurebuilder.py and build/depswriter.py for the current tools. + +It iterates over a number of search paths and builds a dependency tree. With +the inputs provided, it walks the dependency tree and outputs all the files +required for compilation. +""" + + +try: + import distutils.version +except ImportError: + # distutils is not available in all environments + distutils = None + +import logging +import optparse +import os +import re +import subprocess +import sys + + +_BASE_REGEX_STRING = '^\s*goog\.%s\(\s*[\'"](.+)[\'"]\s*\)' +req_regex = re.compile(_BASE_REGEX_STRING % 'require') +prov_regex = re.compile(_BASE_REGEX_STRING % 'provide') +ns_regex = re.compile('^ns:((\w+\.)*(\w+))$') +version_regex = re.compile('[\.0-9]+') + + +def IsValidFile(ref): + """Returns true if the provided reference is a file and exists.""" + return os.path.isfile(ref) + + +def IsJsFile(ref): + """Returns true if the provided reference is a JavaScript file.""" + return ref.endswith('.js') + + +def IsNamespace(ref): + """Returns true if the provided reference is a namespace.""" + return re.match(ns_regex, ref) is not None + + +def IsDirectory(ref): + """Returns true if the provided reference is a directory.""" + return os.path.isdir(ref) + + +def ExpandDirectories(refs): + """Expands any directory references into inputs. + + Description: + Looks for any directories in the provided references. Found directories + are recursively searched for .js files, which are then added to the result + list. + + Args: + refs: a list of references such as files, directories, and namespaces + + Returns: + A list of references with directories removed and replaced by any + .js files that are found in them. Also, the paths will be normalized. + """ + result = [] + for ref in refs: + if IsDirectory(ref): + # Disable 'Unused variable' for subdirs + # pylint: disable=unused-variable + for (directory, subdirs, filenames) in os.walk(ref): + for filename in filenames: + if IsJsFile(filename): + result.append(os.path.join(directory, filename)) + else: + result.append(ref) + return map(os.path.normpath, result) + + +class DependencyInfo(object): + """Represents a dependency that is used to build and walk a tree.""" + + def __init__(self, filename): + self.filename = filename + self.provides = [] + self.requires = [] + + def __str__(self): + return '%s Provides: %s Requires: %s' % (self.filename, + repr(self.provides), + repr(self.requires)) + + +def BuildDependenciesFromFiles(files): + """Build a list of dependencies from a list of files. + + Description: + Takes a list of files, extracts their provides and requires, and builds + out a list of dependency objects. + + Args: + files: a list of files to be parsed for goog.provides and goog.requires. + + Returns: + A list of dependency objects, one for each file in the files argument. + """ + result = [] + filenames = set() + for filename in files: + if filename in filenames: + continue + + # Python 3 requires the file encoding to be specified + if (sys.version_info[0] < 3): + file_handle = open(filename, 'r') + else: + file_handle = open(filename, 'r', encoding='utf8') + + try: + dep = CreateDependencyInfo(filename, file_handle) + result.append(dep) + finally: + file_handle.close() + + filenames.add(filename) + + return result + + +def CreateDependencyInfo(filename, source): + """Create dependency info. + + Args: + filename: Filename for source. + source: File-like object containing source. + + Returns: + A DependencyInfo object with provides and requires filled. + """ + dep = DependencyInfo(filename) + for line in source: + if re.match(req_regex, line): + dep.requires.append(re.search(req_regex, line).group(1)) + if re.match(prov_regex, line): + dep.provides.append(re.search(prov_regex, line).group(1)) + return dep + + +def BuildDependencyHashFromDependencies(deps): + """Builds a hash for searching dependencies by the namespaces they provide. + + Description: + Dependency objects can provide multiple namespaces. This method enumerates + the provides of each dependency and adds them to a hash that can be used + to easily resolve a given dependency by a namespace it provides. + + Args: + deps: a list of dependency objects used to build the hash. + + Raises: + Exception: If a multiple files try to provide the same namepace. + + Returns: + A hash table { namespace: dependency } that can be used to resolve a + dependency by a namespace it provides. + """ + dep_hash = {} + for dep in deps: + for provide in dep.provides: + if provide in dep_hash: + raise Exception('Duplicate provide (%s) in (%s, %s)' % ( + provide, + dep_hash[provide].filename, + dep.filename)) + dep_hash[provide] = dep + return dep_hash + + +def CalculateDependencies(paths, inputs): + """Calculates the dependencies for given inputs. + + Description: + This method takes a list of paths (files, directories) and builds a + searchable data structure based on the namespaces that each .js file + provides. It then parses through each input, resolving dependencies + against this data structure. The final output is a list of files, + including the inputs, that represent all of the code that is needed to + compile the given inputs. + + Args: + paths: the references (files, directories) that are used to build the + dependency hash. + inputs: the inputs (files, directories, namespaces) that have dependencies + that need to be calculated. + + Raises: + Exception: if a provided input is invalid. + + Returns: + A list of all files, including inputs, that are needed to compile the given + inputs. + """ + deps = BuildDependenciesFromFiles(paths + inputs) + search_hash = BuildDependencyHashFromDependencies(deps) + result_list = [] + seen_list = [] + for input_file in inputs: + if IsNamespace(input_file): + namespace = re.search(ns_regex, input_file).group(1) + if namespace not in search_hash: + raise Exception('Invalid namespace (%s)' % namespace) + input_file = search_hash[namespace].filename + if not IsValidFile(input_file) or not IsJsFile(input_file): + raise Exception('Invalid file (%s)' % input_file) + seen_list.append(input_file) + file_handle = open(input_file, 'r') + try: + for line in file_handle: + if re.match(req_regex, line): + require = re.search(req_regex, line).group(1) + ResolveDependencies(require, search_hash, result_list, seen_list) + finally: + file_handle.close() + result_list.append(input_file) + + # All files depend on base.js, so put it first. + base_js_path = FindClosureBasePath(paths) + if base_js_path: + result_list.insert(0, base_js_path) + else: + logging.warning('Closure Library base.js not found.') + + return result_list + + +def FindClosureBasePath(paths): + """Given a list of file paths, return Closure base.js path, if any. + + Args: + paths: A list of paths. + + Returns: + The path to Closure's base.js file including filename, if found. + """ + + for path in paths: + pathname, filename = os.path.split(path) + + if filename == 'base.js': + f = open(path) + + is_base = False + + # Sanity check that this is the Closure base file. Check that this + # is where goog is defined. This is determined by the @provideGoog + # flag. + for line in f: + if '@provideGoog' in line: + is_base = True + break + + f.close() + + if is_base: + return path + +def ResolveDependencies(require, search_hash, result_list, seen_list): + """Takes a given requirement and resolves all of the dependencies for it. + + Description: + A given requirement may require other dependencies. This method + recursively resolves all dependencies for the given requirement. + + Raises: + Exception: when require does not exist in the search_hash. + + Args: + require: the namespace to resolve dependencies for. + search_hash: the data structure used for resolving dependencies. + result_list: a list of filenames that have been calculated as dependencies. + This variable is the output for this function. + seen_list: a list of filenames that have been 'seen'. This is required + for the dependency->dependent ordering. + """ + if require not in search_hash: + raise Exception('Missing provider for (%s)' % require) + + dep = search_hash[require] + if not dep.filename in seen_list: + seen_list.append(dep.filename) + for sub_require in dep.requires: + ResolveDependencies(sub_require, search_hash, result_list, seen_list) + result_list.append(dep.filename) + + +def GetDepsLine(dep, base_path): + """Returns a JS string for a dependency statement in the deps.js file. + + Args: + dep: The dependency that we're printing. + base_path: The path to Closure's base.js including filename. + """ + return 'goog.addDependency("%s", %s, %s);' % ( + GetRelpath(dep.filename, base_path), dep.provides, dep.requires) + + +def GetRelpath(path, start): + """Return a relative path to |path| from |start|.""" + # NOTE: Python 2.6 provides os.path.relpath, which has almost the same + # functionality as this function. Since we want to support 2.4, we have + # to implement it manually. :( + path_list = os.path.abspath(os.path.normpath(path)).split(os.sep) + start_list = os.path.abspath( + os.path.normpath(os.path.dirname(start))).split(os.sep) + + common_prefix_count = 0 + for i in range(0, min(len(path_list), len(start_list))): + if path_list[i] != start_list[i]: + break + common_prefix_count += 1 + + # Always use forward slashes, because this will get expanded to a url, + # not a file path. + return '/'.join(['..'] * (len(start_list) - common_prefix_count) + + path_list[common_prefix_count:]) + + +def PrintLine(msg, out): + out.write(msg) + out.write('\n') + + +def PrintDeps(source_paths, deps, out): + """Print out a deps.js file from a list of source paths. + + Args: + source_paths: Paths that we should generate dependency info for. + deps: Paths that provide dependency info. Their dependency info should + not appear in the deps file. + out: The output file. + + Returns: + True on success, false if it was unable to find the base path + to generate deps relative to. + """ + base_path = FindClosureBasePath(source_paths + deps) + if not base_path: + return False + + PrintLine('// This file was autogenerated by calcdeps.py', out) + excludesSet = set(deps) + + for dep in BuildDependenciesFromFiles(source_paths + deps): + if not dep.filename in excludesSet: + PrintLine(GetDepsLine(dep, base_path), out) + + return True + + +def PrintScript(source_paths, out): + for index, dep in enumerate(source_paths): + PrintLine('// Input %d' % index, out) + f = open(dep, 'r') + PrintLine(f.read(), out) + f.close() + + +def GetJavaVersion(): + """Returns the string for the current version of Java installed.""" + proc = subprocess.Popen(['java', '-version'], stderr=subprocess.PIPE) + proc.wait() + version_line = proc.stderr.read().splitlines()[0] + return version_regex.search(version_line.decode('utf-8')).group() + + +def FilterByExcludes(options, files): + """Filters the given files by the exlusions specified at the command line. + + Args: + options: The flags to calcdeps. + files: The files to filter. + Returns: + A list of files. + """ + excludes = [] + if options.excludes: + excludes = ExpandDirectories(options.excludes) + + excludesSet = set(excludes) + return [i for i in files if not i in excludesSet] + + +def GetPathsFromOptions(options): + """Generates the path files from flag options. + + Args: + options: The flags to calcdeps. + Returns: + A list of files in the specified paths. (strings). + """ + + search_paths = options.paths + if not search_paths: + search_paths = ['.'] # Add default folder if no path is specified. + + search_paths = ExpandDirectories(search_paths) + return FilterByExcludes(options, search_paths) + + +def GetInputsFromOptions(options): + """Generates the inputs from flag options. + + Args: + options: The flags to calcdeps. + Returns: + A list of inputs (strings). + """ + inputs = options.inputs + if not inputs: # Parse stdin + logging.info('No inputs specified. Reading from stdin...') + inputs = filter(None, [line.strip('\n') for line in sys.stdin.readlines()]) + + logging.info('Scanning files...') + inputs = ExpandDirectories(inputs) + + return FilterByExcludes(options, inputs) + + +def Compile(compiler_jar_path, source_paths, out, flags=None): + """Prepares command-line call to Closure compiler. + + Args: + compiler_jar_path: Path to the Closure compiler .jar file. + source_paths: Source paths to build, in order. + flags: A list of additional flags to pass on to Closure compiler. + """ + args = ['java', '-jar', compiler_jar_path] + for path in source_paths: + args += ['--js', path] + + if flags: + args += flags + + logging.info('Compiling with the following command: %s', ' '.join(args)) + proc = subprocess.Popen(args, stdout=subprocess.PIPE) + (stdoutdata, stderrdata) = proc.communicate() + if proc.returncode != 0: + logging.error('JavaScript compilation failed.') + sys.exit(1) + else: + out.write(stdoutdata.decode('utf-8')) + + +def main(): + """The entrypoint for this script.""" + + logging.basicConfig(format='calcdeps.py: %(message)s', level=logging.INFO) + + usage = 'usage: %prog [options] arg' + parser = optparse.OptionParser(usage) + parser.add_option('-i', + '--input', + dest='inputs', + action='append', + help='The inputs to calculate dependencies for. Valid ' + 'values can be files, directories, or namespaces ' + '(ns:goog.net.XhrIo). Only relevant to "list" and ' + '"script" output.') + parser.add_option('-p', + '--path', + dest='paths', + action='append', + help='The paths that should be traversed to build the ' + 'dependencies.') + parser.add_option('-d', + '--dep', + dest='deps', + action='append', + help='Directories or files that should be traversed to ' + 'find required dependencies for the deps file. ' + 'Does not generate dependency information for names ' + 'provided by these files. Only useful in "deps" mode.') + parser.add_option('-e', + '--exclude', + dest='excludes', + action='append', + help='Files or directories to exclude from the --path ' + 'and --input flags') + parser.add_option('-o', + '--output_mode', + dest='output_mode', + action='store', + default='list', + help='The type of output to generate from this script. ' + 'Options are "list" for a list of filenames, "script" ' + 'for a single script containing the contents of all the ' + 'file, "deps" to generate a deps.js file for all ' + 'paths, or "compiled" to produce compiled output with ' + 'the Closure compiler.') + parser.add_option('-c', + '--compiler_jar', + dest='compiler_jar', + action='store', + help='The location of the Closure compiler .jar file.') + parser.add_option('-f', + '--compiler_flag', + '--compiler_flags', # for backwards compatibility + dest='compiler_flags', + action='append', + help='Additional flag to pass to the Closure compiler. ' + 'May be specified multiple times to pass multiple flags.') + parser.add_option('--output_file', + dest='output_file', + action='store', + help=('If specified, write output to this path instead of ' + 'writing to standard output.')) + + (options, args) = parser.parse_args() + + search_paths = GetPathsFromOptions(options) + + if options.output_file: + out = open(options.output_file, 'w') + else: + out = sys.stdout + + if options.output_mode == 'deps': + result = PrintDeps(search_paths, ExpandDirectories(options.deps or []), out) + if not result: + logging.error('Could not find Closure Library in the specified paths') + sys.exit(1) + + return + + inputs = GetInputsFromOptions(options) + + logging.info('Finding Closure dependencies...') + deps = CalculateDependencies(search_paths, inputs) + output_mode = options.output_mode + + if output_mode == 'script': + PrintScript(deps, out) + elif output_mode == 'list': + # Just print out a dep per line + for dep in deps: + PrintLine(dep, out) + elif output_mode == 'compiled': + # Make sure a .jar is specified. + if not options.compiler_jar: + logging.error('--compiler_jar flag must be specified if --output is ' + '"compiled"') + sys.exit(1) + + # User friendly version check. + if distutils and not (distutils.version.LooseVersion(GetJavaVersion()) > + distutils.version.LooseVersion('1.6')): + logging.error('Closure Compiler requires Java 1.6 or higher.') + logging.error('Please visit http://www.java.com/getjava') + sys.exit(1) + + Compile(options.compiler_jar, deps, out, options.compiler_flags) + + else: + logging.error('Invalid value for --output flag.') + sys.exit(1) + +if __name__ == '__main__': + main() diff --git a/chromium/third_party/google-closure-library/closure/bin/generate_closure_unit_tests/generate_closure_unit_tests.js b/chromium/third_party/google-closure-library/closure/bin/generate_closure_unit_tests/generate_closure_unit_tests.js new file mode 100644 index 00000000000..a8de2bf2ef1 --- /dev/null +++ b/chromium/third_party/google-closure-library/closure/bin/generate_closure_unit_tests/generate_closure_unit_tests.js @@ -0,0 +1,273 @@ +// Copyright 2017 The Closure Library Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS-IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +const parse5 = require('parse5'); +const fs = require('fs'); +const path = require('path'); +const process = require('process'); + +const PATH = ''; + +/** + * @typedef {{ + * basePath: string, + * depsFile: string, + * output: string, + * overwriteExistingFiles: boolean, + * paths: !Array<{path: string, directory: boolean}>, + * recursive: boolean, + * }} + */ +let Args; + + +/** + * Entry point for this script. + */ +function main() { + let args; + + try { + args = processArgs(process.argv.slice(2)); + } catch (error) { + showHelp(error.message); + } + + for (const path of args.paths) { + if (path.directory) { + generateHtmlForDirectory(path.path, args); + } else { + maybeGenerateHtmlForFile(path.path, args); + } + } +} + + +/** + * Shows script help. + * @param {string=} errorMessage + */ +function showHelp(errorMessage = '') { + if (errorMessage) { + console.error(errorMessage); + } + + console.log(`Auto generates _test.html files for Closure unit tests. + + Usage: + generate_test_html [OPTIONS]... PATH... + + Paths: + list of paths to directories or _test.js files. For directories a + _test.html file will be generated for each _test.js file in the + directory. + + Options: + --base + Path to base.js file. + --dep_file: + Path to a deps file to use for all tests. If not specified but a + *_test_deps.js file exists it will be automatically included. + --recursive + generate _test.html for each _test.js in each directory recursively. + Defaults to false. + --output + output file path, should be named _test.html. Only valid if a single + _test.js file is specified for PATH. + --overwrite + overwrite any existing _test.html files. Defaults to true.`); + process.exit(errorMessage ? 1 : 0); +} + +/** + * @param {!Array<string>} args + * @return {!Args} + */ +function processArgs(args) { + const processedArgs = { + basePath: '', + output: '', + overwriteExistingFiles: true, + paths: [], + recursive: false, + }; + args.forEach((arg) => { + const [key, value] = arg.split('='); + switch (key) { + case '--base': + processedArgs.basePath = value; + break; + case '--dep_file': + processedArgs.depsFile = value; + break; + case '--help': + showHelp(); + break; + case '--recursive': + processedArgs.recursive = String(value).toLowerCase() !== 'false'; + break; + case '--output': + processedArgs.output = value; + break; + case '--overwrite': + processedArgs.overwriteExistingFiles = + String(value).toLowerCase() !== 'false'; + break; + default: + const stats = fs.statSync(arg); + processedArgs.paths.push({path: arg, directory: stats.isDirectory()}); + } + }); + + if (!processedArgs.basePath.endsWith('base.js')) { + throw new Error( + `Path to base must end with base.js: ${processedArgs.basePath}.`); + } + + if (processedArgs.output) { + if (processedArgs.paths.length > 1) { + throw new Error( + 'Cannot specify an output file when there is more than one path.'); + } + if (processedArgs.paths.some(path => path.directory)) { + throw new Error('Cannot specify an output when path is a directory.'); + } + if (!processedArgs.output.endsWith('_test.html')) { + throw new Error('Output file should end with _test.html.'); + } + } + + if (processedArgs.depsFile && !fs.existsSync(processedArgs.depsFile)) { + throw new Error( + 'Specified deps file does not exist: ' + processedArgs.depsFile); + } + + return processedArgs; +} + + +/** + * Generates one _test.html file for each _test.js file in the given directory. + * + * @param {string} directory Directory to generate files for. + * @param {!Args} args + */ +function generateHtmlForDirectory(directory, args) { + const directoryContents = fs.readdirSync(directory); + const subDirectories = []; + + for (const filename of directoryContents) { + const fullname = path.join(directory, filename); + const stats = fs.statSync(fullname); + if (stats.isDirectory() && args.recursive) { + subDirectories.push(fullname); + } else if (stats.isFile()) { + maybeGenerateHtmlForFile(fullname, args); + } + } + + for (const subDirectory of subDirectories) { + generateHtmlForDirectory(subDirectory, args); + } +} + + +/** + * Generates a _test.html file for the given file if it is a _test.js file. + * @param {string} filename Full path to the file. + * @param {!Args} args + */ +function maybeGenerateHtmlForFile(filename, args) { + if (!filename.endsWith('_test.js')) { + return; + } + + const htmlFilename = + args.output || filename.replace('_test.js', '_test.html'); + if (!args.overwriteExistingFiles && fs.existsSync(htmlFilename)) { + console.warn(`"${htmlFilename}" exists - skipping.`); + return; + } + + const originalJs = fs.readFileSync(filename, 'utf8'); + const provide = /goog\.(?:provide|module)\('([^']*?)'\);/g.exec(originalJs); + + if (!provide || !provide[1]) { + console.error( + `File ${filename} does not provide or module the tests, ` + + 'cannot generate html.'); + return; + } + + const newJS = `goog.require('${provide[1]}');`; + const title = `Closure Unit Tests - ${provide[1]}`; + + const baseFileName = filename.replace('_test.js', ''); + const testDomFilename = baseFileName + '_test_dom.html'; + const testDom = fs.existsSync(testDomFilename) ? + fs.readFileSync(testDomFilename, 'utf8') : + ''; + + const testBootstrapFilename = baseFileName + '_test_bootstrap.js'; + const pathToBootstrap = fs.existsSync(testBootstrapFilename) ? + path.basename(testBootstrapFilename) : + ''; + + const testDepsFilename = args.depsFile || baseFileName + '_test_deps.js'; + const pathToDeps = + fs.existsSync(testDepsFilename) ? path.basename(testDepsFilename) : ''; + + const pathToBase = path.relative(path.dirname(htmlFilename), args.basePath); + + const html = createHtml( + title, pathToBootstrap, pathToDeps, newJS, testDom, pathToBase); + fs.writeFileSync(htmlFilename, html); +} + +/** + * @param {string} title Title of the test. + * @param {string} pathToBootstrap Path to a bootstrap javascript file to run + * first, if any. Generally this file will contain closure defines. + * @param {string} pathToDeps Bath to a custom deps file, if any. + * @param {string} js Script content of the test. + * @param {string} testDom Any test DOM related to the test or the empty string + * if none. + * @param {string} pathToBase Path to the base.js file. + * @return {string} The text content of the test HTML file. + */ +function createHtml( + title, pathToBootstrap, pathToDeps, js, testDom, pathToBase) { + // Use parse5 to parse and reserialize the test dom. This generates any + // optional tags (html, body, head) if missing. Meaning test doms can specify + // these tags, if needed. + return parse5.serialize(parse5.parse(`<!DOCTYPE html> +<!-- DO NOT EDIT. This file auto-generated by generate_closure_unit_tests.js --> +<!-- +Copyright 2017 The Closure Library Authors. All Rights Reserved. + +Use of this source code is governed by the Apache License, Version 2.0. +See the COPYING file for details. +--> +<meta charset="UTF-8" /> +${pathToBootstrap ? `<script src=${pathToBootstrap}></script>` : ''} +<script src="${pathToBase}"></script> +${pathToDeps ? `<script src=${pathToDeps}></script>` : ''} +<script>${js}</script> +<title>${title}</title>` + testDom)); +} + + +if (require.main === module) { + main(); +} diff --git a/chromium/third_party/google-closure-library/closure/bin/generate_closure_unit_tests/package.json b/chromium/third_party/google-closure-library/closure/bin/generate_closure_unit_tests/package.json new file mode 100644 index 00000000000..6db974f25e5 --- /dev/null +++ b/chromium/third_party/google-closure-library/closure/bin/generate_closure_unit_tests/package.json @@ -0,0 +1,19 @@ +{ + "name": "generate_closure_unit_tests", + "version": "1.0.0", + "description": "Generates *_test.html unit test files given *_test.js and *_test_dom.html files.", + "main": "generate_closure_unit_tests.js", + "bin": { + "generate_closure_unit_tests": "./generate_closure_unit_tests.js" + }, + "author": "johnplaisted@google.com", + "licenses": [ + { + "type": "Apache-2.0", + "url": "https://raw.githubusercontent.com/google/closure-library/master/LICENSE" + } + ], + "dependencies": { + "parse5": "^4.0.0" + } +} diff --git a/chromium/third_party/google-closure-library/closure/bin/labs/code/closure.el b/chromium/third_party/google-closure-library/closure/bin/labs/code/closure.el new file mode 100644 index 00000000000..394c4837496 --- /dev/null +++ b/chromium/third_party/google-closure-library/closure/bin/labs/code/closure.el @@ -0,0 +1,39 @@ +;; Copyright 2013 The Closure Library Authors. All Rights Reserved. +;; +;; Licensed under the Apache License, Version 2.0 (the "License"); +;; you may not use this file except in compliance with the License. +;; You may obtain a copy of the License at +;; +;; http://www.apache.org/licenses/LICENSE-2.0 +;; +;; Unless required `by applicable law or agreed to in writing, software +;; distributed under the License is distributed on an "AS-IS" BASIS, +;; WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +;; See the License for the specific language governing permissions and +;; limitations under the License. + +;; Closure JS code editing functions for emacs. + +;; Author: nnaze@google.com (Nathan Naze) + +;; Remember the path of this file, as we will base our paths on it. +(setq closure-el-path load-file-name) + +(defun closure-el-directory () + "Get the directory the closure.el file lives in." + (file-name-directory closure-el-path)) + +(defun closure-generate-jsdoc-path() + "The path of the generate_jsdoc.py script." + (concat (closure-el-directory) "generate_jsdoc.py")) + +(defun closure-insert-jsdoc () + "Insert JSDoc for the next function after the cursor." + (interactive) + (save-excursion ; Remembers cursor location + (call-process-region + (point) (point-max) + (closure-generate-jsdoc-path) + t t))) + +(provide 'closure) diff --git a/chromium/third_party/google-closure-library/closure/bin/labs/code/closure_test.el b/chromium/third_party/google-closure-library/closure/bin/labs/code/closure_test.el new file mode 100644 index 00000000000..0a44e97dc6b --- /dev/null +++ b/chromium/third_party/google-closure-library/closure/bin/labs/code/closure_test.el @@ -0,0 +1,31 @@ +;; Copyright 2013 The Closure Library Authors. All Rights Reserved. +;; +;; Licensed under the Apache License, Version 2.0 (the "License"); +;; you may not use this file except in compliance with the License. +;; You may obtain a copy of the License at +;; +;; http://www.apache.org/licenses/LICENSE-2.0 +;; +;; Unless required `by applicable law or agreed to in writing, software +;; distributed under the License is distributed on an "AS-IS" BASIS, +;; WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +;; See the License for the specific language governing permissions and +;; limitations under the License. + +;; Unit tests for closure_test.el + +;; Author: nnaze@google.com (Nathan Naze) + +(require 'cl) + +(setq closure-test-directory (file-name-directory load-file-name)) + +(load-file (concat closure-test-directory "closure.el")) + +(setq closure-el-path "/test/path/closure.el") + +(assert (string= "/test/path/" (closure-el-directory))) + +(assert (string= + "/test/path/generate_jsdoc.py" + (closure-generate-jsdoc-path))) diff --git a/chromium/third_party/google-closure-library/closure/bin/labs/code/generate_jsdoc.py b/chromium/third_party/google-closure-library/closure/bin/labs/code/generate_jsdoc.py new file mode 100755 index 00000000000..fc5ca45a48f --- /dev/null +++ b/chromium/third_party/google-closure-library/closure/bin/labs/code/generate_jsdoc.py @@ -0,0 +1,171 @@ +#!/usr/bin/env python +# +# Copyright 2013 The Closure Library Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS-IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Tool to insert JsDoc before a function. + +This script attempts to find the first function passed in to stdin, generate +JSDoc for it (with argument names and possibly return value), and inject +it in the string. This is intended to be used as a subprocess by editors +such as emacs and vi. +""" + +import re +import sys + + +# Matches a typical Closure-style function definition. +_FUNCTION_REGEX = re.compile(r""" +# Start of line +^ + +# Indentation +(?P<indentation>[ ]*) + +# Identifier (handling split across line) +(?P<identifier>\w+(\s*\.\s*\w+)*) + +# "= function" +\s* = \s* function \s* + +# opening paren +\( + +# Function arguments +(?P<arguments>(?:\s|\w+|,)*) + +# closing paren +\) + +# opening bracket +\s* { + +""", re.MULTILINE | re.VERBOSE) + + +def _MatchFirstFunction(script): + """Match the first function seen in the script.""" + return _FUNCTION_REGEX.search(script) + + +def _ParseArgString(arg_string): + """Parse an argument string (inside parens) into parameter names.""" + for arg in arg_string.split(','): + arg = arg.strip() + if arg: + yield arg + + +def _ExtractFunctionBody(script, indentation=0): + """Attempt to return the function body.""" + + # Real extraction would require a token parser and state machines. + # We look for first bracket at the same level of indentation. + regex_str = r'{(.*?)^[ ]{%d}}' % indentation + + function_regex = re.compile(regex_str, re.MULTILINE | re.DOTALL) + match = function_regex.search(script) + if match: + return match.group(1) + + +def _ContainsReturnValue(function_body): + """Attempt to determine if the function body returns a value.""" + return_regex = re.compile(r'\breturn\b[^;]') + + # If this matches, we assume they're returning something. + return bool(return_regex.search(function_body)) + + +def _InsertString(original_string, inserted_string, index): + """Insert a string into another string at a given index.""" + return original_string[0:index] + inserted_string + original_string[index:] + + +def _GenerateJsDoc(args, return_val=False): + """Generate JSDoc for a function. + + Args: + args: A list of names of the argument. + return_val: Whether the function has a return value. + + Returns: + The JSDoc as a string. + """ + + lines = [] + lines.append('/**') + + lines += [' * @param {} %s' % arg for arg in args] + + if return_val: + lines.append(' * @return') + + lines.append(' */') + + return '\n'.join(lines) + '\n' + + +def _IndentString(source_string, indentation): + """Indent string some number of characters.""" + lines = [(indentation * ' ') + line + for line in source_string.splitlines(True)] + return ''.join(lines) + + +def InsertJsDoc(script): + """Attempt to insert JSDoc for the first seen function in the script. + + Args: + script: The script, as a string. + + Returns: + Returns the new string if function was found and JSDoc inserted. Otherwise + returns None. + """ + + match = _MatchFirstFunction(script) + if not match: + return + + # Add argument flags. + args_string = match.group('arguments') + args = _ParseArgString(args_string) + + start_index = match.start(0) + function_to_end = script[start_index:] + + lvalue_indentation = len(match.group('indentation')) + + return_val = False + function_body = _ExtractFunctionBody(function_to_end, lvalue_indentation) + if function_body: + return_val = _ContainsReturnValue(function_body) + + jsdoc = _GenerateJsDoc(args, return_val) + if lvalue_indentation: + jsdoc = _IndentString(jsdoc, lvalue_indentation) + + return _InsertString(script, jsdoc, start_index) + + +if __name__ == '__main__': + stdin_script = sys.stdin.read() + result = InsertJsDoc(stdin_script) + + if result: + sys.stdout.write(result) + else: + sys.stdout.write(stdin_script) diff --git a/chromium/third_party/google-closure-library/closure/bin/labs/code/generate_jsdoc_test.py b/chromium/third_party/google-closure-library/closure/bin/labs/code/generate_jsdoc_test.py new file mode 100755 index 00000000000..470bab40867 --- /dev/null +++ b/chromium/third_party/google-closure-library/closure/bin/labs/code/generate_jsdoc_test.py @@ -0,0 +1,159 @@ +#!/usr/bin/env python +# +# Copyright 2013 The Closure Library Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required `by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS-IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + +"""Unit test for generate_jsdoc.""" + +__author__ = 'nnaze@google.com (Nathan Naze)' + + +import re +import unittest + +import generate_jsdoc + + +class InsertJsDocTestCase(unittest.TestCase): + """Unit test for source. Tests the parser on a known source input.""" + + def testMatchFirstFunction(self): + match = generate_jsdoc._MatchFirstFunction(_TEST_SOURCE) + self.assertNotEqual(None, match) + self.assertEqual('aaa, bbb, ccc', match.group('arguments')) + + match = generate_jsdoc._MatchFirstFunction(_INDENTED_SOURCE) + self.assertNotEqual(None, match) + self.assertEqual('', match.group('arguments')) + + match = generate_jsdoc._MatchFirstFunction(_ODD_NEWLINES_SOURCE) + self.assertEqual('goog.\nfoo.\nbar\n.baz.\nqux', match.group('identifier')) + + def testParseArgString(self): + self.assertEqual(['foo', 'bar', 'baz'], + list(generate_jsdoc._ParseArgString('foo, bar, baz'))) + + def testExtractFunctionBody(self): + self.assertEqual('\n // Function comments.\n return;\n', + generate_jsdoc._ExtractFunctionBody(_TEST_SOURCE)) + + self.assertEqual('\n var bar = 3;\n return true;\n', + generate_jsdoc._ExtractFunctionBody(_INDENTED_SOURCE, 2)) + + def testContainsValueReturn(self): + self.assertTrue(generate_jsdoc._ContainsReturnValue(_INDENTED_SOURCE)) + self.assertFalse(generate_jsdoc._ContainsReturnValue(_TEST_SOURCE)) + + def testInsertString(self): + self.assertEqual('abc123def', + generate_jsdoc._InsertString('abcdef', '123', 3)) + + def testInsertJsDoc(self): + self.assertEqual(_EXPECTED_INDENTED_SOURCE, + generate_jsdoc.InsertJsDoc(_INDENTED_SOURCE)) + + self.assertEqual(_EXPECTED_TEST_SOURCE, + generate_jsdoc.InsertJsDoc(_TEST_SOURCE)) + + self.assertEqual(_EXPECTED_ODD_NEWLINES_SOURCE, + generate_jsdoc.InsertJsDoc(_ODD_NEWLINES_SOURCE)) + + +_INDENTED_SOURCE = """\ + boo.foo.woo = function() { + var bar = 3; + return true; + }; +""" + +_EXPECTED_INDENTED_SOURCE = """\ + /** + * @return + */ + boo.foo.woo = function() { + var bar = 3; + return true; + }; +""" + + +_TEST_SOURCE = """\ + +// Random comment. + +goog.foo.bar = function (aaa, bbb, ccc) { + // Function comments. + return; +}; +""" + +_EXPECTED_TEST_SOURCE = """\ + +// Random comment. + +/** + * @param {} aaa + * @param {} bbb + * @param {} ccc + */ +goog.foo.bar = function (aaa, bbb, ccc) { + // Function comments. + return; +}; +""" + +_ODD_NEWLINES_SOURCE = """\ +goog. +foo. +bar +.baz. +qux + = + + function + +(aaa, + +bbb, ccc) { + // Function comments. + return; +}; +""" + +_EXPECTED_ODD_NEWLINES_SOURCE = """\ +/** + * @param {} aaa + * @param {} bbb + * @param {} ccc + */ +goog. +foo. +bar +.baz. +qux + = + + function + +(aaa, + +bbb, ccc) { + // Function comments. + return; +}; +""" + +if __name__ == '__main__': + unittest.main() diff --git a/chromium/third_party/google-closure-library/closure/bin/labs/code/run_el_tests.sh b/chromium/third_party/google-closure-library/closure/bin/labs/code/run_el_tests.sh new file mode 100755 index 00000000000..ded2a74a119 --- /dev/null +++ b/chromium/third_party/google-closure-library/closure/bin/labs/code/run_el_tests.sh @@ -0,0 +1,31 @@ +#!/bin/bash +# +# Copyright 2013 The Closure Library Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS-IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# Wraps the unit tests not using the Google framework so they may be run +# on the continuous build. +# +# Author: nnaze@google.com (Nathan Naze) +# +# Wraps the unit tests not using the Google framework so they may be run +# run on the continuous build. + +set -e + +source googletest.sh || exit 1 + +CLOSURE_SRCDIR=$TEST_SRCDIR/google3/javascript/closure/labs/bin/code/ + +emacs --script $CLOSURE_SRCDIR/closure_test.el diff --git a/chromium/third_party/google-closure-library/closure/bin/labs/code/run_py_tests.sh b/chromium/third_party/google-closure-library/closure/bin/labs/code/run_py_tests.sh new file mode 100755 index 00000000000..d075ebba1f8 --- /dev/null +++ b/chromium/third_party/google-closure-library/closure/bin/labs/code/run_py_tests.sh @@ -0,0 +1,28 @@ +#!/bin/bash +# +# Copyright 2013 The Closure Library Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS-IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# Wraps the unit tests not using the Google framework so they may be run +# on the continuous build. + +set -e + +source googletest.sh || exit 1 + +CLOSURE_SRCDIR=$TEST_SRCDIR/google3/javascript/closure/labs/bin/code/ + +PYTHONPATH=$CLOSURE_SRCDIR + +$CLOSURE_SRCDIR/generate_jsdoc_test.py diff --git a/chromium/third_party/google-closure-library/closure/bin/logos/logo.svg b/chromium/third_party/google-closure-library/closure/bin/logos/logo.svg new file mode 100644 index 00000000000..2cb6b88a31d --- /dev/null +++ b/chromium/third_party/google-closure-library/closure/bin/logos/logo.svg @@ -0,0 +1,25 @@ +<?xml version="1.0" standalone="no"?> +<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" + "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"> +<!-- +Copyright 2013 The Closure Library Authors. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS-IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +--> +<svg xmlns="http://www.w3.org/2000/svg" version="1.1" viewBox="0 0 9 9"> + <title>Closure Logo</title> + <path d="M2,2 h2 a2,2 0 1 0 -2,2 z" fill="#0066CF" /> + <path d="M7,2 h-2 a2,2 0 1 1 2,2 z" fill="#FD0100" /> + <path d="M2,7 h2 a2,2 0 1 1 -2,-2 z" fill="#FEC502" /> + <path d="M7,7 h-2 a2,2 0 1 0 2,-2 z" fill="#009338" /> +</svg> diff --git a/chromium/third_party/google-closure-library/closure/bin/logos/logoandlabel.svg b/chromium/third_party/google-closure-library/closure/bin/logos/logoandlabel.svg new file mode 100644 index 00000000000..69c2d6849b4 --- /dev/null +++ b/chromium/third_party/google-closure-library/closure/bin/logos/logoandlabel.svg @@ -0,0 +1,25 @@ +<?xml version="1.0" standalone="no"?> +<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" + "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"> +<!-- +Copyright 2013 The Closure Library Authors. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS-IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +--> +<svg xmlns="http://www.w3.org/2000/svg" + xmlns:xlink="http://www.w3.org/1999/xlink" + viewBox="0 0 200 250"> + <image x="0" y="0" width="200" height="200" xlink:href="logo.svg" /> + <text text-anchor="middle" x="100" y="245" font-family="Helvetica" + font-size="53" font-weight="bold">Closure</text> +</svg> |