Commit 0e0644ab authored by kalman@chromium.org's avatar kalman@chromium.org

Docserver: Attach intro metadata to the template itself, not to an object which

contains the template.

BUG=320339
R=yoz@chromium.org, mkearney@chromium.org

Review URL: https://codereview.chromium.org/65643009

git-svn-id: svn://svn.chromium.org/chrome/trunk/src@235762 0039d316-1c4b-4281-b951-d872f2087c98
parent b41c3dc4
application: chrome-apps-doc application: chrome-apps-doc
version: 2-42-1 version: 2-42-2
runtime: python27 runtime: python27
api_version: 1 api_version: 1
threadsafe: false threadsafe: false
......
...@@ -79,7 +79,8 @@ class CachingFileSystem(FileSystem): ...@@ -79,7 +79,8 @@ class CachingFileSystem(FileSystem):
else: else:
file_version = dir_stat.child_versions.get(file_path) file_version = dir_stat.child_versions.get(file_path)
if file_version is None: if file_version is None:
raise FileNotFoundError('No stat found for %s in %s' % (path, dir_path)) raise FileNotFoundError('No stat found for %s in %s (found %s)' %
(path, dir_path, dir_stat.child_versions))
stat_info = StatInfo(file_version) stat_info = StatInfo(file_version)
return stat_info return stat_info
......
...@@ -2,4 +2,4 @@ cron: ...@@ -2,4 +2,4 @@ cron:
- description: Repopulates all cached data. - description: Repopulates all cached data.
url: /_cron url: /_cron
schedule: every 5 minutes schedule: every 5 minutes
target: 2-42-1 target: 2-42-2
...@@ -18,6 +18,20 @@ from third_party.handlebar import Handlebar ...@@ -18,6 +18,20 @@ from third_party.handlebar import Handlebar
_H1_REGEX = re.compile('<h1[^>.]*?>.*?</h1>', flags=re.DOTALL) _H1_REGEX = re.compile('<h1[^>.]*?>.*?</h1>', flags=re.DOTALL)
class _HandlebarWithContext(Handlebar):
'''Extends Handlebar with a get() method so that templates can not only
render intros with {{+intro}} but also access properties on them, like
{{intro.title}}, {{intro.toc}}, etc.
'''
def __init__(self, text, name, context):
Handlebar.__init__(self, text, name=name)
self._context = context
def get(self, key):
return self._context.get(key)
# TODO(kalman): rename this HTMLDataSource or other, then have separate intro # TODO(kalman): rename this HTMLDataSource or other, then have separate intro
# article data sources created as instances of it. # article data sources created as instances of it.
class _IntroParser(HTMLParser): class _IntroParser(HTMLParser):
...@@ -62,48 +76,49 @@ class _IntroParser(HTMLParser): ...@@ -62,48 +76,49 @@ class _IntroParser(HTMLParser):
elif self._recent_tag in ['h2', 'h3']: elif self._recent_tag in ['h2', 'h3']:
self._current_heading['title'] += data self._current_heading['title'] += data
class IntroDataSource(DataSource): class IntroDataSource(DataSource):
'''This class fetches the intros for a given API. From this intro, a table '''This class fetches the intros for a given API. From this intro, a table
of contents dictionary is created, which contains the headings in the intro. of contents dictionary is created, which contains the headings in the intro.
''' '''
def __init__(self, server_instance, _): def __init__(self, server_instance, request):
self._template_renderer = server_instance.template_renderer
self._request = request
self._cache = server_instance.compiled_fs_factory.Create( self._cache = server_instance.compiled_fs_factory.Create(
server_instance.host_file_system_provider.GetTrunk(), server_instance.host_file_system_provider.GetTrunk(),
self._MakeIntroDict, self._MakeIntro,
IntroDataSource) IntroDataSource)
self._ref_resolver = server_instance.ref_resolver_factory.Create() self._ref_resolver = server_instance.ref_resolver_factory.Create()
def _MakeIntroDict(self, intro_path, intro): def _MakeIntro(self, intro_path, intro):
# Guess the name of the API from the path to the intro. # Guess the name of the API from the path to the intro.
api_name = os.path.splitext(intro_path.split('/')[-1])[0] api_name = os.path.splitext(intro_path.split('/')[-1])[0]
intro_with_links = self._ref_resolver.ResolveAllLinks(intro, intro_with_links = self._ref_resolver.ResolveAllLinks(
namespace=api_name) intro, namespace=api_name)
# TODO(kalman): Do $ref replacement after rendering the template, not
# before, so that (a) $ref links can contain template annotations, and (b) # TODO(kalman): In order to pick up every header tag, and therefore make a
# we can use CompiledFileSystem.ForTemplates to create the templates and # complete TOC, the render context of the Handlebar needs to be passed
# save ourselves some effort. # through to here. Even if there were a mechanism to do this it would
apps_parser = _IntroParser() # break caching; we'd need to do the TOC parsing *after* rendering the full
apps_parser.feed(Handlebar(intro_with_links).render( # template, and that would probably be expensive.
{ 'is_apps': True }).text) intro_parser = _IntroParser()
extensions_parser = _IntroParser() intro_parser.feed(
extensions_parser.feed(Handlebar(intro_with_links).render( self._template_renderer.Render(Handlebar(intro_with_links),
{ 'is_apps': False }).text) self._request,
# TODO(cduvall): Use the normal template rendering system, so we can check # Avoid nasty surprises.
# errors. data_sources=('partials', 'strings'),
if extensions_parser.page_title != apps_parser.page_title: warn=False))
logging.error(
'Title differs for apps and extensions: Apps: %s, Extensions: %s.' %
(extensions_parser.page_title, apps_parser.page_title))
# The templates will render the heading themselves, so remove it from the # The templates will render the heading themselves, so remove it from the
# HTML content. # HTML content.
intro_with_links = re.sub(_H1_REGEX, '', intro_with_links, count=1) intro_with_links = re.sub(_H1_REGEX, '', intro_with_links, count=1)
return {
'intro': Handlebar(intro_with_links), context = {
'title': apps_parser.page_title, 'title': intro_parser.page_title,
'apps_toc': apps_parser.toc, 'toc': intro_parser.toc,
'extensions_toc': extensions_parser.toc,
} }
return _HandlebarWithContext(intro_with_links, intro_path, context)
def get(self, key): def get(self, key):
path = FormatKey(key) path = FormatKey(key)
......
...@@ -17,16 +17,16 @@ class IntroDataSourceTest(unittest.TestCase): ...@@ -17,16 +17,16 @@ class IntroDataSourceTest(unittest.TestCase):
def testIntro(self): def testIntro(self):
intro_data_source = IntroDataSource(self._server_instance, None) intro_data_source = IntroDataSource(self._server_instance, None)
data = intro_data_source.get('test') data = intro_data_source.get('test')
self.assertEqual('hi', data['title']) self.assertEqual('hi', data.get('title'))
# TODO(kalman): test links. # TODO(kalman): test links.
expected_toc = [{'subheadings': [{'link': '', 'title': u'inner'}], expected_toc = [{'subheadings': [{'link': '', 'title': u'inner'}],
'link': '', 'link': '',
'title': u'first'}, 'title': u'first'},
{'subheadings': [], 'link': '', 'title': u'second'}] {'subheadings': [], 'link': '', 'title': u'second'}]
self.assertEqual(expected_toc, data['apps_toc']) self.assertEqual(expected_toc, data.get('toc'))
self.assertEqual(expected_toc, data['extensions_toc'])
self.assertEqual('you<h2>first</h2><h3>inner</h3><h2>second</h2>', self.assertEqual('you<h2>first</h2><h3>inner</h3><h2>second</h2>',
data['intro'].render().text) data.Render().text)
if __name__ == '__main__': if __name__ == '__main__':
unittest.main() unittest.main()
...@@ -10,6 +10,7 @@ import traceback ...@@ -10,6 +10,7 @@ import traceback
from urlparse import urlsplit from urlparse import urlsplit
from data_source_registry import CreateDataSources from data_source_registry import CreateDataSources
from environment import IsPreviewServer
from file_system import FileNotFoundError from file_system import FileNotFoundError
from redirector import Redirector from redirector import Redirector
from servlet import Servlet, Response from servlet import Servlet, Response
...@@ -47,6 +48,8 @@ class RenderServlet(Servlet): ...@@ -47,6 +48,8 @@ class RenderServlet(Servlet):
try: try:
return self._GetSuccessResponse(path, server_instance) return self._GetSuccessResponse(path, server_instance)
except FileNotFoundError: except FileNotFoundError:
if IsPreviewServer():
logging.error(traceback.format_exc())
# Maybe it didn't find the file because its canonical location is # Maybe it didn't find the file because its canonical location is
# somewhere else; this is distinct from "redirects", which are typically # somewhere else; this is distinct from "redirects", which are typically
# explicit. This is implicit. # explicit. This is implicit.
......
...@@ -16,21 +16,37 @@ class TemplateRenderer(object): ...@@ -16,21 +16,37 @@ class TemplateRenderer(object):
def __init__(self, server_instance): def __init__(self, server_instance):
self._server_instance = server_instance self._server_instance = server_instance
def Render(self, template, request): def Render(self, template, request, data_sources=None, warn=True):
'''Renders |template| using |request|.
Specify |data_sources| to only include the DataSources with the given names
when rendering the template.
Specify |warn| whether to warn (e.g. logging.warning) if there are template
rendering errors. It may be useful to disable this when errors are expected,
for example when doing an initial page render to determine document title.
'''
assert isinstance(template, Handlebar), type(template) assert isinstance(template, Handlebar), type(template)
server_instance = self._server_instance render_context = self._CreateDataSources(request)
render_context = { if data_sources is not None:
'api_list': server_instance.api_list_data_source_factory.Create(), render_context = dict((name, d) for name, d in render_context.iteritems()
'apis': server_instance.api_data_source_factory.Create(request), if name in data_sources)
render_context.update({
'apps_samples_url': GITHUB_BASE, 'apps_samples_url': GITHUB_BASE,
'base_path': server_instance.base_path, 'base_path': self._server_instance.base_path,
'extensions_samples_url': EXTENSIONS_SAMPLES, 'extensions_samples_url': EXTENSIONS_SAMPLES,
'samples': server_instance.samples_data_source_factory.Create(request), 'static': self._server_instance.base_path + 'static',
'static': server_instance.base_path + 'static', })
} render_data = template.Render(render_context)
render_context.update(CreateDataSources(server_instance, request=request)) if warn and render_data.errors:
render_data = template.render(render_context)
if render_data.errors:
logging.error('Handlebar error(s) rendering %s:\n%s' % logging.error('Handlebar error(s) rendering %s:\n%s' %
(template._name, ' \n'.join(render_data.errors))) (template._name, ' \n'.join(render_data.errors)))
return render_data.text return render_data.text
def _CreateDataSources(self, request):
server_instance = self._server_instance
data_sources = CreateDataSources(server_instance, request=request)
data_sources.update({
'api_list': server_instance.api_list_data_source_factory.Create(),
'apis': server_instance.api_data_source_factory.Create(request),
'samples': server_instance.samples_data_source_factory.Create(request),
})
return data_sources
...@@ -467,4 +467,4 @@ For an example that implements keyboard navigation and ARIA properties, see ...@@ -467,4 +467,4 @@ For an example that implements keyboard navigation and ARIA properties, see
(compare it to (compare it to
<a href="http://src.chromium.org/viewvc/chrome/trunk/src/chrome/common/extensions/docs/examples/extensions/news/">examples/extensions/news</a>). <a href="http://src.chromium.org/viewvc/chrome/trunk/src/chrome/common/extensions/docs/examples/extensions/news/">examples/extensions/news</a>).
For more examples and for help in viewing the source code, For more examples and for help in viewing the source code,
see <a href="samples.html">Samples</a>. see <a href="samples.html">Samples</a>.
\ No newline at end of file
<h1>Manifest File Format</h1> <h1>Manifest File Format</h1>
<p> <p>
Every extension, installable web app, and theme has a Every extension, installable web app, and theme has a
<a href="http://www.json.org">JSON</a>-formatted manifest file, <a href="http://www.json.org">JSON</a>-formatted manifest file,
......
...@@ -4,9 +4,9 @@ ...@@ -4,9 +4,9 @@
<tr> <tr>
<td><a href="{{a.name}}.html">{{a.name}}</a></td> <td><a href="{{a.name}}.html">{{a.name}}</a></td>
<td>{{{a.description}}}</td> <td>{{{a.description}}}</td>
{{?a.is_stable}} {{?is_stable}}
<td>{{a.version}}</td> <td>{{a.version}}</td>
{{/a.is_stable}} {{/is_stable}}
</tr> </tr>
{{/apis}} {{/apis}}
</table> </table>
...@@ -13,21 +13,20 @@ ...@@ -13,21 +13,20 @@
{{?api.channelWarning.trunk +partials.warning_trunk/}} {{?api.channelWarning.trunk +partials.warning_trunk/}}
{{?api.channelWarning.dev +partials.warning_dev/}} {{?api.channelWarning.dev +partials.warning_dev/}}
{{?api.channelWarning.beta +partials.warning_beta/}} {{?api.channelWarning.beta +partials.warning_beta/}}
{{?intro.apps_toc}} {{?intro}}
{{+partials.table_of_contents toc_items:intro.apps_toc {{+partials.table_of_contents toc_items:intro.toc
has_toc:true
api:api api:api
samples_list:api.samples.apps samples_list:api.samples.apps
title:strings.apps_title/}} title:strings.apps_title/}}
{{:intro.apps_toc}} {{:intro}}
{{+partials.table_of_contents has_toc:false {{+partials.table_of_contents toc_items:false
api:api api:api
samples_list:api.samples.apps samples_list:api.samples.apps
title:strings.apps_title/}} title:strings.apps_title/}}
{{/intro.apps_toc}} {{/intro}}
{{+partials.intro_table api:api/}} {{+partials.intro_table api:api/}}
{{- This is unindented because it contains <pre> tags -}} {{- This is unindented because it contains <pre> tags -}}
{{?intro +intro.intro is_apps:true/}} {{?intro +intro platform:strings.app is_apps:true/}}
{{+partials.api_reference samples_list:api.samples.apps {{+partials.api_reference samples_list:api.samples.apps
title:strings.apps_title title:strings.apps_title
api:api/}} api:api/}}
......
...@@ -10,21 +10,10 @@ ...@@ -10,21 +10,10 @@
{{+partials.sidenav items:sidenavs.apps/}} {{+partials.sidenav items:sidenavs.apps/}}
<div id="gc-pagecontent"> <div id="gc-pagecontent">
<h1 class="page_title">{{article.title}}</h1> <h1 class="page_title">{{article.title}}</h1>
{{?article.apps_toc}} {{+partials.table_of_contents toc_items:article.toc
{{+partials.table_of_contents toc_items:article.apps_toc
has_toc:true
title:strings.apps_title/}} title:strings.apps_title/}}
{{/article.apps_toc}}
{{- This may contain <pre> tags so it is not indented -}} {{- This may contain <pre> tags so it is not indented -}}
{{+article.intro {{+article platform:strings.app is_apps:true/}}
apis:apis
beta_apis:beta_apis
dev_apis:dev_apis
manifest_source:manifest_source
permissions:permissions
platform:strings.app
stable_apis:stable_apis
is_apps:true/}}
</div> </div>
</div> </div>
</body> </body>
......
...@@ -13,21 +13,20 @@ ...@@ -13,21 +13,20 @@
{{?api.channelWarning.trunk +partials.warning_trunk/}} {{?api.channelWarning.trunk +partials.warning_trunk/}}
{{?api.channelWarning.dev +partials.warning_dev/}} {{?api.channelWarning.dev +partials.warning_dev/}}
{{?api.channelWarning.beta +partials.warning_beta/}} {{?api.channelWarning.beta +partials.warning_beta/}}
{{?intro.extensions_toc}} {{?intro}}
{{+partials.table_of_contents toc_items:intro.extensions_toc {{+partials.table_of_contents toc_items:intro.toc
has_toc:true
api:api api:api
samples_list:api.samples.extensions samples_list:api.samples.extensions
title:strings.extensions_title/}} title:strings.extensions_title/}}
{{:intro.extensions_toc}} {{:intro}}
{{+partials.table_of_contents has_toc:false {{+partials.table_of_contents toc_items:false
api:api api:api
samples_list:api.samples.extensions samples_list:api.samples.extensions
title:strings.extensions_title/}} title:strings.extensions_title/}}
{{/intro.extensions_toc}} {{/intro}}
{{+partials.intro_table api:api/}} {{+partials.intro_table api:api/}}
{{- This is unindented because it contains <pre> tags -}} {{- This is unindented because it contains <pre> tags -}}
{{?intro +intro.intro is_apps:false/}} {{?intro +intro platform:strings.app is_apps:false/}}
{{+partials.api_reference samples_list:api.samples.extensions {{+partials.api_reference samples_list:api.samples.extensions
title:strings.extensions_title title:strings.extensions_title
api:api/}} api:api/}}
......
...@@ -10,21 +10,10 @@ ...@@ -10,21 +10,10 @@
{{+partials.sidenav items:sidenavs.extensions/}} {{+partials.sidenav items:sidenavs.extensions/}}
<div id="gc-pagecontent"> <div id="gc-pagecontent">
<h1 class="page_title">{{article.title}}</h1> <h1 class="page_title">{{article.title}}</h1>
{{?article.extensions_toc}} {{+partials.table_of_contents toc_items:article.toc
{{+partials.table_of_contents toc_items:article.extensions_toc
has_toc:true
title:strings.extensions_title/}} title:strings.extensions_title/}}
{{/article.extensions_toc}}
{{- This may contain <pre> tags so it is not indented -}} {{- This may contain <pre> tags so it is not indented -}}
{{+article.intro {{+article platform:strings.extension is_apps:false /}}
apis:apis
beta_apis:beta_apis
dev_apis:dev_apis
manifest_source:manifest_source
permissions:permissions
platform:strings.extension
stable_apis:stable_apis
is_apps:false/}}
</div> </div>
</div> </div>
</body> </body>
......
<div id="toc"> <div id="toc">
<ol> <ol>
{{?has_toc}} {{?toc_items}}
{{#i:toc_items}} {{#i:toc_items}}
<li> <li>
<a href="#{{i.link}}">{{{i.title}}}</a> <a href="#{{i.link}}">{{{i.title}}}</a>
...@@ -11,7 +11,7 @@ ...@@ -11,7 +11,7 @@
{{/}} {{/}}
</li> </li>
{{/toc_items}} {{/toc_items}}
{{/has_toc}} {{/toc_items}}
{{?api}} {{?api}}
<div class="api-reference"> <div class="api-reference">
<li> <li>
......
{{+partials.standard_apps_article {{+partials.standard_apps_article
article:intros.api_index article:(intros.api_index
is_apps:true stable_apis:api_list.apps.chrome.stable
stable_apis:api_list.apps.chrome.stable beta_apis:api_list.apps.chrome.beta
beta_apis:api_list.apps.chrome.beta dev_apis:api_list.apps.chrome.dev) /}}
dev_apis:api_list.apps.chrome.dev/}}
{{+partials.standard_apps_article article:intros.declare_permissions {{+partials.standard_apps_article
permissions:permissions.declare_apps/}} article:(intros.declare_permissions
permissions:permissions.declare_apps)/}}
{{+partials.standard_apps_article {{+partials.standard_apps_article
article:intros.experimental article:(intros.experimental apis:api_list.apps.experimental) /}}
apis:api_list.apps.experimental/}}
{{+partials.standard_apps_article article:intros.manifest {{+partials.standard_apps_article
manifest_source:manifest_source.apps/}} article:(intros.manifest manifest_source:manifest_source.apps) /}}
{{+partials.standard_extensions_article {{+partials.standard_extensions_article
article:intros.api_index article:(intros.api_index
stable_apis:api_list.extensions.chrome.stable stable_apis:api_list.extensions.chrome.stable
beta_apis:api_list.extensions.chrome.beta beta_apis:api_list.extensions.chrome.beta
dev_apis:api_list.extensions.chrome.dev/}} dev_apis:api_list.extensions.chrome.dev) /}}
{{+partials.standard_extensions_article article:intros.declare_permissions {{+partials.standard_extensions_article
permissions:permissions.declare_extensions/}} article:(intros.declare_permissions
permissions:permissions.declare_extensions)/}}
{{+partials.standard_extensions_article {{+partials.standard_extensions_article
article:intros.experimental article:(intros.experimental apis:api_list.extensions.experimental)/}}
apis:api_list.extensions.experimental/}}
{{+partials.standard_extensions_article {{+partials.standard_extensions_article
article:intros.manifest article:(intros.manifest manifest_source:manifest_source.extensions) /}}
manifest_source:manifest_source.extensions/}}
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment