cerrado @ 8a2461aa05895cc7828bc9619b50fa5dee5ed1f4

feat: Add config parsing
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000000000000000000000000000000000000..e660fd93d3196215552065b1e63bf6a2f393ed86
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1 @@
+bin/
diff --git a/Makefile b/Makefile
index 7a0e125baadd8ba25b560106a0b1bd91ec30df17..ecf165dce0342209fc2879c732b2eb8d7b06eba0 100644
--- a/Makefile
+++ b/Makefile
@@ -3,3 +3,6 @@ 	go build -o bin/cerrado
 
 run:
 	go run .
+
+test:
+	go test -v --tags=unit ./...
diff --git a/README.md b/README.md
new file mode 100644
index 0000000000000000000000000000000000000000..5a723e55161cbc997e4cbeec226fd7bd9555e258
--- /dev/null
+++ b/README.md
@@ -0,0 +1,3 @@
+# Cerrado
+
+Read only single user mail based forge for git.
diff --git a/config.example.scfg b/config.example.scfg
new file mode 100644
index 0000000000000000000000000000000000000000..eda4f38d14d06608e6447089bbf13c5416092d65
--- /dev/null
+++ b/config.example.scfg
@@ -0,0 +1,22 @@
+scan /srv/git/ {
+    public true
+}
+
+# TBD
+#user admin:iKlvHe1g0UoXE
+#
+#list main {
+#    server smtp.example.com
+#    user admin@admin.com
+#    password 1234567
+#    security tls
+#    authentication plain
+#    default false
+#}
+#
+#repository cerrado {
+#    title Cerrado
+#    description "Self host single person readonly forge"
+#    list main
+#    public true
+#}
diff --git a/go.mod b/go.mod
index cca006e89444dbdcf6c81c9f4f0f9acf6ed0bb6b..bfbe03ad8f3b8502d8d8f9ba9d3967552b1171e1 100644
--- a/go.mod
+++ b/go.mod
@@ -2,4 +2,8 @@ module git.gabrielgio.me/cerrado
 
 go 1.22.2
 
-require golang.org/x/sync v0.7.0 // indirect
+require (
+	git.sr.ht/~emersion/go-scfg v0.0.0-20240128091534-2ae16e782082
+	github.com/google/go-cmp v0.6.0
+	golang.org/x/sync v0.7.0
+)
diff --git a/go.sum b/go.sum
index e8ef4a360a1437536e8a3053e4ffc82fc69ce9c5..a98204454c0ef47546d171a1dada140655d0b45e 100644
--- a/go.sum
+++ b/go.sum
@@ -1,2 +1,8 @@
+git.sr.ht/~emersion/go-scfg v0.0.0-20240128091534-2ae16e782082 h1:9Udx5fm4vRtmgDIBjy2ef5QioHbzpw5oHabbhpAUyEw=
+git.sr.ht/~emersion/go-scfg v0.0.0-20240128091534-2ae16e782082/go.mod h1:ybgvEJTIx5XbaspSviB3KNa6OdPmAZqDoSud7z8fFlw=
+github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
+github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
+github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
 golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M=
 golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
diff --git a/main.go b/main.go
index 7c80564f57dfa106d4a4074f6c4586bc0a9c18ba..ba441fe71b8c6c0ea4aff7a8805d2e4729059e27 100644
--- a/main.go
+++ b/main.go
@@ -2,16 +2,20 @@ package main
 
 import (
 	"context"
+	"encoding/json"
+	"flag"
 	"log/slog"
 	"net/http"
 	"os"
 	"os/signal"
 	"time"
 
+	"git.gabrielgio.me/cerrado/pkg/config"
 	"git.gabrielgio.me/cerrado/pkg/worker"
 )
 
 func main() {
+
 	ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, os.Kill)
 	defer stop()
 	if err := run(ctx); err != nil {
@@ -20,10 +24,36 @@ 	}
 }
 
 func run(ctx context.Context) error {
+	var (
+		configPath = flag.String("config", "config.example.scfg", "File path for the configuration file")
+	)
+
+	flag.Parse()
+
 	mux := http.NewServeMux()
 	mux.HandleFunc("/", func(w http.ResponseWriter, _ *http.Request) {
-		if _, err := w.Write([]byte("Hello world!")); err != nil {
+
+		f, err := os.Open(*configPath)
+		if err != nil {
+			slog.Error("Error openning config file json", "error", err, "path", *configPath)
+			return
+		}
+
+		c, err := config.Parse(f)
+		if err != nil {
+			slog.Error("Error parsing config", "error", err, "path", *configPath)
+			return
+		}
+
+		b, err := json.MarshalIndent(c, "", "	")
+		if err != nil {
+			slog.Error("Error parsing json", "error", err)
+			return
+		}
+
+		if _, err := w.Write(b); err != nil {
 			slog.Error("Error handling index", "error", err)
+			return
 		}
 	})
 	serverTask := worker.NewServerTask(&http.Server{Handler: mux, Addr: "0.0.0.0:8080"})
diff --git a/pkg/config/config.go b/pkg/config/config.go
new file mode 100644
index 0000000000000000000000000000000000000000..ba1614f6fa318316db0b44a319846bf4a275bba6
--- /dev/null
+++ b/pkg/config/config.go
@@ -0,0 +1,84 @@
+package config
+
+import (
+	"fmt"
+	"io"
+	"strconv"
+
+	"git.sr.ht/~emersion/go-scfg"
+)
+
+type (
+	Scan struct {
+		Path   string
+		Public bool
+	}
+
+	Configuration struct {
+		Scan *Scan
+	}
+)
+
+func Parse(r io.Reader) (*Configuration, error) {
+	block, err := scfg.Read(r)
+	if err != nil {
+		return nil, err
+	}
+
+	config := defaultConfiguration()
+
+	err = setScan(block, config.Scan)
+	if err != nil {
+		return nil, err
+	}
+
+	return config, nil
+}
+
+func defaultConfiguration() *Configuration {
+	return &Configuration{
+		Scan: &Scan{
+			Public: true,
+			Path:   "",
+		},
+	}
+}
+
+func setScan(block scfg.Block, scan *Scan) error {
+	scanDir := block.Get("scan")
+	err := setString(scanDir, &scan.Path)
+	if err != nil {
+		return err
+	}
+
+	public := scanDir.Children.Get("public")
+	return setBool(public, &scan.Public)
+}
+
+func setBool(dir *scfg.Directive, field *bool) error {
+
+	if dir != nil {
+		p1 := first(dir.Params)
+		v, err := strconv.ParseBool(p1)
+		if err != nil {
+			return fmt.Errorf("Error parsing bool param of %s: %w", dir.Name, err)
+		}
+		*field = v
+	}
+	return nil
+}
+
+func setString(dir *scfg.Directive, field *string) error {
+	if dir != nil {
+		*field = first(dir.Params)
+	}
+	return nil
+}
+
+func first[T any](v []T) T {
+	if len(v) == 0 {
+		var zero T
+		return zero
+	}
+	return v[0]
+}
diff --git a/pkg/config/config_test.go b/pkg/config/config_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..c8cd8876055e2cabe66e1ee574b5c36df358efcb
--- /dev/null
+++ b/pkg/config/config_test.go
@@ -0,0 +1,56 @@
+// go:build unit
+package config
+
+import (
+	"strings"
+	"testing"
+
+	"github.com/google/go-cmp/cmp"
+)
+
+func TestConfig(t *testing.T) {
+	testCases := []struct {
+		name           string
+		config         string
+		expectedConfig *Configuration
+	}{
+		{
+			name:   "minimal scan",
+			config: `scan "/srv/git"`,
+			expectedConfig: &Configuration{
+				Scan: &Scan{
+					Public: true,
+					Path:   "/srv/git",
+				},
+			},
+		},
+		{
+			name: "complete scan",
+			config: `scan "/srv/git" {
+	public false
+}`,
+			expectedConfig: &Configuration{
+				Scan: &Scan{
+					Public: false,
+					Path:   "/srv/git",
+				},
+			},
+		},
+	}
+
+	for _, tc := range testCases {
+		t.Run(tc.name, func(t *testing.T) {
+			r := strings.NewReader(tc.config)
+			config, err := Parse(r)
+			if err != nil {
+				t.Fatalf("Error parsing config %s", err.Error())
+			}
+
+			if diff := cmp.Diff(tc.expectedConfig, config); diff != "" {
+				t.Errorf("Wrong result given - wanted + got\n %s", diff)
+			}
+
+		})
+
+	}
+}