package capability import ( "bytes" "errors" "fmt" "strings" ) var ( // ErrArgumentsRequired is returned if no arguments are giving with a // capability that requires arguments ErrArgumentsRequired = errors.New("arguments required") // ErrArguments is returned if arguments are given with a capabilities that // not supports arguments ErrArguments = errors.New("arguments not allowed") // ErrEmtpyArgument is returned when an empty value is given ErrEmtpyArgument = errors.New("empty argument") // ErrMultipleArguments multiple argument given to a capabilities that not // support it ErrMultipleArguments = errors.New("multiple arguments not allowed") ) // List represents a list of capabilities type List struct { m map[Capability]*entry sort []string } type entry struct { Name Capability Values []string } // NewList returns a new List of capabilities func NewList() *List { return &List{ m: make(map[Capability]*entry), } } // IsEmpty returns true if the List is empty func (l *List) IsEmpty() bool { return len(l.sort) == 0 } // Decode decodes list of capabilities from raw into the list func (l *List) Decode(raw []byte) error { // git 1.x receive pack used to send a leading space on its // git-receive-pack capabilities announcement. We just trim space to be // tolerant to space changes in different versions. raw = bytes.TrimSpace(raw) if len(raw) == 0 { return nil } for _, data := range bytes.Split(raw, []byte{' '}) { pair := bytes.SplitN(data, []byte{'='}, 2) c := Capability(pair[0]) if len(pair) == 1 { if err := l.Add(c); err != nil { return err } continue } if err := l.Add(c, string(pair[1])); err != nil { return err } } return nil } // Get returns the values for a capability func (l *List) Get(capability Capability) []string { if _, ok := l.m[capability]; !ok { return nil } return l.m[capability].Values } // Set sets a capability removing the previous values func (l *List) Set(capability Capability, values ...string) error { if _, ok := l.m[capability]; ok { delete(l.m, capability) } return l.Add(capability, values...) } // Add adds a capability, values are optional func (l *List) Add(c Capability, values ...string) error { if err := l.validate(c, values); err != nil { return err } if !l.Supports(c) { l.m[c] = &entry{Name: c} l.sort = append(l.sort, c.String()) } if len(values) == 0 { return nil } if known[c] && !multipleArgument[c] && len(l.m[c].Values) > 0 { return ErrMultipleArguments } l.m[c].Values = append(l.m[c].Values, values...) return nil } func (l *List) validateNoEmptyArgs(values []string) error { for _, v := range values { if v == "" { return ErrEmtpyArgument } } return nil } func (l *List) validate(c Capability, values []string) error { if !known[c] { return l.validateNoEmptyArgs(values) } if requiresArgument[c] && len(values) == 0 { return ErrArgumentsRequired } if !requiresArgument[c] && len(values) != 0 { return ErrArguments } if !multipleArgument[c] && len(values) > 1 { return ErrMultipleArguments } return l.validateNoEmptyArgs(values) } // Supports returns true if capability is present func (l *List) Supports(capability Capability) bool { _, ok := l.m[capability] return ok } // Delete deletes a capability from the List func (l *List) Delete(capability Capability) { if !l.Supports(capability) { return } delete(l.m, capability) for i, c := range l.sort { if c != string(capability) { continue } l.sort = append(l.sort[:i], l.sort[i+1:]...) return } } // All returns a slice with all defined capabilities. func (l *List) All() []Capability { var cs []Capability for _, key := range l.sort { cs = append(cs, Capability(key)) } return cs } // String generates the capabilities strings, the capabilities are sorted in // insertion order func (l *List) String() string { var o []string for _, key := range l.sort { cap := l.m[Capability(key)] if len(cap.Values) == 0 { o = append(o, key) continue } for _, value := range cap.Values { o = append(o, fmt.Sprintf("%s=%s", key, value)) } } return strings.Join(o, " ") }