Skip to content
This repository has been archived by the owner on Dec 15, 2021. It is now read-only.

Commit

Permalink
[JENKINS-26126] Crude static HTML reference documentation generator.
Browse files Browse the repository at this point in the history
  • Loading branch information
jglick committed Dec 15, 2015
1 parent 5f6e8fd commit 5cab320
Show file tree
Hide file tree
Showing 3 changed files with 157 additions and 45 deletions.
Expand Up @@ -26,11 +26,10 @@

import hudson.Extension;
import hudson.Functions;
import hudson.model.Descriptor;
import hudson.model.RootAction;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Comparator;
import java.util.List;
Expand All @@ -40,11 +39,13 @@
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.lang.model.SourceVersion;
import javax.servlet.RequestDispatcher;
import javax.servlet.ServletException;
import jenkins.model.Jenkins;
import net.sf.json.JSONObject;
import org.jenkinsci.plugins.workflow.steps.Step;
import org.jenkinsci.plugins.workflow.steps.StepDescriptor;
import org.jenkinsci.plugins.workflow.structs.DescribableHelper;
import org.kohsuke.accmod.Restricted;
import org.kohsuke.accmod.restrictions.DoNotUse;
import org.kohsuke.accmod.restrictions.NoExternalUse;
Expand Down Expand Up @@ -229,6 +230,100 @@ public HttpResponse doGenerateSnippet(StaplerRequest req, @QueryParameter String
}
}

@Restricted(NoExternalUse.class)
public static final String STATIC_URL = ACTION_URL + "/static";

@Restricted(DoNotUse.class)
public void doStatic(StaplerRequest req, StaplerResponse rsp) throws Exception {
rsp.setContentType("text/html;charset=UTF-8");
PrintWriter pw = rsp.getWriter();
pw.println("<html><head><title>Jenkins Workflow Reference</title></head><body>");
pw.println("<h1>Steps</h1>");
for (StepDescriptor d : getStepDescriptors(false)) {
generateStepHelp(d, pw);
}
pw.println("<h1>Advanced/Deprecated Steps</h1>");
for (StepDescriptor d : getStepDescriptors(true)) {
generateStepHelp(d, pw);
}
pw.println("<h1>Variables</h1>");
for (GlobalVariable v : getGlobalVariables()) {
pw.println("<h2><code>" + v.getName() + "</code></h2>");
RequestDispatcher rd = req.getView(v, "help");
if (rd != null) {
/* TODO does not work:
rd.include(req, rsp);
*/
} else {
pw.println("(no help)");
}
}
pw.println("</body></html>");
}
private static void generateStepHelp(StepDescriptor d, PrintWriter pw) throws Exception {
pw.println("<h2><code>" + d.getFunctionName() + "</code>: " + d.getDisplayName() + "</h2>");
try {
generateHelp(DescribableHelper.schemaFor(d.clazz), pw, 3);
} catch (Exception x) {
pw.println("<pre><code>" + /*Util.escape(Functions.printThrowable(x))*/x + "</code></pre>");
}
}
private static void generateHelp(DescribableHelper.Schema schema, PrintWriter pw, int headerLevel) throws Exception {
String help = schema.getHelp(null);
if (help != null) {
pw.println(help);
} // TODO else could use RequestDispatcher (as in Descriptor.doHelp) to serve template-based help
for (String attr : schema.mandatoryParameters()) {
pw.println("<h" + headerLevel + "><code>" + attr + "</code></h" + headerLevel + ">");
generateAttrHelp(schema, attr, pw, headerLevel);
}
for (String attr : schema.parameters().keySet()) {
if (schema.mandatoryParameters().contains(attr)) {
continue;
}
pw.println("<h" + headerLevel + "><code>" + attr + "</code> (optional)</h" + headerLevel + ">");
generateAttrHelp(schema, attr, pw, headerLevel);
}
}
private static void generateAttrHelp(DescribableHelper.Schema schema, String attr, PrintWriter pw, int headerLevel) throws Exception {
String help = schema.getHelp(attr);
if (help != null) {
pw.println(help);
}
DescribableHelper.ParameterType type = schema.parameters().get(attr);
describeType(type, pw, headerLevel);
}
private static void describeType(DescribableHelper.ParameterType type, PrintWriter pw, int headerLevel) throws Exception {
int nextHeaderLevel = Math.min(6, headerLevel + 1);
if (type instanceof DescribableHelper.AtomicType) {
pw.println("<p><strong>Type:</strong>" + type + "</p>");
} else if (type instanceof DescribableHelper.EnumType) {
pw.println("<p><strong>Values:</strong></p><ul>");
for (String v : ((DescribableHelper.EnumType) type).getValues()) {
pw.println("<li><code>" + v + "</code></li>");
}
pw.println("</ul>");
} else if (type instanceof DescribableHelper.ArrayType) {
pw.println("<p><strong>Array/List</strong></p>");
describeType(((DescribableHelper.ArrayType) type).getElementType(), pw, headerLevel);
} else if (type instanceof DescribableHelper.HomogeneousObjectType) {
pw.println("<p><strong>Nested object</strong></p>");
generateHelp(((DescribableHelper.HomogeneousObjectType) type).getType(), pw, nextHeaderLevel);
} else if (type instanceof DescribableHelper.HeterogeneousObjectType) {
pw.println("<p><strong>Nested choice of objects</strong></p><ul>");
for (Map.Entry<String,DescribableHelper.Schema> entry : ((DescribableHelper.HeterogeneousObjectType) type).getTypes().entrySet()) {
pw.println("<li><code>$class: '" + entry.getKey() + "'</code></li>");
generateHelp(entry.getValue(), pw, nextHeaderLevel);
}
pw.println("</ul>");
} else if (type instanceof DescribableHelper.ErrorType) {
Exception x = ((DescribableHelper.ErrorType) type).getError();
pw.println("<pre><code>" + /*Util.escape(Functions.printThrowable(x))*/x + "</code></pre>");
} else {
assert false : type;
}
}

private static class StepDescriptorComparator implements Comparator<StepDescriptor>, Serializable {
@Override
public int compare(StepDescriptor o1, StepDescriptor o2) {
Expand Down
Expand Up @@ -94,5 +94,8 @@ THE SOFTWARE.
</f:dropdownListBlock>
</j:forEach>
</f:dropdownList>
<f:block>
<a href="${rootURL}/${it.STATIC_URL}" target="_blank">Static documentation</a>
</f:block>
</f:optionalBlock>
</j:jelly>
Expand Up @@ -274,57 +274,61 @@ public String getDisplayName() {
public static abstract class ParameterType {
ParameterType() {}
static ParameterType of(Type type) {
if (type instanceof Class) {
Class<?> c = (Class<?>) type;
if (c == String.class || Primitives.unwrap(c).isPrimitive()) {
return new AtomicType(c);
}
if (Enum.class.isAssignableFrom(c)) {
List<String> constants = new ArrayList<String>();
for (Enum<?> value : c.asSubclass(Enum.class).getEnumConstants()) {
constants.add(value.name());
try {
if (type instanceof Class) {
Class<?> c = (Class<?>) type;
if (c == String.class || Primitives.unwrap(c).isPrimitive()) {
return new AtomicType(c);
}
return new EnumType(c, constants.toArray(new String[constants.size()]));
}
if (c == URL.class) {
return new AtomicType(String.class);
}
if (c.isArray()) {
return new ArrayType(of(c.getComponentType()));
}
// Assume it is a nested object of some sort.
Set<Class<?>> subtypes = findSubtypes(c);
if (subtypes.isEmpty() || subtypes.equals(Collections.singleton(c))) {
// Probably homogeneous. (Might be heterogeneous with no registered implementations, or might be concrete but subclassable.)
return new HomogeneousObjectType(schemaFor(c));
} else {
// Definitely heterogeneous.
Map<String,List<Class<?>>> subtypesBySimpleName = new HashMap<String,List<Class<?>>>();
for (Class<?> subtype : subtypes) {
String simpleName = subtype.getSimpleName();
List<Class<?>> bySimpleName = subtypesBySimpleName.get(simpleName);
if (bySimpleName == null) {
subtypesBySimpleName.put(simpleName, bySimpleName = new ArrayList<Class<?>>());
if (Enum.class.isAssignableFrom(c)) {
List<String> constants = new ArrayList<String>();
for (Enum<?> value : c.asSubclass(Enum.class).getEnumConstants()) {
constants.add(value.name());
}
bySimpleName.add(subtype);
return new EnumType(c, constants.toArray(new String[constants.size()]));
}
if (c == URL.class) {
return new AtomicType(String.class);
}
if (c.isArray()) {
return new ArrayType(of(c.getComponentType()));
}
Map<String,Schema> types = new TreeMap<String,Schema>();
for (Map.Entry<String,List<Class<?>>> entry : subtypesBySimpleName.entrySet()) {
if (entry.getValue().size() == 1) { // normal case: unambiguous via simple name
types.put(entry.getKey(), schemaFor(entry.getValue().get(0)));
} else { // have to diambiguate via FQN
for (Class<?> subtype : entry.getValue()) {
types.put(subtype.getName(), schemaFor(subtype));
// Assume it is a nested object of some sort.
Set<Class<?>> subtypes = findSubtypes(c);
if (subtypes.isEmpty() || subtypes.equals(Collections.singleton(c))) {
// Probably homogeneous. (Might be heterogeneous with no registered implementations, or might be concrete but subclassable.)
return new HomogeneousObjectType(schemaFor(c));
} else {
// Definitely heterogeneous.
Map<String,List<Class<?>>> subtypesBySimpleName = new HashMap<String,List<Class<?>>>();
for (Class<?> subtype : subtypes) {
String simpleName = subtype.getSimpleName();
List<Class<?>> bySimpleName = subtypesBySimpleName.get(simpleName);
if (bySimpleName == null) {
subtypesBySimpleName.put(simpleName, bySimpleName = new ArrayList<Class<?>>());
}
bySimpleName.add(subtype);
}
Map<String,Schema> types = new TreeMap<String,Schema>();
for (Map.Entry<String,List<Class<?>>> entry : subtypesBySimpleName.entrySet()) {
if (entry.getValue().size() == 1) { // normal case: unambiguous via simple name
types.put(entry.getKey(), schemaFor(entry.getValue().get(0)));
} else { // have to diambiguate via FQN
for (Class<?> subtype : entry.getValue()) {
types.put(subtype.getName(), schemaFor(subtype));
}
}
}
return new HeterogeneousObjectType(c, types);
}
return new HeterogeneousObjectType(c, types);
}
if (acceptsList(type)) {
return new ArrayType(of(((ParameterizedType) type).getActualTypeArguments()[0]));
}
throw new UnsupportedOperationException("do not know how to categorize attributes of type " + type);
} catch (Exception x) {
return new ErrorType(x);
}
if (acceptsList(type)) {
return new ArrayType(of(((ParameterizedType) type).getActualTypeArguments()[0]));
}
throw new UnsupportedOperationException("do not know how to categorize attributes of type " + type);
}
}

Expand Down Expand Up @@ -427,6 +431,16 @@ public Map<String,Schema> getTypes() {
}
}

public static final class ErrorType extends ParameterType {
private final Exception error;
ErrorType(Exception error) {
this.error = error;
}
public Exception getError() {
return error;
}
}

/**
* Removes configuration of any properties based on {@link DataBoundSetter} which appear unmodified from the default.
* @param clazz the class of {@code o}
Expand Down

0 comments on commit 5cab320

Please sign in to comment.