main
  1package generator
  2
  3import (
  4	"bytes"
  5	"fmt"
  6	"html/template"
  7	"os"
  8	"path/filepath"
  9	"strings"
 10
 11	"github.com/alecthomas/chroma/v2/formatters/html"
 12	"github.com/alecthomas/chroma/v2/lexers"
 13	"github.com/alecthomas/chroma/v2/styles"
 14
 15	"mokhan.ca/xlgmokha/gitmal/internal/git"
 16	"mokhan.ca/xlgmokha/gitmal/internal/links"
 17	"mokhan.ca/xlgmokha/gitmal/internal/pool"
 18	"mokhan.ca/xlgmokha/gitmal/internal/templates"
 19)
 20
 21func GenerateBlobs(files []git.Blob, params Params) error {
 22	formatterOptions := []html.Option{
 23		html.WithLineNumbers(true),
 24		html.WithLinkableLineNumbers(true, "L"),
 25		html.WithClasses(true),
 26		html.WithCSSComments(false),
 27	}
 28
 29	md := createMarkdown()
 30	formatter := html.New(formatterOptions...)
 31	style := styles.Get("github")
 32
 33	dirsSet := links.BuildDirSet(files)
 34	filesSet := links.BuildFileSet(files)
 35
 36	return pool.Run(files, func(blob git.Blob) error {
 37		var content string
 38		data, isBin, err := git.BlobContent(params.Ref, blob.Path, params.RepoDir)
 39		if err != nil {
 40			return err
 41		}
 42
 43		isImg := isImage(blob.Path)
 44		if !isBin {
 45			content = string(data)
 46		}
 47
 48		outPath := filepath.Join(params.OutputDir, "blob", params.Ref.DirName(), blob.Path) + ".html"
 49		if err := os.MkdirAll(filepath.Dir(outPath), 0o755); err != nil {
 50			return err
 51		}
 52
 53		f, err := os.Create(outPath)
 54		if err != nil {
 55			return err
 56		}
 57		defer f.Close()
 58
 59		depth := 0
 60		if strings.Contains(blob.Path, "/") {
 61			depth = len(strings.Split(blob.Path, "/")) - 1
 62		}
 63		rootHref := strings.Repeat("../", depth+2)
 64
 65		if isMarkdown(blob.Path) {
 66			var b bytes.Buffer
 67			if err := md.Convert([]byte(content), &b); err != nil {
 68				return err
 69			}
 70
 71			contentHTML := links.Resolve(
 72				b.String(),
 73				blob.Path,
 74				rootHref,
 75				params.Ref.DirName(),
 76				dirsSet,
 77				filesSet,
 78			)
 79
 80			return templates.MarkdownTemplate.ExecuteTemplate(f, "layout.gohtml", templates.MarkdownParams{
 81				LayoutParams: templates.LayoutParams{
 82					Title:            fmt.Sprintf("%s/%s at %s", params.Name, blob.Path, params.Ref),
 83					Name:             params.Name,
 84					RootHref:         rootHref,
 85					CurrentRefDir:    params.Ref.DirName(),
 86					Selected:         "code",
 87					NeedsMarkdownCSS: true,
 88					NeedsSyntaxCSS:   true,
 89					Year:             currentYear(),
 90				},
 91				HeaderParams: templates.HeaderParams{
 92					Ref:         params.Ref,
 93					Breadcrumbs: breadcrumbs(params.Name, blob.Path, true),
 94				},
 95				Blob:    blob,
 96				Content: template.HTML(contentHTML),
 97			})
 98		}
 99
100		var contentHTML template.HTML
101		if !isBin {
102			var b bytes.Buffer
103			lx := lexers.Match(blob.Path)
104			if lx == nil {
105				lx = lexers.Fallback
106			}
107			iterator, _ := lx.Tokenise(nil, content)
108			if err := formatter.Format(&b, style, iterator); err != nil {
109				return err
110			}
111			contentHTML = template.HTML(b.String())
112		} else if isImg {
113			rawPath := filepath.Join(params.OutputDir, "raw", params.Ref.DirName(), blob.Path)
114			if err := os.MkdirAll(filepath.Dir(rawPath), 0o755); err != nil {
115				return err
116			}
117
118			rf, err := os.Create(rawPath)
119			if err != nil {
120				return err
121			}
122
123			if _, err := rf.Write(data); err != nil {
124				rf.Close()
125				return err
126			}
127			if err := rf.Close(); err != nil {
128				return err
129			}
130
131			relativeRawPath := filepath.Join(rootHref, "raw", params.Ref.DirName(), blob.Path)
132			contentHTML = template.HTML(fmt.Sprintf(`<img src="%s" alt="%s" />`, relativeRawPath, blob.FileName))
133		}
134
135		return templates.BlobTemplate.ExecuteTemplate(f, "layout.gohtml", templates.BlobParams{
136			LayoutParams: templates.LayoutParams{
137				Title:          fmt.Sprintf("%s/%s at %s", params.Name, blob.Path, params.Ref),
138				Name:           params.Name,
139				RootHref:       rootHref,
140				CurrentRefDir:  params.Ref.DirName(),
141				Selected:       "code",
142				NeedsSyntaxCSS: !isBin,
143				Year:           currentYear(),
144			},
145			HeaderParams: templates.HeaderParams{
146				Ref:         params.Ref,
147				Breadcrumbs: breadcrumbs(params.Name, blob.Path, true),
148			},
149			Blob:     blob,
150			IsBinary: isBin,
151			IsImage:  isImg,
152			Content:  contentHTML,
153		})
154	})
155}