blob: c505ed457588e074ce40e9be4a3d5a50eb288475 [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 Borowitz3b744b12016-08-19 16:11:10 -040025import java.io.IOException;
26import java.util.List;
27import java.util.Map;
Dave Borowitz9de65952012-08-13 16:09:45 -070028import org.eclipse.jgit.diff.RawText;
29import org.eclipse.jgit.errors.LargeObjectException;
30import org.eclipse.jgit.errors.MissingObjectException;
31import org.eclipse.jgit.lib.Constants;
32import org.eclipse.jgit.lib.ObjectId;
33import org.eclipse.jgit.lib.ObjectLoader;
Dave Borowitz7c0a8332014-05-01 11:07:04 -070034import org.eclipse.jgit.lib.ObjectReader;
Dave Borowitz9de65952012-08-13 16:09:45 -070035import org.eclipse.jgit.util.RawParseUtils;
Dave Borowitzab6f7d22014-04-29 11:39:20 -070036import org.slf4j.Logger;
37import org.slf4j.LoggerFactory;
Shawn Pearcec10cc992015-02-09 00:22:33 -080038import prettify.parser.Prettify;
39import syntaxhighlight.ParseResult;
40
Dave Borowitz9de65952012-08-13 16:09:45 -070041/** Soy data converter for git blobs. */
42public class BlobSoyData {
Dave Borowitzab6f7d22014-04-29 11:39:20 -070043 private static final Logger log = LoggerFactory.getLogger(BlobSoyData.class);
44
Dave Borowitz9de65952012-08-13 16:09:45 -070045 /**
Dave Borowitzdc81e5e2016-07-27 18:11:12 -040046 * 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 Borowitz9de65952012-08-13 16:09:45 -070049 */
50 private static final int MAX_FILE_SIZE = 10 << 20;
51
52 private final GitilesView view;
Dave Borowitz7c0a8332014-05-01 11:07:04 -070053 private final ObjectReader reader;
Dave Borowitz9de65952012-08-13 16:09:45 -070054
Dave Borowitz7c0a8332014-05-01 11:07:04 -070055 public BlobSoyData(ObjectReader reader, GitilesView view) {
56 this.reader = reader;
Dave Borowitz9de65952012-08-13 16:09:45 -070057 this.view = view;
Dave Borowitz9de65952012-08-13 16:09:45 -070058 }
59
Han-Wen Nienhuysc0200f62016-05-02 17:34:51 +020060 public Map<String, Object> toSoyData(ObjectId blobId) throws MissingObjectException, IOException {
Dave Borowitz9de65952012-08-13 16:09:45 -070061 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 Borowitz7c0a8332014-05-01 11:07:04 -070069 ObjectLoader loader = reader.open(blobId, Constants.OBJ_BLOB);
Dave Borowitz9de65952012-08-13 16:09:45 -070070 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 Borowitz9de65952012-08-13 16:09:45 -070080 if (content != null) {
Dave Borowitzec6a9cc2014-04-25 15:51:23 -040081 data.put("lines", prettify(path, content));
Shawn Pearcefdd98f62015-02-12 10:04:07 -080082 if (path != null && path.endsWith(".md")) {
83 data.put("docUrl", GitilesView.doc().copyFrom(view).toUrl());
84 }
Dave Borowitz96a6f472014-11-04 16:38:20 -080085 } else {
Dave Borowitzec6a9cc2014-04-25 15:51:23 -040086 data.put("lines", null);
Dave Borowitz9de65952012-08-13 16:09:45 -070087 data.put("size", Long.toString(loader.getSize()));
88 }
89 if (path != null && view.getRevision().getPeeledType() == OBJ_COMMIT) {
Dave Borowitz7745e7f2014-04-27 19:47:47 -060090 data.put("fileUrl", GitilesView.path().copyFrom(view).toUrl());
Dave Borowitz9de65952012-08-13 16:09:45 -070091 data.put("logUrl", GitilesView.log().copyFrom(view).toUrl());
Dave Borowitz6ec0c872014-01-29 13:59:37 -080092 data.put("blameUrl", GitilesView.blame().copyFrom(view).toUrl());
Dave Borowitz9de65952012-08-13 16:09:45 -070093 }
94 return data;
95 }
96
Dave Borowitzab6f7d22014-04-29 11:39:20 -070097 private SoyListData prettify(String path, String content) {
98 List<ParseResult> results = parse(path, content);
Dave Borowitzec6a9cc2014-04-25 15:51:23 -040099 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 Nienhuysc0200f62016-05-02 17:34:51 +0200105 checkState(
106 r.getOffset() >= last,
107 "out-of-order ParseResult, expected %s >= %s",
108 r.getOffset(),
109 last);
David Pursehouse390888b2016-06-17 16:55:02 +0900110 writeResult(lines, null, content, last, r.getOffset());
Dave Borowitzec6a9cc2014-04-25 15:51:23 -0400111 last = r.getOffset() + r.getLength();
David Pursehouse390888b2016-06-17 16:55:02 +0900112 writeResult(lines, r.getStyleKeysString(), content, r.getOffset(), last);
Dave Borowitzec6a9cc2014-04-25 15:51:23 -0400113 }
114 if (last < content.length()) {
115 writeResult(lines, null, content, last, content.length());
116 }
117 return lines;
118 }
119
Dave Borowitzab6f7d22014-04-29 11:39:20 -0700120 private List<ParseResult> parse(String path, String content) {
Shawn Pearcec10cc992015-02-09 00:22:33 -0800121 String lang = extension(path, content);
Dave Borowitzab6f7d22014-04-29 11:39:20 -0700122 try {
Shawn Pearcec10cc992015-02-09 00:22:33 -0800123 return ThreadSafePrettifyParser.INSTANCE.parse(lang, content);
Dave Borowitzab6f7d22014-04-29 11:39:20 -0700124 } 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 Borowitzdc81e5e2016-07-27 18:11:12 -0400133 private static void writeResult(SoyListData lines, String classes, String s, int start, int end) {
Dave Borowitzec6a9cc2014-04-25 15:51:23 -0400134 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 Borowitzdc81e5e2016-07-27 18:11:12 -0400142 start = nl + 1;
Dave Borowitzec6a9cc2014-04-25 15:51:23 -0400143 if (start == s.length()) {
David Pursehouse390888b2016-06-17 16:55:02 +0900144 return;
Dave Borowitzec6a9cc2014-04-25 15:51:23 -0400145 }
146 line = new SoyListData();
147 lines.add(line);
148 }
149 addSpan(line, classes, s, start, end);
Dave Borowitzec6a9cc2014-04-25 15:51:23 -0400150 }
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 Borowitzec6a9cc2014-04-25 15:51:23 -0400161 private static int nextLineBreak(String s, int start, int end) {
Dave Borowitzdc81e5e2016-07-27 18:11:12 -0400162 int n = s.indexOf('\n', start);
163 return n < end ? n : -1;
Dave Borowitzec6a9cc2014-04-25 15:51:23 -0400164 }
165
166 private static String extension(String path, String content) {
Dave Borowitz9de65952012-08-13 16:09:45 -0700167 if (content.startsWith("#!/bin/sh") || content.startsWith("#!/bin/bash")) {
168 return "sh";
169 } else if (content.startsWith("#!/usr/bin/perl")) {
Dave Borowitzec6a9cc2014-04-25 15:51:23 -0400170 return "pl";
Dave Borowitz9de65952012-08-13 16:09:45 -0700171 } 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 Borowitzec6a9cc2014-04-25 15:51:23 -0400179 String ext = ((0 < dot) && (slash < dot)) ? path.substring(dot + 1) : null;
180 if ("txt".equalsIgnoreCase(ext)) {
Dave Borowitz9de65952012-08-13 16:09:45 -0700181 return null;
Dave Borowitzec6a9cc2014-04-25 15:51:23 -0400182 } else if ("mk".equalsIgnoreCase(ext)) {
Dave Borowitz9de65952012-08-13 16:09:45 -0700183 return "sh";
184 } else if ("Makefile".equalsIgnoreCase(path)
185 || ((0 < slash) && "Makefile".equalsIgnoreCase(path.substring(slash + 1)))) {
186 return "sh";
187 } else {
Dave Borowitzec6a9cc2014-04-25 15:51:23 -0400188 return ext;
Dave Borowitz9de65952012-08-13 16:09:45 -0700189 }
190 }
191}