func (w *Writer) redact(lines [][]byte, matchMap map[int][]int) [][]byte {
	secretIdxsByLine := map[int][]int{}
	for secretIdx, lineIndexes := range matchMap {
		for _, lineIdx := range lineIndexes {
			secretIdxsByLine[lineIdx] = append(secretIdxsByLine[lineIdx], secretIdx)
		}
	}
	for i, line := range lines {
		linesToRedact := w.secretLinesToRedact(i, matchMap)
		if linesToRedact == nil {
			continue
		}
		var ranges []matchRange
		for _, lineToRedact := range linesToRedact {
			ranges = append(ranges, allRanges(line, lineToRedact)...)
		}
		lines[i] = redact(line, mergeAllRanges(ranges))
	}
	return lines
}
redact hides the given secrets in the given lines.
package filterwriter
import (
	"bytes"
	"io"
	"sort"
	"strings"
	"sync"
	"time"
	"github.com/bitrise-io/go-utils/log"
)
const RedactStr = "[REDACTED]"
var newLine = []byte("\n")
type Writer struct {
	writer  io.Writer
	secrets [][][]byte
	chunk []byte
	store [][]byte
	mux   sync.Mutex
	timer *time.Timer
}
func New(secrets []string, target io.Writer) *Writer {
	extendedSecrets := secrets
	for _, secret := range secrets {
		if strings.Contains(secret, "\n") {
			extendedSecrets = append(extendedSecrets, strings.ReplaceAll(secret, "\n", `\n`))
		}
	}
	return &Writer{
		writer:  target,
		secrets: secretsByteList(extendedSecrets),
	}
}
func (w *Writer) Write(p []byte) (int, error) {
	defer func() {
		w.mux.Unlock()
	}()
	w.mux.Lock()
	data := append(w.chunk, p...)
	lastLines, chunk := splitAfterNewline(data)
	w.chunk = chunk
	if len(chunk) > 0 {
		if w.timer != nil {
			w.timer.Stop()
			w.timer.C = nil
		}
		w.timer = time.AfterFunc(100*time.Millisecond, func() {
			if _, err := w.Flush(); err != nil {
				log.Errorf("Failed to print last lines: %s", err)
			}
		})
	}
	if len(lastLines) == 0 {
		return len(p), nil
	}
	for _, line := range lastLines {
		lines := append(w.store, line)
		matchMap, partialMatchIndexes := w.matchSecrets(lines)
		var linesToPrint [][]byte
		linesToPrint, w.store = w.matchLines(lines, partialMatchIndexes)
		if linesToPrint == nil {
			continue
		}
		redactedLines := w.redact(linesToPrint, matchMap)
		redactedBytes := bytes.Join(redactedLines, nil)
		if c, err := w.writer.Write(redactedBytes); err != nil {
			return c, err
		}
	}
	return len(p), nil
}
func (w *Writer) Flush() (int, error) {
	defer func() {
		w.mux.Unlock()
	}()
	w.mux.Lock()
	if len(w.chunk) > 0 {
		chunk := w.chunk
		w.chunk = nil
		w.store = append(w.store, chunk)
	}
	matchMap, _ := w.matchSecrets(w.store)
	redactedLines := w.redact(w.store, matchMap)
	w.store = nil
	return w.writer.Write(bytes.Join(redactedLines, nil))
}
func (w *Writer) matchSecrets(lines [][]byte) (matchMap map[int][]int, partialMatchIndexes map[int]bool) {
	matchMap = make(map[int][]int)
	partialMatchIndexes = make(map[int]bool)
	for secretIdx, secret := range w.secrets {
		secretLine := secret[0]  
		var lineIndexes []int    
		for i, line := range lines {
			if bytes.Contains(line, secretLine) {
				lineIndexes = append(lineIndexes, i)
			}
		}
		if len(lineIndexes) == 0 {
			continue
		}
		for _, lineIdx := range lineIndexes {
			if len(secret) == 1 {
				indexes := matchMap[secretIdx]
				matchMap[secretIdx] = append(indexes, lineIdx)
				continue
			}
			partialMatch := true
			match := false
			for i := lineIdx + 1; i < len(lines); i++ {
				secretLineIdx := i - lineIdx
				secretLine = secret[secretLineIdx]
				line := lines[i]
				if !bytes.Contains(line, secretLine) {
					partialMatch = false
					break
				}
				if secretLineIdx == len(secret)-1 {
					match = true
					break
				}
			}
			if match {
				indexes := matchMap[secretIdx]
				matchMap[secretIdx] = append(indexes, lineIdx)
				continue
			}
			if partialMatch {
				partialMatchIndexes[lineIdx] = true
			}
		}
	}
	return
}
func (w *Writer) linesToKeepRange(partialMatchIndexes map[int]bool) int {
	first := -1
	for lineIdx := range partialMatchIndexes {
		if first == -1 {
			first = lineIdx
			continue
		}
		if first > lineIdx {
			first = lineIdx
		}
	}
	return first
}
func (w *Writer) matchLines(lines [][]byte, partialMatchIndexes map[int]bool) ([][]byte, [][]byte) {
	first := w.linesToKeepRange(partialMatchIndexes)
	switch first {
	case -1:
		return lines, nil
	case 0:
		return nil, lines
	default:
		return lines[:first], lines[first:]
	}
}
func (w *Writer) secretLinesToRedact(lineIdxToRedact int, matchMap map[int][]int) [][]byte {
	secretIdxsByLine := make(map[int][]int)
	for secretIdx, lineIndexes := range matchMap {
		for _, lineIdx := range lineIndexes {
			secretIdxsByLine[lineIdx] = append(secretIdxsByLine[lineIdx], secretIdx)
		}
	}
	var secretChunks [][]byte
	for firstMatchingLineIdx, secretIndexes := range secretIdxsByLine {
		if lineIdxToRedact < firstMatchingLineIdx {
			continue
		}
		for _, secretIdx := range secretIndexes {
			secret := w.secrets[secretIdx]
			if lineIdxToRedact > firstMatchingLineIdx+len(secret)-1 {
				continue
			}
			secretLineIdx := lineIdxToRedact - firstMatchingLineIdx
			secretChunks = append(secretChunks, secret[secretLineIdx])
		}
	}
	sort.Slice(secretChunks, func(i, j int) bool { return len(secretChunks[i]) < len(secretChunks[j]) })
	return secretChunks
}
func redact(line []byte, ranges []matchRange) []byte {
	var offset int  
	for _, r := range ranges {
		length := r.last - r.first
		first := r.first + offset
		last := first + length
		toRedact := line[first:last]
		redactStr := RedactStr
		if bytes.HasSuffix(toRedact, []byte("\n")) {
			redactStr += "\n"
		}
		newLine := append([]byte{}, line[:first]...)
		newLine = append(newLine, redactStr...)
		newLine = append(newLine, line[last:]...)
		offset += len(redactStr) - length
		line = newLine
	}
	return line
}