#!/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 idl_schema import json_schema import model import optparse import os import schema_loader 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('' % chromium_favicon) head.Append('' % 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('') self.wfile.write(head.Render()) self.wfile.write('') self.wfile.write(body.Render()) self.wfile.write('') 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('') ) body.Append( '' '
' '
' % 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('''''') def _LoadModel(self, basedir, name): """Loads and returns the model for the |name| API from either its JSON or IDL file, e.g. name=contextMenus will be loaded from |basedir|/context_menus.json, name=alarms will be loaded from |basedir|/alarms.idl. """ loaders = { 'json': json_schema.Load, 'idl': idl_schema.Load } # APIs are referred to like "webRequest" but that's in a file # "web_request.json" so we need to unixify the name. unix_name = model.UnixName(name) for loader_ext, loader_fn in loaders.items(): file_path = '%s/%s.%s' % (basedir, unix_name, loader_ext) if os.path.exists(file_path): # For historical reasons these files contain a singleton list with the # model, so just return that single object. return (loader_fn(file_path)[0], file_path) raise ValueError('File for model "%s" not found' % name) def _ShowCompiledFile(self, parsed_url, head, body): """Show the compiled version of a json or idl 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) try: # Get main file. (api_def, file_path) = self._LoadModel(filedir, filename) namespace = api_model.AddNamespace(api_def, file_path) type_generator = cpp_type_generator.CppTypeGenerator( api_model, schema_loader.SchemaLoader(filedir), namespace) # Get the model's dependencies. for dependency in api_def.get('dependencies', []): # Dependencies can contain : in which case they don't refer to APIs, # rather, permissions or manifest keys. if ':' in dependency: continue (api_def, file_path) = self._LoadModel(filedir, dependency) referenced_namespace = api_model.AddNamespace(api_def, file_path) if referenced_namespace: type_generator.AddNamespace(referenced_namespace, cpp_util.Classname(referenced_namespace.name).lower()) # Generate code cpp_namespace = 'generated_api_schemas' if file_ext == '.h': cpp_code = (h_generator.HGenerator(type_generator, cpp_namespace) .Generate(namespace).Render()) elif file_ext == '.cc': cpp_code = (cc_generator.CCGenerator(type_generator, cpp_namespace) .Generate(namespace).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('') 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('
')
      body.Append('compiler error: %s' % error)
      body.Append('Check server log for more details')
      body.Append('
') 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('') html.Append('
') # 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('') html.Append('
') # The files, with appropriate handlers. html.Append('') 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 %s' % opts.port) print('The extension documentation can be found at:') print('') print(' http://localhost:%s/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()