importer.py 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172
  1. import web
  2. import re
  3. import os.path
  4. from cache import Cache, to_dir, assert_file_exists
  5. ALIAS_PREFIX = '.'
  6. def separate_imports(content):
  7. """
  8. Separate the import section from the rest of the content of a file. Returns
  9. a tuple with a list of imports, and the remaning part of the string.
  10. Imports are located in the beginning of a file, and can take the following
  11. forms:
  12. # No imports
  13. content
  14. # Single file import
  15. import foo
  16. content
  17. # Multiple files import
  18. import foo, bar
  19. content
  20. # Multiline import
  21. import foo, bar, \
  22. baz
  23. content
  24. """
  25. content = content.lstrip()
  26. imports = []
  27. filename = '[a-zA-Z0-9\._-]+'
  28. comma = re.compile(' *, *(?:\\\\\r?\n *)?', re.M)
  29. file_list = '%s(?:%s%s?)*' % (filename, comma.pattern, filename)
  30. m = re.match('^import (%s)(?:(?:\r?\n)+((?:.|\n)*))?$' % file_list,
  31. content, re.M)
  32. if m:
  33. imports = comma.split(m.group(1))
  34. content = m.group(2)
  35. print 'groups:', m.groups()
  36. if not content:
  37. content = ''
  38. return imports, content
  39. class Importer(Cache):
  40. def __init__(self, files=[], extension=None, **kwargs):
  41. super(Importer, self).__init__(**kwargs)
  42. self.aliases = {}
  43. if extension:
  44. self.extension = '.' + extension
  45. else:
  46. self.extension = ''
  47. for f in files:
  48. self.add(f)
  49. def add(self, path):
  50. path = self.create_full_paths([path])[0]
  51. Cache.add(self, path, absolute=True)
  52. def set_alias(self, alias, path):
  53. """
  54. Add an alias for the path to a file, relative to the importer's root
  55. directory. Aliases are used to shorten the names of imports. E.g. using
  56. "@jquery" in an import could point to "static/js/jquery-min-1.7.1.js".
  57. """
  58. path = self.root + path + self.extension
  59. assert_file_exists(path)
  60. self.aliases[alias] = path
  61. web.debug('Added alias "%s" for path "%s".' % (alias, path))
  62. def create_full_paths(self, files, relative_file=None):
  63. """
  64. Create full paths out of a file list:
  65. 1. Replace any aliases.
  66. 2. Look for direct siblings of the loaded file, if any (relative_file).
  67. 3. Look in the root folder of the Import object.
  68. """
  69. replaced = []
  70. alias_len = len(ALIAS_PREFIX)
  71. if relative_file:
  72. relative_dir = to_dir(relative_file)
  73. for i, path in enumerate(files):
  74. if path[:alias_len] == ALIAS_PREFIX:
  75. # Alias syntax is used, assert that the alias exists
  76. alias = path[alias_len:]
  77. if alias not in self.aliases:
  78. raise ValueError('Alias "%s" has not been set.' % alias)
  79. # Alias exists, translate is to a full path
  80. replaced.append(self.aliases[alias])
  81. else:
  82. if relative_file:
  83. relative_path = relative_dir + path + self.extension
  84. if os.path.exists(relative_path):
  85. # Relative import
  86. replaced.append(relative_path)
  87. continue
  88. # Import from root
  89. path = self.root + path + self.extension
  90. assert_file_exists(path)
  91. replaced.append(path)
  92. return replaced
  93. def concatenate(self, files, relative_path=False):
  94. #if relative_path:
  95. # files = self.create_full_paths(files, relative_path)
  96. map(assert_file_exists, files)
  97. self.loaded = []
  98. self.import_map = {}
  99. concat = ''
  100. for path in files:
  101. f = open(path, 'r')
  102. raw = f.read()
  103. f.close()
  104. # Parse imports from file content
  105. imports, content = separate_imports(raw)
  106. if imports:
  107. print 'imports:', imports
  108. print 'content:', content
  109. self.import_map.update({path: imports})
  110. # Only add imported files that have not been loaded yet
  111. new_imports = filter(lambda i: i not in self.loaded, imports)
  112. # Prepend imports that have not been loaded yet before the file's
  113. # content
  114. if new_imports:
  115. new_imports = self.create_full_paths(new_imports, path)
  116. # Check if there is a recursive import
  117. for f in new_imports:
  118. if f == path:
  119. raise ImportError('Recursive import in file "%s".' % f)
  120. if f in self.import_map and path in self.import_map[f]:
  121. raise ImportError('Recursive import in of "%s" in ' \
  122. + 'file "%s".' % (f, path))
  123. concat += self.concatenate(new_imports)
  124. #concat += self.concatenate(new_imports, path)
  125. concat += content
  126. self.loaded.append(path)
  127. return concat
  128. def content(self):
  129. return self.concatenate(self.files)