apkdoc @ dd2a1c38a5a9f561d58b58da56d3230fd715fcde

diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000000000000000000000000000000000000..f331ccd56052fda427b7b1b8b581d13c01147937
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,2 @@
+bin/
+vendor/
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000000000000000000000000000000000000..e3ed19e0cdbc143c4992c36be62a00cf8cd29b11
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,13 @@
+BIN?=apkdoc
+GO_RUN?= go run -v
+GO_BUILD?= go build -v
+
+all: build
+
+run:
+	$(GO_RUN) .
+
+build:
+	$(GO_BUILD) \
+		-o bin/$(BIN) \
+		.
diff --git a/go.mod b/go.mod
new file mode 100644
index 0000000000000000000000000000000000000000..ccd8d0788985ebaa95e48f2be71765140b01fd93
--- /dev/null
+++ b/go.mod
@@ -0,0 +1,3 @@
+module git.sr.ht/~gabrielgio/apkdoc
+
+go 1.20
diff --git a/main.go b/main.go
new file mode 100644
index 0000000000000000000000000000000000000000..9e64dbb8cf255a51a31e830010f39b975e214188
--- /dev/null
+++ b/main.go
@@ -0,0 +1,75 @@
+package main
+
+import (
+	"archive/tar"
+	"bufio"
+	"compress/gzip"
+	"errors"
+	"flag"
+	"fmt"
+	"io"
+	"net/http"
+)
+
+func fechIndex(url string) (io.ReadCloser, error) {
+	resp, err := http.Get(url)
+	if err != nil {
+		return nil, err
+	}
+
+	if resp.StatusCode != 200 {
+		return nil, errors.New("Invlid response")
+	}
+
+	return resp.Body, nil
+}
+
+func main() {
+	url := flag.String("url", "", "Url to the APKINDEX.tar.gz")
+	flag.Parse()
+
+	tarStream, err := fechIndex(*url)
+	if err != nil {
+		panic("Error fecthing the index: " + err.Error())
+	}
+
+	defer tarStream.Close()
+
+	archive, err := gzip.NewReader(tarStream)
+	if err != nil {
+		panic("Error creating gzip reader: " + err.Error())
+	}
+
+	tr := tar.NewReader(archive)
+
+	for {
+		h, err := tr.Next()
+		if err != nil {
+			panic("Error reading next tar entry: " + err.Error())
+		}
+
+		if h.FileInfo().Name() == "APKINDEX" {
+			break
+		}
+	}
+
+	s := bufio.NewScanner(tr)
+
+	entries := make([]*Entry, 0)
+	lines := make([]string, 0)
+
+	for s.Scan() {
+		l := s.Text()
+		if l == "" {
+			entry := Parse(lines)
+			entries = append(entries, entry)
+			lines = make([]string, 0)
+		} else {
+			lines = append(lines, l)
+		}
+	}
+
+	for _, e := range entries {
+		fmt.Printf("%+v\n", e)
+	}
+}
diff --git a/parser.go b/parser.go
new file mode 100644
index 0000000000000000000000000000000000000000..998df918ed8f8749db4ccde332ca1c3b61317405
--- /dev/null
+++ b/parser.go
@@ -0,0 +1,85 @@
+package main
+
+import (
+	"strconv"
+	"strings"
+	"time"
+)
+
+type (
+	// https://wiki.alpinelinux.org/wiki/Apk_spec
+	Entry struct {
+		Checksum         string     // C
+		Name             string     // P
+		Architecture     *string    // A
+		PackageSize      int        // S
+		InstalledSize    *int       // I
+		Description      string     // T
+		Url              string     // U
+		License          string     // L
+		Origin           *string    // o
+		Maintainer       *string    // m
+		BuildTime        *time.Time // t
+		Commit           *string    // c
+		ProviderPriority *int       // k
+		Dependencies     []string   // D
+		Provides         []string   // p
+		InstallIf        []string   // i
+	}
+)
+
+func ptr[T any](v T) *T {
+	return &v
+}
+
+func split(line string) (string, string) {
+	parts := strings.SplitN(line, ":", 2)
+	return parts[0], parts[1]
+}
+
+func toInt(v string) int {
+	i, _ := strconv.Atoi(v)
+	return i
+}
+
+func Parse(lines []string) *Entry {
+	entry := &Entry{}
+	for _, line := range lines {
+		r, c := split(line)
+		switch r {
+		case "C":
+			entry.Checksum = c
+		case "P":
+			entry.Name = c
+		case "A":
+			entry.Architecture = &c
+		case "S":
+			entry.PackageSize = toInt(c)
+		case "I":
+			entry.InstalledSize = ptr(toInt(c))
+		case "T":
+			entry.Description = c
+		case "U":
+			entry.Url = c
+		case "L":
+			entry.License = c
+		case "o":
+			entry.Origin = &c
+		case "m":
+			entry.Maintainer = &c
+		case "t":
+			entry.BuildTime = ptr(time.Unix(int64(toInt(c)), 0))
+		case "c":
+			entry.Commit = &c
+		case "k":
+			entry.ProviderPriority = ptr(toInt(c))
+		case "D":
+			entry.Dependencies = strings.Split(c, " ")
+		case "p":
+			entry.Dependencies = strings.Split(c, " ")
+		case "i":
+			entry.Dependencies = strings.Split(c, " ")
+		}
+	}
+	return entry
+}