| #!/usr/bin/env python |
| |
| # Copyright (c) 2012 The Chromium Authors. All rights reserved. |
| # Use of this source code is governed by a BSD-style license that can be |
| # found in the LICENSE file. |
| """Server for viewing the compiled C++ code from tools/json_schema_compiler. |
| """ |
| |
| import cc_generator |
| import code |
| import cpp_type_generator |
| import cpp_util |
| import h_generator |
| import json_schema |
| import model |
| import optparse |
| import os |
| import sys |
| import urlparse |
| from highlighters import ( |
| pygments_highlighter, none_highlighter, hilite_me_highlighter) |
| from BaseHTTPServer import BaseHTTPRequestHandler, HTTPServer |
| |
| class CompilerHandler(BaseHTTPRequestHandler): |
| """A HTTPRequestHandler that outputs the result of tools/json_schema_compiler. |
| """ |
| def do_GET(self): |
| parsed_url = urlparse.urlparse(self.path) |
| request_path = self._GetRequestPath(parsed_url) |
| |
| chromium_favicon = 'http://codereview.chromium.org/static/favicon.ico' |
| |
| head = code.Code() |
| head.Append('<link rel="icon" href="%s">' % chromium_favicon) |
| head.Append('<link rel="shortcut icon" href="%s">' % chromium_favicon) |
| |
| body = code.Code() |
| |
| try: |
| if os.path.isdir(request_path): |
| self._ShowPanels(parsed_url, head, body) |
| else: |
| self._ShowCompiledFile(parsed_url, head, body) |
| finally: |
| self.wfile.write('<html><head>') |
| self.wfile.write(head.Render()) |
| self.wfile.write('</head><body>') |
| self.wfile.write(body.Render()) |
| self.wfile.write('</body></html>') |
| |
| def _GetRequestPath(self, parsed_url, strip_nav=False): |
| """Get the relative path from the current directory to the requested file. |
| """ |
| path = parsed_url.path |
| if strip_nav: |
| path = parsed_url.path.replace('/nav', '') |
| return os.path.normpath(os.curdir + path) |
| |
| def _ShowPanels(self, parsed_url, head, body): |
| """Show the previewer frame structure. |
| |
| Code panes are populated via XHR after links in the nav pane are clicked. |
| """ |
| (head.Append('<style>') |
| .Append('body {') |
| .Append(' margin: 0;') |
| .Append('}') |
| .Append('.pane {') |
| .Append(' height: 100%;') |
| .Append(' overflow-x: auto;') |
| .Append(' overflow-y: scroll;') |
| .Append(' display: inline-block;') |
| .Append('}') |
| .Append('#nav_pane {') |
| .Append(' width: 20%;') |
| .Append('}') |
| .Append('#nav_pane ul {') |
| .Append(' list-style-type: none;') |
| .Append(' padding: 0 0 0 1em;') |
| .Append('}') |
| .Append('#cc_pane {') |
| .Append(' width: 40%;') |
| .Append('}') |
| .Append('#h_pane {') |
| .Append(' width: 40%;') |
| .Append('}') |
| .Append('</style>') |
| ) |
| |
| body.Append( |
| '<div class="pane" id="nav_pane">%s</div>' |
| '<div class="pane" id="h_pane"></div>' |
| '<div class="pane" id="cc_pane"></div>' % |
| self._RenderNavPane(parsed_url.path[1:]) |
| ) |
| |
| # The Javascript that interacts with the nav pane and panes to show the |
| # compiled files as the URL or highlighting options change. |
| body.Append('''<script type="text/javascript"> |
| // Calls a function for each highlighter style <select> element. |
| function forEachHighlighterStyle(callback) { |
| var highlighterStyles = |
| document.getElementsByClassName('highlighter_styles'); |
| for (var i = 0; i < highlighterStyles.length; ++i) |
| callback(highlighterStyles[i]); |
| } |
| |
| // Called when anything changes, such as the highlighter or hashtag. |
| function updateEverything() { |
| var highlighters = document.getElementById('highlighters'); |
| var highlighterName = highlighters.value; |
| |
| // Cache in localStorage for when the page loads next. |
| localStorage.highlightersValue = highlighterName; |
| |
| // Show/hide the highlighter styles. |
| var highlighterStyleName = ''; |
| forEachHighlighterStyle(function(highlighterStyle) { |
| if (highlighterStyle.id === highlighterName + '_styles') { |
| highlighterStyle.removeAttribute('style') |
| highlighterStyleName = highlighterStyle.value; |
| } else { |
| highlighterStyle.setAttribute('style', 'display:none') |
| } |
| |
| // Cache in localStorage for when the page next loads. |
| localStorage[highlighterStyle.id + 'Value'] = highlighterStyle.value; |
| }); |
| |
| // Populate the code panes. |
| function populateViaXHR(elementId, requestPath) { |
| var xhr = new XMLHttpRequest(); |
| xhr.onreadystatechange = function() { |
| if (xhr.readyState != 4) |
| return; |
| if (xhr.status != 200) { |
| alert('XHR error to ' + requestPath); |
| return; |
| } |
| document.getElementById(elementId).innerHTML = xhr.responseText; |
| }; |
| xhr.open('GET', requestPath, true); |
| xhr.send(); |
| } |
| |
| var targetName = window.location.hash; |
| targetName = targetName.substring('#'.length); |
| targetName = targetName.substring(0, targetName.length - '.json'.length); |
| |
| if (targetName !== '') { |
| var basePath = window.location.pathname; |
| var query = 'highlighter=' + highlighterName + '&' + |
| 'style=' + highlighterStyleName; |
| populateViaXHR('h_pane', basePath + '/' + targetName + '.h?' + query); |
| populateViaXHR('cc_pane', basePath + '/' + targetName + '.cc?' + query); |
| } |
| } |
| |
| // Initial load: set the values of highlighter and highlighterStyles from |
| // localStorage. |
| (function() { |
| var cachedValue = localStorage.highlightersValue; |
| if (cachedValue) |
| document.getElementById('highlighters').value = cachedValue; |
| |
| forEachHighlighterStyle(function(highlighterStyle) { |
| var cachedValue = localStorage[highlighterStyle.id + 'Value']; |
| if (cachedValue) |
| highlighterStyle.value = cachedValue; |
| }); |
| })(); |
| |
| window.addEventListener('hashchange', updateEverything, false); |
| updateEverything(); |
| </script>''') |
| |
| def _ShowCompiledFile(self, parsed_url, head, body): |
| """Show the compiled version of a json file given the path to the compiled |
| file. |
| """ |
| |
| api_model = model.Model() |
| |
| request_path = self._GetRequestPath(parsed_url) |
| (file_root, file_ext) = os.path.splitext(request_path) |
| (filedir, filename) = os.path.split(file_root) |
| json_file_path = os.path.normpath(file_root + '.json') |
| |
| try: |
| # Get main json file |
| api_defs = json_schema.Load(json_file_path) |
| namespace = api_model.AddNamespace(api_defs[0], json_file_path) |
| type_generator = cpp_type_generator.CppTypeGenerator( |
| 'previewserver::api', namespace, namespace.unix_name) |
| |
| # Get json file depedencies |
| for dependency in api_defs[0].get('dependencies', []): |
| json_file_path = os.path.join(filedir, dependency + '.json') |
| api_defs = json_schema.Load(json_file_path) |
| referenced_namespace = api_model.AddNamespace(api_defs[0], |
| json_file_path) |
| if referenced_namespace: |
| type_generator.AddNamespace(referenced_namespace, |
| cpp_util.Classname(referenced_namespace.name).lower()) |
| |
| # Generate code |
| if file_ext == '.h': |
| cpp_code = (h_generator.HGenerator(namespace, type_generator) |
| .Generate().Render()) |
| elif file_ext == '.cc': |
| cpp_code = (cc_generator.CCGenerator(namespace, type_generator) |
| .Generate().Render()) |
| else: |
| self.send_error(404, "File not found: %s" % request_path) |
| return |
| |
| # Do highlighting on the generated code |
| (highlighter_param, style_param) = self._GetHighlighterParams(parsed_url) |
| head.Append('<style>' + |
| self.server.highlighters[highlighter_param].GetCSS(style_param) + |
| '</style>') |
| body.Append(self.server.highlighters[highlighter_param] |
| .GetCodeElement(cpp_code, style_param)) |
| except IOError: |
| self.send_error(404, "File not found: %s" % request_path) |
| return |
| except (TypeError, KeyError, AttributeError, |
| AssertionError, NotImplementedError) as error: |
| body.Append('<pre>') |
| body.Append('compiler error: ' + str(error)) |
| body.Append('Check server log for more details') |
| body.Append('</pre>') |
| raise |
| |
| def _GetHighlighterParams(self, parsed_url): |
| """Get the highlighting parameters from a parsed url. |
| """ |
| query_dict = urlparse.parse_qs(parsed_url.query) |
| return (query_dict.get('highlighter', ['pygments'])[0], |
| query_dict.get('style', ['colorful'])[0]) |
| |
| def _RenderNavPane(self, path): |
| """Renders an HTML nav pane. |
| |
| This consists of a select element to set highlight style, and a list of all |
| files at |path| with the appropriate onclick handlers to open either |
| subdirectories or JSON files. |
| """ |
| html = code.Code() |
| |
| # Highlighter chooser. |
| html.Append('<select id="highlighters" onChange="updateEverything()">') |
| for name, highlighter in self.server.highlighters.items(): |
| html.Append('<option value="%s">%s</option>' % |
| (name, highlighter.DisplayName())) |
| html.Append('</select>') |
| |
| html.Append('<br/>') |
| |
| # Style for each highlighter. |
| # The correct highlighting will be shown by Javascript. |
| for name, highlighter in self.server.highlighters.items(): |
| styles = sorted(highlighter.GetStyles()) |
| if not styles: |
| continue |
| |
| html.Append('<select class="highlighter_styles" id="%s_styles" ' |
| 'onChange="updateEverything()">' % name) |
| for style in styles: |
| html.Append('<option>%s</option>' % style) |
| html.Append('</select>') |
| |
| html.Append('<br/>') |
| |
| # The files, with appropriate handlers. |
| html.Append('<ul>') |
| |
| # Make path point to a non-empty directory. This can happen if a URL like |
| # http://localhost:8000 is navigated to. |
| if path == '': |
| path = os.curdir |
| |
| # Firstly, a .. link if this isn't the root. |
| if not os.path.samefile(os.curdir, path): |
| normpath = os.path.normpath(os.path.join(path, os.pardir)) |
| html.Append('<li><a href="/%s">%s/</a>' % (normpath, os.pardir)) |
| |
| # Each file under path/ |
| for filename in sorted(os.listdir(path)): |
| full_path = os.path.join(path, filename) |
| (file_root, file_ext) = os.path.splitext(full_path) |
| if os.path.isdir(full_path): |
| html.Append('<li><a href="/%s/">%s/</a>' % (full_path, filename)) |
| elif file_ext == '.json': |
| # cc/h panes will automatically update via the hash change event. |
| html.Append('<li><a href="#%s">%s</a>' % |
| (filename, filename)) |
| |
| html.Append('</ul>') |
| |
| return html.Render() |
| |
| class PreviewHTTPServer(HTTPServer, object): |
| def __init__(self, server_address, handler, highlighters): |
| super(PreviewHTTPServer, self).__init__(server_address, handler) |
| self.highlighters = highlighters |
| |
| |
| if __name__ == '__main__': |
| parser = optparse.OptionParser( |
| description='Runs a server to preview the json_schema_compiler output.', |
| usage='usage: %prog [option]...') |
| parser.add_option('-p', '--port', default=8000, |
| help='port to run the server on') |
| |
| (opts, argv) = parser.parse_args() |
| |
| try: |
| print('Starting previewserver on port %d' % opts.port) |
| print('The extension documentation can be found at:') |
| print('') |
| print(' http://localhost:%d/chrome/common/extensions/api' % opts.port) |
| print('') |
| |
| highlighters = { |
| 'hilite': hilite_me_highlighter.HiliteMeHighlighter(), |
| 'none': none_highlighter.NoneHighlighter() |
| } |
| try: |
| highlighters['pygments'] = pygments_highlighter.PygmentsHighlighter() |
| except ImportError as e: |
| pass |
| |
| server = PreviewHTTPServer(('', int(opts.port)), |
| CompilerHandler, |
| highlighters) |
| server.serve_forever() |
| except KeyboardInterrupt: |
| server.socket.close() |