diff options
| author | Paul Buetow <paul@buetow.org> | 2025-09-06 13:19:01 +0300 |
|---|---|---|
| committer | Paul Buetow <paul@buetow.org> | 2025-09-06 13:19:01 +0300 |
| commit | 04f290dbeeee8a6fcbc70fed253a968336bcb2ab (patch) | |
| tree | 3ee23a4ac4bcc5b43b43697cfb0e905735fc6331 /internal/textutil/textutil.go | |
| parent | 5e966f50111adf6e2cb2683fe588f6fe033fa931 (diff) | |
more tests
Diffstat (limited to 'internal/textutil/textutil.go')
| -rw-r--r-- | internal/textutil/textutil.go | 114 |
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 +} + |
