| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155 |
- import web
- import re
- import os.path
- from cache import Cache, to_dir, assert_file_exists
- ALIAS_PREFIX = '@'
- def separate_imports(content):
- """
- Separate the import section from the rest of the content of a file. Returns
- a tuple with a list of imports, and the remaning part of the string.
- Imports are located in the beginning of a file, and can take the following
- forms:
- # No imports
- content
- # Single file import
- import foo
- content
- # Multiple files import
- import foo, bar
- content
- # Multiline import
- import foo, bar, \
- baz
- content
- """
- content = content.lstrip()
- imports = []
- comma = re.compile(' *, *(?:\\\\\r?\n *)?')
- file_list = '\w+(?:%s\w+?)*' % comma.pattern
- m = re.match('^import (%s)(?:(?:\r?\n)+(.*))?$' % file_list, content, re.M)
- if m:
- imports = comma.split(m.group(1))
- content = m.group(2)
- if not content:
- content = ''
- return imports, content
- class Importer(Cache):
- def __init__(self, root, extension=None):
- Cache.__init__(self)
- self.root = to_dir(root + '/')
- self.aliases = {}
- if extension:
- self.extension = '.' + extension
- else:
- self.extension = ''
- def set_alias(self, name, path):
- """
- Add an alias for the path to a file, relative to the importer's root
- directory. Aliases are used to shorten the names of imports. E.g. using
- "@jquery" in an import could point to "static/js/jquery-min-1.7.1.js".
- """
- path = self.root + path + self.extension
- assert_file_exists(path)
- self.aliases[name] = path
- def create_full_paths(self, files, relative_file=None):
- """
- Create full paths out of a file list:
- 1. Replace any aliases.
- 2. Look for direct siblings of the loaded file, if any (relative_file).
- 3. Look in the root folder of the Import object.
- """
- replaced = []
- alias_len = len(ALIAS_PREFIX)
- if relative_file:
- relative_dir = to_dir(relative_file)
- for i, path in enumerate(files):
- if path[:alias_len] == ALIAS_PREFIX:
- # Alias syntax is used, assert that the alias exists
- alias = path[alias_len:]
- if alias not in self.aliases:
- raise ValueError('Alias "%s" has not been set.' % alias)
- # Alias exists, translate is to a full path
- replaced.append(self.aliases[alias])
- else:
- if relative_file:
- relative_path = relative_dir + path + self.extension
- if os.path.exists(relative_path):
- # Relative import
- replaced.append(relative_path)
- continue
- # Import from root
- path = self.root + path + self.extension
- assert_file_exists(path)
- replaced.append(path)
- return replaced
- def concatenate(self, files):
- files = self.create_full_paths(files)
- map(assert_file_exists, files)
- self.loaded = []
- self.import_map = {}
- concat = ''
- for path in files:
- f = open(path, 'r')
- raw = f.read()
- f.close()
- # Parse imports from file content
- imports, content = separate_imports(raw)
- self.import_map.update({path: imports})
- # Only add imported files that have not been loaded yet
- new_imports = filter(lambda i: i not in self.loaded, imports)
- # Prepend imports that have not been loaded yet before the file's
- # content
- if new_imports:
- new_imports = self.create_full_paths(new_imports, path)
- # Check if there is a recursive import
- for f in new_imports:
- if f == path:
- raise ImportError('Recursive import in file "%s".' % f)
- if f in self.import_map and path in self.import_map[f]:
- raise ImportError('Recursive import in of "%s" in ' \
- + 'file "%s".' % (f, path))
- concat += self.concatenate(new_imports)
- concat += content
- self.loaded += path
- return concat
- def content(self):
- return self.concatenate(self.files)
|