Chromium Code Reviews| OLD | NEW |
|---|---|
| 1 # Copyright (c) 2012 The Chromium Authors. All rights reserved. | 1 # Copyright (c) 2012 The Chromium Authors. All rights reserved. |
| 2 # Use of this source code is governed by a BSD-style license that can be | 2 # Use of this source code is governed by a BSD-style license that can be |
| 3 # found in the LICENSE file. | 3 # found in the LICENSE file. |
| 4 | 4 |
| 5 from HTMLParser import HTMLParser | 5 from HTMLParser import HTMLParser |
| 6 import logging | 6 import logging |
| 7 import os | 7 import os |
| 8 import re | 8 import re |
| 9 | 9 |
| 10 from extensions_paths import INTROS_TEMPLATES, ARTICLES_TEMPLATES | 10 from extensions_paths import INTROS_TEMPLATES, ARTICLES_TEMPLATES |
| 11 from data_source import DataSource | 11 from data_source import DataSource |
| 12 from docs_server_utils import FormatKey | 12 from docs_server_utils import FormatKey |
| 13 from file_system import FileNotFoundError | 13 from file_system import FileNotFoundError |
| 14 from future import Future | 14 from future import Future |
| 15 from third_party.handlebar import Handlebar | 15 from third_party.handlebar import Handlebar |
| 16 | 16 |
| 17 | 17 |
| 18 _H1_REGEX = re.compile('<h1[^>.]*?>.*?</h1>', flags=re.DOTALL) | 18 _H1_REGEX = re.compile('<h1[^>.]*?>.*?</h1>', flags=re.DOTALL) |
| 19 | 19 |
| 20 | 20 |
| 21 class _HandlebarWithContext(Handlebar): | |
| 22 '''Extends Handlebar with a get() method so that templates can not only | |
| 23 render intros with {{+intro}} but also access properties on them, like | |
| 24 {{intro.title}}, {{intro.toc}}, etc. | |
| 25 ''' | |
| 26 | |
| 27 def __init__(self, text, name, context): | |
| 28 Handlebar.__init__(self, text, name=name) | |
| 29 self._context = context | |
| 30 | |
| 31 def get(self, key): | |
| 32 return self._context.get(key) | |
| 33 | |
| 34 | |
| 21 # TODO(kalman): rename this HTMLDataSource or other, then have separate intro | 35 # TODO(kalman): rename this HTMLDataSource or other, then have separate intro |
| 22 # article data sources created as instances of it. | 36 # article data sources created as instances of it. |
| 23 class _IntroParser(HTMLParser): | 37 class _IntroParser(HTMLParser): |
| 24 ''' An HTML parser which will parse table of contents and page title info out | 38 ''' An HTML parser which will parse table of contents and page title info out |
| 25 of an intro. | 39 of an intro. |
| 26 ''' | 40 ''' |
| 27 def __init__(self): | 41 def __init__(self): |
| 28 HTMLParser.__init__(self) | 42 HTMLParser.__init__(self) |
| 29 self.toc = [] | 43 self.toc = [] |
| 30 self.page_title = None | 44 self.page_title = None |
| (...skipping 24 matching lines...) Expand all Loading... | |
| 55 if self._recent_tag is None: | 69 if self._recent_tag is None: |
| 56 return | 70 return |
| 57 if self._recent_tag == 'h1': | 71 if self._recent_tag == 'h1': |
| 58 if self.page_title is None: | 72 if self.page_title is None: |
| 59 self.page_title = data | 73 self.page_title = data |
| 60 else: | 74 else: |
| 61 self.page_title += data | 75 self.page_title += data |
| 62 elif self._recent_tag in ['h2', 'h3']: | 76 elif self._recent_tag in ['h2', 'h3']: |
| 63 self._current_heading['title'] += data | 77 self._current_heading['title'] += data |
| 64 | 78 |
| 79 | |
| 65 class IntroDataSource(DataSource): | 80 class IntroDataSource(DataSource): |
| 66 '''This class fetches the intros for a given API. From this intro, a table | 81 '''This class fetches the intros for a given API. From this intro, a table |
| 67 of contents dictionary is created, which contains the headings in the intro. | 82 of contents dictionary is created, which contains the headings in the intro. |
| 68 ''' | 83 ''' |
| 69 | 84 |
| 70 def __init__(self, server_instance, _): | 85 def __init__(self, server_instance, request): |
| 86 self._template_renderer = server_instance.template_renderer | |
| 87 self._request = request | |
| 71 self._cache = server_instance.compiled_fs_factory.Create( | 88 self._cache = server_instance.compiled_fs_factory.Create( |
| 72 server_instance.host_file_system_provider.GetTrunk(), | 89 server_instance.host_file_system_provider.GetTrunk(), |
| 73 self._MakeIntroDict, | 90 self._MakeIntro, |
| 74 IntroDataSource) | 91 IntroDataSource) |
| 75 self._ref_resolver = server_instance.ref_resolver_factory.Create() | 92 self._ref_resolver = server_instance.ref_resolver_factory.Create() |
| 76 | 93 |
| 77 def _MakeIntroDict(self, intro_path, intro): | 94 def _MakeIntro(self, intro_path, intro): |
| 78 # Guess the name of the API from the path to the intro. | 95 # Guess the name of the API from the path to the intro. |
| 79 api_name = os.path.splitext(intro_path.split('/')[-1])[0] | 96 api_name = os.path.splitext(intro_path.split('/')[-1])[0] |
| 80 intro_with_links = self._ref_resolver.ResolveAllLinks(intro, | 97 intro_with_links = self._ref_resolver.ResolveAllLinks( |
| 81 namespace=api_name) | 98 intro, namespace=api_name) |
| 82 # TODO(kalman): Do $ref replacement after rendering the template, not | 99 |
| 83 # before, so that (a) $ref links can contain template annotations, and (b) | 100 # TODO(kalman): In order to pick up every header tag, and therefore make a |
| 84 # we can use CompiledFileSystem.ForTemplates to create the templates and | 101 # complete TOC, the render context of the Handlebar needs to be passed |
| 85 # save ourselves some effort. | 102 # through to here. Even if there were a mechanism to do this it would |
| 86 apps_parser = _IntroParser() | 103 # break caching; we'd need to do the TOC parsing *after* rendering the full |
| 87 apps_parser.feed(Handlebar(intro_with_links).render( | 104 # template, and that would probably be expensive. |
| 88 { 'is_apps': True }).text) | 105 intro_parser = _IntroParser() |
| 89 extensions_parser = _IntroParser() | 106 intro_parser.feed( |
| 90 extensions_parser.feed(Handlebar(intro_with_links).render( | 107 self._template_renderer.Render(Handlebar(intro_with_links), |
| 91 { 'is_apps': False }).text) | 108 self._request, |
| 92 # TODO(cduvall): Use the normal template rendering system, so we can check | 109 # Avoid nasty surprises. |
| 93 # errors. | 110 data_sources=('partials', 'strings'), |
| 94 if extensions_parser.page_title != apps_parser.page_title: | 111 warn=False)) |
| 95 logging.error( | 112 |
| 96 'Title differs for apps and extensions: Apps: %s, Extensions: %s.' % | |
| 97 (extensions_parser.page_title, apps_parser.page_title)) | |
| 98 # The templates will render the heading themselves, so remove it from the | 113 # The templates will render the heading themselves, so remove it from the |
| 99 # HTML content. | 114 # HTML content. |
| 100 intro_with_links = re.sub(_H1_REGEX, '', intro_with_links, count=1) | 115 intro_with_links = re.sub(_H1_REGEX, '', intro_with_links, count=1) |
| 101 return { | 116 |
| 102 'intro': Handlebar(intro_with_links), | 117 context = { |
| 103 'title': apps_parser.page_title, | 118 'title': intro_parser.page_title, |
| 104 'apps_toc': apps_parser.toc, | 119 'toc': intro_parser.toc, |
|
not at google - send to devlin
2013/11/16 21:54:41
I needed to lose the extension/app differentiation
| |
| 105 'extensions_toc': extensions_parser.toc, | |
| 106 } | 120 } |
| 121 return _HandlebarWithContext(intro_with_links, intro_path, context) | |
| 107 | 122 |
| 108 def get(self, key): | 123 def get(self, key): |
| 109 path = FormatKey(key) | 124 path = FormatKey(key) |
| 110 def get_from_base_path(base_path): | 125 def get_from_base_path(base_path): |
| 111 return self._cache.GetFromFile('%s/%s' % (base_path, path)).Get() | 126 return self._cache.GetFromFile('%s/%s' % (base_path, path)).Get() |
| 112 base_paths = (INTROS_TEMPLATES, ARTICLES_TEMPLATES) | 127 base_paths = (INTROS_TEMPLATES, ARTICLES_TEMPLATES) |
| 113 for base_path in base_paths: | 128 for base_path in base_paths: |
| 114 try: | 129 try: |
| 115 return get_from_base_path(base_path) | 130 return get_from_base_path(base_path) |
| 116 except FileNotFoundError: | 131 except FileNotFoundError: |
| 117 continue | 132 continue |
| 118 # Not found. Do the first operation again so that we get a stack trace - we | 133 # Not found. Do the first operation again so that we get a stack trace - we |
| 119 # know that it'll fail. | 134 # know that it'll fail. |
| 120 get_from_base_path(base_paths[0]) | 135 get_from_base_path(base_paths[0]) |
| 121 raise AssertionError() | 136 raise AssertionError() |
| 122 | 137 |
| 123 def Cron(self): | 138 def Cron(self): |
| 124 # TODO(kalman): Walk through the intros and articles directory. | 139 # TODO(kalman): Walk through the intros and articles directory. |
| 125 return Future(value=()) | 140 return Future(value=()) |
| OLD | NEW |