Index: chrome/common/extensions/docs/server2/path_canonicalizer.py |
diff --git a/chrome/common/extensions/docs/server2/path_canonicalizer.py b/chrome/common/extensions/docs/server2/path_canonicalizer.py |
index 8b5f77c03d09f82ef7bcecdcd179fc279f25f948..b4498a7f611cd02c5c965a9467252143863ab287 100644 |
--- a/chrome/common/extensions/docs/server2/path_canonicalizer.py |
+++ b/chrome/common/extensions/docs/server2/path_canonicalizer.py |
@@ -2,48 +2,106 @@ |
# Use of this source code is governed by a BSD-style license that can be |
# found in the LICENSE file. |
+import logging |
import os |
+import posixpath |
+import traceback |
+ |
+from branch_utility import BranchUtility |
+from file_system import FileNotFoundError |
from third_party.json_schema_compiler.model import UnixName |
import svn_constants |
+def _SimplifyFileName(file_name): |
+ return (posixpath.splitext(file_name)[0] |
+ .lower() |
+ .replace('.', '') |
+ .replace('-', '') |
+ .replace('_', '')) |
+ |
class PathCanonicalizer(object): |
'''Transforms paths into their canonical forms. Since the dev server has had |
many incarnations - e.g. there didn't use to be apps/ - there may be old |
paths lying around the webs. We try to redirect those to where they are now. |
''' |
- def __init__(self, channel, compiled_fs_factory): |
- self._channel = channel |
- self._identity_fs = compiled_fs_factory.CreateIdentity(PathCanonicalizer) |
+ def __init__(self, compiled_fs_factory): |
+ # Map of simplified API names (for typo detection) to their real paths. |
+ def make_public_apis(_, file_names): |
+ return dict((_SimplifyFileName(name), name) for name in file_names) |
+ self._public_apis = compiled_fs_factory.Create(make_public_apis, |
+ PathCanonicalizer) |
def Canonicalize(self, path): |
- starts_with_channel, path_without_channel = (False, path) |
- if path.startswith('%s/' % self._channel): |
- starts_with_channel, path_without_channel = ( |
- True, path[len(self._channel) + 1:]) |
- |
- if any(path.startswith(prefix) |
- for prefix in ('extensions/', 'apps/', 'static/')): |
- return path |
- |
- if '/' in path_without_channel or path_without_channel == '404.html': |
- return path |
- |
- apps_templates = self._identity_fs.GetFromFileListing( |
- '/'.join((svn_constants.PUBLIC_TEMPLATE_PATH, 'apps'))) |
- extensions_templates = self._identity_fs.GetFromFileListing( |
- '/'.join((svn_constants.PUBLIC_TEMPLATE_PATH, 'extensions'))) |
- |
- if self._channel is None or not starts_with_channel: |
- apps_path = 'apps/%s' % path_without_channel |
- extensions_path = 'extensions/%s' % path_without_channel |
+ '''Returns the canonical path for |path|, and whether that path is a |
+ permanent canonicalisation (e.g. when we redirect from a channel to a |
+ channel-less URL) or temporary (e.g. when we redirect from an apps-only API |
+ to an extensions one - we may at some point enable it for extensions). |
+ ''' |
+ class ReturnType(object): |
+ def __init__(self, path, permanent): |
+ self.path = path |
+ self.permanent = permanent |
+ |
+ # Catch incorrect comparisons by disabling ==/!=. |
+ def __eq__(self, _): raise NotImplementedError() |
+ def __ne__(self, _): raise NotImplementedError() |
+ |
+ # Strip any channel info off it. There are no channels anymore. |
+ for channel_name in BranchUtility.GetAllChannelNames(): |
+ channel_prefix = channel_name + '/' |
+ if path.startswith(channel_prefix): |
+ # Redirect now so that we can set the permanent-redirect bit. Channel |
+ # redirects are the only things that should be permanent redirects; |
+ # anything else *could* change, so is temporary. |
+ return ReturnType(path[len(channel_prefix):], True) |
+ |
+ # No further work needed for static. |
+ if path.startswith('static/'): |
+ return ReturnType(path, False) |
+ |
+ # People go to just "extensions" or "apps". Redirect to the directory. |
+ if path in ('extensions', 'apps'): |
+ return ReturnType(path + '/', False) |
+ |
+ # The rest of this function deals with trying to figure out what API page |
+ # for extensions/apps to redirect to, if any. We see a few different cases |
+ # here: |
+ # - Unqualified names ("browserAction.html"). These are easy to resolve; |
+ # figure out whether it's an extension or app API and redirect. |
+ # - but what if it's both? Well, assume extensions. Maybe later we can |
+ # check analytics and see which is more popular. |
+ # - Wrong names ("apps/browserAction.html"). This really does happen, |
+ # damn it, so do the above logic but record which is the default. |
+ if path.startswith(('extensions/', 'apps/')): |
+ default_platform, reference_path = path.split('/', 1) |
+ else: |
+ default_platform, reference_path = ('extensions', path) |
+ |
+ try: |
+ apps_public = self._public_apis.GetFromFileListing( |
+ '/'.join((svn_constants.PUBLIC_TEMPLATE_PATH, 'apps'))) |
+ extensions_public = self._public_apis.GetFromFileListing( |
+ '/'.join((svn_constants.PUBLIC_TEMPLATE_PATH, 'extensions'))) |
+ except FileNotFoundError: |
+ # Probably offline. |
+ logging.warning(traceback.format_exc()) |
+ return ReturnType(path, False) |
+ |
+ simple_reference_path = _SimplifyFileName(reference_path) |
+ apps_path = apps_public.get(simple_reference_path) |
+ extensions_path = extensions_public.get(simple_reference_path) |
+ |
+ if apps_path is None: |
+ if extensions_path is None: |
+ # No idea. Just return the original path. It'll probably 404. |
+ pass |
+ else: |
+ path = 'extensions/%s' % extensions_path |
else: |
- apps_path = '%s/apps/%s' % (self._channel, path_without_channel) |
- extensions_path = '%s/extensions/%s' % (self._channel, |
- path_without_channel) |
- |
- unix_path = UnixName(os.path.splitext(path_without_channel)[0]) |
- if unix_path in extensions_templates: |
- return extensions_path |
- if unix_path in apps_templates: |
- return apps_path |
- return extensions_path |
+ if extensions_path is None: |
+ path = 'apps/%s' % apps_path |
+ else: |
+ assert apps_path == extensions_path |
+ path = '%s/%s' % (default_platform, apps_path) |
+ |
+ return ReturnType(path, False) |