1#!/usr/bin/env python
2# Copyright 2014 The Chromium Authors. All rights reserved.
3# Use of this source code is governed by a BSD-style license that can be
4# found in the LICENSE file.
5
6"""Builds the complete main.html file from the basic components.
7"""
8
9from HTMLParser import HTMLParser
10import argparse
11import os
12import re
13import sys
14
15
16def error(msg):
17  print 'Error: %s' % msg
18  sys.exit(1)
19
20
21class HtmlChecker(HTMLParser):
22  def __init__(self):
23    HTMLParser.__init__(self)
24    self.ids = set()
25
26  def handle_starttag(self, tag, attrs):
27    for (name, value) in attrs:
28      if name == 'id':
29        if value in self.ids:
30          error('Duplicate id: %s' % value)
31        self.ids.add(value)
32
33
34class GenerateWebappHtml:
35  def __init__(self, template_files, js_files, instrumented_js_files,
36               template_rel_dir):
37
38    self.js_files = js_files
39    self.instrumented_js_files = instrumented_js_files
40    self.template_rel_dir = template_rel_dir
41
42    self.templates_expected = set()
43    for template in template_files:
44      self.templates_expected.add(os.path.basename(template))
45
46    self.templates_found = set()
47
48  def includeJavascript(self, output):
49    for js_path in sorted(self.js_files):
50      js_file = os.path.basename(js_path)
51      output.write('    <script src="' + js_file + '"></script>\n')
52
53    for js_path in sorted(self.instrumented_js_files):
54      js_file = os.path.basename(js_path)
55      output.write('    <script src="' + js_file + '" data-cover></script>\n')
56
57  def verifyTemplateList(self):
58    """Verify that all the expected templates were found."""
59    if self.templates_expected > self.templates_found:
60      extra = self.templates_expected - self.templates_found
61      print 'Extra templates specified:', extra
62      return False
63    return True
64
65  def validateTemplate(self, template_path):
66    template = os.path.basename(template_path)
67    if template in self.templates_expected:
68      self.templates_found.add(template)
69      return True
70    return False
71
72  def processTemplate(self, output, template_file, indent):
73    with open(os.path.join(self.template_rel_dir, template_file), 'r') as \
74        input_template:
75      first_line = True
76      skip_header_comment = False
77
78      for line in input_template:
79        # If the first line is the start of a copyright notice, then
80        # skip over the entire comment.
81        # This will remove the copyright info from the included files,
82        # but leave the one on the main template.
83        if first_line and re.match(r'<!--', line):
84          skip_header_comment = True
85        first_line = False
86        if skip_header_comment:
87          if re.search(r'-->', line):
88            skip_header_comment = False
89          continue
90
91        m = re.match(
92            r'^(\s*)<meta-include src="(.+)"\s*/>\s*$',
93            line)
94        if m:
95          prefix = m.group(1)
96          template_name = m.group(2)
97          if not self.validateTemplate(template_name):
98            error('Found template not in list of expected templates: %s' %
99                  template_name)
100          self.processTemplate(output, template_name, indent + len(prefix))
101          continue
102
103        m = re.match(r'^\s*<meta-include type="javascript"\s*/>\s*$', line)
104        if m:
105          self.includeJavascript(output)
106          continue
107
108        if line.strip() == '':
109          output.write('\n')
110        else:
111          output.write((' ' * indent) + line)
112
113
114def parseArgs():
115  parser = argparse.ArgumentParser()
116  parser.add_argument(
117    '--js', nargs='+', help='The Javascript files to include in HTML <head>')
118  parser.add_argument(
119    '--templates',
120    nargs='*',
121    default=[],
122    help='The html template files used by input-template')
123  parser.add_argument(
124    '--exclude-js',
125    nargs='*',
126    default=[],
127    help='The Javascript files to exclude from <--js> and <--instrumentedjs>')
128  parser.add_argument(
129    '--instrument-js',
130    nargs='*',
131    default=[],
132    help='Javascript to include and instrument for code coverage')
133  parser.add_argument(
134    '--dir-for-templates',
135    default = ".",
136    help='Directory template references in html are relative to')
137  parser.add_argument('output_file')
138  parser.add_argument('input_template')
139  return parser.parse_args(sys.argv[1:])
140
141
142def main():
143  args = parseArgs()
144
145  out_file = args.output_file
146  js_files = set(args.js) - set(args.exclude_js)
147
148  # Create the output directory if it does not exist.
149  out_directory = os.path.dirname(out_file)
150  if not os.path.exists(out_directory):
151    os.makedirs(out_directory)
152
153  # Generate the main HTML file from the templates.
154  with open(out_file, 'w') as output:
155    gen = GenerateWebappHtml(args.templates, js_files, args.instrument_js,
156                             args.dir_for_templates)
157    gen.processTemplate(output, args.input_template, 0)
158
159    # Verify that all the expected templates were found.
160    if not gen.verifyTemplateList():
161      error('Extra templates specified')
162
163  # Verify that the generated HTML file is valid.
164  with open(out_file, 'r') as input_html:
165    parser = HtmlChecker()
166    parser.feed(input_html.read())
167
168
169if __name__ == '__main__':
170  sys.exit(main())
171