| OLD | NEW |
| (Empty) |
| 1 #!/usr/bin/env python | |
| 2 | |
| 3 # Copyright (c) 2012 The Chromium Authors. All rights reserved. | |
| 4 # Use of this source code is governed by a BSD-style license that can be | |
| 5 # found in the LICENSE file. | |
| 6 """Server for viewing the compiled C++ code from tools/json_schema_compiler. | |
| 7 """ | |
| 8 | |
| 9 import cc_generator | |
| 10 import code | |
| 11 import cpp_type_generator | |
| 12 import cpp_util | |
| 13 import h_generator | |
| 14 import idl_schema | |
| 15 import json_schema | |
| 16 import model | |
| 17 import optparse | |
| 18 import os | |
| 19 import sys | |
| 20 import urlparse | |
| 21 from highlighters import ( | |
| 22 pygments_highlighter, none_highlighter, hilite_me_highlighter) | |
| 23 from BaseHTTPServer import BaseHTTPRequestHandler, HTTPServer | |
| 24 | |
| 25 class CompilerHandler(BaseHTTPRequestHandler): | |
| 26 """A HTTPRequestHandler that outputs the result of tools/json_schema_compiler. | |
| 27 """ | |
| 28 def do_GET(self): | |
| 29 parsed_url = urlparse.urlparse(self.path) | |
| 30 request_path = self._GetRequestPath(parsed_url) | |
| 31 | |
| 32 chromium_favicon = 'http://codereview.chromium.org/static/favicon.ico' | |
| 33 | |
| 34 head = code.Code() | |
| 35 head.Append('<link rel="icon" href="%s">' % chromium_favicon) | |
| 36 head.Append('<link rel="shortcut icon" href="%s">' % chromium_favicon) | |
| 37 | |
| 38 body = code.Code() | |
| 39 | |
| 40 try: | |
| 41 if os.path.isdir(request_path): | |
| 42 self._ShowPanels(parsed_url, head, body) | |
| 43 else: | |
| 44 self._ShowCompiledFile(parsed_url, head, body) | |
| 45 finally: | |
| 46 self.wfile.write('<html><head>') | |
| 47 self.wfile.write(head.Render()) | |
| 48 self.wfile.write('</head><body>') | |
| 49 self.wfile.write(body.Render()) | |
| 50 self.wfile.write('</body></html>') | |
| 51 | |
| 52 def _GetRequestPath(self, parsed_url, strip_nav=False): | |
| 53 """Get the relative path from the current directory to the requested file. | |
| 54 """ | |
| 55 path = parsed_url.path | |
| 56 if strip_nav: | |
| 57 path = parsed_url.path.replace('/nav', '') | |
| 58 return os.path.normpath(os.curdir + path) | |
| 59 | |
| 60 def _ShowPanels(self, parsed_url, head, body): | |
| 61 """Show the previewer frame structure. | |
| 62 | |
| 63 Code panes are populated via XHR after links in the nav pane are clicked. | |
| 64 """ | |
| 65 (head.Append('<style>') | |
| 66 .Append('body {') | |
| 67 .Append(' margin: 0;') | |
| 68 .Append('}') | |
| 69 .Append('.pane {') | |
| 70 .Append(' height: 100%;') | |
| 71 .Append(' overflow-x: auto;') | |
| 72 .Append(' overflow-y: scroll;') | |
| 73 .Append(' display: inline-block;') | |
| 74 .Append('}') | |
| 75 .Append('#nav_pane {') | |
| 76 .Append(' width: 20%;') | |
| 77 .Append('}') | |
| 78 .Append('#nav_pane ul {') | |
| 79 .Append(' list-style-type: none;') | |
| 80 .Append(' padding: 0 0 0 1em;') | |
| 81 .Append('}') | |
| 82 .Append('#cc_pane {') | |
| 83 .Append(' width: 40%;') | |
| 84 .Append('}') | |
| 85 .Append('#h_pane {') | |
| 86 .Append(' width: 40%;') | |
| 87 .Append('}') | |
| 88 .Append('</style>') | |
| 89 ) | |
| 90 | |
| 91 body.Append( | |
| 92 '<div class="pane" id="nav_pane">%s</div>' | |
| 93 '<div class="pane" id="h_pane"></div>' | |
| 94 '<div class="pane" id="cc_pane"></div>' % | |
| 95 self._RenderNavPane(parsed_url.path[1:]) | |
| 96 ) | |
| 97 | |
| 98 # The Javascript that interacts with the nav pane and panes to show the | |
| 99 # compiled files as the URL or highlighting options change. | |
| 100 body.Append('''<script type="text/javascript"> | |
| 101 // Calls a function for each highlighter style <select> element. | |
| 102 function forEachHighlighterStyle(callback) { | |
| 103 var highlighterStyles = | |
| 104 document.getElementsByClassName('highlighter_styles'); | |
| 105 for (var i = 0; i < highlighterStyles.length; ++i) | |
| 106 callback(highlighterStyles[i]); | |
| 107 } | |
| 108 | |
| 109 // Called when anything changes, such as the highlighter or hashtag. | |
| 110 function updateEverything() { | |
| 111 var highlighters = document.getElementById('highlighters'); | |
| 112 var highlighterName = highlighters.value; | |
| 113 | |
| 114 // Cache in localStorage for when the page loads next. | |
| 115 localStorage.highlightersValue = highlighterName; | |
| 116 | |
| 117 // Show/hide the highlighter styles. | |
| 118 var highlighterStyleName = ''; | |
| 119 forEachHighlighterStyle(function(highlighterStyle) { | |
| 120 if (highlighterStyle.id === highlighterName + '_styles') { | |
| 121 highlighterStyle.removeAttribute('style') | |
| 122 highlighterStyleName = highlighterStyle.value; | |
| 123 } else { | |
| 124 highlighterStyle.setAttribute('style', 'display:none') | |
| 125 } | |
| 126 | |
| 127 // Cache in localStorage for when the page next loads. | |
| 128 localStorage[highlighterStyle.id + 'Value'] = highlighterStyle.value; | |
| 129 }); | |
| 130 | |
| 131 // Populate the code panes. | |
| 132 function populateViaXHR(elementId, requestPath) { | |
| 133 var xhr = new XMLHttpRequest(); | |
| 134 xhr.onreadystatechange = function() { | |
| 135 if (xhr.readyState != 4) | |
| 136 return; | |
| 137 if (xhr.status != 200) { | |
| 138 alert('XHR error to ' + requestPath); | |
| 139 return; | |
| 140 } | |
| 141 document.getElementById(elementId).innerHTML = xhr.responseText; | |
| 142 }; | |
| 143 xhr.open('GET', requestPath, true); | |
| 144 xhr.send(); | |
| 145 } | |
| 146 | |
| 147 var targetName = window.location.hash; | |
| 148 targetName = targetName.substring('#'.length); | |
| 149 targetName = targetName.split('.', 1)[0] | |
| 150 | |
| 151 if (targetName !== '') { | |
| 152 var basePath = window.location.pathname; | |
| 153 var query = 'highlighter=' + highlighterName + '&' + | |
| 154 'style=' + highlighterStyleName; | |
| 155 populateViaXHR('h_pane', basePath + '/' + targetName + '.h?' + query); | |
| 156 populateViaXHR('cc_pane', basePath + '/' + targetName + '.cc?' + query); | |
| 157 } | |
| 158 } | |
| 159 | |
| 160 // Initial load: set the values of highlighter and highlighterStyles from | |
| 161 // localStorage. | |
| 162 (function() { | |
| 163 var cachedValue = localStorage.highlightersValue; | |
| 164 if (cachedValue) | |
| 165 document.getElementById('highlighters').value = cachedValue; | |
| 166 | |
| 167 forEachHighlighterStyle(function(highlighterStyle) { | |
| 168 var cachedValue = localStorage[highlighterStyle.id + 'Value']; | |
| 169 if (cachedValue) | |
| 170 highlighterStyle.value = cachedValue; | |
| 171 }); | |
| 172 })(); | |
| 173 | |
| 174 window.addEventListener('hashchange', updateEverything, false); | |
| 175 updateEverything(); | |
| 176 </script>''') | |
| 177 | |
| 178 def _LoadModel(self, basedir, name): | |
| 179 """Loads and returns the model for the |name| API from either its JSON or | |
| 180 IDL file, e.g. | |
| 181 name=contextMenus will be loaded from |basedir|/context_menus.json, | |
| 182 name=alarms will be loaded from |basedir|/alarms.idl. | |
| 183 """ | |
| 184 loaders = { | |
| 185 'json': json_schema.Load, | |
| 186 'idl': idl_schema.Load | |
| 187 } | |
| 188 # APIs are referred to like "webRequest" but that's in a file | |
| 189 # "web_request.json" so we need to unixify the name. | |
| 190 unix_name = model.UnixName(name) | |
| 191 for loader_ext, loader_fn in loaders.items(): | |
| 192 file_path = '%s/%s.%s' % (basedir, unix_name, loader_ext) | |
| 193 if os.path.exists(file_path): | |
| 194 # For historical reasons these files contain a singleton list with the | |
| 195 # model, so just return that single object. | |
| 196 return (loader_fn(file_path)[0], file_path) | |
| 197 raise ValueError('File for model "%s" not found' % name) | |
| 198 | |
| 199 def _ShowCompiledFile(self, parsed_url, head, body): | |
| 200 """Show the compiled version of a json or idl file given the path to the | |
| 201 compiled file. | |
| 202 """ | |
| 203 api_model = model.Model() | |
| 204 | |
| 205 request_path = self._GetRequestPath(parsed_url) | |
| 206 (file_root, file_ext) = os.path.splitext(request_path) | |
| 207 (filedir, filename) = os.path.split(file_root) | |
| 208 | |
| 209 try: | |
| 210 # Get main file. | |
| 211 (api_def, file_path) = self._LoadModel(filedir, filename) | |
| 212 namespace = api_model.AddNamespace(api_def, file_path) | |
| 213 type_generator = cpp_type_generator.CppTypeGenerator( | |
| 214 'previewserver::api', namespace, namespace.unix_name) | |
| 215 | |
| 216 # Get the model's dependencies. | |
| 217 for dependency in api_def.get('dependencies', []): | |
| 218 # Dependencies can contain : in which case they don't refer to APIs, | |
| 219 # rather, permissions or manifest keys. | |
| 220 if ':' in dependency: | |
| 221 continue | |
| 222 (api_def, file_path) = self._LoadModel(filedir, dependency) | |
| 223 referenced_namespace = api_model.AddNamespace(api_def, file_path) | |
| 224 if referenced_namespace: | |
| 225 type_generator.AddNamespace(referenced_namespace, | |
| 226 cpp_util.Classname(referenced_namespace.name).lower()) | |
| 227 | |
| 228 # Generate code | |
| 229 if file_ext == '.h': | |
| 230 cpp_code = (h_generator.HGenerator(namespace, type_generator) | |
| 231 .Generate().Render()) | |
| 232 elif file_ext == '.cc': | |
| 233 cpp_code = (cc_generator.CCGenerator(namespace, type_generator) | |
| 234 .Generate().Render()) | |
| 235 else: | |
| 236 self.send_error(404, "File not found: %s" % request_path) | |
| 237 return | |
| 238 | |
| 239 # Do highlighting on the generated code | |
| 240 (highlighter_param, style_param) = self._GetHighlighterParams(parsed_url) | |
| 241 head.Append('<style>' + | |
| 242 self.server.highlighters[highlighter_param].GetCSS(style_param) + | |
| 243 '</style>') | |
| 244 body.Append(self.server.highlighters[highlighter_param] | |
| 245 .GetCodeElement(cpp_code, style_param)) | |
| 246 except IOError: | |
| 247 self.send_error(404, "File not found: %s" % request_path) | |
| 248 return | |
| 249 except (TypeError, KeyError, AttributeError, | |
| 250 AssertionError, NotImplementedError) as error: | |
| 251 body.Append('<pre>') | |
| 252 body.Append('compiler error: %s' % error) | |
| 253 body.Append('Check server log for more details') | |
| 254 body.Append('</pre>') | |
| 255 raise | |
| 256 | |
| 257 def _GetHighlighterParams(self, parsed_url): | |
| 258 """Get the highlighting parameters from a parsed url. | |
| 259 """ | |
| 260 query_dict = urlparse.parse_qs(parsed_url.query) | |
| 261 return (query_dict.get('highlighter', ['pygments'])[0], | |
| 262 query_dict.get('style', ['colorful'])[0]) | |
| 263 | |
| 264 def _RenderNavPane(self, path): | |
| 265 """Renders an HTML nav pane. | |
| 266 | |
| 267 This consists of a select element to set highlight style, and a list of all | |
| 268 files at |path| with the appropriate onclick handlers to open either | |
| 269 subdirectories or JSON files. | |
| 270 """ | |
| 271 html = code.Code() | |
| 272 | |
| 273 # Highlighter chooser. | |
| 274 html.Append('<select id="highlighters" onChange="updateEverything()">') | |
| 275 for name, highlighter in self.server.highlighters.items(): | |
| 276 html.Append('<option value="%s">%s</option>' % | |
| 277 (name, highlighter.DisplayName())) | |
| 278 html.Append('</select>') | |
| 279 | |
| 280 html.Append('<br/>') | |
| 281 | |
| 282 # Style for each highlighter. | |
| 283 # The correct highlighting will be shown by Javascript. | |
| 284 for name, highlighter in self.server.highlighters.items(): | |
| 285 styles = sorted(highlighter.GetStyles()) | |
| 286 if not styles: | |
| 287 continue | |
| 288 | |
| 289 html.Append('<select class="highlighter_styles" id="%s_styles" ' | |
| 290 'onChange="updateEverything()">' % name) | |
| 291 for style in styles: | |
| 292 html.Append('<option>%s</option>' % style) | |
| 293 html.Append('</select>') | |
| 294 | |
| 295 html.Append('<br/>') | |
| 296 | |
| 297 # The files, with appropriate handlers. | |
| 298 html.Append('<ul>') | |
| 299 | |
| 300 # Make path point to a non-empty directory. This can happen if a URL like | |
| 301 # http://localhost:8000 is navigated to. | |
| 302 if path == '': | |
| 303 path = os.curdir | |
| 304 | |
| 305 # Firstly, a .. link if this isn't the root. | |
| 306 if not os.path.samefile(os.curdir, path): | |
| 307 normpath = os.path.normpath(os.path.join(path, os.pardir)) | |
| 308 html.Append('<li><a href="/%s">%s/</a>' % (normpath, os.pardir)) | |
| 309 | |
| 310 # Each file under path/ | |
| 311 for filename in sorted(os.listdir(path)): | |
| 312 full_path = os.path.join(path, filename) | |
| 313 (file_root, file_ext) = os.path.splitext(full_path) | |
| 314 if os.path.isdir(full_path) and not full_path.endswith('.xcodeproj'): | |
| 315 html.Append('<li><a href="/%s/">%s/</a>' % (full_path, filename)) | |
| 316 elif file_ext in ['.json', '.idl']: | |
| 317 # cc/h panes will automatically update via the hash change event. | |
| 318 html.Append('<li><a href="#%s">%s</a>' % | |
| 319 (filename, filename)) | |
| 320 | |
| 321 html.Append('</ul>') | |
| 322 | |
| 323 return html.Render() | |
| 324 | |
| 325 class PreviewHTTPServer(HTTPServer, object): | |
| 326 def __init__(self, server_address, handler, highlighters): | |
| 327 super(PreviewHTTPServer, self).__init__(server_address, handler) | |
| 328 self.highlighters = highlighters | |
| 329 | |
| 330 | |
| 331 if __name__ == '__main__': | |
| 332 parser = optparse.OptionParser( | |
| 333 description='Runs a server to preview the json_schema_compiler output.', | |
| 334 usage='usage: %prog [option]...') | |
| 335 parser.add_option('-p', '--port', default='8000', | |
| 336 help='port to run the server on') | |
| 337 | |
| 338 (opts, argv) = parser.parse_args() | |
| 339 | |
| 340 try: | |
| 341 print('Starting previewserver on port %s' % opts.port) | |
| 342 print('The extension documentation can be found at:') | |
| 343 print('') | |
| 344 print(' http://localhost:%s/chrome/common/extensions/api' % opts.port) | |
| 345 print('') | |
| 346 | |
| 347 highlighters = { | |
| 348 'hilite': hilite_me_highlighter.HiliteMeHighlighter(), | |
| 349 'none': none_highlighter.NoneHighlighter() | |
| 350 } | |
| 351 try: | |
| 352 highlighters['pygments'] = pygments_highlighter.PygmentsHighlighter() | |
| 353 except ImportError as e: | |
| 354 pass | |
| 355 | |
| 356 server = PreviewHTTPServer(('', int(opts.port)), | |
| 357 CompilerHandler, | |
| 358 highlighters) | |
| 359 server.serve_forever() | |
| 360 except KeyboardInterrupt: | |
| 361 server.socket.close() | |
| OLD | NEW |