func readConfigFile(configPath string) (*configFile, error) {
	resolvedConfigPath, err := filepath.Abs(configPath)
	if err != nil {
		return nil, oops.Wrapf(err, "unable to resolve config file path")
	}
	content, err := ioutil.ReadFile(resolvedConfigPath)
	if err != nil {
		return nil, oops.Wrapf(err, "unable to read file")
	}
	configFile := configFile{configPath: resolvedConfigPath}
	if err := json.Unmarshal(content, &configFile); err != nil {
		return nil, oops.Wrapf(err, "unable to parse json")
	}
	return &configFile, nil
}
readConfigFile reads the configuration at configPath and returns the json file representation.
package config
import (
	"encoding/json"
	"io/ioutil"
	"os"
	"path/filepath"
	"github.com/samsarahq/go/oops"
)
type configFile struct {
	configPath string
	Extends      *string  `json:"extends"`
	Path         *string  `json:"path"`
	DesiredTasks []string `json:"desiredTasks"`
}
func resolveWorkingDir(filePath, pathOption string) (string, error) {
	configPath, err := filepath.Abs(filePath)
	if err != nil {
		return "", oops.Wrapf(err, "unable to find config file %s", filePath)
	}
	configPath, err = filepath.EvalSymlinks(configPath)
	if err != nil {
		return "", oops.Wrapf(err, "failed to evaluate symlinks for config file: %s", filePath)
	}
	return filepath.Join(filepath.Dir(configPath), pathOption), nil
}
func (c configFile) ToConfig() (*Config, error) {
	config := Config{
		DesiredTasks: c.DesiredTasks,
		ConfigPath:   c.configPath,
	}
	if c.Path == nil {
		return nil, oops.Errorf("config must specify path: %s", c.configPath)
	}
	resolvedPath, err := resolveWorkingDir(c.configPath, *c.Path)
	if err != nil {
		return nil, oops.Wrapf(err, "failed to resolve path for %s", *c.Path)
	}
	config.WorkingDir = resolvedPath
	return &config, nil
}
type Config struct {
	WorkingDir string
	ConfigPath string
	DesiredTasks []string
}
func (c *Config) With(configFile *configFile) (*Config, error) {
	copy := *c
	if path := configFile.Path; path != nil {
		resolvedPath, err := resolveWorkingDir(configFile.configPath, *path)
		if err != nil {
			return nil, oops.Wrapf(err, "failed to resolve path for %s", *path)
		}
		copy.WorkingDir = resolvedPath
	}
	if configFile.DesiredTasks != nil {
		merged := make(map[string]struct{})
		for _, task := range c.DesiredTasks {
			merged[task] = struct{}{}
		}
		for _, task := range configFile.DesiredTasks {
			merged[task] = struct{}{}
		}
		mergedTasks := make([]string, 0, len(merged))
		for task := range merged {
			mergedTasks = append(mergedTasks, task)
		}
		copy.DesiredTasks = mergedTasks
	}
	copy.ConfigPath = configFile.configPath
	return &copy, nil
}