1package capability
2
3import (
4	"bytes"
5	"errors"
6	"fmt"
7	"strings"
8)
9
10var (
11	// ErrArgumentsRequired is returned if no arguments are giving with a
12	// capability that requires arguments
13	ErrArgumentsRequired = errors.New("arguments required")
14	// ErrArguments is returned if arguments are given with a capabilities that
15	// not supports arguments
16	ErrArguments = errors.New("arguments not allowed")
17	// ErrEmptyArgument is returned when an empty value is given
18	ErrEmptyArgument = errors.New("empty argument")
19	// ErrMultipleArguments multiple argument given to a capabilities that not
20	// support it
21	ErrMultipleArguments = errors.New("multiple arguments not allowed")
22)
23
24// List represents a list of capabilities
25type List struct {
26	m    map[Capability]*entry
27	sort []string
28}
29
30type entry struct {
31	Name   Capability
32	Values []string
33}
34
35// NewList returns a new List of capabilities
36func NewList() *List {
37	return &List{
38		m: make(map[Capability]*entry),
39	}
40}
41
42// IsEmpty returns true if the List is empty
43func (l *List) IsEmpty() bool {
44	return len(l.sort) == 0
45}
46
47// Decode decodes list of capabilities from raw into the list
48func (l *List) Decode(raw []byte) error {
49	// git 1.x receive pack used to send a leading space on its
50	// git-receive-pack capabilities announcement. We just trim space to be
51	// tolerant to space changes in different versions.
52	raw = bytes.TrimSpace(raw)
53
54	if len(raw) == 0 {
55		return nil
56	}
57
58	for _, data := range bytes.Split(raw, []byte{' '}) {
59		pair := bytes.SplitN(data, []byte{'='}, 2)
60
61		c := Capability(pair[0])
62		if len(pair) == 1 {
63			if err := l.Add(c); err != nil {
64				return err
65			}
66
67			continue
68		}
69
70		if err := l.Add(c, string(pair[1])); err != nil {
71			return err
72		}
73	}
74
75	return nil
76}
77
78// Get returns the values for a capability
79func (l *List) Get(capability Capability) []string {
80	if _, ok := l.m[capability]; !ok {
81		return nil
82	}
83
84	return l.m[capability].Values
85}
86
87// Set sets a capability removing the previous values
88func (l *List) Set(capability Capability, values ...string) error {
89	delete(l.m, capability)
90	return l.Add(capability, values...)
91}
92
93// Add adds a capability, values are optional
94func (l *List) Add(c Capability, values ...string) error {
95	if err := l.validate(c, values); err != nil {
96		return err
97	}
98
99	if !l.Supports(c) {
100		l.m[c] = &entry{Name: c}
101		l.sort = append(l.sort, c.String())
102	}
103
104	if len(values) == 0 {
105		return nil
106	}
107
108	if known[c] && !multipleArgument[c] && len(l.m[c].Values) > 0 {
109		return ErrMultipleArguments
110	}
111
112	l.m[c].Values = append(l.m[c].Values, values...)
113	return nil
114}
115
116func (l *List) validateNoEmptyArgs(values []string) error {
117	for _, v := range values {
118		if v == "" {
119			return ErrEmptyArgument
120		}
121	}
122	return nil
123}
124
125func (l *List) validate(c Capability, values []string) error {
126	if !known[c] {
127		return l.validateNoEmptyArgs(values)
128	}
129	if requiresArgument[c] && len(values) == 0 {
130		return ErrArgumentsRequired
131	}
132
133	if !requiresArgument[c] && len(values) != 0 {
134		return ErrArguments
135	}
136
137	if !multipleArgument[c] && len(values) > 1 {
138		return ErrMultipleArguments
139	}
140	return l.validateNoEmptyArgs(values)
141}
142
143// Supports returns true if capability is present
144func (l *List) Supports(capability Capability) bool {
145	_, ok := l.m[capability]
146	return ok
147}
148
149// Delete deletes a capability from the List
150func (l *List) Delete(capability Capability) {
151	if !l.Supports(capability) {
152		return
153	}
154
155	delete(l.m, capability)
156	for i, c := range l.sort {
157		if c != string(capability) {
158			continue
159		}
160
161		l.sort = append(l.sort[:i], l.sort[i+1:]...)
162		return
163	}
164}
165
166// All returns a slice with all defined capabilities.
167func (l *List) All() []Capability {
168	var cs []Capability
169	for _, key := range l.sort {
170		cs = append(cs, Capability(key))
171	}
172
173	return cs
174}
175
176// String generates the capabilities strings, the capabilities are sorted in
177// insertion order
178func (l *List) String() string {
179	var o []string
180	for _, key := range l.sort {
181		cap := l.m[Capability(key)]
182		if len(cap.Values) == 0 {
183			o = append(o, key)
184			continue
185		}
186
187		for _, value := range cap.Values {
188			o = append(o, fmt.Sprintf("%s=%s", key, value))
189		}
190	}
191
192	return strings.Join(o, " ")
193}
194