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}