package generator import ( "bytes" "fmt" "html/template" "os" "path/filepath" "strings" "github.com/alecthomas/chroma/v2/formatters/html" "github.com/alecthomas/chroma/v2/lexers" "github.com/alecthomas/chroma/v2/styles" "mokhan.ca/xlgmokha/gitmal/internal/git" "mokhan.ca/xlgmokha/gitmal/internal/links" "mokhan.ca/xlgmokha/gitmal/internal/pool" "mokhan.ca/xlgmokha/gitmal/internal/templates" ) func GenerateBlobs(files []git.Blob, params Params) error { formatterOptions := []html.Option{ html.WithLineNumbers(true), html.WithLinkableLineNumbers(true, "L"), html.WithClasses(true), html.WithCSSComments(false), } md := createMarkdown() formatter := html.New(formatterOptions...) style := styles.Get("github") dirsSet := links.BuildDirSet(files) filesSet := links.BuildFileSet(files) return pool.Run(files, func(blob git.Blob) error { var content string data, isBin, err := git.BlobContent(params.Ref, blob.Path, params.RepoDir) if err != nil { return err } isImg := isImage(blob.Path) if !isBin { content = string(data) } outPath := filepath.Join(params.OutputDir, "blob", params.Ref.DirName(), blob.Path) + ".html" if err := os.MkdirAll(filepath.Dir(outPath), 0o755); err != nil { return err } f, err := os.Create(outPath) if err != nil { return err } defer f.Close() depth := 0 if strings.Contains(blob.Path, "/") { depth = len(strings.Split(blob.Path, "/")) - 1 } rootHref := strings.Repeat("../", depth+2) if isMarkdown(blob.Path) { var b bytes.Buffer if err := md.Convert([]byte(content), &b); err != nil { return err } contentHTML := links.Resolve( b.String(), blob.Path, rootHref, params.Ref.DirName(), dirsSet, filesSet, ) return templates.MarkdownTemplate.ExecuteTemplate(f, "layout.gohtml", templates.MarkdownParams{ LayoutParams: templates.LayoutParams{ Title: fmt.Sprintf("%s/%s at %s", params.Name, blob.Path, params.Ref), Name: params.Name, RootHref: rootHref, CurrentRefDir: params.Ref.DirName(), Selected: "code", NeedsMarkdownCSS: true, NeedsSyntaxCSS: true, }, HeaderParams: templates.HeaderParams{ Ref: params.Ref, Breadcrumbs: breadcrumbs(params.Name, blob.Path, true), }, Blob: blob, Content: template.HTML(contentHTML), }) } var contentHTML template.HTML if !isBin { var b bytes.Buffer lx := lexers.Match(blob.Path) if lx == nil { lx = lexers.Fallback } iterator, _ := lx.Tokenise(nil, content) if err := formatter.Format(&b, style, iterator); err != nil { return err } contentHTML = template.HTML(b.String()) } else if isImg { rawPath := filepath.Join(params.OutputDir, "raw", params.Ref.DirName(), blob.Path) if err := os.MkdirAll(filepath.Dir(rawPath), 0o755); err != nil { return err } rf, err := os.Create(rawPath) if err != nil { return err } if _, err := rf.Write(data); err != nil { rf.Close() return err } if err := rf.Close(); err != nil { return err } relativeRawPath := filepath.Join(rootHref, "raw", params.Ref.DirName(), blob.Path) contentHTML = template.HTML(fmt.Sprintf(`%s`, relativeRawPath, blob.FileName)) } return templates.BlobTemplate.ExecuteTemplate(f, "layout.gohtml", templates.BlobParams{ LayoutParams: templates.LayoutParams{ Title: fmt.Sprintf("%s/%s at %s", params.Name, blob.Path, params.Ref), Name: params.Name, RootHref: rootHref, CurrentRefDir: params.Ref.DirName(), Selected: "code", NeedsSyntaxCSS: !isBin, }, HeaderParams: templates.HeaderParams{ Ref: params.Ref, Breadcrumbs: breadcrumbs(params.Name, blob.Path, true), }, Blob: blob, IsBinary: isBin, IsImage: isImg, Content: contentHTML, }) }) }