Roll src/third_party/WebKit f36d5e0:68b67cd (svn 193299:193303)
[chromium-blink-merge.git] / tools / json_schema_compiler / preview.py
blobaceb575305b600e7800a887e24c867105c412f08
1 #!/usr/bin/env python
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 """
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 shlex
20 import urlparse
21 from highlighters import (
22 pygments_highlighter, none_highlighter, hilite_me_highlighter)
23 from BaseHTTPServer import BaseHTTPRequestHandler, HTTPServer
24 from cpp_namespace_environment import CppNamespaceEnvironment
25 from schema_loader import SchemaLoader
28 class CompilerHandler(BaseHTTPRequestHandler):
29 """A HTTPRequestHandler that outputs the result of tools/json_schema_compiler.
30 """
31 def do_GET(self):
32 parsed_url = urlparse.urlparse(self.path)
33 request_path = self._GetRequestPath(parsed_url)
35 chromium_favicon = 'http://codereview.chromium.org/static/favicon.ico'
37 head = code.Code()
38 head.Append('<link rel="icon" href="%s">' % chromium_favicon)
39 head.Append('<link rel="shortcut icon" href="%s">' % chromium_favicon)
41 body = code.Code()
43 try:
44 if os.path.isdir(request_path):
45 self._ShowPanels(parsed_url, head, body)
46 else:
47 self._ShowCompiledFile(parsed_url, head, body)
48 finally:
49 self.wfile.write('<html><head>')
50 self.wfile.write(head.Render())
51 self.wfile.write('</head><body>')
52 self.wfile.write(body.Render())
53 self.wfile.write('</body></html>')
55 def _GetRequestPath(self, parsed_url, strip_nav=False):
56 """Get the relative path from the current directory to the requested file.
57 """
58 path = parsed_url.path
59 if strip_nav:
60 path = parsed_url.path.replace('/nav', '')
61 return os.path.normpath(os.curdir + path)
63 def _ShowPanels(self, parsed_url, head, body):
64 """Show the previewer frame structure.
66 Code panes are populated via XHR after links in the nav pane are clicked.
67 """
68 (head.Append('<style>')
69 .Append('body {')
70 .Append(' margin: 0;')
71 .Append('}')
72 .Append('.pane {')
73 .Append(' height: 100%;')
74 .Append(' overflow-x: auto;')
75 .Append(' overflow-y: scroll;')
76 .Append(' display: inline-block;')
77 .Append('}')
78 .Append('#nav_pane {')
79 .Append(' width: 20%;')
80 .Append('}')
81 .Append('#nav_pane ul {')
82 .Append(' list-style-type: none;')
83 .Append(' padding: 0 0 0 1em;')
84 .Append('}')
85 .Append('#cc_pane {')
86 .Append(' width: 40%;')
87 .Append('}')
88 .Append('#h_pane {')
89 .Append(' width: 40%;')
90 .Append('}')
91 .Append('</style>')
94 body.Append(
95 '<div class="pane" id="nav_pane">%s</div>'
96 '<div class="pane" id="h_pane"></div>'
97 '<div class="pane" id="cc_pane"></div>' %
98 self._RenderNavPane(parsed_url.path[1:])
101 # The Javascript that interacts with the nav pane and panes to show the
102 # compiled files as the URL or highlighting options change.
103 body.Append('''<script type="text/javascript">
104 // Calls a function for each highlighter style <select> element.
105 function forEachHighlighterStyle(callback) {
106 var highlighterStyles =
107 document.getElementsByClassName('highlighter_styles');
108 for (var i = 0; i < highlighterStyles.length; ++i)
109 callback(highlighterStyles[i]);
112 // Called when anything changes, such as the highlighter or hashtag.
113 function updateEverything() {
114 var highlighters = document.getElementById('highlighters');
115 var highlighterName = highlighters.value;
117 // Cache in localStorage for when the page loads next.
118 localStorage.highlightersValue = highlighterName;
120 // Show/hide the highlighter styles.
121 var highlighterStyleName = '';
122 forEachHighlighterStyle(function(highlighterStyle) {
123 if (highlighterStyle.id === highlighterName + '_styles') {
124 highlighterStyle.removeAttribute('style')
125 highlighterStyleName = highlighterStyle.value;
126 } else {
127 highlighterStyle.setAttribute('style', 'display:none')
130 // Cache in localStorage for when the page next loads.
131 localStorage[highlighterStyle.id + 'Value'] = highlighterStyle.value;
134 // Populate the code panes.
135 function populateViaXHR(elementId, requestPath) {
136 var xhr = new XMLHttpRequest();
137 xhr.onreadystatechange = function() {
138 if (xhr.readyState != 4)
139 return;
140 if (xhr.status != 200) {
141 alert('XHR error to ' + requestPath);
142 return;
144 document.getElementById(elementId).innerHTML = xhr.responseText;
146 xhr.open('GET', requestPath, true);
147 xhr.send();
150 var targetName = window.location.hash;
151 targetName = targetName.substring('#'.length);
152 targetName = targetName.split('.', 1)[0]
154 if (targetName !== '') {
155 var basePath = window.location.pathname;
156 var query = 'highlighter=' + highlighterName + '&' +
157 'style=' + highlighterStyleName;
158 populateViaXHR('h_pane', basePath + '/' + targetName + '.h?' + query);
159 populateViaXHR('cc_pane', basePath + '/' + targetName + '.cc?' + query);
163 // Initial load: set the values of highlighter and highlighterStyles from
164 // localStorage.
165 (function() {
166 var cachedValue = localStorage.highlightersValue;
167 if (cachedValue)
168 document.getElementById('highlighters').value = cachedValue;
170 forEachHighlighterStyle(function(highlighterStyle) {
171 var cachedValue = localStorage[highlighterStyle.id + 'Value'];
172 if (cachedValue)
173 highlighterStyle.value = cachedValue;
175 })();
177 window.addEventListener('hashchange', updateEverything, false);
178 updateEverything();
179 </script>''')
181 def _ShowCompiledFile(self, parsed_url, head, body):
182 """Show the compiled version of a json or idl file given the path to the
183 compiled file.
185 api_model = model.Model()
187 request_path = self._GetRequestPath(parsed_url)
188 (file_root, file_ext) = os.path.splitext(request_path)
189 (filedir, filename) = os.path.split(file_root)
191 schema_loader = SchemaLoader("./",
192 filedir,
193 self.server.include_rules,
194 self.server.cpp_namespace_pattern)
195 try:
196 # Get main file.
197 namespace = schema_loader.ResolveNamespace(filename)
198 type_generator = cpp_type_generator.CppTypeGenerator(
199 api_model,
200 schema_loader,
201 namespace)
203 # Generate code
204 if file_ext == '.h':
205 cpp_code = (h_generator.HGenerator(type_generator)
206 .Generate(namespace).Render())
207 elif file_ext == '.cc':
208 cpp_code = (cc_generator.CCGenerator(type_generator)
209 .Generate(namespace).Render())
210 else:
211 self.send_error(404, "File not found: %s" % request_path)
212 return
214 # Do highlighting on the generated code
215 (highlighter_param, style_param) = self._GetHighlighterParams(parsed_url)
216 head.Append('<style>' +
217 self.server.highlighters[highlighter_param].GetCSS(style_param) +
218 '</style>')
219 body.Append(self.server.highlighters[highlighter_param]
220 .GetCodeElement(cpp_code, style_param))
221 except IOError:
222 self.send_error(404, "File not found: %s" % request_path)
223 return
224 except (TypeError, KeyError, AttributeError,
225 AssertionError, NotImplementedError) as error:
226 body.Append('<pre>')
227 body.Append('compiler error: %s' % error)
228 body.Append('Check server log for more details')
229 body.Append('</pre>')
230 raise
232 def _GetHighlighterParams(self, parsed_url):
233 """Get the highlighting parameters from a parsed url.
235 query_dict = urlparse.parse_qs(parsed_url.query)
236 return (query_dict.get('highlighter', ['pygments'])[0],
237 query_dict.get('style', ['colorful'])[0])
239 def _RenderNavPane(self, path):
240 """Renders an HTML nav pane.
242 This consists of a select element to set highlight style, and a list of all
243 files at |path| with the appropriate onclick handlers to open either
244 subdirectories or JSON files.
246 html = code.Code()
248 # Highlighter chooser.
249 html.Append('<select id="highlighters" onChange="updateEverything()">')
250 for name, highlighter in self.server.highlighters.items():
251 html.Append('<option value="%s">%s</option>' %
252 (name, highlighter.DisplayName()))
253 html.Append('</select>')
255 html.Append('<br/>')
257 # Style for each highlighter.
258 # The correct highlighting will be shown by Javascript.
259 for name, highlighter in self.server.highlighters.items():
260 styles = sorted(highlighter.GetStyles())
261 if not styles:
262 continue
264 html.Append('<select class="highlighter_styles" id="%s_styles" '
265 'onChange="updateEverything()">' % name)
266 for style in styles:
267 html.Append('<option>%s</option>' % style)
268 html.Append('</select>')
270 html.Append('<br/>')
272 # The files, with appropriate handlers.
273 html.Append('<ul>')
275 # Make path point to a non-empty directory. This can happen if a URL like
276 # http://localhost:8000 is navigated to.
277 if path == '':
278 path = os.curdir
280 # Firstly, a .. link if this isn't the root.
281 if not os.path.samefile(os.curdir, path):
282 normpath = os.path.normpath(os.path.join(path, os.pardir))
283 html.Append('<li><a href="/%s">%s/</a>' % (normpath, os.pardir))
285 # Each file under path/
286 for filename in sorted(os.listdir(path)):
287 full_path = os.path.join(path, filename)
288 _, file_ext = os.path.splitext(full_path)
289 if os.path.isdir(full_path) and not full_path.endswith('.xcodeproj'):
290 html.Append('<li><a href="/%s/">%s/</a>' % (full_path, filename))
291 elif file_ext in ['.json', '.idl']:
292 # cc/h panes will automatically update via the hash change event.
293 html.Append('<li><a href="#%s">%s</a>' %
294 (filename, filename))
296 html.Append('</ul>')
298 return html.Render()
301 class PreviewHTTPServer(HTTPServer, object):
302 def __init__(self,
303 server_address,
304 handler,
305 highlighters,
306 include_rules,
307 cpp_namespace_pattern):
308 super(PreviewHTTPServer, self).__init__(server_address, handler)
309 self.highlighters = highlighters
310 self.include_rules = include_rules
311 self.cpp_namespace_pattern = cpp_namespace_pattern
314 if __name__ == '__main__':
315 parser = optparse.OptionParser(
316 description='Runs a server to preview the json_schema_compiler output.',
317 usage='usage: %prog [option]...')
318 parser.add_option('-p', '--port', default='8000',
319 help='port to run the server on')
320 parser.add_option('-n', '--namespace', default='generated_api_schemas',
321 help='C++ namespace for generated files. e.g extensions::api.')
322 parser.add_option('-I', '--include-rules',
323 help='A list of paths to include when searching for referenced objects,'
324 ' with the namespace separated by a \':\'. Example: '
325 '/foo/bar:Foo::Bar::%(namespace)s')
327 (opts, argv) = parser.parse_args()
329 def split_path_and_namespace(path_and_namespace):
330 if ':' not in path_and_namespace:
331 raise ValueError('Invalid include rule "%s". Rules must be of '
332 'the form path:namespace' % path_and_namespace)
333 return path_and_namespace.split(':', 1)
335 include_rules = []
336 if opts.include_rules:
337 include_rules = map(split_path_and_namespace,
338 shlex.split(opts.include_rules))
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('')
347 highlighters = {
348 'hilite': hilite_me_highlighter.HiliteMeHighlighter(),
349 'none': none_highlighter.NoneHighlighter()
351 try:
352 highlighters['pygments'] = pygments_highlighter.PygmentsHighlighter()
353 except ImportError as e:
354 pass
356 server = PreviewHTTPServer(('', int(opts.port)),
357 CompilerHandler,
358 highlighters,
359 include_rules,
360 opts.namespace)
361 server.serve_forever()
362 except KeyboardInterrupt:
363 server.socket.close()