1diff --git a/.gitignore b/.gitignore
2new file mode 100644
3index 0000000000000000000000000000000000000000..f331ccd56052fda427b7b1b8b581d13c01147937
4--- /dev/null
5+++ b/.gitignore
6@@ -0,0 +1,2 @@
7+bin/
8+vendor/
9diff --git a/Makefile b/Makefile
10new file mode 100644
11index 0000000000000000000000000000000000000000..e3ed19e0cdbc143c4992c36be62a00cf8cd29b11
12--- /dev/null
13+++ b/Makefile
14@@ -0,0 +1,13 @@
15+BIN?=apkdoc
16+GO_RUN?= go run -v
17+GO_BUILD?= go build -v
18+
19+all: build
20+
21+run:
22+ $(GO_RUN) .
23+
24+build:
25+ $(GO_BUILD) \
26+ -o bin/$(BIN) \
27+ .
28diff --git a/go.mod b/go.mod
29new file mode 100644
30index 0000000000000000000000000000000000000000..ccd8d0788985ebaa95e48f2be71765140b01fd93
31--- /dev/null
32+++ b/go.mod
33@@ -0,0 +1,3 @@
34+module git.sr.ht/~gabrielgio/apkdoc
35+
36+go 1.20
37diff --git a/main.go b/main.go
38new file mode 100644
39index 0000000000000000000000000000000000000000..9e64dbb8cf255a51a31e830010f39b975e214188
40--- /dev/null
41+++ b/main.go
42@@ -0,0 +1,75 @@
43+package main
44+
45+import (
46+ "archive/tar"
47+ "bufio"
48+ "compress/gzip"
49+ "errors"
50+ "flag"
51+ "fmt"
52+ "io"
53+ "net/http"
54+)
55+
56+func fechIndex(url string) (io.ReadCloser, error) {
57+ resp, err := http.Get(url)
58+ if err != nil {
59+ return nil, err
60+ }
61+
62+ if resp.StatusCode != 200 {
63+ return nil, errors.New("Invlid response")
64+ }
65+
66+ return resp.Body, nil
67+}
68+
69+func main() {
70+ url := flag.String("url", "", "Url to the APKINDEX.tar.gz")
71+ flag.Parse()
72+
73+ tarStream, err := fechIndex(*url)
74+ if err != nil {
75+ panic("Error fecthing the index: " + err.Error())
76+ }
77+
78+ defer tarStream.Close()
79+
80+ archive, err := gzip.NewReader(tarStream)
81+ if err != nil {
82+ panic("Error creating gzip reader: " + err.Error())
83+ }
84+
85+ tr := tar.NewReader(archive)
86+
87+ for {
88+ h, err := tr.Next()
89+ if err != nil {
90+ panic("Error reading next tar entry: " + err.Error())
91+ }
92+
93+ if h.FileInfo().Name() == "APKINDEX" {
94+ break
95+ }
96+ }
97+
98+ s := bufio.NewScanner(tr)
99+
100+ entries := make([]*Entry, 0)
101+ lines := make([]string, 0)
102+
103+ for s.Scan() {
104+ l := s.Text()
105+ if l == "" {
106+ entry := Parse(lines)
107+ entries = append(entries, entry)
108+ lines = make([]string, 0)
109+ } else {
110+ lines = append(lines, l)
111+ }
112+ }
113+
114+ for _, e := range entries {
115+ fmt.Printf("%+v\n", e)
116+ }
117+}
118diff --git a/parser.go b/parser.go
119new file mode 100644
120index 0000000000000000000000000000000000000000..998df918ed8f8749db4ccde332ca1c3b61317405
121--- /dev/null
122+++ b/parser.go
123@@ -0,0 +1,85 @@
124+package main
125+
126+import (
127+ "strconv"
128+ "strings"
129+ "time"
130+)
131+
132+type (
133+ // https://wiki.alpinelinux.org/wiki/Apk_spec
134+ Entry struct {
135+ Checksum string // C
136+ Name string // P
137+ Architecture *string // A
138+ PackageSize int // S
139+ InstalledSize *int // I
140+ Description string // T
141+ Url string // U
142+ License string // L
143+ Origin *string // o
144+ Maintainer *string // m
145+ BuildTime *time.Time // t
146+ Commit *string // c
147+ ProviderPriority *int // k
148+ Dependencies []string // D
149+ Provides []string // p
150+ InstallIf []string // i
151+ }
152+)
153+
154+func ptr[T any](v T) *T {
155+ return &v
156+}
157+
158+func split(line string) (string, string) {
159+ parts := strings.SplitN(line, ":", 2)
160+ return parts[0], parts[1]
161+}
162+
163+func toInt(v string) int {
164+ i, _ := strconv.Atoi(v)
165+ return i
166+}
167+
168+func Parse(lines []string) *Entry {
169+ entry := &Entry{}
170+ for _, line := range lines {
171+ r, c := split(line)
172+ switch r {
173+ case "C":
174+ entry.Checksum = c
175+ case "P":
176+ entry.Name = c
177+ case "A":
178+ entry.Architecture = &c
179+ case "S":
180+ entry.PackageSize = toInt(c)
181+ case "I":
182+ entry.InstalledSize = ptr(toInt(c))
183+ case "T":
184+ entry.Description = c
185+ case "U":
186+ entry.Url = c
187+ case "L":
188+ entry.License = c
189+ case "o":
190+ entry.Origin = &c
191+ case "m":
192+ entry.Maintainer = &c
193+ case "t":
194+ entry.BuildTime = ptr(time.Unix(int64(toInt(c)), 0))
195+ case "c":
196+ entry.Commit = &c
197+ case "k":
198+ entry.ProviderPriority = ptr(toInt(c))
199+ case "D":
200+ entry.Dependencies = strings.Split(c, " ")
201+ case "p":
202+ entry.Dependencies = strings.Split(c, " ")
203+ case "i":
204+ entry.Dependencies = strings.Split(c, " ")
205+ }
206+ }
207+ return entry
208+}