func (wf *Workflow) RunToRegex(procNamePatterns ...string) {
	procsToRun := []WorkflowProcess{}
	for _, pattern := range procNamePatterns {
		regexpPtrn := regexp.MustCompile(pattern)
		for procName, proc := range wf.Procs() {
			matches := regexpPtrn.MatchString(procName)
			if matches {
				procsToRun = append(procsToRun, proc)
			}
		}
	}
	wf.RunToProcs(procsToRun...)
}
RunToRegex runs all processes upstream of, and including, the process
whose name matches any of the provided regexp patterns
package scipipe
import (
	"fmt"
	"os"
	"regexp"
	"sort"
	"strings"
	"sync"
	"time"
)
type Workflow struct {
	name              string
	procs             map[string]WorkflowProcess
	concurrentTasks   chan struct{}
	concurrentTasksMx sync.Mutex
	sink              *Sink
	driver            WorkflowProcess
	logFile           string
	PlotConf          WorkflowPlotConf
}
type WorkflowPlotConf struct {
	EdgeLabels bool
}
type WorkflowProcess interface {
	Name() string
	InPorts() map[string]*InPort
	OutPorts() map[string]*OutPort
	InParamPorts() map[string]*InParamPort
	OutParamPorts() map[string]*OutParamPort
	Ready() bool
	Run()
	Fail(interface{})
	Failf(string, ...interface{})
}
func NewWorkflow(name string, maxConcurrentTasks int) *Workflow {
	wf := newWorkflowWithoutLogging(name, maxConcurrentTasks)
	allowedCharsPtrn := regexp.MustCompile("[^a-z0-9_]")
	wfNameNormalized := allowedCharsPtrn.ReplaceAllString(strings.ToLower(name), "-")
	wf.logFile = "log/scipipe-" + time.Now().Format("20060102-150405") + "-" + wfNameNormalized + ".log"
	InitLogAuditToFile(wf.logFile)
	return wf
}
func NewWorkflowCustomLogFile(name string, maxConcurrentTasks int, logFile string) *Workflow {
	wf := newWorkflowWithoutLogging(name, maxConcurrentTasks)
	wf.logFile = logFile
	InitLogAuditToFile(logFile)
	return wf
}
func newWorkflowWithoutLogging(name string, maxConcurrentTasks int) *Workflow {
	wf := &Workflow{
		name:            name,
		procs:           map[string]WorkflowProcess{},
		concurrentTasks: make(chan struct{}, maxConcurrentTasks),
		PlotConf:        WorkflowPlotConf{EdgeLabels: true},
	}
	sink := NewSink(wf, name+"_default_sink")
	wf.sink = sink
	wf.driver = sink
	return wf
}
func (wf *Workflow) Name() string {
	return wf.name
}
func (wf *Workflow) NewProc(procName string, commandPattern string) *Process {
	proc := NewProc(wf, procName, commandPattern)
	return proc
}
func (wf *Workflow) Proc(procName string) WorkflowProcess {
	if _, ok := wf.procs[procName]; !ok {
		wf.Failf("No process named (%s)", procName)
	}
	return wf.procs[procName]
}
func (wf *Workflow) ProcsSorted() []WorkflowProcess {
	keys := []string{}
	for k := range wf.Procs() {
		keys = append(keys, k)
	}
	sort.Strings(keys)
	procs := []WorkflowProcess{}
	for _, k := range keys {
		procs = append(procs, wf.Proc(k))
	}
	return procs
}
func (wf *Workflow) Procs() map[string]WorkflowProcess {
	return wf.procs
}
func (wf *Workflow) AddProc(proc WorkflowProcess) {
	if wf.procs[proc.Name()] != nil {
		wf.Failf("A process with name (%s) already exists in the workflow! Use a more unique name!", proc.Name())
	}
	wf.procs[proc.Name()] = proc
}
func (wf *Workflow) AddProcs(procs ...WorkflowProcess) {
	for _, proc := range procs {
		wf.AddProc(proc)
	}
}
func (wf *Workflow) Sink() *Sink {
	return wf.sink
}
func (wf *Workflow) SetSink(sink *Sink) {
	if wf.sink.Ready() {
		wf.Fail("Trying to replace a sink which is already connected. Are you combining SetSink() with ConnectFinalOutPort()? That is not allowed!")
	}
	wf.sink = sink
}
func (wf *Workflow) IncConcurrentTasks(slots int) {
	wf.concurrentTasksMx.Lock()
	for i := 0; i < slots; i++ {
		wf.concurrentTasks <- struct{}{}
		Debug.Println("Increased concurrent tasks")
	}
	wf.concurrentTasksMx.Unlock()
}
func (wf *Workflow) DecConcurrentTasks(slots int) {
	for i := 0; i < slots; i++ {
		<-wf.concurrentTasks
		Debug.Println("Decreased concurrent tasks")
	}
}
func (wf *Workflow) PlotGraph(filePath string) {
	dot := wf.DotGraph()
	createDirs(filePath)
	dotFile, err := os.Create(filePath)
	CheckWithMsg(err, "Could not create dot file "+filePath)
	_, errDot := dotFile.WriteString(dot)
	if errDot != nil {
		wf.Failf("Could not write to DOT-file %s: %s", dotFile.Name(), errDot)
	}
}
func (wf *Workflow) PlotGraphPDF(filePath string) {
	wf.PlotGraph(filePath)
	ExecCmd(fmt.Sprintf("dot -Tpdf %s -o %s.pdf", filePath, filePath))
}
func (wf *Workflow) DotGraph() (dot string) {
	dot = fmt.Sprintf(`digraph "%s" {`+"\n", wf.Name())
	dot += `  rankdir=LR;` + "\n"
	dot += `  graph [fontname="Arial",fontsize=13,color="#384A52",fontcolor="#384A52"];` + "\n"
	dot += `  node  [fontname="Arial",fontsize=11,color="#384A52",fontcolor="#384A52",fillcolor="#EFF2F5",shape=box,style=filled];` + "\n"
	dot += `  edge  [fontname="Arial",fontsize=9, color="#384A52",fontcolor="#384A52"];` + "\n"
	con := ""
	remToDotPtn := regexp.MustCompile(`^.*\.`)
	for _, p := range wf.ProcsSorted() {
		dot += fmt.Sprintf(`  "%s" [shape=box];`+"\n", p.Name())
		for opname, op := range p.OutPorts() {
			for rpname, rp := range op.RemotePorts {
				if wf.PlotConf.EdgeLabels {
					con += fmt.Sprintf(`  "%s" -> "%s" [taillabel="%s", headlabel="%s"];`+"\n", op.Process().Name(), rp.Process().Name(), remToDotPtn.ReplaceAllString(opname, ""), remToDotPtn.ReplaceAllString(rpname, ""))
				} else {
					con += fmt.Sprintf(`  "%s" -> "%s";`+"\n", op.Process().Name(), rp.Process().Name())
				}
			}
		}
		for popname, pop := range p.OutParamPorts() {
			for rpname, rp := range pop.RemotePorts {
				if wf.PlotConf.EdgeLabels {
					con += fmt.Sprintf(`  "%s" -> "%s" [style="dashed", taillabel="%s", headlabel="%s"];`+"\n", pop.Process().Name(), rp.Process().Name(), remToDotPtn.ReplaceAllString(popname, ""), remToDotPtn.ReplaceAllString(rpname, ""))
				} else {
					con += fmt.Sprintf(`  "%s" -> "%s" [style="dashed"];`+"\n", pop.Process().Name(), rp.Process().Name())
				}
			}
		}
	}
	dot += con
	dot += "}\n"
	return
}
func (wf *Workflow) Run() {
	wf.runProcs(wf.procs)
}
func (wf *Workflow) RunTo(finalProcNames ...string) {
	procs := []WorkflowProcess{}
	for _, procName := range finalProcNames {
		procs = append(procs, wf.Proc(procName))
	}
	wf.RunToProcs(procs...)
}