/ lib / loop.sh
loop.sh
  1  ## \brief Control the loops within your scripts (pause/stop them).
  2  ## \desc The loop.sh library provides functions to control loops within shells scripts.
  3  ## When used, it allows to control the execution of already running loops from whatever
  4  ## location or terminal. It is therefore possible to pause a script from another terminal
  5  ## without hitting Control-Z in the shell process running the script.
  6  ##
  7  ## This execution control mechanism can even allow to control several loops and inner loops
  8  ## (nested loops) at the same time, or make different scripts dependents on each other.
  9  
 10  ## \example In a script:
 11  ## \example-code bash
 12  ##   #!/bin/bash
 13  ##   source $(shellm-core-path)
 14  ##   shellm source shellm/loop
 15  ##
 16  ##   loop init "script.loop"
 17  ##
 18  ##   i=0
 19  ##   while true; do
 20  ##
 21  ##     loop control "script.loop" || break
 22  ##
 23  ##     echo "$i"
 24  ##     (( i++ ))
 25  ##     sleep 1
 26  ##   done
 27  
 28  ## \example Then, from another terminal:
 29  ## \example-code console
 30  ##   $ loop pause "script.loop"
 31  ##   $ loop resume "script.loop"
 32  ##   $ loop stop "script.loop"
 33  
 34  ## \function loop_alive <NAME>
 35  ## \function-brief Check if the loop is alive (exists and not paused).
 36  ## \function-argument NAME The name of the loop to check.
 37  ## \function-return 0 The loop is alive.
 38  ## \function-return 1 The loop is not alive.
 39  loop_alive() {
 40    loop_exists "$1" && ! loop_paused "$1"
 41  }
 42  
 43  ## \function loop_control <NAME>
 44  ## \function-brief Wait as long as the loop is paused, return 1 when it's dead.
 45  ## Use this function like this: `loop_control $NAME || break`
 46  ## This function is a shortcut for:
 47  ##   if loop_paused $NAME; then
 48  ##     loop_wait $NAME
 49  ##   elif loop_dead $NAME; then
 50  ##     break
 51  ##   fi
 52  ## \function-argument NAME The name of the loop to control.
 53  loop_control() {
 54    if loop_paused "$1"; then
 55      loop_wait "$1"
 56    elif loop_dead "$1"; then
 57      return 1
 58    fi
 59  }
 60  
 61  ## \function loop_dead <NAME>
 62  ## \function-brief Check if the loop is dead.
 63  ## \function-argument NAME The name of the loop to check.
 64  ## \function-return 0 The loop is dead.
 65  ## \function-return 1 The loop is not dead.
 66  loop_dead() {
 67    ! loop_exists "$1"
 68  }
 69  
 70  ## \function loop_exists <NAME>
 71  ## \function-brief Check if the loop exists.
 72  ## \function-argument NAME The name of the loop to check.
 73  ## \function-return 0 The loop exists.
 74  ## \function-return 1 The loop does not exist.
 75  loop_exists() {
 76    [ -f "${__loop_datadir}/$1" ]
 77  }
 78  
 79  ## \function loop_init <NAME>
 80  ## \function-brief Initialize a loop.
 81  ## \function-argument NAME The name of the loop to initialize.
 82  ## \function-return 0 The loop was correctly initialized.
 83  ## \function-return 1 The loop was already initialized.
 84  ## \function-stderr A warning when the loop was already initialized.
 85  loop_init() {
 86    if ! loop_exists "$1"; then
 87      touch "${__loop_datadir}/$1"
 88    else
 89      echo "loop: '$1' already initialized" >&2
 90      return 1
 91    fi
 92  }
 93  
 94  ## \function loop_pause <NAME>
 95  ## \function-brief Pause a loop.
 96  ## \function-argument NAME The name of the loop to pause.
 97  ## \function-return 0 The loop existed and was paused.
 98  ## \function-return 1 The loop did not exist.
 99  loop_pause() {
100    if loop_exists "$1"; then
101      echo "paused" > "${__loop_datadir}/$1"
102    else
103      return 1
104    fi
105  }
106  
107  ## \function loop_paused <NAME>
108  ## \function-brief Check if the loop is paused.
109  ## \function-argument NAME The name of the loop to check.
110  ## \function-return 0 The loop is paused.
111  ## \function-return 1 The loop is not paused.
112  loop_paused() {
113    if ! grep -q paused "${__loop_datadir}/$1" 2>/dev/null; then
114      return 1
115    fi
116  }
117  
118  ## \function loop_resume <NAME>
119  ## \function-brief Resume a loop.
120  ## \function-argument NAME The name of the loop to resume.
121  ## \function-return 0 The loop existed and was resumed.
122  ## \function-return 1 The loop did not exist.
123  loop_resume() {
124    if loop_exists "$1"; then
125      echo "" > "${__loop_datadir}/$1"
126    else
127      return 1
128    fi
129  }
130  
131  ## \function loop_stop <NAME>
132  ## \function-brief Stop a loop.
133  ## \function-argument NAME The name of the loop to stop.
134  ## \function-return 0 The loop existed and was stopped.
135  ## \function-return 1 The loop did not exist.
136  loop_stop() {
137    rm "${__loop_datadir}/$1" 2>/dev/null
138  }
139  
140  ## \function loop_wait <NAME>
141  ## \function-brief Wait as long as a loop is paused.
142  ## \function-argument NAME The name of the loop to wait.
143  loop_wait() {
144    while loop_paused "$1"; do
145      sleep 1;
146    done
147  }
148  
149  ## \function loop_list
150  ## \function-brief List the currently existing loops.
151  ## \function-stdout The existing loops.
152  loop_list() {
153    local loop_file
154    find "${__loop_datadir}" -type f | while read -r loop_file; do
155      echo "${loop_file##*/}"
156    done
157  }
158  
159  ## \function loop <COMMAND> <NAME>
160  ## \function-brief Main wrapper function accepting subcommands.
161  ## COMMAND can be the following:
162  ##
163  ##   - `alive`: return True if the loop is alive, False otherwise.
164  ##   - `control`: shortcut for loop paused? wait. loop dead? break.
165  ##   - `dead`: return True if the loop is dead, False otherwise.
166  ##   - `exists`: return True if loop has been initialized, False otherwise.
167  ##   - `init`: init a new loop control and start it.
168  ##   - `pause`: pause the loop. It will wait until resumed or stopped.
169  ##   - `resume`: resume the loop.
170  ##   - `stop`: definitely stop the loop.
171  ##   - `wait`: wait as long as loop is paused.
172  ## \function-argument COMMAND The subcommand to run.
173  ## \function-argument NAME The name of the loop on which to act.
174  ## \function-return ? The return code of the subcommand.
175  ## \function-return 1 When the subcommand is unknown.
176  ## \function-stderr Warning when unknown subcommmand.
177  loop() {
178    __loop_datadir="/tmp/loop"
179  
180    ! [ -d "${__loop_datadir}" ] && mkdir "${__loop_datadir}"
181  
182    local loop_command="$1"
183    local var="$2"
184  
185    case "${loop_command}" in
186      alive) loop_alive "${var}" ;;
187      control) loop_control "${var}" ;;
188      dead) loop_dead "${var}" ;;
189      exists) loop_exists "${var}" ;;
190      init) loop_init "${var}" ;;
191      list) loop_list ;;
192      pause) loop_pause "${var}" ;;
193      paused) loop_paused "${var}" ;;
194      resume) loop_resume "${var}" ;;
195      stop) loop_stop "${var}" ;;
196      wait) loop_wait "${var}" ;;
197      *)
198        echo "loop: unknow command '${loop_command}'" >&2
199        return 1
200      ;;
201    esac
202  }