All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.jenkinsci.plugins.tokenmacro.TokenMacro Maven / Gradle / Ivy

The newest version!
/*
 * The MIT License
 *
 * Copyright 2011 CloudBees, Inc.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */
package org.jenkinsci.plugins.tokenmacro;

import com.google.common.collect.ListMultimap;
import hudson.ExtensionList;
import hudson.ExtensionPoint;
import hudson.Util;
import hudson.model.AbstractBuild;
import hudson.model.Hudson;
import hudson.model.TaskListener;
import org.apache.commons.lang.StringUtils;

import java.io.IOException;
import java.text.MessageFormat;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

/**
 * A macro that expands to text values in the context of a {@link AbstractBuild}.
 *
 * 

* Various plugins, such as email-ext and description-setter, has this concept of producing some textual * value out of a build (to become the e-mail content/subject, to be come the build description, etc), * and the user is allowed to configure how those strings look like. * *

* In such situation, it is useful to have a notion of "macro tokens", one that look like like ${foobar}, * and expands to some string value when evaluated. This is exactly such an abstraction, and it is placed * in its own plugin in the hope that it's reusable by other plugins. * *

* In more general form, the macro would have the following syntax structure: * *

 * ${MACRONAME [, ARG, ARG, ...]}
 * ARG := NAME [ = 'value' ]
 * 
* * *

Views

*

* Implementation should have help.jelly that renders a DT tag that shows the syntax of the macro, * followed by a DD tag that shows the details. See existing use of this extension point for the general * guide line of the syntax. * *

* Plugins interested in using the list of tags can use the "/lib/token-macro" taglib like the following, * which expands to the HTML that lists all the tags and their usages: * *

 * <help xmlons="/lib/token-macro"/>
 * 
* * @author Kohsuke Kawaguchi */ public abstract class TokenMacro implements ExtensionPoint { /** * Returns true if this object can evaluate the macro of the given name. * * @param macroName * By convention we encourage all caps name. * * @return true * ... to claim the macro of the given name and have {@link #evaluate(AbstractBuild, TaskListener, String, Map, ListMultimap)} called. */ public abstract boolean acceptsMacroName(String macroName); /** * Evaluates the macro and produces the token. * * *

Locale

*

* If the token is to produce a human readable text, it should do so by using the implicit locale associated * with the calling thread — see {@code Functions.getCurrentLocale()}. * * @param context * The build object for which this macro is evaluated. * @param listener * If the progress/status needs to be reported to the build console output, this object can be used. * @param macroName * The macro name that {@linkplain #acceptsMacroName(String) you accepted} * @param arguments * Arguments as a map. If multiple values are specified for one key, this will only retain the last one. * This is passed in separately from {@code argumentMultimap} because * @param argumentMultimap * The same arguments, but in a multi-map. If multiple values are specified for one key, all of them * are retained here in the order of appearance. For those macros that support multiple values for the same key * this is more accurate than {@code arguments}, but it's bit more tedious to use. * * @return * The result of the evaluation. Must not be null. * * @throws MacroEvaluationException * If the evaluation failed, for example because of the parameter error, and that the error message * should be presented. * @throws IOException * Other fatal {@link IOException}s that should leave the stack trace in the console. * @throws InterruptedException * If the evaluation involves some remoting operation, user might cancel the build, which results * in an {@link InterruptedException}. Don't catch it, just propagate. */ public abstract String evaluate(AbstractBuild context, TaskListener listener, String macroName, Map arguments, ListMultimap argumentMultimap) throws MacroEvaluationException, IOException, InterruptedException; /** * Returns true if this object allows for nested content replacements. * * @return true * ... to have the replaced text passed again to {@link #expand(AbstractBuild, TaskListener, String)} for additional expansion. */ public boolean hasNestedContent() { return false; } /** * All registered extension points. */ public static ExtensionList all() { return Hudson.getInstance().getExtensionList(TokenMacro.class); } /** * Expands all the macros, and throws an exception if there's any problem found. * * @param stringWithMacro * String that contains macro references in it, like "foo bar ${zot}". */ public static String expand(AbstractBuild context, TaskListener listener, String stringWithMacro) throws MacroEvaluationException, IOException, InterruptedException { return expand(context, listener, stringWithMacro, true, null); } public static String expand(AbstractBuild context, TaskListener listener, String stringWithMacro, boolean throwException, List privateTokens) throws MacroEvaluationException, IOException, InterruptedException { if ( StringUtils.isBlank( stringWithMacro ) ) return stringWithMacro; StringBuffer sb = new StringBuffer(); Tokenizer tokenizer = new Tokenizer(stringWithMacro); ExtensionList all = all(); if(privateTokens!=null) { all.addAll( privateTokens ); } while (tokenizer.find()) { String replacement = null; if(tokenizer.isEscaped()) { replacement = tokenizer.group().substring(1); } else { String tokenName = tokenizer.getTokenName(); ListMultimap args = tokenizer.getArgs(); Map map = new HashMap(); for (Entry e : args.entries()) { map.put(e.getKey(),e.getValue()); } for (TokenMacro tm : all) { if (tm.acceptsMacroName(tokenName)) { try { replacement = tm.evaluate(context,listener,tokenName,map,args); if(tm.hasNestedContent()) { replacement = expand(context,listener,replacement,throwException,privateTokens); } } catch(MacroEvaluationException e) { if(throwException) { throw e; } else { replacement = String.format("[Error replacing '%s' - %s]", tokenName, e.getMessage()); } } break; } } if (replacement == null && throwException) throw new MacroEvaluationException(String.format("Unrecognized macro '%s' in '%s'", tokenName, stringWithMacro)); } if (replacement == null && !throwException) // just put the token back in since we don't want to throw the exception tokenizer.appendReplacement(sb, tokenizer.group()); else tokenizer.appendReplacement(sb, replacement); } tokenizer.appendTail(sb); return sb.toString(); } /** * Expands everything that needs to be expanded. * * Expands all the macros, environment variables, and build variables. * Throws an exception if there's any problem found. * * This should be more convenient than having plugins do all 3 separately. * * @param stringWithMacro * String that contains macro references in it, like "foo bar ${zot}". */ public static String expandAll(AbstractBuild context, TaskListener listener, String stringWithMacro) throws MacroEvaluationException, IOException, InterruptedException { return expandAll(context,listener,stringWithMacro,true,null); } public static String expandAll(AbstractBuild context, TaskListener listener, String stringWithMacro, boolean throwException, List privateTokens) throws MacroEvaluationException, IOException, InterruptedException { // Do nothing for an empty String if (stringWithMacro==null || stringWithMacro.length()==0) return stringWithMacro; // Expand environment variables String s = context.getEnvironment(listener).expand(stringWithMacro); // Expand build variables s = Util.replaceMacro(s,context.getBuildVariableResolver()); // Expand Macros s = expand(context,listener,s,throwException,privateTokens); return s; } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy