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.
11 import cpp_type_generator
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.
32 parsed_url
= urlparse
.urlparse(self
.path
)
33 request_path
= self
._GetRequestPath
(parsed_url
)
35 chromium_favicon
= 'http://codereview.chromium.org/static/favicon.ico'
38 head
.Append('<link rel="icon" href="%s">' % chromium_favicon
)
39 head
.Append('<link rel="shortcut icon" href="%s">' % chromium_favicon
)
44 if os
.path
.isdir(request_path
):
45 self
._ShowPanels
(parsed_url
, head
, body
)
47 self
._ShowCompiledFile
(parsed_url
, head
, body
)
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.
58 path
= parsed_url
.path
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.
68 (head
.Append('<style>')
70 .Append(' margin: 0;')
73 .Append(' height: 100%;')
74 .Append(' overflow-x: auto;')
75 .Append(' overflow-y: scroll;')
76 .Append(' display: inline-block;')
78 .Append('#nav_pane {')
79 .Append(' width: 20%;')
81 .Append('#nav_pane ul {')
82 .Append(' list-style-type: none;')
83 .Append(' padding: 0 0 0 1em;')
86 .Append(' width: 40%;')
89 .Append(' width: 40%;')
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;
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)
140 if (xhr.status != 200) {
141 alert('XHR error to ' + requestPath);
144 document.getElementById(elementId).innerHTML = xhr.responseText;
146 xhr.open('GET', requestPath, true);
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
166 var cachedValue = localStorage.highlightersValue;
168 document.getElementById('highlighters').value = cachedValue;
170 forEachHighlighterStyle(function(highlighterStyle) {
171 var cachedValue = localStorage[highlighterStyle.id + 'Value'];
173 highlighterStyle.value = cachedValue;
177 window.addEventListener('hashchange', updateEverything, false);
181 def _ShowCompiledFile(self
, parsed_url
, head
, body
):
182 """Show the compiled version of a json or idl file given the path to the
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("./",
193 self
.server
.include_rules
,
194 self
.server
.cpp_namespace_pattern
)
197 namespace
= schema_loader
.ResolveNamespace(filename
)
198 type_generator
= cpp_type_generator
.CppTypeGenerator(
204 cpp_namespace
= 'generated_api_schemas'
206 cpp_code
= (h_generator
.HGenerator(type_generator
)
207 .Generate(namespace
).Render())
208 elif file_ext
== '.cc':
209 cpp_code
= (cc_generator
.CCGenerator(type_generator
)
210 .Generate(namespace
).Render())
212 self
.send_error(404, "File not found: %s" % request_path
)
215 # Do highlighting on the generated code
216 (highlighter_param
, style_param
) = self
._GetHighlighterParams
(parsed_url
)
217 head
.Append('<style>' +
218 self
.server
.highlighters
[highlighter_param
].GetCSS(style_param
) +
220 body
.Append(self
.server
.highlighters
[highlighter_param
]
221 .GetCodeElement(cpp_code
, style_param
))
223 self
.send_error(404, "File not found: %s" % request_path
)
225 except (TypeError, KeyError, AttributeError,
226 AssertionError, NotImplementedError) as error
:
228 body
.Append('compiler error: %s' % error
)
229 body
.Append('Check server log for more details')
230 body
.Append('</pre>')
233 def _GetHighlighterParams(self
, parsed_url
):
234 """Get the highlighting parameters from a parsed url.
236 query_dict
= urlparse
.parse_qs(parsed_url
.query
)
237 return (query_dict
.get('highlighter', ['pygments'])[0],
238 query_dict
.get('style', ['colorful'])[0])
240 def _RenderNavPane(self
, path
):
241 """Renders an HTML nav pane.
243 This consists of a select element to set highlight style, and a list of all
244 files at |path| with the appropriate onclick handlers to open either
245 subdirectories or JSON files.
249 # Highlighter chooser.
250 html
.Append('<select id="highlighters" onChange="updateEverything()">')
251 for name
, highlighter
in self
.server
.highlighters
.items():
252 html
.Append('<option value="%s">%s</option>' %
253 (name
, highlighter
.DisplayName()))
254 html
.Append('</select>')
258 # Style for each highlighter.
259 # The correct highlighting will be shown by Javascript.
260 for name
, highlighter
in self
.server
.highlighters
.items():
261 styles
= sorted(highlighter
.GetStyles())
265 html
.Append('<select class="highlighter_styles" id="%s_styles" '
266 'onChange="updateEverything()">' % name
)
268 html
.Append('<option>%s</option>' % style
)
269 html
.Append('</select>')
273 # The files, with appropriate handlers.
276 # Make path point to a non-empty directory. This can happen if a URL like
277 # http://localhost:8000 is navigated to.
281 # Firstly, a .. link if this isn't the root.
282 if not os
.path
.samefile(os
.curdir
, path
):
283 normpath
= os
.path
.normpath(os
.path
.join(path
, os
.pardir
))
284 html
.Append('<li><a href="/%s">%s/</a>' % (normpath
, os
.pardir
))
286 # Each file under path/
287 for filename
in sorted(os
.listdir(path
)):
288 full_path
= os
.path
.join(path
, filename
)
289 (file_root
, file_ext
) = os
.path
.splitext(full_path
)
290 if os
.path
.isdir(full_path
) and not full_path
.endswith('.xcodeproj'):
291 html
.Append('<li><a href="/%s/">%s/</a>' % (full_path
, filename
))
292 elif file_ext
in ['.json', '.idl']:
293 # cc/h panes will automatically update via the hash change event.
294 html
.Append('<li><a href="#%s">%s</a>' %
295 (filename
, filename
))
302 class PreviewHTTPServer(HTTPServer
, object):
308 cpp_namespace_pattern
):
309 super(PreviewHTTPServer
, self
).__init
__(server_address
, handler
)
310 self
.highlighters
= highlighters
311 self
.include_rules
= include_rules
312 self
.cpp_namespace_pattern
= cpp_namespace_pattern
315 if __name__
== '__main__':
316 parser
= optparse
.OptionParser(
317 description
='Runs a server to preview the json_schema_compiler output.',
318 usage
='usage: %prog [option]...')
319 parser
.add_option('-p', '--port', default
='8000',
320 help='port to run the server on')
321 parser
.add_option('-n', '--namespace', default
='generated_api_schemas',
322 help='C++ namespace for generated files. e.g extensions::api.')
323 parser
.add_option('-I', '--include-rules',
324 help='A list of paths to include when searching for referenced objects,'
325 ' with the namespace separated by a \':\'. Example: '
326 '/foo/bar:Foo::Bar::%(namespace)s')
328 (opts
, argv
) = parser
.parse_args()
330 def split_path_and_namespace(path_and_namespace
):
331 if ':' not in path_and_namespace
:
332 raise ValueError('Invalid include rule "%s". Rules must be of '
333 'the form path:namespace' % path_and_namespace
)
334 return path_and_namespace
.split(':', 1)
337 if opts
.include_rules
:
338 include_rules
= map(split_path_and_namespace
,
339 shlex
.split(opts
.include_rules
))
342 print('Starting previewserver on port %s' % opts
.port
)
343 print('The extension documentation can be found at:')
345 print(' http://localhost:%s/chrome/common/extensions/api' % opts
.port
)
349 'hilite': hilite_me_highlighter
.HiliteMeHighlighter(),
350 'none': none_highlighter
.NoneHighlighter()
353 highlighters
['pygments'] = pygments_highlighter
.PygmentsHighlighter()
354 except ImportError as e
:
357 server
= PreviewHTTPServer(('', int(opts
.port
)),
362 server
.serve_forever()
363 except KeyboardInterrupt:
364 server
.socket
.close()