blob: 09dd548db199aea4c9dea4a1d179d0664befa461 [file] [log] [blame]
Dave Borowitz9de65952012-08-13 16:09:45 -07001// 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
15package com.google.gitiles;
16
Dave Borowitzec6a9cc2014-04-25 15:51:23 -040017import static com.google.common.base.Preconditions.checkState;
Dave Borowitz9de65952012-08-13 16:09:45 -070018import static org.eclipse.jgit.lib.Constants.OBJ_COMMIT;
19
Dave Borowitzec6a9cc2014-04-25 15:51:23 -040020import com.google.common.base.Strings;
Dave Borowitzab6f7d22014-04-29 11:39:20 -070021import com.google.common.collect.ImmutableList;
Dave Borowitz9de65952012-08-13 16:09:45 -070022import com.google.common.collect.Maps;
Dave Borowitzec6a9cc2014-04-25 15:51:23 -040023import com.google.template.soy.data.SoyListData;
24import com.google.template.soy.data.SoyMapData;
Dave Borowitz9de65952012-08-13 16:09:45 -070025
26import org.eclipse.jgit.diff.RawText;
27import org.eclipse.jgit.errors.LargeObjectException;
28import org.eclipse.jgit.errors.MissingObjectException;
29import org.eclipse.jgit.lib.Constants;
30import org.eclipse.jgit.lib.ObjectId;
31import org.eclipse.jgit.lib.ObjectLoader;
Dave Borowitz7c0a8332014-05-01 11:07:04 -070032import org.eclipse.jgit.lib.ObjectReader;
Dave Borowitz9de65952012-08-13 16:09:45 -070033import org.eclipse.jgit.util.RawParseUtils;
Dave Borowitzab6f7d22014-04-29 11:39:20 -070034import org.slf4j.Logger;
35import org.slf4j.LoggerFactory;
Dave Borowitz9de65952012-08-13 16:09:45 -070036
37import java.io.IOException;
Dave Borowitzec6a9cc2014-04-25 15:51:23 -040038import java.util.List;
Dave Borowitz9de65952012-08-13 16:09:45 -070039import java.util.Map;
40
Shawn Pearcec10cc992015-02-09 00:22:33 -080041import prettify.parser.Prettify;
42import syntaxhighlight.ParseResult;
43
Dave Borowitz9de65952012-08-13 16:09:45 -070044/** Soy data converter for git blobs. */
45public class BlobSoyData {
Dave Borowitzab6f7d22014-04-29 11:39:20 -070046 private static final Logger log = LoggerFactory.getLogger(BlobSoyData.class);
47
Dave Borowitz9de65952012-08-13 16:09:45 -070048 /**
49 * Maximum number of bytes to load from a supposed text file for display.
50 * Files larger than this will be displayed as binary files, even if the
51 * contents was text. For example really big XML files may be above this limit
52 * and will get displayed as binary.
53 */
54 private static final int MAX_FILE_SIZE = 10 << 20;
55
56 private final GitilesView view;
Dave Borowitz7c0a8332014-05-01 11:07:04 -070057 private final ObjectReader reader;
Dave Borowitz9de65952012-08-13 16:09:45 -070058
Dave Borowitz7c0a8332014-05-01 11:07:04 -070059 public BlobSoyData(ObjectReader reader, GitilesView view) {
60 this.reader = reader;
Dave Borowitz9de65952012-08-13 16:09:45 -070061 this.view = view;
Dave Borowitz9de65952012-08-13 16:09:45 -070062 }
63
Han-Wen Nienhuysc0200f62016-05-02 17:34:51 +020064 public Map<String, Object> toSoyData(ObjectId blobId) throws MissingObjectException, IOException {
Dave Borowitz9de65952012-08-13 16:09:45 -070065 return toSoyData(null, blobId);
66 }
67
68 public Map<String, Object> toSoyData(String path, ObjectId blobId)
69 throws MissingObjectException, IOException {
70 Map<String, Object> data = Maps.newHashMapWithExpectedSize(4);
71 data.put("sha", ObjectId.toString(blobId));
72
Dave Borowitz7c0a8332014-05-01 11:07:04 -070073 ObjectLoader loader = reader.open(blobId, Constants.OBJ_BLOB);
Dave Borowitz9de65952012-08-13 16:09:45 -070074 String content;
75 try {
76 byte[] raw = loader.getCachedBytes(MAX_FILE_SIZE);
77 content = !RawText.isBinary(raw) ? RawParseUtils.decode(raw) : null;
78 } catch (LargeObjectException.OutOfMemory e) {
79 throw e;
80 } catch (LargeObjectException e) {
81 content = null;
82 }
83
Dave Borowitz9de65952012-08-13 16:09:45 -070084 if (content != null) {
Dave Borowitzec6a9cc2014-04-25 15:51:23 -040085 data.put("lines", prettify(path, content));
Shawn Pearcefdd98f62015-02-12 10:04:07 -080086 if (path != null && path.endsWith(".md")) {
87 data.put("docUrl", GitilesView.doc().copyFrom(view).toUrl());
88 }
Dave Borowitz96a6f472014-11-04 16:38:20 -080089 } else {
Dave Borowitzec6a9cc2014-04-25 15:51:23 -040090 data.put("lines", null);
Dave Borowitz9de65952012-08-13 16:09:45 -070091 data.put("size", Long.toString(loader.getSize()));
92 }
93 if (path != null && view.getRevision().getPeeledType() == OBJ_COMMIT) {
Dave Borowitz7745e7f2014-04-27 19:47:47 -060094 data.put("fileUrl", GitilesView.path().copyFrom(view).toUrl());
Dave Borowitz9de65952012-08-13 16:09:45 -070095 data.put("logUrl", GitilesView.log().copyFrom(view).toUrl());
Dave Borowitz6ec0c872014-01-29 13:59:37 -080096 data.put("blameUrl", GitilesView.blame().copyFrom(view).toUrl());
Dave Borowitz9de65952012-08-13 16:09:45 -070097 }
98 return data;
99 }
100
Dave Borowitzab6f7d22014-04-29 11:39:20 -0700101 private SoyListData prettify(String path, String content) {
102 List<ParseResult> results = parse(path, content);
Dave Borowitzec6a9cc2014-04-25 15:51:23 -0400103 SoyListData lines = new SoyListData();
104 SoyListData line = new SoyListData();
105 lines.add(line);
106
107 int last = 0;
108 for (ParseResult r : results) {
Han-Wen Nienhuysc0200f62016-05-02 17:34:51 +0200109 checkState(
110 r.getOffset() >= last,
111 "out-of-order ParseResult, expected %s >= %s",
112 r.getOffset(),
113 last);
Dave Borowitzec6a9cc2014-04-25 15:51:23 -0400114 line = writeResult(lines, null, content, last, r.getOffset());
115 last = r.getOffset() + r.getLength();
116 line = writeResult(lines, r.getStyleKeysString(), content, r.getOffset(), last);
117 }
118 if (last < content.length()) {
119 writeResult(lines, null, content, last, content.length());
120 }
121 return lines;
122 }
123
Dave Borowitzab6f7d22014-04-29 11:39:20 -0700124 private List<ParseResult> parse(String path, String content) {
Shawn Pearcec10cc992015-02-09 00:22:33 -0800125 String lang = extension(path, content);
Dave Borowitzab6f7d22014-04-29 11:39:20 -0700126 try {
Shawn Pearcec10cc992015-02-09 00:22:33 -0800127 return ThreadSafePrettifyParser.INSTANCE.parse(lang, content);
Dave Borowitzab6f7d22014-04-29 11:39:20 -0700128 } catch (StackOverflowError e) {
129 // TODO(dborowitz): Aaagh. Make prettify use RE2. Or replace it something
130 // else. Or something.
131 log.warn("StackOverflowError prettifying " + view.toUrl());
132 return ImmutableList.of(
133 new ParseResult(0, content.length(), ImmutableList.of(Prettify.PR_PLAIN)));
134 }
135 }
136
Han-Wen Nienhuysc0200f62016-05-02 17:34:51 +0200137 private static SoyListData writeResult(
138 SoyListData lines, String classes, String s, int start, int end) {
Dave Borowitzec6a9cc2014-04-25 15:51:23 -0400139 SoyListData line = lines.getListData(lines.length() - 1);
140 while (true) {
141 int nl = nextLineBreak(s, start, end);
142 if (nl < 0) {
143 break;
144 }
145 addSpan(line, classes, s, start, nl);
146
147 start = nl + (isCrNl(s, nl) ? 2 : 1);
148 if (start == s.length()) {
149 return null;
150 }
151 line = new SoyListData();
152 lines.add(line);
153 }
154 addSpan(line, classes, s, start, end);
155 return line;
156 }
157
158 private static void addSpan(SoyListData line, String classes, String s, int start, int end) {
159 if (end - start > 0) {
160 if (Strings.isNullOrEmpty(classes)) {
161 classes = Prettify.PR_PLAIN;
162 }
163 line.add(new SoyMapData("classes", classes, "text", s.substring(start, end)));
164 }
165 }
166
167 private static boolean isCrNl(String s, int n) {
168 return s.charAt(n) == '\r' && n != s.length() - 1 && s.charAt(n + 1) == '\n';
169 }
170
171 private static int nextLineBreak(String s, int start, int end) {
172 for (int i = start; i < end; i++) {
173 if (s.charAt(i) == '\n' || s.charAt(i) == '\r') {
174 return i;
175 }
176 }
177 return -1;
178 }
179
180 private static String extension(String path, String content) {
Dave Borowitz9de65952012-08-13 16:09:45 -0700181 if (content.startsWith("#!/bin/sh") || content.startsWith("#!/bin/bash")) {
182 return "sh";
183 } else if (content.startsWith("#!/usr/bin/perl")) {
Dave Borowitzec6a9cc2014-04-25 15:51:23 -0400184 return "pl";
Dave Borowitz9de65952012-08-13 16:09:45 -0700185 } else if (content.startsWith("#!/usr/bin/python")) {
186 return "py";
187 } else if (path == null) {
188 return null;
189 }
190
191 int slash = path.lastIndexOf('/');
192 int dot = path.lastIndexOf('.');
Dave Borowitzec6a9cc2014-04-25 15:51:23 -0400193 String ext = ((0 < dot) && (slash < dot)) ? path.substring(dot + 1) : null;
194 if ("txt".equalsIgnoreCase(ext)) {
Dave Borowitz9de65952012-08-13 16:09:45 -0700195 return null;
Dave Borowitzec6a9cc2014-04-25 15:51:23 -0400196 } else if ("mk".equalsIgnoreCase(ext)) {
Dave Borowitz9de65952012-08-13 16:09:45 -0700197 return "sh";
198 } else if ("Makefile".equalsIgnoreCase(path)
199 || ((0 < slash) && "Makefile".equalsIgnoreCase(path.substring(slash + 1)))) {
200 return "sh";
201 } else {
Dave Borowitzec6a9cc2014-04-25 15:51:23 -0400202 return ext;
Dave Borowitz9de65952012-08-13 16:09:45 -0700203 }
204 }
205}