James Moger
2011-10-02 f762b160efd5cafd919a6fd7f9587f578eceb454
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";
JM 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()));
8c5d72 94         String linkPattern = "<li><a href=''{0}''>{1}</a></li>";
dd7961 95         StringBuilder sb = new StringBuilder();
JM 96         for (File file : markdownFiles) {
e0054b 97             String documentName = getDocumentName(file);
424fe1 98             if (!params.skips.contains(documentName)) {
JM 99                 String displayName = documentName;
100                 if (aliasMap.containsKey(documentName)) {
101                     displayName = aliasMap.get(documentName);
333f55 102                 } else {
JM 103                     displayName = displayName.replace('_', ' ');
424fe1 104                 }
JM 105                 String fileName = documentName + ".html";
106                 sb.append(MessageFormat.format(linkPattern, fileName, displayName));
107                 sb.append(" | ");
e0054b 108             }
dd7961 109         }
JM 110         sb.setLength(sb.length() - 3);
111         sb.trimToSize();
a4d249 112
892570 113         String htmlHeader = FileUtils.readContent(new File(params.pageHeader), "\n");
a3f474 114
e7a153 115         String htmlAdSnippet = null;
JM 116         if (!StringUtils.isEmpty(params.adSnippet)) {
117             File snippet = new File(params.adSnippet);
118             if (snippet.exists()) {
119                 htmlAdSnippet = FileUtils.readContent(snippet, "\n");
120             }
121         }
892570 122         String htmlFooter = FileUtils.readContent(new File(params.pageFooter), "\n");
e7a153 123         String links = sb.toString();
JM 124         String header = MessageFormat.format(htmlHeader, Constants.FULL_NAME, links);
125         if (!StringUtils.isEmpty(params.analyticsSnippet)) {
126             File snippet = new File(params.analyticsSnippet);
127             if (snippet.exists()) {
128                 String htmlSnippet = FileUtils.readContent(snippet, "\n");
129                 header = header.replace("<!-- ANALYTICS -->", htmlSnippet);
130             }
a3f474 131         }
00afd7 132         final String date = new SimpleDateFormat("yyyy-MM-dd").format(new Date());
2a7306 133         final String footer = MessageFormat.format(htmlFooter, "generated " + date);
dd7961 134         for (File file : markdownFiles) {
JM 135             try {
e0054b 136                 String documentName = getDocumentName(file);
424fe1 137                 if (!params.skips.contains(documentName)) {
JM 138                     String fileName = documentName + ".html";
139                     System.out.println(MessageFormat.format("  {0} => {1}", file.getName(),
88598b 140                             fileName));
230632 141                     String rawContent = FileUtils.readContent(file, "\n");
JM 142                     String markdownContent = rawContent;
88598b 143
230632 144                     Map<String, List<String>> nomarkdownMap = new HashMap<String, List<String>>();
JM 145
146                     // extract sections marked as no-markdown
147                     int nmd = 0;
148                     for (String token : params.nomarkdown) {
149                         StringBuilder strippedContent = new StringBuilder();
150
151                         String nomarkdownKey = "%NOMARKDOWN" + nmd + "%";
152                         String[] kv = token.split(":", 2);
153                         String beginToken = kv[0];
154                         String endToken = kv[1];
155
156                         // strip nomarkdown chunks from markdown and cache them
157                         List<String> chunks = new Vector<String>();
158                         int beginCode = 0;
159                         int endCode = 0;
160                         while ((beginCode = markdownContent.indexOf(beginToken, endCode)) > -1) {
161                             if (endCode == 0) {
162                                 strippedContent.append(markdownContent.substring(0, beginCode));
163                             } else {
88598b 164                                 strippedContent.append(markdownContent
JM 165                                         .substring(endCode, beginCode));
166                             }
230632 167                             strippedContent.append(nomarkdownKey);
JM 168                             endCode = markdownContent.indexOf(endToken, beginCode);
169                             chunks.add(markdownContent.substring(beginCode, endCode));
170                             nomarkdownMap.put(nomarkdownKey, chunks);
171                         }
172
173                         // get remainder of text
174                         if (endCode < markdownContent.length()) {
88598b 175                             strippedContent.append(markdownContent.substring(endCode,
JM 176                                     markdownContent.length()));
230632 177                         }
JM 178                         markdownContent = strippedContent.toString();
88598b 179                         nmd++;
230632 180                     }
JM 181
182                     // transform markdown to html
183                     String content = transformMarkdown(markdownContent.toString());
184
185                     // reinsert nomarkdown chunks
88598b 186                     for (Map.Entry<String, List<String>> nomarkdown : nomarkdownMap.entrySet()) {
JM 187                         for (String chunk : nomarkdown.getValue()) {
230632 188                             content = content.replaceFirst(nomarkdown.getKey(), chunk);
JM 189                         }
190                     }
88598b 191
424fe1 192                     for (String token : params.substitutions) {
230632 193                         String[] kv = token.split("=", 2);
424fe1 194                         content = content.replace(kv[0], kv[1]);
a3f474 195                     }
88598b 196                     for (String token : params.regex) {
5c563c 197                         String[] kv = token.split("!!!", 2);
JM 198                         content = content.replaceAll(kv[0], kv[1]);
199                     }
a3f474 200                     for (String alias : params.properties) {
230632 201                         String[] kv = alias.split("=", 2);
a3f474 202                         String loadedContent = generatePropertiesContent(new File(kv[1]));
JM 203                         content = content.replace(kv[0], loadedContent);
424fe1 204                     }
JM 205                     for (String alias : params.loads) {
88598b 206                         String[] kv = alias.split("=", 2);
892570 207                         String loadedContent = FileUtils.readContent(new File(kv[1]), "\n");
424fe1 208                         loadedContent = StringUtils.escapeForHtml(loadedContent, false);
JM 209                         loadedContent = StringUtils.breakLinesForHtml(loadedContent);
210                         content = content.replace(kv[0], loadedContent);
211                     }
212                     OutputStreamWriter writer = new OutputStreamWriter(new FileOutputStream(
213                             new File(destinationFolder, fileName)), Charset.forName("UTF-8"));
214                     writer.write(header);
e7a153 215                     if (!StringUtils.isEmpty(htmlAdSnippet)) {
JM 216                         writer.write(htmlAdSnippet);
217                     }
424fe1 218                     writer.write(content);
JM 219                     writer.write(footer);
220                     writer.close();
dd7961 221                 }
JM 222             } catch (Throwable t) {
223                 System.err.println("Failed to transform " + file.getName());
224                 t.printStackTrace();
225             }
226         }
227     }
228
229     private static String getDocumentName(File file) {
2a7306 230         String displayName = file.getName().substring(0, file.getName().lastIndexOf('.'))
JM 231                 .toLowerCase();
892570 232         int underscore = displayName.indexOf('_') + 1;
JM 233         if (underscore > -1) {
234             // trim leading ##_ which is to control display order
235             return displayName.substring(underscore);
236         }
237         return displayName;
dd7961 238     }
a4d249 239
a3f474 240     private static String generatePropertiesContent(File propertiesFile) throws Exception {
JM 241         // Read the current Gitblit properties
242         BufferedReader propertiesReader = new BufferedReader(new FileReader(propertiesFile));
243
244         Vector<Setting> settings = new Vector<Setting>();
245         List<String> comments = new ArrayList<String>();
246         String line = null;
247         while ((line = propertiesReader.readLine()) != null) {
248             if (line.length() == 0) {
249                 Setting s = new Setting("", "", comments);
250                 settings.add(s);
251                 comments.clear();
252             } else {
253                 if (line.charAt(0) == '#') {
254                     comments.add(line.substring(1).trim());
255                 } else {
256                     String[] kvp = line.split("=", 2);
257                     String key = kvp[0].trim();
258                     Setting s = new Setting(key, kvp[1].trim(), comments);
259                     settings.add(s);
260                     comments.clear();
261                 }
262             }
263         }
264         propertiesReader.close();
265
266         StringBuilder sb = new StringBuilder();
267         for (Setting setting : settings) {
268             for (String comment : setting.comments) {
269                 if (comment.contains(SINCE) || comment.contains(RESTART_REQUIRED)
8f73a7 270                         || comment.contains(CASE_SENSITIVE) || comment.contains(SPACE_DELIMITED)) {
88598b 271                     sb.append(MessageFormat.format(
JM 272                             "<span style=\"color:#004000;\"># <i>{0}</i></span>",
273                             transformMarkdown(comment)));
a3f474 274                 } else {
88598b 275                     sb.append(MessageFormat.format("<span style=\"color:#004000;\"># {0}</span>",
JM 276                             transformMarkdown(comment)));
a3f474 277                 }
JM 278                 sb.append("<br/>\n");
279             }
280             if (!StringUtils.isEmpty(setting.name)) {
88598b 281                 sb.append(MessageFormat
JM 282                         .format("<span style=\"color:#000080;\">{0}</span> = <span style=\"color:#800000;\">{1}</span>",
283                                 setting.name, StringUtils.escapeForHtml(setting.value, false)));
a3f474 284             }
JM 285             sb.append("<br/>\n");
286         }
287
288         return sb.toString();
289     }
88598b 290
a3f474 291     private static String transformMarkdown(String comment) throws ParseException {
JM 292         String md = MarkdownUtils.transformMarkdown(comment);
293         if (md.startsWith("<p>")) {
294             md = md.substring(3);
295         }
296         if (md.endsWith("</p>")) {
297             md = md.substring(0, md.length() - 4);
298         }
299         return md;
300     }
301
dd7961 302     private static void usage(JCommander jc, ParameterException t) {
2a7306 303         System.out.println(Constants.getGitBlitVersion());
dd7961 304         System.out.println();
JM 305         if (t != null) {
306             System.out.println(t.getMessage());
307             System.out.println();
308         }
309         if (jc != null) {
310             jc.usage();
311         }
312         System.exit(0);
a3f474 313     }
JM 314
88598b 315     /**
JM 316      * Setting represents a setting with its comments from the properties file.
317      */
a3f474 318     private static class Setting {
JM 319         final String name;
320         final String value;
321         final List<String> comments;
322
323         Setting(String name, String value, List<String> comments) {
324             this.name = name;
325             this.value = value;
326             this.comments = new ArrayList<String>(comments);
327         }
dd7961 328     }
a4d249 329
88598b 330     /**
JM 331      * JCommander Parameters class for BuildSite.
332      */
dd7961 333     @Parameters(separators = " ")
JM 334     private static class Params {
335
336         @Parameter(names = { "--sourceFolder" }, description = "Markdown Source Folder", required = true)
337         public String sourceFolder;
338
339         @Parameter(names = { "--outputFolder" }, description = "HTML Ouptut Folder", required = true)
340         public String outputFolder;
341
342         @Parameter(names = { "--pageHeader" }, description = "Page Header HTML Snippet", required = true)
343         public String pageHeader;
344
345         @Parameter(names = { "--pageFooter" }, description = "Page Footer HTML Snippet", required = true)
346         public String pageFooter;
347
e7a153 348         @Parameter(names = { "--adSnippet" }, description = "Ad HTML Snippet", required = false)
JM 349         public String adSnippet;
350
351         @Parameter(names = { "--analyticsSnippet" }, description = "Analytics HTML Snippet", required = false)
352         public String analyticsSnippet;
353
424fe1 354         @Parameter(names = { "--skip" }, description = "Filename to skip", required = false)
JM 355         public List<String> skips = new ArrayList<String>();
356
e0054b 357         @Parameter(names = { "--alias" }, description = "Filename=Linkname aliases", required = false)
JM 358         public List<String> aliases = new ArrayList<String>();
359
1f9dae 360         @Parameter(names = { "--substitute" }, description = "%TOKEN%=value", required = false)
a4d249 361         public List<String> substitutions = new ArrayList<String>();
JM 362
1f9dae 363         @Parameter(names = { "--load" }, description = "%TOKEN%=filename", required = false)
JM 364         public List<String> loads = new ArrayList<String>();
365
a3f474 366         @Parameter(names = { "--properties" }, description = "%TOKEN%=filename", required = false)
JM 367         public List<String> properties = new ArrayList<String>();
368
230632 369         @Parameter(names = { "--nomarkdown" }, description = "%STARTTOKEN%:%ENDTOKEN%", required = false)
JM 370         public List<String> nomarkdown = new ArrayList<String>();
371
5c563c 372         @Parameter(names = { "--regex" }, description = "searchPattern!!!replacePattern", required = false)
JM 373         public List<String> regex = new ArrayList<String>();
374
dd7961 375     }
JM 376 }