]> gerrit.simantics Code Review - simantics/platform.git/blob
d9e65e28c39208b575234aae41e3a94000702912
[simantics/platform.git] /
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\">");
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 class=\"code-doc-box\"><div class=\"code\">");
235         signature.append("class ");
236         if(typeClass.context.length > 0) {
237             signature.append('(');
238             boolean first = true;
239             for(TPred cx : typeClass.context) {
240                 if(first)
241                     first = false;
242                 else
243                     signature.append(", ");
244                 cx.toString(tuc, signature);
245             }
246             signature.append(") => ");
247         }
248         signature.append(name);
249         for(TVar p : typeClass.parameters) {
250             signature.append(' ');
251             p.toName(tuc, signature);
252         } 
253         signature.append("</div><div class=\"doc\">");
254         container.addChild(new HtmlNode(signature));
255         
256         if(typeClass.documentation != null) {
257             MarkdownParser parser = new MarkdownParser();
258             container.addChild(parser.parseDocument(typeClass.documentation));
259         }
260         else
261             LOGGER.info(name);
262         
263         for(String methodName : typeClass.methodNames) {
264             if(!documentedValues.add(methodName))
265                 LOGGER.warn("Method '" + methodName + "' has already been documented in " + documentationName + ".");
266             generateValueDocumentation(container, module, methodName, new TypeUnparsingContext(tuc));
267         }
268         container.addChild(new HtmlNode("</div></div>"));
269     }
270
271     private void generateDataDocumentation(Node container, String name) {
272         int p = name.lastIndexOf('/');
273         Module module;
274         if(p >= 0) {
275             module = getModule(name.substring(0, p));
276             name = name.substring(p+1);
277         }
278         else
279             module = getRelatedModule();
280         
281         if(module != null)
282             generateDataDocumentation(container, module, name);
283     }
284     
285     private void generateDataDocumentation(Node container, Module module, String name) {
286         TypeDescriptor typeDescriptor = module.getTypeDescriptor(name);
287         if(typeDescriptor == null) {
288             StringBuilder error = new StringBuilder();
289             error.append("Didn't find the type " + name + ".");
290             container.addChild(new CodeBlockNode(error));
291             return;
292         }
293         
294         TypeUnparsingContext tuc = new TypeUnparsingContext();
295         StringBuilder signature = new StringBuilder();
296         signature.append("<div class=\"code-doc-box\"><div class=\"code\">");
297         signature.append("data ");
298         signature.append(typeDescriptor.name.name);
299         if(typeDescriptor instanceof TypeConstructor) {
300             for(TVar p : ((TypeConstructor)typeDescriptor).parameters) {
301                 signature.append(' ');
302                 p.toName(tuc, signature);
303             }
304         }
305         String moduleName = module.getName();
306         if(!moduleName.equals(documentationName)) {
307             signature.append(" <span class=\"greyed\">(").append(moduleName).append(")</span>");
308         }
309         signature.append("</div><div class=\"doc\">");
310         container.addChild(new HtmlNode(signature));
311         
312         if(typeDescriptor.getDocumentation() != null) {
313             MarkdownParser parser = new MarkdownParser();
314             container.addChild(parser.parseDocument(typeDescriptor.getDocumentation()));
315         }
316         else
317             LOGGER.info(name);
318         
319         if(typeDescriptor instanceof TypeConstructor) {
320             for(Constructor constructor : ((TypeConstructor)typeDescriptor).constructors) {
321                 if(!documentedValues.add(constructor.name.name))
322                     LOGGER.warn("Method '" + constructor.name.name + "' has already been documented in " + documentationName + ".");
323                 generateValueDocumentation(container, module, constructor.name.name, new TypeUnparsingContext(tuc));
324             }
325         }
326         container.addChild(new HtmlNode("</div></div>"));
327     }
328 }