diff --git a/README.md b/README.md index db1cf89..9eb762c 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,7 @@ CQ-Unix-Toolkit =============== +![CQ Unix Toolkit logo](logo.png) Table of contents ----------------- @@ -21,8 +22,8 @@ Introduction CQ Unix Toolkit is a set of POSIX shell tools that calls curl and other 3rd party commands to perform some different tasks on Adobe CQ platform such as: -* Create, Build, upload, list, download, install and deletion of CRX zip - packages +* Create, build, rewrite, upload, list, download, install and deletion of CRX + zip packages * Maintenance tasks: consistency checks, TarPM compaction and index merge, DataStore garbage collection * Clear/invalidate dispatcher cache for subtree specified by `/statfilelevel` @@ -56,10 +57,14 @@ Below there is a list of separate tools and short purpose phrase for each one: * `cqpkg` -- Creates empty zip package on local filesystem using provided specification (name, group, version, paths, filters) which is valid and minimal CRX FileVault package. CQ connection not required. +* `cqrepkg`-- Read or rewrite package definition and/or content i.e. name, + group etc and JCR filters * `cqbld` -- Builds remotely uploaded CQ package using connection parameters * `cqcp` -- Makes a copy of remote CQ package to your local environment * `cqget` -- Makes a copy of CQ resource to your local environment -* `cqrun` -- Install uploaded CQ package on remote instanse +* `cqrun` -- Install uploaded CQ package on remote instance +* `cqrev` -- Revert previously installed CQ package on remote instance (if + still exists) * `cqdel` -- Remove completely remotely available CQ package * `cqput` -- Upload package from your local environment * `cqls` -- List packages uploaded/installed in remote CQ @@ -69,6 +74,7 @@ Below there is a list of separate tools and short purpose phrase for each one: * `cqmrg` -- Merge CQ TarPM indexes * `cqtpm` -- Deletes effectively removed content from TarPM CQ storage * `cqwfl` -- Display active (or broken) workflow instances +* `cqjcr` -- Browse and modify JCR tree on nodes and properties level * `cqosgi` -- Display bundles list and manage them by starting/stopping on demand * `cqclr` -- Simulates activation on dispatcher to clear its cache. Use dispatcher URL, (not CQ one) as instance URL (-i option). @@ -126,9 +132,14 @@ Notes on Windows/Cygwin compatibility In order to use toolkit on cygwin make sure you have marked/installed the following cygwin packages: -* `util-linux` (required for all tools) +* `util-linux` (required for all tools) + ![Marking curl package](doc/cygwin1-curl.png) * `curl` (required for almost all tools) -* `zip` (required for cqpkg tool) + ![Marking util-linux package](doc/cygwin2-util-linux.png) +* `unzip`, `zip` (required for cqrepkg, cqpkg tools) + ![Marking zip/unzip package](doc/cygwin3-zip.png) + +If you have any problems, please see [cygwin installation screencast](http://www.youtube.com/watch?v=11ilswbIjkg). To test commands just type in command line the following expressions and compare results: diff --git a/cqapi b/cqapi index fd8619c..97051a6 100755 --- a/cqapi +++ b/cqapi @@ -38,10 +38,6 @@ Examples: cqapi -s # Returns command and parameter required for # sed extended mode (extended regexp) to # be compatible with BSD/Mac OS X. - cqapi -w # Returns command and parameter required for - # extended printing purposes (interpreting - # escape sequences) to be compatible - # with BSD/Mac OS X. echo " " | cqapi -f # Convert space into URL-encoded format echo "Col1\tCol2" | cqapi -F 0 # Format tab-separated columns into echo "Col1\tCol2" | cqapi -F 1 # fully-aligned output (0) @@ -62,8 +58,6 @@ Options: stack trace at the same time -e Detect if on stdin thereis exception stacktrace -v Provides toolkit version - -w Print echo command line or path with escaped sequences - turned on -f Encode text into URL-friendly format (percent enc) -F Preformatting of columns (filled with tabs for machine-readable format) @@ -379,7 +373,32 @@ _format_output() -OPTIONS=":Pu:p:i:csC:H:fF:Evwe-:" + +_format_output() +{ + ECHO=$(which echo) + [ ${?} -ne 0 -o -z "${ECHO}" ] && ECHO="echo" || ECHO="${ECHO} -e " + SEP='|' + machine_friendly=${1} + output=$(cat -) + if [ "${machine_friendly}" -eq 1 ] + then + ${ECHO} "${output}" + else + COLUMN=$(which column) + if [ ${?} -ne 0 -o -z "${COLUMN}" ] + then + echo "Cannot find column utility" + exit 1 + fi + ${ECHO} "${output}" | tr '\t' "${SEP}" | column -xt -s "${SEP}" + fi + exit +} + + + +OPTIONS=":Pu:p:i:csC:H:fF:Eve-:" PARSE_OPTIONS="u:p:i:" sed --version >/dev/null 2>/dev/null @@ -423,7 +442,6 @@ case "${mode}" in E) _java_exception;; e) _java_exception_detection;; v) _version;; - w) _echopath;; f) _urlencode;; F) _format_output "${argument}";; esac diff --git a/cqjcr b/cqjcr new file mode 100755 index 0000000..c19a344 --- /dev/null +++ b/cqjcr @@ -0,0 +1,439 @@ +#!/bin/sh +# CQ Unix Toolkit +# Copyright (C) 2013 Cognifide Limited +# +# 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. +_usage() +{ + cat << EOT +Usage: $(basename "${0}") [OPTION...] OPERATION +Allows to browse and modify JCR tree in easy and automated manner like UNIX ls, +touch and mkdir commands + +Examples: + cqjcr -u admin -l / # Return JCR child nodes for '/' existing on + # the local instance + cqjcr -i http://localhost:5510 # Return JCR child nodes for '/content' for + -l /conten -p secret # localhost instance on tcp port 5510 with + # password provided: 'secret' + cqjcr -u admin -s / # Show JCR properties for '/' node in pretty + # format + cqjcr -u admin -s /content -m # Show JCR properties for '/content' node in + # machine-readable format + cqjcr -u admin -c /content/new # Create (or modify) JCR node 'new' under + # '/content' node with default + # 'nt:unstructured' primary type + cqjcr -u admin -c /content/new # Create (or modift) JCR node 'new' under + -t cq:Page # '/content' node with 'cq:Page' primary + # type + cqjcr -u admin -a /content/www # Create or modify JCR node 'www' under + -n name1 -v val1 # '/content' node with 2 properties: one + -n name2 -v val2 # with name: 'name1' and value 'val1', second + # with name: 'name2' and value 'val2' + cqjcr -u admin -d /content/trash # Delete node 'trash' under '/content' for + # local instance + +Operations: + + -s PATHSPEC show properties for specified JCR nodes 'PATHSPEC' + -d PATHSPEC delete node specified by path 'PATHSPEC' + -l PATHSPEC show JCR children nodes for specified 'PATHSPEC' node + -a PATHSPEC create/modify node specified by 'PATHSPEC' + -c PATHSPEC create empty node specified by 'PATHSPEC' + Each non-exising element from path will be created + +Options: + + -u use specified username for connection + -p use provided password for authentication + -i use specified instance URL to connect + + -m machine-friendly mode for -s option + -t JCRTYPE in conjuction with -c allows to specify jcr:PrimaryType + -n NAME in conjuction with -a allows to specify property name + (can be used many times but in pairs with -v) + -v VALUE in conjuction with -a allows to specify property value + (can be used many times but in pairs with -n) + +EOT +exit 1 +} + +_list_children() +{ + FILEPATH="${root}.1.json" + URL="${instance}${FILEPATH}" + REFERERHEADER="Referer: ${URL}" + STATUS=$("${CURLBIN}" \ + -s \ + --write-out "%{http_code}" \ + -u "${AUTH}" \ + -H "${REFERERHEADER}" \ + "${URL}") + + EXITCODE="${?}" + "${API}" -C "${EXITCODE}" + if [ ${EXITCODE} -ne 0 ] + then + exit ${EXITCODE} + fi + STATUSCODE=$(echo "${STATUS}" | grep -o -E '[0-9]{3}' | tail -n 1) + "${API}" -H "${STATUSCODE}" + EXITCODE2=$? + if [ ${EXITCODE2} -ne 0 ] + then + exit ${EXITCODE2} + fi + + STATUS=$(echo "${STATUS}" | ${SEDX} 's#[0-9]{3}$##') + NEW_NODE=':\{' + NODE_NAME='[^"]+' + STR_SEP='"' + NODE_REGEXP_FULL_EXTRACT="(${STR_SEP}${NODE_NAME}${STR_SEP}${NEW_NODE})" + NODE_REGEXP_EXTRACT="${STR_SEP}(${NODE_NAME})${STR_SEP}${NEW_NODE}" + FULL_NODE_REGEXP=".*${NODE_REGEXP_EXTRACT}.*" + CLEARED_STATUS=$(echo "${STATUS}" \ + | tr -d '\t' \ + | ${SEDX} "s#${NODE_REGEXP_FULL_EXTRACT}#${TAB}\1#g" \ + | tr '\t' '\n' \ + | grep -E "${FULL_NODE_REGEXP}" \ + | ${SEDX} "s#${FULL_NODE_REGEXP}#\1#") + if [ ! -z "${CLEARED_STATUS}" ] + then + printf "%s\n" "${CLEARED_STATUS}" + fi + exit 0 +} + +_list_properties() +{ + FILEPATH="${root}.json" + URL="${instance}${FILEPATH}" + REFERERHEADER="Referer: ${URL}" + STATUS=$(${CURLBIN} \ + -s \ + --write-out "%{http_code}" \ + -u "${AUTH}" \ + -H "${REFERERHEADER}" \ + "${URL}") + + EXITCODE=${?} + "${API}" -C "${EXITCODE}" + if [ ${EXITCODE} -ne 0 ] + then + exit ${EXITCODE} + fi + STATUSCODE=$(echo "${STATUS}" | grep -o -E '[0-9]{3}' | tail -n 1) + "${API}" -H "${STATUSCODE}" + EXITCODE2=$? + if [ ${EXITCODE2} -ne 0 ] + then + exit ${EXITCODE2} + fi + + NODE_NAME='[^"]+' + NUMBER='-?[0-9\.]+' + STRING='[^"]*' + ARR='\[("[^"]*",?)*\]' + BOOL='(true|false)' + QT='"' + PROP_ARR="(${QT}${NODE_NAME}${QT}:${ARR},?)" + PROP_BOOL="(${QT}${NODE_NAME}${QT}:${BOOL},?)" + PROP_STR="(${QT}${NODE_NAME}${QT}:${QT}${STRING}${QT},?)" + PROP_NUM="(${QT}${NODE_NAME}${QT}:${NUMBER},?)" + TOKENS_ARR="${QT}(${NODE_NAME})${QT}:(${ARR}),?" + TOKENS_BOOL="${QT}(${NODE_NAME})${QT}:(${BOOL}),?" + TOKENS_STR="${QT}(${NODE_NAME})${QT}:${QT}(${STRING})${QT},?" + TOKENS_NUM="${QT}(${NODE_NAME})${QT}:(${NUMBER}),?" + + + STATUS=$(echo "${STATUS}" | ${SEDX} 's#[0-9]{3}$##') + STATUS=$(echo "${STATUS}" | ${SEDX} 's#\\\"#%9999%#' \ + | ${SEDX} "s#${PROP_NUM}#${TAB}\1${TAB}#g" \ + | tr '\t' '\n' \ + | ${SEDX} "s#${PROP_STR}#${TAB}\1${TAB}#g" \ + | tr '\t' '\n' \ + | ${SEDX} "s#${PROP_BOOL}#${TAB}\1${TAB}#g" \ + | tr '\t' '\n' \ + | ${SEDX} "s#${PROP_ARR}#${TAB}\1${TAB}#g" \ + | tr '\t' '\n' \ + | grep -E "(${PROP_NUM}|${PROP_STR}|${PROP_ARR}|${PROP_BOOL})" \ + | ${SEDX} "s#.*${TOKENS_NUM}.*#\1${TAB}\2#" \ + | ${SEDX} "s#.*${TOKENS_STR}.*#\1${TAB}\2#" \ + | ${SEDX} "s#.*${TOKENS_BOOL}.*#\1${TAB}\2#" \ + | ${SEDX} "s#.*${TOKENS_ARR}.*#\1${TAB}\2#" \ + | ${SEDX} 's#%9999%#"#g') + + if [ "${machine_friendly}" -eq 0 ] + then + SEP='|' + COLUMN=$(which column) + if [ ${?} -ne 0 -o -z "${COLUMN}" ] + then + echo "Cannot find column utility" >&2 + exit 1 + fi + printf "NAME\tVALUE\n%s\n" "${STATUS}" | tr '\t' "${SEP}" \ + | "${COLUMN}" -xt -c 2 -s "${SEP}" 2>/dev/null + else + printf "%s\n" "${STATUS}" + fi + exit 0 +} + +_modify_properties() +{ + if [ ${n_counter} -le 0 ] + then + echo "Use -n/-v to provide property name/value to add or modify." >&2 + _usage + fi + echo "Creating/modyfing ${root}" + actions="" + for operation in ${operations} + do + propname=$(echo "${operation}" | cut -f1 -d '=' | ${SEDX} 's#%20# #g') + propvalue=$(echo "${operation}" | cut -f2- -d '=' | ${SEDX} 's#%20# #g') + echo " Setting '${propname}' to: '${propvalue}' " + actions="${actions}\055F\000${propname}=${propvalue}\000" + done + + FILEPATH="${root}" + URL="${instance}${FILEPATH}" + REFERERHEADER="Referer: ${URL}" + args="${actions}${URL}" + + # shellcheck disable=SC2059 + STATUS=$(printf "${args}" | \ + xargs -0 "${CURLBIN}" \ + -s -X POST \ + --write-out "%{http_code}" \ + -u "${AUTH}" \ + -H "${REFERERHEADER}") + EXITCODE=${?} + if [ ${EXITCODE} -ne 0 ] + then + echo "There was a problem calling: xargs + curl" >&2 + exit ${EXITCODE} + fi + STATUSCODE=$(echo "${STATUS}" | grep -o -E '[0-9]{3}' | tail -n 1) + "${API}" -H "${STATUSCODE}" + EXITCODE2=$? + if [ ${EXITCODE2} -ne 0 ] + then + exit ${EXITCODE2} + fi + echo "Successfully created/modified: ${root}" +} + +_create_node() +{ + [ -z "${type}" ] && type="nt:unstructured" + FILEPATH="${root}" + URL="${instance}${FILEPATH}" + REFERERHEADER="Referer: ${URL}" + STATUS=$(${CURLBIN} \ + -s \ + -X POST \ + -F"jcr:primaryType=${type}" \ + --write-out "%{http_code}" \ + -u "${AUTH}" \ + -H "${REFERERHEADER}" \ + "${URL}") + + EXITCODE=${?} + "${API}" -C "${EXITCODE}" + if [ ${EXITCODE} -ne 0 ] + then + exit ${EXITCODE} + fi + STATUSCODE=$(echo "$STATUS" | grep -o -E '[0-9]{3}' | tail -n 1) + "${API}" -H "${STATUSCODE}" + EXITCODE2=$? + if [ ${EXITCODE2} -ne 0 ] + then + exit ${EXITCODE2} + fi + echo "Created/modified: ${root}" +} + +_delete_node() +{ + FILEPATH="${root}" + URL="${instance}${FILEPATH}" + REFERERHEADER="Referer: ${URL}" + STATUS=$(${CURLBIN} \ + -s \ + -X DELETE \ + --write-out "%{http_code}" \ + -u "${AUTH}" \ + -H "${REFERERHEADER}" \ + "${URL}") + + EXITCODE=${?} + "${API}" -C ${EXITCODE} + if [ ${EXITCODE} -ne 0 ] + then + exit ${EXITCODE} + fi + STATUSCODE=$(echo "${STATUS}" | grep -o -E '[0-9]{3}' | tail -n 1) + "${API}" -H "${STATUSCODE}" + EXITCODE2=$? + if [ ${EXITCODE2} -ne 0 ] + then + exit ${EXITCODE2} + fi + echo "Deleted: ${root}" +} + +_use_option_warning() +{ + echo "You need to choose one of the following operation:" >&2 + echo " -l / -s / -c / -a" >&2 + _usage +} + + +TAB=$(printf '\t') +CWD=$(dirname "${0}") +API="${CWD}/cqapi" +"${API}" -P >/dev/null 2>/dev/null +if [ ${?} -ne 0 ] +then + echo "Fatal: cannot find or test cqapi command" >&2 + exit 1 +fi + +CURLBIN=$("${API}" -c) +if [ ${?} -ne 0 ] +then + echo "Fatal: cannot find curl" >&2 + exit 1 +fi + +SEDX=$("${API}" -s) +# API common options +cmdapi=$("${API}" -P "${@}") +username=$(echo "${cmdapi}" | cut -f1) +password=$(echo "${cmdapi}" | cut -f2) +instance=$(echo "${cmdapi}" | cut -f3) +passed=$(echo "${cmdapi}" | cut -f4) +apigetopts=$(echo "${cmdapi}" | cut -f5) + +root="" +list=0 +modify=0 +properties=0 +create=0 +type="" +operations="" +property_id="" +n_counter=0 +v_counter=0 +machine_friendly=0 +delete=0 +while getopts ":l:s:a:c:t:n:v:d:m${apigetopts}" opt; do + case "${opt}" in + l) + list=1 + root="${OPTARG}";; + s) + properties=1 + root="${OPTARG}";; + a) + modify=1 + root="${OPTARG}";; + c) + create=1 + root="${OPTARG}";; + d) + delete=1 + root="${OPTARG}";; + m) + machine_friendly=1;; + t) + type="${OPTARG}";; + v) + v_counter=$((v_counter+1)) + modify=1 + if [ ! -z "${property_id}" ] + then + propvalue=$(echo "${OPTARG}" | sed 's# #%20#g') + operations="${operations} ${property_id}=${propvalue}" + property_id="" + else + echo "For each -v option there must be -n preceeding"\ + "option specified" >&2 + echo "" + _usage + fi;; + n) + n_counter=$((n_counter+1)) + modify=1; + property_id=$(echo "${OPTARG}" | sed 's# #%20#g');; + \?) + echo "Invalid option: -$OPTARG" >&2; _usage;; + :) + echo "Option -$OPTARG requires an argument." >&2; _usage;; + esac +done +shift $((OPTIND-1)) + +if [ ${#} -gt 0 -o "${passed}" -eq 0 ] +then + _use_option_warning +fi +if [ $((list+properties+create+delete+modify)) -ne 1 ] +then + _use_option_warning +fi + +if [ ${n_counter} -ne ${v_counter} ] +then + echo "Options: -n and -v are connected together. You need to specify" \ + "matching pairs of such options (name/value)." >&2 + echo "" + _usage +fi +if [ ${machine_friendly} -eq 1 -a ${properties} -eq 0 ] +then + echo "Option: -m can be used only in conjuction with -s" >&2 + echo "" + _usage +fi +if [ ${create} -eq 0 -a ! -z "${type}" ] +then + echo "Option: -t can be used only in conjuction with -c" >&2 + echo "" + _usage +fi +if [ ${modify} -eq 0 -a ${n_counter} -gt 0 ] +then + echo "Options: -n/-v can be used only in conjuction with -a" >&2 + echo "" + _usage +fi +root_valid=$(echo "${root}" | grep -c '^/') +if [ "${root_valid}" -ne 1 ] +then + echo "PATHSPEC argument needs to be valid JCR path, i.e. /content" >&2 + echo "" + _usage +fi +AUTH="${username}:${password}" +[ ${list} -eq 1 ] && _list_children +[ ${properties} -eq 1 ] && _list_properties +[ ${modify} -eq 1 ] && _modify_properties +[ ${create} -eq 1 ] && _create_node +[ ${delete} -eq 1 ] && _delete_node + diff --git a/cqrepkg b/cqrepkg new file mode 100755 index 0000000..8947469 --- /dev/null +++ b/cqrepkg @@ -0,0 +1,774 @@ +#!/bin/sh +# CQ Unix Toolkit +# Copyright (C) 2013 Cognifide Limited +# +# 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. +_usage() +{ + cat < "${1}" + rm "${1}.new" +} + +detect_zip_tools() +{ + P7ZIP=$(which 7za 2>/dev/null) + exitcode=${?} + if [ ${exitcode} -ne 0 -o -z "${P7ZIP}" ] + then + P7ZIP="" + fi + + JAR=$(which jar 2>/dev/null) + exitcode=${?} + if [ ${exitcode} -ne 0 -o -z "${JAR}" ] + then + JAR="" + fi + + UNZIP=$(which unzip 2>/dev/null) + exitcode=${?} + if [ ${exitcode} -ne 0 -o -z "${UNZIP}" ] + then + UNZIP="" + fi + + case "${force_extractor_name}" in + jar) UNZIP=""; P7ZIP="";; + unzip) JAR=""; P7ZIP="";; + 7za) JAR=""; UNZIP="";; + esac +} + +_prepare_intermediary_paths_for_unpack() +{ + partial_path="" + for path_element in $(echo "${1}" | tr '/' ' ') + do + partial_path="${partial_path}/${path_element}" + if [ "${partial_path}" != "${1}" ] + then + unpack_args="${unpack_args}\000jcr_root${partial_path}/.content.xml" + fi + done +} + +prepare_unpack_args() +{ + unpack_args="META-INF/*\000jcr_root/.content.xml" + for new_filter in ${new_filters} + do + _prepare_intermediary_paths_for_unpack "${new_filter}" + unpack_args="${unpack_args}\000jcr_root${new_filter}/*" + done + filters_to_leave=$(printf "%s\n" "${delta}" | grep '^=' | cut -f2 -d ' ') + for filter_to_leave in ${filters_to_leave} + do + _prepare_intermediary_paths_for_unpack "${filter_to_leave}" + unpack_args="${unpack_args}\000jcr_root${filter_to_leave}/*" + done +} + +replace_property() +{ + prop=${1} + old=${2} + new=${3} + esc1='s#\\#\\\\#g; s#\$#\\$#g; s#{#\\{#g; s#}#\\}#g; s#\*#\\*#g;' + esc2='s#\.#\\.#g; s#\?#\\?#g; s#\^#\\^#g; s#\[#\\[#g; s#\]#\\]#g;' + esc3='s#|#\\|#g; s#(#\\(#g; s#)#\\)#g; s#\+#\\+#g' + old_esc=$(echo "${old}" | sed "${esc1}${esc2}${esc3}") + + tag_open="${ENO}${prop}${ENC}" + tag_open_repl="${ENO}${prop}${ENCR}" + sed_op="s#${tag_open}${old_esc}${ENC2}#${tag_open_repl}${new}${ENC2}#" + [ -n "${new}" ] && ${SEDX} "${sed_op}" "${propertiesfn}" \ + > "${propertiesfn}.new" && replace_with_new_file "${propertiesfn}" + + sed_op="s#name=\"${old_esc}\"#name=\"${new}\"#" + [ -n "${new}" ] && ${SEDX} "${sed_op}" "${definitionfn}" \ + > "${definitionfn}.new" && replace_with_new_file "${definitionfn}" +} + +replace_properties() +{ + replace_property name "${pkg_name}" "${new_name}" + replace_property group "${pkg_group}" "${new_group}" + replace_property groupId "${pkg_group}" "${new_group}" + replace_property description "${pkg_description}" "${new_description}" + replace_property version "${pkg_version}" "${new_version}" +} + +change_filters() +{ + filter="${1}" + filters_to_change="${2}" + new_filters_f_short="" + new_filters_f_long="" + new_filters_d="" + for filter_to_change in ${filters_to_change} + do + new_filter_f_short="${FLTA1}${filter_to_change}${FLTA2R}" + new_filter_f_long="${FLTB1}${filter_to_change}${FLTB2R}" + new_filters_f_short=$(printf "%s%s" "${new_filters_f_short}" \ + "${new_filter_f_short}") + new_filters_f_long=$(printf "%s%s" "${new_filters_f_long}" \ + "${new_filter_f_long}") + + new_filter_d="${DFLT1R}${filter_to_change}${DFLT2R}" + new_filters_d=$(printf "%s%s" "${new_filters_d}" "${new_filter_d}") + done + repl1="s#${FLTA1}${filter}${FLTA2}#${new_filters_f_short}#;" + repl2="s#${FLTB1}${filter}${FLTB2}#${new_filters_f_long}#;" + repl3="s#${DFLT1}${filter}${DFLT2}#${new_filters_d}#;" + printf "F %s\nF %s\nD %s\n" "${repl1}" "${repl2}" "${repl3}" +} + +remove_filter() +{ + filter="${1}" + repl1="s#${FLTA1}${filter}${FLTA2}##;" + repl2="s#${FLTB1}${filter}${FLTB2}##;" + repl3="s#${DFLT1}${filter}${DFLT2}##;" + printf "F %s\nF %s\nD %s\n" "${repl1}" "${repl2}" "${repl3}" +} + +sanitize_filters() +{ + TAB=$(printf '\t') + tr -d '\t\n' \ + | ${SEDX} "s##-->${TAB}#g" \ + | tr '\t' '\n' \ + | ${SEDX} "s###g" \ + | tr '\n' '\t' \ + | ${SEDX} "s#(${TAB})+#${TAB}#g" \ + | tr '\t' '\n' +} + +group_into_filters() +{ + TAB=$(printf '\t') + tr -d '\t\n' \ + | ${SEDX} "s#${FLTA1}#${TAB}${FLTA1}#g" \ + | ${SEDX} "s#${FLTE}#${FLTE}${TAB}#g" \ + | ${SEDX} "s#${FLTA1}([^\"]+)${FLTA2}#${FLTA1}\1${FLTA2R}${TAB}#g" \ + | tr '\t' '\n' +} + +replace_filters() +{ + TAB=$(printf '\t') + sanitize_filters < "${filtersfn}" | group_into_filters > "${filtersfn}.new" \ + && replace_with_new_file "${filtersfn}" + echo "${seds_def}" | ${SEDX} \ + -f /dev/stdin "${definitionfn}" > "${definitionfn}.new" \ + && replace_with_new_file "${definitionfn}" + echo "${seds_flt}" | ${SEDX} \ + -f /dev/stdin "${filtersfn}" | tr -s '\n' | uniq > "${filtersfn}.new" \ + && replace_with_new_file "${filtersfn}" +} + +process_delta_item() +{ + op=$(echo "${1}" | cut -c1) + filter=$(echo "${1}" | cut -f2 -d ' ') + new_filter=$(echo "${1}" | cut -f4- -d ' ') + if [ "${op}" = "R" ] + then + remove_filter "${filter}" + fi + if [ "${op}" = "M" ] + then + change_filters "${filter}" "${new_filter}" + fi +} + +display_last() +{ + if [ -n "${1}" ] + then + echo "Y${1}" + fi +} + +compact_item() +{ + op=$(echo "${1}" | cut -c1) + last_item=$(echo "${2}" | grep -e '^N' | cut -c2-) + + filter=$(echo "${1}" | cut -f2 -d ' ') + new_filter=$(echo "${1}" | cut -f4- -d ' ') + last_op=$(echo "${last_item}" | cut -c1) + last_filter=$(echo "${last_item}" | cut -f2 -d ' ') + last_new_filters=$(echo "${last_item}" | cut -f4- -d ' ') + + if [ "${op}" = "F" ] + then + display_last "${last_item}" + fi + if [ "${op}" = "R" -o "${op}" = "=" ] + then + echo "Y${1}" + display_last "${last_item}" + fi + if [ "${op}" = "M" ] + then + if [ "${last_op}" = "M" -a "${filter}" = "${last_filter}" ] + then + printf "N%s %s -> %s %s\n" "${op}" "${filter}" "${new_filter}" \ + "${last_new_filters}" + else + echo "N${1}" + display_last "${last_item}" + fi + fi +} + +compact_delta() +{ + printf "%s\nF\n" "${delta}" \ + | while read item; do \ + compacted=$(compact_item "${item}" "${compacted}"); \ + echo "${compacted}" | grep -E '^Y' | cut -c2- ; \ + compacted=$(echo "${compacted}" | grep -E -v '^Y'); \ + done +} + +process_delta() +{ + DFLT1="<([^<]+)\"" + DFLT2="\"([^>]+)>" + DFLT1R="<\1\"" + DFLT2R="\"\2>" + + leave_count=$(echo "${delta}" | grep -c '^=') + change_count=$(echo "${delta}" | grep -c '^M') + + compacted_delta=$(compact_delta "${delta}") + printf "%s\n" "${compacted_delta}" + + seds_flt=$(echo "${compacted_delta}" \ + | while read item; do process_delta_item "${item}"; done \ + | grep '^F' | cut -f2- -d ' ') + seds_def=$(echo "${compacted_delta}" \ + | while read item; do process_delta_item "${item}"; done \ + | grep '^D' | cut -f2- -d ' ') + + if [ "${change_count}" -eq 0 -a "${leave_count}" -eq 0 ] + then + MSG="Cannot repackage: effective target package will be empty." + echo "${MSG}" >&2 + _clean_up + exit 1 + fi +} + +update_delta() +{ + delta="${1} +${delta}" +} + +preprocess_filters() +{ + for filter in ${pkg_filters} + do + pts="${#filter}" + filters_to_change=$(echo "${new_filters}" | grep -E "${filter}") + if [ -n "${filters_to_change}" ] + then + for flt in ${filters_to_change} + do + changes="${changes} +${flt}${TAB}${pts}${TAB}${filter}" + done + fi + done + + + for filter in ${new_filters} + do + my_changes=$(echo "${changes}" | grep -E "^${filter}${TAB}" | sort -nr\ + -k2) + to_change=$(echo "${my_changes}" | head -1 | cut -f 3) + if [ "${to_change}" = "${filter}" ] + then + update_delta "= ${to_change}" + else + if [ -n "${to_change}" -a -n "${filter}" ] + then + update_delta "M ${to_change} -> ${filter}" + fi + fi + for old_filter in ${pkg_filters} + do + to_leave=$(echo "${old_filter}" | grep -E "^${filter}") + if [ -n "${to_leave}" ] + then + update_delta "= ${to_leave}" + fi + done + done + + delta=$(echo "${delta}" | sort | uniq) + for old_filter in ${pkg_filters} + do + to_remove=$(echo "${delta}" | grep -Ev '^R ' | cut -f2 -d ' ' \ + | grep -c "^${old_filter}") + if [ "${to_remove}" -eq 0 ] + then + update_delta "R ${old_filter}" + fi + done + + delta=$(echo "${delta}" | grep -v '^$') +} + +save_rewritten_zip() +{ + cd "${tempdir}" + rm -f "${full_dest_zip_fn}" 2>/dev/null + if [ -n "${P7ZIP}" ] + then + "${P7ZIP}" a -r "${full_dest_zip_fn}" ./ 2>/dev/null >/dev/null + exitcode=${?} + else + if [ -n "${JAR}" ] + then + "${JAR}" cf "${full_dest_zip_fn}" ./ 2>/dev/null + exitcode=${?} + else + "${ZIP}" -q -r - ./ > "${full_dest_zip_fn}" 2>/dev/null + exitcode=${?} + fi + fi + cd "${dir}" + if [ ${exitcode} -ne 0 ] + then + echo "Cannot create zip package properly." >&2 + fi +} + +extract_required_resources() +{ + unzip_used=0 + if [ -n "${P7ZIP}" ] + then + # shellcheck disable=SC2059 + # shellcheck disable=SC2034 + debugging=$(printf "${unpack_args}" | tr -d '*' | xargs -0 \ + "${P7ZIP}" x -o"${tempdir}" "${full_src_zip_fn}") + exitcode=${?} + else + if [ -n "${JAR}" ] + then + cd "${tempdir}" + # shellcheck disable=SC2059 + debugging=$(printf "${unpack_args}" | tr -d '*' \ + | xargs -0 "${JAR}" xf "${full_src_zip_fn}") + exitcode=${?} + cd "${dir}" + else + # shellcheck disable=SC2059 + debugging=$(printf "${unpack_args}" | xargs -0 "${UNZIP}" -qq \ + -ud "${tempdir}" "${full_src_zip_fn}" 2>&1) + exitcode=${?} + unzip_used=1 + fi + fi +} + +get_property() +{ + prop=${1} + echo "${PROPS}" | grep -E -o "${ENO}${prop}${ENT}" \ + | ${SEDX} "s#.*${ENO}${prop}${ENT}.*#\1#" +} + +_clean_up() +{ + tree_level=$(echo "${tempdir}" | tr -cd '/' | wc -c) + if [ -n "${tempdir}" -a "${tree_level}" -gt 1 ] + then + # shellcheck disable=SC2115 + rm -fr "${tempdir}/" >/dev/null + else + echo "Covardly refused to clean up '${tempdir}' tmp directory" >&2 + fi +} + +CWD=$(dirname "${0}") +API="${CWD}/cqapi" +"${API}" -P >/dev/null +if [ ${?} -ne 0 ] +then + echo "Fatal: cannot find or test cqapi command" >&2 + exit 1 +fi + +SEDX=$("${API}" -s) + +raw=0 +modify=0 +show_props=0 +show_filters=0 +change_only_properties=1 +while getopts ":g:v:r:n:o:d:PFX:" opt +do + case "${opt}" in + n) + modify=1 + new_name="${OPTARG}";; + g) + modify=1 + new_group="${OPTARG}";; + v) + modify=1 + new_version="${OPTARG}";; + d) + modify=1 + new_description="${OPTARG}";; + o) + modify=1 + dest_zip_fn="${OPTARG}";; + r) + modify=1 + change_only_properties=0 + _add_filter "${OPTARG}";; + P) + raw=1 + show_props=1;; + F) + raw=1 + show_filters=1;; + X) + force_extractor_name="${OPTARG}";; + \?) + echo "Invalid option: -${OPTARG}" >&2; _usage;; + :) + echo "Option -${OPTARG} requires an argument." >&2; _usage;; + esac +done +shift $((OPTIND-1)) + +if [ ${#} -ne 1 ] +then + _usage +fi + +if [ "${modify}" -eq 1 -a "${raw}" -eq 1 ] +then + MOD="-n/-g/-v/-d/-o/-r" + echo "Modification options ${MOD} and raw file output " \ + "options -P/-F shouldn't be mixed" >&2 + _usage +fi + +if [ "${show_filters}" -eq 1 -a "${show_props}" -eq 1 ] +then + echo "Options -P and -F cannot be used together" >&2 + _usage +fi + +if [ "${force_extractor_name}" != "" \ + -a "${force_extractor_name}" != "jar" \ + -a "${force_extractor_name}" != "unzip" \ + -a "${force_extractor_name}" != "7za" ] +then + echo "Specify the correct extraction tool (only one) for -X option:" >&2 + echo " jar" >&2 + echo " unzip" >&2 + echo " 7za" >&2 + echo >&2 + _usage +fi + +src_zip_fn=${1} +dir=$(pwd) + +dir_src_zip_fn=$(dirname "${src_zip_fn}") +full_src_zip_fn=$(cd "${dir_src_zip_fn}"; pwd)/$(basename "${src_zip_fn}") + +if [ ! -f "${full_src_zip_fn}" ] +then + printf "Cannot find file: '%s'\n" "${full_src_zip_fn}" >&2 + exit 1 +fi + +new_filters=${filter_definition} + +detect_zip_tools +if [ -z "${UNZIP}" -a -z "${P7ZIP}" -a -z "${JAR}" ] +then + echo "Cannot find any proper required unzip-like tool: 7za, jar, unzip." \ + " Aborted" >&2 + exit 1 +fi + +METAINF="META-INF" +VAULT="${METAINF}/vault" +FILTER="filter.xml" +PROPERTIES="properties.xml" + +if [ -n "${P7ZIP}" ] +then + PROPSN=$("${P7ZIP}" x -so "${full_src_zip_fn}" "${VAULT}/${PROPERTIES}" \ + 2>/dev/null) + PROPS=$(echo "${PROPSN}" | tr -d '\n') + FILTERS=$("${P7ZIP}" x -so "${full_src_zip_fn}" "${VAULT}/${FILTER}" \ + 2>/dev/null) +else + if [ -n "${JAR}" ] + then + readdir=$(mktemp -d cqrepkgXXXXXXXXXX) + # shellcheck disable=SC2034 + hidden_output=$(cd "${readdir}" && "${JAR}" xf "${full_src_zip_fn}" \ + "${VAULT}/${PROPERTIES}" "${VAULT}/${FILTER}") + PROPSN=$(cat "${readdir}/${VAULT}/${PROPERTIES}") + PROPS=$(echo "${PROPSN}" | tr -d '\n') + FILTERS=$(cat "${readdir}/${VAULT}/${FILTER}") + rm -f "${readdir}/${VAULT}/${FILTER}" 2>/dev/null + rm -f "${readdir}/${VAULT}/${PROPERTIES}" 2>/dev/null + rmdir "${readdir}/${VAULT}" 2>/dev/null + rmdir "${readdir}/${METAINF}" 2>/dev/null + rmdir "${readdir}" 2>/dev/null + else + PROPSN=$("${UNZIP}" -p "${full_src_zip_fn}" "${VAULT}/${PROPERTIES}" \ + 2>/dev/null) + PROPS=$(echo "${PROPSN}" | tr -d '\n') + FILTERS=$("${UNZIP}" -p "${full_src_zip_fn}" "${VAULT}/${FILTER}" \ + 2>/dev/null) + fi +fi + +[ "${show_filters}" -eq 1 ] && echo "${FILTERS}" +[ "${show_props}" -eq 1 ] && echo "${PROPSN}" + +if [ -z "${PROPS}" -o -z "${FILTERS}" ] +then + echo "Incorrect source zip package: '${full_src_zip_fn}'." >&2 + exit 2 +fi + +[ "${raw}" -eq 1 ] && exit 0 + +ENO="" +ENCR="\">" +ENC2="" +ENTXT="[^<]*" +ENT="${ENC}(${ENTXT})${ENC2}" +FO="" +FLTA2R="\"/>" +FLTB2="\"[ ]*>(.*)${FLTE}" +FLTB2R="\">\1${FLTE}" +FILTERS=$(echo "${FILTERS}" | sanitize_filters) + +pkg_name=$(get_property name) +pkg_group=$(get_property group) +pkg_description=$(get_property description) +pkg_version=$(get_property version) +pkg_build=$(get_property buildCount) +pkg_created=$(get_property created) +pkg_modified=$(get_property lastModified) + +TAB=$(printf '\t') +pkg_filters=$(echo "${FILTERS}" | group_into_filters \ + | grep -E "${FO}" | ${SEDX} "s#.*${FO}([^\"]*).*#\1#g") + +if [ "${modify}" -eq 0 ] +then + printf "Name\t\t%s\n" "${pkg_name}" + printf "Group\t\t%s\n" "${pkg_group}" + printf "Version\t\t%s\n" "${pkg_version}" + printf "Descr\t\t%s\n" "${pkg_description}" + printf "Build\t\t%s\n" "${pkg_build}" + printf "Create\t\t%s\n" "${pkg_created}" + printf "Modify\t\t%s\n\n" "${pkg_modified}" + for filter in ${pkg_filters} + do + printf "Filter\t\t%s\n" "${filter}" + done + exit 0 +fi + +[ -n "${new_name}" ] && zip_name="${new_name}" || zip_name="${pkg_name}" +[ -n "${new_version}" ] && zip_version="${new_version}" \ + || zip_version="${pkg_version}" + + +if [ -z "${dest_zip_fn}" ] +then + full_dest_zip_fn="${dir}/${zip_name}-${zip_version}-repkged.zip" +else + dir_dest_zip_fn=$(dirname "${dest_zip_fn}") + full_dest_zip_fn=$(cd "${dir_dest_zip_fn}"; \ + pwd)/$(basename "${dest_zip_fn}") +fi + + +# Modify +ZIP=$(which zip) +if [ ${?} -ne 0 -o -z "${ZIP}" ] +then + ZIP="" +fi + +if [ -z "${ZIP}" -a -z "${P7ZIP}" -a -z "${JAR}" ] +then + echo "Cannot find any proper required zip-like tool: 7za, jar, zip." \ + " Aborted" >&2 + exit 2 +fi + +tempdir=$(mktemp -d cqrepkgXXXXXXXXXX) +exitcode=${?} +if [ ${exitcode} -ne 0 ] +then + echo "Cannot create temporary directory ${tempdir}. Aborted" >&2 + exit 1 +fi + +parenttempdir=$(dirname "${tempdir}") +tempdir=$(cd "${parenttempdir}"; pwd)/$(basename "${tempdir}") +exitcode=${?} +if [ ${exitcode} -ne 0 ] +then + echo "Cannot detect full path for temporary directory ${tempdir}" >&2 + exit 1 +fi + + +vault="${tempdir}/${VAULT}" +if [ "${change_only_properties}" -eq 0 ] +then + preprocess_filters + process_delta + prepare_unpack_args +else + unpack_args="" +fi +extract_required_resources + +if [ ${exitcode} -ne 0 ] +then + if [ "${unzip_used}" -eq 0 ] + then + echo "Cannot unpack CQ package properly." >&2 + echo >&2 + fi + if [ -n "${debugging}" ] + then + echo "Extraction log:" >&2 + echo "${debugging}" >&2 + fi + if [ "${unzip_used}" -eq 0 ] + then + _clean_up + exit 1 + fi +fi + +filtersfn="${vault}/${FILTER}" +propertiesfn="${vault}/${PROPERTIES}" +definitionfn="${vault}/definition/.content.xml" + +replace_properties +if [ "${change_only_properties}" -eq 0 ] +then + replace_filters +fi +save_rewritten_zip +_clean_up + +if [ ${exitcode} -eq 0 ] +then + echo "Package '${full_src_zip_fn}' rewritten into: '${full_dest_zip_fn}'" +else + exit 3 +fi diff --git a/cqrev b/cqrev new file mode 100755 index 0000000..5ac2268 --- /dev/null +++ b/cqrev @@ -0,0 +1,121 @@ +#!/bin/sh +# CQ Unix Toolkit +# Copyright (C) 2013 Cognifide Limited +# +# 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. +_usage() +{ + cat </dev/null +if [ ${?} -ne 0 ] +then + echo "Fatal: cannot find or test cqapi command" >&2 + exit 1 +fi + +CURLBIN=$("${API}" -c) +if [ ${?} -ne 0 ] +then + echo "Fatal: cannot find curl" >&2 + exit 1 +fi + +# API common options +cmdapi=$("${API}" -P "${@}") +username=$(echo "${cmdapi}" | cut -f1) +password=$(echo "${cmdapi}" | cut -f2) +instance=$(echo "${cmdapi}" | cut -f3) +passed=$(echo "${cmdapi}" | cut -f4) +apigetopts=$(echo "${cmdapi}" | cut -f5) + +# Custom arguments +while getopts ":g:${apigetopts}" opt +do + case ${opt} in + g) + group="${OPTARG}";; + \?) + echo "Invalid option: -${OPTARG}" >&2; _usage;; + :) + echo "Option -${OPTARG} requires an argument." >&2; _usage;; + esac +done +shift $((OPTIND-1)) + +if [ ${#} -ne 1 -o "${passed}" -eq 0 ] +then + _usage +fi + +name=${1} + +if [ -z "${group}" ] +then + group_switch="" + group_arg="" +else + group_switch="-F" + group_arg="group=${group}" +fi + + +REFERER="${instance}/crx/packmgr" +AUTH="${username}:${password}" +FILEPATH="/crx/packmgr/service.jsp" +REFERERHEADER="Referer: ${REFERER}" + +STATUS=$(${CURLBIN} \ + -s \ + --write-out "%{http_code}" \ + -u "${AUTH}" \ + -H "${REFERERHEADER}" \ + -F cmd=uninst \ + ${group_switch} "${group_arg}" \ + -F "name=${name}" \ + "${instance}${FILEPATH}") + +EXITCODE=${?} +"${API}" -C ${EXITCODE} +STATUSCODE=$(echo "${STATUS}" | tail -n 1) +STATUS=$(echo "${STATUS}" | sed '$d') +"${API}" -H "${STATUSCODE}" +EXITCODE2=${?} +echo "${STATUS}" +exit $((EXITCODE + EXITCODE2)) + diff --git a/doc/cygwin1-curl.png b/doc/cygwin1-curl.png new file mode 100644 index 0000000..c7c6a5f Binary files /dev/null and b/doc/cygwin1-curl.png differ diff --git a/doc/cygwin2-util-linux.png b/doc/cygwin2-util-linux.png new file mode 100644 index 0000000..c3fa49c Binary files /dev/null and b/doc/cygwin2-util-linux.png differ diff --git a/doc/cygwin3-zip.png b/doc/cygwin3-zip.png new file mode 100644 index 0000000..aa7d360 Binary files /dev/null and b/doc/cygwin3-zip.png differ diff --git a/logo.png b/logo.png new file mode 100644 index 0000000..7df7a03 Binary files /dev/null and b/logo.png differ diff --git a/logo.svg b/logo.svg new file mode 100644 index 0000000..b700f2b --- /dev/null +++ b/logo.svg @@ -0,0 +1,570 @@ + + + + + + + + + + unsorted + + + + + + + + + + + + + + image/svg+xml + + + en + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + UNIX UNIX