Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(207)

Unified Diff: tools/testing/legpad/legpad.py

Issue 9866022: legpad.py (python script that generates legpad.html files) (Closed) Base URL: https://dart.googlecode.com/svn/branches/bleeding_edge/dart
Patch Set: throw on compilation error Created 8 years, 9 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
« tools/testing/legpad/legpad.dart ('K') | « tools/testing/legpad/legpad.dart ('k') | no next file » | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
Index: tools/testing/legpad/legpad.py
diff --git a/tools/testing/legpad/legpad.py b/tools/testing/legpad/legpad.py
new file mode 100644
index 0000000000000000000000000000000000000000..951f3009997c3a3922080ecb2f6b267465510906
--- /dev/null
+++ b/tools/testing/legpad/legpad.py
@@ -0,0 +1,368 @@
+#!/usr/bin/env python
ahe 2012/03/30 08:55:32 I'm saddened by how much code is in this CL that i
+
+# Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file
+# for details. All rights reserved. Use of this source code is governed by a
+# BSD-style license that can be found in the LICENSE file.
+
+"""
+Legpad is used to compile .dart files to javascript, using the dart2js compiler.
+
+This is accomplished by creating an html file (usually called
+<something>.legpad.html) that executes the dart2js compiler when the page
ahe 2012/03/30 08:55:32 legpad.py requires a file in this directory named
+is loaded by a web browser (or DumpRenderTree).
+
+The <something>.legpad.html file contains:
+
+ 1. all the dart files that compose a user's dart program
+ 2. all the dart files of dart:core and other standard dart libraries
+ (or any other symbol that can follow "dart:" in an import statement
+ 3. legpad.dart (compiled to javascript)
+
+The contents of each dart file is placed in a separate <script> tag.
+
+When the html page is loaded by a browser, the leg compiler is invoked
+and the dart program is compiled to javascript. The generated javascript is
+placed in a <pre> element with id "output".
+
+When the html page is passed to DumpRenderTree, the dumped output will
+have the generated javascript.
+
+See 'example.sh' for an example of how to run legpad.
+"""
+
+import logging
ahe 2012/03/30 08:55:32 Seriously?
+import optparse
+import os.path
+import platform
+import re
+import subprocess
+import sys
+
+
+class FileNotFoundException(Exception):
ahe 2012/03/30 08:55:32 This seems excessive. The exception is never caugh
+ def __init__(self, file_name):
+ self._name = file_name
+
+ def __str__(self):
+ return self._name
+
+
+class CommandFailedException(Exception):
ahe 2012/03/30 08:55:32 Ditto.
+ def __init__(self, message):
+ self._message = message
+
+ def GetMessage(self):
+ return self._message
+
+
+# Template for the legpad.html page we're going to generate.
+HTML = """<!DOCTYPE html>
ahe 2012/03/30 08:55:32 It is confusing that you have HTML and html variab
+<html>
+<head>
+ <style type="text/css">
+ textarea {
+ width: 100%;
+ height: 200px;
+ }
+ .label {
+ margin-top: 5px;
+ }
+ pre {
+ border: 2px solid black;
+ }
+ </style>
+ {{script_tags}}
ahe 2012/03/30 08:55:32 Why not %(script_tags)?
+ <script type="text/javascript">
+ if (window.layoutTestController) {
+ layoutTestController.dumpAsText();
+ }
+ </script>
+</head>
+<body>
+ <h1>Legpad</h1>
+ <div class="label">Input:</div>
+ <textarea id="input"></textarea>
+ <div class="label">Compiler Messages:</div>
+ <pre id="warnings"></pre>
+ <div class="label">Timing:</div>
+ <pre id="timing"></pre>
+ <div class="label">Output:</div>
+ <pre id="output"></pre>
+ <script type="text/javascript">
+ {{LEGPAD_JS}}
+ </script>
+</body>
+</html>
+"""
+
+# This finds everything after the word "Output:" in the html page.
+# (Note, because the javascript we're fishing out spans multiple lines
+# we need to use the DOTALL switch here.)
+OUTPUT_JAVASCRIPT_REGEX = re.compile(".*\nOutput:\n(.*)\n#EOF", re.DOTALL)
+
+# If the legpad.dart encounters a compilation error, the generated
+# javascript will contains the words "legpad compilation error".
+COMPILATION_ERROR_REGEX = re.compile(".*legpad compilation error.*", re.DOTALL)
ahe 2012/03/30 08:55:33 legpad -> dart2js.
+
+# We use "application/inert" here to make the browser ignore the
+# these script tags. (legpad.dart will fish out the contents as needed.)
+#
+SCRIPT_TAG = """<script type="application/inert" id="{{id}}">
+{{contents}}
+</script>
+"""
+
+# Regex that finds #import, #source and #native directives in .dart files.
+# match.group(1) = "import", "source" or "native"
+# match.group(2) = url of file being imported
+DIRECTIVE_RE = re.compile(r"^#(import|source|native)\([\"']([^\"']*)[\"']")
+
+# id of script tag that holds name of the top dart file to be compiled,
ahe 2012/03/30 08:55:33 Not a proper sentence.
+# (This file name passed will be passed to the leg compiler by legpad.dart.)
+MAIN_ID = "main_id"
+
+# TODO(mattsh): read this from some config file once ahe/zundel create it
ahe 2012/03/30 08:55:33 Not a proper sentence.
+DART_LIBRARIES = {
+ "core" : "lib/compiler/implementation/lib/core.dart",
+ "coreimpl" : "lib/compiler/implementation/lib/coreimpl.dart",
+ "dom" : "lib/dom/frog/dom_frog.dart",
+ "html" : "lib/html/frog/html_frog.dart",
+ "io" : "lib/compiler/implementation/lib/io.dart",
+ "isolate" : "lib/isolate/isolate_leg.dart",
+ "json" : "lib/json/json.dart",
+ "uri" : "lib/uri/uri.dart",
+ "utf" : "lib/utf/utf.dart",
+}
+
+class Pad(object):
+ """
+ Accumulates all source files that are needed to compile a dart program,
+ and places them in <script> tags on an html page.
+ """
+
+ def __init__(self, argv):
+ parser = optparse.OptionParser(usage=
+ "%prog [options] file_to_compile.dart"
ahe 2012/03/30 08:55:33 Inconsistent formatting of arguments.
+ )
+ parser.add_option("-o", "--out",
+ help="name of javascript output file")
+ parser.add_option("-v", "--verbose", action="store_true",
+ help="more verbose logging")
+ (options, args) = parser.parse_args(argv)
+
+ log_level = logging.INFO
+ if options.verbose:
+ log_level = logging.DEBUG
+ logging.basicConfig(level=log_level)
+
+ if len(args) < 2:
+ parser.print_help()
+ sys.exit(1)
ahe 2012/03/30 08:55:33 I generally dislike constructors that do anything
+
+ self.main_file = os.path.abspath(args[1])
+
+ # directory of this script
+ self.legpad_dir = os.path.abspath(os.path.dirname(argv[0]))
+
+ # root of dart source repo
+ self.dart_dir = os.path.dirname(os.path.dirname(os.path.dirname(
+ self.legpad_dir)))
+
+ logging.debug("dart_dir: '%s'" % self.dart_dir)
+
+ if options.out:
+ # user has specified an output file name
+ self.js_file = os.path.abspath(options.out)
+ else:
+ # User didn't specify an output file, so use the input
+ # file name as the base of the output file name.
+ self.js_file = self.main_file + ".legpad.js"
ahe 2012/03/30 08:55:33 I don't like the default of creating "junk" at ran
+
+ logging.debug("js_file: '%s" % self.js_file)
+
+ # this is the html file that we pass to DumpRenderTree
ahe 2012/03/30 08:55:33 Not proper sentence.
+ self.html_file = self.main_file + ".legpad.html"
ahe 2012/03/30 08:55:33 Also creates junk in the source directory.
+ logging.debug("html_file: '%s'" % self.html_file)
+
+ # map from file name to File object (contains entries for all corelib
ahe 2012/03/30 08:55:33 Ditto.
+ # and all other dart files needed to compile main_file)
+ self.name_to_file = {}
+
+ # map from script tag id to File object
ahe 2012/03/30 08:55:33 Not a proper sentence.
+ self.id_to_file = {}
+
+ self.load_libraries()
ahe 2012/03/30 08:55:33 I don't think most of the following code belongs i
+ self.load_file(self.main_file)
+
+ html = self.generate_html()
+ write_file(self.html_file, html)
+
+ js = self.generate_js()
+ write_file(self.js_file, js)
+
+ line_count = len(js.splitlines())
+ logging.debug("generated '%s' (%d lines)", self.js_file, line_count)
+
+ match = COMPILATION_ERROR_REGEX.match(js)
+ if match:
+ sys.exit(1)
+
+ def generate_html(self):
ahe 2012/03/30 08:55:33 Our style guide says MethodName, not method_name.
+ tags = []
+ for f in self.id_to_file.values():
+ tags.append(self._create_tag(f.id, f.contents))
+ tags.append(self._create_tag(MAIN_ID, self.shorten(self.main_file)))
ahe 2012/03/30 08:55:33 I thought this was a bug.
+ html = HTML.replace("{{script_tags}}", "".join(tags))
ahe 2012/03/30 08:55:33 Why not: html = HTML % {'script_tags': "".join(ta
+
+ legpad_js = os.path.join(self.legpad_dir, "legpad.dart.js")
+ check_exists(legpad_js)
+
+ html = html.replace("{{LEGPAD_JS}}", read_file(legpad_js))
+ return html
+
+ def generate_js(self):
ahe 2012/03/30 08:55:33 MethodName
+ drt = os.path.join(self.dart_dir, "client/tests/drt/DumpRenderTree")
+ if platform.system() == 'Darwin':
+ drt += ".app"
ahe 2012/03/30 08:55:33 This is not enough, please see my previous comment
+ elif platform.system() == 'Windows':
+ raise Exception("legpad does not run on Windows")
ahe 2012/03/30 08:55:33 Actually, you should not use Exception for this. S
+
+ check_exists(drt)
+ args = []
+ args.append(drt)
+ args.append(self.html_file)
+
+ stdout = run_command(args)
+ match = OUTPUT_JAVASCRIPT_REGEX.match(stdout)
+ if not match:
+ raise Exception("can't find regex in DumpRenderTree output")
+ return match.group(1)
+
+ @staticmethod
+ def _create_tag(id, contents):
ahe 2012/03/30 08:55:33 _MethodName, and did you mean private or protected
+ s = SCRIPT_TAG
+ s = s.replace("{{id}}", id)
ahe 2012/03/30 08:55:33 Why not use Python's % operator?
+ # TODO(mattsh) - need to html escape here
ahe 2012/03/30 08:55:33 Formatting of comment.
+ s = s.replace("{{contents}}", contents)
ahe 2012/03/30 08:55:33 %
+ return s
+
+ def dart_library(self, name):
ahe 2012/03/30 08:55:33 MethodName
+ path = DART_LIBRARIES[name]
+ if not path:
+ raise Exception("unrecognized 'dart:%s'", name)
ahe 2012/03/30 08:55:33 Don't raise Exception.
+ return os.path.join(self.dart_dir, path)
+
+ def load_libraries(self):
ahe 2012/03/30 08:55:33 MethodName
+ for name in DART_LIBRARIES:
+ self.load_file(self.dart_library(name))
+
+ def load_file(self, name):
ahe 2012/03/30 08:55:33 MethodName
+ name = os.path.abspath(name)
+ if name in self.name_to_file:
+ return
+ f = File(self, name)
+ self.name_to_file[f.name] = f
+ if f.id in self.id_to_file:
+ raise Exception("ambiguous id '%s'" % f.id)
ahe 2012/03/30 08:55:33 Exception
+ self.id_to_file[f.id] = f
+ f.directives()
+
+ def shorten(self, name):
ahe 2012/03/30 08:55:33 MethodName
+ """
+ Change that full path of the dart svn repo to simply "dartdir"
+ """
+ return name.replace(self.dart_dir, "dartdir")
+
+ def make_id(self, name):
ahe 2012/03/30 08:55:33 MethodName.
+ """
+ Generates an id (based on the file name) for the <script> tag that will
+ hold the contents of this file.
+ """
+ return self.shorten(name).replace("/", "_").replace(".", "_")
ahe 2012/03/30 08:55:33 This doesn't work on Windows. Adding .replace(os.s
+
+
+class File(object):
+ def __init__(self, pad, name):
+ self.pad = pad
+ self.name = name
+ self.id = pad.make_id(name)
+ check_exists(name)
+ with open(self.name, "r") as f:
ahe 2012/03/30 08:55:33 Why don't use read_file below?
+ self.contents = f.read()
+
+ def directives(self):
ahe 2012/03/30 08:55:33 MethodName.
+ """Load files referenced by #source, #import and #native directives."""
+ lines = self.contents.split("\n")
+ self.line_number = 0
+ for line in lines:
+ self.line_number += 1
+ self._directive(line)
+
+ def _directive(self, line):
ahe 2012/03/30 08:55:33 _MethodName. Is this method protected or private?
+ match = DIRECTIVE_RE.match(line)
+ if not match:
+ return
+ url = match.group(2)
+ if url.startswith("dart:"):
+ path = self.pad.dart_library(url[len("dart:"):])
+ else:
+ path = os.path.join(os.path.dirname(self.name), url)
+ self.pad.load_file(path)
+
+
+def read_file(file_name):
ahe 2012/03/30 08:55:33 MethodName
+ check_exists(file_name)
+ with open(file_name, "r") as input:
+ contents = input.read()
+ logging.debug("read_file '%s' (%d bytes)" % (file_name, len(contents)))
+ return contents
+
+
+def write_file(file_name, contents):
ahe 2012/03/30 08:55:33 MethodName
+ with open(file_name, "w") as output:
+ output.write(contents)
+
+ check_exists(file_name)
+ logging.debug("write_file '%s' (%d bytes)" % (file_name, len(contents)))
+
+
+def check_exists(file_name):
ahe 2012/03/30 08:55:33 MethodName
+ if not os.path.exists(file_name):
+ raise FileNotFoundException(file_name)
+
+
+def format_command(args):
ahe 2012/03/30 08:55:33 MethodName
+ return ' '.join(args)
+
+
+def run_command(args):
ahe 2012/03/30 08:55:33 MethodName
+ """
+ Args:
+ command: comamnd with arguments to exec
+ Returns:
+ all output that this command sent to stdout
+ """
+
+ command = format_command(args)
+ logging.info("RUNNING: '%s'" % command)
+ child = subprocess.Popen(args,
+ stdout=subprocess.PIPE,
+ stderr=subprocess.PIPE,
+ close_fds=True)
+ (stdout, stderr) = child.communicate()
+ exit_code = child.wait()
+ if exit_code:
+ for line in stderr.splitlines():
+ logging.info(line)
+ msg = "FAILURE (exit_code=%d): '%s'" % (exit_code, command)
+ logging.error(msg)
+ raise CommandFailedException(msg)
+ logging.debug("SUCCEEDED (%d bytes)" % len(stdout))
+ return stdout
+
+
+def main(argv):
+ Pad(argv)
+
+if __name__ == "__main__":
+ sys.exit(main(sys.argv))
« tools/testing/legpad/legpad.dart ('K') | « tools/testing/legpad/legpad.dart ('k') | no next file » | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698