| Index: cms/sources.py |
| =================================================================== |
| --- a/cms/sources.py |
| +++ b/cms/sources.py |
| @@ -338,16 +338,48 @@ |
| for base in self._bases: |
| if base.has_file(filename): |
| return base.read_file(filename, binary) |
| raise KeyError('File not found {}'.format(filename)) |
| def list_files(self, subdir): |
| return {f for base in self._bases for f in base.list_files(subdir)} |
| + def list_pages(self): |
| + # We can't let the base class implementation of `list_pages` handle |
| + # this on top of `list_files` because the same page can have multiple |
| + # possible source files. When those source files reside in different |
| + # base sources, only the first of them should be visible. Possible |
| + # source files of the page in later sources should be shadowed to avoid |
| + # unexpected conflicts. |
| + all_seen = set() |
| + for base in self._bases: |
| + base_seen = set() |
| + for page, ext in base.list_pages(): |
| + if page not in all_seen: |
| + yield page, ext |
| + base_seen.add(page) |
| + all_seen.update(base_seen) |
| + |
| + def has_page(self, page, format=None): |
| + # This function has to behave consistently with `list_pages` so we must |
| + # check if the page (in any format) exists in earlier bases before we |
| + # check later ones. |
| + for base in self._bases: |
| + if base.has_page(page): |
| + if format is None: |
| + return True |
| + # If a page exists in an earlier base in another format than |
| + # requested, it will shadow the same page in later bases. |
| + # Therefore, as as soon as we find a base that has the page in |
| + # any format, we can delegate to it completely. |
| + return base.has_page(page, format) |
| + else: |
| + return False |
| + |
| def _memoize(func): |
| """Cache results of functions calls.""" |
| memoized = {} |
| def wrapper(*args): |
| try: |
| return memoized[args] |