diff options
Diffstat (limited to 'tensorflow/tensorboard/java/org/tensorflow/tensorboard/vulcanize/Vulcanize.java')
-rw-r--r-- | tensorflow/tensorboard/java/org/tensorflow/tensorboard/vulcanize/Vulcanize.java | 317 |
1 files changed, 317 insertions, 0 deletions
diff --git a/tensorflow/tensorboard/java/org/tensorflow/tensorboard/vulcanize/Vulcanize.java b/tensorflow/tensorboard/java/org/tensorflow/tensorboard/vulcanize/Vulcanize.java new file mode 100644 index 0000000000..e572415856 --- /dev/null +++ b/tensorflow/tensorboard/java/org/tensorflow/tensorboard/vulcanize/Vulcanize.java @@ -0,0 +1,317 @@ +// Copyright 2017 The TensorFlow Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package org.tensorflow.tensorboard.vulcanize; + +import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.base.Verify.verifyNotNull; +import static java.nio.charset.StandardCharsets.UTF_8; + +import com.google.common.base.CharMatcher; +import com.google.common.base.Joiner; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.Iterables; +import com.google.javascript.jscomp.BasicErrorManager; +import com.google.javascript.jscomp.CheckLevel; +import com.google.javascript.jscomp.Compiler; +import com.google.javascript.jscomp.CompilerOptions; +import com.google.javascript.jscomp.CompilerOptions.LanguageMode; +import com.google.javascript.jscomp.CompilerOptions.Reach; +import com.google.javascript.jscomp.JSError; +import com.google.javascript.jscomp.PropertyRenamingPolicy; +import com.google.javascript.jscomp.SourceFile; +import com.google.javascript.jscomp.VariableRenamingPolicy; +import com.google.protobuf.TextFormat; +import io.bazel.rules.closure.Webpath; +import io.bazel.rules.closure.webfiles.BuildInfo.Webfiles; +import io.bazel.rules.closure.webfiles.BuildInfo.WebfilesSource; +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.nio.file.StandardOpenOption; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import org.jsoup.Jsoup; +import org.jsoup.nodes.Comment; +import org.jsoup.nodes.DataNode; +import org.jsoup.nodes.Document; +import org.jsoup.nodes.Element; +import org.jsoup.nodes.Html5Printer; +import org.jsoup.nodes.Node; +import org.jsoup.nodes.TextNode; +import org.jsoup.parser.Parser; +import org.jsoup.parser.Tag; + +/** Simple one-off solution for TensorBoard vulcanization. */ +public final class Vulcanize { + + private static final Parser parser = Parser.htmlParser(); + private static final Map<Webpath, Path> webfiles = new HashMap<>(); + private static final Set<Webpath> alreadyInlined = new HashSet<>(); + private static final Set<String> legalese = new HashSet<>(); + private static final List<String> licenses = new ArrayList<>(); + private static final List<Webpath> stack = new ArrayList<>(); + private static Webpath outputPath; + private static Node licenseComment; + private static boolean nominify; + + public static void main(String[] args) throws IOException { + Webpath inputPath = Webpath.get(args[0]); + outputPath = Webpath.get(args[1]); + Path output = Paths.get(args[2]); + for (int i = 3; i < args.length; i++) { + Webfiles manifest = loadWebfilesPbtxt(Paths.get(args[i])); + for (WebfilesSource src : manifest.getSrcList()) { + webfiles.put(Webpath.get(src.getWebpath()), Paths.get(src.getPath())); + } + } + stack.add(inputPath); + Document document = parse(Files.readAllBytes(webfiles.get(inputPath))); + transform(document); + if (licenseComment != null) { + licenseComment.attr("comment", String.format("\n%s\n", Joiner.on("\n\n").join(licenses))); + } + Files.write( + output, + Html5Printer.stringify(document).getBytes(UTF_8), + StandardOpenOption.WRITE, + StandardOpenOption.CREATE, + StandardOpenOption.TRUNCATE_EXISTING); + } + + private static void transform(Node root) throws IOException { + Node node = checkNotNull(root); + Node newNode; + while (true) { + newNode = enterNode(node); + if (node.equals(root)) { + root = newNode; + } + node = newNode; + if (node.childNodeSize() > 0) { + node = node.childNode(0); + } else { + while (true) { + newNode = leaveNode(node); + if (node.equals(root)) { + root = newNode; + } + node = newNode; + if (node.equals(root)) { + return; + } + Node next = node.nextSibling(); + if (next == null) { + if (node.parentNode() == null) { + return; + } + node = verifyNotNull(node.parentNode(), "unexpected root: %s", node); + } else { + node = next; + break; + } + } + } + } + } + + private static Node enterNode(Node node) throws IOException { + Node newNode = node; + if (node instanceof Element) { + if (node.nodeName().equals("link") && node.attr("rel").equals("import")) { + // Inline HTML. + Webpath href = me().lookup(Webpath.get(node.attr("href"))); + if (alreadyInlined.add(href)) { + newNode = + parse(Files.readAllBytes(checkNotNull(webfiles.get(href), "%s in %s", href, me()))); + stack.add(href); + node.replaceWith(newNode); + } else { + newNode = new TextNode("", node.baseUri()); + node.replaceWith(newNode); + } + } else if (node.nodeName().equals("script")) { + nominify = node.hasAttr("nominify"); + node.removeAttr("nominify"); + Webpath src; + String script; + if (node.attr("src").isEmpty()) { + // Minify JavaScript. + StringBuilder sb = new StringBuilder(); + for (Node child : node.childNodes()) { + if (child instanceof DataNode) { + sb.append(((DataNode) child).getWholeData()); + } + } + src = me(); + script = sb.toString(); + } else { + // Inline JavaScript. + src = me().lookup(Webpath.get(node.attr("src"))); + Path other = webfiles.get(src); + if (other != null) { + script = new String(Files.readAllBytes(other), UTF_8); + node.removeAttr("src"); + } else { + src = me(); + script = ""; + } + } + script = minify(src, script); + newNode = + new Element(Tag.valueOf("script"), node.baseUri(), node.attributes()) + .appendChild(new DataNode(script, node.baseUri())); + node.replaceWith(newNode); + } else if (node.nodeName().equals("link") + && node.attr("rel").equals("stylesheet") + && !node.attr("href").isEmpty()) { + // Inline CSS. + Webpath href = me().lookup(Webpath.get(node.attr("href"))); + Path other = webfiles.get(href); + if (other != null) { + newNode = + new Element(Tag.valueOf("style"), node.baseUri(), node.attributes()) + .appendChild( + new DataNode(new String(Files.readAllBytes(other), UTF_8), node.baseUri())); + newNode.removeAttr("rel"); + newNode.removeAttr("href"); + node.replaceWith(newNode); + } + } + rootifyAttribute(newNode, "href"); + rootifyAttribute(newNode, "src"); + rootifyAttribute(newNode, "action"); + rootifyAttribute(newNode, "assetpath"); + } else if (node instanceof Comment) { + String text = ((Comment) node).getData(); + if (text.contains("@license")) { + handleLicense(text); + if (licenseComment == null) { + licenseComment = node; + } else { + newNode = new TextNode("", node.baseUri()); + node.replaceWith(newNode); + } + } else { + newNode = new TextNode("", node.baseUri()); + node.replaceWith(newNode); + } + } + return newNode; + } + + private static String minify(Webpath src, String script) { + if (nominify) { + return script; + } + Compiler compiler = new Compiler(new JsPrintlessErrorManager()); + CompilerOptions options = new CompilerOptions(); + options.skipAllCompilerPasses(); // too lazy to get externs + options.setLanguageIn(LanguageMode.ECMASCRIPT_2016); + options.setLanguageOut(LanguageMode.ECMASCRIPT5); + options.setContinueAfterErrors(true); + options.setManageClosureDependencies(false); + options.setRenamingPolicy(VariableRenamingPolicy.LOCAL, PropertyRenamingPolicy.OFF); + options.setShadowVariables(true); + options.setInlineVariables(Reach.LOCAL_ONLY); + options.setFlowSensitiveInlineVariables(true); + options.setInlineFunctions(Reach.LOCAL_ONLY); + options.setAssumeClosuresOnlyCaptureReferences(false); + options.setCheckGlobalThisLevel(CheckLevel.OFF); + options.setFoldConstants(true); + options.setCoalesceVariableNames(true); + options.setDeadAssignmentElimination(true); + options.setCollapseVariableDeclarations(true); + options.setConvertToDottedProperties(true); + options.setLabelRenaming(true); + options.setRemoveDeadCode(true); + options.setOptimizeArgumentsArray(true); + options.setRemoveUnusedVariables(Reach.LOCAL_ONLY); + options.setCollapseObjectLiterals(true); + options.setProtectHiddenSideEffects(true); + //options.setPrettyPrint(true); + compiler.disableThreads(); + compiler.compile( + ImmutableList.<SourceFile>of(), + ImmutableList.of(SourceFile.fromCode(src.toString(), script)), + options); + return compiler.toSource(); + } + + private static void handleLicense(String text) { + if (legalese.add(CharMatcher.whitespace().removeFrom(text))) { + licenses.add(CharMatcher.anyOf("\r\n").trimFrom(text)); + } + } + + private static Node leaveNode(Node node) { + if (node instanceof Document) { + stack.remove(stack.size() - 1); + } + return node; + } + + private static Webpath me() { + return Iterables.getLast(stack); + } + + private static void rootifyAttribute(Node node, String attribute) { + String value = node.attr(attribute); + if (value.isEmpty()) { + return; + } + Webpath uri = Webpath.get(value); + if (webfiles.containsKey(uri)) { + node.attr(attribute, outputPath.getParent().relativize(uri).toString()); + } + } + + private static Document parse(byte[] bytes) { + return parse(new ByteArrayInputStream(bytes)); + } + + private static Document parse(InputStream input) { + Document document; + try { + document = Jsoup.parse(input, null, "", parser); + } catch (IOException e) { + throw new AssertionError("I/O error when parsing byte array D:", e); + } + document.outputSettings().indentAmount(0); + document.outputSettings().prettyPrint(false); + return document; + } + + private static Webfiles loadWebfilesPbtxt(Path path) throws IOException { + Webfiles.Builder build = Webfiles.newBuilder(); + TextFormat.getParser().merge(new String(Files.readAllBytes(path), UTF_8), build); + return build.build(); + } + + private static final class JsPrintlessErrorManager extends BasicErrorManager { + + @Override + public void println(CheckLevel level, JSError error) {} + + @Override + public void printSummary() {} + } +} |