add a few scripts from org.evolvis.tartools:maven-parent
authormirabilos <t.glaser@tarent.de>
Thu, 20 Dec 2018 01:28:10 +0000 (02:28 +0100)
committermirabilos <mirabilos@evolvis.org>
Thu, 20 Dec 2018 01:28:10 +0000 (02:28 +0100)
• jenkins-job-backup sshs into a Jenkins and downloads updates
  for all *.xml files in the cwd, assuming their basename is
  a Jenkins job’s name
• mvnrepo.sh displays mvnrepository.com URLs of all dependencies
  and Maven plugins, for manual update checks
• sortdeps.sh sorts <dependencies> content (in a Maven POM)
• sortplug.sh sorts <plugins> content, similarily

mksh/jenkins-job-backup [new file with mode: 0644]
mksh/mvnrepo.sh [new file with mode: 0644]
mksh/mvnrepo.xsl [new file with mode: 0644]
mksh/progress-bar
mksh/sortdeps.sh [new file with mode: 0644]
mksh/sortplug.sh [new file with mode: 0644]

diff --git a/mksh/jenkins-job-backup b/mksh/jenkins-job-backup
new file mode 100644 (file)
index 0000000..dfc938e
--- /dev/null
@@ -0,0 +1,65 @@
+#!/usr/bin/env mksh
+# -*- mode: sh -*-
+#-
+# Copyright © 2016, 2017, 2018
+#      mirabilos <t.glaser@tarent.de>
+#
+# Provided that these terms and disclaimer and all copyright notices
+# are retained or reproduced in an accompanying document, permission
+# is granted to deal in this work without restriction, including un‐
+# limited rights to use, publicly perform, distribute, sell, modify,
+# merge, give away, or sublicence.
+#
+# This work is provided “AS IS” and WITHOUT WARRANTY of any kind, to
+# the utmost extent permitted by applicable law, neither express nor
+# implied; without malicious intent or gross negligence. In no event
+# may a licensor, author or contributor be held liable for indirect,
+# direct, other damage, loss, or other issues arising in any way out
+# of dealing in the work, even if advised of the possibility of such
+# damage or existence of a defect, except proven that it results out
+# of said person’s immediate fault when using the work as intended.
+#-
+# Update all locally-existing *.xml files from the same directory as
+# this script from a remote Jenkins’ job configuration (needs root).
+
+jenkins=ci-busyapps.lan.tarent.de
+
+LC_ALL=C; export LC_ALL
+unset LANGUAGE
+
+set -e
+set -o pipefail
+cd "$(dirname "$0")"
+
+for x in *.xml; do
+       if [[ ! -e $x ]]; then
+               print -r -- "E: ${x@Q} does not exist!"
+               exit 1
+       fi
+       print -nr -- "I: Processing $x…"
+       x=${x%.xml}
+
+       ssh -n -l root $jenkins "
+               cat /var/lib/jenkins/jobs/${x@Q}/config.xml
+               echo
+           " | sed --posix \
+           -e 's!\([&]\)apos[;]!\1#39;!g' \
+           -e 's!\([&]\)quot[;]!\1#34;!g' \
+           -e 's!\(-Dgpg.passphrase=\)[^ "]*\([ "]\)!\1<SECRET/>\2!g' \
+           >"$x.xml~"
+
+       while IFS= read -r line; do
+               print -r -- "$line"
+               [[ $line = '    <hudson.security.AuthorizationMatrixProperty>' ]] || \
+                   continue
+               while IFS= read -r line; do
+                       [[ $line = '    </hudson.security.AuthorizationMatrixProperty>' ]] && \
+                           break
+                       print -r -- "$line"
+               done | sort -u
+               print -r -- '    </hudson.security.AuthorizationMatrixProperty>'
+       done <"$x.xml~" >"$x.xml"
+       print " done."
+done
+rm -f *~
+print "I: All done, success."
diff --git a/mksh/mvnrepo.sh b/mksh/mvnrepo.sh
new file mode 100644 (file)
index 0000000..8372188
--- /dev/null
@@ -0,0 +1,219 @@
+#!/usr/bin/env mksh
+# -*- mode: sh -*-
+#-
+# Copyright © 2018
+#      mirabilos <t.glaser@tarent.de>
+#
+# Provided that these terms and disclaimer and all copyright notices
+# are retained or reproduced in an accompanying document, permission
+# is granted to deal in this work without restriction, including un‐
+# limited rights to use, publicly perform, distribute, sell, modify,
+# merge, give away, or sublicence.
+#
+# This work is provided “AS IS” and WITHOUT WARRANTY of any kind, to
+# the utmost extent permitted by applicable law, neither express nor
+# implied; without malicious intent or gross negligence. In no event
+# may a licensor, author or contributor be held liable for indirect,
+# direct, other damage, loss, or other issues arising in any way out
+# of dealing in the work, even if advised of the possibility of such
+# damage or existence of a defect, except proven that it results out
+# of said person’s immediate fault when using the work as intended.
+#-
+# Create mvnrepository.com URLs for all dependencies, extensions and
+# Maven plugins, sorted and categorised, for up-to-date checks.
+
+LC_ALL=C; export LC_ALL
+unset LANGUAGE
+
+set -e
+set -o pipefail
+me=$(realpath "$0/..")
+. "$me/assockit.ksh"
+. "$me/progress-bar"
+
+die() {
+       print -ru2 -- "E: $*"
+       exit 1
+}
+
+set -A grouping 'Parent' 'Project' 'Dependencies' 'Extensions' 'Plugins' 'Plugin Deps'
+
+function xml2path {
+       # thankfully only HT, CR, LF are, out of all C0 controls, valid in XML
+       xmlstarlet tr "$me/mvnrepo.xsl" "$1" | { tr '\n' '\ 1'; print; } | \
+           sed -e 's!\ 1$!!' -e "s!'\ 1//!'\ 2//!g" -e "s!'\\\\''\ 2!'\\\\''\ 1!g" | \
+           tr '\ 2' '\n' | \
+           grep -E "^[^']*/(groupId|artifactId|version)='" >target/pom.xp
+}
+
+function extract {
+       local line value base path p t x bron=$1 lines=$2
+
+       # thankfully neither ' nor = are valid in XML names
+       set +e
+       while IFS= read -r line; do
+               draw_progress_bar
+               if [[ $line = *'\ 1'* ]]; then
+                       print -ru2 -- "W: Embedded newline in ${line@Q}"
+                       continue
+               fi
+               value=${line#*=}
+               eval "value=$value" # trust our XSLT escaping code
+               (( $? )) && die "Error reading value from line ${line@Q}"
+               line=${line%%=*}
+               base=${line##*/}
+               line=${line%/*}
+               asso_sets "$value" "$bron" "$line" "$base"
+       done <target/pom.xp
+       asso_loadk "$bron"
+       for path in "${asso_y[@]}"; do
+               draw_progress_bar
+               p=${path//'['+([0-9])']'}
+               if [[ $p = //project/parent ]]; then
+                       t=0
+               elif [[ $p = //project ]]; then
+                       t=1
+               elif [[ $p = */extension ]]; then
+                       t=3
+               elif [[ $p = */plugin/dependencies/dependency ]]; then
+                       t=5
+               elif [[ $p = */plugin ]]; then
+                       t=4
+               elif [[ $p = */dependencies/dependency ]]; then
+                       t=2
+               elif [[ $p = */@(configuration|exclusions)/* ]]; then
+                       # skip well-known others silently
+                       continue
+               else
+                       print -ru2 -- "W: Unknown XPath: $path"
+                       continue
+               fi
+               x=$(asso_getv "$bron" "$path" groupId)
+               [[ -n $x ]] || case $t {
+               (1)     x=$(asso_getv "$bron" //project/parent groupId)
+                       [[ -n $x ]] || die "No groupId for project or parent"
+                       ;;
+               (4)     x=org.apache.maven.plugins
+                       #print -ru2 -- "W: missing groupId for $path"
+                       ;;
+               (*)     die "missing groupId for $path"
+                       ;;
+               }
+               [[ $x = */* ]] && die "wtf, groupId ${x@Q} for $path contains a slash"
+               t+=/$x
+               x=$(asso_getv "$bron" "$path" artifactId)
+               if [[ -n $x ]]; then
+                       t+=/$x
+                       [[ $x = */* ]] && die "wtf, artifactId ${x@Q} for $path contains a slash"
+                       x=$(asso_getv "$bron" "$path" version)
+                       [[ $x = *'${'* || $x = *[\\/:\"\<\>\|?*]* ]] && x=
+               fi
+               [[ -n $x ]] && asso_sets "$x" versions "$t"
+               asso_setnull $lines "$t"
+       done
+       set -e
+}
+
+function output {
+       local lineno=-1 nlines line vsn lines=$1
+       local last=-1 typ
+
+       set +e
+       asso_loadk $lines
+       nlines=${#asso_y[*]}
+       (( nlines )) || print -r -- "(none)"
+       while (( ++lineno < nlines )); do
+               draw_progress_bar
+               line=${asso_y[lineno]}
+               vsn=$(asso_getv versions "$line")
+               [[ -n $vsn ]] && line+=/$vsn
+               typ=${line::1}
+               line=${line:2}
+               if (( typ < 2 )); then
+                       print -nr -- "${grouping[typ]}: "
+               elif (( typ != last )); then
+                       print
+                       print -r -- "${grouping[typ]}:"
+               fi
+               (( last = typ ))
+               print -r -- "https://mvnrepository.com/artifact/$line"
+       done
+       set -e
+}
+
+function drop {
+       local lineno=-1 nlines line vsn lines=$1 from=$2
+
+       set +e
+       asso_loadk $lines
+       nlines=${#asso_y[*]}
+       while (( ++lineno < nlines )); do
+               draw_progress_bar
+               asso_unset $from "${asso_y[lineno]}"
+       done
+       set -e
+}
+
+mkdir -p target
+rm -f target/effective-pom.xml
+mvn -B -N help:effective-pom -Doutput=target/effective-pom.xml >&2 &
+xml2path pom.xml
+
+Lxp=$(wc -l <target/pom.xp)
+# first estimate
+Lxe=$((Lxp * 3 / 2))
+Lop=$((Lxp / 3))
+Loe=300 # outrageous, I know, but it makes things smoother
+set +e
+init_progress_bar $((2*Lxp + 2 + 2*Lxe + 1 + Lop + 1 + Lop + Loe))
+set -e
+
+LN=$_cur_progress_bar
+extract p plines
+Lxpr=$((_cur_progress_bar - LN))
+
+while [[ -n $(jobs) ]]; do wait; done
+set +e
+draw_progress_bar
+set -e
+
+xml2path target/effective-pom.xml
+
+Lxe=$(wc -l <target/pom.xp)
+# re-estimate
+Loe=$(( (Lxe-Lxp) / 3 ))
+set +e
+asso_loadk plines
+Lop=${#asso_y[*]}
+_cnt_progress_bar=$((Lxpr + 2 + 2*Lxe + 1 + Lop + 1 + Lop + Loe))
+draw_progress_bar
+set -e
+
+LN=$_cur_progress_bar
+extract e elines
+Lxer=$((_cur_progress_bar - LN))
+rm -f target/effective-pom.xml target/pom.xp
+
+# recalculate
+set +e
+asso_loadk elines
+Loe=${#asso_y[*]}
+_cnt_progress_bar=$((Lxpr + 2 + Lxer + 1 + Lop + 1 + Lop + Loe))
+draw_progress_bar
+set -e
+
+drop plines elines
+
+set +e
+asso_loadk elines
+Loe=${#asso_y[*]}
+_cnt_progress_bar=$((Lxpr + 2 + Lxer + 1 + Lop + 1 + Lop + Loe))
+draw_progress_bar
+set -e
+
+output plines
+print
+print Effective POM extras:
+output elines
+set +e
+done_progress_bar
diff --git a/mksh/mvnrepo.xsl b/mksh/mvnrepo.xsl
new file mode 100644 (file)
index 0000000..e5bf72b
--- /dev/null
@@ -0,0 +1,83 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+       This XSLT 1.0 file, converting XML to an XPath string list, is:
+       Copyright © 2018 mirabilos <t.glaser@tarent.de>
+       Licensor: tarent solutions GmbH, Bonn
+
+       This is a derivative work of an original Work retrieved from the
+       StackOverflow/StackExchange network, whose Original Author is:
+       © 2011 Dimitre Novatchev <https://stackoverflow.com/users/36305>
+       Source: https://stackoverflow.com/a/4747858/2171120
+       Question by ant <https://stackoverflow.com/users/169277>
+
+       Further incorporated works from the same site are by:
+
+       © 2014 Sam Harwell <https://stackoverflow.com/users/138304>
+       Source: https://stackoverflow.com/a/24831920/2171120
+       Question by Mithil <https://stackoverflow.com/users/34219>
+
+       © 2011 Mads Hansen <https://stackoverflow.com/users/14419>
+       Source: https://stackoverflow.com/a/7523245/2171120
+       Question by Paul <https://stackoverflow.com/users/925899>
+
+       This Adaption may be Distributed or Publicly Performed under the
+       CC-BY-SA 3.0 (unported) licence or (at Your option) any later
+       version of that licence, as published by Creative Commons, with
+       no associated URI or title of the Work supplied. Licence URI:
+       https://creativecommons.org/licenses/by-sa/3.0/legalcode.txt
+-->
+<!DOCTYPE xsl:stylesheet [
+<!ENTITY nl "&#x0A;">
+]>
+<!-- https://stackoverflow.com/a/4747858/2171120 -->
+<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
+       <xsl:output method="text" encoding="UTF-8" indent="no"/>
+       <xsl:strip-space elements="*"/>
+       <xsl:variable name="sq">'</xsl:variable>
+       <xsl:template name="quote">
+               <xsl:param name="str"/>
+               <xsl:choose>
+                       <xsl:when test="contains($str, $sq)">
+                               <xsl:value-of select="substring-before($str, $sq)"/>
+                               <xsl:text>'\''</xsl:text>
+                               <xsl:call-template name="quote">
+                                       <xsl:with-param name="str" select="substring-after($str, $sq)"/>
+                               </xsl:call-template>
+                       </xsl:when>
+                       <xsl:otherwise>
+                               <xsl:value-of select="$str"/>
+                       </xsl:otherwise>
+               </xsl:choose>
+       </xsl:template>
+       <xsl:template name="renderEQvalue">
+               <xsl:text>='</xsl:text>
+               <xsl:call-template name="quote">
+                       <xsl:with-param name="str" select="."/>
+               </xsl:call-template>
+       </xsl:template>
+       <xsl:template match="*[@* or not(*)]">
+               <xsl:if test="not(*)">
+                       <xsl:text>/</xsl:text>
+                       <xsl:apply-templates select="ancestor-or-self::*" mode="path"/>
+                       <xsl:call-template name="renderEQvalue"/>
+                       <xsl:text>'&nl;</xsl:text>
+               </xsl:if>
+               <xsl:apply-templates select="@*|*"/>
+       </xsl:template>
+       <xsl:template match="*" mode="path">
+               <xsl:value-of select="concat('/', name())"/>
+               <xsl:variable name="precSiblings" select="count(preceding-sibling::*[name()=name(current())])"/>
+               <xsl:variable name="nextSiblings" select="count(following-sibling::*[name()=name(current())])"/>
+               <xsl:if test="$precSiblings or $nextSiblings">
+                       <xsl:value-of select="concat('[', $precSiblings + 1, ']')"/>
+               </xsl:if>
+       </xsl:template>
+       <xsl:template match="@*">
+               <xsl:text>/</xsl:text>
+               <xsl:apply-templates select="../ancestor-or-self::*" mode="path"/>
+               <xsl:text>[@</xsl:text>
+               <xsl:value-of select="name()"/>
+               <xsl:call-template name="renderEQvalue"/>
+               <xsl:text>']&nl;</xsl:text>
+       </xsl:template>
+</xsl:stylesheet>
index 12b4353..f7a7d41 100644 (file)
@@ -1,8 +1,5 @@
 # -*- mode: sh -*-
-# $MirOS: contrib/hosted/tg/progress-bar,v 1.4 2018/12/02 08:12:31 tg Exp $
 #-
-# Copyright © 2018
-#      mirabilos <m@mirbsd.org>
 # Copyright © 2017
 #      mirabilos <t.glaser@tarent.de>
 # Copyright © 2015, 2017
 # init_progress_bar trashes the EXIT and SIGWINCH traps, which later
 # are cleared, again, by done_progress_bar; note this forces using a
 # “while [[ -n $(jobs) ]]; do wait; done” loop instead of just wait.
-# Use “redo_progress_bar [±]$n” to rerender updating the estimate.
 
 # global variables used by this library
 _cnt_progress_bar=0
 _cur_progress_bar=0
 isin_progress_bar=0
 nlin_progress_bar=0
-_cch_progress_bar=
 
 if [[ $KSH_VERSION = @(\@\(#\)MIRBSD KSH R)@(5[5-9]|[6-9][0-9]|[1-9][0-9][0-9])\ * ]]; then
        alias global='typeset -g'
@@ -52,9 +47,8 @@ fi
 function init_progress_bar {
        global -i _cnt_progress_bar=$1 _cur_progress_bar=0
        global -i nlin_progress_bar=$LINES isin_progress_bar=1
-       _cch_progress_bar=
 
-       trap 'done_progress_bar $?' EXIT
+       trap 'done_progress_bar 1' EXIT
        trap 'sigwinch_progress_bar' WINCH
        # set up scrolling region, draw initial empty bar
        sigwinch_progress_bar
@@ -80,14 +74,12 @@ function sigwinch_progress_bar {
 function done_progress_bar {
        (( isin_progress_bar )) || return 0
        isin_progress_bar=0
-       _cch_progress_bar=
        # save position; clear scrolling region; restore position;
        # save position; clear rest of screen; restore position
        print -nu2 "\\e7\\e[0;0r\\e8\\e7\\e[J\\e8"
        trap - WINCH
        trap - EXIT
-       [[ -z $1 ]] || return $1
-       (( _cur_progress_bar == _cnt_progress_bar )) || \
+       [[ -n $1 ]] || (( _cur_progress_bar == _cnt_progress_bar )) || \
            print -ru2 W: expected $_cnt_progress_bar draw_progress_bar calls, \
            got only $_cur_progress_bar
 }
@@ -102,29 +94,10 @@ function draw_progress_bar {
        draw_progress_bar_internal
 }
 
-function redo_progress_bar {
-       if [[ $1 = +* ]]; then
-               (( _cnt_progress_bar += ${1#+} ))
-       elif [[ $1 = -* ]]; then
-               (( _cnt_progress_bar -= ${1#-} ))
-       else
-               _cnt_progress_bar=$1
-       fi
-       if (( _cur_progress_bar > _cnt_progress_bar )); then
-               print -ru2 W: new estimate $_cnt_progress_bar too low \
-                   after $_cur_progress_bar calls
-               _cur_progress_bar=$_cnt_progress_bar
-       fi
-       _cch_progress_bar=
-       draw_progress_bar_internal
-}
-
 function draw_progress_bar_internal {
-       local bar num w=$COLUMNS pct
+       local bar num w=$COLUMNS
 
        ((# num = (_cur_progress_bar * w * 8) / _cnt_progress_bar ))
-       ((# pct = _cur_progress_bar * 100 / _cnt_progress_bar ))
-       [[ $_cch_progress_bar != $num.$pct ]] || return 0
        while ((# num >= 8 )); do
                bar+=█
                ((# num -= 8 ))
@@ -139,12 +112,11 @@ function draw_progress_bar_internal {
        (1) bar+=▏ ;;
        }
        # fill complete line, right-align completion percentage display
-       local -R$w spc="$pct%"
+       local -R$w spc="$((# _cur_progress_bar * 100 / _cnt_progress_bar))%"
        # elide percentage when it stops fitting
        ((# (_cur_progress_bar * w / _cnt_progress_bar) > (w - 4) )) && spc=
        # save position; go to last line; set colours;
        # output a line full of spaces (and completion percentage);
        # jump to first column; output bar (line præfix); restore position
        print -nu2 -- "\\e7\\e[$nlin_progress_bar;1H\\e[0;1;33;44m$spc\\r$bar\\e8"
-       _cch_progress_bar=$num.$pct
 }
diff --git a/mksh/sortdeps.sh b/mksh/sortdeps.sh
new file mode 100644 (file)
index 0000000..30831aa
--- /dev/null
@@ -0,0 +1,166 @@
+#!/usr/bin/env mksh
+# -*- mode: sh -*-
+#-
+# Copyright © 2018
+#      mirabilos <t.glaser@tarent.de>
+#
+# Provided that these terms and disclaimer and all copyright notices
+# are retained or reproduced in an accompanying document, permission
+# is granted to deal in this work without restriction, including un‐
+# limited rights to use, publicly perform, distribute, sell, modify,
+# merge, give away, or sublicence.
+#
+# This work is provided “AS IS” and WITHOUT WARRANTY of any kind, to
+# the utmost extent permitted by applicable law, neither express nor
+# implied; without malicious intent or gross negligence. In no event
+# may a licensor, author or contributor be held liable for indirect,
+# direct, other damage, loss, or other issues arising in any way out
+# of dealing in the work, even if advised of the possibility of such
+# damage or existence of a defect, except proven that it results out
+# of said person’s immediate fault when using the work as intended.
+#-
+# Pipe a Maven POM <dependencies> or <exclusions> element’s content,
+# without the surrounding grouping element, into this to sort them.
+
+LC_ALL=C; export LC_ALL
+unset LANGUAGE
+
+set -e
+cr=$'\r'
+lf=$'\n'
+(( ${#cr} == 1 ))
+set -o pipefail
+
+# offsets in element array
+OTYPE=0
+OgroupId=1
+OartifactId=2
+Oversion=5
+Otype=4
+Oclassifier=3
+Oscope=6
+OsystemPath=8
+Oexclusions=9
+Ooptional=7
+OPRECOMMENT=10
+OINCOMMENT=11
+
+exec 4<&0
+(
+       print '<toplevel>'
+       tr "$cr" "$lf" <&4
+       print '</toplevel>'
+) | xmlstarlet fo -e UTF-8 -n - |&
+
+die() {
+       print -ru2 -- "E: $*"
+       print -ru2 -- "N: line: ${line@Q}"
+       exit 1
+}
+
+set -A sortlines
+IFS= read -pr line
+[[ $line = '<?xml version="1.0" encoding="UTF-8"?>' ]] || \
+    die unexpected first line not XML declaration
+IFS= read -pr line
+[[ $line = '<toplevel>' ]] || die unexpected second line not start tag
+
+set -A el
+state=0
+while IFS= read -pr line; do
+       case $state:$line {
+       (0:'<!--'*'-->')
+               el[OPRECOMMENT]+=$line ;;
+       (1:'<!--'*'-->')
+               el[OINCOMMENT]+=$line ;;
+       (0:'<!--'*)
+               el[OPRECOMMENT]+=$line
+               while IFS= read -pr line; do
+                       el[OPRECOMMENT]+=\ ${line##*([   ])}
+                       [[ $line = *'-->' ]] && break
+               done
+               [[ $line = *'-->' ]] || die unclosed comment ;;
+       (1:'<!--'*)
+               el[OINCOMMENT]+=$line
+               while IFS= read -pr line; do
+                       el[OINCOMMENT]+=\ ${line##*([    ])}
+                       [[ $line = *'-->' ]] && break
+               done
+               [[ $line = *'-->' ]] || die unclosed comment ;;
+       (0:'</toplevel>')
+               state=9 ;;
+       (0:'<dependency>')
+               el[OTYPE]=0
+               state=1 ;;
+       (0:'<exclusion>')
+               el[OTYPE]=1
+               state=1 ;;
+       (1:'</dependency>'|1:'</exclusion>')
+               sortlines+=("${el[0]}$cr${el[1]}$cr${el[2]}$cr${el[3]}$cr${el[4]}$cr${el[5]}$cr${el[6]}$cr${el[7]}$cr${el[8]}$cr${el[9]}$cr${el[10]}$cr${el[11]}")
+               set -A el
+               state=0 ;;
+       (1:'<'@(groupId|artifactId|version|type|classifier|scope|systemPath|exclusions|optional)'/>')
+               ;;
+       (1:'<groupId>'*'</groupId>')
+               x=${line#'<groupId>'}
+               el[OgroupId]=${x%'</groupId>'} ;;
+       (1:'<artifactId>'*'</artifactId>')
+               x=${line#'<artifactId>'}
+               el[OartifactId]=${x%'</artifactId>'} ;;
+       (1:'<version>'*'</version>')
+               x=${line#'<version>'}
+               el[Oversion]=${x%'</version>'} ;;
+       (1:'<type>'*'</type>')
+               x=${line#'<type>'}
+               el[Otype]=${x%'</type>'} ;;
+       (1:'<classifier>'*'</classifier>')
+               x=${line#'<classifier>'}
+               el[Oclassifier]=${x%'</classifier>'} ;;
+       (1:'<scope>'*'</scope>')
+               x=${line#'<scope>'}
+               el[Oscope]=${x%'</scope>'} ;;
+       (1:'<systemPath>'*'</systemPath>')
+               x=${line#'<systemPath>'}
+               el[OsystemPath]=${x%'</systemPath>'} ;;
+       (1:'<optional>'*'</optional>')
+               x=${line#'<optional>'}
+               el[Ooptional]=${x%'</optional>'} ;;
+       (1:'<exclusions>')
+               x=
+               while IFS= read -pr line; do
+                       [[ $line = '</exclusions>' ]] && break
+                       x+=$line
+               done
+               [[ $line = '</exclusions>' ]] || die unterminated exclusions
+               line=$x
+               x=$(mksh "$0" <<<"$line") || die could not sort sub-exclusions
+               el[Oexclusions]=${x//"$lf"} ;;
+       (*)
+               die illegal line in state $state ;;
+       }
+done
+(( state == 9 )) || die unexpected last line not end tag
+
+for x in "${sortlines[@]}"; do
+       print -r -- "$x"
+done | sort -u |&
+
+set -A beg -- '<dependency>' '<exclusion>'
+set -A end -- '</dependency>' '</exclusion>'
+while IFS="$cr" read -prA el; do
+       [[ -n ${el[OPRECOMMENT]} ]] && print -r -- "${el[OPRECOMMENT]}"
+       print -r -- "${beg[el[OTYPE]]}"
+       [[ -n ${el[OINCOMMENT]} ]] && print -r -- "${el[OINCOMMENT]}"
+       [[ -n ${el[OgroupId]} ]] && print -r -- "<groupId>${el[OgroupId]}</groupId>"
+       [[ -n ${el[OartifactId]} ]] && print -r -- "<artifactId>${el[OartifactId]}</artifactId>"
+       [[ -n ${el[Oversion]} ]] && print -r -- "<version>${el[Oversion]}</version>"
+       [[ -n ${el[Otype]} ]] && print -r -- "<type>${el[Otype]}</type>"
+       [[ -n ${el[Oclassifier]} ]] && print -r -- "<classifier>${el[Oclassifier]}</classifier>"
+       [[ -n ${el[Oscope]} ]] && print -r -- "<scope>${el[Oscope]}</scope>"
+       [[ -n ${el[OsystemPath]} ]] && print -r -- "<systemPath>${el[OsystemPath]}</systemPath>"
+       [[ -n ${el[Oexclusions]} ]] && print -r -- "<exclusions>${el[Oexclusions]}</exclusions>"
+       [[ -n ${el[Ooptional]} ]] && print -r -- "<optional>${el[Ooptional]}</optional>"
+       print -r -- "${end[el[OTYPE]]}"
+done
+
+exit 0
diff --git a/mksh/sortplug.sh b/mksh/sortplug.sh
new file mode 100644 (file)
index 0000000..9a104c1
--- /dev/null
@@ -0,0 +1,243 @@
+#!/usr/bin/env mksh
+# -*- mode: sh -*-
+#-
+# Copyright © 2018
+#      mirabilos <t.glaser@tarent.de>
+#
+# Provided that these terms and disclaimer and all copyright notices
+# are retained or reproduced in an accompanying document, permission
+# is granted to deal in this work without restriction, including un‐
+# limited rights to use, publicly perform, distribute, sell, modify,
+# merge, give away, or sublicence.
+#
+# This work is provided “AS IS” and WITHOUT WARRANTY of any kind, to
+# the utmost extent permitted by applicable law, neither express nor
+# implied; without malicious intent or gross negligence. In no event
+# may a licensor, author or contributor be held liable for indirect,
+# direct, other damage, loss, or other issues arising in any way out
+# of dealing in the work, even if advised of the possibility of such
+# damage or existence of a defect, except proven that it results out
+# of said person’s immediate fault when using the work as intended.
+#-
+# Pipe a Maven POM’s <plugins> element’s content without the plugins
+# element itself through this to sort them.
+
+LC_ALL=C; export LC_ALL
+unset LANGUAGE
+
+set -e
+cr=$'\r'
+lf=$'\n'
+(( ${#cr} == 1 ))
+set -o pipefail
+me=$(realpath "$0/..")
+
+# offsets in element array
+OgroupId=1
+OartifactId=0
+Oversion=5
+Oextensions=6
+Oexecutions=7
+Odependencies=8
+Ogoals=9
+Oinherited=2
+Oconfiguration=10
+OPRECOMMENT=4
+OINCOMMENT=3
+
+exec 4<&0
+(
+       print '<toplevel>'
+       tr "$cr" "$lf" <&4
+       print '</toplevel>'
+) | xmlstarlet fo -e UTF-8 -t - |&
+
+die() {
+       print -ru2 -- "E: $*"
+       print -ru2 -- "N: line: ${line@Q}"
+       exit 1
+}
+
+set -A sortlines
+IFS= read -pr line
+[[ $line = '<?xml version="1.0" encoding="UTF-8"?>' ]] || \
+    die unexpected first line not XML declaration
+IFS= read -pr line
+[[ $line = '<toplevel>' ]] || die unexpected second line not start tag
+
+set -A el
+el[OgroupId]=org.apache.maven.plugins
+state=0
+while IFS= read -pr line; do
+       case $state:$line {
+       (0:*(   )'<!--'*'-->')
+               el[OPRECOMMENT]+=$line ;;
+       (1:*(   )'<!--'*'-->')
+               el[OINCOMMENT]+=$line ;;
+       (0:*(   )'<!--'*)
+               el[OPRECOMMENT]+=$line
+               while IFS= read -pr line; do
+                       el[OPRECOMMENT]+=\ ${line##*([   ])}
+                       [[ $line = *'-->' ]] && break
+               done
+               [[ $line = *'-->' ]] || die unclosed comment ;;
+       (1:*(   )'<!--'*)
+               el[OINCOMMENT]+=$line
+               while IFS= read -pr line; do
+                       el[OINCOMMENT]+=\ ${line##*([    ])}
+                       [[ $line = *'-->' ]] && break
+               done
+               [[ $line = *'-->' ]] || die unclosed comment ;;
+       (0:'</toplevel>')
+               state=9 ;;
+       (0:'    <plugin>')
+               state=1 ;;
+       (1:'    </plugin>')
+               if [[ -n ${el[Odependencies]} ]]; then
+                       line=${el[Odependencies]}
+                       x=$(mksh "$me/sortdeps.sh" <<<"$line") || \
+                           die could not sort dependencies
+                       el[Odependencies]=${x//"$lf"}
+               fi
+               sortlines+=("${el[0]}$cr${el[1]}$cr${el[2]}$cr${el[3]}$cr${el[4]}$cr${el[5]}$cr${el[6]}$cr${el[7]}$cr${el[8]}$cr${el[9]}$cr${el[10]}")
+               set -A el
+               el[OgroupId]=org.apache.maven.plugins
+               state=0 ;;
+       (1:'            <'@(artifactId|version|extensions|executions|dependencies|goals|inherited|configuration)'/>')
+               ;;
+       (1:'            <groupId>'*'</groupId>')
+               x=${line#'              <groupId>'}
+               el[OgroupId]=${x%'</groupId>'} ;;
+       (1:'            <groupId>')
+               x=
+               while IFS= read -pr line; do
+                       [[ $line = '            </groupId>' ]] && break
+                       x+=$line
+               done
+               [[ $line = '            </groupId>' ]] || \
+                   die unterminated tag groupId
+               [[ -n $x ]] || die empty groupId
+               el[OgroupId]=$x ;;
+       (1:'            <artifactId>'*'</artifactId>')
+               x=${line#'              <artifactId>'}
+               el[OartifactId]=${x%'</artifactId>'} ;;
+       (1:'            <artifactId>')
+               x=
+               while IFS= read -pr line; do
+                       [[ $line = '            </artifactId>' ]] && break
+                       x+=$line
+               done
+               [[ $line = '            </artifactId>' ]] || \
+                   die unterminated tag artifactId
+               el[OartifactId]=$x ;;
+       (1:'            <version>'*'</version>')
+               x=${line#'              <version>'}
+               el[Oversion]=${x%'</version>'} ;;
+       (1:'            <version>')
+               x=
+               while IFS= read -pr line; do
+                       [[ $line = '            </version>' ]] && break
+                       x+=$line
+               done
+               [[ $line = '            </version>' ]] || \
+                   die unterminated tag version
+               el[Oversion]=$x ;;
+       (1:'            <extensions>'*'</extensions>')
+               x=${line#'              <extensions>'}
+               el[Oextensions]=${x%'</extensions>'} ;;
+       (1:'            <extensions>')
+               x=
+               while IFS= read -pr line; do
+                       [[ $line = '            </extensions>' ]] && break
+                       x+=$line
+               done
+               [[ $line = '            </extensions>' ]] || \
+                   die unterminated tag extensions
+               el[Oextensions]=$x ;;
+       (1:'            <executions>'*'</executions>')
+               x=${line#'              <executions>'}
+               el[Oexecutions]=${x%'</executions>'} ;;
+       (1:'            <executions>')
+               x=
+               while IFS= read -pr line; do
+                       [[ $line = '            </executions>' ]] && break
+                       x+=$line
+               done
+               [[ $line = '            </executions>' ]] || \
+                   die unterminated tag executions
+               el[Oexecutions]=$x ;;
+       (1:'            <dependencies>'*'</dependencies>')
+               x=${line#'              <dependencies>'}
+               el[Odependencies]=${x%'</dependencies>'} ;;
+       (1:'            <dependencies>')
+               x=
+               while IFS= read -pr line; do
+                       [[ $line = '            </dependencies>' ]] && break
+                       x+=$line
+               done
+               [[ $line = '            </dependencies>' ]] || \
+                   die unterminated tag dependencies
+               el[Odependencies]=$x ;;
+       (1:'            <goals>'*'</goals>')
+               x=${line#'              <goals>'}
+               el[Ogoals]=${x%'</goals>'} ;;
+       (1:'            <goals>')
+               x=
+               while IFS= read -pr line; do
+                       [[ $line = '            </goals>' ]] && break
+                       x+=$line
+               done
+               [[ $line = '            </goals>' ]] || \
+                   die unterminated tag goals
+               el[Ogoals]=$x ;;
+       (1:'            <inherited>'*'</inherited>')
+               x=${line#'              <inherited>'}
+               el[Oinherited]=${x%'</inherited>'} ;;
+       (1:'            <inherited>')
+               x=
+               while IFS= read -pr line; do
+                       [[ $line = '            </inherited>' ]] && break
+                       x+=$line
+               done
+               [[ $line = '            </inherited>' ]] || \
+                   die unterminated tag inherited
+               el[Oinherited]=$x ;;
+       (1:'            <configuration>'*'</configuration>')
+               x=${line#'              <configuration>'}
+               el[Oconfiguration]=${x%'</configuration>'} ;;
+       (1:'            <configuration>')
+               x=
+               while IFS= read -pr line; do
+                       [[ $line = '            </configuration>' ]] && break
+                       x+=$line
+               done
+               [[ $line = '            </configuration>' ]] || \
+                   die unterminated tag configuration
+               el[Oconfiguration]=$x ;;
+       (*)
+               die illegal line in state $state ;;
+       }
+done
+(( state == 9 )) || die unexpected last line not end tag
+
+for x in "${sortlines[@]}"; do
+       print -r -- "$x"
+done | sort -u |&
+
+while IFS="$cr" read -prA el; do
+       [[ -n ${el[OPRECOMMENT]} ]] && print -r -- "${el[OPRECOMMENT]}"
+       print -r -- '<plugin>'
+       [[ -n ${el[OINCOMMENT]} ]] && print -r -- "${el[OINCOMMENT]}"
+       [[ -n ${el[OgroupId]} ]] && print -r -- "<groupId>${el[OgroupId]}</groupId>"
+       [[ -n ${el[OartifactId]} ]] && print -r -- "<artifactId>${el[OartifactId]}</artifactId>"
+       [[ -n ${el[Oversion]} ]] && print -r -- "<version>${el[Oversion]}</version>"
+       [[ -n ${el[Oextensions]} ]] && print -r -- "<extensions>${el[Oextensions]}</extensions>"
+       [[ -n ${el[Oexecutions]} ]] && print -r -- "<executions>${el[Oexecutions]}</executions>"
+       [[ -n ${el[Odependencies]} ]] && print -r -- "<dependencies>${el[Odependencies]}</dependencies>"
+       [[ -n ${el[Ogoals]} ]] && print -r -- "<goals>${el[Ogoals]}</goals>"
+       [[ -n ${el[Oinherited]} ]] && print -r -- "<inherited>${el[Oinherited]}</inherited>"
+       [[ -n ${el[Oconfiguration]} ]] && print -r -- "<configuration>${el[Oconfiguration]}</configuration>"
+       print -r -- '</plugin>'
+done
+
+exit 0