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

Commit

Permalink
Browse files Browse the repository at this point in the history
[JENKINS-26126] Noting merge of #275.
  • Loading branch information
jglick committed Dec 16, 2015
2 parents 521099b + 43cb95a commit 4934c1c
Show file tree
Hide file tree
Showing 12 changed files with 565 additions and 62 deletions.
4 changes: 4 additions & 0 deletions CHANGES.md
Expand Up @@ -2,6 +2,10 @@

Only noting significant user changes, not internal code cleanups and minor bug fixes.

## 1.13 (upcoming)

* [JENKINS-26126](https://issues.jenkins-ci.org/browse/JENKINS-26126) (partial): introspect Workflow steps to generate static reference documentation (link from _Snippet Generator_). Planned to be used for IDE auto-completion as well.

## 1.12 (Dec 14 2015)

Same as beta 3.
Expand Down
Expand Up @@ -27,10 +27,12 @@
import groovy.lang.GroovyObjectSupport;
import hudson.EnvVars;
import hudson.Extension;
import hudson.model.EnvironmentContributor;
import hudson.model.Run;
import hudson.util.LogTaskListener;
import java.io.IOException;
import java.io.Serializable;
import java.util.Collection;
import java.util.Collections;
import java.util.Map;
import java.util.TreeMap;
Expand All @@ -39,6 +41,8 @@
import jenkins.model.RunAction2;
import org.jenkinsci.plugins.workflow.steps.EnvironmentExpander;
import org.jenkinsci.plugins.workflow.support.actions.EnvironmentAction;
import org.kohsuke.accmod.Restricted;
import org.kohsuke.accmod.restrictions.DoNotUse;
import org.kohsuke.stapler.export.Exported;
import org.kohsuke.stapler.export.ExportedBean;

Expand Down Expand Up @@ -126,6 +130,10 @@ private EnvActionImpl() {
throw new IllegalStateException("no associated build");
}
}
@Restricted(DoNotUse.class)
public Collection<EnvironmentContributor> getEnvironmentContributors() {
return EnvironmentContributor.all();
}
}

}
104 changes: 102 additions & 2 deletions cps/src/main/java/org/jenkinsci/plugins/workflow/cps/Snippetizer.java
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,105 @@ 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) {
pw.println("(help for variables not currently supported here)");
/* TODO RequestDispatcher.include sends that content, but then closes the stream and prevents further output from appearing.
Also ${rootURL} etc. are not set, but no idea what JellyContext to pass to Functions.initPageVariables
Not clear how to fix these issues except by rewriting all of this to be driven from a static.jelly page.
Also need to use new PrintWriter(new OutputStreamWriter(rsp.getOutputStream(), "UTF-8")) and pw.flush() at the end
(cannot use getWriter since RequestDispatcher.include will call getOutputStream).
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>" + DescribableHelper.CLAZZ + ": '" + 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
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<?jelly escape-by-default='true'?>
<j:jelly xmlns:j="jelly:core">
<j:jelly xmlns:j="jelly:core" xmlns:st="jelly:stapler">
<p>
Environment variables are accessible from Groovy code as <code>env.VARNAME</code>.
You can write to such properties as well:
Expand All @@ -26,14 +26,18 @@ node {
The following is a general list of variables (by name) that are available;
see the notes below the list for Workflow-specific details.
</p>
<iframe src="${rootURL}/env-vars.html" width="100%"/> <!-- TODO rather inline EnvironmentContributor/EnvVarsHtml/index.groovy -->
<!-- Cf. EnvironmentContributor/EnvVarsHtml/index.groovy -->
<j:forEach var="ec" items="${it.environmentContributors}">
<st:include it="${ec}" page="buildEnv" optional="true"/>
</j:forEach>
<p>
The following variables are currently unavailable inside a workflow script:
</p>
<ul>
<li><code>EXECUTOR_NUMBER</code></li>
<li><code>NODE_LABELS</code></li>
<li><code>WORKSPACE</code></li>
<!-- TODO when JENKINS-24141 fixed: SCM.all().each { e -> st.include(class:e.clazz, page:"buildEnv", optional:true) } -->
<li>SCM-specific variables such as <code>SVN_REVISION</code></li>
</ul>
<p>
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>

This file was deleted.

This file was deleted.

Expand Up @@ -40,22 +40,29 @@
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.logging.ConsoleHandler;
import java.util.logging.Handler;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.annotation.CheckForNull;
import javax.annotation.Nonnull;
import org.apache.commons.httpclient.NameValuePair;
import static org.hamcrest.CoreMatchers.*;
import org.jenkinsci.plugins.workflow.steps.CatchErrorStep;
import org.jenkinsci.plugins.workflow.steps.CoreStep;
import org.jenkinsci.plugins.workflow.steps.EchoStep;
import org.jenkinsci.plugins.workflow.steps.PwdStep;
import org.jenkinsci.plugins.workflow.steps.Step;
import org.jenkinsci.plugins.workflow.steps.StepDescriptor;
import org.jenkinsci.plugins.workflow.structs.DescribableHelper;
import org.jenkinsci.plugins.workflow.support.steps.ExecutorStep;
import org.jenkinsci.plugins.workflow.support.steps.StageStep;
import org.jenkinsci.plugins.workflow.support.steps.WorkspaceStep;
import org.jenkinsci.plugins.workflow.support.steps.build.BuildTriggerStep;
import org.jenkinsci.plugins.workflow.support.steps.input.InputStep;
import org.junit.Test;
import static org.junit.Assert.*;
import org.junit.BeforeClass;
import org.junit.ClassRule;
import org.jvnet.hudson.test.Email;
import org.jvnet.hudson.test.Issue;
Expand All @@ -66,6 +73,14 @@ public class SnippetizerTest {

@ClassRule public static JenkinsRule r = new JenkinsRule();

private static final Logger logger = Logger.getLogger(DescribableHelper.class.getName());
@BeforeClass public static void logging() {
logger.setLevel(Level.ALL);
Handler handler = new ConsoleHandler();
handler.setLevel(Level.ALL);
logger.addHandler(handler);
}

@Test public void basics() throws Exception {
assertRoundTrip(new EchoStep("hello world"), "echo 'hello world'");
StageStep s = new StageStep("Build");
Expand Down Expand Up @@ -188,4 +203,17 @@ private void assertGenerateSnippet(@Nonnull String json, @Nonnull String respons
assertEquals(responseText, response.getContentAsString().trim());
}

@Issue("JENKINS-26126")
@Test public void doStatic() throws Exception {
JenkinsRule.WebClient wc = r.createWebClient();
String html = wc.goTo(Snippetizer.STATIC_URL).getWebResponse().getContentAsString();
assertThat("text from LoadStep/help-path.html is included", html, containsString("the Groovy file to load"));
assertThat("SubversionSCM.workspaceUpdater is mentioned as an attribute of a value of GenericSCMStep.delegate", html, containsString("workspaceUpdater"));
assertThat("CheckoutUpdater is mentioned as an option", html, containsString("CheckoutUpdater"));
/* TODO see comment in Snippetizer
assertThat("text from RunWrapperBinder/help.jelly is included", html, containsString("may be used to refer to the currently running build"));
*/
assertThat("content is written to the end", html, containsString("</body></html>"));
}

}

0 comments on commit 4934c1c

Please sign in to comment.