| // Package yaml provides the ability to work with glide.yaml files. |
| package yaml |
| |
| import ( |
| "fmt" |
| "reflect" |
| "strings" |
| |
| "github.com/Masterminds/glide/util" |
| "github.com/Masterminds/vcs" |
| "gopkg.in/yaml.v2" |
| ) |
| |
| // FromYaml takes a yaml string and converts it to a Config instance. |
| func FromYaml(yml string) (*Config, error) { |
| c := &Config{} |
| err := yaml.Unmarshal([]byte(yml), &c) |
| if err != nil { |
| return nil, err |
| } |
| |
| // The ref property is for the legacy yaml file structure. |
| // This sets the currect version to the ref if a version isn't |
| // already set. |
| for _, v := range c.Imports { |
| if v.Reference == "" && v.Ref != "" { |
| v.Reference = v.Ref |
| } |
| v.Ref = "" |
| |
| // Make sure only legitimate VCS are listed. |
| v.VcsType = filterVcsType(v.VcsType) |
| |
| // Get the root name for the package |
| o := v.Name |
| v.Name = util.GetRootFromPackage(v.Name) |
| subpkg := strings.TrimPrefix(o, v.Name) |
| if len(subpkg) > 0 && subpkg != o { |
| v.Subpackages = append(v.Subpackages, strings.TrimPrefix(subpkg, "/")) |
| } |
| } |
| for _, v := range c.DevImports { |
| if v.Reference == "" && v.Ref != "" { |
| v.Reference = v.Ref |
| } |
| v.Ref = "" |
| |
| v.VcsType = filterVcsType(v.VcsType) |
| |
| // Get the root name for the package |
| o := v.Name |
| v.Name = util.GetRootFromPackage(v.Name) |
| subpkg := strings.TrimPrefix(o, v.Name) |
| if len(subpkg) > 0 && subpkg != o { |
| v.Subpackages = append(v.Subpackages, subpkg) |
| } |
| } |
| return c, nil |
| } |
| |
| // ToYaml takes a *Config instance and converts it into a yaml string. |
| func ToYaml(cfg *Config) (string, error) { |
| yml, err := yaml.Marshal(&cfg) |
| if err != nil { |
| return "", err |
| } |
| return string(yml), nil |
| } |
| |
| // Config is the top-level configuration object. |
| type Config struct { |
| Parent *Config `yaml:"-"` |
| Name string `yaml:"package"` |
| Imports Dependencies `yaml:"import"` |
| DevImports Dependencies `yaml:"devimport,omitempty"` |
| } |
| |
| // HasDependency returns true if the given name is listed as an import or dev import. |
| func (c *Config) HasDependency(name string) bool { |
| for _, d := range c.Imports { |
| if d.Name == name { |
| return true |
| } |
| } |
| for _, d := range c.DevImports { |
| if d.Name == name { |
| return true |
| } |
| } |
| return false |
| } |
| |
| // HasRecursiveDependency returns true if this config or one of it's parents has this dependency |
| func (c *Config) HasRecursiveDependency(name string) bool { |
| if c.HasDependency(name) == true { |
| return true |
| } else if c.Parent != nil { |
| return c.Parent.HasRecursiveDependency(name) |
| } |
| return false |
| } |
| |
| // GetRoot follows the Parent down to the top node |
| func (c *Config) GetRoot() *Config { |
| if c.Parent != nil { |
| return c.Parent.GetRoot() |
| } |
| return c |
| } |
| |
| // Clone performs a deep clone of the Config instance |
| func (c *Config) Clone() *Config { |
| n := &Config{} |
| n.Name = c.Name |
| n.Imports = c.Imports.Clone() |
| n.DevImports = c.DevImports.Clone() |
| return n |
| } |
| |
| // Dependencies is a collection of Dependency |
| type Dependencies []*Dependency |
| |
| // Dependency describes a package that the present package depends upon. |
| type Dependency struct { |
| Name string `yaml:"package"` |
| Reference string `yaml:"version,omitempty"` |
| Ref string `yaml:"ref,omitempty"` |
| Pin string `yaml:"pin,omitempty"` |
| Repository string `yaml:"repo,omitempty"` |
| VcsType string `yaml:"vcs,omitempty"` |
| Subpackages []string `yaml:"subpackages,omitempty"` |
| Arch []string `yaml:"arch,omitempty"` |
| Os []string `yaml:"os,omitempty"` |
| UpdateAsVendored bool `yaml:"-"` |
| } |
| |
| // GetRepo retrieves a Masterminds/vcs repo object configured for the root |
| // of the package being retrieved. |
| func (d *Dependency) GetRepo(dest string) (vcs.Repo, error) { |
| |
| // The remote location is either the configured repo or the package |
| // name as an https url. |
| var remote string |
| if len(d.Repository) > 0 { |
| remote = d.Repository |
| } else { |
| remote = "https://" + d.Name |
| } |
| |
| // If the VCS type has a value we try that first. |
| if len(d.VcsType) > 0 && d.VcsType != "None" { |
| switch vcs.Type(d.VcsType) { |
| case vcs.Git: |
| return vcs.NewGitRepo(remote, dest) |
| case vcs.Svn: |
| return vcs.NewSvnRepo(remote, dest) |
| case vcs.Hg: |
| return vcs.NewHgRepo(remote, dest) |
| case vcs.Bzr: |
| return vcs.NewBzrRepo(remote, dest) |
| default: |
| return nil, fmt.Errorf("Unknown VCS type %s set for %s", d.VcsType, d.Name) |
| } |
| } |
| |
| // When no type set we try to autodetect. |
| return vcs.NewRepo(remote, dest) |
| } |
| |
| func (d *Dependency) Clone() *Dependency { |
| return &Dependency{ |
| Name: d.Name, |
| Reference: d.Reference, |
| Pin: d.Pin, |
| Repository: d.Repository, |
| VcsType: d.VcsType, |
| Subpackages: d.Subpackages, |
| Arch: d.Arch, |
| Os: d.Os, |
| UpdateAsVendored: d.UpdateAsVendored, |
| } |
| } |
| |
| // Get a dependency by name |
| func (d Dependencies) Get(name string) *Dependency { |
| for _, dep := range d { |
| if dep.Name == name { |
| return dep |
| } |
| } |
| return nil |
| } |
| |
| func (d Dependencies) Clone() Dependencies { |
| n := make(Dependencies, 0, 1) |
| for _, v := range d { |
| n = append(n, v.Clone()) |
| } |
| return n |
| } |
| |
| // DeDupe cleans up duplicates on a list of dependencies. |
| func (d Dependencies) DeDupe() (Dependencies, error) { |
| checked := map[string]*Dependency{} |
| for _, dep := range d { |
| // The first time we encounter a dependency add it to the list |
| if val, ok := checked[dep.Name]; !ok { |
| checked[dep.Name] = dep |
| } else { |
| // In here we've encountered a dependency for the second time. |
| // Make sure the details are the same or return an error. |
| if dep.Reference != val.Reference { |
| return d, fmt.Errorf("Import %s repeated with different versions '%s' and '%s'", dep.Name, dep.Reference, val.Reference) |
| } |
| if dep.Repository != val.Repository || dep.VcsType != val.VcsType { |
| return d, fmt.Errorf("Import %s repeated with different Repository details", dep.Name) |
| } |
| if !reflect.DeepEqual(dep.Os, val.Os) || !reflect.DeepEqual(dep.Arch, val.Arch) { |
| return d, fmt.Errorf("Import %s repeated with different OS or Architecture filtering", dep.Name) |
| } |
| checked[dep.Name].Subpackages = stringArrayDeDupe(checked[dep.Name].Subpackages, dep.Subpackages...) |
| } |
| } |
| |
| imports := make(Dependencies, 0, 1) |
| for _, dep := range checked { |
| imports = append(imports, dep) |
| } |
| |
| return imports, nil |
| } |
| |
| func stringArrayDeDupe(s []string, items ...string) []string { |
| for _, item := range items { |
| exists := false |
| for _, v := range s { |
| if v == item { |
| exists = true |
| } |
| } |
| if !exists { |
| s = append(s, item) |
| } |
| } |
| return s |
| } |
| |
| func filterVcsType(vcs string) string { |
| switch vcs { |
| case "git", "hg", "bzr", "svn": |
| return vcs |
| case "mercurial": |
| return "hg" |
| case "bazaar": |
| return "bzr" |
| case "subversion": |
| return "svn" |
| default: |
| return "" |
| } |
| } |