Open-CMSIS-Pack/cpackget

View on GitHub
cmd/xml/pidx.go

Summary

Maintainability
A
0 mins
Test Coverage
A
92%
/* SPDX-License-Identifier: Apache-2.0 */
/* Copyright Contributors to the cpackget project. */

package xml

import (
    "encoding/xml"
    "path"
    "path/filepath"
    "strings"

    errs "github.com/open-cmsis-pack/cpackget/cmd/errors"
    "github.com/open-cmsis-pack/cpackget/cmd/utils"
    log "github.com/sirupsen/logrus"
)

var (
    PdscIndexNotFound = -1
)

// PidxXML maps the PIDX file format.
// Ref: https://github.com/ARM-software/CMSIS_5/blob/develop/CMSIS/Utilities/PackIndex.xsd
type PidxXML struct {
    XMLName       xml.Name `xml:"index"`
    SchemaVersion string   `xml:"schemaVersion,attr"`
    Vendor        string   `xml:"vendor"`
    URL           string   `xml:"url"`

    Pindex struct {
        XMLName xml.Name  `xml:"pindex"`
        Pdscs   []PdscTag `xml:"pdsc"`
    } `xml:"pindex"`

    pdscList map[string][]PdscTag
    fileName string
}

// PdscTag maps a <pdsc> tag that goes in PIDX files.
type PdscTag struct {
    XMLName xml.Name `xml:"pdsc"`
    Vendor  string   `xml:"vendor,attr"`
    URL     string   `xml:"url,attr"`
    Name    string   `xml:"name,attr"`
    Version string   `xml:"version,attr"`
}

// NewPidxXML creates a new instance of the PidxXML struct.
func NewPidxXML(fileName string) *PidxXML {
    log.Debugf("Initializing PidxXML object for \"%s\"", fileName)
    p := new(PidxXML)
    p.fileName = fileName
    return p
}

// AddPdsc takes in a PdscTag and add it to the <pindex> tag.
func (p *PidxXML) AddPdsc(pdsc PdscTag) error {
    log.Debugf("Adding pdsc tag \"%s\" to \"%s\"", pdsc, p.fileName)
    if p.HasPdsc(pdsc) != PdscIndexNotFound {
        return errs.ErrPdscEntryExists
    }

    key := pdsc.Key()
    p.pdscList[key] = append(p.pdscList[key], pdsc)
    return nil
}

// RemovePdsc takes in a PdscTag and remove it from the <pindex> tag.
func (p *PidxXML) RemovePdsc(pdsc PdscTag) error {
    log.Debugf("Removing pdsc tag \"%s\" from \"%s\"", pdsc, p.fileName)

    // removeInfo serves as a helper to identify which pdsc tags need removal
    // key is mandatory pdscTag.Key() formatted as Vendor.Pack[.x.y.z]
    // index is the index of the pdsc tags available for Vendor.Pack.x.y.z,
    type removeInfo struct {
        key   string
        index int
    }

    toRemove := []removeInfo{}

    if pdsc.Version != "" {
        if index := p.HasPdsc(pdsc); index != PdscIndexNotFound {
            toRemove = append(toRemove, removeInfo{
                key:   pdsc.Key(),
                index: index,
            })
        }
    } else {
        // Version is omitted, search all versions
        targetKey := pdsc.Key()
        for key := range p.pdscList {
            if strings.Contains(key, targetKey) {
                tags := p.pdscList[key]
                index := PdscIndexNotFound
                for i, tag := range tags {
                    if tag.URL == pdsc.URL {
                        index = i
                        break
                    }
                }
                if index != PdscIndexNotFound {
                    toRemove = append(toRemove, removeInfo{
                        key:   key,
                        index: index,
                    })
                }
            }
        }
    }

    if len(toRemove) == 0 {
        return errs.ErrPdscEntryNotFound
    }

    for _, info := range toRemove {
        log.Debugf("Removing \"%v:%d\"", info.key, info.index)
        p.pdscList[info.key] = append(p.pdscList[info.key][:info.index], p.pdscList[info.key][info.index+1:]...)
        if len(p.pdscList[info.key]) == 0 {
            delete(p.pdscList, info.key)
        }
    }

    return nil
}

// HasPdsc tells whether of not pdsc is already present in this pidx file.
// It returns the index of the matching pdsc tag, or -1 if not found
func (p *PidxXML) HasPdsc(pdsc PdscTag) int {
    index := PdscIndexNotFound
    if tags, found := p.pdscList[pdsc.Key()]; found {
        for i, tag := range tags {
            if tag.URL == pdsc.URL {
                index = i
                break
            }
        }
    }

    log.Debugf("Checking if pidx \"%s\" contains \"%s (%s)\": %d", p.fileName, pdsc.Key(), pdsc.URL, index)
    return index
}

// ListPdscTags returns a map of PdscTags in the pidx document
func (p *PidxXML) ListPdscTags() []PdscTag {
    tags := []PdscTag{}
    for _, pdscTags := range p.pdscList {
        tags = append(tags, pdscTags...)
    }
    return tags
}

// FindPdscTags takes in a sample pdscTag and returns the actual PDSC tag inside this PidxXML.
func (p *PidxXML) FindPdscTags(pdsc PdscTag) []PdscTag {
    log.Debugf("Searching for pdsc \"%s\"", pdsc.Key())
    if pdsc.Version != "" {
        foundTags := p.pdscList[pdsc.Key()]
        log.Debugf("\"%s\" contains %d pdsc tag(s) for \"%s\"", p.fileName, len(foundTags), pdsc.Key())
        return foundTags
    }

    // No version, means "Vendor.Pack", so we need
    // to find matching tags that start with "Vendor.Pack",
    // and there might be many
    targetKey := pdsc.Key()
    foundTags := []PdscTag{}
    for key := range p.pdscList {
        if strings.Contains(key, targetKey) {
            foundTags = append(foundTags, p.pdscList[key]...)
        }
    }

    log.Debugf("\"%s\" contains %d pdsc tag(s) for \"%s\"", p.fileName, len(foundTags), pdsc.Key())
    return foundTags
}

// Read reads FileName into this PidxXML struct and allocates memory for all PDSC tags.
func (p *PidxXML) Read() error {
    log.Debugf("Reading pidx from file \"%s\"", p.fileName)

    p.pdscList = make(map[string][]PdscTag)

    // Create a new empty l
    if !utils.FileExists(p.fileName) {
        log.Debugf("\"%v\" not found. Creating a new one.", p.fileName)
        p.SchemaVersion = "1.1.0"
        vendorName := ""
        if p.URL == "" {
            vendorName = "local_repository.pidx"
        } else {
            vendorName = path.Base(p.fileName)
        }
        p.Vendor = strings.TrimSuffix(vendorName, filepath.Ext(vendorName))
        return p.Write()
    }

    if err := utils.ReadXML(p.fileName, p); err != nil {
        return err
    }

    for _, pdsc := range p.Pindex.Pdscs {
        key := pdsc.Key()
        log.Debugf("Registring \"%s\"", key)
        p.pdscList[key] = append(p.pdscList[key], pdsc)
    }

    // truncate Pindex.Pdscs
    p.Pindex.Pdscs = p.Pindex.Pdscs[:0]

    return nil
}

// Save saves this PidxXML struct into its fileName.
func (p *PidxXML) Write() error {
    log.Debugf("Writing pidx file to \"%s\"", p.fileName)

    // Use p.pdscList as the main source of pdsc tags
    for _, pdscs := range p.pdscList {
        p.Pindex.Pdscs = append(p.Pindex.Pdscs, pdscs...)
    }

    err := utils.WriteXML(p.fileName, p)

    // Truncate Pindex.Pdscs in case a new Write is requested
    p.Pindex.Pdscs = p.Pindex.Pdscs[:0]

    return err
}

// Key returns this pdscTag unique key.
func (p *PdscTag) Key() string {
    return p.Vendor + "." + p.Name + "." + p.Version
}

// YamlPackId formats the packId as specified in
// https://github.com/Open-CMSIS-Pack/devtools/blob/tools/toolbox/0.10.0/tools/projmgr/docs/Manual/YML-Format.md#pack-name-conventions
func (p *PdscTag) YamlPackID() string {
    return p.Vendor + "::" + p.Name + "@" + p.Version
}

// PackURL constructs a URL of a pack using the tag's attributes.
func (p *PdscTag) PackURL() string {
    return p.URL + p.Key() + ".pack"
}