Skip to content

Commit ca59701

Browse files
committed
Add --stdin
Allow stdin content streams
1 parent 9d3e3ed commit ca59701

File tree

3 files changed

+151
-84
lines changed

3 files changed

+151
-84
lines changed

README.md

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -15,21 +15,29 @@ Usage:
1515
goreplace
1616
1717
Application Options:
18-
-m, --mode=[replace|line|lineinfile] replacement mode - replace: replace match with term; line: replace line with term; lineinfile:
19-
replace line with term or if not found append to term to file (default: replace)
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)
2023
-s, --search= search term
2124
-r, --replace= replacement term
22-
-i, --case-insensitive ignore case of pattern to match upper and lowercase characters
25+
-i, --case-insensitive ignore case of pattern to match upper
26+
and lowercase characters
27+
--stdin process stdin as input
2328
--once replace search term only one in a file
24-
--once-remove-match replace search term only one in a file and also don't keep matching lines (for line and lineinfile
25-
mode)
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)
2632
--regex treat pattern as regex
2733
--regex-backrefs enable backreferences in replace term
2834
--regex-posix parse regex term as POSIX regex
2935
--path= use files in this path
30-
--path-pattern= file pattern (* for wildcard, only basename of file)
36+
--path-pattern= file pattern (* for wildcard, only
37+
basename of file)
3138
--path-regex= file pattern (regex, full path)
32-
--ignore-empty ignore empty file list, otherwise this will result in an error
39+
--ignore-empty ignore empty file list, otherwise this
40+
will result in an error
3341
-v, --verbose verbose mode
3442
--dry-run dry run mode
3543
-V, --version show version and exit
@@ -57,7 +65,7 @@ Application Options:
5765
## Installation
5866

5967
```bash
60-
GOREPLACE_VERSION=0.4.0 \
68+
GOREPLACE_VERSION=0.5.0 \
6169
&& wget -O /usr/local/bin/go-replace https://github.com/webdevops/goreplace/releases/download/$GOREPLACE_VERSION/gr-64-linux \
6270
&& chmod +x /usr/local/bin/go-replace
6371
```

goreplace.go

Lines changed: 120 additions & 76 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ import (
1616

1717
const (
1818
Author = "webdevops.io"
19-
Version = "0.4.0"
19+
Version = "0.5.0"
2020
)
2121

2222
type changeset struct {
@@ -44,6 +44,7 @@ var opts struct {
4444
Search []string `short:"s" long:"search" required:"true" description:"search term"`
4545
Replace []string `short:"r" long:"replace" required:"true" description:"replacement term" `
4646
CaseInsensitive bool `short:"i" long:"case-insensitive" description:"ignore case of pattern to match upper and lowercase characters"`
47+
Stdin bool ` long:"stdin" description:"process stdin as input"`
4748
Once bool ` long:"once" description:"replace search term only one in a file"`
4849
OnceRemoveMatch bool ` long:"once-remove-match" description:"replace search term only one in a file and also don't keep matching lines (for line and lineinfile mode)"`
4950
Regex bool ` long:"regex" description:"treat pattern as regex"`
@@ -81,40 +82,14 @@ func applyChangesetsToFile(fileitem fileitem, changesets []changeset) (string, b
8182
r := bufio.NewReader(file)
8283
line, e := Readln(r)
8384
for e == nil {
84-
writeLine := true
85+
newLine, lineChanged, skipLine := applyChangesetsToLine(line, changesets)
8586

86-
for i := range changesets {
87-
changeset := changesets[i]
88-
89-
// --once, only do changeset once if already applied to file
90-
if opts.Once && changeset.MatchFound {
91-
// --once-without-match, skip matching lines
92-
if opts.OnceRemoveMatch && searchMatch(line, changeset) {
93-
// matching line, not writing to buffer as requsted
94-
writeLine = false
95-
writeBufferToFile = true
96-
break
97-
}
98-
} else {
99-
// search and replace
100-
if searchMatch(line, changeset) {
101-
// --mode=line or --mode=lineinfile
102-
if opts.ModeIsReplaceLine || opts.ModeIsLineInFile {
103-
// replace whole line with replace term
104-
line = changeset.Replace
105-
} else {
106-
// replace only term inside line
107-
line = replaceText(line, changeset)
108-
}
109-
110-
changesets[i].MatchFound = true
111-
writeBufferToFile = true
112-
}
113-
}
87+
if lineChanged || skipLine {
88+
writeBufferToFile = true
11489
}
11590

116-
if (writeLine) {
117-
buffer.WriteString(line + "\n")
91+
if !skipLine {
92+
buffer.WriteString(newLine + "\n")
11893
}
11994

12095
line, e = Readln(r)
@@ -140,6 +115,43 @@ func applyChangesetsToFile(fileitem fileitem, changesets []changeset) (string, b
140115
return output, status, err
141116
}
142117

118+
func applyChangesetsToLine(line string, changesets []changeset) (string, bool, bool) {
119+
changed := false
120+
skipLine := false
121+
122+
for i := range changesets {
123+
changeset := changesets[i]
124+
125+
// --once, only do changeset once if already applied to file
126+
if opts.Once && changeset.MatchFound {
127+
// --once-without-match, skip matching lines
128+
if opts.OnceRemoveMatch && searchMatch(line, changeset) {
129+
// matching line, not writing to buffer as requsted
130+
skipLine = true
131+
changed = true
132+
break
133+
}
134+
} else {
135+
// search and replace
136+
if searchMatch(line, changeset) {
137+
// --mode=line or --mode=lineinfile
138+
if opts.ModeIsReplaceLine || opts.ModeIsLineInFile {
139+
// replace whole line with replace term
140+
line = changeset.Replace
141+
} else {
142+
// replace only term inside line
143+
line = replaceText(line, changeset)
144+
}
145+
146+
changesets[i].MatchFound = true
147+
changed = true
148+
}
149+
}
150+
}
151+
152+
return line, changed, skipLine
153+
}
154+
143155
// Readln returns a single line (without the ending \n)
144156
// from the input buffered reader.
145157
// An error is returned iff there is an error with the
@@ -195,13 +207,13 @@ func writeContentToFile(fileitem fileitem, content bytes.Buffer) (string, bool)
195207
// Log message
196208
func logMessage(message string) {
197209
if opts.Verbose {
198-
fmt.Println(message)
210+
fmt.Fprintln(os.Stderr, message)
199211
}
200212
}
201213

202214
// Log error object as message
203215
func logError(err error) {
204-
fmt.Printf("Error: %s\n", err)
216+
fmt.Fprintln(os.Stderr, "Error: %s\n", err)
205217
}
206218

207219
// Build search term
@@ -343,43 +355,23 @@ func handleSpecialCliOptions(argparser *flags.Parser, args []string) ([]string)
343355
return args
344356
}
345357

346-
func main() {
347-
var changesets = []changeset {}
358+
func actionProcessStdin(changesets []changeset) (int) {
359+
scanner := bufio.NewScanner(os.Stdin)
360+
for scanner.Scan() {
361+
line := scanner.Text()
348362

349-
var argparser = flags.NewParser(&opts, flags.PassDoubleDash)
350-
args, err := argparser.Parse()
363+
newLine, _, skipLine := applyChangesetsToLine(line, changesets)
351364

352-
args = handleSpecialCliOptions(argparser, args)
353-
354-
// check if there is an parse error
355-
if err != nil {
356-
logError(err)
357-
fmt.Println()
358-
argparser.WriteHelp(os.Stdout)
359-
os.Exit(1)
360-
}
361-
362-
// check if search and replace options have equal lenght (equal number of options)
363-
if len(opts.Search) != len(opts.Replace) {
364-
// error: unequal numbers of search and replace options
365-
err := errors.New("Unequal numbers of search or replace options")
366-
logError(err)
367-
fmt.Println()
368-
argparser.WriteHelp(os.Stdout)
369-
os.Exit(1)
365+
if !skipLine {
366+
fmt.Println(newLine)
367+
}
370368
}
371369

372-
// build changesets
373-
for i := range opts.Search {
374-
search := opts.Search[i]
375-
replace := opts.Replace[i]
376-
377-
changeset := changeset{buildSearchTerm(search), replace, false}
378-
379-
changesets = append(changesets, changeset)
380-
}
370+
return 0
371+
}
381372

382-
// check if there is at least one file to process
373+
func actionProcessFiles(changesets []changeset, args []string, argparser *flags.Parser) (int) {
374+
// check if there is at least one file to process
383375
if (len(args) == 0) {
384376
if (opts.IgnoreEmpty) {
385377
// no files found, but we should ignore empty filelist
@@ -389,9 +381,9 @@ func main() {
389381
// no files found, print error and exit with error code
390382
err := errors.New("No files specified")
391383
logError(err)
392-
fmt.Println()
384+
fmt.Fprintln(os.Stderr, "")
393385
argparser.WriteHelp(os.Stdout)
394-
os.Exit(1)
386+
return 1
395387
}
396388
}
397389

@@ -426,20 +418,72 @@ func main() {
426418
} else if opts.Verbose {
427419
title := fmt.Sprintf("%s:", result.File.Path)
428420

429-
fmt.Println()
430-
fmt.Println(title)
431-
fmt.Println(strings.Repeat("-", len(title)))
432-
fmt.Println()
433-
fmt.Println(result.Output)
434-
fmt.Println()
421+
fmt.Fprintln(os.Stderr, "")
422+
fmt.Fprintln(os.Stderr, title)
423+
fmt.Fprintln(os.Stderr, strings.Repeat("-", len(title)))
424+
fmt.Fprintln(os.Stderr, "")
425+
fmt.Fprintln(os.Stderr, result.Output)
426+
fmt.Fprintln(os.Stderr, "")
435427
}
436428
}
437429

438430
if errorCount >= 1 {
439-
fmt.Println(fmt.Sprintf("[ERROR] %s failed with %d error(s)", argparser.Command.Name, errorCount))
431+
fmt.Fprintln(os.Stderr, fmt.Sprintf("[ERROR] %s failed with %d error(s)", argparser.Command.Name, errorCount))
432+
return 1
433+
}
434+
435+
return 0
436+
}
437+
438+
func buildChangesets(argparser *flags.Parser) ([]changeset){
439+
var changesets []changeset
440+
441+
// check if search and replace options have equal lenght (equal number of options)
442+
if len(opts.Search) != len(opts.Replace) {
443+
// error: unequal numbers of search and replace options
444+
err := errors.New("Unequal numbers of search or replace options")
445+
logError(err)
446+
fmt.Fprintln(os.Stderr, "")
447+
argparser.WriteHelp(os.Stdout)
448+
os.Exit(1)
449+
}
450+
451+
// build changesets
452+
for i := range opts.Search {
453+
search := opts.Search[i]
454+
replace := opts.Replace[i]
455+
456+
changeset := changeset{buildSearchTerm(search), replace, false}
457+
changesets = append(changesets, changeset)
458+
}
459+
460+
return changesets
461+
}
462+
463+
func main() {
464+
var argparser = flags.NewParser(&opts, flags.PassDoubleDash)
465+
args, err := argparser.Parse()
466+
467+
args = handleSpecialCliOptions(argparser, args)
468+
469+
// check if there is an parse error
470+
if err != nil {
471+
logError(err)
472+
fmt.Fprintln(os.Stderr, "")
473+
argparser.WriteHelp(os.Stdout)
440474
os.Exit(1)
475+
}
476+
477+
changesets := buildChangesets(argparser)
478+
479+
exitMode := 0
480+
if opts.Stdin {
481+
// use stdin as input
482+
exitMode = actionProcessStdin(changesets)
441483
} else {
442-
os.Exit(0)
484+
// use and process files (see args)
485+
exitMode = actionProcessFiles(changesets, args, argparser)
443486
}
444487

488+
os.Exit(exitMode)
445489
}

tests/main.t

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ Usage:
2020
-r, --replace= replacement term
2121
-i, --case-insensitive ignore case of pattern to match upper
2222
and lowercase characters
23+
--stdin process stdin as input
2324
--once replace search term only one in a file
2425
--once-remove-match replace search term only one in a file
2526
and also don't keep matching lines (for
@@ -62,6 +63,20 @@ Testing replace mode:
6263
this is the third ___xxx line
6364
this is the last line
6465
66+
Testing replace mode with stdin:
67+
68+
$ cat > test.txt <<EOF
69+
> this is a testline
70+
> this is the second line
71+
> this is the third foobar line
72+
> this is the last line
73+
> EOF
74+
$ cat test.txt | goreplace -s foobar -r ___xxx --stdin
75+
this is a testline
76+
this is the second line
77+
this is the third ___xxx line
78+
this is the last line
79+
6580
Testing replace mode with multiple matches:
6681
6782
$ cat > test.txt <<EOF

0 commit comments

Comments
 (0)