func (dir *Dir) SchemaNames(instance *tengo.Instance) (names []string, err error) {
	if !dir.Config.Changed("schema") {
		return nil, nil
	}
	schemaValue := dir.Config.Get("schema")                         
	rawSchemaValue := dir.Config.GetRaw("schema")                   
	if rawSchemaValue != schemaValue && rawSchemaValue[0] == '`' {  
		variables := map[string]string{
			"HOST":        instance.Host,
			"PORT":        strconv.Itoa(instance.Port),
			"USER":        dir.Config.Get("user"),
			"PASSWORD":    dir.Config.Get("password"),
			"ENVIRONMENT": dir.Config.Get("environment"),
			"DIRNAME":     dir.BaseName(),
			"DIRPATH":     dir.Path,
		}
		shellOut, err := util.NewInterpolatedShellOut(schemaValue, variables)
		if err == nil {
			names, err = shellOut.RunCaptureSplit()
		}
		if err != nil {
			return nil, err
		}
	} else if schemaValue == "*" || looksLikeRegex(schemaValue) {
		if names, err = instance.SchemaNames(); err != nil {
			return nil, err
		}
		sort.Strings(names)
		if schemaValue != "*" {
			re, err := regexp.Compile(schemaValue[1 : len(schemaValue)-1])
			if err != nil {
				return nil, err
			}
			keepNames := []string{}
			for _, name := range names {
				if re.MatchString(name) {
					keepNames = append(keepNames, name)
				}
			}
			names = keepNames
		}
	} else {
		names = dir.Config.GetSlice("schema", ',', true)
	}
	ignoreSchema, err := dir.Config.GetRegexp("ignore-schema")
	if err != nil {
		return nil, err
	}
	return filterSchemaNames(names, ignoreSchema), nil
}
SchemaNames interprets the value of the dir's "schema" option, returning one
or more schema names that the statements in dir's *.sql files will be applied
to, in cases where no schema name is explicitly specified in SQL statements.
If the ignore-schema option is set, it will filter out matching results from
the returned slice.
An instance must be supplied since the value may be instance-specific.
package fs
import (
	"fmt"
	"io/ioutil"
	"net/url"
	"os"
	"path/filepath"
	"regexp"
	"runtime"
	"sort"
	"strconv"
	"strings"
	log "github.com/sirupsen/logrus"
	"github.com/skeema/mybase"
	"github.com/skeema/skeema/internal/util"
	"github.com/skeema/tengo"
)
type Dir struct {
	Path              string
	Config            *mybase.Config
	OptionFile        *mybase.File
	SQLFiles          []SQLFile
	LogicalSchemas    []*LogicalSchema  
	ParseError        error             
	IgnoredStatements []*Statement      
	repoBase          string            
}
type LogicalSchema struct {
	Name      string
	CharSet   string
	Collation string
	Creates   map[tengo.ObjectKey]*Statement
	Alters    []*Statement  
}
func (logicalSchema *LogicalSchema) AddStatement(stmt *Statement) error {
	switch stmt.Type {
	case StatementTypeCreate:
		if origStmt, already := logicalSchema.Creates[stmt.ObjectKey()]; already {
			return DuplicateDefinitionError{
				ObjectKey: stmt.ObjectKey(),
				FirstFile: origStmt.File,
				FirstLine: origStmt.LineNo,
				DupeFile:  stmt.File,
				DupeLine:  stmt.LineNo,
			}
		}
		logicalSchema.Creates[stmt.ObjectKey()] = stmt
		return nil
	case StatementTypeAlter:
		logicalSchema.Alters = append(logicalSchema.Alters, stmt)
		return nil
	default:
		return nil
	}
}
func ParseDir(dirPath string, globalConfig *mybase.Config) (*Dir, error) {
	cleaned, err := filepath.Abs(filepath.Clean(dirPath))
	if err != nil {
		return nil, err
	}
	dir := &Dir{
		Path:   cleaned,
		Config: globalConfig.Clone(),
	}
	var parentFiles []*mybase.File
	parentFiles, dir.repoBase, err = ParentOptionFiles(dirPath, globalConfig)
	if err != nil {
		return nil, err
	}
	for _, optionFile := range parentFiles {
		dir.Config.AddSource(optionFile)
	}
	dir.parseContents()
	return dir, dir.ParseError
}
func (dir *Dir) String() string {
	return dir.Path
}
func (dir *Dir) BaseName() string {
	return filepath.Base(dir.Path)
}
func (dir *Dir) RelPath() string {
	rel, err := filepath.Rel(dir.repoBase, dir.Path)
	if dir.repoBase == "" || err != nil {
		return dir.BaseName()
	}
	return rel
}
func (dir *Dir) Delete() error {
	return os.RemoveAll(dir.Path)
}
func (dir *Dir) HasFile(name string) (bool, error) {
	_, err := os.Lstat(filepath.Join(dir.Path, name))
	if err == nil {
		return true, nil
	} else if os.IsNotExist(err) {
		return false, nil
	}
	return false, err
}
func (dir *Dir) Subdirs() ([]*Dir, error) {
	fileInfos, err := ioutil.ReadDir(dir.Path)
	if err != nil {
		return nil, err
	}
	result := make([]*Dir, 0, len(fileInfos))
	for _, fi := range fileInfos {
		if fi.IsDir() && fi.Name()[0] != '.' {
			sub := &Dir{
				Path:     filepath.Join(dir.Path, fi.Name()),
				Config:   dir.Config.Clone(),
				repoBase: dir.repoBase,
			}
			sub.parseContents()
			result = append(result, sub)
		}
	}
	return result, nil
}
func (dir *Dir) CreateSubdir(name string, optionFile *mybase.File) (*Dir, error) {
	dirPath := filepath.Join(dir.Path, name)
	if dir.OptionFile != nil && dir.OptionFile.SomeSectionHasOption("schema") {
		return nil, fmt.Errorf("Cannot use dir %s: parent option file %s defines schema option", dirPath, dir.OptionFile)
	} else if _, ok := dir.Config.Source("schema").(*mybase.File); ok {
		return nil, fmt.Errorf("Cannot use dir %s: an ancestor option file defines schema option", dirPath)
	}
	if fi, err := os.Stat(dirPath); os.IsNotExist(err) {
		err = os.MkdirAll(dirPath, 0777)
		if err != nil {
			return nil, fmt.Errorf("Unable to create directory %s: %s", dirPath, err)
		}
	} else if err != nil {
		return nil, err
	} else if !fi.IsDir() {
		return nil, fmt.Errorf("Path %s already exists but is not a directory", dirPath)
	} else {
		fileInfos, err := ioutil.ReadDir(dirPath)
		if err != nil {
			return nil, err
		}
		for _, fi := range fileInfos {
			if fi.Name() == ".skeema" {
				return nil, fmt.Errorf("Cannot use dir %s: already has .skeema file", dirPath)
			} else if strings.HasSuffix(fi.Name(), ".sql") {
				return nil, fmt.Errorf("Cannot use dir %s: Already contains *.sql files", dirPath)
			}
		}
	}
	if optionFile != nil {
		optionFile.Dir = dirPath
		if err := optionFile.Write(false); err != nil {
			return nil, fmt.Errorf("Cannot use dir %s: Unable to write to %s: %s", dirPath, optionFile.Path(), err)
		}
	}
	sub := &Dir{
		Path:     dirPath,
		Config:   dir.Config.Clone(),
		repoBase: dir.repoBase,
	}
	sub.parseContents()
	return sub, sub.ParseError
}
func (dir *Dir) CreateOptionFile(optionFile *mybase.File) (err error) {
	if dir.OptionFile != nil {
		return fmt.Errorf("Directory %s already has an option file", dir)
	}
	optionFile.Dir = dir.Path
	if err := optionFile.Write(false); err != nil {
		return fmt.Errorf("Unable to write to %s: %s", optionFile.Path(), err)
	}
	if dir.OptionFile, err = parseOptionFile(dir.Path, dir.repoBase, dir.Config); err != nil {
		return err
	}
	dir.Config.AddSource(dir.OptionFile)
	return nil
}
func (dir *Dir) Hostnames() ([]string, error) {
	if dir.Config.Changed("host-wrapper") {
		variables := map[string]string{
			"HOST":        dir.Config.Get("host"),
			"ENVIRONMENT": dir.Config.Get("environment"),
			"DIRNAME":     dir.BaseName(),
			"DIRPATH":     dir.Path,
			"SCHEMA":      dir.Config.Get("schema"),
		}
		shellOut, err := util.NewInterpolatedShellOut(dir.Config.Get("host-wrapper"), variables)
		if err != nil {
			return nil, err
		}
		return shellOut.RunCaptureSplit()
	}
	return dir.Config.GetSlice("host", ',', true), nil
}
func (dir *Dir) Instances() ([]*tengo.Instance, error) {
	hosts, err := dir.Hostnames()
	if err != nil {
		return nil, err
	} else if len(hosts) == 0 {
		return nil, nil
	}
	var userAndPass string
	if !dir.Config.Changed("password") {
		userAndPass = dir.Config.Get("user")
	} else {
		userAndPass = fmt.Sprintf("%s:%s", dir.Config.Get("user"), dir.Config.Get("password"))
	}
	params, err := dir.InstanceDefaultParams()
	if err != nil {
		return nil, fmt.Errorf("Invalid connection options: %s", err)
	}
	portValue := dir.Config.GetIntOrDefault("port")
	portWasSupplied := dir.Config.Supplied("port")
	portIsntDefault := dir.Config.Changed("port")
	socketValue := dir.Config.Get("socket")
	socketWasSupplied := dir.Config.Supplied("socket")
	var instances []*tengo.Instance
	for _, host := range hosts {
		var dsn string
		thisPortValue := portValue
		if host == "localhost" && (socketWasSupplied || !portWasSupplied) {
			dsn = fmt.Sprintf("%s@unix(%s)/?%s", userAndPass, socketValue, params)
		} else {
			splitHost, splitPort, err := tengo.SplitHostOptionalPort(host)
			if err != nil {
				return nil, err
			}
			if splitPort > 0 {
				if portIsntDefault && portValue != splitPort {
					return nil, fmt.Errorf("Port was supplied as %d inside hostname %s but as %d in option file", splitPort, host, portValue)
				}
				host = splitHost
				thisPortValue = splitPort
			}
			dsn = fmt.Sprintf("%s@tcp(%s:%d)/?%s", userAndPass, host, thisPortValue, params)
		}
		instance, err := util.NewInstance("mysql", dsn)
		if err != nil {
			if dir.Config.Changed("password") {
				safeUserPass := fmt.Sprintf("%s:*****", dir.Config.Get("user"))
				dsn = strings.Replace(dsn, userAndPass, safeUserPass, 1)
			}
			return nil, fmt.Errorf("Invalid connection information for %s (DSN=%s): %s", dir, dsn, err)
		}
		instances = append(instances, instance)
	}
	return instances, nil
}
func (dir *Dir) FirstInstance() (*tengo.Instance, error) {
	instances, err := dir.Instances()
	if len(instances) == 0 || err != nil {
		return nil, err
	}
	var lastErr error
	for _, instance := range instances {
		if lastErr = dir.ValidateInstance(instance); lastErr == nil {
			return instance, nil
		}
	}
	if len(instances) == 1 {
		return nil, fmt.Errorf("Unable to connect to %s for %s: %s", instances[0], dir, lastErr)
	}
	return nil, fmt.Errorf("Unable to connect to any of %d instances for %s; last error %s", len(instances), dir, lastErr)
}
func (dir *Dir) ValidateInstance(instance *tengo.Instance) error {
	ok, err := instance.Valid()
	if !ok {
		return err
	}
	instFlavor := instance.Flavor()
	confFlavor := tengo.NewFlavor(dir.Config.Get("flavor"))
	if instFlavor.Known() {
		if confFlavor != tengo.FlavorUnknown && instFlavor.Family() != confFlavor.Family() {
			log.Warnf("Instance %s actual flavor %s differs from dir %s configured flavor %s", instance, instFlavor, dir, confFlavor)
		}
	} else if confFlavor.Known() {
		log.Debugf("Instance %s flavor cannot be parsed; using dir %s configured flavor %s instead", instance, dir, confFlavor)
		instance.SetFlavor(confFlavor)
	} else {
		log.Warnf("Unable to determine database vendor/version of %s. To set manually, use the \"flavor\" option in %s", instance, filepath.Join(dir.Path, ".skeema"))
	}
	return nil
}