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
3# found in the LICENSE file.
4
5import copy
6import logging
7
8from compiled_file_system import SingleFile, Unicode
9from data_source import DataSource
10from extensions_paths import JSON_TEMPLATES
11from future import Gettable, Future
12from third_party.json_schema_compiler.json_parse import Parse
13
14
15def _AddLevels(items, level):
16  '''Add a 'level' key to each item in |items|. 'level' corresponds to how deep
17  in |items| an item is. |level| sets the starting depth.
18  '''
19  for item in items:
20    item['level'] = level
21    if 'items' in item:
22      _AddLevels(item['items'], level + 1)
23
24
25def _AddSelected(items, path):
26  '''Add 'selected' and 'child_selected' properties to |items| so that the
27  sidenav can be expanded to show which menu item has been selected. Returns
28  True if an item was marked 'selected'.
29  '''
30  for item in items:
31    if item.get('href', '') == path:
32      item['selected'] = True
33      return True
34    if 'items' in item:
35      if _AddSelected(item['items'], path):
36        item['child_selected'] = True
37        return True
38
39  return False
40
41
42class SidenavDataSource(DataSource):
43  '''Provides templates with access to JSON files used to create the side
44  navigation bar.
45  '''
46  def __init__(self, server_instance, request):
47    self._cache = server_instance.compiled_fs_factory.Create(
48        server_instance.host_file_system_provider.GetTrunk(),
49        self._CreateSidenavDict,
50        SidenavDataSource)
51    self._server_instance = server_instance
52    self._request = request
53
54  @SingleFile
55  @Unicode
56  def _CreateSidenavDict(self, _, content):
57    items = Parse(content)
58    # Start at level 2, the top <ul> element is level 1.
59    _AddLevels(items, level=2)
60    self._QualifyHrefs(items)
61    return items
62
63  def _QualifyHrefs(self, items):
64    '''Force hrefs in |items| to either be absolute (http://...) or qualified
65    (beginning with /, in which case it will be moved relative to |base_path|).
66    Relative hrefs emit a warning and should be updated.
67    '''
68    for item in items:
69      if 'items' in item:
70        self._QualifyHrefs(item['items'])
71
72      href = item.get('href')
73      if href is not None and not href.startswith(('http://', 'https://')):
74        if not href.startswith('/'):
75          logging.warn('Paths in sidenav must be qualified. %s is not.' % href)
76        else:
77          href = href.lstrip('/')
78        item['href'] = self._server_instance.base_path + href
79
80  def Cron(self):
81    futures = [self._cache.GetFromFile('%s/%s_sidenav.json' %
82                                       (JSON_TEMPLATES, platform))
83               for platform in ('apps', 'extensions')]
84    return Future(delegate=Gettable(lambda: [f.Get() for f in futures]))
85
86  def get(self, key):
87    sidenav = copy.deepcopy(self._cache.GetFromFile(
88        '%s/%s_sidenav.json' % (JSON_TEMPLATES, key)).Get())
89    _AddSelected(sidenav, self._server_instance.base_path + self._request.path)
90    return sidenav
91