-
Notifications
You must be signed in to change notification settings - Fork 2
/
Copy pathextract.go
142 lines (125 loc) · 2.96 KB
/
extract.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
package getdoc
import (
"bytes"
"context"
"path"
"sync"
"github.com/go-faster/errors"
"golang.org/x/sync/errgroup"
"github.com/gotd/getdoc/dl"
)
// Downloader abstracts documentation fetching.
type Downloader interface {
Get(ctx context.Context, layer int, key string) ([]byte, error)
}
// Extract uses Downloader to extract documentation.
func Extract(ctx context.Context, d Downloader) (*Doc, error) {
return ExtractLayer(ctx, dl.NoLayer, d)
}
type Extractor struct {
d Downloader
layer int
workers int
doc *Doc
mux sync.Mutex
}
func newExtractor(layer, workers int, d Downloader) *Extractor {
return &Extractor{
d: d,
layer: layer,
workers: workers,
}
}
func (e *Extractor) consume(ctx context.Context, layer int, category, key string) error {
data, err := e.d.Get(ctx, layer, path.Join(category, key))
if err != nil {
return errors.Wrapf(err, "get")
}
e.mux.Lock()
defer e.mux.Unlock()
reader := bytes.NewReader(data)
switch category {
case CategoryConstructor:
t, err := ParseConstructor(reader)
if err != nil {
return errors.Wrap(err, "parse constructor")
}
e.doc.Constructors[t.Name] = *t
case CategoryType:
t, err := ParseType(reader)
if err != nil {
return errors.Wrap(err, "parse type")
}
e.doc.Types[t.Name] = *t
case CategoryMethod:
t, err := ParseMethod(reader)
if err != nil {
return errors.Wrap(err, "parse method")
}
e.doc.Methods[t.Name] = *t
for _, er := range t.Errors {
e.doc.Errors[er.Type] = er
}
}
return nil
}
func (e *Extractor) Extract(ctx context.Context) (*Doc, error) {
data, err := e.d.Get(ctx, e.layer, "schema")
if err != nil {
return nil, errors.Wrap(err, "get schema")
}
index, err := ParseIndex(bytes.NewReader(data))
if err != nil {
return nil, errors.Wrap(err, "parse index")
}
e.doc = &Doc{
Index: *index,
Methods: map[string]Method{},
Types: map[string]Type{},
Constructors: map[string]Constructor{},
Errors: map[string]Error{},
}
type Job struct {
Category, Key string
}
jobs := make(chan Job, e.workers)
g, ctx := errgroup.WithContext(ctx)
for i := 0; i < e.workers; i++ {
g.Go(func() error {
for {
select {
case <-ctx.Done():
return nil
case job, ok := <-jobs:
if !ok {
return nil
}
if err := e.consume(ctx, e.layer, job.Category, job.Key); err != nil {
return errors.Wrap(err, "consume")
}
}
}
})
}
g.Go(func() error {
defer close(jobs)
for _, category := range index.Categories {
for _, v := range category.Values {
select {
case jobs <- Job{category.Name, v}:
case <-ctx.Done():
return ctx.Err()
}
}
}
return nil
})
if err := g.Wait(); err != nil {
return nil, errors.Wrap(err, "wait")
}
return e.doc, nil
}
// ExtractLayer uses Downloader to extract documentation of specified layer.
func ExtractLayer(ctx context.Context, layer int, d Downloader) (*Doc, error) {
return newExtractor(layer, 64, d).Extract(ctx)
}