Skip to content

Commit 82c4341

Browse files
committed
Add template mode
Allows usage of golang templates as stdin or file
1 parent 49a4a2b commit 82c4341

File tree

3 files changed

+154
-70
lines changed

3 files changed

+154
-70
lines changed

README.md

Lines changed: 26 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -15,40 +15,34 @@ Usage:
1515
goreplace
1616
1717
Application Options:
18-
-m, --mode=[replace|line|lineinfile] replacement mode - replace: replace
19-
match with term; line: replace line with
20-
term; lineinfile: replace line with term
21-
or if not found append to term to file
22-
(default: replace)
23-
-s, --search= search term
24-
-r, --replace= replacement term
25-
-i, --case-insensitive ignore case of pattern to match upper
26-
and lowercase characters
27-
--stdin process stdin as input
28-
--once replace search term only one in a file
29-
--once-remove-match replace search term only one in a file
30-
and also don't keep matching lines (for
31-
line and lineinfile mode)
32-
--regex treat pattern as regex
33-
--regex-backrefs enable backreferences in replace term
34-
--regex-posix parse regex term as POSIX regex
35-
--path= use files in this path
36-
--path-pattern= file pattern (* for wildcard, only
37-
basename of file)
38-
--path-regex= file pattern (regex, full path)
39-
--ignore-empty ignore empty file list, otherwise this
40-
will result in an error
41-
-v, --verbose verbose mode
42-
--dry-run dry run mode
43-
-V, --version show version and exit
44-
-h, --help show this help message
18+
-m, --mode=[replace|line|lineinfile|template] replacement mode - replace: replace match with term; line: replace line with term; lineinfile: replace line with
19+
term or if not found append to term to file; template: parse content as golang template, search value have to
20+
start uppercase (default: replace)
21+
-s, --search= search term
22+
-r, --replace= replacement term
23+
-i, --case-insensitive ignore case of pattern to match upper and lowercase characters
24+
--stdin process stdin as input
25+
--once replace search term only one in a file
26+
--once-remove-match replace search term only one in a file and also don't keep matching lines (for line and lineinfile mode)
27+
--regex treat pattern as regex
28+
--regex-backrefs enable backreferences in replace term
29+
--regex-posix parse regex term as POSIX regex
30+
--path= use files in this path
31+
--path-pattern= file pattern (* for wildcard, only basename of file)
32+
--path-regex= file pattern (regex, full path)
33+
--ignore-empty ignore empty file list, otherwise this will result in an error
34+
-v, --verbose verbose mode
35+
--dry-run dry run mode
36+
-V, --version show version and exit
37+
-h, --help show this help message
4538
```
4639

47-
| Mode | Description |
48-
|:-----------|:-------------------------------------------------------------------------------------------------------------------------------------------------------|
49-
| replace | Replace search term inside one line with replacement. |
50-
| line | Replace line (if matched term is inside) with replacement. |
51-
| lineinfile | Replace line (if matched term is inside) with replacement. If no match is found in the whole file the line will be appended to the bottom of the file. |
40+
| Mode | Description |
41+
|:-----------|:---------------------------------------------------------------------------------------------------------------------------------------------------------------|
42+
| replace | Replace search term inside one line with replacement. |
43+
| line | Replace line (if matched term is inside) with replacement. |
44+
| lineinfile | Replace line (if matched term is inside) with replacement. If no match is found in the whole file the line will be appended to the bottom of the file. |
45+
| template | Parse content as [golang template](https://golang.org/pkg/text/template/), arguments are available via `{{.Arg.Name}}` or environment vars via `{{.Env.Name}}` |
5246

5347

5448
### Examples

goreplace.go

Lines changed: 108 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import (
1111
"os"
1212
"strings"
1313
"regexp"
14+
"text/template"
1415
flags "github.com/jessevdk/go-flags"
1516
)
1617

@@ -36,11 +37,17 @@ type fileitem struct {
3637
Path string
3738
}
3839

40+
type templateData struct {
41+
Arg map[string]string
42+
Env map[string]string
43+
}
44+
3945
var opts struct {
40-
Mode string `short:"m" long:"mode" description:"replacement mode - replace: replace match with term; line: replace line with term; lineinfile: replace line with term or if not found append to term to file" default:"replace" choice:"replace" choice:"line" choice:"lineinfile"`
46+
Mode string `short:"m" long:"mode" description:"replacement mode - replace: replace match with term; line: replace line with term; lineinfile: replace line with term or if not found append to term to file; template: parse content as golang template, search value have to start uppercase" default:"replace" choice:"replace" choice:"line" choice:"lineinfile" choice:"template"`
4147
ModeIsReplaceMatch bool
4248
ModeIsReplaceLine bool
4349
ModeIsLineInFile bool
50+
ModeIsTemplate bool
4451
Search []string `short:"s" long:"search" required:"true" description:"search term"`
4552
Replace []string `short:"r" long:"replace" required:"true" description:"replacement term" `
4653
CaseInsensitive bool `short:"i" long:"case-insensitive" description:"ignore case of pattern to match upper and lowercase characters"`
@@ -115,6 +122,27 @@ func applyChangesetsToFile(fileitem fileitem, changesets []changeset) (string, b
115122
return output, status, err
116123
}
117124

125+
// Apply changesets to file
126+
func applyTemplateToFile(fileitem fileitem, changesets []changeset) (string, bool, error) {
127+
var (
128+
err error = nil
129+
output string = ""
130+
status bool = true
131+
)
132+
133+
// try open file
134+
buffer, err := ioutil.ReadFile(fileitem.Path)
135+
if err != nil {
136+
return output, false, err
137+
}
138+
139+
content := parseContentAsTemplate(string(buffer), changesets)
140+
141+
output, status = writeContentToFile(fileitem, content)
142+
143+
return output, status, err
144+
}
145+
118146
func applyChangesetsToLine(line string, changesets []changeset) (string, bool, bool) {
119147
changed := false
120148
skipLine := false
@@ -329,14 +357,22 @@ func handleSpecialCliOptions(argparser *flags.Parser, args []string) ([]string)
329357
opts.ModeIsReplaceMatch = true
330358
opts.ModeIsReplaceLine = false
331359
opts.ModeIsLineInFile = false
360+
opts.ModeIsTemplate = false
332361
case "line":
333362
opts.ModeIsReplaceMatch = false
334363
opts.ModeIsReplaceLine = true
335364
opts.ModeIsLineInFile = false
365+
opts.ModeIsTemplate = false
336366
case "lineinfile":
337367
opts.ModeIsReplaceMatch = false
338368
opts.ModeIsReplaceLine = false
339369
opts.ModeIsLineInFile = true
370+
opts.ModeIsTemplate = false
371+
case "template":
372+
opts.ModeIsReplaceMatch = false
373+
opts.ModeIsReplaceLine = false
374+
opts.ModeIsLineInFile = false
375+
opts.ModeIsTemplate = true
340376
}
341377

342378
// --path
@@ -355,7 +391,7 @@ func handleSpecialCliOptions(argparser *flags.Parser, args []string) ([]string)
355391
return args
356392
}
357393

358-
func actionProcessStdin(changesets []changeset) (int) {
394+
func actionProcessStdinReplace(changesets []changeset) (int) {
359395
scanner := bufio.NewScanner(os.Stdin)
360396
for scanner.Scan() {
361397
line := scanner.Text()
@@ -370,6 +406,58 @@ func actionProcessStdin(changesets []changeset) (int) {
370406
return 0
371407
}
372408

409+
410+
func generateTemplateData(changesets []changeset) (templateData) {
411+
// init
412+
var ret templateData
413+
ret.Arg = make(map[string]string)
414+
ret.Env = make(map[string]string)
415+
416+
// add changesets
417+
for i := range changesets {
418+
changeset := changesets[i]
419+
ret.Arg[changeset.Search.String()] = changeset.Replace
420+
}
421+
422+
// add env variables
423+
for _, e := range os.Environ() {
424+
pair := strings.Split(e, "=")
425+
ret.Env[pair[0]] = pair[1]
426+
}
427+
428+
return ret
429+
}
430+
431+
func parseContentAsTemplate(templateContent string, changesets []changeset) bytes.Buffer {
432+
var content bytes.Buffer
433+
data := generateTemplateData(changesets)
434+
435+
tmpl, err := template.New("template").Parse(templateContent)
436+
if err != nil {
437+
logError(err)
438+
}
439+
err = tmpl.Execute(&content, &data)
440+
if err != nil {
441+
logError(err)
442+
}
443+
444+
return content
445+
}
446+
447+
func actionProcessStdinTemplate(changesets []changeset) (int) {
448+
var buffer bytes.Buffer
449+
450+
scanner := bufio.NewScanner(os.Stdin)
451+
for scanner.Scan() {
452+
buffer.WriteString(scanner.Text() + "\n")
453+
}
454+
455+
content := parseContentAsTemplate(buffer.String(), changesets)
456+
fmt.Println(content.String())
457+
458+
return 0
459+
}
460+
373461
func actionProcessFiles(changesets []changeset, args []string, argparser *flags.Parser) (int) {
374462
// check if there is at least one file to process
375463
if (len(args) == 0) {
@@ -397,7 +485,17 @@ func actionProcessFiles(changesets []changeset, args []string, argparser *flags.
397485

398486
wg.Add(1)
399487
go func(file fileitem, changesets []changeset) {
400-
output, status, err := applyChangesetsToFile(file, changesets)
488+
var (
489+
err error = nil
490+
output string = ""
491+
status bool = true
492+
)
493+
494+
if opts.ModeIsTemplate {
495+
output, status, err = applyTemplateToFile(file, changesets)
496+
} else {
497+
output, status, err = applyChangesetsToFile(file, changesets)
498+
}
401499
results <- changeresult{file, output, status, err}
402500
wg.Done()
403501
} (file, changesets);
@@ -478,8 +576,13 @@ func main() {
478576

479577
exitMode := 0
480578
if opts.Stdin {
481-
// use stdin as input
482-
exitMode = actionProcessStdin(changesets)
579+
if opts.ModeIsTemplate {
580+
// use stdin as input
581+
exitMode = actionProcessStdinTemplate(changesets)
582+
} else {
583+
// use stdin as input
584+
exitMode = actionProcessStdinReplace(changesets)
585+
}
483586
} else {
484587
// use and process files (see args)
485588
exitMode = actionProcessFiles(changesets, args, argparser)

tests/main.t

Lines changed: 20 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -6,39 +6,6 @@ Go Replace tests:
66

77
Usage:
88

9-
$ goreplace -h
10-
Usage:
11-
goreplace
12-
13-
Application Options:
14-
-m, --mode=[replace|line|lineinfile] replacement mode - replace: replace
15-
match with term; line: replace line with
16-
term; lineinfile: replace line with term
17-
or if not found append to term to file
18-
(default: replace)
19-
-s, --search= search term
20-
-r, --replace= replacement term
21-
-i, --case-insensitive ignore case of pattern to match upper
22-
and lowercase characters
23-
--stdin process stdin as input
24-
--once replace search term only one in a file
25-
--once-remove-match replace search term only one in a file
26-
and also don't keep matching lines (for
27-
line and lineinfile mode)
28-
--regex treat pattern as regex
29-
--regex-backrefs enable backreferences in replace term
30-
--regex-posix parse regex term as POSIX regex
31-
--path= use files in this path
32-
--path-pattern= file pattern (* for wildcard, only
33-
basename of file)
34-
--path-regex= file pattern (regex, full path)
35-
--ignore-empty ignore empty file list, otherwise this
36-
will result in an error
37-
-v, --verbose verbose mode
38-
--dry-run dry run mode
39-
-V, --version show version and exit
40-
-h, --help show this help message
41-
[1]
429
$ goreplace -V
4310
goreplace version [0-9]+.[0-9]+.[0-9]+ (re)
4411

@@ -363,3 +330,23 @@ Testing replace mode with path option:
363330
this is the third foobar line
364331
this is the foobar forth foobar line
365332
this is the last line
333+
334+
335+
Testing template mode:
336+
337+
$ cat > test.txt <<EOF
338+
> {{23 -}} < {{- 45}}
339+
> {{.Arg.Foobar}}
340+
> this is a testline
341+
> this is the second line
342+
> this is the third foobar line
343+
> this is the last line
344+
> EOF
345+
$ goreplace --mode=template -s Foobar -r ___xxx test.txt
346+
$ cat test.txt
347+
23<45
348+
___xxx
349+
this is a testline
350+
this is the second line
351+
this is the third foobar line
352+
this is the last line

0 commit comments

Comments
 (0)