| package cfg | 
 |  | 
 | import ( | 
 | 	"crypto/sha256" | 
 | 	"encoding/hex" | 
 | 	"fmt" | 
 | 	"io/ioutil" | 
 | 	"sort" | 
 | 	"strings" | 
 | 	"time" | 
 |  | 
 | 	"github.com/sdboyer/gps" | 
 |  | 
 | 	"gopkg.in/yaml.v2" | 
 | ) | 
 |  | 
 | // Lockfile represents a glide.lock file. | 
 | type Lockfile struct { | 
 | 	Hash       string    `yaml:"hash"` | 
 | 	Updated    time.Time `yaml:"updated"` | 
 | 	Imports    Locks     `yaml:"imports"` | 
 | 	DevImports Locks     `yaml:"testImports"` // TODO remove and fold in as prop | 
 | } | 
 |  | 
 | // LockfileFromSolverLock transforms a gps.Lock into a glide *Lockfile. | 
 | func LockfileFromSolverLock(r gps.Lock) (*Lockfile, error) { | 
 | 	if r == nil { | 
 | 		return nil, fmt.Errorf("no gps lock data provided to transform") | 
 | 	} | 
 |  | 
 | 	// Create and write out a new lock file from the result | 
 | 	lf := &Lockfile{ | 
 | 		Hash:    hex.EncodeToString(r.InputHash()), | 
 | 		Updated: time.Now(), | 
 | 	} | 
 |  | 
 | 	for _, p := range r.Projects() { | 
 | 		pi := p.Ident() | 
 | 		l := &Lock{ | 
 | 			Name: string(pi.ProjectRoot), | 
 | 		} | 
 |  | 
 | 		if l.Name != pi.NetworkName && pi.NetworkName != "" { | 
 | 			l.Repository = pi.NetworkName | 
 | 		} | 
 |  | 
 | 		v := p.Version() | 
 | 		// There's (currently) no way gps can emit a non-paired version in a | 
 | 		// solution, so this unchecked type assertion should be safe. | 
 | 		// | 
 | 		// TODO might still be better to check and return out with an err if | 
 | 		// not, though | 
 | 		switch tv := v.(type) { | 
 | 		case gps.Revision: | 
 | 			l.Revision = tv.String() | 
 | 		case gps.PairedVersion: | 
 | 			l.Revision = v.(gps.PairedVersion).Underlying().String() | 
 | 			switch v.Type() { | 
 | 			case "branch": | 
 | 				l.Branch = v.String() | 
 | 			case "semver", "version": | 
 | 				l.Version = v.String() | 
 | 			} | 
 | 		case gps.UnpairedVersion: | 
 | 			// this should not be possible - error if we hit it | 
 | 			return nil, fmt.Errorf("should not be possible - gps returned an unpaired version for %s", pi) | 
 | 		} | 
 |  | 
 | 		lf.Imports = append(lf.Imports, l) | 
 | 	} | 
 |  | 
 | 	return lf, nil | 
 | } | 
 |  | 
 | // LockfileFromYaml returns an instance of Lockfile from YAML | 
 | func LockfileFromYaml(yml []byte) (*Lockfile, bool, error) { | 
 | 	lock := &Lockfile{} | 
 | 	err := yaml.Unmarshal([]byte(yml), lock) | 
 | 	if err == nil { | 
 | 		return lock, false, nil | 
 | 	} | 
 |  | 
 | 	llock := &lLockfile1{} | 
 | 	err2 := yaml.Unmarshal([]byte(yml), llock) | 
 | 	if err2 != nil { | 
 | 		return nil, false, err2 | 
 | 	} | 
 | 	return llock.Convert(), true, nil | 
 | } | 
 |  | 
 | // Marshal converts a Lockfile instance to YAML | 
 | func (lf *Lockfile) Marshal() ([]byte, error) { | 
 | 	sort.Sort(lf.Imports) | 
 | 	sort.Sort(lf.DevImports) | 
 | 	yml, err := yaml.Marshal(&lf) | 
 | 	if err != nil { | 
 | 		return []byte{}, err | 
 | 	} | 
 | 	return yml, nil | 
 | } | 
 |  | 
 | // MarshalYAML is a hook for gopkg.in/yaml.v2. | 
 | // It sorts import subpackages lexicographically for reproducibility. | 
 | func (lf *Lockfile) MarshalYAML() (interface{}, error) { | 
 | 	// Ensure elements on testImport don't already exist on import. | 
 | 	var newDI Locks | 
 | 	var found bool | 
 | 	for _, imp := range lf.DevImports { | 
 | 		found = false | 
 | 		for i := 0; i < len(lf.Imports); i++ { | 
 | 			if lf.Imports[i].Name == imp.Name { | 
 | 				found = true | 
 | 				if lf.Imports[i].Version != imp.Version { | 
 | 					return lf, fmt.Errorf("Generating lock YAML produced conflicting versions of %s. import (%s), testImport (%s)", imp.Name, lf.Imports[i].Version, imp.Version) | 
 | 				} | 
 | 			} | 
 | 		} | 
 |  | 
 | 		if !found { | 
 | 			newDI = append(newDI, imp) | 
 | 		} | 
 | 	} | 
 | 	lf.DevImports = newDI | 
 |  | 
 | 	return lf, nil | 
 | } | 
 |  | 
 | // WriteFile writes a Glide lock file. | 
 | // | 
 | // This is a convenience function that marshals the YAML and then writes it to | 
 | // the given file. If the file exists, it will be clobbered. | 
 | func (lf *Lockfile) WriteFile(lockpath string) error { | 
 | 	o, err := lf.Marshal() | 
 | 	if err != nil { | 
 | 		return err | 
 | 	} | 
 | 	return ioutil.WriteFile(lockpath, o, 0666) | 
 | } | 
 |  | 
 | // InputHash returns the hash of the input arguments that resulted in this lock | 
 | // file. | 
 | func (lf *Lockfile) InputHash() []byte { | 
 | 	b, err := hex.DecodeString(lf.Hash) | 
 | 	if err != nil { | 
 | 		return nil | 
 | 	} | 
 | 	return b | 
 | } | 
 |  | 
 | // Projects returns the list of projects enumerated in the lock file. | 
 | func (lf *Lockfile) Projects() []gps.LockedProject { | 
 | 	all := append(lf.Imports, lf.DevImports...) | 
 | 	lp := make([]gps.LockedProject, len(all)) | 
 |  | 
 | 	for k, l := range all { | 
 | 		r := gps.Revision(l.Revision) | 
 |  | 
 | 		var v gps.Version | 
 | 		if l.Version != "" { | 
 | 			v = gps.NewVersion(l.Version).Is(r) | 
 | 		} else if l.Branch != "" { | 
 | 			v = gps.NewBranch(l.Branch).Is(r) | 
 | 		} else { | 
 | 			v = r | 
 | 		} | 
 |  | 
 | 		id := gps.ProjectIdentifier{ | 
 | 			ProjectRoot: gps.ProjectRoot(l.Name), | 
 | 			NetworkName: l.Repository, | 
 | 		} | 
 | 		lp[k] = gps.NewLockedProject(id, v, nil) | 
 | 	} | 
 |  | 
 | 	return lp | 
 | } | 
 |  | 
 | // Clone returns a clone of Lockfile | 
 | func (lf *Lockfile) Clone() *Lockfile { | 
 | 	n := &Lockfile{} | 
 | 	n.Hash = lf.Hash | 
 | 	n.Updated = lf.Updated | 
 | 	n.Imports = lf.Imports.Clone() | 
 | 	n.DevImports = lf.DevImports.Clone() | 
 |  | 
 | 	return n | 
 | } | 
 |  | 
 | // Fingerprint returns a hash of the contents minus the date. This allows for | 
 | // two lockfiles to be compared irrespective of their updated times. | 
 | // TODO remove, or seriously re-adapt | 
 | func (lf *Lockfile) Fingerprint() ([32]byte, error) { | 
 | 	c := lf.Clone() | 
 | 	c.Updated = time.Time{} // Set the time to be the nil equivalent | 
 | 	sort.Sort(c.Imports) | 
 | 	sort.Sort(c.DevImports) | 
 | 	yml, err := c.Marshal() | 
 | 	if err != nil { | 
 | 		return [32]byte{}, err | 
 | 	} | 
 |  | 
 | 	return sha256.Sum256(yml), nil | 
 | } | 
 |  | 
 | // ReadLockFile loads the contents of a glide.lock file. | 
 | func ReadLockFile(lockpath string) (*Lockfile, error) { | 
 | 	yml, err := ioutil.ReadFile(lockpath) | 
 | 	if err != nil { | 
 | 		return nil, err | 
 | 	} | 
 | 	lock, _, err := LockfileFromYaml(yml) | 
 | 	if err != nil { | 
 | 		return nil, err | 
 | 	} | 
 | 	return lock, nil | 
 | } | 
 |  | 
 | // Locks is a slice of locked dependencies. | 
 | type Locks []*Lock | 
 |  | 
 | // Clone returns a Clone of Locks. | 
 | func (l Locks) Clone() Locks { | 
 | 	n := make(Locks, 0, len(l)) | 
 | 	for _, v := range l { | 
 | 		n = append(n, v.Clone()) | 
 | 	} | 
 | 	return n | 
 | } | 
 |  | 
 | // Len returns the length of the Locks. This is needed for sorting with | 
 | // the sort package. | 
 | func (l Locks) Len() int { | 
 | 	return len(l) | 
 | } | 
 |  | 
 | // Less is needed for the sort interface. It compares two locks based on | 
 | // their name. | 
 | func (l Locks) Less(i, j int) bool { | 
 |  | 
 | 	// Names are normalized to lowercase because case affects sorting order. For | 
 | 	// example, Masterminds comes before kylelemons. Making them lowercase | 
 | 	// causes kylelemons to come first which is what is expected. | 
 | 	return strings.ToLower(l[i].Name) < strings.ToLower(l[j].Name) | 
 | } | 
 |  | 
 | // Swap is needed for the sort interface. It swaps the position of two | 
 | // locks. | 
 | func (l Locks) Swap(i, j int) { | 
 | 	l[i], l[j] = l[j], l[i] | 
 | } | 
 |  | 
 | // Lock represents an individual locked dependency. | 
 | type Lock struct { | 
 | 	Name       string `yaml:"name"` | 
 | 	Version    string `yaml:"version,omitempty"` | 
 | 	Branch     string `yaml:"branch,omitempty"` | 
 | 	Revision   string `yaml:"revision"` | 
 | 	Repository string `yaml:"repo,omitempty"` | 
 | } | 
 |  | 
 | func (l *Lock) UnmarshalYAML(unmarshal func(interface{}) error) error { | 
 | 	nl := struct { | 
 | 		Name       string `yaml:"name"` | 
 | 		Version    string `yaml:"version,omitempty"` | 
 | 		Branch     string `yaml:"branch,omitempty"` | 
 | 		Revision   string `yaml:"revision"` | 
 | 		Repository string `yaml:"repo,omitempty"` | 
 | 	}{} | 
 |  | 
 | 	err := unmarshal(&nl) | 
 | 	if err != nil { | 
 | 		return err | 
 | 	} | 
 |  | 
 | 	// If Revision field is empty, then we can be certain this is either a | 
 | 	// legacy file, or just plain invalid | 
 | 	if nl.Revision == "" { | 
 | 		return fmt.Errorf("dependency %s is missing a revision; is this a legacy glide.lock file?", nl.Name) | 
 | 	} | 
 |  | 
 | 	l.Name = nl.Name | 
 | 	l.Version = nl.Version | 
 | 	l.Branch = nl.Branch | 
 | 	l.Revision = nl.Revision | 
 | 	l.Repository = nl.Repository | 
 |  | 
 | 	return nil | 
 | } | 
 |  | 
 | // Clone creates a clone of a Lock. | 
 | func (l *Lock) Clone() *Lock { | 
 | 	var l2 Lock | 
 | 	l2 = *l | 
 | 	return &l2 | 
 | } | 
 |  | 
 | // LockFromDependency converts a Dependency to a Lock | 
 | // TODO remove | 
 | func LockFromDependency(dep *Dependency) *Lock { | 
 | 	l := &Lock{ | 
 | 		Name:       dep.Name, | 
 | 		Repository: dep.Repository, | 
 | 	} | 
 |  | 
 | 	return l | 
 | } | 
 |  | 
 | // NewLockfile is used to create an instance of Lockfile. | 
 | // TODO remove | 
 | func NewLockfile(ds, tds Dependencies, hash string) (*Lockfile, error) { | 
 | 	lf := &Lockfile{ | 
 | 		Hash:       hash, | 
 | 		Updated:    time.Now(), | 
 | 		Imports:    make([]*Lock, len(ds)), | 
 | 		DevImports: make([]*Lock, len(tds)), | 
 | 	} | 
 |  | 
 | 	for i := 0; i < len(ds); i++ { | 
 | 		lf.Imports[i] = LockFromDependency(ds[i]) | 
 | 	} | 
 |  | 
 | 	sort.Sort(lf.Imports) | 
 |  | 
 | 	var found bool | 
 | 	for i := 0; i < len(tds); i++ { | 
 | 		found = false | 
 | 		for ii := 0; ii < len(ds); ii++ { | 
 | 			if ds[ii].Name == tds[i].Name { | 
 | 				found = true | 
 | 				if ds[ii].ConstraintsEq(*tds[i]) { | 
 | 					return &Lockfile{}, fmt.Errorf("Generating lock produced conflicting versions of %s. import (%s), testImport (%s)", tds[i].Name, ds[ii].GetConstraint(), tds[i].GetConstraint()) | 
 | 				} | 
 | 				break | 
 | 			} | 
 | 		} | 
 | 		if !found { | 
 | 			lf.DevImports[i] = LockFromDependency(tds[i]) | 
 | 		} | 
 | 	} | 
 |  | 
 | 	sort.Sort(lf.DevImports) | 
 |  | 
 | 	return lf, nil | 
 | } | 
 |  | 
 | // LockfileFromMap takes a map of dependencies and generates a lock Lockfile instance. | 
 | // TODO remove | 
 | func LockfileFromMap(ds map[string]*Dependency, hash string) *Lockfile { | 
 | 	lf := &Lockfile{ | 
 | 		Hash:    hash, | 
 | 		Updated: time.Now(), | 
 | 		Imports: make([]*Lock, len(ds)), | 
 | 	} | 
 |  | 
 | 	i := 0 | 
 | 	for name, dep := range ds { | 
 | 		lf.Imports[i] = LockFromDependency(dep) | 
 | 		lf.Imports[i].Name = name | 
 | 		i++ | 
 | 	} | 
 |  | 
 | 	sort.Sort(lf.Imports) | 
 |  | 
 | 	return lf | 
 | } |