1package core
2
3import (
4 "fmt"
5 "reflect"
6 "regexp"
7 "strings"
8
9 "github.com/MichaelMure/git-bug/cache"
10 "github.com/MichaelMure/git-bug/repository"
11 "github.com/pkg/errors"
12)
13
14var ErrImportNorSupported = errors.New("import is not supported")
15var ErrExportNorSupported = errors.New("export is not supported")
16
17var bridgeImpl map[string]reflect.Type
18
19// Bridge is a wrapper around a BridgeImpl that will bind low-level
20// implementation with utility code to provide high-level functions.
21type Bridge struct {
22 Name string
23 repo *cache.RepoCache
24 impl BridgeImpl
25 conf Configuration
26}
27
28// Register will register a new BridgeImpl
29func Register(impl BridgeImpl) {
30 if bridgeImpl == nil {
31 bridgeImpl = make(map[string]reflect.Type)
32 }
33 bridgeImpl[impl.Target()] = reflect.TypeOf(impl)
34}
35
36// Targets return all known bridge implementation target
37func Targets() []string {
38 var result []string
39
40 for key := range bridgeImpl {
41 result = append(result, key)
42 }
43
44 return result
45}
46
47func NewBridge(repo *cache.RepoCache, target string, name string) (*Bridge, error) {
48 implType, ok := bridgeImpl[target]
49 if !ok {
50 return nil, fmt.Errorf("unknown bridge target %v", target)
51 }
52
53 impl := reflect.New(implType).Elem().Interface().(BridgeImpl)
54
55 bridge := &Bridge{
56 Name: name,
57 repo: repo,
58 impl: impl,
59 }
60
61 return bridge, nil
62}
63
64func NewBridgeFullName(repo *cache.RepoCache, fullName string) (*Bridge, error) {
65 target, name, err := splitFullName(fullName)
66 if err != nil {
67 return nil, err
68 }
69
70 return NewBridge(repo, target, name)
71}
72
73func DefaultBridge(repo *cache.RepoCache) (*Bridge, error) {
74 bridges, err := ConfiguredBridges(repo)
75 if err != nil {
76 return nil, err
77 }
78
79 if len(bridges) == 0 {
80 return nil, fmt.Errorf("no configured bridge")
81 }
82
83 if len(bridges) > 1 {
84 return nil, fmt.Errorf("multiple bridge configured")
85 }
86
87 target, name, err := splitFullName(bridges[0])
88 if err != nil {
89 return nil, err
90 }
91
92 return NewBridge(repo, target, name)
93}
94
95func splitFullName(fullName string) (string, string, error) {
96 split := strings.Split(fullName, ".")
97
98 if len(split) != 2 {
99 return "", "", fmt.Errorf("bad bridge fullname: %s", fullName)
100 }
101
102 return split[0], split[1], nil
103}
104
105func ConfiguredBridges(repo repository.RepoCommon) ([]string, error) {
106 configs, err := repo.ReadConfigs("git-bug.bridge.")
107 if err != nil {
108 return nil, errors.Wrap(err, "can't read configured bridges")
109 }
110
111 re, err := regexp.Compile(`git-bug.bridge.([^\.]+\.[^\.]+)`)
112 if err != nil {
113 panic(err)
114 }
115
116 set := make(map[string]interface{})
117
118 for key := range configs {
119 res := re.FindStringSubmatch(key)
120
121 if res == nil {
122 continue
123 }
124
125 set[res[1]] = nil
126 }
127
128 result := make([]string, len(set))
129
130 i := 0
131 for key := range set {
132 result[i] = key
133 i++
134 }
135
136 return result, nil
137}
138
139func RemoveBridge(repo repository.RepoCommon, fullName string) error {
140 re, err := regexp.Compile(`^[^\.]+\.[^\.]+$`)
141 if err != nil {
142 panic(err)
143 }
144
145 if !re.MatchString(fullName) {
146 return fmt.Errorf("bad bridge fullname: %s", fullName)
147 }
148
149 keyPrefix := fmt.Sprintf("git-bug.bridge.%s", fullName)
150 return repo.RmConfigs(keyPrefix)
151}
152
153func (b *Bridge) Configure() error {
154 conf, err := b.impl.Configure(b.repo)
155 if err != nil {
156 return err
157 }
158
159 b.conf = conf
160
161 return b.storeConfig(conf)
162}
163
164func (b *Bridge) storeConfig(conf Configuration) error {
165 for key, val := range conf {
166 storeKey := fmt.Sprintf("git-bug.bridge.%s.%s.%s", b.impl.Target(), b.Name, key)
167
168 err := b.repo.StoreConfig(storeKey, val)
169 if err != nil {
170 return errors.Wrap(err, "error while storing bridge configuration")
171 }
172 }
173
174 return nil
175}
176
177func (b Bridge) getConfig() (Configuration, error) {
178 var err error
179 if b.conf == nil {
180 b.conf, err = b.loadConfig()
181 if err != nil {
182 return nil, err
183 }
184 }
185
186 return b.conf, nil
187}
188
189func (b Bridge) loadConfig() (Configuration, error) {
190 keyPrefix := fmt.Sprintf("git-bug.bridge.%s.%s.", b.impl.Target(), b.Name)
191
192 pairs, err := b.repo.ReadConfigs(keyPrefix)
193 if err != nil {
194 return nil, errors.Wrap(err, "error while reading bridge configuration")
195 }
196
197 result := make(Configuration, len(pairs))
198 for key, value := range pairs {
199 key := strings.TrimPrefix(key, keyPrefix)
200 result[key] = value
201 }
202
203 return result, nil
204}
205
206func (b Bridge) ImportAll() error {
207 importer := b.impl.Importer()
208 if importer == nil {
209 return ErrImportNorSupported
210 }
211
212 conf, err := b.getConfig()
213 if err != nil {
214 return err
215 }
216
217 return b.impl.Importer().ImportAll(b.repo, conf)
218}
219
220func (b Bridge) Import(id string) error {
221 importer := b.impl.Importer()
222 if importer == nil {
223 return ErrImportNorSupported
224 }
225
226 conf, err := b.getConfig()
227 if err != nil {
228 return err
229 }
230
231 return b.impl.Importer().Import(b.repo, conf, id)
232}
233
234func (b Bridge) ExportAll() error {
235 exporter := b.impl.Exporter()
236 if exporter == nil {
237 return ErrExportNorSupported
238 }
239
240 conf, err := b.getConfig()
241 if err != nil {
242 return err
243 }
244
245 return b.impl.Exporter().ExportAll(b.repo, conf)
246}
247
248func (b Bridge) Export(id string) error {
249 exporter := b.impl.Exporter()
250 if exporter == nil {
251 return ErrExportNorSupported
252 }
253
254 conf, err := b.getConfig()
255 if err != nil {
256 return err
257 }
258
259 return b.impl.Exporter().Export(b.repo, conf, id)
260}