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, |
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 |