bridge.go

  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}