source.py 3.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132
  1. # Copyright 2009 The Closure Library Authors. All Rights Reserved.
  2. #
  3. # Licensed under the Apache License, Version 2.0 (the "License");
  4. # you may not use this file except in compliance with the License.
  5. # You may obtain a copy of the License at
  6. #
  7. # http://www.apache.org/licenses/LICENSE-2.0
  8. #
  9. # Unless required by applicable law or agreed to in writing, software
  10. # distributed under the License is distributed on an "AS-IS" BASIS,
  11. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. # See the License for the specific language governing permissions and
  13. # limitations under the License.
  14. """Scans a source JS file for its provided and required namespaces.
  15. Simple class to scan a JavaScript file and express its dependencies.
  16. """
  17. __author__ = 'nnaze@google.com'
  18. import codecs
  19. import re
  20. _BASE_REGEX_STRING = r'^\s*goog\.%s\(\s*[\'"](.+)[\'"]\s*\)'
  21. _MODULE_REGEX = re.compile(_BASE_REGEX_STRING % 'module')
  22. _PROVIDE_REGEX = re.compile(_BASE_REGEX_STRING % 'provide')
  23. _REQUIRE_REGEX_STRING = (r'^\s*(?:(?:var|let|const)\s+[a-zA-Z0-9$_,:{}\s]*'
  24. r'\s*=\s*)?goog\.require\(\s*[\'"](.+)[\'"]\s*\)')
  25. _REQUIRES_REGEX = re.compile(_REQUIRE_REGEX_STRING)
  26. class Source(object):
  27. """Scans a JavaScript source for its provided and required namespaces."""
  28. # Matches a "/* ... */" comment.
  29. # Note: We can't definitively distinguish a "/*" in a string literal without a
  30. # state machine tokenizer. We'll assume that a line starting with whitespace
  31. # and "/*" is a comment.
  32. _COMMENT_REGEX = re.compile(
  33. r"""
  34. ^\s* # Start of a new line and whitespace
  35. /\* # Opening "/*"
  36. .*? # Non greedy match of any characters (including newlines)
  37. \*/ # Closing "*/""",
  38. re.MULTILINE | re.DOTALL | re.VERBOSE)
  39. def __init__(self, source):
  40. """Initialize a source.
  41. Args:
  42. source: str, The JavaScript source.
  43. """
  44. self.provides = set()
  45. self.requires = set()
  46. self.is_goog_module = False
  47. self._source = source
  48. self._ScanSource()
  49. def GetSource(self):
  50. """Get the source as a string."""
  51. return self._source
  52. @classmethod
  53. def _StripComments(cls, source):
  54. return cls._COMMENT_REGEX.sub('', source)
  55. @classmethod
  56. def _HasProvideGoogFlag(cls, source):
  57. """Determines whether the @provideGoog flag is in a comment."""
  58. for comment_content in cls._COMMENT_REGEX.findall(source):
  59. if '@provideGoog' in comment_content:
  60. return True
  61. return False
  62. def _ScanSource(self):
  63. """Fill in provides and requires by scanning the source."""
  64. stripped_source = self._StripComments(self.GetSource())
  65. source_lines = stripped_source.splitlines()
  66. for line in source_lines:
  67. match = _PROVIDE_REGEX.match(line)
  68. if match:
  69. self.provides.add(match.group(1))
  70. match = _MODULE_REGEX.match(line)
  71. if match:
  72. self.provides.add(match.group(1))
  73. self.is_goog_module = True
  74. match = _REQUIRES_REGEX.match(line)
  75. if match:
  76. self.requires.add(match.group(1))
  77. # Closure's base file implicitly provides 'goog'.
  78. # This is indicated with the @provideGoog flag.
  79. if self._HasProvideGoogFlag(self.GetSource()):
  80. if len(self.provides) or len(self.requires):
  81. raise Exception(
  82. 'Base file should not provide or require namespaces.')
  83. self.provides.add('goog')
  84. def GetFileContents(path):
  85. """Get a file's contents as a string.
  86. Args:
  87. path: str, Path to file.
  88. Returns:
  89. str, Contents of file.
  90. Raises:
  91. IOError: An error occurred opening or reading the file.
  92. """
  93. fileobj = None
  94. try:
  95. fileobj = codecs.open(path, encoding='utf-8-sig')
  96. return fileobj.read()
  97. except IOError as error:
  98. raise IOError('An error occurred opening or reading the file: %s. %s'
  99. % (path, error))
  100. finally:
  101. if fileobj is not None:
  102. fileobj.close()