001/*
002 * Copyright (c) 2012-2017 Institut National des Sciences Appliquées de Lyon (INSA-Lyon)
003 *
004 * All rights reserved. This program and the accompanying materials
005 * are made available under the terms of the Eclipse Public License v1.0
006 * which accompanies this distribution, and is available at
007 * http://www.eclipse.org/legal/epl-v10.html
008 */
009
010package org.eclipse.golo.doc;
011
012import gololang.FunctionReference;
013import gololang.TemplateEngine;
014import gololang.Predefined;
015
016import java.io.IOException;
017import java.io.InputStream;
018import java.io.InputStreamReader;
019import java.nio.file.Path;
020import java.nio.charset.StandardCharsets;
021import java.util.HashMap;
022import java.util.Map;
023import java.util.Set;
024import java.util.TreeSet;
025
026public abstract class AbstractProcessor {
027
028  public abstract String render(ModuleDocumentation module) throws Throwable;
029
030  public abstract void process(Map<String, ModuleDocumentation> modules, Path targetFolder) throws Throwable;
031
032  private TemplateEngine templateEngine = new TemplateEngine();
033  private HashMap<String, FunctionReference> templateCache = new HashMap<>();
034
035  private Path targetFolder;
036  private Set<ModuleDocumentation> modules = new TreeSet<>();
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    modules.add(module);
052  }
053
054  protected String fileExtension() {
055    return "";
056  }
057
058  protected FunctionReference template(String name, String format) throws IOException {
059    String key = name + "-" + format;
060    if (templateCache.containsKey(key)) {
061      return templateCache.get(key);
062    }
063    InputStream in = AbstractProcessor.class.getResourceAsStream("/org/eclipse/golo/doc/" + key);
064    if (in == null) {
065      throw new IllegalArgumentException("There is no template " + name + " for format: " + format);
066    }
067    try (InputStreamReader reader = new InputStreamReader(in, StandardCharsets.UTF_8)) {
068      StringBuilder builder = new StringBuilder();
069      char[] buffer = new char[1024];
070      int nread;
071      while ((nread = reader.read(buffer)) > 0) {
072        builder.append(buffer, 0, nread);
073      }
074      FunctionReference compiledTemplate = templateEngine.compile(builder.toString());
075      templateCache.put(key, compiledTemplate);
076      return compiledTemplate;
077    }
078  }
079
080  public Path outputFile(String name) {
081    if (targetFolder == null) {
082      throw new IllegalStateException("no target folder defined");
083    }
084    return targetFolder.resolve(name.replace('.', '/')
085        + (fileExtension().isEmpty() ? "" : ("." + fileExtension())));
086  }
087
088  /**
089   * Return the absolute path of the file containing the given element.
090   */
091  public Path docFile(DocumentationElement doc) {
092    DocumentationElement parent = doc;
093    while (parent.parent() != parent) {
094      parent = parent.parent();
095    }
096    return outputFile(parent.name());
097  }
098
099  /**
100   * Returns the link to the given filename from the given document.
101   */
102  public String linkToFile(DocumentationElement src, String dst) {
103    Path doc = docFile(src);
104    if (doc.getParent() != null) {
105      doc = doc.getParent();
106    }
107    return doc.relativize(outputFile(dst)).toString();
108  }
109
110  /**
111   * Returns the link to the given filename from the given filename.
112   */
113  public String linkToFile(String src, String dst) {
114    Path out = outputFile(src);
115    if (out.getParent() != null) {
116      out = out.getParent();
117    }
118    return out.relativize(outputFile(dst)).toString();
119  }
120
121  protected void renderIndex(String templateName) throws Throwable {
122    FunctionReference indexTemplate = template(templateName, fileExtension());
123    String index = (String) indexTemplate.invoke(this);
124    Predefined.textToFile(index, outputFile(templateName));
125  }
126
127  /**
128   * Change the section level of the given markdown line.
129   * <p>
130   * For instance, {@code subsection("# Title", 1)} gives {@code "## Title"}
131   * <p>
132   * Used when displaying a markdown Golo documentation inside the golodoc, to avoid the user to
133   * remember at which level to start subsections for each documentation element.
134   */
135  private static String subsection(String line, int level) {
136    if (!line.trim().startsWith("#") || line.startsWith("    ")) {
137      return line;
138    }
139    StringBuilder output = new StringBuilder();
140    for (int i = 0; i < level; i++) {
141      output.append('#');
142    }
143    output.append(line.trim());
144    return output.toString();
145  }
146
147  public static String adaptSections(String documentation, int rootLevel) {
148    StringBuilder output = new StringBuilder();
149    for (String line: documentation.split("\n")) {
150      output.append(subsection(line, rootLevel));
151      output.append("\n");
152    }
153    return output.toString();
154  }
155}