]> gerrit.simantics Code Review - simantics/platform.git/blob - bundles/org.simantics.scl.compiler/src/org/simantics/scl/compiler/markdown/html/SCLDocumentationExtensionNodeHandler.java
Added anchors for SCL data types and classes in HTML documentation
[simantics/platform.git] / bundles / org.simantics.scl.compiler / src / org / simantics / scl / compiler / markdown / html / SCLDocumentationExtensionNodeHandler.java
1 package org.simantics.scl.compiler.markdown.html;
2
3 import java.util.ArrayList;
4 import java.util.Collections;
5
6 import org.simantics.scl.compiler.common.datatypes.Constructor;
7 import org.simantics.scl.compiler.elaboration.modules.SCLValue;
8 import org.simantics.scl.compiler.elaboration.modules.TypeClass;
9 import org.simantics.scl.compiler.elaboration.modules.TypeConstructor;
10 import org.simantics.scl.compiler.elaboration.modules.TypeDescriptor;
11 import org.simantics.scl.compiler.environment.filter.AcceptAllNamespaceFilter;
12 import org.simantics.scl.compiler.errors.Failable;
13 import org.simantics.scl.compiler.markdown.internal.ExtensionNodeHandler;
14 import org.simantics.scl.compiler.markdown.internal.HtmlEscape;
15 import org.simantics.scl.compiler.markdown.internal.MarkdownParser;
16 import org.simantics.scl.compiler.markdown.nodes.CodeBlockNode;
17 import org.simantics.scl.compiler.markdown.nodes.DocumentNode;
18 import org.simantics.scl.compiler.markdown.nodes.HtmlNode;
19 import org.simantics.scl.compiler.markdown.nodes.Node;
20 import org.simantics.scl.compiler.module.Module;
21 import org.simantics.scl.compiler.module.repository.ModuleRepository;
22 import org.simantics.scl.compiler.types.TPred;
23 import org.simantics.scl.compiler.types.TVar;
24 import org.simantics.scl.compiler.types.Types;
25 import org.simantics.scl.compiler.types.util.TypeUnparsingContext;
26 import org.slf4j.Logger;
27 import org.slf4j.LoggerFactory;
28
29 import gnu.trove.map.hash.THashMap;
30 import gnu.trove.procedure.TObjectProcedure;
31 import gnu.trove.set.hash.THashSet;
32
33 public class SCLDocumentationExtensionNodeHandler implements ExtensionNodeHandler {
34
35     private static final Logger LOGGER = LoggerFactory.getLogger(SCLDocumentationExtensionNodeHandler.class);
36     final ModuleRepository moduleRepository;
37     final String documentationName;
38     
39     Failable<Module> relatedModule;
40     THashMap<String,Failable<Module>> moduleCache = new THashMap<String,Failable<Module>>(); 
41     
42     THashSet<String> documentedValues = new THashSet<String>();
43     THashSet<String> documentedClasses = new THashSet<String>();
44     THashSet<String> documentedTypeConstructors = new THashSet<String>();
45     
46     public SCLDocumentationExtensionNodeHandler(ModuleRepository moduleRepository,
47             String documentationName) {
48         this.moduleRepository = moduleRepository;
49         this.documentationName = documentationName;
50     }
51
52     @Override
53     public DocumentNode expandBlock(String extension, String content) {
54         if(extension.equals("value")) {
55             String[] names = content.split(",");
56             DocumentNode container = new DocumentNode();
57             for(String name : names) {
58                 name = name.trim();
59                 if(name.isEmpty())
60                     continue;
61                 if(!documentedValues.add(name))
62                     LOGGER.warn("Value '" + name + "' has already been documented in " + documentationName + ".");
63                 generateValueDocumentation(container, name);
64             }
65             return container;
66         }
67         else if(extension.equals("class")) {
68             String[] names = content.split(",");
69             DocumentNode container = new DocumentNode();
70             for(String name : names) {
71                 name = name.trim();
72                 if(name.isEmpty())
73                     continue;
74                 if(!documentedClasses.add(name))
75                     LOGGER.warn("Class '" + name + "' has already been documented in " + documentationName + ".");
76                 generateClassDocumentation(container, name);
77             }
78             return container;
79         }
80         else if(extension.equals("data")) {
81             String[] names = content.split(",");
82             DocumentNode container = new DocumentNode();
83             for(String name : names) {
84                 name = name.trim();
85                 if(name.isEmpty())
86                     continue;
87                 if(!documentedTypeConstructors.add(name))
88                     LOGGER.warn("Type constructor '" + name + "' has already been documented in " + documentationName + ".");
89                 generateDataDocumentation(container, name);
90             }
91             return container;
92         }
93         else if(extension.equals("undocumented")) {
94             Module module = getRelatedModule();
95             if(module == null)
96                 return null;
97             final ArrayList<String> undocumentedValues = new ArrayList<String>(); 
98             module.findValuesForPrefix("", AcceptAllNamespaceFilter.INSTANCE, new TObjectProcedure<SCLValue>() {
99                 @Override
100                 public boolean execute(SCLValue value) {
101                     if(value.isPrivate())
102                         return true;
103                     String name = value.getName().name;
104                     if(documentedValues.contains(name) ||
105                             name.charAt(0) == '_' ||
106                             (name.contains("$") && Character.isAlphabetic(name.charAt(0))))
107                         return true;
108                     undocumentedValues.add(name);
109                     return true;
110                 }
111             });
112             Collections.sort(undocumentedValues);
113
114             DocumentNode container = new DocumentNode();
115             for(String name : undocumentedValues) {
116                 generateValueDocumentation(container, name);
117             }
118             return container;
119         }
120         else
121             return null;
122     }
123     
124     private Module getRelatedModule() {
125         if(relatedModule == null) {
126             relatedModule = moduleRepository.getModule(documentationName);
127             if(!relatedModule.didSucceed())
128                 LOGGER.warn("Couldn't load the module " + documentationName);
129         }
130         if(relatedModule.didSucceed())
131             return relatedModule.getResult();
132         else
133             return null;
134     }
135     
136     private Module getModule(String moduleName) {
137         Failable<Module> fm = moduleCache.get(moduleName);
138         if(fm == null) {
139             fm = moduleRepository.getModule(moduleName);
140             moduleCache.put(moduleName, fm);
141             if(!fm.didSucceed())
142                 LOGGER.warn("Couldn't load the module " + moduleName);
143         }
144         if(fm.didSucceed())
145             return fm.getResult();
146         else
147             return null;
148     }
149     
150     private void generateValueDocumentation(Node container, String name) {
151         int p = name.lastIndexOf('/');
152         Module module;
153         if(p >= 0) {
154             while(p > 0 && name.charAt(p-1) == '/')
155                 --p;
156             module = getModule(name.substring(0, p));
157             name = name.substring(p+1);
158         }
159         else
160             module = getRelatedModule();
161         
162         if(module != null)
163             generateValueDocumentation(container, module, name);
164     }
165     
166     private void generateValueDocumentation(Node container, Module module, String name, TypeUnparsingContext tuc) {
167         SCLValue value = module.getValue(name);
168         if(value == null) {
169             StringBuilder error = new StringBuilder();
170             error.append("Didn't find the value '" + name + "'.");
171             LOGGER.error(error.toString());
172             container.addChild(new CodeBlockNode(error));
173             return;
174         }
175         if(value.isPrivate())
176             LOGGER.warn("Documentation " + documentationName + " refers to a private value " + name + ".");
177         
178         StringBuilder signature = new StringBuilder();
179         signature.append("<div id=\"")
180         .append(HtmlEscape.escape(name))
181         .append("\" class=\"code-doc-box\"><div class=\"code value\">");
182         char firstChar = name.charAt(0);
183         if(!Character.isAlphabetic(firstChar) && firstChar != '_')
184             name = "(" + name + ")";
185         signature.append(HtmlEscape.escape(name)).append(" :: ");
186         String typeStr = Types.removeForAll(value.getType(), new ArrayList<TVar>()).toString(tuc);
187         signature.append(HtmlEscape.escape(typeStr));
188         String moduleName = module.getName();
189         if(!moduleName.equals(documentationName)) {
190             signature.append(" <span class=\"greyed\">(").append(moduleName).append(")</span>");
191         }
192         signature.append("</div><div class=\"doc\">");
193         container.addChild(new HtmlNode(signature));
194         
195         if(value.documentation != null) {
196             MarkdownParser parser = new MarkdownParser();
197             container.addChild(parser.parseDocument(value.documentation));
198         }
199         else
200             LOGGER.info(name);
201         container.addChild(new HtmlNode("</div></div>"));
202     }
203     
204     private void generateValueDocumentation(Node container, Module module, String name) {
205         generateValueDocumentation(container, module, name, new TypeUnparsingContext());
206     }
207     
208     private void generateClassDocumentation(Node container, String name) {
209         int p = name.lastIndexOf('/');
210         Module module;
211         if(p >= 0) {
212             module = getModule(name.substring(0, p));
213             name = name.substring(p+1);
214         }
215         else
216             module = getRelatedModule();
217         
218         if(module != null)
219             generateClassDocumentation(container, module, name);
220     }
221     
222     private void generateClassDocumentation(Node container, Module module, String name) {
223         TypeClass typeClass = module.getTypeClass(name);
224         if(typeClass == null) {
225             StringBuilder error = new StringBuilder();
226             error.append("Didn't find the type class '" + name + "'.");
227             LOGGER.error(error.toString());
228             container.addChild(new CodeBlockNode(error));
229             return;
230         }
231         
232         TypeUnparsingContext tuc = new TypeUnparsingContext();
233         StringBuilder signature = new StringBuilder();
234         signature.append("<div id=\"class-")
235         .append(HtmlEscape.escape(name))
236         .append("\" class=\"code-doc-box\"><div class=\"code class\">");
237         signature.append("class ");
238         if(typeClass.context.length > 0) {
239             signature.append('(');
240             boolean first = true;
241             for(TPred cx : typeClass.context) {
242                 if(first)
243                     first = false;
244                 else
245                     signature.append(", ");
246                 cx.toString(tuc, signature);
247             }
248             signature.append(") => ");
249         }
250         signature.append(name);
251         for(TVar p : typeClass.parameters) {
252             signature.append(' ');
253             p.toName(tuc, signature);
254         } 
255         signature.append("</div><div class=\"doc\">");
256         container.addChild(new HtmlNode(signature));
257         
258         if(typeClass.documentation != null) {
259             MarkdownParser parser = new MarkdownParser();
260             container.addChild(parser.parseDocument(typeClass.documentation));
261         }
262         else
263             LOGGER.info(name);
264         
265         for(String methodName : typeClass.methodNames) {
266             if(!documentedValues.add(methodName))
267                 LOGGER.warn("Method '" + methodName + "' has already been documented in " + documentationName + ".");
268             generateValueDocumentation(container, module, methodName, new TypeUnparsingContext(tuc));
269         }
270         container.addChild(new HtmlNode("</div></div>"));
271     }
272
273     private void generateDataDocumentation(Node container, String name) {
274         int p = name.lastIndexOf('/');
275         Module module;
276         if(p >= 0) {
277             module = getModule(name.substring(0, p));
278             name = name.substring(p+1);
279         }
280         else
281             module = getRelatedModule();
282         
283         if(module != null)
284             generateDataDocumentation(container, module, name);
285     }
286     
287     private void generateDataDocumentation(Node container, Module module, String name) {
288         TypeDescriptor typeDescriptor = module.getTypeDescriptor(name);
289         if(typeDescriptor == null) {
290             StringBuilder error = new StringBuilder();
291             error.append("Didn't find the type " + name + ".");
292             container.addChild(new CodeBlockNode(error));
293             return;
294         }
295         
296         TypeUnparsingContext tuc = new TypeUnparsingContext();
297         StringBuilder signature = new StringBuilder();
298         signature.append("<div id=\"data-")
299         .append(HtmlEscape.escape(name))
300         .append("\" class=\"code-doc-box\"><div class=\"code data\">");
301         signature.append("data ");
302         signature.append(typeDescriptor.name.name);
303         if(typeDescriptor instanceof TypeConstructor) {
304             for(TVar p : ((TypeConstructor)typeDescriptor).parameters) {
305                 signature.append(' ');
306                 p.toName(tuc, signature);
307             }
308         }
309         String moduleName = module.getName();
310         if(!moduleName.equals(documentationName)) {
311             signature.append(" <span class=\"greyed\">(").append(moduleName).append(")</span>");
312         }
313         signature.append("</div><div class=\"doc\">");
314         container.addChild(new HtmlNode(signature));
315         
316         if(typeDescriptor.getDocumentation() != null) {
317             MarkdownParser parser = new MarkdownParser();
318             container.addChild(parser.parseDocument(typeDescriptor.getDocumentation()));
319         }
320         else
321             LOGGER.info(name);
322         
323         if(typeDescriptor instanceof TypeConstructor) {
324             for(Constructor constructor : ((TypeConstructor)typeDescriptor).constructors) {
325                 if(!documentedValues.add(constructor.name.name))
326                     LOGGER.warn("Method '" + constructor.name.name + "' has already been documented in " + documentationName + ".");
327                 generateValueDocumentation(container, module, constructor.name.name, new TypeUnparsingContext(tuc));
328             }
329         }
330         container.addChild(new HtmlNode("</div></div>"));
331     }
332 }