-
-
Notifications
You must be signed in to change notification settings - Fork 86
Switching plugin to use ConsoleAnnotator rather than ConsoleNote #132
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 4 commits
a6548c4
6127b45
2c65175
4aef746
2f6981e
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,135 @@ | ||
| /* | ||
| * The MIT License | ||
| * | ||
| * Copyright 2018 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 hudson.plugins.ansicolor; | ||
|
|
||
| import hudson.Extension; | ||
| import hudson.MarkupText; | ||
| import hudson.console.ConsoleAnnotator; | ||
| import hudson.console.ConsoleAnnotatorFactory; | ||
| import hudson.model.Queue; | ||
| import hudson.model.Run; | ||
| import java.io.IOException; | ||
| import java.util.logging.Level; | ||
| import java.util.logging.Logger; | ||
| import jenkins.model.Jenkins; | ||
| import org.apache.commons.io.output.CountingOutputStream; | ||
| import org.apache.commons.io.output.NullOutputStream; | ||
| import org.apache.commons.lang.StringEscapeUtils; | ||
| import org.jenkinsci.plugins.workflow.flow.FlowExecutionOwner; | ||
| import org.jenkinsci.plugins.workflow.graph.FlowNode; | ||
|
|
||
| /** | ||
| * Applies ANSI coloration to log files where requested. | ||
| */ | ||
| final class ColorConsoleAnnotator extends ConsoleAnnotator<Object> { | ||
|
|
||
| private static final Logger LOGGER = Logger.getLogger(ColorConsoleAnnotator.class.getName()); | ||
|
|
||
| private static final long serialVersionUID = 1; | ||
|
|
||
| private final String colorMapName; | ||
| private final String charset; | ||
|
|
||
| ColorConsoleAnnotator(String colorMapName, String charset) { | ||
| this.colorMapName = colorMapName; | ||
| this.charset = charset; | ||
| LOGGER.fine("creating annotator with colorMapName=" + colorMapName + " charset=" + charset); | ||
| } | ||
|
|
||
| @Override | ||
| public ConsoleAnnotator<Object> annotate(Object context, MarkupText text) { | ||
| String s = text.getText(); | ||
| if (s.indexOf('\u001B') != -1) { | ||
| AnsiColorMap colorMap = Jenkins.get().getDescriptorByType(AnsiColorBuildWrapper.DescriptorImpl.class).getColorMap(colorMapName); | ||
| CountingOutputStream outgoing = new CountingOutputStream(new NullOutputStream()); | ||
| class EmitterImpl implements AnsiAttributeElement.Emitter { | ||
| CountingOutputStream incoming; | ||
| int adjustment; | ||
| int lastPoint = -1; // multiple HTML tags may be emitted for one control sequence | ||
|
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The triumph of test-driven development over comprehensibility. |
||
| @Override | ||
| public void emitHtml(String html) { | ||
| LOGGER.finest("emitting " + html + " @" + incoming.getCount()); | ||
| text.addMarkup(incoming.getCount(), html); | ||
| if (incoming.getCount() != lastPoint) { | ||
| lastPoint = incoming.getCount(); | ||
| int hide = incoming.getCount() - outgoing.getCount() - adjustment; | ||
| LOGGER.finest("hiding " + hide + " @" + (outgoing.getCount() + adjustment)); | ||
| text.addMarkup(outgoing.getCount() + adjustment, outgoing.getCount() + adjustment + hide, "<span style=\"display: none\">", "</span>"); | ||
| adjustment += hide; | ||
| } | ||
| } | ||
| } | ||
| EmitterImpl emitter = new EmitterImpl(); | ||
| CountingOutputStream incoming = new CountingOutputStream(new AnsiHtmlOutputStream(outgoing, colorMap, emitter)); | ||
| emitter.incoming = incoming; | ||
| try { | ||
| byte[] data = s.getBytes(charset); | ||
| for (int i = 0; i < data.length; i++) { | ||
| // Do not use write(byte[]) as offsets in incoming would not be accurate. | ||
| incoming.write(data[i]); | ||
| } | ||
| } catch (IOException x) { | ||
| LOGGER.log(Level.WARNING, null, x); | ||
| } | ||
| LOGGER.finer(() -> "\"" + StringEscapeUtils.escapeJava(s) + "\" → \"" + StringEscapeUtils.escapeJava(text.toString(true)) + "\""); | ||
| } | ||
| return this; | ||
| } | ||
|
|
||
| @Extension | ||
| public static final class Factory extends ConsoleAnnotatorFactory<Object> { | ||
|
|
||
| @Override | ||
| public ConsoleAnnotator<Object> newInstance(Object context) { | ||
| LOGGER.fine("context=" + context); | ||
| if (context instanceof Run) { | ||
| ColorizedAction action = ((Run) context).getAction(ColorizedAction.class); | ||
| if (action != null) { | ||
| return new ColorConsoleAnnotator(action.colorMapName, ((Run) context).getCharset().name()); | ||
| } | ||
| } else if (Jenkins.get().getPlugin("workflow-api") != null && context instanceof FlowNode) { | ||
|
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Did not try to verify behavior when the plugin is not in fact installed. Generally speaking,
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I agree that making it a normal dependency would be safer, but I did some testing with the plugins not installed and things seem to work fine. New freestyle builds are still colored correctly, and executing |
||
| FlowNode node = (FlowNode) context; | ||
| FlowExecutionOwner owner = node.getExecution().getOwner(); | ||
| if (owner != null) { | ||
| Queue.Executable exec = null; | ||
| try { | ||
| exec = owner.getExecutable(); | ||
| } catch (IOException x) { | ||
| LOGGER.log(Level.WARNING, null, x); | ||
| } | ||
| if (exec instanceof Run) { | ||
| ColorizedAction action = ((Run) exec).getAction(ColorizedAction.class); | ||
| if (action != null) { | ||
| return new ColorConsoleAnnotator(action.colorMapName, /* JEp-206 */ "UTF-8"); | ||
| } | ||
| } | ||
| } | ||
| } | ||
| return null; | ||
| } | ||
|
|
||
| } | ||
|
|
||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,41 @@ | ||
| /* | ||
| * The MIT License | ||
| * | ||
| * Copyright 2018 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 hudson.plugins.ansicolor; | ||
|
|
||
| import hudson.model.InvisibleAction; | ||
|
|
||
| /** | ||
| * Marker for the fact that a build used colorization. | ||
| * Note that the specific log span(s) are ignored. | ||
|
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Alternately, could go the route of jenkinsci/timestamper-plugin#25 and just have a global option to turn on ANSI coloring (with a specified colormap), and deprecate the step & build wrapper altogether. This would be more in line with how Blue Ocean works: it just assumes that if it sees these escape sequences, it should render them. |
||
| */ | ||
| final class ColorizedAction extends InvisibleAction { | ||
|
|
||
| final String colorMapName; | ||
|
|
||
| ColorizedAction(String colorMapName) { | ||
| this.colorMapName = colorMapName; | ||
| } | ||
|
|
||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Apparently I should have offered jenkinsci/jenkins#3662 up for backport. At the time it did not seem like a major fix. But in fact this patch will not work without it for freestyle builds or per-step logs (still works for Pipeline whole-build logs): you get a
Classfor thecontextand there is no way to look forColorizedAction.Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since that fix is not in any LTS line yet, I think it would be good to release what we currently have in master for anyone using JEP-210 in recent LTS lines, and then we can release this afterwards as a complete fix for future LTS lines. EDIT: Done with the recently released 0.5.3.