diff --git a/jenkins-editor-other/testscripts/declarative-full/Jenkinsfile b/jenkins-editor-other/testscripts/declarative-full/Jenkinsfile index 476d36d..1b7fcee 100644 --- a/jenkins-editor-other/testscripts/declarative-full/Jenkinsfile +++ b/jenkins-editor-other/testscripts/declarative-full/Jenkinsfile @@ -1,213 +1,229 @@ -/** - * Jenkinsfile (Declarative Pipeline) - * ---------------------------------- - * a convergence file from examples of - * https://jenkins.io/doc/book/pipeline/jenkinsfile/ - * - * Not a really working pipeline script but enough to check outline handles all DSL parts when - * declarative... - */ +/** +* Jenkinsfile (Declarative Pipeline) +* ---------------------------------- +* a convergence file from examples of +* https://jenkins.io/doc/book/pipeline/jenkinsfile/ +* +* Not a really working pipeline script but enough to check outline handles all DSL parts when +* declarative... +*/ pipeline { - environment { - CC = 'clang' - } - - agent { - - docker { - image 'maven:3-alpine' - label 'my-defined-label' - args '-v /tmp:/tmp' - } - } - - triggers { - cron('H */4 * * 1-5') - } - - tools { - maven 'apache-maven-3.0.1' - } - - options { - timeout(time: 1, unit: 'HOURS') - } - - parameters { - string(name: 'Greeting', defaultValue: 'Hello', description: 'How should I greet the world?') - } - - stages{ - stage('Example') { - agent { dockerfile { dir 'someSubDir' } } - - environment { - DEBUG_FLAGS = '-g' - } - steps { - echo 'Hello World' - - script { - def browsers = ['chrome', 'firefox'] - for (int i = 0; i < browsers.size(); ++i) { - echo "Testing the ${browsers[i]} browser" - } - } - - sh 'printenv' - echo "Running ${env.BUILD_ID} on ${env.JENKINS_URL}" - } - } - - stage('Example Deploy1') { - when { - branch 'production' - } - steps { - echo 'Deploying' - } - } - stage('Example Deploy2') { - when { - allOf { - not { - branch 'production' - } - environment name: 'DEPLOY_TO', value: 'production' - } - } - steps { - echo 'Deploying' - } - } - - stage('Example Deploy3') { - agent { - node { - label 'my-defined-label' - customWorkspace '/some/other/path' - } -} - when { - branch 'production' - anyOf { - environment name: 'DEPLOY_TO', value: 'production' - environment name: 'DEPLOY_TO', value: 'staging' - } + environment { + CC = 'clang' + } + + agent { + + docker { + image 'maven:3-alpine' + label 'my-defined-label' + args '-v /tmp:/tmp' + } + } + + triggers { + cron('H */4 * * 1-5') + } + + tools { + maven 'apache-maven-3.0.1' + } + + options { + timeout(time: 1, unit: 'HOURS') + } + + parameters { + string(name: 'Greeting', defaultValue: 'Hello', description: 'How should I greet the world?') + } + + stages { + stage('Example') { + agent { + dockerfile { + dir 'someSubDir' } - steps { - echo 'Deploying' + + } + + environment { + DEBUG_FLAGS = '-g' + } + steps { + echo 'Hello World' + + script { + def browsers = ['chrome', 'firefox'] + for (int i = 0; i < browsers.size(); ++i) { + echo "Testing the $ { + browsers[i] + } + browser" + } } - } - stage('Parallel Stage') { - when { - branch 'master' - } - failFast true - parallel { - stage('Branch A') { - agent { - label "for-branch-a" - } - steps { - echo "On Branch A" - } - } - stage('Branch B') { - agent { - label "for-branch-b" - } - steps { - echo "On Branch B" - } - } - } - } - - stage('Build') { - agent any - steps { - checkout scm - sh 'make' - stash includes: '**/target/*.jar', name: 'app' - } - } - - stage('Test on Linux') { - agent { - label 'linux' - } - steps { - unstash 'app' - sh 'make check' - } - post { - always { - junit '**/target/*.xml' - } - } - } - stage('Test on Windows') { - agent { - label 'windows' - } - steps { - unstash 'app' - bat 'make check' - } - post { - always { - junit '**/target/*.xml' - } - } - } - - stage('Test') { - steps { - echo 'Testing..' - /* `make check` returns non-zero on test failures, - * using `true` to allow the Pipeline to continue nonetheless - */ - sh 'make check || true' - junit '**/target/*.xml' - } - } - stage('Deploy') { - when { - expression { - currentBuild.result == null || currentBuild.result == 'SUCCESS' - } - } - steps { - echo 'Deploying....' - sh 'make publish' - } - } - - } - post { - always { - junit '**/target/*.xml' - } - failure { - // mail to: team@example.com, subject: 'The Pipeline failed :(' - } - - changed { - // ... - } + } + + sh 'printenv' + echo "Running $ { + env.BUILD_ID + } + on $ { + env.JENKINS_URL + } + " + } + } +} + +stage('Example Deploy1') { + when { + branch 'production' + } + steps { + echo 'Deploying' + } +} +stage('Example Deploy2') { + when { + allOf { + not { + branch 'production' + } + environment name: 'DEPLOY_TO', value: 'production' + } + } + steps { + echo 'Deploying' + } +} + +stage('Example Deploy3') { + agent { + node { + label 'my-defined-label' + customWorkspace '/some/other/path' + } + } + when { + branch 'production' + anyOf { + environment name: 'DEPLOY_TO', value: 'production' + environment name: 'DEPLOY_TO', value: 'staging' + } + } + steps { + echo 'Deploying' + } +} +stage('Parallel Stage') { + when { + branch 'master' + } + failFast true + parallel { + stage('Branch A') { + agent { + label "for-branch-a" + } + steps { + echo "On Branch A" + } + } + stage('Branch B') { + agent { + label "for-branch-b" + } + steps { + echo "On Branch B" + } + } + } +} + +stage('Build') { + agent any + steps { + checkout scm + sh 'make' + stash includes: '**/target/*.jar', name: 'app' + } +} + +stage('Test on Linux') { + agent { + label 'linux' + } + steps { + unstash 'app' + sh 'make check' + } + post { + always { + junit '**/target/*.xml' + } + } +} +stage('Test on Windows') { + agent { + label 'windows' + } + steps { + unstash 'app' + bat 'make check' + } + post { + always { + junit '**/target/*.xml' + } + } +} + +stage('Test') { + steps { + echo 'Testing..' + /* `make check` returns non-zero on test failures, + * using `true` to allow the Pipeline to continue nonetheless + */ + sh 'make check || true' + junit '**/target/*.xml' + } +} +stage('Deploy') { + when { + expression { + currentBuild.result == null || currentBuild.result == 'SUCCESS' + } + } + steps { + echo 'Deploying....' + sh 'make publish' + } +} + +} +post { +always { + junit '**/target/*.xml' +} +failure { + // mail to: team@example.com, subject: 'The Pipeline failed :(' +} + +changed { + // ... +} + +success { + // ... +} + +unstable { + // ... +} + +aborted { + // ... +} +} - success { - // ... - } - - unstable { - // ... - } - - aborted { - // ... - } - } - } \ No newline at end of file diff --git a/jenkins-editor-plugin/plugin.xml b/jenkins-editor-plugin/plugin.xml index 38d8d87..8f5d021 100644 --- a/jenkins-editor-plugin/plugin.xml +++ b/jenkins-editor-plugin/plugin.xml @@ -25,6 +25,14 @@ id="jenkinseditor.preferences.keyword.editor" label="editor"> + + + + @@ -151,6 +159,12 @@ categoryId="jenkinseditor.commands.category" id="jenkinseditor.editor.commands.fetchLastBuildLogs"> + + @@ -183,6 +197,10 @@ commandId="jenkinseditor.editor.commands.fetchLastBuildLogs" class="de.jcup.jenkinseditor.handlers.RestoreBuildLogToConsoleHandler"> + + @@ -207,6 +225,12 @@ commandId="jenkinseditor.editor.commands.quickoutline" schemeId="org.eclipse.ui.defaultAcceleratorConfiguration"> + + @@ -244,7 +268,8 @@ value="de.jcup.jenkinseditor.JenkinsEditor"/> - + + @@ -356,6 +381,21 @@ id="jenkinseditor.preferences.keyword.tasks"> + + + + + + + + diff --git a/jenkins-editor-plugin/src/main/java-eclipse/de/jcup/jenkinseditor/JenkinsEditor.java b/jenkins-editor-plugin/src/main/java-eclipse/de/jcup/jenkinseditor/JenkinsEditor.java index d1cd5c3..a370d2c 100644 --- a/jenkins-editor-plugin/src/main/java-eclipse/de/jcup/jenkinseditor/JenkinsEditor.java +++ b/jenkins-editor-plugin/src/main/java-eclipse/de/jcup/jenkinseditor/JenkinsEditor.java @@ -15,6 +15,7 @@ */ package de.jcup.jenkinseditor; +import org.eclipse.jface.text.IDocument; import org.eclipse.jface.text.source.SourceViewerConfiguration; import org.eclipse.swt.widgets.Shell; import org.eclipse.ui.texteditor.IDocumentProvider; @@ -46,6 +47,8 @@ public class JenkinsEditor extends AbstractGroovyBasedEditor { private static final JenkinsFileDocumentProvider SHARED_FILE_DOCUMENT_PROVIDER = new JenkinsFileDocumentProvider(); private PipelineConfigData pipelineConfigData = new PipelineConfigData(); + + private static final SimpleJenkinsDeclartivePipelineSourceFormatter sourceFormatter = new SimpleJenkinsDeclartivePipelineSourceFormatter(); @Override @@ -113,7 +116,13 @@ protected String getEditorIconPath() { protected String getEditorIconPathOnError() { return "icons/jenkinseditor/jenkins-editor-with-error.png"; } - + String getText() { + IDocument doc = getDocument(); + if (doc == null) { + return ""; + } + return doc.get(); + } /** * * @return replay data for this script, never null @@ -122,4 +131,11 @@ public PipelineConfigData getReplayData() { return pipelineConfigData; } + public void formatSourceCode() { + String code = getText(); + sourceFormatter.setIndent(JenkinsEditorPreferences.getInstance().getSourceFormatIndention()); + String formatted = sourceFormatter.format(code); + getDocument().set(formatted); + } + } diff --git a/jenkins-editor-plugin/src/main/java-eclipse/de/jcup/jenkinseditor/handlers/FormatJenkinsSourceHandler.java b/jenkins-editor-plugin/src/main/java-eclipse/de/jcup/jenkinseditor/handlers/FormatJenkinsSourceHandler.java new file mode 100644 index 0000000..75802d0 --- /dev/null +++ b/jenkins-editor-plugin/src/main/java-eclipse/de/jcup/jenkinseditor/handlers/FormatJenkinsSourceHandler.java @@ -0,0 +1,28 @@ +/* + * Copyright 2019 Albert Tregnaghi + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions + * and limitations under the License. + * + */ + package de.jcup.jenkinseditor.handlers; + +import de.jcup.jenkinseditor.JenkinsEditor; + +public class FormatJenkinsSourceHandler extends AbstractJenkinsCLIHandler { + + @Override + protected void executeOnActiveJenkinsEditor(JenkinsEditor editor) { + editor.formatSourceCode(); + } + + +} \ No newline at end of file diff --git a/jenkins-editor-plugin/src/main/java-eclipse/de/jcup/jenkinseditor/preferences/JenkinsEditorFormatterPreferencePage.java b/jenkins-editor-plugin/src/main/java-eclipse/de/jcup/jenkinseditor/preferences/JenkinsEditorFormatterPreferencePage.java new file mode 100644 index 0000000..0d553c3 --- /dev/null +++ b/jenkins-editor-plugin/src/main/java-eclipse/de/jcup/jenkinseditor/preferences/JenkinsEditorFormatterPreferencePage.java @@ -0,0 +1,51 @@ +package de.jcup.jenkinseditor.preferences; +/* + * Copyright 2024 Albert Tregnaghi + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions + * and limitations under the License. + * + */ + +import static de.jcup.jenkinseditor.preferences.JenkinsEditorPreferenceConstants.*; + +import org.eclipse.jface.preference.FieldEditorPreferencePage; +import org.eclipse.jface.preference.IntegerFieldEditor; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.ui.IWorkbench; +import org.eclipse.ui.IWorkbenchPreferencePage; + +public class JenkinsEditorFormatterPreferencePage extends FieldEditorPreferencePage implements IWorkbenchPreferencePage { + + public JenkinsEditorFormatterPreferencePage() { + setPreferenceStore(JenkinsEditorPreferences.getInstance().getPreferenceStore()); + } + + @Override + public void init(IWorkbench workbench) { + + } + + @Override + protected void createFieldEditors() { + Composite parent = getFieldEditorParent(); + + IntegerFieldEditor indentEditor = new IntegerFieldEditor(P_SOURCE_FORMAT_INDENT.getId(), + "Indent / Tab replacement", parent); + indentEditor.setValidRange(2, 10); + addField(indentEditor); + indentEditor.getLabelControl(parent).setToolTipText( + "Amount of spaces used by source formatter to make indention"); + + + } + +} \ No newline at end of file diff --git a/jenkins-editor-plugin/src/main/java-eclipse/de/jcup/jenkinseditor/preferences/JenkinsEditorPreferenceConstants.java b/jenkins-editor-plugin/src/main/java-eclipse/de/jcup/jenkinseditor/preferences/JenkinsEditorPreferenceConstants.java index cb5206a..49fe702 100644 --- a/jenkins-editor-plugin/src/main/java-eclipse/de/jcup/jenkinseditor/preferences/JenkinsEditorPreferenceConstants.java +++ b/jenkins-editor-plugin/src/main/java-eclipse/de/jcup/jenkinseditor/preferences/JenkinsEditorPreferenceConstants.java @@ -39,6 +39,8 @@ public enum JenkinsEditorPreferenceConstants implements PreferenceIdentifiable{ P_EDITOR_CODEASSIST_NO_PROPOSALS_FOR_GETTER_OR_SETTERS("codeAssistNoProposalsForGetterOrSetter"), P_EDITOR_CODEASSIST_TOOLTIPS_ENABLED("codeAssistTooltipsEnabled"), + P_SOURCE_FORMAT_INDENT("sourceFormatIndent"), + P_JENKINS_URL("jenkinsURL"), P_PATH_TO_JENKINS_CLI_JAR("pathToJenkinsCliJar"), diff --git a/jenkins-editor-plugin/src/main/java-eclipse/de/jcup/jenkinseditor/preferences/JenkinsEditorPreferenceInitializer.java b/jenkins-editor-plugin/src/main/java-eclipse/de/jcup/jenkinseditor/preferences/JenkinsEditorPreferenceInitializer.java index 9a662aa..059dbff 100644 --- a/jenkins-editor-plugin/src/main/java-eclipse/de/jcup/jenkinseditor/preferences/JenkinsEditorPreferenceInitializer.java +++ b/jenkins-editor-plugin/src/main/java-eclipse/de/jcup/jenkinseditor/preferences/JenkinsEditorPreferenceInitializer.java @@ -49,6 +49,8 @@ public void initializeDefaultPreferences() { store.setDefault(P_EDITOR_CODEASSIST_PROPOSALS_ENABLED.getId(), true); store.setDefault(P_EDITOR_CODEASSIST_NO_PROPOSALS_FOR_GETTER_OR_SETTERS.getId(), true); store.setDefault(P_EDITOR_CODEASSIST_TOOLTIPS_ENABLED.getId(), true); + + store.setDefault(P_SOURCE_FORMAT_INDENT.getId(), 3); store.setDefault(P_EDITOR_SHOW_ALSO_NON_STRICT_CODE_PROPOSALS.getId(), false); @@ -81,7 +83,6 @@ public void initializeDefaultPreferences() { preferences.setDefaultColor(COLOR_JENKINS_VARIABLES, JenkinsEditorColorConstants.DARK_BLUE); preferences.setDefaultColor(COLOR_JAVA_LITERAL, JenkinsEditorColorConstants.KEYWORD_DEFAULT_PURPLE); - } private JenkinsEditorPreferences getPreferences() { diff --git a/jenkins-editor-plugin/src/main/java-eclipse/de/jcup/jenkinseditor/preferences/JenkinsEditorPreferences.java b/jenkins-editor-plugin/src/main/java-eclipse/de/jcup/jenkinseditor/preferences/JenkinsEditorPreferences.java index 1300357..4551244 100644 --- a/jenkins-editor-plugin/src/main/java-eclipse/de/jcup/jenkinseditor/preferences/JenkinsEditorPreferences.java +++ b/jenkins-editor-plugin/src/main/java-eclipse/de/jcup/jenkinseditor/preferences/JenkinsEditorPreferences.java @@ -81,6 +81,10 @@ public boolean isCodeAssistTooltipsEnabled() { public String getJenkinsURL() { return getStringPreference(JenkinsEditorPreferenceConstants.P_JENKINS_URL); } + + public int getSourceFormatIndention() { + return getPreferenceStore().getInt(P_SOURCE_FORMAT_INDENT.getId()); + } @Override public boolean isEditorAutoCreateEndBracketsEnabled() { diff --git a/jenkins-editor-plugin/src/main/java/de/jcup/jenkinseditor/SimpleJenkinsDeclartivePipelineSourceFormatter.java b/jenkins-editor-plugin/src/main/java/de/jcup/jenkinseditor/SimpleJenkinsDeclartivePipelineSourceFormatter.java new file mode 100644 index 0000000..aadc193 --- /dev/null +++ b/jenkins-editor-plugin/src/main/java/de/jcup/jenkinseditor/SimpleJenkinsDeclartivePipelineSourceFormatter.java @@ -0,0 +1,238 @@ +package de.jcup.jenkinseditor; + +import java.util.ArrayList; +import java.util.List; +import java.util.regex.Pattern; + +/** + * An extreme simple code formatter for Jenkins pipeline syntax (see + * https://www.jenkins.io/doc/book/pipeline/syntax/). + * + * We do not parse the groovy model here, but just inspect text and make some + * logic here. Simple, but should work + * + * @author Albert Tregnaghi + * + */ +public class SimpleJenkinsDeclartivePipelineSourceFormatter { + + private static final char CHAR_CLOSE_BRACKET = '}'; + private static final char CHAR_OPEN_BRACKET = '{'; + private static final String CLOSE_BRACKET = String.valueOf(CHAR_CLOSE_BRACKET); + private static final String QUOTED_CLOSE_BRACKET = Pattern.quote(CLOSE_BRACKET); + private static final String OPEN_BRACKET = String.valueOf(CHAR_OPEN_BRACKET); + private static final String QUOTED_OPEN_BRACKET = Pattern.quote(OPEN_BRACKET); + + VariableEscapeSupport variableEscapeSupport = new VariableEscapeSupport(); + + private int indent = 3; + + public void setIndent(int indent) { + this.indent = indent; + } + + public String format(String source) { + if (source == null) { + return null; + } + + String[] linesArray = source.split("\n"); + maskVariables(linesArray); + List lines1 = createListWithTrimmedLinesAndClosingBracketsHandled(linesArray); + List lines2 = createListWithClosingBracketsHandled(lines1); + handleIndention(lines2); + + return convertToString(lines2); + } + + private void maskVariables(String[] linesArray) { + for (int i = 0; i < linesArray.length; i++) { + String line = linesArray[i]; + linesArray[i]=variableEscapeSupport.escape(line); + } + } + + private List createListWithClosingBracketsHandled(List lines) { + List lines1 = createListWithOpeningBracketsAllInNewLine(lines); + List lines2 = movebackOpeningBracketsToFormerLine(lines1); + List lines3 = movebackClosingBracketsToFormerElse(lines2); + + return lines3; + } + + private List movebackOpeningBracketsToFormerLine(List linesWithOpenBracketsStandalone) { + List result2 = new ArrayList(linesWithOpenBracketsStandalone.size()); + for (int i = 0; i < linesWithOpenBracketsStandalone.size(); i++) { + String line = linesWithOpenBracketsStandalone.get(i); + if (i == 0) { + result2.add(line); + // first one is not inspected - just added + continue; + } + if (!line.contentEquals(OPEN_BRACKET)) { + result2.add(line); + continue; + } + // open bracket + int index = result2.size() - 1; + String old = result2.get(index); + if (old.trim().isEmpty()) { + // empty line - here we do not format, wished by developer + result2.add(line); + }else { + result2.remove(index); + result2.add(old + " " + OPEN_BRACKET); + } + + } + return result2; + } + + private List movebackClosingBracketsToFormerElse(List lines) { + List result2 = new ArrayList(lines.size()); + for (int i = 0; i < lines.size(); i++) { + String line = lines.get(i).trim(); + if (i == 0) { + result2.add(line); + // first one is not inspected - just added + continue; + } + if (!line.toLowerCase().startsWith("else")) { + result2.add(line); + continue; + } + // open bracket + int index = result2.size() - 1; + String old = result2.get(index); + if (old.trim().startsWith(CLOSE_BRACKET)){ + result2.remove(index); + result2.add(old+ " " + line.trim()); + }else { + result2.add(line); + } + + } + return result2; + } + + private List createListWithOpeningBracketsAllInNewLine(List lines) { + List result = new ArrayList(lines.size()); + + for (String line : lines) { + int count = count(CHAR_OPEN_BRACKET, line); + if (count > 0) { + String[] openedParts = line.split(QUOTED_OPEN_BRACKET); + if (openedParts.length > 0) { + int added = 0; + for (String open : openedParts) { + String trimmedPart = open.trim(); + if (! ("".contentEquals(trimmedPart))) { + result.add(trimmedPart); + } + added++; + if (added <= count) { + result.add(OPEN_BRACKET); + } + } + } else { + // edge case - only bracket(s)... + for (int b = 0; b < count; b++) { + result.add(OPEN_BRACKET); + } + } + + } else { + // just add as is + result.add(line); + } + } + return result; + } + + // trim all lines + add as list + separate closing parts + private List createListWithTrimmedLinesAndClosingBracketsHandled(String[] lines) { + List result = new ArrayList(lines.length); + for (int i = 0; i < lines.length; i++) { + String trimmed = lines[i].trim(); + + int count = count(CHAR_CLOSE_BRACKET, trimmed); + + if (count > 0) { + String[] closedParts = trimmed.split(QUOTED_CLOSE_BRACKET); + if (closedParts.length > 0) { + int added = 0; + for (String closed : closedParts) { + String trimmedPart = closed.trim(); + if (! ("".contentEquals(trimmedPart))) { + result.add(trimmedPart); + } + added++; + if (added <= count) { + result.add(CLOSE_BRACKET); + } + } + } else { + // edge case - only bracket(s)... + for (int b = 0; b < count; b++) { + result.add(CLOSE_BRACKET); + } + } + + } else { + result.add(trimmed); + } + } + return result; + } + + private void handleIndention(List list) { + int level = 0; + for (int i = 0; i < list.size(); i++) { + String line = list.get(i); + level = level - count('}', line); + + int currentLevel = level; + + level = level + count('{', line); + + String prefix = indent(currentLevel); + list.remove(i); + list.add(i, prefix + line); + + } + } + + private String convertToString(List list) { + int max = list.size(); + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < max; i++) { + String line = list.get(i); + sb.append(variableEscapeSupport.unescape(line)); + if (i < max - 1) { + sb.append("\n"); + } + } + return sb.toString(); + } + + private int count(char part, String line) { + int count = 0; + for (char c : line.toCharArray()) { + if (c == part) { + count++; + } + } + return count; + } + + private String indent(int level) { + StringBuilder sb = new StringBuilder(); + for (int l = 0; l < level; l++) { + for (int i = 0; i < indent; i++) { + sb.append(' '); + } + } + return sb.toString(); + } + +} diff --git a/jenkins-editor-plugin/src/main/java/de/jcup/jenkinseditor/VariableEscapeSupport.java b/jenkins-editor-plugin/src/main/java/de/jcup/jenkinseditor/VariableEscapeSupport.java new file mode 100644 index 0000000..bf99e8c --- /dev/null +++ b/jenkins-editor-plugin/src/main/java/de/jcup/jenkinseditor/VariableEscapeSupport.java @@ -0,0 +1,28 @@ +package de.jcup.jenkinseditor; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class VariableEscapeSupport { + + private static final String PREFIX_1 = "${"; + private static final String POSTFIX_1 = "}"; + private static final Pattern MASK_VARS = Pattern.compile("(\\$\\{)(.*)(\\})"); + private static final String QUOTE_REPLACEMENT1 = Matcher.quoteReplacement(PREFIX_1)+"$2"+Matcher.quoteReplacement(POSTFIX_1); + + private static final String PREFIX_2 = "________START_VARESCAPE____"; + private static final String POSTFIX_2 = "________END___VARESCAPE___"; + private static final String QUOTE_REPLACEMENT2 = Matcher.quoteReplacement(PREFIX_2)+"$2"+Matcher.quoteReplacement(POSTFIX_2); + + private static final Pattern UNMASK_VARS = Pattern.compile("("+PREFIX_2+")(.*)("+POSTFIX_2+")"); + + public String escape(String text) { + Matcher matcher = MASK_VARS.matcher(text); + return matcher.replaceAll(QUOTE_REPLACEMENT2); + } + + public String unescape(String text) { + Matcher matcher = UNMASK_VARS.matcher(text); + return matcher.replaceAll(QUOTE_REPLACEMENT1); + } +} diff --git a/jenkins-editor-plugin/src/test/java/de/jcup/jenkinseditor/SimpleJenkinsDeclartivePipelineSourceFormatterTest.java b/jenkins-editor-plugin/src/test/java/de/jcup/jenkinseditor/SimpleJenkinsDeclartivePipelineSourceFormatterTest.java new file mode 100644 index 0000000..035c6ad --- /dev/null +++ b/jenkins-editor-plugin/src/test/java/de/jcup/jenkinseditor/SimpleJenkinsDeclartivePipelineSourceFormatterTest.java @@ -0,0 +1,103 @@ +package de.jcup.jenkinseditor; + +import static org.junit.Assert.*; + +import java.nio.file.Path; +import java.nio.file.Paths; + +import org.junit.Before; +import org.junit.Test; + +import de.jcup.jenkinseditor.test.TestFileReader; + +public class SimpleJenkinsDeclartivePipelineSourceFormatterTest { + + private SimpleJenkinsDeclartivePipelineSourceFormatter toTest; + private static final Path FORMATTER_PARENT_FOLDER = Paths.get("./src/test/resources/formatter"); + + @Before + public void before() { + toTest = new SimpleJenkinsDeclartivePipelineSourceFormatter(); + } + + private void assertFormattedAsExpected(String filePartName) { + /* prepare */ + Path originPath = FORMATTER_PARENT_FOLDER.resolve(filePartName + ".jenkinsfile"); + Path expectedPath = FORMATTER_PARENT_FOLDER.resolve(filePartName + "-expected.jenkinsfile"); + + String origin = TestFileReader.DEFAULT.readTestFile(originPath); + String expected = TestFileReader.DEFAULT.readTestFile(expectedPath); + + /* execute */ + String formatted = toTest.format(origin); + + /* test */ + assertEquals(expected, formatted); + } + + @Test + public void format1_file_formatted_as_expected() throws Exception { + assertFormattedAsExpected("format1"); + } + + @Test + public void format2_file_formatted_as_expected() throws Exception { + assertFormattedAsExpected("format2"); + } + + @Test + public void format3_file_formatted_as_expected() throws Exception { + assertFormattedAsExpected("format3"); + } + + @Test + public void format4_file_formatted_as_expected() throws Exception { + assertFormattedAsExpected("format4"); + } + + @Test + public void format5_file_formatted_as_expected() throws Exception { + assertFormattedAsExpected("format5"); + } + + @Test + public void format6_file_formatted_as_expected() throws Exception { + assertFormattedAsExpected("format6"); + } + @Test + public void format7_indention_5_file_formatted_as_expected() throws Exception { + toTest.setIndent(5); + assertFormattedAsExpected("format7"); + } + + @Test + public void format10_file_formatted_as_expected() throws Exception { + assertFormattedAsExpected("format10"); + } + + @Test + public void format11_file_formatted_as_expected() throws Exception { + assertFormattedAsExpected("format11"); + } + + @Test + public void null_works_as_well() { + assertEquals(null, toTest.format(null)); + } + + @Test + public void empty_string() { + assertEquals("", toTest.format("")); + } + + @Test + public void blank_string() { + assertEquals("", toTest.format(" ")); + } + + @Test + public void pipeline_empty_one_liner() { + assertEquals("pipeline {\n}", toTest.format("pipeline {}")); + } + +} diff --git a/jenkins-editor-plugin/src/test/java/de/jcup/jenkinseditor/VariableEscapeSupportTest.java b/jenkins-editor-plugin/src/test/java/de/jcup/jenkinseditor/VariableEscapeSupportTest.java new file mode 100644 index 0000000..0ca193a --- /dev/null +++ b/jenkins-editor-plugin/src/test/java/de/jcup/jenkinseditor/VariableEscapeSupportTest.java @@ -0,0 +1,38 @@ +package de.jcup.jenkinseditor; + +import static org.junit.Assert.*; + +import org.junit.Before; +import org.junit.Test; + +public class VariableEscapeSupportTest { + + private VariableEscapeSupport supportToTest; + private static final String PREFIX = "________START_VARESCAPE____"; + private static final String POSTFIX = "________END___VARESCAPE___"; + + @Before + public void before() { + supportToTest = new VariableEscapeSupport(); + } + + @Test + public void escape_hello_world() { + assertEquals("hello world", supportToTest.escape("hello world")); + } + + @Test + public void unescape_hello_world() { + assertEquals("hello world", supportToTest.unescape("hello world")); + } + + @Test + public void escape_hello_var1() { + assertEquals("hello "+PREFIX+"env.var1"+POSTFIX, supportToTest.escape("hello ${env.var1}")); } + + @Test + public void unescape_hello_var1() { + assertEquals("hello ${env.var1}", supportToTest.unescape("hello "+PREFIX+"env.var1"+POSTFIX)); + } + +} diff --git a/jenkins-editor-plugin/src/test/java/de/jcup/jenkinseditor/test/TestFileReader.java b/jenkins-editor-plugin/src/test/java/de/jcup/jenkinseditor/test/TestFileReader.java new file mode 100644 index 0000000..3180e78 --- /dev/null +++ b/jenkins-editor-plugin/src/test/java/de/jcup/jenkinseditor/test/TestFileReader.java @@ -0,0 +1,29 @@ +package de.jcup.jenkinseditor.test; + +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Iterator; +import java.util.List; + +public class TestFileReader { + + public static final TestFileReader DEFAULT = new TestFileReader(); + + + public String readTestFile(Path path) { + try { + List lines = Files.readAllLines(path); + StringBuilder sb = new StringBuilder(); + for (Iterator lineIt=lines.iterator();lineIt.hasNext();) { + sb.append(lineIt.next()); + if (lineIt.hasNext()) { + sb.append("\n"); + } + } + return sb.toString(); + } catch (Exception e) { + throw new IllegalStateException("Cannot read test file: "+path, e); + } + + } +} diff --git a/jenkins-editor-plugin/src/test/resources/formatter/format1-expected.jenkinsfile b/jenkins-editor-plugin/src/test/resources/formatter/format1-expected.jenkinsfile new file mode 100644 index 0000000..cc99074 --- /dev/null +++ b/jenkins-editor-plugin/src/test/resources/formatter/format1-expected.jenkinsfile @@ -0,0 +1,4 @@ +pipeline { + agent any + something else +} \ No newline at end of file diff --git a/jenkins-editor-plugin/src/test/resources/formatter/format1.jenkinsfile b/jenkins-editor-plugin/src/test/resources/formatter/format1.jenkinsfile new file mode 100644 index 0000000..db35a3b --- /dev/null +++ b/jenkins-editor-plugin/src/test/resources/formatter/format1.jenkinsfile @@ -0,0 +1,4 @@ +pipeline { + agent any +something else +} \ No newline at end of file diff --git a/jenkins-editor-plugin/src/test/resources/formatter/format10-expected.jenkinsfile b/jenkins-editor-plugin/src/test/resources/formatter/format10-expected.jenkinsfile new file mode 100644 index 0000000..500fa30 --- /dev/null +++ b/jenkins-editor-plugin/src/test/resources/formatter/format10-expected.jenkinsfile @@ -0,0 +1,17 @@ +pipeline { + agent any + environment { + CC = 'clang' + } + // the stages... + stages { + stage('Example') { + environment { + DEBUG_FLAGS = '-g' + } + steps { + sh 'printenv' + } + } + } +} \ No newline at end of file diff --git a/jenkins-editor-plugin/src/test/resources/formatter/format10.jenkinsfile b/jenkins-editor-plugin/src/test/resources/formatter/format10.jenkinsfile new file mode 100644 index 0000000..3c29abb --- /dev/null +++ b/jenkins-editor-plugin/src/test/resources/formatter/format10.jenkinsfile @@ -0,0 +1,15 @@ +pipeline +{ + agent any + environment { + CC = 'clang' +} +// the stages... + stages + { + stage('Example') { + environment { + DEBUG_FLAGS = '-g' + } + steps { sh 'printenv' } } + }} \ No newline at end of file diff --git a/jenkins-editor-plugin/src/test/resources/formatter/format11-expected.jenkinsfile b/jenkins-editor-plugin/src/test/resources/formatter/format11-expected.jenkinsfile new file mode 100644 index 0000000..96f0a98 --- /dev/null +++ b/jenkins-editor-plugin/src/test/resources/formatter/format11-expected.jenkinsfile @@ -0,0 +1,65 @@ +node('node') { + + + currentBuild.result = "SUCCESS" + + try { + // this try will stop the outline scanning ! + + stage 'Checkout' // would never be part of outline because just a + // expression to call stage - no closure or method + + checkout scm + + stage 'Test' + + env.NODE_ENV = "test" + + print "Environment will be : ${env.NODE_ENV}" + + sh 'node -v' + sh 'npm prune' + sh 'npm install' + sh 'npm test' + + stage 'Build Docker' + + sh './dockerBuild.sh' + + stage 'Deploy' + + echo 'Push to Repo' + sh './dockerPushToRepo.sh' + + echo 'ssh to web server and tell it to pull new image' + sh 'ssh deploy@xxxxx.xxxxx.com running/xxxxxxx/dockerRun.sh' + + stage 'Cleanup' + + echo 'prune and cleanup' + sh 'npm prune' + sh 'rm node_modules -rf' + + mail body: 'project build successful', + from: 'xxxx@yyyyy.com', + replyTo: 'xxxx@yyyy.com', + subject: 'project build successful', + to: 'yyyyy@yyyy.com' + + } + + + catch (err) { + + currentBuild.result = "FAILURE" + + mail body: "project build error is here: ${env.BUILD_URL}" , + from: 'xxxx@yyyy.com', + replyTo: 'yyyy@yyyy.com', + subject: 'project build failed', + to: 'zzzz@yyyyy.com' + + throw err + } + +} \ No newline at end of file diff --git a/jenkins-editor-plugin/src/test/resources/formatter/format11.jenkinsfile b/jenkins-editor-plugin/src/test/resources/formatter/format11.jenkinsfile new file mode 100644 index 0000000..f25c6bb --- /dev/null +++ b/jenkins-editor-plugin/src/test/resources/formatter/format11.jenkinsfile @@ -0,0 +1,64 @@ +node('node') { + + + currentBuild.result = "SUCCESS" + + try { // this try will stop the outline scanning ! + + stage 'Checkout' // would never be part of outline because just a + // expression to call stage - no closure or method + + checkout scm + + stage 'Test' + + env.NODE_ENV = "test" + + print "Environment will be : ${env.NODE_ENV}" + + sh 'node -v' + sh 'npm prune' + sh 'npm install' + sh 'npm test' + + stage 'Build Docker' + + sh './dockerBuild.sh' + + stage 'Deploy' + + echo 'Push to Repo' + sh './dockerPushToRepo.sh' + + echo 'ssh to web server and tell it to pull new image' + sh 'ssh deploy@xxxxx.xxxxx.com running/xxxxxxx/dockerRun.sh' + + stage 'Cleanup' + + echo 'prune and cleanup' + sh 'npm prune' + sh 'rm node_modules -rf' + + mail body: 'project build successful', + from: 'xxxx@yyyyy.com', + replyTo: 'xxxx@yyyy.com', + subject: 'project build successful', + to: 'yyyyy@yyyy.com' + + } + + + catch (err) { + + currentBuild.result = "FAILURE" + + mail body: "project build error is here: ${env.BUILD_URL}" , + from: 'xxxx@yyyy.com', + replyTo: 'yyyy@yyyy.com', + subject: 'project build failed', + to: 'zzzz@yyyyy.com' + + throw err + } + +} \ No newline at end of file diff --git a/jenkins-editor-plugin/src/test/resources/formatter/format2-expected.jenkinsfile b/jenkins-editor-plugin/src/test/resources/formatter/format2-expected.jenkinsfile new file mode 100644 index 0000000..cc99074 --- /dev/null +++ b/jenkins-editor-plugin/src/test/resources/formatter/format2-expected.jenkinsfile @@ -0,0 +1,4 @@ +pipeline { + agent any + something else +} \ No newline at end of file diff --git a/jenkins-editor-plugin/src/test/resources/formatter/format2.jenkinsfile b/jenkins-editor-plugin/src/test/resources/formatter/format2.jenkinsfile new file mode 100644 index 0000000..670f10c --- /dev/null +++ b/jenkins-editor-plugin/src/test/resources/formatter/format2.jenkinsfile @@ -0,0 +1,3 @@ +pipeline { + agent any +something else} \ No newline at end of file diff --git a/jenkins-editor-plugin/src/test/resources/formatter/format3-expected.jenkinsfile b/jenkins-editor-plugin/src/test/resources/formatter/format3-expected.jenkinsfile new file mode 100644 index 0000000..a1a5231 --- /dev/null +++ b/jenkins-editor-plugin/src/test/resources/formatter/format3-expected.jenkinsfile @@ -0,0 +1,6 @@ +pipeline { + agent any + environment { + CC = 'clang' + } +} \ No newline at end of file diff --git a/jenkins-editor-plugin/src/test/resources/formatter/format3.jenkinsfile b/jenkins-editor-plugin/src/test/resources/formatter/format3.jenkinsfile new file mode 100644 index 0000000..552f540 --- /dev/null +++ b/jenkins-editor-plugin/src/test/resources/formatter/format3.jenkinsfile @@ -0,0 +1,5 @@ +pipeline { + agent any + environment { + CC = 'clang' +}} \ No newline at end of file diff --git a/jenkins-editor-plugin/src/test/resources/formatter/format4-expected.jenkinsfile b/jenkins-editor-plugin/src/test/resources/formatter/format4-expected.jenkinsfile new file mode 100644 index 0000000..a1a5231 --- /dev/null +++ b/jenkins-editor-plugin/src/test/resources/formatter/format4-expected.jenkinsfile @@ -0,0 +1,6 @@ +pipeline { + agent any + environment { + CC = 'clang' + } +} \ No newline at end of file diff --git a/jenkins-editor-plugin/src/test/resources/formatter/format4.jenkinsfile b/jenkins-editor-plugin/src/test/resources/formatter/format4.jenkinsfile new file mode 100644 index 0000000..e8543d2 --- /dev/null +++ b/jenkins-editor-plugin/src/test/resources/formatter/format4.jenkinsfile @@ -0,0 +1,6 @@ +pipeline { + agent any + environment + { + CC = 'clang' +}} \ No newline at end of file diff --git a/jenkins-editor-plugin/src/test/resources/formatter/format5-expected.jenkinsfile b/jenkins-editor-plugin/src/test/resources/formatter/format5-expected.jenkinsfile new file mode 100644 index 0000000..104ee00 --- /dev/null +++ b/jenkins-editor-plugin/src/test/resources/formatter/format5-expected.jenkinsfile @@ -0,0 +1,9 @@ +pipeline { + agent any + environment + + + { + CC = 'clang' + } +} \ No newline at end of file diff --git a/jenkins-editor-plugin/src/test/resources/formatter/format5.jenkinsfile b/jenkins-editor-plugin/src/test/resources/formatter/format5.jenkinsfile new file mode 100644 index 0000000..30867d4 --- /dev/null +++ b/jenkins-editor-plugin/src/test/resources/formatter/format5.jenkinsfile @@ -0,0 +1,8 @@ +pipeline { + agent any + environment + + + { + CC = 'clang' +}} \ No newline at end of file diff --git a/jenkins-editor-plugin/src/test/resources/formatter/format6-expected.jenkinsfile b/jenkins-editor-plugin/src/test/resources/formatter/format6-expected.jenkinsfile new file mode 100644 index 0000000..597abb5 --- /dev/null +++ b/jenkins-editor-plugin/src/test/resources/formatter/format6-expected.jenkinsfile @@ -0,0 +1,9 @@ +node { + stage('Example') { + if (env.BRANCH_NAME == 'master') { + echo 'I only execute on the master branch' + } else { + echo 'I execute elsewhere' + } + } +} \ No newline at end of file diff --git a/jenkins-editor-plugin/src/test/resources/formatter/format6.jenkinsfile b/jenkins-editor-plugin/src/test/resources/formatter/format6.jenkinsfile new file mode 100644 index 0000000..ee0560d --- /dev/null +++ b/jenkins-editor-plugin/src/test/resources/formatter/format6.jenkinsfile @@ -0,0 +1,9 @@ +node { + stage('Example') { + if (env.BRANCH_NAME == 'master') { + echo 'I only execute on the master branch' + } else { + echo 'I execute elsewhere' + } + } +} \ No newline at end of file diff --git a/jenkins-editor-plugin/src/test/resources/formatter/format7-expected.jenkinsfile b/jenkins-editor-plugin/src/test/resources/formatter/format7-expected.jenkinsfile new file mode 100644 index 0000000..108acaa --- /dev/null +++ b/jenkins-editor-plugin/src/test/resources/formatter/format7-expected.jenkinsfile @@ -0,0 +1,9 @@ +pipeline { + agent any + environment + + + { + CC = 'clang' + } +} \ No newline at end of file diff --git a/jenkins-editor-plugin/src/test/resources/formatter/format7.jenkinsfile b/jenkins-editor-plugin/src/test/resources/formatter/format7.jenkinsfile new file mode 100644 index 0000000..30867d4 --- /dev/null +++ b/jenkins-editor-plugin/src/test/resources/formatter/format7.jenkinsfile @@ -0,0 +1,8 @@ +pipeline { + agent any + environment + + + { + CC = 'clang' +}} \ No newline at end of file