main
  1package generator
  2
  3import (
  4	"fmt"
  5	"os"
  6	"path/filepath"
  7	"strings"
  8	"time"
  9
 10	"mokhan.ca/xlgmokha/gitmal/internal/git"
 11	"mokhan.ca/xlgmokha/gitmal/internal/templates"
 12)
 13
 14func currentYear() int {
 15	return time.Now().Year()
 16}
 17
 18const Dot = "·"
 19
 20func Echo(a ...any) {
 21	_, _ = fmt.Fprintln(os.Stderr, a...)
 22}
 23
 24func breadcrumbs(rootName string, path string, isFile bool) []templates.Breadcrumb {
 25	// Root list
 26	if path == "" {
 27		return []templates.Breadcrumb{
 28			{
 29				Name:  rootName,
 30				Href:  "./index.html",
 31				IsDir: true,
 32			},
 33		}
 34	}
 35
 36	// Paths from git are already with '/'
 37	parts := strings.Split(path, "/")
 38
 39	// Build breadcrumbs relative to the file location so links work in static output
 40	// Example: for a/b/c.txt, at /blob/<ref>/a/b/c.txt.html
 41	// - root: ../../index.html
 42	// - a: ../index.html
 43	// - b: index.html
 44	// - c.txt: (no link)
 45	d := len(parts)
 46
 47	// current directory depth relative to ref
 48	if isFile {
 49		d -= 1
 50	}
 51
 52	crumbs := make([]templates.Breadcrumb, 0, len(parts))
 53
 54	// root
 55	crumbs = append(crumbs, templates.Breadcrumb{
 56		Name:  rootName,
 57		Href:  "./" + strings.Repeat("../", d) + "index.html",
 58		IsDir: true,
 59	})
 60
 61	// intermediate directories
 62	for i := 0; i < len(parts)-1; i++ {
 63		name := parts[i]
 64		// target directory depth t = i+1
 65		up := d - (i + 1)
 66		href := "./" + strings.Repeat("../", up) + "index.html"
 67		crumbs = append(crumbs, templates.Breadcrumb{
 68			Name:  name,
 69			Href:  href,
 70			IsDir: true,
 71		})
 72	}
 73
 74	// final file (no link)
 75	crumbs = append(crumbs, templates.Breadcrumb{
 76		Name:  parts[len(parts)-1],
 77		IsDir: !isFile,
 78	})
 79
 80	return crumbs
 81}
 82
 83func humanizeSize(size int64) string {
 84	const unit = 1024
 85	if size < unit {
 86		return fmt.Sprintf("%d B", size)
 87	}
 88	div, exp := int64(unit), 0
 89	for n := size / unit; n >= unit; n /= unit {
 90		div *= unit
 91		exp++
 92	}
 93	return fmt.Sprintf("%.1f %cB", float64(size)/float64(div), "KMGTPE"[exp])
 94}
 95
 96func isMarkdown(path string) bool {
 97	lower := strings.ToLower(path)
 98	if strings.HasSuffix(lower, ".md") || strings.HasSuffix(lower, ".markdown") || strings.HasSuffix(lower, ".mdown") || strings.HasSuffix(lower, ".mkd") || strings.HasSuffix(lower, ".mkdown") {
 99		return true
100	}
101	return false
102}
103
104func isImage(path string) bool {
105	switch filepath.Ext(path) {
106	case ".png", ".jpg", ".jpeg", ".gif", ".webp":
107		return true
108	default:
109		return false
110	}
111}
112
113func ContainsBranch(branches []git.Ref, branch string) bool {
114	for _, b := range branches {
115		if b.String() == branch {
116			return true
117		}
118	}
119	return false
120}
121
122func HasConflictingBranchNames(branches []git.Ref) (bool, git.Ref, git.Ref) {
123	uniq := make(map[string]git.Ref, len(branches))
124	for _, b := range branches {
125		if a, exists := uniq[b.DirName()]; exists {
126			return true, a, b
127		}
128		uniq[b.DirName()] = b
129	}
130	return false, git.Ref{}, git.Ref{}
131}