├── go.mod ├── .github └── workflows │ └── go.yml ├── LICENSE ├── go.sum ├── README.md ├── cmd └── replace │ └── main.go ├── replace_test.go └── replace.go /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/icholy/replace 2 | 3 | go 1.14 4 | 5 | require ( 6 | golang.org/x/text v0.3.3 7 | gotest.tools/v3 v3.0.2 8 | ) 9 | -------------------------------------------------------------------------------- /.github/workflows/go.yml: -------------------------------------------------------------------------------- 1 | name: Go 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | pull_request: 7 | branches: [ master ] 8 | 9 | jobs: 10 | 11 | build: 12 | runs-on: ubuntu-latest 13 | steps: 14 | - uses: actions/checkout@v3 15 | 16 | - name: Set up Go 17 | uses: actions/setup-go@v3 18 | with: 19 | go-version: 1.18 20 | 21 | - name: Build 22 | run: go build -v ./... 23 | 24 | - name: Test 25 | run: go test -v ./... 26 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Ilia Choly 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/google/go-cmp v0.3.0 h1:crn/baboCvb5fXaQ0IJ1SGTsTVrWpDsCWC8EGETZijY= 2 | github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= 3 | github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= 4 | github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 5 | github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= 6 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 7 | golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 8 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 9 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 10 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 11 | golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k= 12 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 13 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 14 | golang.org/x/tools v0.0.0-20190624222133-a101b041ded4/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= 15 | gotest.tools v1.4.0 h1:BjtEgfuw8Qyd+jPvQz8CfoxiO/UjFEidWinwEXZiWv0= 16 | gotest.tools v2.2.0+incompatible h1:VsBPFP1AI068pPrMxtb/S8Zkgf9xEmTLJjfM+P5UIEo= 17 | gotest.tools/v3 v3.0.2 h1:kG1BFyqVHuQoVQiR1bWGnfz/fmHvvuiSPIV7rvl360E= 18 | gotest.tools/v3 v3.0.2/go.mod h1:3SzNCllyD9/Y+b5r9JIKQ474KzkZyqLqEfYqMsX94Bk= 19 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Streaming text replacement 2 | 3 | [![GoDoc](https://godoc.org/github.com/icholy/replace?status.svg)](https://godoc.org/github.com/icholy/replace) 4 | 5 | > This package provides a [x/text/transform.Transformer](https://godoc.org/golang.org/x/text/transform#Transformer) 6 | > implementation that replaces text 7 | 8 | ## Example 9 | 10 | ``` go 11 | package main 12 | 13 | import ( 14 | "io" 15 | "os" 16 | "regexp" 17 | 18 | "github.com/icholy/replace" 19 | ) 20 | 21 | func main() { 22 | f, _ := os.Open("file") 23 | defer f.Close() 24 | 25 | r := replace.Chain(f, 26 | // simple replace 27 | replace.String("foo", "bar"), 28 | replace.Bytes([]byte("thing"), []byte("test")), 29 | 30 | // remove all words that start with baz 31 | replace.Regexp(regexp.MustCompile(`baz\w*`), nil), 32 | 33 | // surround all words with parentheses 34 | replace.RegexpString(regexp.MustCompile(`\w+`), "($0)"), 35 | 36 | // increment all numbers 37 | replace.RegexpStringFunc(regexp.MustCompile(`\d+`), func(match string) string { 38 | x, _ := strconv.Atoi(match) 39 | return strconv.Itoa(x+1) 40 | }), 41 | ) 42 | 43 | _, _ = io.Copy(os.Stdout, r) 44 | } 45 | ``` 46 | 47 | ## Notes Regexp* functions 48 | 49 | * `RegexpTransformer` is stateful and cannot be used concurrently. 50 | * The `replace` functions should not save or modify any `[]byte` parameters they recieve. 51 | * If a match is longer than `MaxMatchSize` it may be skipped (Default 2kb). 52 | * For better performance, reduce the `MaxMatchSize` size to the largest possible match. 53 | * Do not use with [transform.Chain](https://pkg.go.dev/golang.org/x/text/transform#Chain), see https://github.com/golang/go/issues/49117. -------------------------------------------------------------------------------- /cmd/replace/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "io" 6 | "io/ioutil" 7 | "log" 8 | "os" 9 | "path/filepath" 10 | "regexp" 11 | 12 | "github.com/icholy/replace" 13 | "golang.org/x/text/transform" 14 | ) 15 | 16 | func main() { 17 | // parse flags 18 | var max int 19 | var old, new string 20 | flag.StringVar(&old, "old", "", "regular expression") 21 | flag.StringVar(&new, "new", "", "replacement expansion") 22 | flag.IntVar(&max, "max", 64<<10, "max match size") 23 | flag.Parse() 24 | // setup the transformer 25 | re, err := regexp.Compile(old) 26 | if err != nil { 27 | log.Fatalf("old: %v", err) 28 | } 29 | tr := replace.RegexpString(re, new) 30 | tr.MaxMatchSize = max 31 | // if stdin is a pipe, read from that 32 | info, _ := os.Stdin.Stat() 33 | if info.Mode()&os.ModeCharDevice == 0 { 34 | r := transform.NewReader(os.Stdin, tr) 35 | if _, err := io.Copy(os.Stdout, r); err != nil { 36 | log.Fatal(err) 37 | } 38 | return 39 | } 40 | // do replacements 41 | for _, name := range flag.Args() { 42 | if err := rewrite(name, tr); err != nil { 43 | log.Printf("%s: %v", name, err) 44 | } 45 | } 46 | } 47 | 48 | // rewrite the named file by passing it through the transformer. 49 | // The rewritten content is first written to a temporary file in the same directory as the file. 50 | // Once the transform is successful, the original file is replaced by the temporary file. 51 | func rewrite(name string, t transform.Transformer) error { 52 | // open original file 53 | f, err := os.Open(name) 54 | if err != nil { 55 | return err 56 | } 57 | defer f.Close() 58 | // create temp file 59 | pattern := filepath.Base(name) + "-temp-*" 60 | tmp, err := ioutil.TempFile(filepath.Dir(name), pattern) 61 | if err != nil { 62 | return err 63 | } 64 | defer os.Remove(tmp.Name()) 65 | defer tmp.Close() 66 | // replace while copying from f to tmp 67 | if _, err := io.Copy(tmp, transform.NewReader(f, t)); err != nil { 68 | return err 69 | } 70 | // make sure the tmp file was successfully written to 71 | if err := tmp.Close(); err != nil { 72 | return err 73 | } 74 | // close the file we're reading from 75 | if err := f.Close(); err != nil { 76 | return err 77 | } 78 | // overwrite the original file with the temp file 79 | return os.Rename(tmp.Name(), name) 80 | } 81 | -------------------------------------------------------------------------------- /replace_test.go: -------------------------------------------------------------------------------- 1 | package replace 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "io" 7 | "regexp" 8 | "strconv" 9 | "strings" 10 | "testing" 11 | 12 | "golang.org/x/text/transform" 13 | "gotest.tools/v3/assert" 14 | ) 15 | 16 | func TestTransformer(t *testing.T) { 17 | tests := []struct { 18 | in, out string 19 | tr transform.Transformer 20 | }{ 21 | { 22 | in: "test", out: "test", 23 | tr: String("", "x"), 24 | }, 25 | { 26 | in: "a", out: "b", 27 | tr: String("a", "b"), 28 | }, 29 | { 30 | in: "yes", out: "no", 31 | tr: String("yes", "no"), 32 | }, 33 | { 34 | in: "what what what", out: "wut wut wut", 35 | tr: String("what", "wut"), 36 | }, 37 | { 38 | in: "???????", out: "*******", 39 | tr: String("?", "*"), 40 | }, 41 | { 42 | in: "no matches", out: "no matches", 43 | tr: String("x", "y"), 44 | }, 45 | { 46 | in: "hello", out: "heLLo", 47 | tr: String("l", "L"), 48 | }, 49 | { 50 | in: "hello", out: "hello", 51 | tr: String("x", "X"), 52 | }, 53 | { 54 | in: "", out: "", 55 | tr: String("x", "X"), 56 | }, 57 | { 58 | in: "radar", out: "ada", 59 | tr: String("r", ""), 60 | }, 61 | { 62 | in: "banana", out: "b<>n<>n<>", 63 | tr: String("a", "<>"), 64 | }, 65 | { 66 | in: "banana", out: "b<><>a", 67 | tr: String("an", "<>"), 68 | }, 69 | { 70 | in: "banana", out: "b<>na", 71 | tr: String("ana", "<>"), 72 | }, 73 | { 74 | in: "banana", out: "banana", 75 | tr: String("a", "a"), 76 | }, 77 | { 78 | in: "xxx", out: "", 79 | tr: String("x", ""), 80 | }, 81 | { 82 | in: strings.Repeat("foo_", 8<<10), out: strings.Repeat("bar_", 8<<10), 83 | tr: String("foo", "bar"), 84 | }, 85 | { 86 | in: "a", out: "b", 87 | tr: RegexpString(regexp.MustCompile("a"), "b"), 88 | }, 89 | { 90 | in: "testing", out: "x", 91 | tr: RegexpString(regexp.MustCompile(".*"), "x"), 92 | }, 93 | { 94 | in: strings.Repeat("--ax-- --bx--", 4<<10), out: strings.Repeat("--xx-- --xx--", 4<<10), 95 | tr: RegexpString(regexp.MustCompile(`--\wx--`), "--xx--"), 96 | }, 97 | { 98 | in: strings.Repeat("--1x-- --2x-- --3x--", 8<<10), out: strings.Repeat("--0x-- --1x-- --2x--", 8<<10), 99 | tr: RegexpStringSubmatchFunc(regexp.MustCompile(`--(\d)x--`), func(match []string) string { 100 | x, _ := strconv.Atoi(match[1]) 101 | return fmt.Sprintf("--%dx--", x-1) 102 | }), 103 | }, 104 | { 105 | in: "1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 ", out: strings.Repeat("num ", 16), 106 | tr: RegexpStringFunc(regexp.MustCompile(`\d+`), func(_ string) string { 107 | return "num" 108 | }), 109 | }, 110 | { 111 | in: "bazzzz buzz foo what biz", out: " foo what ", 112 | tr: Regexp(regexp.MustCompile(`b\w+z\w*`), nil), 113 | }, 114 | { 115 | in: "a", out: "replaced", 116 | tr: RegexpSubmatchFunc(regexp.MustCompile("a(123)?"), func(_ [][]byte) []byte { 117 | return []byte("replaced") 118 | }), 119 | }, 120 | { 121 | in: "this is a test", out: "(this) (is) (a) (test)", 122 | tr: RegexpString(regexp.MustCompile(`\w+`), "($0)"), 123 | }, 124 | } 125 | for i, tt := range tests { 126 | t.Run(strconv.Itoa(i), func(t *testing.T) { 127 | result, _, err := transform.String(tt.tr, tt.in) 128 | assert.NilError(t, err) 129 | assert.DeepEqual(t, result, tt.out) 130 | }) 131 | } 132 | } 133 | 134 | func TestOverflowDst(t *testing.T) { 135 | var calls int 136 | s := strings.Repeat("x", 8<<10) 137 | tr := RegexpStringFunc(regexp.MustCompile("x"), func(_ string) string { 138 | calls++ 139 | return s 140 | }) 141 | 142 | result, _, err := transform.String(tr, "x") 143 | assert.NilError(t, err) 144 | assert.Equal(t, result, s) 145 | assert.Equal(t, calls, 1) 146 | } 147 | 148 | func TestChain(t *testing.T) { 149 | var input bytes.Buffer 150 | input.WriteString(strings.Repeat("x", 1000)) 151 | input.WriteString(strings.Repeat("y", 1000)) 152 | input.WriteString(strings.Repeat("z", 1000)) 153 | r := Chain(&input, 154 | RegexpString(regexp.MustCompile("x+"), "1"), 155 | RegexpString(regexp.MustCompile("y+"), "2"), 156 | RegexpString(regexp.MustCompile("z+"), "3"), 157 | ) 158 | output, err := io.ReadAll(r) 159 | assert.NilError(t, err) 160 | assert.DeepEqual(t, string(output), "123") 161 | } 162 | -------------------------------------------------------------------------------- /replace.go: -------------------------------------------------------------------------------- 1 | package replace 2 | 3 | import ( 4 | "bytes" 5 | "io" 6 | "regexp" 7 | 8 | "golang.org/x/text/transform" 9 | ) 10 | 11 | // Chain returns a reader which applies all provided transformers. 12 | func Chain(r io.Reader, tt ...transform.Transformer) io.Reader { 13 | for _, t := range tt { 14 | r = transform.NewReader(r, t) 15 | } 16 | return r 17 | } 18 | 19 | // Transformer replaces text in a stream 20 | // See: http://golang.org/x/text/transform 21 | type Transformer struct { 22 | transform.NopResetter 23 | 24 | old, new []byte 25 | oldlen int 26 | } 27 | 28 | var _ transform.Transformer = (*Transformer)(nil) 29 | 30 | // Bytes returns a transformer that replaces all instances of old with new. 31 | // Unlike bytes.Replace, empty old values don't match anything. 32 | func Bytes(old, new []byte) Transformer { 33 | return Transformer{old: old, new: new, oldlen: len(old)} 34 | } 35 | 36 | // String returns a transformer that replaces all instances of old with new. 37 | // Unlike strings.Replace, empty old values don't match anything. 38 | func String(old, new string) Transformer { 39 | return Bytes([]byte(old), []byte(new)) 40 | } 41 | 42 | // Transform implements golang.org/x/text/transform#Transformer 43 | func (t Transformer) Transform(dst, src []byte, atEOF bool) (nDst, nSrc int, err error) { 44 | var n int 45 | // don't do anything for empty old string. We're forced to do this because an optimization in 46 | // transform.String prevents us from generating any output when the src is empty. 47 | // see: https://github.com/golang/text/blob/master/transform/transform.go#L570-L576 48 | if t.oldlen == 0 { 49 | n, err = fullcopy(dst, src) 50 | return n, n, err 51 | } 52 | // replace all instances of old with new 53 | for { 54 | i := bytes.Index(src[nSrc:], t.old) 55 | if i == -1 { 56 | break 57 | } 58 | // copy everything up to the match 59 | n, err = fullcopy(dst[nDst:], src[nSrc:nSrc+i]) 60 | nSrc += n 61 | nDst += n 62 | if err != nil { 63 | return 64 | } 65 | // copy the new value 66 | n, err = fullcopy(dst[nDst:], t.new) 67 | if err != nil { 68 | return 69 | } 70 | nDst += n 71 | nSrc += t.oldlen 72 | } 73 | // if we're at the end, tack on any remaining bytes 74 | if atEOF { 75 | n, err = fullcopy(dst[nDst:], src[nSrc:]) 76 | nDst += n 77 | nSrc += n 78 | return 79 | } 80 | // skip everything except the trailing len(r.old) - 1 81 | // we do this becasue there could be a match straddling 82 | // the boundary 83 | if skip := len(src[nSrc:]) - t.oldlen + 1; skip > 0 { 84 | n, err = fullcopy(dst[nDst:], src[nSrc:nSrc+skip]) 85 | nSrc += n 86 | nDst += n 87 | if err != nil { 88 | return 89 | } 90 | } 91 | err = transform.ErrShortSrc 92 | return 93 | } 94 | 95 | // RegexpTransformer replaces regexp matches in a stream 96 | // See: http://golang.org/x/text/transform 97 | // Note: this Transformer is not safe for concurrent use. 98 | type RegexpTransformer struct { 99 | // MaxMatchSize is the maximum size of a regexp match. 100 | // If a match exceeds this limit, it may be omitted. 101 | // (Default is 2kb). 102 | MaxMatchSize int 103 | 104 | re *regexp.Regexp 105 | replace func(src []byte, index []int) []byte 106 | overflow []byte 107 | } 108 | 109 | var _ transform.Transformer = (*RegexpTransformer)(nil) 110 | 111 | // RegexpIndexFunc returns a transformer that replaces all matches of re with the return value of replace. 112 | // The replace function recieves the underlying src buffer and indexes into that buffer. 113 | // The []byte parameter passed to replace should not be modified and is not guaranteed to be valid after the function returns. 114 | func RegexpIndexFunc(re *regexp.Regexp, replace func(src []byte, index []int) []byte) *RegexpTransformer { 115 | return &RegexpTransformer{ 116 | re: re, 117 | replace: replace, 118 | MaxMatchSize: 2 << 10, 119 | } 120 | } 121 | 122 | // Regexp returns a transformer that replaces all matches of re with new 123 | func Regexp(re *regexp.Regexp, new []byte) *RegexpTransformer { 124 | return RegexpIndexFunc(re, func(_ []byte, _ []int) []byte { return new }) 125 | } 126 | 127 | // RegexpString returns a transformer that replaces all matches of re with template 128 | // Inside template, $ signs are interpreted as in Expand, so for instance $1 represents the text of the first submatch. 129 | func RegexpString(re *regexp.Regexp, template string) *RegexpTransformer { 130 | return RegexpIndexFunc(re, func(src []byte, index []int) []byte { 131 | return re.Expand(nil, []byte(template), src, index) 132 | }) 133 | } 134 | 135 | // RegexpFunc returns a transformer that replaces all matches of re with the result of calling replace with the match. 136 | // The []byte parameter passed to replace should not be modified and is not guaranteed to be valid after the function returns. 137 | func RegexpFunc(re *regexp.Regexp, replace func([]byte) []byte) *RegexpTransformer { 138 | return RegexpIndexFunc(re, func(src []byte, index []int) []byte { 139 | return replace(src[index[0]:index[1]]) 140 | }) 141 | } 142 | 143 | // RegexpStringFunc returns a transformer that replaces all matches of re with the result of calling replace with the match. 144 | func RegexpStringFunc(re *regexp.Regexp, replace func(string) string) *RegexpTransformer { 145 | return RegexpIndexFunc(re, func(src []byte, index []int) []byte { 146 | return []byte(replace(string(src[index[0]:index[1]]))) 147 | }) 148 | } 149 | 150 | // RegexpSubmatchFunc returns a transformer that replaces all matches of re with the result of calling replace with the submatch. 151 | // The [][]byte parameter passed to replace should not be modified and is not guaranteed to be valid after the function returns. 152 | func RegexpSubmatchFunc(re *regexp.Regexp, replace func([][]byte) []byte) *RegexpTransformer { 153 | return RegexpIndexFunc(re, func(src []byte, index []int) []byte { 154 | match := make([][]byte, 1+re.NumSubexp()) 155 | for i := range match { 156 | start, end := index[i*2], index[i*2+1] 157 | if start >= 0 { 158 | match[i] = src[start:end] 159 | } 160 | } 161 | return replace(match) 162 | }) 163 | } 164 | 165 | // RegexpStringSubmatchFunc returns a transformer that replaces all matches of re with the result of calling replace with the submatch. 166 | func RegexpStringSubmatchFunc(re *regexp.Regexp, replace func([]string) string) *RegexpTransformer { 167 | return RegexpIndexFunc(re, func(src []byte, index []int) []byte { 168 | match := make([]string, 1+re.NumSubexp()) 169 | for i := range match { 170 | start, end := index[i*2], index[i*2+1] 171 | if start >= 0 { 172 | match[i] = string(src[start:end]) 173 | } 174 | } 175 | return []byte(replace(match)) 176 | }) 177 | } 178 | 179 | // Transform implements golang.org/x/text/transform#Transformer 180 | func (t *RegexpTransformer) Transform(dst, src []byte, atEOF bool) (nDst, nSrc int, err error) { 181 | var n int 182 | // copy any overflow from the last call 183 | if len(t.overflow) > 0 { 184 | n, err = fullcopy(dst, t.overflow) 185 | nDst += n 186 | if err != nil { 187 | t.overflow = t.overflow[n:] 188 | return 189 | } 190 | t.overflow = nil 191 | } 192 | for _, index := range t.re.FindAllSubmatchIndex(src, -1) { 193 | // copy everything up to the match 194 | n, err = fullcopy(dst[nDst:], src[nSrc:index[0]]) 195 | nSrc += n 196 | nDst += n 197 | if err != nil { 198 | return 199 | } 200 | // skip the match if it ends at the end the src buffer. 201 | // it could potentially match more 202 | if index[1] == len(src) && !atEOF { 203 | break 204 | } 205 | // copy the replacement 206 | rep := t.replace(src, index) 207 | n, err = fullcopy(dst[nDst:], rep) 208 | nDst += n 209 | nSrc = index[1] 210 | if err != nil { 211 | t.overflow = rep[n:] 212 | return 213 | } 214 | } 215 | // if we're at the end, tack on any remaining bytes 216 | if atEOF { 217 | n, err = fullcopy(dst[nDst:], src[nSrc:]) 218 | nDst += n 219 | nSrc += n 220 | return 221 | } 222 | // skip any bytes which exceede the max match size 223 | if skip := len(src[nSrc:]) - t.MaxMatchSize; skip > 0 { 224 | n, err = fullcopy(dst[nDst:], src[nSrc:nSrc+skip]) 225 | nSrc += n 226 | nDst += n 227 | if err != nil { 228 | return 229 | } 230 | } 231 | err = transform.ErrShortSrc 232 | return 233 | } 234 | 235 | // Reset resets the state and allows a Transformer to be reused. 236 | func (t *RegexpTransformer) Reset() { 237 | t.overflow = nil 238 | } 239 | 240 | func fullcopy(dst, src []byte) (n int, err error) { 241 | n = copy(dst, src) 242 | if n < len(src) { 243 | err = transform.ErrShortDst 244 | } 245 | return 246 | } 247 | --------------------------------------------------------------------------------