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.
12 import cpp_type_generator
22 from highlighters
import (
23 pygments_highlighter
, none_highlighter
, hilite_me_highlighter
)
24 from BaseHTTPServer
import BaseHTTPRequestHandler
, HTTPServer
26 class CompilerHandler(BaseHTTPRequestHandler
):
27 """A HTTPRequestHandler that outputs the result of tools/json_schema_compiler.
30 parsed_url
= urlparse
.urlparse(self
.path
)
31 request_path
= self
._GetRequestPath
(parsed_url
)
33 chromium_favicon
= 'http://codereview.chromium.org/static/favicon.ico'
36 head
.Append('<link rel="icon" href="%s">' % chromium_favicon
)
37 head
.Append('<link rel="shortcut icon" href="%s">' % chromium_favicon
)
42 if os
.path
.isdir(request_path
):
43 self
._ShowPanels
(parsed_url
, head
, body
)
45 self
._ShowCompiledFile
(parsed_url
, head
, body
)
47 self
.wfile
.write('<html><head>')
48 self
.wfile
.write(head
.Render())
49 self
.wfile
.write('</head><body>')
50 self
.wfile
.write(body
.Render())
51 self
.wfile
.write('</body></html>')
53 def _GetRequestPath(self
, parsed_url
, strip_nav
=False):
54 """Get the relative path from the current directory to the requested file.
56 path
= parsed_url
.path
58 path
= parsed_url
.path
.replace('/nav', '')
59 return os
.path
.normpath(os
.curdir
+ path
)
61 def _ShowPanels(self
, parsed_url
, head
, body
):
62 """Show the previewer frame structure.
64 Code panes are populated via XHR after links in the nav pane are clicked.
66 (head
.Append('<style>')
68 .Append(' margin: 0;')
71 .Append(' height: 100%;')
72 .Append(' overflow-x: auto;')
73 .Append(' overflow-y: scroll;')
74 .Append(' display: inline-block;')
76 .Append('#nav_pane {')
77 .Append(' width: 20%;')
79 .Append('#nav_pane ul {')
80 .Append(' list-style-type: none;')
81 .Append(' padding: 0 0 0 1em;')
84 .Append(' width: 40%;')
87 .Append(' width: 40%;')
93 '<div class="pane" id="nav_pane">%s</div>'
94 '<div class="pane" id="h_pane"></div>'
95 '<div class="pane" id="cc_pane"></div>' %
96 self
._RenderNavPane
(parsed_url
.path
[1:])
99 # The Javascript that interacts with the nav pane and panes to show the
100 # compiled files as the URL or highlighting options change.
101 body
.Append('''<script type="text/javascript">
102 // Calls a function for each highlighter style <select> element.
103 function forEachHighlighterStyle(callback) {
104 var highlighterStyles =
105 document.getElementsByClassName('highlighter_styles');
106 for (var i = 0; i < highlighterStyles.length; ++i)
107 callback(highlighterStyles[i]);
110 // Called when anything changes, such as the highlighter or hashtag.
111 function updateEverything() {
112 var highlighters = document.getElementById('highlighters');
113 var highlighterName = highlighters.value;
115 // Cache in localStorage for when the page loads next.
116 localStorage.highlightersValue = highlighterName;
118 // Show/hide the highlighter styles.
119 var highlighterStyleName = '';
120 forEachHighlighterStyle(function(highlighterStyle) {
121 if (highlighterStyle.id === highlighterName + '_styles') {
122 highlighterStyle.removeAttribute('style')
123 highlighterStyleName = highlighterStyle.value;
125 highlighterStyle.setAttribute('style', 'display:none')
128 // Cache in localStorage for when the page next loads.
129 localStorage[highlighterStyle.id + 'Value'] = highlighterStyle.value;
132 // Populate the code panes.
133 function populateViaXHR(elementId, requestPath) {
134 var xhr = new XMLHttpRequest();
135 xhr.onreadystatechange = function() {
136 if (xhr.readyState != 4)
138 if (xhr.status != 200) {
139 alert('XHR error to ' + requestPath);
142 document.getElementById(elementId).innerHTML = xhr.responseText;
144 xhr.open('GET', requestPath, true);
148 var targetName = window.location.hash;
149 targetName = targetName.substring('#'.length);
150 targetName = targetName.split('.', 1)[0]
152 if (targetName !== '') {
153 var basePath = window.location.pathname;
154 var query = 'highlighter=' + highlighterName + '&' +
155 'style=' + highlighterStyleName;
156 populateViaXHR('h_pane', basePath + '/' + targetName + '.h?' + query);
157 populateViaXHR('cc_pane', basePath + '/' + targetName + '.cc?' + query);
161 // Initial load: set the values of highlighter and highlighterStyles from
164 var cachedValue = localStorage.highlightersValue;
166 document.getElementById('highlighters').value = cachedValue;
168 forEachHighlighterStyle(function(highlighterStyle) {
169 var cachedValue = localStorage[highlighterStyle.id + 'Value'];
171 highlighterStyle.value = cachedValue;
175 window.addEventListener('hashchange', updateEverything, false);
179 def _LoadModel(self
, basedir
, name
):
180 """Loads and returns the model for the |name| API from either its JSON or
182 name=contextMenus will be loaded from |basedir|/context_menus.json,
183 name=alarms will be loaded from |basedir|/alarms.idl.
186 'json': json_schema
.Load
,
187 'idl': idl_schema
.Load
189 # APIs are referred to like "webRequest" but that's in a file
190 # "web_request.json" so we need to unixify the name.
191 unix_name
= model
.UnixName(name
)
192 for loader_ext
, loader_fn
in loaders
.items():
193 file_path
= '%s/%s.%s' % (basedir
, unix_name
, loader_ext
)
194 if os
.path
.exists(file_path
):
195 # For historical reasons these files contain a singleton list with the
196 # model, so just return that single object.
197 return (loader_fn(file_path
)[0], file_path
)
198 raise ValueError('File for model "%s" not found' % name
)
200 def _ShowCompiledFile(self
, parsed_url
, head
, body
):
201 """Show the compiled version of a json or idl file given the path to the
204 api_model
= model
.Model()
206 request_path
= self
._GetRequestPath
(parsed_url
)
207 (file_root
, file_ext
) = os
.path
.splitext(request_path
)
208 (filedir
, filename
) = os
.path
.split(file_root
)
212 (api_def
, file_path
) = self
._LoadModel
(filedir
, filename
)
213 namespace
= api_model
.AddNamespace(api_def
, file_path
)
214 type_generator
= cpp_type_generator
.CppTypeGenerator(
216 compiler
.TypeNamespaceResolver(filedir
),
219 # Get the model's dependencies.
220 for dependency
in api_def
.get('dependencies', []):
221 # Dependencies can contain : in which case they don't refer to APIs,
222 # rather, permissions or manifest keys.
223 if ':' in dependency
:
225 (api_def
, file_path
) = self
._LoadModel
(filedir
, dependency
)
226 referenced_namespace
= api_model
.AddNamespace(api_def
, file_path
)
227 if referenced_namespace
:
228 type_generator
.AddNamespace(referenced_namespace
,
229 cpp_util
.Classname(referenced_namespace
.name
).lower())
233 cpp_code
= (h_generator
.HGenerator(namespace
, type_generator
)
234 .Generate().Render())
235 elif file_ext
== '.cc':
236 cpp_code
= (cc_generator
.CCGenerator(namespace
, type_generator
)
237 .Generate().Render())
239 self
.send_error(404, "File not found: %s" % request_path
)
242 # Do highlighting on the generated code
243 (highlighter_param
, style_param
) = self
._GetHighlighterParams
(parsed_url
)
244 head
.Append('<style>' +
245 self
.server
.highlighters
[highlighter_param
].GetCSS(style_param
) +
247 body
.Append(self
.server
.highlighters
[highlighter_param
]
248 .GetCodeElement(cpp_code
, style_param
))
250 self
.send_error(404, "File not found: %s" % request_path
)
252 except (TypeError, KeyError, AttributeError,
253 AssertionError, NotImplementedError) as error
:
255 body
.Append('compiler error: %s' % error
)
256 body
.Append('Check server log for more details')
257 body
.Append('</pre>')
260 def _GetHighlighterParams(self
, parsed_url
):
261 """Get the highlighting parameters from a parsed url.
263 query_dict
= urlparse
.parse_qs(parsed_url
.query
)
264 return (query_dict
.get('highlighter', ['pygments'])[0],
265 query_dict
.get('style', ['colorful'])[0])
267 def _RenderNavPane(self
, path
):
268 """Renders an HTML nav pane.
270 This consists of a select element to set highlight style, and a list of all
271 files at |path| with the appropriate onclick handlers to open either
272 subdirectories or JSON files.
276 # Highlighter chooser.
277 html
.Append('<select id="highlighters" onChange="updateEverything()">')
278 for name
, highlighter
in self
.server
.highlighters
.items():
279 html
.Append('<option value="%s">%s</option>' %
280 (name
, highlighter
.DisplayName()))
281 html
.Append('</select>')
285 # Style for each highlighter.
286 # The correct highlighting will be shown by Javascript.
287 for name
, highlighter
in self
.server
.highlighters
.items():
288 styles
= sorted(highlighter
.GetStyles())
292 html
.Append('<select class="highlighter_styles" id="%s_styles" '
293 'onChange="updateEverything()">' % name
)
295 html
.Append('<option>%s</option>' % style
)
296 html
.Append('</select>')
300 # The files, with appropriate handlers.
303 # Make path point to a non-empty directory. This can happen if a URL like
304 # http://localhost:8000 is navigated to.
308 # Firstly, a .. link if this isn't the root.
309 if not os
.path
.samefile(os
.curdir
, path
):
310 normpath
= os
.path
.normpath(os
.path
.join(path
, os
.pardir
))
311 html
.Append('<li><a href="/%s">%s/</a>' % (normpath
, os
.pardir
))
313 # Each file under path/
314 for filename
in sorted(os
.listdir(path
)):
315 full_path
= os
.path
.join(path
, filename
)
316 (file_root
, file_ext
) = os
.path
.splitext(full_path
)
317 if os
.path
.isdir(full_path
) and not full_path
.endswith('.xcodeproj'):
318 html
.Append('<li><a href="/%s/">%s/</a>' % (full_path
, filename
))
319 elif file_ext
in ['.json', '.idl']:
320 # cc/h panes will automatically update via the hash change event.
321 html
.Append('<li><a href="#%s">%s</a>' %
322 (filename
, filename
))
328 class PreviewHTTPServer(HTTPServer
, object):
329 def __init__(self
, server_address
, handler
, highlighters
):
330 super(PreviewHTTPServer
, self
).__init
__(server_address
, handler
)
331 self
.highlighters
= highlighters
334 if __name__
== '__main__':
335 parser
= optparse
.OptionParser(
336 description
='Runs a server to preview the json_schema_compiler output.',
337 usage
='usage: %prog [option]...')
338 parser
.add_option('-p', '--port', default
='8000',
339 help='port to run the server on')
341 (opts
, argv
) = parser
.parse_args()
344 print('Starting previewserver on port %s' % opts
.port
)
345 print('The extension documentation can be found at:')
347 print(' http://localhost:%s/chrome/common/extensions/api' % opts
.port
)
351 'hilite': hilite_me_highlighter
.HiliteMeHighlighter(),
352 'none': none_highlighter
.NoneHighlighter()
355 highlighters
['pygments'] = pygments_highlighter
.PygmentsHighlighter()
356 except ImportError as e
:
359 server
= PreviewHTTPServer(('', int(opts
.port
)),
362 server
.serve_forever()
363 except KeyboardInterrupt:
364 server
.socket
.close()