1a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)#!/usr/bin/env python
2a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)# Copyright 2014 The Chromium Authors. All rights reserved.
3a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)# Use of this source code is governed by a BSD-style license that can be
4a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)# found in the LICENSE file.
5a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)
6a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)"""Builds the complete main.html file from the basic components.
7a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)"""
8a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)
9a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)from HTMLParser import HTMLParser
10116680a4aac90f2aa7413d9095a592090648e557Ben Murdochimport argparse
11a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)import os
12a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)import re
13a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)import sys
14a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)
15a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)
16a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)def error(msg):
17a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)  print 'Error: %s' % msg
18a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)  sys.exit(1)
19a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)
20a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)
21a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)class HtmlChecker(HTMLParser):
22a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)  def __init__(self):
23a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    HTMLParser.__init__(self)
24a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    self.ids = set()
25a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)
26a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)  def handle_starttag(self, tag, attrs):
27a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    for (name, value) in attrs:
28a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)      if name == 'id':
29a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)        if value in self.ids:
30a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)          error('Duplicate id: %s' % value)
31a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)        self.ids.add(value)
32a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)
33a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)
34a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)class GenerateWebappHtml:
351320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci  def __init__(self, template_files, js_files, instrumented_js_files,
361320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci               template_rel_dir):
37116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch
38a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    self.js_files = js_files
39116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch    self.instrumented_js_files = instrumented_js_files
401320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci    self.template_rel_dir = template_rel_dir
41a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)
42a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    self.templates_expected = set()
43a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    for template in template_files:
44a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)      self.templates_expected.add(os.path.basename(template))
45a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)
46a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    self.templates_found = set()
47a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)
48a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)  def includeJavascript(self, output):
49a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    for js_path in sorted(self.js_files):
50a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)      js_file = os.path.basename(js_path)
51a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)      output.write('    <script src="' + js_file + '"></script>\n')
52a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)
53116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch    for js_path in sorted(self.instrumented_js_files):
54116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch      js_file = os.path.basename(js_path)
55116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch      output.write('    <script src="' + js_file + '" data-cover></script>\n')
56116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch
57a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)  def verifyTemplateList(self):
58a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    """Verify that all the expected templates were found."""
59a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    if self.templates_expected > self.templates_found:
60a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)      extra = self.templates_expected - self.templates_found
61a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)      print 'Extra templates specified:', extra
62a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)      return False
63a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    return True
64a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)
65a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)  def validateTemplate(self, template_path):
66a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    template = os.path.basename(template_path)
67a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    if template in self.templates_expected:
68a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)      self.templates_found.add(template)
69a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)      return True
70a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    return False
71a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)
72a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)  def processTemplate(self, output, template_file, indent):
731320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci    with open(os.path.join(self.template_rel_dir, template_file), 'r') as \
741320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci        input_template:
75a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)      first_line = True
76a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)      skip_header_comment = False
77a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)
78116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch      for line in input_template:
79a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)        # If the first line is the start of a copyright notice, then
80a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)        # skip over the entire comment.
81a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)        # This will remove the copyright info from the included files,
82a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)        # but leave the one on the main template.
83a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)        if first_line and re.match(r'<!--', line):
84a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)          skip_header_comment = True
85a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)        first_line = False
86a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)        if skip_header_comment:
87a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)          if re.search(r'-->', line):
88a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)            skip_header_comment = False
89a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)          continue
90a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)
91a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)        m = re.match(
92a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)            r'^(\s*)<meta-include src="(.+)"\s*/>\s*$',
93a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)            line)
94a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)        if m:
95a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)          prefix = m.group(1)
96a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)          template_name = m.group(2)
97a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)          if not self.validateTemplate(template_name):
98a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)            error('Found template not in list of expected templates: %s' %
99a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)                  template_name)
100a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)          self.processTemplate(output, template_name, indent + len(prefix))
101a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)          continue
102a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)
103a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)        m = re.match(r'^\s*<meta-include type="javascript"\s*/>\s*$', line)
104a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)        if m:
105a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)          self.includeJavascript(output)
106a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)          continue
107a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)
108a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)        if line.strip() == '':
109a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)          output.write('\n')
110a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)        else:
111a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)          output.write((' ' * indent) + line)
112a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)
113a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)
114116680a4aac90f2aa7413d9095a592090648e557Ben Murdochdef parseArgs():
115116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch  parser = argparse.ArgumentParser()
116116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch  parser.add_argument(
117116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch    '--js', nargs='+', help='The Javascript files to include in HTML <head>')
118116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch  parser.add_argument(
119116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch    '--templates',
120116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch    nargs='*',
121116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch    default=[],
122116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch    help='The html template files used by input-template')
123116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch  parser.add_argument(
124116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch    '--exclude-js',
125116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch    nargs='*',
126116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch    default=[],
127116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch    help='The Javascript files to exclude from <--js> and <--instrumentedjs>')
128116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch  parser.add_argument(
129116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch    '--instrument-js',
130116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch    nargs='*',
131116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch    default=[],
132116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch    help='Javascript to include and instrument for code coverage')
1331320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci  parser.add_argument(
1341320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci    '--dir-for-templates',
1351320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci    default = ".",
1361320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci    help='Directory template references in html are relative to')
137116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch  parser.add_argument('output_file')
138116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch  parser.add_argument('input_template')
139116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch  return parser.parse_args(sys.argv[1:])
140a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)
141a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)
142a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)def main():
143116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch  args = parseArgs()
144116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch
145116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch  out_file = args.output_file
146116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch  js_files = set(args.js) - set(args.exclude_js)
147116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch
148116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch  # Create the output directory if it does not exist.
149116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch  out_directory = os.path.dirname(out_file)
150116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch  if not os.path.exists(out_directory):
151116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch    os.makedirs(out_directory)
152a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)
153a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)  # Generate the main HTML file from the templates.
154a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)  with open(out_file, 'w') as output:
1551320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci    gen = GenerateWebappHtml(args.templates, js_files, args.instrument_js,
1561320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci                             args.dir_for_templates)
157116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch    gen.processTemplate(output, args.input_template, 0)
158a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)
159a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    # Verify that all the expected templates were found.
160a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    if not gen.verifyTemplateList():
161a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)      error('Extra templates specified')
162a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)
163a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)  # Verify that the generated HTML file is valid.
164116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch  with open(out_file, 'r') as input_html:
165a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    parser = HtmlChecker()
166116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch    parser.feed(input_html.read())
167a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)
168a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)
169a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)if __name__ == '__main__':
170a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)  sys.exit(main())
171