summaryrefslogtreecommitdiff
path: root/internal/textutil/textutil.go
diff options
context:
space:
mode:
authorPaul Buetow <paul@buetow.org>2025-09-06 13:19:01 +0300
committerPaul Buetow <paul@buetow.org>2025-09-06 13:19:01 +0300
commit04f290dbeeee8a6fcbc70fed253a968336bcb2ab (patch)
tree3ee23a4ac4bcc5b43b43697cfb0e905735fc6331 /internal/textutil/textutil.go
parent5e966f50111adf6e2cb2683fe588f6fe033fa931 (diff)
more tests
Diffstat (limited to 'internal/textutil/textutil.go')
-rw-r--r--internal/textutil/textutil.go114
1 files changed, 114 insertions, 0 deletions
diff --git a/internal/textutil/textutil.go b/internal/textutil/textutil.go
new file mode 100644
index 0000000..7ef2680
--- /dev/null
+++ b/internal/textutil/textutil.go
@@ -0,0 +1,114 @@
+package textutil
+
+import "strings"
+
+// RenderTemplate performs simple {{var}} replacement in a template string.
+func RenderTemplate(t string, vars map[string]string) string {
+ if t == "" || len(vars) == 0 {
+ return t
+ }
+ out := t
+ for k, v := range vars {
+ out = strings.ReplaceAll(out, "{{"+k+"}}", v)
+ }
+ return out
+}
+
+// StripCodeFences removes surrounding Markdown triple-backtick fences.
+func StripCodeFences(s string) string {
+ t := strings.TrimSpace(s)
+ if t == "" {
+ return t
+ }
+ lines := strings.Split(t, "\n")
+ start := 0
+ for start < len(lines) && strings.TrimSpace(lines[start]) == "" {
+ start++
+ }
+ end := len(lines) - 1
+ for end >= 0 && strings.TrimSpace(lines[end]) == "" {
+ end--
+ }
+ if start >= len(lines) || end < 0 || start > end {
+ return t
+ }
+ first := strings.TrimSpace(lines[start])
+ last := strings.TrimSpace(lines[end])
+ if strings.HasPrefix(first, "```") && last == "```" && end > start {
+ inner := strings.Join(lines[start+1:end], "\n")
+ return inner
+ }
+ return t
+}
+
+// InstructionFromSelection extracts the first inline instruction and returns
+// (instruction, cleanedSelection). It detects markers on the earliest position
+// per line in precedence: strict ;text;, /* */, <!-- -->, //, #, --.
+func InstructionFromSelection(sel string) (string, string) {
+ lines := strings.Split(sel, "\n")
+ for idx, line := range lines {
+ if instr, cleaned, ok := FindFirstInstructionInLine(line); ok && strings.TrimSpace(instr) != "" {
+ lines[idx] = cleaned
+ return instr, strings.Join(lines, "\n")
+ }
+ }
+ return "", sel
+}
+
+// FindFirstInstructionInLine returns (instruction, cleaned, ok) for a single line.
+func FindFirstInstructionInLine(line string) (instr, cleaned string, ok bool) {
+ type cand struct{ start, end int; text string }
+ cands := []cand{}
+ if t, l, r, ok := FindStrictInlineTag(line); ok {
+ cands = append(cands, cand{start: l, end: r, text: t})
+ }
+ if i := strings.Index(line, "/*"); i >= 0 {
+ if j := strings.Index(line[i+2:], "*/"); j >= 0 {
+ start := i
+ end := i + 2 + j + 2
+ text := strings.TrimSpace(line[i+2 : i+2+j])
+ cands = append(cands, cand{start: start, end: end, text: text})
+ }
+ }
+ if i := strings.Index(line, "<!--"); i >= 0 {
+ if j := strings.Index(line[i+4:], "-->"); j >= 0 {
+ start := i
+ end := i + 4 + j + 3
+ text := strings.TrimSpace(line[i+4 : i+4+j])
+ cands = append(cands, cand{start: start, end: end, text: text})
+ }
+ }
+ if i := strings.Index(line, "//"); i >= 0 {
+ cands = append(cands, cand{start: i, end: len(line), text: strings.TrimSpace(line[i+2:])})
+ }
+ if i := strings.Index(line, "#"); i >= 0 {
+ cands = append(cands, cand{start: i, end: len(line), text: strings.TrimSpace(line[i+1:])})
+ }
+ if i := strings.Index(line, "--"); i >= 0 {
+ cands = append(cands, cand{start: i, end: len(line), text: strings.TrimSpace(line[i+2:])})
+ }
+ if len(cands) == 0 { return "", line, false }
+ best := cands[0]
+ for _, c := range cands[1:] {
+ if c.start >= 0 && (best.start < 0 || c.start < best.start) { best = c }
+ }
+ cleaned = strings.TrimRight(line[:best.start]+line[best.end:], " \t")
+ return best.text, cleaned, true
+}
+
+// FindStrictInlineTag finds ;text; with no spaces after/before semicolons.
+func FindStrictInlineTag(line string) (text string, left, right int, ok bool) {
+ for i := 0; i < len(line); i++ {
+ if line[i] != ';' { continue }
+ if i+1 < len(line) && line[i+1] == ' ' { continue }
+ for j := i + 1; j < len(line); j++ {
+ if line[j] == ';' {
+ if j-1 >= 0 && line[j-1] == ' ' { continue }
+ inner := strings.TrimSpace(line[i+1 : j])
+ if inner != "" { return inner, i, j + 1, true }
+ }
+ }
+ }
+ return "", -1, -1, false
+}
+