Upstream version 10.39.225.0
[platform/framework/web/crosswalk.git] / src / tools / json_schema_compiler / preview.py
1 #!/usr/bin/env python
2
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 """
8
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
26
27
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)
34
35     chromium_favicon = 'http://codereview.chromium.org/static/favicon.ico'
36
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)
40
41     body = code.Code()
42
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>')
54
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)
62
63   def _ShowPanels(self, parsed_url, head, body):
64     """Show the previewer frame structure.
65
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>')
92     )
93
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:])
99     )
100
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]);
110 }
111
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;
116
117   // Cache in localStorage for when the page loads next.
118   localStorage.highlightersValue = highlighterName;
119
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')
128     }
129
130     // Cache in localStorage for when the page next loads.
131     localStorage[highlighterStyle.id + 'Value'] = highlighterStyle.value;
132   });
133
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;
143       }
144       document.getElementById(elementId).innerHTML = xhr.responseText;
145     };
146     xhr.open('GET', requestPath, true);
147     xhr.send();
148   }
149
150   var targetName = window.location.hash;
151   targetName = targetName.substring('#'.length);
152   targetName = targetName.split('.', 1)[0]
153
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);
160   }
161 }
162
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;
169
170 forEachHighlighterStyle(function(highlighterStyle) {
171   var cachedValue = localStorage[highlighterStyle.id + 'Value'];
172   if (cachedValue)
173     highlighterStyle.value = cachedValue;
174 });
175 })();
176
177 window.addEventListener('hashchange', updateEverything, false);
178 updateEverything();
179 </script>''')
180
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.
184     """
185     api_model = model.Model()
186
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)
190
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)
202
203       # Generate code
204       cpp_namespace = 'generated_api_schemas'
205       if file_ext == '.h':
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())
211       else:
212         self.send_error(404, "File not found: %s" % request_path)
213         return
214
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) +
219           '</style>')
220       body.Append(self.server.highlighters[highlighter_param]
221           .GetCodeElement(cpp_code, style_param))
222     except IOError:
223       self.send_error(404, "File not found: %s" % request_path)
224       return
225     except (TypeError, KeyError, AttributeError,
226         AssertionError, NotImplementedError) as error:
227       body.Append('<pre>')
228       body.Append('compiler error: %s' % error)
229       body.Append('Check server log for more details')
230       body.Append('</pre>')
231       raise
232
233   def _GetHighlighterParams(self, parsed_url):
234     """Get the highlighting parameters from a parsed url.
235     """
236     query_dict = urlparse.parse_qs(parsed_url.query)
237     return (query_dict.get('highlighter', ['pygments'])[0],
238         query_dict.get('style', ['colorful'])[0])
239
240   def _RenderNavPane(self, path):
241     """Renders an HTML nav pane.
242
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.
246     """
247     html = code.Code()
248
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>')
255
256     html.Append('<br/>')
257
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())
262       if not styles:
263         continue
264
265       html.Append('<select class="highlighter_styles" id="%s_styles" '
266                   'onChange="updateEverything()">' % name)
267       for style in styles:
268         html.Append('<option>%s</option>' % style)
269       html.Append('</select>')
270
271     html.Append('<br/>')
272
273     # The files, with appropriate handlers.
274     html.Append('<ul>')
275
276     # Make path point to a non-empty directory. This can happen if a URL like
277     # http://localhost:8000 is navigated to.
278     if path == '':
279       path = os.curdir
280
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))
285
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))
296
297     html.Append('</ul>')
298
299     return html.Render()
300
301
302 class PreviewHTTPServer(HTTPServer, object):
303   def __init__(self,
304                server_address,
305                handler,
306                highlighters,
307                include_rules,
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
313
314
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')
327
328   (opts, argv) = parser.parse_args()
329
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)
335
336   include_rules = []
337   if opts.include_rules:
338     include_rules = map(split_path_and_namespace,
339                         shlex.split(opts.include_rules))
340
341   try:
342     print('Starting previewserver on port %s' % opts.port)
343     print('The extension documentation can be found at:')
344     print('')
345     print('  http://localhost:%s/chrome/common/extensions/api' % opts.port)
346     print('')
347
348     highlighters = {
349       'hilite': hilite_me_highlighter.HiliteMeHighlighter(),
350       'none': none_highlighter.NoneHighlighter()
351     }
352     try:
353       highlighters['pygments'] = pygments_highlighter.PygmentsHighlighter()
354     except ImportError as e:
355       pass
356
357     server = PreviewHTTPServer(('', int(opts.port)),
358                                CompilerHandler,
359                                highlighters,
360                                include_rules,
361                                opts.namespace)
362     server.serve_forever()
363   except KeyboardInterrupt:
364     server.socket.close()