<commit_before>package essh

import (
	"bufio"
	"bytes"
	"fmt"
	"github.com/Songmu/wrapcommander"
	fatihColor "github.com/fatih/color"
	"github.com/kardianos/osext"
	"github.com/kohkimakimoto/essh/support/color"
	"github.com/kohkimakimoto/essh/support/helper"
	"github.com/yuin/gopher-lua"
	"io"
	"io/ioutil"
	"os"
	"os/exec"
	"path/filepath"
	"runtime"
	"strings"
	"sync"
	"text/template"
)

// system configurations.
var (
	SystemWideConfigFile         string
	SystemWideOverrideConfigFile string
	UserConfigFile               string
	UserOverrideConfigFile       string
	UserDataDir                  string
	WorkingDirConfigFile         string
	WorkingDirOverrideConfigFile string
	WorkingDataDir               string
	WorkingDir                   string
	Executable                   string
)

// flags
var (
	versionFlag            bool
	helpFlag               bool
	printFlag              bool
	colorFlag              bool
	noColorFlag            bool
	debugFlag              bool
	hostsFlag              bool
	quietFlag              bool
	allFlag                bool
	tagsFlag               bool
	tasksFlag              bool
	genFlag                bool
	updateFlag             bool
	withGlobalFlag         bool
	cleanAllFlag           bool
	cleanModulesFlag       bool
	cleanTmpFlag           bool
	zshCompletionModeFlag  bool
	zshCompletionFlag      bool
	zshCompletionHostsFlag bool
	zshCompletionTagsFlag  bool
	zshCompletionTasksFlag bool
	bashCompletionFlag     bool
	aliasesFlag            bool
	execFlag               bool
	fileFlag               bool
	prefixFlag             bool
	parallelFlag           bool
	privilegedFlag         bool
	ptyFlag                bool
	workindDirVar          string
	configVar              string
	selectVar              []string = []string{}
	targetVar              []string = []string{}
	backendVar             string
	prefixStringVar        string
	driverVar              string
)

const (
	ExitErr = 1
)

func Start() (exitStatus int) {
	defer func() {
		if e := recover(); e != nil {
			exitStatus = ExitErr
			if zshCompletionModeFlag && !debugFlag {
				// suppress printing error in running completion code.
				return
			}

			printError(e)
		}
	}()

	if os.Getenv("ESSH_DEBUG") != "" {
		debugFlag = true
	}

	if len(os.Args) == 1 {
		printUsage()
		return
	}

	osArgs := os.Args[1:]
	args := []string{}
	doesNotParseOption := false

	// parsing options
	// Essh uses only double dash options like `--print`,
	// because of preventing conflict to the ssh options.
	for {
		if len(osArgs) == 0 {
			break
		}

		arg := osArgs[0]

		if doesNotParseOption {
			// restructure args to remove essh options.
			args = append(args, arg)
		} else if arg == "--print" {
			printFlag = true
		} else if arg == "--version" {
			versionFlag = true
		} else if arg == "--help" {
			helpFlag = true
		} else if arg == "--color" {
			colorFlag = true
		} else if arg == "--no-color" {
			noColorFlag = true
		} else if arg == "--debug" {
			debugFlag = true
		} else if arg == "--hosts" {
			hostsFlag = true
		} else if arg == "--quiet" {
			quietFlag = true
		} else if arg == "--all" {
			allFlag = true
		} else if arg == "--tasks" {
			tasksFlag = true
		} else if arg == "--select" {
			if len(osArgs) < 2 {
				printError("--select reguires an argument.")
				return ExitErr
			}
			selectVar = append(selectVar, osArgs[1])
			osArgs = osArgs[1:]
		} else if strings.HasPrefix(arg, "--select=") {
			selectVar = append(selectVar, strings.Split(arg, "=")[1])
		} else if arg == "--tags" {
			tagsFlag = true
		} else if arg == "--gen" {
			genFlag = true
		} else if arg == "--update" {
			updateFlag = true
		} else if arg == "--clean-modules" {
			cleanModulesFlag = true
		} else if arg == "--clean-tmp" {
			cleanTmpFlag = true
		} else if arg == "--clean-all" {
			cleanAllFlag = true
		} else if arg == "--with-global" {
			withGlobalFlag = true
		} else if arg == "--zsh-completion" {
			zshCompletionFlag = true
			zshCompletionModeFlag = true
		} else if arg == "--zsh-completion-hosts" {
			zshCompletionHostsFlag = true
			zshCompletionModeFlag = true
		} else if arg == "--zsh-completion-tags" {
			zshCompletionTagsFlag = true
			zshCompletionModeFlag = true
		} else if arg == "--zsh-completion-tasks" {
			zshCompletionTasksFlag = true
			zshCompletionModeFlag = true
		} else if arg == "--bash-completion" {
			// TODO
			bashCompletionFlag = true
		} else if arg == "--aliases" {
			aliasesFlag = true
		} else if arg == "--working-dir" {
			if len(osArgs) < 2 {
				printError("--working-dir reguires an argument.")
				return ExitErr
			}
			workindDirVar = osArgs[1]
			osArgs = osArgs[1:]
		} else if strings.HasPrefix(arg, "--working-dir=") {
			workindDirVar = strings.Split(arg, "=")[1]
		} else if arg == "--config" {
			if len(osArgs) < 2 {
				printError("--config reguires an argument.")
				return ExitErr
			}
			configVar = osArgs[1]
			osArgs = osArgs[1:]
		} else if strings.HasPrefix(arg, "--config=") {
			configVar = strings.Split(arg, "=")[1]
		} else if arg == "--exec" {
			execFlag = true
		} else if arg == "--privileged" {
			privilegedFlag = true
		} else if arg == "--parallel" {
			parallelFlag = true
		} else if arg == "--prefix" {
			prefixFlag = true
		} else if arg == "--prefix-string" {
			if len(osArgs) < 2 {
				printError("--prefix-string reguires an argument.")
				return ExitErr
			}
			prefixStringVar = osArgs[1]
			osArgs = osArgs[1:]
		} else if strings.HasPrefix(arg, "--prefix-string=") {
			prefixStringVar = strings.Split(arg, "=")[1]
		} else if arg == "--driver" {
			if len(osArgs) < 2 {
				printError("--driver reguires an argument.")
				return ExitErr
			}
			driverVar = osArgs[1]
			osArgs = osArgs[1:]
		} else if strings.HasPrefix(arg, "--driver=") {
			driverVar = strings.Split(arg, "=")[1]
		} else if arg == "--target" {
			if len(osArgs) < 2 {
				printError("--target reguires an argument.")
				return ExitErr
			}
			targetVar = append(targetVar, osArgs[1])
			osArgs = osArgs[1:]
		} else if strings.HasPrefix(arg, "--target=") {
			targetVar = append(targetVar, strings.Split(arg, "=")[1])
		} else if arg == "--backend" {
			if len(osArgs) < 2 {
				printError("--backend reguires an argument.")
				return ExitErr
			}
			backendVar = osArgs[1]
			osArgs = osArgs[1:]
		} else if strings.HasPrefix(arg, "--backend=") {
			backendVar = strings.Split(arg, "=")[1]
		} else if arg == "--file" {
			fileFlag = true
		} else if arg == "--pty" {
			ptyFlag = true
		} else if arg == "--" {
			doesNotParseOption = true
			// to behave same ssh. pass the `--` to the ssh.
			args = append(args, arg)
		} else {
			// restructure args to remove essh options.
			args = append(args, arg)
		}

		osArgs = osArgs[1:]
	}

	if colorFlag {
		fatihColor.NoColor = false
	}

	if noColorFlag {
		fatihColor.NoColor = true
	}

	if os.Getenv("ESSH_DEBUG") != "" {
		debugFlag = true
	}

	if workindDirVar != "" {
		err := os.Chdir(workindDirVar)
		if err != nil {
			printError(err)
			return ExitErr
		}
	}

	// decide the wokingDirConfigFile
	wd, err := os.Getwd()
	if err != nil {
		printError(fmt.Errorf("couldn't get working dir %v\n", err))
		return ExitErr
	}

	WorkingDir = wd
	WorkingDataDir = filepath.Join(wd, ".essh")
	WorkingDirConfigFile = filepath.Join(wd, "esshconfig.lua")

	workingDirConfigFileBasename := filepath.Base(WorkingDirConfigFile)
	workingDirConfigFileDir := filepath.Dir(WorkingDirConfigFile)
	workingDirConfigFileBasenameExtension := filepath.Ext(workingDirConfigFileBasename)
	workingDirConfigFileName := workingDirConfigFileBasename[0 : len(workingDirConfigFileBasename)-len(workingDirConfigFileBasenameExtension)]

	WorkingDirOverrideConfigFile = filepath.Join(workingDirConfigFileDir, workingDirConfigFileName+"_override"+workingDirConfigFileBasenameExtension)

	// overwrite config file path by --config option.
	if configVar != "" {
		if filepath.IsAbs(configVar) {
			WorkingDirConfigFile = configVar
		} else {
			WorkingDirConfigFile = filepath.Join(wd, configVar)
		}
	}

	if helpFlag {
		printUsage()
		return
	}

	if cleanAllFlag || cleanModulesFlag || cleanTmpFlag {
		err := removeRegistryData()
		if err != nil {
			printError(err)
			return ExitErr
		}
		return
	}

	if versionFlag {
		fmt.Printf("%s (%s)\n", Version, CommitHash)
		return
	}

	if zshCompletionFlag {
		s, err := sprintByTemplate(ZSH_COMPLETION)
		if err != nil {
			printError(err)
			return ExitErr
		}

		fmt.Print(s)
		return
	}

	if aliasesFlag {
		s, err := sprintByTemplate(ALIASES_CODE)
		if err != nil {
			printError(err)
			return ExitErr
		}

		fmt.Print(s)
		return
	}

	if bashCompletionFlag {
		fmt.Print(BASH_COMPLETION)
		return
	}

	// set up the lua state.
	L := lua.NewState()
	defer L.Close()
	InitLuaState(L)

	if debugFlag {
		fmt.Printf("[essh debug] init lua state\n")
	}

	// generate temporary ssh config file
	tmpFile, err := ioutil.TempFile("", "essh.ssh_config.")
	if err != nil {
		printError(err)
		return ExitErr
	}
	defer func() {
		tmpFile.Close()
		os.Remove(tmpFile.Name())

		if debugFlag {
			fmt.Printf("[essh debug] deleted config file: %s \n", tmpFile.Name())
		}
	}()
	temporarySSHConfigFile := tmpFile.Name()

	if debugFlag {
		fmt.Printf("[essh debug] generated config file: %s \n", temporarySSHConfigFile)
	}

	lessh, ok := toLTable(L.GetGlobal("essh"))
	if !ok {
		printError(fmt.Errorf("essh must be a table"))
		return ExitErr
	}

	// set temporary ssh config file path
	lessh.RawSetString("ssh_config", lua.LString(temporarySSHConfigFile))

	// user context
	CurrentRegistry = NewRegistry(UserDataDir, RegistryTypeGlobal)
	GlobalRegistry = CurrentRegistry

	if err := CurrentRegistry.MkDirs(); err != nil {
		printError(err)
		return ExitErr
	}

	// load system wide config
	if _, err := os.Stat(SystemWideConfigFile); err == nil {
		if debugFlag {
			fmt.Printf("[essh debug] loading config file: %s\n", SystemWideConfigFile)
		}

		if err := CurrentRegistry.MkDirs(); err != nil {
			printError(err)
			return ExitErr

		}

		if err := L.DoFile(SystemWideConfigFile); err != nil {
			printError(err)
			return ExitErr
		}

		if debugFlag {
			fmt.Printf("[essh debug] loaded config file: %s\n", SystemWideConfigFile)
		}
	}

	// load per-user wide config
	if _, err := os.Stat(UserConfigFile); err == nil {
		if debugFlag {
			fmt.Printf("[essh debug] loading config file: %s\n", UserConfigFile)
		}

		if err := CurrentRegistry.MkDirs(); err != nil {
			printError(err)
			return ExitErr
		}

		if err := L.DoFile(UserConfigFile); err != nil {
			printError(err)
			return ExitErr
		}

		if debugFlag {
			fmt.Printf("[essh debug] loaded config file: %s\n", UserConfigFile)
		}
	}

	// load current dir config
	// change context to working dir context
	CurrentRegistry = NewRegistry(WorkingDataDir, RegistryTypeLocal)
	LocalRegistry = CurrentRegistry

	if _, err := os.Stat(WorkingDirConfigFile); err == nil {
		if debugFlag {
			fmt.Printf("[essh debug] loading config file: %s\n", WorkingDirConfigFile)
		}

		if err := CurrentRegistry.MkDirs(); err != nil {
			printError(err)
			return ExitErr
		}

		if err := L.DoFile(WorkingDirConfigFile); err != nil {
			printError(err)
			return ExitErr
		}

		if debugFlag {
			fmt.Printf("[essh debug] loaded config file: %s\n", WorkingDirConfigFile)
		}
	}

	// load override config
	if _, err := os.Stat(WorkingDirOverrideConfigFile); err == nil {
		if debugFlag {
			fmt.Printf("[essh debug] loading config file: %s\n", WorkingDirOverrideConfigFile)
		}

		if err := L.DoFile(WorkingDirOverrideConfigFile); err != nil {
			printError(err)
			return ExitErr
		}

		if debugFlag {
			fmt.Printf("[essh debug] loaded config file: %s\n", WorkingDirOverrideConfigFile)
		}
	}

	CurrentRegistry = GlobalRegistry
	// load override user config
	if _, err := os.Stat(UserOverrideConfigFile); err == nil {
		if debugFlag {
			fmt.Printf("[essh debug] loading config file: %s\n", UserOverrideConfigFile)
		}

		if err := CurrentRegistry.MkDirs(); err != nil {
			printError(err)
			return ExitErr
		}

		if err := L.DoFile(UserOverrideConfigFile); err != nil {
			printError(err)
			return ExitErr
		}

		if debugFlag {
			fmt.Printf("[essh debug] loaded config file: %s\n", UserOverrideConfigFile)
		}
	}

	// load override global config
	if _, err := os.Stat(SystemWideOverrideConfigFile); err == nil {
		if debugFlag {
			fmt.Printf("[essh debug] loading config file: %s\n", SystemWideOverrideConfigFile)
		}

		if err := CurrentRegistry.MkDirs(); err != nil {
			printError(err)
			return ExitErr
		}

		if err := L.DoFile(SystemWideOverrideConfigFile); err != nil {
			printError(err)
			return ExitErr
		}

		if debugFlag {
			fmt.Printf("[essh debug] loaded config file: %s\n", SystemWideOverrideConfigFile)
		}
	}

	// validate config
	if err := validateConfig(); err != nil {
		printError(err)
		return ExitErr
	}

	// show hosts for zsh completion
	if zshCompletionHostsFlag {
		for _, host := range SortedPublicHosts() {
			if !host.Hidden {
				fmt.Printf("%s\t%s\n", ColonEscape(host.Name), ColonEscape(host.DescriptionOrDefault()))
			}
		}

		return
	}

	// show tasks for zsh completion
	if zshCompletionTasksFlag {
		for _, task := range SortedTasks() {
			if !task.Disabled && !task.Hidden {
				fmt.Printf("%s\t%s\n", ColonEscape(task.Name), ColonEscape(task.DescriptionOrDefault()))
			}
		}
		return
	}

	if zshCompletionTagsFlag {
		for _, tag := range Tags() {
			fmt.Printf("%s\n", ColonEscape(tag))
		}
		return
	}

	// only print hosts list
	if hostsFlag {
		var hosts []*Host
		if len(selectVar) > 0 {
			hosts = HostsByNames(selectVar)
		} else {
			hosts = SortedHosts()
		}
		tb := helper.NewPlainTable(os.Stdout)
		if !quietFlag {
			if allFlag {
				tb.SetHeader([]string{"NAME", "DESCRIPTION", "TAGS", "REGISTRY", "SCOPE", "HIDDEN"})
			} else {
				tb.SetHeader([]string{"NAME", "DESCRIPTION", "TAGS", "REGISTRY"})
			}

		}
		for _, host := range hosts {
			if (!host.Hidden && !host.Private) || allFlag {
				if quietFlag {
					tb.Append([]string{host.Name})
				} else {
					if allFlag {
						hidden := "false"
						if host.Hidden {
							hidden = "true"
						}
						scope := "public"
						if host.Private {
							scope = "private"
						}
						tb.Append([]string{host.Name, host.Description, strings.Join(host.Tags, ","), host.Registry.TypeString(), scope, hidden})
					} else {
						tb.Append([]string{host.Name, host.Description, strings.Join(host.Tags, ","), host.Registry.TypeString()})
					}

				}
			}
		}
		tb.Render()

		return
	}

	// only print tags list
	if tagsFlag {
		tb := helper.NewPlainTable(os.Stdout)
		if !quietFlag {
			tb.SetHeader([]string{"NAME", "PUBLIC_HOSTS", "HOSTS"})
		}
		for _, tag := range Tags() {
			if quietFlag {
				tb.Append([]string{tag})
			} else {
				tb.Append([]string{tag, fmt.Sprintf("%d", len(HostsByTag(tag, true))), fmt.Sprintf("%d", len(HostsByTag(tag, false)))})
			}
		}
		tb.Render()

		return
	}

	// only print tasks list
	if tasksFlag {
		tb := helper.NewPlainTable(os.Stdout)
		if !quietFlag {
			if allFlag {
				tb.SetHeader([]string{"NAME", "DESCRIPTION", "REGISTRY", "DISABLED", "HIDDEN"})
			} else {
				tb.SetHeader([]string{"NAME", "DESCRIPTION", "REGISTRY"})
			}
		}

		for _, t := range SortedTasks() {
			if (!t.Hidden && !t.Disabled) || allFlag {
				if quietFlag {
					tb.Append([]string{t.Name})
				} else {
					if allFlag {
						tb.Append([]string{t.Name, t.Description, t.Registry.TypeString(), fmt.Sprintf("%v", t.Disabled), fmt.Sprintf("%v", t.Hidden)})
					} else {
						tb.Append([]string{t.Name, t.Description, t.Registry.TypeString()})
					}
				}
			}
		}
		tb.Render()

		return
	}

	outputConfig, ok := toString(lessh.RawGetString("ssh_config"))
	if !ok {
		printError(fmt.Errorf("invalid value %v in the 'ssh_config'", lessh.RawGetString("ssh_config")))
		return ExitErr
	}

	// generate ssh hosts config
	content, err := UpdateSSHConfig(outputConfig, SortedPublicHosts())
	if err != nil {
		printError(err)
		return ExitErr
	}

	// only print generated config
	if printFlag {
		fmt.Println(string(content))
		return
	}

	// only generating contents
	if genFlag {
		return
	}

	// select running mode and run it.
	if execFlag {
		if len(args) == 0 {
			printError("exec mode requires 1 parameter at latest.")
			return ExitErr
		}

		command := strings.Join(args, " ")

		// create temporary task
		task := NewTask()
		task.Name = "--exec"
		task.Pty = ptyFlag
		task.Parallel = parallelFlag
		task.Privileged = privilegedFlag
		task.Driver = driverVar
		if fileFlag {
			task.File = command
		} else {
			task.Script = []map[string]string{
				map[string]string{"code": command},
			}
		}
		if backendVar != "" {
			task.Backend = backendVar
		}
		task.Targets = targetVar

		if prefixFlag || prefixStringVar != "" {
			task.UsePrefix = true
		}

		if prefixStringVar != "" {
			task.Prefix = prefixStringVar
		}

		err := runTask(outputConfig, task)
		if err != nil {
			printError(err)
			return ExitErr
		}

		return
	} else {
		// try to get a task.
		if len(args) > 0 {
			taskName := args[0]
			task := GetEnabledTask(taskName)
			if task != nil {
				if len(args) >= 2 {
					printError("too many arguments.")
					return ExitErr
				} else {
					err := runTask(outputConfig, task)
					if err != nil {
						printError(err)
						return ExitErr
					}
					return
				}
			}
		}

		if updateFlag && len(args) == 0 {
			// run just "essh --update"
			return
		}

		// no argument
		if len(args) == 0 {
			printUsage()
			return
		}

		// run ssh command
		err, ex := runSSH(L, outputConfig, args)
		if err != nil {
			printError(err)
			return ExitErr
		}

		exitStatus = ex
	}

	return
}

func UpdateSSHConfig(outputConfig string, enabledHosts []*Host) ([]byte, error) {
	if debugFlag {
		fmt.Printf("[essh debug] output ssh_config contents to the file: %s \n", outputConfig)
	}

	// generate ssh hosts config
	content, err := GenHostsConfig(enabledHosts)
	if err != nil {
		return nil, err
	}

	// update temporary ssh config file
	err = ioutil.WriteFile(outputConfig, content, 0644)
	if err != nil {
		return nil, err
	}

	return content, nil
}

func runTask(config string, task *Task) error {
	if debugFlag {
		fmt.Printf("[essh debug] run task: %s\n", task.Name)
	}

	if task.Registry != nil {
		// change current registry
		CurrentRegistry = task.Registry
	}

	// re generate config (task).
	if task.Registry == nil {
		// this is "--exec" command mode. use only public hosts
		_, err := UpdateSSHConfig(config, SortedPublicHosts())
		if err != nil {
			return err
		}
	} else {
		_, err := UpdateSSHConfig(config, SameRegistryHosts(task.Registry.Type))
		if err != nil {
			return err
		}
	}

	if task.Prepare != nil {
		if debugFlag {
			fmt.Printf("[essh debug] run prepare function.\n")
		}

		err := task.Prepare()
		if err != nil {
			return err
		}
	}

	// get target hosts.
	if task.IsRemoteTask() {
		// run remotely.
		var hosts []*Host
		if task.Registry == nil {
			hosts = FindPublicHosts(task.TargetsSlice())
		} else {
			hosts = FindHostsInRegistry(task.TargetsSlice(), task.Registry.Type)
		}

		if len(hosts) == 0 {
			return fmt.Errorf("There are not hosts to run the command. you must specify the valid hosts.")
		}

		// handle stdin
		stdinChs := make([]chan ([]byte), len(hosts))
		for i, _ := range hosts {
			stdinChs[i] = make(chan []byte, 256)
		}
		go func() {
			processStdin(stdinChs)
		}()

		wg := &sync.WaitGroup{}
		m := new(sync.Mutex)
		for i, host := range hosts {
			if task.Parallel {
				wg.Add(1)
				go func(host *Host) {
					err := runRemoteTaskScript(config, task, host, hosts, stdinChs[i], m)
					if err != nil {
						fmt.Fprintf(os.Stderr, color.FgRB("essh error: %v\n", err))
						panic(err)
					}

					wg.Done()
				}(host)
			} else {
				err := runRemoteTaskScript(config, task, host, hosts, stdinChs[i], m)
				if err != nil {
					return err
				}
			}
		}
		wg.Wait()
	} else {
		// run locally.
		var hosts []*Host
		if task.Registry == nil {
			hosts = FindPublicHosts(task.TargetsSlice())
		} else {
			hosts = FindHostsInRegistry(task.TargetsSlice(), task.Registry.Type)
		}

		if len(task.Targets) >= 1 && len(hosts) == 0 {
			return fmt.Errorf("There are not hosts to run the command. you must specify the valid hosts.")
		}

		wg := &sync.WaitGroup{}
		m := new(sync.Mutex)

		if len(hosts) == 0 {
			// local no host task
			// This pattern should run just exec. should not use magic to pipe stdin to multi targets.
			err := runLocalTaskScript(config, task, nil, hosts, nil, m)
			if err != nil {
				return err
			}
			return nil
		}

		// handle stdin
		stdinChs := make([]chan ([]byte), len(hosts))
		for i, _ := range hosts {
			stdinChs[i] = make(chan []byte, 256)
		}
		go func() {
			processStdin(stdinChs)
		}()

		for i, host := range hosts {
			if task.Parallel {
				wg.Add(1)
				go func(host *Host) {
					err := runLocalTaskScript(config, task, host, hosts, stdinChs[i], m)
					if err != nil {
						fmt.Fprintf(os.Stderr, color.FgRB("essh error: %v\n", err))
						panic(err)
					}

					wg.Done()
				}(host)
			} else {
				err := runLocalTaskScript(config, task, host, hosts, stdinChs[i], m)
				if err != nil {
					return err
				}
			}
		}
		wg.Wait()
	}

	return nil
}

func runRemoteTaskScript(sshConfigPath string, task *Task, host *Host, hosts []*Host, stdinCh chan []byte, m *sync.Mutex) error {
	// setup ssh command args
	var sshCommandArgs []string
	if task.Pty {
		sshCommandArgs = []string{"-t", "-t", "-F", sshConfigPath, host.Name}
	} else {
		sshCommandArgs = []string{"-F", sshConfigPath, host.Name}
	}

	// generate commands by using driver
	driver := DefaultDriver
	if task.Driver != "" {
		driver = FindDriverInRegistry(task.Driver, task.Registry)
		if driver == nil {
			return fmt.Errorf("invalid driver name '%s'", task.Driver)
		}
	}
	if debugFlag {
		fmt.Printf("[essh debug] driver: %s \n", driver.Name)
	}

	var script string
	content, err := driver.GenerateRunnableContent(sshConfigPath, task, host)
	if err != nil {
		return err
	}
	script += content

	if task.Privileged {
		script = "sudo su - <<\\EOF-ESSH-PRIVILEGED\n" + script + "\n" + "EOF-ESSH-PRIVILEGED"
	}

	// inspired by https://github.com/laravel/envoy
	delimiter := "EOF-ESSH-SCRIPT"
	sshCommandArgs = append(sshCommandArgs, "bash", "-s", "<<\\"+delimiter+"\n"+script+"\n"+delimiter)

	cmd := exec.Command("ssh", sshCommandArgs[:]...)
	if debugFlag {
		fmt.Printf("[essh debug] real ssh command: %v \n", cmd.Args)
	}

	prefix := ""
	if task.UsePrefix {
		prefixTmp := task.Prefix
		if prefixTmp == "" {
			if task.IsRemoteTask() {
				prefixTmp = DefaultPrefixRemote
			} else {
				prefixTmp = DefaultPrefixLocal
			}
		}

		funcMap := template.FuncMap{
			"ShellEscape":         ShellEscape,
			"ToUpper":             strings.ToUpper,
			"ToLower":             strings.ToLower,
			"EnvKeyEscape":        EnvKeyEscape,
			"HostnameAlignString": HostnameAlignString(host, hosts),
		}

		dict := map[string]interface{}{
			"Host": host,
			"Task": task,
		}
		tmpl, err := template.New("T").Funcs(funcMap).Parse(prefixTmp)
		if err != nil {
			return err
		}
		var b bytes.Buffer
		err = tmpl.Execute(&b, dict)
		if err != nil {
			return err
		}

		prefix = b.String()
	}

	if stdinCh == nil {
		cmd.Stdin = os.Stdin
	} else {
		stdin, err := cmd.StdinPipe()
		if err != nil {
			return err
		}
		go handleInput(stdinCh, stdin)
	}

	if len(hosts) <= 1 && prefix == "" {
		cmd.Stdout = os.Stdout
	} else {
		stdout, err := cmd.StdoutPipe()
		if err != nil {
			return err
		}
		go scanLines(stdout, os.Stdout, prefix, m)
	}

	if len(hosts) <= 1 && prefix == "" {
		cmd.Stderr = os.Stderr
	} else {
		stderr, err := cmd.StderrPipe()
		if err != nil {
			return err
		}
		go scanLines(stderr, os.Stderr, prefix, m)
	}

	err = cmd.Start()
	if err != nil {
		return err
	}

	return cmd.Wait()
}

func runLocalTaskScript(sshConfigPath string, task *Task, host *Host, hosts []*Host, stdinCh chan []byte, m *sync.Mutex) error {
	var shell, flag string
	if runtime.GOOS == "windows" {
		shell = "cmd"
		flag = "/C"
	} else {
		shell = "/bin/sh"
		flag = "-c"
	}

	// generate commands by using driver
	driver := DefaultDriver
	if task.Driver != "" {
		driver = FindDriverInRegistry(task.Driver, task.Registry)
		if driver == nil {
			return fmt.Errorf("invalid driver name '%s'", task.Driver)
		}
	}
	if debugFlag {
		fmt.Printf("[essh debug] driver: %s \n", driver.Name)
	}

	var script string
	content, err := driver.GenerateRunnableContent(sshConfigPath, task, host)
	if err != nil {
		return err
	}
	script += content

	if task.Privileged {
		script = "cd " + WorkingDir + "\n" + script
		script = "sudo su - <<\\EOF-ESSH-PRIVILEGED\n" + script + "\n" + "EOF-ESSH-PRIVILEGED"
	}

	cmd := exec.Command(shell, flag, script)
	if debugFlag {
		fmt.Printf("[essh debug] real local command: %v \n", cmd.Args)
	}

	prefix := ""
	if host == nil && task.UsePrefix {
		// simple local task (does not specify the hosts)<commit_msg>try to change privileged behavior.<commit_after>package essh

import (
	"bufio"
	"bytes"
	"fmt"
	"github.com/Songmu/wrapcommander"
	fatihColor "github.com/fatih/color"
	"github.com/kardianos/osext"
	"github.com/kohkimakimoto/essh/support/color"
	"github.com/kohkimakimoto/essh/support/helper"
	"github.com/yuin/gopher-lua"
	"io"
	"io/ioutil"
	"os"
	"os/exec"
	"path/filepath"
	"runtime"
	"strings"
	"sync"
	"text/template"
)

// system configurations.
var (
	SystemWideConfigFile         string
	SystemWideOverrideConfigFile string
	UserConfigFile               string
	UserOverrideConfigFile       string
	UserDataDir                  string
	WorkingDirConfigFile         string
	WorkingDirOverrideConfigFile string
	WorkingDataDir               string
	WorkingDir                   string
	Executable                   string
)

// flags
var (
	versionFlag            bool
	helpFlag               bool
	printFlag              bool
	colorFlag              bool
	noColorFlag            bool
	debugFlag              bool
	hostsFlag              bool
	quietFlag              bool
	allFlag                bool
	tagsFlag               bool
	tasksFlag              bool
	genFlag                bool
	updateFlag             bool
	withGlobalFlag         bool
	cleanAllFlag           bool
	cleanModulesFlag       bool
	cleanTmpFlag           bool
	zshCompletionModeFlag  bool
	zshCompletionFlag      bool
	zshCompletionHostsFlag bool
	zshCompletionTagsFlag  bool
	zshCompletionTasksFlag bool
	bashCompletionFlag     bool
	aliasesFlag            bool
	execFlag               bool
	fileFlag               bool
	prefixFlag             bool
	parallelFlag           bool
	privilegedFlag         bool
	ptyFlag                bool
	workindDirVar          string
	configVar              string
	selectVar              []string = []string{}
	targetVar              []string = []string{}
	backendVar             string
	prefixStringVar        string
	driverVar              string
)

const (
	ExitErr = 1
)

func Start() (exitStatus int) {
	defer func() {
		if e := recover(); e != nil {
			exitStatus = ExitErr
			if zshCompletionModeFlag && !debugFlag {
				// suppress printing error in running completion code.
				return
			}

			printError(e)
		}
	}()

	if os.Getenv("ESSH_DEBUG") != "" {
		debugFlag = true
	}

	if len(os.Args) == 1 {
		printUsage()
		return
	}

	osArgs := os.Args[1:]
	args := []string{}
	doesNotParseOption := false

	// parsing options
	// Essh uses only double dash options like `--print`,
	// because of preventing conflict to the ssh options.
	for {
		if len(osArgs) == 0 {
			break
		}

		arg := osArgs[0]

		if doesNotParseOption {
			// restructure args to remove essh options.
			args = append(args, arg)
		} else if arg == "--print" {
			printFlag = true
		} else if arg == "--version" {
			versionFlag = true
		} else if arg == "--help" {
			helpFlag = true
		} else if arg == "--color" {
			colorFlag = true
		} else if arg == "--no-color" {
			noColorFlag = true
		} else if arg == "--debug" {
			debugFlag = true
		} else if arg == "--hosts" {
			hostsFlag = true
		} else if arg == "--quiet" {
			quietFlag = true
		} else if arg == "--all" {
			allFlag = true
		} else if arg == "--tasks" {
			tasksFlag = true
		} else if arg == "--select" {
			if len(osArgs) < 2 {
				printError("--select reguires an argument.")
				return ExitErr
			}
			selectVar = append(selectVar, osArgs[1])
			osArgs = osArgs[1:]
		} else if strings.HasPrefix(arg, "--select=") {
			selectVar = append(selectVar, strings.Split(arg, "=")[1])
		} else if arg == "--tags" {
			tagsFlag = true
		} else if arg == "--gen" {
			genFlag = true
		} else if arg == "--update" {
			updateFlag = true
		} else if arg == "--clean-modules" {
			cleanModulesFlag = true
		} else if arg == "--clean-tmp" {
			cleanTmpFlag = true
		} else if arg == "--clean-all" {
			cleanAllFlag = true
		} else if arg == "--with-global" {
			withGlobalFlag = true
		} else if arg == "--zsh-completion" {
			zshCompletionFlag = true
			zshCompletionModeFlag = true
		} else if arg == "--zsh-completion-hosts" {
			zshCompletionHostsFlag = true
			zshCompletionModeFlag = true
		} else if arg == "--zsh-completion-tags" {
			zshCompletionTagsFlag = true
			zshCompletionModeFlag = true
		} else if arg == "--zsh-completion-tasks" {
			zshCompletionTasksFlag = true
			zshCompletionModeFlag = true
		} else if arg == "--bash-completion" {
			// TODO
			bashCompletionFlag = true
		} else if arg == "--aliases" {
			aliasesFlag = true
		} else if arg == "--working-dir" {
			if len(osArgs) < 2 {
				printError("--working-dir reguires an argument.")
				return ExitErr
			}
			workindDirVar = osArgs[1]
			osArgs = osArgs[1:]
		} else if strings.HasPrefix(arg, "--working-dir=") {
			workindDirVar = strings.Split(arg, "=")[1]
		} else if arg == "--config" {
			if len(osArgs) < 2 {
				printError("--config reguires an argument.")
				return ExitErr
			}
			configVar = osArgs[1]
			osArgs = osArgs[1:]
		} else if strings.HasPrefix(arg, "--config=") {
			configVar = strings.Split(arg, "=")[1]
		} else if arg == "--exec" {
			execFlag = true
		} else if arg == "--privileged" {
			privilegedFlag = true
		} else if arg == "--parallel" {
			parallelFlag = true
		} else if arg == "--prefix" {
			prefixFlag = true
		} else if arg == "--prefix-string" {
			if len(osArgs) < 2 {
				printError("--prefix-string reguires an argument.")
				return ExitErr
			}
			prefixStringVar = osArgs[1]
			osArgs = osArgs[1:]
		} else if strings.HasPrefix(arg, "--prefix-string=") {
			prefixStringVar = strings.Split(arg, "=")[1]
		} else if arg == "--driver" {
			if len(osArgs) < 2 {
				printError("--driver reguires an argument.")
				return ExitErr
			}
			driverVar = osArgs[1]
			osArgs = osArgs[1:]
		} else if strings.HasPrefix(arg, "--driver=") {
			driverVar = strings.Split(arg, "=")[1]
		} else if arg == "--target" {
			if len(osArgs) < 2 {
				printError("--target reguires an argument.")
				return ExitErr
			}
			targetVar = append(targetVar, osArgs[1])
			osArgs = osArgs[1:]
		} else if strings.HasPrefix(arg, "--target=") {
			targetVar = append(targetVar, strings.Split(arg, "=")[1])
		} else if arg == "--backend" {
			if len(osArgs) < 2 {
				printError("--backend reguires an argument.")
				return ExitErr
			}
			backendVar = osArgs[1]
			osArgs = osArgs[1:]
		} else if strings.HasPrefix(arg, "--backend=") {
			backendVar = strings.Split(arg, "=")[1]
		} else if arg == "--file" {
			fileFlag = true
		} else if arg == "--pty" {
			ptyFlag = true
		} else if arg == "--" {
			doesNotParseOption = true
			// to behave same ssh. pass the `--` to the ssh.
			args = append(args, arg)
		} else {
			// restructure args to remove essh options.
			args = append(args, arg)
		}

		osArgs = osArgs[1:]
	}

	if colorFlag {
		fatihColor.NoColor = false
	}

	if noColorFlag {
		fatihColor.NoColor = true
	}

	if os.Getenv("ESSH_DEBUG") != "" {
		debugFlag = true
	}

	if workindDirVar != "" {
		err := os.Chdir(workindDirVar)
		if err != nil {
			printError(err)
			return ExitErr
		}
	}

	// decide the wokingDirConfigFile
	wd, err := os.Getwd()
	if err != nil {
		printError(fmt.Errorf("couldn't get working dir %v\n", err))
		return ExitErr
	}

	WorkingDir = wd
	WorkingDataDir = filepath.Join(wd, ".essh")
	WorkingDirConfigFile = filepath.Join(wd, "esshconfig.lua")

	workingDirConfigFileBasename := filepath.Base(WorkingDirConfigFile)
	workingDirConfigFileDir := filepath.Dir(WorkingDirConfigFile)
	workingDirConfigFileBasenameExtension := filepath.Ext(workingDirConfigFileBasename)
	workingDirConfigFileName := workingDirConfigFileBasename[0 : len(workingDirConfigFileBasename)-len(workingDirConfigFileBasenameExtension)]

	WorkingDirOverrideConfigFile = filepath.Join(workingDirConfigFileDir, workingDirConfigFileName+"_override"+workingDirConfigFileBasenameExtension)

	// overwrite config file path by --config option.
	if configVar != "" {
		if filepath.IsAbs(configVar) {
			WorkingDirConfigFile = configVar
		} else {
			WorkingDirConfigFile = filepath.Join(wd, configVar)
		}
	}

	if helpFlag {
		printUsage()
		return
	}

	if cleanAllFlag || cleanModulesFlag || cleanTmpFlag {
		err := removeRegistryData()
		if err != nil {
			printError(err)
			return ExitErr
		}
		return
	}

	if versionFlag {
		fmt.Printf("%s (%s)\n", Version, CommitHash)
		return
	}

	if zshCompletionFlag {
		s, err := sprintByTemplate(ZSH_COMPLETION)
		if err != nil {
			printError(err)
			return ExitErr
		}

		fmt.Print(s)
		return
	}

	if aliasesFlag {
		s, err := sprintByTemplate(ALIASES_CODE)
		if err != nil {
			printError(err)
			return ExitErr
		}

		fmt.Print(s)
		return
	}

	if bashCompletionFlag {
		fmt.Print(BASH_COMPLETION)
		return
	}

	// set up the lua state.
	L := lua.NewState()
	defer L.Close()
	InitLuaState(L)

	if debugFlag {
		fmt.Printf("[essh debug] init lua state\n")
	}

	// generate temporary ssh config file
	tmpFile, err := ioutil.TempFile("", "essh.ssh_config.")
	if err != nil {
		printError(err)
		return ExitErr
	}
	defer func() {
		tmpFile.Close()
		os.Remove(tmpFile.Name())

		if debugFlag {
			fmt.Printf("[essh debug] deleted config file: %s \n", tmpFile.Name())
		}
	}()
	temporarySSHConfigFile := tmpFile.Name()

	if debugFlag {
		fmt.Printf("[essh debug] generated config file: %s \n", temporarySSHConfigFile)
	}

	lessh, ok := toLTable(L.GetGlobal("essh"))
	if !ok {
		printError(fmt.Errorf("essh must be a table"))
		return ExitErr
	}

	// set temporary ssh config file path
	lessh.RawSetString("ssh_config", lua.LString(temporarySSHConfigFile))

	// user context
	CurrentRegistry = NewRegistry(UserDataDir, RegistryTypeGlobal)
	GlobalRegistry = CurrentRegistry

	if err := CurrentRegistry.MkDirs(); err != nil {
		printError(err)
		return ExitErr
	}

	// load system wide config
	if _, err := os.Stat(SystemWideConfigFile); err == nil {
		if debugFlag {
			fmt.Printf("[essh debug] loading config file: %s\n", SystemWideConfigFile)
		}

		if err := CurrentRegistry.MkDirs(); err != nil {
			printError(err)
			return ExitErr

		}

		if err := L.DoFile(SystemWideConfigFile); err != nil {
			printError(err)
			return ExitErr
		}

		if debugFlag {
			fmt.Printf("[essh debug] loaded config file: %s\n", SystemWideConfigFile)
		}
	}

	// load per-user wide config
	if _, err := os.Stat(UserConfigFile); err == nil {
		if debugFlag {
			fmt.Printf("[essh debug] loading config file: %s\n", UserConfigFile)
		}

		if err := CurrentRegistry.MkDirs(); err != nil {
			printError(err)
			return ExitErr
		}

		if err := L.DoFile(UserConfigFile); err != nil {
			printError(err)
			return ExitErr
		}

		if debugFlag {
			fmt.Printf("[essh debug] loaded config file: %s\n", UserConfigFile)
		}
	}

	// load current dir config
	// change context to working dir context
	CurrentRegistry = NewRegistry(WorkingDataDir, RegistryTypeLocal)
	LocalRegistry = CurrentRegistry

	if _, err := os.Stat(WorkingDirConfigFile); err == nil {
		if debugFlag {
			fmt.Printf("[essh debug] loading config file: %s\n", WorkingDirConfigFile)
		}

		if err := CurrentRegistry.MkDirs(); err != nil {
			printError(err)
			return ExitErr
		}

		if err := L.DoFile(WorkingDirConfigFile); err != nil {
			printError(err)
			return ExitErr
		}

		if debugFlag {
			fmt.Printf("[essh debug] loaded config file: %s\n", WorkingDirConfigFile)
		}
	}

	// load override config
	if _, err := os.Stat(WorkingDirOverrideConfigFile); err == nil {
		if debugFlag {
			fmt.Printf("[essh debug] loading config file: %s\n", WorkingDirOverrideConfigFile)
		}

		if err := L.DoFile(WorkingDirOverrideConfigFile); err != nil {
			printError(err)
			return ExitErr
		}

		if debugFlag {
			fmt.Printf("[essh debug] loaded config file: %s\n", WorkingDirOverrideConfigFile)
		}
	}

	CurrentRegistry = GlobalRegistry
	// load override user config
	if _, err := os.Stat(UserOverrideConfigFile); err == nil {
		if debugFlag {
			fmt.Printf("[essh debug] loading config file: %s\n", UserOverrideConfigFile)
		}

		if err := CurrentRegistry.MkDirs(); err != nil {
			printError(err)
			return ExitErr
		}

		if err := L.DoFile(UserOverrideConfigFile); err != nil {
			printError(err)
			return ExitErr
		}

		if debugFlag {
			fmt.Printf("[essh debug] loaded config file: %s\n", UserOverrideConfigFile)
		}
	}

	// load override global config
	if _, err := os.Stat(SystemWideOverrideConfigFile); err == nil {
		if debugFlag {
			fmt.Printf("[essh debug] loading config file: %s\n", SystemWideOverrideConfigFile)
		}

		if err := CurrentRegistry.MkDirs(); err != nil {
			printError(err)
			return ExitErr
		}

		if err := L.DoFile(SystemWideOverrideConfigFile); err != nil {
			printError(err)
			return ExitErr
		}

		if debugFlag {
			fmt.Printf("[essh debug] loaded config file: %s\n", SystemWideOverrideConfigFile)
		}
	}

	// validate config
	if err := validateConfig(); err != nil {
		printError(err)
		return ExitErr
	}

	// show hosts for zsh completion
	if zshCompletionHostsFlag {
		for _, host := range SortedPublicHosts() {
			if !host.Hidden {
				fmt.Printf("%s\t%s\n", ColonEscape(host.Name), ColonEscape(host.DescriptionOrDefault()))
			}
		}

		return
	}

	// show tasks for zsh completion
	if zshCompletionTasksFlag {
		for _, task := range SortedTasks() {
			if !task.Disabled && !task.Hidden {
				fmt.Printf("%s\t%s\n", ColonEscape(task.Name), ColonEscape(task.DescriptionOrDefault()))
			}
		}
		return
	}

	if zshCompletionTagsFlag {
		for _, tag := range Tags() {
			fmt.Printf("%s\n", ColonEscape(tag))
		}
		return
	}

	// only print hosts list
	if hostsFlag {
		var hosts []*Host
		if len(selectVar) > 0 {
			hosts = HostsByNames(selectVar)
		} else {
			hosts = SortedHosts()
		}
		tb := helper.NewPlainTable(os.Stdout)
		if !quietFlag {
			if allFlag {
				tb.SetHeader([]string{"NAME", "DESCRIPTION", "TAGS", "REGISTRY", "SCOPE", "HIDDEN"})
			} else {
				tb.SetHeader([]string{"NAME", "DESCRIPTION", "TAGS", "REGISTRY"})
			}

		}
		for _, host := range hosts {
			if (!host.Hidden && !host.Private) || allFlag {
				if quietFlag {
					tb.Append([]string{host.Name})
				} else {
					if allFlag {
						hidden := "false"
						if host.Hidden {
							hidden = "true"
						}
						scope := "public"
						if host.Private {
							scope = "private"
						}
						tb.Append([]string{host.Name, host.Description, strings.Join(host.Tags, ","), host.Registry.TypeString(), scope, hidden})
					} else {
						tb.Append([]string{host.Name, host.Description, strings.Join(host.Tags, ","), host.Registry.TypeString()})
					}

				}
			}
		}
		tb.Render()

		return
	}

	// only print tags list
	if tagsFlag {
		tb := helper.NewPlainTable(os.Stdout)
		if !quietFlag {
			tb.SetHeader([]string{"NAME", "PUBLIC_HOSTS", "HOSTS"})
		}
		for _, tag := range Tags() {
			if quietFlag {
				tb.Append([]string{tag})
			} else {
				tb.Append([]string{tag, fmt.Sprintf("%d", len(HostsByTag(tag, true))), fmt.Sprintf("%d", len(HostsByTag(tag, false)))})
			}
		}
		tb.Render()

		return
	}

	// only print tasks list
	if tasksFlag {
		tb := helper.NewPlainTable(os.Stdout)
		if !quietFlag {
			if allFlag {
				tb.SetHeader([]string{"NAME", "DESCRIPTION", "REGISTRY", "DISABLED", "HIDDEN"})
			} else {
				tb.SetHeader([]string{"NAME", "DESCRIPTION", "REGISTRY"})
			}
		}

		for _, t := range SortedTasks() {
			if (!t.Hidden && !t.Disabled) || allFlag {
				if quietFlag {
					tb.Append([]string{t.Name})
				} else {
					if allFlag {
						tb.Append([]string{t.Name, t.Description, t.Registry.TypeString(), fmt.Sprintf("%v", t.Disabled), fmt.Sprintf("%v", t.Hidden)})
					} else {
						tb.Append([]string{t.Name, t.Description, t.Registry.TypeString()})
					}
				}
			}
		}
		tb.Render()

		return
	}

	outputConfig, ok := toString(lessh.RawGetString("ssh_config"))
	if !ok {
		printError(fmt.Errorf("invalid value %v in the 'ssh_config'", lessh.RawGetString("ssh_config")))
		return ExitErr
	}

	// generate ssh hosts config
	content, err := UpdateSSHConfig(outputConfig, SortedPublicHosts())
	if err != nil {
		printError(err)
		return ExitErr
	}

	// only print generated config
	if printFlag {
		fmt.Println(string(content))
		return
	}

	// only generating contents
	if genFlag {
		return
	}

	// select running mode and run it.
	if execFlag {
		if len(args) == 0 {
			printError("exec mode requires 1 parameter at latest.")
			return ExitErr
		}

		command := strings.Join(args, " ")

		// create temporary task
		task := NewTask()
		task.Name = "--exec"
		task.Pty = ptyFlag
		task.Parallel = parallelFlag
		task.Privileged = privilegedFlag
		task.Driver = driverVar
		if fileFlag {
			task.File = command
		} else {
			task.Script = []map[string]string{
				map[string]string{"code": command},
			}
		}
		if backendVar != "" {
			task.Backend = backendVar
		}
		task.Targets = targetVar

		if prefixFlag || prefixStringVar != "" {
			task.UsePrefix = true
		}

		if prefixStringVar != "" {
			task.Prefix = prefixStringVar
		}

		err := runTask(outputConfig, task)
		if err != nil {
			printError(err)
			return ExitErr
		}

		return
	} else {
		// try to get a task.
		if len(args) > 0 {
			taskName := args[0]
			task := GetEnabledTask(taskName)
			if task != nil {
				if len(args) >= 2 {
					printError("too many arguments.")
					return ExitErr
				} else {
					err := runTask(outputConfig, task)
					if err != nil {
						printError(err)
						return ExitErr
					}
					return
				}
			}
		}

		if updateFlag && len(args) == 0 {
			// run just "essh --update"
			return
		}

		// no argument
		if len(args) == 0 {
			printUsage()
			return
		}

		// run ssh command
		err, ex := runSSH(L, outputConfig, args)
		if err != nil {
			printError(err)
			return ExitErr
		}

		exitStatus = ex
	}

	return
}

func UpdateSSHConfig(outputConfig string, enabledHosts []*Host) ([]byte, error) {
	if debugFlag {
		fmt.Printf("[essh debug] output ssh_config contents to the file: %s \n", outputConfig)
	}

	// generate ssh hosts config
	content, err := GenHostsConfig(enabledHosts)
	if err != nil {
		return nil, err
	}

	// update temporary ssh config file
	err = ioutil.WriteFile(outputConfig, content, 0644)
	if err != nil {
		return nil, err
	}

	return content, nil
}

func runTask(config string, task *Task) error {
	if debugFlag {
		fmt.Printf("[essh debug] run task: %s\n", task.Name)
	}

	if task.Registry != nil {
		// change current registry
		CurrentRegistry = task.Registry
	}

	// re generate config (task).
	if task.Registry == nil {
		// this is "--exec" command mode. use only public hosts
		_, err := UpdateSSHConfig(config, SortedPublicHosts())
		if err != nil {
			return err
		}
	} else {
		_, err := UpdateSSHConfig(config, SameRegistryHosts(task.Registry.Type))
		if err != nil {
			return err
		}
	}

	if task.Prepare != nil {
		if debugFlag {
			fmt.Printf("[essh debug] run prepare function.\n")
		}

		err := task.Prepare()
		if err != nil {
			return err
		}
	}

	// get target hosts.
	if task.IsRemoteTask() {
		// run remotely.
		var hosts []*Host
		if task.Registry == nil {
			hosts = FindPublicHosts(task.TargetsSlice())
		} else {
			hosts = FindHostsInRegistry(task.TargetsSlice(), task.Registry.Type)
		}

		if len(hosts) == 0 {
			return fmt.Errorf("There are not hosts to run the command. you must specify the valid hosts.")
		}

		// handle stdin
		stdinChs := make([]chan ([]byte), len(hosts))
		for i, _ := range hosts {
			stdinChs[i] = make(chan []byte, 256)
		}
		go func() {
			processStdin(stdinChs)
		}()

		wg := &sync.WaitGroup{}
		m := new(sync.Mutex)
		for i, host := range hosts {
			if task.Parallel {
				wg.Add(1)
				go func(host *Host) {
					err := runRemoteTaskScript(config, task, host, hosts, stdinChs[i], m)
					if err != nil {
						fmt.Fprintf(os.Stderr, color.FgRB("essh error: %v\n", err))
						panic(err)
					}

					wg.Done()
				}(host)
			} else {
				err := runRemoteTaskScript(config, task, host, hosts, stdinChs[i], m)
				if err != nil {
					return err
				}
			}
		}
		wg.Wait()
	} else {
		// run locally.
		var hosts []*Host
		if task.Registry == nil {
			hosts = FindPublicHosts(task.TargetsSlice())
		} else {
			hosts = FindHostsInRegistry(task.TargetsSlice(), task.Registry.Type)
		}

		if len(task.Targets) >= 1 && len(hosts) == 0 {
			return fmt.Errorf("There are not hosts to run the command. you must specify the valid hosts.")
		}

		wg := &sync.WaitGroup{}
		m := new(sync.Mutex)

		if len(hosts) == 0 {
			// local no host task
			// This pattern should run just exec. should not use magic to pipe stdin to multi targets.
			err := runLocalTaskScript(config, task, nil, hosts, nil, m)
			if err != nil {
				return err
			}
			return nil
		}

		// handle stdin
		stdinChs := make([]chan ([]byte), len(hosts))
		for i, _ := range hosts {
			stdinChs[i] = make(chan []byte, 256)
		}
		go func() {
			processStdin(stdinChs)
		}()

		for i, host := range hosts {
			if task.Parallel {
				wg.Add(1)
				go func(host *Host) {
					err := runLocalTaskScript(config, task, host, hosts, stdinChs[i], m)
					if err != nil {
						fmt.Fprintf(os.Stderr, color.FgRB("essh error: %v\n", err))
						panic(err)
					}

					wg.Done()
				}(host)
			} else {
				err := runLocalTaskScript(config, task, host, hosts, stdinChs[i], m)
				if err != nil {
					return err
				}
			}
		}
		wg.Wait()
	}

	return nil
}

func runRemoteTaskScript(sshConfigPath string, task *Task, host *Host, hosts []*Host, stdinCh chan []byte, m *sync.Mutex) error {
	// setup ssh command args
	var sshCommandArgs []string
	if task.Pty {
		sshCommandArgs = []string{"-t", "-t", "-F", sshConfigPath, host.Name}
	} else {
		sshCommandArgs = []string{"-F", sshConfigPath, host.Name}
	}

	// generate commands by using driver
	driver := DefaultDriver
	if task.Driver != "" {
		driver = FindDriverInRegistry(task.Driver, task.Registry)
		if driver == nil {
			return fmt.Errorf("invalid driver name '%s'", task.Driver)
		}
	}
	if debugFlag {
		fmt.Printf("[essh debug] driver: %s \n", driver.Name)
	}

	var script string
	content, err := driver.GenerateRunnableContent(sshConfigPath, task, host)
	if err != nil {
		return err
	}
	script += content

	if task.Privileged {
		script = "sudo su <<\\EOF-ESSH-PRIVILEGED\n" + script + "\n" + "EOF-ESSH-PRIVILEGED"
	}

	// inspired by https://github.com/laravel/envoy
	delimiter := "EOF-ESSH-SCRIPT"
	sshCommandArgs = append(sshCommandArgs, "bash", "-s", "<<\\"+delimiter+"\n"+script+"\n"+delimiter)

	cmd := exec.Command("ssh", sshCommandArgs[:]...)
	if debugFlag {
		fmt.Printf("[essh debug] real ssh command: %v \n", cmd.Args)
	}

	prefix := ""
	if task.UsePrefix {
		prefixTmp := task.Prefix
		if prefixTmp == "" {
			if task.IsRemoteTask() {
				prefixTmp = DefaultPrefixRemote
			} else {
				prefixTmp = DefaultPrefixLocal
			}
		}

		funcMap := template.FuncMap{
			"ShellEscape":         ShellEscape,
			"ToUpper":             strings.ToUpper,
			"ToLower":             strings.ToLower,
			"EnvKeyEscape":        EnvKeyEscape,
			"HostnameAlignString": HostnameAlignString(host, hosts),
		}

		dict := map[string]interface{}{
			"Host": host,
			"Task": task,
		}
		tmpl, err := template.New("T").Funcs(funcMap).Parse(prefixTmp)
		if err != nil {
			return err
		}
		var b bytes.Buffer
		err = tmpl.Execute(&b, dict)
		if err != nil {
			return err
		}

		prefix = b.String()
	}

	if stdinCh == nil {
		cmd.Stdin = os.Stdin
	} else {
		stdin, err := cmd.StdinPipe()
		if err != nil {
			return err
		}
		go handleInput(stdinCh, stdin)
	}

	if len(hosts) <= 1 && prefix == "" {
		cmd.Stdout = os.Stdout
	} else {
		stdout, err := cmd.StdoutPipe()
		if err != nil {
			return err
		}
		go scanLines(stdout, os.Stdout, prefix, m)
	}

	if len(hosts) <= 1 && prefix == "" {
		cmd.Stderr = os.Stderr
	} else {
		stderr, err := cmd.StderrPipe()
		if err != nil {
			return err
		}
		go scanLines(stderr, os.Stderr, prefix, m)
	}

	err = cmd.Start()
	if err != nil {
		return err
	}

	return cmd.Wait()
}

func runLocalTaskScript(sshConfigPath string, task *Task, host *Host, hosts []*Host, stdinCh chan []byte, m *sync.Mutex) error {
	var shell, flag string
	if runtime.GOOS == "windows" {
		shell = "cmd"
		flag = "/C"
	} else {
		shell = "/bin/sh"
		flag = "-c"
	}

	// generate commands by using driver
	driver := DefaultDriver
	if task.Driver != "" {
		driver = FindDriverInRegistry(task.Driver, task.Registry)
		if driver == nil {
			return fmt.Errorf("invalid driver name '%s'", task.Driver)
		}
	}
	if debugFlag {
		fmt.Printf("[essh debug] driver: %s \n", driver.Name)
	}

	var script string
	content, err := driver.GenerateRunnableContent(sshConfigPath, task, host)
	if err != nil {
		return err
	}
	script += content

	if task.Privileged {
		script = "cd " + WorkingDir + "\n" + script
		script = "sudo su <<\\EOF-ESSH-PRIVILEGED\n" + script + "\n" + "EOF-ESSH-PRIVILEGED"
	}

	cmd := exec.Command(shell, flag, script)
	if debugFlag {
		fmt.Printf("[essh debug] real local command: %v \n", cmd.Args)
	}

	prefix := ""
	if host == nil && task.UsePrefix {
		// simple local task (does not specify the hosts)
{'fraction_non_alphanumeric': 0.13752876438219108, 'fraction_numerical': 0.002441220610305153, 'mean_word_length': 2.8765125659323614, 'pattern_counts': {'":': 16, '<': 33, '<?xml version=': 0, '>': 11, 'https://': 2, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '7166618', 'n_tokens_mistral': 19852, 'n_tokens_neox': 17123, 'n_words': 4335}