// Copyright 2015 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. // Package vendorfile is the meta-data file for vendoring. // Round-trips unknown fields. // It will also allow moving the vendor file to new locations. package vendorfile import ( "bytes" "encoding/json" "fmt" "io" "io/ioutil" "sort" ) // Name of the vendor file. const Name = "vendor.json" // File is the structure of the vendor file. type File struct { RootPath string // Import path of vendor folder Comment string Ignore string Package []*Package // all preserves unknown values. all map[string]interface{} } // Package represents each package. type Package struct { field map[string]interface{} // If delete is set to true the package will not be written to the vendor file. Remove bool // If new is set to true the package will be treated as a new package to the file. Add bool // See the vendor spec for definitions. Origin string Path string Tree bool Revision string RevisionTime string Version string VersionExact string ChecksumSHA1 string Comment string } func (pkg *Package) PathOrigin() string { if len(pkg.Origin) > 0 { return pkg.Origin } return pkg.Path } // The following stringer functions are useful for debugging. type packageList []*Package func (list packageList) String() string { buf := &bytes.Buffer{} for _, item := range list { buf.WriteString("\t") buf.WriteString(fmt.Sprintf("(%v) ", item.field)) if item.Remove { buf.WriteString(" X ") } buf.WriteString(item.Path) buf.WriteRune('\n') } buf.WriteRune('\n') return buf.String() } var ( rootPathNames = []string{"rootPath"} packageNames = []string{"package", "Package"} ignoreNames = []string{"ignore"} originNames = []string{"origin"} pathNames = []string{"path", "canonical", "Canonical", "vendor", "Vendor"} treeNames = []string{"tree"} revisionNames = []string{"revision", "Revision", "version", "Version"} revisionTimeNames = []string{"revisionTime", "RevisionTime", "versionTime", "VersionTime"} versionNames = []string{"version"} versionExactNames = []string{"versionExact"} checksumSHA1Names = []string{"checksumSHA1"} commentNames = []string{"comment", "Comment"} ) type vendorPackageSort []interface{} func (vp vendorPackageSort) Len() int { return len(vp) } func (vp vendorPackageSort) Swap(i, j int) { vp[i], vp[j] = vp[j], vp[i] } func (vp vendorPackageSort) Less(i, j int) bool { a := vp[i].(map[string]interface{}) b := vp[j].(map[string]interface{}) aPath, _ := a[pathNames[0]].(string) bPath, _ := b[pathNames[0]].(string) if aPath == bPath { aOrigin, _ := a[originNames[0]].(string) bOrigin, _ := b[originNames[0]].(string) return len(aOrigin) > len(bOrigin) } return aPath < bPath } func setField(fieldObj interface{}, object map[string]interface{}, names []string) { loop: for _, name := range names { raw, found := object[name] if !found { continue } switch field := fieldObj.(type) { default: panic("unknown type") case *string: value, is := raw.(string) if !is { continue loop } *field = value if len(value) != 0 { break loop } case *bool: value, is := raw.(bool) if !is { continue loop } *field = value if value { break loop } } } } func setObject(fieldObj interface{}, object map[string]interface{}, names []string, hideEmpty bool) { switch field := fieldObj.(type) { default: panic("unknown type") case string: for i, name := range names { if i != 0 || (hideEmpty && len(field) == 0) { delete(object, name) continue } object[name] = field } case bool: for i, name := range names { if i != 0 || (hideEmpty && !field) { delete(object, name) continue } object[name] = field } } } // getRawPackageList gets the array of items from all object. func (vf *File) getRawPackageList() []interface{} { var rawPackageList []interface{} for index, name := range packageNames { rawPackageListObject, found := vf.all[name] if !found { continue } if index != 0 { vf.all[packageNames[0]] = rawPackageListObject delete(vf.all, name) } var is bool rawPackageList, is = rawPackageListObject.([]interface{}) if is { break } } return rawPackageList } // toFields moves values from "all" to the field values. func (vf *File) toFields() { setField(&vf.RootPath, vf.all, rootPathNames) setField(&vf.Comment, vf.all, commentNames) setField(&vf.Ignore, vf.all, ignoreNames) rawPackageList := vf.getRawPackageList() vf.Package = make([]*Package, len(rawPackageList)) for index, rawPackage := range rawPackageList { object, is := rawPackage.(map[string]interface{}) if !is { continue } pkg := &Package{} vf.Package[index] = pkg pkg.field = object setField(&pkg.Origin, object, originNames) setField(&pkg.Path, object, pathNames) setField(&pkg.Tree, object, treeNames) setField(&pkg.Revision, object, revisionNames) setField(&pkg.RevisionTime, object, revisionTimeNames) setField(&pkg.Version, object, versionNames) setField(&pkg.VersionExact, object, versionExactNames) setField(&pkg.ChecksumSHA1, object, checksumSHA1Names) setField(&pkg.Comment, object, commentNames) } } // toAll moves values from field values to "all". func (vf *File) toAll() { delete(vf.all, "Tool") setObject(vf.RootPath, vf.all, rootPathNames, true) setObject(vf.Comment, vf.all, commentNames, false) setObject(vf.Ignore, vf.all, ignoreNames, false) rawPackageList := vf.getRawPackageList() setPkgFields := func(pkg *Package) { if pkg.Origin == pkg.Path { pkg.Origin = "" } if pkg.field == nil { pkg.field = make(map[string]interface{}, 10) } setObject(pkg.Origin, pkg.field, originNames, true) setObject(pkg.Path, pkg.field, pathNames, false) setObject(pkg.Tree, pkg.field, treeNames, true) setObject(pkg.Revision, pkg.field, revisionNames, false) setObject(pkg.RevisionTime, pkg.field, revisionTimeNames, true) setObject(pkg.Version, pkg.field, versionNames, true) setObject(pkg.VersionExact, pkg.field, versionExactNames, true) setObject(pkg.ChecksumSHA1, pkg.field, checksumSHA1Names, true) setObject(pkg.Comment, pkg.field, commentNames, true) } for i := len(vf.Package) - 1; i >= 0; i-- { pkg := vf.Package[i] switch { case pkg.Remove: for index, rawObj := range rawPackageList { raw, is := rawObj.(map[string]interface{}) if !is { continue } same := true for key, value := range pkg.field { if raw[key] != value { same = false break } } if same { rawPackageList[index] = nil } } case pkg.Add: setPkgFields(pkg) rawPackageList = append(rawPackageList, pkg.field) default: if pkg.field == nil { pkg.field = make(map[string]interface{}, 10) } delete(pkg.field, "local") delete(pkg.field, "Local") setPkgFields(pkg) } } nextRawPackageList := make([]interface{}, 0, len(rawPackageList)) for _, raw := range rawPackageList { if raw == nil { continue } nextRawPackageList = append(nextRawPackageList, raw) } vf.all[packageNames[0]] = nextRawPackageList } // Marshal the vendor file to the specified writer. // Retains read fields. func (vf *File) Marshal(w io.Writer) error { if vf.all == nil { vf.all = map[string]interface{}{} } vf.toAll() rawList := vf.getRawPackageList() sort.Sort(vendorPackageSort(rawList)) jb, err := json.Marshal(vf.all) if err != nil { return err } buf := &bytes.Buffer{} err = json.Indent(buf, jb, "", "\t") if err != nil { return err } _, err = io.Copy(w, buf) return err } // Unmarshal the vendor file from the specified reader. // Stores internally all fields. func (vf *File) Unmarshal(r io.Reader) error { bb, err := ioutil.ReadAll(r) if err != nil { return err } if vf.all == nil { vf.all = make(map[string]interface{}, 3) } err = json.Unmarshal(bb, &vf.all) if err != nil { return err } vf.toFields() return nil }