001/* 002 * Copyright (c) 2012-2018 Institut National des Sciences Appliquées de Lyon (INSA Lyon) and others 003 * 004 * This program and the accompanying materials are made available under the 005 * terms of the Eclipse Public License 2.0 which is available at 006 * http://www.eclipse.org/legal/epl-2.0. 007 * 008 * SPDX-License-Identifier: EPL-2.0 009 */ 010 011package org.eclipse.golo.doc; 012 013import gololang.FunctionReference; 014import gololang.TemplateEngine; 015import gololang.IO; 016 017import java.io.IOException; 018import java.io.InputStream; 019import java.io.InputStreamReader; 020import java.nio.file.Path; 021import java.nio.file.FileSystems; 022import java.nio.charset.StandardCharsets; 023import java.util.*; 024 025public abstract class AbstractProcessor { 026 027 public abstract String render(ModuleDocumentation module) throws Throwable; 028 029 public abstract void process(Collection<ModuleDocumentation> modules, Path targetFolder) throws Throwable; 030 031 private final TemplateEngine templateEngine = new TemplateEngine(); 032 private final Map<String, FunctionReference> templateCache = new HashMap<>(); 033 034 private Path targetFolder; 035 private final Set<ModuleDocumentation> modules = new TreeSet<>(); 036 private Map<String, Set<ModuleDocumentation>> packages = new TreeMap<>(); 037 038 public void setTargetFolder(Path target) { 039 this.targetFolder = target.toAbsolutePath(); 040 } 041 042 public Path getTargetFolder() { 043 return this.targetFolder; 044 } 045 046 public Set<ModuleDocumentation> modules() { 047 return modules; 048 } 049 050 protected void addModule(ModuleDocumentation module) { 051 this.modules.add(module); 052 if (!module.isEmpty()) { 053 String packageName = module.packageName(); 054 if (!packageName.isEmpty()) { 055 packages.putIfAbsent(packageName, new TreeSet<ModuleDocumentation>()); 056 packages.get(packageName).add(module); 057 } 058 } 059 } 060 061 protected Set<ModuleDocumentation> getSubmodulesOf(ModuleDocumentation doc) { 062 return this.packages.getOrDefault(doc.moduleName(), Collections.emptySet()); 063 } 064 065 public Set<Map.Entry<String, Set<ModuleDocumentation>>> getPackages() { 066 return this.packages.entrySet(); 067 } 068 069 protected String fileExtension() { 070 return ""; 071 } 072 073 protected FunctionReference template(String name, String format) throws IOException { 074 String key = name + "-" + format; 075 if (templateCache.containsKey(key)) { 076 return templateCache.get(key); 077 } 078 InputStream in = AbstractProcessor.class.getResourceAsStream("/org/eclipse/golo/doc/" + key); 079 if (in == null) { 080 throw new IllegalArgumentException("There is no template " + name + " for format: " + format); 081 } 082 try (InputStreamReader reader = new InputStreamReader(in, StandardCharsets.UTF_8)) { 083 StringBuilder builder = new StringBuilder(); 084 char[] buffer = new char[1024]; 085 int nread; 086 while ((nread = reader.read(buffer)) > 0) { 087 builder.append(buffer, 0, nread); 088 } 089 FunctionReference compiledTemplate = templateEngine.compile(builder.toString()); 090 templateCache.put(key, compiledTemplate); 091 return compiledTemplate; 092 } 093 } 094 095 public Path outputFile(String name) { 096 if (targetFolder == null) { 097 throw new IllegalStateException("no target folder defined"); 098 } 099 return targetFolder.resolve(name.replace(".", FileSystems.getDefault().getSeparator()) 100 + (fileExtension().isEmpty() ? "" : ("." + fileExtension()))); 101 } 102 103 /** 104 * Return the absolute path of the file containing the given element. 105 */ 106 public Path docFile(DocumentationElement doc) { 107 DocumentationElement parent = doc; 108 while (parent.parent() != parent) { 109 parent = parent.parent(); 110 } 111 if (parent instanceof ModuleDocumentation) { 112 return outputFile(((ModuleDocumentation) parent).moduleName()); 113 } 114 return outputFile(parent.name()); 115 } 116 117 /** 118 * Returns the link to the given filename from the given document. 119 */ 120 public String linkToFile(DocumentationElement src, String dst) { 121 Path doc = docFile(src); 122 if (doc.getParent() != null) { 123 doc = doc.getParent(); 124 } 125 // The replace is to have a valid relative uri on Windows... 126 // I'd rather use URI::relativize, but it only works when one URI is the strict prefix of the other 127 // i.e. can't generate relative URIs containing '..' (what a shame!) 128 return doc.relativize(outputFile(dst)).toString().replace(FileSystems.getDefault().getSeparator(), "/"); 129 } 130 131 /** 132 * Returns the link to the given filename from the given filename. 133 */ 134 public String linkToFile(String src, String dst) { 135 // 136 Path out = outputFile(src); 137 if (out.getParent() != null) { 138 out = out.getParent(); 139 } 140 // The replace is to have a valid relative uri on Windows... 141 // I'd rather use URI::relativize, but it only works when one URI is the strict prefix of the other 142 // i.e. can't generate relative URIs containing '..' (what a shame!) 143 return out.relativize(outputFile(dst)).toString().replace(FileSystems.getDefault().getSeparator(), "/"); 144 } 145 146 protected void renderIndex(String templateName) throws Throwable { 147 FunctionReference indexTemplate = template(templateName, fileExtension()); 148 String index = (String) indexTemplate.invoke(this); 149 IO.textToFile(index, outputFile(templateName)); 150 } 151 152 /** 153 * Change the section level of the given markdown line. 154 * <p> 155 * For instance, {@code subsection("# Title", 1)} gives {@code "## Title"} 156 * <p> 157 * Used when displaying a markdown Golo documentation inside the golodoc, to avoid the user to 158 * remember at which level to start subsections for each documentation element. 159 */ 160 private static String subsection(String line, int level) { 161 if (!line.trim().startsWith("#") || line.startsWith(" ")) { 162 return line; 163 } 164 StringBuilder output = new StringBuilder(); 165 for (int i = 0; i < level; i++) { 166 output.append('#'); 167 } 168 output.append(line.trim()); 169 return output.toString(); 170 } 171 172 public static String adaptSections(String documentation, int rootLevel) { 173 if (documentation == null || documentation.isEmpty()) { 174 return ""; 175 } 176 if (rootLevel == 0) { 177 return documentation; 178 } 179 StringBuilder output = new StringBuilder(); 180 for (String line: documentation.split("\n")) { 181 output.append(subsection(line, rootLevel)); 182 output.append("\n"); 183 } 184 return output.toString(); 185 } 186}