@@ -600,8 +600,8 @@ doco-target::__create() {
600600 event resolve " created $1 $TARGET_NAME " " $TARGET_NAME "
601601}
602602
603- doco-target::get () { REPLY=(" ${TARGET[@]} " ); this " ${1-exists} " " ${ @: 2} " ; }
604- doco-target::has-count () { REPLY=( " ${TARGET[@]} " ) ; eval " (( ${# REPLY[@]} ${1-} ))" ; }
603+ doco-target::get () { REPLY=(" ${TARGET[@]} " ); this " $@ " ; }
604+ doco-target::has-count () { this get ; eval " (( ${# REPLY[@]} ${1-} ))" ; }
605605
606606doco-target::readonly () { readonly " ${TARGET_VAR} " ; this " $@ " ; }
607607
@@ -617,23 +617,38 @@ doco-target::set() {
617617
618618doco-target::set-default () { this exists || this set " $@ " ; }
619619
620+ doco-target::foreach () {
621+ this get; for REPLY in " ${REPLY[@]} " ; do with-targets " $REPLY " -- " $@ " ; done
622+ }
620623@current-target::set () { fail " @current group is read-only" ; }
621624@current-target::declare-service () { fail " @current is a group, but a service was expected" ; }
622625@current-target::declare-group () { : ; }
623626
627+ @current-target::exists () { [[ ${HAVE_SERVICES-} ]]; }
628+ @current-target::get () {
629+ REPLY=(); [[ ! ${HAVE_SERVICES-} ]] || REPLY=(" ${TARGET[@]} " ); this " $@ "
630+ }
631+
624632with-targets () {
625633 local s=(); while (( $# )) && [[ $1 != -- ]]; do s+=(" $1 " ); shift ; done
626634 all-targets " ${s[@]} " || return
627- # oh bash, why do you hate us so...
628- local DOCO_SERVICES; DOCO_SERVICES=(" ${REPLY[@]} " ); readonly DOCO_SERVICES
629- " ${@: 2} "
635+ __apply-targets = " ${REPLY[@]} " -- " ${@: 2} "
630636}
631637
638+ without-targets () { __apply-targets ' ' -- " $@ " ; }
639+
640+ __apply-targets () {
641+ local HAVE_SERVICES=$1 DOCO_SERVICES DOCO_COMMAND
642+ DOCO_SERVICES=(); shift
643+ while (( $# )) && [[ $1 != -- ]]; do DOCO_SERVICES+=(" $1 " ); shift ; done
644+ readonly DOCO_SERVICES
645+ " ${@: 2} "
646+ }
632647# set REPLY to merge of all given target names
633648all-targets () {
634649 local services=()
635650 while (( $# )) ; do
636- target " $1 " get || [[ $1 == @current ]] ||
651+ target " $1 " get exists || [[ $1 == @current ]] ||
637652 fail " '$1 ' is not a known group or service" || return
638653 for REPLY in " ${REPLY[@]} " ; do
639654 [[ " ${services[*]-} " == * " $REPLY " * ]] || services+=(" $REPLY " )
@@ -646,16 +661,12 @@ all-targets() {
646661# set REPLY to contents of the first existing target
647662any-target () {
648663 for REPLY; do
649- if target " $REPLY " get; then return ; fi
664+ if target " $REPLY " get exists ; then return ; fi
650665 done
651666 REPLY=(); false
652667}
653668
654- foreach-service () {
655- for REPLY in ${DOCO_SERVICES[@]+" ${DOCO_SERVICES[@]} " } ; do
656- with-targets " $REPLY " -- " $@ "
657- done
658- }
669+ foreach-service () { target @current foreach " $@ " ; }
659670have-services () { target @current has-count " $@ " ; }
660671project-name () {
661672 REPLY=${COMPOSE_PROJECT_NAME-}
@@ -664,12 +675,16 @@ project-name() {
664675 ! (( $# )) || REPLY=$REPLY " _${1} _${2-1} " # container name
665676}
666677require-services () {
667- local REPLY=(" ${DOCO_SERVICES[@]} " )
668- case " $1 ${# REPLY[@]} " in
669- ? 1|-0|.* ) return ;; # 1 is always acceptable
670- ? 0) fail " no services specified for $2 " ;;
671- [-1]* ) fail " $2 cannot be used on multiple services" ;;
672- esac
678+ [[ ${1-} == [-+1.] ]] ||
679+ fail " require-services first argument must be ., -, +, or 1" || return
680+ (( $# > 1 )) || set -- " $1 " " ${DOCO_COMMAND:- the current command} "
681+ (( $# > 2 )) || set -- " $1 " " $2 " @current
682+ any-target " ${@: 3} " || true
683+ case " $1 ${# REPLY[@]} " in
684+ ? 1|-0|.* ) return ;; # 1 is always acceptable
685+ ? 0) fail " no services specified for $2 " ;;
686+ [-1]* ) fail " $2 cannot be used on multiple services" ;;
687+ esac
673688}
674689services-matching () {
675690 REPLY=()
@@ -703,7 +718,7 @@ generate-jq-func() {
703718}
704719ALIAS () { mdsh-splitwords " $1 " ; GROUP " ${REPLY[@]} " += " ${@: 2} " ; }
705720alias-exists () { target " $1 " exists; }
706- get-alias () { target " $1 " get || true ; }
721+ get-alias () { target " $1 " get; }
707722set-alias () { target " $1 " set " $@ " ; }
708723with-alias () { target " $1 " call " ${@: 2} " ; }
709724with-service () { mdsh-splitwords " $1 " ; with-targets @current " ${REPLY[@]} " -- " ${@: 2} " ; }
@@ -782,7 +797,10 @@ doco.version() { docker-compose version "$@"; }
782797
783798# Commands that accept services
784799compose-targeted () {
785- any-target @current || true # ok if none are set
800+ if any-target @current; then
801+ # Non-default target; make sure it's not empty
802+ with-targets " ${REPLY[@]} " -- require-services + " ${DOCO_COMMAND:- $1 } " || return
803+ fi
786804 compose " $@ " " ${REPLY[@]} "
787805}
788806# Commands that don't accept a list of services
@@ -800,36 +818,33 @@ compose-singular() {
800818 argv+=(" $1 " ); if [[ $1 =~ $opts ]]; then shift ; argv+=(" $1 " ); fi
801819 done
802820
803- if ! any-target @current || ! (( ${# REPLY[@]} )) ; then
804- # no current or default target, check command line for one
805- if (( ! $# )) || ! target " $1 " get; then
806- fail " No service/group specified for $cmd " || return
807- else
808- shift # remove the explicit target from the tail
809- fi
810- fi
811- if (( ${# REPLY[@]} != 1 )) ; then
812- with-targets " ${REPLY[@]} " -- require-services 1 " $cmd "
813- else
814- compose " ${argv[@]} " " ${REPLY[@]} " " $@ "
821+ if ! any-target @current; then
822+ # no current or default target, check command line for one and remove it
823+ if is-target-name " ${1-} " && target " $1 " get exists; then shift ; fi
815824 fi
825+
826+ with-targets " ${REPLY[@]} " -- require-services 1 " ${DOCO_COMMAND:- $cmd } " || return
827+ compose " ${argv[@]} " " ${REPLY[@]} " " $@ "
816828}
817829
830+ doco_had_args=0 # times doco has been called with arguments
831+
818832loco_do () {
819833 project-is-finalized ||
820834 fail " doco CLI cannot be used before the project spec is finalized" || return
835+ (( ! $# )) || doco_had_args=1
821836 case ${1-} in
822837 --* =* ) doco-optarg " $@ " ;; # --[option]=value
823838 --* ) doco-option " $@ " ;; # --[option]
824839 -[^=]=* ) doco-optarg " $@ " ;; # -a=bcd
825840 -[^=]?* ) doco-options " $@ " ;; # -abcd
826841 -? ) doco-option " $@ " ;; # -x
827- ' ' ) doco-null-command ;; # empty or missing command
842+ ' ' ) doco-null " $@ " ;; # empty or missing command
828843 * ) doco-other " $@ " ;; # commands, services, and groups
829844 esac
830845}
831- doco-null-command () {
832- if target " @current " exists ; then
846+ doco-null () {
847+ if (( doco_had_args && ! $# )) ; then
833848 target " @current" get
834849 ${REPLY[@]+printf ' %s\n' " ${REPLY[@]} " } # only output lines if there are some
835850 else
@@ -864,14 +879,17 @@ doco-optarg() {
864879 fi
865880}
866881doco-other () {
867- if fn-exists " doco.$1 " ; then " doco.$@ "
882+ if fn-exists " doco.$1 " ; then
883+ with-command " ${DOCO_COMMAND:- $1 } " " doco.$@ "
868884 elif is-target-name " $1 " && target " $1 " exists; then
869885 with-targets @current " $1 " -- doco " ${@: 2} "
870886 else fail " '$1 ' is not a recognized option, command, service, or group"
871887 fi
872888}
873- # Execute the rest of the command line with NO specified service(s)
874- doco.-- () { with-targets -- doco " $@ " ; }
889+
890+ with-command () { local DOCO_COMMAND=$1 ; " ${@: 2} " ; }
891+ # Execute the rest of the command line without specified services
892+ doco.-- () { without-targets doco " $@ " ; }
875893doco.--dry-run () {
876894 docker () { printf -v REPLY ' %q' " docker" " $@ " ; echo " ${REPLY# } " ; } >&2
877895 docker-compose () { printf -v REPLY ' %q' " docker-compose" " $@ " ; echo " ${REPLY# } " ; } >&2
@@ -887,49 +905,51 @@ function doco.--with=() {
887905}
888906function doco.--with-default=() {
889907 if target @current has-count || ! target " $1 " exists; then doco " ${@: 2} "
890- else with-targets " $1 " -- doco " ${@: 2} " ; fi
908+ else with-targets " $1 " -- with-command " ${DOCO_COMMAND-} " doco " ${@: 2} " ; fi
891909}
892910function doco.--require-services=() {
893911 [[ ${1: 0: 1} == [-+1.] ]] || loco_error " --require-services argument must begin with ., -, +, or 1"
894- mdsh-splitwords " $1 " && require-services " ${REPLY[@]} " " ${2-} " && doco " ${@: 2} " ;
912+ mdsh-splitwords " $1 " ; (( ${# REPLY[@]} > 1 )) || REPLY+=(" ${DOCO_COMMAND:- ${2-} } " )
913+ require-services " ${REPLY[@]: 0: 2} " && doco " ${@: 2} "
914+ }
915+ doco.cmd () {
916+ [[ ${DOCO_COMMAND-} != cmd ]] || local DOCO_COMMAND=$2
917+ doco --with-default cmd-default --require-services " $@ "
895918}
896- doco.cmd () { doco --with-default cmd-default --require-services " $@ " ; }
897919doco.cp () {
898920 local opts=() seen=' '
899921 while (( $# )) ; do
900922 case " $1 " in
901923 -a|--archive|-L|--follow-link) opts+=(" $1 " ) ;;
902924 --help|-h) docker help cp || true ; return ;;
903- -* ) loco_error " Unrecognized option $1 ; see 'docker help cp'" ;;
925+ -* ) fail " Unrecognized option $1 ; see 'docker help cp'" || return ;;
904926 * ) break ;;
905927 esac
906928 shift
907929 done
908- (( $# == 2 )) || loco_error " cp requires two non-option arguments (src and dest)"
930+ (( $# == 2 )) || fail " cp requires two non-option arguments (src and dest)" || return
909931 while (( $# )) ; do
910932 if [[ $1 == * :* ]]; then
911- [[ ! " $seen " ]] || loco_error " cp: only one argument may contain a :"
933+ [[ ! " $seen " ]] || fail " cp: only one argument may contain a :" || return
912934 seen=yes
913935 if [[ " ${1%%:* } " ]]; then
914936 project-name " ${1%%:* } " ; set -- " $REPLY :${1#*: } " " ${@: 2} "
915- elif have-services ' ==1' ; then
916- project-name " ${DOCO_SERVICES[0]} " ; set -- " $REPLY $1 " " ${@: 2} "
917937 else
918- doco --with-default=shell-default -- require-services= 1 cp " ${opts[@]} " " $@ "
919- return
938+ require-services 1 cp @current shell-default || return
939+ project-name " $REPLY " ; set -- " $REPLY $1 " " ${ @: 2} "
920940 fi
921941 elif [[ $1 != /* && $1 != - ]]; then
922942 # make paths relative to original run directory
923943 set -- " $LOCO_PWD /$1 " " ${@: 2} " ;
924944 fi
925945 opts+=(" $1 " ); shift
926946 done
927- [[ " $seen " ]] || loco_error " cp: either source or destination must contain a :"
947+ [[ " $seen " ]] || fail " cp: either source or destination must contain a :" || return
928948 docker cp ${opts[@]+" ${opts[@]} " }
929949}
930950doco.foreach () { foreach-service doco " $@ " ; }
931951doco.jq () { RUN_JQ " $@ " < " $DOCO_CONFIG " ; }
932- doco.sh () { doco cmd 1 exec bash " $@ " ; }
952+ doco.sh () { doco --with-default cmd-default exec bash " $@ " ; }
933953DEFINE " ${mdsh_raw_jq_api[*]} "
934954
935955# shellcheck disable=SC2059 # argument is a printf format string
0 commit comments