func Load(home string) (*Config, error) {
	file, err := IsEmpty(home)
	if err != nil {
		return nil, err
	}
	c := &Config{
		File: file,
	}
	data, err := ioutil.ReadFile(file)
	if err != nil {
		return nil, err
	}
	if err := yaml.Unmarshal(data, &c); err != nil {
		return nil, err
	}
	return c, nil
}
Load is used to return the unmarshalled config, and
returns an error when trying to get the users home directory or
while marshalling the config.
package config
import (
	"fmt"
	"io/ioutil"
	"os"
	"path/filepath"
	"sort"
	"strings"
	"github.com/craftcms/nitro/pkg/helpers"
	"gopkg.in/yaml.v3"
)
var (
	DirectoryName = ".nitro"
	ErrNoConfigFile = fmt.Errorf("there is no config file for the environment")
	ErrEmptyfile = fmt.Errorf("the config file appears to be empty")
	FileName = "nitro.yaml"
	DefaultEnvs = map[string]string{
		"PHP_DISPLAY_ERRORS":              "on",
		"PHP_MEMORY_LIMIT":                "512M",
		"PHP_MAX_EXECUTION_TIME":          "5000",
		"PHP_UPLOAD_MAX_FILESIZE":         "512M",
		"PHP_MAX_INPUT_VARS":              "5000",
		"PHP_POST_MAX_SIZE":               "512M",
		"PHP_OPCACHE_ENABLE":              "0",
		"PHP_OPCACHE_REVALIDATE_FREQ":     "0",
		"PHP_OPCACHE_VALIDATE_TIMESTAMPS": "0",
		"XDEBUG_MODE":                     "off",
		"XDEBUG_SESSION":                  "PHPSTORM",
		"XDEBUG_CONFIG":                   "",
		"BLACKFIRE_SERVER_ID":             "",
		"BLACKFIRE_SERVER_TOKEN":          "",
	}
)
type Config struct {
	Containers []Container `json:"containers,omitempty" yaml:"containers,omitempty"`
	Blackfire  Blackfire   `json:"blackfire,omitempty" yaml:"blackfire,omitempty"`
	Databases  []Database  `json:"databases,omitempty" yaml:"databases,omitempty"`
	Services   Services    `json:"services" yaml:"services"`
	Sites      []Site      `json:"sites,omitempty" yaml:"sites,omitempty"`
	File       string      `json:"-" yaml:"-"`
}
func (c *Config) AllSitesWithHostnames(site Site, addr string) map[string][]string {
	hostnames := make(map[string][]string)
	for _, s := range c.Sites {
		if site.Hostname == s.Hostname {
			continue
		}
		hostnames[addr] = append(s.Aliases, s.Hostname)
	}
	return hostnames
}
func (c *Config) FindContainerByName(name string) (*Container, error) {
	for _, c := range c.Containers {
		if c.Name == name {
			return &c, nil
		}
	}
	return nil, fmt.Errorf("unable to find container with name %s", name)
}
func (c *Config) FindSiteByHostName(hostname string) (*Site, error) {
	for _, s := range c.Sites {
		if s.Hostname == hostname {
			return &s, nil
		}
	}
	return nil, fmt.Errorf("unable to find site with hostname %s", hostname)
}
func (c *Config) ListOfSitesByDirectory(home, wd string) []Site {
	var found []Site
	for _, s := range c.Sites {
		p, _ := s.GetAbsContainerPath(home)
		if strings.Contains(p, wd) {
			found = append(found, s)
		}
	}
	if len(found) > 0 {
		var exactMatches []Site
		var bestMatches = 0
		var b Site
		var m = 0
		for _, s := range found {
			containerPath := s.GetContainerPath()
			p, _ := s.GetAbsContainerPath(home)
			segments := strings.Split(containerPath, "/")
			var matchingSegments = len(segments)
			if wd == p {
				exactMatches = append(exactMatches, s)
			} else if matchingSegments > 1 && matchingSegments > m {
				bestMatches += 1
				m = matchingSegments
				b = s
			}
		}
		if len(exactMatches) > 0 {
			return exactMatches
		}
		if bestMatches == 1 {
			return []Site{b}
		}
		return found
	}
	return c.Sites
}
type Blackfire struct {
	ServerID    string `json:"server_id,omitempty" yaml:"server_id,omitempty"`
	ServerToken string `json:"server_token,omitempty" yaml:"server_token,omitempty"`
}
type Container struct {
	Name string `json:"name" yaml:"name"`
	Image string `json:"image" yaml:"image"`
	Tag string `json:"tag" yaml:"tag"`
	Ports []string `json:"ports,omitempty" yaml:"ports,omitempty"`
	Volumes []string `json:"volumes,omitempty" yaml:"volumes,omitempty"`
	WebGui  int    `json:"web_gui,omitempty" yaml:"web_gui,omitempty"`
	EnvFile string `json:"env_file,omitempty" yaml:"env_file,omitempty"`
}
func (c *Config) AddContainer(container Container) error {
	for _, e := range c.Containers {
		if e.Name == container.Name {
			return fmt.Errorf("an existing container %q already exists", e.Name)
		}
	}
	c.Containers = append(c.Containers, container)
	sort.SliceStable(c.Containers, func(i, j int) bool {
		return c.Containers[i].Name < c.Containers[j].Name
	})
	return nil
}
type Database struct {
	Engine  string `json:"engine" yaml:"engine"`
	Version string `json:"version" yaml:"version"`
	Port    string `json:"port" yaml:"port"`
}
func (d *Database) GetHostname() (string, error) {
	if d.Engine == "" || d.Version == "" || d.Port == "" {
		return "", fmt.Errorf("the engine, version, and port must be defined for the database")
	}
	return fmt.Sprintf("%s-%s-%s.database.nitro", d.Engine, d.Version, d.Port), nil
}
type Services struct {
	DynamoDB bool `json:"dynamodb"`
	Mailhog  bool `json:"mailhog"`
	Minio    bool `json:"minio"`
	Redis    bool `json:"redis"`
}
type Site struct {
	Hostname   string   `json:"hostname" yaml:"hostname"`
	Aliases    []string `json:"aliases,omitempty" yaml:"aliases,omitempty"`
	Path       string   `json:"path" yaml:"path"`
	Version    string   `json:"version" yaml:"version"`
	PHP        PHP      `json:"php,omitempty" yaml:"php,omitempty"`
	Extensions []string `json:"extensions,omitempty" yaml:"extensions,omitempty"`
	Webroot    string   `json:"webroot" yaml:"webroot"`
	Xdebug     bool     `json:"xdebug" yaml:"xdebug"`
	Blackfire  bool     `json:"blackfire" yaml:"blackfire"`
}
func (s *Site) GetAbsPath(home string) (string, error) {
	return cleanPath(home, s.Path)
}
func (s *Site) GetAbsContainerPath(home string) (string, error) {
	return cleanPath(home, s.Path+string(os.PathSeparator)+s.GetContainerPath())
}
func (s *Site) GetContainerPath() string {
	webroot := strings.TrimRight(s.Webroot, "/")
	if strings.Contains(webroot, "/") {
		parts := strings.Split(webroot, "/")
		if len(parts) >= 2 {
			return strings.Join(parts[:len(parts)-1], "/")
		}
	}
	return ""
}
func (s *Site) AsEnvs(addr string) []string {
	var envs []string
	envs = append(envs, phpVars(s.PHP, s.Version)...)
	return append(envs, xdebugVars(s.PHP, s.Xdebug, s.Version, s.Hostname, addr)...)
}
func (c *Config) SetPHPBoolSetting(hostname, setting string, value bool) error {
	for i, s := range c.Sites {
		if s.Hostname == hostname {
			switch setting {
			case "display_errors":
				c.Sites[i].PHP.DisplayErrors = value
				return nil
			case "opcache_enable":
				c.Sites[i].PHP.OpcacheEnable = value
				return nil
			case "opcache_validate_timestamps":
				c.Sites[i].PHP.OpcacheValidateTimestamps = value
				return nil
			default:
				return fmt.Errorf("unknown php setting %s", setting)
			}
		}
	}
	return fmt.Errorf("unable to find the site: %s", hostname)
}
func (c *Config) SetSiteAlias(hostname, alias string) error {
	for i, s := range c.Sites {
		if s.Hostname != hostname {
			continue
		}
		switch len(c.Sites[i].Aliases) == 0 {
		case false:
			for _, a := range c.Sites[i].Aliases {
				if a == alias {
					return fmt.Errorf("alias %s is already set for %s", alias, hostname)
				}
				c.Sites[i].Aliases = append(c.Sites[i].Aliases, alias)
				sort.Strings(c.Sites[i].Aliases)
				return nil
			}
		default:
			c.Sites[i].Aliases = append(c.Sites[i].Aliases, alias)
			return nil
		}
	}
	return fmt.Errorf("unable to find the site: %s", hostname)
}
func (c *Config) SetPHPExtension(hostname, extension string) error {
	for i, s := range c.Sites {
		if s.Hostname == hostname {
			for _, e := range c.Sites[i].Extensions {
				if e == extension {
					return fmt.Errorf("extension %s is already set for %s", extension, hostname)
				}
			}
			c.Sites[i].Extensions = append(c.Sites[i].Extensions, extension)
			sort.Strings(c.Sites[i].Extensions)
			return nil
		}
	}
	return fmt.Errorf("unable to find the site: %s", hostname)
}
func (c *Config) SetPHPIntSetting(hostname, setting string, value int) error {
	for i, s := range c.Sites {
		if s.Hostname == hostname {
			switch setting {
			case "max_execution_time":
				c.Sites[i].PHP.MaxExecutionTime = value
				return nil
			case "max_input_vars":
				c.Sites[i].PHP.MaxInputVars = value
				return nil
			case "max_input_time":
				c.Sites[i].PHP.MaxInputTime = value
				return nil
			case "opcache_revalidate_freq":
				c.Sites[i].PHP.OpcacheRevalidateFreq = value
				return nil
			default:
				return fmt.Errorf("unknown php setting %s", setting)
			}
		}
	}
	return fmt.Errorf("unable to find the site: %s", hostname)
}
func (c *Config) SetPHPStrSetting(hostname, setting, value string) error {
	for i, s := range c.Sites {
		if s.Hostname == hostname {
			switch setting {
			case "post_max_size":
				c.Sites[i].PHP.PostMaxSize = value
				return nil
			case "max_file_upload":
				c.Sites[i].PHP.MaxFileUpload = value
				return nil
			case "memory_limit":
				c.Sites[i].PHP.MemoryLimit = value
				return nil
			case "upload_max_file_size":
				c.Sites[i].PHP.UploadMaxFileSize = value
				return nil
			default:
				return fmt.Errorf("unknown php setting %s", setting)
			}
		}
	}
	return fmt.Errorf("unable to find the site: %s", hostname)
}
type PHP struct {
	DisplayErrors             bool   `json:"display_errors,omitempty" yaml:"display_errors,omitempty"`
	MaxExecutionTime          int    `json:"max_execution_time,omitempty" yaml:"max_execution_time,omitempty"`
	MaxInputVars              int    `json:"max_input_vars,omitempty" yaml:"max_input_vars,omitempty"`
	MaxInputTime              int    `json:"max_input_time,omitempty" yaml:"max_input_time,omitempty"`
	MaxFileUpload             string `json:"max_file_upload,omitempty" yaml:"max_file_upload,omitempty"`
	MemoryLimit               string `json:"memory_limit,omitempty" yaml:"memory_limit,omitempty"`
	OpcacheEnable             bool   `json:"opcache_enable,omitempty" yaml:"opcache_enable,omitempty"`
	OpcacheRevalidateFreq     int    `json:"opcache_revalidate_freq,omitempty" yaml:"opcache_revalidate_freq,omitempty"`
	OpcacheValidateTimestamps bool   `json:"opcache_validate_timestamps,omitempty" yaml:"opcache_validate_timestamps,omitempty"`
	PostMaxSize               string `json:"post_max_size,omitempty" yaml:"post_max_size,omitempty"`
	UploadMaxFileSize         string `json:"upload_max_file_size,omitempty" yaml:"upload_max_file_size,omitempty"`
}