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 5 import copy 6 import logging 7 import posixpath 8 9 from compiled_file_system import Cache, SingleFile, Unicode 10 from data_source import DataSource 11 from extensions_paths import JSON_TEMPLATES 12 from future import Future 13 from third_party.json_schema_compiler.json_parse import Parse 14 15 16 def _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 26 def _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 56 class 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