| Index: client/tools/htmlconverter.py
|
| ===================================================================
|
| --- client/tools/htmlconverter.py (revision 6168)
|
| +++ client/tools/htmlconverter.py (working copy)
|
| @@ -1,558 +0,0 @@
|
| -# Copyright (c) 2011, 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.
|
| -
|
| -#!/usr/bin/env python
|
| -#
|
| -
|
| -"""Rewrites HTML files, converting Dart script sections into JavaScript.
|
| -
|
| -Process HTML files, and internally changes script sections that use Dart code
|
| -into JavaScript sections. It also can optimize the HTML to inline code.
|
| -"""
|
| -
|
| -from HTMLParser import HTMLParser
|
| -import os.path
|
| -from os.path import abspath, basename, dirname, exists, isabs, join
|
| -import base64, re, optparse, os, shutil, subprocess, sys, tempfile, codecs
|
| -import urllib2
|
| -
|
| -CLIENT_PATH = dirname(dirname(abspath(__file__)))
|
| -DART_PATH = dirname(CLIENT_PATH)
|
| -TOOLS_PATH = join(DART_PATH, 'tools')
|
| -
|
| -sys.path.append(TOOLS_PATH)
|
| -import utils
|
| -
|
| -DART_MIME_TYPE = "application/dart"
|
| -LIBRARY_PATTERN = "^#library\(.*\);"
|
| -IMPORT_SOURCE_MATCHER = re.compile(
|
| - r"^ *(#import|#source)(\(['\"])([^'\"]*)(.*\);)", re.MULTILINE)
|
| -DOM_IMPORT_MATCHER = re.compile(
|
| - r"^#import\(['\"]dart\:dom['\"].*\);", re.MULTILINE)
|
| -HTML_IMPORT_MATCHER = re.compile(
|
| - r"^#import\(['\"]dart\:html['\"].*\);", re.MULTILINE)
|
| -
|
| -FROG_NOT_FOUND_ERROR = (
|
| -"""Couldn't find compiler: please run the following commands:
|
| - $ cd %s/frog
|
| - $ ./tools/build.py -m release""")
|
| -
|
| -ENTRY_POINT = """
|
| -#library('entry');
|
| -#import('%s', prefix: 'original');
|
| -main() => original.main();
|
| -"""
|
| -
|
| -CSS_TEMPLATE = '<style type="text/css">%s</style>'
|
| -CHROMIUM_SCRIPT_TEMPLATE = '<script type="application/javascript">%s</script>'
|
| -
|
| -DARTIUM_TO_JS_SCRIPT = """
|
| -<script type="text/javascript">
|
| - (function() {
|
| - // Let the user know that Dart is required.
|
| - if (!window.navigator.webkitStartDart) {
|
| - if (confirm(
|
| - "You are trying to run Dart code on a browser " +
|
| - "that doesn't support Dart. Do you want to redirect to " +
|
| - "a version compiled to JavaScript instead?")) {
|
| - var addr = window.location;
|
| - window.location = addr.toString().replace('-dart.html', '-js.html');
|
| - }
|
| - } else {
|
| - window.navigator.webkitStartDart();
|
| - }
|
| -})();
|
| -</script>
|
| -"""
|
| -
|
| -def adjustImports(contents):
|
| - def repl(matchobj):
|
| - path = matchobj.group(3)
|
| - if not path.startswith('dart:'):
|
| - path = abspath(path)
|
| - return (matchobj.group(1) + matchobj.group(2) + path + matchobj.group(4))
|
| - return IMPORT_SOURCE_MATCHER.sub(repl, contents)
|
| -
|
| -class DartCompiler(object):
|
| - """ Common code for compiling Dart script tags in an HTML file. """
|
| -
|
| - def __init__(self, verbose=False,
|
| - extra_flags=""):
|
| - self.verbose = verbose
|
| - self.extra_flags = extra_flags
|
| -
|
| - def compileCode(self, src=None, body=None):
|
| - """ Compile the given source code.
|
| -
|
| - Either the script tag has a src attribute or a non-empty body (one of the
|
| - arguments will be none, the other is not).
|
| -
|
| - Args:
|
| - src: a string pointing to a Dart script file.
|
| - body: a string containing Dart code.
|
| - """
|
| -
|
| - outdir = tempfile.mkdtemp()
|
| - indir = None
|
| - useDartHtml = False
|
| - if src is not None:
|
| - if body is not None and body.strip() != '':
|
| - raise ConverterException(
|
| - "The script body should be empty if src is specified")
|
| - elif src.endswith('.dart'):
|
| - indir = tempfile.mkdtemp()
|
| - inputfile = abspath(src)
|
| - with open(inputfile, 'r') as f:
|
| - contents = f.read();
|
| -
|
| - if HTML_IMPORT_MATCHER.search(contents):
|
| - useDartHtml = True
|
| -
|
| - # We will import the source file to emulate in JS that code is run after
|
| - # DOMContentLoaded. We need a #library to ensure #import won't fail:
|
| - if not re.search(LIBRARY_PATTERN, contents, re.MULTILINE):
|
| - inputfile = join(indir, 'code.dart')
|
| - with open(inputfile, 'w') as f:
|
| - f.write("#library('code');")
|
| - f.write(adjustImports(contents))
|
| -
|
| - else:
|
| - raise ConverterException("invalid file type:" + src)
|
| - else:
|
| - if body is None or body.strip() == '':
|
| - # nothing to do
|
| - print 'Warning: empty script tag with no src attribute'
|
| - return ''
|
| -
|
| - indir = tempfile.mkdtemp()
|
| - # eliminate leading spaces in front of directives
|
| - body = adjustImports(body)
|
| -
|
| - if HTML_IMPORT_MATCHER.search(body):
|
| - useDartHtml = True
|
| -
|
| - inputfile = join(indir, 'code.dart')
|
| - with open(inputfile, 'w') as f:
|
| - f.write("#library('inlinedcode');\n")
|
| - f.write(body)
|
| -
|
| - wrappedfile = join(indir, 'entry.dart')
|
| - with open(wrappedfile, 'w') as f:
|
| - f.write(ENTRY_POINT % inputfile)
|
| -
|
| - status, out, err = execute(self.compileCommand(wrappedfile, outdir),
|
| - self.verbose)
|
| - if status:
|
| - raise ConverterException('compilation errors')
|
| -
|
| - # Inline the compiled code in the page
|
| - with open(self.outputFileName(wrappedfile, outdir), 'r') as f:
|
| - res = f.read()
|
| -
|
| - # Cleanup
|
| - if indir is not None:
|
| - shutil.rmtree(indir)
|
| - shutil.rmtree(outdir)
|
| - return CHROMIUM_SCRIPT_TEMPLATE % res
|
| -
|
| - def compileCommand(self, inputfile, outdir):
|
| - binary = abspath(join(DART_PATH,
|
| - utils.GetBuildRoot(utils.GuessOS(),
|
| - 'release', 'ia32'),
|
| - 'frog', 'bin', 'frogsh'))
|
| - if not exists(binary):
|
| - raise ConverterException(FROG_NOT_FOUND_ERROR % DART_PATH)
|
| -
|
| - cmd = [binary, '--compile-only',
|
| - '--libdir=' + join(DART_PATH, 'frog', 'lib'),
|
| - '--out=' + self.outputFileName(inputfile, outdir)]
|
| - if self.extra_flags != "":
|
| - cmd.append(self.extra_flags);
|
| - cmd.append(inputfile)
|
| - return cmd
|
| -
|
| - def outputFileName(self, inputfile, outdir):
|
| - return join(outdir, basename(inputfile) + '.js')
|
| -
|
| -def execute(cmd, verbose=False):
|
| - """Execute a command in a subprocess. """
|
| - if verbose: print 'Executing: ' + ' '.join(cmd)
|
| - try:
|
| - pipe = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
| - output, err = pipe.communicate()
|
| - if pipe.returncode != 0:
|
| - print 'Execution failed: ' + output + '\n' + err
|
| - if verbose or pipe.returncode != 0:
|
| - print output
|
| - print err
|
| - return pipe.returncode, output, err
|
| - except Exception as e:
|
| - print 'Exception when executing: ' + ' '.join(cmd)
|
| - print e
|
| - return 1, None, None
|
| -
|
| -
|
| -def convertPath(project_path, prefix_path):
|
| - """ Convert a project path (whose root corresponds to the current working
|
| - directory) to a system path.
|
| - Args:
|
| - - project_path: path in the project context.
|
| - - prefix_path: prefix for relative paths.
|
| - """
|
| - if isabs(project_path):
|
| - # TODO(sigmund): add a flag to pass in the root-level for absolute paths.
|
| - return project_path[1:]
|
| - elif not (project_path.startswith('http://') or
|
| - project_path.startswith('https://')):
|
| - return join(prefix_path, project_path)
|
| - else:
|
| - return project_path
|
| -
|
| -def encodeImage(rootDir, filename):
|
| - """ Returns a base64 url encoding for an image """
|
| - filetype = filename[-3:]
|
| - if filetype == 'svg': filetype = 'svg+xml'
|
| - with open(join(rootDir, filename), 'r') as f:
|
| - return 'url(data:image/%s;charset=utf-8;base64,%s)' % (
|
| - filetype,
|
| - base64.b64encode(f.read()))
|
| -
|
| -def processCss(filename):
|
| - """ Reads and converts a css file by replacing all image refernces into
|
| - base64 encoded images.
|
| - """
|
| - css = open(filename, 'r').read()
|
| - cssDir = os.path.split(filename)[0]
|
| - def transformUrl(match):
|
| - imagefile = match.group(1)
|
| - # if the image is not local or can't be found, leave the url alone:
|
| - if (imagefile.startswith('http://')
|
| - or imagefile.startswith('https://')
|
| - or not exists(join(cssDir, imagefile))):
|
| - return match.group(0)
|
| - return encodeImage(cssDir, imagefile)
|
| -
|
| - pattern = 'url\((.*\.(svg|png|jpg|gif))\)'
|
| - return re.sub(pattern, transformUrl, css)
|
| -
|
| -class DartHTMLConverter(HTMLParser):
|
| - """ An HTML processor that inlines css and compiled dart code.
|
| -
|
| - Args:
|
| - - compiler: an implementation of DartAnyCompiler
|
| - - prefix_path: prefix for relative paths encountered in the HTML.
|
| - """
|
| - def __init__(self, compiler, prefix_path):
|
| - HTMLParser.__init__(self)
|
| - self.in_dart_tag = False
|
| - self.output = []
|
| - self.dart_inline_code = []
|
| - self.contains_dart = False
|
| - self.compiler = compiler
|
| - self.prefix_path = prefix_path
|
| -
|
| - def inlineCss(self, attrDic):
|
| - path = convertPath(attrDic['href'], self.prefix_path)
|
| - self.output.append(CSS_TEMPLATE % processCss(path))
|
| -
|
| - def compileScript(self, attrDic):
|
| - if 'src' in attrDic:
|
| - self.output.append(self.compiler.compileCode(
|
| - src=convertPath(attrDic.pop('src'), self.prefix_path),
|
| - body=None))
|
| - else:
|
| - self.in_dart_tag = True
|
| - # no tag is generated until we parse the body of the tag
|
| - self.dart_inline_code = []
|
| - return True
|
| -
|
| - def convertImage(self, attrDic):
|
| - pass
|
| -
|
| - def starttagHelper(self, tag, attrs, isEnd):
|
| - attrDic = dict(attrs)
|
| -
|
| - # collect all script files, and generate a single script before </body>
|
| - if (tag == 'script' and 'type' in attrDic
|
| - and (attrDic['type'] == DART_MIME_TYPE)):
|
| - if self.compileScript(attrDic):
|
| - return
|
| -
|
| - # convert css imports into inlined css
|
| - elif (tag == 'link' and
|
| - 'rel' in attrDic and attrDic['rel'] == 'stylesheet' and
|
| - 'type' in attrDic and attrDic['type'] == 'text/css' and
|
| - 'href' in attrDic):
|
| - self.inlineCss(attrDic)
|
| - return
|
| -
|
| - elif tag == 'img' and 'src' in attrDic:
|
| - self.convertImage(attrDic)
|
| -
|
| - # emit everything else as in the input
|
| - self.output.append('<%s%s%s>' % (
|
| - tag + (' ' if len(attrDic) else ''),
|
| - ' '.join(['%s="%s"' % (k, attrDic[k]) for k in attrDic]),
|
| - '/' if isEnd else ''))
|
| -
|
| - def handle_starttag(self, tag, attrs):
|
| - self.starttagHelper(tag, attrs, False)
|
| -
|
| - def handle_startendtag(self, tag, attrs):
|
| - self.starttagHelper(tag, attrs, True)
|
| -
|
| - def handle_data(self, data):
|
| - if self.in_dart_tag:
|
| - # collect the dart source code and compile it all at once when no more
|
| - # script tags can be included. Note: the code will anyways start on
|
| - # DOMContentLoaded, so moving the script is OK.
|
| - self.dart_inline_code.append(data)
|
| - else:
|
| - self.output.append(data),
|
| -
|
| - def handle_endtag(self, tag):
|
| - if tag == 'script' and self.in_dart_tag:
|
| - self.in_dart_tag = False
|
| - self.output.append(self.compiler.compileCode(
|
| - src=None, body='\n'.join(self.dart_inline_code)))
|
| - else:
|
| - self.output.append('</%s>' % tag)
|
| -
|
| - def handle_charref(self, ref):
|
| - self.output.append('&#%s;' % ref)
|
| -
|
| - def handle_entityref(self, name):
|
| - self.output.append('&%s;' % name)
|
| -
|
| - def handle_comment(self, data):
|
| - self.output.append('<!--%s-->' % data)
|
| -
|
| - def handle_decl(self, decl):
|
| - self.output.append('<!%s>' % decl)
|
| -
|
| - def unknown_decl(self, data):
|
| - self.output.append('<!%s>' % data)
|
| -
|
| - def handle_pi(self, data):
|
| - self.output.append('<?%s>' % data)
|
| -
|
| - def getResult(self):
|
| - return ''.join(self.output)
|
| -
|
| -
|
| -class DartToDartHTMLConverter(DartHTMLConverter):
|
| - def __init__(self, prefix_path, outdir, verbose):
|
| - # Note: can't use super calls because HTMLParser is not a subclass of object
|
| - DartHTMLConverter.__init__(self, None, prefix_path)
|
| - self.outdir = outdir
|
| - self.verbose = verbose
|
| -
|
| - def compileScript(self, attrDic):
|
| - self.contains_dart = True
|
| - if 'src' in attrDic:
|
| - status, out, err = execute([
|
| - sys.executable,
|
| - join(DART_PATH, 'tools', 'copy_dart.py'),
|
| - self.outdir,
|
| - convertPath(attrDic['src'], self.prefix_path)],
|
| - self.verbose)
|
| -
|
| - if status:
|
| - raise ConverterException('exception calling copy_dart.py')
|
| -
|
| - # do not rewrite the script tag
|
| - return False
|
| -
|
| - def handle_endtag(self, tag):
|
| - if tag == 'body' and self.contains_dart:
|
| - self.output.append(DARTIUM_TO_JS_SCRIPT)
|
| - DartHTMLConverter.handle_endtag(self, tag)
|
| -
|
| -# A data URL for a blank 1x1 PNG. The PNG's data is from
|
| -# convert -size 1x1 +set date:create +set date:modify \
|
| -# xc:'rgba(0,0,0,0)' 1x1.png
|
| -# base64.b64encode(open('1x1.png').read())
|
| -# (The +set stuff is because just doing "-strip" apparently doesn't work;
|
| -# it leaves several info chunks resulting in a 224-byte PNG.)
|
| -BLANK_IMAGE_BASE64_URL = 'data:image/png;charset=utf-8;base64,%s' % (
|
| - ('iVBORw0KGgoAAAANSUhEUgAAAAEAAAABEAQAAADljNBBAAAAAmJLR0T//xSrMc0AAAAJc'
|
| - 'EhZcwAAAEgAAABIAEbJaz4AAAAJdnBBZwAAAAEAAAABAMeVX+0AAAANSURBVAjXY2BgYG'
|
| - 'AAAAAFAAFe8yo6AAAAAElFTkSuQmCC'))
|
| -
|
| -class OfflineHTMLConverter(DartHTMLConverter):
|
| - def __init__(self, prefix_path, outdir, verbose, inline_images):
|
| - # Note: can't use super calls because HTMLParser is not a subclass of object
|
| - DartHTMLConverter.__init__(self, None, prefix_path)
|
| - self.outdir = outdir
|
| - self.verbose = verbose
|
| - self.inline_images = inline_images # Inline as data://, vs. use local file.
|
| -
|
| - def compileScript(self, attrDic):
|
| - # do not rewrite the script tag
|
| - return False
|
| -
|
| - def downloadImageUrlToEncode(self, url):
|
| - """ Downloads an image and returns a base64 url encoding for it.
|
| - May throw if the download fails.
|
| - """
|
| - # Don't try to re-encode an image that's already data://.
|
| - filetype = url[-3:]
|
| - if filetype == 'svg': filetype = 'svg+xml'
|
| - if self.verbose:
|
| - print 'Downloading ' + url
|
| - f = urllib2.urlopen(url)
|
| -
|
| - return 'data:image/%s;charset=utf-8;base64,%s' % (
|
| - filetype,
|
| - base64.b64encode(f.read()))
|
| -
|
| - def downloadImageUrlToFile(self, url):
|
| - """Downloads an image and returns the filename. May throw if the
|
| - download fails.
|
| - """
|
| - extension = os.path.splitext(url)[1]
|
| - # mkstemp() happens to work to create a non-temporary, so we use it.
|
| - filename = tempfile.mkstemp(extension, 'img_', self.prefix_path)[1]
|
| - if self.verbose:
|
| - print 'Downloading %s to %s' % (url, filename)
|
| - writeOut(urllib2.urlopen(url).read(), filename)
|
| - return os.path.join(self.prefix_path, os.path.basename(filename))
|
| -
|
| - def downloadImage(self, url):
|
| - """Downloads an image either to file or to data://, and return the URL."""
|
| - if url.startswith('data:image/'):
|
| - return url
|
| - try:
|
| - if self.inline_images:
|
| - return self.downloadImageUrlToEncode(url)
|
| - else:
|
| - return self.downloadImageUrlToFile(url)
|
| - except:
|
| - print '*** Image download failed: %s' % url
|
| - return BLANK_IMAGE_BASE64_URL
|
| -
|
| - def convertImage(self, attrDic):
|
| - attrDic['src'] = self.downloadImage(attrDic['src'])
|
| -
|
| -def safeMakeDirs(dirname):
|
| - """ Creates a directory and, if necessary its parent directories.
|
| -
|
| - This function will safely return if other concurrent jobs try to create the
|
| - same directory.
|
| - """
|
| - if not exists(dirname):
|
| - try:
|
| - os.makedirs(dirname)
|
| - except Exception:
|
| - # this check allows invoking this script concurrently in many jobs
|
| - if not exists(dirname):
|
| - raise
|
| -
|
| -class ConverterException(Exception):
|
| - """ An exception encountered during the convertion process """
|
| - pass
|
| -
|
| -def Flags():
|
| - """ Constructs a parser for extracting flags from the command line. """
|
| - result = optparse.OptionParser()
|
| - result.add_option("--verbose",
|
| - help="Print verbose output",
|
| - default=False,
|
| - action="store_true")
|
| - result.add_option("-o", "--out",
|
| - help="Output directory",
|
| - type="string",
|
| - default=None,
|
| - action="store")
|
| - result.add_option("-t", "--target",
|
| - help="The target html to generate",
|
| - metavar="[js,chromium,dartium]",
|
| - default='chromium')
|
| - result.add_option("--extra-flags",
|
| - help="Extra flags for dartc",
|
| - type="string",
|
| - default="")
|
| - result.set_usage("htmlconverter.py input.html -o OUTDIR")
|
| - return result
|
| -
|
| -def writeOut(contents, filepath):
|
| - """ Writes contents to a file, ensuring that the output directory exists. """
|
| - safeMakeDirs(dirname(filepath))
|
| - with open(filepath, 'w') as f:
|
| - f.write(contents)
|
| - print "Generated output in: " + abspath(filepath)
|
| -
|
| -def convertForDartium(filename, outdirBase, outfile, verbose):
|
| - """ Converts a file for a dartium target. """
|
| - with open(filename, 'r') as f:
|
| - contents = f.read()
|
| - prefix_path = dirname(filename)
|
| -
|
| - # outdirBase is the directory to place all subdirectories for other dart files
|
| - # and resources.
|
| - converter = DartToDartHTMLConverter(prefix_path, outdirBase, verbose)
|
| - converter.feed(contents)
|
| - converter.close()
|
| - writeOut(converter.getResult(), outfile)
|
| -
|
| -def convertForChromium(
|
| - filename, extra_flags, outfile, verbose):
|
| - """ Converts a file for a chromium target. """
|
| - with open(filename, 'r') as f:
|
| - contents = f.read()
|
| - prefix_path = dirname(filename)
|
| - converter = DartHTMLConverter(
|
| - DartCompiler(verbose, extra_flags), prefix_path)
|
| - converter.feed(contents)
|
| - converter.close()
|
| - writeOut(converter.getResult(), outfile)
|
| -
|
| -def convertForOffline(filename, outfile, verbose, encode_images):
|
| - """ Converts a file for offline use. """
|
| - with codecs.open(filename, 'r', 'utf-8') as f:
|
| - contents = f.read()
|
| - converter = OfflineHTMLConverter(dirname(filename),
|
| - dirname(outfile),
|
| - verbose,
|
| - encode_images)
|
| - converter.feed(contents)
|
| - converter.close()
|
| -
|
| - contents = converter.getResult()
|
| - safeMakeDirs(dirname(outfile))
|
| - with codecs.open(outfile, 'w', 'utf-8') as f:
|
| - f.write(contents)
|
| - print "Generated output in: " + abspath(outfile)
|
| -
|
| -RED_COLOR = "\033[31m"
|
| -NO_COLOR = "\033[0m"
|
| -
|
| -def main():
|
| - parser = Flags()
|
| - options, args = parser.parse_args()
|
| - if len(args) < 1 or not options.out or not options.target:
|
| - parser.print_help()
|
| - return 1
|
| -
|
| - try:
|
| - filename = args[0]
|
| - extension = filename[filename.rfind('.'):]
|
| - if extension != '.html' and extension != '.htm':
|
| - print "Invalid input file extension: %s" % extension
|
| - return 1
|
| - outfile = join(options.out, filename)
|
| - if 'chromium' in options.target or 'js' in options.target:
|
| - convertForChromium(filename,
|
| - options.extra_flags,
|
| - outfile.replace(extension, '-js' + extension), options.verbose)
|
| - if 'dartium' in options.target:
|
| - convertForDartium(filename, options.out,
|
| - outfile.replace(extension, '-dart' + extension), options.verbose)
|
| - except Exception as e:
|
| - print "%sERROR%s: %s" % (RED_COLOR, NO_COLOR, str(e))
|
| - return 1
|
| - return 0
|
| -
|
| -if __name__ == '__main__':
|
| - sys.exit(main())
|
|
|