diff --git a/build.go b/build.go index 08af6d5..8a0f7c3 100644 --- a/build.go +++ b/build.go @@ -64,12 +64,11 @@ func Build(bc BuildConfig) error { // chroma styles var chromaStylesBuff bytes.Buffer - chromaStyle := bc.ChromaStyle - if chromaStyle == nil { - chromaStyle = styles.Get("swapoff") + if bc.ChromaStyle == nil { + bc.ChromaStyle = styles.Get("swapoff") } - if err := chromaHTML.New().WriteCSS(&chromaStylesBuff, chromaStyle); err != nil { + if err := chromaHTML.New().WriteCSS(&chromaStylesBuff, bc.ChromaStyle); err != nil { return err } @@ -96,15 +95,13 @@ func Build(bc BuildConfig) error { } // posts - allPostsByLangTag, visiblePostsByLangTag, invisiblePostsByLangTag, err := generatePostsLists( - gat, - bc.InPath, - c.Langs, - assetsOutPath, - chromaStyle, - c.ResponsiveImgMediaQueries, - c.ResponsiveImgSizes, - c.Latex, + postsLists, err := generatePostsLists( + generatePostsListsInput{ + bc: &bc, + c: c, + gat: gat, + assetsOutPath: assetsOutPath, + }, ) if err != nil { return err @@ -114,7 +111,7 @@ func Build(bc BuildConfig) error { baseTemplate, err := createBaseTemplateWithIncludes( bc.TemplateFuncs, path.Join(bc.InPath, "includes"), - invisiblePostsByLangTag, + postsLists.invisiblePostsByLangTag, gat, c.URL, c.ResponsiveImgSizes, @@ -155,7 +152,7 @@ func Build(bc BuildConfig) error { // home page homePageTemplateData := TemplateData{ - Posts: visiblePostsByLangTag[l.Tag], + Posts: postsLists.visiblePostsByLangTag[l.Tag], Lang: l, Author: c.Author, Color: c.Color, @@ -189,7 +186,7 @@ func Build(bc BuildConfig) error { Img: c.defaultImgByLangTag[l.Tag], Lang: l, Page: "404", - Posts: visiblePostsByLangTag[l.Tag], + Posts: postsLists.visiblePostsByLangTag[l.Tag], Title: fmt.Sprintf("Not found - %v", c.Title), ResponsiveImgMediaQueries: c.ResponsiveImgMediaQueries, URL: "/404.html", @@ -202,14 +199,14 @@ func Build(bc BuildConfig) error { } // post page - if len(visiblePostsByLangTag) > 0 || len(invisiblePostsByLangTag) > 0 { + if len(postsLists.visiblePostsByLangTag) > 0 || len(postsLists.invisiblePostsByLangTag) > 0 { postsDirOutPath := path.Join(langOutPath, "posts") err = os.Mkdir(postsDirOutPath, os.ModeDir|os.ModePerm) if err != nil { return err } - for _, p := range allPostsByLangTag[l.Tag] { + for _, p := range postsLists.allPostsByLangTag[l.Tag] { postDirPath := path.Join(postsDirOutPath, p.Slug) err := os.Mkdir(postDirPath, os.ModeDir|os.ModePerm) if err != nil { @@ -225,7 +222,7 @@ func Build(bc BuildConfig) error { Lang: l, Author: c.Author, ResponsiveImgMediaQueries: c.ResponsiveImgMediaQueries, - Posts: visiblePostsByLangTag[l.Tag], + Posts: postsLists.visiblePostsByLangTag[l.Tag], } postPageTemplateData.AlternateLinks = generateAlternateLinks(nil, []string{"posts", p.Slug}, c.Langs) diff --git a/posts.go b/posts.go index 90709b3..15970df 100644 --- a/posts.go +++ b/posts.go @@ -12,7 +12,6 @@ import ( "strings" "time" - "github.com/alecthomas/chroma" chromaHTML "github.com/alecthomas/chroma/formatters/html" "github.com/alecthomas/chroma/lexers" "github.com/efreitasn/egen/internal/latex" @@ -35,23 +34,6 @@ var ( } ) -// Post is a post received by a template. -type Post struct { - Title string - Content template.HTML - Slug string - Excerpt string - Img *Img - Date time.Time - LastUpdateDate time.Time - Lang *Lang - // relative - URL string - // pat is a tree composed of any files in the post's path - // whose name doesn't match any item in nonPostAssetsRxs. - pat *assetsTreeNode -} - type postYAMLFrontMatter struct { Title string `yaml:"title"` Excerpt string `yaml:"excerpt"` @@ -65,26 +47,32 @@ type postYAMLDataFileContent struct { Img AssetRelPath } -func generatePostsLists( - gat *assetsTreeNode, - inPath string, - langs []*Lang, - assetsOutPath string, - chromaStyle *chroma.Style, - responsiveImgMediaQueries string, - responsiveImgSizes []int, - latex bool, -) (allPostsByLangTag, visiblePostsByLangTag, invisiblePostsByLangTag map[string][]*Post, err error) { - postsInPath := path.Join(inPath, "posts") +type ( + generatePostsListsInput struct { + bc *BuildConfig + c *config + gat *assetsTreeNode + assetsOutPath string + } + + generatePostsListsOutput struct { + allPostsByLangTag, visiblePostsByLangTag, invisiblePostsByLangTag map[string][]*Post + } +) + +func generatePostsLists(input generatePostsListsInput) (*generatePostsListsOutput, error) { + postsInPath := path.Join(input.bc.InPath, "posts") postsFileInfos, err := os.ReadDir(postsInPath) if err != nil { - return nil, nil, nil, err + return nil, err } - allPostsByLangTag = make(map[string][]*Post) - visiblePostsByLangTag = make(map[string][]*Post) - invisiblePostsByLangTag = make(map[string][]*Post) + output := generatePostsListsOutput{ + allPostsByLangTag: make(map[string][]*Post), + visiblePostsByLangTag: make(map[string][]*Post), + invisiblePostsByLangTag: make(map[string][]*Post), + } for _, postsFileInfo := range postsFileInfos { if !postsFileInfo.IsDir() { @@ -96,13 +84,13 @@ func generatePostsLists( pat, err := generateAssetsTree(postDirPath, nonPostAssetsRxs) if err != nil { - return nil, nil, nil, fmt.Errorf("generating pat for %v post: %v", postSlug, err) + return nil, fmt.Errorf("generating pat for %v post: %v", postSlug, err) } // this condition exists so that assetsPathOut is only created if the post // has at least one asset. if pat.firstChild != nil { - assetsPathOut := path.Join(assetsOutPath, postSlug) + assetsPathOut := path.Join(input.assetsOutPath, postSlug) // it's checked whether assetsPathOut already exists because it could've // been already created when generating the global assets tree (GAT) if @@ -111,33 +99,33 @@ func generatePostsLists( if os.IsNotExist(err) { err := os.Mkdir(assetsPathOut, os.ModeDir|os.ModePerm) if err != nil { - return nil, nil, nil, fmt.Errorf("creating %v: %v", assetsPathOut, err) + return nil, fmt.Errorf("creating %v: %v", assetsPathOut, err) } } else { - return nil, nil, nil, err + return nil, err } } if err = pat.process(assetsPathOut, false); err != nil { - return nil, nil, nil, fmt.Errorf("processing pat: %v", err) + return nil, fmt.Errorf("processing pat: %v", err) } } // data.yaml file postYAMLDataFile, err := os.Open(path.Join(postDirPath, "data.yaml")) if err != nil { - return nil, nil, nil, fmt.Errorf("opening %v data.yaml: %v", postSlug, err) + return nil, fmt.Errorf("opening %v data.yaml: %v", postSlug, err) } var postYAMLData postYAMLDataFileContent err = yaml.NewDecoder(postYAMLDataFile).Decode(&postYAMLData) if err != nil { - return nil, nil, nil, fmt.Errorf("decoding %v data.yaml: %v", postSlug, err) + return nil, fmt.Errorf("decoding %v data.yaml: %v", postSlug, err) } postDate, err := time.Parse(time.RFC3339, postYAMLData.Date) if err != nil { - return nil, nil, nil, fmt.Errorf("parsing %v data.yaml date: %v", postSlug, err) + return nil, fmt.Errorf("parsing %v data.yaml date: %v", postSlug, err) } var postLastUpdateDate time.Time @@ -145,12 +133,12 @@ func generatePostsLists( if postYAMLData.LastUpdateDate != "" { postLastUpdateDate, err = time.Parse(time.RFC3339, postYAMLData.LastUpdateDate) if err != nil { - return nil, nil, nil, fmt.Errorf("parsing %v data.yaml lastUpdateDate: %v", postSlug, err) + return nil, fmt.Errorf("parsing %v data.yaml lastUpdateDate: %v", postSlug, err) } } // content_*.md files - for _, l := range langs { + for _, l := range input.c.Langs { var postURL string if l.Default { @@ -173,32 +161,34 @@ func generatePostsLists( postContent, err := os.ReadFile(postContentFilePath) if err != nil { if os.IsNotExist(err) { - return nil, nil, nil, fmt.Errorf("%v for %v post doesn't exist", postContentFilename, postSlug) + return nil, fmt.Errorf("%v for %v post doesn't exist", postContentFilename, postSlug) } - return nil, nil, nil, err + return nil, err } if !postContentRegExp.Match(postContent) { - return nil, nil, nil, fmt.Errorf("post content at %v is invalid", postContentFilePath) + return nil, fmt.Errorf("post content at %v is invalid", postContentFilePath) } matchesIndexes := postContentRegExp.FindSubmatchIndex(postContent) postContentYAML := postContent[matchesIndexes[2]:matchesIndexes[3]] postContentMD := postContent[matchesIndexes[4]:matchesIndexes[5]] + p.generateContent(input, l, postContentMD) + // yaml var yamlData postYAMLFrontMatter err = yaml.Unmarshal(postContentYAML, &yamlData) if err != nil { - return nil, nil, nil, fmt.Errorf("parsing YAML content of %v: %v", postContentFilePath, err) + return nil, fmt.Errorf("parsing YAML content of %v: %v", postContentFilePath, err) } if yamlData.Title == "" { - return nil, nil, nil, fmt.Errorf("title field in %v post frontmatter in %v cannot be empty", p.Slug, l.Tag) + return nil, fmt.Errorf("title field in %v post frontmatter in %v cannot be empty", p.Slug, l.Tag) } if yamlData.Excerpt == "" { - return nil, nil, nil, fmt.Errorf("excerpt field in %v post frontmatter in %v cannot be empty", p.Slug, l.Tag) + return nil, fmt.Errorf("excerpt field in %v post frontmatter in %v cannot be empty", p.Slug, l.Tag) } p.Title = yamlData.Title @@ -206,7 +196,7 @@ func generatePostsLists( if postYAMLData.Img != "" { if yamlData.ImgAlt == "" { - return nil, nil, nil, fmt.Errorf("img alt in %v for %v post not provided", l.Tag, p.Slug) + return nil, fmt.Errorf("img alt in %v for %v post not provided", l.Tag, p.Slug) } p.Img = &Img{ @@ -215,414 +205,452 @@ func generatePostsLists( } } - mdProcessor := blackfriday.New(blackfriday.WithExtensions(blackfriday.CommonExtensions)) - rootNode := mdProcessor.Parse(postContentMD) - latexBlockMap := map[*blackfriday.Node]struct{}{} - inlineLatexMap := map[*blackfriday.Node]struct{}{} + if output.allPostsByLangTag[l.Tag] == nil { + output.allPostsByLangTag[l.Tag] = make([]*Post, 0, 1) + } - rootNode.Walk(func(node *blackfriday.Node, entering bool) blackfriday.WalkStatus { - switch { - // Remove img tags from inside p tags. - case node.Type == blackfriday.Image && entering: - oldParent := node.Parent + output.allPostsByLangTag[l.Tag] = append(output.allPostsByLangTag[l.Tag], &p) - if oldParent.Type == blackfriday.Paragraph { - newParent := oldParent.Parent + if postYAMLData.Feed { + if output.visiblePostsByLangTag[l.Tag] == nil { + output.visiblePostsByLangTag[l.Tag] = make([]*Post, 0, 1) + } - // this should never happen - if newParent.Type == blackfriday.Paragraph { - node.Unlink() + output.visiblePostsByLangTag[l.Tag] = append(output.visiblePostsByLangTag[l.Tag], &p) + } else { + if output.invisiblePostsByLangTag[l.Tag] == nil { + output.invisiblePostsByLangTag[l.Tag] = make([]*Post, 0, 1) + } - return blackfriday.GoToNext - } + output.invisiblePostsByLangTag[l.Tag] = append(output.invisiblePostsByLangTag[l.Tag], &p) + } + } + } - oldParentChildren := getBFNodeChildren(oldParent) - nodeOldParentIndex := findBFNodeIndex(node, oldParent) + return &output, nil +} - var oldParentChildrenAfterNode []*blackfriday.Node - if nodeOldParentIndex+1 < len(oldParentChildren) { - oldParentChildrenAfterNode = oldParentChildren[nodeOldParentIndex+1:] - } +// Post is a post received by a template. +type Post struct { + Title string + Content template.HTML + Slug string + Excerpt string + Img *Img + Date time.Time + LastUpdateDate time.Time + Lang *Lang + // relative + URL string + // pat is a tree composed of any files in the post's path + // whose name doesn't match any item in nonPostAssetsRxs. + pat *assetsTreeNode +} - if oldParent.Next == nil { - newParent.AppendChild(node) +func (p *Post) generateContent(input generatePostsListsInput, l *Lang, markdown []byte) error { + mdProcessor := blackfriday.New(blackfriday.WithExtensions(blackfriday.CommonExtensions)) + rootNode := mdProcessor.Parse(markdown) - if oldParentChildrenAfterNode != nil { - pNode := blackfriday.NewNode(blackfriday.Paragraph) + latexBlockMap, inlineLatexMap := p.processContentBFTree(input, rootNode) - for _, c := range oldParentChildrenAfterNode { - pNode.AppendChild(c) - } - newParent.AppendChild(pNode) - } - } else { - oldParentNewParentIndex := findBFNodeIndex(oldParent, newParent) - newParentChildren := getBFNodeChildren(newParent) - newParentChildrenAfterOldParent := newParentChildren[oldParentNewParentIndex+1:] + err := latexGenerator.SetDirPath(input.bc.InPath) + if err != nil { + return fmt.Errorf("setting latex image generator dir path: %w", err) + } - newParent.AppendChild(node) + err = p.renderContentBFTree(input, l, rootNode, latexBlockMap, inlineLatexMap) + if err != nil { + return err + } - if oldParentChildrenAfterNode != nil { - pNode := blackfriday.NewNode(blackfriday.Paragraph) + return nil +} - for _, c := range oldParentChildrenAfterNode { - pNode.AppendChild(c) - } +func (p *Post) processContentBFTree(input generatePostsListsInput, rootNode *blackfriday.Node) (latexBlockMap, inlineLatexMap map[*blackfriday.Node]struct{}) { + latexBlockMap = map[*blackfriday.Node]struct{}{} + inlineLatexMap = map[*blackfriday.Node]struct{}{} - newParent.AppendChild(pNode) - } + rootNode.Walk(func(node *blackfriday.Node, entering bool) blackfriday.WalkStatus { + switch { + // Remove img tags from inside p tags. + case node.Type == blackfriday.Image && entering: + oldParent := node.Parent - for _, c := range newParentChildrenAfterOldParent { - newParent.AppendChild(c) - } + if oldParent.Type == blackfriday.Paragraph { + newParent := oldParent.Parent + + // this should never happen + if newParent.Type == blackfriday.Paragraph { + node.Unlink() + + return blackfriday.GoToNext + } + + oldParentChildren := getBFNodeChildren(oldParent) + nodeOldParentIndex := findBFNodeIndex(node, oldParent) + + var oldParentChildrenAfterNode []*blackfriday.Node + if nodeOldParentIndex+1 < len(oldParentChildren) { + oldParentChildrenAfterNode = oldParentChildren[nodeOldParentIndex+1:] + } + + if oldParent.Next == nil { + newParent.AppendChild(node) + + if oldParentChildrenAfterNode != nil { + pNode := blackfriday.NewNode(blackfriday.Paragraph) + + for _, c := range oldParentChildrenAfterNode { + pNode.AppendChild(c) } + newParent.AppendChild(pNode) + } + } else { + oldParentNewParentIndex := findBFNodeIndex(oldParent, newParent) + newParentChildren := getBFNodeChildren(newParent) + newParentChildrenAfterOldParent := newParentChildren[oldParentNewParentIndex+1:] + + newParent.AppendChild(node) - if len(oldParentChildren) == 1 { - oldParent.Unlink() + if oldParentChildrenAfterNode != nil { + pNode := blackfriday.NewNode(blackfriday.Paragraph) + + for _, c := range oldParentChildrenAfterNode { + pNode.AppendChild(c) } + + newParent.AppendChild(pNode) } - case node.Type == blackfriday.Text && entering: - if latex { - for i := 0; i < len(node.Literal); { - if node.Literal[i] == '$' { - if i != 0 && node.Literal[i-1] == '\\' { - node.Literal = slices.Delete(node.Literal, i-1, i) - i++ - continue - } + for _, c := range newParentChildrenAfterOldParent { + newParent.AppendChild(c) + } + } - if len(node.Literal) > i+1 && node.Literal[i+1] == '$' { - var ( - found bool - - start = i + 2 - end = start - ) - - for ; end < len(node.Literal); end++ { - if node.Literal[end] == '$' && node.Literal[end-1] != '\\' && len(node.Literal) > end+1 && node.Literal[end+1] == '$' { - found = true - break - } - } - - if !found { - return blackfriday.GoToNext - } - - content := node.Literal[start:end] - - // If it's empty (i.e. $$$$), remove it. - if len(content) == 0 { - node.Literal = slices.Delete(node.Literal, start-2, end+2) - i++ - continue - } - - // If the first $ is not on the 0th position, then the current block needs to be - // splitted. - if i != 0 { - textNode := blackfriday.NewNode(blackfriday.Text) - textNode.Literal = node.Literal[:i] - - node.InsertBefore(textNode) - } - - // The content after the ending $$, if there's any, is the caption. - node.Title = node.Literal[end+2:] - node.Literal = content - latexBlockMap[node] = struct{}{} - - return blackfriday.GoToNext - } + if len(oldParentChildren) == 1 { + oldParent.Unlink() + } + } - // Inline latex. - var ( - found bool + case node.Type == blackfriday.Text && entering: + if input.c.Latex { + for i := 0; i < len(node.Literal); { + if node.Literal[i] == '$' { + if i != 0 && node.Literal[i-1] == '\\' { + node.Literal = slices.Delete(node.Literal, i-1, i) + i++ + continue + } - start = i + 1 - end = start - ) + if len(node.Literal) > i+1 && node.Literal[i+1] == '$' { + var ( + found bool - for ; end < len(node.Literal); end++ { - if node.Literal[end] == '$' && node.Literal[end-1] != '\\' { - found = true - break - } - } + start = i + 2 + end = start + ) - if !found { - return blackfriday.GoToNext + for ; end < len(node.Literal); end++ { + if node.Literal[end] == '$' && node.Literal[end-1] != '\\' && len(node.Literal) > end+1 && node.Literal[end+1] == '$' { + found = true + break } + } - content := node.Literal[start:end] + if !found { + return blackfriday.GoToNext + } - // If it's empty (i.e. $$), remove it. - if len(content) == 0 { - node.Literal = slices.Delete(node.Literal, start-1, end+1) - i++ - continue - } + content := node.Literal[start:end] - // If the starting $ is not on the 0th position, then a text node needs to be inserted - // before the current node. - if i != 0 { - textNode := blackfriday.NewNode(blackfriday.Text) - textNode.Literal = node.Literal[:i] + // If it's empty (i.e. $$$$), remove it. + if len(content) == 0 { + node.Literal = slices.Delete(node.Literal, start-2, end+2) + i++ + continue + } - node.InsertBefore(textNode) - } + // If the first $ is not on the 0th position, then the current block needs to be + // splitted. + if i != 0 { + textNode := blackfriday.NewNode(blackfriday.Text) + textNode.Literal = node.Literal[:i] - // If the ending $ is not on the last position, then a text node needs to be inserted - // after the current node. - if end != len(node.Literal)-1 { - textNode := blackfriday.NewNode(blackfriday.Text) - textNode.Literal = node.Literal[end+1:] - - if node.Next == nil { - node.Parent.AppendChild(textNode) - } else { - node.Next.InsertBefore(textNode) - } - } + node.InsertBefore(textNode) + } - node.Literal = content - inlineLatexMap[node] = struct{}{} + // The content after the ending $$, if there's any, is the caption. + node.Title = node.Literal[end+2:] + node.Literal = content + latexBlockMap[node] = struct{}{} - return blackfriday.GoToNext + return blackfriday.GoToNext + } + + // Inline latex. + var ( + found bool + + start = i + 1 + end = start + ) + + for ; end < len(node.Literal); end++ { + if node.Literal[end] == '$' && node.Literal[end-1] != '\\' { + found = true + break } + } + + if !found { + return blackfriday.GoToNext + } + + content := node.Literal[start:end] + // If it's empty (i.e. $$), remove it. + if len(content) == 0 { + node.Literal = slices.Delete(node.Literal, start-1, end+1) i++ + continue } - } - } - return blackfriday.GoToNext - }) + // If the starting $ is not on the 0th position, then a text node needs to be inserted + // before the current node. + if i != 0 { + textNode := blackfriday.NewNode(blackfriday.Text) + textNode.Literal = node.Literal[:i] - var htmlBuff bytes.Buffer + node.InsertBefore(textNode) + } - var bfTraverseErr error - r := blackfriday.NewHTMLRenderer(blackfriday.HTMLRendererParameters{ - Flags: blackfriday.HrefTargetBlank | blackfriday.NoreferrerLinks, - }) + // If the ending $ is not on the last position, then a text node needs to be inserted + // after the current node. + if end != len(node.Literal)-1 { + textNode := blackfriday.NewNode(blackfriday.Text) + textNode.Literal = node.Literal[end+1:] - err = latexGenerator.SetDirPath(inPath) - if err != nil { - return nil, nil, nil, fmt.Errorf("setting latex image generator dir path: %w", err) - } + if node.Next == nil { + node.Parent.AppendChild(textNode) + } else { + node.Next.InsertBefore(textNode) + } + } + + node.Literal = content + inlineLatexMap[node] = struct{}{} - // traverse the tree to render each node - rootNode.Walk(func(bfNode *blackfriday.Node, entering bool) blackfriday.WalkStatus { - switch { - case bfNode.Type == blackfriday.CodeBlock && entering: - if !mdCodeBlockInfoRegExp.Match(bfNode.Info) { return blackfriday.GoToNext } - cbInfoMatches := mdCodeBlockInfoRegExp.FindStringSubmatch(string(bfNode.Info)) - lang := cbInfoMatches[1] + i++ + } + } + } - hLines := make([][2]int, 0) + return blackfriday.GoToNext + }) - if cbInfoMatches[2] != "" { - hLinesMatches := mdCodeBlockInfoHLinesRegExp.FindAllStringSubmatch(cbInfoMatches[2], -1) + return latexBlockMap, inlineLatexMap +} - for _, hLinesMatch := range hLinesMatches { - startLine, err := strconv.Atoi(hLinesMatch[1]) - if err != nil { - return blackfriday.GoToNext - } +func (p *Post) renderContentBFTree(input generatePostsListsInput, l *Lang, rootNode *blackfriday.Node, latexBlockMap, inlineLatexMap map[*blackfriday.Node]struct{}) error { + var ( + traverseErr error + htmlBuff bytes.Buffer + ) - endLine, err := strconv.Atoi(hLinesMatch[2]) - if err != nil { - return blackfriday.GoToNext - } + r := blackfriday.NewHTMLRenderer(blackfriday.HTMLRendererParameters{ + Flags: blackfriday.HrefTargetBlank | blackfriday.NoreferrerLinks, + }) - hLines = append(hLines, [2]int{ - startLine, - endLine, - }) - } - } + rootNode.Walk(func(bfNode *blackfriday.Node, entering bool) blackfriday.WalkStatus { + switch { + case bfNode.Type == blackfriday.CodeBlock && entering: + if !mdCodeBlockInfoRegExp.Match(bfNode.Info) { + return blackfriday.GoToNext + } - lexer := lexers.Get(lang) - if lexer == nil { - bfTraverseErr = fmt.Errorf("no lexer found for %v code in %v post (%v)", lang, p.Slug, l.Tag) + cbInfoMatches := mdCodeBlockInfoRegExp.FindStringSubmatch(string(bfNode.Info)) + lang := cbInfoMatches[1] - return blackfriday.Terminate - } + hLines := make([][2]int, 0) - iterator, _ := lexer.Tokenise(nil, string(bfNode.Literal)) - formatter := chromaHTML.New( - chromaHTML.WithClasses(true), - chromaHTML.HighlightLines(hLines), - ) + if cbInfoMatches[2] != "" { + hLinesMatches := mdCodeBlockInfoHLinesRegExp.FindAllStringSubmatch(cbInfoMatches[2], -1) - var formattedCode bytes.Buffer - err := formatter.Format(&formattedCode, chromaStyle, iterator) + for _, hLinesMatch := range hLinesMatches { + startLine, err := strconv.Atoi(hLinesMatch[1]) if err != nil { - bfTraverseErr = err - - return blackfriday.Terminate + return blackfriday.GoToNext } - if _, err = htmlBuff.Write(formattedCode.Bytes()); err != nil { - bfTraverseErr = err - - return blackfriday.Terminate + endLine, err := strconv.Atoi(hLinesMatch[2]) + if err != nil { + return blackfriday.GoToNext } - return blackfriday.GoToNext + hLines = append(hLines, [2]int{ + startLine, + endLine, + }) + } + } - case bfNode.Type == blackfriday.Image && entering: - if bfNode.FirstChild == nil || string(bfNode.FirstChild.Literal) == "" { - bfTraverseErr = fmt.Errorf("%v img in %v post in %v must have an alt attribute", string(bfNode.LinkData.Destination), p.Slug, l.Tag) + lexer := lexers.Get(lang) + if lexer == nil { + traverseErr = fmt.Errorf("no lexer found for %v code in %v post (%v)", lang, p.Slug, l.Tag) - return blackfriday.Terminate - } - - title := string(bfNode.Title) - alt := string(bfNode.FirstChild.Literal) + return blackfriday.Terminate + } - node, searchedInPAT := findByRelPathInGATOrPAT(gat, p.pat, AssetRelPath(bfNode.LinkData.Destination)) - if node == nil { - bfTraverseErr = fmt.Errorf( - "%v img not found in %v post", - string(bfNode.LinkData.Destination), - p.Slug, - ) + iterator, _ := lexer.Tokenise(nil, string(bfNode.Literal)) + formatter := chromaHTML.New( + chromaHTML.WithClasses(true), + chromaHTML.HighlightLines(hLines), + ) - return blackfriday.Terminate - } + var formattedCode bytes.Buffer + err := formatter.Format(&formattedCode, input.bc.ChromaStyle, iterator) + if err != nil { + traverseErr = err - node.addSizes(responsiveImgSizes...) + return blackfriday.Terminate + } - if err := node.processSizes(); err != nil { - bfTraverseErr = fmt.Errorf("while processing sizes for %v img: %v", node.path, err) + if _, err = htmlBuff.Write(formattedCode.Bytes()); err != nil { + traverseErr = err - return blackfriday.Terminate - } + return blackfriday.Terminate + } - var figcaption string - if title != "" { - figcaption = fmt.Sprintf("
%v
", title) - } + return blackfriday.GoToNext - var src string - if searchedInPAT { - src = node.assetLink(postSlug, node.findOriginalSize()) - } else { - src = node.assetLink("", node.findOriginalSize()) - } + case bfNode.Type == blackfriday.Image && entering: + if bfNode.FirstChild == nil || string(bfNode.FirstChild.Literal) == "" { + traverseErr = fmt.Errorf("%v img in %v post in %v must have an alt attribute", string(bfNode.LinkData.Destination), p.Slug, l.Tag) - var img string - if responsiveImgMediaQueries != "" { - var srcset string - if searchedInPAT { - srcset = node.generateSrcSetValue(postSlug) - } else { - srcset = node.generateSrcSetValue("") - } + return blackfriday.Terminate + } - img = fmt.Sprintf(`%v`, srcset, responsiveImgMediaQueries, src, alt) - } else { - img = fmt.Sprintf(`%v`, src, alt) - } + title := string(bfNode.Title) + alt := string(bfNode.FirstChild.Literal) - htmlBuff.WriteString( - fmt.Sprintf(`
%v%v
`, src, img, figcaption), - ) + node, searchedInPAT := findByRelPathInGATOrPAT(input.gat, p.pat, AssetRelPath(bfNode.LinkData.Destination)) + if node == nil { + traverseErr = fmt.Errorf( + "%v img not found in %v post", + string(bfNode.LinkData.Destination), + p.Slug, + ) - return blackfriday.SkipChildren + return blackfriday.Terminate + } - case bfNode.Type == blackfriday.Text && mapContains(latexBlockMap, bfNode): - if !entering { - return blackfriday.GoToNext - } + node.addSizes(input.c.ResponsiveImgSizes...) - svgBs, err := latexGenerator.SVGBlock(bfNode.Literal) - if err != nil { - bfTraverseErr = fmt.Errorf("generating latex block in %v post: %w", p.Slug, err) + if err := node.processSizes(); err != nil { + traverseErr = fmt.Errorf("while processing sizes for %v img: %v", node.path, err) - return blackfriday.Terminate - } + return blackfriday.Terminate + } - var figCaption string - if len(bfNode.Title) > 0 { - figCaption = fmt.Sprintf("
%s
", bfNode.Title) - } + var figcaption string + if title != "" { + figcaption = fmt.Sprintf("
%v
", title) + } - fmt.Fprintf( - &htmlBuff, - `
%s
%s
`, - svgBs, - figCaption, - ) + var src string + if searchedInPAT { + src = node.assetLink(p.Slug, node.findOriginalSize()) + } else { + src = node.assetLink("", node.findOriginalSize()) + } - return blackfriday.GoToNext + var img string + if input.c.ResponsiveImgMediaQueries != "" { + var srcset string + if searchedInPAT { + srcset = node.generateSrcSetValue(p.Slug) + } else { + srcset = node.generateSrcSetValue("") + } - case bfNode.Type == blackfriday.Text && mapContains(inlineLatexMap, bfNode): - if !entering { - return blackfriday.GoToNext - } + img = fmt.Sprintf(`%v`, srcset, input.c.ResponsiveImgMediaQueries, src, alt) + } else { + img = fmt.Sprintf(`%v`, src, alt) + } - svgBs, err := latexGenerator.SVGInline(bfNode.Literal) - if err != nil { - bfTraverseErr = fmt.Errorf("generating inline latex in %v post: %w", p.Slug, err) + htmlBuff.WriteString( + fmt.Sprintf(`
%v%v
`, src, img, figcaption), + ) - return blackfriday.Terminate - } + return blackfriday.SkipChildren - fmt.Fprintf(&htmlBuff, `%s`, svgBs) + case bfNode.Type == blackfriday.Text && mapContains(latexBlockMap, bfNode): + if !entering { + return blackfriday.GoToNext + } - return blackfriday.GoToNext + svgBs, err := latexGenerator.SVGBlock(bfNode.Literal) + if err != nil { + traverseErr = fmt.Errorf("generating latex block in %v post: %w", p.Slug, err) - case bfNode.Type == blackfriday.Paragraph: - firstChildIsEmpty := bfNode.FirstChild == nil || len(strings.Trim(string(bfNode.FirstChild.Literal), "\n\t ")) == 0 - onlyChildIsLatexBlock := bfNode.FirstChild != nil && mapContains(latexBlockMap, bfNode.FirstChild) && bfNode.FirstChild.Next == nil + return blackfriday.Terminate + } - if firstChildIsEmpty || onlyChildIsLatexBlock { - return blackfriday.GoToNext - } + var figCaption string + if len(bfNode.Title) > 0 { + figCaption = fmt.Sprintf("
%s
", bfNode.Title) + } - bfNode.FirstChild.Literal = bytes.TrimLeft(bfNode.FirstChild.Literal, "\n\t ") - bfNode.LastChild.Literal = bytes.TrimRight(bfNode.LastChild.Literal, "\n\t ") + fmt.Fprintf( + &htmlBuff, + `
%s
%s
`, + svgBs, + figCaption, + ) - return r.RenderNode(&htmlBuff, bfNode, entering) + return blackfriday.GoToNext - default: - return r.RenderNode(&htmlBuff, bfNode, entering) - } - }) - if bfTraverseErr != nil { - return nil, nil, nil, bfTraverseErr + case bfNode.Type == blackfriday.Text && mapContains(inlineLatexMap, bfNode): + if !entering { + return blackfriday.GoToNext } - p.Content = template.HTML(htmlBuff.Bytes()) + svgBs, err := latexGenerator.SVGInline(bfNode.Literal) + if err != nil { + traverseErr = fmt.Errorf("generating inline latex in %v post: %w", p.Slug, err) - if allPostsByLangTag[l.Tag] == nil { - allPostsByLangTag[l.Tag] = make([]*Post, 0, 1) + return blackfriday.Terminate } - allPostsByLangTag[l.Tag] = append(allPostsByLangTag[l.Tag], &p) + fmt.Fprintf(&htmlBuff, `%s`, svgBs) - if postYAMLData.Feed { - if visiblePostsByLangTag[l.Tag] == nil { - visiblePostsByLangTag[l.Tag] = make([]*Post, 0, 1) - } + return blackfriday.GoToNext - visiblePostsByLangTag[l.Tag] = append(visiblePostsByLangTag[l.Tag], &p) - } else { - if invisiblePostsByLangTag[l.Tag] == nil { - invisiblePostsByLangTag[l.Tag] = make([]*Post, 0, 1) - } + case bfNode.Type == blackfriday.Paragraph: + firstChildIsEmpty := bfNode.FirstChild == nil || len(strings.Trim(string(bfNode.FirstChild.Literal), "\n\t ")) == 0 + onlyChildIsLatexBlock := bfNode.FirstChild != nil && mapContains(latexBlockMap, bfNode.FirstChild) && bfNode.FirstChild.Next == nil - invisiblePostsByLangTag[l.Tag] = append(invisiblePostsByLangTag[l.Tag], &p) + if firstChildIsEmpty || onlyChildIsLatexBlock { + return blackfriday.GoToNext } + + bfNode.FirstChild.Literal = bytes.TrimLeft(bfNode.FirstChild.Literal, "\n\t ") + bfNode.LastChild.Literal = bytes.TrimRight(bfNode.LastChild.Literal, "\n\t ") + + return r.RenderNode(&htmlBuff, bfNode, entering) + + default: + return r.RenderNode(&htmlBuff, bfNode, entering) } + }) + if traverseErr != nil { + return traverseErr } - return allPostsByLangTag, visiblePostsByLangTag, invisiblePostsByLangTag, nil + p.Content = template.HTML(htmlBuff.Bytes()) + + return nil }