File: partition_message.go

package info (click to toggle)
golang-github-nicholas-fedor-shoutrrr 0.8.15-1
  • links: PTS, VCS
  • area: main
  • in suites: sid
  • size: 4,200 kB
  • sloc: sh: 49; makefile: 5
file content (118 lines) | stat: -rw-r--r-- 2,892 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
package util

import (
	"strings"

	"github.com/nicholas-fedor/shoutrrr/pkg/types"
)

// ellipsis is the suffix appended to truncated strings.
const ellipsis = " [...]"

// PartitionMessage splits a string into chunks of at most chunkSize runes.
// It searches the last distance runes for a whitespace to improve readability,
// adding chunks until reaching maxCount or maxTotal runes, returning the chunks
// and the number of omitted runes.
func PartitionMessage(
	input string,
	limits types.MessageLimit,
	distance int,
) ([]types.MessageItem, int) {
	items := make([]types.MessageItem, 0, limits.ChunkCount-1)
	runes := []rune(input)
	chunkOffset := 0
	maxTotal := Min(len(runes), limits.TotalChunkSize)
	maxCount := limits.ChunkCount - 1

	if len(input) == 0 {
		// If the message is empty, return an empty array
		return items, 0
	}

	for range maxCount {
		// If no suitable split point is found, use the chunkSize
		chunkEnd := chunkOffset + limits.ChunkSize
		// ... and start next chunk directly after this one
		nextChunkStart := chunkEnd

		if chunkEnd >= maxTotal {
			// The chunk is smaller than the limit, no need to search
			chunkEnd = maxTotal
			nextChunkStart = maxTotal
		} else {
			for r := range distance {
				rp := chunkEnd - r
				if runes[rp] == '\n' || runes[rp] == ' ' {
					// Suitable split point found
					chunkEnd = rp
					// Since the split is on a whitespace, skip it in the next chunk
					nextChunkStart = chunkEnd + 1

					break
				}
			}
		}

		items = append(items, types.MessageItem{
			Text: string(runes[chunkOffset:chunkEnd]),
		})

		chunkOffset = nextChunkStart
		if chunkOffset >= maxTotal {
			break
		}
	}

	return items, len(runes) - chunkOffset
}

// Ellipsis truncates a string to maxLength characters, appending an ellipsis if needed.
func Ellipsis(text string, maxLength int) string {
	if len(text) > maxLength {
		text = text[:maxLength-len(ellipsis)] + ellipsis
	}

	return text
}

// MessageItemsFromLines creates MessageItem batches compatible with the given limits.
func MessageItemsFromLines(plain string, limits types.MessageLimit) [][]types.MessageItem {
	maxCount := limits.ChunkCount
	lines := strings.Split(plain, "\n")
	batches := make([][]types.MessageItem, 0)
	items := make([]types.MessageItem, 0, Min(maxCount, len(lines)))

	totalLength := 0

	for _, line := range lines {
		maxLen := limits.ChunkSize

		if len(items) == maxCount || totalLength+maxLen > limits.TotalChunkSize {
			batches = append(batches, items)
			items = items[:0]
		}

		runes := []rune(line)
		if len(runes) > maxLen {
			// Trim and add ellipsis
			runes = runes[:maxLen-len(ellipsis)]
			line = string(runes) + ellipsis
		}

		if len(runes) < 1 {
			continue
		}

		items = append(items, types.MessageItem{
			Text: line,
		})

		totalLength += len(runes)
	}

	if len(items) > 0 {
		batches = append(batches, items)
	}

	return batches
}