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
7import posixpath
8
9from compiled_file_system import Cache, SingleFile, Unicode
10from data_source import DataSource
11from extensions_paths import JSON_TEMPLATES
12from future import Future
13from third_party.json_schema_compiler.json_parse import Parse
14
15
16def _AddLevels(items, level):
17  '''Add a 'level' key to each item in |items|. 'level' corresponds to how deep
18  in |items| an item is. |level| sets the starting depth.
19  '''
20  for item in items:
21    item['level'] = level
22    if 'items' in item:
23      _AddLevels(item['items'], level + 1)
24
25
26def _AddAnnotations(items, path, parent=None):
27  '''Add 'selected', 'child_selected' and 'related' properties to
28  |items| so that the sidenav can be expanded to show which menu item has
29  been selected and the related pages section can be drawn. 'related'
30  is added to all items with the same parent as the selected item.
31  If more than one item exactly matches the path, the deepest one is considered
32  'selected'. A 'parent' property is added to the selected path.
33
34  Returns True if an item was marked 'selected'.
35  '''
36  for item in items:
37    if 'items' in item:
38      if _AddAnnotations(item['items'], path, item):
39        item['child_selected'] = True
40        return True
41
42    if item.get('href', '') == path:
43      item['selected'] = True
44      if parent:
45        item['parent'] = { 'title': parent.get('title', None),
46                          'href': parent.get('href', None) }
47
48      for sibling in items:
49        sibling['related'] = True
50
51      return True
52
53  return False
54
55
56class SidenavDataSource(DataSource):
57  '''Provides templates with access to JSON files used to create the side
58  navigation bar.
59  '''
60  def __init__(self, server_instance, request):
61    self._cache = server_instance.compiled_fs_factory.Create(
62        server_instance.host_file_system_provider.GetMaster(),
63        self._CreateSidenavDict,
64        SidenavDataSource)
65    self._server_instance = server_instance
66    self._request = request
67
68  @Cache
69  @SingleFile
70  @Unicode
71  def _CreateSidenavDict(self, _, content):
72    items = Parse(content)
73    # Start at level 2, the top <ul> element is level 1.
74    _AddLevels(items, level=2)
75    self._QualifyHrefs(items)
76    return items
77
78  def _QualifyHrefs(self, items):
79    '''Force hrefs in |items| to either be absolute (http://...) or qualified
80    (beginning with /, in which case it will be moved relative to |base_path|).
81    Relative hrefs emit a warning and should be updated.
82    '''
83    for item in items:
84      if 'items' in item:
85        self._QualifyHrefs(item['items'])
86
87      href = item.get('href')
88      if href is not None and not href.startswith(('http://', 'https://')):
89        if not href.startswith('/'):
90          logging.warn('Paths in sidenav must be qualified. %s is not.' % href)
91        else:
92          href = href.lstrip('/')
93        item['href'] = self._server_instance.base_path + href
94
95  def Refresh(self, path=None):
96    return self._cache.GetFromFile(
97        posixpath.join(JSON_TEMPLATES, 'chrome_sidenav.json'))
98
99  def get(self, key):
100    # TODO(mangini/kalman): Use |key| to decide which sidenav to use,
101    # which will require a more complex Refresh method.
102    sidenav = self._cache.GetFromFile(
103        posixpath.join(JSON_TEMPLATES, 'chrome_sidenav.json')).Get()
104    sidenav = copy.deepcopy(sidenav)
105    _AddAnnotations(sidenav,
106                    self._server_instance.base_path + self._request.path)
107    return sidenav
108