James Moger
2011-10-17 b2fde8f0dfe2d60b08724e92f919c1f68223101f
commit | author | age
f13c4c 1 /*
JM 2  * Copyright 2011 gitblit.com.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *     http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
22fc5e 16 package com.gitblit.build;
dd7961 17
a3f474 18 import java.io.BufferedReader;
dd7961 19 import java.io.File;
JM 20 import java.io.FileOutputStream;
a3f474 21 import java.io.FileReader;
dd7961 22 import java.io.FilenameFilter;
JM 23 import java.io.OutputStreamWriter;
24 import java.nio.charset.Charset;
25 import java.text.MessageFormat;
a3f474 26 import java.text.ParseException;
dd7961 27 import java.text.SimpleDateFormat;
e0054b 28 import java.util.ArrayList;
dd7961 29 import java.util.Arrays;
JM 30 import java.util.Date;
e0054b 31 import java.util.HashMap;
JM 32 import java.util.List;
33 import java.util.Map;
a3f474 34 import java.util.Vector;
dd7961 35
JM 36 import com.beust.jcommander.JCommander;
37 import com.beust.jcommander.Parameter;
38 import com.beust.jcommander.ParameterException;
39 import com.beust.jcommander.Parameters;
22fc5e 40 import com.gitblit.Constants;
892570 41 import com.gitblit.utils.FileUtils;
dd7961 42 import com.gitblit.utils.MarkdownUtils;
1f9dae 43 import com.gitblit.utils.StringUtils;
dd7961 44
892570 45 /**
JM 46  * Builds the web site or deployment documentation from Markdown source files.
47  * 
48  * All Markdown source files must have the .mkd extension.
49  * 
50  * Natural string sort order of the Markdown source filenames is the order of
51  * page links. "##_" prefixes are used to control the sort order.
52  * 
53  * @author James Moger
54  * 
55  */
dd7961 56 public class BuildSite {
a3f474 57
8f73a7 58     private static final String SPACE_DELIMITED = "SPACE-DELIMITED";
0aa6ec 59
88598b 60     private static final String CASE_SENSITIVE = "CASE-SENSITIVE";
a3f474 61
88598b 62     private static final String RESTART_REQUIRED = "RESTART REQUIRED";
a3f474 63
88598b 64     private static final String SINCE = "SINCE";
dd7961 65
JM 66     public static void main(String... args) {
67         Params params = new Params();
68         JCommander jc = new JCommander(params);
69         try {
70             jc.parse(args);
71         } catch (ParameterException t) {
72             usage(jc, t);
73         }
74
75         File sourceFolder = new File(params.sourceFolder);
76         File destinationFolder = new File(params.outputFolder);
77         File[] markdownFiles = sourceFolder.listFiles(new FilenameFilter() {
78
79             @Override
80             public boolean accept(File dir, String name) {
81                 return name.toLowerCase().endsWith(".mkd");
82             }
83         });
84         Arrays.sort(markdownFiles);
85
e0054b 86         Map<String, String> aliasMap = new HashMap<String, String>();
a4d249 87         for (String alias : params.aliases) {
JM 88             String[] values = alias.split("=");
e0054b 89             aliasMap.put(values[0], values[1]);
JM 90         }
a4d249 91
2a7306 92         System.out.println(MessageFormat.format("Generating site from {0} Markdown Docs in {1} ",
JM 93                 markdownFiles.length, sourceFolder.getAbsolutePath()));
a4d249 94
892570 95         String htmlHeader = FileUtils.readContent(new File(params.pageHeader), "\n");
a3f474 96
e7a153 97         String htmlAdSnippet = null;
JM 98         if (!StringUtils.isEmpty(params.adSnippet)) {
99             File snippet = new File(params.adSnippet);
100             if (snippet.exists()) {
101                 htmlAdSnippet = FileUtils.readContent(snippet, "\n");
102             }
103         }
892570 104         String htmlFooter = FileUtils.readContent(new File(params.pageFooter), "\n");
00afd7 105         final String date = new SimpleDateFormat("yyyy-MM-dd").format(new Date());
2a7306 106         final String footer = MessageFormat.format(htmlFooter, "generated " + date);
dd7961 107         for (File file : markdownFiles) {
0aa6ec 108             String documentName = getDocumentName(file);
JM 109             if (params.skips.contains(documentName)) {
110                 continue;
111             }
dd7961 112             try {
0aa6ec 113                 String links = createLinks(file, markdownFiles, aliasMap, params.skips);
JM 114                 String header = MessageFormat.format(htmlHeader, Constants.FULL_NAME, links);
115                 if (!StringUtils.isEmpty(params.analyticsSnippet)) {
116                     File snippet = new File(params.analyticsSnippet);
117                     if (snippet.exists()) {
118                         String htmlSnippet = FileUtils.readContent(snippet, "\n");
119                         header = header.replace("<!-- ANALYTICS -->", htmlSnippet);
230632 120                     }
dd7961 121                 }
0aa6ec 122
JM 123                 String fileName = documentName + ".html";
124                 System.out.println(MessageFormat.format("  {0} => {1}", file.getName(), fileName));
125                 String rawContent = FileUtils.readContent(file, "\n");
126                 String markdownContent = rawContent;
127
128                 Map<String, List<String>> nomarkdownMap = new HashMap<String, List<String>>();
129
130                 // extract sections marked as no-markdown
131                 int nmd = 0;
132                 for (String token : params.nomarkdown) {
133                     StringBuilder strippedContent = new StringBuilder();
134
135                     String nomarkdownKey = "%NOMARKDOWN" + nmd + "%";
136                     String[] kv = token.split(":", 2);
137                     String beginToken = kv[0];
138                     String endToken = kv[1];
139
140                     // strip nomarkdown chunks from markdown and cache them
141                     List<String> chunks = new Vector<String>();
142                     int beginCode = 0;
143                     int endCode = 0;
144                     while ((beginCode = markdownContent.indexOf(beginToken, endCode)) > -1) {
145                         if (endCode == 0) {
146                             strippedContent.append(markdownContent.substring(0, beginCode));
147                         } else {
148                             strippedContent.append(markdownContent.substring(endCode, beginCode));
149                         }
150                         strippedContent.append(nomarkdownKey);
151                         endCode = markdownContent.indexOf(endToken, beginCode);
152                         chunks.add(markdownContent.substring(beginCode, endCode));
153                         nomarkdownMap.put(nomarkdownKey, chunks);
154                     }
155
156                     // get remainder of text
157                     if (endCode < markdownContent.length()) {
158                         strippedContent.append(markdownContent.substring(endCode,
159                                 markdownContent.length()));
160                     }
161                     markdownContent = strippedContent.toString();
162                     nmd++;
163                 }
164
165                 // transform markdown to html
166                 String content = transformMarkdown(markdownContent.toString());
167
168                 // reinsert nomarkdown chunks
169                 for (Map.Entry<String, List<String>> nomarkdown : nomarkdownMap.entrySet()) {
170                     for (String chunk : nomarkdown.getValue()) {
171                         content = content.replaceFirst(nomarkdown.getKey(), chunk);
172                     }
173                 }
174
175                 for (String token : params.substitutions) {
176                     String[] kv = token.split("=", 2);
177                     content = content.replace(kv[0], kv[1]);
178                 }
179                 for (String token : params.regex) {
180                     String[] kv = token.split("!!!", 2);
181                     content = content.replaceAll(kv[0], kv[1]);
182                 }
183                 for (String alias : params.properties) {
184                     String[] kv = alias.split("=", 2);
185                     String loadedContent = generatePropertiesContent(new File(kv[1]));
186                     content = content.replace(kv[0], loadedContent);
187                 }
188                 for (String alias : params.loads) {
189                     String[] kv = alias.split("=", 2);
190                     String loadedContent = FileUtils.readContent(new File(kv[1]), "\n");
191                     loadedContent = StringUtils.escapeForHtml(loadedContent, false);
192                     loadedContent = StringUtils.breakLinesForHtml(loadedContent);
193                     content = content.replace(kv[0], loadedContent);
194                 }
195                 OutputStreamWriter writer = new OutputStreamWriter(new FileOutputStream(new File(
196                         destinationFolder, fileName)), Charset.forName("UTF-8"));
197                 writer.write(header);
198                 if (!StringUtils.isEmpty(htmlAdSnippet)) {
199                     writer.write(htmlAdSnippet);
200                 }
201                 writer.write(content);
202                 writer.write(footer);
203                 writer.close();
dd7961 204             } catch (Throwable t) {
JM 205                 System.err.println("Failed to transform " + file.getName());
206                 t.printStackTrace();
207             }
208         }
209     }
210
211     private static String getDocumentName(File file) {
2a7306 212         String displayName = file.getName().substring(0, file.getName().lastIndexOf('.'))
JM 213                 .toLowerCase();
892570 214         int underscore = displayName.indexOf('_') + 1;
JM 215         if (underscore > -1) {
216             // trim leading ##_ which is to control display order
217             return displayName.substring(underscore);
218         }
219         return displayName;
dd7961 220     }
a4d249 221
0aa6ec 222     private static String createLinks(File currentFile, File[] markdownFiles,
JM 223             Map<String, String> aliasMap, List<String> skips) {
224         String linkPattern = "<li><a href=''{0}''>{1}</a></li>";
225         String currentLinkPattern = "<li class=''active''><a href=''{0}''>{1}</a></li>";
226         StringBuilder sb = new StringBuilder();
227         for (File file : markdownFiles) {
228             String documentName = getDocumentName(file);
229             if (!skips.contains(documentName)) {
230                 String displayName = documentName;
231                 if (aliasMap.containsKey(documentName)) {
232                     displayName = aliasMap.get(documentName);
233                 } else {
234                     displayName = displayName.replace('_', ' ');
235                 }
236                 String fileName = documentName + ".html";
237                 if (currentFile.getName().equals(file.getName())) {
238                     sb.append(MessageFormat.format(currentLinkPattern, fileName, displayName));
239                 } else {
240                     sb.append(MessageFormat.format(linkPattern, fileName, displayName));
241                 }
242             }
243         }
244         sb.setLength(sb.length() - 3);
245         sb.trimToSize();
246         return sb.toString();
247     }
248
a3f474 249     private static String generatePropertiesContent(File propertiesFile) throws Exception {
JM 250         // Read the current Gitblit properties
251         BufferedReader propertiesReader = new BufferedReader(new FileReader(propertiesFile));
252
253         Vector<Setting> settings = new Vector<Setting>();
254         List<String> comments = new ArrayList<String>();
255         String line = null;
256         while ((line = propertiesReader.readLine()) != null) {
257             if (line.length() == 0) {
258                 Setting s = new Setting("", "", comments);
259                 settings.add(s);
260                 comments.clear();
261             } else {
262                 if (line.charAt(0) == '#') {
263                     comments.add(line.substring(1).trim());
264                 } else {
265                     String[] kvp = line.split("=", 2);
266                     String key = kvp[0].trim();
267                     Setting s = new Setting(key, kvp[1].trim(), comments);
268                     settings.add(s);
269                     comments.clear();
270                 }
271             }
272         }
273         propertiesReader.close();
274
275         StringBuilder sb = new StringBuilder();
276         for (Setting setting : settings) {
277             for (String comment : setting.comments) {
278                 if (comment.contains(SINCE) || comment.contains(RESTART_REQUIRED)
8f73a7 279                         || comment.contains(CASE_SENSITIVE) || comment.contains(SPACE_DELIMITED)) {
88598b 280                     sb.append(MessageFormat.format(
JM 281                             "<span style=\"color:#004000;\"># <i>{0}</i></span>",
282                             transformMarkdown(comment)));
a3f474 283                 } else {
88598b 284                     sb.append(MessageFormat.format("<span style=\"color:#004000;\"># {0}</span>",
JM 285                             transformMarkdown(comment)));
a3f474 286                 }
JM 287                 sb.append("<br/>\n");
288             }
289             if (!StringUtils.isEmpty(setting.name)) {
88598b 290                 sb.append(MessageFormat
JM 291                         .format("<span style=\"color:#000080;\">{0}</span> = <span style=\"color:#800000;\">{1}</span>",
292                                 setting.name, StringUtils.escapeForHtml(setting.value, false)));
a3f474 293             }
JM 294             sb.append("<br/>\n");
295         }
296
297         return sb.toString();
298     }
88598b 299
a3f474 300     private static String transformMarkdown(String comment) throws ParseException {
JM 301         String md = MarkdownUtils.transformMarkdown(comment);
302         if (md.startsWith("<p>")) {
303             md = md.substring(3);
304         }
305         if (md.endsWith("</p>")) {
306             md = md.substring(0, md.length() - 4);
307         }
308         return md;
309     }
310
dd7961 311     private static void usage(JCommander jc, ParameterException t) {
2a7306 312         System.out.println(Constants.getGitBlitVersion());
dd7961 313         System.out.println();
JM 314         if (t != null) {
315             System.out.println(t.getMessage());
316             System.out.println();
317         }
318         if (jc != null) {
319             jc.usage();
320         }
321         System.exit(0);
a3f474 322     }
JM 323
88598b 324     /**
JM 325      * Setting represents a setting with its comments from the properties file.
326      */
a3f474 327     private static class Setting {
JM 328         final String name;
329         final String value;
330         final List<String> comments;
331
332         Setting(String name, String value, List<String> comments) {
333             this.name = name;
334             this.value = value;
335             this.comments = new ArrayList<String>(comments);
336         }
dd7961 337     }
a4d249 338
88598b 339     /**
JM 340      * JCommander Parameters class for BuildSite.
341      */
dd7961 342     @Parameters(separators = " ")
JM 343     private static class Params {
344
345         @Parameter(names = { "--sourceFolder" }, description = "Markdown Source Folder", required = true)
346         public String sourceFolder;
347
348         @Parameter(names = { "--outputFolder" }, description = "HTML Ouptut Folder", required = true)
349         public String outputFolder;
350
351         @Parameter(names = { "--pageHeader" }, description = "Page Header HTML Snippet", required = true)
352         public String pageHeader;
353
354         @Parameter(names = { "--pageFooter" }, description = "Page Footer HTML Snippet", required = true)
355         public String pageFooter;
356
e7a153 357         @Parameter(names = { "--adSnippet" }, description = "Ad HTML Snippet", required = false)
JM 358         public String adSnippet;
359
360         @Parameter(names = { "--analyticsSnippet" }, description = "Analytics HTML Snippet", required = false)
361         public String analyticsSnippet;
362
424fe1 363         @Parameter(names = { "--skip" }, description = "Filename to skip", required = false)
JM 364         public List<String> skips = new ArrayList<String>();
365
e0054b 366         @Parameter(names = { "--alias" }, description = "Filename=Linkname aliases", required = false)
JM 367         public List<String> aliases = new ArrayList<String>();
368
1f9dae 369         @Parameter(names = { "--substitute" }, description = "%TOKEN%=value", required = false)
a4d249 370         public List<String> substitutions = new ArrayList<String>();
JM 371
1f9dae 372         @Parameter(names = { "--load" }, description = "%TOKEN%=filename", required = false)
JM 373         public List<String> loads = new ArrayList<String>();
374
a3f474 375         @Parameter(names = { "--properties" }, description = "%TOKEN%=filename", required = false)
JM 376         public List<String> properties = new ArrayList<String>();
377
230632 378         @Parameter(names = { "--nomarkdown" }, description = "%STARTTOKEN%:%ENDTOKEN%", required = false)
JM 379         public List<String> nomarkdown = new ArrayList<String>();
380
5c563c 381         @Parameter(names = { "--regex" }, description = "searchPattern!!!replacePattern", required = false)
JM 382         public List<String> regex = new ArrayList<String>();
383
dd7961 384     }
JM 385 }