def family_limit(): """ example """ return ( Family("limits").add( Limits({"total": 15, "prio": 10, "other": 20}), # use dictionnary Defstatus("complete")), Limit("record", 50), # record active/submit tasks Inlimit("record"), Limit("total", 2), # limiter Family("limit").add( Defstatus("complete"), Family("prio").add( # on top: submitted first Inlimit("../limits:prio"), [Family("%03d" % step).add( Task("process"), Variables(STEP=step)) for step in range(0, 120 + 1, 3)]), Family("side").add( # below: take remaining tokens Inlimit("../limits:other"), [Family("%03d" % step).add( Task("process"), Variables(STEP=step)) for step in range(0, 120, 3)])))
def family_limiter(): """ alternative example """ return ( Family("limiter").add( Limit("total", 10), Inlimit("total"), Task("alarm").add( Complete("limits eq complete"), Trigger("../limiter:total gt 8")), # relative path Family("limits").add( Defstatus("complete"), Family("weaker").add( # weaker is located above, not to be shadowed Trigger("../limiter:total le 10"), [Family("%03d" % step).add( Task("process"), Variables(STEP=step)) for step in range(0, 120, 3)]), Family("prio").add( # favourite shall not lead weaker to starve Trigger("../limiter:total le 15"), [Family("%03d" % step).add( Task("process"), Variables(STEP=step)) for step in range(0, 120, 3)]))))
def create_families(): """ provider """ return (Family("f4").add( Variables(SLEEP=2), Repeat("NAME", ["a", "b", "c", "d", "e", "f"], kind="enumerated"), Task("t1")), Family("f5").add( Repeat("DATE", 20170101, 20200105, kind="date"), Task("t1").add(Repeat("PARAM", 1, 10, kind="integer"), Label("info", ""))))
def create(name=os.getenv("SUITE", "elearning")): return Suite(name).add( Defstatus("suspended"), # start immediately or not for this demo Clock("real"), Edit(ECF_INCLUDE=HOME, # header files ECF_FILES=HOME, # script template .ecf ECF_HOME=HOME), # job + local output files Family("f1").add( Task("t1").add( Event(1), Label("info", ""), Meter("step", -1, 100)), Task("t2").add( Late("-c 01:00"), Meter("step", -1, 100), Event("a"), Event("b"), Trigger("t1:step gt 0")), Task("t3").add( Trigger("t2:a")), Task("t4").add( Complete("t2:b"), Trigger("t2 eq complete and not t2:b"))), Family("f2").add( Task("t1").add( Time("00:30 23:30 00:30")), Task("t2").add( Day("sunday")), Task("t3").add( Time("12:00"), Date("1.*.*")), Task("t4").add( Time("+00:02")), Task("t5").add( Time("00:02"))))
def create(name): """ suite provider """ return Suite(name).add( Defstatus("suspended"), Edit(ECF_INCLUDE=HOME, # header files ECF_FILES=HOME, # script template .ecf ECF_HOME=HOME), # job + local output files Family("f1").add( Task("t1").add( Label("info", ""), Late("-c 01:00"), Meter("step", -1, 100)), Task("t2").add( Meter("step", -1, 100), Event("a"), Event("b"), Trigger("t1:step gt 0")), Task("t3").add( Trigger("t2:a")), Task("t4").add( Complete("t2:b"), Trigger("t2 eq complete and not t2:b"))), Family("f2").add( Task("t1").add( Time("00:30 23:30 00:30")), Task("t2").add( Day("sunday")), Task("t3").add( Time("12:00"), Date("1.*.*")), Task("t4").add( Time("+00:02")), Task("t5").add( Time("00:02"))))
def process(): """ provide 'daily' example family """ return (Family("process").add( Trigger("process ne aborted"), # STOP ASAP Family("daily").add( Task("simple"), Repeat("YMD", 20180101, 20321212, kind="date"), Family("decade").add(Task("simple"), Label("info", "Show-Icons-Complete"), Complete("../daily:YMD % 10 ne 0"))), Family("monthly").add( Task("simple"), Trigger("monthly:YM lt daily:YMD / 100 or daily eq complete"), Repeat(kind="enum", name="YM", start=[ "%d" % YM for YM in range(201801, 203212 + 1) if (YM % 100) < 13 and (YM % 100) != 0 ]), Family("odd").add(Task("simple"), Complete("../monthly:YM % 2 eq 0"))), Family("yearly").add( Task("simple"), Repeat("Y", 2018, 2032, kind="integer"), Trigger("yearly:Y lt daily:YMD / 10000 or daily eq complete"), Family("decade").add(Task("simple"), Complete("(../yearly:Y % 10) ne 0")), Family("century").add(Task("simple"), Complete("(../yearly:Y % 100) ne 0")))))
def call_task(name, start, stop, inc): """ leaf task """ meter = None if start != stop: meter = Meter("step", -1, int(stop)) return Task(name).add( events(), Edit(BEG=start, FIN=stop, BY=inc), meter)
def family_case(): """ case block as a family example """ return ( Family("case_var").add( Task("case").add( Defstatus("complete"), Edit(VAR=1)), Task("when_1").add( Trigger("case:VAR == 1"), Complete("case:VAR != 1")), Task("when_2").add( Trigger("case:VAR eq 2"), Complete("case:VAR ne 2"))), Family("case_meter").add( Task("case").add( Meter("STEP", -1, 48)), Task("when_1").add( Trigger("case:STEP eq 1"), Complete("case==complete")), Task("when_2").add( Trigger("case:STEP eq 2"), Complete("case eq complete"))))
def family_for(): """ for family """ return ( Family("for").add( process(), Repeat(kind="integer", name="STEP", start=1, end=240, step=3)), Family("loop").add(process(), Repeat("PARAM", PARAMS, kind="string")), Family("parallel").add(Limit("lim", 2), Inlimit("lim"), [ Family(param).add(Edit(PARAM=param), process().add(Label("info", param))) for param in PARAMS ]), Family("explode").add( Limit("lim", 2), Inlimit("lim"), # LIST COMPREHENSION: [Task("t%d" % num) for num in range(1, 5 + 1)]))
def model(fclen=240, name="model", dependencies="acquisition"): # leaf task deploy("""# task wrapper, turned into a job by ecFlow %manual useful information for Operators... %end %comment # comment, ... ecflow_ui Edit %end %nopp # no preprocessing here %end ID=%ID:-1% # model id, default value -1, -1: HRES, 0: CF, N: PF ecflow_client --event 1 # ecflow hook: send an event for i in $(seq 0 %FCLENGTH:240%); do # variable with default value sleep %SLEEP:0%; ecflow_client --meter step $i # share progress with ecFlow done ecflow_client --label info "OK" # report a label to ecFlow""", files + name + extn) return Task(name).add( # HRES Trigger(dependencies + " eq complete"), Variables(FCLENGTH=fclen), # python variable to suite variable Meter("step", -1, fclen), Label("info", ""))
def family_if(): """ if block as a family example """ return ( Family("if_then_else").add( Task("if").add(Event(1)), Task("then").add(Trigger("if:1"), Complete("if==complete and not if:1")), Task("else").add(Complete("if:1"), Trigger("if eq complete and not if:1"))), Family("if").add( # one script Task("model").add(Event(1))), Family("then").add(Trigger("if/model:1"), Complete("if eq complete and not if/model:1"), Task("model")), Family("else").add(Complete("if/model:1"), Trigger("if eq complete and not if/model:1"), Task("model")))
DEFS = Defs() DEFS.add( Suite(NAME).add( Defstatus("suspended"), # so that jobs do not start immediately Repeat(kind="day", step=1), Variables(ECF_HOME=HOME, ECF_INCLUDE=HOME + "/include", ECF_FILES=HOME + "/files"), [ Family(str(cycle)).add( Variables(CYCLE=cycle, LAST_STEP=LAST_STEP[cycle]), cycle_trigger(cycle), Family("analysis").add( Task("get_observations"), Task("run_analysis").add(Trigger([ "get_observations", ])), Task("post_processing").add(Trigger([ "run_analysis", ]))), Family("forecast").add( Trigger("analysis == complete"), Task("get_input_data").add(Label("info", "")), Task("run_forecast").add( Trigger([ "get_input_data", ]), Meter("step", 0, LAST_STEP[cycle]))), Family("archive").add( Family("analysis").add(
#!/usr/bin/env python from __future__ import print_function import os import ecf as ecflow from ecf import (Defstatus, Suite, Family, Task, Variables) print("Creating suite definition") ECF_HOME = os.path.join(os.getenv("HOME"), "ecflow_server") NAME = os.getenv("SUITE", "elearning") DEFS = ecflow.Defs() DEFS.add( # suite definition Suite(NAME).add( Defstatus("suspended"), # so that jobs do not start immediately Variables(ECF_HOME=ECF_HOME, ECF_FILES=ECF_HOME + "/files", ECF_INCLUDE=ECF_HOME + "/include"), Family("f1").add( # hosting family Task("t1"), # a first task Task("t2"), # a second task ))) if __name__ == '__main__': HOST = os.getenv("ECF_HOST", "localhost") PORT = int(os.getenv("ECF_PORT", "%d" % (1500 + os.getuid()))) CLIENT = ecflow.Client(HOST, PORT) NODE = "/" + NAME # replace suite node: add f1, delete t1 t2 CLIENT.replace(NODE, DEFS) print("replaced node %s into" % NODE, HOST, PORT)
def process(): """ provide leaf task """ return Task("process")
from ecf import (Defstatus, Suite, Task, Variables) ECF_HOME = os.path.join(os.getenv("HOME"), "ecflow_server") ECF_FILES = ECF_HOME + "/files" NAME = os.getenv("SUITE", "elearning") DEFS = ecflow.Defs() DEFS.add( # suite definition Suite(NAME).add( Defstatus("suspended"), # so that jobs do not start immediately Variables( # we can add multiple variables at once ECF_HOME=ECF_HOME, # where job files are created by ecflow ECF_FILES=ECF_FILES, # where to find script templates .ecf ECF_INCLUDE=ECF_HOME + "/include", # where to find head.h tail.h SLEEP=1, # user variable to be inherited by task/families below ), Task("t1").add( Variables(SLEEP=5), # overwriting with value 5 for task t1 ), Task("t2").add( Variables(SLEEP=7), # overwriting with value 7 for task t2 ))) SCRIPT_TEMPLATE = """#!/bin/bash %include <head.h> echo "I will now sleep for %SLEEP:1% seconds" sleep %SLEEP:1% %include <tail.h> """ if __name__ == '__main__': with open(ECF_FILES + "/t1.ecf", "w") as t1: print(SCRIPT_TEMPLATE, file=t1)
import os import ecf as ecflow from ecf import (Defstatus, Suite, Family, Task, Variables, Trigger, Event) ECF_HOME = os.path.join(os.getenv("HOME"), "ecflow_server") NAME = os.getenv("SUITE", "elearning") DEFS = ecflow.Defs() DEFS.add( # suite definition Suite(NAME).add( Defstatus("suspended"), # so that jobs do not start immediately Variables( # add multiple variables at once: ECF_HOME=ECF_HOME, # where job files are created by ecflow ECF_FILES=ECF_HOME + "/files", # where to find script template ECF_INCLUDE=ECF_HOME + "/include", # where to find head.h tail.h SLEEP=5), Family("f1").add( Task("t1"), Task("t2").add( Trigger("t1 eq complete"), Event("a"), Event("1")), Task("t3").add( Trigger("t2:a")), Task("t4").add( Trigger("t2:1"))))) SCRIPT_TEMPLATE = """#!/bin/bash %include <head.h> echo "I will now sleep for %SLEEP:1% seconds" sleep %SLEEP:1%; ecflow_client --event a # set first event sleep %SLEEP:!%; ecflow_client --event 1 # set a second event sleep %SLEEP:1%; # don't sleep too much anyway
#!/usr/bin/env python2.7 from __future__ import print_function """ add another task, another manual """ import os from ecf import (Defs, Defstatus, Suite, Variable, Task, Client) print("Creating suite definition") ECF_HOME = os.path.join(os.getenv("HOME"), "ecflow_server") NAME = os.getenv("SUITE", "elearning") DEFS = Defs() DEFS.add( # suite definition Suite(NAME).add( Defstatus("suspended"), # so that jobs do not start immediately Variable("ECF_HOME", ECF_HOME), Task("t1"), # first task Task("t2"), # second task )) SCRIPT_TEMPLATE = """%manual Manual for task t2 Operations: if this task fails, set it to complete, report next working day Analyst: Check something ... %end %include <head.h> echo "I am part of a suite that lives in %ECF_HOME%" %include <tail.h> %manual There can be multiple manual pages in the same file. When viewed they are simply concatenated. %end """
from __future__ import print_function import os import ecf as ecflow from ecf import (Date, Day, Defs, Defstatus, Suite, Family, Task, If, # If attribute in use example Edit, Label, Repeat, Time, Trigger, Defs, Client) HOME = os.getenv("HOME") + "/ecflow_course"; NAME = "data_acquisition"; DEFS = Defs() DEFS.add(Suite(NAME).add( Defstatus("suspended"), # so that jobs do not start immediately Repeat(kind="day", step=1), Edit(ECF_HOME=HOME, ECF_INCLUDE=HOME, ECF_FILES=HOME + "/acq", SLEEP=2), [Family(city).add( Family("archive").add( [Family(obs_type).add( If(test=city in ("Exeter", "Toulouse", "Offenbach"), then=Time("00:00 23:00 01:00")), If(city in ("Washington"), Time("00:00 23:00 03:00")), If(city in ("Tokyo"), Time("12:00")), If(city in ("Melbourne"), Day("monday")), If(city in ("Montreal"), Date("1.*.*")), Task("get").add(Label("info", "")), Task("process").add(Trigger("get eq complete")), Task("store").add(Trigger("get eq complete"))) for obs_type in ("observations", "fields", "images")])) for city in ("Exeter", "Toulouse", "Offenbach", "Washington", "Tokyo", "Melbourne", "Montreal")])) # print(DEFS); DEFS.generate_scripts(); RESULT = DEFS.simulate(); # print(RESULT) CLIENT = Client(os.getenv("ECF_HOST", "localhost"), os.getenv("ECF_PORT", 2500)) CLIENT.replace("/%s" % NAME, DEFS)
#!/usr/bin/env python from __future__ import print_function import os import ecf as ecflow from ecf import (Defstatus, Suite, Family, Task, Variables, Trigger) TASK3 = Task("t3") # a python object can be set, and added later to the suite ECF_HOME = os.path.join(os.getenv("HOME"), "ecflow_server") NAME = os.getenv("SUITE", "elearning") DEFS = ecflow.Defs() DEFS.add( # suite definition Suite(NAME).add( Defstatus("suspended"), # so that jobs do not start immediately Variables( ECF_HOME=ECF_HOME, # where job files are created by ecflow ECF_FILES=ECF_HOME + "/files", # where to find script template ECF_INCLUDE=ECF_HOME + "/include", # where to find head.h tail.h SLEEP=1, ), Family("f1").add( Task("t1").add(Variables(SLEEP=5)), Task("t2").add(Variables(SLEEP=7), Trigger("t1 eq complete")), TASK3, Task("t4").add(Trigger(["t1", "t2"]))))) SCRIPT_TEMPLATE = """#!/bin/bash %include <head.h> echo "I will now sleep for %SLEEP:1% seconds"; sleep %SLEEP:1% %include <tail.h> """ if __name__ == '__main__':
def call_consumer(selection): lead = "/%s/consumer/admin/leader:1" % selection prod = "/%s/consumer/produce" % selection return Family("consumer").add( Defstatus("suspended"), Edit(SLEEP=10, PRODUCE="no", CONSUME="no"), Family("limit").add( Defstatus("complete"), Limit("consume", 7),), Family("admin").add( # set manually with the GUI or alter the event 1 so # that producer 1 becomes leader # default: producer0 leads Task("leader").add( Event("1"), # text this task is dummy task not designed to run Defstatus("complete"))), Edit(PRODUCE="yes", # default : task does both produce/consume CONSUME="yes"), call_task("produce", beg, fin, by).add( # this task will do both produde/consume serially Label("info", "both produce and consume in one task")), Family("produce0").add( # loop inside the task, report progress with a Meter not_consumer(), Label("info", "loop inside the job"), call_task("produce", beg, fin, by)), Family("produce1").add( # choice is to submit a new job for each step, here Label("info", "loop in the suite (repeat), submit one job per step"), not_consumer(), call_task("produce", '%STEP%', "%STEP%", by).add( Repeat("STEP", beg, fin, by, kind="integer"))), Family("consume").add( Label("info", "use ecflow_client --wait %TRIGGER% in the job"), not_producer(), Inlimit("limit:consume"), Edit(CALL_WAITER=1, # $step will be interpreted in the job! TRIGGER="../produce:step gt consume:$step or " + "../produce eq complete"), call_task("consume", beg, fin, by)), Family("consume0or1").add( Label("info", "an event may indicate the leader to trigger from"), not_producer(), Inlimit("limit:consume"), call_task("consume", "%STEP%", "%STEP%", by), Repeat("STEP", beg, fin, by, kind="integer"), trigger("(%s and (consume0or1:STEP lt %s1/produce:STEP" % ( lead, prod) + " or %s==complete)) or " % prod + "(not %s and (consume0or1:STEP lt %s0/produce:step" % ( lead, prod) + " or %s0/produce==complete))" % prod)), Family("consume1").add( Label("info", "spread consumer in multiple families"), not_producer(), Inlimit("limit:consume"), consume1(producer=prod)), Family("consume2").add( # change manually with the GUI the limit to reduce/increase the load Label("info", "one family per step to consume"), Inlimit("limit:consume"), not_producer(), consume2(beg, fin, prod)))
v = "2.7" v = "3.5" INST = "/usr/local/apps/ecflow/lib/python%s/site-packages/ecflow:" % v sys.path.append(INST) INST = "/usr/local/lib/python%s/site-packages/ecflow" % v sys.path.append(INST) from ecf import (Defs, Suite, Defstatus, Edit, Task) if 0: raise Exception( #ERR: could not import ecf. Does the following line help?"+ "\nexport PYTHONPATH=$PYTHONPATH:%s" % INST) print("Creating suite definition") ECF_HOME = os.path.join(os.getenv("HOME"), "ecflow_server") NAME = os.getenv("SUITE", "elearning") DEFS = Defs() DEFS.add( Suite(NAME).add( # simplest suite definition Defstatus("suspended"), # so that jobs do not start immediately Edit( ECF_HOME=ECF_HOME, # where to find jobs + output files ECF_FILES=ECF_HOME + "/files", # script template .ecf ECF_INCLUDE=ECF_HOME + "/include"), # include files .h Task("t1"))) # a first task if __name__ == '__main__': print(DEFS) NAME = ECF_HOME + NAME print("Saving definition to file '%s.def'" % NAME) os.system("mkdir -p %s" % NAME) DEFS.save_as_defs("%s.def" % NAME) # an external client can use it
import ecf as ecflow from ecf import (Defstatus, Suite, Family, Task, Variables, Trigger, Event, Meter) ECF_HOME = os.path.join(os.getenv("HOME"), "ecflow_server") NAME = os.getenv("SUITE", "elearning") DEFS = ecflow.Defs() DEFS.add( # suite definition Suite(NAME).add( Defstatus("suspended"), # so that jobs do not start immediately Variables( # add multiple variables at once ECF_HOME=ECF_HOME, # where job files are created by ecflow ECF_FILES=ECF_HOME + "/files", # where to find script templates ECF_INCLUDE=ECF_HOME + "/include", # where to find head.h tail.h SLEEP=2), Family("f2").add( Task("t1").add(Meter("step", -1, 240)), Task("t2").add(Trigger("t1 eq complete"), Event("a"), Event("b")), Task("t3").add(Trigger("t2:a")), Task("t4").add(Trigger("t2:b")), Task("t5").add(Trigger("t1:step ge 24")), Task("t6").add(Trigger("t1:step ge 48")), Task("t7").add(Trigger("t1:step ge 120"))))) SCRIPT_TEMPLATE = """#!/bin/bash %include <head.h> STEP=0 while [[ $STEP -le 240 ]] ; do ecflow_client --meter step $STEP # share progress msg="The date is now $(date)"; sleep %SLEEP:1% (( STEP = STEP + 1)) done
ECF_HOME = os.path.join(os.getenv("HOME"), "ecflow_server") ECF_INCLUDE = ECF_HOME + "/include" NAME = os.getenv("SUITE", "elearning") DEFS = ecflow.Defs() DEFS.add( # suite definition Suite(NAME).add( Defstatus("suspended"), # so that jobs do not start immediately Variables( # we can add multiple variables at once ECF_HOME=ECF_HOME, # where job files are created by ecflow ECF_FILES=ECF_HOME + "/files", # where to find script template ECF_INCLUDE=ECF_INCLUDE, # where to find head.h tail.h SLEEP=2, ), Family("f3").add( Task("t1").add(Label("info", "none"), Meter("step", -1, 240))))) SCRIPT_TEMPLATE = """#!/bin/bash %include <head.h> STEP=0 while [[ $STEP -le 240 ]] ; do sleep %SLEEP:1%; ecflow_client --meter step $STEP # share progress msg="The date is now $(date)" ecflow_client --label info "$msg" (( STEP = STEP + 1)) done ecflow_client --label info "job's done" %include <tail.h> """ if __name__ == '__main__':
##################################################################### # suite definition acq = "acquisition" deploy("echo acq %TASK%", files + acq + extn) # create wrapper post = "postproc" deploy("ecflow_client --label info %TASK%", files + post + extn) suite = Suite("course").add( Defstatus("suspended"), Repeat("YMD", 20180101, 20321212, kind="date"), Variables(ECF_HOME=home, # jobs files are created there by ecflow ECF_FILES=home + "/files", # task-wrappers location ECF_INCLUDE=home + "/include", # include files location # ECF_OUT=home, # output files location on remote systems, # no directory created there by ecflow... ECF_EXTN=extn, ), # task wrapper extension Task(acq).add(Event(1)), Family("ensemble").add( # ENS Complete(acq + ":1"), [Family("%02d" % num).add( Variables(ID=num), model(360, dependencies="../../" + acq)) # relative path... for num in xrange(0, 10)]), model(240, dependencies=acq), # HRES Task(post).add( Trigger("model eq complete"), Label("info", ""))) ##################################################################### head = """#!/bin/bash set -e # stop the shell on first error
def create_family_f5(): return Family("f5").add(Limit( "l1", 2), Inlimit("l1"), Variables(SLEEP=2), [ Task("t%d" % idn).add(Late("-s 00:03 -a 00:10")) for idn in xrange(1, 10) ])
def create_family_f5(): """ provider """ return Family("f5").add(Limit("l1", 2), Inlimit("l1"), Variables(SLEEP=2), [Task("t%d" % tid) for tid in range(1, 10)])
#!/usr/bin/env python2.7 """ back archive example """ from __future__ import print_function import os import ecf as ecflow from ecf import (Defs, Defstatus, Suite, Family, Task, Variables, Label, Limit, Inlimit, Repeat, Trigger) HOME = os.getenv("HOME") + "/ecflow_server" NAME = "back_archiving"; FILES = HOME + "/back"; DEFS = Defs() DEFS.add(Suite(NAME).add( Defstatus("suspended"), # so that jobs do not start immediately Repeat(kind="day", step=1), Variables(ECF_HOME=HOME, ECF_INCLUDE=HOME, ECF_FILES=FILES, SLEEP=2), Limit("access", 2), [Family(kind).add( Repeat("DATE", 19900101, 19950712, kind="date"), Variables(KIND=kind), Task("get_old").add(Inlimit("access"), Label("info", "")), Task("convert").add(Trigger("get_old == complete")), Task("save_new").add(Trigger("convert eq complete")) ) for kind in ("analysis", "forecast", "climatology", "observations", "images")])) # print(DEFS); DEFS.generate_scripts(); RESULT = DEFS.simulate(); print(RESULT) CLIENT = ecflow.Client(os.getenv("ECF_HOST", "localhost"), os.getenv("ECF_PORT", 2500)) CLIENT.replace("/%s" % NAME, DEFS)
from ecf import (Defstatus, Suite, Family, Task, Edit, Time, Date, Day, Clock, Defs, Client) ECF_HOME = os.path.join(os.getenv("HOME"), "ecflow_server") NAME = os.getenv("SUITE", "elearning") DEFS = Defs() DEFS.add( # suite definition Suite(NAME).add( Clock("real"), Defstatus("suspended"), # so that jobs do not start immediately Edit( # we can add multiple variables at once ECF_HOME=ECF_HOME, # where job files are created by ecflow ECF_FILES=ECF_HOME + "/files", # where to find script templates ECF_INCLUDE=ECF_HOME + "/include", # where to find head.h tail.h SLEEP=2, ), Family("f2").add( Task("t1").add(Time("00:30 23:30 00:30")), Task("t2").add(Day("sunday")), Task("t3").add(Date("16.01.2018"), Time("12:00")), Task("t4").add(Time("+00:02")), Task("t5").add(Time("00:02"))))) SCRIPT_TEMPLATE = """#!/bin/bash %include <head.h> STEP=0 while [[ $STEP -le 240 ]] ; do sleep %SLEEP:1%; ecflow_client --meter step $STEP # share progress (( STEP = STEP + 1)) done %include <tail.h> """