apkdoc @ 33727db8bee991115906d3408145d5e0806a2455

feat: Add initial template renderer for markdown
diff --git a/Makefile b/Makefile
index e3ed19e0cdbc143c4992c36be62a00cf8cd29b11..0c1a1f65bd08e64ab7d5342576176d22fc145fbf 100644
--- a/Makefile
+++ b/Makefile
@@ -4,10 +4,14 @@ GO_BUILD?= go build -v
 
 all: build
 
-run:
+run: tmpl
 	$(GO_RUN) .
 
-build:
+build: tmpl
 	$(GO_BUILD) \
 		-o bin/$(BIN) \
 		.
+
+tmpl:
+	cd ./templates && \
+	qtc *
diff --git a/go.mod b/go.mod
index ccd8d0788985ebaa95e48f2be71765140b01fd93..6ec9ad4c3ea0771cc12e0f0a3feadff13616085b 100644
--- a/go.mod
+++ b/go.mod
@@ -1,3 +1,8 @@
 module git.sr.ht/~gabrielgio/apkdoc
 
 go 1.20
+
+require (
+	github.com/valyala/bytebufferpool v1.0.0 // indirect
+	github.com/valyala/quicktemplate v1.7.0 // indirect
+)
diff --git a/go.sum b/go.sum
new file mode 100644
index 0000000000000000000000000000000000000000..9b4d04803e4106351206e0a5975f5da6faffd9f0
--- /dev/null
+++ b/go.sum
@@ -0,0 +1,21 @@
+github.com/andybalholm/brotli v1.0.2/go.mod h1:loMXtMfwqflxFJPmdbJO0a3KNoPuLBgiu3qAvBg8x/Y=
+github.com/andybalholm/brotli v1.0.3/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig=
+github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
+github.com/klauspost/compress v1.13.4/go.mod h1:8dP1Hq4DHOhN9w426knH3Rhby4rFm6D8eO+e+Dq5Gzg=
+github.com/klauspost/compress v1.13.5/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk=
+github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
+github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
+github.com/valyala/fasthttp v1.30.0/go.mod h1:2rsYD01CKFrjjsvFxx75KlEUNpWNBY9JWD3K/7o2Cus=
+github.com/valyala/quicktemplate v1.7.0 h1:LUPTJmlVcb46OOUY3IeD9DojFpAVbsG+5WFTcjMJzCM=
+github.com/valyala/quicktemplate v1.7.0/go.mod h1:sqKJnoaOF88V07vkO+9FL8fb9uZg/VPSJnLYn+LmLk8=
+github.com/valyala/tcplisten v1.0.0/go.mod h1:T0xQ8SeCZGxckz9qRXTfG43PvQ/mcWh7FwZEA7Ioqkc=
+golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8=
+golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
+golang.org/x/net v0.0.0-20210510120150-4163338589ed/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
+golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210514084401-e8d321eab015/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
+golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
+golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
+golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
diff --git a/main.go b/main.go
index 9e64dbb8cf255a51a31e830010f39b975e214188..cfc56c4e483d0a58972fda410b6a563f45629d71 100644
--- a/main.go
+++ b/main.go
@@ -6,9 +6,12 @@ 	"bufio"
 	"compress/gzip"
 	"errors"
 	"flag"
-	"fmt"
 	"io"
 	"net/http"
+	"os"
+
+	"git.sr.ht/~gabrielgio/apkdoc/parser"
+	"git.sr.ht/~gabrielgio/apkdoc/templates"
 )
 
 func fechIndex(url string) (io.ReadCloser, error) {
@@ -26,6 +29,7 @@ }
 
 func main() {
 	url := flag.String("url", "", "Url to the APKINDEX.tar.gz")
+	output := flag.String("output", "index.md", "Output path")
 	flag.Parse()
 
 	tarStream, err := fechIndex(*url)
@@ -55,13 +59,13 @@ 	}
 
 	s := bufio.NewScanner(tr)
 
-	entries := make([]*Entry, 0)
+	entries := make([]*parser.Entry, 0)
 	lines := make([]string, 0)
 
 	for s.Scan() {
 		l := s.Text()
 		if l == "" {
-			entry := Parse(lines)
+			entry := parser.Parse(lines)
 			entries = append(entries, entry)
 			lines = make([]string, 0)
 		} else {
@@ -69,7 +73,10 @@ 			lines = append(lines, l)
 		}
 	}
 
-	for _, e := range entries {
-		fmt.Printf("%+v\n", e)
+	file, err := os.Create(*output)
+	if err != nil {
+		panic("Error opening output file: " + err.Error())
 	}
+
+	templates.WriteMarkdownTemplate(file, entries)
 }
diff --git a/parser.go b/parser/parser.go
rename from parser.go
rename to parser/parser.go
index 998df918ed8f8749db4ccde332ca1c3b61317405..8996b8cd81c06890f565e445197e6a8fbc4db895 100644
--- a/parser.go
+++ b/parser/parser.go
@@ -1,4 +1,4 @@
-package main
+package parser
 
 import (
 	"strconv"
@@ -10,10 +10,11 @@ type (
 	// https://wiki.alpinelinux.org/wiki/Apk_spec
 	Entry struct {
 		Checksum         string     // C
+		Version          string     // V
 		Name             string     // P
 		Architecture     *string    // A
 		PackageSize      int        // S
-		InstalledSize    *int       // I
+		InstalledSize    int        // I
 		Description      string     // T
 		Url              string     // U
 		License          string     // L
@@ -49,6 +50,8 @@ 		r, c := split(line)
 		switch r {
 		case "C":
 			entry.Checksum = c
+		case "V":
+			entry.Version = c
 		case "P":
 			entry.Name = c
 		case "A":
@@ -56,7 +59,7 @@ 			entry.Architecture = &c
 		case "S":
 			entry.PackageSize = toInt(c)
 		case "I":
-			entry.InstalledSize = ptr(toInt(c))
+			entry.InstalledSize = toInt(c)
 		case "T":
 			entry.Description = c
 		case "U":
diff --git a/templates/index.md.qtpl b/templates/index.md.qtpl
new file mode 100644
index 0000000000000000000000000000000000000000..879795a6f96627cd2575cb99092d6718be4c4486
--- /dev/null
+++ b/templates/index.md.qtpl
@@ -0,0 +1,33 @@
+{% import "git.sr.ht/~gabrielgio/apkdoc/parser" %}
+{% import "strings" %}
+
+{% code 
+func reduce(lines []string) string {
+    return strings.Join(lines, " ")
+}
+%}
+
+{% func MarkdownTemplate(entries []*parser.Entry) %}
+# Apks Alpine 3.18
+{% for _, e := range entries %}
+## {%s e.Name %}
+
+**Version**: {%s e.Version%}
+**Description**: {%s e.Description%}
+{% if e.Maintainer != nil %}**Maintainer**: {%s *e.Maintainer%}{% endif %}
+**Checksum**:{%s e.Checksum%}
+{% if e.Architecture != nil %}**Architecture**: {%s *e.Architecture%}{% endif %}
+**Size**: {%d e.PackageSize%}B
+**Installed size**: {%d e.InstalledSize%}
+**Url**: {%s e.Url%}
+**License**: {%s e.License%}
+{% if e.Origin != nil %}**Origin**: {%s *e.Origin%}{% endif %}
+{% if e.BuildTime != nil %}**Build time**: {%s e.BuildTime.String() %}{% endif %}
+{% if e.Commit != nil %}**Commit**: {%s *e.Commit %}{% endif %}
+{% if e.ProviderPriority != nil %}**Provider Priority**: {%d *e.ProviderPriority %}{% endif %}
+{% if len(e.Dependencies) > 0 %}**Dependencies:**: {%s reduce(e.Dependencies) %}{% endif %}
+{% if len(e.Provides) > 0 %}**Provides:**: {%s reduce(e.Provides) %}{% endif %}
+{% if len(e.InstallIf) > 0 %}**Install if:**: {%s reduce(e.InstallIf) %}{% endif %}
+{% endfor %}
+
+{% endfunc %}
diff --git a/templates/index.md.qtpl.go b/templates/index.md.qtpl.go
new file mode 100644
index 0000000000000000000000000000000000000000..af3d34a5d9a07cdd2e9a370ac306523268c03092
--- /dev/null
+++ b/templates/index.md.qtpl.go
@@ -0,0 +1,215 @@
+// Code generated by qtc from "index.md.qtpl". DO NOT EDIT.
+// See https://github.com/valyala/quicktemplate for details.
+
+//line index.md.qtpl:1
+package templates
+
+//line index.md.qtpl:1
+import "git.sr.ht/~gabrielgio/apkdoc/parser"
+
+//line index.md.qtpl:2
+import "strings"
+
+//line index.md.qtpl:4
+import (
+	qtio422016 "io"
+
+	qt422016 "github.com/valyala/quicktemplate"
+)
+
+//line index.md.qtpl:4
+var (
+	_ = qtio422016.Copy
+	_ = qt422016.AcquireByteBuffer
+)
+
+//line index.md.qtpl:5
+func reduce(lines []string) string {
+	return strings.Join(lines, " ")
+}
+
+//line index.md.qtpl:10
+func StreamMarkdownTemplate(qw422016 *qt422016.Writer, entries []*parser.Entry) {
+//line index.md.qtpl:10
+	qw422016.N().S(`
+# Apks Alpine 3.18
+`)
+//line index.md.qtpl:12
+	for _, e := range entries {
+//line index.md.qtpl:12
+		qw422016.N().S(`
+## `)
+//line index.md.qtpl:13
+		qw422016.E().S(e.Name)
+//line index.md.qtpl:13
+		qw422016.N().S(`
+
+**Version**: `)
+//line index.md.qtpl:15
+		qw422016.E().S(e.Version)
+//line index.md.qtpl:15
+		qw422016.N().S(`
+**Description**: `)
+//line index.md.qtpl:16
+		qw422016.E().S(e.Description)
+//line index.md.qtpl:16
+		qw422016.N().S(`
+`)
+//line index.md.qtpl:17
+		if e.Maintainer != nil {
+//line index.md.qtpl:17
+			qw422016.N().S(`**Maintainer**: `)
+//line index.md.qtpl:17
+			qw422016.E().S(*e.Maintainer)
+//line index.md.qtpl:17
+		}
+//line index.md.qtpl:17
+		qw422016.N().S(`
+**Checksum**:`)
+//line index.md.qtpl:18
+		qw422016.E().S(e.Checksum)
+//line index.md.qtpl:18
+		qw422016.N().S(`
+`)
+//line index.md.qtpl:19
+		if e.Architecture != nil {
+//line index.md.qtpl:19
+			qw422016.N().S(`**Architecture**: `)
+//line index.md.qtpl:19
+			qw422016.E().S(*e.Architecture)
+//line index.md.qtpl:19
+		}
+//line index.md.qtpl:19
+		qw422016.N().S(`
+**Size**: `)
+//line index.md.qtpl:20
+		qw422016.N().D(e.PackageSize)
+//line index.md.qtpl:20
+		qw422016.N().S(`B
+**Installed size**: `)
+//line index.md.qtpl:21
+		qw422016.N().D(e.InstalledSize)
+//line index.md.qtpl:21
+		qw422016.N().S(`
+**Url**: `)
+//line index.md.qtpl:22
+		qw422016.E().S(e.Url)
+//line index.md.qtpl:22
+		qw422016.N().S(`
+**License**: `)
+//line index.md.qtpl:23
+		qw422016.E().S(e.License)
+//line index.md.qtpl:23
+		qw422016.N().S(`
+`)
+//line index.md.qtpl:24
+		if e.Origin != nil {
+//line index.md.qtpl:24
+			qw422016.N().S(`**Origin**: `)
+//line index.md.qtpl:24
+			qw422016.E().S(*e.Origin)
+//line index.md.qtpl:24
+		}
+//line index.md.qtpl:24
+		qw422016.N().S(`
+`)
+//line index.md.qtpl:25
+		if e.BuildTime != nil {
+//line index.md.qtpl:25
+			qw422016.N().S(`**Build time**: `)
+//line index.md.qtpl:25
+			qw422016.E().S(e.BuildTime.String())
+//line index.md.qtpl:25
+		}
+//line index.md.qtpl:25
+		qw422016.N().S(`
+`)
+//line index.md.qtpl:26
+		if e.Commit != nil {
+//line index.md.qtpl:26
+			qw422016.N().S(`**Commit**: `)
+//line index.md.qtpl:26
+			qw422016.E().S(*e.Commit)
+//line index.md.qtpl:26
+		}
+//line index.md.qtpl:26
+		qw422016.N().S(`
+`)
+//line index.md.qtpl:27
+		if e.ProviderPriority != nil {
+//line index.md.qtpl:27
+			qw422016.N().S(`**Provider Priority**: `)
+//line index.md.qtpl:27
+			qw422016.N().D(*e.ProviderPriority)
+//line index.md.qtpl:27
+		}
+//line index.md.qtpl:27
+		qw422016.N().S(`
+`)
+//line index.md.qtpl:28
+		if len(e.Dependencies) > 0 {
+//line index.md.qtpl:28
+			qw422016.N().S(`**Dependencies:**: `)
+//line index.md.qtpl:28
+			qw422016.E().S(reduce(e.Dependencies))
+//line index.md.qtpl:28
+		}
+//line index.md.qtpl:28
+		qw422016.N().S(`
+`)
+//line index.md.qtpl:29
+		if len(e.Provides) > 0 {
+//line index.md.qtpl:29
+			qw422016.N().S(`**Provides:**: `)
+//line index.md.qtpl:29
+			qw422016.E().S(reduce(e.Provides))
+//line index.md.qtpl:29
+		}
+//line index.md.qtpl:29
+		qw422016.N().S(`
+`)
+//line index.md.qtpl:30
+		if len(e.InstallIf) > 0 {
+//line index.md.qtpl:30
+			qw422016.N().S(`**Install if:**: `)
+//line index.md.qtpl:30
+			qw422016.E().S(reduce(e.InstallIf))
+//line index.md.qtpl:30
+		}
+//line index.md.qtpl:30
+		qw422016.N().S(`
+`)
+//line index.md.qtpl:31
+	}
+//line index.md.qtpl:31
+	qw422016.N().S(`
+
+`)
+//line index.md.qtpl:33
+}
+
+//line index.md.qtpl:33
+func WriteMarkdownTemplate(qq422016 qtio422016.Writer, entries []*parser.Entry) {
+//line index.md.qtpl:33
+	qw422016 := qt422016.AcquireWriter(qq422016)
+//line index.md.qtpl:33
+	StreamMarkdownTemplate(qw422016, entries)
+//line index.md.qtpl:33
+	qt422016.ReleaseWriter(qw422016)
+//line index.md.qtpl:33
+}
+
+//line index.md.qtpl:33
+func MarkdownTemplate(entries []*parser.Entry) string {
+//line index.md.qtpl:33
+	qb422016 := qt422016.AcquireByteBuffer()
+//line index.md.qtpl:33
+	WriteMarkdownTemplate(qb422016, entries)
+//line index.md.qtpl:33
+	qs422016 := string(qb422016.B)
+//line index.md.qtpl:33
+	qt422016.ReleaseByteBuffer(qb422016)
+//line index.md.qtpl:33
+	return qs422016
+//line index.md.qtpl:33
+}