func expandVarsIn(envs map[string]string, command string) string {
	expanded := command
	for k, v := range envs {
		if strings.HasPrefix(v, "$") {
			if _, ok := envs[v]; ok {
				log.Printf("Warning, skipping non-expandable environment var %s=%v\n", k, v)
				continue
			}
		}
		varName := "$" + k
		expanded = strings.Replace(expanded, varName, v, -1)
	}
	return expanded
}
expandVarsIn returns a command with all occurrences of environment variables replaced by known values.
package main
import (
	"bytes"
	"errors"
	"fmt"
	"io/ioutil"
	"log"
	"os"
	"os/exec"
	"path"
	"path/filepath"
	"sort"
	"strconv"
	"strings"
	"time"
	"text/template"
	"github.com/antonmedv/expr"
	"gopkg.in/yaml.v2"
)
type Migration struct {
	Filename     string   `yaml:"-"`
	Description  string   `yaml:"-"`
	IfExpression string   `yaml:"if"`
	DoSection    []string `yaml:"do"`
	UndoSection  []string `yaml:"undo"`
	ViewSection  []string `yaml:"view"`
}
func evaluateCondition(ifExpression string, envs []string) (bool, error) {
	if len(ifExpression) == 0 {
		return true, nil
	}
	envMap := map[string]string{}
	for _, each := range envs {
		kv := strings.Split(each, "=")
		if len(kv) != 2 {
			continue
		}
		k := strings.TrimSpace(kv[0])
		v := strings.TrimSpace(kv[1])
		envMap[k] = v
	}
	program, err := expr.Compile(ifExpression, expr.Env(envMap))
	if err != nil {
		return false, err
	}
	output, err := expr.Run(program, envMap)
	if err != nil {
		return false, err
	}
	if b, ok := output.(bool); ok {
		return b, nil
	}
	return false, errors.New("expression does not evaluate to a boolean")
}
var timeNow = time.Now
func NewFilenameWithIndex(desc string) string {
	all, err := LoadMigrationsBetweenAnd(".", "", "")
	if err != nil {
		printError(err.Error())
		return ""
	}
	sanitized := strings.Replace(strings.ToLower(desc), " ", "_", -1)
	if len(all) == 0 {
		return fmt.Sprintf("010_%s.yaml", sanitized)
	}
	lastFilename := all[len(all)-1].Filename
	hasTimestamp := regexpTimestamp.MatchString(lastFilename)
	hasIndex := regexpIndex.MatchString(lastFilename)
	if hasIndex {
		i, err := strconv.Atoi(lastFilename[:3])
		if err != nil {
			fmt.Printf("%T, %v", i, i)
		}
		return fmt.Sprintf("%03d_%s.yaml", i+5, sanitized)
	}
	if hasTimestamp {
		return fmt.Sprintf("300_%s.yaml", sanitized)
	}
	return fmt.Sprintf("010_%s.yaml", sanitized)
}
func LoadMigration(absFilename string) (m Migration, err error) {
	data, err := ioutil.ReadFile(absFilename)
	if err != nil {
		wd, _ := os.Getwd()
		return m, fmt.Errorf("in %s, %s reading failed: %v", wd, absFilename, err)
	}
	m.Filename = filepath.Base(absFilename)
	err = yaml.Unmarshal(data, &m)
	if err != nil {
		err = fmt.Errorf("%s parsing failed: %v", absFilename, err)
	}
	return
}
func (m Migration) ToYAML() ([]byte, error) {
	out := new(bytes.Buffer)
	err := migrationTemplate.Execute(out, m)
	return out.Bytes(), err
}
func ExecuteAll(ifExpression string, commands []string, envs []string, verbose bool) error {
	pass, err := evaluateCondition(ifExpression, envs)
	if err != nil {
		log.Printf("unable to evaluate condition [%s] because:%v\n", ifExpression, err)
		return errAbort
	}
	if !pass {
		log.Printf(".. skipping ... (%d) commands because %s is false.\n", len(commands), ifExpression)
		return nil
	}
	if len(commands) == 0 {
		return nil
	}
	tempScript := path.Join(os.TempDir(), "gmig.sh")
	content := new(bytes.Buffer)
	fmt.Fprintln(content, setupShellScript(verbose))
	for _, each := range commands {
		fmt.Fprintln(content, each)
	}
	if err := ioutil.WriteFile(tempScript, content.Bytes(), os.ModePerm); err != nil {
		return fmt.Errorf("failed to write temporary migration section:%v", err)
	}
	if verbose {
		log.Println("--- BEGIN gmig.sh:\n", content.String(), "--- END gmig.sh")
	}
	defer func() {
		if err := os.Remove(tempScript); err != nil {
			log.Printf("warning: failed to remove temporary migration execution script:%s\n", tempScript)
		}
	}()
	cmd := exec.Command("sh", "-c", tempScript)
	cmd.Env = append(os.Environ(), envs...)  
	if out, err := runCommand(cmd); err != nil {
		return fmt.Errorf("failed to run migration section:\n%s\nerror:%v", string(out), err)
	} else {
		fmt.Println(string(out))
	}
	return nil
}
func LogAll(ifExpression string, commands []string, envs []string, verbose bool) error {
	pass, err := evaluateCondition(ifExpression, envs)
	if err != nil {
		log.Printf("unable to evaluate condition [%s] because:%v\n", ifExpression, err)
		return errAbort
	}
	if !pass {
		log.Printf(".. skipping ... (%d) commands because %s is false.\n", len(commands), ifExpression)
		return nil
	}
	if len(commands) == 0 {
		return nil
	}
	allEnv := append(os.Environ(), envs...)
	envMap := map[string]string{}
	for _, each := range allEnv {
		kv := strings.Split(each, "=")
		envMap[kv[0]] = kv[1]
	}
	for _, each := range commands {
		log.Println(expandVarsIn(envMap, each))
	}
	return nil
}