| Dave Borowitz | 9de6595 | 2012-08-13 16:09:45 -0700 | [diff] [blame] | 1 | // Copyright 2012 Google Inc. All Rights Reserved. |
| 2 | // |
| 3 | // Licensed under the Apache License, Version 2.0 (the "License"); |
| 4 | // you may not use this file except in compliance with the License. |
| 5 | // You may obtain a copy of the License at |
| 6 | // |
| 7 | // http://www.apache.org/licenses/LICENSE-2.0 |
| 8 | // |
| 9 | // Unless required by applicable law or agreed to in writing, software |
| 10 | // distributed under the License is distributed on an "AS IS" BASIS, |
| 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 12 | // See the License for the specific language governing permissions and |
| 13 | // limitations under the License. |
| 14 | |
| 15 | package com.google.gitiles; |
| 16 | |
| Dave Borowitz | ec6a9cc | 2014-04-25 15:51:23 -0400 | [diff] [blame] | 17 | import static com.google.common.base.Preconditions.checkState; |
| Dave Borowitz | 9de6595 | 2012-08-13 16:09:45 -0700 | [diff] [blame] | 18 | import static org.eclipse.jgit.lib.Constants.OBJ_COMMIT; |
| 19 | |
| Dave Borowitz | ec6a9cc | 2014-04-25 15:51:23 -0400 | [diff] [blame] | 20 | import com.google.common.base.Strings; |
| Dave Borowitz | ab6f7d2 | 2014-04-29 11:39:20 -0700 | [diff] [blame] | 21 | import com.google.common.collect.ImmutableList; |
| Dave Borowitz | 9de6595 | 2012-08-13 16:09:45 -0700 | [diff] [blame] | 22 | import com.google.common.collect.Maps; |
| Dave Borowitz | ec6a9cc | 2014-04-25 15:51:23 -0400 | [diff] [blame] | 23 | import com.google.template.soy.data.SoyListData; |
| 24 | import com.google.template.soy.data.SoyMapData; |
| Dave Borowitz | 3b744b1 | 2016-08-19 16:11:10 -0400 | [diff] [blame] | 25 | import java.io.IOException; |
| 26 | import java.util.List; |
| 27 | import java.util.Map; |
| Dave Borowitz | 9de6595 | 2012-08-13 16:09:45 -0700 | [diff] [blame] | 28 | import org.eclipse.jgit.diff.RawText; |
| 29 | import org.eclipse.jgit.errors.LargeObjectException; |
| 30 | import org.eclipse.jgit.errors.MissingObjectException; |
| 31 | import org.eclipse.jgit.lib.Constants; |
| 32 | import org.eclipse.jgit.lib.ObjectId; |
| 33 | import org.eclipse.jgit.lib.ObjectLoader; |
| Dave Borowitz | 7c0a833 | 2014-05-01 11:07:04 -0700 | [diff] [blame] | 34 | import org.eclipse.jgit.lib.ObjectReader; |
| Dave Borowitz | 9de6595 | 2012-08-13 16:09:45 -0700 | [diff] [blame] | 35 | import org.eclipse.jgit.util.RawParseUtils; |
| Dave Borowitz | ab6f7d2 | 2014-04-29 11:39:20 -0700 | [diff] [blame] | 36 | import org.slf4j.Logger; |
| 37 | import org.slf4j.LoggerFactory; |
| Shawn Pearce | c10cc99 | 2015-02-09 00:22:33 -0800 | [diff] [blame] | 38 | import prettify.parser.Prettify; |
| 39 | import syntaxhighlight.ParseResult; |
| 40 | |
| Dave Borowitz | 9de6595 | 2012-08-13 16:09:45 -0700 | [diff] [blame] | 41 | /** Soy data converter for git blobs. */ |
| 42 | public class BlobSoyData { |
| Dave Borowitz | ab6f7d2 | 2014-04-29 11:39:20 -0700 | [diff] [blame] | 43 | private static final Logger log = LoggerFactory.getLogger(BlobSoyData.class); |
| 44 | |
| Dave Borowitz | 9de6595 | 2012-08-13 16:09:45 -0700 | [diff] [blame] | 45 | /** |
| Dave Borowitz | dc81e5e | 2016-07-27 18:11:12 -0400 | [diff] [blame] | 46 | * Maximum number of bytes to load from a supposed text file for display. Files larger than this |
| 47 | * will be displayed as binary files, even if the contents was text. For example really big XML |
| 48 | * files may be above this limit and will get displayed as binary. |
| Dave Borowitz | 9de6595 | 2012-08-13 16:09:45 -0700 | [diff] [blame] | 49 | */ |
| 50 | private static final int MAX_FILE_SIZE = 10 << 20; |
| 51 | |
| 52 | private final GitilesView view; |
| Dave Borowitz | 7c0a833 | 2014-05-01 11:07:04 -0700 | [diff] [blame] | 53 | private final ObjectReader reader; |
| Dave Borowitz | 9de6595 | 2012-08-13 16:09:45 -0700 | [diff] [blame] | 54 | |
| Dave Borowitz | 7c0a833 | 2014-05-01 11:07:04 -0700 | [diff] [blame] | 55 | public BlobSoyData(ObjectReader reader, GitilesView view) { |
| 56 | this.reader = reader; |
| Dave Borowitz | 9de6595 | 2012-08-13 16:09:45 -0700 | [diff] [blame] | 57 | this.view = view; |
| Dave Borowitz | 9de6595 | 2012-08-13 16:09:45 -0700 | [diff] [blame] | 58 | } |
| 59 | |
| Han-Wen Nienhuys | c0200f6 | 2016-05-02 17:34:51 +0200 | [diff] [blame] | 60 | public Map<String, Object> toSoyData(ObjectId blobId) throws MissingObjectException, IOException { |
| Dave Borowitz | 9de6595 | 2012-08-13 16:09:45 -0700 | [diff] [blame] | 61 | return toSoyData(null, blobId); |
| 62 | } |
| 63 | |
| 64 | public Map<String, Object> toSoyData(String path, ObjectId blobId) |
| 65 | throws MissingObjectException, IOException { |
| 66 | Map<String, Object> data = Maps.newHashMapWithExpectedSize(4); |
| 67 | data.put("sha", ObjectId.toString(blobId)); |
| 68 | |
| Dave Borowitz | 7c0a833 | 2014-05-01 11:07:04 -0700 | [diff] [blame] | 69 | ObjectLoader loader = reader.open(blobId, Constants.OBJ_BLOB); |
| Dave Borowitz | 9de6595 | 2012-08-13 16:09:45 -0700 | [diff] [blame] | 70 | String content; |
| 71 | try { |
| 72 | byte[] raw = loader.getCachedBytes(MAX_FILE_SIZE); |
| 73 | content = !RawText.isBinary(raw) ? RawParseUtils.decode(raw) : null; |
| 74 | } catch (LargeObjectException.OutOfMemory e) { |
| 75 | throw e; |
| 76 | } catch (LargeObjectException e) { |
| 77 | content = null; |
| 78 | } |
| 79 | |
| Dave Borowitz | 9de6595 | 2012-08-13 16:09:45 -0700 | [diff] [blame] | 80 | if (content != null) { |
| Dave Borowitz | ec6a9cc | 2014-04-25 15:51:23 -0400 | [diff] [blame] | 81 | data.put("lines", prettify(path, content)); |
| Shawn Pearce | fdd98f6 | 2015-02-12 10:04:07 -0800 | [diff] [blame] | 82 | if (path != null && path.endsWith(".md")) { |
| 83 | data.put("docUrl", GitilesView.doc().copyFrom(view).toUrl()); |
| 84 | } |
| Dave Borowitz | 96a6f47 | 2014-11-04 16:38:20 -0800 | [diff] [blame] | 85 | } else { |
| Dave Borowitz | ec6a9cc | 2014-04-25 15:51:23 -0400 | [diff] [blame] | 86 | data.put("lines", null); |
| Dave Borowitz | 9de6595 | 2012-08-13 16:09:45 -0700 | [diff] [blame] | 87 | data.put("size", Long.toString(loader.getSize())); |
| 88 | } |
| 89 | if (path != null && view.getRevision().getPeeledType() == OBJ_COMMIT) { |
| Dave Borowitz | 7745e7f | 2014-04-27 19:47:47 -0600 | [diff] [blame] | 90 | data.put("fileUrl", GitilesView.path().copyFrom(view).toUrl()); |
| Dave Borowitz | 9de6595 | 2012-08-13 16:09:45 -0700 | [diff] [blame] | 91 | data.put("logUrl", GitilesView.log().copyFrom(view).toUrl()); |
| Dave Borowitz | 6ec0c87 | 2014-01-29 13:59:37 -0800 | [diff] [blame] | 92 | data.put("blameUrl", GitilesView.blame().copyFrom(view).toUrl()); |
| Dave Borowitz | 9de6595 | 2012-08-13 16:09:45 -0700 | [diff] [blame] | 93 | } |
| 94 | return data; |
| 95 | } |
| 96 | |
| Dave Borowitz | ab6f7d2 | 2014-04-29 11:39:20 -0700 | [diff] [blame] | 97 | private SoyListData prettify(String path, String content) { |
| 98 | List<ParseResult> results = parse(path, content); |
| Dave Borowitz | ec6a9cc | 2014-04-25 15:51:23 -0400 | [diff] [blame] | 99 | SoyListData lines = new SoyListData(); |
| 100 | SoyListData line = new SoyListData(); |
| 101 | lines.add(line); |
| 102 | |
| 103 | int last = 0; |
| 104 | for (ParseResult r : results) { |
| Han-Wen Nienhuys | c0200f6 | 2016-05-02 17:34:51 +0200 | [diff] [blame] | 105 | checkState( |
| 106 | r.getOffset() >= last, |
| 107 | "out-of-order ParseResult, expected %s >= %s", |
| 108 | r.getOffset(), |
| 109 | last); |
| David Pursehouse | 390888b | 2016-06-17 16:55:02 +0900 | [diff] [blame] | 110 | writeResult(lines, null, content, last, r.getOffset()); |
| Dave Borowitz | ec6a9cc | 2014-04-25 15:51:23 -0400 | [diff] [blame] | 111 | last = r.getOffset() + r.getLength(); |
| David Pursehouse | 390888b | 2016-06-17 16:55:02 +0900 | [diff] [blame] | 112 | writeResult(lines, r.getStyleKeysString(), content, r.getOffset(), last); |
| Dave Borowitz | ec6a9cc | 2014-04-25 15:51:23 -0400 | [diff] [blame] | 113 | } |
| 114 | if (last < content.length()) { |
| 115 | writeResult(lines, null, content, last, content.length()); |
| 116 | } |
| 117 | return lines; |
| 118 | } |
| 119 | |
| Dave Borowitz | ab6f7d2 | 2014-04-29 11:39:20 -0700 | [diff] [blame] | 120 | private List<ParseResult> parse(String path, String content) { |
| Shawn Pearce | c10cc99 | 2015-02-09 00:22:33 -0800 | [diff] [blame] | 121 | String lang = extension(path, content); |
| Dave Borowitz | ab6f7d2 | 2014-04-29 11:39:20 -0700 | [diff] [blame] | 122 | try { |
| Shawn Pearce | c10cc99 | 2015-02-09 00:22:33 -0800 | [diff] [blame] | 123 | return ThreadSafePrettifyParser.INSTANCE.parse(lang, content); |
| Dave Borowitz | ab6f7d2 | 2014-04-29 11:39:20 -0700 | [diff] [blame] | 124 | } catch (StackOverflowError e) { |
| 125 | // TODO(dborowitz): Aaagh. Make prettify use RE2. Or replace it something |
| 126 | // else. Or something. |
| 127 | log.warn("StackOverflowError prettifying " + view.toUrl()); |
| 128 | return ImmutableList.of( |
| 129 | new ParseResult(0, content.length(), ImmutableList.of(Prettify.PR_PLAIN))); |
| 130 | } |
| 131 | } |
| 132 | |
| Dave Borowitz | dc81e5e | 2016-07-27 18:11:12 -0400 | [diff] [blame] | 133 | private static void writeResult(SoyListData lines, String classes, String s, int start, int end) { |
| Dave Borowitz | ec6a9cc | 2014-04-25 15:51:23 -0400 | [diff] [blame] | 134 | SoyListData line = lines.getListData(lines.length() - 1); |
| 135 | while (true) { |
| 136 | int nl = nextLineBreak(s, start, end); |
| 137 | if (nl < 0) { |
| 138 | break; |
| 139 | } |
| 140 | addSpan(line, classes, s, start, nl); |
| 141 | |
| Dave Borowitz | dc81e5e | 2016-07-27 18:11:12 -0400 | [diff] [blame] | 142 | start = nl + 1; |
| Dave Borowitz | ec6a9cc | 2014-04-25 15:51:23 -0400 | [diff] [blame] | 143 | if (start == s.length()) { |
| David Pursehouse | 390888b | 2016-06-17 16:55:02 +0900 | [diff] [blame] | 144 | return; |
| Dave Borowitz | ec6a9cc | 2014-04-25 15:51:23 -0400 | [diff] [blame] | 145 | } |
| 146 | line = new SoyListData(); |
| 147 | lines.add(line); |
| 148 | } |
| 149 | addSpan(line, classes, s, start, end); |
| Dave Borowitz | ec6a9cc | 2014-04-25 15:51:23 -0400 | [diff] [blame] | 150 | } |
| 151 | |
| 152 | private static void addSpan(SoyListData line, String classes, String s, int start, int end) { |
| 153 | if (end - start > 0) { |
| 154 | if (Strings.isNullOrEmpty(classes)) { |
| 155 | classes = Prettify.PR_PLAIN; |
| 156 | } |
| 157 | line.add(new SoyMapData("classes", classes, "text", s.substring(start, end))); |
| 158 | } |
| 159 | } |
| 160 | |
| Dave Borowitz | ec6a9cc | 2014-04-25 15:51:23 -0400 | [diff] [blame] | 161 | private static int nextLineBreak(String s, int start, int end) { |
| Dave Borowitz | dc81e5e | 2016-07-27 18:11:12 -0400 | [diff] [blame] | 162 | int n = s.indexOf('\n', start); |
| 163 | return n < end ? n : -1; |
| Dave Borowitz | ec6a9cc | 2014-04-25 15:51:23 -0400 | [diff] [blame] | 164 | } |
| 165 | |
| 166 | private static String extension(String path, String content) { |
| Dave Borowitz | 9de6595 | 2012-08-13 16:09:45 -0700 | [diff] [blame] | 167 | if (content.startsWith("#!/bin/sh") || content.startsWith("#!/bin/bash")) { |
| 168 | return "sh"; |
| 169 | } else if (content.startsWith("#!/usr/bin/perl")) { |
| Dave Borowitz | ec6a9cc | 2014-04-25 15:51:23 -0400 | [diff] [blame] | 170 | return "pl"; |
| Dave Borowitz | 9de6595 | 2012-08-13 16:09:45 -0700 | [diff] [blame] | 171 | } else if (content.startsWith("#!/usr/bin/python")) { |
| 172 | return "py"; |
| 173 | } else if (path == null) { |
| 174 | return null; |
| 175 | } |
| 176 | |
| 177 | int slash = path.lastIndexOf('/'); |
| 178 | int dot = path.lastIndexOf('.'); |
| Dave Borowitz | ec6a9cc | 2014-04-25 15:51:23 -0400 | [diff] [blame] | 179 | String ext = ((0 < dot) && (slash < dot)) ? path.substring(dot + 1) : null; |
| 180 | if ("txt".equalsIgnoreCase(ext)) { |
| Dave Borowitz | 9de6595 | 2012-08-13 16:09:45 -0700 | [diff] [blame] | 181 | return null; |
| Dave Borowitz | ec6a9cc | 2014-04-25 15:51:23 -0400 | [diff] [blame] | 182 | } else if ("mk".equalsIgnoreCase(ext)) { |
| Dave Borowitz | 9de6595 | 2012-08-13 16:09:45 -0700 | [diff] [blame] | 183 | return "sh"; |
| 184 | } else if ("Makefile".equalsIgnoreCase(path) |
| 185 | || ((0 < slash) && "Makefile".equalsIgnoreCase(path.substring(slash + 1)))) { |
| 186 | return "sh"; |
| 187 | } else { |
| Dave Borowitz | ec6a9cc | 2014-04-25 15:51:23 -0400 | [diff] [blame] | 188 | return ext; |
| Dave Borowitz | 9de6595 | 2012-08-13 16:09:45 -0700 | [diff] [blame] | 189 | } |
| 190 | } |
| 191 | } |