wrapBody.go

  1// SPDX-FileCopyrightText: Amolith <amolith@secluded.site>
  2//
  3// SPDX-License-Identifier: AGPL-3.0-or-later
  4
  5package main
  6
  7import (
  8	"regexp"
  9	"strings"
 10)
 11
 12var numberedListRegex = regexp.MustCompile(`^\d+\.\s`)
 13
 14func formatBody(body string) (string, error) {
 15	lines := strings.Split(body, "\n")
 16	var result []string
 17
 18	for _, line := range lines {
 19		trimmed := strings.TrimSpace(line)
 20		if trimmed == "" {
 21			result = append(result, "")
 22			continue
 23		}
 24
 25		if strings.HasPrefix(trimmed, "- ") || strings.HasPrefix(trimmed, "* ") {
 26			marker := trimmed[:2]
 27			content := trimmed[2:]
 28			wrapped := wrapWithHangingIndent(marker, "  ", content, 72)
 29			result = append(result, wrapped)
 30			continue
 31		}
 32
 33		if numberedListRegex.MatchString(trimmed) {
 34			parts := strings.SplitN(trimmed, " ", 2)
 35			marker := parts[0] + " "
 36			content := ""
 37			if len(parts) > 1 {
 38				content = parts[1]
 39			}
 40			indent := strings.Repeat(" ", len(marker))
 41			wrapped := wrapWithHangingIndent(marker, indent, content, 72)
 42			result = append(result, wrapped)
 43			continue
 44		}
 45
 46		result = append(result, wordWrap(trimmed, 72))
 47	}
 48
 49	return strings.Join(result, "\n"), nil
 50}
 51
 52func wrapWithHangingIndent(firstPrefix, contPrefix, text string, width int) string {
 53	firstWidth := width - len(firstPrefix)
 54	contWidth := width - len(contPrefix)
 55
 56	words := strings.Fields(text)
 57	if len(words) == 0 {
 58		return firstPrefix
 59	}
 60
 61	var lines []string
 62	var currentLine strings.Builder
 63	var currentWidth int
 64	isFirstLine := true
 65
 66	for _, word := range words {
 67		wordLen := len(word)
 68		maxWidth := firstWidth
 69		if !isFirstLine {
 70			maxWidth = contWidth
 71		}
 72
 73		if currentLine.Len() == 0 {
 74			currentLine.WriteString(word)
 75			currentWidth = wordLen
 76		} else if currentWidth+1+wordLen <= maxWidth {
 77			currentLine.WriteString(" ")
 78			currentLine.WriteString(word)
 79			currentWidth += 1 + wordLen
 80		} else {
 81			if isFirstLine {
 82				lines = append(lines, firstPrefix+currentLine.String())
 83				isFirstLine = false
 84			} else {
 85				lines = append(lines, contPrefix+currentLine.String())
 86			}
 87			currentLine.Reset()
 88			currentLine.WriteString(word)
 89			currentWidth = wordLen
 90		}
 91	}
 92
 93	if currentLine.Len() > 0 {
 94		if isFirstLine {
 95			lines = append(lines, firstPrefix+currentLine.String())
 96		} else {
 97			lines = append(lines, contPrefix+currentLine.String())
 98		}
 99	}
100
101	return strings.Join(lines, "\n")
102}
103
104func wordWrap(text string, width int) string {
105	words := strings.Fields(text)
106	if len(words) == 0 {
107		return ""
108	}
109
110	var result strings.Builder
111	var currentLine strings.Builder
112	var currentWidth int
113
114	for _, word := range words {
115		wordLen := len(word)
116
117		if currentLine.Len() == 0 {
118			currentLine.WriteString(word)
119			currentWidth = wordLen
120		} else if currentWidth+1+wordLen <= width {
121			currentLine.WriteString(" ")
122			currentLine.WriteString(word)
123			currentWidth += 1 + wordLen
124		} else {
125			if result.Len() > 0 {
126				result.WriteString("\n")
127			}
128			result.WriteString(currentLine.String())
129			currentLine.Reset()
130			currentLine.WriteString(word)
131			currentWidth = wordLen
132		}
133	}
134
135	if currentLine.Len() > 0 {
136		if result.Len() > 0 {
137			result.WriteString("\n")
138		}
139		result.WriteString(currentLine.String())
140	}
141
142	return result.String()
143}