package com.laytonsmith.tools.docgen; import com.laytonsmith.PureUtilities.ClassLoading.ClassDiscovery; import com.laytonsmith.PureUtilities.ClassLoading.ClassDiscoveryCache; import com.laytonsmith.PureUtilities.ClassLoading.DynamicClassLoader; import com.laytonsmith.PureUtilities.Common.StringUtils; import com.laytonsmith.annotations.api; import com.laytonsmith.commandhelper.CommandHelperFileLocations; import com.laytonsmith.core.events.Event; import com.laytonsmith.core.functions.Function; import java.io.File; import java.io.IOException; import java.io.OutputStream; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.net.MalformedURLException; import java.net.URL; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.HashMap; import java.util.Map.Entry; import java.util.regex.Matcher; import java.util.regex.Pattern; /** * * @author cgallarno */ public class ExtensionDocGen { private static final String nl = StringUtils.nl(); private static final Pattern WIKI_LINK = Pattern.compile("\\[([a-zA-Z]+://[^ ]+) ([^\\]]+)]"); public static void generate(File inputExtension, OutputStream outputStream) throws InstantiationException, IllegalAccessException, MalformedURLException, IOException { ClassDiscovery customDiscovery = new ClassDiscovery(); ClassDiscoveryCache cache = new ClassDiscoveryCache(CommandHelperFileLocations.getDefault().getCacheDirectory()); customDiscovery.setClassDiscoveryCache(cache); URL url = new URL("jar:" + inputExtension.toURI().toURL() + "!/"); customDiscovery.addDiscoveryLocation(url); customDiscovery.setDefaultClassLoader(ExtensionDocGen.class.getClassLoader()); StringBuilder fdocs = new StringBuilder(); DynamicClassLoader classloader = new DynamicClassLoader(); classloader.addJar(url); //functions HashMap<Class, ArrayList<Class<Function>>> functionMap = new HashMap<Class, ArrayList<Class<Function>>>(); for (Class<Function> cf : customDiscovery.loadClassesWithAnnotationThatExtend(api.class, Function.class, classloader, true)) { Class enclosing = cf.getEnclosingClass(); if (functionMap.containsKey(enclosing)) { functionMap.get(enclosing).add(cf); } else { functionMap.put(enclosing, new ArrayList<Class<Function>>()); functionMap.get(enclosing).add(cf); } } ArrayList<Entry<Class, ArrayList<Class<Function>>>> functionEntryList = new ArrayList<Entry<Class, ArrayList<Class<Function>>>>(functionMap.entrySet()); Collections.sort(functionEntryList, new Comparator<Entry<Class, ArrayList<Class<Function>>>>() { @Override public int compare(Entry<Class, ArrayList<Class<Function>>> o1, Entry<Class, ArrayList<Class<Function>>> o2) { return o1.getKey().getName().compareTo(o2.getKey().getName()); } }); for (Entry<Class, ArrayList<Class<Function>>> e : functionEntryList) { Collections.sort(e.getValue(), new Comparator<Class<Function>>() { @Override public int compare(Class<Function> o1, Class<Function> o2) { return o1.getName().compareTo(o2.getName()); } }); } if(!functionEntryList.isEmpty()){ fdocs.append("# Functions").append(nl); } for (Entry<Class, ArrayList<Class<Function>>> entry : functionEntryList) { Class enclosingClass = entry.getKey(); String[] split = enclosingClass.getName().split("\\."); fdocs.append("## ").append(split[split.length - 1]).append(nl); try { Method docsMethod = enclosingClass.getMethod("docs", (Class[]) null); Object o = enclosingClass.newInstance(); fdocs.append((String) docsMethod.invoke(o, (Object[]) null)).append(nl).append(nl); } catch (NoSuchMethodException | SecurityException | InstantiationException | IllegalAccessException | IllegalArgumentException | InvocationTargetException exception) { } for (Class<Function> cf : entry.getValue()) { Function f = cf.newInstance(); if (f.appearInDocumentation()) { DocGen.DocInfo di = new DocGen.DocInfo(f.docs()); String d = "### " + markdownEscape(di.ret) + " " + markdownEscape(f.getName()) + "(" + markdownEscape(di.originalArgs) + "):" + nl + convertWiki(di.topDesc != null?di.topDesc:di.desc) + nl + convertWiki(di.extendedDesc != null?nl + di.extendedDesc + nl:""); fdocs.append(d).append(nl); } } } //events HashMap<Class, ArrayList<Class<Event>>> eventMap = new HashMap<>(); for (Class<Event> ce : customDiscovery.loadClassesWithAnnotationThatExtend(api.class, Event.class, classloader, true)) { Class enclosing = ce.getEnclosingClass(); if (eventMap.containsKey(enclosing)) { eventMap.get(enclosing).add(ce); } else { eventMap.put(enclosing, new ArrayList<Class<Event>>()); eventMap.get(enclosing).add(ce); } } ArrayList<Entry<Class, ArrayList<Class<Event>>>> eventEntryList = new ArrayList<>(eventMap.entrySet()); Collections.sort(eventEntryList, new Comparator<Entry<Class, ArrayList<Class<Event>>>>() { @Override public int compare(Entry<Class, ArrayList<Class<Event>>> o1, Entry<Class, ArrayList<Class<Event>>> o2) { return o1.getKey().getName().compareTo(o2.getKey().getName()); } }); for (Entry<Class, ArrayList<Class<Event>>> e : eventEntryList) { Collections.sort(e.getValue(), new Comparator<Class<Event>>() { @Override public int compare(Class<Event> o1, Class<Event> o2) { return o1.getName().compareTo(o2.getName()); } }); } if(!eventEntryList.isEmpty()){ fdocs.append("# Events").append(nl); } for (Entry<Class, ArrayList<Class<Event>>> entry : eventEntryList) { Class enclosingClass = entry.getKey(); String[] split = enclosingClass.getName().split("\\."); fdocs.append("## ").append(split[split.length - 1]).append(nl); try { Method docsMethod = enclosingClass.getMethod("docs", (Class[]) null); Object o = enclosingClass.newInstance(); fdocs.append((String) docsMethod.invoke(o, (Object[]) null)).append(nl).append(nl); } catch (NoSuchMethodException | SecurityException | InstantiationException | IllegalAccessException | IllegalArgumentException | InvocationTargetException exception) { } for (Class<Event> ce : entry.getValue()) { Event e = ce.newInstance(); Pattern p = Pattern.compile("\\{(.*?)\\} *?(.*?) *?\\{(.*?)\\} *?\\{(.*?)\\}"); Matcher m = p.matcher(e.docs()); if (m.find()) { String name = e.getName(); String description = m.group(2).trim(); String prefilter = DocGen.PrefilterData.Get(m.group(1).split("\\|"), DocGen.MarkupType.MARKDOWN); String eventData = DocGen.EventData.Get(m.group(3).split("\\|"), DocGen.MarkupType.MARKDOWN); String mutability = DocGen.MutabilityData.Get(m.group(4).split("\\|"), DocGen.MarkupType.MARKDOWN); //String manualTrigger = ManualTriggerData.Get(m.group(5).split("\\|"), DocGen.MarkupType.MARKDOWN); //String since = e.since().toString(); fdocs.append("### ").append(markdownEscape(name)).append(nl); fdocs.append(description).append(nl); fdocs.append("#### Prefilters").append(nl).append(prefilter).append(nl); fdocs.append("#### Event Data").append(nl).append(eventData).append(nl); fdocs.append("#### Mutable Fields").append(nl).append(mutability).append(nl); } } } outputStream.write(fdocs.toString().getBytes("UTF-8")); } /** * Converts selected wiki markup into markdown. * @param input * @return */ private static String convertWiki(String input){ String output = input; Matcher m = WIKI_LINK.matcher(input); if(m.find()){ output = m.replaceAll("[$2]($1)"); } return output; } private static String markdownEscape(String input){ return input.replace("*", "\\*") .replace("_", "\\_"); } }