cerrado @ 78f3b007bf42846feff8342b64223252c3a74e2a

ref: Simplify path builder code
diff --git a/README.md b/README.md
index e49e6bc371427c1555d6339562a74b0b66f0bf80..bd5e69eb48b984a8f2648b4e5474352d62a015fa 100644
--- a/README.md
+++ b/README.md
@@ -23,8 +23,6 @@ To run the project you just need to do a make run.
 
 ### TODO
 
-- Add path to tree view
-    - Fix href with extra slash
 - Add message to tags
 - Add link to tar browser from commit page
 - Add patch to the commit page
diff --git a/pkg/u/file.go b/pkg/u/file.go
index fafe0fb00f4b30262594af5ab6b32873515301bb..5010b3eef2a8ea0c37b6bbeb23b7ab721197a93c 100644
--- a/pkg/u/file.go
+++ b/pkg/u/file.go
@@ -4,7 +4,7 @@ import (
 	"errors"
 	"log/slog"
 	"os"
-	"path/filepath"
+	"strings"
 )
 
 func FileExist(filename string) bool {
@@ -22,21 +22,43 @@ 	}
 }
 
 // This is just a slin wraper to make easier to compose path in the template
-type Pathing string
+type Pathing struct {
+	sb strings.Builder
+}
 
-func Root() Pathing {
-	return "/"
+func NewPathing() *Pathing {
+	return &Pathing{}
 }
 
-func (s Pathing) AddPath(p string) Pathing {
-	return Pathing(filepath.Join(string(s), p))
+func (s *Pathing) AddPath(p string) *Pathing {
+	if len(p) == 0 {
+		return s
+	}
+
+	// if it has trailing / remove it
+	if p[len(p)-1] == '/' {
+		p = p[:len(p)-1]
+		return s.AddPath(p)
+	}
+
+	// if it does not have it so add
+	if p[0] == '/' {
+		s.sb.WriteString(p)
+	} else {
+		s.sb.WriteString("/" + p)
+	}
+
+	return s
 }
 
-func (s Pathing) AddPaths(p []string) Pathing {
-	f := filepath.Join(p...)
-	return Pathing(filepath.Join(string(s), f))
+func (s *Pathing) AddPaths(p []string) *Pathing {
+	for _, v := range p {
+		s.AddPath(v)
+	}
+
+	return s
 }
 
-func (s Pathing) Done() string {
-	return string(s)
+func (s *Pathing) Done() string {
+	return s.sb.String()
 }
diff --git a/pkg/u/file_test.go b/pkg/u/file_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..b7d69752f1a0d9d7e6b0e7c828a417e1f8162b15
--- /dev/null
+++ b/pkg/u/file_test.go
@@ -0,0 +1,59 @@
+// go:build unit
+package u
+
+import "testing"
+
+func TestPathing(t *testing.T) {
+	testCases := []struct {
+		name string
+		in   []any
+		out  string
+	}{
+		{
+			name: "root",
+			in:   []any{},
+			out:  "",
+		},
+		{
+			name: "empty",
+			in: []any{
+				"/",
+				[]string{"/", "/"},
+				"/",
+				[]string{"/"},
+			},
+			out: "",
+		},
+		{
+			name: "empty",
+			in: []any{
+				"usr",
+				[]string{"/share/", "lib"},
+				"/demo",
+				[]string{"/out//"},
+			},
+			out: "/usr/share/lib/demo/out",
+		},
+	}
+
+	for _, tc := range testCases {
+		t.Run(tc.name, func(t *testing.T) {
+			r := NewPathing()
+
+			for _, v := range tc.in {
+				switch s := v.(type) {
+				case string:
+					r = r.AddPath(s)
+				case []string:
+					r = r.AddPaths(s)
+				}
+			}
+
+			path := r.Done()
+			if tc.out != path {
+				t.Errorf("String mismatch: wanted %s got %s", tc.out, path)
+			}
+
+		})
+	}
+}
diff --git a/templates/gititemtree.qtpl b/templates/gititemtree.qtpl
index 86fb29cbac5c8aa52d98b729ca13e839ca839db8..5898506af0e41201754aec08dcb72171efd2a675 100644
--- a/templates/gititemtree.qtpl
+++ b/templates/gititemtree.qtpl
@@ -15,7 +15,7 @@ )
 %}
 
 {% code func url(name, mode, ref, filename string, path []string) string {
-    return u.Root().
+    return u.NewPathing().
         AddPath(name).
         AddPath(mode).
         AddPath(ref).
diff --git a/templates/gititemtree.qtpl.go b/templates/gititemtree.qtpl.go
index c0fc3a7787bc21e5a1755a73a878af40c1531c8c..f8d1fd2880caeda1c09bed34c6cdf9e8193bbec0 100644
--- a/templates/gititemtree.qtpl.go
+++ b/templates/gititemtree.qtpl.go
@@ -38,7 +38,7 @@ )
 
 //line gititemtree.qtpl:17
 func url(name, mode, ref, filename string, path []string) string {
-	return u.Root().
+	return u.NewPathing().
 		AddPath(name).
 		AddPath(mode).
 		AddPath(ref).